Added service periods to route names
Added service periods to route names

file:b/busui/about.php (new)
  <?php
  include('common.inc.php');
  ?>
  <p>
  Some icons by Joseph Wain / glyphish.com
  <?
  include_footer();
  ?>
 
<?php <?php
date_default_timezone_set('Australia/ACT'); date_default_timezone_set('Australia/ACT');
$APIurl = "http://localhost:8765"; $APIurl = "http://localhost:8765";
error_reporting(E_ALL ^ E_NOTICE); error_reporting(E_ALL ^ E_NOTICE);
   
function isDebug() function isDebug()
{ {
return true; return true;
} }
   
  function debug($msg) {
  if (isDebug()) echo "<!-- $msg -->";
  }
function isFastDevice() { function isFastDevice() {
return true; return true;
} }
   
function include_header($pageTitle, $opendiv = true, $geolocate = false) { function include_header($pageTitle, $opendiv = true, $geolocate = false) {
// if (isDebug()) // set php error level high // if (isDebug()) // set php error level high
echo ' echo '
<!DOCTYPE html> <!DOCTYPE html>
<html> <html>
<head> <head>
<title>bus.lambdacomplex.org - '.$pageTitle.'</title> <title>bus.lambdacomplex.org - '.$pageTitle.'</title>
<link rel="stylesheet" href="http://code.jquery.com/mobile/1.0a2/jquery.mobile-1.0a2.min.css" /> <link rel="stylesheet" href="http://code.jquery.com/mobile/1.0a2/jquery.mobile-1.0a2.min.css" />
<script src="http://code.jquery.com/jquery-1.4.3.min.js"></script><script type="text/javascript" <style type="text/css">
src="http://code.jquery.com/mobile/1.0a2/jquery.mobile-1.0a2.min.js"></script> .ui-navbar {
<script type="text/javascript" src="docs/docs.js"></script> padding-bottom: 18px;
<meta name="apple-mobile-web-app-capable" content="yes" /> width: 100%;
  }
  </style>
  <script src="http://code.jquery.com/jquery-1.4.3.min.js"></script>
  <script type="text/javascript" src="http://code.jquery.com/mobile/1.0a2/jquery.mobile-1.0a2.min.js"></script>
  <meta name="apple-mobile-web-app-capable" content="yes" />
<meta name="apple-mobile-web-app-status-bar-style" content="black" /> <meta name="apple-mobile-web-app-status-bar-style" content="black" />
<link rel="apple-touch-startup-image" href="startup.png" /> <link rel="apple-touch-startup-image" href="startup.png" />
<link rel="apple-touch-icon" href="apple-touch-icon.png" /> <link rel="apple-touch-icon" href="apple-touch-icon.png" />';
</head> if ($geolocate) {
  echo "<script>
   
  function setCookie(c_name,value,expiredays)
  {
  var exdate=new Date();
  exdate.setDate(exdate.getDate()+expiredays);
  document.cookie=c_name+ \"=\" +escape(value)+
  ((expiredays==null) ? \"\" : \";expires=\"+exdate.toUTCString());
  }
   
  function getCookie(c_name)
  {
  if (document.cookie.length>0)
  {
  c_start=document.cookie.indexOf(c_name + \"=\");
  if (c_start!=-1)
  {
  c_start=c_start + c_name.length+1;
  c_end=document.cookie.indexOf(\";\",c_start);
  if (c_end==-1) c_end=document.cookie.length;
  return unescape(document.cookie.substring(c_start,c_end));
  }
  }
  return \"\";
  }
   
  function success(position) {
  $('#geolocate').val(position.coords.latitude+','+position.coords.longitude);
  setCookie('geolocate',position.coords.latitude+','+position.coords.longitude,1);
  }
   
  function error(msg) {
  console.log(msg);
  }
   
  if (navigator.geolocation) {
  navigator.geolocation.getCurrentPosition(success, error);
  }
   
  </script> ";
  }
  echo '</head>
<body> <body>
'; ';
if ($opendiv) echo '<div data-role="page"> if ($opendiv) echo '<div data-role="page">
<div data-role="header"> <div data-role="header">
<h1>'.$pageTitle.'</h1> <h1>'.$pageTitle.'</h1>
<a href="index.php" data-icon="back" class="ui-btn-right">Home</a>  
</div><!-- /header --> </div><!-- /header -->
<div data-role="content"> '; <div data-role="content"> ';
} }
   
function include_footer() function include_footer()
{ {
echo '</div>'; echo '</div>';
} }
   
function service_period() function service_period()
{ {
switch (date('w')){ switch (date('w')){
   
case 0: case 0:
return 'sunday'; return 'sunday';
case 6: case 6:
return 'saturday'; return 'saturday';
default: default:
return 'weekday'; return 'weekday';
} }
} }
   
function midnight_seconds() function midnight_seconds()
{ {
// from http://www.perturb.org/display/Perlfunc__Seconds_Since_Midnight.html // from http://www.perturb.org/display/Perlfunc__Seconds_Since_Midnight.html
$secs = (date("G") * 3600) + (date("i") * 60) + date("s"); $secs = (date("G") * 3600) + (date("i") * 60) + date("s");
return $secs; return $secs;
} }
   
function midnight_seconds_to_time($seconds) function midnight_seconds_to_time($seconds)
{ {
$midnight = mktime (0, 0, 0, date("n"), date("j"), date("Y")); $midnight = mktime (0, 0, 0, date("n"), date("j"), date("Y"));
return date("h:ia",$midnight+$seconds); return date("h:ia",$midnight+$seconds);
} }
function getPage($url) function getPage($url)
{ {
$ch = curl_init($url); $ch = curl_init($url);
curl_setopt( $ch, CURLOPT_RETURNTRANSFER, 1 ); curl_setopt( $ch, CURLOPT_RETURNTRANSFER, 1 );
curl_setopt( $ch, CURLOPT_HEADER, 0 ); curl_setopt( $ch, CURLOPT_HEADER, 0 );
$page = curl_exec($ch); $page = curl_exec($ch);
curl_close($ch); curl_close($ch);
return $page; return $page;
} }
function array_flatten($a,$f=array()){ function array_flatten($a,$f=array()){
if(!$a||!is_array($a))return ''; if(!$a||!is_array($a))return '';
foreach($a as $k=>$v){ foreach($a as $k=>$v){
if(is_array($v))$f=array_flatten($v,$f); if(is_array($v))$f=array_flatten($v,$f);
else $f[$k]=$v; else $f[$k]=$v;
} }
return $f; return $f;
} }
   
function staticmap($mapPoints) function staticmap($mapPoints, $zoom = 0, $markerImage = "iconb")
{ {
$width = 300; $width = 300;
$height = 300; $height = 300;
if (sizeof($mapPoints) < 1) return ""; $metersperpixel[9]=305.492*$width;
if (sizeof($mapPoints) === 1) $center = "{$mapPoints[0][0]},{$mapPoints[0][1]}"; $metersperpixel[10]=152.746*$width;
if (sizeof($mapPoints) > 1) $center = "{$mapPoints[0][0]},{$mapPoints[0][1]}"; //TODO average points $metersperpixel[11]=76.373*$width;
$markers = ""; $metersperpixel[12]=38.187*$width;
foreach ($mapPoints as $index => $mapPoint) { $metersperpixel[13]=19.093*$width;
if (sizeof($mapPoints) === 1) { $metersperpixel[14]=9.547*$width;
$markers .= $mapPoint[0].",".$mapPoint[1].",ol-marker"; $metersperpixel[15]=4.773*$width;
} else { $metersperpixel[16]=2.387*$width;
$markers .= $mapPoint[0].",".$mapPoint[1].",lightblue".($index+1); // $metersperpixel[17]=1.193*$width;
  $center = "";
  $markers = "";
  $minlat = 999;
  $minlon = 999;
  $maxlat = 0;
  $maxlon = 0;
   
  if (sizeof($mapPoints) < 1) return "map error";
  if (sizeof($mapPoints) === 1) {
  if ($zoom == 0) $zoom = 14;
  $markers .= "{$mapPoints[0][0]},{$mapPoints[0][1]},$markerimage";
  $center = "{$mapPoints[0][0]},{$mapPoints[0][1]}";
  } else {
  foreach ($mapPoints as $index => $mapPoint) {
  $markers .= $mapPoint[0].",".$mapPoint[1].",".$markerImage.($index+1);
  if ($index+1 != sizeof($mapPoints)) $markers .= "|";
  if ($mapPoint[0] < $minlat) $minlat = $mapPoint[0];
  if ($mapPoint[0] > $maxlat) $maxlat = $mapPoint[0];
  if ($mapPoint[1] < $minlon) $minlon = $mapPoint[1];
  if ($mapPoint[1] > $maxlon) $maxlon = $mapPoint[1];
  $totalLat += $mapPoint[0];
  $totalLon += $mapPoint[1];
} }
} if ($zoom == 0) {
return '<img src="staticmaplite/staticmap.php?center='.$center.'&zoom=14&size='.$width.'x'.$height.'&maptype=mapnik&markers='.$markers.'" width=$width height=$height>'; $mapwidthinmeters = distance($minlat,$minlon,$minlat,$maxlon);
  foreach (array_reverse($metersperpixel,true) as $zoomLevel => $maxdistance)
  {
  if ($zoom == 0 && $mapwidthinmeters < ($maxdistance + 50)) $zoom = $zoomLevel;
  }
  }
  $center = $totalLat/sizeof($mapPoints).",".$totalLon/sizeof($mapPoints);
  }
   
  return '<img src="staticmaplite/staticmap.php?center='.$center.'&zoom='.$zoom.'&size='.$width.'x'.$height.'&maptype=mapnik&markers='.$markers.'" width='.$width.' height='.$height.'>';
  }
   
  function distance($lat1, $lng1, $lat2, $lng2)
  {
  $pi80 = M_PI / 180;
  $lat1 *= $pi80;
  $lng1 *= $pi80;
  $lat2 *= $pi80;
  $lng2 *= $pi80;
   
  $r = 6372.797; // mean radius of Earth in km
  $dlat = $lat2 - $lat1;
  $dlng = $lng2 - $lng1;
  $a = sin($dlat / 2) * sin($dlat / 2) + cos($lat1) * cos($lat2) * sin($dlng / 2) * sin($dlng / 2);
  $c = 2 * atan2(sqrt($a), sqrt(1 - $a));
  $km = $r * $c;
   
  return $km * 1000;
  }
   
  function decodePolylineToArray($encoded)
  {
  // source: http://latlongeeks.com/forum/viewtopic.php?f=4&t=5
  $length = strlen($encoded);
  $index = 0;
  $points = array();
  $lat = 0;
  $lng = 0;
   
  while ($index < $length)
  {
  // Temporary variable to hold each ASCII byte.
  $b = 0;
   
  // The encoded polyline consists of a latitude value followed by a
  // longitude value. They should always come in pairs. Read the
  // latitude value first.
  $shift = 0;
  $result = 0;
  do
  {
  // The `ord(substr($encoded, $index++))` statement returns the ASCII
  // code for the character at $index. Subtract 63 to get the original
  // value. (63 was added to ensure proper ASCII characters are displayed
  // in the encoded polyline string, which is `human` readable)
  $b = ord(substr($encoded, $index++)) - 63;
   
  // AND the bits of the byte with 0x1f to get the original 5-bit `chunk.
  // Then left shift the bits by the required amount, which increases
  // by 5 bits each time.
  // OR the value into $results, which sums up the individual 5-bit chunks
  // into the original value. Since the 5-bit chunks were reversed in
  // order during encoding, reading them in this way ensures proper
  // summation.
  $result |= ($b & 0x1f) << $shift;
  $shift += 5;
  }
  // Continue while the read byte is >= 0x20 since the last `chunk`
  // was not OR'd with 0x20 during the conversion process. (Signals the end)
  while ($b >= 0x20);
   
  // Check if negative, and convert. (All negative values have the last bit
  // set)
  $dlat = (($result & 1) ? ~($result >> 1) : ($result >> 1));
   
  // Compute actual latitude since value is offset from previous value.
  $lat += $dlat;
   
  // The next values will correspond to the longitude for this point.
  $shift = 0;
  $result = 0;
  do
  {
  $b = ord(substr($encoded, $index++)) - 63;
  $result |= ($b & 0x1f) << $shift;
  $shift += 5;
  }
  while ($b >= 0x20);
   
  $dlng = (($result & 1) ? ~($result >> 1) : ($result >> 1));
  $lng += $dlng;
   
  // The actual latitude and longitude values were multiplied by
  // 1e5 before encoding so that they could be converted to a 32-bit
  // integer representation. (With a decimal accuracy of 5 places)
  // Convert back to original values.
  $points[] = array($lat * 1e-5, $lng * 1e-5);
  }
   
  return $points;
  }
   
  function object2array($object) {
  if (is_object($object)) {
  foreach ($object as $key => $value) {
  $array[$key] = $value;
  }
  }
  else {
  $array = $object;
  }
  return $array;
  }
   
  function geocode($query, $giveOptions) {
  $url = "http://geocoding.cloudmade.com/daa03470bb8740298d4b10e3f03d63e6/geocoding/v2/find.js?query=".$query."&bbox=-35.5,149.00,-35.15,149.1930&return_location=true&bbox_only=true";
  $contents = json_decode(getPage($url));
  if ($giveOptions) return $contents->features;
  elseif (isset($contents->features[0]->centroid)) return $contents->features[0]->centroid->coordinates[0].",".$contents->features[0]->centroid->coordinates[1];
  else return "";
  }
   
  function reverseGeocode($lat,$lng) {
  $url = "http://geocoding.cloudmade.com/daa03470bb8740298d4b10e3f03d63e6/geocoding/v2/find.js?around=".$lat.",".$lng."&distance=closest&object_type=road";
  $contents = json_decode(getPage($url));
  return $contents->features[0]->properties->name;
  }
   
  function startsWith($haystack,$needle,$case=true) {
  if($case){return (strcmp(substr($haystack, 0, strlen($needle)),$needle)===0);}
  return (strcasecmp(substr($haystack, 0, strlen($needle)),$needle)===0);
  }
   
  function endsWith($haystack,$needle,$case=true) {
  if($case){return (strcmp(substr($haystack, strlen($haystack) - strlen($needle)),$needle)===0);}
  return (strcasecmp(substr($haystack, strlen($haystack) - strlen($needle)),$needle)===0);
} }
?> ?>
   
 Binary files /dev/null and b/busui/images/01-refresh.png differ
 Binary files /dev/null and b/busui/images/02-redo.png differ
 Binary files /dev/null and b/busui/images/06-magnify.png differ
 Binary files /dev/null and b/busui/images/07-map-marker.png differ
 Binary files /dev/null and b/busui/images/101-gameplan.png differ
 Binary files /dev/null and b/busui/images/102-walk.png differ
 Binary files /dev/null and b/busui/images/103-map.png differ
 Binary files /dev/null and b/busui/images/113-navigation.png differ
 Binary files /dev/null and b/busui/images/121-landscape.png differ
 Binary files /dev/null and b/busui/images/13-target.png differ
 Binary files /dev/null and b/busui/images/139-flags.png differ
 Binary files /dev/null and b/busui/images/145-persondot.png differ
 Binary files /dev/null and b/busui/images/184-warning.png differ
 Binary files /dev/null and b/busui/images/193-location-arrow.png differ
 Binary files /dev/null and b/busui/images/28-star.png differ
 Binary files /dev/null and b/busui/images/53-house.png differ
 Binary files /dev/null and b/busui/images/55-network.png differ
 Binary files /dev/null and b/busui/images/57-download.png differ
 Binary files /dev/null and b/busui/images/58-bookmark.png differ
 Binary files /dev/null and b/busui/images/59-flag.png differ
 Binary files /dev/null and b/busui/images/60-signpost.png differ
 Binary files /dev/null and b/busui/images/73-radar.png differ
 Binary files /dev/null and b/busui/images/74-location.png differ
 Binary files /dev/null and b/busui/images/83-calendar.png differ
<?php <?php
include('common.inc.php'); include('common.inc.php');
include_header("bus.lambdacomplex.org",false) include_header("bus.lambdacomplex.org",false, true)
?> ?>
<div data-role="page" data-theme="b" id="jqm-home" class="ui-page ui-body-b ui-page-active"> <div data-role="page" data-theme="b" id="jqm-home" class="ui-page ui-body-b ui-page-active">
<div id="jqm-homeheader"> <div id="jqm-homeheader">
<center><h1 id="jqm-logo"><img src="apple-touch-icon.png" alt="jQuery Mobile Framework" width="64" height="64" /> <center><h1 id="jqm-logo"><img src="apple-touch-icon.png" alt="logo" width="64" height="64" /><br>
bus.lambdacomplex.org</h1></center> bus.lambdacomplex.org</h1></center>
</div> </div>
<div data-role="content"> <div data-role="content">
  <a href="tripPlanner.php" data-role="button">Launch Trip Planner...</a>
<ul data-role="listview" data-inset="true" data-theme="c" data-dividertheme="b"> <ul data-role="listview" data-inset="true" data-theme="c" data-dividertheme="b">
<li data-role="list-divider">Stops</li> <li data-role="list-divider">Timetables - Stops</li>
<li><a href="stopList.php">All stops List</a></li> <li><a href="stopList.php">Major (Timing Point) Stops</a></li>
<li class="nearby"><a href="stopList.php?nearby=yes">Nearby List</a></li> <li><a href="stopList.php">All Stops</a></li>
<li><a href="stopList.php?favourites=yes">Favourites List</a></li> <li><a href="stopList.php?nearbyfavs=yes">Nearby/Favourite Stops</a></li>
</ul> </ul>
<ul data-role="listview" data-inset="true" data-theme="c" data-dividertheme="b"> <ul data-role="listview" data-inset="true" data-theme="c" data-dividertheme="b">
<li data-role="list-divider">Routes</li> <li data-role="list-divider">Timetables - Routes</li>
<li><a href="routeList.php">All Routes List</a></li> <li><a href="routeList.php">All Routes</a></li>
<li class="nearby"><a href="routeList.php?nearby=yes">Nearby List</a></li> <li><a href="routeList.php?nearbyfavs=yes">Nearby/Favourites Routes</a></li>
<li><a href="routeList.php?favourites=yes">Favourites List</a></li>  
</ul> </ul>
<div class="ui-body ui-body-c info"> <div class="ui-body ui-body-c">
<p class="latlng"></p> Current Location: <input type="text" id="geolocate" name="geolocate"/> Search? Update to Here?<br>
Time: <?php echo date("H:m"); ?> <br> Time: <?php echo date("H:m"); ?> <br>
Service Period: <?php echo ucwords(service_period()); ?> Service Period: <?php echo ucwords(service_period()); ?><br>
</div> </div>
</div> </div>
</div> </div>
</body> </body>
</html> </html>
   
<?php <?php
include('common.inc.php'); include('common.inc.php');
include_header("Routes"); include_header("Routes");
/* data-filter="true">'; echo'
echo "<script> $('#routeList').listnav({prefixes: ['to'] }); </script>";*/ <div data-role="navbar">
  <ul>
  <li><a href="routeList.php" class="ui-btn-active">By Final Destination...</a></li>
  <li><a href="routeList.php?bynumber=yes">By Number... </a></li>
  <li><a href="routeList.php?bysuburb=yes">By Suburb... </a></li>
  </ul>
  </div>
  ';
echo ' <ul data-role="listview">'; echo ' <ul data-role="listview">';
$url = $APIurl."/json/routes"; $url = $APIurl."/json/routes";
   
$contents = json_decode(getPage($url)); $contents = json_decode(getPage($url));
  debug(print_r($contents,true));
   
  function printRoutes($routes){
  foreach($routes as $row) {
  echo '<li>'.$row[1].' <a href="trip.php?routeid='.$row[0].'">'.$row[2]." (".ucwords($row[3]).")</a></li>\n";
  }
  }
   
if ($_REQUEST['bynumber']) { if ($_REQUEST['bynumber']) {
$routeSeries = Array(); $routeSeries = Array();
foreach ($contents as $key => $row) { foreach ($contents as $key => $row) {
foreach (explode(" ",$row[1]) as $routeNumber ) { foreach (explode(" ",$row[1]) as $routeNumber ) {
$seriesNum = substr($routeNumber, 0, -1)."0"; $seriesNum = substr($routeNumber, 0, -1)."0";
if ($seriesNum == "0") $seriesNum = $routeNumber; if ($seriesNum == "0") $seriesNum = $routeNumber;
$routeSeries[$seriesNum][$seriesNum."-".$row[1]."-".$row[0]] = $row; $routeSeries[$seriesNum][$seriesNum."-".$row[1]."-".$row[0]] = $row;
   
} }
} }
ksort($routeSeries); ksort($routeSeries);
foreach ($routeSeries as $series => $routes) foreach ($routeSeries as $series => $routes)
{ {
echo '<li>'.$series."... <ul>\n"; echo '<li>'.$series."... <ul>\n";
foreach($routes as $row) { printRoutes($routes);
echo '<li>'.$row[1].' <a href="route.php?routeid='.$row[0].'">'.$row[2]."</a></li>\n";  
}  
echo "</ul></li>\n"; echo "</ul></li>\n";
} }
} else { } else {
foreach ($contents as $key => $row) { foreach ($contents as $key => $row) {
$routeDestinations[$row[2]][] = $row; $routeDestinations[$row[2]][] = $row;
} }
foreach ($routeDestinations as $destination => $routes) foreach ($routeDestinations as $destination => $routes)
{ {
echo '<li>'.$destination."... <ul>\n"; echo '<li>'.$destination."... <ul>\n";
foreach($routes as $row) { printRoutes($routes);
echo '<li>'.$row[1].' <a href="trip.php?routeid='.$row[0].'">'.$row[2]."</a></li>\n";  
}  
echo "</ul></li>\n"; echo "</ul></li>\n";
} }
} }
echo "</ul>\n"; echo "</ul>\n";
echo'  
<div data-role="footer" data-id="foo1" data-position="fixed">  
<div data-role="navbar">  
<ul>  
<li><a href="routeList.php" class="ui-btn-active">By Final Destination...</a></li>  
<li><a href="routeList.php?bynumber=yes">By Number... </a></li>  
<li><a href="routeList.php?bysuburb=yes">By Suburb... </a></li>  
</ul>  
';  
include_footer(); include_footer();
?> ?>
   
#!/usr/bin/python2.5 #!/usr/bin/python2.5
   
# Copyright (C) 2007 Google Inc. # Copyright (C) 2007 Google Inc.
# #
# Licensed under the Apache License, Version 2.0 (the "License"); # Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License. # you may not use this file except in compliance with the License.
# You may obtain a copy of the License at # You may obtain a copy of the License at
# #
# http://www.apache.org/licenses/LICENSE-2.0 # http://www.apache.org/licenses/LICENSE-2.0
# #
# Unless required by applicable law or agreed to in writing, software # Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, # distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and # See the License for the specific language governing permissions and
# limitations under the License. # limitations under the License.
   
""" """
An example application that uses the transitfeed module. An example application that uses the transitfeed module.
   
You must provide a Google Maps API key. You must provide a Google Maps API key.
""" """
   
   
import BaseHTTPServer, sys, urlparse import BaseHTTPServer, sys, urlparse
import bisect import bisect
from gtfsscheduleviewer.marey_graph import MareyGraph from gtfsscheduleviewer.marey_graph import MareyGraph
import gtfsscheduleviewer import gtfsscheduleviewer
import mimetypes import mimetypes
import os.path import os.path
import re import re
import signal import signal
import simplejson import simplejson
import socket import socket
import time import time
import transitfeed import transitfeed
from transitfeed import util from transitfeed import util
import urllib import urllib
   
   
# By default Windows kills Python with Ctrl+Break. Instead make Ctrl+Break # By default Windows kills Python with Ctrl+Break. Instead make Ctrl+Break
# raise a KeyboardInterrupt. # raise a KeyboardInterrupt.
if hasattr(signal, 'SIGBREAK'): if hasattr(signal, 'SIGBREAK'):
signal.signal(signal.SIGBREAK, signal.default_int_handler) signal.signal(signal.SIGBREAK, signal.default_int_handler)
   
   
mimetypes.add_type('text/plain', '.vbs') mimetypes.add_type('text/plain', '.vbs')
   
   
class ResultEncoder(simplejson.JSONEncoder): class ResultEncoder(simplejson.JSONEncoder):
def default(self, obj): def default(self, obj):
try: try:
iterable = iter(obj) iterable = iter(obj)
except TypeError: except TypeError:
pass pass
else: else:
return list(iterable) return list(iterable)
return simplejson.JSONEncoder.default(self, obj) return simplejson.JSONEncoder.default(self, obj)
   
# Code taken from # Code taken from
# http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/425210/index_txt # http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/425210/index_txt
# An alternate approach is shown at # An alternate approach is shown at
# http://mail.python.org/pipermail/python-list/2003-July/212751.html # http://mail.python.org/pipermail/python-list/2003-July/212751.html
# but it requires multiple threads. A sqlite object can only be used from one # but it requires multiple threads. A sqlite object can only be used from one
# thread. # thread.
class StoppableHTTPServer(BaseHTTPServer.HTTPServer): class StoppableHTTPServer(BaseHTTPServer.HTTPServer):
def server_bind(self): def server_bind(self):
BaseHTTPServer.HTTPServer.server_bind(self) BaseHTTPServer.HTTPServer.server_bind(self)
self.socket.settimeout(1) self.socket.settimeout(1)
self._run = True self._run = True
   
def get_request(self): def get_request(self):
while self._run: while self._run:
try: try:
sock, addr = self.socket.accept() sock, addr = self.socket.accept()
sock.settimeout(None) sock.settimeout(None)
return (sock, addr) return (sock, addr)
except socket.timeout: except socket.timeout:
pass pass
   
def stop(self): def stop(self):
self._run = False self._run = False
   
def serve(self): def serve(self):
while self._run: while self._run:
self.handle_request() self.handle_request()
   
   
def StopToTuple(stop): def StopToTuple(stop):
"""Return tuple as expected by javascript function addStopMarkerFromList""" """Return tuple as expected by javascript function addStopMarkerFromList"""
return (stop.stop_id, stop.stop_name, float(stop.stop_lat), return (stop.stop_id, stop.stop_name, float(stop.stop_lat),
float(stop.stop_lon), stop.location_type) float(stop.stop_lon), stop.location_type)
   
   
class ScheduleRequestHandler(BaseHTTPServer.BaseHTTPRequestHandler): class ScheduleRequestHandler(BaseHTTPServer.BaseHTTPRequestHandler):
def do_GET(self): def do_GET(self):
scheme, host, path, x, params, fragment = urlparse.urlparse(self.path) scheme, host, path, x, params, fragment = urlparse.urlparse(self.path)
parsed_params = {} parsed_params = {}
for k in params.split('&'): for k in params.split('&'):
k = urllib.unquote(k) k = urllib.unquote(k)
if '=' in k: if '=' in k:
k, v = k.split('=', 1) k, v = k.split('=', 1)
parsed_params[k] = unicode(v, 'utf8') parsed_params[k] = unicode(v, 'utf8')
else: else:
parsed_params[k] = '' parsed_params[k] = ''
   
if path == '/': if path == '/':
return self.handle_GET_home() return self.handle_GET_home()
   
m = re.match(r'/json/([a-z]{1,64})', path) m = re.match(r'/json/([a-z]{1,64})', path)
if m: if m:
handler_name = 'handle_json_GET_%s' % m.group(1) handler_name = 'handle_json_GET_%s' % m.group(1)
handler = getattr(self, handler_name, None) handler = getattr(self, handler_name, None)
if callable(handler): if callable(handler):
return self.handle_json_wrapper_GET(handler, parsed_params) return self.handle_json_wrapper_GET(handler, parsed_params)
   
# Restrict allowable file names to prevent relative path attacks etc # Restrict allowable file names to prevent relative path attacks etc
m = re.match(r'/file/([a-z0-9_-]{1,64}\.?[a-z0-9_-]{1,64})$', path) m = re.match(r'/file/([a-z0-9_-]{1,64}\.?[a-z0-9_-]{1,64})$', path)
if m and m.group(1): if m and m.group(1):
try: try:
f, mime_type = self.OpenFile(m.group(1)) f, mime_type = self.OpenFile(m.group(1))
return self.handle_static_file_GET(f, mime_type) return self.handle_static_file_GET(f, mime_type)
except IOError, e: except IOError, e:
print "Error: unable to open %s" % m.group(1) print "Error: unable to open %s" % m.group(1)
# Ignore and treat as 404 # Ignore and treat as 404
   
m = re.match(r'/([a-z]{1,64})', path) m = re.match(r'/([a-z]{1,64})', path)
if m: if m:
handler_name = 'handle_GET_%s' % m.group(1) handler_name = 'handle_GET_%s' % m.group(1)
handler = getattr(self, handler_name, None) handler = getattr(self, handler_name, None)
if callable(handler): if callable(handler):
return handler(parsed_params) return handler(parsed_params)
   
return self.handle_GET_default(parsed_params, path) return self.handle_GET_default(parsed_params, path)
   
def OpenFile(self, filename): def OpenFile(self, filename):
"""Try to open filename in the static files directory of this server. """Try to open filename in the static files directory of this server.
Return a tuple (file object, string mime_type) or raise an exception.""" Return a tuple (file object, string mime_type) or raise an exception."""
(mime_type, encoding) = mimetypes.guess_type(filename) (mime_type, encoding) = mimetypes.guess_type(filename)
assert mime_type assert mime_type
# A crude guess of when we should use binary mode. Without it non-unix # A crude guess of when we should use binary mode. Without it non-unix
# platforms may corrupt binary files. # platforms may corrupt binary files.
if mime_type.startswith('text/'): if mime_type.startswith('text/'):
mode = 'r' mode = 'r'
else: else:
mode = 'rb' mode = 'rb'
return open(os.path.join(self.server.file_dir, filename), mode), mime_type return open(os.path.join(self.server.file_dir, filename), mode), mime_type
   
def handle_GET_default(self, parsed_params, path): def handle_GET_default(self, parsed_params, path):
self.send_error(404) self.send_error(404)
   
def handle_static_file_GET(self, fh, mime_type): def handle_static_file_GET(self, fh, mime_type):
content = fh.read() content = fh.read()
self.send_response(200) self.send_response(200)
self.send_header('Content-Type', mime_type) self.send_header('Content-Type', mime_type)
self.send_header('Content-Length', str(len(content))) self.send_header('Content-Length', str(len(content)))
self.end_headers() self.end_headers()
self.wfile.write(content) self.wfile.write(content)
   
def AllowEditMode(self): def AllowEditMode(self):
return False return False
   
def handle_GET_home(self): def handle_GET_home(self):
schedule = self.server.schedule schedule = self.server.schedule
(min_lat, min_lon, max_lat, max_lon) = schedule.GetStopBoundingBox() (min_lat, min_lon, max_lat, max_lon) = schedule.GetStopBoundingBox()
forbid_editing = ('true', 'false')[self.AllowEditMode()] forbid_editing = ('true', 'false')[self.AllowEditMode()]
   
agency = ', '.join(a.agency_name for a in schedule.GetAgencyList()).encode('utf-8') agency = ', '.join(a.agency_name for a in schedule.GetAgencyList()).encode('utf-8')
   
key = self.server.key key = self.server.key
host = self.server.host host = self.server.host
   
# A very simple template system. For a fixed set of values replace [xxx] # A very simple template system. For a fixed set of values replace [xxx]
# with the value of local variable xxx # with the value of local variable xxx
f, _ = self.OpenFile('index.html') f, _ = self.OpenFile('index.html')
content = f.read() content = f.read()
for v in ('agency', 'min_lat', 'min_lon', 'max_lat', 'max_lon', 'key', for v in ('agency', 'min_lat', 'min_lon', 'max_lat', 'max_lon', 'key',
'host', 'forbid_editing'): 'host', 'forbid_editing'):
content = content.replace('[%s]' % v, str(locals()[v])) content = content.replace('[%s]' % v, str(locals()[v]))
   
self.send_response(200) self.send_response(200)
self.send_header('Content-Type', 'text/html') self.send_header('Content-Type', 'text/html')
self.send_header('Content-Length', str(len(content))) self.send_header('Content-Length', str(len(content)))
self.end_headers() self.end_headers()
self.wfile.write(content) self.wfile.write(content)
   
def handle_json_GET_routepatterns(self, params): def handle_json_GET_routepatterns(self, params):
"""Given a route_id generate a list of patterns of the route. For each """Given a route_id generate a list of patterns of the route. For each
pattern include some basic information and a few sample trips.""" pattern include some basic information and a few sample trips."""
schedule = self.server.schedule schedule = self.server.schedule
route = schedule.GetRoute(params.get('route', None)) route = schedule.GetRoute(params.get('route', None))
if not route: if not route:
self.send_error(404) self.send_error(404)
return return
time = int(params.get('time', 0)) time = int(params.get('time', 0))
sample_size = 10 # For each pattern return the start time for this many trips sample_size = 10 # For each pattern return the start time for this many trips
   
pattern_id_trip_dict = route.GetPatternIdTripDict() pattern_id_trip_dict = route.GetPatternIdTripDict()
patterns = [] patterns = []
   
for pattern_id, trips in pattern_id_trip_dict.items(): for pattern_id, trips in pattern_id_trip_dict.items():
time_stops = trips[0].GetTimeStops() time_stops = trips[0].GetTimeStops()
if not time_stops: if not time_stops:
continue continue
has_non_zero_trip_type = False; has_non_zero_trip_type = False;
for trip in trips: for trip in trips:
if trip['trip_type'] and trip['trip_type'] != '0': if trip['trip_type'] and trip['trip_type'] != '0':
has_non_zero_trip_type = True has_non_zero_trip_type = True
name = u'%s to %s, %d stops' % (time_stops[0][2].stop_name, time_stops[-1][2].stop_name, len(time_stops)) name = u'%s to %s, %d stops' % (time_stops[0][2].stop_name, time_stops[-1][2].stop_name, len(time_stops))
transitfeed.SortListOfTripByTime(trips) transitfeed.SortListOfTripByTime(trips)
   
num_trips = len(trips) num_trips = len(trips)
if num_trips <= sample_size: if num_trips <= sample_size:
start_sample_index = 0 start_sample_index = 0
num_after_sample = 0 num_after_sample = 0
else: else:
# Will return sample_size trips that start after the 'time' param. # Will return sample_size trips that start after the 'time' param.
   
# Linear search because I couldn't find a built-in way to do a binary # Linear search because I couldn't find a built-in way to do a binary
# search with a custom key. # search with a custom key.
start_sample_index = len(trips) start_sample_index = len(trips)
for i, trip in enumerate(trips): for i, trip in enumerate(trips):
if trip.GetStartTime() >= time: if trip.GetStartTime() >= time:
start_sample_index = i start_sample_index = i
break break
   
num_after_sample = num_trips - (start_sample_index + sample_size) num_after_sample = num_trips - (start_sample_index + sample_size)
if num_after_sample < 0: if num_after_sample < 0:
# Less than sample_size trips start after 'time' so return all the # Less than sample_size trips start after 'time' so return all the
# last sample_size trips. # last sample_size trips.
num_after_sample = 0 num_after_sample = 0
start_sample_index = num_trips - sample_size start_sample_index = num_trips - sample_size
   
sample = [] sample = []
for t in trips[start_sample_index:start_sample_index + sample_size]: for t in trips[start_sample_index:start_sample_index + sample_size]:
sample.append( (t.GetStartTime(), t.trip_id) ) sample.append( (t.GetStartTime(), t.trip_id) )
   
patterns.append((name, pattern_id, start_sample_index, sample, patterns.append((name, pattern_id, start_sample_index, sample,
num_after_sample, (0,1)[has_non_zero_trip_type])) num_after_sample, (0,1)[has_non_zero_trip_type]))
   
patterns.sort() patterns.sort()
return patterns return patterns
   
def handle_json_wrapper_GET(self, handler, parsed_params): def handle_json_wrapper_GET(self, handler, parsed_params):
"""Call handler and output the return value in JSON.""" """Call handler and output the return value in JSON."""
schedule = self.server.schedule schedule = self.server.schedule
result = handler(parsed_params) result = handler(parsed_params)
content = ResultEncoder().encode(result) content = ResultEncoder().encode(result)
self.send_response(200) self.send_response(200)
self.send_header('Content-Type', 'text/plain') self.send_header('Content-Type', 'text/plain')
self.send_header('Content-Length', str(len(content))) self.send_header('Content-Length', str(len(content)))
self.end_headers() self.end_headers()
self.wfile.write(content) self.wfile.write(content)
   
def handle_json_GET_routes(self, params): def handle_json_GET_routes(self, params):
"""Return a list of all routes.""" """Return a list of all routes."""
schedule = self.server.schedule schedule = self.server.schedule
result = [] result = []
for r in schedule.GetRouteList(): for r in schedule.GetRouteList():
result.append( (r.route_id, r.route_short_name, r.route_long_name) ) servicep = None
  for t in schedule.GetTripList():
  if t.route_id == r.route_id:
  servicep = t.service_period
  break
  result.append( (r.route_id, r.route_short_name, r.route_long_name, servicep.service_id) )
result.sort(key = lambda x: x[1:3]) result.sort(key = lambda x: x[1:3])
return result return result
   
def handle_json_GET_routerow(self, params): def handle_json_GET_routerow(self, params):
schedule = self.server.schedule schedule = self.server.schedule
route = schedule.GetRoute(params.get('route', None)) route = schedule.GetRoute(params.get('route', None))
return [transitfeed.Route._FIELD_NAMES, route.GetFieldValuesTuple()] return [transitfeed.Route._FIELD_NAMES, route.GetFieldValuesTuple()]
def handle_json_GET_routetrips(self, params): def handle_json_GET_routetrips(self, params):
""" Get a trip for a route_id (preferablly the next one) """ """ Get a trip for a route_id (preferablly the next one) """
schedule = self.server.schedule schedule = self.server.schedule
query = params.get('route_id', None).lower() query = params.get('route_id', None).lower()
result = [] result = []
for t in schedule.GetTripList(): for t in schedule.GetTripList():
if t.route_id == query: if t.route_id == query:
result.append ( (t.GetStartTime(), t.trip_id) ) result.append ( (t.GetStartTime(), t.trip_id) )
""" UGH fails for 300s """  
"""return result"""  
return sorted(result, key=lambda trip: trip[0]) return sorted(result, key=lambda trip: trip[0])
def handle_json_GET_triprows(self, params): def handle_json_GET_triprows(self, params):
"""Return a list of rows from the feed file that are related to this """Return a list of rows from the feed file that are related to this
trip.""" trip."""
schedule = self.server.schedule schedule = self.server.schedule
try: try:
trip = schedule.GetTrip(params.get('trip', None)) trip = schedule.GetTrip(params.get('trip', None))
except KeyError: except KeyError:
# if a non-existent trip is searched for, the return nothing # if a non-existent trip is searched for, the return nothing
return return
route = schedule.GetRoute(trip.route_id) route = schedule.GetRoute(trip.route_id)
trip_row = dict(trip.iteritems()) trip_row = dict(trip.iteritems())
route_row = dict(route.iteritems()) route_row = dict(route.iteritems())
return [['trips.txt', trip_row], ['routes.txt', route_row]] return [['trips.txt', trip_row], ['routes.txt', route_row]]
   
def handle_json_GET_tripstoptimes(self, params): def handle_json_GET_tripstoptimes(self, params):
schedule = self.server.schedule schedule = self.server.schedule
try: try:
trip = schedule.GetTrip(params.get('trip')) trip = schedule.GetTrip(params.get('trip'))
except KeyError: except KeyError:
# if a non-existent trip is searched for, the return nothing # if a non-existent trip is searched for, the return nothing
return return
time_stops = trip.GetTimeStops() time_stops = trip.GetTimeStops()
stops = [] stops = []
times = [] times = []
for arr,dep,stop in time_stops: for arr,dep,stop in time_stops:
stops.append(StopToTuple(stop)) stops.append(StopToTuple(stop))
times.append(arr) times.append(arr)
return [stops, times] return [stops, times]
   
def handle_json_GET_tripshape(self, params): def handle_json_GET_tripshape(self, params):
schedule = self.server.schedule schedule = self.server.schedule
try: try:
trip = schedule.GetTrip(params.get('trip')) trip = schedule.GetTrip(params.get('trip'))
except KeyError: except KeyError:
# if a non-existent trip is searched for, the return nothing # if a non-existent trip is searched for, the return nothing
return return
points = [] points = []
if trip.shape_id: if trip.shape_id:
shape = schedule.GetShape(trip.shape_id) shape = schedule.GetShape(trip.shape_id)
for (lat, lon, dist) in shape.points: for (lat, lon, dist) in shape.points:
points.append((lat, lon)) points.append((lat, lon))
else: else:
time_stops = trip.GetTimeStops() time_stops = trip.GetTimeStops()
for arr,dep,stop in time_stops: for arr,dep,stop in time_stops:
points.append((stop.stop_lat, stop.stop_lon)) points.append((stop.stop_lat, stop.stop_lon))
return points return points
   
def handle_json_GET_neareststops(self, params): def handle_json_GET_neareststops(self, params):
"""Return a list of the nearest 'limit' stops to 'lat', 'lon'""" """Return a list of the nearest 'limit' stops to 'lat', 'lon'"""
schedule = self.server.schedule schedule = self.server.schedule
lat = float(params.get('lat')) lat = float(params.get('lat'))
lon = float(params.get('lon')) lon = float(params.get('lon'))
limit = int(params.get('limit')) limit = int(params.get('limit'))
stops = schedule.GetNearestStops(lat=lat, lon=lon, n=limit) stops = schedule.GetNearestStops(lat=lat, lon=lon, n=limit)
return [StopToTuple(s) for s in stops] return [StopToTuple(s) for s in stops]
   
def handle_json_GET_boundboxstops(self, params): def handle_json_GET_boundboxstops(self, params):
"""Return a list of up to 'limit' stops within bounding box with 'n','e' """Return a list of up to 'limit' stops within bounding box with 'n','e'
and 's','w' in the NE and SW corners. Does not handle boxes crossing and 's','w' in the NE and SW corners. Does not handle boxes crossing
longitude line 180.""" longitude line 180."""
schedule = self.server.schedule schedule = self.server.schedule
n = float(params.get('n')) n = float(params.get('n'))
e = float(params.get('e')) e = float(params.get('e'))
s = float(params.get('s')) s = float(params.get('s'))
w = float(params.get('w')) w = float(params.get('w'))
limit = int(params.get('limit')) limit = int(params.get('limit'))
stops = schedule.GetStopsInBoundingBox(north=n, east=e, south=s, west=w, n=limit) stops = schedule.GetStopsInBoundingBox(north=n, east=e, south=s, west=w, n=limit)
return [StopToTuple(s) for s in stops] return [StopToTuple(s) for s in stops]
   
def handle_json_GET_stops(self, params): def handle_json_GET_stops(self, params):
schedule = self.server.schedule schedule = self.server.schedule
return [StopToTuple(s) for s in schedule.GetStopList()] return [StopToTuple(s) for s in schedule.GetStopList()]
def handle_json_GET_timingpoints(self, params): def handle_json_GET_timingpoints(self, params):
schedule = self.server.schedule schedule = self.server.schedule
matches = [] matches = []
for s in schedule.GetStopList(): for s in schedule.GetStopList():
if s.stop_code.find("Wj") == -1: if s.stop_code.find("Wj") == -1:
matches.append(StopToTuple(s)) matches.append(StopToTuple(s))
return matches return matches
   
def handle_json_GET_stopsearch(self, params): def handle_json_GET_stopsearch(self, params):
schedule = self.server.schedule schedule = self.server.schedule
query = params.get('q', None).lower() query = params.get('q', None).lower()
matches = [] matches = []
for s in schedule.GetStopList(): for s in schedule.GetStopList():
if s.stop_id.lower().find(query) != -1 or s.stop_name.lower().find(query) != -1: if s.stop_id.lower().find(query) != -1 or s.stop_name.lower().find(query) != -1:
matches.append(StopToTuple(s)) matches.append(StopToTuple(s))
return matches return matches
   
def handle_json_GET_stop(self, params): def handle_json_GET_stop(self, params):
schedule = self.server.schedule schedule = self.server.schedule
query = params.get('stop_id', None).lower() query = params.get('stop_id', None).lower()
for s in schedule.GetStopList(): for s in schedule.GetStopList():
if s.stop_id.lower() == query: if s.stop_id.lower() == query:
return StopToTuple(s) return StopToTuple(s)
return [] return []
   
def handle_json_GET_stoptrips(self, params): def handle_json_GET_stoptrips(self, params):
"""Given a stop_id and time in seconds since midnight return the next """Given a stop_id and time in seconds since midnight return the next
trips to visit the stop.""" trips to visit the stop."""
schedule = self.server.schedule schedule = self.server.schedule
stop = schedule.GetStop(params.get('stop', None)) stop = schedule.GetStop(params.get('stop', None))
time = int(params.get('time', 0)) time = int(params.get('time', 0))
service_period = params.get('service_period', None) service_period = params.get('service_period', None)
time_trips = stop.GetStopTimeTrips(schedule) time_trips = stop.GetStopTimeTrips(schedule)
time_trips.sort() # OPT: use bisect.insort to make this O(N*ln(N)) -> O(N) time_trips.sort() # OPT: use bisect.insort to make this O(N*ln(N)) -> O(N)
# Keep the first 15 after param 'time'. # Keep the first 15 after param 'time'.
# Need make a tuple to find correct bisect point # Need make a tuple to find correct bisect point
time_trips = time_trips[bisect.bisect_left(time_trips, (time, 0)):] time_trips = time_trips[bisect.bisect_left(time_trips, (time, 0)):]
time_trips = time_trips[:15] time_trips = time_trips[:15]
# TODO: combine times for a route to show next 2 departure times # TODO: combine times for a route to show next 2 departure times
result = [] result = []
for time, (trip, index), tp in time_trips: for time, (trip, index), tp in time_trips:
headsign = None headsign = None
# Find the most recent headsign from the StopTime objects # Find the most recent headsign from the StopTime objects
for stoptime in trip.GetStopTimes()[index::-1]: for stoptime in trip.GetStopTimes()[index::-1]:
if stoptime.stop_headsign: if stoptime.stop_headsign:
headsign = stoptime.stop_headsign headsign = stoptime.stop_headsign
break break
# If stop_headsign isn't found, look for a trip_headsign # If stop_headsign isn't found, look for a trip_headsign
if not headsign: if not headsign:
headsign = trip.trip_headsign headsign = trip.trip_headsign
route = schedule.GetRoute(trip.route_id) route = schedule.GetRoute(trip.route_id)
trip_name = '' trip_name = ''
if route.route_short_name: if route.route_short_name:
trip_name += route.route_short_name trip_name += route.route_short_name
if route.route_long_name: if route.route_long_name:
if len(trip_name): if len(trip_name):
trip_name += " - " trip_name += " - "
trip_name += route.route_long_name trip_name += route.route_long_name
if headsign: if headsign:
trip_name += " (Direction: %s)" % headsign trip_name += " (Direction: %s)" % headsign
if service_period == None or trip.service_id == service_period: if service_period == None or trip.service_id == service_period:
result.append((time, (trip.trip_id, trip_name, trip.service_id), tp)) result.append((time, (trip.trip_id, trip_name, trip.service_id), tp))
return result return result
   
def handle_GET_ttablegraph(self,params): def handle_GET_ttablegraph(self,params):
"""Draw a Marey graph in SVG for a pattern (collection of trips in a route """Draw a Marey graph in SVG for a pattern (collection of trips in a route
that visit the same sequence of stops).""" that visit the same sequence of stops)."""
schedule = self.server.schedule schedule = self.server.schedule
marey = MareyGraph() marey = MareyGraph()
trip = schedule.GetTrip(params.get('trip', None)) trip = schedule.GetTrip(params.get('trip', None))
route = schedule.GetRoute(trip.route_id) route = schedule.GetRoute(trip.route_id)
height = int(params.get('height', 300)) height = int(params.get('height', 300))
   
if not route: if not route:
print 'no such route' print 'no such route'
self.send_error(404) self.send_error(404)
return return
   
pattern_id_trip_dict = route.GetPatternIdTripDict() pattern_id_trip_dict = route.GetPatternIdTripDict()
pattern_id = trip.pattern_id pattern_id = trip.pattern_id
if pattern_id not in pattern_id_trip_dict: if pattern_id not in pattern_id_trip_dict:
print 'no pattern %s found in %s' % (pattern_id, pattern_id_trip_dict.keys()) print 'no pattern %s found in %s' % (pattern_id, pattern_id_trip_dict.keys())
self.send_error(404) self.send_error(404)
return return
triplist = pattern_id_trip_dict[pattern_id] triplist = pattern_id_trip_dict[pattern_id]
   
pattern_start_time = min((t.GetStartTime() for t in triplist)) pattern_start_time = min((t.GetStartTime() for t in triplist))
pattern_end_time = max((t.GetEndTime() for t in triplist)) pattern_end_time = max((t.GetEndTime() for t in triplist))
   
marey.SetSpan(pattern_start_time,pattern_end_time) marey.SetSpan(pattern_start_time,pattern_end_time)
marey.Draw(triplist[0].GetPattern(), triplist, height) marey.Draw(triplist[0].GetPattern(), triplist, height)
   
content = marey.Draw() content = marey.Draw()
   
self.send_response(200) self.send_response(200)
self.send_header('Content-Type', 'image/svg+xml') self.send_header('Content-Type', 'image/svg+xml')
self.send_header('Content-Length', str(len(content))) self.send_header('Content-Length', str(len(content)))
self.end_headers() self.end_headers()
self.wfile.write(content) self.wfile.write(content)
   
   
def FindPy2ExeBase(): def FindPy2ExeBase():
"""If this is running in py2exe return the install directory else return """If this is running in py2exe return the install directory else return
None""" None"""
# py2exe puts gtfsscheduleviewer in library.zip. For py2exe setup.py is # py2exe puts gtfsscheduleviewer in library.zip. For py2exe setup.py is
# configured to put the data next to library.zip. # configured to put the data next to library.zip.
windows_ending = gtfsscheduleviewer.__file__.find('\\library.zip\\') windows_ending = gtfsscheduleviewer.__file__.find('\\library.zip\\')
if windows_ending != -1: if windows_ending != -1:
return transitfeed.__file__[:windows_ending] return transitfeed.__file__[:windows_ending]
else: else:
return None return None
   
   
def FindDefaultFileDir(): def FindDefaultFileDir():
"""Return the path of the directory containing the static files. By default """Return the path of the directory containing the static files. By default
the directory is called 'files'. The location depends on where setup.py put the directory is called 'files'. The location depends on where setup.py put
it.""" it."""
base = FindPy2ExeBase() base = FindPy2ExeBase()
if base: if base:
return os.path.join(base, 'schedule_viewer_files') return os.path.join(base, 'schedule_viewer_files')
else: else:
# For all other distributions 'files' is in the gtfsscheduleviewer # For all other distributions 'files' is in the gtfsscheduleviewer
# directory. # directory.
base = os.path.dirname(gtfsscheduleviewer.__file__) # Strip __init__.py base = os.path.dirname(gtfsscheduleviewer.__file__) # Strip __init__.py
return os.path.join(base, 'files') return os.path.join(base, 'files')
   
   
def GetDefaultKeyFilePath(): def GetDefaultKeyFilePath():
"""In py2exe return absolute path of file in the base directory and in all """In py2exe return absolute path of file in the base directory and in all
other distributions return relative path 'key.txt'""" other distributions return relative path 'key.txt'"""
windows_base = FindPy2ExeBase() windows_base = FindPy2ExeBase()
if windows_base: if windows_base:
return os.path.join(windows_base, 'key.txt') return os.path.join(windows_base, 'key.txt')
else: else:
return 'key.txt' return 'key.txt'
   
   
def main(RequestHandlerClass = ScheduleRequestHandler): def main(RequestHandlerClass = ScheduleRequestHandler):
usage = \ usage = \
'''%prog [options] [<input GTFS.zip>] '''%prog [options] [<input GTFS.zip>]
   
Runs a webserver that lets you explore a <input GTFS.zip> in your browser. Runs a webserver that lets you explore a <input GTFS.zip> in your browser.
   
If <input GTFS.zip> is omited the filename is read from the console. Dragging If <input GTFS.zip> is omited the filename is read from the console. Dragging
a file into the console may enter the filename. a file into the console may enter the filename.
''' '''
parser = util.OptionParserLongError( parser = util.OptionParserLongError(
usage=usage, version='%prog '+transitfeed.__version__) usage=usage, version='%prog '+transitfeed.__version__)
parser.add_option('--feed_filename', '--feed', dest='feed_filename', parser.add_option('--feed_filename', '--feed', dest='feed_filename',
help='file name of feed to load') help='file name of feed to load')
parser.add_option('--key', dest='key', parser.add_option('--key', dest='key',
help='Google Maps API key or the name ' help='Google Maps API key or the name '
'of a text file that contains an API key') 'of a text file that contains an API key')
parser.add_option('--host', dest='host', help='Host name of Google Maps') parser.add_option('--host', dest='host', help='Host name of Google Maps')
parser.add_option('--port', dest='port', type='int', parser.add_option('--port', dest='port', type='int',
help='port on which to listen') help='port on which to listen')
parser.add_option('--file_dir', dest='file_dir', parser.add_option('--file_dir', dest='file_dir',
help='directory containing static files') help='directory containing static files')
parser.add_option('-n', '--noprompt', action='store_false', parser.add_option('-n', '--noprompt', action='store_false',
dest='manual_entry', dest='manual_entry',
help='disable interactive prompts') help='disable interactive prompts')
parser.set_defaults(port=8765, parser.set_defaults(port=8765,
host='maps.google.com', host='maps.google.com',
file_dir=FindDefaultFileDir(), file_dir=FindDefaultFileDir(),
manual_entry=True) manual_entry=True)
(options, args) = parser.parse_args() (options, args) = parser.parse_args()
   
if not os.path.isfile(os.path.join(options.file_dir, 'index.html')): if not os.path.isfile(os.path.join(options.file_dir, 'index.html')):
print "Can't find index.html with --file_dir=%s" % options.file_dir print "Can't find index.html with --file_dir=%s" % options.file_dir
exit(1) exit(1)
   
if not options.feed_filename and len(args) == 1: if not options.feed_filename and len(args) == 1:
options.feed_filename = args[0] options.feed_filename = args[0]
   
if not options.feed_filename and options.manual_entry: if not options.feed_filename and options.manual_entry:
options.feed_filename = raw_input('Enter Feed Location: ').strip('"') options.feed_filename = raw_input('Enter Feed Location: ').strip('"')
   
default_key_file = GetDefaultKeyFilePath() default_key_file = GetDefaultKeyFilePath()
if not options.key and os.path.isfile(default_key_file): if not options.key and os.path.isfile(default_key_file):
options.key = open(default_key_file).read().strip() options.key = open(default_key_file).read().strip()
   
if options.key and os.path.isfile(options.key): if options.key and os.path.isfile(options.key):
options.key = open(options.key).read().strip() options.key = open(options.key).read().strip()
   
schedule = transitfeed.Schedule(problem_reporter=transitfeed.ProblemReporter()) schedule = transitfeed.Schedule(problem_reporter=transitfeed.ProblemReporter())
print 'Loading data from feed "%s"...' % options.feed_filename print 'Loading data from feed "%s"...' % options.feed_filename
print '(this may take a few minutes for larger cities)' print '(this may take a few minutes for larger cities)'
schedule.Load(options.feed_filename) schedule.Load(options.feed_filename)
   
server = StoppableHTTPServer(server_address=('', options.port), server = StoppableHTTPServer(server_address=('', options.port),
RequestHandlerClass=RequestHandlerClass) RequestHandlerClass=RequestHandlerClass)
server.key = options.key server.key = options.key
server.schedule = schedule server.schedule = schedule
server.file_dir = options.file_dir server.file_dir = options.file_dir
server.host = options.host server.host = options.host
server.feed_path = options.feed_filename server.feed_path = options.feed_filename
   
print ("To view, point your browser at http://localhost:%d/" % print ("To view, point your browser at http://localhost:%d/" %
(server.server_port)) (server.server_port))
server.serve_forever() server.serve_forever()
   
   
if __name__ == '__main__': if __name__ == '__main__':
main() main()
   
  cache/tiles
  cache/map
 
  [InternetShortcut]
  URL=http://gplotter.offwhite.net/
 
  [InternetShortcut]
  URL=http://brennan.offwhite.net/blog/2005/07/23/new-google-maps-icons-free/
 
 Binary files /dev/null and b/busui/staticmaplite/images/markers/Thumbs.db differ
 Binary files /dev/null and b/busui/staticmaplite/images/markers/iconb.png differ
 Binary files /dev/null and b/busui/staticmaplite/images/markers/iconb1.png differ
 Binary files /dev/null and b/busui/staticmaplite/images/markers/iconb10.png differ
 Binary files /dev/null and b/busui/staticmaplite/images/markers/iconb11.png differ
 Binary files /dev/null and b/busui/staticmaplite/images/markers/iconb12.png differ
 Binary files /dev/null and b/busui/staticmaplite/images/markers/iconb13.png differ
 Binary files /dev/null and b/busui/staticmaplite/images/markers/iconb14.png differ
 Binary files /dev/null and b/busui/staticmaplite/images/markers/iconb15.png differ
 Binary files /dev/null and b/busui/staticmaplite/images/markers/iconb16.png differ
 Binary files /dev/null and b/busui/staticmaplite/images/markers/iconb17.png differ
 Binary files /dev/null and b/busui/staticmaplite/images/markers/iconb18.png differ
 Binary files /dev/null and b/busui/staticmaplite/images/markers/iconb19.png differ
 Binary files /dev/null and b/busui/staticmaplite/images/markers/iconb2.png differ
 Binary files /dev/null and b/busui/staticmaplite/images/markers/iconb20.png differ
 Binary files /dev/null and b/busui/staticmaplite/images/markers/iconb21.png differ
 Binary files /dev/null and b/busui/staticmaplite/images/markers/iconb22.png differ
 Binary files /dev/null and b/busui/staticmaplite/images/markers/iconb23.png differ
 Binary files /dev/null and b/busui/staticmaplite/images/markers/iconb24.png differ
 Binary files /dev/null and b/busui/staticmaplite/images/markers/iconb25.png differ
 Binary files /dev/null and b/busui/staticmaplite/images/markers/iconb3.png differ
 Binary files /dev/null and b/busui/staticmaplite/images/markers/iconb4.png differ
 Binary files /dev/null and b/busui/staticmaplite/images/markers/iconb5.png differ
 Binary files /dev/null and b/busui/staticmaplite/images/markers/iconb6.png differ
 Binary files /dev/null and b/busui/staticmaplite/images/markers/iconb7.png differ
 Binary files /dev/null and b/busui/staticmaplite/images/markers/iconb8.png differ
 Binary files /dev/null and b/busui/staticmaplite/images/markers/iconb9.png differ
 Binary files /dev/null and b/busui/staticmaplite/images/markers/icong.png differ
 Binary files /dev/null and b/busui/staticmaplite/images/markers/icong1.png differ
 Binary files /dev/null and b/busui/staticmaplite/images/markers/icong10.png differ
 Binary files /dev/null and b/busui/staticmaplite/images/markers/icong11.png differ
 Binary files /dev/null and b/busui/staticmaplite/images/markers/icong12.png differ
 Binary files /dev/null and b/busui/staticmaplite/images/markers/icong13.png differ
 Binary files /dev/null and b/busui/staticmaplite/images/markers/icong14.png differ
 Binary files /dev/null and b/busui/staticmaplite/images/markers/icong15.png differ
 Binary files /dev/null and b/busui/staticmaplite/images/markers/icong16.png differ
 Binary files /dev/null and b/busui/staticmaplite/images/markers/icong17.png differ
 Binary files /dev/null and b/busui/staticmaplite/images/markers/icong18.png differ
 Binary files /dev/null and b/busui/staticmaplite/images/markers/icong19.png differ
 Binary files /dev/null and b/busui/staticmaplite/images/markers/icong2.png differ
 Binary files /dev/null and b/busui/staticmaplite/images/markers/icong20.png differ
 Binary files /dev/null and b/busui/staticmaplite/images/markers/icong21.png differ
 Binary files /dev/null and b/busui/staticmaplite/images/markers/icong22.png differ
 Binary files /dev/null and b/busui/staticmaplite/images/markers/icong23.png differ
 Binary files /dev/null and b/busui/staticmaplite/images/markers/icong24.png differ
 Binary files /dev/null and b/busui/staticmaplite/images/markers/icong25.png differ
 Binary files /dev/null and b/busui/staticmaplite/images/markers/icong3.png differ
 Binary files /dev/null and b/busui/staticmaplite/images/markers/icong4.png differ
 Binary files /dev/null and b/busui/staticmaplite/images/markers/icong5.png differ
 Binary files /dev/null and b/busui/staticmaplite/images/markers/icong6.png differ
 Binary files /dev/null and b/busui/staticmaplite/images/markers/icong7.png differ
 Binary files /dev/null and b/busui/staticmaplite/images/markers/icong8.png differ
 Binary files /dev/null and b/busui/staticmaplite/images/markers/icong9.png differ
 Binary files /dev/null and b/busui/staticmaplite/images/markers/iconr.png differ
 Binary files /dev/null and b/busui/staticmaplite/images/markers/iconr1.png differ
 Binary files /dev/null and b/busui/staticmaplite/images/markers/iconr10.png differ
 Binary files /dev/null and b/busui/staticmaplite/images/markers/iconr11.png differ
 Binary files /dev/null and b/busui/staticmaplite/images/markers/iconr12.png differ
 Binary files /dev/null and b/busui/staticmaplite/images/markers/iconr13.png differ
 Binary files /dev/null and b/busui/staticmaplite/images/markers/iconr14.png differ
 Binary files /dev/null and b/busui/staticmaplite/images/markers/iconr15.png differ
 Binary files /dev/null and b/busui/staticmaplite/images/markers/iconr16.png differ
 Binary files /dev/null and b/busui/staticmaplite/images/markers/iconr17.png differ
 Binary files /dev/null and b/busui/staticmaplite/images/markers/iconr18.png differ
 Binary files /dev/null and b/busui/staticmaplite/images/markers/iconr19.png differ
 Binary files /dev/null and b/busui/staticmaplite/images/markers/iconr2.png differ
 Binary files /dev/null and b/busui/staticmaplite/images/markers/iconr20.png differ
 Binary files /dev/null and b/busui/staticmaplite/images/markers/iconr21.png differ
 Binary files /dev/null and b/busui/staticmaplite/images/markers/iconr22.png differ
 Binary files /dev/null and b/busui/staticmaplite/images/markers/iconr23.png differ
 Binary files /dev/null and b/busui/staticmaplite/images/markers/iconr24.png differ
 Binary files /dev/null and b/busui/staticmaplite/images/markers/iconr25.png differ
 Binary files /dev/null and b/busui/staticmaplite/images/markers/iconr3.png differ
 Binary files /dev/null and b/busui/staticmaplite/images/markers/iconr4.png differ
 Binary files /dev/null and b/busui/staticmaplite/images/markers/iconr5.png differ
 Binary files /dev/null and b/busui/staticmaplite/images/markers/iconr6.png differ
 Binary files /dev/null and b/busui/staticmaplite/images/markers/iconr7.png differ
 Binary files /dev/null and b/busui/staticmaplite/images/markers/iconr8.png differ
 Binary files /dev/null and b/busui/staticmaplite/images/markers/iconr9.png differ
 Binary files /dev/null and b/busui/staticmaplite/images/markers/icons.psd differ
 Binary files /dev/null and b/busui/staticmaplite/images/markers/lightblue1.png differ
 Binary files /dev/null and b/busui/staticmaplite/images/markers/lightblue2.png differ
 Binary files /dev/null and b/busui/staticmaplite/images/markers/lightblue3.png differ
 Binary files /dev/null and b/busui/staticmaplite/images/markers/lightblue4.png differ
 Binary files /dev/null and b/busui/staticmaplite/images/markers/lightblue5.png differ
 Binary files /dev/null and b/busui/staticmaplite/images/markers/ol-marker-blue.png differ
 Binary files /dev/null and b/busui/staticmaplite/images/markers/ol-marker-gold.png differ
 Binary files /dev/null and b/busui/staticmaplite/images/markers/ol-marker-green.png differ
 Binary files /dev/null and b/busui/staticmaplite/images/markers/ol-marker.png differ
 Binary files /dev/null and b/busui/staticmaplite/images/osm_logo.png differ
  <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
  "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
  <html xmlns="http://www.w3.org/1999/xhtml" xml:lang="de-de" lang="de-de">
  <head>
  <!--
  CSS based on template of Dandelion wiki engine by Radomir Dopieralski who released this
  template under the terms of GNU GPL. http://dandelion.sheep.art.pl/
  -->
  <meta http-equiv="content-type" content="text/html; charset=utf-8" />
  <title>staticMapLite</title>
  <style type="text/css">
  html{font:96% sans-serif;color:#000;background:#f7f7f7;line-height:1.4;}
  body{color:#333;}
  #wrapper{margin:auto;width:60em;position:relative;}
  #header{padding:0px 0px 7px 0px; height: 1em;}
  #header h1 { float:left; width: 40%; }
  #content{background:white;padding:1em;border:1px solid #e0d78a;outline:0.5em solid #fef4a4; margin:0.5em 0;padding:20px;min-height:20em;}
  h1{margin-top:0px;}
  h1,h2,h3,h4,h5,h6{letter-spacing:0.05em;color:#1474CA;font-weight:normal;}
  h1 a:hover,h2 a:hover,h3 a:hover,h4 a:hover,h5 a:hover,h6 a:hover{text-decoration:none;}
  a{color:#1474CA;text-decoration:none;}
  a:visited{color:#1474CA;}
  a.pending{color:#c174a0;}
  a:hover{text-decoration:underline;}
  a img{border:none;}
  input,textarea{font-size:94%;border:1px solid #999;background:#fff;color:#666;outline:0.2em solid #eee;padding:0px;line-height:1.2;margin:0.5em;vertical-align:middle;}
  textarea{display:block;margin:0.5em auto;width:100%;}
  pre{outline:0.4em solid #eee;padding:0.5em;margin:0.5em;border:1px solid #e0d78a;background:#fef4a4;color:#644e22;}
  img{border:1px solid #ccc;outline:0.25em solid #eee;padding:0.25em;margin:0.25em 0 0.25em 0.5em;background:#fff;}
  hr{height:0;border:none;color:#fff;background:transparent;border-bottom:1px solid #ccc; margin:0.5em 0;}
  #diff {outline:none;border:none;}
  #diff ins{color:green;text-decoration:none;font-weight:bold;}
  #diff del{color:red;text-decoration:line-through;}
  #diff{background:#fff;line-height:1.25;padding:1em;white-space:pre-wrap;word-wrap:break-word; white-space:-moz-pre-wrap;white-space:-pre-wrap;white-space:-o-pre-wrap;width:97%;}
  hr{margin:10px 0 10px 0;height:0px;overflow:hidden;border:0px;border-top:2px solid #ccc;}
  .error{color:#F25A5A;font-weight:bold;}
  form{display:inline;}
  #contentTextarea{height:44em;}
  #toc{margin:5px 0 5px 10px;padding:6px 5px 7px 0px;float:right;list-style:none;outline:0.4em solid #eee;background:#fef4a4;border:1px solid #e0d78a;}
  #toc ul{list-style:none;padding:3px 0 3px 10px;}
  #toc li{font-size:11px;padding-left:10px;}
  #toc ul li{font-size:10px;}
  #toc ul ul li{font-size:9px;}
  #toc ul ul ul li{font-size:8px;}
  #toc ul ul ul ul li{font-size:7px;}
  .pageVersionsList{letter-spacing:0px;font-variant:normal;font-size:12px;}
  #renameForm{float:left;}
  .clear{clear:both;}
  .tagList{padding:0.2em 0.4em 0.2em 0.4em;margin-top:0.5em;border:1px dashed #e0d78a;background:#fef4a4;color:#644e22;}
  .tagCloud{float:right;width:200px;padding:0.5em;margin:1em;border:1px dashed #e0d78a;background:#fef4a4;color:#644e22;}
  #fileTable{border-collapse:collapse;}
  #fileTable td{border:1px solid #FEF4A4;padding:2px 6px 2px 6px;}
  h2 span.par-edit, h3 span.par-edit, h4 span.par-edit, h5 span.par-edit, h6 span.par-edit {display:none;}
  h2:hover span.par-edit, h3:hover span.par-edit, h4:hover span.par-edit, h5:hover span.par-edit, h6:hover span.par-edit {display:inline;font-size:x-small;}
  .comment-item { border:1px solid #999;color:#666;outline:0.2em solid #eee; }
  .resizeTextareaDiv { margin-top: 5px;}
  a.toolbarTextareaItem { padding-right: 10px; }
  a.external:after { content: "\2197";}
  </style>
  </head>
 
  <body>
  <div id="wrapper">
  <div id="header">
  </div>
  <div id="content">
 
  <div class="par-div">
  <h2>
  staticMapLite - simple map for your website
  </h2>
  <p>
  <img src="staticmap.php?center=40.714728,-73.998672&zoom=14&size=865x512&maptype=mapnik" width="865" height="512" /></p>
  <p>
  This image was created using the following simple &lt;img> tag:
  <pre>&lt;img src="staticmap.php?center=40.714728,-73.998672&amp;zoom=14&amp;size=865x512&amp;maptype=mapnik" /&gt;</pre>
  </p>
  </div>
  <hr />
  <div class="par-div">
  <h3>
  Place Markers
  </h3>
 
  <p>
  <img src="staticmap.php?center=40.714728,-73.998672&zoom=14&size=865x512&maptype=mapnik&markers=40.702147,-74.015794,lightblue1|40.711614,-74.012318,lightblue2|40.718217,-73.998284,lightblue3" width="865" height="512" />
  </p><p> Add markers by appending them to the image URL:
  <pre>markers=40.702147,-74.015794,lightblue1|40.711614,-74.012318,lightblue2|40.718217,-73.998284,lightblue3</pre>
  </p>
  </div>
  <hr />
  <div class="par-div">
  <h3>
  Use Different Map Styles (Tile Sources)
  </h3>
 
  <p>
  <div style="float:left; margin-right: 10px">
  <img src="staticmap.php?center=40.714728,-73.998672&zoom=14&size=256x256&maptype=mapnik" width="256" height="256" />
  <pre>maptype=mapnik</pre>
  </div>
  <div style="float:left; margin-right: 10px">
  <img src="staticmap.php?center=40.714728,-73.998672&zoom=14&size=256x256&maptype=osmarenderer" width="256" height="256" />
  <pre>maptype=osmarenderer</pre>
  </div>
  <div style="float:left; margin-right: 10px">
  <img src="staticmap.php?center=40.714728,-73.998672&zoom=14&size=256x256&maptype=cycle" width="256" height="256" />
  <pre>maptype=cycle</pre>
  </div>
  <br style="clear:both" />
  </p>
  </div>
 
  </div>
  <div id="footer">
  <div style="text-align:center;padding:7px;color:#ccc">
  sponsored by <a href="http://dfacts.de">dFacts Network</a>
  </div>
  </div>
  </div>
  </body>
  </html>
  chcon -R -t httpd_sys_content_rw_t cache
 
 
  <?php
 
  /**
  * staticMapLite 0.02
  *
  * Copyright 2009 Gerhard Koch
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
  * You may obtain a copy of the License at
  *
  * http://www.apache.org/licenses/LICENSE-2.0
  *
  * Unless required by applicable law or agreed to in writing, software
  * distributed under the License is distributed on an "AS IS" BASIS,
  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  * See the License for the specific language governing permissions and
  * limitations under the License.
  *
  * @author Gerhard Koch <gerhard.koch AT ymail.com>
  *
  * USAGE:
  *
  * staticmap.php?center=40.714728,-73.998672&zoom=14&size=512x512&maptype=mapnik&markers=40.702147,-74.015794,blues|40.711614,-74.012318,greeng|40.718217,-73.998284,redc
  *
  */
 
  error_reporting(0);
  ini_set('display_errors','off');
 
  Class staticMapLite {
 
  protected $tileSize = 256;
  protected $tileSrcUrl = array( 'mapnik' => 'http://tile.openstreetmap.org/{Z}/{X}/{Y}.png',
  'osmarenderer' => 'http://c.tah.openstreetmap.org/Tiles/tile/{Z}/{X}/{Y}.png',
  'cycle' => 'http://c.andy.sandbox.cloudmade.com/tiles/cycle/{Z}/{X}/{Y}.png'
  );
 
  protected $tileDefaultSrc = 'mapnik';
  protected $markerBaseDir = 'images/markers';
  protected $osmLogo = 'images/osm_logo.png';
 
  protected $useTileCache = true;
  protected $tileCacheBaseDir = './cache/tiles';
 
  protected $useMapCache = true;
  protected $mapCacheBaseDir = './cache/maps';
  protected $mapCacheID = '';
  protected $mapCacheFile = '';
  protected $mapCacheExtension = 'png';
 
  protected $zoom, $lat, $lon, $width, $height, $markers, $image, $maptype;
  protected $centerX, $centerY, $offsetX, $offsetY;
 
  public function __construct(){
  $this->zoom = 0;
  $this->lat = 0;
  $this->lon = 0;
  $this->width = 500;
  $this->height = 350;
  $this->markers = array();
  $this->maptype = $this->tileDefaultSrc;
  }
 
  public function parseParams(){
  global $_GET;
 
  // get zoom from GET paramter
  $this->zoom = $_GET['zoom']?intval($_GET['zoom']):0;
  if($this->zoom>18)$this->zoom = 18;
 
  // get lat and lon from GET paramter
  list($this->lat,$this->lon) = split(',',$_GET['center']);
  $this->lat = floatval($this->lat);
  $this->lon = floatval($this->lon);
 
  // get zoom from GET paramter
  if($_GET['size']){
  list($this->width, $this->height) = split('x',$_GET['size']);
  $this->width = intval($this->width);
  $this->height = intval($this->height);
  }
  if($_GET['markers']){
  $markers = split('%7C|\|',$_GET['markers']);
  foreach($markers as $marker){
  list($markerLat, $markerLon, $markerImage) = split(',',$marker);
  $markerLat = floatval($markerLat);
  $markerLon = floatval($markerLon);
  $markerImage = basename($markerImage);
  $this->markers[] = array('lat'=>$markerLat, 'lon'=>$markerLon, 'image'=>$markerImage);
  }
 
  }
  if($_GET['maptype']){
  if(array_key_exists($_GET['maptype'],$this->tileSrcUrl)) $this->maptype = $_GET['maptype'];
  }
  }
 
  public function lonToTile($long, $zoom){
  return (($long + 180) / 360) * pow(2, $zoom);
  }
 
  public function latToTile($lat, $zoom){
  return (1 - log(tan($lat * pi()/180) + 1 / cos($lat* pi()/180)) / pi()) /2 * pow(2, $zoom);
  }
 
  public function initCoords(){
  $this->centerX = $this->lonToTile($this->lon, $this->zoom);
  $this->centerY = $this->latToTile($this->lat, $this->zoom);
  $this->offsetX = floor((floor($this->centerX)-$this->centerX)*$this->tileSize);
  $this->offsetY = floor((floor($this->centerY)-$this->centerY)*$this->tileSize);
  }
 
  public function createBaseMap(){
  $this->image = imagecreatetruecolor($this->width, $this->height);
  $startX = floor($this->centerX-($this->width/$this->tileSize)/2);
  $startY = floor($this->centerY-($this->height/$this->tileSize)/2);
  $endX = ceil($this->centerX+($this->width/$this->tileSize)/2);
  $endY = ceil($this->centerY+($this->height/$this->tileSize)/2);
  $this->offsetX = -floor(($this->centerX-floor($this->centerX))*$this->tileSize);
  $this->offsetY = -floor(($this->centerY-floor($this->centerY))*$this->tileSize);
  $this->offsetX += floor($this->width/2);
  $this->offsetY += floor($this->height/2);
  $this->offsetX += floor($startX-floor($this->centerX))*$this->tileSize;
  $this->offsetY += floor($startY-floor($this->centerY))*$this->tileSize;
 
  for($x=$startX; $x<=$endX; $x++){
  for($y=$startY; $y<=$endY; $y++){
  $url = str_replace(array('{Z}','{X}','{Y}'),array($this->zoom, $x, $y), $this->tileSrcUrl[$this->maptype]);
  $tileImage = imagecreatefromstring($this->fetchTile($url));
  $destX = ($x-$startX)*$this->tileSize+$this->offsetX;
  $destY = ($y-$startY)*$this->tileSize+$this->offsetY;
  imagecopy($this->image, $tileImage, $destX, $destY, 0, 0, $this->tileSize, $this->tileSize);
  }
  }
  }
 
 
  public function placeMarkers(){
  foreach($this->markers as $marker){
  $markerLat = $marker['lat'];
  $markerLon = $marker['lon'];
  $markerImage = $marker['image'];
  $markerIndex++;
  $markerFilename = $markerImage?(file_exists($this->markerBaseDir.'/'.$markerImage.".png")?$markerImage:'lightblue'.$markerIndex):'lightblue'.$markerIndex;
  if(file_exists($this->markerBaseDir.'/'.$markerFilename.".png")){
  $markerImg = imagecreatefrompng($this->markerBaseDir.'/'.$markerFilename.".png");
  } else {
  $markerImg = imagecreatefrompng($this->markerBaseDir.'/lightblue1.png');
  }
  $destX = floor(($this->width/2)-$this->tileSize*($this->centerX-$this->lonToTile($markerLon, $this->zoom)));
  $destY = floor(($this->height/2)-$this->tileSize*($this->centerY-$this->latToTile($markerLat, $this->zoom)));
  $destY = $destY - imagesy($markerImg);
 
  imagecopy($this->image, $markerImg, $destX, $destY, 0, 0, imagesx($markerImg), imagesy($markerImg));
 
  };
  }
 
 
 
  public function tileUrlToFilename($url){
  return $this->tileCacheBaseDir."/".str_replace(array('http://'),'',$url);
  }
 
  public function checkTileCache($url){
  $filename = $this->tileUrlToFilename($url);
  if(file_exists($filename)){
  return file_get_contents($filename);
  }
  }
 
  public function checkMapCache(){
  $this->mapCacheID = md5($this->serializeParams());
  $filename = $this->mapCacheIDToFilename();
  if(file_exists($filename)) return true;
  }
 
  public function serializeParams(){
  return join("&",array($this->zoom,$this->lat,$this->lon,$this->width,$this->height, serialize($this->markers),$this->maptype));
  }
 
  public function mapCacheIDToFilename(){
  if(!$this->mapCacheFile){
  $this->mapCacheFile = $this->mapCacheBaseDir."/".substr($this->mapCacheID,0,2)."/".substr($this->mapCacheID,2,2)."/".substr($this->mapCacheID,4);
  }
  return $this->mapCacheFile.".".$this->mapCacheExtension;
  }
 
 
 
  public function mkdir_recursive($pathname, $mode){
  return mkdir($pathname, $mode, true);
  }
  public function writeTileToCache($url, $data){
  $filename = $this->tileUrlToFilename($url);
  $this->mkdir_recursive(dirname($filename),0777);
  file_put_contents($filename, $data);
  }
 
  public function fetchTile($url){
  if($this->useTileCache && ($cached = $this->checkTileCache($url))) return $cached;
  $ch = curl_init();
  curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
  curl_setopt($ch, CURLOPT_USERAGENT, "Mozilla/4.0");
  curl_setopt($ch, CURLOPT_URL, $url);
  $tile = curl_exec($ch);
  curl_close($ch);
  if($this->useTileCache){
  $this->writeTileToCache($url,$tile);
  }
  return $tile;
 
  }
 
  public function copyrightNotice(){
  $logoImg = imagecreatefrompng($this->osmLogo);
  imagecopy($this->image, $logoImg, imagesx($this->image)-imagesx($logoImg), imagesy($this->image)-imagesy($logoImg), 0, 0, imagesx($logoImg), imagesy($logoImg));
 
  }
 
  public function sendHeader(){
  header('Content-Type: image/png');
  $expires = 60*60*24*14;
  header("Pragma: public");
  header("Cache-Control: maxage=".$expires);
  header('Expires: ' . gmdate('D, d M Y H:i:s', time()+$expires) . ' GMT');
  }
 
  public function makeMap(){
  $this->initCoords();
  $this->createBaseMap();
  if(count($this->markers))$this->placeMarkers();
  if($this->osmLogo) $this->copyrightNotice();
  }
 
  public function showMap(){
  $this->parseParams();
  if($this->useMapCache){
  // use map cache, so check cache for map
  if(!$this->checkMapCache()){
  // map is not in cache, needs to be build
  $this->makeMap();
  $this->mkdir_recursive(dirname($this->mapCacheIDToFilename()),0777);
  imagepng($this->image,$this->mapCacheIDToFilename(),9);
  $this->sendHeader();
  if(file_exists($this->mapCacheIDToFilename())){
  return file_get_contents($this->mapCacheIDToFilename());
  } else {
  return imagepng($this->image);
  }
  } else {
  // map is in cache
  $this->sendHeader();
  return file_get_contents($this->mapCacheIDToFilename());
  }
 
  } else {
  // no cache, make map, send headers and deliver png
  $this->makeMap();
  $this->sendHeader();
  return imagepng($this->image);
 
  }
  }
 
  }
 
  $map = new staticMapLite();
  print $map->showMap();
 
  ?>
 
<?php <?php
include('common.inc.php'); include('common.inc.php');
$url = $APIurl."/json/stop?stop_id=".$_REQUEST['stopid']; $url = $APIurl."/json/stop?stop_id=".$_REQUEST['stopid'];
$stop = json_decode(getPage($url)); $stop = json_decode(getPage($url));
   
include_header("Trips passing ".$stop[1]); include_header("Trips passing ".$stop[1]);
echo '<div data-role="content" class="ui-content" role="main"><p>'.staticmap(Array(0 => Array($stop[2],$stop[3]))).'</p>'; echo '<div data-role="content" class="ui-content" role="main"><p>'.staticmap(Array(0 => Array($stop[2],$stop[3]))).'</p>';
echo ' <ul data-role="listview" >'; echo ' <ul data-role="listview" >';
$url = $APIurl."/json/stoptrips?stop=".$_REQUEST['stopid']."&time=".midnight_seconds()."&service_period=".service_period(); $url = $APIurl."/json/stoptrips?stop=".$_REQUEST['stopid']."&time=".midnight_seconds()."&service_period=".service_period();
$trips = json_decode(getPage($url)); $trips = json_decode(getPage($url));
  debug(print_r($trips,true));
foreach ($trips as $row) foreach ($trips as $row)
{ {
echo '<li>'; echo '<li>';
echo '<h3><a href="trip.php?stopid='.$_REQUEST['stopid'].'&tripid='.$row[1][0].'">'.$row[1][1].'</a></h3>'; echo '<h3><a href="trip.php?stopid='.$_REQUEST['stopid'].'&tripid='.$row[1][0].'">'.$row[1][1].'</a></h3>';
echo '<p class="ui-li-aside"><strong>'.midnight_seconds_to_time($row[0]).'</strong></p>'; echo '<p class="ui-li-aside"><strong>'.midnight_seconds_to_time($row[0]).'</strong></p>';
echo '</li>'; echo '</li>';
} }
if (sizeof($trips) == 0) echo "<li> <center>No trips in the near future.</center> </li>"; if (sizeof($trips) == 0) echo "<li> <center>No trips in the near future.</center> </li>";
echo '</ul></div>'; echo '</ul></div>';
include_footer(); include_footer();
?> ?>
   
<?php <?php
include('common.inc.php'); include('common.inc.php');
include_header("Stops"); include_header("Stops");
  echo'
  <div data-role="navbar">
  <ul>
  <li><a href="stopList.php" class="ui-btn-active">Timing Points</a></li>
  <li><a href="stopList.php?allstops=yes">All Stops</a></li>
  </ul>
  </div>
  ';
echo ' <ul data-role="listview" data-filter="true">'; echo ' <ul data-role="listview" data-filter="true">';
$url = $APIurl."/json/timingpoints"; $url = $APIurl."/json/timingpoints";
if ($_REQUEST['allstops']) $url = $APIurl."/json/stops"; if ($_REQUEST['allstops']) $url = $APIurl."/json/stops";
if ($_REQUEST['lat'] && $_REQUEST['lon']) $url = $APIurl."/json/neareststops?lat={$_REQUEST['lat']}&lon={$_REQUEST['lon']}&limit=15"; if ($_REQUEST['lat'] && $_REQUEST['lon']) $url = $APIurl."/json/neareststops?lat={$_REQUEST['lat']}&lon={$_REQUEST['lon']}&limit=15";
$contents = json_decode(getPage($url)); $contents = json_decode(getPage($url));
  debug(print_r($contents,true));
foreach ($contents as $key => $row) { foreach ($contents as $key => $row) {
$stopName[$key] = $row[1]; $stopName[$key] = $row[1];
} }
   
// Sort the data with volume descending, edition ascending // Sort the data with volume descending, edition ascending
// Add $data as the last parameter, to sort by the common key // Add $data as the last parameter, to sort by the common key
array_multisort($stopName, SORT_ASC, $contents); array_multisort($stopName, SORT_ASC, $contents);
   
foreach ($contents as $row) foreach ($contents as $row)
{ {
   
echo '<li><a href="stop.php?stopid='.$row[0].'">'.$row[1].'</a></li>'; echo '<li><a href="stop.php?stopid='.$row[0].'">'.$row[1].'</a></li>';
} }
echo '</ul>'; echo '</ul>';
echo'  
<div data-role="footer" data-id="foo1" data-position="fixed">  
<div data-role="navbar">  
<ul>  
<li><a href="stopList.php" class="ui-btn-active">Timing Points</a></li>  
<li><a href="stopList.php?allstops=yes">All Stops</a></li>  
</ul>  
';  
include_footer(); include_footer();
?> ?>
   
   
<?php <?php
include('common.inc.php'); include('common.inc.php');
$tripid = $_REQUEST['tripid']; $tripid = $_REQUEST['tripid'];
if ($_REQUEST['routeid']) { if ($_REQUEST['routeid']) {
$url = $APIurl."/json/routetrips?route_id=".$_REQUEST['routeid']; $url = $APIurl."/json/routetrips?route_id=".$_REQUEST['routeid'];
$trips = json_decode(getPage($url)); $trips = json_decode(getPage($url));
  debug(print_r($trips,true));
foreach ($trips as $trip) foreach ($trips as $trip)
{ {
if ($trip[0] < midnight_seconds()) { if ($trip[0] < midnight_seconds()) {
$tripid = $trip[1]; $tripid = $trip[1];
break; break;
} }
} }
if (!($tripid > 0)) $tripid = $trips[0][1]; if (!($tripid > 0)) $tripid = $trips[0][1];
} }
$url = $APIurl."/json/triprows?trip=".$tripid; $url = $APIurl."/json/triprows?trip=".$tripid;
$trips = array_flatten(json_decode(getPage($url))); $trips = array_flatten(json_decode(getPage($url)));
  debug(print_r($trips,true));
include_header("Stops on ". $trips[1]->route_short_name . ' '. $trips[1]->route_long_name); include_header("Stops on ". $trips[1]->route_short_name . ' '. $trips[1]->route_long_name);
echo ' <ul data-role="listview" >'; echo ' <ul data-role="listview" >';
   
   
$url = $APIurl."/json/tripstoptimes?trip=".$tripid; $url = $APIurl."/json/tripstoptimes?trip=".$tripid;
   
$json = json_decode(getPage($url)); $json = json_decode(getPage($url));
  debug(print_r($json,true));
$stops = $json[0]; $stops = $json[0];
$times = $json[1]; $times = $json[1];
foreach ($stops as $key => $row) foreach ($stops as $key => $row)
{ {
echo '<li>'; echo '<li>';
echo '<h3><a href="stop.php?stopid='.$row[0].'">'.$row[1].'</a></h3>'; echo '<h3><a href="stop.php?stopid='.$row[0].'">'.$row[1].'</a></h3>';
echo '<p class="ui-li-aside">'.midnight_seconds_to_time($times[$key]).'</p>'; echo '<p class="ui-li-aside">'.midnight_seconds_to_time($times[$key]).'</p>';
echo '</li>'; echo '</li>';
} }
echo '</ul>'; echo '</ul>';
include_footer(); include_footer();
?> ?>
   
  <?php
  include('common.inc.php');
  include_header("Trip Planner", true, true);
  function tripPlanForm($errorMessage = "")
  {
  $from = (isset($_REQUEST['from']) ? $_REQUEST['from'] : "Brigalow");
  $to = (isset($_REQUEST['to']) ? $_REQUEST['to'] : "Barry");
  $date = (isset($_REQUEST['date']) ? $_REQUEST['date'] : date("m/d/Y"));
  $time = (isset($_REQUEST['time']) ? $_REQUEST['time'] : date("h:ia"));
  echo "<font color=red>$errorMessage</font>";
  echo '<form action="tripPlanner.php" method="post">
  <div data-role="fieldcontain">
  <label for="from">I would like to go from</label>
  <input type="text" name="from" id="from" value="' . $from . '" />
  <a href="#" style="display:none" name="fromHere" id="fromHere"/>Here?</a>
  </div>
  <div data-role="fieldcontain">
  <label for="to"> to </label>
  <input type="text" name="to" id="to" value="' . $to . '" />
  <a href="#" style="display:none" name="toHere" id="toHere"/>Here?</a>
  </div>
  <div data-role="fieldcontain">
  <label for="date"> on </label>
  <input type="date" name="date" id="date" value="' . $date . '" />
  </div>
  <div data-role="fieldcontain">
  <label for="time"> at </label>
  <input type="time" name="time" id="time" value="' . $time . '" />
  </div>
  <input type="submit" value="Go!"></form>';
  echo "<script>
  $('#toHere').click(function(event) { $('#to').val(getCookie('geolocate')); return false;});
  $('#toHere').show();
  $('#fromHere').click(function(event) { $('#from').val(getCookie('geolocate')); return false;});
  $('#fromHere').show();
 
  </script>";
  }
 
  function processItinerary($itineraryNumber, $itinerary)
  {
  echo '<div data-role="collapsible" ' . ($itineraryNumber > 0 ? 'data-collapsed="true"' : "") . '> <h3> Option #' . ($itineraryNumber + 1) . ": " . floor($itinerary->duration / 60000) . " minutes ({$itinerary->startTime} to {$itinerary->endTime})</h3><p>";
  echo "Walking time: " . floor($itinerary->walkTime / 60000) . " minutes (" . floor($itinerary->walkDistance) . " meters)<br>\n";
  echo "Transit time: " . floor($itinerary->transitTime / 60000) . " minutes<br>\n";
  echo "Waiting time: " . floor($itinerary->waitingTime / 60000) . " minutes<br>\n";
 
 
 
  if (is_array($itinerary->legs->leg)) {
  $legMarkers = array();
  foreach ($itinerary->legs->leg as $legNumber => $leg) {
  $legMarkers[] = array($leg->from->lat, $leg->from->lon);
  }
  echo '' . staticmap($legMarkers) . "<br>\n";
  echo '<ul>';
  foreach ($itinerary->legs->leg as $legNumber => $leg) {
  echo '<li>';
  processLeg($legNumber, $leg);
  echo "</li>";
  }
  echo "</ul>";
  } else {
  echo '' . staticmap(array(array($itinerary->legs->leg->from->lat, $itinerary->legs->leg->from->lon))) . "<br>\n";
  processLeg(0, $itinerary->legs->leg);
  }
 
  echo "</p></div>";
  }
 
  function processLeg($legNumber, $leg) {
  $legArray = object2array($leg);
  echo '<h3>Leg #' . ($legNumber + 1) . " ( {$legArray['@mode']} from: {$leg->from->name} to {$leg->to->name}, " . floor($leg->duration / 60000) . " minutes) </h3>\n";
  if ($legArray["@mode"] === "BUS") {
  echo "Take bus {$legArray['@route']} " . str_replace("To", "towards", $legArray['@headsign']) . "<br>";
  } else {
  $walkStepMarkers = array();
  foreach ($leg->steps->walkSteps as $stepNumber => $step) {
  $walkStepMarkers[] = array($step->lat, $step->lon);
  }
  echo "" . staticmap($walkStepMarkers, "icong") . "<br>\n";
  foreach ($leg->steps->walkSteps as $stepNumber => $step) {
  echo "Walking step " . ($stepNumber + 1) . " $step->absoluteDirection / $step->relativeDirection on $step->streetName for " . floor($step->distance) . " meters<br>\n";
  }
  }
  }
 
  if ($_REQUEST['time']) {
  $toPlace = (startsWith($_REQUEST['to'], "-") ? $_REQUEST['to'] : geocode(urlencode($_REQUEST['to']), false));
  $fromPlace = (startsWith($_REQUEST['from'], "-") ? $_REQUEST['from'] : geocode(urlencode($_REQUEST['from']), false));
  if ($toPlace == "" || $fromPlace == "") {
  $errorMessage = "";
  if ($toPlace === "")
  $errorMessage .= urlencode($_REQUEST['to']) . " not found.<br>\n";
  if ($fromPlace === "")
  $errorMessage .= urlencode($_REQUEST['from']) . " not found.<br>\n";
  tripPlanForm($errorMessage);
  } else {
  $url = "http://localhost:8080/opentripplanner-api-webapp/ws/plan?_dc=1290254798856&arriveBy=false&date=" . urlencode($_REQUEST['date']) . "&time=" . urlencode($_REQUEST['time']) . "&mode=TRANSIT%2CWALK&optimize=QUICK&maxWalkDistance=840&wheelchair=false&toPlace=$toPlace&fromPlace=$fromPlace&intermediatePlaces=";
  $ch = curl_init($url);
  curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
  curl_setopt($ch, CURLOPT_HEADER, 0);
  curl_setopt($ch, CURLOPT_HTTPHEADER, array("Accept: application/json"));
  $page = curl_exec($ch);
  curl_close($ch);
  $tripplan = json_decode($page);
  debug(print_r($triplan,true));
  echo "<h1> From: {$tripplan->plan->from->name} To: {$tripplan->plan->to->name} </h1>";
  echo "<h1> At: {$tripplan->plan->date} </h1>";
 
  if (is_array($tripplan->plan->itineraries->itinerary)) {
  echo '<div data-role="collapsible-set">';
  foreach ($tripplan->plan->itineraries->itinerary as $itineraryNumber => $itinerary) {
  processItinerary($itineraryNumber, $itinerary);
  }
  echo "</div>";
  } else {
  processItinerary(0, $tripplan->plan->itineraries->itinerary);
  }
 
  }
  } else {
  tripPlanForm();
  }
  include_footer();
  ?>
require 'rubygems' require 'rubygems'
require 'nokogiri' require 'nokogiri'
require 'open-uri' require 'open-uri'
require 'pp' require 'pp'
require 'yaml' require 'yaml'
class Array class Array
def to_yaml_style def to_yaml_style
:inline :inline
end end
end end
   
   
def makeTimetable(table, period, short_name) def makeTimetable(table, period, short_name)
timetable = {"between_stops" => [], "short_name" => short_name} timetable = {"between_stops" => [], "short_name" => short_name}
time_points = table.xpath('tr[1]//th').map do |tp| time_points = table.xpath('tr[1]//th').map do |tp|
if tp.content != "\302\240" && tp.content != "" && tp.content != "<br/>" if tp.content != "\302\240" && tp.content != "" && tp.content != "<br/>"
timing_point = tp.content.squeeze(" ").gsub("Bus Station"," Bus Station ").gsub(" Platform"," (Platform").gsub(" - "," - ").gsub("\n"," ").gsub("\r"," ").gsub("\t"," ").gsub("\\"," / ").gsub("/"," / ").gsub(","," ").gsub("\302\240","").squeeze(" ").strip timing_point = tp.content.squeeze(" ").gsub("Shops"," ").gsub("Bus Station"," Bus Station ").gsub(" Platform"," (Platform").gsub(" - "," - ").gsub("\n"," ").gsub("\r"," ").gsub("\t"," ").gsub("\\"," / ").gsub("/"," / ").gsub(","," ").gsub("\302\240","").squeeze(" ").strip
if (tp.content.match('Platform')) if (tp.content.match('Platform'))
timing_point.concat(")") timing_point.concat(")")
end; end;
if tp.to_s.match(/[0-9][0-9][0-9]/) or tp.to_s.include? "Wheelchair" if tp.to_s.match(/[0-9][0-9][0-9]/) or tp.to_s.include? "Wheelchair"
timing_point = nil timing_point = nil
end end
timing_point timing_point
end end
end end
time_points.delete(nil) time_points.delete(nil)
timetable["time_points"] = time_points.to_a timetable["time_points"] = time_points.to_a
timetable["long_name"] = "To " + time_points.last timetable["long_name"] = "To " + time_points.last
periodtimes = [] periodtimes = []
table.css('tr').each do |row| table.css('tr').each do |row|
times = row.css('td').map do |cell| times = row.css('td').map do |cell|
time = cell.content.squeeze(" ").strip time = cell.content.squeeze(" ").strip
time = time.gsub(/ *A\S?M/,"a").gsub(/ ?P\S?M/,"p").gsub(/ *a\S?m/,"a").gsub(/ ?p\S?m/,"p") time = time.gsub(/ *A\S?M/,"a").gsub(/ ?P\S?M/,"p").gsub(/ *a\S?m/,"a").gsub(/ ?p\S?m/,"p")
time = time.gsub("12:08 AM","1208x").gsub(":","").gsub("1.","1").gsub("2.","2").gsub("3.","3").gsub("4.","4") time = time.gsub("12:08 AM","1208x").gsub(":","").gsub("1.","1").gsub("2.","2").gsub("3.","3").gsub("4.","4")
time = time.gsub("5.","5").gsub("6.","6").gsub("7.","7").gsub("8.","8").gsub("9.","9").gsub("10.","10") time = time.gsub("5.","5").gsub("6.","6").gsub("7.","7").gsub("8.","8").gsub("9.","9").gsub("10.","10")
time = time.gsub("11.","11").gsub("12.","12").gsub(/\.+/,"-").gsub("\302\240","") time = time.gsub("11.","11").gsub("12.","12").gsub(/\.+/,"-").gsub("\302\240","")
if time == "" or time.include? "chool" or time.include? "On Race Days" or time.include? "Bus" if time == "" or time.include? "chool" or time.include? "On Race Days" or time.include? "Bus"
time = nil # This hacky way is faster than using position()>1 xpath on <TD>s! time = nil # This hacky way is faster than using position()>1 xpath on <TD>s!
end end
time time
end end
times.delete(nil) times.delete(nil)
if not times.empty? if not times.empty?
if not (route = times.shift) if not (route = times.shift)
raise("TODO: account for shifting route numbers eg. intertown/redex 62/162") raise("TODO: account for shifting route numbers eg. intertown/redex 62/162")
end end
periodtimes << times.to_a periodtimes << times.to_a
end end
end end
if periodtimes.size < 1 if periodtimes.size < 1
raise "No times for route " + short_name + " in period " + period raise "No times for route " + short_name + " in period " + period
end end
timetable[period] = periodtimes.to_a timetable[period] = periodtimes.to_a
# pp timetable # pp timetable
filename = timetable["short_name"] + "-" + timetable["long_name"]+ "." + period + ".yml" filename = timetable["short_name"] + "-" + timetable["long_name"]+ "." + period + ".yml"
filename = filename.downcase.gsub(" ","-").gsub("/","-").gsub("(","").gsub(")","") filename = filename.downcase.gsub(" ","-").gsub("/","-").gsub("(","").gsub(")","")
puts "Saving " + filename puts "Saving " + filename
File.open("#{File.dirname(__FILE__)}/output/"+filename, "w") do |f| File.open("#{File.dirname(__FILE__)}/output/"+filename, "w") do |f|
f.write timetable.to_yaml f.write timetable.to_yaml
end end
timetable timetable
end end
   
Dir.glob("source-html/*oute*.htm*") { |file| Dir.glob("source-html/*oute*.htm*") { |file|
puts "Opened " + file puts "Opened " + file
doc = Nokogiri::HTML(open(file)) doc = Nokogiri::HTML(open(file))
# Search for nodes by css # Search for nodes by css
timetables = [] timetables = []
short_name = ""; short_name = "";
doc.xpath('//title').each do |title| doc.xpath('//title').each do |title|
short_name = title.content.gsub("Route_","").gsub("Route ","").gsub("route ","").gsub(", ","/").gsub("ACTION Buses Timetable for ","").squeeze(" ").strip short_name = title.content.gsub("Route_","").gsub("Route ","").gsub("route ","").gsub(", ","/").gsub("ACTION Buses Timetable for ","").squeeze(" ").strip
end end
if short_name == "" if short_name == ""
raise "Route number(s) not found in <title> tag" raise "Route number(s) not found in <title> tag"
end end
   
doc.xpath('//table[preceding::text()="Weekdays"]').each do |table| doc.xpath('//table[preceding::text()="Weekdays"]').each do |table|
timetables << makeTimetable(table, "stop_times", short_name) timetables << makeTimetable(table, "stop_times", short_name)
end end
doc.xpath('//table[preceding::text()="This timetable is effective from Monday 15th November 2010."]').each do |table| doc.xpath('//table[preceding::text()="This timetable is effective from Monday 15th November 2010."]').each do |table|
timetables << makeTimetable(table, "stop_times", short_name) if short_name[0].chr != "9" or short_name.size == 1
  timetables << makeTimetable(table, "stop_times", short_name)
  end
end end
#all tables are weekdays on some really malformatted timetables #all tables are weekdays on some really malformatted timetables
if short_name == "170" if short_name == "170"
doc.xpath('//table').each do |table| doc.xpath('//table').each do |table|
timetables << makeTimetable(table, "stop_times", short_name) timetables << makeTimetable(table, "stop_times", short_name)
end end
end end
#weekends #weekends
doc.xpath('//table[preceding::text()="Saturdays" and following::a]').each do |table| doc.xpath('//table[preceding::text()="Saturdays" and following::a]').each do |table|
timetables << makeTimetable(table, "stop_times_saturday", short_name) timetables << makeTimetable(table, "stop_times_saturday", short_name)
end end
doc.xpath('//table[preceding::text()="Sundays"]').each do |table| doc.xpath('//table[preceding::text()="Sundays"]').each do |table|
timetables << makeTimetable(table, "stop_times_sunday", short_name) timetables << makeTimetable(table, "stop_times_sunday", short_name)
end end
#930/934 special cases #930/934 special cases
doc.xpath('//table[preceding::text()="Saturday" and following::h2]').each do |table| doc.xpath('//table[preceding::text()="Saturday" and following::h2]').each do |table|
timetables << makeTimetable(table, "stop_times_saturday", short_name) timetables << makeTimetable(table, "stop_times_saturday", short_name)
end end
doc.xpath('//table[preceding::text()="Sunday"]').each do |table| doc.xpath('//table[preceding::text()="Sunday"]').each do |table|
timetables << makeTimetable(table, "stop_times_sunday", short_name) timetables << makeTimetable(table, "stop_times_sunday", short_name)
end end
#route 81 = Weekdays - School Holidays Only #route 81 = Weekdays - School Holidays Only
doc.xpath('//table[preceding::text()="Weekdays - School Holidays Only "]').each do |table| doc.xpath('//table[preceding::text()="Weekdays - School Holidays Only "]').each do |table|
timetable = makeTimetable(table, "stop_times", short_name) timetable = makeTimetable(table, "stop_times", short_name)
#TODO set active date range to only be holidays #TODO set active date range to only be holidays
timetables << timetable; timetables << timetable;
end end
   
if timetables.size > 2 if timetables.size > 2
puts "WARNING: " + file + " more than 2 timetables (weekend split?):" + timetables.size.to_s puts "WARNING: " + file + " more than 2 timetables (weekend split?):" + timetables.size.to_s
end end
if timetables.size < 2 if timetables.size < 2
puts "WARNING: " + file + " less than 2 timetables (weekday loop service?):" + timetables.size.to_s puts "WARNING: " + file + " less than 2 timetables (weekday loop service?):" + timetables.size.to_s
elsif not (timetables[0]["time_points"] - timetables[1]["time_points"].reverse).empty? elsif not (timetables[0]["time_points"] - timetables[1]["time_points"].reverse).empty?
puts "WARNING: first pair of timetable timing points are not complementary for "+ file puts "WARNING: first pair of timetable timing points are not complementary for "+ file
pp(timetables[0]["time_points"] - timetables[1]["time_points"].reverse) pp(timetables[0]["time_points"] - timetables[1]["time_points"].reverse)
end end
if timetables.size < 1 if timetables.size < 1
raise "No timetables extracted from " + file raise "No timetables extracted from " + file
end end
} }
   
require 'rubygems' require 'rubygems'
require 'pp' require 'pp'
require 'yaml' require 'yaml'
class Array class Array
def to_yaml_style def to_yaml_style
:inline :inline
end end
end end
Dir.chdir("output") Dir.chdir("output")
   
def getTimePoints() def getTimePoints()
$time_points = [] $time_points = []
$time_points_sources = Hash.new([]) $time_points_sources = Hash.new([])
Dir.glob("*.yml") { |file| Dir.glob("*.yml") { |file|
timetable = YAML::load_file(file) timetable = YAML::load_file(file)
$time_points = $time_points | timetable["time_points"] $time_points = $time_points | timetable["time_points"]
timetable["time_points"].each do |timepoint| timetable["time_points"].each do |timepoint|
$time_points_sources[timepoint] = $time_points_sources[timepoint] | [ file ] $time_points_sources[timepoint] = $time_points_sources[timepoint] | [ file ]
end end
} }
end end
def correctTimePoints() def correctTimePoints()
time_point_corrections = {"North Lynehamham" => "North Lyneham", time_point_corrections = {"North Lynehamham" => "North Lyneham",
"Woden Bus Station Platform 10)" => "Woden Bus Station (Platform 10)", "Woden Bus Station Platform 10)" => "Woden Bus Station (Platform 10)",
"Saint AndrewsVillage Hughes"=>"Saint Andrews Village Hughes", "Saint AndrewsVillage Hughes"=>"Saint Andrews Village Hughes",
"Flemmington Road / Sandford St"=>"Flemington Road / Sandford St", "Flemmington Road / Sandford St"=>"Flemington Road / Sandford St",
"City Interchange"=>"City Bus Station", "City Interchange"=>"City Bus Station",
"City Interchange (Platform 9)"=>"City Bus Station (Platform 9)", "City Interchange (Platform 9)"=>"City Bus Station (Platform 9)",
"City Bus Station Platfrom 9"=>"City Bus Station (Platform 9)", "City Bus Station Platfrom 9"=>"City Bus Station (Platform 9)",
"Belconnen Community Bus StationPlatform 2)"=>"Belconnen Community Bus Station (Platform 2)", "Belconnen Community Bus StationPlatform 2)"=>"Belconnen Community Bus Station (Platform 2)",
"Bridbabella Gardens Nursing Home"=>"Brindabella Gardens Nursing Home", "Bridbabella Gardens Nursing Home"=>"Brindabella Gardens Nursing Home",
"Bridbabella GardensNursing Home"=> "Brindabella Gardens Nursing Home", "Bridbabella GardensNursing Home"=> "Brindabella Gardens Nursing Home",
"BrindabellaBusiness Park"=> "Brindabella Business Park", "BrindabellaBusiness Park"=> "Brindabella Business Park",
"NarrabundahTerminus"=>"Narrabundah Terminus", "NarrabundahTerminus"=>"Narrabundah Terminus",
"Railway StationKingston"=>"Railway Station Kingston", "Railway StationKingston"=>"Railway Station Kingston",
"Saint AndrewsVillage Hughes"=>"Saint Andrews Village Hughes", "Saint AndrewsVillage Hughes"=>"Saint Andrews Village Hughes",
"Dickson ShopsAntill Street"=>"Dickson Shops", "DicksonAntill Street"=>"Dickson",
"Cohen St Bus Station (Platform 3)" => "Cohen Street Bus Station (Platform 3)", "Cohen St Bus Station (Platform 3)" => "Cohen Street Bus Station (Platform 3)",
"Cohen St Bus Station (Platform 6)" => "Cohen Street Bus Station (Platform 6)", "Cohen St Bus Station (Platform 6)" => "Cohen Street Bus Station (Platform 6)",
"Newcastle Streetafter Isa Street" => "Newcastle Street after Isa Street", "Newcastle Streetafter Isa Street" => "Newcastle Street after Isa Street",
"Newcastle St after Isa St" => "Newcastle Street after Isa Street", "Newcastle St after Isa St" => "Newcastle Street after Isa Street",
"Newcastle Street after Isa St" => "Newcastle Street after Isa Street", "Newcastle Street after Isa St" => "Newcastle Street after Isa Street",
"Northbourne Ave / Antill St" => "Northbourne Avenue / Antill St", "Northbourne Ave / Antill St" => "Northbourne Avenue / Antill St",
"Macarthur / Northbourne" => "Macarthur / Northbourne Ave", "Macarthur / Northbourne" => "Macarthur / Northbourne Ave",
"Macarthur Ave / Northbourne" => "Macarthur / Northbourne Ave", "Macarthur Ave / Northbourne" => "Macarthur / Northbourne Ave",
"Kings Ave / National Cct"=> "Kings Ave / National Circuit", "Kings Ave / National Cct"=> "Kings Ave / National Circuit",
"Kosciuszco Ave / Everard Street"=>"Kosciuszko / Everard", "Kosciuszco Ave / Everard Street"=>"Kosciuszko / Everard",
"Lithgow St Terminus" => "Lithgow St Terminus Fyshwick", "Lithgow St Terminus" => "Lithgow St Terminus Fyshwick",
"Hospice Menindee Dr" => "Hospice / Menindee Dr", "Hospice Menindee Dr" => "Hospice / Menindee Dr",
"Gungahlin Market Place"=> "Gungahlin Marketplace", "Gungahlin Market Place"=> "Gungahlin Marketplace",
"Gwyder Square Kaleen"=> "Gwydir Square Kaleen", "Gwyder Square Kaleen"=> "Gwydir Square Kaleen",
"Flemington Road / Nullabor Ave"=>"Flemington Rd / Nullabor Ave", "Flemington Road / Nullabor Ave"=>"Flemington Rd / Nullabor Ave",
"Flemington Road / Sandford St"=>"Flemington Rd / Sandford St", "Flemington Road / Sandford St"=>"Flemington Rd / Sandford St",
"Heagney Cres Clift Cres Richardson"=> "Heagney / Clift Richardson", "Heagney Cres Clift Cres Richardson"=> "Heagney / Clift Richardson",
"Charnwood Shops (Tillyard Drive)"=> "Charnwood Shops", "Charnwood (Tillyard Drive)"=> "Charnwood",
"charnwood Shops"=> "Charnwood Shops", "charnwood"=> "Charnwood",
"Black Moutain- Telstra Tower"=>"Black Mountain Telstra Tower", "Black Moutain- Telstra Tower"=>"Black Mountain Telstra Tower",
"Bonython Primary"=> "Bonython Primary School", "Bonython Primary"=> "Bonython Primary School",
"Athllon Drive / Sulwood Dr Kambah"=>"Athllon / Sulwood Kambah", "Athllon Drive / Sulwood Dr Kambah"=>"Athllon / Sulwood Kambah",
"Alexander Machonochie Centre Hume"=>"Alexander Maconochie Centre", "Alexander Machonochie Centre Hume"=>"Alexander Maconochie Centre",
"Alexander Maconochie Centre Hume"=>"Alexander Maconochie Centre", "Alexander Maconochie Centre Hume"=>"Alexander Maconochie Centre",
"Anthony Rolfe Ave / Moonight Ave" =>"Anthony Rolfe Av / Moonlight Av", "Anthony Rolfe Ave / Moonight Ave" =>"Anthony Rolfe Av / Moonlight Av",
"Australian National Botanic Gardens"=>"Botanic Gardens", "Australian National Botanic Gardens"=>"Botanic Gardens",
"Calwell shops"=> "Calwell Shops", "Calwell shops"=> "Calwell",
"Chuculba / William Slim Drive"=>"Chuculba / William Slim Dr", "Chuculba / William Slim Drive"=>"Chuculba / William Slim Dr",
"Fyshwick direct Factory Outlet"=>"Fyshwick Direct Factory Outlet", "Fyshwick direct Factory Outlet"=>"Fyshwick Direct Factory Outlet",
"Kaleen Village / Maibrynong"=>"Kaleen Village / Marybrynong", "Kaleen Village / Maibrynong"=>"Kaleen Village / Marybrynong",
"Kaleen Village / Marybrynong Ave"=>"Kaleen Village / Marybrynong", "Kaleen Village / Marybrynong Ave"=>"Kaleen Village / Marybrynong",
"National Aquarium"=>"National Zoo and Aquarium", "National Aquarium"=>"National Zoo and Aquarium",
"chisholm Shops"=>"Chisholm Shops", "chisholm"=>"Chisholm",
"O'connor Shops"=>"O'Connor Shops", "O'connor"=>"O'Connor",
"Mckellar Shops"=>"McKellar Shops", "Mckellar"=>"McKellar",
"Melba shops"=> "Melba Shops",  
"William Web / Ginninderra Drive"=>"William Webb / Ginninderra Drive", "William Web / Ginninderra Drive"=>"William Webb / Ginninderra Drive",
"Procor / Mead"=>"Proctor / Mead", "Procor / Mead"=>"Proctor / Mead",
"Fyshwick DirectFactory Outlet"=>"Fyshwick Direct Factory Outlet", "Fyshwick DirectFactory Outlet"=>"Fyshwick Direct Factory Outlet",
"Yarrulumla Shops"=>"Yarralumla Shops", "Yarrulumla"=>"Yarralumla",
"Tharwa Dr / Pocket Ave"=>"Tharwa Dr / Pockett Ave", "Tharwa Dr / Pocket Ave"=>"Tharwa Dr / Pockett Ave",
"Paul Coe / Mirrebei Dr"=>"Paul Coe / Mirrabei Dr", "Paul Coe / Mirrebei Dr"=>"Paul Coe / Mirrabei Dr",
"Mirrebei Drive / Dam Wall"=>"Mirrabei Drive / Dam Wall", "Mirrebei Drive / Dam Wall"=>"Mirrabei Drive / Dam Wall",
"Tharwa / Knoke" => "Tharwa Drive / Knoke Ave", "Tharwa / Knoke" => "Tharwa Drive / Knoke Ave",
"Tharwa / Pocket" => "Tharwa Dr / Pockett Ave", "Tharwa / Pocket" => "Tharwa Drive / Pockett Ave",
  'Tharwa Dr / Pockett Ave' => "Tharwa Drive / Pockett Ave",
"Outrim / Duggan" => "Outtrim / Duggan", "Outrim / Duggan" => "Outtrim / Duggan",
"ANU Burton and Garran Hall Daley Rd" => "Burton and Garran Hall Daley Road", "ANU Burton and Garran Hall Daley Rd" => "Burton and Garran Hall Daley Road",
"Farrer Primary"=>"Farrer Primary School", "Farrer Primary"=>"Farrer Primary School",
"St Thomas More Campbell"=>"St Thomas More's Campbell", "St Thomas More Campbell"=>"St Thomas More's Campbell",
"Lyneham Shops"=>"Lyneham Shops Wattle Street", "Lyneham Wattle Street"=>"Lyneham / Wattle St",
  "Dickson" => "Dickson / Antill St",
  'Dickson Antill Street' => 'Dickson / Antill St',
  "Livingston / Kambah" => "Kambah / Livingston St",
  'Melba shops' => 'Melba',
  'St Clare of Assisi' => 'St Clare of Assisi Primary',
  'War Memorial Limestone Ave' => 'War Memorial / Limestone Ave'
   
} }
time_point_corrections.each do |wrong, right| time_point_corrections.each do |wrong, right|
$time_points_sources[wrong].each do |wrongfile| $time_points_sources[wrong].each do |wrongfile|
badtimetable = YAML::load_file(wrongfile) badtimetable = YAML::load_file(wrongfile)
badentrynumber = badtimetable["time_points"].index wrong badentrynumber = badtimetable["time_points"].index wrong
badtimetable["time_points"][badentrynumber] = right badtimetable["time_points"][badentrynumber] = right
puts "Corrected '" + wrong + "' to '" + right + "' in " + wrongfile puts "Corrected '" + wrong + "' to '" + right + "' in " + wrongfile
File.open(wrongfile, "w") do |f| File.open(wrongfile, "w") do |f|
f.write badtimetable.to_yaml f.write badtimetable.to_yaml
end end
end end
end end
end end
   
getTimePoints() getTimePoints()
#pp $time_points.sort! #pp $time_points.sort!
#pp $time_points_sources.sort #pp $time_points_sources.sort
   
   
correctTimePoints() correctTimePoints()
getTimePoints() getTimePoints()
correctTimePoints() correctTimePoints()
getTimePoints() getTimePoints()
pp $time_points.sort! pp $time_points.sort!
   
#!/usr/bin/ruby #!/usr/bin/ruby
   
require 'highline.rb' require 'highline.rb'
include HighLine include HighLine
   
require 'rubygems' require 'rubygems'
require 'postgres' require 'postgres'
require 'json' require 'json'
require 'net/http' require 'net/http'
def cbr_geocode(query) def cbr_geocode(query)
base_url = "http://geocoding.cloudmade.com/daa03470bb8740298d4b10e3f03d63e6/geocoding/v2/find.js?query=" base_url = "http://geocoding.cloudmade.com/daa03470bb8740298d4b10e3f03d63e6/geocoding/v2/find.js?query="
url = "#{base_url}#{URI.encode(query)}&bbox=-35.47,148.83,-35.16,149.25&return_location=true" url = "#{base_url}#{URI.encode(query)}&bbox=-35.47,148.83,-35.16,149.25&return_location=true"
resp = Net::HTTP.get_response(URI.parse(url)) resp = Net::HTTP.get_response(URI.parse(url))
data = resp.body data = resp.body
pp url pp url
# we convert the returned JSON data to native Ruby # we convert the returned JSON data to native Ruby
# data structure - a hash # data structure - a hash
result = JSON.parse(data) result = JSON.parse(data)
   
# if the hash has 'Error' as a key, we raise an error # if the hash has 'Error' as a key, we raise an error
if result.has_key? 'Error' if result.has_key? 'Error'
raise "web service error" raise "web service error"
end end
return result return result
end end
class Array class Array
   
def find_dups def find_dups
inject(Hash.new(0)) { |h,e| h[e] += 1; h }.select { |k,v| v > 1 }.collect { |x| x.first } inject(Hash.new(0)) { |h,e| h[e] += 1; h }.select { |k,v| v > 1 }.collect { |x| x.first }
end end
end end
   
require 'yaml' require 'yaml'
require 'pp' require 'pp'
Dir.chdir("output") Dir.chdir("output")
   
def getTimePoints() def getTimePoints()
$time_points = [] $time_points = []
$time_points_sources = Hash.new([]) $time_points_sources = Hash.new([])
Dir.glob("*.yml") { |file| Dir.glob("*.yml") { |file|
  pp file
timetable = YAML::load_file(file) timetable = YAML::load_file(file)
$time_points = $time_points | timetable["time_points"] $time_points = $time_points | timetable["time_points"]
timetable["time_points"].each do |timepoint| timetable["time_points"].each do |timepoint|
$time_points_sources[timepoint] = $time_points_sources[timepoint] | [ file ] $time_points_sources[timepoint] = $time_points_sources[timepoint] | [ file ]
end end
} }
end end
   
getTimePoints() getTimePoints()
$time_points.sort! $time_points.sort!
   
connbus = PGconn.connect("localhost", 5432, '', '', "bus", "postgres", connbus = PGconn.connect("localhost", 5432, '', '', "bus", "postgres",
"snmc") "snmc")
   
if ask_if("Insert Timing Point names to database?") if ask_if("Insert Timing Point names to database?")
$time_points.each do |time_point| $time_points.each do |time_point|
begin begin
time_point = time_point.gsub(/\\/, '\&\&').gsub(/'/, "''") # DON'T PUT MORE GSUB HERE time_point = time_point.gsub(/\\/, '\&\&').gsub(/'/, "''") # DON'T PUT MORE GSUB HERE
res = connbus.exec("INSERT INTO timing_point (name) VALUES ('#{time_point}')") res = connbus.exec("INSERT INTO timing_point (name) VALUES ('#{time_point}')")
puts "Put '#{time_point}' into DB" puts "Put '#{time_point}' into DB"
rescue PGError => e rescue PGError => e
puts "Error inserting '#{time_point}' to DB #{e}" puts "Error inserting '#{time_point}' to DB #{e}"
#conn.close() if conn #conn.close() if conn
end end
end end
end end
   
   
if ask_if("Fill null Timing Points from geocoder?") if ask_if("Fill null Timing Points from geocoder?")
begin begin
null_points = connbus.exec('SELECT name FROM timing_point WHERE lat IS null OR lng IS null;') null_points = connbus.exec('SELECT name FROM timing_point WHERE lat IS null OR lng IS null;')
rescue PGError => e rescue PGError => e
puts "Error selecting null points from DB #{e}" puts "Error selecting null points from DB #{e}"
#conn.close() if conn #conn.close() if conn
end end
   
null_points.each do |null_point_name| null_points.each do |null_point_name|
pp null_point_name pp null_point_name
name = null_point_name.to_s.gsub(/\\/, '\&\&').gsub(/'/, "''") name = null_point_name.to_s.gsub(/\\/, '\&\&').gsub(/'/, "''")
results = cbr_geocode(null_point_name.to_s.gsub("Shops", "")) results = cbr_geocode(null_point_name.to_s.gsub("Shops", ""))
if !results.empty? if !results.empty?
results['features'].each_with_index { |feature,index| results['features'].each_with_index { |feature,index|
print "#{index}: #{feature['properties']['name']} (#{feature['location']}) => #{feature['centroid']['coordinates']}\n" print "#{index}: #{feature['properties']['name']} (#{feature['location']}) => #{feature['centroid']['coordinates']}\n"
} }
nodeID = ask("Enter selected node ID:", :integer) nodeID = ask("Enter selected node ID:", :integer)
if results['features'].at(nodeID) != nil if results['features'].at(nodeID) != nil
node = results['features'][nodeID] node = results['features'][nodeID]
puts "Location #{node['centroid']['coordinates'][0]},#{node['centroid']['coordinates'][1]} for #{null_point_name}" puts "Location #{node['centroid']['coordinates'][0]},#{node['centroid']['coordinates'][1]} for #{null_point_name}"
begin begin
res = connbus.exec("UPDATE timing_point SET lat = #{node['centroid']['coordinates'][0]*10000000}, lng = res = connbus.exec("UPDATE timing_point SET lat = #{node['centroid']['coordinates'][0]*10000000}, lng =
#{node['centroid']['coordinates'][1]*10000000},guess = true WHERE name = '#{name}'") #{node['centroid']['coordinates'][1]*10000000},guess = true WHERE name = '#{name}'")
puts "Put '#{null_point_name}' into DB" puts "Put '#{null_point_name}' into DB"
rescue PGError => e rescue PGError => e
puts "Error inserting '#{null_point_name}' to DB #{e}" puts "Error inserting '#{null_point_name}' to DB #{e}"
ask_if("Continue?") ask_if("Continue?")
#conn.close() if conn #conn.close() if conn
end end
else else
puts "Uhh, there was no suggestion ID like that. Try again next time!" puts "Uhh, there was no suggestion ID like that. Try again next time!"
end end
else else
puts "Uhh, there were no geocoding results. Try again next time!" puts "Uhh, there were no geocoding results. Try again next time!"
end end
end end
end end
   
   
   
options: options:
start_date: 20101115 start_date: 20101115
end_date: 20111231 end_date: 20111231
remove_date: 20111231 remove_date: 20111231
agency_name: ACT Internal Omnibus Network (ACTION) agency_name: ACT Internal Omnibus Network (ACTION)
agency_url: http://www.action.act.gov.au/ agency_url: http://www.action.act.gov.au/
agency_timezone: Australia/Sydney agency_timezone: Australia/Sydney
   
   
stops: stops:
- { name: ACTEW AGL House,stop_code: ACTEW AGL House, lat: -35.282374, lng: 149.132047} - { name: ACTEW AGL House,stop_code: ACTEW AGL House, lat: -35.282374, lng: 149.132047}
- { name: ADFA,stop_code: ADFA, lat: -35.2937972, lng: 149.1643403} - { name: ADFA,stop_code: ADFA, lat: -35.2937972, lng: 149.1643403}
- { name: Ainslie,stop_code: Ainslie, lat: -35.2620105, lng: 149.1443302} - { name: Ainslie,stop_code: Ainslie, lat: -35.2620105, lng: 149.1443302}
- { name: Ainslie Shops,stop_code: Ainslie Shops, lat: -35.26198, lng: 149.14535}  
- { name: Alexander Maconochie Centre,stop_code: Alexander Maconochie Centre, lat: -35.3720651, lng: 149.1696618} - { name: Alexander Maconochie Centre,stop_code: Alexander Maconochie Centre, lat: -35.3720651, lng: 149.1696618}
- { name: Alpen & Clifford St,stop_code: Alpen & Clifford St, lat: -35.20562, lng: 149.06259} - { name: Alpen & Clifford St,stop_code: Alpen & Clifford St, lat: -35.20562, lng: 149.06259}
- { name: Anthony Rolfe Av / Moonlight Av,stop_code: Anthony Rolfe Av / Moonlight Av, lat: -35.1856021, lng: 149.1543639} - { name: Anthony Rolfe Av / Moonlight Av,stop_code: Anthony Rolfe Av / Moonlight Av, lat: -35.1856021, lng: 149.1543639}
- { name: Aranda,stop_code: Aranda, lat: -35.257534, lng: 149.0762963} - { name: Aranda,stop_code: Aranda, lat: -35.257534, lng: 149.0762963}
- { name: Aranda Shops,stop_code: Aranda Shops, lat: -35.25753, lng: 149.0763}  
- { name: Athllon / Sulwood Kambah,stop_code: Athllon / Sulwood Kambah, lat: -35.38442, lng: 149.09328} - { name: Athllon / Sulwood Kambah,stop_code: Athllon / Sulwood Kambah, lat: -35.38442, lng: 149.09328}
- { name: Australian Institute of Sport,stop_code: Australian Institute of Sport, lat: -35.246351, lng: 149.101478} - { name: Australian Institute of Sport,stop_code: Australian Institute of Sport, lat: -35.246351, lng: 149.101478}
- { name: Belconnen Community Bus Station,stop_code: Belconnen Community Bus Station, lat: -35.23987, lng: 149.0619} - { name: Belconnen Community Bus Station,stop_code: Belconnen Community Bus Station, lat: -35.23987, lng: 149.0619}
- { name: Belconnen Community Bus Station (Platform 1),stop_code: Belconnen Community Bus Station (Platform 1), lat: -35.23982, lng: 149.06978} - { name: Belconnen Community Bus Station (Platform 1),stop_code: Belconnen Community Bus Station (Platform 1), lat: -35.23982, lng: 149.06978}
- { name: Belconnen Community Bus Station (Platform 2),stop_code: Belconnen Community Bus Station (Platform 2), lat: -35.23982, lng: 149.06926} - { name: Belconnen Community Bus Station (Platform 2),stop_code: Belconnen Community Bus Station (Platform 2), lat: -35.23982, lng: 149.06926}
- { name: Belconnen Community Bus Station (Platform 3),stop_code: Belconnen Community Bus Station (Platform 3), lat: -35.23986, lng: 149.06847} - { name: Belconnen Community Bus Station (Platform 3),stop_code: Belconnen Community Bus Station (Platform 3), lat: -35.23986, lng: 149.06847}
- { name: Belconnen Community Bus Station (Platform 4),stop_code: Belconnen Community Bus Station (Platform 4), lat: -35.23994, lng: 149.06887} - { name: Belconnen Community Bus Station (Platform 4),stop_code: Belconnen Community Bus Station (Platform 4), lat: -35.23994, lng: 149.06887}
- { name: Belconnen Community Bus Station (Platform 5),stop_code: Belconnen Community Bus Station (Platform 5), lat: -35.23994, lng: 149.06928} - { name: Belconnen Community Bus Station (Platform 5),stop_code: Belconnen Community Bus Station (Platform 5), lat: -35.23994, lng: 149.06928}
- { name: Belconnen Community Bus Station (Platform 6),stop_code: Belconnen Community Bus Station (Platform 6), lat: -35.23994, lng: 149.0698} - { name: Belconnen Community Bus Station (Platform 6),stop_code: Belconnen Community Bus Station (Platform 6), lat: -35.23994, lng: 149.0698}
- { name: Belconnen Way,stop_code: Belconnen Way, lat: -35.24809, lng: 149.06765} - { name: Belconnen Way,stop_code: Belconnen Way, lat: -35.24809, lng: 149.06765}
- { name: Bimberi Centre,stop_code: Bimberi Centre, lat: -35.2219941, lng: 149.1546928} - { name: Bimberi Centre,stop_code: Bimberi Centre, lat: -35.2219941, lng: 149.1546928}
- { name: Black Mountain Telstra Tower,stop_code: Black Mountain Telstra Tower, lat: -35.2748058, lng: 149.0972461} - { name: Black Mountain Telstra Tower,stop_code: Black Mountain Telstra Tower, lat: -35.2748058, lng: 149.0972461}
- { name: Bonython,stop_code: Bonython, lat: -35.4297416, lng: 149.0814517} - { name: Bonython,stop_code: Bonython, lat: -35.4297416, lng: 149.0814517}
- { name: Bonython Primary School,stop_code: Bonython Primary School, lat: -35.431019, lng: 149.0831217} - { name: Bonython Primary School,stop_code: Bonython Primary School, lat: -35.431019, lng: 149.0831217}
- { name: Botanic Gardens,stop_code: Botanic Gardens, lat: -35.278643, lng: 149.1093237} - { name: Botanic Gardens,stop_code: Botanic Gardens, lat: -35.278643, lng: 149.1093237}
- { name: Brindabella Business Park,stop_code: Brindabella Business Park, lat: -35.314496, lng: 149.189145} - { name: Brindabella Business Park,stop_code: Brindabella Business Park, lat: -35.314496, lng: 149.189145}
- { name: Brindabella Gardens Nursing Home,stop_code: Brindabella Gardens Nursing Home, lat: -35.3294459, lng: 149.0806116} - { name: Brindabella Gardens Nursing Home,stop_code: Brindabella Gardens Nursing Home, lat: -35.3294459, lng: 149.0806116}
- { name: Bugden Sternberg,stop_code: Bugden Sternberg, lat: -35.4017223, lng: 149.0992172} - { name: Bugden Sternberg,stop_code: Bugden Sternberg, lat: -35.4017223, lng: 149.0992172}
- { name: Burton and Garran Hall Daley Road,stop_code: Burton and Garran Hall Daley Road, lat: -35.2753671, lng: 149.1172822} - { name: Burton and Garran Hall Daley Road,stop_code: Burton and Garran Hall Daley Road, lat: -35.2753671, lng: 149.1172822}
- { name: Calvary Hospital,stop_code: Calvary Hospital, lat: -35.25212, lng: 149.09088} - { name: Calvary Hospital,stop_code: Calvary Hospital, lat: -35.25212, lng: 149.09088}
- { name: Calwell Shops,stop_code: Calwell Shops, lat: -35.43524, lng: 149.113942} - { name: Calwell,stop_code: Calwell, lat: -35.43524, lng: 149.113942}
- { name: Cameron Ave Bus Station,stop_code: Cameron Ave Bus Station, lat: -35.2410195, lng: 149.0722506} - { name: Cameron Ave Bus Station,stop_code: Cameron Ave Bus Station, lat: -35.2410195, lng: 149.0722506}
- { name: Cameron Ave Bus Station (Platform 1),stop_code: Cameron Ave Bus Station (Platform 1), lat: -35.2410195, lng: 149.0722506} - { name: Cameron Ave Bus Station (Platform 1),stop_code: Cameron Ave Bus Station (Platform 1), lat: -35.2410195, lng: 149.0722506}
- { name: Cameron Ave Bus Station (Platform 2),stop_code: Cameron Ave Bus Station (Platform 2), lat: -35.2410108, lng: 149.0717142} - { name: Cameron Ave Bus Station (Platform 2),stop_code: Cameron Ave Bus Station (Platform 2), lat: -35.2410108, lng: 149.0717142}
- { name: Cameron Ave Bus Station (Platform 3),stop_code: Cameron Ave Bus Station (Platform 3), lat: -35.2410064, lng: 149.0710758} - { name: Cameron Ave Bus Station (Platform 3),stop_code: Cameron Ave Bus Station (Platform 3), lat: -35.2410064, lng: 149.0710758}
- { name: Cameron Ave Bus Station (Platform 4),stop_code: Cameron Ave Bus Station (Platform 4), lat: -35.2411773, lng: 149.0709793} - { name: Cameron Ave Bus Station (Platform 4),stop_code: Cameron Ave Bus Station (Platform 4), lat: -35.2411773, lng: 149.0709793}
- { name: Cameron Ave Bus Station (Platform 5),stop_code: Cameron Ave Bus Station (Platform 5), lat: -35.241186, lng: 149.0720789} - { name: Cameron Ave Bus Station (Platform 5),stop_code: Cameron Ave Bus Station (Platform 5), lat: -35.241186, lng: 149.0720789}
- { name: Campbell Park Offices,stop_code: Campbell Park Offices, lat: -35.28368, lng: 149.17045} - { name: Campbell Park Offices,stop_code: Campbell Park Offices, lat: -35.28368, lng: 149.17045}
- { name: Canberra College Weston Campus,stop_code: Canberra College Weston Campus, lat: -35.3490278, lng: 149.0486277} - { name: Canberra College Weston Campus,stop_code: Canberra College Weston Campus, lat: -35.3490278, lng: 149.0486277}
- { name: Canberra Hospital,stop_code: Canberra Hospital, lat: -35.3459462, lng: 149.1012001} - { name: Canberra Hospital,stop_code: Canberra Hospital, lat: -35.3459462, lng: 149.1012001}
- { name: Canberra Times,stop_code: Canberra Times, lat: -35.3245431, lng: 149.1705533} - { name: Canberra Times,stop_code: Canberra Times, lat: -35.3245431, lng: 149.1705533}
- { name: Caswell Drive,stop_code: Caswell Drive, lat: -35.25922, lng: 149.08576} - { name: Caswell Drive,stop_code: Caswell Drive, lat: -35.25922, lng: 149.08576}
- { name: Causeway,stop_code: Causeway, lat: -35.31615, lng: 149.15058} - { name: Causeway,stop_code: Causeway, lat: -35.31615, lng: 149.15058}
- { name: Centrelink Tuggeranong,stop_code: Centrelink Tuggeranong, lat: -35.4207496, lng: 149.0700973} - { name: Centrelink Tuggeranong,stop_code: Centrelink Tuggeranong, lat: -35.4207496, lng: 149.0700973}
- { name: Chapman,stop_code: Chapman, lat: -35.3557877, lng: 149.0408111} - { name: Chapman,stop_code: Chapman, lat: -35.3557877, lng: 149.0408111}
- { name: Chapman Shops,stop_code: Chapman Shops, lat: -35.35579, lng: 149.04082}  
- { name: Charnwood,stop_code: Charnwood, lat: -35.2052138, lng: 149.0337266} - { name: Charnwood,stop_code: Charnwood, lat: -35.2052138, lng: 149.0337266}
- { name: Charnwood Shops,stop_code: Charnwood Shops, lat: -35.20472, lng: 149.03336}  
- { name: Charnwood Tillyard Dr,stop_code: Charnwood Tillyard Dr, lat: -35.20295, lng: 149.04027} - { name: Charnwood Tillyard Dr,stop_code: Charnwood Tillyard Dr, lat: -35.20295, lng: 149.04027}
- { name: Chifley,stop_code: Chifley, lat: -35.350985, lng: 149.077319} - { name: Chifley,stop_code: Chifley, lat: -35.350985, lng: 149.077319}
- { name: Chifley Shops,stop_code: Chifley Shops, lat: -35.35099, lng: 149.07732} - { name: Chisholm,stop_code: Chisholm, lat: -35.41341, lng: 149.12833}
- { name: Chisholm Shops,stop_code: Chisholm Shops, lat: -35.41341, lng: 149.12833}  
- { name: Chuculba / William Slim Dr,stop_code: Chuculba / William Slim Dr, lat: -35.208931, lng: 149.088499} - { name: Chuculba / William Slim Dr,stop_code: Chuculba / William Slim Dr, lat: -35.208931, lng: 149.088499}
- { name: CIT Weston,stop_code: CIT Weston, lat: -35.330234, lng: 149.058632} - { name: CIT Weston,stop_code: CIT Weston, lat: -35.330234, lng: 149.058632}
- { name: City Bus Station,stop_code: City Bus Station, lat: -35.2794346, lng: 149.1305879} - { name: City Bus Station,stop_code: City Bus Station, lat: -35.2794346, lng: 149.1305879}
- { name: City Bus Station (Platform 1),stop_code: City Bus Station (Platform 1), lat: -35.2794346, lng: 149.1305879} - { name: City Bus Station (Platform 1),stop_code: City Bus Station (Platform 1), lat: -35.2794346, lng: 149.1305879}
- { name: City Bus Station (Platform 10),stop_code: City Bus Station (Platform 10), lat: -35.2793571, lng: 149.1293659} - { name: City Bus Station (Platform 10),stop_code: City Bus Station (Platform 10), lat: -35.2793571, lng: 149.1293659}
- { name: City Bus Station (Platform 11),stop_code: City Bus Station (Platform 11), lat: -35.2787905, lng: 149.1288627} - { name: City Bus Station (Platform 11),stop_code: City Bus Station (Platform 11), lat: -35.2787905, lng: 149.1288627}
- { name: City Bus Station (Platform 2),stop_code: City Bus Station (Platform 2), lat: -35.278907, lng: 149.130612} - { name: City Bus Station (Platform 2),stop_code: City Bus Station (Platform 2), lat: -35.278907, lng: 149.130612}
- { name: City Bus Station (Platform 3),stop_code: City Bus Station (Platform 3), lat: -35.2787886, lng: 149.1304779} - { name: City Bus Station (Platform 3),stop_code: City Bus Station (Platform 3), lat: -35.2787886, lng: 149.1304779}
- { name: City Bus Station (Platform 4),stop_code: City Bus Station (Platform 4), lat: -35.2785658, lng: 149.1301727} - { name: City Bus Station (Platform 4),stop_code: City Bus Station (Platform 4), lat: -35.2785658, lng: 149.1301727}
- { name: City Bus Station (Platform 5),stop_code: City Bus Station (Platform 5), lat: -35.2785242, lng: 149.1297348} - { name: City Bus Station (Platform 5),stop_code: City Bus Station (Platform 5), lat: -35.2785242, lng: 149.1297348}
- { name: City Bus Station (Platform 7),stop_code: City Bus Station (Platform 7), lat: -35.27843, lng: 149.130345} - { name: City Bus Station (Platform 7),stop_code: City Bus Station (Platform 7), lat: -35.27843, lng: 149.130345}
- { name: City Bus Station (Platform 8),stop_code: City Bus Station (Platform 8), lat: -35.2778798, lng: 149.1305995} - { name: City Bus Station (Platform 8),stop_code: City Bus Station (Platform 8), lat: -35.2778798, lng: 149.1305995}
- { name: City Bus Station (Platform 9),stop_code: City Bus Station (Platform 9), lat: -35.2783224, lng: 149.130726} - { name: City Bus Station (Platform 9),stop_code: City Bus Station (Platform 9), lat: -35.2783224, lng: 149.130726}
- { name: City West,stop_code: City West, lat: -35.2788605, lng: 149.1257969} - { name: City West,stop_code: City West, lat: -35.2788605, lng: 149.1257969}
- { name: Cnr Kerrigan/Lhotsky,stop_code: Cnr Kerrigan/Lhotsky, lat: -35.1995716, lng: 149.0285277} - { name: Cnr Kerrigan/Lhotsky,stop_code: Cnr Kerrigan/Lhotsky, lat: -35.1995716, lng: 149.0285277}
- { name: Cnr Tillyard Dr & Spalding St,stop_code: Cnr Tillyard Dr & Spalding St, lat: -35.2040477, lng: 149.0393052} - { name: Cnr Tillyard Dr & Spalding St,stop_code: Cnr Tillyard Dr & Spalding St, lat: -35.2040477, lng: 149.0393052}
- { name: Cohen Street Bus Station,stop_code: Cohen Street Bus Station, lat: -35.2394775, lng: 149.0602031} - { name: Cohen Street Bus Station,stop_code: Cohen Street Bus Station, lat: -35.2394775, lng: 149.0602031}
- { name: Cohen Street Bus Station (Platform 1),stop_code: Cohen Street Bus Station (Platform 1), lat: -35.2394775, lng: 149.0602031} - { name: Cohen Street Bus Station (Platform 1),stop_code: Cohen Street Bus Station (Platform 1), lat: -35.2394775, lng: 149.0602031}
- { name: Cohen Street Bus Station (Platform 2),stop_code: Cohen Street Bus Station (Platform 2), lat: -35.2396467, lng: 149.0602152} - { name: Cohen Street Bus Station (Platform 2),stop_code: Cohen Street Bus Station (Platform 2), lat: -35.2396467, lng: 149.0602152}
- { name: Cohen Street Bus Station (Platform 3),stop_code: Cohen Street Bus Station (Platform 3), lat: -35.239764, lng: 149.0604531} - { name: Cohen Street Bus Station (Platform 3),stop_code: Cohen Street Bus Station (Platform 3), lat: -35.239764, lng: 149.0604531}
- { name: Cohen Street Bus Station (Platform 4),stop_code: Cohen Street Bus Station (Platform 4), lat: -35.239844, lng: 149.0600683} - { name: Cohen Street Bus Station (Platform 4),stop_code: Cohen Street Bus Station (Platform 4), lat: -35.239844, lng: 149.0600683}
- { name: Cohen Street Bus Station (Platform 5),stop_code: Cohen Street Bus Station (Platform 5), lat: -35.2401211, lng: 149.0597102} - { name: Cohen Street Bus Station (Platform 5),stop_code: Cohen Street Bus Station (Platform 5), lat: -35.2401211, lng: 149.0597102}
- { name: Cohen Street Bus Station (Platform 6),stop_code: Cohen Street Bus Station (Platform 6), lat: -35.2400028, lng: 149.060315} - { name: Cohen Street Bus Station (Platform 6),stop_code: Cohen Street Bus Station (Platform 6), lat: -35.2400028, lng: 149.060315}
- { name: Conder Primary,stop_code: Conder Primary, lat: -35.4643475, lng: 149.0986908} - { name: Conder Primary,stop_code: Conder Primary, lat: -35.4643475, lng: 149.0986908}
- { name: Cook,stop_code: Cook, lat: -35.2596, lng: 149.0638} - { name: Cook,stop_code: Cook, lat: -35.2596, lng: 149.0638}
- { name: Cook Shops,stop_code: Cook Shops, lat: -35.25898, lng: 149.06343}  
- { name: Cooleman Court,stop_code: Cooleman Court, lat: -35.34147, lng: 149.05338} - { name: Cooleman Court,stop_code: Cooleman Court, lat: -35.34147, lng: 149.05338}
- { name: Copland College,stop_code: Copland College, lat: -35.2127018, lng: 149.0596387} - { name: Copland College,stop_code: Copland College, lat: -35.2127018, lng: 149.0596387}
- { name: Curtin,stop_code: Curtin, lat: -35.3248779, lng: 149.081441} - { name: Curtin,stop_code: Curtin, lat: -35.3248779, lng: 149.081441}
- { name: Curtin Shops,stop_code: Curtin Shops, lat: -35.32515, lng: 149.08224}  
- { name: Deakin,stop_code: Deakin, lat: -35.3158608, lng: 149.1084563} - { name: Deakin,stop_code: Deakin, lat: -35.3158608, lng: 149.1084563}
- { name: Deakin Shops,stop_code: Deakin Shops, lat: -35.31473, lng: 149.10771}  
- { name: Deamer / Clift Richardson,stop_code: Deamer / Clift Richardson, lat: -35.4319597, lng: 149.1187876} - { name: Deamer / Clift Richardson,stop_code: Deamer / Clift Richardson, lat: -35.4319597, lng: 149.1187876}
- { name: Dickson,stop_code: Dickson, lat: -35.2498434, lng: 149.1391218} - { name: Dickson / Antill St,stop_code: Dickson / Antill St, lat: -35.2489, lng: 149.14012}
- { name: Dickson College,stop_code: Dickson College, lat: -35.24923, lng: 149.15315} - { name: Dickson College,stop_code: Dickson College, lat: -35.24923, lng: 149.15315}
- { name: Dickson Cowper St,stop_code: Dickson Cowper St, lat: -35.250297, lng: 149.141336} - { name: Dickson / Cowper St,stop_code: Dickson / Cowper St, lat: -35.250297, lng: 149.141336}
- { name: Dickson Shops,stop_code: Dickson Shops, lat: -35.25045, lng: 149.14044}  
- { name: Dickson Shops/Antill St,stop_code: Dickson Shops/Antill St, lat: -35.2251335, lng: 149.1658895}  
- { name: Duffy,stop_code: Duffy, lat: -35.3366908, lng: 149.0324311} - { name: Duffy,stop_code: Duffy, lat: -35.3366908, lng: 149.0324311}
- { name: Duffy Primary,stop_code: Duffy Primary, lat: -35.334219, lng: 149.033656} - { name: Duffy Primary,stop_code: Duffy Primary, lat: -35.334219, lng: 149.033656}
- { name: Dunlop,stop_code: Dunlop, lat: -35.1942693, lng: 149.0206702} - { name: Dunlop,stop_code: Dunlop, lat: -35.1942693, lng: 149.0206702}
- { name: Erindale Centre,stop_code: Erindale Centre, lat: -35.4038881, lng: 149.0992283} - { name: Erindale Centre,stop_code: Erindale Centre, lat: -35.4038881, lng: 149.0992283}
- { name: Erindale Dr / Charleston St Monash,stop_code: Erindale Dr / Charleston St Monash, lat: -35.414616, lng: 149.07888} - { name: Erindale Dr / Charleston St Monash,stop_code: Erindale Dr / Charleston St Monash, lat: -35.414616, lng: 149.07888}
- { name: Erindale / Sternberg Cres,stop_code: Erindale / Sternberg Cres, lat: -35.4014472, lng: 149.0956545} - { name: Erindale / Sternberg Cres,stop_code: Erindale / Sternberg Cres, lat: -35.4014472, lng: 149.0956545}
- { name: Evatt,stop_code: Evatt, lat: -35.2091093, lng: 149.0735343} - { name: Evatt,stop_code: Evatt, lat: -35.2091093, lng: 149.0735343}
- { name: Evatt Shops,stop_code: Evatt Shops, lat: -35.21203, lng: 149.06505}  
- { name: Eye Hospital,stop_code: Eye Hospital, lat: -35.3341884, lng: 149.1656213} - { name: Eye Hospital,stop_code: Eye Hospital, lat: -35.3341884, lng: 149.1656213}
- { name: Fairbairn Park,stop_code: Fairbairn Park, lat: -35.3001773, lng: 149.2041185} - { name: Fairbairn Park,stop_code: Fairbairn Park, lat: -35.3001773, lng: 149.2041185}
- { name: Farrer Primary School,stop_code: Farrer Primary School, lat: -35.37887, lng: 149.10641} - { name: Farrer Primary School,stop_code: Farrer Primary School, lat: -35.37887, lng: 149.10641}
- { name: Farrer Terminus,stop_code: Farrer Terminus, lat: -35.3771794, lng: 149.1046948} - { name: Farrer Terminus,stop_code: Farrer Terminus, lat: -35.3771794, lng: 149.1046948}
- { name: Federation Square,stop_code: Federation Square, lat: -35.1908726, lng: 149.0848153} - { name: Federation Square,stop_code: Federation Square, lat: -35.1908726, lng: 149.0848153}
- { name: Fisher,stop_code: Fisher, lat: -35.3605627, lng: 149.0576481} - { name: Fisher,stop_code: Fisher, lat: -35.3605627, lng: 149.0576481}
- { name: Fisher Shops,stop_code: Fisher Shops, lat: -35.36056, lng: 149.05765}  
- { name: Flemington Rd,stop_code: Flemington Rd, lat: -35.20756, lng: 149.14778} - { name: Flemington Rd,stop_code: Flemington Rd, lat: -35.20756, lng: 149.14778}
- { name: Flemington Rd / Nullabor Ave,stop_code: Flemington Rd / Nullabor Ave, lat: -35.2008585, lng: 149.1493407} - { name: Flemington Rd / Nullabor Ave,stop_code: Flemington Rd / Nullabor Ave, lat: -35.2008585, lng: 149.1493407}
- { name: Flemington Rd / Sandford St,stop_code: Flemington Rd / Sandford St, lat: -35.221231, lng: 149.144645} - { name: Flemington Rd / Sandford St,stop_code: Flemington Rd / Sandford St, lat: -35.221231, lng: 149.144645}
- { name: Florey Shops,stop_code: Florey Shops, lat: -35.2258544, lng: 149.0546214} - { name: Florey,stop_code: Florey, lat: -35.2258544, lng: 149.0546214}
- { name: Flynn,stop_code: Flynn, lat: -35.2019283, lng: 149.0478356} - { name: Flynn,stop_code: Flynn, lat: -35.2019283, lng: 149.0478356}
- { name: Fraser,stop_code: Fraser, lat: -35.1896539, lng: 149.0435012} - { name: Fraser,stop_code: Fraser, lat: -35.1896539, lng: 149.0435012}
- { name: Fraser East Terminus,stop_code: Fraser East Terminus, lat: -35.1896539, lng: 149.0435012} - { name: Fraser East Terminus,stop_code: Fraser East Terminus, lat: -35.1896539, lng: 149.0435012}
- { name: Fraser Shops,stop_code: Fraser Shops, lat: -35.18966, lng: 149.0435}  
- { name: Fraser West Terminus,stop_code: Fraser West Terminus, lat: -35.191513, lng: 149.038006} - { name: Fraser West Terminus,stop_code: Fraser West Terminus, lat: -35.191513, lng: 149.038006}
- { name: Fyshwick Direct Factory Outlet,stop_code: Fyshwick Direct Factory Outlet, lat: -35.3359862, lng: 149.1796322} - { name: Fyshwick Direct Factory Outlet,stop_code: Fyshwick Direct Factory Outlet, lat: -35.3359862, lng: 149.1796322}
- { name: Fyshwick Terminus,stop_code: Fyshwick Terminus, lat: -35.3285202, lng: 149.1785592} - { name: Fyshwick Terminus,stop_code: Fyshwick Terminus, lat: -35.3285202, lng: 149.1785592}
- { name: Garran,stop_code: Garran, lat: -35.3423286, lng: 149.10811} - { name: Garran,stop_code: Garran, lat: -35.3423286, lng: 149.10811}
- { name: Garran Shops,stop_code: Garran Shops, lat: -35.34236, lng: 149.1082}  
- { name: Geoscience Australia,stop_code: Geoscience Australia, lat: -35.3429702, lng: 149.1583893} - { name: Geoscience Australia,stop_code: Geoscience Australia, lat: -35.3429702, lng: 149.1583893}
- { name: Giralang,stop_code: Giralang, lat: -35.2115608, lng: 149.0960692} - { name: Giralang,stop_code: Giralang, lat: -35.2115608, lng: 149.0960692}
- { name: Giralang Shops,stop_code: Giralang Shops, lat: -35.2115608, lng: 149.0960692}  
- { name: Gordon Primary,stop_code: Gordon Primary, lat: -35.455517, lng: 149.086978} - { name: Gordon Primary,stop_code: Gordon Primary, lat: -35.455517, lng: 149.086978}
- { name: Gowrie,stop_code: Gowrie, lat: -35.4120264, lng: 149.1110804} - { name: Gowrie,stop_code: Gowrie, lat: -35.4120264, lng: 149.1110804}
- { name: Gowrie Shops,stop_code: Gowrie Shops, lat: -35.4120264, lng: 149.1110804}  
- { name: Gungahlin Marketplace,stop_code: Gungahlin Marketplace, lat: -35.1769532, lng: 149.1319017} - { name: Gungahlin Marketplace,stop_code: Gungahlin Marketplace, lat: -35.1769532, lng: 149.1319017}
- { name: Gwydir Square Kaleen,stop_code: Gwydir Square Kaleen, lat: -35.2338677, lng: 149.1031998} - { name: Gwydir Square Kaleen,stop_code: Gwydir Square Kaleen, lat: -35.2338677, lng: 149.1031998}
- { name: Hackett,stop_code: Hackett, lat: -35.2481617, lng: 149.1626094} - { name: Hackett,stop_code: Hackett, lat: -35.2481617, lng: 149.1626094}
- { name: Hackett Shops,stop_code: Hackett Shops, lat: -35.24825, lng: 149.16271}  
- { name: Hawker,stop_code: Hawker, lat: -35.2437386, lng: 149.0432804} - { name: Hawker,stop_code: Hawker, lat: -35.2437386, lng: 149.0432804}
- { name: Hawker College,stop_code: Hawker College, lat: -35.2454598, lng: 149.0324251} - { name: Hawker College,stop_code: Hawker College, lat: -35.2454598, lng: 149.0324251}
- { name: Hawker Shops,stop_code: Hawker Shops, lat: -35.24398, lng: 149.04361}  
- { name: Heagney / Clift Richardson,stop_code: Heagney / Clift Richardson, lat: -35.4251299, lng: 149.11375} - { name: Heagney / Clift Richardson,stop_code: Heagney / Clift Richardson, lat: -35.4251299, lng: 149.11375}
- { name: Hibberson / Kate Crace,stop_code: Hibberson / Kate Crace, lat: -35.1861642, lng: 149.1391756} - { name: Hibberson / Kate Crace,stop_code: Hibberson / Kate Crace, lat: -35.1861642, lng: 149.1391756}
- { name: Higgins,stop_code: Higgins, lat: -35.2313901, lng: 149.0271811} - { name: Higgins,stop_code: Higgins, lat: -35.2313901, lng: 149.0271811}
- { name: Higgins Shops,stop_code: Higgins Shops, lat: -35.23136, lng: 149.02611}  
- { name: Holder,stop_code: Holder, lat: -35.3378123, lng: 149.0449433} - { name: Holder,stop_code: Holder, lat: -35.3378123, lng: 149.0449433}
- { name: Holder Shops,stop_code: Holder Shops, lat: -35.33781, lng: 149.04494}  
- { name: Holt,stop_code: Holt, lat: -35.223099, lng: 149.0126269} - { name: Holt,stop_code: Holt, lat: -35.223099, lng: 149.0126269}
- { name: Holt Shops,stop_code: Holt Shops, lat: -35.2231, lng: 149.01263}  
- { name: Hoskins Street / Oodgeroo Ave,stop_code: Hoskins Street / Oodgeroo Ave, lat: -35.201095, lng: 149.139941} - { name: Hoskins Street / Oodgeroo Ave,stop_code: Hoskins Street / Oodgeroo Ave, lat: -35.201095, lng: 149.139941}
- { name: Hospice / Menindee Dr,stop_code: Hospice / Menindee Dr, lat: -35.303557, lng: 149.151627} - { name: Hospice / Menindee Dr,stop_code: Hospice / Menindee Dr, lat: -35.303557, lng: 149.151627}
- { name: Hughes,stop_code: Hughes, lat: -35.3339223, lng: 149.093854} - { name: Hughes,stop_code: Hughes, lat: -35.3339223, lng: 149.093854}
- { name: Hughes Shops,stop_code: Hughes Shops, lat: -35.3335, lng: 149.09392}  
- { name: Isaacs,stop_code: Isaacs, lat: -35.3669823, lng: 149.1119217} - { name: Isaacs,stop_code: Isaacs, lat: -35.3669823, lng: 149.1119217}
- { name: Isaacs Shops,stop_code: Isaacs Shops, lat: -35.36698, lng: 149.11192} - { name: Isabella,stop_code: Isabella, lat: -35.4285703, lng: 149.0916837}
- { name: Isabella Shops,stop_code: Isabella Shops, lat: -35.4285703, lng: 149.0916837}  
- { name: Jamison Centre,stop_code: Jamison Centre, lat: -35.2527268, lng: 149.0713712} - { name: Jamison Centre,stop_code: Jamison Centre, lat: -35.2527268, lng: 149.0713712}
- { name: John James Hospital,stop_code: John James Hospital, lat: -35.3200295, lng: 149.0955996} - { name: John James Hospital,stop_code: John James Hospital, lat: -35.3200295, lng: 149.0955996}
- { name: Kaleen Village / Marybrynong,stop_code: Kaleen Village / Marybrynong, lat: -35.2274031, lng: 149.1075421} - { name: Kaleen Village / Marybrynong,stop_code: Kaleen Village / Marybrynong, lat: -35.2274031, lng: 149.1075421}
- { name: Kambah High,stop_code: Kambah High, lat: -35.3847749, lng: 149.0720245} - { name: Kambah High,stop_code: Kambah High, lat: -35.3847749, lng: 149.0720245}
  - { name: Kambah / Livingston St,stop_code: Kambah / Livingston St, lat: -35.3883359, lng: 149.0811471}
- { name: Kambah Village,stop_code: Kambah Village, lat: -35.3800314, lng: 149.0576581} - { name: Kambah Village,stop_code: Kambah Village, lat: -35.3800314, lng: 149.0576581}
- { name: Katherine Ave / Horse Park Drive,stop_code: Katherine Ave / Horse Park Drive, lat: -35.1680901, lng: 149.1321801} - { name: Katherine Ave / Horse Park Drive,stop_code: Katherine Ave / Horse Park Drive, lat: -35.1680901, lng: 149.1321801}
- { name: Kerrigan / Lhotsky,stop_code: Kerrigan / Lhotsky, lat: -35.193801, lng: 149.035689} - { name: Kerrigan / Lhotsky,stop_code: Kerrigan / Lhotsky, lat: -35.193801, lng: 149.035689}
- { name: Kings Ave / National Circuit,stop_code: Kings Ave / National Circuit, lat: -35.305004, lng: 149.13262} - { name: Kings Ave / National Circuit,stop_code: Kings Ave / National Circuit, lat: -35.305004, lng: 149.13262}
- { name: Kingston,stop_code: Kingston, lat: -35.3197448, lng: 149.1375261} - { name: Kingston,stop_code: Kingston, lat: -35.3197448, lng: 149.1375261}
- { name: Kippax,stop_code: Kippax, lat: -35.22225, lng: 149.0195627} - { name: Kippax,stop_code: Kippax, lat: -35.22225, lng: 149.0195627}
- { name: Kippax Centre,stop_code: Kippax Centre, lat: -35.22172, lng: 149.01995} - { name: Kippax Centre,stop_code: Kippax Centre, lat: -35.22172, lng: 149.01995}
- { name: Kosciuszko / Everard,stop_code: Kosciuszko / Everard, lat: -35.188901, lng: 149.1216937} - { name: Kosciuszko / Everard,stop_code: Kosciuszko / Everard, lat: -35.188901, lng: 149.1216937}
- { name: Lanyon Market Place,stop_code: Lanyon Market Place, lat: -35.4573, lng: 149.09199} - { name: Lanyon Market Place,stop_code: Lanyon Market Place, lat: -35.4573, lng: 149.09199}
  - { name: Latham,stop_code: Latham, lat: -35.21848, lng: 149.03214}
- { name: Latham Post Office,stop_code: Latham Post Office, lat: -35.21906, lng: 149.03223} - { name: Latham Post Office,stop_code: Latham Post Office, lat: -35.21906, lng: 149.03223}
- { name: Latham Shops,stop_code: Latham Shops, lat: -35.21848, lng: 149.03214}  
- { name: Lathlain St Bus Station,stop_code: Lathlain St Bus Station, lat: -35.2396657, lng: 149.0633993} - { name: Lathlain St Bus Station,stop_code: Lathlain St Bus Station, lat: -35.2396657, lng: 149.0633993}
- { name: Lathlain St Bus Station (Platform 1),stop_code: Lathlain St Bus Station (Platform 1), lat: -35.2408973, lng: 149.0639887} - { name: Lathlain St Bus Station (Platform 1),stop_code: Lathlain St Bus Station (Platform 1), lat: -35.2408973, lng: 149.0639887}
- { name: Lathlain St Bus Station (Platform 2),stop_code: Lathlain St Bus Station (Platform 2), lat: -35.2406038, lng: 149.0638922} - { name: Lathlain St Bus Station (Platform 2),stop_code: Lathlain St Bus Station (Platform 2), lat: -35.2406038, lng: 149.0638922}
- { name: Lathlain St Bus Station (Platform 3),stop_code: Lathlain St Bus Station (Platform 3), lat: -35.2400517, lng: 149.0637152} - { name: Lathlain St Bus Station (Platform 3),stop_code: Lathlain St Bus Station (Platform 3), lat: -35.2400517, lng: 149.0637152}
- { name: Lathlain St Bus Station (Platform 4),stop_code: Lathlain St Bus Station (Platform 4), lat: -35.2396657, lng: 149.0633993} - { name: Lathlain St Bus Station (Platform 4),stop_code: Lathlain St Bus Station (Platform 4), lat: -35.2396657, lng: 149.0633993}
- { name: Lathlain St Bus Station (Platform 5),stop_code: Lathlain St Bus Station (Platform 5), lat: -35.2405468, lng: 149.0636669} - { name: Lathlain St Bus Station (Platform 5),stop_code: Lathlain St Bus Station (Platform 5), lat: -35.2405468, lng: 149.0636669}
- { name: Lathlain St Bus Station (Platform 6),stop_code: Lathlain St Bus Station (Platform 6), lat: -35.2410486, lng: 149.0638326} - { name: Lathlain St Bus Station (Platform 6),stop_code: Lathlain St Bus Station (Platform 6), lat: -35.2410486, lng: 149.0638326}
- { name: Lewis Luxton/Woodcock Dr,stop_code: Lewis Luxton/Woodcock Dr, lat: -35.4422566, lng: 149.0854375} - { name: Lewis Luxton/Woodcock Dr,stop_code: Lewis Luxton/Woodcock Dr, lat: -35.4422566, lng: 149.0854375}
- { name: Lithgow St Terminus Fyshwick,stop_code: Lithgow St Terminus Fyshwick, lat: -35.3296912, lng: 149.1668153} - { name: Lithgow St Terminus Fyshwick,stop_code: Lithgow St Terminus Fyshwick, lat: -35.3296912, lng: 149.1668153}
- { name: Livingston Shops Kambah,stop_code: Livingston Shops Kambah, lat: -35.3883359, lng: 149.0811471}  
- { name: Livingston Shops / Kambah,stop_code: Livingston Shops / Kambah, lat: -35.390246, lng: 149.07822}  
- { name: Lyneham,stop_code: Lyneham, lat: -35.2523304, lng: 149.1246184} - { name: Lyneham,stop_code: Lyneham, lat: -35.2523304, lng: 149.1246184}
- { name: Lyneham High,stop_code: Lyneham High, lat: -35.2524016, lng: 149.130254} - { name: Lyneham High,stop_code: Lyneham High, lat: -35.2524016, lng: 149.130254}
- { name: Lyneham Shops Wattle Street,stop_code: Lyneham Shops Wattle Street, lat: -35.25205, lng: 149.12524} - { name: Lyneham / Wattle St,stop_code: Lyneham / Wattle St, lat: -35.25205, lng: 149.12524}
- { name: Lyons,stop_code: Lyons, lat: -35.3415779, lng: 149.0765703} - { name: Lyons,stop_code: Lyons, lat: -35.3415779, lng: 149.0765703}
- { name: Lyons Shops,stop_code: Lyons Shops, lat: -35.34019, lng: 149.0771}  
- { name: Macarthur / Miller O'Connor,stop_code: Macarthur / Miller O'Connor, lat: -35.2587584, lng: 149.1153561} - { name: Macarthur / Miller O'Connor,stop_code: Macarthur / Miller O'Connor, lat: -35.2587584, lng: 149.1153561}
- { name: Macarthur / Northbourne Ave,stop_code: Macarthur / Northbourne Ave, lat: -35.26051, lng: 149.13224} - { name: Macarthur / Northbourne Ave,stop_code: Macarthur / Northbourne Ave, lat: -35.26051, lng: 149.13224}
- { name: Macgregor,stop_code: Macgregor, lat: -35.2100645, lng: 149.0122952} - { name: Macgregor,stop_code: Macgregor, lat: -35.2100645, lng: 149.0122952}
- { name: Macgregor Shops,stop_code: Macgregor Shops, lat: -35.2100645, lng: 149.0122952}  
- { name: MacKillop College Isabella Campus,stop_code: MacKillop College Isabella Campus, lat: -35.42597, lng: 149.09172} - { name: MacKillop College Isabella Campus,stop_code: MacKillop College Isabella Campus, lat: -35.42597, lng: 149.09172}
- { name: MacKillop College Wanniassa Campus,stop_code: MacKillop College Wanniassa Campus, lat: -35.4056, lng: 149.089774} - { name: MacKillop College Wanniassa Campus,stop_code: MacKillop College Wanniassa Campus, lat: -35.4056, lng: 149.089774}
- { name: Macquarie,stop_code: Macquarie, lat: -35.2483414, lng: 149.0600666} - { name: Macquarie,stop_code: Macquarie, lat: -35.2483414, lng: 149.0600666}
- { name: Majura Business Park,stop_code: Majura Business Park, lat: -35.2987, lng: 149.18561} - { name: Majura Business Park,stop_code: Majura Business Park, lat: -35.2987, lng: 149.18561}
- { name: Manning Clarke / Oodgeroo,stop_code: Manning Clarke / Oodgeroo, lat: -35.193236, lng: 149.146534} - { name: Manning Clarke / Oodgeroo,stop_code: Manning Clarke / Oodgeroo, lat: -35.193236, lng: 149.146534}
- { name: Manuka,stop_code: Manuka, lat: -35.3200096, lng: 149.1341344} - { name: Manuka,stop_code: Manuka, lat: -35.3200096, lng: 149.1341344}
- { name: Manuka / Captain Cook Cres,stop_code: Manuka / Captain Cook Cres, lat: -35.3217, lng: 149.13445} - { name: Manuka / Captain Cook Cres,stop_code: Manuka / Captain Cook Cres, lat: -35.3217, lng: 149.13445}
- { name: McKellar,stop_code: McKellar, lat: -35.2174267, lng: 149.0742108} - { name: McKellar,stop_code: McKellar, lat: -35.2174267, lng: 149.0742108}
- { name: McKellar Shops,stop_code: McKellar Shops, lat: -35.2182, lng: 149.07555}  
- { name: Melba,stop_code: Melba, lat: -35.2083104, lng: 149.0485366} - { name: Melba,stop_code: Melba, lat: -35.2083104, lng: 149.0485366}
- { name: Melba Shops,stop_code: Melba Shops, lat: -35.21004, lng: 149.05302}  
- { name: Mentone View / Tharwa Drive,stop_code: Mentone View / Tharwa Drive, lat: -35.45144, lng: 149.0919} - { name: Mentone View / Tharwa Drive,stop_code: Mentone View / Tharwa Drive, lat: -35.45144, lng: 149.0919}
- { name: Merici College,stop_code: Merici College, lat: -35.266525, lng: 149.137037} - { name: Merici College,stop_code: Merici College, lat: -35.266525, lng: 149.137037}
- { name: Mirrabei Drive / Dam Wall,stop_code: Mirrabei Drive / Dam Wall, lat: -35.177453, lng: 149.124291} - { name: Mirrabei Drive / Dam Wall,stop_code: Mirrabei Drive / Dam Wall, lat: -35.177453, lng: 149.124291}
- { name: Monash,stop_code: Monash, lat: -35.4190254, lng: 149.0834805} - { name: Monash,stop_code: Monash, lat: -35.4190254, lng: 149.0834805}
- { name: Monash Goodwin Village,stop_code: Monash Goodwin Village, lat: -35.421084, lng: 149.097438} - { name: Monash Goodwin Village,stop_code: Monash Goodwin Village, lat: -35.421084, lng: 149.097438}
- { name: Monash Primary,stop_code: Monash Primary, lat: -35.414879, lng: 149.089411} - { name: Monash Primary,stop_code: Monash Primary, lat: -35.414879, lng: 149.089411}
- { name: Mount Neighbour School,stop_code: Mount Neighbour School, lat: -35.382445, lng: 149.051518} - { name: Mount Neighbour School,stop_code: Mount Neighbour School, lat: -35.382445, lng: 149.051518}
- { name: Narrabundah,stop_code: Narrabundah, lat: -35.332605, lng: 149.154049} - { name: Narrabundah,stop_code: Narrabundah, lat: -35.332605, lng: 149.154049}
- { name: Narrabundah College,stop_code: Narrabundah College, lat: -35.3362106, lng: 149.1471005} - { name: Narrabundah College,stop_code: Narrabundah College, lat: -35.3362106, lng: 149.1471005}
- { name: Narrabundah Terminus,stop_code: Narrabundah Terminus, lat: -35.332605, lng: 149.154049} - { name: Narrabundah Terminus,stop_code: Narrabundah Terminus, lat: -35.332605, lng: 149.154049}
- { name: National Circ / Canberra Ave,stop_code: National Circ / Canberra Ave, lat: -35.31407, lng: 149.13011} - { name: National Circ / Canberra Ave,stop_code: National Circ / Canberra Ave, lat: -35.31407, lng: 149.13011}
- { name: National Hockey Centre Lyneham,stop_code: National Hockey Centre Lyneham, lat: -35.2446729, lng: 149.1288303} - { name: National Hockey Centre Lyneham,stop_code: National Hockey Centre Lyneham, lat: -35.2446729, lng: 149.1288303}
- { name: National Museum of Australia,stop_code: National Museum of Australia, lat: -35.29248, lng: 149.1205367} - { name: National Museum of Australia,stop_code: National Museum of Australia, lat: -35.29248, lng: 149.1205367}
- { name: National Zoo and Aquarium,stop_code: National Zoo and Aquarium, lat: -35.29915, lng: 149.07025} - { name: National Zoo and Aquarium,stop_code: National Zoo and Aquarium, lat: -35.29915, lng: 149.07025}
- { name: Newcastle Street after Isa Street,stop_code: Newcastle Street after Isa Street, lat: -35.3255, lng: 149.173291} - { name: Newcastle Street after Isa Street,stop_code: Newcastle Street after Isa Street, lat: -35.3255, lng: 149.173291}
- { name: Ngunnawal Primary,stop_code: Ngunnawal Primary, lat: -35.1688551, lng: 149.1112569} - { name: Ngunnawal Primary,stop_code: Ngunnawal Primary, lat: -35.1688551, lng: 149.1112569}
- { name: Nicholls Primary,stop_code: Nicholls Primary, lat: -35.1905592, lng: 149.0876716} - { name: Nicholls Primary,stop_code: Nicholls Primary, lat: -35.1905592, lng: 149.0876716}
- { name: Northbourne Avenue / Antill St,stop_code: Northbourne Avenue / Antill St, lat: -35.248287, lng: 149.134241} - { name: Northbourne Avenue / Antill St,stop_code: Northbourne Avenue / Antill St, lat: -35.248287, lng: 149.134241}
- { name: North Lyneham,stop_code: North Lyneham, lat: -35.2385618, lng: 149.1221188} - { name: North Lyneham,stop_code: North Lyneham, lat: -35.2385618, lng: 149.1221188}
- { name: O'Connor,stop_code: O'Connor, lat: -35.2640376, lng: 149.1226107} - { name: O'Connor,stop_code: O'Connor, lat: -35.2640376, lng: 149.1226107}
- { name: O'Connor Shops,stop_code: O'Connor Shops, lat: -35.2640376, lng: 149.1226107}  
- { name: Olims Hotel,stop_code: Olims Hotel, lat: -35.27597, lng: 149.1428} - { name: Olims Hotel,stop_code: Olims Hotel, lat: -35.27597, lng: 149.1428}
- { name: Outtrim / Duggan,stop_code: Outtrim / Duggan, lat: -35.435871, lng: 149.097692} - { name: Outtrim / Duggan,stop_code: Outtrim / Duggan, lat: -35.435871, lng: 149.097692}
- { name: Page Shops,stop_code: Page Shops, lat: -35.2360695, lng: 149.0536554} - { name: Page,stop_code: Page, lat: -35.2360695, lng: 149.0536554}
- { name: Parliament House,stop_code: Parliament House, lat: -35.3081571, lng: 149.1244592} - { name: Parliament House,stop_code: Parliament House, lat: -35.3081571, lng: 149.1244592}
- { name: Paul Coe / Mirrabei Dr,stop_code: Paul Coe / Mirrabei Dr, lat: -35.17467, lng: 149.12005} - { name: Paul Coe / Mirrabei Dr,stop_code: Paul Coe / Mirrabei Dr, lat: -35.17467, lng: 149.12005}
- { name: Pearce,stop_code: Pearce, lat: -35.3625413, lng: 149.0815935} - { name: Pearce,stop_code: Pearce, lat: -35.3625413, lng: 149.0815935}
- { name: Pearce Shops,stop_code: Pearce Shops, lat: -35.3625413, lng: 149.0815935}  
- { name: Police College Weston,stop_code: Police College Weston, lat: -35.33018, lng: 149.05458} - { name: Police College Weston,stop_code: Police College Weston, lat: -35.33018, lng: 149.05458}
- { name: Proctor / Mead,stop_code: Proctor / Mead, lat: -35.415305, lng: 149.127204} - { name: Proctor / Mead,stop_code: Proctor / Mead, lat: -35.415305, lng: 149.127204}
- { name: Railway Station Kingston,stop_code: Railway Station Kingston, lat: -35.319602, lng: 149.149083} - { name: Railway Station Kingston,stop_code: Railway Station Kingston, lat: -35.319602, lng: 149.149083}
- { name: Red Hill,stop_code: Red Hill, lat: -35.336505, lng: 149.131645} - { name: Red Hill,stop_code: Red Hill, lat: -35.336505, lng: 149.131645}
- { name: Red Hill Shops,stop_code: Red Hill Shops, lat: -35.336505, lng: 149.131645}  
- { name: Rivett,stop_code: Rivett, lat: -35.3473758, lng: 149.0365438} - { name: Rivett,stop_code: Rivett, lat: -35.3473758, lng: 149.0365438}
- { name: Rivett Shops,stop_code: Rivett Shops, lat: -35.34737, lng: 149.03654}  
- { name: Russell Offices,stop_code: Russell Offices, lat: -35.2973294, lng: 149.1508803} - { name: Russell Offices,stop_code: Russell Offices, lat: -35.2973294, lng: 149.1508803}
- { name: Sainsbury Street,stop_code: Sainsbury Street, lat: -35.3885, lng: 149.09643} - { name: Sainsbury Street,stop_code: Sainsbury Street, lat: -35.3885, lng: 149.09643}
- { name: Saint Andrews Village Hughes,stop_code: Saint Andrews Village Hughes, lat: -35.328097, lng: 149.088685} - { name: Saint Andrews Village Hughes,stop_code: Saint Andrews Village Hughes, lat: -35.328097, lng: 149.088685}
- { name: Scullin Shops,stop_code: Scullin Shops, lat: -35.23356, lng: 149.04056} - { name: Scullin,stop_code: Scullin, lat: -35.23356, lng: 149.04056}
- { name: Shoalhaven / Katherine Ave,stop_code: Shoalhaven / Katherine Ave, lat: -35.16823, lng: 149.12791} - { name: Shoalhaven / Katherine Ave,stop_code: Shoalhaven / Katherine Ave, lat: -35.16823, lng: 149.12791}
- { name: Southlands Mawson,stop_code: Southlands Mawson, lat: -35.3650685, lng: 149.0945962} - { name: Southlands Mawson,stop_code: Southlands Mawson, lat: -35.3650685, lng: 149.0945962}
- { name: Southwell Park,stop_code: Southwell Park, lat: -35.24573, lng: 149.1321} - { name: Southwell Park,stop_code: Southwell Park, lat: -35.24573, lng: 149.1321}
- { name: Spence,stop_code: Spence, lat: -35.194735, lng: 149.062352} - { name: Spence,stop_code: Spence, lat: -35.194735, lng: 149.062352}
- { name: Spence Shops,stop_code: Spence Shops, lat: -35.19968, lng: 149.06763}  
- { name: Spence Terminus,stop_code: Spence Terminus, lat: -35.199684, lng: 149.0676196} - { name: Spence Terminus,stop_code: Spence Terminus, lat: -35.199684, lng: 149.0676196}
- { name: St Clare of Assisi,stop_code: St Clare of Assisi, lat: -35.46063, lng: 149.09627}  
- { name: St Clare of Assisi Primary,stop_code: St Clare of Assisi Primary, lat: -35.4606284, lng: 149.0962704} - { name: St Clare of Assisi Primary,stop_code: St Clare of Assisi Primary, lat: -35.4606284, lng: 149.0962704}
- { name: St Francis Xavier Florey,stop_code: St Francis Xavier Florey, lat: -35.223951, lng: 149.0406888} - { name: St Francis Xavier Florey,stop_code: St Francis Xavier Florey, lat: -35.223951, lng: 149.0406888}
- { name: Stromlo High Waramanga,stop_code: Stromlo High Waramanga, lat: -35.3551186, lng: 149.0547624} - { name: Stromlo High Waramanga,stop_code: Stromlo High Waramanga, lat: -35.3551186, lng: 149.0547624}
- { name: St Thomas More's Campbell,stop_code: St Thomas More's Campbell, lat: -35.286717, lng: 149.156836} - { name: St Thomas More's Campbell,stop_code: St Thomas More's Campbell, lat: -35.286717, lng: 149.156836}
- { name: Sydney Ave,stop_code: Sydney Ave, lat: -35.31193, lng: 149.13105} - { name: Sydney Ave,stop_code: Sydney Ave, lat: -35.31193, lng: 149.13105}
- { name: Taverner St / Erindale Dr,stop_code: Taverner St / Erindale Dr, lat: -35.4059104, lng: 149.0809317} - { name: Taverner St / Erindale Dr,stop_code: Taverner St / Erindale Dr, lat: -35.4059104, lng: 149.0809317}
- { name: Tharwa Drive,stop_code: Tharwa Drive, lat: -35.458251, lng: 149.091652} - { name: Tharwa Drive,stop_code: Tharwa Drive, lat: -35.458251, lng: 149.091652}
- { name: Tharwa Drive / Knoke Ave,stop_code: Tharwa Drive / Knoke Ave, lat: -35.47281, lng: 149.08926} - { name: Tharwa Drive / Knoke Ave,stop_code: Tharwa Drive / Knoke Ave, lat: -35.47281, lng: 149.08926}
- { name: Tharwa Dr / Pockett Ave,stop_code: Tharwa Dr / Pockett Ave, lat: -35.47348, lng: 149.09178} - { name: Tharwa Drive / Pockett Ave,stop_code: Tharwa Drive / Pockett Ave, lat: -35.47348, lng: 149.09178}
- { name: Theodore,stop_code: Theodore, lat: -35.4464808, lng: 149.1234651} - { name: Theodore,stop_code: Theodore, lat: -35.4464808, lng: 149.1234651}
- { name: Tillyard / Spalding,stop_code: Tillyard / Spalding, lat: -35.199204, lng: 149.044556} - { name: Tillyard / Spalding,stop_code: Tillyard / Spalding, lat: -35.199204, lng: 149.044556}
- { name: Torrens Shops,stop_code: Torrens Shops, lat: -35.3730889, lng: 149.087327} - { name: Torrens,stop_code: Torrens, lat: -35.3730889, lng: 149.087327}
- { name: Tuggeranong Bus Station,stop_code: Tuggeranong Bus Station, lat: -35.41465, lng: 149.06537} - { name: Tuggeranong Bus Station,stop_code: Tuggeranong Bus Station, lat: -35.41465, lng: 149.06537}
- { name: Tuggeranong Bus Station (Platform 3),stop_code: Tuggeranong Bus Station (Platform 3), lat: -35.4147569, lng: 149.0657435} - { name: Tuggeranong Bus Station (Platform 3),stop_code: Tuggeranong Bus Station (Platform 3), lat: -35.4147569, lng: 149.0657435}
- { name: Tuggeranong Bus Station (Platform 4),stop_code: Tuggeranong Bus Station (Platform 4), lat: -35.4144924, lng: 149.0655423} - { name: Tuggeranong Bus Station (Platform 4),stop_code: Tuggeranong Bus Station (Platform 4), lat: -35.4144924, lng: 149.0655423}
- { name: Tuggeranong Bus Station (Platform 5),stop_code: Tuggeranong Bus Station (Platform 5), lat: -35.414217, lng: 149.0653492} - { name: Tuggeranong Bus Station (Platform 5),stop_code: Tuggeranong Bus Station (Platform 5), lat: -35.414217, lng: 149.0653492}
- { name: Tuggeranong Bus Station (Platform 7),stop_code: Tuggeranong Bus Station (Platform 7), lat: -35.4146761, lng: 149.0654565} - { name: Tuggeranong Bus Station (Platform 7),stop_code: Tuggeranong Bus Station (Platform 7), lat: -35.4146761, lng: 149.0654565}
- { name: Tuggeranong Bus Station (Platform 8),stop_code: Tuggeranong Bus Station (Platform 8), lat: -35.4149428, lng: 149.0656523} - { name: Tuggeranong Bus Station (Platform 8),stop_code: Tuggeranong Bus Station (Platform 8), lat: -35.4149428, lng: 149.0656523}
- { name: University of Canberra,stop_code: University of Canberra, lat: -35.2423222, lng: 149.0831522} - { name: University of Canberra,stop_code: University of Canberra, lat: -35.2423222, lng: 149.0831522}
- { name: Wanniassa High,stop_code: Wanniassa High, lat: -35.3952462, lng: 149.0852655} - { name: Wanniassa High,stop_code: Wanniassa High, lat: -35.3952462, lng: 149.0852655}
- { name: Waramanga,stop_code: Waramanga, lat: -35.3526825, lng: 149.0594712} - { name: Waramanga,stop_code: Waramanga, lat: -35.3526825, lng: 149.0594712}
- { name: Waramanga Shops,stop_code: Waramanga Shops, lat: -35.35268, lng: 149.05948} - { name: War Memorial / Limestone Ave,stop_code: War Memorial / Limestone Ave, lat: -35.280477, lng: 149.149085}
- { name: War Memorial Limestone Ave,stop_code: War Memorial Limestone Ave, lat: -35.280477, lng: 149.149085}  
- { name: Watson,stop_code: Watson, lat: -35.2389399, lng: 149.1535345} - { name: Watson,stop_code: Watson, lat: -35.2389399, lng: 149.1535345}
- { name: Watson Shops,stop_code: Watson Shops, lat: -35.2389399, lng: 149.1535345}  
- { name: Watson Terminus,stop_code: Watson Terminus, lat: -35.2374698, lng: 149.1534553} - { name: Watson Terminus,stop_code: Watson Terminus, lat: -35.2374698, lng: 149.1534553}
- { name: Weetangera Shops,stop_code: Weetangera Shops, lat: -35.248393, lng: 149.0506342} - { name: Weetangera,stop_code: Weetangera, lat: -35.248393, lng: 149.0506342}
- { name: Westfield Bus Station,stop_code: Westfield Bus Station, lat: -35.23875, lng: 149.0638} - { name: Westfield Bus Station,stop_code: Westfield Bus Station, lat: -35.23875, lng: 149.0638}
- { name: Westfield Bus Station (Platform 1),stop_code: Westfield Bus Station (Platform 1), lat: -35.23872, lng: 149.06387} - { name: Westfield Bus Station (Platform 1),stop_code: Westfield Bus Station (Platform 1), lat: -35.23872, lng: 149.06387}
- { name: Westfield Bus Station (Platform 2),stop_code: Westfield Bus Station (Platform 2), lat: -35.23882, lng: 149.0637} - { name: Westfield Bus Station (Platform 2),stop_code: Westfield Bus Station (Platform 2), lat: -35.23882, lng: 149.0637}
- { name: West Macgregor,stop_code: West Macgregor, lat: -35.21207, lng: 149.00165} - { name: West Macgregor,stop_code: West Macgregor, lat: -35.21207, lng: 149.00165}
- { name: Weston Creek Terminus,stop_code: Weston Creek Terminus, lat: -35.342728, lng: 149.0524906} - { name: Weston Creek Terminus,stop_code: Weston Creek Terminus, lat: -35.342728, lng: 149.0524906}
- { name: Weston Primary,stop_code: Weston Primary, lat: -35.3305221, lng: 149.0524281} - { name: Weston Primary,stop_code: Weston Primary, lat: -35.3305221, lng: 149.0524281}
- { name: William Webb / Ginninderra Drive,stop_code: William Webb / Ginninderra Drive, lat: -35.222395, lng: 149.0706} - { name: William Webb / Ginninderra Drive,stop_code: William Webb / Ginninderra Drive, lat: -35.222395, lng: 149.0706}
- { name: Woden Bus Station,stop_code: Woden Bus Station, lat: -35.34433, lng: 149.08742} - { name: Woden Bus Station,stop_code: Woden Bus Station, lat: -35.34433, lng: 149.08742}
- { name: Woden Bus Station (Platform 10),stop_code: Woden Bus Station (Platform 10), lat: -35.3439501, lng: 149.0877369} - { name: Woden Bus Station (Platform 10),stop_code: Woden Bus Station (Platform 10), lat: -35.3439501, lng: 149.0877369}
- { name: Woden Bus Station (Platform 11),stop_code: Woden Bus Station (Platform 11), lat: -35.3439129, lng: 149.0876216} - { name: Woden Bus Station (Platform 11),stop_code: Woden Bus Station (Platform 11), lat: -35.3439129, lng: 149.0876216}
- { name: Woden Bus Station (Platform 12),stop_code: Woden Bus Station (Platform 12), lat: -35.3442094, lng: 149.0876444} - { name: Woden Bus Station (Platform 12),stop_code: Woden Bus Station (Platform 12), lat: -35.3442094, lng: 149.0876444}
- { name: Woden Bus Station (Platform 14),stop_code: Woden Bus Station (Platform 14), lat: -35.34438, lng: 149.0872662} - { name: Woden Bus Station (Platform 14),stop_code: Woden Bus Station (Platform 14), lat: -35.34438, lng: 149.0872662}
- { name: Woden Bus Station (Platform 15),stop_code: Woden Bus Station (Platform 15), lat: -35.3444271, lng: 149.0869631} - { name: Woden Bus Station (Platform 15),stop_code: Woden Bus Station (Platform 15), lat: -35.3444271, lng: 149.0869631}
- { name: Woden Bus Station (Platform 16),stop_code: Woden Bus Station (Platform 16), lat: -35.344484, lng: 149.0866144} - { name: Woden Bus Station (Platform 16),stop_code: Woden Bus Station (Platform 16), lat: -35.344484, lng: 149.0866144}
- { name: Woden Bus Station (Platform 2),stop_code: Woden Bus Station (Platform 2), lat: -35.3447574, lng: 149.0862912} - { name: Woden Bus Station (Platform 2),stop_code: Woden Bus Station (Platform 2), lat: -35.3447574, lng: 149.0862912}
- { name: Woden Bus Station (Platform 3),stop_code: Woden Bus Station (Platform 3), lat: -35.344566, lng: 149.086774} - { name: Woden Bus Station (Platform 3),stop_code: Woden Bus Station (Platform 3), lat: -35.344566, lng: 149.086774}
- { name: Woden Bus Station (Platform 4),stop_code: Woden Bus Station (Platform 4), lat: -35.3445222, lng: 149.0870436} - { name: Woden Bus Station (Platform 4),stop_code: Woden Bus Station (Platform 4), lat: -35.3445222, lng: 149.0870436}
- { name: Woden Bus Station (Platform 5),stop_code: Woden Bus Station (Platform 5), lat: -35.3444741, lng: 149.0873533} - { name: Woden Bus Station (Platform 5),stop_code: Woden Bus Station (Platform 5), lat: -35.3444741, lng: 149.0873533}
- { name: Woden Bus Station (Platform 6),stop_code: Woden Bus Station (Platform 6), lat: -35.34445, lng: 149.0875371} - { name: Woden Bus Station (Platform 6),stop_code: Woden Bus Station (Platform 6), lat: -35.34445, lng: 149.0875371}
- { name: Woden Bus Station (Platform 9),stop_code: Woden Bus Station (Platform 9), lat: -35.3442083, lng: 149.0877771} - { name: Woden Bus Station (Platform 9),stop_code: Woden Bus Station (Platform 9), lat: -35.3442083, lng: 149.0877771}
- { name: Woodcock / Clare Dennis,stop_code: Woodcock / Clare Dennis, lat: -35.4422566, lng: 149.0854375} - { name: Woodcock / Clare Dennis,stop_code: Woodcock / Clare Dennis, lat: -35.4422566, lng: 149.0854375}
- { name: Yarralumla Shops,stop_code: Yarralumla Shops, lat: -35.30725, lng: 149.0972} - { name: Yarralumla,stop_code: Yarralumla, lat: -35.30725, lng: 149.0972}
- { name: Andrea Place,stop_code: Wjz1ceG, lat: -35.4375289, lng: 149.0757996} - { name: Andrea Place,stop_code: Wjz1ceG, lat: -35.4375289, lng: 149.0757996}
- { name: Tarlton Place,stop_code: Wjz1kvl, lat: -35.4366017, lng: 149.0890756} - { name: Tarlton Place,stop_code: Wjz1kvl, lat: -35.4366017, lng: 149.0890756}
- { name: Don Dunstan Drive,stop_code: Wjz16U7, lat: -35.4302659, lng: 149.0722593} - { name: Don Dunstan Drive,stop_code: Wjz16U7, lat: -35.4302659, lng: 149.0722593}
- { name: Salmon Place,stop_code: WjrWY3_, lat: -35.3952466, lng: 149.0527528} - { name: Salmon Place,stop_code: WjrWY3_, lat: -35.3952466, lng: 149.0527528}
- { name: Crozier Circuit,stop_code: WjrWSUa, lat: -35.3867455, lng: 149.0504459} - { name: Crozier Circuit,stop_code: WjrWSUa, lat: -35.3867455, lng: 149.0504459}
- { name: Mouat Street,stop_code: Wjz5LYB, lat: -35.2464052, lng: 149.1278592} - { name: Mouat Street,stop_code: Wjz5LYB, lat: -35.2464052, lng: 149.1278592}
- { name: Mackennal Street,stop_code: Wjz5LsC, lat: -35.2463364, lng: 149.1223897} - { name: Mackennal Street,stop_code: Wjz5LsC, lat: -35.2463364, lng: 149.1223897}
- { name: Clianthus Street,stop_code: Wjz5Krx, lat: -35.2529666, lng: 149.1223781} - { name: Clianthus Street,stop_code: Wjz5Krx, lat: -35.2529666, lng: 149.1223781}
- { name: Way Street,stop_code: Wjz5BWh, lat: -35.2591172, lng: 149.1164155} - { name: Way Street,stop_code: Wjz5BWh, lat: -35.2591172, lng: 149.1164155}
- { name: Cockle Street,stop_code: Wjz5AGB, lat: -35.2642702, lng: 149.1141435} - { name: Cockle Street,stop_code: Wjz5AGB, lat: -35.2642702, lng: 149.1141435}
- { name: Froggatt Street,stop_code: Wjz5H0p, lat: -35.2714838, lng: 149.1180142} - { name: Froggatt Street,stop_code: Wjz5H0p, lat: -35.2714838, lng: 149.1180142}
- { name: McClintock Street,stop_code: Wjz6ElH, lat: -35.2404264, lng: 149.1210434} - { name: McClintock Street,stop_code: Wjz6ElH, lat: -35.2404264, lng: 149.1210434}
- { name: Cossington Smith Crescent,stop_code: Wjz6FEI, lat: -35.2382959, lng: 149.1252507} - { name: Cossington Smith Crescent,stop_code: Wjz6FEI, lat: -35.2382959, lng: 149.1252507}
- { name: Dumas Street,stop_code: Wjz6cz2, lat: -35.2199304, lng: 149.0791416} - { name: Dumas Street,stop_code: Wjz6cz2, lat: -35.2199304, lng: 149.0791416}
- { name: Buggy Crescent,stop_code: Wjz64OE, lat: -35.2207286, lng: 149.0717368} - { name: Buggy Crescent,stop_code: Wjz64OE, lat: -35.2207286, lng: 149.0717368}
- { name: Owen Dixon Drive,stop_code: Wjz6eWi, lat: -35.2096321, lng: 149.0835148} - { name: Owen Dixon Drive,stop_code: Wjz6eWi, lat: -35.2096321, lng: 149.0835148}
- { name: Baldwin Drive,stop_code: Wjz6kCT, lat: -35.217402, lng: 149.0910262} - { name: Baldwin Drive,stop_code: Wjz6kCT, lat: -35.217402, lng: 149.0910262}
- { name: Jacob Place,stop_code: Wjr-TRM, lat: -35.2021703, lng: 149.0498418} - { name: Jacob Place,stop_code: Wjr-TRM, lat: -35.2021703, lng: 149.0498418}
- { name: Love Street,stop_code: Wjr_MMi, lat: -35.200018, lng: 149.0491234} - { name: Love Street,stop_code: Wjr_MMi, lat: -35.200018, lng: 149.0491234}
- { name: Box Place,stop_code: Wjr-IeY, lat: -35.2176259, lng: 149.032238} - { name: Box Place,stop_code: Wjr-IeY, lat: -35.2176259, lng: 149.032238}
- { name: Macrossan Crescent,stop_code: Wjr-J8t, lat: -35.2161747, lng: 149.0315719} - { name: Macrossan Crescent,stop_code: Wjr-J8t, lat: -35.2161747, lng: 149.0315719}
- { name: Want Place,stop_code: Wjr-Jm9, lat: -35.2124379, lng: 149.0325045} - { name: Want Place,stop_code: Wjr-Jm9, lat: -35.2124379, lng: 149.0325045}
- { name: Fellows Street,stop_code: Wjr-J44, lat: -35.2135626, lng: 149.0296181} - { name: Fellows Street,stop_code: Wjr-J44, lat: -35.2135626, lng: 149.0296181}
- { name: Osburn Drive,stop_code: Wjr-BB3, lat: -35.2129096, lng: 149.0241561} - { name: Osburn Drive,stop_code: Wjr-BB3, lat: -35.2129096, lng: 149.0241561}
- { name: Solomon Crescent,stop_code: Wjr-AY4, lat: -35.2190044, lng: 149.0282415} - { name: Solomon Crescent,stop_code: Wjr-AY4, lat: -35.2190044, lng: 149.0282415}
- { name: Onslow Street,stop_code: Wjr-IcO, lat: -35.2191858, lng: 149.0319716} - { name: Onslow Street,stop_code: Wjr-IcO, lat: -35.2191858, lng: 149.0319716}
- { name: Onslow Street,stop_code: Wjr-IqS, lat: -35.2202741, lng: 149.034858} - { name: Onslow Street,stop_code: Wjr-IqS, lat: -35.2202741, lng: 149.034858}
- { name: Kingsford Smith Drive,stop_code: Wjr-H-a, lat: -35.2232851, lng: 149.039343} - { name: Kingsford Smith Drive,stop_code: Wjr-H-a, lat: -35.2232851, lng: 149.039343}
- { name: Krefft Street,stop_code: Wjr-Q4G, lat: -35.2192221, lng: 149.0415189} - { name: Krefft Street,stop_code: Wjr-Q4G, lat: -35.2192221, lng: 149.0415189}
- { name: Maribyrnong Avenue,stop_code: Wjz6zon, lat: -35.2269858, lng: 149.1109391} - { name: Maribyrnong Avenue,stop_code: Wjz6zon, lat: -35.2269858, lng: 149.1109391}
- { name: Maribyrnong Avenue,stop_code: Wjz6ytu, lat: -35.2291622, lng: 149.1110812} - { name: Maribyrnong Avenue,stop_code: Wjz6ytu, lat: -35.2291622, lng: 149.1110812}
- { name: Belconnen Way,stop_code: Wjz5mpm, lat: -35.2538531, lng: 149.0889493} - { name: Belconnen Way,stop_code: Wjz5mpm, lat: -35.2538531, lng: 149.0889493}
- { name: Belconnen Way,stop_code: Wjz5mxf, lat: -35.2538241, lng: 149.0902637} - { name: Belconnen Way,stop_code: Wjz5mxf, lat: -35.2538241, lng: 149.0902637}
- { name: Belconnen Way,stop_code: Wjr-MNh, lat: -35.2433401, lng: 149.0492618} - { name: Belconnen Way,stop_code: Wjr-MNh, lat: -35.2433401, lng: 149.0492618}
- { name: Belconnen Way,stop_code: Wjr-Mqd, lat: -35.2422956, lng: 149.0448568} - { name: Belconnen Way,stop_code: Wjr-Mqd, lat: -35.2422956, lng: 149.0448568}
- { name: Belconnen Way,stop_code: Wjr-EYe, lat: -35.2408449, lng: 149.0394925} - { name: Belconnen Way,stop_code: Wjr-EYe, lat: -35.2408449, lng: 149.0394925}
- { name: Belconnen Way,stop_code: Wjr-EA_, lat: -35.2407288, lng: 149.0362953} - { name: Belconnen Way,stop_code: Wjr-EA_, lat: -35.2407288, lng: 149.0362953}
- { name: Mackinolty Street,stop_code: Wjr-Fw4, lat: -35.2382916, lng: 149.035194} - { name: Mackinolty Street,stop_code: Wjr-Fw4, lat: -35.2382916, lng: 149.035194}
- { name: Challinor Crescent,stop_code: Wjr-Vnf, lat: -35.2331848, lng: 149.054555} - { name: Challinor Crescent,stop_code: Wjr-Vnf, lat: -35.2331848, lng: 149.054555}
- { name: Lightfoot Crescent,stop_code: Wjr-Ws2, lat: -35.230167, lng: 149.0557628} - { name: Lightfoot Crescent,stop_code: Wjr-Ws2, lat: -35.230167, lng: 149.0557628}
- { name: Nanson Place,stop_code: Wjr-PyX, lat: -35.2259882, lng: 149.0472724} - { name: Nanson Place,stop_code: Wjr-PyX, lat: -35.2259882, lng: 149.0472724}
- { name: Kulgera Street,stop_code: WjrZKZn, lat: -35.2510294, lng: 149.0396391} - { name: Kulgera Street,stop_code: WjrZKZn, lat: -35.2510294, lng: 149.0396391}
- { name: King Edward Terrace,stop_code: Wjz4S1U, lat: -35.2983385, lng: 149.1296979} - { name: King Edward Terrace,stop_code: Wjz4S1U, lat: -35.2983385, lng: 149.1296979}
- { name: King George Terrace,stop_code: Wjz4RbQ, lat: -35.3021238, lng: 149.1308574} - { name: King George Terrace,stop_code: Wjz4RbQ, lat: -35.3021238, lng: 149.1308574}
- { name: James Street,stop_code: Wjz3fCx, lat: -35.333256, lng: 149.0798309} - { name: James Street,stop_code: Wjz3fCx, lat: -35.333256, lng: 149.0798309}
- { name: Kent Street,stop_code: Wjz4peM, lat: -35.322342, lng: 149.0979263} - { name: Kent Street,stop_code: Wjz4peM, lat: -35.322342, lng: 149.0979263}
- { name: Fuller Street,stop_code: Wjz4qgy, lat: -35.3208475, lng: 149.098981} - { name: Fuller Street,stop_code: Wjz4qgy, lat: -35.3208475, lng: 149.098981}
- { name: Hopetoun Circuit,stop_code: Wjz4A7o, lat: -35.3052441, lng: 149.107042} - { name: Hopetoun Circuit,stop_code: Wjz4A7o, lat: -35.3052441, lng: 149.107042}
- { name: De Chair Street,stop_code: Wjz4qTw, lat: -35.3162151, lng: 149.1045086} - { name: De Chair Street,stop_code: Wjz4qTw, lat: -35.3162151, lng: 149.1045086}
- { name: Macgregor Street,stop_code: Wjz4qs0, lat: -35.3182278, lng: 149.09964} - { name: Macgregor Street,stop_code: Wjz4qs0, lat: -35.3182278, lng: 149.09964}
- { name: Stonehaven Crescent,stop_code: Wjz4yzk, lat: -35.3186155, lng: 149.1123352} - { name: Stonehaven Crescent,stop_code: Wjz4yzk, lat: -35.3186155, lng: 149.1123352}
- { name: Dominion Circuit,stop_code: Wjz4H0P, lat: -35.3152936, lng: 149.1185178} - { name: Dominion Circuit,stop_code: Wjz4H0P, lat: -35.3152936, lng: 149.1185178}
- { name: Schlich Street,stop_code: Wjz4tpE, lat: -35.3038329, lng: 149.1005569} - { name: Schlich Street,stop_code: Wjz4tpE, lat: -35.3038329, lng: 149.1005569}
- { name: Weston Street,stop_code: Wjz4z67, lat: -35.3107704, lng: 149.1065979} - { name: Weston Street,stop_code: Wjz4z67, lat: -35.3107704, lng: 149.1065979}
- { name: Musgrave Street,stop_code: Wjz4tUp, lat: -35.3044055, lng: 149.1056974} - { name: Musgrave Street,stop_code: Wjz4tUp, lat: -35.3044055, lng: 149.1056974}
- { name: Hopetoun Circuit,stop_code: Wjz4A2c, lat: -35.3082791, lng: 149.1066534} - { name: Hopetoun Circuit,stop_code: Wjz4A2c, lat: -35.3082791, lng: 149.1066534}
- { name: Lienhop Street,stop_code: Wjz1HTi, lat: -35.4423392, lng: 149.1260397} - { name: Lienhop Street,stop_code: Wjz1HTi, lat: -35.4423392, lng: 149.1260397}
- { name: Hartung Crescent,stop_code: Wjz1zN3, lat: -35.4464057, lng: 149.1147796} - { name: Hartung Crescent,stop_code: Wjz1zN3, lat: -35.4464057, lng: 149.1147796}
- { name: Lawrence Wackett Crescent,stop_code: Wjz1HEb, lat: -35.4471149, lng: 149.1245306} - { name: Lawrence Wackett Crescent,stop_code: Wjz1HEb, lat: -35.4471149, lng: 149.1245306}
- { name: Callister Crescent,stop_code: Wjz1xWZ, lat: -35.4565002, lng: 149.1174205} - { name: Callister Crescent,stop_code: Wjz1xWZ, lat: -35.4565002, lng: 149.1174205}
- { name: Chippindall Circuit,stop_code: Wjz1Gjj, lat: -35.4504956, lng: 149.1205257} - { name: Chippindall Circuit,stop_code: Wjz1Gjj, lat: -35.4504956, lng: 149.1205257}
- { name: Fidge Street,stop_code: Wjz1rQ6, lat: -35.4440887, lng: 149.1038388} - { name: Fidge Street,stop_code: Wjz1rQ6, lat: -35.4440887, lng: 149.1038388}
- { name: Weavers Crescent,stop_code: Wjz1xRC, lat: -35.4544199, lng: 149.1154761} - { name: Weavers Crescent,stop_code: Wjz1xRC, lat: -35.4544199, lng: 149.1154761}
- { name: Kiddle Crescent,stop_code: Wjz1CdY, lat: -35.4270927, lng: 149.1090734} - { name: Kiddle Crescent,stop_code: Wjz1CdY, lat: -35.4270927, lng: 149.1090734}
- { name: Fairley Crescent,stop_code: Wjz1G89, lat: -35.4527651, lng: 149.1190457} - { name: Fairley Crescent,stop_code: Wjz1G89, lat: -35.4527651, lng: 149.1190457}
- { name: Fairley Crescent,stop_code: Wjz1F5W, lat: -35.4547272, lng: 149.1186974} - { name: Fairley Crescent,stop_code: Wjz1F5W, lat: -35.4547272, lng: 149.1186974}
- { name: Muscio Place,stop_code: Wjz2EdX, lat: -35.416214, lng: 149.120065} - { name: Muscio Place,stop_code: Wjz2EdX, lat: -35.416214, lng: 149.120065}
- { name: Clift Crescent,stop_code: Wjz1CRl, lat: -35.4269745, lng: 149.1151677} - { name: Clift Crescent,stop_code: Wjz1CRl, lat: -35.4269745, lng: 149.1151677}
- { name: Southern Close,stop_code: Wjz1K49, lat: -35.428009, lng: 149.1176708} - { name: Southern Close,stop_code: Wjz1K49, lat: -35.428009, lng: 149.1176708}
- { name: Clift Crescent,stop_code: Wjz1J4T, lat: -35.4330044, lng: 149.1185777} - { name: Clift Crescent,stop_code: Wjz1J4T, lat: -35.4330044, lng: 149.1185777}
- { name: Prichard Circuit,stop_code: Wjz1K89, lat: -35.4308171, lng: 149.1191218} - { name: Prichard Circuit,stop_code: Wjz1K89, lat: -35.4308171, lng: 149.1191218}
- { name: Twamley Crescent,stop_code: Wjz1JD7, lat: -35.4309354, lng: 149.1230759} - { name: Twamley Crescent,stop_code: Wjz1JD7, lat: -35.4309354, lng: 149.1230759}
- { name: Monaro Highway,stop_code: Wjz1JTP, lat: -35.4312901, lng: 149.126776} - { name: Monaro Highway,stop_code: Wjz1JTP, lat: -35.4312901, lng: 149.126776}
- { name: Deamer Crescent,stop_code: Wjz1S5I, lat: -35.4271223, lng: 149.1292791} - { name: Deamer Crescent,stop_code: Wjz1S5I, lat: -35.4271223, lng: 149.1292791}
- { name: Monaro Highway,stop_code: Wjz1SfM, lat: -35.4260286, lng: 149.1309478} - { name: Monaro Highway,stop_code: Wjz1SfM, lat: -35.4260286, lng: 149.1309478}
- { name: Henry Melville Crescent,stop_code: Wjz1TLL, lat: -35.4199685, lng: 149.1361715} - { name: Henry Melville Crescent,stop_code: Wjz1TLL, lat: -35.4199685, lng: 149.1361715}
- { name: Muntz Street,stop_code: Wjz1Lxu, lat: -35.4241367, lng: 149.1234749} - { name: Muntz Street,stop_code: Wjz1Lxu, lat: -35.4241367, lng: 149.1234749}
- { name: Mofflin Street,stop_code: Wjz1Liw, lat: -35.4239889, lng: 149.1208993} - { name: Mofflin Street,stop_code: Wjz1Liw, lat: -35.4239889, lng: 149.1208993}
- { name: Tuck Place,stop_code: Wjz1DLm, lat: -35.4200572, lng: 149.1136804} - { name: Tuck Place,stop_code: Wjz1DLm, lat: -35.4200572, lng: 149.1136804}
- { name: Proctor Street,stop_code: Wjz2M5R, lat: -35.4160071, lng: 149.129533} - { name: Proctor Street,stop_code: Wjz2M5R, lat: -35.4160071, lng: 149.129533}
- { name: Hynes Place,stop_code: Wjz2wY-, lat: -35.4166279, lng: 149.1173443} - { name: Hynes Place,stop_code: Wjz2wY-, lat: -35.4166279, lng: 149.1173443}
- { name: Sweet Place,stop_code: Wjz2EL2, lat: -35.4149132, lng: 149.1244544} - { name: Sweet Place,stop_code: Wjz2EL2, lat: -35.4149132, lng: 149.1244544}
- { name: Schoales Place,stop_code: WjrXZiM, lat: -35.3470777, lng: 149.0553331} - { name: Schoales Place,stop_code: WjrXZiM, lat: -35.3470777, lng: 149.0553331}
- { name: Logue Place,stop_code: WjrXRW0, lat: -35.3471147, lng: 149.0502999} - { name: Logue Place,stop_code: WjrXRW0, lat: -35.3471147, lng: 149.0502999}
- { name: Finlayson Place,stop_code: Wjz2NPZ, lat: -35.4118681, lng: 149.1378765} - { name: Finlayson Place,stop_code: Wjz2NPZ, lat: -35.4118681, lng: 149.1378765}
- { name: Namatjira Drive,stop_code: WjrXZz3, lat: -35.3461161, lng: 149.0570563} - { name: Namatjira Drive,stop_code: WjrXZz3, lat: -35.3461161, lng: 149.0570563}
- { name: Wark Street,stop_code: Wjz3nLq, lat: -35.3325054, lng: 149.0919265} - { name: Wark Street,stop_code: Wjz3nLq, lat: -35.3325054, lng: 149.0919265}
- { name: McCulloch Street,stop_code: Wjz49Y5, lat: -35.3233291, lng: 149.0831296} - { name: McCulloch Street,stop_code: Wjz49Y5, lat: -35.3233291, lng: 149.0831296}
- { name: Novar Street,stop_code: Wjz4shf, lat: -35.3086912, lng: 149.0984092} - { name: Novar Street,stop_code: Wjz4shf, lat: -35.3086912, lng: 149.0984092}
- { name: Novar Street,stop_code: Wjz4rk2, lat: -35.3126013, lng: 149.0982349} - { name: Novar Street,stop_code: Wjz4rk2, lat: -35.3126013, lng: 149.0982349}
- { name: Denison Street,stop_code: Wjz4hPC, lat: -35.323921, lng: 149.0935136} - { name: Denison Street,stop_code: Wjz4hPC, lat: -35.323921, lng: 149.0935136}
- { name: Jensen Street,stop_code: Wjz4gou, lat: -35.3314972, lng: 149.0892541} - { name: Jensen Street,stop_code: Wjz4gou, lat: -35.3314972, lng: 149.0892541}
- { name: Denison Street,stop_code: Wjz4hMe, lat: -35.3259558, lng: 149.0929241} - { name: Denison Street,stop_code: Wjz4hMe, lat: -35.3259558, lng: 149.0929241}
- { name: Yarra Glen,stop_code: Wjz4gt5, lat: -35.3281248, lng: 149.0887511} - { name: Yarra Glen,stop_code: Wjz4gt5, lat: -35.3281248, lng: 149.0887511}
- { name: Carruthers Street,stop_code: Wjz49Wd, lat: -35.324698, lng: 149.0833563} - { name: Carruthers Street,stop_code: Wjz49Wd, lat: -35.324698, lng: 149.0833563}
- { name: Shiels Place,stop_code: Wjz4arc, lat: -35.3185933, lng: 149.0779149} - { name: Shiels Place,stop_code: Wjz4arc, lat: -35.3185933, lng: 149.0779149}
- { name: Heysen Street,stop_code: WjrYUG8, lat: -35.3306155, lng: 149.058622} - { name: Heysen Street,stop_code: WjrYUG8, lat: -35.3306155, lng: 149.058622}
- { name: Dunstan Street,stop_code: Wjz4aH6, lat: -35.3184453, lng: 149.0804542} - { name: Dunstan Street,stop_code: Wjz4aH6, lat: -35.3184453, lng: 149.0804542}
- { name: Mair Place,stop_code: Wjz48dZ, lat: -35.3281016, lng: 149.0761465} - { name: Mair Place,stop_code: Wjz48dZ, lat: -35.3281016, lng: 149.0761465}
- { name: Jennings Street,stop_code: Wjz499S, lat: -35.3252899, lng: 149.0759651} - { name: Jennings Street,stop_code: Wjz499S, lat: -35.3252899, lng: 149.0759651}
- { name: O'Loghlen Street,stop_code: Wjr-IMR, lat: -35.2216889, lng: 149.0389433} - { name: O'Loghlen Street,stop_code: Wjr-IMR, lat: -35.2216889, lng: 149.0389433}
- { name: Carruthers Street,stop_code: Wjz48qI, lat: -35.3302472, lng: 149.0785498} - { name: Carruthers Street,stop_code: Wjz48qI, lat: -35.3302472, lng: 149.0785498}
- { name: Heysen Street,stop_code: WjrYUj0, lat: -35.3299526, lng: 149.0543559} - { name: Heysen Street,stop_code: WjrYUj0, lat: -35.3299526, lng: 149.0543559}
- { name: Heysen Street,stop_code: Wjz37Lm, lat: -35.3321544, lng: 149.0697369} - { name: Heysen Street,stop_code: Wjz37Lm, lat: -35.3321544, lng: 149.0697369}
- { name: Burnie Street,stop_code: Wjz3d3K, lat: -35.3459087, lng: 149.0743512} - { name: Burnie Street,stop_code: Wjz3d3K, lat: -35.3459087, lng: 149.0743512}
- { name: Derwent Street,stop_code: Wjz3ee-, lat: -35.3383098, lng: 149.0761505} - { name: Derwent Street,stop_code: Wjz3ee-, lat: -35.3383098, lng: 149.0761505}
- { name: Anne Place,stop_code: Wjz3fa8, lat: -35.3360845, lng: 149.0750477} - { name: Anne Place,stop_code: Wjz3fa8, lat: -35.3360845, lng: 149.0750477}
- { name: McInnes Street,stop_code: WjrX-Lw, lat: -35.3381915, lng: 149.0592024} - { name: McInnes Street,stop_code: WjrX-Lw, lat: -35.3381915, lng: 149.0592024}
- { name: Lycett Street,stop_code: WjrX_xY, lat: -35.3364869, lng: 149.0583028} - { name: Lycett Street,stop_code: WjrX_xY, lat: -35.3364869, lng: 149.0583028}
- { name: Meldrum Street,stop_code: WjrX_iU, lat: -35.3361318, lng: 149.0556038} - { name: Meldrum Street,stop_code: WjrX_iU, lat: -35.3361318, lng: 149.0556038}
- { name: Namatjira Drive,stop_code: WjrX-m2, lat: -35.3386886, lng: 149.0543559} - { name: Namatjira Drive,stop_code: WjrX-m2, lat: -35.3386886, lng: 149.0543559}
- { name: Mather Street,stop_code: WjrX-sE, lat: -35.3402511, lng: 149.0565615} - { name: Mather Street,stop_code: WjrX-sE, lat: -35.3402511, lng: 149.0565615}
- { name: Buvelot Street,stop_code: Wjz354b, lat: -35.345459, lng: 149.062772} - { name: Buvelot Street,stop_code: Wjz354b, lat: -35.345459, lng: 149.062772}
- { name: Gask Place,stop_code: Wjz1et6, lat: -35.4269117, lng: 149.0777759} - { name: Gask Place,stop_code: Wjz1et6, lat: -35.4269117, lng: 149.0777759}
- { name: Drumston Street,stop_code: Wjz1nxQ, lat: -35.4243695, lng: 149.0911255} - { name: Drumston Street,stop_code: Wjz1nxQ, lat: -35.4243695, lng: 149.0911255}
- { name: Athllon Drive,stop_code: Wjz1f8Y, lat: -35.4250198, lng: 149.076216} - { name: Athllon Drive,stop_code: Wjz1f8Y, lat: -35.4250198, lng: 149.076216}
- { name: Anketell Street,stop_code: Wjz1f2H, lat: -35.4237487, lng: 149.0744748} - { name: Anketell Street,stop_code: Wjz1f2H, lat: -35.4237487, lng: 149.0744748}
- { name: Lake Tuggeranong cycle track,stop_code: Wjz1f7q, lat: -35.4203787, lng: 149.0740032} - { name: Lake Tuggeranong cycle track,stop_code: Wjz1f7q, lat: -35.4203787, lng: 149.0740032}
- { name: Forlonge Street,stop_code: Wjz2bHS, lat: -35.400824, lng: 149.0814035} - { name: Forlonge Street,stop_code: Wjz2bHS, lat: -35.400824, lng: 149.0814035}
- { name: Derham Court,stop_code: Wjz2aLs, lat: -35.4037395, lng: 149.081019} - { name: Derham Court,stop_code: Wjz2aLs, lat: -35.4037395, lng: 149.081019}
- { name: Mortimer Lewis Drive,stop_code: Wjz2a26, lat: -35.4069683, lng: 149.0736259} - { name: Mortimer Lewis Drive,stop_code: Wjz2a26, lat: -35.4069683, lng: 149.0736259}
- { name: Nunan Crescent,stop_code: Wjz29Ya, lat: -35.4114741, lng: 149.0833189} - { name: Nunan Crescent,stop_code: Wjz29Ya, lat: -35.4114741, lng: 149.0833189}
- { name: William Webb Drive,stop_code: Wjz6e8G, lat: -35.2110071, lng: 149.0758577} - { name: William Webb Drive,stop_code: Wjz6e8G, lat: -35.2110071, lng: 149.0758577}
- { name: Evelyn Owen Crescent,stop_code: Wjr_w0L, lat: -35.1995769, lng: 149.0194714} - { name: Evelyn Owen Crescent,stop_code: Wjr_w0L, lat: -35.1995769, lng: 149.0194714}
- { name: Cusack Place,stop_code: Wjr_Ow3, lat: -35.1889085, lng: 149.0461463} - { name: Cusack Place,stop_code: Wjr_Ow3, lat: -35.1889085, lng: 149.0461463}
- { name: Binns Street,stop_code: Wjr_GGq, lat: -35.1875953, lng: 149.0370811} - { name: Binns Street,stop_code: Wjr_GGq, lat: -35.1875953, lng: 149.0370811}
- { name: Clubbe Crescent,stop_code: Wjr-uUb, lat: -35.2108896, lng: 149.0174054} - { name: Clubbe Crescent,stop_code: Wjr-uUb, lat: -35.2108896, lng: 149.0174054}
- { name: Southern Cross Drive,stop_code: Wjr-s5D, lat: -35.2180783, lng: 149.0083939} - { name: Southern Cross Drive,stop_code: Wjr-s5D, lat: -35.2180783, lng: 149.0083939}
- { name: Higgins Place,stop_code: Wjr-yOB, lat: -35.2313222, lng: 149.0276235} - { name: Higgins Place,stop_code: Wjr-yOB, lat: -35.2313222, lng: 149.0276235}
- { name: Southern Cross Drive,stop_code: Wjr-Hoi, lat: -35.2274077, lng: 149.0341216} - { name: Southern Cross Drive,stop_code: Wjr-Hoi, lat: -35.2274077, lng: 149.0341216}
- { name: Wollongong Street,stop_code: WjzcgD0, lat: -35.3271927, lng: 149.1779495} - { name: Wollongong Street,stop_code: WjzcgD0, lat: -35.3271927, lng: 149.1779495}
- { name: Taubman Street,stop_code: Wjzbfpl, lat: -35.3363832, lng: 149.1658515} - { name: Taubman Street,stop_code: Wjzbfpl, lat: -35.3363832, lng: 149.1658515}
- { name: Wiluna Street,stop_code: Wjzc8l0, lat: -35.3285713, lng: 149.1642018} - { name: Wiluna Street,stop_code: Wjzc8l0, lat: -35.3285713, lng: 149.1642018}
- { name: Whyalla Street,stop_code: Wjzbnmb, lat: -35.3331064, lng: 149.1753196} - { name: Whyalla Street,stop_code: Wjzbnmb, lat: -35.3331064, lng: 149.1753196}
- { name: Allen Street,stop_code: Wjz3_3L, lat: -35.3347817, lng: 149.1404124} - { name: Allen Street,stop_code: Wjz3_3L, lat: -35.3347817, lng: 149.1404124}
- { name: Goyder Street,stop_code: Wjz3-aW, lat: -35.3414521, lng: 149.1420263} - { name: Goyder Street,stop_code: Wjz3-aW, lat: -35.3414521, lng: 149.1420263}
- { name: Alfred Place,stop_code: Wjza_-f, lat: -35.3767042, lng: 149.237157} - { name: Alfred Place,stop_code: Wjza_-f, lat: -35.3767042, lng: 149.237157}
- { name: Farrer Place,stop_code: WjzbXms, lat: -35.3550134, lng: 149.2306199} - { name: Farrer Place,stop_code: WjzbXms, lat: -35.3550134, lng: 149.2306199}
- { name: Bazley Street,stop_code: Wjr_Vbj, lat: -35.1923583, lng: 149.0533723} - { name: Bazley Street,stop_code: Wjr_Vbj, lat: -35.1923583, lng: 149.0533723}
- { name: Tuggeranong Parkway,stop_code: Wjz33GY, lat: -35.3577485, lng: 149.0706526} - { name: Tuggeranong Parkway,stop_code: Wjz33GY, lat: -35.3577485, lng: 149.0706526}
- { name: Kalgoorlie Crescent,stop_code: WjrXXFn, lat: -35.3581997, lng: 149.0587995} - { name: Kalgoorlie Crescent,stop_code: WjrXXFn, lat: -35.3581997, lng: 149.0587995}
- { name: Jarrahdale Street,stop_code: WjrXWQ8, lat: -35.3621767, lng: 149.0600261} - { name: Jarrahdale Street,stop_code: WjrXWQ8, lat: -35.3621767, lng: 149.0600261}
- { name: Kapunda Street,stop_code: WjrXW7A, lat: -35.3597972, lng: 149.0523061} - { name: Kapunda Street,stop_code: WjrXW7A, lat: -35.3597972, lng: 149.0523061}
- { name: Nannine Place,stop_code: WjrXXq3, lat: -35.3578077, lng: 149.0557251} - { name: Nannine Place,stop_code: WjrXXq3, lat: -35.3578077, lng: 149.0557251}
- { name: Greenvale Street,stop_code: WjrXXd0, lat: -35.3559956, lng: 149.0529772} - { name: Greenvale Street,stop_code: WjrXXd0, lat: -35.3559956, lng: 149.0529772}
- { name: Hindmarsh Drive,stop_code: Wjz35av, lat: -35.3464684, lng: 149.064395} - { name: Hindmarsh Drive,stop_code: Wjz35av, lat: -35.3464684, lng: 149.064395}
- { name: Bangalay Crescent,stop_code: WjrXIDX, lat: -35.348916, lng: 149.0363428} - { name: Bangalay Crescent,stop_code: WjrXIDX, lat: -35.348916, lng: 149.0363428}
- { name: Buvelot Street,stop_code: WjrX-FV, lat: -35.3422149, lng: 149.0596338} - { name: Buvelot Street,stop_code: WjrX-FV, lat: -35.3422149, lng: 149.0596338}
- { name: Chevalier Street,stop_code: Wjz356k, lat: -35.3440169, lng: 149.0629513} - { name: Chevalier Street,stop_code: Wjz356k, lat: -35.3440169, lng: 149.0629513}
- { name: Larakia Street,stop_code: Wjz358l, lat: -35.3480588, lng: 149.0643043} - { name: Larakia Street,stop_code: Wjz358l, lat: -35.3480588, lng: 149.0643043}
- { name: Tiwi Place,stop_code: Wjz348u, lat: -35.3534586, lng: 149.0644857} - { name: Tiwi Place,stop_code: Wjz348u, lat: -35.3534586, lng: 149.0644857}
- { name: Bidia Place,stop_code: Wjz337w, lat: -35.354642, lng: 149.0633068} - { name: Bidia Place,stop_code: Wjz337w, lat: -35.354642, lng: 149.0633068}
- { name: Dalabon Crescent,stop_code: WjrXXK9, lat: -35.355219, lng: 149.0585637} - { name: Dalabon Crescent,stop_code: WjrXXK9, lat: -35.355219, lng: 149.0585637}
- { name: Kalgoorlie Crescent,stop_code: WjrXXyQ, lat: -35.3576967, lng: 149.0580467} - { name: Kalgoorlie Crescent,stop_code: WjrXXyQ, lat: -35.3576967, lng: 149.0580467}
- { name: Tristania Street,stop_code: WjrXRks, lat: -35.3453958, lng: 149.0438991} - { name: Tristania Street,stop_code: WjrXRks, lat: -35.3453958, lng: 149.0438991}
- { name: Damala Street,stop_code: WjrXYL4, lat: -35.3488355, lng: 149.0584095} - { name: Damala Street,stop_code: WjrXYL4, lat: -35.3488355, lng: 149.0584095}
- { name: Somerset Street,stop_code: WjrXLaD, lat: -35.3355436, lng: 149.0316183} - { name: Somerset Street,stop_code: WjrXLaD, lat: -35.3355436, lng: 149.0316183}
- { name: Frayne Place,stop_code: WjrXQZX, lat: -35.3502779, lng: 149.0514717} - { name: Frayne Place,stop_code: WjrXQZX, lat: -35.3502779, lng: 149.0514717}
- { name: Dixon Drive,stop_code: WjrXTgl, lat: -35.3370298, lng: 149.0436997} - { name: Dixon Drive,stop_code: WjrXTgl, lat: -35.3370298, lng: 149.0436997}
- { name: Hyndes Crescent,stop_code: WjrXTqY, lat: -35.3357893, lng: 149.0460156} - { name: Hyndes Crescent,stop_code: WjrXTqY, lat: -35.3357893, lng: 149.0460156}
- { name: Nelumbo Street,stop_code: WjrXQ65, lat: -35.349419, lng: 149.040696} - { name: Nelumbo Street,stop_code: WjrXQ65, lat: -35.349419, lng: 149.040696}
- { name: Hindmarsh Drive,stop_code: WjrXKoe, lat: -35.3424911, lng: 149.0339533} - { name: Hindmarsh Drive,stop_code: WjrXKoe, lat: -35.3424911, lng: 149.0339533}
- { name: Burrinjuck Crescent,stop_code: WjrXLR-, lat: -35.3335487, lng: 149.0390846} - { name: Burrinjuck Crescent,stop_code: WjrXLR-, lat: -35.3335487, lng: 149.0390846}
- { name: Warragamba Avenue,stop_code: WjrYEpn, lat: -35.3306598, lng: 149.0341649} - { name: Warragamba Avenue,stop_code: WjrYEpn, lat: -35.3306598, lng: 149.0341649}
- { name: Tantangara Street,stop_code: WjrXKBE, lat: -35.3395611, lng: 149.0360582} - { name: Tantangara Street,stop_code: WjrXKBE, lat: -35.3395611, lng: 149.0360582}
- { name: Counsel Street,stop_code: WjrYMbF, lat: -35.3298385, lng: 149.0428712} - { name: Counsel Street,stop_code: WjrYMbF, lat: -35.3298385, lng: 149.0428712}
- { name: Hyndes Crescent,stop_code: WjrYMrj, lat: -35.3296313, lng: 149.0450622} - { name: Hyndes Crescent,stop_code: WjrYMrj, lat: -35.3296313, lng: 149.0450622}
- { name: Mulley Street,stop_code: WjrYMHm, lat: -35.3294538, lng: 149.0477466} - { name: Mulley Street,stop_code: WjrYMHm, lat: -35.3294538, lng: 149.0477466}
- { name: Mulley Street,stop_code: WjrYMGB, lat: -35.3301626, lng: 149.0481758} - { name: Mulley Street,stop_code: WjrYMGB, lat: -35.3301626, lng: 149.0481758}
- { name: Dixon Drive,stop_code: WjrXTSe, lat: -35.3328347, lng: 149.0489873} - { name: Dixon Drive,stop_code: WjrXTSe, lat: -35.3328347, lng: 149.0489873}
- { name: Calder Crescent,stop_code: WjrXTIp, lat: -35.3346742, lng: 149.0480789} - { name: Calder Crescent,stop_code: WjrXTIp, lat: -35.3346742, lng: 149.0480789}
- { name: Woodger Place,stop_code: Wjr_V2c, lat: -35.192985, lng: 149.0517177} - { name: Woodger Place,stop_code: Wjr_V2c, lat: -35.192985, lng: 149.0517177}
- { name: Watt Place,stop_code: Wjz2ve3, lat: -35.3770117, lng: 149.0968721} - { name: Watt Place,stop_code: Wjz2ve3, lat: -35.3770117, lng: 149.0968721}
- { name: Pearce Avenue,stop_code: WjzcBHZ, lat: -35.3020154, lng: 149.2024041} - { name: Pearce Avenue,stop_code: WjzcBHZ, lat: -35.3020154, lng: 149.2024041}
- { name: Duffy Place,stop_code: WjrXLgs, lat: -35.3371612, lng: 149.0328459} - { name: Duffy Place,stop_code: WjrXLgs, lat: -35.3371612, lng: 149.0328459}
- { name: Renmark Street,stop_code: WjrXKfG, lat: -35.338018, lng: 149.0318393} - { name: Renmark Street,stop_code: WjrXKfG, lat: -35.338018, lng: 149.0318393}
- { name: Anstey Street,stop_code: Wjz3aaB, lat: -35.3631322, lng: 149.0756066} - { name: Anstey Street,stop_code: Wjz3aaB, lat: -35.3631322, lng: 149.0756066}
- { name: Lhotsky Street,stop_code: Wjr-L1H, lat: -35.2046871, lng: 149.0304447} - { name: Lhotsky Street,stop_code: Wjr-L1H, lat: -35.2046871, lng: 149.0304447}
- { name: McCay Place,stop_code: Wjz39PE, lat: -35.3683683, lng: 149.0827167} - { name: McCay Place,stop_code: Wjz39PE, lat: -35.3683683, lng: 149.0827167}
- { name: Hodgson Crescent,stop_code: Wjz3h5c, lat: -35.3666525, lng: 149.0847118} - { name: Hodgson Crescent,stop_code: Wjz3h5c, lat: -35.3666525, lng: 149.0847118}
- { name: Collings Street,stop_code: Wjz3j2F, lat: -35.3580142, lng: 149.0853648} - { name: Collings Street,stop_code: Wjz3j2F, lat: -35.3580142, lng: 149.0853648}
- { name: Marr Street,stop_code: Wjz3it1, lat: -35.3614164, lng: 149.0886297} - { name: Marr Street,stop_code: Wjz3it1, lat: -35.3614164, lng: 149.0886297}
- { name: Pialligo Avenue,stop_code: Wjzcrp_, lat: -35.3142011, lng: 149.1887666} - { name: Pialligo Avenue,stop_code: Wjzcrp_, lat: -35.3142011, lng: 149.1887666}
- { name: Brindabella Circuit,stop_code: WjzcrK3, lat: -35.3111478, lng: 149.190364} - { name: Brindabella Circuit,stop_code: WjzcrK3, lat: -35.3111478, lng: 149.190364}
- { name: Dakota Drive,stop_code: Wjzcuw1, lat: -35.2989793, lng: 149.188937} - { name: Dakota Drive,stop_code: Wjzcuw1, lat: -35.2989793, lng: 149.188937}
- { name: Fairbairn Avenue,stop_code: WjzcJ0K, lat: -35.3040486, lng: 149.2062653} - { name: Fairbairn Avenue,stop_code: WjzcJ0K, lat: -35.3040486, lng: 149.2062653}
- { name: Anthony Rolfe Avenue,stop_code: Wjzf3oM, lat: -35.1836894, lng: 149.1556666} - { name: Anthony Rolfe Avenue,stop_code: Wjzf3oM, lat: -35.1836894, lng: 149.1556666}
- { name: Binns Street,stop_code: Wjr_Gxf, lat: -35.1878657, lng: 149.0352296} - { name: Binns Street,stop_code: Wjr_Gxf, lat: -35.1878657, lng: 149.0352296}
- { name: Lhotsky Street,stop_code: Wjr_Es4, lat: -35.1970405, lng: 149.0338265} - { name: Lhotsky Street,stop_code: Wjr_Es4, lat: -35.1970405, lng: 149.0338265}
- { name: Rogers Street,stop_code: Wjr_FTN, lat: -35.1897508, lng: 149.038952} - { name: Rogers Street,stop_code: Wjr_FTN, lat: -35.1897508, lng: 149.038952}
- { name: Kerrigan Street,stop_code: Wjr_xLL, lat: -35.1892698, lng: 149.0264062} - { name: Kerrigan Street,stop_code: Wjr_xLL, lat: -35.1892698, lng: 149.0264062}
- { name: Bandt Place,stop_code: Wjr_xnT, lat: -35.1892671, lng: 149.0223682} - { name: Bandt Place,stop_code: Wjr_xnT, lat: -35.1892671, lng: 149.0223682}
- { name: Filshie Close,stop_code: Wjr_FXR, lat: -35.1922038, lng: 149.0402464} - { name: Filshie Close,stop_code: Wjr_FXR, lat: -35.1922038, lng: 149.0402464}
- { name: Donnison Place,stop_code: Wjr_E1y, lat: -35.1992571, lng: 149.0303603} - { name: Donnison Place,stop_code: Wjr_E1y, lat: -35.1992571, lng: 149.0303603}
- { name: Edlington Street,stop_code: Wjr_NDY, lat: -35.1895167, lng: 149.04724} - { name: Edlington Street,stop_code: Wjr_NDY, lat: -35.1895167, lng: 149.04724}
- { name: Nish Place,stop_code: Wjr_Vt9, lat: -35.191134, lng: 149.055871} - { name: Nish Place,stop_code: Wjr_Vt9, lat: -35.191134, lng: 149.055871}
- { name: Shrivell Circuit,stop_code: Wjr_wm3, lat: -35.195762, lng: 149.0214528} - { name: Shrivell Circuit,stop_code: Wjr_wm3, lat: -35.195762, lng: 149.0214528}
- { name: O'Reilly Street,stop_code: Wjr-thp, lat: -35.2158247, lng: 149.0109263} - { name: O'Reilly Street,stop_code: Wjr-thp, lat: -35.2158247, lng: 149.0109263}
- { name: Eddison Place,stop_code: Wjr_NFt, lat: -35.1935465, lng: 149.0479464} - { name: Eddison Place,stop_code: Wjr_NFt, lat: -35.1935465, lng: 149.0479464}
- { name: Garrad Court,stop_code: Wjr_MjV, lat: -35.1979805, lng: 149.0445264} - { name: Garrad Court,stop_code: Wjr_MjV, lat: -35.1979805, lng: 149.0445264}
- { name: Covington Crescent,stop_code: Wjr-Tf_, lat: -35.2002734, lng: 149.0432168} - { name: Covington Crescent,stop_code: Wjr-Tf_, lat: -35.2002734, lng: 149.0432168}
- { name: Moyes Crescent,stop_code: Wjr-zOn, lat: -35.2256125, lng: 149.0272189} - { name: Moyes Crescent,stop_code: Wjr-zOn, lat: -35.2256125, lng: 149.0272189}
- { name: Noakes Court,stop_code: Wjr-Lzm, lat: -35.2030997, lng: 149.0354829} - { name: Noakes Court,stop_code: Wjr-Lzm, lat: -35.2030997, lng: 149.0354829}
- { name: Hirschfeld Crescent,stop_code: Wjr-tbm, lat: -35.2140927, lng: 149.0093105} - { name: Hirschfeld Crescent,stop_code: Wjr-tbm, lat: -35.2140927, lng: 149.0093105}
- { name: Florey Drive,stop_code: Wjr-DNK, lat: -35.2044788, lng: 149.0277602} - { name: Florey Drive,stop_code: Wjr-DNK, lat: -35.2044788, lng: 149.0277602}
- { name: Lhotsky Street,stop_code: Wjr-DQE, lat: -35.2029293, lng: 149.0277662} - { name: Lhotsky Street,stop_code: Wjr-DQE, lat: -35.2029293, lng: 149.0277662}
- { name: Ginninderra Drive,stop_code: Wjr_oP1, lat: -35.1980445, lng: 149.0158736} - { name: Ginninderra Drive,stop_code: Wjr_oP1, lat: -35.1980445, lng: 149.0158736}
- { name: Krefft Street,stop_code: Wjr-Pk6, lat: -35.2243699, lng: 149.0432872} - { name: Krefft Street,stop_code: Wjr-Pk6, lat: -35.2243699, lng: 149.0432872}
- { name: Kerrigan Street,stop_code: Wjr_o_j, lat: -35.1950629, lng: 149.0175978} - { name: Kerrigan Street,stop_code: Wjr_o_j, lat: -35.1950629, lng: 149.0175978}
- { name: Lance Hill Avenue,stop_code: Wjr_wjn, lat: -35.1975263, lng: 149.0216638} - { name: Lance Hill Avenue,stop_code: Wjr_wjn, lat: -35.1975263, lng: 149.0216638}
- { name: Florey Drive,stop_code: Wjr-CS2, lat: -35.2068071, lng: 149.0268212} - { name: Florey Drive,stop_code: Wjr-CS2, lat: -35.2068071, lng: 149.0268212}
- { name: Kerrigan Street,stop_code: Wjr_oJA, lat: -35.1964177, lng: 149.0152805} - { name: Kerrigan Street,stop_code: Wjr_oJA, lat: -35.1964177, lng: 149.0152805}
- { name: Rossell Place,stop_code: Wjr-KJQ, lat: -35.2073355, lng: 149.037506} - { name: Rossell Place,stop_code: Wjr-KJQ, lat: -35.2073355, lng: 149.037506}
- { name: O'Reilly Street,stop_code: Wjr-smi, lat: -35.2178617, lng: 149.0106876} - { name: O'Reilly Street,stop_code: Wjr-smi, lat: -35.2178617, lng: 149.0106876}
- { name: Archdall Street,stop_code: Wjr-vJY, lat: -35.2019113, lng: 149.0157184} - { name: Archdall Street,stop_code: Wjr-vJY, lat: -35.2019113, lng: 149.0157184}
- { name: Cumpston Place,stop_code: Wjr-BL8, lat: -35.2118565, lng: 149.025622} - { name: Cumpston Place,stop_code: Wjr-BL8, lat: -35.2118565, lng: 149.025622}
- { name: Nulsen Circuit,stop_code: Wjr-S6B, lat: -35.2066123, lng: 149.0412991} - { name: Nulsen Circuit,stop_code: Wjr-S6B, lat: -35.2066123, lng: 149.0412991}
- { name: Tulloch Place,stop_code: Wjr-RnT, lat: -35.2112095, lng: 149.0444601} - { name: Tulloch Place,stop_code: Wjr-RnT, lat: -35.2112095, lng: 149.0444601}
- { name: Grigson Place,stop_code: Wjr-s_F, lat: -35.2172009, lng: 149.0180976} - { name: Grigson Place,stop_code: Wjr-s_F, lat: -35.2172009, lng: 149.0180976}
- { name: Hampton Gardens,stop_code: Wjr-rQJ, lat: -35.2244007, lng: 149.0167658} - { name: Hampton Gardens,stop_code: Wjr-rQJ, lat: -35.2244007, lng: 149.0167658}
- { name: Rentoul Place,stop_code: Wjr-Rs8, lat: -35.2139046, lng: 149.0449606} - { name: Rentoul Place,stop_code: Wjr-Rs8, lat: -35.2139046, lng: 149.0449606}
- { name: Krefft Street,stop_code: Wjr-Q8c, lat: -35.2217975, lng: 149.042121} - { name: Krefft Street,stop_code: Wjr-Q8c, lat: -35.2217975, lng: 149.042121}
- { name: Dalley Crescent,stop_code: Wjr-AHx, lat: -35.2199899, lng: 149.0262529} - { name: Dalley Crescent,stop_code: Wjr-AHx, lat: -35.2199899, lng: 149.0262529}
- { name: Southern Cross Drive,stop_code: Wjr-z_L, lat: -35.222191, lng: 149.0291286} - { name: Southern Cross Drive,stop_code: Wjr-z_L, lat: -35.222191, lng: 149.0291286}
- { name: Starke Street,stop_code: Wjr-sV3, lat: -35.2212162, lng: 149.0172455} - { name: Starke Street,stop_code: Wjr-sV3, lat: -35.2212162, lng: 149.0172455}
- { name: Drake Brockman Drive,stop_code: Wjr-qcc, lat: -35.230013, lng: 149.0092125} - { name: Drake Brockman Drive,stop_code: Wjr-qcc, lat: -35.230013, lng: 149.0092125}
- { name: Southern Cross Drive,stop_code: Wjr-st9, lat: -35.2186471, lng: 149.0119654} - { name: Southern Cross Drive,stop_code: Wjr-st9, lat: -35.2186471, lng: 149.0119654}
- { name: Messenger Street,stop_code: Wjr-jRn, lat: -35.2235756, lng: 149.0053113} - { name: Messenger Street,stop_code: Wjr-jRn, lat: -35.2235756, lng: 149.0053113}
- { name: Armstrong Crescent,stop_code: Wjr-syd, lat: -35.2203046, lng: 149.0133355} - { name: Armstrong Crescent,stop_code: Wjr-syd, lat: -35.2203046, lng: 149.0133355}
- { name: Holt Place,stop_code: Wjr-rjD, lat: -35.2249706, lng: 149.0111289} - { name: Holt Place,stop_code: Wjr-rjD, lat: -35.2249706, lng: 149.0111289}
- { name: Drake Brockman Drive,stop_code: Wjr-qyr, lat: -35.2315106, lng: 149.0137011} - { name: Drake Brockman Drive,stop_code: Wjr-qyr, lat: -35.2315106, lng: 149.0137011}
- { name: Ashburner Street,stop_code: Wjr-yrh, lat: -35.2309899, lng: 149.0230231} - { name: Ashburner Street,stop_code: Wjr-yrh, lat: -35.2309899, lng: 149.0230231}
- { name: Grout Place,stop_code: Wjr-jNB, lat: -35.2265208, lng: 149.0056756} - { name: Grout Place,stop_code: Wjr-jNB, lat: -35.2265208, lng: 149.0056756}
- { name: Starke Street,stop_code: Wjr-yni, lat: -35.2281496, lng: 149.0217011} - { name: Starke Street,stop_code: Wjr-yni, lat: -35.2281496, lng: 149.0217011}
- { name: Hardwick Crescent,stop_code: Wjr-zom, lat: -35.2270626, lng: 149.0231771} - { name: Hardwick Crescent,stop_code: Wjr-zom, lat: -35.2270626, lng: 149.0231771}
- { name: Davidson Street,stop_code: Wjr-ywh, lat: -35.2330631, lng: 149.0245222} - { name: Davidson Street,stop_code: Wjr-ywh, lat: -35.2330631, lng: 149.0245222}
- { name: Kriewaldt Circuit,stop_code: Wjr-yJZ, lat: -35.2292857, lng: 149.0266955} - { name: Kriewaldt Circuit,stop_code: Wjr-yJZ, lat: -35.2292857, lng: 149.0266955}
- { name: Kriewaldt Circuit,stop_code: Wjr-ySy, lat: -35.228821, lng: 149.0276438} - { name: Kriewaldt Circuit,stop_code: Wjr-ySy, lat: -35.228821, lng: 149.0276438}
- { name: Starke Street,stop_code: Wjr-zWb, lat: -35.2259772, lng: 149.0283569} - { name: Starke Street,stop_code: Wjr-zWb, lat: -35.2259772, lng: 149.0283569}
- { name: Chave Street,stop_code: Wjr-zC9, lat: -35.2234474, lng: 149.0242983} - { name: Chave Street,stop_code: Wjr-zC9, lat: -35.2234474, lng: 149.0242983}
- { name: Dethridge Street,stop_code: Wjr-GeX, lat: -35.2287693, lng: 149.0321955} - { name: Dethridge Street,stop_code: Wjr-GeX, lat: -35.2287693, lng: 149.0321955}
- { name: Davidson Street,stop_code: Wjr-xLK, lat: -35.2332476, lng: 149.0263679} - { name: Davidson Street,stop_code: Wjr-xLK, lat: -35.2332476, lng: 149.0263679}
- { name: Drake Brockman Drive,stop_code: Wjr-xxu, lat: -35.2373929, lng: 149.0246092} - { name: Drake Brockman Drive,stop_code: Wjr-xxu, lat: -35.2373929, lng: 149.0246092}
- { name: Tanumbirini Street,stop_code: Wjr-Ekp, lat: -35.2412759, lng: 149.032879} - { name: Tanumbirini Street,stop_code: Wjr-Ekp, lat: -35.2412759, lng: 149.032879}
- { name: Crawford Street,stop_code: WjzbYue, lat: -35.3493054, lng: 149.2316145} - { name: Crawford Street,stop_code: WjzbYue, lat: -35.3493054, lng: 149.2316145}
- { name: Antill Street,stop_code: WjzbYD0, lat: -35.3491814, lng: 149.232803} - { name: Antill Street,stop_code: WjzbYD0, lat: -35.3491814, lng: 149.232803}
- { name: Alinga Street,stop_code: Wjz5FSY, lat: -35.2780524, lng: 149.1269928} - { name: Alinga Street,stop_code: Wjz5FSY, lat: -35.2780524, lng: 149.1269928}
- { name: Uriarra Road,stop_code: WjzbRdA, lat: -35.3446934, lng: 149.2184308} - { name: Uriarra Road,stop_code: WjzbRdA, lat: -35.3446934, lng: 149.2184308}
- { name: Pound Street,stop_code: Wjzj5cC, lat: -35.3451754, lng: 149.2404108} - { name: Pound Street,stop_code: Wjzj5cC, lat: -35.3451754, lng: 149.2404108}
- { name: Uriarra Road,stop_code: WjzbRBx, lat: -35.3449879, lng: 149.2226535} - { name: Uriarra Road,stop_code: WjzbRBx, lat: -35.3449879, lng: 149.2226535}
- { name: Alinga Street,stop_code: Wjz5Neo, lat: -35.27843, lng: 149.130345} - { name: Alinga Street,stop_code: Wjz5Neo, lat: -35.27843, lng: 149.130345}
- { name: Redwood Avenue,stop_code: WjzaJ9a, lat: -35.391582, lng: 149.2069701} - { name: Redwood Avenue,stop_code: WjzaJ9a, lat: -35.391582, lng: 149.2069701}
- { name: Canberra Avenue,stop_code: WjzbPQW, lat: -35.3565184, lng: 149.2259167} - { name: Canberra Avenue,stop_code: WjzbPQW, lat: -35.3565184, lng: 149.2259167}
- { name: Kenneth Place,stop_code: WjzbVBj, lat: -35.3667378, lng: 149.233235} - { name: Kenneth Place,stop_code: WjzbVBj, lat: -35.3667378, lng: 149.233235}
- { name: Cooma Street,stop_code: WjzbVCw, lat: -35.3663608, lng: 149.2335824} - { name: Cooma Street,stop_code: WjzbVCw, lat: -35.3663608, lng: 149.2335824}
- { name: Gibbs Place,stop_code: Wjz9JIL, lat: -35.4330525, lng: 149.2131844} - { name: Gibbs Place,stop_code: Wjz9JIL, lat: -35.4330525, lng: 149.2131844}
- { name: Parkview Crescent,stop_code: WjzaK0g, lat: -35.3868815, lng: 149.2056751} - { name: Parkview Crescent,stop_code: WjzaK0g, lat: -35.3868815, lng: 149.2056751}
- { name: Dixon Place,stop_code: WjzaDIK, lat: -35.3781802, lng: 149.2021825} - { name: Dixon Place,stop_code: WjzaDIK, lat: -35.3781802, lng: 149.2021825}
- { name: Rutledge Street,stop_code: WjzbXBT, lat: -35.3553953, lng: 149.2338714} - { name: Rutledge Street,stop_code: WjzbXBT, lat: -35.3553953, lng: 149.2338714}
- { name: Brindabella Circuit,stop_code: WjzcrrQ, lat: -35.3131274, lng: 149.188611} - { name: Brindabella Circuit,stop_code: WjzcrrQ, lat: -35.3131274, lng: 149.188611}
- { name: Benjamin Way,stop_code: Wjz57tg, lat: -35.2461188, lng: 149.0669661} - { name: Benjamin Way,stop_code: Wjz57tg, lat: -35.2461188, lng: 149.0669661}
- { name: Greene Place,stop_code: Wjz57T_, lat: -35.2441569, lng: 149.0719751} - { name: Greene Place,stop_code: Wjz57T_, lat: -35.2441569, lng: 149.0719751}
- { name: Gatehouse Place,stop_code: Wjz5f2j, lat: -35.2479775, lng: 149.0739202} - { name: Gatehouse Place,stop_code: Wjz5f2j, lat: -35.2479775, lng: 149.0739202}
- { name: Crisp Circuit,stop_code: Wjz688N, lat: -35.2439868, lng: 149.0759082} - { name: Crisp Circuit,stop_code: Wjz688N, lat: -35.2439868, lng: 149.0759082}
- { name: Cobbett Place,stop_code: Wjz68g-, lat: -35.2436119, lng: 149.0775571} - { name: Cobbett Place,stop_code: Wjz68g-, lat: -35.2436119, lng: 149.0775571}
- { name: Braybrooke Street,stop_code: Wjz5vjd, lat: -35.2470998, lng: 149.0983861} - { name: Braybrooke Street,stop_code: Wjz5vjd, lat: -35.2470998, lng: 149.0983861}
- { name: Watkin Street,stop_code: Wjz5v68, lat: -35.2454993, lng: 149.0956677} - { name: Watkin Street,stop_code: Wjz5v68, lat: -35.2454993, lng: 149.0956677}
- { name: Dunlop Court,stop_code: Wjz6gQ0, lat: -35.2413491, lng: 149.0928379} - { name: Dunlop Court,stop_code: Wjz6gQ0, lat: -35.2413491, lng: 149.0928379}
- { name: Eardley Street,stop_code: Wjz6gJc, lat: -35.2402968, lng: 149.0916132} - { name: Eardley Street,stop_code: Wjz6gJc, lat: -35.2402968, lng: 149.0916132}
- { name: Leverrier Crescent,stop_code: Wjz6oEz, lat: -35.243821, lng: 149.1030282} - { name: Leverrier Crescent,stop_code: Wjz6oEz, lat: -35.243821, lng: 149.1030282}
- { name: Krantzcke Circuit,stop_code: Wjz7pfP, lat: -35.189616, lng: 149.0978803} - { name: Krantzcke Circuit,stop_code: Wjz7pfP, lat: -35.189616, lng: 149.0978803}
- { name: Temperley Street,stop_code: Wjz7p2n, lat: -35.1926501, lng: 149.0958323} - { name: Temperley Street,stop_code: Wjz7p2n, lat: -35.1926501, lng: 149.0958323}
- { name: Temperley Street,stop_code: Wjz7iV0, lat: -35.1885169, lng: 149.0941253} - { name: Temperley Street,stop_code: Wjz7iV0, lat: -35.1885169, lng: 149.0941253}
- { name: Temperley Street,stop_code: Wjz7iG_, lat: -35.1872252, lng: 149.0926713} - { name: Temperley Street,stop_code: Wjz7iG_, lat: -35.1872252, lng: 149.0926713}
- { name: Curran Drive,stop_code: Wjz7ilp, lat: -35.1856235, lng: 149.0877402} - { name: Curran Drive,stop_code: Wjz7ilp, lat: -35.1856235, lng: 149.0877402}
- { name: Ayers Fowler Street,stop_code: Wjz7i7r, lat: -35.1841251, lng: 149.0850218} - { name: Ayers Fowler Street,stop_code: Wjz7i7r, lat: -35.1841251, lng: 149.0850218}
- { name: McClelland Avenue,stop_code: Wjz7jsi, lat: -35.1807665, lng: 149.0890046} - { name: McClelland Avenue,stop_code: Wjz7jsi, lat: -35.1807665, lng: 149.0890046}
- { name: Whiteside Court,stop_code: Wjz7qfu, lat: -35.1838151, lng: 149.0974127} - { name: Whiteside Court,stop_code: Wjz7qfu, lat: -35.1838151, lng: 149.0974127}
- { name: Oldershaw Court,stop_code: Wjz7qvq, lat: -35.1841768, lng: 149.1001944} - { name: Oldershaw Court,stop_code: Wjz7qvq, lat: -35.1841768, lng: 149.1001944}
- { name: Ryder Place,stop_code: Wjz7qkM, lat: -35.1864502, lng: 149.0992461} - { name: Ryder Place,stop_code: Wjz7qkM, lat: -35.1864502, lng: 149.0992461}
- { name: Lexcen Avenue,stop_code: Wjz7qwq, lat: -35.1890336, lng: 149.101522} - { name: Lexcen Avenue,stop_code: Wjz7qwq, lat: -35.1890336, lng: 149.101522}
- { name: Anne Clark Avenue,stop_code: Wjz7rOj, lat: -35.1820066, lng: 149.104114} - { name: Anne Clark Avenue,stop_code: Wjz7rOj, lat: -35.1820066, lng: 149.104114}
- { name: Biddell Place,stop_code: Wjz7rMm, lat: -35.1831434, lng: 149.104114} - { name: Biddell Place,stop_code: Wjz7rMm, lat: -35.1831434, lng: 149.104114}
- { name: Lexcen Avenue,stop_code: Wjz7q-_, lat: -35.1844351, lng: 149.1063899} - { name: Lexcen Avenue,stop_code: Wjz7q-_, lat: -35.1844351, lng: 149.1063899}
- { name: Quist Place,stop_code: Wjz7yfG, lat: -35.1841768, lng: 149.108729} - { name: Quist Place,stop_code: Wjz7yfG, lat: -35.1841768, lng: 149.108729}
- { name: Kelleway Avenue,stop_code: Wjz7r-a, lat: -35.1793714, lng: 149.1053784} - { name: Kelleway Avenue,stop_code: Wjz7r-a, lat: -35.1793714, lng: 149.1053784}
- { name: Wanganeen Avenue,stop_code: Wjz7Add, lat: -35.1743073, lng: 149.10816} - { name: Wanganeen Avenue,stop_code: Wjz7Add, lat: -35.1743073, lng: 149.10816}
- { name: Bimbiang Crescent,stop_code: Wjz7tOr, lat: -35.1710517, lng: 149.1042404} - { name: Bimbiang Crescent,stop_code: Wjz7tOr, lat: -35.1710517, lng: 149.1042404}
- { name: Bargang Crescent,stop_code: Wjz7txI, lat: -35.1716718, lng: 149.1018381} - { name: Bargang Crescent,stop_code: Wjz7txI, lat: -35.1716718, lng: 149.1018381}
- { name: Horse Park Drive,stop_code: Wjz7tug, lat: -35.1685711, lng: 149.0999415} - { name: Horse Park Drive,stop_code: Wjz7tug, lat: -35.1685711, lng: 149.0999415}
- { name: Horse Park Drive,stop_code: Wjz7tvK, lat: -35.1673308, lng: 149.1005105} - { name: Horse Park Drive,stop_code: Wjz7tvK, lat: -35.1673308, lng: 149.1005105}
- { name: Warabin Crescent,stop_code: Wjz7tLG, lat: -35.1677443, lng: 149.1032921} - { name: Warabin Crescent,stop_code: Wjz7tLG, lat: -35.1677443, lng: 149.1032921}
- { name: Wanganeen Avenue,stop_code: Wjz7Bg7, lat: -35.1720853, lng: 149.109298} - { name: Wanganeen Avenue,stop_code: Wjz7Bg7, lat: -35.1720853, lng: 149.109298}
- { name: Bunburung Close,stop_code: Wjz7BqG, lat: -35.1711551, lng: 149.1115106} - { name: Bunburung Close,stop_code: Wjz7BqG, lat: -35.1711551, lng: 149.1115106}
- { name: Unaipon Avenue,stop_code: Wjz7BC3, lat: -35.1683127, lng: 149.1120164} - { name: Unaipon Avenue,stop_code: Wjz7BC3, lat: -35.1683127, lng: 149.1120164}
- { name: Gurubun Close,stop_code: Wjz7BJK, lat: -35.1687262, lng: 149.1142923} - { name: Gurubun Close,stop_code: Wjz7BJK, lat: -35.1687262, lng: 149.1142923}
- { name: Deumonga Court,stop_code: Wjz7BED, lat: -35.1720853, lng: 149.1141026} - { name: Deumonga Court,stop_code: Wjz7BED, lat: -35.1720853, lng: 149.1141026}
- { name: Mirrabei Drive,stop_code: Wjz7BWN, lat: -35.1712067, lng: 149.1171372} - { name: Mirrabei Drive,stop_code: Wjz7BWN, lat: -35.1712067, lng: 149.1171372}
- { name: Ferguson Circuit,stop_code: Wjz7AGv, lat: -35.1762193, lng: 149.113913} - { name: Ferguson Circuit,stop_code: Wjz7AGv, lat: -35.1762193, lng: 149.113913}
- { name: Taggerty Street,stop_code: Wjz7AEw, lat: -35.1781829, lng: 149.1141659} - { name: Taggerty Street,stop_code: Wjz7AEw, lat: -35.1781829, lng: 149.1141659}
- { name: Tipiloura Street,stop_code: Wjz7CqJ, lat: -35.1654186, lng: 149.1114474} - { name: Tipiloura Street,stop_code: Wjz7CqJ, lat: -35.1654186, lng: 149.1114474}
- { name: Windradyne Street,stop_code: Wjz7CA3, lat: -35.16423, lng: 149.1119532} - { name: Windradyne Street,stop_code: Wjz7CA3, lat: -35.16423, lng: 149.1119532}
- { name: Mirrabei Drive,stop_code: Wjz7CKg, lat: -35.1630413, lng: 149.1137233} - { name: Mirrabei Drive,stop_code: Wjz7CKg, lat: -35.1630413, lng: 149.1137233}
- { name: Naas Close,stop_code: Wjz7IDY, lat: -35.1730154, lng: 149.1242809} - { name: Naas Close,stop_code: Wjz7IDY, lat: -35.1730154, lng: 149.1242809}
- { name: Paul Coe Crescent,stop_code: Wjz7Ikc, lat: -35.1750825, lng: 149.1204878} - { name: Paul Coe Crescent,stop_code: Wjz7Ikc, lat: -35.1750825, lng: 149.1204878}
- { name: Milari Street,stop_code: Wjz7HfF, lat: -35.178803, lng: 149.1197924} - { name: Milari Street,stop_code: Wjz7HfF, lat: -35.178803, lng: 149.1197924}
- { name: Paul Coe Crescent,stop_code: Wjz7IoZ, lat: -35.1777695, lng: 149.1227637} - { name: Paul Coe Crescent,stop_code: Wjz7IoZ, lat: -35.1777695, lng: 149.1227637}
- { name: Shoalhaven Avenue,stop_code: Wjz7IuJ, lat: -35.1736356, lng: 149.1225108} - { name: Shoalhaven Avenue,stop_code: Wjz7IuJ, lat: -35.1736356, lng: 149.1225108}
- { name: Tyenna Close,stop_code: Wjz7JP1, lat: -35.1705349, lng: 149.1257982} - { name: Tyenna Close,stop_code: Wjz7JP1, lat: -35.1705349, lng: 149.1257982}
- { name: Katherine Avenue,stop_code: Wjz7R6d, lat: -35.1681577, lng: 149.1286431} - { name: Katherine Avenue,stop_code: Wjz7R6d, lat: -35.1681577, lng: 149.1286431}
- { name: Timboram Street,stop_code: Wjz7R5z, lat: -35.1690363, lng: 149.1291488} - { name: Timboram Street,stop_code: Wjz7R5z, lat: -35.1690363, lng: 149.1291488}
- { name: Carstairs Circuit,stop_code: Wjz7RHe, lat: -35.1700698, lng: 149.135534} - { name: Carstairs Circuit,stop_code: Wjz7RHe, lat: -35.1700698, lng: 149.135534}
- { name: Horse Park Drive,stop_code: Wjz7SN-, lat: -35.1660013, lng: 149.1378981} - { name: Horse Park Drive,stop_code: Wjz7SN-, lat: -35.1660013, lng: 149.1378981}
- { name: Boreham Lane,stop_code: Wjz7PIc, lat: -35.1805599, lng: 149.135534} - { name: Boreham Lane,stop_code: Wjz7PIc, lat: -35.1805599, lng: 149.135534}
- { name: Swain Street,stop_code: Wjz7Pjj, lat: -35.1813349, lng: 149.1316144} - { name: Swain Street,stop_code: Wjz7Pjj, lat: -35.1813349, lng: 149.1316144}
- { name: Gundaroo Drive,stop_code: Wjz7yNW, lat: -35.1883262, lng: 149.1159763} - { name: Gundaroo Drive,stop_code: Wjz7yNW, lat: -35.1883262, lng: 149.1159763}
- { name: Hibberson Street,stop_code: Wjz7OBc, lat: -35.1853732, lng: 149.1341431} - { name: Hibberson Street,stop_code: Wjz7OBc, lat: -35.1853732, lng: 149.1341431}
- { name: Sarre Street,stop_code: Wjz7PNV, lat: -35.1828992, lng: 149.1380246} - { name: Sarre Street,stop_code: Wjz7PNV, lat: -35.1828992, lng: 149.1380246}
- { name: Gundaroo Drive,stop_code: Wjz7X3O, lat: -35.1814007, lng: 149.1404901} - { name: Gundaroo Drive,stop_code: Wjz7X3O, lat: -35.1814007, lng: 149.1404901}
- { name: Tesselaar Street,stop_code: Wjz7Xiv, lat: -35.1817108, lng: 149.1427028} - { name: Tesselaar Street,stop_code: Wjz7Xiv, lat: -35.1817108, lng: 149.1427028}
- { name: Sarson Street,stop_code: Wjzf31y, lat: -35.1828475, lng: 149.151111} - { name: Sarson Street,stop_code: Wjzf31y, lat: -35.1828475, lng: 149.151111}
- { name: Kalianna Street,stop_code: Wjzf2hJ, lat: -35.1880144, lng: 149.154019} - { name: Kalianna Street,stop_code: Wjzf2hJ, lat: -35.1880144, lng: 149.154019}
- { name: Nimbera Street,stop_code: Wjzf1X3, lat: -35.1923543, lng: 149.1600249} - { name: Nimbera Street,stop_code: Wjzf1X3, lat: -35.1923543, lng: 149.1600249}
- { name: Mapleton Avenue,stop_code: Wjzf91m, lat: -35.1934909, lng: 149.1618582} - { name: Mapleton Avenue,stop_code: Wjzf91m, lat: -35.1934909, lng: 149.1618582}
- { name: Elabana Street,stop_code: Wjzf0EJ, lat: -35.1997419, lng: 149.1581283} - { name: Elabana Street,stop_code: Wjzf0EJ, lat: -35.1997419, lng: 149.1581283}
- { name: Cudgewa Lane,stop_code: Wjze7Cp, lat: -35.2014466, lng: 149.1565478} - { name: Cudgewa Lane,stop_code: Wjze7Cp, lat: -35.2014466, lng: 149.1565478}
- { name: Oodgeroo Avenue,stop_code: Wjz6_7M, lat: -35.2008784, lng: 149.1404901} - { name: Oodgeroo Avenue,stop_code: Wjz6_7M, lat: -35.2008784, lng: 149.1404901}
- { name: Hoskins Street,stop_code: Wjz6TZN, lat: -35.2021182, lng: 149.1392257} - { name: Hoskins Street,stop_code: Wjz6TZN, lat: -35.2021182, lng: 149.1392257}
- { name: The Valley Avenue,stop_code: Wjz7GPB, lat: -35.1867085, lng: 149.1264936} - { name: The Valley Avenue,stop_code: Wjz7GPB, lat: -35.1867085, lng: 149.1264936}
- { name: The Valley Avenue,stop_code: Wjz7Gxm, lat: -35.188002, lng: 149.1234035} - { name: The Valley Avenue,stop_code: Wjz7Gxm, lat: -35.188002, lng: 149.1234035}
- { name: Kosciuszko Avenue,stop_code: Wjz7F5C, lat: -35.1906966, lng: 149.118141} - { name: Kosciuszko Avenue,stop_code: Wjz7F5C, lat: -35.1906966, lng: 149.118141}
- { name: Burrowa Street,stop_code: Wjz7xJz, lat: -35.191011, lng: 149.1141277} - { name: Burrowa Street,stop_code: Wjz7xJz, lat: -35.191011, lng: 149.1141277}
- { name: Kosciuszko Avenue,stop_code: Wjz7wZg, lat: -35.1967555, lng: 149.1165529} - { name: Kosciuszko Avenue,stop_code: Wjz7wZg, lat: -35.1967555, lng: 149.1165529}
- { name: Kosciuszko Avenue,stop_code: Wjz7EjH, lat: -35.1978404, lng: 149.1211679} - { name: Kosciuszko Avenue,stop_code: Wjz7EjH, lat: -35.1978404, lng: 149.1211679}
- { name: Kosciuszko Avenue,stop_code: Wjz7Ezf, lat: -35.1975304, lng: 149.1231277} - { name: Kosciuszko Avenue,stop_code: Wjz7Ezf, lat: -35.1975304, lng: 149.1231277}
- { name: Everard Street,stop_code: Wjz7FNw, lat: -35.193955, lng: 149.126474} - { name: Everard Street,stop_code: Wjz7FNw, lat: -35.193955, lng: 149.126474}
- { name: Vicars Street,stop_code: Wjz6-16, lat: -35.20994, lng: 149.1394383} - { name: Vicars Street,stop_code: Wjz6-16, lat: -35.20994, lng: 149.1394383}
- { name: McEacharn Place,stop_code: Wjz6Zb2, lat: -35.214395, lng: 149.1408607} - { name: McEacharn Place,stop_code: Wjz6Zb2, lat: -35.214395, lng: 149.1408607}
- { name: Brookes Street,stop_code: Wjz6Z8D, lat: -35.216009, lng: 149.1414929} - { name: Brookes Street,stop_code: Wjz6Z8D, lat: -35.216009, lng: 149.1414929}
- { name: Grimwade Street,stop_code: Wjz6QPM, lat: -35.2200763, lng: 149.1377788} - { name: Grimwade Street,stop_code: Wjz6QPM, lat: -35.2200763, lng: 149.1377788}
- { name: Brookes Street,stop_code: Wjz6Yc1, lat: -35.2193016, lng: 149.1407817} - { name: Brookes Street,stop_code: Wjz6Yc1, lat: -35.2193016, lng: 149.1407817}
- { name: Darling Street,stop_code: Wjz6YiM, lat: -35.2207864, lng: 149.1433105} - { name: Darling Street,stop_code: Wjz6YiM, lat: -35.2207864, lng: 149.1433105}
- { name: Flemington Road,stop_code: Wjz6XiO, lat: -35.226071, lng: 149.143256} - { name: Flemington Road,stop_code: Wjz6XiO, lat: -35.226071, lng: 149.143256}
- { name: Well Station Road,stop_code: Wjze2eG, lat: -35.2288072, lng: 149.1527323} - { name: Well Station Road,stop_code: Wjze2eG, lat: -35.2288072, lng: 149.1527323}
- { name: Well Station Road,stop_code: Wjze3gN, lat: -35.2275265, lng: 149.154199} - { name: Well Station Road,stop_code: Wjze3gN, lat: -35.2275265, lng: 149.154199}
- { name: Federal Highway,stop_code: Wjze3Vq, lat: -35.2267416, lng: 149.1606727} - { name: Federal Highway,stop_code: Wjze3Vq, lat: -35.2267416, lng: 149.1606727}
- { name: Federal Highway,stop_code: Wjzebjj, lat: -35.2253369, lng: 149.1645164} - { name: Federal Highway,stop_code: Wjzebjj, lat: -35.2253369, lng: 149.1645164}
- { name: Antill Street,stop_code: Wjze8v0, lat: -35.2393099, lng: 149.1654981} - { name: Antill Street,stop_code: Wjze8v0, lat: -35.2393099, lng: 149.1654981}
- { name: Fison Street,stop_code: Wjze8bf, lat: -35.2414165, lng: 149.1630705} - { name: Fison Street,stop_code: Wjze8bf, lat: -35.2414165, lng: 149.1630705}
- { name: Dobbie Place,stop_code: Wjze0Pi, lat: -35.2418709, lng: 149.1591256} - { name: Dobbie Place,stop_code: Wjze0Pi, lat: -35.2418709, lng: 149.1591256}
- { name: Knox Street,stop_code: Wjze0vR, lat: -35.2388968, lng: 149.1555853} - { name: Knox Street,stop_code: Wjze0vR, lat: -35.2388968, lng: 149.1555853}
- { name: Dickinson Street,stop_code: Wjze1c2, lat: -35.2356747, lng: 149.1518427} - { name: Dickinson Street,stop_code: Wjze1c2, lat: -35.2356747, lng: 149.1518427}
- { name: Harvey Street,stop_code: Wjze1gi, lat: -35.2384424, lng: 149.1535117} - { name: Harvey Street,stop_code: Wjze1gi, lat: -35.2384424, lng: 149.1535117}
- { name: Bradfield Street,stop_code: Wjz6UYK, lat: -35.2407969, lng: 149.1499714} - { name: Bradfield Street,stop_code: Wjz6UYK, lat: -35.2407969, lng: 149.1499714}
- { name: Atherton Street,stop_code: Wjz6Upu, lat: -35.2429035, lng: 149.1442058} - { name: Atherton Street,stop_code: Wjz6Upu, lat: -35.2429035, lng: 149.1442058}
- { name: Melba Street,stop_code: Wjz6Ugw, lat: -35.2441014, lng: 149.142992} - { name: Melba Street,stop_code: Wjz6Ugw, lat: -35.2441014, lng: 149.142992}
- { name: Melba Street,stop_code: Wjz5_ie, lat: -35.2476948, lng: 149.1423851} - { name: Melba Street,stop_code: Wjz5_ie, lat: -35.2476948, lng: 149.1423851}
- { name: Antill Street,stop_code: Wjz5_y0, lat: -35.2482318, lng: 149.1449139} - { name: Antill Street,stop_code: Wjz5_y0, lat: -35.2482318, lng: 149.1449139}
- { name: Antill Street,stop_code: Wjzd73N, lat: -35.2474057, lng: 149.1515393} - { name: Antill Street,stop_code: Wjzd73N, lat: -35.2474057, lng: 149.1515393}
- { name: Antill Street,stop_code: Wjzd7sL, lat: -35.2462079, lng: 149.1554841} - { name: Antill Street,stop_code: Wjzd7sL, lat: -35.2462079, lng: 149.1554841}
- { name: Madigan Street,stop_code: Wjzd7_6, lat: -35.2443079, lng: 149.1601371} - { name: Madigan Street,stop_code: Wjzd7_6, lat: -35.2443079, lng: 149.1601371}
- { name: Madigan Street,stop_code: Wjzdfaz, lat: -35.2479426, lng: 149.1635256} - { name: Madigan Street,stop_code: Wjzdfaz, lat: -35.2479426, lng: 149.1635256}
- { name: Madigan Street,stop_code: Wjzd6XP, lat: -35.2527713, lng: 149.1610527} - { name: Madigan Street,stop_code: Wjzd6XP, lat: -35.2527713, lng: 149.1610527}
- { name: Phillip Avenue,stop_code: Wjzd6Pn, lat: -35.2524079, lng: 149.1590701} - { name: Phillip Avenue,stop_code: Wjzd6Pn, lat: -35.2524079, lng: 149.1590701}
- { name: Salomons Place,stop_code: Wjzd6lW, lat: -35.2515158, lng: 149.1544172} - { name: Salomons Place,stop_code: Wjzd6lW, lat: -35.2515158, lng: 149.1544172}
- { name: Agnew Street,stop_code: Wjzd6iW, lat: -35.2535643, lng: 149.1544576} - { name: Agnew Street,stop_code: Wjzd6iW, lat: -35.2535643, lng: 149.1544576}
- { name: Bourke Street,stop_code: Wjz4Pt5, lat: -35.3116531, lng: 149.1326324} - { name: Bourke Street,stop_code: Wjz4Pt5, lat: -35.3116531, lng: 149.1326324}
- { name: Nyrang Street,stop_code: Wjzc1qE, lat: -35.3251161, lng: 149.1555115} - { name: Nyrang Street,stop_code: Wjzc1qE, lat: -35.3251161, lng: 149.1555115}
- { name: Bunda Street,stop_code: Wjz5NeC, lat: -35.2778798, lng: 149.1305995} - { name: Bunda Street,stop_code: Wjz5NeC, lat: -35.2778798, lng: 149.1305995}
- { name: Justinian Street,stop_code: Wjz3mWn, lat: -35.3409621, lng: 149.0945298} - { name: Justinian Street,stop_code: Wjz3mWn, lat: -35.3409621, lng: 149.0945298}
- { name: Wisdom Street,stop_code: Wjz3mQ4, lat: -35.3398419, lng: 149.0928819} - { name: Wisdom Street,stop_code: Wjz3mQ4, lat: -35.3398419, lng: 149.0928819}
- { name: Robson Street,stop_code: Wjz3C9Q, lat: -35.3419855, lng: 149.108934} - { name: Robson Street,stop_code: Wjz3C9Q, lat: -35.3419855, lng: 149.108934}
- { name: Ingamells Street,stop_code: Wjz3uJV, lat: -35.339486, lng: 149.1035524} - { name: Ingamells Street,stop_code: Wjz3uJV, lat: -35.339486, lng: 149.1035524}
- { name: Robson Street,stop_code: Wjz3C9J, lat: -35.3418945, lng: 149.1087966} - { name: Robson Street,stop_code: Wjz3C9J, lat: -35.3418945, lng: 149.1087966}
- { name: Wisdom Street,stop_code: Wjz3n-4, lat: -35.3330183, lng: 149.0941258} - { name: Wisdom Street,stop_code: Wjz3n-4, lat: -35.3330183, lng: 149.0941258}
- { name: Kent Street,stop_code: Wjz4qia, lat: -35.3194535, lng: 149.0984183} - { name: Kent Street,stop_code: Wjz4qia, lat: -35.3194535, lng: 149.0984183}
- { name: Kent Street,stop_code: Wjz4gXk, lat: -35.3296011, lng: 149.0945736} - { name: Kent Street,stop_code: Wjz4gXk, lat: -35.3296011, lng: 149.0945736}
- { name: McCaughey Street,stop_code: Wjz5Guy, lat: -35.2727878, lng: 149.1223747} - { name: McCaughey Street,stop_code: Wjz5Guy, lat: -35.2727878, lng: 149.1223747}
- { name: McCaughey Street,stop_code: Wjz5Iw8, lat: -35.2660466, lng: 149.1231132} - { name: McCaughey Street,stop_code: Wjz5Iw8, lat: -35.2660466, lng: 149.1231132}
- { name: Macpherson Street,stop_code: Wjz5Imu, lat: -35.2614148, lng: 149.1208459} - { name: Macpherson Street,stop_code: Wjz5Imu, lat: -35.2614148, lng: 149.1208459}
- { name: Macarthur Avenue,stop_code: Wjz5Jpu, lat: -35.2594072, lng: 149.1221624} - { name: Macarthur Avenue,stop_code: Wjz5Jpu, lat: -35.2594072, lng: 149.1221624}
- { name: Karri Street,stop_code: Wjz5JuJ, lat: -35.2560391, lng: 149.1225279} - { name: Karri Street,stop_code: Wjz5JuJ, lat: -35.2560391, lng: 149.1225279}
- { name: Jarrah Street,stop_code: Wjz5KgT, lat: -35.2544701, lng: 149.1213129} - { name: Jarrah Street,stop_code: Wjz5KgT, lat: -35.2544701, lng: 149.1213129}
- { name: Fawkner Street,stop_code: Wjz5OIf, lat: -35.2737328, lng: 149.1354944} - { name: Fawkner Street,stop_code: Wjz5OIf, lat: -35.2737328, lng: 149.1354944}
- { name: Ainslie Avenue,stop_code: Wjz5V64, lat: -35.2780918, lng: 149.1394963} - { name: Ainslie Avenue,stop_code: Wjz5V64, lat: -35.2780918, lng: 149.1394963}
- { name: Ainslie Avenue,stop_code: Wjz5NRJ, lat: -35.2787111, lng: 149.1375365} - { name: Ainslie Avenue,stop_code: Wjz5NRJ, lat: -35.2787111, lng: 149.1375365}
- { name: Gooreen Street,stop_code: Wjz5Vls, lat: -35.2787911, lng: 149.1427895} - { name: Gooreen Street,stop_code: Wjz5Vls, lat: -35.2787911, lng: 149.1427895}
- { name: Limestone Avenue,stop_code: Wjz5VAq, lat: -35.2796604, lng: 149.14553} - { name: Limestone Avenue,stop_code: Wjz5VAq, lat: -35.2796604, lng: 149.14553}
- { name: Fairbairn Avenue,stop_code: Wjz5VUU, lat: -35.2825429, lng: 149.15037} - { name: Fairbairn Avenue,stop_code: Wjz5VUU, lat: -35.2825429, lng: 149.15037}
- { name: Fairbairn Avenue,stop_code: Wjzd8br, lat: -35.2857037, lng: 149.16333} - { name: Fairbairn Avenue,stop_code: Wjzd8br, lat: -35.2857037, lng: 149.16333}
- { name: Glossop Crescent,stop_code: Wjzd0yM, lat: -35.2866868, lng: 149.1570161} - { name: Glossop Crescent,stop_code: Wjzd0yM, lat: -35.2866868, lng: 149.1570161}
- { name: Savige Street,stop_code: Wjzd02s, lat: -35.286331, lng: 149.1509776} - { name: Savige Street,stop_code: Wjzd02s, lat: -35.286331, lng: 149.1509776}
- { name: Chowne Street,stop_code: Wjz5UHK, lat: -35.2854924, lng: 149.1472635} - { name: Chowne Street,stop_code: Wjz5UHK, lat: -35.2854924, lng: 149.1472635}
- { name: Euree Street,stop_code: Wjz5Vg4, lat: -35.2821666, lng: 149.1422877} - { name: Euree Street,stop_code: Wjz5Vg4, lat: -35.2821666, lng: 149.1422877}
- { name: White Crescent,stop_code: Wjzd0EU, lat: -35.2880133, lng: 149.158501} - { name: White Crescent,stop_code: Wjzd0EU, lat: -35.2880133, lng: 149.158501}
- { name: Chauvel Street,stop_code: Wjzc7si, lat: -35.2905765, lng: 149.1549056} - { name: Chauvel Street,stop_code: Wjzc7si, lat: -35.2905765, lng: 149.1549056}
- { name: Bungey Street,stop_code: Wjzc7bs, lat: -35.2911202, lng: 149.1523397} - { name: Bungey Street,stop_code: Wjzc7bs, lat: -35.2911202, lng: 149.1523397}
- { name: Constitution Avenue,stop_code: Wjz4_wS, lat: -35.2930129, lng: 149.145973} - { name: Constitution Avenue,stop_code: Wjz4_wS, lat: -35.2930129, lng: 149.145973}
- { name: Wendouree Drive,stop_code: Wjz4_jm, lat: -35.2909901, lng: 149.1425844} - { name: Wendouree Drive,stop_code: Wjz4_jm, lat: -35.2909901, lng: 149.1425844}
- { name: Parkes Way,stop_code: Wjz5MEL, lat: -35.2874399, lng: 149.1362625} - { name: Parkes Way,stop_code: Wjz5MEL, lat: -35.2874399, lng: 149.1362625}
- { name: General Bridges Drive,stop_code: Wjzce4H, lat: -35.2960675, lng: 149.1623594} - { name: General Bridges Drive,stop_code: Wjzce4H, lat: -35.2960675, lng: 149.1623594}
- { name: Vowels Road,stop_code: WjzceFT, lat: -35.2977187, lng: 149.1693894} - { name: Vowels Road,stop_code: WjzceFT, lat: -35.2977187, lng: 149.1693894}
- { name: Vowels Road,stop_code: WjzcdDs, lat: -35.299411, lng: 149.1675181} - { name: Vowels Road,stop_code: WjzcdDs, lat: -35.299411, lng: 149.1675181}
- { name: Morshead Drive,stop_code: Wjzcdi7, lat: -35.3025893, lng: 149.1642813} - { name: Morshead Drive,stop_code: Wjzcdi7, lat: -35.3025893, lng: 149.1642813}
- { name: Morshead Drive,stop_code: Wjzcd8D, lat: -35.3039101, lng: 149.1635732} - { name: Morshead Drive,stop_code: Wjzcd8D, lat: -35.3039101, lng: 149.1635732}
- { name: Menindee Drive,stop_code: Wjzc59p, lat: -35.3037863, lng: 149.1523455} - { name: Menindee Drive,stop_code: Wjzc59p, lat: -35.3037863, lng: 149.1523455}
- { name: Menindee Drive,stop_code: Wjzc45R, lat: -35.3061389, lng: 149.1514351} - { name: Menindee Drive,stop_code: Wjzc45R, lat: -35.3061389, lng: 149.1514351}
- { name: Canberra Avenue,stop_code: Wjz4VKr, lat: -35.3221513, lng: 149.1468833} - { name: Canberra Avenue,stop_code: Wjz4VKr, lat: -35.3221513, lng: 149.1468833}
- { name: Canberra Avenue,stop_code: Wjz4VRQ, lat: -35.3226878, lng: 149.148704} - { name: Canberra Avenue,stop_code: Wjz4VRQ, lat: -35.3226878, lng: 149.148704}
- { name: Wickham Crescent,stop_code: Wjz4FEJ, lat: -35.3260887, lng: 149.125286} - { name: Wickham Crescent,stop_code: Wjz4FEJ, lat: -35.3260887, lng: 149.125286}
- { name: Vancouver Street,stop_code: Wjz4ECF, lat: -35.3278218, lng: 149.1238193} - { name: Vancouver Street,stop_code: Wjz4ECF, lat: -35.3278218, lng: 149.1238193}
- { name: Friendship Street,stop_code: Wjz3LP9, lat: -35.3353724, lng: 149.1259941} - { name: Friendship Street,stop_code: Wjz3LP9, lat: -35.3353724, lng: 149.1259941}
- { name: Quiros Street,stop_code: Wjz3LN9, lat: -35.3367339, lng: 149.1259435} - { name: Quiros Street,stop_code: Wjz3LN9, lat: -35.3367339, lng: 149.1259435}
- { name: Bremer Street,stop_code: Wjz4MAz, lat: -35.3290192, lng: 149.1346333} - { name: Bremer Street,stop_code: Wjz4MAz, lat: -35.3290192, lng: 149.1346333}
- { name: Favenc Circle,stop_code: Wjz4Ue5, lat: -35.327397, lng: 149.140921} - { name: Favenc Circle,stop_code: Wjz4Ue5, lat: -35.327397, lng: 149.140921}
- { name: Stuart Street,stop_code: Wjz4Ujk, lat: -35.3295839, lng: 149.1425394} - { name: Stuart Street,stop_code: Wjz4Ujk, lat: -35.3295839, lng: 149.1425394}
- { name: Captain Cook Crescent,stop_code: Wjz3_Ji, lat: -35.3339111, lng: 149.146681} - { name: Captain Cook Crescent,stop_code: Wjz3_Ji, lat: -35.3339111, lng: 149.146681}
- { name: McKinlay Place,stop_code: Wjz4UwD, lat: -35.3313913, lng: 149.1456952} - { name: McKinlay Place,stop_code: Wjz4UwD, lat: -35.3313913, lng: 149.1456952}
- { name: McKinlay Street,stop_code: Wjz4VEF, lat: -35.3264205, lng: 149.1472235} - { name: McKinlay Street,stop_code: Wjz4VEF, lat: -35.3264205, lng: 149.1472235}
- { name: Leeton Street,stop_code: Wjzc1n0, lat: -35.3216636, lng: 149.1532292} - { name: Leeton Street,stop_code: Wjzc1n0, lat: -35.3216636, lng: 149.1532292}
- { name: Boolimba Crescent,stop_code: Wjzc090, lat: -35.3312849, lng: 149.15186} - { name: Boolimba Crescent,stop_code: Wjzc090, lat: -35.3312849, lng: 149.15186}
- { name: Iluka Street,stop_code: Wjzb7nW, lat: -35.3324815, lng: 149.1544899} - { name: Iluka Street,stop_code: Wjzb7nW, lat: -35.3324815, lng: 149.1544899}
- { name: Mugga Way,stop_code: Wjz3Kxb, lat: -35.342056, lng: 149.1231366} - { name: Mugga Way,stop_code: Wjz3Kxb, lat: -35.342056, lng: 149.1231366}
- { name: Mugga Way,stop_code: Wjz3JDp, lat: -35.3435515, lng: 149.1235159} - { name: Mugga Way,stop_code: Wjz3JDp, lat: -35.3435515, lng: 149.1235159}
- { name: Mugga Way,stop_code: Wjz3JJs, lat: -35.344686, lng: 149.1248435} - { name: Mugga Way,stop_code: Wjz3JJs, lat: -35.344686, lng: 149.1248435}
- { name: Beagle Street,stop_code: Wjz3Rdo, lat: -35.3450469, lng: 149.1304068} - { name: Beagle Street,stop_code: Wjz3Rdo, lat: -35.3450469, lng: 149.1304068}
- { name: Monaro Crescent,stop_code: Wjz3ShE, lat: -35.3422498, lng: 149.1321257} - { name: Monaro Crescent,stop_code: Wjz3ShE, lat: -35.3422498, lng: 149.1321257}
- { name: Astrolabe Street,stop_code: Wjz3T8Z, lat: -35.337043, lng: 149.1311337} - { name: Astrolabe Street,stop_code: Wjz3T8Z, lat: -35.337043, lng: 149.1311337}
- { name: Bell Street,stop_code: Wjz4MpW, lat: -35.3311406, lng: 149.1338209} - { name: Bell Street,stop_code: Wjz4MpW, lat: -35.3311406, lng: 149.1338209}
- { name: Goyder Street,stop_code: Wjz3-Jb, lat: -35.3392754, lng: 149.1466095} - { name: Goyder Street,stop_code: Wjz3-Jb, lat: -35.3392754, lng: 149.1466095}
- { name: Narupai Street,stop_code: Wjzb6cp, lat: -35.3401203, lng: 149.1523581} - { name: Narupai Street,stop_code: Wjzb6cp, lat: -35.3401203, lng: 149.1523581}
- { name: Kyeema Street,stop_code: Wjzb7wf, lat: -35.3368722, lng: 149.1561338} - { name: Kyeema Street,stop_code: Wjzb7wf, lat: -35.3368722, lng: 149.1561338}
- { name: Matina Street,stop_code: Wjzb7HN, lat: -35.335349, lng: 149.1583716} - { name: Matina Street,stop_code: Wjzb7HN, lat: -35.335349, lng: 149.1583716}
- { name: Kootara Crescent,stop_code: Wjzb7S4, lat: -35.3330282, lng: 149.1586877} - { name: Kootara Crescent,stop_code: Wjzb7S4, lat: -35.3330282, lng: 149.1586877}
- { name: Goyder Street,stop_code: Wjz3SUA, lat: -35.3426508, lng: 149.1388551} - { name: Goyder Street,stop_code: Wjz3SUA, lat: -35.3426508, lng: 149.1388551}
- { name: Narrabundah Lane,stop_code: Wjzb4vx, lat: -35.3490259, lng: 149.1553622} - { name: Narrabundah Lane,stop_code: Wjzb4vx, lat: -35.3490259, lng: 149.1553622}
- { name: Dalby Street,stop_code: Wjzc1tq, lat: -35.3228774, lng: 149.1550358} - { name: Dalby Street,stop_code: Wjzc1tq, lat: -35.3228774, lng: 149.1550358}
- { name: Canberra Avenue,stop_code: Wjzbfnr, lat: -35.332383, lng: 149.1647873} - { name: Canberra Avenue,stop_code: Wjzbfnr, lat: -35.332383, lng: 149.1647873}
- { name: Newcastle Street,stop_code: Wjzc9WV, lat: -35.3250576, lng: 149.1722805} - { name: Newcastle Street,stop_code: Wjzc9WV, lat: -35.3250576, lng: 149.1722805}
- { name: Albany Street,stop_code: WjzchQP, lat: -35.3235189, lng: 149.1817987} - { name: Albany Street,stop_code: WjzchQP, lat: -35.3235189, lng: 149.1817987}
- { name: Townsville Street,stop_code: Wjzcod5, lat: -35.3281204, lng: 149.1848684} - { name: Townsville Street,stop_code: Wjzcod5, lat: -35.3281204, lng: 149.1848684}
- { name: Townsville Street,stop_code: Wjzcoab, lat: -35.3303968, lng: 149.1849583} - { name: Townsville Street,stop_code: Wjzcoab, lat: -35.3303968, lng: 149.1849583}
- { name: Townsville Street,stop_code: WjzcgX_, lat: -35.3293219, lng: 149.1833416} - { name: Townsville Street,stop_code: WjzcgX_, lat: -35.3293219, lng: 149.1833416}
- { name: Jindalee Crescent,stop_code: Wjz3r_u, lat: -35.3540946, lng: 149.1057023} - { name: Jindalee Crescent,stop_code: Wjz3r_u, lat: -35.3540946, lng: 149.1057023}
- { name: Arrellah Place,stop_code: Wjz3rQi, lat: -35.3565695, lng: 149.104185} - { name: Arrellah Place,stop_code: Wjz3rQi, lat: -35.3565695, lng: 149.104185}
- { name: Coreen Place,stop_code: Wjz3z0c, lat: -35.3591474, lng: 149.106777} - { name: Coreen Place,stop_code: Wjz3z0c, lat: -35.3591474, lng: 149.106777}
- { name: Bromby Street,stop_code: Wjz3y4z, lat: -35.3619315, lng: 149.1072828} - { name: Bromby Street,stop_code: Wjz3y4z, lat: -35.3619315, lng: 149.1072828}
- { name: Yamba Drive,stop_code: Wjz3pZQ, lat: -35.366623, lng: 149.1062713} - { name: Yamba Drive,stop_code: Wjz3pZQ, lat: -35.366623, lng: 149.1062713}
- { name: Beasley Street,stop_code: Wjz3x3A, lat: -35.3680664, lng: 149.1072196} - { name: Beasley Street,stop_code: Wjz3x3A, lat: -35.3680664, lng: 149.1072196}
- { name: Bee Place,stop_code: Wjz3xwa, lat: -35.3702316, lng: 149.1122771} - { name: Bee Place,stop_code: Wjz3xwa, lat: -35.3702316, lng: 149.1122771}
- { name: Yamba Drive,stop_code: Wjz3wrK, lat: -35.3733761, lng: 149.1115817} - { name: Yamba Drive,stop_code: Wjz3wrK, lat: -35.3733761, lng: 149.1115817}
- { name: Dookie Street,stop_code: Wjz3woC, lat: -35.3754381, lng: 149.1112656} - { name: Dookie Street,stop_code: Wjz3woC, lat: -35.3754381, lng: 149.1112656}
- { name: Shepherdson Place,stop_code: Wjz2DPD, lat: -35.378737, lng: 149.1155013} - { name: Shepherdson Place,stop_code: Wjz2DPD, lat: -35.378737, lng: 149.1155013}
- { name: Pudney Street,stop_code: Wjz2DEs, lat: -35.3811081, lng: 149.1139208} - { name: Pudney Street,stop_code: Wjz2DEs, lat: -35.3811081, lng: 149.1139208}
- { name: Woodgate Street,stop_code: Wjz2C5I, lat: -35.3831852, lng: 149.1074202} - { name: Woodgate Street,stop_code: Wjz2C5I, lat: -35.3831852, lng: 149.1074202}
- { name: Muresk Street,stop_code: Wjz2uSZ, lat: -35.3823742, lng: 149.1050643} - { name: Muresk Street,stop_code: Wjz2uSZ, lat: -35.3823742, lng: 149.1050643}
- { name: Longerenong Street,stop_code: Wjz2vL4, lat: -35.3762782, lng: 149.1023627} - { name: Longerenong Street,stop_code: Wjz2vL4, lat: -35.3762782, lng: 149.1023627}
- { name: Pridham Street,stop_code: Wjz3oih, lat: -35.3744422, lng: 149.0986886} - { name: Pridham Street,stop_code: Wjz3oih, lat: -35.3744422, lng: 149.0986886}
- { name: Lambrigg Street,stop_code: Wjz3oeM, lat: -35.3718451, lng: 149.0980006} - { name: Lambrigg Street,stop_code: Wjz3oeM, lat: -35.3718451, lng: 149.0980006}
- { name: Beasley Street,stop_code: Wjz3hXO, lat: -35.3681696, lng: 149.0952079} - { name: Beasley Street,stop_code: Wjz3hXO, lat: -35.3681696, lng: 149.0952079}
- { name: Wilkins Street,stop_code: Wjz3peD, lat: -35.3657466, lng: 149.0976102} - { name: Wilkins Street,stop_code: Wjz3peD, lat: -35.3657466, lng: 149.0976102}
- { name: Prior Place,stop_code: Wjz3oge, lat: -35.3754535, lng: 149.0983799} - { name: Prior Place,stop_code: Wjz3oge, lat: -35.3754535, lng: 149.0983799}
- { name: Athllon Drive,stop_code: Wjz2nLE, lat: -35.3766237, lng: 149.0922366} - { name: Athllon Drive,stop_code: Wjz2nLE, lat: -35.3766237, lng: 149.0922366}
- { name: Brookman Street,stop_code: Wjz2nug, lat: -35.3773453, lng: 149.0890124} - { name: Brookman Street,stop_code: Wjz2nug, lat: -35.3773453, lng: 149.0890124}
- { name: Batchelor Street,stop_code: Wjz3gcu, lat: -35.3726637, lng: 149.0864364} - { name: Batchelor Street,stop_code: Wjz3gcu, lat: -35.3726637, lng: 149.0864364}
- { name: Gouger Street,stop_code: Wjz3gB5, lat: -35.3720623, lng: 149.0900243} - { name: Gouger Street,stop_code: Wjz3gB5, lat: -35.3720623, lng: 149.0900243}
- { name: Garratt Street,stop_code: Wjz2k5E, lat: -35.3945084, lng: 149.0853457} - { name: Garratt Street,stop_code: Wjz2k5E, lat: -35.3945084, lng: 149.0853457}
- { name: Sternberg Crescent,stop_code: Wjz2cKo, lat: -35.3937869, lng: 149.0809204} - { name: Sternberg Crescent,stop_code: Wjz2cKo, lat: -35.3937869, lng: 149.0809204}
- { name: Fincham Crescent,stop_code: Wjz2crQ, lat: -35.3954875, lng: 149.0787077} - { name: Fincham Crescent,stop_code: Wjz2crQ, lat: -35.3954875, lng: 149.0787077}
- { name: Byrne Street,stop_code: Wjz2kbO, lat: -35.3956421, lng: 149.0869894} - { name: Byrne Street,stop_code: Wjz2kbO, lat: -35.3956421, lng: 149.0869894}
- { name: Athllon Drive,stop_code: Wjz2lDC, lat: -35.3870716, lng: 149.090679} - { name: Athllon Drive,stop_code: Wjz2lDC, lat: -35.3870716, lng: 149.090679}
- { name: Sulwood Drive,stop_code: Wjz2u2j, lat: -35.3853192, lng: 149.095863} - { name: Sulwood Drive,stop_code: Wjz2u2j, lat: -35.3853192, lng: 149.095863}
- { name: Sulwood Drive,stop_code: Wjz2ugd, lat: -35.3865047, lng: 149.0985182} - { name: Sulwood Drive,stop_code: Wjz2ugd, lat: -35.3865047, lng: 149.0985182}
- { name: Sulwood Drive,stop_code: Wjz2tyn, lat: -35.3904732, lng: 149.1013631} - { name: Sulwood Drive,stop_code: Wjz2tyn, lat: -35.3904732, lng: 149.1013631}
- { name: Sulwood Drive,stop_code: Wjz2sLr, lat: -35.3928439, lng: 149.1028803} - { name: Sulwood Drive,stop_code: Wjz2sLr, lat: -35.3928439, lng: 149.1028803}
- { name: Lansell Circuit,stop_code: Wjz2qJ7, lat: -35.4048663, lng: 149.1024781} - { name: Lansell Circuit,stop_code: Wjz2qJ7, lat: -35.4048663, lng: 149.1024781}
- { name: Grattan Court,stop_code: Wjz2r9X, lat: -35.4024569, lng: 149.098142} - { name: Grattan Court,stop_code: Wjz2r9X, lat: -35.4024569, lng: 149.098142}
- { name: Wheeler Crescent,stop_code: Wjz2jFF, lat: -35.4026479, lng: 149.0922959} - { name: Wheeler Crescent,stop_code: Wjz2jFF, lat: -35.4026479, lng: 149.0922959}
- { name: Snowden Place,stop_code: Wjz2isR, lat: -35.4057431, lng: 149.0896883} - { name: Snowden Place,stop_code: Wjz2isR, lat: -35.4057431, lng: 149.0896883}
- { name: Sturdee Crescent,stop_code: Wjz2iVd, lat: -35.4077519, lng: 149.0942596} - { name: Sturdee Crescent,stop_code: Wjz2iVd, lat: -35.4077519, lng: 149.0942596}
- { name: Crocker Place,stop_code: Wjz2q9z, lat: -35.4079064, lng: 149.0976735} - { name: Crocker Place,stop_code: Wjz2q9z, lat: -35.4079064, lng: 149.0976735}
- { name: Bugden Avenue,stop_code: Wjz2F6d, lat: -35.4098598, lng: 149.1177053} - { name: Bugden Avenue,stop_code: Wjz2F6d, lat: -35.4098598, lng: 149.1177053}
- { name: Bugden Avenue,stop_code: Wjz2xyM, lat: -35.4130074, lng: 149.113099} - { name: Bugden Avenue,stop_code: Wjz2xyM, lat: -35.4130074, lng: 149.113099}
- { name: Woods Place,stop_code: Wjz2pVO, lat: -35.4135227, lng: 149.1062081} - { name: Woods Place,stop_code: Wjz2pVO, lat: -35.4135227, lng: 149.1062081}
- { name: Stacy Street,stop_code: Wjz2oQE, lat: -35.4171292, lng: 149.1046908} - { name: Stacy Street,stop_code: Wjz2oQE, lat: -35.4171292, lng: 149.1046908}
- { name: Gilday Place,stop_code: Wjz2Gff, lat: -35.403475, lng: 149.1191048} - { name: Gilday Place,stop_code: Wjz2Gff, lat: -35.403475, lng: 149.1191048}
- { name: Demaine Crescent,stop_code: Wjz2Gu5, lat: -35.404351, lng: 149.1216336} - { name: Demaine Crescent,stop_code: Wjz2Gu5, lat: -35.404351, lng: 149.1216336}
- { name: Coyne Street,stop_code: Wjz2FDo, lat: -35.4095553, lng: 149.1235301} - { name: Coyne Street,stop_code: Wjz2FDo, lat: -35.4095553, lng: 149.1235301}
- { name: Coyne Street,stop_code: Wjz2F_q, lat: -35.4093651, lng: 149.1276548} - { name: Coyne Street,stop_code: Wjz2F_q, lat: -35.4093651, lng: 149.1276548}
- { name: Akhurst Grove,stop_code: Wjz1cz3, lat: -35.4395376, lng: 149.079087} - { name: Akhurst Grove,stop_code: Wjz1cz3, lat: -35.4395376, lng: 149.079087}
- { name: Andrea Place,stop_code: Wjz1d0X, lat: -35.4360866, lng: 149.0748513} - { name: Andrea Place,stop_code: Wjz1d0X, lat: -35.4360866, lng: 149.0748513}
- { name: Andrea Place,stop_code: Wjz15Xb, lat: -35.4340778, lng: 149.0723858} - { name: Andrea Place,stop_code: Wjz15Xb, lat: -35.4340778, lng: 149.0723858}
- { name: Harcus Close,stop_code: Wjz1klr, lat: -35.4381985, lng: 149.087748} - { name: Harcus Close,stop_code: Wjz1klr, lat: -35.4381985, lng: 149.087748}
- { name: Woodcock Drive,stop_code: Wjz1kyn, lat: -35.4398982, lng: 149.0904032} - { name: Woodcock Drive,stop_code: Wjz1kyn, lat: -35.4398982, lng: 149.0904032}
- { name: Stella Hume Street,stop_code: Wjz16Q9, lat: -35.4280509, lng: 149.0709317} - { name: Stella Hume Street,stop_code: Wjz16Q9, lat: -35.4280509, lng: 149.0709317}
- { name: Ragless Circuit,stop_code: WjrWXL8, lat: -35.3985958, lng: 149.0586576} - { name: Ragless Circuit,stop_code: WjrWXL8, lat: -35.3985958, lng: 149.0586576}
- { name: Learmonth Drive,stop_code: WjrWXIP, lat: -35.4004264, lng: 149.0594265} - { name: Learmonth Drive,stop_code: WjrWXIP, lat: -35.4004264, lng: 149.0594265}
- { name: Meredith Circuit,stop_code: WjrWQRL, lat: -35.3938608, lng: 149.049706} - { name: Meredith Circuit,stop_code: WjrWQRL, lat: -35.3938608, lng: 149.049706}
- { name: Bateman Street,stop_code: WjrWRWi, lat: -35.3908805, lng: 149.0506492} - { name: Bateman Street,stop_code: WjrWRWi, lat: -35.3908805, lng: 149.0506492}
- { name: Boddington Crescent,stop_code: WjrWSX9, lat: -35.3847561, lng: 149.0504459} - { name: Boddington Crescent,stop_code: WjrWSX9, lat: -35.3847561, lng: 149.0504459}
- { name: Eagle Circuit,stop_code: WjrWSBZ, lat: -35.383041, lng: 149.0472484} - { name: Eagle Circuit,stop_code: WjrWSBZ, lat: -35.383041, lng: 149.0472484}
- { name: Archibald Street,stop_code: Wjz5LLF, lat: -35.2446872, lng: 149.1252507} - { name: Archibald Street,stop_code: Wjz5LLF, lat: -35.2446872, lng: 149.1252507}
- { name: Archibald Street,stop_code: Wjz5LDv, lat: -35.2442061, lng: 149.1235678} - { name: Archibald Street,stop_code: Wjz5LDv, lat: -35.2442061, lng: 149.1235678}
- { name: Tharwa Drive,stop_code: Wjz1gBy, lat: -35.4601891, lng: 149.0907826} - { name: Tharwa Drive,stop_code: Wjz1gBy, lat: -35.4601891, lng: 149.0907826}
- { name: Pocket Avenue,stop_code: Wjz0v3X, lat: -35.4670374, lng: 149.0967252} - { name: Pocket Avenue,stop_code: Wjz0v3X, lat: -35.4670374, lng: 149.0967252}
- { name: Troughton Street,stop_code: Wjz0unz, lat: -35.4697663, lng: 149.0990011} - { name: Troughton Street,stop_code: Wjz0unz, lat: -35.4697663, lng: 149.0990011}
- { name: Paperbark Street,stop_code: Wjz0uQv, lat: -35.4714653, lng: 149.1043747} - { name: Paperbark Street,stop_code: Wjz0uQv, lat: -35.4714653, lng: 149.1043747}
- { name: Wollemi Place,stop_code: Wjz0C4B, lat: -35.4716198, lng: 149.1071563} - { name: Wollemi Place,stop_code: Wjz0C4B, lat: -35.4716198, lng: 149.1071563}
- { name: Kallista Place,stop_code: Wjz0Cpn, lat: -35.4735247, lng: 149.1110759} - { name: Kallista Place,stop_code: Wjz0Cpn, lat: -35.4735247, lng: 149.1110759}
- { name: Wollemi Place,stop_code: Wjz0Bv9, lat: -35.4753782, lng: 149.1107598} - { name: Wollemi Place,stop_code: Wjz0Bv9, lat: -35.4753782, lng: 149.1107598}
- { name: Galbraith Close,stop_code: Wjz0t_T, lat: -35.4749148, lng: 149.1061448} - { name: Galbraith Close,stop_code: Wjz0t_T, lat: -35.4749148, lng: 149.1061448}
- { name: Bellchambers Crescent,stop_code: Wjz0tno, lat: -35.4754811, lng: 149.0988746} - { name: Bellchambers Crescent,stop_code: Wjz0tno, lat: -35.4754811, lng: 149.0988746}
- { name: Forsythe Street,stop_code: Wjz0u92, lat: -35.4739881, lng: 149.0969148} - { name: Forsythe Street,stop_code: Wjz0u92, lat: -35.4739881, lng: 149.0969148}
- { name: Menzies Court,stop_code: Wjz0lYC, lat: -35.4770256, lng: 149.0948286} - { name: Menzies Court,stop_code: Wjz0lYC, lat: -35.4770256, lng: 149.0948286}
- { name: Olive Pink Crescent,stop_code: Wjz0t9g, lat: -35.4795997, lng: 149.0972309} - { name: Olive Pink Crescent,stop_code: Wjz0t9g, lat: -35.4795997, lng: 149.0972309}
- { name: Tharwa Drive,stop_code: Wjz0kHU, lat: -35.4837695, lng: 149.0925527} - { name: Tharwa Drive,stop_code: Wjz0kHU, lat: -35.4837695, lng: 149.0925527}
- { name: Tharwa Drive,stop_code: Wjz0klX, lat: -35.4821222, lng: 149.0884434} - { name: Tharwa Drive,stop_code: Wjz0klX, lat: -35.4821222, lng: 149.0884434}
- { name: Tharwa Drive,stop_code: Wjz0lcW, lat: -35.477386, lng: 149.0870526} - { name: Tharwa Drive,stop_code: Wjz0lcW, lat: -35.477386, lng: 149.0870526}
- { name: McVilly Close,stop_code: Wjz0eVg, lat: -35.4740911, lng: 149.0835756} - { name: McVilly Close,stop_code: Wjz0eVg, lat: -35.4740911, lng: 149.0835756}
- { name: Robert Lewis Court,stop_code: Wjz0m65, lat: -35.4702811, lng: 149.0845871} - { name: Robert Lewis Court,stop_code: Wjz0m65, lat: -35.4702811, lng: 149.0845871}
- { name: Hickenbotham Street,stop_code: Wjz0n3A, lat: -35.4669344, lng: 149.0852193} - { name: Hickenbotham Street,stop_code: Wjz0n3A, lat: -35.4669344, lng: 149.0852193}
- { name: Oxenham Circuit,stop_code: Wjz1gnx, lat: -35.4589532, lng: 149.0880641} - { name: Oxenham Circuit,stop_code: Wjz1gnx, lat: -35.4589532, lng: 149.0880641}
- { name: Knoke Avenue,stop_code: Wjz1h9y, lat: -35.4574599, lng: 149.0866733} - { name: Knoke Avenue,stop_code: Wjz1h9y, lat: -35.4574599, lng: 149.0866733}
- { name: McGilvray Close,stop_code: Wjz1h4G, lat: -35.4554516, lng: 149.0853457} - { name: McGilvray Close,stop_code: Wjz1h4G, lat: -35.4554516, lng: 149.0853457}
- { name: Woodcock Drive,stop_code: Wjz1heN, lat: -35.4541126, lng: 149.0869262} - { name: Woodcock Drive,stop_code: Wjz1heN, lat: -35.4541126, lng: 149.0869262}
- { name: Donohoe Place,stop_code: Wjz1ic5, lat: -35.4496838, lng: 149.0858515} - { name: Donohoe Place,stop_code: Wjz1ic5, lat: -35.4496838, lng: 149.0858515}
- { name: Dempsey Place,stop_code: Wjz1bTA, lat: -35.4422159, lng: 149.0824376} - { name: Dempsey Place,stop_code: Wjz1bTA, lat: -35.4422159, lng: 149.0824376}
- { name: Akhurst Grove,stop_code: Wjz1cI3, lat: -35.438868, lng: 149.0804778} - { name: Akhurst Grove,stop_code: Wjz1cI3, lat: -35.438868, lng: 149.0804778}
- { name: Mackennal Street,stop_code: Wjz5Lh-, lat: -35.248398, lng: 149.12138} - { name: Mackennal Street,stop_code: Wjz5Lh-, lat: -35.248398, lng: 149.12138}
- { name: Dyson Street,stop_code: Wjz5Kve, lat: -35.2497723, lng: 149.1218849} - { name: Dyson Street,stop_code: Wjz5Kve, lat: -35.2497723, lng: 149.1218849}
- { name: Miller Street,stop_code: Wjz5CW3, lat: -35.2534813, lng: 149.1160707} - { name: Miller Street,stop_code: Wjz5CW3, lat: -35.2534813, lng: 149.1160707}
- { name: Miller Street,stop_code: Wjz5BPB, lat: -35.2580866, lng: 149.1154899} - { name: Miller Street,stop_code: Wjz5BPB, lat: -35.2580866, lng: 149.1154899}
- { name: Fairfax Street,stop_code: Wjz5BaH, lat: -35.2589798, lng: 149.1087583} - { name: Fairfax Street,stop_code: Wjz5BaH, lat: -35.2589798, lng: 149.1087583}
- { name: Miller Street,stop_code: Wjz5ASf, lat: -35.2613846, lng: 149.1149009} - { name: Miller Street,stop_code: Wjz5ASf, lat: -35.2613846, lng: 149.1149009}
- { name: David Street,stop_code: Wjz5zJi, lat: -35.2679801, lng: 149.113807} - { name: David Street,stop_code: Wjz5zJi, lat: -35.2679801, lng: 149.113807}
- { name: Nicholson Crescent,stop_code: Wjz5zOq, lat: -35.2700411, lng: 149.1153216} - { name: Nicholson Crescent,stop_code: Wjz5zOq, lat: -35.2700411, lng: 149.1153216}
- { name: Boldrewood Street,stop_code: Wjz5GeU, lat: -35.2729264, lng: 149.1200337} - { name: Boldrewood Street,stop_code: Wjz5GeU, lat: -35.2729264, lng: 149.1200337}
- { name: Colville Street,stop_code: Wjz6EBY, lat: -35.2403577, lng: 149.1242409} - { name: Colville Street,stop_code: Wjz6EBY, lat: -35.2403577, lng: 149.1242409}
- { name: Northbourne Avenue,stop_code: Wjz6Myj, lat: -35.2424881, lng: 149.1344225} - { name: Northbourne Avenue,stop_code: Wjz6Myj, lat: -35.2424881, lng: 149.1344225}
- { name: Federal Highway,stop_code: Wjz6Vj2, lat: -35.2363715, lng: 149.1421638} - { name: Federal Highway,stop_code: Wjz6Vj2, lat: -35.2363715, lng: 149.1421638}
- { name: Claxton Crescent,stop_code: Wjz6Fze, lat: -35.2360279, lng: 149.123147} - { name: Claxton Crescent,stop_code: Wjz6Fze, lat: -35.2360279, lng: 149.123147}
- { name: Barsdell Place,stop_code: Wjz6cjg, lat: -35.2200412, lng: 149.0766172} - { name: Barsdell Place,stop_code: Wjz6cjg, lat: -35.2200412, lng: 149.0766172}
- { name: Bean Crescent,stop_code: Wjz6c8c, lat: -35.2217598, lng: 149.0751026} - { name: Bean Crescent,stop_code: Wjz6c8c, lat: -35.2217598, lng: 149.0751026}
- { name: Grover Crescent,stop_code: Wjz64Yc, lat: -35.2190101, lng: 149.0723258} - { name: Grover Crescent,stop_code: Wjz64Yc, lat: -35.2190101, lng: 149.0723258}
- { name: Bennetts Close,stop_code: Wjz6c7A, lat: -35.2169478, lng: 149.074177} - { name: Bennetts Close,stop_code: Wjz6c7A, lat: -35.2169478, lng: 149.074177}
- { name: Pirani Place,stop_code: Wjz6eGq, lat: -35.2096321, lng: 149.0809063} - { name: Pirani Place,stop_code: Wjz6eGq, lat: -35.2096321, lng: 149.0809063}
- { name: William Webb Drive,stop_code: Wjz6eoG, lat: -35.2110071, lng: 149.0784661} - { name: William Webb Drive,stop_code: Wjz6eoG, lat: -35.2110071, lng: 149.0784661}
- { name: Gleadow Street,stop_code: Wjz65_2, lat: -35.2116258, lng: 149.0722394} - { name: Gleadow Street,stop_code: Wjz65_2, lat: -35.2116258, lng: 149.0722394}
- { name: William Webb Drive,stop_code: Wjz64CB, lat: -35.2176067, lng: 149.0687895} - { name: William Webb Drive,stop_code: Wjz64CB, lat: -35.2176067, lng: 149.0687895}
- { name: Kerrigan Street,stop_code: Wjr_F9a, lat: -35.1938253, lng: 149.031231} - { name: Kerrigan Street,stop_code: Wjr_F9a, lat: -35.1938253, lng: 149.031231}
- { name: Tillyard Drive,stop_code: Wjr_NaX, lat: -35.1930428, lng: 149.043112} - { name: Tillyard Drive,stop_code: Wjr_NaX, lat: -35.1930428, lng: 149.043112}
- { name: Reuther Street,stop_code: Wjr_M6A, lat: -35.1956738, lng: 149.0413435} - { name: Reuther Street,stop_code: Wjr_M6A, lat: -35.1956738, lng: 149.0413435}
- { name: Shakespeare Crescent,stop_code: Wjr_FV4, lat: -35.1935916, lng: 149.039268} - { name: Shakespeare Crescent,stop_code: Wjr_FV4, lat: -35.1935916, lng: 149.039268}
- { name: Lawrence Close,stop_code: Wjr-CnE, lat: -35.206318, lng: 149.0223041} - { name: Lawrence Close,stop_code: Wjr-CnE, lat: -35.206318, lng: 149.0223041}
- { name: Pockley Close,stop_code: Wjr-D1B, lat: -35.2045158, lng: 149.0193788} - { name: Pockley Close,stop_code: Wjr-D1B, lat: -35.2045158, lng: 149.0193788}
- { name: Osburn Drive,stop_code: Wjr-ux-, lat: -35.2099601, lng: 149.0143872} - { name: Osburn Drive,stop_code: Wjr-ux-, lat: -35.2099601, lng: 149.0143872}
- { name: Spofforth Street,stop_code: Wjr-kZV, lat: -35.2186221, lng: 149.0075381} - { name: Spofforth Street,stop_code: Wjr-kZV, lat: -35.2186221, lng: 149.0075381}
- { name: Fullagar Crescent,stop_code: Wjr-yDR, lat: -35.2278849, lng: 149.0252438} - { name: Fullagar Crescent,stop_code: Wjr-yDR, lat: -35.2278849, lng: 149.0252438}
- { name: Dethridge Street,stop_code: Wjr-G49, lat: -35.2302721, lng: 149.0298424} - { name: Dethridge Street,stop_code: Wjr-G49, lat: -35.2302721, lng: 149.0298424}
- { name: Hodges Street,stop_code: Wjr-GcG, lat: -35.2301944, lng: 149.0319226} - { name: Hodges Street,stop_code: Wjr-GcG, lat: -35.2301944, lng: 149.0319226}
- { name: Southern Cross Drive,stop_code: Wjr-Hi1, lat: -35.2261454, lng: 149.032398} - { name: Southern Cross Drive,stop_code: Wjr-Hi1, lat: -35.2261454, lng: 149.032398}
- { name: Albany Street,stop_code: WjzcgLt, lat: -35.3267279, lng: 149.1797667} - { name: Albany Street,stop_code: WjzcgLt, lat: -35.3267279, lng: 149.1797667}
- { name: Collie Street,stop_code: Wjzcgzn, lat: -35.3293028, lng: 149.178368} - { name: Collie Street,stop_code: Wjzcgzn, lat: -35.3293028, lng: 149.178368}
- { name: Faulding Street,stop_code: WjzbfzE, lat: -35.3354178, lng: 149.1678599} - { name: Faulding Street,stop_code: WjzbfzE, lat: -35.3354178, lng: 149.1678599}
- { name: Wormald Street,stop_code: Wjzbfr6, lat: -35.3349204, lng: 149.1655287} - { name: Wormald Street,stop_code: Wjzbfr6, lat: -35.3349204, lng: 149.1655287}
- { name: Lithgow Street,stop_code: Wjzc8im, lat: -35.3300635, lng: 149.1644887} - { name: Lithgow Street,stop_code: Wjzc8im, lat: -35.3300635, lng: 149.1644887}
- { name: Ipswich Street,stop_code: Wjzc8c1, lat: -35.3291272, lng: 149.1628031} - { name: Ipswich Street,stop_code: Wjzc8c1, lat: -35.3291272, lng: 149.1628031}
- { name: Whyalla Street,stop_code: Wjzbn5y, lat: -35.3338671, lng: 149.1730601} - { name: Whyalla Street,stop_code: Wjzbn5y, lat: -35.3338671, lng: 149.1730601}
- { name: Hamelin Crescent,stop_code: Wjz3TZj, lat: -35.3338162, lng: 149.1384399} - { name: Hamelin Crescent,stop_code: Wjz3TZj, lat: -35.3338162, lng: 149.1384399}
- { name: Sprent Street,stop_code: Wjz3_o2, lat: -35.3372978, lng: 149.1435685} - { name: Sprent Street,stop_code: Wjz3_o2, lat: -35.3372978, lng: 149.1435685}
- { name: Goyder Street,stop_code: Wjz3-r-, lat: -35.3403989, lng: 149.1448954} - { name: Goyder Street,stop_code: Wjz3-r-, lat: -35.3403989, lng: 149.1448954}
- { name: Jerrabomberra Avenue,stop_code: Wjzb5vw, lat: -35.3436462, lng: 149.155296} - { name: Jerrabomberra Avenue,stop_code: Wjzb5vw, lat: -35.3436462, lng: 149.155296}
- { name: Northbourne Avenue,stop_code: Wjz5N7c, lat: -35.2774279, lng: 149.1287001} - { name: Northbourne Avenue,stop_code: Wjz5N7c, lat: -35.2774279, lng: 149.1287001}
- { name: Crawford Street,stop_code: WjzbYnD, lat: -35.3485475, lng: 149.2307657} - { name: Crawford Street,stop_code: WjzbYnD, lat: -35.3485475, lng: 149.2307657}
- { name: Uriarra Road,stop_code: WjzbZ3m, lat: -35.3459335, lng: 149.227726} - { name: Uriarra Road,stop_code: WjzbZ3m, lat: -35.3459335, lng: 149.227726}
- { name: Farrer Place,stop_code: WjzbXmQ, lat: -35.3550126, lng: 149.2311068} - { name: Farrer Place,stop_code: WjzbXmQ, lat: -35.3550126, lng: 149.2311068}
- { name: Crawford Street,stop_code: WjzbYzg, lat: -35.3519226, lng: 149.2332104} - { name: Crawford Street,stop_code: WjzbYzg, lat: -35.3519226, lng: 149.2332104}
- { name: Yass Road,stop_code: Wjzj5BH, lat: -35.3447463, lng: 149.2446946} - { name: Yass Road,stop_code: Wjzj5BH, lat: -35.3447463, lng: 149.2446946}
- { name: Endurance Avenue,stop_code: Wjzj6z9, lat: -35.3407864, lng: 149.2440483} - { name: Endurance Avenue,stop_code: Wjzj6z9, lat: -35.3407864, lng: 149.2440483}
- { name: Erin Street,stop_code: WjzbZqS, lat: -35.3465484, lng: 149.2325494} - { name: Erin Street,stop_code: WjzbZqS, lat: -35.3465484, lng: 149.2325494}
- { name: Alinga Street,stop_code: Wjz5F-1, lat: -35.2783161, lng: 149.1271286} - { name: Alinga Street,stop_code: Wjz5F-1, lat: -35.2783161, lng: 149.1271286}
- { name: Crawford Street,stop_code: WjzbZ77, lat: -35.3430401, lng: 149.2274615} - { name: Crawford Street,stop_code: WjzbZ77, lat: -35.3430401, lng: 149.2274615}
- { name: Alinga Street,stop_code: Wjz5N6V, lat: -35.2783725, lng: 149.1297843} - { name: Alinga Street,stop_code: Wjz5N6V, lat: -35.2783725, lng: 149.1297843}
- { name: Uriarra Road,stop_code: WjzbRdl, lat: -35.3446304, lng: 149.2181472} - { name: Uriarra Road,stop_code: WjzbRdl, lat: -35.3446304, lng: 149.2181472}
- { name: Uriarra Road,stop_code: WjzbJSj, lat: -35.3441148, lng: 149.2140644} - { name: Uriarra Road,stop_code: WjzbJSj, lat: -35.3441148, lng: 149.2140644}
- { name: Uriarra Road,stop_code: WjzbZ3n, lat: -35.3458022, lng: 149.2277877} - { name: Uriarra Road,stop_code: WjzbZ3n, lat: -35.3458022, lng: 149.2277877}
- { name: Uriarra Road,stop_code: WjzbRBs, lat: -35.344722, lng: 149.2224303} - { name: Uriarra Road,stop_code: WjzbRBs, lat: -35.344722, lng: 149.2224303}
- { name: Alinga Street,stop_code: Wjz5N5_, lat: -35.2785242, lng: 149.1297348} - { name: Alinga Street,stop_code: Wjz5N5_, lat: -35.2785242, lng: 149.1297348}
- { name: Alinga Street,stop_code: Wjz5Ndm, lat: -35.2785658, lng: 149.1301727} - { name: Alinga Street,stop_code: Wjz5Ndm, lat: -35.2785658, lng: 149.1301727}
- { name: Woodhill Link,stop_code: WjzaArS, lat: -35.3953167, lng: 149.1995002} - { name: Woodhill Link,stop_code: WjzaArS, lat: -35.3953167, lng: 149.1995002}
- { name: Nicholii Loop,stop_code: WjzaAXA, lat: -35.3954806, lng: 149.2047447} - { name: Nicholii Loop,stop_code: WjzaAXA, lat: -35.3954806, lng: 149.2047447}
- { name: Mariners Court,stop_code: WjzaAdv, lat: -35.3938794, lng: 149.1962366} - { name: Mariners Court,stop_code: WjzaAdv, lat: -35.3938794, lng: 149.1962366}
- { name: Canberra Avenue,stop_code: WjzbBu_, lat: -35.3437537, lng: 149.1997253} - { name: Canberra Avenue,stop_code: WjzbBu_, lat: -35.3437537, lng: 149.1997253}
- { name: Broughton Place,stop_code: WjzbPXf, lat: -35.3567667, lng: 149.2261434} - { name: Broughton Place,stop_code: WjzbPXf, lat: -35.3567667, lng: 149.2261434}
- { name: Tharwa Road,stop_code: WjzbPpi, lat: -35.3586252, lng: 149.2208441} - { name: Tharwa Road,stop_code: WjzbPpi, lat: -35.3586252, lng: 149.2208441}
- { name: Hayes Street,stop_code: WjzbWDe, lat: -35.3596366, lng: 149.2330229} - { name: Hayes Street,stop_code: WjzbWDe, lat: -35.3596366, lng: 149.2330229}
- { name: Cooma Street,stop_code: WjzbXwk, lat: -35.3591416, lng: 149.2331706} - { name: Cooma Street,stop_code: WjzbXwk, lat: -35.3591416, lng: 149.2331706}
- { name: Cooma Street,stop_code: WjzbVxf, lat: -35.369131, lng: 149.233084} - { name: Cooma Street,stop_code: WjzbVxf, lat: -35.369131, lng: 149.233084}
- { name: Cooma Street,stop_code: WjzbVy2, lat: -35.3689098, lng: 149.232863} - { name: Cooma Street,stop_code: WjzbVy2, lat: -35.3689098, lng: 149.232863}
- { name: Old Cooma Road,stop_code: Wjz9JdV, lat: -35.4328562, lng: 149.2080577} - { name: Old Cooma Road,stop_code: Wjz9JdV, lat: -35.4328562, lng: 149.2080577}
- { name: Cooma Street,stop_code: WjzbXAb, lat: -35.3564366, lng: 149.2330826} - { name: Cooma Street,stop_code: WjzbXAb, lat: -35.3564366, lng: 149.2330826}
- { name: Kinlyside Avenue,stop_code: WjzbwuF, lat: -35.3717405, lng: 149.1994726} - { name: Kinlyside Avenue,stop_code: WjzbwuF, lat: -35.3717405, lng: 149.1994726}
- { name: Darmody Place,stop_code: WjzbwDR, lat: -35.37069, lng: 149.2008683} - { name: Darmody Place,stop_code: WjzbwDR, lat: -35.37069, lng: 149.2008683}
- { name: Halloran Drive,stop_code: WjzbwMd, lat: -35.3755316, lng: 149.2028602} - { name: Halloran Drive,stop_code: WjzbwMd, lat: -35.3755316, lng: 149.2028602}
- { name: Maloney Street,stop_code: WjzbG5c, lat: -35.3611934, lng: 149.2054955} - { name: Maloney Street,stop_code: WjzbG5c, lat: -35.3611934, lng: 149.2054955}
- { name: Kendall Avenue North,stop_code: WjzbJRl, lat: -35.3445935, lng: 149.2139248} - { name: Kendall Avenue North,stop_code: WjzbJRl, lat: -35.3445935, lng: 149.2139248}
- { name: Canberra Avenue,stop_code: WjzbfPy, lat: -35.3352335, lng: 149.1703836} - { name: Canberra Avenue,stop_code: WjzbfPy, lat: -35.3352335, lng: 149.1703836}
- { name: Flinders Way,stop_code: Wjz4OqF, lat: -35.3195494, lng: 149.1335622} - { name: Flinders Way,stop_code: Wjz4OqF, lat: -35.3195494, lng: 149.1335622}
- { name: Burbury Close,stop_code: Wjz4Pk_, lat: -35.3121631, lng: 149.1324213} - { name: Burbury Close,stop_code: Wjz4Pk_, lat: -35.3121631, lng: 149.1324213}
- { name: Mort Street,stop_code: Wjz5NeF, lat: -35.2783224, lng: 149.130726} - { name: Mort Street,stop_code: Wjz5NeF, lat: -35.2783224, lng: 149.130726}
- { name: East Row,stop_code: Wjz5Ndz, lat: -35.2788601, lng: 149.130649} - { name: East Row,stop_code: Wjz5Ndz, lat: -35.2788601, lng: 149.130649}
- { name: East Row,stop_code: Wjz5NcA, lat: -35.2794346, lng: 149.1305879} - { name: East Row,stop_code: Wjz5NcA, lat: -35.2794346, lng: 149.1305879}
- { name: East Row,stop_code: Wjz5Nds, lat: -35.2787886, lng: 149.1304779} - { name: East Row,stop_code: Wjz5Nds, lat: -35.2787886, lng: 149.1304779}
- { name: Justinian Street,stop_code: Wjz3mPO, lat: -35.3407241, lng: 149.0937831} - { name: Justinian Street,stop_code: Wjz3mPO, lat: -35.3407241, lng: 149.0937831}
- { name: Wisdom Street,stop_code: Wjz3mI_, lat: -35.3396179, lng: 149.0925471} - { name: Wisdom Street,stop_code: Wjz3mI_, lat: -35.3396179, lng: 149.0925471}
- { name: Birdwood Street,stop_code: Wjz3vrf, lat: -35.3348497, lng: 149.099817} - { name: Birdwood Street,stop_code: Wjz3vrf, lat: -35.3348497, lng: 149.099817}
- { name: McNicoll Street,stop_code: Wjz3vqN, lat: -35.3360119, lng: 149.1006409} - { name: McNicoll Street,stop_code: Wjz3vqN, lat: -35.3360119, lng: 149.1006409}
- { name: Ingamells Street,stop_code: Wjz3C4O, lat: -35.3400601, lng: 149.1074834} - { name: Ingamells Street,stop_code: Wjz3C4O, lat: -35.3400601, lng: 149.1074834}
- { name: Ingamells Street,stop_code: Wjz3uQf, lat: -35.339661, lng: 149.1040329} - { name: Ingamells Street,stop_code: Wjz3uQf, lat: -35.339661, lng: 149.1040329}
- { name: Ingamells Street,stop_code: Wjz3C4q, lat: -35.3400391, lng: 149.106977} - { name: Ingamells Street,stop_code: Wjz3C4q, lat: -35.3400391, lng: 149.106977}
- { name: Dennis Street,stop_code: Wjz3B5o, lat: -35.344996, lng: 149.1070285} - { name: Dennis Street,stop_code: Wjz3B5o, lat: -35.344996, lng: 149.1070285}
- { name: Yamba Drive,stop_code: Wjz3lVM, lat: -35.3477625, lng: 149.0952366} - { name: Yamba Drive,stop_code: Wjz3lVM, lat: -35.3477625, lng: 149.0952366}
- { name: Yamba Drive,stop_code: Wjz3lVG, lat: -35.3476365, lng: 149.095065} - { name: Yamba Drive,stop_code: Wjz3lVG, lat: -35.3476365, lng: 149.095065}
- { name: Kent Street,stop_code: Wjz3n-H, lat: -35.3331304, lng: 149.0950356} - { name: Kent Street,stop_code: Wjz3n-H, lat: -35.3331304, lng: 149.0950356}
- { name: Yamba Drive,stop_code: Wjz3mAg, lat: -35.3402021, lng: 149.0903851} - { name: Yamba Drive,stop_code: Wjz3mAg, lat: -35.3402021, lng: 149.0903851}
- { name: Kent Street,stop_code: Wjz4q8_, lat: -35.3203709, lng: 149.0981179} - { name: Kent Street,stop_code: Wjz4q8_, lat: -35.3203709, lng: 149.0981179}
- { name: Kent Street,stop_code: Wjz4p1K, lat: -35.325336, lng: 149.0963669} - { name: Kent Street,stop_code: Wjz4p1K, lat: -35.325336, lng: 149.0963669}
- { name: Kent Street,stop_code: Wjz4p2R, lat: -35.3247128, lng: 149.0966244} - { name: Kent Street,stop_code: Wjz4p2R, lat: -35.3247128, lng: 149.0966244}
- { name: Kent Street,stop_code: Wjz4gYg, lat: -35.329258, lng: 149.0944878} - { name: Kent Street,stop_code: Wjz4gYg, lat: -35.329258, lng: 149.0944878}
- { name: Barry Drive,stop_code: Wjz5G6U, lat: -35.2729086, lng: 149.1187429} - { name: Barry Drive,stop_code: Wjz5G6U, lat: -35.2729086, lng: 149.1187429}
- { name: McCaughey Street,stop_code: Wjz5Hw8, lat: -35.2715996, lng: 149.1231371} - { name: McCaughey Street,stop_code: Wjz5Hw8, lat: -35.2715996, lng: 149.1231371}
- { name: McCaughey Street,stop_code: Wjz5HDd, lat: -35.2662951, lng: 149.1231711} - { name: McCaughey Street,stop_code: Wjz5HDd, lat: -35.2662951, lng: 149.1231711}
- { name: Macpherson Street,stop_code: Wjz5Iqp, lat: -35.2646152, lng: 149.1221727} - { name: Macpherson Street,stop_code: Wjz5Iqp, lat: -35.2646152, lng: 149.1221727}
- { name: Bluebell Street,stop_code: Wjz5IjX, lat: -35.2637604, lng: 149.1215219} - { name: Bluebell Street,stop_code: Wjz5IjX, lat: -35.2637604, lng: 149.1215219}
- { name: Macarthur Avenue,stop_code: Wjz5Jpp, lat: -35.2597672, lng: 149.1221194} - { name: Macarthur Avenue,stop_code: Wjz5Jpp, lat: -35.2597672, lng: 149.1221194}
- { name: Hovea Street,stop_code: Wjz5Jyz, lat: -35.258945, lng: 149.123718} - { name: Hovea Street,stop_code: Wjz5Jyz, lat: -35.258945, lng: 149.123718}
- { name: Hovea Street,stop_code: Wjz5JzP, lat: -35.2582197, lng: 149.123961} - { name: Hovea Street,stop_code: Wjz5JzP, lat: -35.2582197, lng: 149.123961}
- { name: Scrivener Street,stop_code: Wjz5Juf, lat: -35.2558204, lng: 149.1217923} - { name: Scrivener Street,stop_code: Wjz5Juf, lat: -35.2558204, lng: 149.1217923}
- { name: Brigalow Street,stop_code: Wjz5KgQ, lat: -35.2547172, lng: 149.1212395} - { name: Brigalow Street,stop_code: Wjz5KgQ, lat: -35.2547172, lng: 149.1212395}
- { name: Northbourne Avenue,stop_code: Wjz5N5k, lat: -35.2787905, lng: 149.1288627} - { name: Northbourne Avenue,stop_code: Wjz5N5k, lat: -35.2787905, lng: 149.1288627}
- { name: Northbourne Avenue,stop_code: Wjz5N4J, lat: -35.2793571, lng: 149.1293659} - { name: Northbourne Avenue,stop_code: Wjz5N4J, lat: -35.2793571, lng: 149.1293659}
- { name: Tillyard Drive,stop_code: Wjr-LNq, lat: -35.2048275, lng: 149.0383141} - { name: Tillyard Drive,stop_code: Wjr-LNq, lat: -35.2048275, lng: 149.0383141}
- { name: College Street,stop_code: Wjz68W5, lat: -35.2423221, lng: 149.0831522} - { name: College Street,stop_code: Wjz68W5, lat: -35.2423221, lng: 149.0831522}
- { name: College Street,stop_code: Wjz6gia, lat: -35.2425616, lng: 149.0874888} - { name: College Street,stop_code: Wjz6gia, lat: -35.2425616, lng: 149.0874888}
- { name: Haydon Drive,stop_code: Wjz5maK, lat: -35.2532079, lng: 149.0867657} - { name: Haydon Drive,stop_code: Wjz5maK, lat: -35.2532079, lng: 149.0867657}
- { name: Marcus Clarke Street,stop_code: Wjz5GMT, lat: -35.2764151, lng: 149.1267199} - { name: Marcus Clarke Street,stop_code: Wjz5GMT, lat: -35.2764151, lng: 149.1267199}
- { name: Flynn Drive,stop_code: Wjz4KNu, lat: -35.2978611, lng: 149.1263289} - { name: Flynn Drive,stop_code: Wjz4KNu, lat: -35.2978611, lng: 149.1263289}
- { name: Beaconsfield Street,stop_code: WjzbnGh, lat: -35.3359862, lng: 149.1796321} - { name: Beaconsfield Street,stop_code: WjzbnGh, lat: -35.3359862, lng: 149.1796321}
- { name: Flinders Way,stop_code: Wjz4Ox0, lat: -35.3203301, lng: 149.1339648} - { name: Flinders Way,stop_code: Wjz4Ox0, lat: -35.3203301, lng: 149.1339648}
- { name: Flinders Way,stop_code: Wjz4OpP, lat: -35.320064, lng: 149.1335699} - { name: Flinders Way,stop_code: Wjz4OpP, lat: -35.320064, lng: 149.1335699}
- { name: Captain Cook Crescent,stop_code: Wjz4NDP, lat: -35.3214366, lng: 149.1350462} - { name: Captain Cook Crescent,stop_code: Wjz4NDP, lat: -35.3214366, lng: 149.1350462}
- { name: Dominion Circuit,stop_code: Wjz4Pa9, lat: -35.314076, lng: 149.1301281} - { name: Dominion Circuit,stop_code: Wjz4Pa9, lat: -35.314076, lng: 149.1301281}
- { name: Summit Track,stop_code: Wjz5qbi, lat: -35.2748058, lng: 149.0972461} - { name: Summit Track,stop_code: Wjz5qbi, lat: -35.2748058, lng: 149.0972461}
- { name: Alpen Street,stop_code: Wjr-_Ua, lat: -35.2054509, lng: 149.0613315} - { name: Alpen Street,stop_code: Wjr-_Ua, lat: -35.2054509, lng: 149.0613315}
- { name: Keenan Street,stop_code: Wjz66kP, lat: -35.2081588, lng: 149.066382} - { name: Keenan Street,stop_code: Wjz66kP, lat: -35.2081588, lng: 149.066382}
- { name: Copland Drive,stop_code: Wjz66lY, lat: -35.2073806, lng: 149.0665685} - { name: Copland Drive,stop_code: Wjz66lY, lat: -35.2073806, lng: 149.0665685}
- { name: Meagher Place,stop_code: Wjz664q, lat: -35.2082119, lng: 149.0631086} - { name: Meagher Place,stop_code: Wjz664q, lat: -35.2082119, lng: 149.0631086}
- { name: Meagher Place,stop_code: Wjz664g, lat: -35.2083936, lng: 149.0629132} - { name: Meagher Place,stop_code: Wjz664g, lat: -35.2083936, lng: 149.0629132}
- { name: Parkes Place,stop_code: Wjz4Rs-, lat: -35.3012441, lng: 149.1338254} - { name: Parkes Place,stop_code: Wjz4Rs-, lat: -35.3012441, lng: 149.1338254}
- { name: Alpen Street,stop_code: Wjr-_Uj, lat: -35.2054305, lng: 149.0615985} - { name: Alpen Street,stop_code: Wjr-_Uj, lat: -35.2054305, lng: 149.0615985}
- { name: Lennox Crossing,stop_code: Wjz4Lh5, lat: -35.2924038, lng: 149.1201999} - { name: Lennox Crossing,stop_code: Wjz4Lh5, lat: -35.2924038, lng: 149.1201999}
- { name: Russell Drive,stop_code: Wjzc54R, lat: -35.3013866, lng: 149.1515283} - { name: Russell Drive,stop_code: Wjzc54R, lat: -35.3013866, lng: 149.1515283}
- { name: Russell Drive,stop_code: Wjzc60A, lat: -35.2986953, lng: 149.151155} - { name: Russell Drive,stop_code: Wjzc60A, lat: -35.2986953, lng: 149.151155}
- { name: Russell Drive,stop_code: Wjz4-WZ, lat: -35.2972194, lng: 149.1503113} - { name: Russell Drive,stop_code: Wjz4-WZ, lat: -35.2972194, lng: 149.1503113}
- { name: Catchpole Street,stop_code: Wjz56Hh, lat: -35.25291, lng: 149.0697814} - { name: Catchpole Street,stop_code: Wjz56Hh, lat: -35.25291, lng: 149.0697814}
- { name: Russell Drive,stop_code: Wjz4-WL, lat: -35.2970826, lng: 149.149927} - { name: Russell Drive,stop_code: Wjz4-WL, lat: -35.2970826, lng: 149.149927}
- { name: Kings Avenue,stop_code: Wjz4RFJ, lat: -35.3034224, lng: 149.1361467} - { name: Kings Avenue,stop_code: Wjz4RFJ, lat: -35.3034224, lng: 149.1361467}
- { name: Kings Avenue,stop_code: Wjz4RwH, lat: -35.3042846, lng: 149.1348585} - { name: Kings Avenue,stop_code: Wjz4RwH, lat: -35.3042846, lng: 149.1348585}
- { name: Bourke Street,stop_code: Wjz4PuC, lat: -35.3109115, lng: 149.1332413} - { name: Bourke Street,stop_code: Wjz4PuC, lat: -35.3109115, lng: 149.1332413}
- { name: Sydney Avenue,stop_code: Wjz4P6x, lat: -35.3112617, lng: 149.1291119} - { name: Sydney Avenue,stop_code: Wjz4P6x, lat: -35.3112617, lng: 149.1291119}
- { name: Waldock Street,stop_code: Wjz3bdj, lat: -35.3557447, lng: 149.0753424} - { name: Waldock Street,stop_code: Wjz3bdj, lat: -35.3557447, lng: 149.0753424}
- { name: Russell Drive,stop_code: Wjz4-Rc, lat: -35.2952651, lng: 149.1479687} - { name: Russell Drive,stop_code: Wjz4-Rc, lat: -35.2952651, lng: 149.1479687}
- { name: Keenan Street,stop_code: Wjr--W0, lat: -35.2097244, lng: 149.0611869} - { name: Keenan Street,stop_code: Wjr--W0, lat: -35.2097244, lng: 149.0611869}
- { name: Keenan Street,stop_code: Wjr--W9, lat: -35.2096897, lng: 149.061394} - { name: Keenan Street,stop_code: Wjr--W9, lat: -35.2096897, lng: 149.061394}
- { name: Chifley Place,stop_code: Wjz3cal, lat: -35.3521568, lng: 149.0752845} - { name: Chifley Place,stop_code: Wjz3cal, lat: -35.3521568, lng: 149.0752845}
- { name: Waldock Street,stop_code: Wjz3bdl, lat: -35.3556201, lng: 149.075221} - { name: Waldock Street,stop_code: Wjz3bdl, lat: -35.3556201, lng: 149.075221}
- { name: Wilsmore Crescent,stop_code: Wjz3b9v, lat: -35.3581498, lng: 149.0754026} - { name: Wilsmore Crescent,stop_code: Wjz3b9v, lat: -35.3581498, lng: 149.0754026}
- { name: Brinsmead Street,stop_code: Wjz39RI, lat: -35.3666487, lng: 149.0827357} - { name: Brinsmead Street,stop_code: Wjz39RI, lat: -35.3666487, lng: 149.0827357}
- { name: McDonald Street,stop_code: Wjz3ceV, lat: -35.3497899, lng: 149.0761589} - { name: McDonald Street,stop_code: Wjz3ceV, lat: -35.3497899, lng: 149.0761589}
- { name: Kingsford Smith Drive,stop_code: Wjr-RKi, lat: -35.2123821, lng: 149.0478391} - { name: Kingsford Smith Drive,stop_code: Wjr-RKi, lat: -35.2123821, lng: 149.0478391}
- { name: Conley Drive,stop_code: Wjr-RZx, lat: -35.213153, lng: 149.050965} - { name: Conley Drive,stop_code: Wjr-RZx, lat: -35.213153, lng: 149.050965}
- { name: Grainger Circuit,stop_code: Wjr-RT-, lat: -35.2113153, lng: 149.0500244} - { name: Grainger Circuit,stop_code: Wjr-RT-, lat: -35.2113153, lng: 149.0500244}
- { name: Horsley Crescent,stop_code: Wjr-Zk3, lat: -35.2136037, lng: 149.0543575} - { name: Horsley Crescent,stop_code: Wjr-Zk3, lat: -35.2136037, lng: 149.0543575}
- { name: Horsley Crescent,stop_code: Wjr-Zk5, lat: -35.2134943, lng: 149.0543506} - { name: Horsley Crescent,stop_code: Wjr-Zk5, lat: -35.2134943, lng: 149.0543506}
- { name: Verbrugghen Street,stop_code: Wjr-ZJc, lat: -35.2128875, lng: 149.0586429} - { name: Verbrugghen Street,stop_code: Wjr-ZJc, lat: -35.2128875, lng: 149.0586429}
- { name: Copland Drive,stop_code: Wjr-ZRJ, lat: -35.2127453, lng: 149.0607491} - { name: Copland Drive,stop_code: Wjr-ZRJ, lat: -35.2127453, lng: 149.0607491}
- { name: Clifford Crescent,stop_code: Wjz66fw, lat: -35.2063185, lng: 149.0646037} - { name: Clifford Crescent,stop_code: Wjz66fw, lat: -35.2063185, lng: 149.0646037}
- { name: Clifford Crescent,stop_code: Wjz66fx, lat: -35.2062629, lng: 149.0647145} - { name: Clifford Crescent,stop_code: Wjz66fx, lat: -35.2062629, lng: 149.0647145}
- { name: Crossley Close,stop_code: Wjr--Lw, lat: -35.2063011, lng: 149.059093} - { name: Crossley Close,stop_code: Wjr--Lw, lat: -35.2063011, lng: 149.059093}
- { name: Crossley Close,stop_code: Wjr--Ki, lat: -35.2068427, lng: 149.0588291} - { name: Crossley Close,stop_code: Wjr--Ki, lat: -35.2068427, lng: 149.0588291}
- { name: Le Gallienne Street,stop_code: Wjr--md, lat: -35.2066211, lng: 149.0544526} - { name: Le Gallienne Street,stop_code: Wjr--md, lat: -35.2066211, lng: 149.0544526}
- { name: Henslowe Place,stop_code: Wjr--6k, lat: -35.2066759, lng: 149.0519744} - { name: Henslowe Place,stop_code: Wjr--6k, lat: -35.2066759, lng: 149.0519744}
- { name: Pattinson Crescent,stop_code: Wjr-SS5, lat: -35.2065999, lng: 149.0489353} - { name: Pattinson Crescent,stop_code: Wjr-SS5, lat: -35.2065999, lng: 149.0489353}
- { name: Lathlain Street,stop_code: Wjz60d1, lat: -35.2406019, lng: 149.0638958} - { name: Lathlain Street,stop_code: Wjz60d1, lat: -35.2406019, lng: 149.0638958}
- { name: Weedon Close,stop_code: Wjz60c5, lat: -35.2408972, lng: 149.0639885} - { name: Weedon Close,stop_code: Wjz60c5, lat: -35.2408972, lng: 149.0639885}
- { name: Lathlain Street,stop_code: Wjz605_, lat: -35.2400517, lng: 149.0637152} - { name: Lathlain Street,stop_code: Wjz605_, lat: -35.2400517, lng: 149.0637152}
- { name: Lathlain Street,stop_code: Wjz606I, lat: -35.2396656, lng: 149.0633992} - { name: Lathlain Street,stop_code: Wjz606I, lat: -35.2396656, lng: 149.0633992}
- { name: Daley Road,stop_code: Wjz5yXo, lat: -35.2749982, lng: 149.1166312} - { name: Daley Road,stop_code: Wjz5yXo, lat: -35.2749982, lng: 149.1166312}
- { name: Macarthur Avenue,stop_code: Wjz5Jaa, lat: -35.2590481, lng: 149.1191164} - { name: Macarthur Avenue,stop_code: Wjz5Jaa, lat: -35.2590481, lng: 149.1191164}
- { name: Bimbimbie Street,stop_code: Wjz68Yy, lat: -35.2411603, lng: 149.0838439} - { name: Bimbimbie Street,stop_code: Wjz68Yy, lat: -35.2411603, lng: 149.0838439}
- { name: Carandini Street,stop_code: Wjr-_3A, lat: -35.2032823, lng: 149.0522538} - { name: Carandini Street,stop_code: Wjr-_3A, lat: -35.2032823, lng: 149.0522538}
- { name: Kingsford Smith Drive,stop_code: Wjr-_Hp, lat: -35.2034703, lng: 149.0589653} - { name: Kingsford Smith Drive,stop_code: Wjr-_Hp, lat: -35.2034703, lng: 149.0589653}
- { name: Alpen Street,stop_code: Wjr-_Og, lat: -35.2042571, lng: 149.0602273} - { name: Alpen Street,stop_code: Wjr-_Og, lat: -35.2042571, lng: 149.0602273}
- { name: Colborne Place,stop_code: Wjz670_, lat: -35.205061, lng: 149.0637667} - { name: Colborne Place,stop_code: Wjz670_, lat: -35.205061, lng: 149.0637667}
- { name: Hancock Street,stop_code: Wjz67k1, lat: -35.2028461, lng: 149.0653269} - { name: Hancock Street,stop_code: Wjz67k1, lat: -35.2028461, lng: 149.0653269}
- { name: Hancock Street,stop_code: Wjz67kk, lat: -35.2025967, lng: 149.0657125} - { name: Hancock Street,stop_code: Wjz67kk, lat: -35.2025967, lng: 149.0657125}
- { name: Copland Drive,stop_code: Wjr-YdU, lat: -35.2186771, lng: 149.0542242} - { name: Copland Drive,stop_code: Wjr-YdU, lat: -35.2186771, lng: 149.0542242}
- { name: Copland Drive,stop_code: Wjr-YcT, lat: -35.2187393, lng: 149.0539932} - { name: Copland Drive,stop_code: Wjr-YcT, lat: -35.2187393, lng: 149.0539932}
- { name: John Cleland Crescent,stop_code: Wjr-Xno, lat: -35.2227935, lng: 149.0548844} - { name: John Cleland Crescent,stop_code: Wjr-Xno, lat: -35.2227935, lng: 149.0548844}
- { name: John Cleland Crescent,stop_code: Wjr-Xky, lat: -35.2247107, lng: 149.0549856} - { name: John Cleland Crescent,stop_code: Wjr-Xky, lat: -35.2247107, lng: 149.0549856}
- { name: Coulter Drive,stop_code: WjrZ_tn, lat: -35.2455787, lng: 149.0560808} - { name: Coulter Drive,stop_code: WjrZ_tn, lat: -35.2455787, lng: 149.0560808}
- { name: Coulter Drive,stop_code: WjrZ_so, lat: -35.2468109, lng: 149.0562979} - { name: Coulter Drive,stop_code: WjrZ_so, lat: -35.2468109, lng: 149.0562979}
- { name: Wiseman Street,stop_code: Wjz56XB, lat: -35.2526099, lng: 149.0728793} - { name: Wiseman Street,stop_code: Wjz56XB, lat: -35.2526099, lng: 149.0728793}
- { name: Fulton Street,stop_code: Wjz5711, lat: -35.2488233, lng: 149.0625779} - { name: Fulton Street,stop_code: Wjz5711, lat: -35.2488233, lng: 149.0625779}
- { name: Melrose Drive,stop_code: Wjz3eRR, lat: -35.3390911, lng: 149.082759} - { name: Melrose Drive,stop_code: Wjz3eRR, lat: -35.3390911, lng: 149.082759}
- { name: Furzer Street,stop_code: Wjz3m31, lat: -35.3408061, lng: 149.0844784} - { name: Furzer Street,stop_code: Wjz3m31, lat: -35.3408061, lng: 149.0844784}
- { name: Barton Highway,stop_code: Wjz79-a, lat: -35.1903384, lng: 149.0833628} - { name: Barton Highway,stop_code: Wjz79-a, lat: -35.1903384, lng: 149.0833628}
- { name: Akuna Street,stop_code: Wjz5Nht, lat: -35.281465, lng: 149.131837} - { name: Akuna Street,stop_code: Wjz5Nht, lat: -35.281465, lng: 149.131837}
- { name: College Street,stop_code: Wjz6giR, lat: -35.2422899, lng: 149.0883846} - { name: College Street,stop_code: Wjz6giR, lat: -35.2422899, lng: 149.0883846}
- { name: Wisdom Street,stop_code: Wjz3mQ5, lat: -35.339761, lng: 149.0927558} - { name: Wisdom Street,stop_code: Wjz3mQ5, lat: -35.339761, lng: 149.0927558}
- { name: Sharwood Crescent,stop_code: Wjr-ZXo, lat: -35.214551, lng: 149.0617978} - { name: Sharwood Crescent,stop_code: Wjr-ZXo, lat: -35.214551, lng: 149.0617978}
- { name: Deffell Street,stop_code: Wjz652H, lat: -35.2150139, lng: 149.0634241} - { name: Deffell Street,stop_code: Wjz652H, lat: -35.2150139, lng: 149.0634241}
- { name: Callaghan Street,stop_code: Wjz65ik, lat: -35.2149321, lng: 149.0656677} - { name: Callaghan Street,stop_code: Wjz65ik, lat: -35.2149321, lng: 149.0656677}
- { name: Alderman Street,stop_code: Wjz65rA, lat: -35.2142446, lng: 149.0673143} - { name: Alderman Street,stop_code: Wjz65rA, lat: -35.2142446, lng: 149.0673143}
- { name: Norton Street,stop_code: Wjz65Hy, lat: -35.2143691, lng: 149.0701627} - { name: Norton Street,stop_code: Wjz65Hy, lat: -35.2143691, lng: 149.0701627}
- { name: Norton Street,stop_code: Wjz65GS, lat: -35.2147682, lng: 149.0705542} - { name: Norton Street,stop_code: Wjz65GS, lat: -35.2147682, lng: 149.0705542}
- { name: Stenhouse Close,stop_code: Wjz66oO, lat: -35.2109547, lng: 149.067737} - { name: Stenhouse Close,stop_code: Wjz66oO, lat: -35.2109547, lng: 149.067737}
- { name: Pitcairn Street,stop_code: Wjz66Fg, lat: -35.2104421, lng: 149.0698018} - { name: Pitcairn Street,stop_code: Wjz66Fg, lat: -35.2104421, lng: 149.0698018}
- { name: Clancy Street,stop_code: Wjz66XM, lat: -35.2090851, lng: 149.0732672} - { name: Clancy Street,stop_code: Wjz66XM, lat: -35.2090851, lng: 149.0732672}
- { name: Kissane Crescent,stop_code: Wjz6ec7, lat: -35.2077712, lng: 149.0749969} - { name: Kissane Crescent,stop_code: Wjz6ec7, lat: -35.2077712, lng: 149.0749969}
- { name: Primmer Court,stop_code: WjrW_zy, lat: -35.3792073, lng: 149.0577944} - { name: Primmer Court,stop_code: WjrW_zy, lat: -35.3792073, lng: 149.0577944}
- { name: Marconi Crescent,stop_code: WjrW_Qk, lat: -35.3783254, lng: 149.0600973} - { name: Marconi Crescent,stop_code: WjrW_Qk, lat: -35.3783254, lng: 149.0600973}
- { name: Marconi Crescent,stop_code: Wjz27d3, lat: -35.3777767, lng: 149.064033} - { name: Marconi Crescent,stop_code: Wjz27d3, lat: -35.3777767, lng: 149.064033}
- { name: Sinclair Street,stop_code: Wjz27dd, lat: -35.3775909, lng: 149.0640777} - { name: Sinclair Street,stop_code: Wjz27dd, lat: -35.3775909, lng: 149.0640777}
- { name: Marconi Crescent,stop_code: Wjz27k8, lat: -35.3787048, lng: 149.065524} - { name: Marconi Crescent,stop_code: Wjz27k8, lat: -35.3787048, lng: 149.065524}
- { name: Lascelles Circuit,stop_code: Wjz26n5, lat: -35.3816653, lng: 149.0653041} - { name: Lascelles Circuit,stop_code: Wjz26n5, lat: -35.3816653, lng: 149.0653041}
- { name: Summerland Circuit,stop_code: Wjz26tG, lat: -35.3833338, lng: 149.0674908} - { name: Summerland Circuit,stop_code: Wjz26tG, lat: -35.3833338, lng: 149.0674908}
- { name: Summerland Circuit,stop_code: Wjz26P8, lat: -35.3848854, lng: 149.0709314} - { name: Summerland Circuit,stop_code: Wjz26P8, lat: -35.3848854, lng: 149.0709314}
- { name: Summerland Circuit,stop_code: Wjz26Om, lat: -35.385045, lng: 149.0711386} - { name: Summerland Circuit,stop_code: Wjz26Om, lat: -35.385045, lng: 149.0711386}
- { name: Mason Street,stop_code: Wjz26WN, lat: -35.3854988, lng: 149.073226} - { name: Mason Street,stop_code: Wjz26WN, lat: -35.3854988, lng: 149.073226}
- { name: Lee Steere Crescent,stop_code: Wjz2def, lat: -35.3876959, lng: 149.0750942} - { name: Lee Steere Crescent,stop_code: Wjz2def, lat: -35.3876959, lng: 149.0750942}
- { name: Kingsmill Street,stop_code: Wjz2d34, lat: -35.3900029, lng: 149.0734943} - { name: Kingsmill Street,stop_code: Wjz2d34, lat: -35.3900029, lng: 149.0734943}
- { name: Summerland Circuit,stop_code: Wjz25Ox, lat: -35.3909341, lng: 149.0714764} - { name: Summerland Circuit,stop_code: Wjz25Ox, lat: -35.3909341, lng: 149.0714764}
- { name: Summerland Circuit,stop_code: Wjz25NL, lat: -35.3911118, lng: 149.0716052} - { name: Summerland Circuit,stop_code: Wjz25NL, lat: -35.3911118, lng: 149.0716052}
- { name: O'Halloran Circuit,stop_code: Wjz24vP, lat: -35.3928088, lng: 149.0677265} - { name: O'Halloran Circuit,stop_code: Wjz24vP, lat: -35.3928088, lng: 149.0677265}
- { name: O'Halloran Circuit,stop_code: Wjz24lu, lat: -35.3939542, lng: 149.0657865} - { name: O'Halloran Circuit,stop_code: Wjz24lu, lat: -35.3939542, lng: 149.0657865}
- { name: O'Halloran Circuit,stop_code: Wjz24cK, lat: -35.3946419, lng: 149.0647484} - { name: O'Halloran Circuit,stop_code: Wjz24cK, lat: -35.3946419, lng: 149.0647484}
- { name: Pinkerton Circuit,stop_code: Wjz248n, lat: -35.3972727, lng: 149.064345} - { name: Pinkerton Circuit,stop_code: Wjz248n, lat: -35.3972727, lng: 149.064345}
- { name: Ragless Circuit,stop_code: Wjz2347, lat: -35.4000362, lng: 149.0625} - { name: Ragless Circuit,stop_code: Wjz2347, lat: -35.4000362, lng: 149.0625}
- { name: Learmonth Drive,stop_code: WjrWXON, lat: -35.4019182, lng: 149.060886} - { name: Learmonth Drive,stop_code: WjrWXON, lat: -35.4019182, lng: 149.060886}
- { name: Learmonth Drive,stop_code: WjrWXNL, lat: -35.4020721, lng: 149.0607315} - { name: Learmonth Drive,stop_code: WjrWXNL, lat: -35.4020721, lng: 149.0607315}
- { name: Learmonth Drive,stop_code: Wjz230G, lat: -35.4032475, lng: 149.0634951} - { name: Learmonth Drive,stop_code: Wjz230G, lat: -35.4032475, lng: 149.0634951}
- { name: Driver Place,stop_code: Wjz66Cd, lat: -35.2065831, lng: 149.0682105} - { name: Driver Place,stop_code: Wjz66Cd, lat: -35.2065831, lng: 149.0682105}
- { name: Willis Street,stop_code: Wjz67xQ, lat: -35.2046532, lng: 149.0691406} - { name: Willis Street,stop_code: Wjz67xQ, lat: -35.2046532, lng: 149.0691406}
- { name: Kellway Street,stop_code: Wjz66KO, lat: -35.2068138, lng: 149.0704302} - { name: Kellway Street,stop_code: Wjz66KO, lat: -35.2068138, lng: 149.0704302}
- { name: Copland Drive,stop_code: Wjz67yW, lat: -35.2040813, lng: 149.0692143} - { name: Copland Drive,stop_code: Wjz67yW, lat: -35.2040813, lng: 149.0692143}
- { name: Edmunds Place,stop_code: Wjz67nz, lat: -35.2006201, lng: 149.0659965} - { name: Edmunds Place,stop_code: Wjz67nz, lat: -35.2006201, lng: 149.0659965}
- { name: Crofts Crescent,stop_code: Wjz701y, lat: -35.1992909, lng: 149.0633518} - { name: Crofts Crescent,stop_code: Wjz701y, lat: -35.1992909, lng: 149.0633518}
- { name: Standbridge Place,stop_code: Wjz701a, lat: -35.1992794, lng: 149.0628172} - { name: Standbridge Place,stop_code: Wjz701a, lat: -35.1992794, lng: 149.0628172}
- { name: Baddeley Crescent,stop_code: Wjr_UUU, lat: -35.2001327, lng: 149.0624944} - { name: Baddeley Crescent,stop_code: Wjr_UUU, lat: -35.2001327, lng: 149.0624944}
- { name: Goyder Street,stop_code: Wjzb705, lat: -35.3370433, lng: 149.1505109} - { name: Goyder Street,stop_code: Wjzb705, lat: -35.3370433, lng: 149.1505109}
- { name: Kingsford Smith Drive,stop_code: Wjr_UPA, lat: -35.1977713, lng: 149.0605874} - { name: Kingsford Smith Drive,stop_code: Wjr_UPA, lat: -35.1977713, lng: 149.0605874}
- { name: Kingsford Smith Drive,stop_code: Wjr_UTL, lat: -35.1947749, lng: 149.060646} - { name: Kingsford Smith Drive,stop_code: Wjr_UTL, lat: -35.1947749, lng: 149.060646}
- { name: Clarey Crescent,stop_code: Wjz707-, lat: -35.1947883, lng: 149.0637942} - { name: Clarey Crescent,stop_code: Wjz707-, lat: -35.1947883, lng: 149.0637942}
- { name: Clarey Crescent,stop_code: Wjz707Z, lat: -35.1948745, lng: 149.0637273} - { name: Clarey Crescent,stop_code: Wjz707Z, lat: -35.1948745, lng: 149.0637273}
- { name: Healy Street,stop_code: Wjz70lp, lat: -35.1966753, lng: 149.0658519} - { name: Healy Street,stop_code: Wjz70lp, lat: -35.1966753, lng: 149.0658519}
- { name: Boote Street,stop_code: Wjz70zB, lat: -35.1976784, lng: 149.0688026} - { name: Boote Street,stop_code: Wjz70zB, lat: -35.1976784, lng: 149.0688026}
- { name: Scattergood Place,stop_code: Wjz70zz, lat: -35.1978567, lng: 149.0687555} - { name: Scattergood Place,stop_code: Wjz70zz, lat: -35.1978567, lng: 149.0687555}
- { name: Owen Dixon Drive,stop_code: Wjz70IY, lat: -35.1970964, lng: 149.0706179} - { name: Owen Dixon Drive,stop_code: Wjz70IY, lat: -35.1970964, lng: 149.0706179}
- { name: Douglass Street,stop_code: Wjz70Wx, lat: -35.1986717, lng: 149.0728065} - { name: Douglass Street,stop_code: Wjz70Wx, lat: -35.1986717, lng: 149.0728065}
- { name: Copland Drive,stop_code: Wjz67_t, lat: -35.200411, lng: 149.0727116} - { name: Copland Drive,stop_code: Wjz67_t, lat: -35.200411, lng: 149.0727116}
- { name: Emerton Street,stop_code: Wjz67BD, lat: -35.2015929, lng: 149.0686908} - { name: Emerton Street,stop_code: Wjz67BD, lat: -35.2015929, lng: 149.0686908}
- { name: Scattergood Place,stop_code: Wjz67Dq, lat: -35.2006561, lng: 149.0686086} - { name: Scattergood Place,stop_code: Wjz67Dq, lat: -35.2006561, lng: 149.0686086}
- { name: Milne Bay Road,stop_code: Wjzce7O, lat: -35.2940494, lng: 149.162512} - { name: Milne Bay Road,stop_code: Wjzce7O, lat: -35.2940494, lng: 149.162512}
- { name: Bimbimbie Street,stop_code: Wjz68Y0, lat: -35.2413091, lng: 149.0832098} - { name: Bimbimbie Street,stop_code: Wjz68Y0, lat: -35.2413091, lng: 149.0832098}
- { name: Bimbimbie Street,stop_code: Wjz68IH, lat: -35.2411129, lng: 149.0812786} - { name: Bimbimbie Street,stop_code: Wjz68IH, lat: -35.2411129, lng: 149.0812786}
- { name: Bimbimbie Street,stop_code: Wjz68Ip, lat: -35.2412881, lng: 149.0809439} - { name: Bimbimbie Street,stop_code: Wjz68Ip, lat: -35.2412881, lng: 149.0809439}
- { name: Drakeford Drive,stop_code: WjrXUoV, lat: -35.3758661, lng: 149.0568376} - { name: Drakeford Drive,stop_code: WjrXUoV, lat: -35.3758661, lng: 149.0568376}
- { name: Tuggeranong Parkway,stop_code: WjrXUsW, lat: -35.3730527, lng: 149.0568719} - { name: Tuggeranong Parkway,stop_code: WjrXUsW, lat: -35.3730527, lng: 149.0568719}
- { name: Banambila Street,stop_code: Wjz5dQt, lat: -35.2573605, lng: 149.0822652} - { name: Banambila Street,stop_code: Wjz5dQt, lat: -35.2573605, lng: 149.0822652}
- { name: Bindaga Street,stop_code: Wjz5dcJ, lat: -35.2573868, lng: 149.075852} - { name: Bindaga Street,stop_code: Wjz5dcJ, lat: -35.2573868, lng: 149.075852}
- { name: Bandjalong Crescent,stop_code: Wjz5d81, lat: -35.2605056, lng: 149.0749293} - { name: Bandjalong Crescent,stop_code: Wjz5d81, lat: -35.2605056, lng: 149.0749293}
- { name: Cooyong Street,stop_code: Wjz5NAQ, lat: -35.2794375, lng: 149.1349942} - { name: Cooyong Street,stop_code: Wjz5NAQ, lat: -35.2794375, lng: 149.1349942}
- { name: Kambah pool Road,stop_code: WjrXMN9, lat: -35.3751239, lng: 149.0489789} - { name: Kambah pool Road,stop_code: WjrXMN9, lat: -35.3751239, lng: 149.0489789}
- { name: Brierly Street,stop_code: WjrX-3w, lat: -35.340876, lng: 149.0522964} - { name: Brierly Street,stop_code: WjrX-3w, lat: -35.340876, lng: 149.0522964}
- { name: Atkinson Street,stop_code: Wjzj4ju, lat: -35.351369, lng: 149.2416919} - { name: Atkinson Street,stop_code: Wjzj4ju, lat: -35.351369, lng: 149.2416919}
- { name: Gungurra Crescent,stop_code: WjrXJ-g, lat: -35.3443528, lng: 149.0396647} - { name: Gungurra Crescent,stop_code: WjrXJ-g, lat: -35.3443528, lng: 149.0396647}
- { name: Comrie Street,stop_code: Wjz2qnG, lat: -35.4038881, lng: 149.0992283} - { name: Comrie Street,stop_code: Wjz2qnG, lat: -35.4038881, lng: 149.0992283}
- { name: Pethebridge Street,stop_code: Wjz3i6e, lat: -35.3603188, lng: 149.084779} - { name: Pethebridge Street,stop_code: Wjz3i6e, lat: -35.3603188, lng: 149.084779}
- { name: Colbee Court,stop_code: Wjz3k1J, lat: -35.3528521, lng: 149.0854118} - { name: Colbee Court,stop_code: Wjz3k1J, lat: -35.3528521, lng: 149.0854118}
- { name: Divine Court,stop_code: Wjz3kcA, lat: -35.3508773, lng: 149.0866243} - { name: Divine Court,stop_code: Wjz3kcA, lat: -35.3508773, lng: 149.0866243}
- { name: Amy Ackman Street,stop_code: Wjz7ZaH, lat: -35.171087, lng: 149.1418054} - { name: Amy Ackman Street,stop_code: Wjz7ZaH, lat: -35.171087, lng: 149.1418054}
- { name: Amy Ackman Street,stop_code: Wjz7ZaP, lat: -35.1710474, lng: 149.141884} - { name: Amy Ackman Street,stop_code: Wjz7ZaP, lat: -35.1710474, lng: 149.141884}
- { name: Amy Ackman Street,stop_code: Wjz7-xb, lat: -35.1662448, lng: 149.1450965} - { name: Amy Ackman Street,stop_code: Wjz7-xb, lat: -35.1662448, lng: 149.1450965}
- { name: Molonglo Drive,stop_code: WjzcrEu, lat: -35.3150059, lng: 149.190788} - { name: Molonglo Drive,stop_code: WjzcrEu, lat: -35.3150059, lng: 149.190788}
- { name: Lochiel Street,stop_code: WjzbUQX, lat: -35.3729581, lng: 149.2368028} - { name: Lochiel Street,stop_code: WjzbUQX, lat: -35.3729581, lng: 149.2368028}
- { name: Noonan Street,stop_code: Wjzi7mf, lat: -35.3766831, lng: 149.2412565} - { name: Noonan Street,stop_code: Wjzi7mf, lat: -35.3766831, lng: 149.2412565}
- { name: Cooma Street,stop_code: WjzbUCp, lat: -35.3717241, lng: 149.2334526} - { name: Cooma Street,stop_code: WjzbUCp, lat: -35.3717241, lng: 149.2334526}
- { name: Cooma Street,stop_code: WjzbWBs, lat: -35.3611492, lng: 149.2334303} - { name: Cooma Street,stop_code: WjzbWBs, lat: -35.3611492, lng: 149.2334303}
- { name: Cooma Street,stop_code: WjzbWzE, lat: -35.3628765, lng: 149.2337473} - { name: Cooma Street,stop_code: WjzbWzE, lat: -35.3628765, lng: 149.2337473}
- { name: Hambly Place,stop_code: WjzbWyW, lat: -35.363411, lng: 149.2340547} - { name: Hambly Place,stop_code: WjzbWyW, lat: -35.363411, lng: 149.2340547}
- { name: Gundaroo Drive,stop_code: Wjz7oZp, lat: -35.1966204, lng: 149.1057315} - { name: Gundaroo Drive,stop_code: Wjz7oZp, lat: -35.1966204, lng: 149.1057315}
- { name: Gundaroo Drive,stop_code: Wjz7xp9, lat: -35.193896, lng: 149.1108506} - { name: Gundaroo Drive,stop_code: Wjz7xp9, lat: -35.193896, lng: 149.1108506}
- { name: Cowper Street,stop_code: Wjz5-6R, lat: -35.2505265, lng: 149.1404751} - { name: Cowper Street,stop_code: Wjz5-6R, lat: -35.2505265, lng: 149.1404751}
- { name: David Walsh Avenue,stop_code: Wjz7YIc, lat: -35.1751298, lng: 149.1466086} - { name: David Walsh Avenue,stop_code: Wjz7YIc, lat: -35.1751298, lng: 149.1466086}
- { name: Barritt Street,stop_code: WjrWTWO, lat: -35.3798917, lng: 149.0512179} - { name: Barritt Street,stop_code: WjrWTWO, lat: -35.3798917, lng: 149.0512179}
- { name: Barritt Street,stop_code: WjrWTJo, lat: -35.3779591, lng: 149.0479511} - { name: Barritt Street,stop_code: WjrWTJo, lat: -35.3779591, lng: 149.0479511}
- { name: Constitution Avenue,stop_code: Wjz5MsT, lat: -35.2846782, lng: 149.133671} - { name: Constitution Avenue,stop_code: Wjz5MsT, lat: -35.2846782, lng: 149.133671}
- { name: Hindmarsh Drive,stop_code: WjrXBSS, lat: -35.3438051, lng: 149.0278253} - { name: Hindmarsh Drive,stop_code: WjrXBSS, lat: -35.3438051, lng: 149.0278253}
- { name: Hindmarsh Drive,stop_code: WjrXBSJ, lat: -35.3439387, lng: 149.0276931} - { name: Hindmarsh Drive,stop_code: WjrXBSJ, lat: -35.3439387, lng: 149.0276931}
- { name: Mort Street,stop_code: Wjz5Oj2, lat: -35.2748472, lng: 149.131256} - { name: Mort Street,stop_code: Wjz5Oj2, lat: -35.2748472, lng: 149.131256}
- { name: Northbourne Avenue,stop_code: Wjz5P8K, lat: -35.2710632, lng: 149.1307122} - { name: Northbourne Avenue,stop_code: Wjz5P8K, lat: -35.2710632, lng: 149.1307122}
- { name: Northbourne Avenue,stop_code: Wjz5SrO, lat: -35.2528485, lng: 149.1336705} - { name: Northbourne Avenue,stop_code: Wjz5SrO, lat: -35.2528485, lng: 149.1336705}
- { name: Northbourne Avenue,stop_code: Wjz5Rsi, lat: -35.2576771, lng: 149.132889} - { name: Northbourne Avenue,stop_code: Wjz5Rsi, lat: -35.2576771, lng: 149.132889}
- { name: Northbourne Avenue,stop_code: Wjz5QmR, lat: -35.2615172, lng: 149.1322602} - { name: Northbourne Avenue,stop_code: Wjz5QmR, lat: -35.2615172, lng: 149.1322602}
- { name: Northbourne Avenue,stop_code: Wjz5Pl0, lat: -35.2681201, lng: 149.1312} - { name: Northbourne Avenue,stop_code: Wjz5Pl0, lat: -35.2681201, lng: 149.1312}
- { name: Northbourne Avenue,stop_code: Wjz5N5h, lat: -35.2790396, lng: 149.1288222} - { name: Northbourne Avenue,stop_code: Wjz5N5h, lat: -35.2790396, lng: 149.1288222}
- { name: Northbourne Avenue,stop_code: Wjz5O3Q, lat: -35.274617, lng: 149.1295599} - { name: Northbourne Avenue,stop_code: Wjz5O3Q, lat: -35.274617, lng: 149.1295599}
- { name: Northbourne Avenue,stop_code: Wjz5P8n, lat: -35.2710038, lng: 149.1301486} - { name: Northbourne Avenue,stop_code: Wjz5P8n, lat: -35.2710038, lng: 149.1301486}
- { name: Northbourne Avenue,stop_code: Wjz5Qi2, lat: -35.2645608, lng: 149.1311834} - { name: Northbourne Avenue,stop_code: Wjz5Qi2, lat: -35.2645608, lng: 149.1311834}
- { name: Northbourne Avenue,stop_code: Wjz5RkN, lat: -35.2577065, lng: 149.1322899} - { name: Northbourne Avenue,stop_code: Wjz5RkN, lat: -35.2577065, lng: 149.1322899}
- { name: Morisset Street,stop_code: WjzbYAM, lat: -35.3512052, lng: 149.2339748} - { name: Morisset Street,stop_code: WjzbYAM, lat: -35.3512052, lng: 149.2339748}
- { name: Kitchener Street,stop_code: Wjz3uDU, lat: -35.338154, lng: 149.1022456} - { name: Kitchener Street,stop_code: Wjz3uDU, lat: -35.338154, lng: 149.1022456}
- { name: Kitchener Street,stop_code: Wjz3uK7, lat: -35.3382669, lng: 149.1024969} - { name: Kitchener Street,stop_code: Wjz3uK7, lat: -35.3382669, lng: 149.1024969}
- { name: Black Mountain Summit Walk,stop_code: Wjz5xl6, lat: -35.278643, lng: 149.1093237} - { name: Black Mountain Summit Walk,stop_code: Wjz5xl6, lat: -35.278643, lng: 149.1093237}
- { name: Athllon Drive,stop_code: Wjz2mTK, lat: -35.3815863, lng: 149.0936139} - { name: Athllon Drive,stop_code: Wjz2mTK, lat: -35.3815863, lng: 149.0936139}
- { name: Baldwin Drive,stop_code: Wjz6keB, lat: -35.2175697, lng: 149.0866478} - { name: Baldwin Drive,stop_code: Wjz6keB, lat: -35.2175697, lng: 149.0866478}
- { name: Flierl Place,stop_code: Wjr_Mxy, lat: -35.1992913, lng: 149.0468658} - { name: Flierl Place,stop_code: Wjr_Mxy, lat: -35.1992913, lng: 149.0468658}
- { name: Fellows Street,stop_code: Wjr-InZ, lat: -35.2169003, lng: 149.0335258} - { name: Fellows Street,stop_code: Wjr-InZ, lat: -35.2169003, lng: 149.0335258}
- { name: Moyes Crescent,stop_code: Wjr-Alc, lat: -35.2183514, lng: 149.021625} - { name: Moyes Crescent,stop_code: Wjr-Alc, lat: -35.2183514, lng: 149.021625}
- { name: Solomon Crescent,stop_code: Wjr-I4P, lat: -35.2191133, lng: 149.0306838} - { name: Solomon Crescent,stop_code: Wjr-I4P, lat: -35.2191133, lng: 149.0306838}
- { name: Chambers Street,stop_code: Wjr-IGJ, lat: -35.2203467, lng: 149.0373003} - { name: Chambers Street,stop_code: Wjr-IGJ, lat: -35.2203467, lng: 149.0373003}
- { name: Maribyrnong Avenue,stop_code: Wjz6zth, lat: -35.2241129, lng: 149.1109391} - { name: Maribyrnong Avenue,stop_code: Wjz6zth, lat: -35.2241129, lng: 149.1109391}
- { name: Macumba Place,stop_code: Wjz6yir, lat: -35.2314837, lng: 149.1098378} - { name: Macumba Place,stop_code: Wjz6yir, lat: -35.2314837, lng: 149.1098378}
- { name: Glossop Crescent,stop_code: Wjzd0oD, lat: -35.2874406, lng: 149.1552177} - { name: Glossop Crescent,stop_code: Wjzd0oD, lat: -35.2874406, lng: 149.1552177}
- { name: Chewings Street,stop_code: Wjr-N9a, lat: -35.2377693, lng: 149.0421213} - { name: Chewings Street,stop_code: Wjr-N9a, lat: -35.2377693, lng: 149.0421213}
- { name: Hinkler Street,stop_code: Wjr-EuB, lat: -35.2395683, lng: 149.034448} - { name: Hinkler Street,stop_code: Wjr-EuB, lat: -35.2395683, lng: 149.034448}
- { name: Ratcliffe Crescent,stop_code: Wjr-VdI, lat: -35.2348097, lng: 149.0539156} - { name: Ratcliffe Crescent,stop_code: Wjr-VdI, lat: -35.2348097, lng: 149.0539156}
- { name: Krefft Street,stop_code: Wjr-PWf, lat: -35.225611, lng: 149.0504341} - { name: Krefft Street,stop_code: Wjr-PWf, lat: -35.225611, lng: 149.0504341}
- { name: Dungowan Street,stop_code: WjrZKnY, lat: -35.2498968, lng: 149.0336595} - { name: Dungowan Street,stop_code: WjrZKnY, lat: -35.2498968, lng: 149.0336595}
- { name: Capital Circle,stop_code: Wjz4IrL, lat: -35.307326, lng: 149.1225503} - { name: Capital Circle,stop_code: Wjz4IrL, lat: -35.307326, lng: 149.1225503}
- { name: National Circuit,stop_code: Wjz4INj, lat: -35.3091118, lng: 149.1261312} - { name: National Circuit,stop_code: Wjz4INj, lat: -35.3091118, lng: 149.1261312}
- { name: Theodore Street,stop_code: Wjz3fO2, lat: -35.3359729, lng: 149.0817737} - { name: Theodore Street,stop_code: Wjz3fO2, lat: -35.3359729, lng: 149.0817737}
- { name: Newdegate Street,stop_code: Wjz4qtY, lat: -35.3172423, lng: 149.100878} - { name: Newdegate Street,stop_code: Wjz4qtY, lat: -35.3172423, lng: 149.100878}
- { name: Hannah Place,stop_code: Wjz4y7z, lat: -35.3159129, lng: 149.1072689} - { name: Hannah Place,stop_code: Wjz4y7z, lat: -35.3159129, lng: 149.1072689}
- { name: Hopetoun Circuit,stop_code: Wjz4yng, lat: -35.316172, lng: 149.1095953} - { name: Hopetoun Circuit,stop_code: Wjz4yng, lat: -35.316172, lng: 149.1095953}
- { name: Stonehaven Crescent,stop_code: Wjz4yGG, lat: -35.3194308, lng: 149.1142224} - { name: Stonehaven Crescent,stop_code: Wjz4yGG, lat: -35.3194308, lng: 149.1142224}
- { name: Melbourne Avenue,stop_code: Wjz4yQ-, lat: -35.3177825, lng: 149.1159796} - { name: Melbourne Avenue,stop_code: Wjz4yQ-, lat: -35.3177825, lng: 149.1159796}
- { name: Melbourne Avenue,stop_code: Wjz4Hbx, lat: -35.3133913, lng: 149.1195724} - { name: Melbourne Avenue,stop_code: Wjz4Hbx, lat: -35.3133913, lng: 149.1195724}
- { name: Freda Gibson Circuit,stop_code: Wjz1HOf, lat: -35.4453654, lng: 149.1258946} - { name: Freda Gibson Circuit,stop_code: Wjz1HOf, lat: -35.4453654, lng: 149.1258946}
- { name: Burdett Crescent,stop_code: Wjz1GsO, lat: -35.4499519, lng: 149.1226442} - { name: Burdett Crescent,stop_code: Wjz1GsO, lat: -35.4499519, lng: 149.1226442}
- { name: Hartung Crescent,stop_code: Wjz1zWz, lat: -35.4457437, lng: 149.1168111} - { name: Hartung Crescent,stop_code: Wjz1zWz, lat: -35.4457437, lng: 149.1168111}
- { name: Cochrane Crescent,stop_code: Wjz1ySn, lat: -35.4481315, lng: 149.1151569} - { name: Cochrane Crescent,stop_code: Wjz1ySn, lat: -35.4481315, lng: 149.1151569}
- { name: Conlon Crescent,stop_code: Wjz1G32, lat: -35.4506139, lng: 149.1174495} - { name: Conlon Crescent,stop_code: Wjz1G32, lat: -35.4506139, lng: 149.1174495}
- { name: Clift Crescent,stop_code: Wjz1CD8, lat: -35.4260286, lng: 149.1122294} - { name: Clift Crescent,stop_code: Wjz1CD8, lat: -35.4260286, lng: 149.1122294}
- { name: Meeson Street,stop_code: Wjz1Kiu, lat: -35.4289549, lng: 149.1207905} - { name: Meeson Street,stop_code: Wjz1Kiu, lat: -35.4289549, lng: 149.1207905}
- { name: Nina Jones Crescent,stop_code: Wjz1S2v, lat: -35.4289254, lng: 149.1290251} - { name: Nina Jones Crescent,stop_code: Wjz1S2v, lat: -35.4289254, lng: 149.1290251}
- { name: Monaro Highway,stop_code: Wjz1TgM, lat: -35.4253782, lng: 149.1323625} - { name: Monaro Highway,stop_code: Wjz1TgM, lat: -35.4253782, lng: 149.1323625}
- { name: Baskerville Street,stop_code: Wjz1LBV, lat: -35.4218605, lng: 149.1241279} - { name: Baskerville Street,stop_code: Wjz1LBV, lat: -35.4218605, lng: 149.1241279}
- { name: McLorinan Street,stop_code: Wjz1DWq, lat: -35.4238411, lng: 149.1166188} - { name: McLorinan Street,stop_code: Wjz1DWq, lat: -35.4238411, lng: 149.1166188}
- { name: Barry Drive,stop_code: Wjz5G6B, lat: -35.2724804, lng: 149.1181797} - { name: Barry Drive,stop_code: Wjz5G6B, lat: -35.2724804, lng: 149.1181797}
- { name: Chinner Crescent,stop_code: Wjr-SAW, lat: -35.2081966, lng: 149.0473834} - { name: Chinner Crescent,stop_code: Wjr-SAW, lat: -35.2081966, lng: 149.0473834}
- { name: O'Halloran Circuit,stop_code: WjrWYDO, lat: -35.3929049, lng: 149.058196} - { name: O'Halloran Circuit,stop_code: WjrWYDO, lat: -35.3929049, lng: 149.058196}
- { name: Chuculba Crescent,stop_code: Wjz6sdJ, lat: -35.21822, lng: 149.09782} - { name: Chuculba Crescent,stop_code: Wjz6sdJ, lat: -35.21822, lng: 149.09782}
- { name: Tucana Street,stop_code: Wjz6t8_, lat: -35.21601, lng: 149.09817} - { name: Tucana Street,stop_code: Wjz6t8_, lat: -35.21601, lng: 149.09817}
- { name: Tucana Street,stop_code: Wjz6t9w, lat: -35.21597, lng: 149.09763} - { name: Tucana Street,stop_code: Wjz6t9w, lat: -35.21597, lng: 149.09763}
- { name: Canopus Crescent,stop_code: Wjz6t4U, lat: -35.21388, lng: 149.09676} - { name: Canopus Crescent,stop_code: Wjz6t4U, lat: -35.21388, lng: 149.09676}
- { name: Purdie Street,stop_code: Wjz5nw6, lat: -35.2491082, lng: 149.0900504} - { name: Purdie Street,stop_code: Wjz5nw6, lat: -35.2491082, lng: 149.0900504}
- { name: Haydon Drive,stop_code: Wjz6hxB, lat: -35.2374959, lng: 149.0907853} - { name: Haydon Drive,stop_code: Wjz6hxB, lat: -35.2374959, lng: 149.0907853}
- { name: Baldwin Drive,stop_code: Wjz6rsL, lat: -35.2242562, lng: 149.1005043} - { name: Baldwin Drive,stop_code: Wjz6rsL, lat: -35.2242562, lng: 149.1005043}
- { name: Baldwin Drive,stop_code: Wjz6rrI, lat: -35.2252509, lng: 149.1005016} - { name: Baldwin Drive,stop_code: Wjz6rrI, lat: -35.2252509, lng: 149.1005016}
- { name: Bindubi Street,stop_code: Wjz5eb2, lat: -35.252833, lng: 149.0749872} - { name: Bindubi Street,stop_code: Wjz5eb2, lat: -35.252833, lng: 149.0749872}
- { name: Bindubi Street,stop_code: Wjz5ec7, lat: -35.2517641, lng: 149.0750194} - { name: Bindubi Street,stop_code: Wjz5ec7, lat: -35.2517641, lng: 149.0750194}
- { name: Bindubi Street,stop_code: Wjz5d57, lat: -35.256585, lng: 149.0734919} - { name: Bindubi Street,stop_code: Wjz5d57, lat: -35.256585, lng: 149.0734919}
- { name: College Street,stop_code: Wjz681S, lat: -35.2428905, lng: 149.0745728} - { name: College Street,stop_code: Wjz681S, lat: -35.2428905, lng: 149.0745728}
- { name: College Street,stop_code: Wjz689c, lat: -35.2430767, lng: 149.0750449} - { name: College Street,stop_code: Wjz689c, lat: -35.2430767, lng: 149.0750449}
- { name: Gwydir Square,stop_code: Wjz6pLi, lat: -35.2336222, lng: 149.1026958} - { name: Gwydir Square,stop_code: Wjz6pLi, lat: -35.2336222, lng: 149.1026958}
- { name: Maribyrnong Avenue,stop_code: Wjz6y90, lat: -35.2324006, lng: 149.1079069} - { name: Maribyrnong Avenue,stop_code: Wjz6y90, lat: -35.2324006, lng: 149.1079069}
- { name: Moruya Circuit,stop_code: Wjz6Apq, lat: -35.2212504, lng: 149.1111434} - { name: Moruya Circuit,stop_code: Wjz6Apq, lat: -35.2212504, lng: 149.1111434}
- { name: Ellenborough Street,stop_code: Wjz6yzQ, lat: -35.2307289, lng: 149.1130906} - { name: Ellenborough Street,stop_code: Wjz6yzQ, lat: -35.2307289, lng: 149.1130906}
- { name: Mouat Street,stop_code: Wjz5L_c, lat: -35.2444385, lng: 149.1272473} - { name: Mouat Street,stop_code: Wjz5L_c, lat: -35.2444385, lng: 149.1272473}
- { name: Mouat Street,stop_code: Wjz5Ti2, lat: -35.2480353, lng: 149.1313351} - { name: Mouat Street,stop_code: Wjz5Ti2, lat: -35.2480353, lng: 149.1313351}
- { name: Aikman Drive,stop_code: Wjz69ht, lat: -35.2375061, lng: 149.0768646} - { name: Aikman Drive,stop_code: Wjz69ht, lat: -35.2375061, lng: 149.0768646}
- { name: Aikman Drive,stop_code: Wjz69gA, lat: -35.2382334, lng: 149.0769344} - { name: Aikman Drive,stop_code: Wjz69gA, lat: -35.2382334, lng: 149.0769344}
- { name: Broad Place,stop_code: WjrWZsS, lat: -35.3891768, lng: 149.0567055} - { name: Broad Place,stop_code: WjrWZsS, lat: -35.3891768, lng: 149.0567055}
- { name: Boddington Crescent,stop_code: WjrWZA3, lat: -35.3893963, lng: 149.0571767} - { name: Boddington Crescent,stop_code: WjrWZA3, lat: -35.3893963, lng: 149.0571767}
- { name: Baldwin Drive,stop_code: Wjz6iYm, lat: -35.2298806, lng: 149.0944438} - { name: Baldwin Drive,stop_code: Wjz6iYm, lat: -35.2298806, lng: 149.0944438}
- { name: Baldwin Drive,stop_code: Wjz6iYk, lat: -35.2300583, lng: 149.0945448} - { name: Baldwin Drive,stop_code: Wjz6iYk, lat: -35.2300583, lng: 149.0945448}
- { name: Athllon Drive,stop_code: Wjz239F, lat: -35.4026063, lng: 149.0647649} - { name: Athllon Drive,stop_code: Wjz239F, lat: -35.4026063, lng: 149.0647649}
- { name: Anketell Street,stop_code: Wjz213w, lat: -35.4123171, lng: 149.0633299} - { name: Anketell Street,stop_code: Wjz213w, lat: -35.4123171, lng: 149.0633299}
- { name: Anketell Street,stop_code: Wjz20ut, lat: -35.415325, lng: 149.0672593} - { name: Anketell Street,stop_code: Wjz20ut, lat: -35.415325, lng: 149.0672593}
- { name: Athllon Drive,stop_code: Wjz3hL_, lat: -35.3650156, lng: 149.0926464} - { name: Athllon Drive,stop_code: Wjz3hL_, lat: -35.3650156, lng: 149.0926464}
- { name: Athllon Drive,stop_code: Wjz3gQn, lat: -35.3725942, lng: 149.0931105} - { name: Athllon Drive,stop_code: Wjz3gQn, lat: -35.3725942, lng: 149.0931105}
- { name: Athllon Drive,stop_code: Wjz3gMq, lat: -35.3757982, lng: 149.0932419} - { name: Athllon Drive,stop_code: Wjz3gMq, lat: -35.3757982, lng: 149.0932419}
- { name: Athllon Drive,stop_code: Wjz238T, lat: -35.4027681, lng: 149.0650277} - { name: Athllon Drive,stop_code: Wjz238T, lat: -35.4027681, lng: 149.0650277}
- { name: Athllon Drive,stop_code: Wjz3kwU, lat: -35.3539843, lng: 149.0913052} - { name: Athllon Drive,stop_code: Wjz3kwU, lat: -35.3539843, lng: 149.0913052}
- { name: Neales Street,stop_code: Wjz6rp1, lat: -35.2268254, lng: 149.0996755} - { name: Neales Street,stop_code: Wjz6rp1, lat: -35.2268254, lng: 149.0996755}
- { name: Neales Street,stop_code: Wjz6rhW, lat: -35.2267553, lng: 149.0994502} - { name: Neales Street,stop_code: Wjz6rhW, lat: -35.2267553, lng: 149.0994502}
- { name: Maribyrnong Avenue,stop_code: Wjz6qea, lat: -35.2288148, lng: 149.0970523} - { name: Maribyrnong Avenue,stop_code: Wjz6qea, lat: -35.2288148, lng: 149.0970523}
- { name: Marcus Clarke Street,stop_code: Wjz5GNG, lat: -35.2762093, lng: 149.1265723} - { name: Marcus Clarke Street,stop_code: Wjz5GNG, lat: -35.2762093, lng: 149.1265723}
- { name: Liversidge Street,stop_code: Wjz5E4O, lat: -35.2851023, lng: 149.1186022} - { name: Liversidge Street,stop_code: Wjz5E4O, lat: -35.2851023, lng: 149.1186022}
- { name: McDonald Place,stop_code: Wjz5w_S, lat: -35.2827048, lng: 149.117182} - { name: McDonald Place,stop_code: Wjz5w_S, lat: -35.2827048, lng: 149.117182}
- { name: Bowes Street,stop_code: Wjz3leq, lat: -35.344135, lng: 149.0864401} - { name: Bowes Street,stop_code: Wjz3leq, lat: -35.344135, lng: 149.0864401}
- { name: Bradley Street,stop_code: Wjz3ldj, lat: -35.3447574, lng: 149.0862912} - { name: Bradley Street,stop_code: Wjz3ldj, lat: -35.3447574, lng: 149.0862912}
- { name: Bradley Street,stop_code: Wjz3ldh, lat: -35.3449697, lng: 149.0863328} - { name: Bradley Street,stop_code: Wjz3ldh, lat: -35.3449697, lng: 149.0863328}
- { name: Pitman,stop_code: Wjz20ni, lat: -35.4149428, lng: 149.0656523} - { name: Pitman,stop_code: Wjz20ni, lat: -35.4149428, lng: 149.0656523}
- { name: Pitman,stop_code: Wjz20nk, lat: -35.4147569, lng: 149.0657435} - { name: Pitman,stop_code: Wjz20nk, lat: -35.4147569, lng: 149.0657435}
- { name: Callam Street,stop_code: Wjz3lmt, lat: -35.3439501, lng: 149.0877369} - { name: Callam Street,stop_code: Wjz3lmt, lat: -35.3439501, lng: 149.0877369}
- { name: Bowes Street,stop_code: Wjz3ldC, lat: -35.344484, lng: 149.0866144} - { name: Bowes Street,stop_code: Wjz3ldC, lat: -35.344484, lng: 149.0866144}
- { name: Bradley Street,stop_code: Wjz3lm0, lat: -35.34438, lng: 149.0872661} - { name: Bradley Street,stop_code: Wjz3lm0, lat: -35.34438, lng: 149.0872661}
- { name: Bradley Street,stop_code: Wjz3ll7, lat: -35.3444741, lng: 149.0873533} - { name: Bradley Street,stop_code: Wjz3ll7, lat: -35.3444741, lng: 149.0873533}
- { name: Bowes Street,stop_code: Wjz3lml, lat: -35.3439129, lng: 149.0876216} - { name: Bowes Street,stop_code: Wjz3lml, lat: -35.3439129, lng: 149.0876216}
- { name: Callam Street,stop_code: Wjz3lmi, lat: -35.3442093, lng: 149.0876443} - { name: Callam Street,stop_code: Wjz3lmi, lat: -35.3442093, lng: 149.0876443}
- { name: Bowes Street,stop_code: Wjz3leo, lat: -35.344368, lng: 149.0864991} - { name: Bowes Street,stop_code: Wjz3leo, lat: -35.344368, lng: 149.0864991}
- { name: Callam Street,stop_code: Wjz3lmq, lat: -35.3442083, lng: 149.0877771} - { name: Callam Street,stop_code: Wjz3lmq, lat: -35.3442083, lng: 149.0877771}
- { name: Eileen Good Street,stop_code: Wjz21g2, lat: -35.414217, lng: 149.0653492} - { name: Eileen Good Street,stop_code: Wjz21g2, lat: -35.414217, lng: 149.0653492}
- { name: Cohen Street,stop_code: Wjr-UJ-, lat: -35.240121, lng: 149.0597101} - { name: Cohen Street,stop_code: Wjr-UJ-, lat: -35.240121, lng: 149.0597101}
- { name: Pitman,stop_code: Wjz20nd, lat: -35.4146761, lng: 149.0654565} - { name: Pitman,stop_code: Wjz20nd, lat: -35.4146761, lng: 149.0654565}
- { name: Cohen Street,stop_code: Wjr-USa, lat: -35.2398454, lng: 149.0600442} - { name: Cohen Street,stop_code: Wjr-USa, lat: -35.2398454, lng: 149.0600442}
- { name: David Walsh Avenue,stop_code: Wjz7YzW, lat: -35.1759253, lng: 149.1462691} - { name: David Walsh Avenue,stop_code: Wjz7YzW, lat: -35.1759253, lng: 149.1462691}
- { name: Kathner Street,stop_code: WjrXBWn, lat: -35.3465295, lng: 149.0286032} - { name: Kathner Street,stop_code: WjrXBWn, lat: -35.3465295, lng: 149.0286032}
- { name: Cohen Street,stop_code: Wjr-USy, lat: -35.2397639, lng: 149.0604531} - { name: Cohen Street,stop_code: Wjr-USy, lat: -35.2397639, lng: 149.0604531}
- { name: Rene Street,stop_code: WjrXHvw, lat: -35.3546272, lng: 149.0344542} - { name: Rene Street,stop_code: WjrXHvw, lat: -35.3546272, lng: 149.0344542}
- { name: Perry Drive,stop_code: WjrXPbD, lat: -35.356823, lng: 149.0426424} - { name: Perry Drive,stop_code: WjrXPbD, lat: -35.356823, lng: 149.0426424}
- { name: Darwinia Terrace,stop_code: WjrXIbT, lat: -35.351342, lng: 149.0321099} - { name: Darwinia Terrace,stop_code: WjrXIbT, lat: -35.351342, lng: 149.0321099}
- { name: Darwinia Terrace,stop_code: WjrXIqp, lat: -35.352473, lng: 149.0342718} - { name: Darwinia Terrace,stop_code: WjrXIqp, lat: -35.352473, lng: 149.0342718}
- { name: Rafferty Street,stop_code: WjrXIbK, lat: -35.3514081, lng: 149.0319332} - { name: Rafferty Street,stop_code: WjrXIbK, lat: -35.3514081, lng: 149.0319332}
- { name: Kathner Street,stop_code: WjrXBWu, lat: -35.3466197, lng: 149.0287455} - { name: Kathner Street,stop_code: WjrXBWu, lat: -35.3466197, lng: 149.0287455}
- { name: Darwinia Terrace,stop_code: WjrXI5u, lat: -35.3499839, lng: 149.0301495} - { name: Darwinia Terrace,stop_code: WjrXI5u, lat: -35.3499839, lng: 149.0301495}
- { name: Rene Street,stop_code: WjrXHuL, lat: -35.3547054, lng: 149.0346008} - { name: Rene Street,stop_code: WjrXHuL, lat: -35.3547054, lng: 149.0346008}
- { name: Musgrove street,stop_code: WjrXHH7, lat: -35.3568349, lng: 149.0364585} - { name: Musgrove street,stop_code: WjrXHH7, lat: -35.3568349, lng: 149.0364585}
- { name: Musgrove street,stop_code: WjrXHHk, lat: -35.3570187, lng: 149.0369096} - { name: Musgrove street,stop_code: WjrXHHk, lat: -35.3570187, lng: 149.0369096}
- { name: Perry Drive,stop_code: WjrXHYJ, lat: -35.356246, lng: 149.0401055} - { name: Perry Drive,stop_code: WjrXHYJ, lat: -35.356246, lng: 149.0401055}
- { name: Bertel Crescent,stop_code: WjrXPgO, lat: -35.3592839, lng: 149.0444246} - { name: Bertel Crescent,stop_code: WjrXPgO, lat: -35.3592839, lng: 149.0444246}
- { name: Namatjira Drive,stop_code: WjrXPFr, lat: -35.3585046, lng: 149.0479415} - { name: Namatjira Drive,stop_code: WjrXPFr, lat: -35.3585046, lng: 149.0479415}
- { name: Namatjira Drive,stop_code: WjrXPFn, lat: -35.358206, lng: 149.0478792} - { name: Namatjira Drive,stop_code: WjrXPFn, lat: -35.358206, lng: 149.0478792}
- { name: Streeton Drive,stop_code: WjrXPJX, lat: -35.3557253, lng: 149.0486263} - { name: Streeton Drive,stop_code: WjrXPJX, lat: -35.3557253, lng: 149.0486263}
- { name: Fremantle Drive,stop_code: WjrXQO9, lat: -35.352521, lng: 149.0490119} - { name: Fremantle Drive,stop_code: WjrXQO9, lat: -35.352521, lng: 149.0490119}
- { name: Bunbury Street,stop_code: WjrXQTq, lat: -35.348941, lng: 149.0494159} - { name: Bunbury Street,stop_code: WjrXQTq, lat: -35.348941, lng: 149.0494159}
- { name: Bunbury Street,stop_code: WjrXQTy, lat: -35.3489683, lng: 149.0495709} - { name: Bunbury Street,stop_code: WjrXQTy, lat: -35.3489683, lng: 149.0495709}
- { name: McKail Crescent,stop_code: WjrXRFB, lat: -35.3473864, lng: 149.048202} - { name: McKail Crescent,stop_code: WjrXRFB, lat: -35.3473864, lng: 149.048202}
- { name: McKail Crescent,stop_code: WjrXRyK, lat: -35.3465911, lng: 149.0470392} - { name: McKail Crescent,stop_code: WjrXRyK, lat: -35.3465911, lng: 149.0470392}
- { name: Streeton Drive,stop_code: WjrXRBQ, lat: -35.3446963, lng: 149.0471083} - { name: Streeton Drive,stop_code: WjrXRBQ, lat: -35.3446963, lng: 149.0471083}
- { name: Streeton Drive,stop_code: WjrXRBJ, lat: -35.344588, lng: 149.0469995} - { name: Streeton Drive,stop_code: WjrXRBJ, lat: -35.344588, lng: 149.0469995}
- { name: Whitney Place,stop_code: WjrX-90, lat: -35.3423165, lng: 149.0529937} - { name: Whitney Place,stop_code: WjrX-90, lat: -35.3423165, lng: 149.0529937}
- { name: Parkinson Street,stop_code: WjrXZv3, lat: -35.3434037, lng: 149.0557375} - { name: Parkinson Street,stop_code: WjrXZv3, lat: -35.3434037, lng: 149.0557375}
- { name: Corinna Street,stop_code: Wjz3dXS, lat: -35.3459117, lng: 149.0842511} - { name: Corinna Street,stop_code: Wjz3dXS, lat: -35.3459117, lng: 149.0842511}
- { name: Clode Crescent,stop_code: Wjr-uhM, lat: -35.2104818, lng: 149.0114129} - { name: Clode Crescent,stop_code: Wjr-uhM, lat: -35.2104818, lng: 149.0114129}
- { name: Gilmore Crescent,stop_code: Wjz3Bea, lat: -35.3442178, lng: 149.1080098} - { name: Gilmore Crescent,stop_code: Wjz3Bea, lat: -35.3442178, lng: 149.1080098}
- { name: Gonzaga Place,stop_code: Wjz2wGU, lat: -35.4184904, lng: 149.1145873} - { name: Gonzaga Place,stop_code: Wjz2wGU, lat: -35.4184904, lng: 149.1145873}
- { name: Rischbieth Crescent,stop_code: Wjz2MYC, lat: -35.4166279, lng: 149.1388559} - { name: Rischbieth Crescent,stop_code: Wjz2MYC, lat: -35.4166279, lng: 149.1388559}
- { name: Penton Place,stop_code: Wjz2Npv, lat: -35.4131394, lng: 149.1331606} - { name: Penton Place,stop_code: Wjz2Npv, lat: -35.4131394, lng: 149.1331606}
- { name: Carruthers Street,stop_code: Wjz4h1X, lat: -35.3255489, lng: 149.0857143} - { name: Carruthers Street,stop_code: Wjz4h1X, lat: -35.3255489, lng: 149.0857143}
- { name: Bunny Street,stop_code: WjrX_SL, lat: -35.3327937, lng: 149.0607695} - { name: Bunny Street,stop_code: WjrX_SL, lat: -35.3327937, lng: 149.0607695}
- { name: Davenport Street,stop_code: Wjz37Zc, lat: -35.3337407, lng: 149.0723488} - { name: Davenport Street,stop_code: Wjz37Zc, lat: -35.3337407, lng: 149.0723488}
- { name: Isabella Drive,stop_code: Wjz1nzY, lat: -35.4229506, lng: 149.0912343} - { name: Isabella Drive,stop_code: Wjz1nzY, lat: -35.4229506, lng: 149.0912343}
- { name: Lake Tuggeranong cycle track,stop_code: Wjz20Vv, lat: -35.4185754, lng: 149.072661} - { name: Lake Tuggeranong cycle track,stop_code: Wjz20Vv, lat: -35.4185754, lng: 149.072661}
- { name: Taverner Street,stop_code: Wjz2b8J, lat: -35.4029944, lng: 149.0757807} - { name: Taverner Street,stop_code: Wjz2b8J, lat: -35.4029944, lng: 149.0757807}
- { name: Nunan Crescent,stop_code: Wjz29-5, lat: -35.4098244, lng: 149.083123} - { name: Nunan Crescent,stop_code: Wjz29-5, lat: -35.4098244, lng: 149.083123}
- { name: Laurens Street,stop_code: Wjz2i3o, lat: -35.4068322, lng: 149.0850166} - { name: Laurens Street,stop_code: Wjz2i3o, lat: -35.4068322, lng: 149.0850166}
- { name: Taverner Street,stop_code: Wjz2aGG, lat: -35.4073408, lng: 149.0812511} - { name: Taverner Street,stop_code: Wjz2aGG, lat: -35.4073408, lng: 149.0812511}
- { name: Taverner Street,stop_code: Wjz2azE, lat: -35.4068027, lng: 149.0799162} - { name: Taverner Street,stop_code: Wjz2azE, lat: -35.4068027, lng: 149.0799162}
- { name: Clutterbuck Crescent,stop_code: Wjz2arg, lat: -35.4068086, lng: 149.0779936} - { name: Clutterbuck Crescent,stop_code: Wjz2arg, lat: -35.4068086, lng: 149.0779936}
- { name: Connibere Crescent,stop_code: Wjz2aaw, lat: -35.4075241, lng: 149.0756429} - { name: Connibere Crescent,stop_code: Wjz2aaw, lat: -35.4075241, lng: 149.0756429}
- { name: Singleton Crescent,stop_code: Wjz29ea, lat: -35.4101319, lng: 149.0751278} - { name: Singleton Crescent,stop_code: Wjz29ea, lat: -35.4101319, lng: 149.0751278}
- { name: Maconochie Crescent,stop_code: Wjz29yh, lat: -35.4129642, lng: 149.0794301} - { name: Maconochie Crescent,stop_code: Wjz29yh, lat: -35.4129642, lng: 149.0794301}
- { name: Checchi Place,stop_code: Wjz28Yv, lat: -35.4165651, lng: 149.0836163} - { name: Checchi Place,stop_code: Wjz28Yv, lat: -35.4165651, lng: 149.0836163}
- { name: Forwood Street,stop_code: Wjz2haF, lat: -35.4129406, lng: 149.0867361} - { name: Forwood Street,stop_code: Wjz2haF, lat: -35.4129406, lng: 149.0867361}
- { name: Harricks Crescent,stop_code: Wjz2hlp, lat: -35.4109006, lng: 149.0878896} - { name: Harricks Crescent,stop_code: Wjz2hlp, lat: -35.4109006, lng: 149.0878896}
- { name: Michell Street,stop_code: Wjz2hBQ, lat: -35.4106404, lng: 149.0911182} - { name: Michell Street,stop_code: Wjz2hBQ, lat: -35.4106404, lng: 149.0911182}
- { name: Beirne Street,stop_code: Wjz2iEO, lat: -35.40876, lng: 149.0925039} - { name: Beirne Street,stop_code: Wjz2iEO, lat: -35.40876, lng: 149.0925039}
- { name: Amsinck Street,stop_code: Wjz2iPv, lat: -35.4062172, lng: 149.093302} - { name: Amsinck Street,stop_code: Wjz2iPv, lat: -35.4062172, lng: 149.093302}
- { name: Mackinnon Street,stop_code: Wjz2izK, lat: -35.4062764, lng: 149.0909078} - { name: Mackinnon Street,stop_code: Wjz2izK, lat: -35.4062764, lng: 149.0909078}
- { name: Tuggeranong Parkway,stop_code: Wjz34Gq, lat: -35.352423, lng: 149.0699271} - { name: Tuggeranong Parkway,stop_code: Wjz34Gq, lat: -35.352423, lng: 149.0699271}
- { name: Tuggeranong Parkway Onramp,stop_code: Wjz33LB, lat: -35.3542352, lng: 149.0701992} - { name: Tuggeranong Parkway Onramp,stop_code: Wjz33LB, lat: -35.3542352, lng: 149.0701992}
- { name: Tuggeranong Parkway,stop_code: Wjz33EK, lat: -35.3589689, lng: 149.0702445} - { name: Tuggeranong Parkway,stop_code: Wjz33EK, lat: -35.3589689, lng: 149.0702445}
- { name: Yambina Crescent,stop_code: WjrXXMe, lat: -35.3589023, lng: 149.0599784} - { name: Yambina Crescent,stop_code: WjrXXMe, lat: -35.3589023, lng: 149.0599784}
- { name: Araluen Street,stop_code: WjrXWsn, lat: -35.3616093, lng: 149.055979} - { name: Araluen Street,stop_code: WjrXWsn, lat: -35.3616093, lng: 149.055979}
- { name: Guinness Place,stop_code: WjrXGDF, lat: -35.3600413, lng: 149.0360091} - { name: Guinness Place,stop_code: WjrXGDF, lat: -35.3600413, lng: 149.0360091}
- { name: Gulgong Place,stop_code: WjrXXb4, lat: -35.3570754, lng: 149.0530316} - { name: Gulgong Place,stop_code: WjrXXb4, lat: -35.3570754, lng: 149.0530316}
- { name: Larakia Street,stop_code: Wjz34c4, lat: -35.3508697, lng: 149.0639869} - { name: Larakia Street,stop_code: Wjz34c4, lat: -35.3508697, lng: 149.0639869}
- { name: Cedrela Place,stop_code: WjrXR3f, lat: -35.3458397, lng: 149.040861} - { name: Cedrela Place,stop_code: WjrXR3f, lat: -35.3458397, lng: 149.040861}
- { name: Blowering Street,stop_code: WjrXLtK, lat: -35.3335671, lng: 149.0346289} - { name: Blowering Street,stop_code: WjrXLtK, lat: -35.3335671, lng: 149.0346289}
- { name: Mt Taylor Zig Zag,stop_code: Wjz39sA, lat: -35.3673329, lng: 149.0783636} - { name: Mt Taylor Zig Zag,stop_code: Wjz39sA, lat: -35.3673329, lng: 149.0783636}
- { name: Beasley Street,stop_code: Wjz3hu6, lat: -35.3658261, lng: 149.0887408} - { name: Beasley Street,stop_code: Wjz3hu6, lat: -35.3658261, lng: 149.0887408}
- { name: Marr Street,stop_code: Wjz3iuk, lat: -35.3604697, lng: 149.0889561} - { name: Marr Street,stop_code: Wjz3iuk, lat: -35.3604697, lng: 149.0889561}
- { name: Catalina Drive,stop_code: Wjzcuop, lat: -35.2989647, lng: 149.1881172} - { name: Catalina Drive,stop_code: Wjzcuop, lat: -35.2989647, lng: 149.1881172}
- { name: Laverton Avenue,stop_code: WjzcJ38, lat: -35.3024713, lng: 149.2056109} - { name: Laverton Avenue,stop_code: WjzcJ38, lat: -35.3024713, lng: 149.2056109}
- { name: Horse Park Drive,stop_code: Wjz7smv, lat: -35.1734671, lng: 149.0988597} - { name: Horse Park Drive,stop_code: Wjz7smv, lat: -35.1734671, lng: 149.0988597}
- { name: Kerrigan Street,stop_code: Wjr_xY9, lat: -35.1918291, lng: 149.028508} - { name: Kerrigan Street,stop_code: Wjr_xY9, lat: -35.1918291, lng: 149.028508}
- { name: Yabsley Place,stop_code: Wjr_Ej0, lat: -35.1981116, lng: 149.0323079} - { name: Yabsley Place,stop_code: Wjr_Ej0, lat: -35.1981116, lng: 149.0323079}
- { name: Rogers Street,stop_code: Wjr_GVA, lat: -35.188117, lng: 149.0399446} - { name: Rogers Street,stop_code: Wjr_GVA, lat: -35.188117, lng: 149.0399446}
- { name: Foskett Street,stop_code: Wjr_N-q, lat: -35.1903433, lng: 149.0507803} - { name: Foskett Street,stop_code: Wjr_N-q, lat: -35.1903433, lng: 149.0507803}
- { name: Nott Street,stop_code: Wjr_NpJ, lat: -35.1935127, lng: 149.0455536} - { name: Nott Street,stop_code: Wjr_NpJ, lat: -35.1935127, lng: 149.0455536}
- { name: Osburn Drive,stop_code: Wjr-uUL, lat: -35.210513, lng: 149.0180445} - { name: Osburn Drive,stop_code: Wjr-uUL, lat: -35.210513, lng: 149.0180445}
- { name: Commonwealth Avenue,stop_code: Wjz4KO9, lat: -35.2975962, lng: 149.1259252} - { name: Commonwealth Avenue,stop_code: Wjz4KO9, lat: -35.2975962, lng: 149.1259252}
- { name: Cowper Street,stop_code: Wjz5_0v, lat: -35.2490065, lng: 149.1400861} - { name: Cowper Street,stop_code: Wjz5_0v, lat: -35.2490065, lng: 149.1400861}
- { name: Captain Cook Crescent,stop_code: Wjz4NDo, lat: -35.3217168, lng: 149.1344712} - { name: Captain Cook Crescent,stop_code: Wjz4NDo, lat: -35.3217168, lng: 149.1344712}
- { name: Marcus Clarke Street,stop_code: Wjz5FIS, lat: -35.279312, lng: 149.1254166} - { name: Marcus Clarke Street,stop_code: Wjz5FIS, lat: -35.279312, lng: 149.1254166}
- { name: Keenan Street,stop_code: Wjz66kG, lat: -35.2081931, lng: 149.0662542} - { name: Keenan Street,stop_code: Wjz66kG, lat: -35.2081931, lng: 149.0662542}
- { name: Moor Place,stop_code: Wjz66t3, lat: -35.2074684, lng: 149.0667796} - { name: Moor Place,stop_code: Wjz66t3, lat: -35.2074684, lng: 149.0667796}
- { name: Connah Street,stop_code: Wjr-Xhh, lat: -35.2268712, lng: 149.0546156} - { name: Connah Street,stop_code: Wjr-Xhh, lat: -35.2268712, lng: 149.0546156}
- { name: Linger Place,stop_code: Wjr--r_, lat: -35.2084885, lng: 149.0569758} - { name: Linger Place,stop_code: Wjr--r_, lat: -35.2084885, lng: 149.0569758}
- { name: Russell Drive,stop_code: Wjzc55s, lat: -35.3007195, lng: 149.1509863} - { name: Russell Drive,stop_code: Wjzc55s, lat: -35.3007195, lng: 149.1509863}
- { name: Reg Saunders Way,stop_code: Wjz4-YV, lat: -35.2961803, lng: 149.1503194} - { name: Reg Saunders Way,stop_code: Wjz4-YV, lat: -35.2961803, lng: 149.1503194}
- { name: Russell Drive,stop_code: Wjzc60i, lat: -35.2988201, lng: 149.1508684} - { name: Russell Drive,stop_code: Wjzc60i, lat: -35.2988201, lng: 149.1508684}
- { name: National Circuit,stop_code: Wjz4Quk, lat: -35.3055692, lng: 149.1330442} - { name: National Circuit,stop_code: Wjz4Quk, lat: -35.3055692, lng: 149.1330442}
- { name: Melrose Drive,stop_code: Wjz3eZ4, lat: -35.3392098, lng: 149.0831308} - { name: Melrose Drive,stop_code: Wjz3eZ4, lat: -35.3392098, lng: 149.0831308}
- { name: Russell Drive,stop_code: Wjz4-KO, lat: -35.2946955, lng: 149.147399} - { name: Russell Drive,stop_code: Wjz4-KO, lat: -35.2946955, lng: 149.147399}
- { name: Chifley Place,stop_code: Wjz3caw, lat: -35.3525528, lng: 149.0755688} - { name: Chifley Place,stop_code: Wjz3caw, lat: -35.3525528, lng: 149.0755688}
- { name: Carslaw Street,stop_code: Wjz3ceY, lat: -35.3495185, lng: 149.0761236} - { name: Carslaw Street,stop_code: Wjz3ceY, lat: -35.3495185, lng: 149.0761236}
- { name: Threlfall Street,stop_code: Wjz3b9L, lat: -35.3581358, lng: 149.0757975} - { name: Threlfall Street,stop_code: Wjz3b9L, lat: -35.3581358, lng: 149.0757975}
- { name: Boult Place,stop_code: Wjr-SHc, lat: -35.2086969, lng: 149.0476925} - { name: Boult Place,stop_code: Wjr-SHc, lat: -35.2086969, lng: 149.0476925}
- { name: Conley Drive,stop_code: Wjr-RZE, lat: -35.2132014, lng: 149.0511677} - { name: Conley Drive,stop_code: Wjr-RZE, lat: -35.2132014, lng: 149.0511677}
- { name: Grainger Circuit,stop_code: Wjr-R_3, lat: -35.2115401, lng: 149.0502887} - { name: Grainger Circuit,stop_code: Wjr-R_3, lat: -35.2115401, lng: 149.0502887}
- { name: Verbrugghen Street,stop_code: Wjr-ZBY, lat: -35.2128526, lng: 149.0583185} - { name: Verbrugghen Street,stop_code: Wjr-ZBY, lat: -35.2128526, lng: 149.0583185}
- { name: Copland Drive,stop_code: Wjr-ZSE, lat: -35.2124829, lng: 149.0606716} - { name: Copland Drive,stop_code: Wjr-ZSE, lat: -35.2124829, lng: 149.0606716}
- { name: Linger Place,stop_code: Wjr--sV, lat: -35.2083253, lng: 149.0568878} - { name: Linger Place,stop_code: Wjr--sV, lat: -35.2083253, lng: 149.0568878}
- { name: Bishop Place,stop_code: Wjr--m3, lat: -35.2067416, lng: 149.0543264} - { name: Bishop Place,stop_code: Wjr--m3, lat: -35.2067416, lng: 149.0543264}
- { name: Henslowe Place,stop_code: Wjr--6t, lat: -35.2065912, lng: 149.0521439} - { name: Henslowe Place,stop_code: Wjr--6t, lat: -35.2065912, lng: 149.0521439}
- { name: Lathlain Street,stop_code: Wjz605N, lat: -35.2405467, lng: 149.0636668} - { name: Lathlain Street,stop_code: Wjz605N, lat: -35.2405467, lng: 149.0636668}
- { name: Lathlain Street,stop_code: Wjz604Y, lat: -35.2410486, lng: 149.0638326} - { name: Lathlain Street,stop_code: Wjz604Y, lat: -35.2410486, lng: 149.0638326}
- { name: Daley Road,stop_code: Wjz5xHC, lat: -35.2799871, lng: 149.1141335} - { name: Daley Road,stop_code: Wjz5xHC, lat: -35.2799871, lng: 149.1141335}
- { name: Macarthur Avenue,stop_code: Wjz5J9d, lat: -35.2594616, lng: 149.1190821} - { name: Macarthur Avenue,stop_code: Wjz5J9d, lat: -35.2594616, lng: 149.1190821}
- { name: Bainton Crescent,stop_code: Wjr-_kG, lat: -35.2027328, lng: 149.0551853} - { name: Bainton Crescent,stop_code: Wjr-_kG, lat: -35.2027328, lng: 149.0551853}
- { name: Alpen Street,stop_code: Wjr-_Nn, lat: -35.2043934, lng: 149.0601598} - { name: Alpen Street,stop_code: Wjr-_Nn, lat: -35.2043934, lng: 149.0601598}
- { name: Broadby Close,stop_code: Wjz671V, lat: -35.204864, lng: 149.0637204} - { name: Broadby Close,stop_code: Wjz671V, lat: -35.204864, lng: 149.0637204}
- { name: Kingsford Smith Drive,stop_code: Wjr-_zv, lat: -35.2030129, lng: 149.0575605} - { name: Kingsford Smith Drive,stop_code: Wjr-_zv, lat: -35.2030129, lng: 149.0575605}
- { name: John Cleland Crescent,stop_code: Wjr-Yg7, lat: -35.2215188, lng: 149.0543538} - { name: John Cleland Crescent,stop_code: Wjr-Yg7, lat: -35.2215188, lng: 149.0543538}
- { name: John Cleland Crescent,stop_code: Wjr-XyN, lat: -35.226202, lng: 149.0581637} - { name: John Cleland Crescent,stop_code: Wjr-XyN, lat: -35.226202, lng: 149.0581637}
- { name: Wiseman Street,stop_code: Wjz56Xu, lat: -35.2524925, lng: 149.0726439} - { name: Wiseman Street,stop_code: Wjz56Xu, lat: -35.2524925, lng: 149.0726439}
- { name: Fulton Street,stop_code: Wjz571j, lat: -35.2486364, lng: 149.0628845} - { name: Fulton Street,stop_code: Wjz571j, lat: -35.2486364, lng: 149.0628845}
- { name: Launceston Street,stop_code: Wjz3m3b, lat: -35.3406241, lng: 149.0847703} - { name: Launceston Street,stop_code: Wjz3m3b, lat: -35.3406241, lng: 149.0847703}
- { name: O'Hanlon Place,stop_code: Wjz79ZQ, lat: -35.190906, lng: 149.0842116} - { name: O'Hanlon Place,stop_code: Wjz79ZQ, lat: -35.190906, lng: 149.0842116}
- { name: O'Hanlon Place,stop_code: Wjz7hb5, lat: -35.1921368, lng: 149.0859491} - { name: O'Hanlon Place,stop_code: Wjz7hb5, lat: -35.1921368, lng: 149.0859491}
- { name: O'Hanlon Place,stop_code: Wjz7hbe, lat: -35.1921183, lng: 149.0860955} - { name: O'Hanlon Place,stop_code: Wjz7hbe, lat: -35.1921183, lng: 149.0860955}
- { name: Jalanga Crescent,stop_code: Wjz5dCr, lat: -35.2561978, lng: 149.0795805} - { name: Jalanga Crescent,stop_code: Wjz5dCr, lat: -35.2561978, lng: 149.0795805}
- { name: Lyttleton Crescent,stop_code: Wjz54_B, lat: -35.2608235, lng: 149.0728514} - { name: Lyttleton Crescent,stop_code: Wjz54_B, lat: -35.2608235, lng: 149.0728514}
- { name: Lyttleton Crescent,stop_code: Wjz54_n, lat: -35.2606623, lng: 149.072551} - { name: Lyttleton Crescent,stop_code: Wjz54_n, lat: -35.2606623, lng: 149.072551}
- { name: Cambridge Street,stop_code: Wjz54CS, lat: -35.2614333, lng: 149.0690577} - { name: Cambridge Street,stop_code: Wjz54CS, lat: -35.2614333, lng: 149.0690577}
- { name: Templeton Street,stop_code: Wjz551Q, lat: -35.2595831, lng: 149.0636761} - { name: Templeton Street,stop_code: Wjz551Q, lat: -35.2595831, lng: 149.0636761}
- { name: Templeton Street,stop_code: Wjz5592, lat: -35.2596812, lng: 149.0639679} - { name: Templeton Street,stop_code: Wjz5592, lat: -35.2596812, lng: 149.0639679}
- { name: Redfern Street,stop_code: WjrZZB7, lat: -35.2565133, lng: 149.0570071} - { name: Redfern Street,stop_code: WjrZZB7, lat: -35.2565133, lng: 149.0570071}
- { name: Coulter Drive,stop_code: WjrZ_o2, lat: -35.2493991, lng: 149.055711} - { name: Coulter Drive,stop_code: WjrZ_o2, lat: -35.2493991, lng: 149.055711}
- { name: Coulter Drive,stop_code: WjrZ_o4, lat: -35.2492379, lng: 149.0556338} - { name: Coulter Drive,stop_code: WjrZ_o4, lat: -35.2492379, lng: 149.0556338}
- { name: Weetangera Place,stop_code: WjrZTMv, lat: -35.2489575, lng: 149.0493939} - { name: Weetangera Place,stop_code: WjrZTMv, lat: -35.2489575, lng: 149.0493939}
- { name: Gillespie Street,stop_code: WjrZTua, lat: -35.2452775, lng: 149.0448362} - { name: Gillespie Street,stop_code: WjrZTua, lat: -35.2452775, lng: 149.0448362}
- { name: Gillespie Street,stop_code: WjrZTu1, lat: -35.2453967, lng: 149.044759} - { name: Gillespie Street,stop_code: WjrZTu1, lat: -35.2453967, lng: 149.044759}
- { name: Hawker Place,stop_code: Wjr-Mg6, lat: -35.2436162, lng: 149.0432913} - { name: Hawker Place,stop_code: Wjr-Mg6, lat: -35.2436162, lng: 149.0432913}
- { name: Hawker Place,stop_code: Wjr-Mgt, lat: -35.2436863, lng: 149.0438835} - { name: Hawker Place,stop_code: Wjr-Mgt, lat: -35.2436863, lng: 149.0438835}
- { name: Murranji Street,stop_code: WjrZT5e, lat: -35.245649, lng: 149.0408365} - { name: Murranji Street,stop_code: WjrZT5e, lat: -35.245649, lng: 149.0408365}
- { name: Erldunda Circuit,stop_code: WjrZLXY, lat: -35.2471491, lng: 149.0403988} - { name: Erldunda Circuit,stop_code: WjrZLXY, lat: -35.2471491, lng: 149.0403988}
- { name: Murranji Street,stop_code: WjrZT6b, lat: -35.2452004, lng: 149.0407936} - { name: Murranji Street,stop_code: WjrZT6b, lat: -35.2452004, lng: 149.0407936}
- { name: Wisdom Street,stop_code: Wjz3mI-, lat: -35.3396854, lng: 149.092654} - { name: Wisdom Street,stop_code: Wjz3mI-, lat: -35.3396854, lng: 149.092654}
- { name: Hardwick Crescent,stop_code: Wjr-z7J, lat: -35.2223574, lng: 149.0195037} - { name: Hardwick Crescent,stop_code: Wjr-z7J, lat: -35.2223574, lng: 149.0195037}
- { name: Ligertwood Street,stop_code: Wjz65aB, lat: -35.2148653, lng: 149.0646456} - { name: Ligertwood Street,stop_code: Wjz65aB, lat: -35.2148653, lng: 149.0646456}
- { name: Alderman Street,stop_code: Wjz65rQ, lat: -35.2142653, lng: 149.0676927} - { name: Alderman Street,stop_code: Wjz65rQ, lat: -35.2142653, lng: 149.0676927}
- { name: Hatfield Street,stop_code: Wjz66oJ, lat: -35.2107077, lng: 149.0674989} - { name: Hatfield Street,stop_code: Wjz66oJ, lat: -35.2107077, lng: 149.0674989}
- { name: Clancy Street,stop_code: Wjz66WS, lat: -35.2092634, lng: 149.0731992} - { name: Clancy Street,stop_code: Wjz66WS, lat: -35.2092634, lng: 149.0731992}
- { name: Marconi Crescent,stop_code: WjrW_zu, lat: -35.3788924, lng: 149.0576496} - { name: Marconi Crescent,stop_code: WjrW_zu, lat: -35.3788924, lng: 149.0576496}
- { name: Marconi Crescent,stop_code: WjrW_RH, lat: -35.3777568, lng: 149.0607135} - { name: Marconi Crescent,stop_code: WjrW_RH, lat: -35.3777568, lng: 149.0607135}
- { name: Marconi Crescent,stop_code: Wjz27k0, lat: -35.3786939, lng: 149.0653235} - { name: Marconi Crescent,stop_code: Wjz27k0, lat: -35.3786939, lng: 149.0653235}
- { name: Lascelles Circuit,stop_code: Wjz27gg, lat: -35.3814094, lng: 149.0656219} - { name: Lascelles Circuit,stop_code: Wjz27gg, lat: -35.3814094, lng: 149.0656219}
- { name: Summerland Circuit,stop_code: Wjz26tw, lat: -35.38347, lng: 149.0674733} - { name: Summerland Circuit,stop_code: Wjz26tw, lat: -35.38347, lng: 149.0674733}
- { name: Mason Street,stop_code: Wjz26WW, lat: -35.3853577, lng: 149.0733293} - { name: Mason Street,stop_code: Wjz26WW, lat: -35.3853577, lng: 149.0733293}
- { name: Lee Steere Crescent,stop_code: Wjz2df1, lat: -35.3875049, lng: 149.0748933} - { name: Lee Steere Crescent,stop_code: Wjz2df1, lat: -35.3875049, lng: 149.0748933}
- { name: Kingsmill Street,stop_code: Wjz2d32, lat: -35.3901917, lng: 149.0734943} - { name: Kingsmill Street,stop_code: Wjz2d32, lat: -35.3901917, lng: 149.0734943}
- { name: Jenke Circuit,stop_code: Wjz24uT, lat: -35.3931517, lng: 149.0676751} - { name: Jenke Circuit,stop_code: Wjz24uT, lat: -35.3931517, lng: 149.0676751}
- { name: O'Halloran Circuit,stop_code: Wjz24lA, lat: -35.3941231, lng: 149.0659575} - { name: O'Halloran Circuit,stop_code: Wjz24lA, lat: -35.3941231, lng: 149.0659575}
- { name: Pinkerton Circuit,stop_code: Wjz2498, lat: -35.3972167, lng: 149.0640703} - { name: Pinkerton Circuit,stop_code: Wjz2498, lat: -35.3972167, lng: 149.0640703}
- { name: Ragless Circuit,stop_code: Wjz234e, lat: -35.4001412, lng: 149.0627055} - { name: Ragless Circuit,stop_code: Wjz234e, lat: -35.4001412, lng: 149.0627055}
- { name: Learmonth Drive,stop_code: Wjz230Q, lat: -35.4030936, lng: 149.0635466} - { name: Learmonth Drive,stop_code: Wjz230Q, lat: -35.4030936, lng: 149.0635466}
- { name: Lavan Place,stop_code: Wjz66C2, lat: -35.2068343, lng: 149.0681005} - { name: Lavan Place,stop_code: Wjz66C2, lat: -35.2068343, lng: 149.0681005}
- { name: Clancy Street,stop_code: Wjz66Lx, lat: -35.2062279, lng: 149.0700922} - { name: Clancy Street,stop_code: Wjz66Lx, lat: -35.2062279, lng: 149.0700922}
- { name: Edmunds Place,stop_code: Wjz70go, lat: -35.2001419, lng: 149.0658463} - { name: Edmunds Place,stop_code: Wjz70go, lat: -35.2001419, lng: 149.0658463}
- { name: Baddeley Crescent,stop_code: Wjr_UUM, lat: -35.2001188, lng: 149.062303} - { name: Baddeley Crescent,stop_code: Wjr_UUM, lat: -35.2001188, lng: 149.062303}
- { name: Captain Cook Crescent,stop_code: Wjz3_z-, lat: -35.3349223, lng: 149.1461306} - { name: Captain Cook Crescent,stop_code: Wjz3_z-, lat: -35.3349223, lng: 149.1461306}
- { name: Kingsford Smith Drive,stop_code: Wjr_UPL, lat: -35.1975228, lng: 149.0606273} - { name: Kingsford Smith Drive,stop_code: Wjr_UPL, lat: -35.1975228, lng: 149.0606273}
- { name: Kingsford Smith Drive,stop_code: Wjr_UTJ, lat: -35.1949558, lng: 149.0607434} - { name: Kingsford Smith Drive,stop_code: Wjr_UTJ, lat: -35.1949558, lng: 149.0607434}
- { name: Healy Street,stop_code: Wjz70kD, lat: -35.196836, lng: 149.0659887} - { name: Healy Street,stop_code: Wjz70kD, lat: -35.196836, lng: 149.0659887}
- { name: Heagney Crescent,stop_code: Wjz2EXs, lat: -35.4174557, lng: 149.1275741} - { name: Heagney Crescent,stop_code: Wjz2EXs, lat: -35.4174557, lng: 149.1275741}
- { name: Monaro Highway,stop_code: Wjz2V0k, lat: -35.4140263, lng: 149.1397991} - { name: Monaro Highway,stop_code: Wjz2V0k, lat: -35.4140263, lng: 149.1397991}
- { name: Willoughby Crescent,stop_code: Wjz2NH0, lat: -35.4123115, lng: 149.1353734} - { name: Willoughby Crescent,stop_code: Wjz2NH0, lat: -35.4123115, lng: 149.1353734}
- { name: Theodore Street,stop_code: Wjz48Q1, lat: -35.3291744, lng: 149.0818599} - { name: Theodore Street,stop_code: Wjz48Q1, lat: -35.3291744, lng: 149.0818599}
- { name: Martin Street,stop_code: Wjz49Ui, lat: -35.3262888, lng: 149.0835377} - { name: Martin Street,stop_code: Wjz49Ui, lat: -35.3262888, lng: 149.0835377}
- { name: Morgan Crescent,stop_code: Wjz4aMo, lat: -35.3209613, lng: 149.082268} - { name: Morgan Crescent,stop_code: Wjz4aMo, lat: -35.3209613, lng: 149.082268}
- { name: Jenkins Street,stop_code: Wjz49dp, lat: -35.3229961, lng: 149.075421} - { name: Jenkins Street,stop_code: Wjz49dp, lat: -35.3229961, lng: 149.075421}
- { name: Launceston Street,stop_code: Wjz3e8l, lat: -35.3425473, lng: 149.0752509} - { name: Launceston Street,stop_code: Wjz3e8l, lat: -35.3425473, lng: 149.0752509}
- { name: McCubbin Street,stop_code: WjrX_bF, lat: -35.3353506, lng: 149.0538045} - { name: McCubbin Street,stop_code: WjrX_bF, lat: -35.3353506, lng: 149.0538045}
- { name: Namatjira Drive,stop_code: WjrX-oT, lat: -35.3424053, lng: 149.0567937} - { name: Namatjira Drive,stop_code: WjrX-oT, lat: -35.3424053, lng: 149.0567937}
- { name: Mather Street,stop_code: WjrX-zT, lat: -35.3402984, lng: 149.0581286} - { name: Mather Street,stop_code: WjrX-zT, lat: -35.3402984, lng: 149.0581286}
- { name: Nambir Court,stop_code: Wjz1edz, lat: -35.4271482, lng: 149.0757082} - { name: Nambir Court,stop_code: Wjz1edz, lat: -35.4271482, lng: 149.0757082}
- { name: Anketell Street,stop_code: Wjz20Eo, lat: -35.4198466, lng: 149.0699766} - { name: Anketell Street,stop_code: Wjz20Eo, lat: -35.4198466, lng: 149.0699766}
- { name: Laurens Street,stop_code: Wjz2aVu, lat: -35.4076897, lng: 149.0836236} - { name: Laurens Street,stop_code: Wjz2aVu, lat: -35.4076897, lng: 149.0836236}
- { name: Charleston Street,stop_code: Wjz28DH, lat: -35.4148504, lng: 149.0799887} - { name: Charleston Street,stop_code: Wjz28DH, lat: -35.4148504, lng: 149.0799887}
- { name: Mault Place,stop_code: Wjz2g6U, lat: -35.4157965, lng: 149.0857566} - { name: Mault Place,stop_code: Wjz2g6U, lat: -35.4157965, lng: 149.0857566}
- { name: Corlette Crescent,stop_code: Wjz2gvd, lat: -35.4146612, lng: 149.0888256} - { name: Corlette Crescent,stop_code: Wjz2gvd, lat: -35.4146612, lng: 149.0888256}
- { name: Nemarang Crescent,stop_code: Wjz33CI, lat: -35.3549749, lng: 149.0689295} - { name: Nemarang Crescent,stop_code: Wjz33CI, lat: -35.3549749, lng: 149.0689295}
- { name: Tuggeranong Parkway Onramp,stop_code: Wjz33KX, lat: -35.3550858, lng: 149.070698} - { name: Tuggeranong Parkway Onramp,stop_code: Wjz33KX, lat: -35.3550858, lng: 149.070698}
- { name: Wambaya Street,stop_code: Wjz33nk, lat: -35.3543462, lng: 149.0657554} - { name: Wambaya Street,stop_code: Wjz33nk, lat: -35.3543462, lng: 149.0657554}
- { name: Wirangu Place,stop_code: WjrXXI2, lat: -35.3565059, lng: 149.058473} - { name: Wirangu Place,stop_code: WjrXXI2, lat: -35.3565059, lng: 149.058473}
- { name: Walpiri Place,stop_code: WjrXYtm, lat: -35.3499821, lng: 149.0560969} - { name: Walpiri Place,stop_code: WjrXYtm, lat: -35.3499821, lng: 149.0560969}
- { name: Bangalay Crescent,stop_code: WjrXIKK, lat: -35.3493279, lng: 149.0374035} - { name: Bangalay Crescent,stop_code: WjrXIKK, lat: -35.3493279, lng: 149.0374035}
- { name: Hindmarsh Drive,stop_code: WjrXJfw, lat: -35.3436463, lng: 149.031771} - { name: Hindmarsh Drive,stop_code: WjrXJfw, lat: -35.3436463, lng: 149.031771}
- { name: Eppalock Street,stop_code: WjrYEg0, lat: -35.3320285, lng: 149.0323493} - { name: Eppalock Street,stop_code: WjrYEg0, lat: -35.3320285, lng: 149.0323493}
- { name: Warragamba Avenue,stop_code: WjrYEWc, lat: -35.3302839, lng: 149.0394086} - { name: Warragamba Avenue,stop_code: WjrYEWc, lat: -35.3302839, lng: 149.0394086}
- { name: Streeton Drive,stop_code: WjrX_1g, lat: -35.336799, lng: 149.0519909} - { name: Streeton Drive,stop_code: WjrX_1g, lat: -35.336799, lng: 149.0519909}
- { name: Hellyer Street,stop_code: WjrXLY1, lat: -35.3346674, lng: 149.0391656} - { name: Hellyer Street,stop_code: WjrXLY1, lat: -35.3346674, lng: 149.0391656}
- { name: Paloona Place,stop_code: WjrXLEL, lat: -35.3369076, lng: 149.0374236} - { name: Paloona Place,stop_code: WjrXLEL, lat: -35.3369076, lng: 149.0374236}
- { name: Leighton Street,stop_code: Wjz39GV, lat: -35.369019, lng: 149.0816284} - { name: Leighton Street,stop_code: Wjz39GV, lat: -35.369019, lng: 149.0816284}
- { name: Foskett Street,stop_code: Wjr_V6V, lat: -35.1904467, lng: 149.0528033} - { name: Foskett Street,stop_code: Wjr_V6V, lat: -35.1904467, lng: 149.0528033}
- { name: Tillyard Drive,stop_code: Wjr_McO, lat: -35.1972013, lng: 149.0429389} - { name: Tillyard Drive,stop_code: Wjr_McO, lat: -35.1972013, lng: 149.0429389}
- { name: Spalding Street,stop_code: Wjr_MhY, lat: -35.1991196, lng: 149.0445095} - { name: Spalding Street,stop_code: Wjr_MhY, lat: -35.1991196, lng: 149.0445095}
- { name: O'Shanassy Street,stop_code: Wjz4a9o, lat: -35.3203323, lng: 149.0754663} - { name: O'Shanassy Street,stop_code: Wjz4a9o, lat: -35.3203323, lng: 149.0754663}
- { name: Owen Dixon Drive,stop_code: Wjz70IW, lat: -35.197242, lng: 149.0706277} - { name: Owen Dixon Drive,stop_code: Wjz70IW, lat: -35.197242, lng: 149.0706277}
- { name: Sport Way,stop_code: Wjr-DTC, lat: -35.2002855, lng: 149.0276101} - { name: Sport Way,stop_code: Wjr-DTC, lat: -35.2002855, lng: 149.0276101}
- { name: Lance Hill Avenue,stop_code: Wjr_wf4, lat: -35.1950004, lng: 149.0199737} - { name: Lance Hill Avenue,stop_code: Wjr_wf4, lat: -35.1950004, lng: 149.0199737}
- { name: Kerrigan Street,stop_code: Wjr_pVW, lat: -35.1938099, lng: 149.0184155} - { name: Kerrigan Street,stop_code: Wjr_pVW, lat: -35.1938099, lng: 149.0184155}
- { name: Douglass Street,stop_code: Wjz70Wi, lat: -35.1986355, lng: 149.0725952} - { name: Douglass Street,stop_code: Wjz70Wi, lat: -35.1986355, lng: 149.0725952}
- { name: Copland Drive,stop_code: Wjz67_v, lat: -35.2002563, lng: 149.0727607} - { name: Copland Drive,stop_code: Wjz67_v, lat: -35.2002563, lng: 149.0727607}
- { name: Gallipoli Road,stop_code: Wjzcend, lat: -35.2937972, lng: 149.1643403} - { name: Gallipoli Road,stop_code: Wjzcend, lat: -35.2937972, lng: 149.1643403}
- { name: Mileham Street,stop_code: Wjr-vNL, lat: -35.2043835, lng: 149.0167621} - { name: Mileham Street,stop_code: Wjr-vNL, lat: -35.2043835, lng: 149.0167621}
- { name: Ginninderra Drive,stop_code: Wjr-Df8, lat: -35.2008175, lng: 149.0201835} - { name: Ginninderra Drive,stop_code: Wjr-Df8, lat: -35.2008175, lng: 149.0201835}
- { name: Clode Crescent,stop_code: Wjr-te3, lat: -35.2122382, lng: 149.0090273} - { name: Clode Crescent,stop_code: Wjr-te3, lat: -35.2122382, lng: 149.0090273}
- { name: Handcock Crescent,stop_code: Wjr-CsO, lat: -35.2082115, lng: 149.0237453} - { name: Handcock Crescent,stop_code: Wjr-CsO, lat: -35.2082115, lng: 149.0237453}
- { name: Prevost Place,stop_code: Wjr-sKW, lat: -35.2178207, lng: 149.0156953} - { name: Prevost Place,stop_code: Wjr-sKW, lat: -35.2178207, lng: 149.0156953}
- { name: Plowman Place,stop_code: Wjr-S9y, lat: -35.2102797, lng: 149.0426899} - { name: Plowman Place,stop_code: Wjr-S9y, lat: -35.2102797, lng: 149.0426899}
- { name: O'Loghlen Street,stop_code: Wjr-HbC, lat: -35.2250302, lng: 149.0316399} - { name: O'Loghlen Street,stop_code: Wjr-HbC, lat: -35.2250302, lng: 149.0316399}
- { name: Southern Cross Drive,stop_code: Wjr-sQ8, lat: -35.2193706, lng: 149.0159919} - { name: Southern Cross Drive,stop_code: Wjr-sQ8, lat: -35.2193706, lng: 149.0159919}
- { name: Armstrong Crescent,stop_code: Wjr-rv7, lat: -35.2221818, lng: 149.0117611} - { name: Armstrong Crescent,stop_code: Wjr-rv7, lat: -35.2221818, lng: 149.0117611}
- { name: Spofforth Street,stop_code: Wjr-kVk, lat: -35.2210905, lng: 149.0066193} - { name: Spofforth Street,stop_code: Wjr-kVk, lat: -35.2210905, lng: 149.0066193}
- { name: Pickworth Street,stop_code: Wjr-rxG, lat: -35.2267918, lng: 149.0140227} - { name: Pickworth Street,stop_code: Wjr-rxG, lat: -35.2267918, lng: 149.0140227}
- { name: Macnaughton Street,stop_code: Wjr-qZg, lat: -35.2296561, lng: 149.0176617} - { name: Macnaughton Street,stop_code: Wjr-qZg, lat: -35.2296561, lng: 149.0176617}
- { name: Powell Street,stop_code: Wjr-rUs, lat: -35.2272548, lng: 149.0178319} - { name: Powell Street,stop_code: Wjr-rUs, lat: -35.2272548, lng: 149.0178319}
- { name: Beaurepaire Crescent,stop_code: Wjr-rNr, lat: -35.226697, lng: 149.016389} - { name: Beaurepaire Crescent,stop_code: Wjr-rNr, lat: -35.226697, lng: 149.016389}
- { name: Hardwick Crescent,stop_code: Wjr-zcC, lat: -35.2243517, lng: 149.0207165} - { name: Hardwick Crescent,stop_code: Wjr-zcC, lat: -35.2243517, lng: 149.0207165}
- { name: Brazel Street,stop_code: Wjr-G5f, lat: -35.2290792, lng: 149.0298564} - { name: Brazel Street,stop_code: Wjr-G5f, lat: -35.2290792, lng: 149.0298564}
- { name: Wearing Street,stop_code: Wjr-xRd, lat: -35.2347078, lng: 149.0270748} - { name: Wearing Street,stop_code: Wjr-xRd, lat: -35.2347078, lng: 149.0270748}
- { name: Drake Brockman Drive,stop_code: Wjr-wDP, lat: -35.2389936, lng: 149.0252414} - { name: Drake Brockman Drive,stop_code: Wjr-wDP, lat: -35.2389936, lng: 149.0252414}
- { name: Castieau Street,stop_code: Wjr-Gsq, lat: -35.2301636, lng: 149.0342818} - { name: Castieau Street,stop_code: Wjr-Gsq, lat: -35.2301636, lng: 149.0342818}
- { name: Ulm Street,stop_code: Wjr-GyJ, lat: -35.2312775, lng: 149.0359574} - { name: Ulm Street,stop_code: Wjr-GyJ, lat: -35.2312775, lng: 149.0359574}
- { name: Wirraway Crescent,stop_code: Wjr-GFM, lat: -35.2324613, lng: 149.03753} - { name: Wirraway Crescent,stop_code: Wjr-GFM, lat: -35.2324613, lng: 149.03753}
- { name: Ross Smith Crescent,stop_code: Wjr-F_m, lat: -35.233261, lng: 149.039515} - { name: Ross Smith Crescent,stop_code: Wjr-F_m, lat: -35.233261, lng: 149.039515}
- { name: Ross Smith Crescent,stop_code: Wjr-FCU, lat: -35.2344506, lng: 149.0363984} - { name: Ross Smith Crescent,stop_code: Wjr-FCU, lat: -35.2344506, lng: 149.0363984}
- { name: Hinkler Street,stop_code: Wjr-Fzd, lat: -35.2360739, lng: 149.0353153} - { name: Hinkler Street,stop_code: Wjr-Fzd, lat: -35.2360739, lng: 149.0353153}
- { name: Delamere Street,stop_code: Wjr-E8A, lat: -35.2437543, lng: 149.031741} - { name: Delamere Street,stop_code: Wjr-E8A, lat: -35.2437543, lng: 149.031741}
- { name: Tanumbirini Street,stop_code: WjrZLdA, lat: -35.245805, lng: 149.0316615} - { name: Tanumbirini Street,stop_code: WjrZLdA, lat: -35.245805, lng: 149.0316615}
- { name: Southwell Street,stop_code: WjrZSKp, lat: -35.2509203, lng: 149.0480636} - { name: Southwell Street,stop_code: WjrZSKp, lat: -35.2509203, lng: 149.0480636}
- { name: De Salis Street,stop_code: WjrZSWs, lat: -35.2533983, lng: 149.050782} - { name: De Salis Street,stop_code: WjrZSWs, lat: -35.2533983, lng: 149.050782}
- { name: Hannaford Street,stop_code: Wjr-MCk, lat: -35.2396029, lng: 149.0464162} - { name: Hannaford Street,stop_code: Wjr-MCk, lat: -35.2396029, lng: 149.0464162}
- { name: Hannaford Street,stop_code: Wjr-M-x, lat: -35.2399127, lng: 149.0508416} - { name: Hannaford Street,stop_code: Wjr-M-x, lat: -35.2399127, lng: 149.0508416}
- { name: Shumack Street,stop_code: WjrZ-aT, lat: -35.2531402, lng: 149.053943} - { name: Shumack Street,stop_code: WjrZ-aT, lat: -35.2531402, lng: 149.053943}
- { name: Coulter Drive,stop_code: WjrZZeD, lat: -35.2558247, lng: 149.0536901} - { name: Coulter Drive,stop_code: WjrZZeD, lat: -35.2558247, lng: 149.0536901}
- { name: Redfern Street,stop_code: WjrZZlR, lat: -35.2567539, lng: 149.055397} - { name: Redfern Street,stop_code: WjrZZlR, lat: -35.2567539, lng: 149.055397}
- { name: Atkinson Street,stop_code: WjrZZH3, lat: -35.2583026, lng: 149.0584315} - { name: Atkinson Street,stop_code: WjrZZH3, lat: -35.2583026, lng: 149.0584315}
- { name: Skinner Street,stop_code: Wjz54mj, lat: -35.2617096, lng: 149.0656385} - { name: Skinner Street,stop_code: Wjz54mj, lat: -35.2617096, lng: 149.0656385}
- { name: Allman Circuit,stop_code: Wjz55vN, lat: -35.2557214, lng: 149.0677248} - { name: Allman Circuit,stop_code: Wjz55vN, lat: -35.2557214, lng: 149.0677248}
- { name: Redfern Street,stop_code: Wjz557P, lat: -35.2555149, lng: 149.0636155} - { name: Redfern Street,stop_code: Wjz557P, lat: -35.2555149, lng: 149.0636155}
- { name: Goulburn Street,stop_code: WjrZ-WW, lat: -35.2535016, lng: 149.0623511} - { name: Goulburn Street,stop_code: WjrZ-WW, lat: -35.2535016, lng: 149.0623511}
- { name: Roberts Street,stop_code: WjrZ-GZ, lat: -35.2532951, lng: 149.0596327} - { name: Roberts Street,stop_code: WjrZ-GZ, lat: -35.2532951, lng: 149.0596327}
- { name: Erskine Street,stop_code: WjrZ-Jc, lat: -35.2513107, lng: 149.058664} - { name: Erskine Street,stop_code: WjrZ-Jc, lat: -35.2513107, lng: 149.058664}
- { name: Erskine Street,stop_code: WjrZ_Fk, lat: -35.2485228, lng: 149.0588536} - { name: Erskine Street,stop_code: WjrZ_Fk, lat: -35.2485228, lng: 149.0588536}
- { name: Thurlow Place,stop_code: Wjz57Q7, lat: -35.2462221, lng: 149.0708857} - { name: Thurlow Place,stop_code: Wjz57Q7, lat: -35.2462221, lng: 149.0708857}
- { name: Maddison Close,stop_code: Wjz5fm2, lat: -35.2452775, lng: 149.0763507} - { name: Maddison Close,stop_code: Wjz5fm2, lat: -35.2452775, lng: 149.0763507}
- { name: Vowels Crescent,stop_code: Wjz5nUz, lat: -35.2493715, lng: 149.094909} - { name: Vowels Crescent,stop_code: Wjz5nUz, lat: -35.2493715, lng: 149.094909}
- { name: Thynne Street,stop_code: Wjz6gUM, lat: -35.2441052, lng: 149.0951619} - { name: Thynne Street,stop_code: Wjz6gUM, lat: -35.2441052, lng: 149.0951619}
- { name: Leverrier Crescent,stop_code: Wjz5vrT, lat: -35.2469189, lng: 149.1007523} - { name: Leverrier Crescent,stop_code: Wjz5vrT, lat: -35.2469189, lng: 149.1007523}
- { name: Braybrooke Street,stop_code: Wjz6oJz, lat: -35.2403705, lng: 149.1030403} - { name: Braybrooke Street,stop_code: Wjz6oJz, lat: -35.2403705, lng: 149.1030403}
- { name: Temperley Street,stop_code: Wjz7hZW, lat: -35.1910485, lng: 149.0953265} - { name: Temperley Street,stop_code: Wjz7hZW, lat: -35.1910485, lng: 149.0953265}
- { name: Dobbin Circuit,stop_code: Wjz7iKx, lat: -35.1849518, lng: 149.0920391} - { name: Dobbin Circuit,stop_code: Wjz7iKx, lat: -35.1849518, lng: 149.0920391}
- { name: Fitzsimmons Street,stop_code: Wjz7jaJ, lat: -35.1819033, lng: 149.0868551} - { name: Fitzsimmons Street,stop_code: Wjz7jaJ, lat: -35.1819033, lng: 149.0868551}
- { name: Kelleway Avenue,stop_code: Wjz7jW4, lat: -35.181955, lng: 149.0941886} - { name: Kelleway Avenue,stop_code: Wjz7jW4, lat: -35.181955, lng: 149.0941886}
- { name: Whatmore Court,stop_code: Wjz7rzg, lat: -35.1815933, lng: 149.1014588} - { name: Whatmore Court,stop_code: Wjz7rzg, lat: -35.1815933, lng: 149.1014588}
- { name: Whitfield Circuit,stop_code: Wjz7pkV, lat: -35.1918235, lng: 149.0995622} - { name: Whitfield Circuit,stop_code: Wjz7pkV, lat: -35.1918235, lng: 149.0995622}
- { name: Lexcen Avenue,stop_code: Wjz7qSX, lat: -35.1847968, lng: 149.1050623} - { name: Lexcen Avenue,stop_code: Wjz7qSX, lat: -35.1847968, lng: 149.1050623}
- { name: Kelleway Avenue,stop_code: Wjz7rRa, lat: -35.1800948, lng: 149.1039243} - { name: Kelleway Avenue,stop_code: Wjz7rRa, lat: -35.1800948, lng: 149.1039243}
- { name: Jabanungga Avenue,stop_code: Wjz7B0w, lat: -35.1727054, lng: 149.107275} - { name: Jabanungga Avenue,stop_code: Wjz7B0w, lat: -35.1727054, lng: 149.107275}
- { name: Newlop Street,stop_code: Wjz7thn, lat: -35.1713618, lng: 149.0985507} - { name: Newlop Street,stop_code: Wjz7thn, lat: -35.1713618, lng: 149.0985507}
- { name: Bicentennial National Trail,stop_code: Wjz7uxi, lat: -35.1663489, lng: 149.1013956} - { name: Bicentennial National Trail,stop_code: Wjz7uxi, lat: -35.1663489, lng: 149.1013956}
- { name: Mundang Crescent,stop_code: Wjz7tIt, lat: -35.169553, lng: 149.1029128} - { name: Mundang Crescent,stop_code: Wjz7tIt, lat: -35.169553, lng: 149.1029128}
- { name: Wanganeen Avenue,stop_code: Wjz7BsE, lat: -35.1699148, lng: 149.1115106} - { name: Wanganeen Avenue,stop_code: Wjz7BsE, lat: -35.1699148, lng: 149.1115106}
- { name: College Street,stop_code: Wjz68W3, lat: -35.2425008, lng: 149.0831669} - { name: College Street,stop_code: Wjz68W3, lat: -35.2425008, lng: 149.0831669}
- { name: Drakeford Drive,stop_code: WjrW_uo, lat: -35.3773291, lng: 149.056161} - { name: Drakeford Drive,stop_code: WjrW_uo, lat: -35.3773291, lng: 149.056161}
- { name: Tuggeranong Parkway,stop_code: WjrXUAm, lat: -35.3726375, lng: 149.0574471} - { name: Tuggeranong Parkway,stop_code: WjrXUAm, lat: -35.3726375, lng: 149.0574471}
- { name: Banambila Street,stop_code: Wjz5l2U, lat: -35.2592266, lng: 149.0857332} - { name: Banambila Street,stop_code: Wjz5l2U, lat: -35.2592266, lng: 149.0857332}
- { name: Bindel Street,stop_code: Wjz5e8Y, lat: -35.2547235, lng: 149.0761202} - { name: Bindel Street,stop_code: Wjz5e8Y, lat: -35.2547235, lng: 149.0761202}
- { name: Kambah pool Road,stop_code: WjrXMFM, lat: -35.3752866, lng: 149.0485475} - { name: Kambah pool Road,stop_code: WjrXMFM, lat: -35.3752866, lng: 149.0485475}
- { name: Carbeen Street,stop_code: WjrXJZ6, lat: -35.3445279, lng: 149.0392999} - { name: Carbeen Street,stop_code: WjrXJZ6, lat: -35.3445279, lng: 149.0392999}
- { name: Collings Street,stop_code: Wjz3jaF, lat: -35.3579826, lng: 149.0867102} - { name: Collings Street,stop_code: Wjz3jaF, lat: -35.3579826, lng: 149.0867102}
- { name: Paramatta Street,stop_code: Wjz3jei, lat: -35.3551755, lng: 149.0862349} - { name: Paramatta Street,stop_code: Wjz3jei, lat: -35.3551755, lng: 149.0862349}
- { name: Aikman Drive,stop_code: Wjz69uI, lat: -35.2341477, lng: 149.0784965} - { name: Aikman Drive,stop_code: Wjz69uI, lat: -35.2341477, lng: 149.0784965}
- { name: Amy Ackman Street,stop_code: Wjz7-oI, lat: -35.1668191, lng: 149.1443901} - { name: Amy Ackman Street,stop_code: Wjz7-oI, lat: -35.1668191, lng: 149.1443901}
- { name: Barracks Flat Drive,stop_code: WjzbUGB, lat: -35.3740947, lng: 149.2349556} - { name: Barracks Flat Drive,stop_code: WjzbUGB, lat: -35.3740947, lng: 149.2349556}
- { name: Ling Place,stop_code: Wjzj0yX, lat: -35.3742978, lng: 149.2450265} - { name: Ling Place,stop_code: Wjzj0yX, lat: -35.3742978, lng: 149.2450265}
- { name: Knowles Place,stop_code: Wjz5FOn, lat: -35.2806054, lng: 149.1260452} - { name: Knowles Place,stop_code: Wjz5FOn, lat: -35.2806054, lng: 149.1260452}
- { name: Gundaroo Drive,stop_code: Wjz7oYv, lat: -35.196789, lng: 149.1057064} - { name: Gundaroo Drive,stop_code: Wjz7oYv, lat: -35.196789, lng: 149.1057064}
- { name: Gundaroo Drive,stop_code: Wjz7xpa, lat: -35.1938349, lng: 149.1107761} - { name: Gundaroo Drive,stop_code: Wjz7xpa, lat: -35.1938349, lng: 149.1107761}
- { name: Barritt Street,stop_code: WjrW_1f, lat: -35.3801683, lng: 149.051853} - { name: Barritt Street,stop_code: WjrW_1f, lat: -35.3801683, lng: 149.051853}
- { name: Barritt Street,stop_code: WjrWTJq, lat: -35.3778081, lng: 149.0480034} - { name: Barritt Street,stop_code: WjrWTJq, lat: -35.3778081, lng: 149.0480034}
- { name: Constitution Avenue,stop_code: Wjz5MsD, lat: -35.2847121, lng: 149.1333531} - { name: Constitution Avenue,stop_code: Wjz5MsD, lat: -35.2847121, lng: 149.1333531}
- { name: Mort Street,stop_code: Wjz5Ok1, lat: -35.2742265, lng: 149.1312268} - { name: Mort Street,stop_code: Wjz5Ok1, lat: -35.2742265, lng: 149.1312268}
- { name: Northbourne Avenue,stop_code: Wjz5SDc, lat: -35.2499285, lng: 149.1341368} - { name: Northbourne Avenue,stop_code: Wjz5SDc, lat: -35.2499285, lng: 149.1341368}
- { name: Northbourne Avenue,stop_code: Wjz5Qgn, lat: -35.2655006, lng: 149.1316277} - { name: Northbourne Avenue,stop_code: Wjz5Qgn, lat: -35.2655006, lng: 149.1316277}
- { name: Northbourne Avenue,stop_code: Wjz5Sqk, lat: -35.2533948, lng: 149.1329835} - { name: Northbourne Avenue,stop_code: Wjz5Sqk, lat: -35.2533948, lng: 149.1329835}
- { name: Coranderrk Street,stop_code: Wjz5MI3, lat: -35.2850249, lng: 149.1353935} - { name: Coranderrk Street,stop_code: Wjz5MI3, lat: -35.2850249, lng: 149.1353935}
- { name: Launceston Street,stop_code: Wjz3eje, lat: -35.3403963, lng: 149.0765097} - { name: Launceston Street,stop_code: Wjz3eje, lat: -35.3403963, lng: 149.0765097}
- { name: Streeton Drive,stop_code: WjrXQ2W, lat: -35.3523853, lng: 149.0417814} - { name: Streeton Drive,stop_code: WjrXQ2W, lat: -35.3523853, lng: 149.0417814}
- { name: Bangalay Crescent,stop_code: WjrXQeH, lat: -35.3495777, lng: 149.0428125} - { name: Bangalay Crescent,stop_code: WjrXQeH, lat: -35.3495777, lng: 149.0428125}
- { name: Sidaway Street,stop_code: WjrXHZU, lat: -35.3560382, lng: 149.0404158} - { name: Sidaway Street,stop_code: WjrXHZU, lat: -35.3560382, lng: 149.0404158}
- { name: Mirrabei Drive,stop_code: Wjz7BST, lat: -35.167951, lng: 149.1157463} - { name: Mirrabei Drive,stop_code: Wjz7BST, lat: -35.167951, lng: 149.1157463}
- { name: Saunders Street,stop_code: Wjz7AJS, lat: -35.174204, lng: 149.1143555} - { name: Saunders Street,stop_code: Wjz7AJS, lat: -35.174204, lng: 149.1143555}
- { name: Amagula Avenue,stop_code: Wjz7zzB, lat: -35.1811799, lng: 149.1126486} - { name: Amagula Avenue,stop_code: Wjz7zzB, lat: -35.1811799, lng: 149.1126486}
- { name: Windradyne Street,stop_code: Wjz7CD7, lat: -35.1617492, lng: 149.1119532} - { name: Windradyne Street,stop_code: Wjz7CD7, lat: -35.1617492, lng: 149.1119532}
- { name: Mirrabei Drive,stop_code: Wjz7If2, lat: -35.1732221, lng: 149.1188441} - { name: Mirrabei Drive,stop_code: Wjz7If2, lat: -35.1732221, lng: 149.1188441}
- { name: Paul Coe Crescent,stop_code: Wjz7Iax, lat: -35.1766844, lng: 149.1196027} - { name: Paul Coe Crescent,stop_code: Wjz7Iax, lat: -35.1766844, lng: 149.1196027}
- { name: Mirrabei Drive,stop_code: Wjz7IFg, lat: -35.1774595, lng: 149.1246602} - { name: Mirrabei Drive,stop_code: Wjz7IFg, lat: -35.1774595, lng: 149.1246602}
- { name: Shoalhaven Avenue,stop_code: Wjz7J-7, lat: -35.167951, lng: 149.1270626} - { name: Shoalhaven Avenue,stop_code: Wjz7J-7, lat: -35.167951, lng: 149.1270626}
- { name: Proserpine Circuit,stop_code: Wjz7RdE, lat: -35.169243, lng: 149.1307293} - { name: Proserpine Circuit,stop_code: Wjz7RdE, lat: -35.169243, lng: 149.1307293}
- { name: Inglewood Street,stop_code: Wjz7Y0J, lat: -35.177732, lng: 149.1403005} - { name: Inglewood Street,stop_code: Wjz7Y0J, lat: -35.177732, lng: 149.1403005}
- { name: Obrien Place,stop_code: Wjz7GSc, lat: -35.1847451, lng: 149.1258614} - { name: Obrien Place,stop_code: Wjz7GSc, lat: -35.1847451, lng: 149.1258614}
- { name: Anthony Rolfe Avenue,stop_code: Wjz7Ppw, lat: -35.1829884, lng: 149.1332581} - { name: Anthony Rolfe Avenue,stop_code: Wjz7Ppw, lat: -35.1829884, lng: 149.1332581}
- { name: Swain Street,stop_code: Wjz7X2n, lat: -35.1817108, lng: 149.1398579} - { name: Swain Street,stop_code: Wjz7X2n, lat: -35.1817108, lng: 149.1398579}
- { name: Petersilka Street,stop_code: Wjz7XxD, lat: -35.1823825, lng: 149.1457373} - { name: Petersilka Street,stop_code: Wjz7XxD, lat: -35.1823825, lng: 149.1457373}
- { name: Thistle Lane,stop_code: Wjzf2rm, lat: -35.1865677, lng: 149.1549041} - { name: Thistle Lane,stop_code: Wjzf2rm, lat: -35.1865677, lng: 149.1549041}
- { name: Horse Park Drive,stop_code: Wjzf0ZL, lat: -35.1961257, lng: 149.1609099} - { name: Horse Park Drive,stop_code: Wjzf0ZL, lat: -35.1961257, lng: 149.1609099}
- { name: Morris West Street,stop_code: Wjz6_vY, lat: -35.2004651, lng: 149.1448522} - { name: Morris West Street,stop_code: Wjz6_vY, lat: -35.2004651, lng: 149.1448522}
- { name: The Valley Avenue,stop_code: Wjz7Oal, lat: -35.1873286, lng: 149.1301603} - { name: The Valley Avenue,stop_code: Wjz7Oal, lat: -35.1873286, lng: 149.1301603}
- { name: Gungahlin Drive,stop_code: Wjz7Fmf, lat: -35.1899217, lng: 149.1203537} - { name: Gungahlin Drive,stop_code: Wjz7Fmf, lat: -35.1899217, lng: 149.1203537}
- { name: Freeling Crescent,stop_code: Wjz7xO6, lat: -35.1928051, lng: 149.1147348} - { name: Freeling Crescent,stop_code: Wjz7xO6, lat: -35.1928051, lng: 149.1147348}
- { name: Kosciuszko Avenue,stop_code: Wjz7E3Z, lat: -35.1976337, lng: 149.1187656} - { name: Kosciuszko Avenue,stop_code: Wjz7E3Z, lat: -35.1976337, lng: 149.1187656}
- { name: Kosciuszko Avenue,stop_code: Wjz7EJ7, lat: -35.1960839, lng: 149.1244553} - { name: Kosciuszko Avenue,stop_code: Wjz7EJ7, lat: -35.1960839, lng: 149.1244553}
- { name: Hoskins Street,stop_code: Wjz6RQW, lat: -35.2136848, lng: 149.1379368} - { name: Hoskins Street,stop_code: Wjz6RQW, lat: -35.2136848, lng: 149.1379368}
- { name: Hoskins Street,stop_code: Wjz6QTd, lat: -35.2168483, lng: 149.1369095} - { name: Hoskins Street,stop_code: Wjz6QTd, lat: -35.2168483, lng: 149.1369095}
- { name: Sandford Street,stop_code: Wjz6Yaq, lat: -35.2205928, lng: 149.1414139} - { name: Sandford Street,stop_code: Wjz6Yaq, lat: -35.2205928, lng: 149.1414139}
- { name: Flemington Road,stop_code: Wjz6Wse, lat: -35.2298796, lng: 149.1438091} - { name: Flemington Road,stop_code: Wjz6Wse, lat: -35.2298796, lng: 149.1438091}
- { name: Federal Highway,stop_code: Wjze3Fa, lat: -35.2267416, lng: 149.1575876} - { name: Federal Highway,stop_code: Wjze3Fa, lat: -35.2267416, lng: 149.1575876}
- { name: Aspinall Street,stop_code: Wjzeaq_, lat: -35.2311306, lng: 149.1668636} - { name: Aspinall Street,stop_code: Wjzeaq_, lat: -35.2311306, lng: 149.1668636}
- { name: Antill Street,stop_code: Wjze0VY, lat: -35.2430274, lng: 149.1613003} - { name: Antill Street,stop_code: Wjze0VY, lat: -35.2430274, lng: 149.1613003}
- { name: Knox Street,stop_code: Wjze1hB, lat: -35.2374923, lng: 149.1539669} - { name: Knox Street,stop_code: Wjze1hB, lat: -35.2374923, lng: 149.1539669}
- { name: Molesworth Street,stop_code: Wjze17N, lat: -35.2336919, lng: 149.1515898} - { name: Molesworth Street,stop_code: Wjze17N, lat: -35.2336919, lng: 149.1515898}
- { name: Phillip Avenue,stop_code: Wjz6UQw, lat: -35.2413339, lng: 149.1484036} - { name: Phillip Avenue,stop_code: Wjz6UQw, lat: -35.2413339, lng: 149.1484036}
- { name: Melba Street,stop_code: Wjz5_mg, lat: -35.2454644, lng: 149.1425874} - { name: Melba Street,stop_code: Wjz5_mg, lat: -35.2454644, lng: 149.1425874}
- { name: Antill Street,stop_code: Wjz5_O4, lat: -35.24786, lng: 149.147645} - { name: Antill Street,stop_code: Wjz5_O4, lat: -35.24786, lng: 149.147645}
- { name: Antill Street,stop_code: Wjzd7LX, lat: -35.2445144, lng: 149.1586198} - { name: Antill Street,stop_code: Wjzd7LX, lat: -35.2445144, lng: 149.1586198}
- { name: Grayson Street,stop_code: WjzdeeQ, lat: -35.2506237, lng: 149.1639253} - { name: Grayson Street,stop_code: WjzdeeQ, lat: -35.2506237, lng: 149.1639253}
- { name: Stott Street,stop_code: Wjzd6Cq, lat: -35.2507889, lng: 149.1563997} - { name: Stott Street,stop_code: Wjzd6Cq, lat: -35.2507889, lng: 149.1563997}
- { name: Hannan Crescent,stop_code: Wjzd68O, lat: -35.254952, lng: 149.1528797} - { name: Hannan Crescent,stop_code: Wjzd68O, lat: -35.254952, lng: 149.1528797}
- { name: Officer Crescent,stop_code: Wjz5ZZQ, lat: -35.2567691, lng: 149.1500474} - { name: Officer Crescent,stop_code: Wjz5ZZQ, lat: -35.2567691, lng: 149.1500474}
- { name: Officer Crescent,stop_code: Wjz5ZO1, lat: -35.2591479, lng: 149.1477412} - { name: Officer Crescent,stop_code: Wjz5ZO1, lat: -35.2591479, lng: 149.1477412}
- { name: Cowper Street,stop_code: Wjz5-5y, lat: -35.2514497, lng: 149.1400942} - { name: Cowper Street,stop_code: Wjz5-5y, lat: -35.2514497, lng: 149.1400942}
- { name: Morphett Street,stop_code: Wjz5SWN, lat: -35.2535974, lng: 149.1390827} - { name: Morphett Street,stop_code: Wjz5SWN, lat: -35.2535974, lng: 149.1390827}
- { name: Dooring Street,stop_code: Wjz5Z5c, lat: -35.2568022, lng: 149.1396491} - { name: Dooring Street,stop_code: Wjz5Z5c, lat: -35.2568022, lng: 149.1396491}
- { name: Majura Avenue,stop_code: Wjz5Za5, lat: -35.2588175, lng: 149.1409439} - { name: Majura Avenue,stop_code: Wjz5Za5, lat: -35.2588175, lng: 149.1409439}
- { name: Cowper Street,stop_code: Wjz5YfD, lat: -35.2606676, lng: 149.1416317} - { name: Cowper Street,stop_code: Wjz5YfD, lat: -35.2606676, lng: 149.1416317}
- { name: Herbert Crescent,stop_code: Wjz5YKO, lat: -35.2618095, lng: 149.1473796} - { name: Herbert Crescent,stop_code: Wjz5YKO, lat: -35.2618095, lng: 149.1473796}
- { name: Wakefield Gardens,stop_code: Wjz5YAK, lat: -35.2627902, lng: 149.1458623} - { name: Wakefield Gardens,stop_code: Wjz5YAK, lat: -35.2627902, lng: 149.1458623}
- { name: Campbell Street,stop_code: Wjz5Yq4, lat: -35.2643388, lng: 149.1435864} - { name: Campbell Street,stop_code: Wjz5Yq4, lat: -35.2643388, lng: 149.1435864}
- { name: Campbell Street,stop_code: Wjz5XnQ, lat: -35.2664452, lng: 149.1432384} - { name: Campbell Street,stop_code: Wjz5XnQ, lat: -35.2664452, lng: 149.1432384}
- { name: Leslie Street,stop_code: Wjz5XrS, lat: -35.2689744, lng: 149.1446925} - { name: Leslie Street,stop_code: Wjz5XrS, lat: -35.2689744, lng: 149.1446925}
- { name: Campbell Street,stop_code: Wjz5XwW, lat: -35.2714003, lng: 149.1461465} - { name: Campbell Street,stop_code: Wjz5XwW, lat: -35.2714003, lng: 149.1461465}
- { name: Gooreen Street,stop_code: Wjz5W3H, lat: -35.2747063, lng: 149.1403907} - { name: Gooreen Street,stop_code: Wjz5W3H, lat: -35.2747063, lng: 149.1403907}
- { name: Gooreen Street,stop_code: Wjz5W8l, lat: -35.276623, lng: 149.1411209} - { name: Gooreen Street,stop_code: Wjz5W8l, lat: -35.276623, lng: 149.1411209}
- { name: Cox Street,stop_code: Wjz5Ycz, lat: -35.2631, lng: 149.1415634} - { name: Cox Street,stop_code: Wjz5Ycz, lat: -35.2631, lng: 149.1415634}
- { name: Foveaux Street,stop_code: Wjz5Y1_, lat: -35.2648034, lng: 149.1406151} - { name: Foveaux Street,stop_code: Wjz5Y1_, lat: -35.2648034, lng: 149.1406151}
- { name: Limestone Avenue,stop_code: Wjz5QUd, lat: -35.2656089, lng: 149.1383392} - { name: Limestone Avenue,stop_code: Wjz5QUd, lat: -35.2656089, lng: 149.1383392}
- { name: Ipima Street,stop_code: Wjz5PLJ, lat: -35.2663315, lng: 149.136253} - { name: Ipima Street,stop_code: Wjz5PLJ, lat: -35.2663315, lng: 149.136253}
- { name: Ijong Street,stop_code: Wjz5PBC, lat: -35.2675907, lng: 149.1347357} - { name: Ijong Street,stop_code: Wjz5PBC, lat: -35.2675907, lng: 149.1347357}
- { name: Torrens Street,stop_code: Wjz5Pwn, lat: -35.2709457, lng: 149.1344196} - { name: Torrens Street,stop_code: Wjz5Pwn, lat: -35.2709457, lng: 149.1344196}
- { name: Fawkner Street,stop_code: Wjz5OLh, lat: -35.2721844, lng: 149.135684} - { name: Fawkner Street,stop_code: Wjz5OLh, lat: -35.2721844, lng: 149.135684}
- { name: Doonkuna Street,stop_code: Wjz5OOo, lat: -35.2757106, lng: 149.1372297} - { name: Doonkuna Street,stop_code: Wjz5OOo, lat: -35.2757106, lng: 149.1372297}
- { name: Ainslie Avenue,stop_code: Wjz5NHD, lat: -35.2798744, lng: 149.1361266} - { name: Ainslie Avenue,stop_code: Wjz5NHD, lat: -35.2798744, lng: 149.1361266}
- { name: Limestone Avenue,stop_code: Wjz5VFA, lat: -35.2815441, lng: 149.146984} - { name: Limestone Avenue,stop_code: Wjz5VFA, lat: -35.2815441, lng: 149.146984}
- { name: Fairbairn Avenue,stop_code: Wjzd0CK, lat: -35.283446, lng: 149.156771} - { name: Fairbairn Avenue,stop_code: Wjzd0CK, lat: -35.283446, lng: 149.156771}
- { name: White Crescent,stop_code: Wjzc7nq, lat: -35.2885152, lng: 149.1537353} - { name: White Crescent,stop_code: Wjzc7nq, lat: -35.2885152, lng: 149.1537353}
- { name: Anzac Parade,stop_code: Wjz5Urj, lat: -35.285706, lng: 149.144029} - { name: Anzac Parade,stop_code: Wjz5Urj, lat: -35.285706, lng: 149.144029}
- { name: Holmes Crescent,stop_code: Wjzc7Ay, lat: -35.2905765, lng: 149.1566757} - { name: Holmes Crescent,stop_code: Wjzc7Ay, lat: -35.2905765, lng: 149.1566757}
- { name: Borella Street,stop_code: Wjz4_Oj, lat: -35.2918933, lng: 149.1481428} - { name: Borella Street,stop_code: Wjz4_Oj, lat: -35.2918933, lng: 149.1481428}
- { name: Parkes Way,stop_code: Wjz4T-X, lat: -35.2891325, lng: 149.1393476} - { name: Parkes Way,stop_code: Wjz4T-X, lat: -35.2891325, lng: 149.1393476}
- { name: Miles Road,stop_code: WjzceHt, lat: -35.2965216, lng: 149.168833} - { name: Miles Road,stop_code: WjzceHt, lat: -35.2965216, lng: 149.168833}
- { name: Vowels Road,stop_code: Wjzcdsn, lat: -35.3011446, lng: 149.1659502} - { name: Vowels Road,stop_code: Wjzcdsn, lat: -35.3011446, lng: 149.1659502}
- { name: Morshead Drive,stop_code: Wjzcd2U, lat: -35.3031671, lng: 149.1626628} - { name: Morshead Drive,stop_code: Wjzcd2U, lat: -35.3031671, lng: 149.1626628}
- { name: Eyre Street,stop_code: Wjz4WnH, lat: -35.3159201, lng: 149.1430396} - { name: Eyre Street,stop_code: Wjz4WnH, lat: -35.3159201, lng: 149.1430396}
- { name: Canberra Avenue,stop_code: Wjz4Ofi, lat: -35.3160439, lng: 149.1301934} - { name: Canberra Avenue,stop_code: Wjz4Ofi, lat: -35.3160439, lng: 149.1301934}
- { name: Flinders Way,stop_code: Wjz4EG2, lat: -35.3304213, lng: 149.1244262} - { name: Flinders Way,stop_code: Wjz4EG2, lat: -35.3304213, lng: 149.1244262}
- { name: Scarborough Street,stop_code: Wjz3KLn, lat: -35.3376003, lng: 149.1247297} - { name: Scarborough Street,stop_code: Wjz3KLn, lat: -35.3376003, lng: 149.1247297}
- { name: Captain Cook Crescent,stop_code: Wjz4NWF, lat: -35.3250038, lng: 149.138898} - { name: Captain Cook Crescent,stop_code: Wjz4NWF, lat: -35.3250038, lng: 149.138898}
- { name: Carnegie Cresent,stop_code: Wjz3_sf, lat: -35.3341586, lng: 149.1437982} - { name: Carnegie Cresent,stop_code: Wjz3_sf, lat: -35.3341586, lng: 149.1437982}
- { name: McKinlay Street,stop_code: Wjz4UIv, lat: -35.328635, lng: 149.1467867} - { name: McKinlay Street,stop_code: Wjz4UIv, lat: -35.328635, lng: 149.1467867}
- { name: Yamba Place,stop_code: Wjz4UYU, lat: -35.3292631, lng: 149.1503427} - { name: Yamba Place,stop_code: Wjz4UYU, lat: -35.3292631, lng: 149.1503427}
- { name: Mugga Way,stop_code: Wjz3KB0, lat: -35.3395291, lng: 149.1229469} - { name: Mugga Way,stop_code: Wjz3KB0, lat: -35.3395291, lng: 149.1229469}
- { name: Mugga Way,stop_code: Wjz3JQO, lat: -35.3455626, lng: 149.1268033} - { name: Mugga Way,stop_code: Wjz3JQO, lat: -35.3455626, lng: 149.1268033}
- { name: La Perouse Street,stop_code: Wjz3Slx, lat: -35.3394651, lng: 149.131936} - { name: La Perouse Street,stop_code: Wjz3Slx, lat: -35.3394651, lng: 149.131936}
- { name: Caley Crescent,stop_code: Wjz3TJe, lat: -35.3335378, lng: 149.135468} - { name: Caley Crescent,stop_code: Wjz3TJe, lat: -35.3335378, lng: 149.135468}
- { name: Goyder Street,stop_code: Wjzb79X, lat: -35.3365565, lng: 149.1529783} - { name: Goyder Street,stop_code: Wjzb79X, lat: -35.3365565, lng: 149.1529783}
- { name: Sir Harold Raggatt Drive,stop_code: Wjzb6EM, lat: -35.342941, lng: 149.1583643} - { name: Sir Harold Raggatt Drive,stop_code: Wjzb6EM, lat: -35.342941, lng: 149.1583643}
- { name: Toolambi Street,stop_code: Wjzb7Cp, lat: -35.333286, lng: 149.156475} - { name: Toolambi Street,stop_code: Wjzb7Cp, lat: -35.333286, lng: 149.156475}
- { name: Narrabundah Lane,stop_code: Wjz3YW3, lat: -35.3523419, lng: 149.1490844} - { name: Narrabundah Lane,stop_code: Wjz3YW3, lat: -35.3523419, lng: 149.1490844}
- { name: Newcastle Street,stop_code: Wjzc9PB, lat: -35.3239975, lng: 149.1704393} - { name: Newcastle Street,stop_code: Wjzc9PB, lat: -35.3239975, lng: 149.1704393}
- { name: Tennant Street,stop_code: Wjzcp0F, lat: -35.3263698, lng: 149.1843675} - { name: Tennant Street,stop_code: Wjzcp0F, lat: -35.3263698, lng: 149.1843675}
- { name: Tennant Street,stop_code: Wjzcg-_, lat: -35.3272591, lng: 149.1832438} - { name: Tennant Street,stop_code: Wjzcg-_, lat: -35.3272591, lng: 149.1832438}
- { name: Albany Street,stop_code: WjzcgSm, lat: -35.3273624, lng: 149.1809901} - { name: Albany Street,stop_code: WjzcgSm, lat: -35.3273624, lng: 149.1809901}
- { name: Yamba Drive,stop_code: Wjz3rML, lat: -35.3588381, lng: 149.1045644} - { name: Yamba Drive,stop_code: Wjz3rML, lat: -35.3588381, lng: 149.1045644}
- { name: Ellwood Crescent,stop_code: Wjz3y9z, lat: -35.3640453, lng: 149.1086104} - { name: Ellwood Crescent,stop_code: Wjz3y9z, lat: -35.3640453, lng: 149.1086104}
- { name: Julia Flynn Avenue,stop_code: Wjz3xi3, lat: -35.3688397, lng: 149.1093058} - { name: Julia Flynn Avenue,stop_code: Wjz3xi3, lat: -35.3688397, lng: 149.1093058}
- { name: Yamba Drive,stop_code: Wjz2DK6, lat: -35.3767783, lng: 149.1134151} - { name: Yamba Drive,stop_code: Wjz2DK6, lat: -35.3767783, lng: 149.1134151}
- { name: McAlpine Place,stop_code: Wjz2Dgb, lat: -35.381175, lng: 149.10938} - { name: McAlpine Place,stop_code: Wjz2Dgb, lat: -35.381175, lng: 149.10938}
- { name: Pye Place,stop_code: Wjz2vzR, lat: -35.3789646, lng: 149.1019944} - { name: Pye Place,stop_code: Wjz2vzR, lat: -35.3789646, lng: 149.1019944}
- { name: Custance Street,stop_code: Wjz3ops, lat: -35.3749061, lng: 149.1001427} - { name: Custance Street,stop_code: Wjz3ops, lat: -35.3749061, lng: 149.1001427}
- { name: Athllon Drive,stop_code: Wjz3hUs, lat: -35.370077, lng: 149.0946389} - { name: Athllon Drive,stop_code: Wjz3hUs, lat: -35.370077, lng: 149.0946389}
- { name: Ward Place,stop_code: Wjz3gUQ, lat: -35.3755566, lng: 149.0951557} - { name: Ward Place,stop_code: Wjz3gUQ, lat: -35.3755566, lng: 149.0951557}
- { name: Goode Street,stop_code: Wjz2f_R, lat: -35.3761632, lng: 149.0842481} - { name: Goode Street,stop_code: Wjz2f_R, lat: -35.3761632, lng: 149.0842481}
- { name: Hawker Street,stop_code: Wjz3g7D, lat: -35.3705636, lng: 149.085208} - { name: Hawker Street,stop_code: Wjz3g7D, lat: -35.3705636, lng: 149.085208}
- { name: Hyland Place,stop_code: Wjz2c-r, lat: -35.3935292, lng: 149.0837652} - { name: Hyland Place,stop_code: Wjz2c-r, lat: -35.3935292, lng: 149.0837652}
- { name: Beaver Place,stop_code: Wjz2civ, lat: -35.3959622, lng: 149.0767882} - { name: Beaver Place,stop_code: Wjz2civ, lat: -35.3959622, lng: 149.0767882}
- { name: Athllon Drive,stop_code: Wjz2mGO, lat: -35.3853996, lng: 149.0925014} - { name: Athllon Drive,stop_code: Wjz2mGO, lat: -35.3853996, lng: 149.0925014}
- { name: Sulwood Drive,stop_code: Wjz2ttB, lat: -35.3885662, lng: 149.1004148} - { name: Sulwood Drive,stop_code: Wjz2ttB, lat: -35.3885662, lng: 149.1004148}
- { name: Sternberg Crescent,stop_code: Wjz2rN0, lat: -35.4027536, lng: 149.1038057} - { name: Sternberg Crescent,stop_code: Wjz2rN0, lat: -35.4027536, lng: 149.1038057}
- { name: Sternberg Crescent,stop_code: Wjz2jPU, lat: -35.401368, lng: 149.0939538} - { name: Sternberg Crescent,stop_code: Wjz2jPU, lat: -35.401368, lng: 149.0939538}
- { name: Harricks Crescent,stop_code: Wjz2iwA, lat: -35.4085873, lng: 149.0906768} - { name: Harricks Crescent,stop_code: Wjz2iwA, lat: -35.4085873, lng: 149.0906768}
- { name: Leach Street,stop_code: Wjz2pmy, lat: -35.4100705, lng: 149.0990011} - { name: Leach Street,stop_code: Wjz2pmy, lat: -35.4100705, lng: 149.0990011}
- { name: Burston Place,stop_code: Wjz2xq1, lat: -35.4129044, lng: 149.1106334} - { name: Burston Place,stop_code: Wjz2xq1, lat: -35.4129044, lng: 149.1106334}
- { name: Garrick Street,stop_code: Wjz2yQZ, lat: -35.4057423, lng: 149.116007} - { name: Garrick Street,stop_code: Wjz2yQZ, lat: -35.4057423, lng: 149.116007}
- { name: Larcombe Crescent,stop_code: Wjz2G9R, lat: -35.4077654, lng: 149.1199409} - { name: Larcombe Crescent,stop_code: Wjz2G9R, lat: -35.4077654, lng: 149.1199409}
- { name: Halley Street,stop_code: Wjz2N0r, lat: -35.4141264, lng: 149.128949} - { name: Halley Street,stop_code: Wjz2N0r, lat: -35.4141264, lng: 149.128949}
- { name: Proctor Street,stop_code: Wjz2EB6, lat: -35.4159442, lng: 149.1230876} - { name: Proctor Street,stop_code: Wjz2EB6, lat: -35.4159442, lng: 149.1230876}
- { name: Webber Crescent,stop_code: Wjz1BFG, lat: -35.4354872, lng: 149.1142337} - { name: Webber Crescent,stop_code: Wjz1BFG, lat: -35.4354872, lng: 149.1142337}
- { name: Tweddle Place,stop_code: Wjz1CS7, lat: -35.4261448, lng: 149.1147427} - { name: Tweddle Place,stop_code: Wjz1CS7, lat: -35.4261448, lng: 149.1147427}
- { name: Wentcher Place,stop_code: Wjz1Dap, lat: -35.4239297, lng: 149.1084839} - { name: Wentcher Place,stop_code: Wjz1Dap, lat: -35.4239297, lng: 149.1084839}
- { name: Laker Crescent,stop_code: Wjz1Dlj, lat: -35.4217144, lng: 149.1096219} - { name: Laker Crescent,stop_code: Wjz1Dlj, lat: -35.4217144, lng: 149.1096219}
- { name: Kiddle Crescent,stop_code: Wjz1C75, lat: -35.4256297, lng: 149.1065242} - { name: Kiddle Crescent,stop_code: Wjz1C75, lat: -35.4256297, lng: 149.1065242}
- { name: Clift Crescent,stop_code: Wjz1vMs, lat: -35.4250115, lng: 149.1042483} - { name: Clift Crescent,stop_code: Wjz1vMs, lat: -35.4250115, lng: 149.1042483}
- { name: Ashley Drive,stop_code: Wjz1vJN, lat: -35.4218175, lng: 149.1034264} - { name: Ashley Drive,stop_code: Wjz1vJN, lat: -35.4218175, lng: 149.1034264}
- { name: Isabella Drive,stop_code: Wjz2w0e, lat: -35.4193446, lng: 149.106777} - { name: Isabella Drive,stop_code: Wjz2w0e, lat: -35.4193446, lng: 149.106777}
- { name: Barraclough Crescent,stop_code: Wjz2osQ, lat: -35.4167685, lng: 149.1006448} - { name: Barraclough Crescent,stop_code: Wjz2osQ, lat: -35.4167685, lng: 149.1006448}
- { name: Kneeshaw Street,stop_code: Wjz2o8V, lat: -35.4197567, lng: 149.0980528} - { name: Kneeshaw Street,stop_code: Wjz2o8V, lat: -35.4197567, lng: 149.0980528}
- { name: Isabella Drive,stop_code: Wjz1v6h, lat: -35.4211477, lng: 149.0958401} - { name: Isabella Drive,stop_code: Wjz1v6h, lat: -35.4211477, lng: 149.0958401}
- { name: Kerkeri Close,stop_code: Wjz1v2R, lat: -35.423569, lng: 149.0965355} - { name: Kerkeri Close,stop_code: Wjz1v2R, lat: -35.423569, lng: 149.0965355}
- { name: Oakwood Place,stop_code: Wjz1viP, lat: -35.4237236, lng: 149.0993804} - { name: Oakwood Place,stop_code: Wjz1viP, lat: -35.4237236, lng: 149.0993804}
- { name: Johnson Drive,stop_code: Wjz1BrK, lat: -35.4337687, lng: 149.1114553} - { name: Johnson Drive,stop_code: Wjz1BrK, lat: -35.4337687, lng: 149.1114553}
- { name: Costello Circuit,stop_code: Wjz1B9T, lat: -35.4350564, lng: 149.1089897} - { name: Costello Circuit,stop_code: Wjz1B9T, lat: -35.4350564, lng: 149.1089897}
- { name: Johnson Drive,stop_code: Wjz1tYG, lat: -35.4334596, lng: 149.1060816} - { name: Johnson Drive,stop_code: Wjz1tYG, lat: -35.4334596, lng: 149.1060816}
- { name: Johnson Drive,stop_code: Wjz1tR7, lat: -35.4323264, lng: 149.1038057} - { name: Johnson Drive,stop_code: Wjz1tR7, lat: -35.4323264, lng: 149.1038057}
- { name: Carter Crescent,stop_code: Wjz1tE0, lat: -35.4363442, lng: 149.1024781} - { name: Carter Crescent,stop_code: Wjz1tE0, lat: -35.4363442, lng: 149.1024781}
- { name: Outtrim Avenue,stop_code: Wjz1tok, lat: -35.4359836, lng: 149.0999494} - { name: Outtrim Avenue,stop_code: Wjz1tok, lat: -35.4359836, lng: 149.0999494}
- { name: Johnson Drive,stop_code: Wjz1tbe, lat: -35.4337687, lng: 149.0971677} - { name: Johnson Drive,stop_code: Wjz1tbe, lat: -35.4337687, lng: 149.0971677}
- { name: Marengo Place,stop_code: Wjz1lQS, lat: -35.4330991, lng: 149.0938171} - { name: Marengo Place,stop_code: Wjz1lQS, lat: -35.4330991, lng: 149.0938171}
- { name: Heddon Place,stop_code: Wjz1lyA, lat: -35.4346444, lng: 149.0907826} - { name: Heddon Place,stop_code: Wjz1lyA, lat: -35.4346444, lng: 149.0907826}
- { name: Pimpampa Close,stop_code: Wjz1lB8, lat: -35.4329445, lng: 149.0902136} - { name: Pimpampa Close,stop_code: Wjz1lB8, lat: -35.4329445, lng: 149.0902136}
- { name: Abercrombie Circuit,stop_code: Wjz0nS3, lat: -35.4649778, lng: 149.0928056} - { name: Abercrombie Circuit,stop_code: Wjz0nS3, lat: -35.4649778, lng: 149.0928056}
- { name: Youl Court,stop_code: Wjz0uuZ, lat: -35.4702296, lng: 149.1008976} - { name: Youl Court,stop_code: Wjz0uuZ, lat: -35.4702296, lng: 149.1008976}
- { name: Milligan Street,stop_code: Wjz0CcV, lat: -35.4719802, lng: 149.1091794} - { name: Milligan Street,stop_code: Wjz0CcV, lat: -35.4719802, lng: 149.1091794}
- { name: Galbraith Close,stop_code: Wjz0B6Y, lat: -35.4758415, lng: 149.1077253} - { name: Galbraith Close,stop_code: Wjz0B6Y, lat: -35.4758415, lng: 149.1077253}
- { name: Olive Pink Crescent,stop_code: Wjz0tB4, lat: -35.4765623, lng: 149.1010241} - { name: Olive Pink Crescent,stop_code: Wjz0tB4, lat: -35.4765623, lng: 149.1010241}
- { name: Bellchambers Crescent,stop_code: Wjz0mMT, lat: -35.474194, lng: 149.0937539} - { name: Bellchambers Crescent,stop_code: Wjz0mMT, lat: -35.474194, lng: 149.0937539}
- { name: Olive Pink Crescent,stop_code: Wjz0kYJ, lat: -35.482637, lng: 149.0950815} - { name: Olive Pink Crescent,stop_code: Wjz0kYJ, lat: -35.482637, lng: 149.0950815}
- { name: Tharwa Drive,stop_code: Wjz0lhu, lat: -35.4790849, lng: 149.0878745} - { name: Tharwa Drive,stop_code: Wjz0lhu, lat: -35.4790849, lng: 149.0878745}
- { name: Robert Lewis Court,stop_code: Wjz0eRx, lat: -35.4713109, lng: 149.0824376} - { name: Robert Lewis Court,stop_code: Wjz0eRx, lat: -35.4713109, lng: 149.0824376}
- { name: Ferry Place,stop_code: Wjz1gaC, lat: -35.4619398, lng: 149.0865469} - { name: Ferry Place,stop_code: Wjz1gaC, lat: -35.4619398, lng: 149.0865469}
- { name: Charles Place,stop_code: Wjz19V7, lat: -35.4570479, lng: 149.0831962} - { name: Charles Place,stop_code: Wjz19V7, lat: -35.4570479, lng: 149.0831962}
- { name: Gaylard Place,stop_code: Wjz1i2p, lat: -35.4513833, lng: 149.0850928} - { name: Gaylard Place,stop_code: Wjz1i2p, lat: -35.4513833, lng: 149.0850928}
- { name: Clare Dennis Avenue,stop_code: Wjz1jf0, lat: -35.442525, lng: 149.0859147} - { name: Clare Dennis Avenue,stop_code: Wjz1jf0, lat: -35.442525, lng: 149.0859147}
- { name: Northbourne Avenue,stop_code: Wjz5RvC, lat: -35.2552151, lng: 149.1332875} - { name: Northbourne Avenue,stop_code: Wjz5RvC, lat: -35.2552151, lng: 149.1332875}
- { name: Northbourne Avenue,stop_code: Wjz5Oci, lat: -35.2741724, lng: 149.1302168} - { name: Northbourne Avenue,stop_code: Wjz5Oci, lat: -35.2741724, lng: 149.1302168}
- { name: Northbourne Avenue,stop_code: Wjz5N4m, lat: -35.279266, lng: 149.1287817} - { name: Northbourne Avenue,stop_code: Wjz5N4m, lat: -35.279266, lng: 149.1287817}
- { name: Northbourne Avenue,stop_code: Wjz5PdJ, lat: -35.2676612, lng: 149.1306865} - { name: Northbourne Avenue,stop_code: Wjz5PdJ, lat: -35.2676612, lng: 149.1306865}
- { name: Northbourne Avenue,stop_code: Wjz5Qmu, lat: -35.2613932, lng: 149.1316889} - { name: Northbourne Avenue,stop_code: Wjz5Qmu, lat: -35.2613932, lng: 149.1316889}
- { name: Northbourne Avenue,stop_code: Wjz5Sux, lat: -35.2509191, lng: 149.1333899} - { name: Northbourne Avenue,stop_code: Wjz5Sux, lat: -35.2509191, lng: 149.1333899}
- { name: Cameron Avenue,stop_code: Wjz60QI, lat: -35.2410106, lng: 149.0717141} - { name: Cameron Avenue,stop_code: Wjz60QI, lat: -35.2410106, lng: 149.0717141}
- { name: Cameron Avenue,stop_code: Wjz60Y4, lat: -35.2410195, lng: 149.0722506} - { name: Cameron Avenue,stop_code: Wjz60Y4, lat: -35.2410195, lng: 149.0722506}
- { name: Cameron Avenue,stop_code: Wjz60QW, lat: -35.241186, lng: 149.0720789} - { name: Cameron Avenue,stop_code: Wjz60QW, lat: -35.241186, lng: 149.0720789}
- { name: Cameron Avenue,stop_code: Wjz60Qa, lat: -35.2411772, lng: 149.0709792} - { name: Cameron Avenue,stop_code: Wjz60Qa, lat: -35.2411772, lng: 149.0709792}
- { name: Cameron Avenue,stop_code: Wjz60Qc, lat: -35.2410063, lng: 149.0710758} - { name: Cameron Avenue,stop_code: Wjz60Qc, lat: -35.2410063, lng: 149.0710758}
- { name: Wilari Place,stop_code: Wjz6u3h, lat: -35.2089622, lng: 149.095889} - { name: Wilari Place,stop_code: Wjz6u3h, lat: -35.2089622, lng: 149.095889}
- { name: Mirrabucca Crescent,stop_code: Wjz6u32, lat: -35.2088899, lng: 149.09552} - { name: Mirrabucca Crescent,stop_code: Wjz6u32, lat: -35.2088899, lng: 149.09552}
- { name: Buriga Street,stop_code: Wjz6mOx, lat: -35.20966, lng: 149.0935299} - { name: Buriga Street,stop_code: Wjz6mOx, lat: -35.20966, lng: 149.0935299}
- { name: Georgina Crescent,stop_code: Wjz6sHv, lat: -35.21947, lng: 149.10295} - { name: Georgina Crescent,stop_code: Wjz6sHv, lat: -35.21947, lng: 149.10295}
- { name: Staaten Crescent,stop_code: Wjz6sZ1, lat: -35.21859, lng: 149.10511} - { name: Staaten Crescent,stop_code: Wjz6sZ1, lat: -35.21859, lng: 149.10511}
- { name: Chuculba Crescent,stop_code: Wjz6uhX, lat: -35.2101981, lng: 149.0994957} - { name: Chuculba Crescent,stop_code: Wjz6uhX, lat: -35.2101981, lng: 149.0994957}
- { name: Antares Crescent,stop_code: Wjz6uwF, lat: -35.2110747, lng: 149.1018989} - { name: Antares Crescent,stop_code: Wjz6uwF, lat: -35.2110747, lng: 149.1018989}
- { name: Snodgrass Crescent,stop_code: WjrWYHH, lat: -35.3956133, lng: 149.0592665} - { name: Snodgrass Crescent,stop_code: WjrWYHH, lat: -35.3956133, lng: 149.0592665}
- { name: O'Halloran Circuit,stop_code: WjrWYDE, lat: -35.3931009, lng: 149.0580053} - { name: O'Halloran Circuit,stop_code: WjrWYDE, lat: -35.3931009, lng: 149.0580053}
- { name: Snodgrass Crescent,stop_code: WjrWYHE, lat: -35.3958129, lng: 149.0592983} - { name: Snodgrass Crescent,stop_code: WjrWYHE, lat: -35.3958129, lng: 149.0592983}
- { name: Chuculba Crescent,stop_code: Wjz6sdP, lat: -35.21844, lng: 149.0979199} - { name: Chuculba Crescent,stop_code: Wjz6sdP, lat: -35.21844, lng: 149.0979199}
- { name: Canopus Crescent,stop_code: Wjz6t3F, lat: -35.21451, lng: 149.09646} - { name: Canopus Crescent,stop_code: Wjz6t3F, lat: -35.21451, lng: 149.09646}
- { name: Purdie Street,stop_code: Wjz5nwb, lat: -35.2493711, lng: 149.0901523} - { name: Purdie Street,stop_code: Wjz5nwb, lat: -35.2493711, lng: 149.0901523}
- { name: Haydon Drive,stop_code: Wjz5mbS, lat: -35.2525252, lng: 149.0869819} - { name: Haydon Drive,stop_code: Wjz5mbS, lat: -35.2525252, lng: 149.0869819}
- { name: Bindubi Street,stop_code: Wjz5e0m, lat: -35.2546115, lng: 149.0739747} - { name: Bindubi Street,stop_code: Wjz5e0m, lat: -35.2546115, lng: 149.0739747}
- { name: Morphy Place,stop_code: Wjz55V-, lat: -35.2594169, lng: 149.0733684} - { name: Morphy Place,stop_code: Wjz55V-, lat: -35.2594169, lng: 149.0733684}
- { name: Gwydir Square,stop_code: Wjz6pLk, lat: -35.2334807, lng: 149.1028323} - { name: Gwydir Square,stop_code: Wjz6pLk, lat: -35.2334807, lng: 149.1028323}
- { name: William Slim Drive,stop_code: Wjz6mip, lat: -35.2096535, lng: 149.0878294} - { name: William Slim Drive,stop_code: Wjz6mip, lat: -35.2096535, lng: 149.0878294}
- { name: Ellenborough Street,stop_code: Wjz6yzH, lat: -35.2308034, lng: 149.1129136} - { name: Ellenborough Street,stop_code: Wjz6yzH, lat: -35.2308034, lng: 149.1129136}
- { name: Moruya Circuit,stop_code: Wjz6Apy, lat: -35.2213073, lng: 149.1113204} - { name: Moruya Circuit,stop_code: Wjz6Apy, lat: -35.2213073, lng: 149.1113204}
- { name: Aikman Drive,stop_code: Wjz69vO, lat: -35.2336108, lng: 149.0786617} - { name: Aikman Drive,stop_code: Wjz69vO, lat: -35.2336108, lng: 149.0786617}
- { name: Baldwin Drive,stop_code: Wjz6iN7, lat: -35.2318153, lng: 149.0928498} - { name: Baldwin Drive,stop_code: Wjz6iN7, lat: -35.2318153, lng: 149.0928498}
- { name: Baldwin Drive,stop_code: Wjz6iNm, lat: -35.2318811, lng: 149.0930643} - { name: Baldwin Drive,stop_code: Wjz6iNm, lat: -35.2318811, lng: 149.0930643}
- { name: Anketell Street,stop_code: Wjz213q, lat: -35.4121336, lng: 149.063177} - { name: Anketell Street,stop_code: Wjz213q, lat: -35.4121336, lng: 149.063177}
- { name: Athllon Drive,stop_code: Wjz3gK-, lat: -35.3712753, lng: 149.0926679} - { name: Athllon Drive,stop_code: Wjz3gK-, lat: -35.3712753, lng: 149.0926679}
- { name: Athllon Drive,stop_code: Wjz3kAx, lat: -35.3511369, lng: 149.0906806} - { name: Athllon Drive,stop_code: Wjz3kAx, lat: -35.3511369, lng: 149.0906806}
- { name: Athllon Drive,stop_code: Wjz3iFK, lat: -35.3637163, lng: 149.0922629} - { name: Athllon Drive,stop_code: Wjz3iFK, lat: -35.3637163, lng: 149.0922629}
- { name: Maribyrnong Avenue,stop_code: Wjz6qe4, lat: -35.2286658, lng: 149.0969557} - { name: Maribyrnong Avenue,stop_code: Wjz6qe4, lat: -35.2286658, lng: 149.0969557}
- { name: Ashburton Circuit,stop_code: Wjz6zAP, lat: -35.2246234, lng: 149.113116} - { name: Ashburton Circuit,stop_code: Wjz6zAP, lat: -35.2246234, lng: 149.113116}
- { name: Daley Road,stop_code: Wjz5yYV, lat: -35.2742188, lng: 149.1173067} - { name: Daley Road,stop_code: Wjz5yYV, lat: -35.2742188, lng: 149.1173067}
- { name: Bradley Street,stop_code: Wjz3ldS, lat: -35.3445222, lng: 149.0870435} - { name: Bradley Street,stop_code: Wjz3ldS, lat: -35.3445222, lng: 149.0870435}
- { name: Bradley Street,stop_code: Wjz3ldT, lat: -35.3444271, lng: 149.0869631} - { name: Bradley Street,stop_code: Wjz3ldT, lat: -35.3444271, lng: 149.0869631}
- { name: Bradley Street,stop_code: Wjz3ldJ, lat: -35.344566, lng: 149.086774} - { name: Bradley Street,stop_code: Wjz3ldJ, lat: -35.344566, lng: 149.086774}
- { name: Callam Street,stop_code: Wjz3llf, lat: -35.34445, lng: 149.0875371} - { name: Callam Street,stop_code: Wjz3llf, lat: -35.34445, lng: 149.0875371}
- { name: Athllon Drive,stop_code: Wjz3kyX, lat: -35.3523555, lng: 149.0913002} - { name: Athllon Drive,stop_code: Wjz3kyX, lat: -35.3523555, lng: 149.0913002}
- { name: Pitman,stop_code: Wjz20nf, lat: -35.4144924, lng: 149.0655423} - { name: Pitman,stop_code: Wjz20nf, lat: -35.4144924, lng: 149.0655423}
- { name: Eileen Good Street,stop_code: Wjz218U, lat: -35.4143897, lng: 149.0652364} - { name: Eileen Good Street,stop_code: Wjz218U, lat: -35.4143897, lng: 149.0652364}
- { name: Cohen Street,stop_code: Wjr-USo, lat: -35.2400027, lng: 149.0603149} - { name: Cohen Street,stop_code: Wjr-USo, lat: -35.2400027, lng: 149.0603149}
- { name: Spinifex Street,stop_code: Wjzc24u, lat: -35.317722, lng: 149.1510115} - { name: Spinifex Street,stop_code: Wjzc24u, lat: -35.317722, lng: 149.1510115}
- { name: The Causeway,stop_code: Wjz4WZo, lat: -35.3175809, lng: 149.1496027} - { name: The Causeway,stop_code: Wjz4WZo, lat: -35.3175809, lng: 149.1496027}
- { name: Parbery Street,stop_code: Wjz4WY7, lat: -35.3176372, lng: 149.1491419} - { name: Parbery Street,stop_code: Wjz4WY7, lat: -35.3176372, lng: 149.1491419}
- { name: Perry Drive,stop_code: WjrXPbu, lat: -35.3568919, lng: 149.0424224} - { name: Perry Drive,stop_code: WjrXPbu, lat: -35.3568919, lng: 149.0424224}
- { name: Darwinia Terrace,stop_code: WjrXI5s, lat: -35.3501807, lng: 149.0301549} - { name: Darwinia Terrace,stop_code: WjrXI5s, lat: -35.3501807, lng: 149.0301549}
- { name: Darwinia Terrace,stop_code: WjrXIqk, lat: -35.3522608, lng: 149.0341457} - { name: Darwinia Terrace,stop_code: WjrXIqk, lat: -35.3522608, lng: 149.0341457}
- { name: Perry Drive,stop_code: WjrXOn_, lat: -35.359526, lng: 149.0445552} - { name: Perry Drive,stop_code: WjrXOn_, lat: -35.359526, lng: 149.0445552}
- { name: Streeton Drive,stop_code: WjrXPR4, lat: -35.3556673, lng: 149.048857} - { name: Streeton Drive,stop_code: WjrXPR4, lat: -35.3556673, lng: 149.048857}
- { name: Fremantle Drive,stop_code: WjrXQOh, lat: -35.3524926, lng: 149.049231} - { name: Fremantle Drive,stop_code: WjrXQOh, lat: -35.3524926, lng: 149.049231}
- { name: Bunbury Street,stop_code: WjrXRMq, lat: -35.3483271, lng: 149.0492963} - { name: Bunbury Street,stop_code: WjrXRMq, lat: -35.3483271, lng: 149.0492963}
- { name: Fremantle Drive,stop_code: WjrXRzE, lat: -35.3464066, lng: 149.0469632} - { name: Fremantle Drive,stop_code: WjrXRzE, lat: -35.3464066, lng: 149.0469632}
- { name: Parkinson Street,stop_code: WjrX-0-, lat: -35.3424839, lng: 149.052828} - { name: Parkinson Street,stop_code: WjrX-0-, lat: -35.3424839, lng: 149.052828}
- { name: Backler Place,stop_code: WjrXZv5, lat: -35.3432647, lng: 149.0558034} - { name: Backler Place,stop_code: WjrXZv5, lat: -35.3432647, lng: 149.0558034}
- { name: Gilmore Crescent,stop_code: Wjz3BfO, lat: -35.3434784, lng: 149.1088951} - { name: Gilmore Crescent,stop_code: Wjz3BfO, lat: -35.3434784, lng: 149.1088951}
routes: routes:
- -
time_points: [Tuggeranong Bus Station (Platform 7), Bonython Primary School, Lanyon Market Place, Conder Primary, Tharwa Drive / Knoke Ave, Gordon Primary, Woodcock / Clare Dennis, Bonython Primary School, Tuggeranong Bus Station] time_points: [Tuggeranong Bus Station (Platform 7), Chisholm, Brindabella Business Park, Fairbairn Park]
long_name: To Tuggeranong Bus Station long_name: To Fairbairn Park
between_stops: {} between_stops:
  Brindabella Business Park-Fairbairn Park: [WjzcrK3, WjzcrrQ, WjzcrEu, WjzcJ0K, WjzcBHZ, WjzcJ38]
short_name: "914" short_name: "786"
stop_times: [[1025a, 1034a, 1040a, 1047a, 1050a, 1054a, 1059a, 1102a, 1112a], [1225p, 1234p, 1240p, 1247p, 1250p, 1254p, 1259p, 102p, 112p], [225p, 234p, 240p, 247p, 250p, 254p, 259p, 302p, 312p], [425p, 434p, 440p, 447p, 450p, 454p, 459p, 502p, 512p], [625p, 634p, 640p, 647p, 650p, 654p, 659p, 702p, 712p]] stop_times: [[646a, 656a, 716a, 726a], [706a, 716a, 736a, 746a], [727a, 737a, 804a, 814a]]
- -
time_points: [Woden Bus Station (Platform 4), Belconnen Community Bus Station, Westfield Bus Station, Cohen Street Bus Station] time_points: [City Bus Station (Platform 8), Macarthur / Northbourne Ave, Northbourne Avenue / Antill St, Flemington Rd / Sandford St, Hoskins Street / Oodgeroo Ave, Manning Clarke / Oodgeroo, Gungahlin Marketplace]
  long_name: To Gungahlin Marketplace
  between_stops:
  Macarthur / Northbourne Ave-Northbourne Avenue / Antill St: [Wjz5RkN, Wjz5Rsi, Wjz5RvC, Wjz5Sqk, Wjz5SrO, Wjz5Sux, Wjz5SDc]
  City Bus Station (Platform 8)-Macarthur / Northbourne Ave: [Wjz5O3Q, Wjz5Oci, Wjz5P8n, Wjz5P8K, Wjz5PdJ, Wjz5Pl0, Wjz5Qi2, Wjz5Qgn, Wjz5Qmu, Wjz5QmR]
  short_name: "57"
  stop_times: [[655a, 701a, 703a, 709a, 717a, 720a, 724a], [725a, 731a, 733a, 739a, 747a, 750a, 754a], [755a, 802a, 804a, 810a, 818a, 821a, 825a], [825a, 832a, 834a, 840a, 848a, 851a, 855a], [855a, 902a, 904a, 910a, 918a, 921a, 925a], [957a, 1003a, 1005a, 1011a, 1019a, 1022a, 1026a], [1055a, 1101a, 1103a, 1109a, 1117a, 1120a, 1124a], [1155a, 1201p, 1203p, 1209p, 1217p, 1220p, 1224p], [1255p, 101p, 103p, 109p, 117p, 120p, 124p], [155p, 201p, 203p, 209p, 217p, 220p, 224p], [255p, 301p, 303p, 310p, 318p, 321p, 325p], [355p, 402p, 404p, 411p, 419p, 422p, 426p], [425p, 432p, 434p, 441p, 449p, 452p, 456p], [455p, 502p, 504p, 511p, 519p, 522p, 526p], [525p, 532p, 534p, 541p, 549p, 552p, 556p], [555p, 602p, 604p, 609p, 617p, 620p, 624p], [625p, 631p, 633p, 638p, 646p, 649p, 653p], [655p, 701p, 703p, 708p, 716p, 719p, 723p]]
  -
  time_points: [Belconnen Community Bus Station (Platform 6), Westfield Bus Station (Platform 2), Cohen Street Bus Station (Platform 5), Charnwood, Fraser East Terminus, Charnwood, Cohen Street Bus Station, Westfield Bus Station, Belconnen Community Bus Station]
  long_name: To Belconnen Community Bus Station
  between_stops:
  Belconnen Community Bus Station (Platform 6)-Westfield Bus Station (Platform 2): []
  Westfield Bus Station-Belconnen Community Bus Station: []
  Cohen Street Bus Station-Westfield Bus Station: []
  Westfield Bus Station (Platform 2)-Cohen Street Bus Station (Platform 5): []
  stop_times_saturday: [["-", "-", "-", 708a, 716a, 723a, 737a, 739a, 743a], ["-", "-", "-", 808a, 816a, 823a, 837a, 839a, 843a], [848a, 850a, 854a, 908a, 916a, 923a, 937a, 939a, 943a], [948a, 950a, 954a, 1008a, 1016a, 1023a, 1037a, 1039a, 1043a], [1048a, 1050a, 1054a, 1108a, 1116a, 1123a, 1137a, 1139a, 1143a], [1148a, 1150a, 1154a, 1208p, 1216p, 1223p, 1237p, 1239p, 1243p], [1248p, 1250p, 1254p, 108p, 116p, 123p, 137p, 139p, 143p], [148p, 150p, 154p, 208p, 216p, 223p, 237p, 239p, 243p], [248p, 250p, 254p, 308p, 316p, 323p, 337p, 339p, 343p], [348p, 350p, 354p, 408p, 416p, 423p, 437p, 439p, 443p], [448p, 450p, 454p, 508p, 516p, 523p, 537p, 539p, 543p], [548p, 550p, 554p, 608p, 616p, 623p, 637p, 639p, 643p], [647p, 649p, 653p, 706p, 714p, 721p, 734p, 736p, 740p], [747p, 749p, 753p, 806p, 814p, 821p, 834p, 836p, 840p], [847p, 849p, 853p, 906p, 914p, 921p, 934p, 936p, 940p], [947p, 949p, 953p, 1006p, 1014p, 1021p, 1034p, 1036p, 1040p], [1047p, 1049p, 1053p, 1106p, 1114p, 1121p, 1134p, 1136p, 1140p]]
  short_name: "907"
  -
  time_points: [Cooleman Court, Duffy, Holder, Weston Primary, Woden Bus Station]
  long_name: To Woden Bus Station
  between_stops: {}
   
  stop_times_saturday: [[824a, 831a, 834a, 837a, 846a], [924a, 931a, 934a, 937a, 946a], [1024a, 1031a, 1034a, 1037a, 1046a], [1124a, 1131a, 1134a, 1137a, 1146a], [1224p, 1231p, 1234p, 1237p, 1246p], [124p, 131p, 134p, 137p, 146p], [224p, 231p, 234p, 237p, 246p], [324p, 331p, 334p, 337p, 346p], [424p, 431p, 434p, 437p, 446p], [524p, 531p, 534p, 537p, 546p], [624p, 631p, 634p, 637p, 646p], [724p, 731p, 734p, 737p, 746p], [824p, 831p, 834p, 837p, 846p], [924p, 931p, 934p, 937p, 946p], [1024p, 1031p, 1034p, 1037p, 1046p]]
  short_name: "925"
  -
  time_points: [Woden Bus Station (Platform 15), Lyons, Chifley, Torrens, Southlands Mawson, Pearce, Woden Bus Station]
  long_name: To Woden Bus Station
  between_stops: {}
   
  short_name: "921"
  stop_times_sunday: [[933a, 936a, 940a, 945a, 951a, 955a, 1001a], [1133a, 1136a, 1140a, 1145a, 1151a, 1155a, 1201p], [133p, 136p, 140p, 145p, 151p, 155p, 201p], [333p, 336p, 340p, 345p, 351p, 355p, 401p], [533p, 536p, 540p, 545p, 551p, 555p, 601p]]
  -
  time_points: [Cohen Street Bus Station (Platform 1), Westfield Bus Station (Platform 1), Belconnen Community Bus Station (Platform 3), Copland College, Tillyard / Spalding, Charnwood, Kerrigan / Lhotsky, Charnwood, Tillyard / Spalding, Copland College, Belconnen Community Bus Station, Westfield Bus Station, Cohen Street Bus Station]
long_name: To Cohen Street Bus Station long_name: To Cohen Street Bus Station
between_stops: between_stops:
Westfield Bus Station-Cohen Street Bus Station: [] Westfield Bus Station-Cohen Street Bus Station: []
Belconnen Community Bus Station-Westfield Bus Station: [] Belconnen Community Bus Station-Westfield Bus Station: []
short_name: "749" Westfield Bus Station (Platform 1)-Belconnen Community Bus Station (Platform 3): []
stop_times: [[753a, 820a, 822a, 827a], [436p, 505p, 507p, 512p], [510p, 539p, 541p, 546p], [540p, 609p, 611p, 616p]] Cohen Street Bus Station (Platform 1)-Westfield Bus Station (Platform 1): []
  short_name: "45"
  stop_times: [["-", "-", "-", "-", "-", 627a, 632a, 638a, 640a, 648a, 658a, 700a, 705a], ["-", "-", "-", "-", "-", 657a, 702a, 708a, 710a, 718a, 728a, 730a, 735a], ["-", "-", "-", "-", "-", 729a, 734a, 740a, 742a, 750a, 800a, 802a, 807a], ["-", "-", "-", "-", "-", 759a, 804a, 810a, 812a, 820a, 830a, 832a, 837a], ["-", "-", "-", "-", "-", 822a, 827a, 833a, 835a, 843a, 853a, 855a, 900a], ["-", "-", "-", "-", "-", 844a, 849a, 855a, 857a, 905a, 915a, 917a, 922a], [828a, 830a, 834a, 842a, 850a, 852a, 857a, 903a, 905a, 913a, 923a, 925a, 930a], [858a, 900a, 904a, 912a, 920a, 922a, 927a, 933a, 935a, 943a, 953a, 955a, 1000a], [921a, 923a, 927a, 935a, 943a, 945a, 950a, 956a, 958a, 1006a, 1016a, 1018a, 1023a], [1021a, 1023a, 1027a, 1035a, 1043a, 1045a, 1050a, 1056a, 1058a, 1106a, 1116a, 1118a, 1123a], [1121a, 1123a, 1127a, 1135a, 1143a, 1145a, 1150a, 1156a, 1158a, 1206p, 1216p, 1218p, 1223p], [1221p, 1223p, 1227p, 1235p, 1243p, 1245p, 1250p, 1256p, 1258p, 106p, 116p, 118p, 123p], [121p, 123p, 127p, 135p, 143p, 145p, 150p, 156p, 158p, 206p, 216p, 218p, 223p], [221p, 223p, 227p, 235p, 243p, 245p, 250p, 256p, 258p, 306p, 316p, 318p, 323p], [258p, 300p, 304p, 312p, 320p, 322p, 327p, 333p, 335p, 343p, 353p, 355p, 400p], [328p, 330p, 334p, 342p, 350p, 352p, 357p, 403p, 405p, 413p, 423p, 425p, 430p], [358p, 400p, 404p, 412p, 420p, 422p, 427p, 433p, 435p, 443p, 453p, 455p, 500p], [428p, 430p, 434p, 442p, 450p, 452p, 457p, 503p, 505p, 513p, 523p, 525p, 530p], [458p, 500p, 504p, 512p, 520p, 522p, 527p, 533p, 535p, 543p, 553p, 555p, 600p], [528p, 530p, 534p, 542p, 550p, 552p, 557p, 603p, 605p, 613p, 623p, 625p, 630p], [558p, 600p, 604p, 612p, 620p, 622p, 627p, 633p, 635p, 643p, 652p, 654p, 659p], [621p, 623p, 627p, 634p, 642p, 644p, 649p, 655p, 657p, 705p, 714p, 716p, 721p], [720p, 722p, 726p, 733p, 741p, 743p, 748p, 754p, 756p, 804p, 813p, 815p, 820p], [820p, 822p, 826p, 833p, 841p, 843p, 848p, 854p, 856p, 904p, 913p, 915p, 920p], [920p, 922p, 926p, 933p, 941p, 943p, 948p, 954p, 956p, 1004p, 1013p, 1015p, 1020p], [1020p, 1022p, 1026p, 1033p, 1041p, 1043p, 1048p, 1054p, 1056p, 1104p, 1113p, 1115p, 1120p], [1120p, 1122p, 1126p, 1133p, 1141p, 1143p, 1148p, 1154p, "-", "-", "-", "-", "-"], []]
  -
  time_points: [Tuggeranong Bus Station (Platform 4), Isabella, Theodore, Calwell, Outtrim / Duggan, Tuggeranong Bus Station]
  long_name: To Tuggeranong Bus Station
  between_stops: {}
   
  short_name: "915"
  stop_times_sunday: [[915a, 925a, 934a, 943a, 946a, 955a], [1115a, 1125a, 1134a, 1143a, 1146a, 1155a], [115p, 125p, 134p, 143p, 146p, 155p], [315p, 325p, 334p, 343p, 346p, 355p], [515p, 525p, 534p, 543p, 546p, 555p], [715p, 725p, 734p, 743p, 746p, 755p]]
  -
  time_points: [Fairbairn Park, Brindabella Business Park, Russell Offices, City Bus Station]
  long_name: To City Bus Station
  between_stops:
  Fairbairn Park-Brindabella Business Park: [WjzcJ38, WjzcBHZ, WjzcJ0K, WjzcrEu, WjzcrrQ, WjzcrK3]
  Russell Offices-City Bus Station: [Wjz4-WZ, Wjz4-WL, Wjz4-Rc, Wjz4-KO, Wjz4_wS, Wjz4_jm, Wjz4T-X, Wjz5MEL, Wjz5MsT, Wjz5MsD, Wjz5Nht]
  short_name: "737"
  stop_times: [[431p, 441p, 455p, 513p], [445p, 455p, 509p, 527p], [505p, 515p, 529p, 547p], [525p, 535p, 549p, 607p], [545p, 555p, 609p, 627p]]
  -
  time_points: [Cohen Street Bus Station (Platform 1), Westfield Bus Station (Platform 2), Belconnen Community Bus Station (Platform 2), Chuculba / William Slim Dr, Gungahlin Marketplace, Kosciuszko / Everard, Flemington Rd / Sandford St, Northbourne Avenue / Antill St, Macarthur / Northbourne Ave, City Bus Station]
  long_name: To City Bus Station
  between_stops:
  Northbourne Avenue / Antill St-Macarthur / Northbourne Ave: [Wjz5SDc, Wjz5Sux, Wjz5SrO, Wjz5Sqk, Wjz5RvC, Wjz5Rsi, Wjz5RkN]
  Belconnen Community Bus Station (Platform 2)-Chuculba / William Slim Dr: [Wjz69gA, Wjz69ht, Wjz69uI, Wjz69vO, Wjz6mip]
  Cohen Street Bus Station (Platform 1)-Westfield Bus Station (Platform 2): []
  Macarthur / Northbourne Ave-City Bus Station: [Wjz5QmR, Wjz5Qmu, Wjz5Qgn, Wjz5Qi2, Wjz5Pl0, Wjz5PdJ, Wjz5P8K, Wjz5P8n, Wjz5Oci, Wjz5O3Q]
  Westfield Bus Station (Platform 2)-Belconnen Community Bus Station (Platform 2): []
  short_name: "56"
  stop_times: [[534a, 536a, 540a, 554a, 605a, 615a, 622a, 628a, 630a, 637a], [614a, 616a, 620a, 634a, 645a, 655a, 702a, 708a, 710a, 717a], [634a, 636a, 640a, 654a, 705a, 715a, 722a, 728a, 730a, 737a], ["-", "-", "-", "-", 723a, 732a, 739a, 745a, 750a, 805a], [658a, 700a, 704a, 718a, 729a, 739a, 746a, 757a, 802a, 818a], [717a, 719a, 723a, 737a, 748a, 802a, 810a, 821a, 826a, 842a], [739a, 741a, 745a, 800a, 811a, 825a, 833a, 844a, 849a, 902a], [802a, 804a, 808a, 823a, 834a, 848a, 856a, 904a, 906a, 913a], [847a, 849a, 853a, 907a, 917a, 927a, 934a, 940a, 942a, 949a], [930a, 932a, 936a, 950a, 1000a, 1010a, 1017a, 1023a, 1025a, 1032a], [1030a, 1032a, 1036a, 1050a, 1100a, 1110a, 1117a, 1123a, 1125a, 1132a], [1130a, 1132a, 1136a, 1150a, 1200p, 1210p, 1217p, 1223p, 1225p, 1232p], [1230p, 1232p, 1236p, 1250p, 100p, 110p, 117p, 123p, 125p, 132p], [130p, 132p, 136p, 150p, 200p, 210p, 217p, 223p, 225p, 232p], [235p, 237p, 241p, 255p, 305p, 315p, 322p, 328p, 330p, 337p], [312p, 314p, 318p, 332p, 342p, 352p, 359p, 406p, 408p, 416p], [340p, 342p, 346p, 400p, 411p, 423p, 431p, 438p, 440p, 448p], [420p, 422p, 426p, 441p, 452p, 504p, 512p, 519p, 521p, 529p], [440p, 442p, 446p, 501p, 512p, 524p, 532p, 539p, 541p, 549p], [456p, 458p, 502p, 517p, 528p, 540p, 548p, 555p, 557p, 604p], [516p, 518p, 522p, 537p, 548p, 600p, 607p, 613p, 615p, 621p], [536p, 538p, 542p, 557p, 607p, 617p, 624p, 630p, 632p, 638p], [555p, 557p, 601p, 615p, 625p, 635p, 642p, 648p, 650p, 656p], [629p, 631p, 635p, 649p, 659p, 709p, 716p, 722p, 724p, 730p], [729p, 731p, 735p, 749p, 759p, 809p, 816p, 822p, 824p, 830p], [829p, 831p, 835p, 849p, 859p, 909p, 916p, 922p, 924p, 930p], [929p, 931p, 935p, 949p, 959p, 1009p, 1016p, 1022p, 1024p, 1030p], [1029p, 1031p, 1035p, 1049p, 1059p, 1109p, 1116p, 1122p, 1124p, 1130p]]
  -
  time_points: [Centrelink Tuggeranong, Tuggeranong Bus Station (Platform 7), Belconnen Community Bus Station, Westfield Bus Station, Cohen Street Bus Station]
  long_name: To Cohen Street Bus Station
  between_stops:
  Westfield Bus Station-Cohen Street Bus Station: []
  Belconnen Community Bus Station-Westfield Bus Station: []
  short_name: "705"
  stop_times: [["-", 723a, 752a, 754a, 759a], ["-", 749a, 818a, 820a, 825a], ["-", 814a, 848a, 850a, 855a], [442p, 447p, 516p, 518p, 523p], [507p, 512p, 541p, 543p, 548p], [535p, 540p, 609p, 611p, 616p]]
  -
  time_points: [Farrer Terminus, Southlands Mawson, Garran, Hughes, City West, City Bus Station, ACTEW AGL House]
  long_name: To ACTEW AGL House
  between_stops:
  City Bus Station-ACTEW AGL House: [Wjz5Nht]
  City West-City Bus Station: []
  short_name: "720"
  stop_times: [[710a, 716a, 728a, 734a, 752a, 756a, 757a], [740a, 746a, 758a, 804a, 822a, 826a, 827a], [816a, 822a, 834a, 840a, 858a, 902a, 903a], [840a, 846a, 858a, 904a, 922a, 926a, 927a]]
  -
  time_points: [City Bus Station (Platform 8), Macarthur / Northbourne Ave, Northbourne Avenue / Antill St, North Lyneham, Kaleen Village / Marybrynong, Giralang, University of Canberra, Belconnen Community Bus Station, Westfield Bus Station, Cohen Street Bus Station]
  long_name: To Cohen Street Bus Station
  between_stops:
  Westfield Bus Station-Cohen Street Bus Station: []
  Macarthur / Northbourne Ave-Northbourne Avenue / Antill St: [Wjz5RkN, Wjz5Rsi, Wjz5RvC, Wjz5Sqk, Wjz5SrO, Wjz5Sux, Wjz5SDc]
  City Bus Station (Platform 8)-Macarthur / Northbourne Ave: [Wjz5O3Q, Wjz5Oci, Wjz5P8n, Wjz5P8K, Wjz5PdJ, Wjz5Pl0, Wjz5Qi2, Wjz5Qgn, Wjz5Qmu, Wjz5QmR]
  Belconnen Community Bus Station-Westfield Bus Station: []
  University of Canberra-Belconnen Community Bus Station: [Wjz681S, Wjz689c]
  short_name: "30"
  stop_times: [[603a, 609a, 611a, 614a, 621a, 628a, 635a, 641a, 643a, 648a], [634a, 640a, 642a, 645a, 652a, 659a, 706a, 712a, 714a, 719a], [701a, 707a, 709a, 712a, 719a, 726a, 735a, 741a, 743a, 748a], [726a, 732a, 734a, 737a, 745a, 753a, 805a, 811a, 813a, 818a], [759a, 806a, 808a, 811a, 819a, 827a, 839a, 845a, 847a, 852a], [829a, 836a, 838a, 841a, 849a, 857a, 909a, 915a, 917a, 922a], [859a, 906a, 908a, 911a, 919a, 927a, 935a, 941a, 943a, 948a], [933a, 939a, 941a, 944a, 951a, 958a, 1005a, 1011a, 1013a, 1018a], [1002a, 1008a, 1010a, 1013a, 1020a, 1027a, 1034a, 1040a, 1042a, 1047a], [1102a, 1108a, 1110a, 1113a, 1120a, 1127a, 1134a, 1140a, 1142a, 1147a], [1202p, 1208p, 1210p, 1213p, 1220p, 1227p, 1234p, 1240p, 1242p, 1247p], [102p, 108p, 110p, 113p, 120p, 127p, 134p, 140p, 142p, 147p], [202p, 208p, 210p, 213p, 220p, 227p, 234p, 240p, 242p, 247p], [302p, 309p, 311p, 316p, 324p, 332p, 344p, 350p, 352p, 357p], [334p, 341p, 343p, 348p, 356p, 404p, 416p, 422p, 424p, 429p], [359p, 406p, 408p, 413p, 421p, 429p, 441p, 447p, 449p, 454p], [429p, 436p, 438p, 443p, 451p, 459p, 511p, 517p, 519p, 524p], [459p, 506p, 508p, 513p, 521p, 529p, 541p, 547p, 549p, 554p], [514p, 521p, 523p, 528p, 536p, 544p, 556p, 602p, 604p, 609p], [529p, 536p, 538p, 543p, 551p, 559p, 611p, 617p, 619p, 624p], [544p, 551p, 553p, 558p, 606p, 614p, 626p, 632p, 634p, 639p], [559p, 606p, 608p, 613p, 621p, 629p, 636p, 642p, 644p, 649p], [633p, 639p, 641p, 644p, 651p, 658p, 705p, 711p, 713p, 718p], [702p, 708p, 710p, 713p, 720p, 727p, 734p, 740p, 742p, 747p], [802p, 808p, 810p, 813p, 820p, 827p, 834p, 840p, 842p, 847p], [902p, 908p, 910p, 913p, 920p, 927p, 934p, 940p, 942p, 947p], [1002p, 1008p, 1010p, 1013p, 1020p, 1027p, 1034p, 1040p, 1042p, 1047p], [1102p, 1108p, 1110p, 1113p, 1120p, 1127p, 1134p, 1140p, 1142p, 1147p]]
  -
  time_points: [Tuggeranong Bus Station (Platform 4), Kambah High, Kambah Village, Woden Bus Station]
  long_name: To Woden Bus Station
  between_stops: {}
   
  stop_times_saturday: [[824a, 831a, 839a, 852a], [924a, 931a, 939a, 952a], [1024a, 1031a, 1039a, 1052a], [1124a, 1131a, 1139a, 1152a], [1224p, 1231p, 1239p, 1252p], [124p, 131p, 139p, 152p], [224p, 231p, 239p, 252p], [324p, 331p, 339p, 352p], [424p, 431p, 439p, 452p], [524p, 531p, 539p, 552p], [624p, 631p, 638p, 649p], [724p, 730p, 737p, 748p], [824p, 830p, 837p, 848p], [924p, 930p, 937p, 948p], [1024p, 1030p, 1037p, 1048p], [1124p, 1130p, 1137p, 1148p]]
  short_name: "962"
  -
  time_points: [Fairbairn Park, Brindabella Business Park, Majura Business Park, Campbell Park Offices, ADFA, War Memorial / Limestone Ave, City Bus Station (Platform 4), Caswell Drive, Aranda, Cook, Jamison Centre, Belconnen Community Bus Station, Westfield Bus Station, Cohen Street Bus Station]
  long_name: To Cohen Street Bus Station
  between_stops:
  Westfield Bus Station-Cohen Street Bus Station: []
  Belconnen Community Bus Station-Westfield Bus Station: []
  Campbell Park Offices-ADFA: [Wjzce7O, Wjzce4H, Wjzcend]
  Fairbairn Park-Brindabella Business Park: [WjzcJ38, WjzcBHZ, WjzcJ0K, WjzcrEu, WjzcrrQ, WjzcrK3]
  short_name: "10"
  stop_times: [["-", "-", "-", "-", "-", "-", 632a, 642a, 644a, 649a, 659a, 709a, 711a, 716a], ["-", "-", "-", "-", "-", "-", 702a, 712a, 714a, 719a, 729a, 739a, 741a, 746a], ["-", "-", "-", "-", "-", "-", 732a, 742a, 744a, 749a, 759a, 809a, 811a, 816a], ["-", "-", "-", "-", "-", "-", 802a, 812a, 814a, 819a, 829a, 839a, 841a, 846a], ["-", "-", "-", 800a, 803a, 808a, 820a, 830a, 832a, 837a, 847a, 857a, 859a, 904a], ["-", "-", "-", 830a, 833a, 838a, 850a, 900a, 902a, 907a, 917a, 927a, 929a, 934a], ["-", "-", "-", 900a, 903a, 908a, 920a, 930a, 932a, 937a, 947a, 957a, 959a, 1004a], [918a, 928a, 933a, 940a, 943a, 948a, 1000a, 1010a, 1012a, 1017a, 1027a, 1037a, 1039a, 1044a], [948a, 958a, 1003a, 1010a, 1013a, 1018a, 1030a, 1040a, 1042a, 1047a, 1057a, 1107a, 1109a, 1114a], [1018a, 1028a, 1033a, 1040a, 1043a, 1048a, 1100a, 1110a, 1112a, 1117a, 1127a, 1137a, 1139a, 1144a], [1048a, 1058a, 1103a, 1110a, 1113a, 1118a, 1130a, 1140a, 1142a, 1147a, 1157a, 1207p, 1209p, 1214p], [1118a, 1128a, 1133a, 1140a, 1143a, 1148a, 1200p, 1210p, 1212p, 1217p, 1227p, 1237p, 1239p, 1244p], [1148a, 1158a, 1203p, 1210p, 1213p, 1218p, 1230p, 1240p, 1242p, 1247p, 1257p, 107p, 109p, 114p], [1218p, 1228p, 1233p, 1240p, 1243p, 1248p, 100p, 110p, 112p, 117p, 127p, 137p, 139p, 144p], [1248p, 1258p, 103p, 110p, 113p, 118p, 130p, 140p, 142p, 147p, 157p, 207p, 209p, 214p], [118p, 128p, 133p, 140p, 143p, 148p, 200p, 210p, 212p, 217p, 227p, 237p, 239p, 244p], [148p, 158p, 203p, 210p, 213p, 218p, 230p, 240p, 242p, 247p, 257p, 307p, 309p, 314p], [218p, 228p, 233p, 240p, 243p, 248p, 300p, 310p, 313p, 318p, 328p, 338p, 340p, 345p], [248p, 258p, 303p, 310p, 314p, 319p, 331p, 341p, 344p, 349p, 359p, 409p, 411p, 416p], [318p, 328p, 333p, 340p, 344p, 349p, 401p, 411p, 414p, 419p, 429p, 439p, 441p, 446p], ["-", "-", "-", "-", "-", "-", 416p, 426p, 429p, 434p, 444p, 454p, 456p, 501p], [348p, 358p, 403p, 410p, 414p, 419p, 431p, 441p, 444p, 449p, 459p, 509p, 511p, 516p], ["-", "-", "-", "-", "-", "-", 446p, 456p, 459p, 504p, 514p, 524p, 526p, 531p], ["-", "-", 431p, 441p, 445p, 450p, 502p, 512p, 515p, 520p, 530p, 537p, 539p, 544p], ["-", "-", "-", "-", "-", "-", 516p, 526p, 529p, 534p, 544p, 554p, 556p, 601p], ["-", "-", 458p, 511p, 515p, 520p, 532p, 542p, 545p, 550p, 600p, 610p, 612p, 617p], ["-", "-", "-", "-", "-", "-", 546p, 556p, 559p, 604p, 614p, 624p, 626p, 631p], ["-", "-", "-", 540p, 544p, 549p, 601p, 611p, 614p, 619p, 629p, 639p, 641p, 646p], ["-", "-", "-", "-", "-", "-", 616p, 626p, 629p, 634p, 644p, 654p, 656p, 701p], ["-", "-", "-", 611p, 615p, 620p, 632p, 642p, 644p, 649p, 659p, 709p, 711p, 716p], ["-", "-", "-", "-", "-", "-", 736p, 746p, 748p, 753p, 803p, 813p, 815p, 820p], ["-", "-", "-", "-", "-", "-", 836p, 846p, 848p, 853p, 903p, 913p, 915p, 920p], ["-", "-", "-", "-", "-", "-", 936p, 946p, 948p, 953p, 1003p, 1013p, 1015p, 1020p], ["-", "-", "-", "-", "-", "-", 1036p, 1046p, 1048p, 1053p, 1103p, 1113p, 1115p, 1120p], ["-", "-", "-", "-", "-", "-", 1136p, 1146p, 1148p, 1153p, 1203a, 1213a, 1215a, 1220a]]
  -
  time_points: [Lithgow St Terminus Fyshwick, Canberra Times, City Bus Station]
  long_name: To City Bus Station
  between_stops: {}
   
  short_name: "780"
  stop_times: [[405p, 421p, 440p], [435p, 451p, 510p]]
  -
  time_points: [Kippax, Higgins, Hawker College, Hawker, Macquarie, Aranda, City Bus Station (Platform 10), Russell Offices, National Circ / Canberra Ave]
  long_name: To National Circ / Canberra Ave
  between_stops:
  City Bus Station (Platform 10)-Russell Offices: [Wjz5Nht, Wjz5MsD, Wjz5MsT, Wjz5MEL, Wjz4T-X, Wjz4_jm, Wjz4_wS, Wjz4-KO, Wjz4-Rc, Wjz4-WL, Wjz4-WZ]
  short_name: "704"
  stop_times: [[738a, 744a, 749a, 754a, 803a, 812a, 825a, 833a, 840a], [753a, 759a, 804a, 809a, 818a, 827a, 840a, 848a, 855a]]
- -
time_points: [Woden Bus Station (Platform 11), Erindale Centre, Proctor / Mead, Deamer / Clift Richardson, Bonython Primary School, Tuggeranong Bus Station] time_points: [Woden Bus Station (Platform 11), Erindale Centre, Proctor / Mead, Deamer / Clift Richardson, Bonython Primary School, Tuggeranong Bus Station]
long_name: To Tuggeranong Bus Station long_name: To Tuggeranong Bus Station
between_stops: {} between_stops: {}
short_name: "66" short_name: "66"
stop_times: [["-", 602a, 610a, 617a, 622a, 631a], [622a, 632a, 640a, 647a, 652a, 701a], [652a, 702a, 710a, 717a, 722a, 731a], [722a, 734a, 744a, 751a, 758a, 808a], [752a, 810a, 820a, 827a, 834a, 844a], [822a, 840a, 850a, 857a, 904a, 914a], [916a, 933a, 941a, 948a, 954a, 1003a], [1022a, 1036a, 1044a, 1051a, 1057a, 1106a], [1122a, 1136a, 1144a, 1151a, 1157a, 1206p], [1222p, 1236p, 1244p, 1251p, 1257p, 106p], [122p, 136p, 144p, 151p, 157p, 206p], [222p, 236p, 244p, 251p, 257p, 307p], [252p, 308p, 319p, 326p, 333p, 343p], [322p, 340p, 351p, 358p, 405p, 415p], [352p, 410p, 421p, 428p, 435p, 445p], [422p, 440p, 451p, 458p, 505p, 515p], [452p, 510p, 521p, 528p, 535p, 545p], [522p, 540p, 551p, 558p, 605p, 615p], [552p, 610p, 621p, 628p, 634p, 643p], [622p, 638p, 646p, 653p, 658p, 707p], [722p, 736p, 744p, 751p, 756p, 805p], [822p, 836p, 844p, 851p, 856p, 905p], [922p, 936p, 944p, 951p, 956p, 1005p], [1022p, 1036p, 1044p, 1051p, 1056p, 1105p], [1122p, 1136p, 1144p, 1151p, 1156p, "-"]] stop_times: [["-", 602a, 610a, 617a, 622a, 631a], [622a, 632a, 640a, 647a, 652a, 701a], [652a, 702a, 710a, 717a, 722a, 731a], [722a, 734a, 744a, 751a, 758a, 808a], [752a, 810a, 820a, 827a, 834a, 844a], [822a, 840a, 850a, 857a, 904a, 914a], [916a, 933a, 941a, 948a, 954a, 1003a], [1022a, 1036a, 1044a, 1051a, 1057a, 1106a], [1122a, 1136a, 1144a, 1151a, 1157a, 1206p], [1222p, 1236p, 1244p, 1251p, 1257p, 106p], [122p, 136p, 144p, 151p, 157p, 206p], [222p, 236p, 244p, 251p, 257p, 307p], [252p, 308p, 319p, 326p, 333p, 343p], [322p, 340p, 351p, 358p, 405p, 415p], [352p, 410p, 421p, 428p, 435p, 445p], [422p, 440p, 451p, 458p, 505p, 515p], [452p, 510p, 521p, 528p, 535p, 545p], [522p, 540p, 551p, 558p, 605p, 615p], [552p, 610p, 621p, 628p, 634p, 643p], [622p, 638p, 646p, 653p, 658p, 707p], [722p, 736p, 744p, 751p, 756p, 805p], [822p, 836p, 844p, 851p, 856p, 905p], [922p, 936p, 944p, 951p, 956p, 1005p], [1022p, 1036p, 1044p, 1051p, 1056p, 1105p], [1122p, 1136p, 1144p, 1151p, 1156p, "-"]]
- -
time_points: [City West, City Bus Station (Platform 10), ACTEW AGL House, Mentone View / Tharwa Drive, Tharwa Dr / Pockett Ave, Lanyon Market Place] time_points: [City West, City Bus Station (Platform 10), ACTEW AGL House, Mentone View / Tharwa Drive, Tharwa Drive / Pockett Ave, Lanyon Market Place]
long_name: To Lanyon Market Place long_name: To Lanyon Market Place
between_stops: between_stops:
ACTEW AGL House-Mentone View / Tharwa Drive: [Wjz33LB, Wjz34Gq, WjrXUAm, WjrXUsW, WjrXUoV, WjrW_uo, Wjz2a26, Wjz1kvl] ACTEW AGL House-Mentone View / Tharwa Drive: [Wjz33LB, Wjz34Gq, WjrXUAm, WjrXUsW, WjrXUoV, WjrW_uo, Wjz2a26, Wjz1kvl]
City West-City Bus Station (Platform 10): [] City West-City Bus Station (Platform 10): []
City Bus Station (Platform 10)-ACTEW AGL House: [Wjz5Nht] City Bus Station (Platform 10)-ACTEW AGL House: [Wjz5Nht]
short_name: "785" short_name: "785"
stop_times: [[505p, 511p, 513p, 549p, 605p, 607p], [530p, 536p, 538p, 614p, 630p, 632p], [545p, 551p, 553p, 629p, 645p, 647p]] stop_times: [[505p, 511p, 513p, 549p, 605p, 607p], [530p, 536p, 538p, 614p, 630p, 632p], [545p, 551p, 553p, 629p, 645p, 647p]]
- -
time_points: [Fyshwick Direct Factory Outlet, Railway Station Kingston, Kings Ave / National Circuit, Russell Offices, City Bus Station (Platform 8), Macarthur / Northbourne Ave, Northbourne Avenue / Antill St, Flemington Rd / Sandford St, Gungahlin Marketplace] time_points: [Fyshwick Direct Factory Outlet, Railway Station Kingston, Kings Ave / National Circuit, Russell Offices, City Bus Station (Platform 8), Macarthur / Northbourne Ave, Northbourne Avenue / Antill St, Flemington Rd / Sandford St, Gungahlin Marketplace]
long_name: To Gungahlin Marketplace long_name: To Gungahlin Marketplace
between_stops: between_stops:
Macarthur / Northbourne Ave-Northbourne Avenue / Antill St: [Wjz5RkN, Wjz5Rsi, Wjz5RvC, Wjz5Sqk, Wjz5SrO, Wjz5Sux, Wjz5SDc] Macarthur / Northbourne Ave-Northbourne Avenue / Antill St: [Wjz5RkN, Wjz5Rsi, Wjz5RvC, Wjz5Sqk, Wjz5SrO, Wjz5Sux, Wjz5SDc]
City Bus Station (Platform 8)-Macarthur / Northbourne Ave: [Wjz5O3Q, Wjz5Oci, Wjz5P8n, Wjz5P8K, Wjz5PdJ, Wjz5Pl0, Wjz5Qi2, Wjz5Qgn, Wjz5Qmu, Wjz5QmR] City Bus Station (Platform 8)-Macarthur / Northbourne Ave: [Wjz5O3Q, Wjz5Oci, Wjz5P8n, Wjz5P8K, Wjz5PdJ, Wjz5Pl0, Wjz5Qi2, Wjz5Qgn, Wjz5Qmu, Wjz5QmR]
Kings Ave / National Circuit-Russell Offices: [Wjz4Quk, Wjz4RwH, Wjz4RFJ] Kings Ave / National Circuit-Russell Offices: [Wjz4Quk, Wjz4RwH, Wjz4RFJ]
Russell Offices-City Bus Station (Platform 8): [Wjz4-WZ, Wjz4-WL, Wjz4-Rc, Wjz4-KO, Wjz4_wS, Wjz4_jm, Wjz4T-X, Wjz5MEL, Wjz5MsT, Wjz5MsD, Wjz5Nht] Russell Offices-City Bus Station (Platform 8): [Wjz4-WZ, Wjz4-WL, Wjz4-Rc, Wjz4-KO, Wjz4_wS, Wjz4_jm, Wjz4T-X, Wjz5MEL, Wjz5MsT, Wjz5MsD, Wjz5Nht]
short_name: "200" short_name: "200"
stop_times: [[658a, 706a, 713a, 717a, 725a, 732a, 734a, 741a, 748a], [713a, 721a, 728a, 732a, 740a, 747a, 749a, 756a, 804a], [728a, 736a, 743a, 747a, 755a, 802a, 804a, 811a, 821a], [743a, 751a, 758a, 803a, 812a, 818a, 820a, 827a, 837a], [758a, 806a, 814a, 820a, 829a, 835a, 837a, 844a, 854a], [813a, 821a, 829a, 835a, 844a, 850a, 852a, 859a, 906a], [828a, 836a, 844a, 850a, 859a, 906a, 908a, 915a, 922a], [843a, 851a, 859a, 903a, 911a, 918a, 920a, 927a, 934a], [858a, 906a, 913a, 917a, 925a, 932a, 934a, 941a, 948a], [913a, 921a, 928a, 932a, 940a, 947a, 949a, 956a, 1003a], [928a, 936a, 943a, 947a, 955a, 1002a, 1004a, 1011a, 1018a], [943a, 951a, 958a, 1002a, 1010a, 1017a, 1019a, 1026a, 1033a], [958a, 1006a, 1013a, 1017a, 1025a, 1032a, 1034a, 1041a, 1048a], [1013a, 1021a, 1028a, 1032a, 1040a, 1047a, 1049a, 1056a, 1103a], [1028a, 1036a, 1043a, 1047a, 1055a, 1102a, 1104a, 1111a, 1118a], [1043a, 1051a, 1058a, 1102a, 1110a, 1117a, 1119a, 1126a, 1133a], [1058a, 1106a, 1113a, 1117a, 1125a, 1132a, 1134a, 1141a, 1148a], [1113a, 1121a, 1128a, 1132a, 1140a, 1147a, 1149a, 1156a, 1203p], [1128a, 1136a, 1143a, 1147a, 1155a, 1202p, 1204p, 1211p, 1218p], [1143a, 1151a, 1158a, 1202p, 1210p, 1217p, 1219p, 1226p, 1233p], [1158a, 1206p, 1213p, 1217p, 1225p, 1232p, 1234p, 1241p, 1248p], [1213p, 1221p, 1228p, 1232p, 1240p, 1247p, 1249p, 1256p, 103p], [1228p, 1236p, 1243p, 1247p, 1255p, 102p, 104p, 111p, 118p], [1243p, 1251p, 1258p, 102p, 110p, 117p, 119p, 126p, 133p], [1258p, 106p, 113p, 117p, 125p, 132p, 134p, 141p, 148p], [113p, 121p, 128p, 132p, 140p, 147p, 149p, 156p, 203p], [128p, 136p, 143p, 147p, 155p, 202p, 204p, 211p, 218p], [143p, 151p, 158p, 202p, 210p, 217p, 219p, 226p, 233p], [158p, 206p, 213p, 217p, 225p, 232p, 234p, 241p, 248p], [213p, 221p, 228p, 232p, 240p, 247p, 249p, 256p, 303p], [228p, 236p, 243p, 247p, 255p, 302p, 304p, 311p, 318p], [243p, 251p, 258p, 302p, 310p, 317p, 319p, 326p, 333p], [258p, 306p, 313p, 317p, 325p, 332p, 334p, 341p, 348p], [313p, 321p, 328p, 332p, 340p, 347p, 349p, 356p, 404p], [328p, 336p, 343p, 347p, 355p, 401p, 404p, 411p, 421p], [343p, 351p, 358p, 403p, 415p, 420p, 423p, 430p, 440p], [358p, 408p, 416p, 422p, 434p, 439p, 442p, 449p, 459p], [413p, 423p, 431p, 437p, 449p, 454p, 457p, 504p, 514p], [428p, 438p, 446p, 452p, 504p, 509p, 512p, 519p, 529p], [443p, 453p, 501p, 507p, 519p, 524p, 527p, 534p, 544p], [458p, 508p, 516p, 522p, 534p, 539p, 542p, 549p, 559p], [513p, 523p, 531p, 537p, 549p, 554p, 557p, 604p, 611p], [528p, 538p, 546p, 552p, 603p, 610p, 612p, 619p, 626p], [543p, 553p, 601p, 605p, 613p, 620p, 622p, 629p, 636p], [558p, 606p, 613p, 617p, 625p, 632p, 634p, 641p, 648p], [613p, 621p, 628p, 632p, 640p, 647p, 649p, 656p, 703p], [628p, 636p, 643p, 647p, 655p, 701p, 703p, 709p, 716p], [643p, 651p, 658p, 702p, 710p, 714p, 716p, 722p, 729p]] stop_times: [[658a, 706a, 713a, 717a, 725a, 732a, 734a, 741a, 748a], [713a, 721a, 728a, 732a, 740a, 747a, 749a, 756a, 804a], [728a, 736a, 743a, 747a, 755a, 802a, 804a, 811a, 821a], [743a, 751a, 758a, 803a, 812a, 818a, 820a, 827a, 837a], [758a, 806a, 814a, 820a, 829a, 835a, 837a, 844a, 854a], [813a, 821a, 829a, 835a, 844a, 850a, 852a, 859a, 906a], [828a, 836a, 844a, 850a, 859a, 906a, 908a, 915a, 922a], [843a, 851a, 859a, 903a, 911a, 918a, 920a, 927a, 934a], [858a, 906a, 913a, 917a, 925a, 932a, 934a, 941a, 948a], [913a, 921a, 928a, 932a, 940a, 947a, 949a, 956a, 1003a], [928a, 936a, 943a, 947a, 955a, 1002a, 1004a, 1011a, 1018a], [943a, 951a, 958a, 1002a, 1010a, 1017a, 1019a, 1026a, 1033a], [958a, 1006a, 1013a, 1017a, 1025a, 1032a, 1034a, 1041a, 1048a], [1013a, 1021a, 1028a, 1032a, 1040a, 1047a, 1049a, 1056a, 1103a], [1028a, 1036a, 1043a, 1047a, 1055a, 1102a, 1104a, 1111a, 1118a], [1043a, 1051a, 1058a, 1102a, 1110a, 1117a, 1119a, 1126a, 1133a], [1058a, 1106a, 1113a, 1117a, 1125a, 1132a, 1134a, 1141a, 1148a], [1113a, 1121a, 1128a, 1132a, 1140a, 1147a, 1149a, 1156a, 1203p], [1128a, 1136a, 1143a, 1147a, 1155a, 1202p, 1204p, 1211p, 1218p], [1143a, 1151a, 1158a, 1202p, 1210p, 1217p, 1219p, 1226p, 1233p], [1158a, 1206p, 1213p, 1217p, 1225p, 1232p, 1234p, 1241p, 1248p], [1213p, 1221p, 1228p, 1232p, 1240p, 1247p, 1249p, 1256p, 103p], [1228p, 1236p, 1243p, 1247p, 1255p, 102p, 104p, 111p, 118p], [1243p, 1251p, 1258p, 102p, 110p, 117p, 119p, 126p, 133p], [1258p, 106p, 113p, 117p, 125p, 132p, 134p, 141p, 148p], [113p, 121p, 128p, 132p, 140p, 147p, 149p, 156p, 203p], [128p, 136p, 143p, 147p, 155p, 202p, 204p, 211p, 218p], [143p, 151p, 158p, 202p, 210p, 217p, 219p, 226p, 233p], [158p, 206p, 213p, 217p, 225p, 232p, 234p, 241p, 248p], [213p, 221p, 228p, 232p, 240p, 247p, 249p, 256p, 303p], [228p, 236p, 243p, 247p, 255p, 302p, 304p, 311p, 318p], [243p, 251p, 258p, 302p, 310p, 317p, 319p, 326p, 333p], [258p, 306p, 313p, 317p, 325p, 332p, 334p, 341p, 348p], [313p, 321p, 328p, 332p, 340p, 347p, 349p, 356p, 404p], [328p, 336p, 343p, 347p, 355p, 401p, 404p, 411p, 421p], [343p, 351p, 358p, 403p, 415p, 420p, 423p, 430p, 440p], [358p, 408p, 416p, 422p, 434p, 439p, 442p, 449p, 459p], [413p, 423p, 431p, 437p, 449p, 454p, 457p, 504p, 514p], [428p, 438p, 446p, 452p, 504p, 509p, 512p, 519p, 529p], [443p, 453p, 501p, 507p, 519p, 524p, 527p, 534p, 544p], [458p, 508p, 516p, 522p, 534p, 539p, 542p, 549p, 559p], [513p, 523p, 531p, 537p, 549p, 554p, 557p, 604p, 611p], [528p, 538p, 546p, 552p, 603p, 610p, 612p, 619p, 626p], [543p, 553p, 601p, 605p, 613p, 620p, 622p, 629p, 636p], [558p, 606p, 613p, 617p, 625p, 632p, 634p, 641p, 648p], [613p, 621p, 628p, 632p, 640p, 647p, 649p, 656p, 703p], [628p, 636p, 643p, 647p, 655p, 701p, 703p, 709p, 716p], [643p, 651p, 658p, 702p, 710p, 714p, 716p, 722p, 729p]]
  -
  time_points: [Tuggeranong Bus Station (Platform 7), Calwell, Chisholm, Erindale / Sternberg Cres, Woden Bus Station (Platform 10), Kings Ave / National Circuit, Russell Offices, City Bus Station (Platform 11), City West]
  long_name: To City West
  between_stops:
  Russell Offices-City Bus Station (Platform 11): [Wjz4-WZ, Wjz4-WL, Wjz4-Rc, Wjz4-KO, Wjz4_wS, Wjz4_jm, Wjz4T-X, Wjz5MEL, Wjz5MsT, Wjz5MsD, Wjz5Nht]
  Kings Ave / National Circuit-Russell Offices: [Wjz4Quk, Wjz4RwH, Wjz4RFJ]
  City Bus Station (Platform 11)-City West: []
  short_name: 67 267
  stop_times: [[603a, 615a, 627a, 635a, 644a, "-", "-", "-", "-"], [633a, 645a, 657a, 705a, 716a, "-", "-", "-", "-"], [702a, 715a, 726a, 735a, 750a, 804a, 808a, 818a, 821a], [718a, 730a, 745a, 755a, 809a, "-", "-", "-", "-"], [731a, 746a, 800a, 810a, 825a, 839a, 843a, 853a, 856a], [803a, 817a, 832a, 842a, 856a, "-", "-", "-", "-"], [833a, 847a, 902a, 912a, 926a, "-", "-", "-", "-"], [903a, 917a, 932a, 940a, 953a, "-", "-", "-", "-"], [1003a, 1016a, 1028a, 1036a, 1049a, "-", "-", "-", "-"], [1103a, 1116a, 1128a, 1136a, 1149a, "-", "-", "-", "-"], [1203p, 1216p, 1228p, 1236p, 1249p, "-", "-", "-", "-"], [103p, 116p, 128p, 136p, 149p, "-", "-", "-", "-"], [203p, 216p, 228p, 236p, 249p, "-", "-", "-", "-"], [303p, 317p, 332p, 342p, 356p, "-", "-", "-", "-"], [333p, 347p, 402p, 412p, 426p, "-", "-", "-", "-"], [403p, 417p, 432p, 442p, 456p, "-", "-", "-", "-"], [433p, 447p, 502p, 512p, 526p, "-", "-", "-", "-"], [503p, 517p, 532p, 542p, 556p, "-", "-", "-", "-"], [533p, 547p, 602p, 612p, 626p, "-", "-", "-", "-"], [603p, 617p, 632p, 640p, 653p, "-", "-", "-", "-"], [703p, 716p, 728p, 736p, 749p, "-", "-", "-", "-"], [803p, 816p, 828p, 836p, 849p, "-", "-", "-", "-"], [903p, 916p, 928p, 936p, 949p, "-", "-", "-", "-"], [1003p, 1016p, 1028p, 1036p, 1049p, "-", "-", "-", "-"], [1103p, 1116p, 1128p, 1136p, "-", "-", "-", "-", "-"]]
  -
  time_points: [Woden Bus Station (Platform 16), Weston Primary, Holder, Duffy, Cooleman Court]
  long_name: To Cooleman Court
  between_stops: {}
   
  stop_times_saturday: [[857a, 907a, 909a, 911a, 919a], [957a, 1007a, 1009a, 1011a, 1019a], [1057a, 1107a, 1109a, 1111a, 1119a], [1157a, 1207p, 1209p, 1211p, 1219p], [1257p, 107p, 109p, 111p, 119p], [157p, 207p, 209p, 211p, 219p], [257p, 307p, 309p, 311p, 319p], [357p, 407p, 409p, 411p, 419p], [457p, 507p, 509p, 511p, 519p], [557p, 607p, 609p, 611p, 619p], [657p, 707p, 709p, 711p, 719p], [757p, 807p, 809p, 811p, 819p], [857p, 907p, 909p, 911p, 919p], [957p, 1007p, 1009p, 1011p, 1019p], [1057p, 1107p, 1109p, 1111p, 1119p]]
  short_name: "925"
  -
  time_points: [Gungahlin Marketplace, Nicholls Primary, Federation Square, Chuculba / William Slim Dr, Belconnen Community Bus Station, Westfield Bus Station, Cohen Street Bus Station]
  long_name: To Cohen Street Bus Station
  between_stops:
  Westfield Bus Station-Cohen Street Bus Station: []
  Belconnen Community Bus Station-Westfield Bus Station: []
  Chuculba / William Slim Dr-Belconnen Community Bus Station: [Wjz6mip, Wjz69vO, Wjz69uI, Wjz69ht, Wjz69gA]
  stop_times_saturday: [[0839a, 0847a, 0900a, 0905a, 0918a, 0920a, 0925a], [0939a, 0947a, 1000a, 1005a, 1018a, 1020a, 1025a], [1039a, 1047a, 1100a, 1105a, 1118a, 1120a, 1125a], [1139a, 1147a, 1200p, 1205p, 1218p, 1220p, 1225p], [1239p, 1247p, 0100p, 0105p, 0118p, 0120p, 0125p], [0139p, 0147p, 0200p, 0205p, 0218p, 0220p, 0225p], [0239p, 0247p, 0300p, 0305p, 0318p, 0320p, 0325p], [0339p, 0347p, 0400p, 0405p, 0418p, 0420p, 0425p], [0439p, 0447p, 0500p, 0505p, 0518p, 0520p, 0525p], [0539p, 0547p, 0600p, 0605p, 0618p, 0620p, 0625p], [0639p, 0647p, 0700p, 0705p, 0718p, 0720p, 0725p]]
  short_name: "952"
  -
  time_points: [Tuggeranong Bus Station (Platform 4), Isabella, Calwell, Theodore, Outtrim / Duggan, Tuggeranong Bus Station]
  long_name: To Tuggeranong Bus Station
  between_stops: {}
   
  stop_times_saturday: [[815a, 825a, 830a, 839a, 846a, 855a], [1015a, 1025a, 1030a, 1039a, 1046a, 1055a], [1215p, 1225p, 1230p, 1239p, 1246p, 1255p], [215p, 225p, 230p, 239p, 246p, 255p], [415p, 425p, 430p, 439p, 446p, 455p], [615p, 625p, 630p, 639p, 646p, 655p], [818p, 828p, 833p, 842p, 849p, 858p], [1018p, 1028p, 1033p, 1042p, 1049p, 1058p]]
  short_name: "912"
  -
  time_points: [Cohen Street Bus Station (Platform 1), Westfield Bus Station (Platform 1), Belconnen Community Bus Station (Platform 3), Calvary Hospital, Jamison Centre, Cook, Hawker, Page, Florey, Cohen Street Bus Station, Westfield Bus Station, Belconnen Community Bus Station]
  long_name: To Belconnen Community Bus Station
  between_stops:
  Westfield Bus Station (Platform 1)-Belconnen Community Bus Station (Platform 3): []
  Cohen Street Bus Station (Platform 1)-Westfield Bus Station (Platform 1): []
  Westfield Bus Station-Belconnen Community Bus Station: []
  Cohen Street Bus Station-Westfield Bus Station: []
  short_name: "74"
  stop_times: [[950a, 952a, 956a, 1005a, 1012a, 1015a, 1023a, 1027a, 1033a, 1039a, 1041a, 1045a], [1120a, 1122a, 1126a, 1135a, 1142a, 1145a, 1153a, 1157a, 1203p, 1209p, 1211p, 1215p], [1250p, 1252p, 1256p, 105p, 112p, 115p, 123p, 127p, 133p, 139p, 141p, 145p], [220p, 222p, 226p, 235p, 242p, 245p, 253p, 257p, 303p, 309p, 311p, 315p]]
  -
  time_points: [Alexander Maconochie Centre, Woden Bus Station]
  long_name: To Woden Bus Station
  between_stops:
  Alexander Maconochie Centre-Woden Bus Station: [Wjz3kAx, Wjz3dXS]
  stop_times_saturday: [[1130a, 1150a], [320p, 340p], [730p, 750p]]
  short_name: "988"
  -
  time_points: [Cohen Street Bus Station (Platform 1), Westfield Bus Station (Platform 1), Belconnen Community Bus Station (Platform 2), Chuculba / William Slim Dr, Ngunnawal Primary, Shoalhaven / Katherine Ave, Gungahlin Marketplace, Anthony Rolfe Av / Moonlight Av, Flemington Rd / Nullabor Ave, Flemington Rd / Sandford St, Macarthur / Northbourne Ave, City Bus Station]
  long_name: To City Bus Station
  between_stops:
  Belconnen Community Bus Station (Platform 2)-Chuculba / William Slim Dr: [Wjz69gA, Wjz69ht, Wjz69uI, Wjz69vO, Wjz6mip]
  Macarthur / Northbourne Ave-City Bus Station: [Wjz5QmR, Wjz5Qmu, Wjz5Qgn, Wjz5Qi2, Wjz5Pl0, Wjz5PdJ, Wjz5P8K, Wjz5P8n, Wjz5Oci, Wjz5O3Q]
  Cohen Street Bus Station (Platform 1)-Westfield Bus Station (Platform 1): []
  Westfield Bus Station (Platform 1)-Belconnen Community Bus Station (Platform 2): []
  short_name: "958"
  stop_times_sunday: [[852a, 854a, 858a, 908a, 919a, 927a, 935a, 944a, 951a, 958a, 1006a, 1013a], [952a, 954a, 958a, 1008a, 1019a, 1027a, 1035a, 1044a, 1051a, 1058a, 1106a, 1113a], [1052a, 1054a, 1058a, 1108a, 1119a, 1127a, 1135a, 1144a, 1151a, 1158a, 1206p, 1213p], [1152a, 1154a, 1158a, 1208p, 1219p, 1227p, 1235p, 1244p, 1251p, 1258p, 106p, 113p], [1252p, 1254p, 1258p, 108p, 119p, 127p, 135p, 144p, 151p, 158p, 206p, 213p], [152p, 154p, 158p, 208p, 219p, 227p, 235p, 244p, 251p, 258p, 306p, 313p], [252p, 254p, 258p, 308p, 319p, 327p, 335p, 344p, 351p, 358p, 406p, 413p], [352p, 354p, 358p, 408p, 419p, 427p, 435p, 444p, 451p, 458p, 506p, 513p], [452p, 454p, 458p, 508p, 519p, 527p, 535p, 544p, 551p, 558p, 606p, 613p], [552p, 554p, 558p, 608p, 619p, 627p, 635p, 644p, 651p, 658p, 706p, 713p], [652p, 654p, 658p, 708p, 719p, 727p, 735p, 744p, 751p, 758p, 806p, 813p]]
  -
  time_points: [Lanyon Market Place, Conder Primary, St Clare of Assisi Primary, Bonython Primary School, Tuggeranong Bus Station (Platform 8), Woden Bus Station (Platform 9), City Bus Station (Platform 3), Belconnen Community Bus Station, Westfield Bus Station, Cohen Street Bus Station]
  long_name: To Cohen Street Bus Station
  between_stops:
  Westfield Bus Station-Cohen Street Bus Station: []
  City Bus Station (Platform 3)-Belconnen Community Bus Station: [Wjz5F-1, Wjz5FSY, Wjz5GMT, Wjz5GNG, Wjz5G6U, Wjz5G6B, Wjz5maK, Wjz5mbS, Wjz5nwb, Wjz5nw6, Wjz6giR, Wjz6gia, Wjz68W3, Wjz68W5, Wjz689c, Wjz681S]
  Belconnen Community Bus Station-Westfield Bus Station: []
  Tuggeranong Bus Station (Platform 8)-Woden Bus Station (Platform 9): [Wjz213q, Wjz238T, Wjz239F, Wjz2lDC, Wjz2mGO, Wjz2mTK, Wjz2nLE, Wjz3gMq, Wjz3gQn, Wjz3gK-, Wjz3hL_, Wjz3iFK, Wjz3kwU, Wjz3kyX, Wjz3kAx]
  Woden Bus Station (Platform 9)-City Bus Station (Platform 3): [Wjz3m3b, Wjz3m3b, Wjz3eRR, Wjz3eZ4, Wjz4KNu, Wjz4KO9, Wjz5Nht]
  short_name: 19 319
  stop_times: [[556a, 602a, 608a, 614a, 625a, 643a, 659a, 719a, 721a, 726a], [622a, 628a, 634a, 640a, 651a, 709a, 725a, 746a, 748a, 753a], [646a, 652a, 658a, 704a, 715a, 733a, 751a, 812a, 814a, 819a], [706a, 712a, 718a, 724a, 735a, 754a, 812a, 833a, 835a, 840a], [723a, 729a, 735a, 743a, 755a, 814a, 832a, 853a, 855a, 900a], [735a, 742a, 752a, 800a, 810a, "-", "-", "-", "-", "-"], [742a, 749a, 755a, 803a, 815a, 834a, 852a, 913a, 915a, 920a], [802a, 809a, 815a, 823a, 835a, 854a, 912a, 933a, 935a, 940a], [822a, 829a, 835a, 843a, 855a, 914a, 932a, 952a, 954a, 959a], [853a, 900a, 906a, 914a, 926a, 944a, 1000a, 1020a, 1022a, 1027a], [926a, 933a, 939a, 945a, 956a, 1014a, 1030a, 1050a, 1052a, 1057a], [957a, 1003a, 1009a, 1015a, 1026a, 1044a, 1100a, 1120a, 1122a, 1127a], [1027a, 1033a, 1039a, 1045a, 1056a, 1114a, 1130a, 1150a, 1152a, 1157a], [1057a, 1103a, 1109a, 1115a, 1126a, 1144a, 1200p, 1220p, 1222p, 1227p], [1127a, 1133a, 1139a, 1145a, 1156a, 1214p, 1230p, 1250p, 1252p, 1257p], [1157a, 1203p, 1209p, 1215p, 1226p, 1244p, 100p, 120p, 122p, 127p], [1227p, 1233p, 1239p, 1245p, 1256p, 114p, 130p, 150p, 152p, 157p], [1257p, 103p, 109p, 115p, 126p, 144p, 200p, 220p, 222p, 227p], [127p, 133p, 139p, 145p, 156p, 214p, 230p, 250p, 252p, 257p], [157p, 203p, 209p, 215p, 226p, 244p, 300p, 321p, 323p, 328p], [226p, 232p, 238p, 244p, 255p, 314p, 332p, 353p, 355p, 400p], [253p, 259p, 305p, 313p, 325p, 344p, 402p, 423p, 425p, 430p], [320p, 327p, 337p, 345p, 355p, "-", "-", "-", "-", "-"], [352p, 359p, 409p, 417p, 427p, "-", "-", "-", "-", "-"], [424p, 431p, 441p, 449p, 459p, "-", "-", "-", "-", "-"], [454p, 501p, 511p, 519p, 529p, "-", "-", "-", "-", "-"], [524p, 531p, 541p, 549p, 559p, "-", "-", "-", "-", "-"], [556p, 603p, 613p, 621p, 631p, "-", "-", "-", "-", "-"], [654p, 700p, 710p, 716p, 725p, "-", "-", "-", "-", "-"], [754p, 800p, 810p, 816p, 825p, "-", "-", "-", "-", "-"], [849p, 855p, 905p, 911p, 920p, "-", "-", "-", "-", "-"], [949p, 955p, 1005p, 1011p, 1020p, "-", "-", "-", "-", "-"], [1049p, 1055p, 1105p, 1111p, 1120p, "-", "-", "-", "-", "-"], []]
  -
  time_points: [Cohen Street Bus Station (Platform 2), Westfield Bus Station (Platform 1), Belconnen Community Bus Station (Platform 3), Calvary Hospital, O'Connor, Burton and Garran Hall Daley Road, National Museum of Australia, City Bus Station (Platform 2), Kings Ave / National Circuit, Deakin, Hughes, Garran, Canberra Hospital, Woden Bus Station]
  long_name: To Woden Bus Station
  between_stops:
  Westfield Bus Station (Platform 1)-Belconnen Community Bus Station (Platform 3): []
  City Bus Station (Platform 2)-Kings Ave / National Circuit: [Wjz5FOn, Wjz4S1U, Wjz4Rs-, Wjz4RFJ, Wjz4RwH, Wjz4Quk]
  Cohen Street Bus Station (Platform 2)-Westfield Bus Station (Platform 1): []
  Canberra Hospital-Woden Bus Station: [Wjz3mWn, Wjz3mPO, Wjz3mAg]
  stop_times_saturday: [[729a, 731a, 735a, 752a, 759a, 804a, 809a, 819a, 828a, 837a, 842a, 846a, 848a, 855a], [829a, 831a, 835a, 852a, 859a, 904a, 909a, 919a, 928a, 937a, 942a, 946a, 948a, 955a], [929a, 931a, 935a, 952a, 959a, 1004a, 1009a, 1019a, 1028a, 1037a, 1042a, 1046a, 1048a, 1055a], [1029a, 1031a, 1035a, 1052a, 1059a, 1104a, 1109a, 1119a, 1128a, 1137a, 1142a, 1146a, 1148a, 1155a], [1129a, 1131a, 1135a, 1152a, 1159a, 1204p, 1209p, 1219p, 1228p, 1237p, 1242p, 1246p, 1248p, 1255p], [1229p, 1231p, 1235p, 1252p, 1259p, 104p, 109p, 119p, 128p, 137p, 142p, 146p, 148p, 155p], [129p, 131p, 135p, 152p, 159p, 204p, 209p, 219p, 228p, 237p, 242p, 246p, 248p, 255p], [229p, 231p, 235p, 252p, 259p, 304p, 309p, 319p, 328p, 337p, 342p, 346p, 348p, 355p], [329p, 331p, 335p, 352p, 359p, 404p, 409p, 419p, 428p, 437p, 442p, 446p, 448p, 455p], [429p, 431p, 435p, 452p, 459p, 504p, 509p, 519p, 528p, 537p, 542p, 546p, 548p, 555p], [529p, 531p, 535p, 552p, 559p, 604p, 609p, 619p, 628p, 637p, 642p, 646p, 648p, 655p], [629p, 631p, 635p, 652p, 659p, 704p, 709p, 719p, 728p, 737p, 742p, 746p, 748p, 755p], [729p, 731p, 735p, 752p, 759p, 804p, 809p, 819p, 828p, 837p, 842p, 846p, 848p, 855p], [829p, 831p, 835p, 852p, 859p, 904p, 909p, 919p, 928p, 937p, 942p, 946p, 948p, 955p], [929p, 931p, 935p, 952p, 959p, 1004p, 1009p, 1019p, 1028p, 1037p, 1042p, 1046p, 1048p, 1055p], [1029p, 1031p, 1035p, 1052p, 1059p, 1104p, 1109p, 1117p, "-", "-", "-", "-", "-", "-"]]
  short_name: "934"
  -
  time_points: [Cohen Street Bus Station (Platform 2), Westfield Bus Station (Platform 1), Belconnen Community Bus Station (Platform 1), City Bus Station (Platform 1), Woden Bus Station (Platform 6), Erindale Centre, Tuggeranong Bus Station]
  long_name: To Tuggeranong Bus Station
  between_stops:
  Cohen Street Bus Station (Platform 2)-Westfield Bus Station (Platform 1): []
  Westfield Bus Station (Platform 1)-Belconnen Community Bus Station (Platform 1): []
  short_name: "900"
  stop_times_sunday: [[731a, 733a, 737a, 757a, 814a, 829a, 835a], [746a, 748a, 752a, 812a, 829a, 844a, 850a], [801a, 803a, 807a, 827a, 844a, 859a, 905a], [816a, 818a, 822a, 842a, 859a, 914a, 920a], [831a, 833a, 837a, 857a, 914a, 929a, 935a], [846a, 848a, 852a, 912a, 929a, 944a, 950a], [901a, 903a, 907a, 927a, 944a, 959a, 1005a], [916a, 918a, 922a, 942a, 959a, 1014a, 1020a], [931a, 933a, 937a, 957a, 1014a, 1029a, 1035a], [946a, 948a, 952a, 1012a, 1029a, 1044a, 1050a], [1001a, 1003a, 1007a, 1027a, 1044a, 1059a, 1105a], [1016a, 1018a, 1022a, 1042a, 1059a, 1114a, 1120a], [1031a, 1033a, 1037a, 1057a, 1114a, 1129a, 1135a], [1046a, 1048a, 1052a, 1112a, 1129a, 1144a, 1150a], [1101a, 1103a, 1107a, 1127a, 1144a, 1159a, 1205p], [1116a, 1118a, 1122a, 1142a, 1159a, 1214p, 1220p], [1131a, 1133a, 1137a, 1157a, 1214p, 1229p, 1235p], [1146a, 1148a, 1152a, 1212p, 1229p, 1244p, 1250p], [1201p, 1203p, 1207p, 1227p, 1244p, 1259p, 105p], [1216p, 1218p, 1222p, 1242p, 1259p, 114p, 120p], [1231p, 1233p, 1237p, 1257p, 114p, 129p, 135p], [1246p, 1248p, 1252p, 112p, 129p, 144p, 150p], [101p, 103p, 107p, 127p, 144p, 159p, 205p], [116p, 118p, 122p, 142p, 159p, 214p, 220p], [131p, 133p, 137p, 157p, 214p, 229p, 235p], [146p, 148p, 152p, 212p, 229p, 244p, 250p], [201p, 203p, 207p, 227p, 244p, 259p, 305p], [216p, 218p, 222p, 242p, 259p, 314p, 320p], [231p, 233p, 237p, 257p, 314p, 329p, 335p], [246p, 248p, 252p, 312p, 329p, 344p, 350p], [301p, 303p, 307p, 327p, 344p, 359p, 405p], [316p, 318p, 322p, 342p, 359p, 414p, 420p], [331p, 333p, 337p, 357p, 414p, 429p, 435p], [346p, 348p, 352p, 412p, 429p, 444p, 450p], [401p, 403p, 407p, 427p, 444p, 459p, 505p], [416p, 418p, 422p, 442p, 459p, 514p, 520p], [431p, 433p, 437p, 457p, 514p, 529p, 535p], [446p, 448p, 452p, 512p, 529p, 544p, 550p], [501p, 503p, 507p, 527p, 544p, 559p, 605p], [516p, 518p, 522p, 542p, 559p, 614p, 620p], [531p, 533p, 537p, 557p, 614p, 629p, 635p], [546p, 548p, 552p, 612p, 629p, 643p, 649p], [601p, 603p, 607p, 627p, 642p, 656p, 702p], [616p, 618p, 622p, 641p, 655p, 709p, 715p], [631p, 633p, 637p, 656p, 710p, 724p, 730p], [646p, 648p, 652p, 711p, 725p, 739p, 745p], [701p, 703p, 707p, 726p, 740p, 754p, 800p]]
  -
  time_points: [City Bus Station (Platform 7), St Thomas More's Campbell, Russell Offices, Hospice / Menindee Dr, ADFA, Campbell Park Offices]
  long_name: To Campbell Park Offices
  between_stops:
  Hospice / Menindee Dr-ADFA: [Wjzcd8D, Wjzcd2U, Wjzcdi7, Wjzcdsn, WjzcdDs, WjzceFT, WjzceHt]
  ADFA-Campbell Park Offices: [Wjzcend, Wjzce4H, Wjzce7O]
  short_name: "9"
  stop_times: [[714a, 726a, 731a, 733a, 741a, 745a], [814a, 829a, 834a, 836a, 844a, 848a], [857a, 911a, 916a, 918a, 926a, 931a], [957a, 1011a, 1016a, 1018a, 1026a, 1029a], [1057a, 1111a, 1116a, 1118a, 1126a, 1129a], [1157a, 1211p, 1216p, 1218p, 1226p, 1229p], [1257p, 111p, 116p, 118p, 126p, 129p], [157p, 211p, 216p, 218p, 226p, 229p], [257p, 312p, 317p, 319p, 327p, 331p], [344p, 359p, 404p, 406p, 414p, 418p], [414p, 429p, 434p, 436p, 444p, 448p], [444p, 459p, 504p, 506p, 514p, 518p], [514p, 529p, 534p, 536p, 544p, 548p], [557p, 612p, 617p, 619p, 627p, 631p], [657p, 708p, 712p, 714p, 720p, 723p], [757p, 808p, 812p, 814p, 820p, 823p], [857p, 908p, 912p, 914p, 920p, 923p], [957p, 1008p, 1012p, 1014p, 1020p, 1023p], [1057p, 1108p, 1112p, 1114p, 1120p, 1123p]]
  -
  time_points: [Belconnen Community Bus Station (Platform 6), Westfield Bus Station (Platform 2), Cohen Street Bus Station (Platform 5), Higgins, Kippax, Higgins, Cohen Street Bus Station, Westfield Bus Station, Belconnen Community Bus Station]
  long_name: To Belconnen Community Bus Station
  between_stops:
  Belconnen Community Bus Station (Platform 6)-Westfield Bus Station (Platform 2): []
  Westfield Bus Station-Belconnen Community Bus Station: []
  Cohen Street Bus Station-Westfield Bus Station: []
  Westfield Bus Station (Platform 2)-Cohen Street Bus Station (Platform 5): []
  stop_times_saturday: [["-", "-", "-", "-", 757a, 807a, 828a, 830a, 834a], [819a, 821a, 825a, 846a, 857a, 907a, 928a, 930a, 934a], [919a, 921a, 925a, 946a, 957a, 1007a, 1028a, 1030a, 1034a], [1019a, 1021a, 1025a, 1046a, 1057a, 1107a, 1128a, 1130a, 1134a], [1119a, 1121a, 1125a, 1146a, 1157a, 1207p, 1228p, 1230p, 1234p], [1219p, 1221p, 1225p, 1246p, 1257p, 107p, 128p, 130p, 134p], [119p, 121p, 125p, 146p, 157p, 207p, 228p, 230p, 234p], [219p, 221p, 225p, 246p, 257p, 307p, 328p, 330p, 334p], [319p, 321p, 325p, 346p, 357p, 407p, 428p, 430p, 434p], [419p, 421p, 425p, 446p, 457p, 507p, 528p, 530p, 534p], [519p, 521p, 525p, 546p, 557p, 607p, 628p, 630p, 634p], [619p, 621p, 625p, 645p, 656p, 706p, 726p, 728p, 732p], [718p, 720p, 724p, 744p, 755p, 805p, 825p, 827p, 831p], [818p, 820p, 824p, 844p, 855p, 905p, 925p, 927p, 931p], [918p, 920p, 924p, 944p, 955p, 1005p, 1025p, 1027p, 1031p], [1018p, 1020p, 1024p, 1044p, 1055p, 1105p, 1125p, 1127p, 1131p], [1118p, 1120p, 1124p, 1144p, 1155p, "-", "-", "-", "-"]]
  short_name: "904"
  -
  time_points: [Belconnen Community Bus Station (Platform 6), Westfield Bus Station (Platform 2), Cohen Street Bus Station (Platform 5), Weetangera, Hawker, Hawker College, Higgins, Kippax]
  long_name: To Kippax
  between_stops:
  Belconnen Community Bus Station (Platform 6)-Westfield Bus Station (Platform 2): []
  Westfield Bus Station (Platform 2)-Cohen Street Bus Station (Platform 5): []
  short_name: "17"
  stop_times: [[706a, 708a, 712a, 716a, 719a, 724a, 729a, 737a], [806a, 808a, 812a, 817a, 820a, 825a, 830a, 838a], [840a, 842a, 846a, 851a, 854a, 859a, 904a, 912a], [854a, 856a, 900a, 905a, 908a, 913a, 918a, 926a], [922a, 924a, 928a, 932a, 935a, 940a, 945a, 951a], [952a, 954a, 958a, 1002a, 1005a, 1010a, 1015a, 1021a], [1022a, 1024a, 1028a, 1032a, 1035a, 1040a, 1045a, 1051a], [1052a, 1054a, 1058a, 1102a, 1105a, 1110a, 1115a, 1121a], [1122a, 1124a, 1128a, 1132a, 1135a, 1140a, 1145a, 1151a], [1152a, 1154a, 1158a, 1202p, 1205p, 1210p, 1215p, 1221p], [1222p, 1224p, 1228p, 1232p, 1235p, 1240p, 1245p, 1251p], [1252p, 1254p, 1258p, 102p, 105p, 110p, 115p, 121p], [122p, 124p, 128p, 132p, 135p, 140p, 145p, 151p], [152p, 154p, 158p, 202p, 205p, 210p, 215p, 221p], [222p, 224p, 228p, 232p, 235p, 240p, 245p, 251p], [249p, 251p, 255p, 259p, 302p, 307p, 313p, 321p], [324p, 326p, 330p, 335p, 338p, 343p, 349p, 357p], [353p, 355p, 359p, 404p, 407p, 412p, 418p, 426p], [412p, 414p, 418p, 423p, 426p, 431p, 437p, 445p], [432p, 434p, 438p, 443p, 446p, 451p, 457p, 505p], [452p, 454p, 458p, 503p, 506p, 511p, 517p, 525p], [512p, 514p, 518p, 523p, 526p, 531p, 537p, 545p], [532p, 534p, 538p, 543p, 546p, 551p, 557p, 605p], [552p, 554p, 558p, 603p, 606p, 611p, 617p, 625p], [612p, 614p, 618p, 623p, 626p, 631p, 636p, 642p], [644p, 646p, 650p, 654p, 657p, 702p, 707p, 713p], [737p, 739p, 743p, 747p, 750p, 755p, 800p, 806p], [837p, 839p, 843p, 847p, 850p, 855p, 900p, 906p], [937p, 939p, 943p, 947p, 950p, 955p, 1000p, 1006p], [1037p, 1039p, 1043p, 1047p, 1050p, 1055p, 1100p, 1106p], [1138p, 1140p, 1144p, 1148p, 1151p, 1156p, 1201a, 1207a]]
  -
  time_points: [Cooleman Court, Holder, Weston Primary, Woden Bus Station (Platform 10), Kings Ave / National Circuit, Russell Offices, ADFA, Campbell Park Offices]
  long_name: To Campbell Park Offices
  between_stops:
  Kings Ave / National Circuit-Russell Offices: [Wjz4Quk, Wjz4RwH, Wjz4RFJ]
  ADFA-Campbell Park Offices: [Wjzcend, Wjzce4H, Wjzce7O]
  Russell Offices-ADFA: [Wjzc60A, Wjzc60i, Wjzc55s, Wjzc54R, Wjzce7O, Wjzce4H, Wjzcend]
  short_name: 25 225
  stop_times: [[612a, 622a, 625a, 634a, "-", "-", "-", "-"], [642a, 652a, 655a, 705a, 719a, 722a, 726a, 730a], [702a, 712a, 715a, 725a, 739a, 743a, 747a, 751a], [734a, 749a, 752a, 805a, 819a, 823a, 827a, 831a], [808a, 823a, 826a, 838a, "-", "-", "-", "-"], [838a, 853a, 856a, 908a, "-", "-", "-", "-"], [910a, 925a, 928a, 938a, "-", "-", "-", "-"], [1012a, 1022a, 1025a, 1035a, "-", "-", "-", "-"], [1112a, 1122a, 1125a, 1135a, "-", "-", "-", "-"], [1212p, 1222p, 1225p, 1235p, "-", "-", "-", "-"], [112p, 122p, 125p, 135p, "-", "-", "-", "-"], [212p, 222p, 225p, 235p, "-", "-", "-", "-"], [312p, 324p, 327p, 336p, "-", "-", "-", "-"], [342p, 354p, 357p, 406p, "-", "-", "-", "-"], [412p, 424p, 427p, 436p, "-", "-", "-", "-"], [512p, 524p, 527p, 536p, "-", "-", "-", "-"], [622p, 633p, 636p, 645p, "-", "-", "-", "-"], [722p, 732p, 735p, 744p, "-", "-", "-", "-"], [822p, 832p, 835p, 844p, "-", "-", "-", "-"], [922p, 932p, 935p, 944p, "-", "-", "-", "-"], [1022p, 1032p, 1035p, 1044p, "-", "-", "-", "-"]]
  -
  time_points: [Campbell Park Offices, ADFA, Hospice / Menindee Dr, Russell Offices, St Thomas More's Campbell, City Bus Station]
  long_name: To City Bus Station
  between_stops:
  ADFA-Hospice / Menindee Dr: [WjzceHt, WjzceFT, WjzcdDs, Wjzcdsn, Wjzcdi7, Wjzcd2U, Wjzcd8D]
  Campbell Park Offices-ADFA: [Wjzce7O, Wjzce4H, Wjzcend]
  short_name: "9"
  stop_times: [["-", 655a, 701a, 703a, 708a, 720a], [720a, 723a, 729a, 731a, 736a, 751a], [752a, 756a, 804a, 806a, 811a, 826a], [822a, 826a, 834a, 836a, 841a, 856a], [852a, 856a, 904a, 906a, 911a, 926a], [934a, 937a, 945a, 947a, 952a, 1006a], [1034a, 1037a, 1045a, 1047a, 1052a, 1106a], [1134a, 1137a, 1145a, 1147a, 1152a, 1206p], [1234p, 1237p, 1245p, 1247p, 1252p, 106p], [134p, 137p, 145p, 147p, 152p, 206p], [234p, 237p, 245p, 247p, 252p, 306p], [335p, 339p, 347p, 349p, 354p, 409p], [352p, 356p, 404p, 406p, 411p, 426p], [422p, 426p, 434p, 436p, 441p, 456p], [452p, 456p, 504p, 506p, 511p, 526p], [522p, 526p, 534p, 536p, 541p, 556p], [552p, 556p, 604p, 606p, 611p, 626p], [628p, 632p, 638p, 640p, 645p, 656p], [728p, 731p, 737p, 739p, 744p, 755p], [828p, 831p, 837p, 839p, 844p, 855p], [928p, 931p, 937p, 939p, 944p, 955p], [1028p, 1031p, 1037p, 1039p, 1044p, 1055p]]
  -
  time_points: [Woden Bus Station (Platform 11), Athllon / Sulwood Kambah, MacKillop College Wanniassa Campus, Monash Primary, Tuggeranong Bus Station]
  long_name: To Tuggeranong Bus Station
  between_stops:
  Woden Bus Station (Platform 11)-Athllon / Sulwood Kambah: [Wjz3kAx, Wjz3kyX, Wjz3kwU, Wjz3iFK, Wjz3hL_, Wjz3gK-, Wjz3gQn, Wjz3gMq, Wjz2mTK]
  short_name: "64"
  stop_times: [["-", "-", 651a, 655a, 702a], [706a, 714a, 721a, 725a, 733a], ["-", "-", 751a, 756a, 805a], [806a, 816a, 823a, 828a, 837a], [836a, 846a, 853a, 858a, 907a], [906a, 916a, 923a, 928a, 936a], [1006a, 1015a, 1022a, 1026a, 1034a], [1106a, 1115a, 1122a, 1126a, 1134a], [1206p, 1215p, 1222p, 1226p, 1234p], [106p, 115p, 122p, 126p, 134p], [206p, 215p, 222p, 226p, 234p], [306p, 316p, 323p, 328p, 337p], [336p, 346p, 353p, 358p, 407p], [406p, 416p, 423p, 428p, 437p], [436p, 446p, 453p, 458p, 507p], [506p, 516p, 523p, 528p, 537p], [536p, 546p, 553p, 558p, 607p], [606p, 616p, 623p, 628p, 636p], [706p, 715p, 722p, 726p, 734p], [806p, 815p, 822p, 826p, 834p], [906p, 915p, 922p, 926p, 934p], [1006p, 1015p, 1022p, 1026p, 1034p], [1106p, 1115p, 1122p, 1126p, 1134p]]
  -
  time_points: [Cohen Street Bus Station (Platform 1), Westfield Bus Station (Platform 1), Belconnen Community Bus Station (Platform 2), Chuculba / William Slim Dr, Federation Square, Nicholls Primary, Ngunnawal Primary, Gungahlin Marketplace]
  long_name: To Gungahlin Market Place
  between_stops:
  Belconnen Community Bus Station (Platform 2)-Chuculba / William Slim Dr: [Wjz69gA, Wjz69ht, Wjz69uI, Wjz69vO, Wjz6mip]
  Cohen Street Bus Station (Platform 1)-Westfield Bus Station (Platform 1): []
  Westfield Bus Station (Platform 1)-Belconnen Community Bus Station (Platform 2): []
  short_name: "951"
  stop_times_sunday: [[920a, 922a, 926a, 934a, 939a, 944a, 954a, 1004a], [1020a, 1022a, 1026a, 1034a, 1039a, 1044a, 1054a, 1104a], [1120a, 1122a, 1126a, 1134a, 1139a, 1144a, 1154a, 1204p], [1220p, 1222p, 1226p, 1234p, 1239p, 1244p, 1254p, 104p], [120p, 122p, 126p, 134p, 139p, 144p, 154p, 204p], [220p, 222p, 226p, 234p, 239p, 244p, 254p, 304p], [320p, 322p, 326p, 334p, 339p, 344p, 354p, 404p], [420p, 422p, 426p, 434p, 439p, 444p, 454p, 504p], [520p, 522p, 526p, 534p, 539p, 544p, 554p, 604p], [620p, 622p, 626p, 634p, 639p, 644p, 654p, 704p]]
  -
  time_points: [Cooleman Court, Rivett, Duffy Primary, Holder, City West, City Bus Station, ACTEW AGL House]
  long_name: To ACTEW AGL House
  between_stops:
  City Bus Station-ACTEW AGL House: [Wjz5Nht]
  City West-City Bus Station: []
  short_name: "729"
  stop_times: [[709a, 715a, 724a, 728a, 749a, 753a, 755a], [739a, 745a, 754a, 758a, 819a, 823a, 825a]]
  -
  time_points: [City West, City Bus Station (Platform 10), Russell Offices, Kings Ave / National Circuit, Woden Bus Station (Platform 12), Erindale Centre, Bugden Sternberg, Gowrie, MacKillop College Isabella Campus, Tuggeranong Bus Station]
  long_name: To Tuggeranong Bus Station
  between_stops:
  Russell Offices-Kings Ave / National Circuit: [Wjz4RFJ, Wjz4RwH, Wjz4Quk]
  City West-City Bus Station (Platform 10): []
  City Bus Station (Platform 10)-Russell Offices: [Wjz5Nht, Wjz5MsD, Wjz5MsT, Wjz5MEL, Wjz4T-X, Wjz4_jm, Wjz4_wS, Wjz4-KO, Wjz4-Rc, Wjz4-WL, Wjz4-WZ]
  short_name: 65 265
  stop_times: [["-", "-", "-", "-", "-", "-", 604a, 608a, 619a, 625a], ["-", "-", "-", "-", 625a, 637a, 638a, 643a, 654a, 700a], ["-", "-", "-", "-", 655a, 710a, 711a, 718a, 734a, 744a], ["-", "-", "-", "-", 725a, 742a, 743a, 750a, 806a, 816a], ["-", "-", "-", "-", 755a, 812a, 813a, 820a, 836a, 846a], ["-", "-", "-", "-", 825a, 842a, 843a, 850a, 906a, 916a], ["-", "-", "-", "-", 855a, 912a, 913a, 920a, 935a, 943a], ["-", "-", "-", "-", 955a, 1009a, 1010a, 1015a, 1027a, 1035a], ["-", "-", "-", "-", 1055a, 1109a, 1110a, 1115a, 1127a, 1135a], ["-", "-", "-", "-", 1155a, 1209p, 1210p, 1215p, 1227p, 1235p], ["-", "-", "-", "-", 1255p, 109p, 110p, 115p, 127p, 135p], ["-", "-", "-", "-", 155p, 209p, 210p, 215p, 227p, 235p], ["-", "-", "-", "-", 255p, 311p, 312p, 318p, 332p, 341p], ["-", "-", "-", "-", 325p, 342p, 343p, 349p, 403p, 412p], ["-", "-", "-", "-", 355p, 412p, 413p, 419p, 433p, 442p], ["-", "-", "-", "-", 420p, 437p, 438p, 444p, 458p, 507p], ["-", "-", "-", "-", 455p, 512p, 513p, 519p, 533p, 542p], [455p, 501p, 510p, 513p, 528p, 545p, 546p, 552p, 606p, 615p], [525p, 531p, 540p, 543p, 558p, 615p, 616p, 622p, 635p, 643p], [555p, 601p, 610p, 613p, 628p, 642p, 643p, 648p, 700p, 708p], ["-", "-", "-", "-", 654p, 708p, 709p, 714p, 726p, 734p], ["-", "-", "-", "-", 754p, 808p, 809p, 814p, 826p, 834p], ["-", "-", "-", "-", 854p, 908p, 909p, 914p, 926p, 934p], ["-", "-", "-", "-", 954p, 1008p, 1009p, 1014p, 1026p, 1034p], ["-", "-", "-", "-", 1054p, 1108p, 1109p, 1114p, 1126p, 1134p]]
  -
  time_points: [Sydney Ave, Russell Offices, City Bus Station (Platform 11), Macarthur / Northbourne Ave, Northbourne Avenue / Antill St, William Webb / Ginninderra Drive, Copland College, Spence, Spence Terminus]
  long_name: To Spence Terminus
  between_stops:
  Macarthur / Northbourne Ave-Northbourne Avenue / Antill St: [Wjz5RkN, Wjz5Rsi, Wjz5RvC, Wjz5Sqk, Wjz5SrO, Wjz5Sux, Wjz5SDc]
  Russell Offices-City Bus Station (Platform 11): [Wjz4-WZ, Wjz4-WL, Wjz4-Rc, Wjz4-KO, Wjz4_wS, Wjz4_jm, Wjz4T-X, Wjz5MEL, Wjz5MsT, Wjz5MsD, Wjz5Nht]
  City Bus Station (Platform 11)-Macarthur / Northbourne Ave: [Wjz5O3Q, Wjz5Oci, Wjz5P8n, Wjz5P8K, Wjz5PdJ, Wjz5Pl0, Wjz5Qi2, Wjz5Qgn, Wjz5Qmu, Wjz5QmR]
  short_name: "701"
  stop_times: [[442p, 450p, 502p, 509p, 512p, 522p, 527p, 534p, 540p], ["-", "-", 520p, 527p, 529p, 539p, 543p, 550p, 554p], [525p, 533p, 543p, 550p, 552p, 602p, 606p, 613p, 617p], [542p, 550p, 600p, 607p, 609p, 619p, 623p, 630p, 634p]]
- -
time_points: [City Bus Station (Platform 8), Macarthur / Northbourne Ave, Northbourne Avenue / Antill St, Flemington Rd / Sandford St, Hibberson / Kate Crace, Gungahlin Marketplace, Katherine Ave / Horse Park Drive, Paul Coe / Mirrabei Dr, Chuculba / William Slim Dr, Belconnen Community Bus Station, Westfield Bus Station, Cohen Street Bus Station] time_points: [City Bus Station (Platform 8), Macarthur / Northbourne Ave, Northbourne Avenue / Antill St, Flemington Rd / Sandford St, Hibberson / Kate Crace, Gungahlin Marketplace, Katherine Ave / Horse Park Drive, Paul Coe / Mirrabei Dr, Chuculba / William Slim Dr, Belconnen Community Bus Station, Westfield Bus Station, Cohen Street Bus Station]
long_name: To Cohen Street Bus Station long_name: To Cohen Street Bus Station
between_stops: between_stops:
Westfield Bus Station-Cohen Street Bus Station: [] Westfield Bus Station-Cohen Street Bus Station: []
Macarthur / Northbourne Ave-Northbourne Avenue / Antill St: [Wjz5RkN, Wjz5Rsi, Wjz5RvC, Wjz5Sqk, Wjz5SrO, Wjz5Sux, Wjz5SDc] Macarthur / Northbourne Ave-Northbourne Avenue / Antill St: [Wjz5RkN, Wjz5Rsi, Wjz5RvC, Wjz5Sqk, Wjz5SrO, Wjz5Sux, Wjz5SDc]
City Bus Station (Platform 8)-Macarthur / Northbourne Ave: [Wjz5O3Q, Wjz5Oci, Wjz5P8n, Wjz5P8K, Wjz5PdJ, Wjz5Pl0, Wjz5Qi2, Wjz5Qgn, Wjz5Qmu, Wjz5QmR] City Bus Station (Platform 8)-Macarthur / Northbourne Ave: [Wjz5O3Q, Wjz5Oci, Wjz5P8n, Wjz5P8K, Wjz5PdJ, Wjz5Pl0, Wjz5Qi2, Wjz5Qgn, Wjz5Qmu, Wjz5QmR]
Belconnen Community Bus Station-Westfield Bus Station: [] Belconnen Community Bus Station-Westfield Bus Station: []
Chuculba / William Slim Dr-Belconnen Community Bus Station: [Wjz6mip, Wjz69vO, Wjz69uI, Wjz69ht, Wjz69gA] Chuculba / William Slim Dr-Belconnen Community Bus Station: [Wjz6mip, Wjz69vO, Wjz69uI, Wjz69ht, Wjz69gA]
short_name: "59" short_name: "59"
stop_times: [["-", "-", "-", "-", 645a, 648a, 703a, 709a, 718a, 734a, 736a, 741a], ["-", "-", "-", "-", 710a, 713a, 728a, 734a, 743a, 800a, 802a, 807a], ["-", "-", "-", "-", 730a, 733a, 748a, 754a, 803a, 820a, 822a, 827a], ["-", "-", "-", "-", 755a, 758a, 813a, 819a, 828a, 845a, 847a, 852a], ["-", "-", "-", "-", 815a, 818a, 833a, 839a, 848a, 905a, 907a, 912a], ["-", "-", "-", "-", 907a, 910a, 925a, 931a, 940a, 956a, 958a, 1003a], ["-", "-", "-", "-", 1007a, 1010a, 1025a, 1031a, 1040a, 1056a, 1058a, 1103a], ["-", "-", "-", "-", 1107a, 1110a, 1125a, 1131a, 1140a, 1156a, 1158a, 1203p], ["-", "-", "-", "-", 1207p, 1210p, 1225p, 1231p, 1240p, 1256p, 1258p, 103p], ["-", "-", "-", "-", 107p, 110p, 125p, 131p, 140p, 156p, 158p, 203p], ["-", "-", "-", "-", 207p, 210p, 225p, 231p, 240p, 256p, 258p, 303p], ["-", "-", "-", "-", 307p, 310p, 325p, 331p, 340p, 356p, 358p, 403p], [328p, 334p, 336p, 344p, 347p, 350p, 405p, 411p, 421p, 438p, 440p, 445p], [337p, 343p, 345p, 353p, 356p, 359p, 414p, 420p, 430p, 447p, 449p, 454p], [351p, 357p, 359p, 408p, 413p, 416p, 431p, 437p, 447p, 504p, 506p, 511p], [409p, 416p, 418p, 427p, 432p, 435p, 450p, 456p, 506p, 523p, 525p, 530p], [423p, 430p, 432p, 441p, 446p, 449p, 504p, 510p, 520p, 537p, 539p, 544p], [437p, 444p, 446p, 455p, 500p, 503p, 518p, 524p, 534p, 551p, 553p, 558p], [453p, 500p, 502p, 511p, 516p, 519p, 534p, 540p, 550p, 607p, 609p, 614p], [507p, 514p, 516p, 525p, 530p, 533p, 548p, 554p, 604p, 620p, 622p, 627p], [524p, 531p, 533p, 542p, 547p, 550p, 605p, 611p, 620p, 636p, 638p, 643p], [544p, 551p, 553p, 602p, 605p, 608p, 623p, 629p, 638p, 654p, 656p, 701p], [557p, 603p, 605p, 612p, 615p, 618p, 633p, 639p, 648p, 704p, 706p, 711p], ["-", "-", "-", "-", 707p, 710p, 725p, 731p, 740p, 756p, 758p, 803p], ["-", "-", "-", "-", 807p, 810p, 825p, 831p, 840p, 856p, 858p, 903p], ["-", "-", "-", "-", 907p, 910p, 925p, 931p, 940p, 956p, 958p, 1003p], ["-", "-", "-", "-", 1007p, 1010p, 1025p, 1031p, 1040p, 1056p, 1058p, 1103p], ["-", "-", "-", "-", 1107p, 1110p, 1125p, 1131p, 1140p, 1156p, 1158p, 1203a]] stop_times: [["-", "-", "-", "-", 645a, 648a, 703a, 709a, 718a, 734a, 736a, 741a], ["-", "-", "-", "-", 710a, 713a, 728a, 734a, 743a, 800a, 802a, 807a], ["-", "-", "-", "-", 730a, 733a, 748a, 754a, 803a, 820a, 822a, 827a], ["-", "-", "-", "-", 755a, 758a, 813a, 819a, 828a, 845a, 847a, 852a], ["-", "-", "-", "-", 815a, 818a, 833a, 839a, 848a, 905a, 907a, 912a], ["-", "-", "-", "-", 907a, 910a, 925a, 931a, 940a, 956a, 958a, 1003a], ["-", "-", "-", "-", 1007a, 1010a, 1025a, 1031a, 1040a, 1056a, 1058a, 1103a], ["-", "-", "-", "-", 1107a, 1110a, 1125a, 1131a, 1140a, 1156a, 1158a, 1203p], ["-", "-", "-", "-", 1207p, 1210p, 1225p, 1231p, 1240p, 1256p, 1258p, 103p], ["-", "-", "-", "-", 107p, 110p, 125p, 131p, 140p, 156p, 158p, 203p], ["-", "-", "-", "-", 207p, 210p, 225p, 231p, 240p, 256p, 258p, 303p], ["-", "-", "-", "-", 307p, 310p, 325p, 331p, 340p, 356p, 358p, 403p], [328p, 334p, 336p, 344p, 347p, 350p, 405p, 411p, 421p, 438p, 440p, 445p], [337p, 343p, 345p, 353p, 356p, 359p, 414p, 420p, 430p, 447p, 449p, 454p], [351p, 357p, 359p, 408p, 413p, 416p, 431p, 437p, 447p, 504p, 506p, 511p], [409p, 416p, 418p, 427p, 432p, 435p, 450p, 456p, 506p, 523p, 525p, 530p], [423p, 430p, 432p, 441p, 446p, 449p, 504p, 510p, 520p, 537p, 539p, 544p], [437p, 444p, 446p, 455p, 500p, 503p, 518p, 524p, 534p, 551p, 553p, 558p], [453p, 500p, 502p, 511p, 516p, 519p, 534p, 540p, 550p, 607p, 609p, 614p], [507p, 514p, 516p, 525p, 530p, 533p, 548p, 554p, 604p, 620p, 622p, 627p], [524p, 531p, 533p, 542p, 547p, 550p, 605p, 611p, 620p, 636p, 638p, 643p], [544p, 551p, 553p, 602p, 605p, 608p, 623p, 629p, 638p, 654p, 656p, 701p], [557p, 603p, 605p, 612p, 615p, 618p, 633p, 639p, 648p, 704p, 706p, 711p], ["-", "-", "-", "-", 707p, 710p, 725p, 731p, 740p, 756p, 758p, 803p], ["-", "-", "-", "-", 807p, 810p, 825p, 831p, 840p, 856p, 858p, 903p], ["-", "-", "-", "-", 907p, 910p, 925p, 931p, 940p, 956p, 958p, 1003p], ["-", "-", "-", "-", 1007p, 1010p, 1025p, 1031p, 1040p, 1056p, 1058p, 1103p], ["-", "-", "-", "-", 1107p, 1110p, 1125p, 1131p, 1140p, 1156p, 1158p, 1203a]]
- -
time_points: [Woden Bus Station (Platform 15), Pearce Shops, Southlands Mawson, Torrens Shops, Chifley Shops, Lyons Shops, Woden Bus Station] time_points: [Spence Terminus, Evatt, Copland College, McKellar, Cohen Street Bus Station (Platform 3), Westfield Bus Station (Platform 1), Belconnen Community Bus Station (Platform 1), City Bus Station (Platform 1), Woden Bus Station (Platform 6), Tuggeranong Bus Station]
long_name: To Woden Bus Station  
between_stops: {}  
   
short_name: "922"  
stop_times: [[1033a, 1039a, 1043a, 1049a, 1054a, 1058a, 1101a], [1233p, 1239p, 1243p, 1249p, 1254p, 1258p, 101p], [233p, 239p, 243p, 249p, 254p, 258p, 301p], [433p, 439p, 443p, 449p, 454p, 458p, 501p], [633p, 639p, 643p, 649p, 654p, 658p, 701p]]  
-  
time_points: [Tuggeranong Bus Station (Platform 7), Calwell Shops, Chisholm Shops, Erindale / Sternberg Cres, Woden Bus Station (Platform 10), Kings Ave / National Circuit, Russell Offices, City Bus Station (Platform 11), City West]  
long_name: To City West  
between_stops:  
Russell Offices-City Bus Station (Platform 11): [Wjz4-WZ, Wjz4-WL, Wjz4-Rc, Wjz4-KO, Wjz4_wS, Wjz4_jm, Wjz4T-X, Wjz5MEL, Wjz5MsT, Wjz5MsD, Wjz5Nht]  
Kings Ave / National Circuit-Russell Offices: [Wjz4Quk, Wjz4RwH, Wjz4RFJ]  
City Bus Station (Platform 11)-City West: []  
short_name: 67 267  
stop_times: [[603a, 615a, 627a, 635a, 644a, "-", "-", "-", "-"], [633a, 645a, 657a, 705a, 716a, "-", "-", "-", "-"], [702a, 715a, 726a, 735a, 750a, 804a, 808a, 818a, 821a], [718a, 730a, 745a, 755a, 809a, "-", "-", "-", "-"], [731a, 746a, 800a, 810a, 825a, 839a, 843a, 853a, 856a], [803a, 817a, 832a, 842a, 856a, "-", "-", "-", "-"], [833a, 847a, 902a, 912a, 926a, "-", "-", "-", "-"], [903a, 917a, 932a, 940a, 953a, "-", "-", "-", "-"], [1003a, 1016a, 1028a, 1036a, 1049a, "-", "-", "-", "-"], [1103a, 1116a, 1128a, 1136a, 1149a, "-", "-", "-", "-"], [1203p, 1216p, 1228p, 1236p, 1249p, "-", "-", "-", "-"], [103p, 116p, 128p, 136p, 149p, "-", "-", "-", "-"], [203p, 216p, 228p, 236p, 249p, "-", "-", "-", "-"], [303p, 317p, 332p, 342p, 356p, "-", "-", "-", "-"], [333p, 347p, 402p, 412p, 426p, "-", "-", "-", "-"], [403p, 417p, 432p, 442p, 456p, "-", "-", "-", "-"], [433p, 447p, 502p, 512p, 526p, "-", "-", "-", "-"], [503p, 517p, 532p, 542p, 556p, "-", "-", "-", "-"], [533p, 547p, 602p, 612p, 626p, "-", "-", "-", "-"], [603p, 617p, 632p, 640p, 653p, "-", "-", "-", "-"], [703p, 716p, 728p, 736p, 749p, "-", "-", "-", "-"], [803p, 816p, 828p, 836p, 849p, "-", "-", "-", "-"], [903p, 916p, 928p, 936p, 949p, "-", "-", "-", "-"], [1003p, 1016p, 1028p, 1036p, 1049p, "-", "-", "-", "-"], [1103p, 1116p, 1128p, 1136p, "-", "-", "-", "-", "-"]]  
-  
time_points: [Woden Bus Station (Platform 16), Weston Primary, Holder, Duffy, Cooleman Court]  
long_name: To Cooleman Court  
between_stops: {}  
   
stop_times_saturday: [[857a, 907a, 909a, 911a, 919a], [957a, 1007a, 1009a, 1011a, 1019a], [1057a, 1107a, 1109a, 1111a, 1119a], [1157a, 1207p, 1209p, 1211p, 1219p], [1257p, 107p, 109p, 111p, 119p], [157p, 207p, 209p, 211p, 219p], [257p, 307p, 309p, 311p, 319p], [357p, 407p, 409p, 411p, 419p], [457p, 507p, 509p, 511p, 519p], [557p, 607p, 609p, 611p, 619p], [657p, 707p, 709p, 711p, 719p], [757p, 807p, 809p, 811p, 819p], [857p, 907p, 909p, 911p, 919p], [957p, 1007p, 1009p, 1011p, 1019p], [1057p, 1107p, 1109p, 1111p, 1119p]]  
short_name: "925"  
-  
time_points: [Gungahlin Marketplace, Dickson College, Russell Offices, Brindabella Business Park, Fairbairn Park]  
long_name: To Fairbairn Park  
between_stops:  
Brindabella Business Park-Fairbairn Park: [WjzcrK3, WjzcrrQ, WjzcrEu, WjzcJ0K, WjzcBHZ, WjzcJ38]  
short_name: "757"  
stop_times: [[650a, 700a, 711a, 725a, 735a], [710a, 720a, 731a, 745a, 755a], [730a, 740a, 751a, 805a, 815a]]  
-  
time_points: [Gungahlin Marketplace, Nicholls Primary, Federation Square, Chuculba / William Slim Dr, Belconnen Community Bus Station, Westfield Bus Station, Cohen Street Bus Station]  
long_name: To Cohen Street Bus Station  
between_stops:  
Westfield Bus Station-Cohen Street Bus Station: []  
Belconnen Community Bus Station-Westfield Bus Station: []  
Chuculba / William Slim Dr-Belconnen Community Bus Station: [Wjz6mip, Wjz69vO, Wjz69uI, Wjz69ht, Wjz69gA]  
stop_times_saturday: [[0839a, 0847a, 0900a, 0905a, 0918a, 0920a, 0925a], [0939a, 0947a, 1000a, 1005a, 1018a, 1020a, 1025a], [1039a, 1047a, 1100a, 1105a, 1118a, 1120a, 1125a], [1139a, 1147a, 1200p, 1205p, 1218p, 1220p, 1225p], [1239p, 1247p, 0100p, 0105p, 0118p, 0120p, 0125p], [0139p, 0147p, 0200p, 0205p, 0218p, 0220p, 0225p], [0239p, 0247p, 0300p, 0305p, 0318p, 0320p, 0325p], [0339p, 0347p, 0400p, 0405p, 0418p, 0420p, 0425p], [0439p, 0447p, 0500p, 0505p, 0518p, 0520p, 0525p], [0539p, 0547p, 0600p, 0605p, 0618p, 0620p, 0625p], [0639p, 0647p, 0700p, 0705p, 0718p, 0720p, 0725p]]  
short_name: "952"  
-  
time_points: [Tuggeranong Bus Station (Platform 4), Isabella Shops, Calwell Shops, Theodore, Outtrim / Duggan, Tuggeranong Bus Station]  
long_name: To Tuggeranong Bus Station  
between_stops: {}  
   
stop_times_saturday: [[815a, 825a, 830a, 839a, 846a, 855a], [1015a, 1025a, 1030a, 1039a, 1046a, 1055a], [1215p, 1225p, 1230p, 1239p, 1246p, 1255p], [215p, 225p, 230p, 239p, 246p, 255p], [415p, 425p, 430p, 439p, 446p, 455p], [615p, 625p, 630p, 639p, 646p, 655p], [818p, 828p, 833p, 842p, 849p, 858p], [1018p, 1028p, 1033p, 1042p, 1049p, 1058p]]  
short_name: "912"  
-  
time_points: [Cohen Street Bus Station (Platform 1), Westfield Bus Station (Platform 1), Belconnen Community Bus Station (Platform 3), Calvary Hospital, Jamison Centre, Cook Shops, Hawker Shops, Page Shops, Florey Shops, Cohen Street Bus Station, Westfield Bus Station, Belconnen Community Bus Station]  
long_name: To Belconnen Community Bus Station  
between_stops:  
Westfield Bus Station (Platform 1)-Belconnen Community Bus Station (Platform 3): []  
Cohen Street Bus Station (Platform 1)-Westfield Bus Station (Platform 1): []  
Westfield Bus Station-Belconnen Community Bus Station: []  
Cohen Street Bus Station-Westfield Bus Station: []  
short_name: "74"  
stop_times: [[950a, 952a, 956a, 1005a, 1012a, 1015a, 1023a, 1027a, 1033a, 1039a, 1041a, 1045a], [1120a, 1122a, 1126a, 1135a, 1142a, 1145a, 1153a, 1157a, 1203p, 1209p, 1211p, 1215p], [1250p, 1252p, 1256p, 105p, 112p, 115p, 123p, 127p, 133p, 139p, 141p, 145p], [220p, 222p, 226p, 235p, 242p, 245p, 253p, 257p, 303p, 309p, 311p, 315p]]  
-  
time_points: [City Bus Station (Platform 8), Macarthur / Northbourne Ave, Flemington Rd / Sandford St, Kosciuszko / Everard, Gungahlin Marketplace, Chuculba / William Slim Dr, William Webb / Ginninderra Drive, Belconnen Community Bus Station, Westfield Bus Station, Cohen Street Bus Station]  
long_name: To Cohen Street Bus Station  
between_stops:  
Westfield Bus Station-Cohen Street Bus Station: []  
City Bus Station (Platform 8)-Macarthur / Northbourne Ave: [Wjz5O3Q, Wjz5Oci, Wjz5P8n, Wjz5P8K, Wjz5PdJ, Wjz5Pl0, Wjz5Qi2, Wjz5Qgn, Wjz5Qmu, Wjz5QmR]  
Belconnen Community Bus Station-Westfield Bus Station: []  
short_name: "956"  
stop_times: [[838a, 844a, 852a, 859a, 909a, 919a, 924a, 930a, 932a, 937a], [938a, 944a, 952a, 959a, 1009a, 1019a, 1024a, 1030a, 1032a, 1037a], [1038a, 1044a, 1052a, 1059a, 1109a, 1119a, 1124a, 1130a, 1132a, 1137a], [1138a, 1144a, 1152a, 1159a, 1209p, 1219p, 1224p, 1230p, 1232p, 1237p], [1238p, 1244p, 1252p, 1259p, 109p, 119p, 124p, 130p, 132p, 137p], [138p, 144p, 152p, 159p, 209p, 219p, 224p, 230p, 232p, 237p], [238p, 244p, 252p, 259p, 309p, 319p, 324p, 330p, 332p, 337p], [338p, 344p, 352p, 359p, 409p, 419p, 424p, 430p, 432p, 437p], [438p, 444p, 452p, 459p, 509p, 519p, 524p, 530p, 532p, 537p], [538p, 544p, 552p, 559p, 609p, 619p, 624p, 630p, 632p, 637p], [638p, 644p, 652p, 659p, 709p, 719p, 724p, 730p, 732p, 737p]]  
-  
time_points: [Alexander Maconochie Centre, Woden Bus Station]  
long_name: To Woden Bus Station  
between_stops: {}  
   
stop_times_saturday: [[1130a, 1150a], [320p, 340p], [730p, 750p]]  
short_name: "988"  
-  
time_points: [Cohen Street Bus Station (Platform 1), Westfield Bus Station (Platform 1), Belconnen Community Bus Station (Platform 2), Chuculba / William Slim Dr, Ngunnawal Primary, Shoalhaven / Katherine Ave, Gungahlin Marketplace, Anthony Rolfe Av / Moonlight Av, Flemington Rd / Nullabor Ave, Flemington Rd / Sandford St, Macarthur / Northbourne Ave, City Bus Station]  
long_name: To City Bus Station  
between_stops:  
Belconnen Community Bus Station (Platform 2)-Chuculba / William Slim Dr: [Wjz69gA, Wjz69ht, Wjz69uI, Wjz69vO, Wjz6mip]  
Macarthur / Northbourne Ave-City Bus Station: [Wjz5QmR, Wjz5Qmu, Wjz5Qgn, Wjz5Qi2, Wjz5Pl0, Wjz5PdJ, Wjz5P8K, Wjz5P8n, Wjz5Oci, Wjz5O3Q]  
Cohen Street Bus Station (Platform 1)-Westfield Bus Station (Platform 1): []  
Westfield Bus Station (Platform 1)-Belconnen Community Bus Station (Platform 2): []  
short_name: "958"  
stop_times_sunday: [[852a, 854a, 858a, 908a, 919a, 927a, 935a, 944a, 951a, 958a, 1006a, 1013a], [952a, 954a, 958a, 1008a, 1019a, 1027a, 1035a, 1044a, 1051a, 1058a, 1106a, 1113a], [1052a, 1054a, 1058a, 1108a, 1119a, 1127a, 1135a, 1144a, 1151a, 1158a, 1206p, 1213p], [1152a, 1154a, 1158a, 1208p, 1219p, 1227p, 1235p, 1244p, 1251p, 1258p, 106p, 113p], [1252p, 1254p, 1258p, 108p, 119p, 127p, 135p, 144p, 151p, 158p, 206p, 213p], [152p, 154p, 158p, 208p, 219p, 227p, 235p, 244p, 251p, 258p, 306p, 313p], [252p, 254p, 258p, 308p, 319p, 327p, 335p, 344p, 351p, 358p, 406p, 413p], [352p, 354p, 358p, 408p, 419p, 427p, 435p, 444p, 451p, 458p, 506p, 513p], [452p, 454p, 458p, 508p, 519p, 527p, 535p, 544p, 551p, 558p, 606p, 613p], [552p, 554p, 558p, 608p, 619p, 627p, 635p, 644p, 651p, 658p, 706p, 713p], [652p, 654p, 658p, 708p, 719p, 727p, 735p, 744p, 751p, 758p, 806p, 813p]]  
-  
time_points: [Cooleman Court, Duffy, Holder, Weston Primary, Woden Bus Station]  
long_name: To Woden Bus Station  
between_stops: {}  
   
short_name: "925"  
stop_times: [[924a, 931a, 934a, 937a, 946a], [1024a, 1031a, 1034a, 1037a, 1046a], [1124a, 1131a, 1134a, 1137a, 1146a], [1224p, 1231p, 1234p, 1237p, 1246p], [124p, 131p, 134p, 137p, 146p], [224p, 231p, 234p, 237p, 246p], [324p, 331p, 334p, 337p, 346p], [424p, 431p, 434p, 437p, 446p], [524p, 531p, 534p, 537p, 546p], [624p, 631p, 634p, 637p, 646p]]  
-  
time_points: [Tuggeranong Bus Station (Platform 7), Heagney / Clift Richardson, Chisholm Shops, Erindale Centre, Tuggeranong Bus Station]  
long_name: To Tuggeranong Bus Station  
between_stops: {}  
   
short_name: "968"  
stop_times_sunday: [[1003a, 1016a, 1024a, 1038a, 1048a], [1203p, 1216p, 1224p, 1238p, 1248p], [203p, 216p, 224p, 238p, 248p], [403p, 416p, 424p, 438p, 448p], [603p, 616p, 624p, 638p, 648p]]  
-  
time_points: [Cohen Street Bus Station (Platform 2), Westfield Bus Station (Platform 1), Belconnen Community Bus Station (Platform 3), Calvary Hospital, O'Connor Shops, Burton and Garran Hall Daley Road, National Museum of Australia, City Bus Station (Platform 2), Kings Ave / National Circuit, Deakin Shops, Hughes Shops, Garran Shops, Canberra Hospital, Woden Bus Station]  
long_name: To Woden Bus Station  
between_stops:  
Westfield Bus Station (Platform 1)-Belconnen Community Bus Station (Platform 3): []  
City Bus Station (Platform 2)-Kings Ave / National Circuit: [Wjz5FOn, Wjz4S1U, Wjz4Rs-, Wjz4RFJ, Wjz4RwH, Wjz4Quk]  
Cohen Street Bus Station (Platform 2)-Westfield Bus Station (Platform 1): []  
Canberra Hospital-Woden Bus Station: [Wjz3mWn, Wjz3mPO, Wjz3mAg]  
stop_times_saturday: [[729a, 731a, 735a, 752a, 759a, 804a, 809a, 819a, 828a, 837a, 842a, 846a, 848a, 855a], [829a, 831a, 835a, 852a, 859a, 904a, 909a, 919a, 928a, 937a, 942a, 946a, 948a, 955a], [929a, 931a, 935a, 952a, 959a, 1004a, 1009a, 1019a, 1028a, 1037a, 1042a, 1046a, 1048a, 1055a], [1029a, 1031a, 1035a, 1052a, 1059a, 1104a, 1109a, 1119a, 1128a, 1137a, 1142a, 1146a, 1148a, 1155a], [1129a, 1131a, 1135a, 1152a, 1159a, 1204p, 1209p, 1219p, 1228p, 1237p, 1242p, 1246p, 1248p, 1255p], [1229p, 1231p, 1235p, 1252p, 1259p, 104p, 109p, 119p, 128p, 137p, 142p, 146p, 148p, 155p], [129p, 131p, 135p, 152p, 159p, 204p, 209p, 219p, 228p, 237p, 242p, 246p, 248p, 255p], [229p, 231p, 235p, 252p, 259p, 304p, 309p, 319p, 328p, 337p, 342p, 346p, 348p, 355p], [329p, 331p, 335p, 352p, 359p, 404p, 409p, 419p, 428p, 437p, 442p, 446p, 448p, 455p], [429p, 431p, 435p, 452p, 459p, 504p, 509p, 519p, 528p, 537p, 542p, 546p, 548p, 555p], [529p, 531p, 535p, 552p, 559p, 604p, 609p, 619p, 628p, 637p, 642p, 646p, 648p, 655p], [629p, 631p, 635p, 652p, 659p, 704p, 709p, 719p, 728p, 737p, 742p, 746p, 748p, 755p], [729p, 731p, 735p, 752p, 759p, 804p, 809p, 819p, 828p, 837p, 842p, 846p, 848p, 855p], [829p, 831p, 835p, 852p, 859p, 904p, 909p, 919p, 928p, 937p, 942p, 946p, 948p, 955p], [929p, 931p, 935p, 952p, 959p, 1004p, 1009p, 1019p, 1028p, 1037p, 1042p, 1046p, 1048p, 1055p], [1029p, 1031p, 1035p, 1052p, 1059p, 1104p, 1109p, 1117p, "-", "-", "-", "-", "-", "-"]]  
short_name: "934"  
-  
time_points: [Cohen Street Bus Station (Platform 2), Westfield Bus Station (Platform 1), Belconnen Community Bus Station (Platform 1), City Bus Station (Platform 1), Woden Bus Station (Platform 6), Erindale Centre, Tuggeranong Bus Station]  
long_name: To Tuggeranong Bus Station  
between_stops:  
Cohen Street Bus Station (Platform 2)-Westfield Bus Station (Platform 1): []  
Westfield Bus Station (Platform 1)-Belconnen Community Bus Station (Platform 1): []  
short_name: "900"  
stop_times_sunday: [[731a, 733a, 737a, 757a, 814a, 829a, 835a], [746a, 748a, 752a, 812a, 829a, 844a, 850a], [801a, 803a, 807a, 827a, 844a, 859a, 905a], [816a, 818a, 822a, 842a, 859a, 914a, 920a], [831a, 833a, 837a, 857a, 914a, 929a, 935a], [846a, 848a, 852a, 912a, 929a, 944a, 950a], [901a, 903a, 907a, 927a, 944a, 959a, 1005a], [916a, 918a, 922a, 942a, 959a, 1014a, 1020a], [931a, 933a, 937a, 957a, 1014a, 1029a, 1035a], [946a, 948a, 952a, 1012a, 1029a, 1044a, 1050a], [1001a, 1003a, 1007a, 1027a, 1044a, 1059a, 1105a], [1016a, 1018a, 1022a, 1042a, 1059a, 1114a, 1120a], [1031a, 1033a, 1037a, 1057a, 1114a, 1129a, 1135a], [1046a, 1048a, 1052a, 1112a, 1129a, 1144a, 1150a], [1101a, 1103a, 1107a, 1127a, 1144a, 1159a, 1205p], [1116a, 1118a, 1122a, 1142a, 1159a, 1214p, 1220p], [1131a, 1133a, 1137a, 1157a, 1214p, 1229p, 1235p], [1146a, 1148a, 1152a, 1212p, 1229p, 1244p, 1250p], [1201p, 1203p, 1207p, 1227p, 1244p, 1259p, 105p], [1216p, 1218p, 1222p, 1242p, 1259p, 114p, 120p], [1231p, 1233p, 1237p, 1257p, 114p, 129p, 135p], [1246p, 1248p, 1252p, 112p, 129p, 144p, 150p], [101p, 103p, 107p, 127p, 144p, 159p, 205p], [116p, 118p, 122p, 142p, 159p, 214p, 220p], [131p, 133p, 137p, 157p, 214p, 229p, 235p], [146p, 148p, 152p, 212p, 229p, 244p, 250p], [201p, 203p, 207p, 227p, 244p, 259p, 305p], [216p, 218p, 222p, 242p, 259p, 314p, 320p], [231p, 233p, 237p, 257p, 314p, 329p, 335p], [246p, 248p, 252p, 312p, 329p, 344p, 350p], [301p, 303p, 307p, 327p, 344p, 359p, 405p], [316p, 318p, 322p, 342p, 359p, 414p, 420p], [331p, 333p, 337p, 357p, 414p, 429p, 435p], [346p, 348p, 352p, 412p, 429p, 444p, 450p], [401p, 403p, 407p, 427p, 444p, 459p, 505p], [416p, 418p, 422p, 442p, 459p, 514p, 520p], [431p, 433p, 437p, 457p, 514p, 529p, 535p], [446p, 448p, 452p, 512p, 529p, 544p, 550p], [501p, 503p, 507p, 527p, 544p, 559p, 605p], [516p, 518p, 522p, 542p, 559p, 614p, 620p], [531p, 533p, 537p, 557p, 614p, 629p, 635p], [546p, 548p, 552p, 612p, 629p, 643p, 649p], [601p, 603p, 607p, 627p, 642p, 656p, 702p], [616p, 618p, 622p, 641p, 655p, 709p, 715p], [631p, 633p, 637p, 656p, 710p, 724p, 730p], [646p, 648p, 652p, 711p, 725p, 739p, 745p], [701p, 703p, 707p, 726p, 740p, 754p, 800p]]  
-  
time_points: [Woden Bus Station (Platform 15), Canberra Hospital, Isaacs Shops, Farrer Terminus, Southlands Mawson, Chifley Shops, Lyons Shops, Woden Bus Station]  
long_name: To Woden Bus Station  
between_stops:  
Woden Bus Station (Platform 15)-Canberra Hospital: [Wjz3mAg, Wjz3mPO, Wjz3mWn]  
short_name: "24"  
stop_times: [["-", "-", "-", 703a, 709a, 715a, 720a, 724a], [702a, 708a, 715a, 720a, 726a, 732a, 737a, 742a], [739a, 746a, 754a, 800a, 806a, 813a, 818a, 823a], [809a, 816a, 824a, 830a, 836a, 843a, 848a, 853a], [839a, 846a, 854a, 900a, 906a, 913a, 918a, 923a], [956a, 1002a, 1009a, 1014a, 1020a, 1026a, 1031a, 1035a], [1056a, 1102a, 1109a, 1114a, 1120a, 1126a, 1131a, 1135a], [1156a, 1202p, 1209p, 1214p, 1220p, 1226p, 1231p, 1235p], [1256p, 102p, 109p, 114p, 120p, 126p, 131p, 135p], [156p, 202p, 209p, 214p, 220p, 226p, 231p, 235p], [256p, 302p, 310p, 316p, 322p, 329p, 334p, 339p], [339p, 346p, 354p, 400p, 406p, 413p, 418p, 423p], [409p, 416p, 424p, 430p, 436p, 443p, 448p, 453p], [439p, 446p, 454p, 500p, 506p, 513p, 518p, 523p], [509p, 516p, 524p, 530p, 536p, 543p, 548p, 553p], [538p, 545p, 553p, 559p, 605p, 612p, 617p, 622p], [608p, 615p, 623p, 629p, 635p, 641p, 646p, 650p], [659p, 705p, 712p, 717p, 723p, 729p, 734p, 738p], [759p, 805p, 812p, 817p, 823p, 829p, 834p, 838p], [859p, 905p, 912p, 917p, 923p, 929p, 934p, 938p], [959p, 1005p, 1012p, 1017p, 1023p, 1029p, 1034p, 1038p], [1059p, 1105p, 1112p, 1117p, 1123p, 1129p, 1134p, 1138p]]  
-  
time_points: [Belconnen Community Bus Station (Platform 6), Westfield Bus Station (Platform 2), Cohen Street Bus Station (Platform 5), Higgins Shops, Kippax, Higgins Shops, Cohen Street Bus Station, Westfield Bus Station, Belconnen Community Bus Station]  
long_name: To Belconnen Community Bus Station  
between_stops:  
Belconnen Community Bus Station (Platform 6)-Westfield Bus Station (Platform 2): []  
Westfield Bus Station-Belconnen Community Bus Station: []  
Cohen Street Bus Station-Westfield Bus Station: []  
Westfield Bus Station (Platform 2)-Cohen Street Bus Station (Platform 5): []  
stop_times_saturday: [["-", "-", "-", "-", 757a, 807a, 828a, 830a, 834a], [819a, 821a, 825a, 846a, 857a, 907a, 928a, 930a, 934a], [919a, 921a, 925a, 946a, 957a, 1007a, 1028a, 1030a, 1034a], [1019a, 1021a, 1025a, 1046a, 1057a, 1107a, 1128a, 1130a, 1134a], [1119a, 1121a, 1125a, 1146a, 1157a, 1207p, 1228p, 1230p, 1234p], [1219p, 1221p, 1225p, 1246p, 1257p, 107p, 128p, 130p, 134p], [119p, 121p, 125p, 146p, 157p, 207p, 228p, 230p, 234p], [219p, 221p, 225p, 246p, 257p, 307p, 328p, 330p, 334p], [319p, 321p, 325p, 346p, 357p, 407p, 428p, 430p, 434p], [419p, 421p, 425p, 446p, 457p, 507p, 528p, 530p, 534p], [519p, 521p, 525p, 546p, 557p, 607p, 628p, 630p, 634p], [619p, 621p, 625p, 645p, 656p, 706p, 726p, 728p, 732p], [718p, 720p, 724p, 744p, 755p, 805p, 825p, 827p, 831p], [818p, 820p, 824p, 844p, 855p, 905p, 925p, 927p, 931p], [918p, 920p, 924p, 944p, 955p, 1005p, 1025p, 1027p, 1031p], [1018p, 1020p, 1024p, 1044p, 1055p, 1105p, 1125p, 1127p, 1131p], [1118p, 1120p, 1124p, 1144p, 1155p, "-", "-", "-", "-"]]  
short_name: "904"  
-  
time_points: [City Bus Station (Platform 7), St Thomas More's Campbell, Russell Offices, Hospice / Menindee Dr, ADFA, Campbell Park Offices]  
long_name: To Campbell Park Offices  
between_stops:  
ADFA-Campbell Park Offices: [Wjzcend, Wjzce4H, Wjzce7O]  
short_name: "9"  
stop_times: [[714a, 726a, 731a, 733a, 741a, 745a], [814a, 829a, 834a, 836a, 844a, 848a], [857a, 911a, 916a, 918a, 926a, 931a], [957a, 1011a, 1016a, 1018a, 1026a, 1029a], [1057a, 1111a, 1116a, 1118a, 1126a, 1129a], [1157a, 1211p, 1216p, 1218p, 1226p, 1229p], [1257p, 111p, 116p, 118p, 126p, 129p], [157p, 211p, 216p, 218p, 226p, 229p], [257p, 312p, 317p, 319p, 327p, 331p], [344p, 359p, 404p, 406p, 414p, 418p], [414p, 429p, 434p, 436p, 444p, 448p], [444p, 459p, 504p, 506p, 514p, 518p], [514p, 529p, 534p, 536p, 544p, 548p], [557p, 612p, 617p, 619p, 627p, 631p], [657p, 708p, 712p, 714p, 720p, 723p], [757p, 808p, 812p, 814p, 820p, 823p], [857p, 908p, 912p, 914p, 920p, 923p], [957p, 1008p, 1012p, 1014p, 1020p, 1023p], [1057p, 1108p, 1112p, 1114p, 1120p, 1123p]]  
-  
time_points: [Belconnen Community Bus Station (Platform 6), Westfield Bus Station (Platform 2), Cohen Street Bus Station (Platform 5), Weetangera Shops, Hawker Shops, Hawker College, Higgins, Kippax]  
long_name: To Kippax  
between_stops:  
Belconnen Community Bus Station (Platform 6)-Westfield Bus Station (Platform 2): []  
Westfield Bus Station (Platform 2)-Cohen Street Bus Station (Platform 5): []  
short_name: "17"  
stop_times: [[706a, 708a, 712a, 716a, 719a, 724a, 729a, 737a], [806a, 808a, 812a, 817a, 820a, 825a, 830a, 838a], [840a, 842a, 846a, 851a, 854a, 859a, 904a, 912a], [854a, 856a, 900a, 905a, 908a, 913a, 918a, 926a], [922a, 924a, 928a, 932a, 935a, 940a, 945a, 951a], [952a, 954a, 958a, 1002a, 1005a, 1010a, 1015a, 1021a], [1022a, 1024a, 1028a, 1032a, 1035a, 1040a, 1045a, 1051a], [1052a, 1054a, 1058a, 1102a, 1105a, 1110a, 1115a, 1121a], [1122a, 1124a, 1128a, 1132a, 1135a, 1140a, 1145a, 1151a], [1152a, 1154a, 1158a, 1202p, 1205p, 1210p, 1215p, 1221p], [1222p, 1224p, 1228p, 1232p, 1235p, 1240p, 1245p, 1251p], [1252p, 1254p, 1258p, 102p, 105p, 110p, 115p, 121p], [122p, 124p, 128p, 132p, 135p, 140p, 145p, 151p], [152p, 154p, 158p, 202p, 205p, 210p, 215p, 221p], [222p, 224p, 228p, 232p, 235p, 240p, 245p, 251p], [249p, 251p, 255p, 259p, 302p, 307p, 313p, 321p], [324p, 326p, 330p, 335p, 338p, 343p, 349p, 357p], [353p, 355p, 359p, 404p, 407p, 412p, 418p, 426p], [412p, 414p, 418p, 423p, 426p, 431p, 437p, 445p], [432p, 434p, 438p, 443p, 446p, 451p, 457p, 505p], [452p, 454p, 458p, 503p, 506p, 511p, 517p, 525p], [512p, 514p, 518p, 523p, 526p, 531p, 537p, 545p], [532p, 534p, 538p, 543p, 546p, 551p, 557p, 605p], [552p, 554p, 558p, 603p, 606p, 611p, 617p, 625p], [612p, 614p, 618p, 623p, 626p, 631p, 636p, 642p], [644p, 646p, 650p, 654p, 657p, 702p, 707p, 713p], [737p, 739p, 743p, 747p, 750p, 755p, 800p, 806p], [837p, 839p, 843p, 847p, 850p, 855p, 900p, 906p], [937p, 939p, 943p, 947p, 950p, 955p, 1000p, 1006p], [1037p, 1039p, 1043p, 1047p, 1050p, 1055p, 1100p, 1106p], [1138p, 1140p, 1144p, 1148p, 1151p, 1156p, 1201a, 1207a]]  
-  
time_points: [Cooleman Court, Holder Shops, Weston Primary, Woden Bus Station (Platform 10), Kings Ave / National Circuit, Russell Offices, ADFA, Campbell Park Offices]  
long_name: To Campbell Park Offices  
between_stops:  
Kings Ave / National Circuit-Russell Offices: [Wjz4Quk, Wjz4RwH, Wjz4RFJ]  
ADFA-Campbell Park Offices: [Wjzcend, Wjzce4H, Wjzce7O]  
Russell Offices-ADFA: [Wjzc60A, Wjzc60i, Wjzc55s, Wjzc54R, Wjzce7O, Wjzce4H, Wjzcend]  
short_name: 25 225  
stop_times: [[612a, 622a, 625a, 634a, "-", "-", "-", "-"], [642a, 652a, 655a, 705a, 719a, 722a, 726a, 730a], [702a, 712a, 715a, 725a, 739a, 743a, 747a, 751a], [734a, 749a, 752a, 805a, 819a, 823a, 827a, 831a], [808a, 823a, 826a, 838a, "-", "-", "-", "-"], [838a, 853a, 856a, 908a, "-", "-", "-", "-"], [910a, 925a, 928a, 938a, "-", "-", "-", "-"], [1012a, 1022a, 1025a, 1035a, "-", "-", "-", "-"], [1112a, 1122a, 1125a, 1135a, "-", "-", "-", "-"], [1212p, 1222p, 1225p, 1235p, "-", "-", "-", "-"], [112p, 122p, 125p, 135p, "-", "-", "-", "-"], [212p, 222p, 225p, 235p, "-", "-", "-", "-"], [312p, 324p, 327p, 336p, "-", "-", "-", "-"], [342p, 354p, 357p, 406p, "-", "-", "-", "-"], [412p, 424p, 427p, 436p, "-", "-", "-", "-"], [512p, 524p, 527p, 536p, "-", "-", "-", "-"], [622p, 633p, 636p, 645p, "-", "-", "-", "-"], [722p, 732p, 735p, 744p, "-", "-", "-", "-"], [822p, 832p, 835p, 844p, "-", "-", "-", "-"], [922p, 932p, 935p, 944p, "-", "-", "-", "-"], [1022p, 1032p, 1035p, 1044p, "-", "-", "-", "-"]]  
-  
time_points: [Woden Bus Station (Platform 15), Lyons Shops, Chifley Shops, Southlands Mawson, Farrer Terminus, Isaacs Shops, Canberra Hospital, Woden Bus Station]  
long_name: To Woden Bus Station  
between_stops:  
Canberra Hospital-Woden Bus Station: [Wjz3mWn, Wjz3mPO, Wjz3mAg]  
short_name: "23"  
stop_times: [[607a, 609a, 613a, 622a, 628a, 634a, 642a, 647a], [644a, 646a, 650a, 659a, 705a, 711a, 719a, 724a], [714a, 716a, 720a, 729a, 736a, 742a, 752a, 757a], [744a, 748a, 753a, 801a, 808a, 814a, 824a, 829a], [814a, 818a, 823a, 831a, 838a, 844a, 854a, 859a], [844a, 848a, 853a, 901a, 908a, 914a, 924a, 929a], [926a, 930a, 934a, 943a, 949a, 955a, 1003a, 1008a], [1026a, 1028a, 1032a, 1041a, 1047a, 1053a, 1101a, 1106a], [1126a, 1128a, 1132a, 1141a, 1147a, 1153a, 1201p, 1206p], [1226p, 1228p, 1232p, 1241p, 1247p, 1253p, 101p, 106p], [126p, 128p, 132p, 141p, 147p, 153p, 201p, 206p], [226p, 228p, 232p, 241p, 247p, 253p, 301p, 306p], [314p, 318p, 323p, 331p, 338p, 344p, 354p, 359p], [344p, 348p, 353p, 401p, 408p, 414p, 424p, 429p], [414p, 418p, 423p, 431p, 438p, 444p, 454p, 459p], [444p, 448p, 453p, 501p, 508p, 514p, 524p, 529p], [514p, 518p, 523p, 531p, 538p, 544p, 554p, 559p], [544p, 548p, 553p, 601p, 608p, 614p, 624p, 629p], [626p, 630p, 634p, 643p, 649p, 655p, 703p, 708p], [726p, 728p, 732p, 741p, 747p, 753p, 801p, 806p], [826p, 828p, 832p, 841p, 847p, 853p, 901p, 906p], [926p, 928p, 932p, 941p, 947p, 953p, 1001p, 1006p], [1026p, 1028p, 1032p, 1041p, 1047p, 1053p, 1101p, 1106p], [1126p, 1128p, 1132p, 1141p, "-", "-", "-", "-"]]  
-  
time_points: [Campbell Park Offices, ADFA, Hospice / Menindee Dr, Russell Offices, St Thomas More's Campbell, City Bus Station]  
long_name: To City Bus Station  
between_stops:  
Campbell Park Offices-ADFA: [Wjzce7O, Wjzce4H, Wjzcend]  
short_name: "9"  
stop_times: [["-", 655a, 701a, 703a, 708a, 720a], [720a, 723a, 729a, 731a, 736a, 751a], [752a, 756a, 804a, 806a, 811a, 826a], [822a, 826a, 834a, 836a, 841a, 856a], [852a, 856a, 904a, 906a, 911a, 926a], [934a, 937a, 945a, 947a, 952a, 1006a], [1034a, 1037a, 1045a, 1047a, 1052a, 1106a], [1134a, 1137a, 1145a, 1147a, 1152a, 1206p], [1234p, 1237p, 1245p, 1247p, 1252p, 106p], [134p, 137p, 145p, 147p, 152p, 206p], [234p, 237p, 245p, 247p, 252p, 306p], [335p, 339p, 347p, 349p, 354p, 409p], [352p, 356p, 404p, 406p, 411p, 426p], [422p, 426p, 434p, 436p, 441p, 456p], [452p, 456p, 504p, 506p, 511p, 526p], [522p, 526p, 534p, 536p, 541p, 556p], [552p, 556p, 604p, 606p, 611p, 626p], [628p, 632p, 638p, 640p, 645p, 656p], [728p, 731p, 737p, 739p, 744p, 755p], [828p, 831p, 837p, 839p, 844p, 855p], [928p, 931p, 937p, 939p, 944p, 955p], [1028p, 1031p, 1037p, 1039p, 1044p, 1055p]]  
-  
time_points: [Woden Bus Station (Platform 11), Athllon / Sulwood Kambah, MacKillop College Wanniassa Campus, Monash Primary, Tuggeranong Bus Station]  
long_name: To Tuggeranong Bus Station  
between_stops:  
Woden Bus Station (Platform 11)-Athllon / Sulwood Kambah: [Wjz3kAx, Wjz3kyX, Wjz3kwU, Wjz3iFK, Wjz3hL_, Wjz3gK-, Wjz3gQn, Wjz3gMq, Wjz2mTK]  
short_name: "64"  
stop_times: [["-", "-", 651a, 655a, 702a], [706a, 714a, 721a, 725a, 733a], ["-", "-", 751a, 756a, 805a], [806a, 816a, 823a, 828a, 837a], [836a, 846a, 853a, 858a, 907a], [906a, 916a, 923a, 928a, 936a], [1006a, 1015a, 1022a, 1026a, 1034a], [1106a, 1115a, 1122a, 1126a, 1134a], [1206p, 1215p, 1222p, 1226p, 1234p], [106p, 115p, 122p, 126p, 134p], [206p, 215p, 222p, 226p, 234p], [306p, 316p, 323p, 328p, 337p], [336p, 346p, 353p, 358p, 407p], [406p, 416p, 423p, 428p, 437p], [436p, 446p, 453p, 458p, 507p], [506p, 516p, 523p, 528p, 537p], [536p, 546p, 553p, 558p, 607p], [606p, 616p, 623p, 628p, 636p], [706p, 715p, 722p, 726p, 734p], [806p, 815p, 822p, 826p, 834p], [906p, 915p, 922p, 926p, 934p], [1006p, 1015p, 1022p, 1026p, 1034p], [1106p, 1115p, 1122p, 1126p, 1134p]]  
-  
time_points: [Cohen Street Bus Station (Platform 1), Westfield Bus Station (Platform 1), Belconnen Community Bus Station (Platform 2), Chuculba / William Slim Dr, Federation Square, Nicholls Primary, Ngunnawal Primary, Gungahlin Marketplace]  
long_name: To Gungahlin Market Place  
between_stops:  
Belconnen Community Bus Station (Platform 2)-Chuculba / William Slim Dr: [Wjz69gA, Wjz69ht, Wjz69uI, Wjz69vO, Wjz6mip]  
Cohen Street Bus Station (Platform 1)-Westfield Bus Station (Platform 1): []  
Westfield Bus Station (Platform 1)-Belconnen Community Bus Station (Platform 2): []  
short_name: "951"  
stop_times_sunday: [[920a, 922a, 926a, 934a, 939a, 944a, 954a, 1004a], [1020a, 1022a, 1026a, 1034a, 1039a, 1044a, 1054a, 1104a], [1120a, 1122a, 1126a, 1134a, 1139a, 1144a, 1154a, 1204p], [1220p, 1222p, 1226p, 1234p, 1239p, 1244p, 1254p, 104p], [120p, 122p, 126p, 134p, 139p, 144p, 154p, 204p], [220p, 222p, 226p, 234p, 239p, 244p, 254p, 304p], [320p, 322p, 326p, 334p, 339p, 344p, 354p, 404p], [420p, 422p, 426p, 434p, 439p, 444p, 454p, 504p], [520p, 522p, 526p, 534p, 539p, 544p, 554p, 604p], [620p, 622p, 626p, 634p, 639p, 644p, 654p, 704p]]  
-  
time_points: [Cooleman Court, Rivett Shops, Duffy Primary, Holder Shops, City West, City Bus Station, ACTEW AGL House]  
long_name: To ACTEW AGL House  
between_stops:  
City Bus Station-ACTEW AGL House: [Wjz5Nht]  
City West-City Bus Station: []  
short_name: "729"  
stop_times: [[709a, 715a, 724a, 728a, 749a, 753a, 755a], [739a, 745a, 754a, 758a, 819a, 823a, 825a]]  
-  
time_points: [City West, City Bus Station (Platform 10), Russell Offices, Kings Ave / National Circuit, Woden Bus Station (Platform 12), Erindale Centre, Bugden Sternberg, Gowrie, MacKillop College Isabella Campus, Tuggeranong Bus Station]  
long_name: To Tuggeranong Bus Station  
between_stops:  
Russell Offices-Kings Ave / National Circuit: [Wjz4RFJ, Wjz4RwH, Wjz4Quk]  
City West-City Bus Station (Platform 10): []  
City Bus Station (Platform 10)-Russell Offices: [Wjz5Nht, Wjz5MsD, Wjz5MsT, Wjz5MEL, Wjz4T-X, Wjz4_jm, Wjz4_wS, Wjz4-KO, Wjz4-Rc, Wjz4-WL, Wjz4-WZ]  
short_name: 65 265  
stop_times: [["-", "-", "-", "-", "-", "-", 604a, 608a, 619a, 625a], ["-", "-", "-", "-", 625a, 637a, 638a, 643a, 654a, 700a], ["-", "-", "-", "-", 655a, 710a, 711a, 718a, 734a, 744a], ["-", "-", "-", "-", 725a, 742a, 743a, 750a, 806a, 816a], ["-", "-", "-", "-", 755a, 812a, 813a, 820a, 836a, 846a], ["-", "-", "-", "-", 825a, 842a, 843a, 850a, 906a, 916a], ["-", "-", "-", "-", 855a, 912a, 913a, 920a, 935a, 943a], ["-", "-", "-", "-", 955a, 1009a, 1010a, 1015a, 1027a, 1035a], ["-", "-", "-", "-", 1055a, 1109a, 1110a, 1115a, 1127a, 1135a], ["-", "-", "-", "-", 1155a, 1209p, 1210p, 1215p, 1227p, 1235p], ["-", "-", "-", "-", 1255p, 109p, 110p, 115p, 127p, 135p], ["-", "-", "-", "-", 155p, 209p, 210p, 215p, 227p, 235p], ["-", "-", "-", "-", 255p, 311p, 312p, 318p, 332p, 341p], ["-", "-", "-", "-", 325p, 342p, 343p, 349p, 403p, 412p], ["-", "-", "-", "-", 355p, 412p, 413p, 419p, 433p, 442p], ["-", "-", "-", "-", 420p, 437p, 438p, 444p, 458p, 507p], ["-", "-", "-", "-", 455p, 512p, 513p, 519p, 533p, 542p], [455p, 501p, 510p, 513p, 528p, 545p, 546p, 552p, 606p, 615p], [525p, 531p, 540p, 543p, 558p, 615p, 616p, 622p, 635p, 643p], [555p, 601p, 610p, 613p, 628p, 642p, 643p, 648p, 700p, 708p], ["-", "-", "-", "-", 654p, 708p, 709p, 714p, 726p, 734p], ["-", "-", "-", "-", 754p, 808p, 809p, 814p, 826p, 834p], ["-", "-", "-", "-", 854p, 908p, 909p, 914p, 926p, 934p], ["-", "-", "-", "-", 954p, 1008p, 1009p, 1014p, 1026p, 1034p], ["-", "-", "-", "-", 1054p, 1108p, 1109p, 1114p, 1126p, 1134p]]  
-  
time_points: [Sydney Ave, Russell Offices, City Bus Station (Platform 11), Macarthur / Northbourne Ave, Northbourne Avenue / Antill St, William Webb / Ginninderra Drive, Copland College, Spence Shops, Spence Terminus]  
long_name: To Spence Terminus  
between_stops:  
Macarthur / Northbourne Ave-Northbourne Avenue / Antill St: [Wjz5RkN, Wjz5Rsi, Wjz5RvC, Wjz5Sqk, Wjz5SrO, Wjz5Sux, Wjz5SDc]  
Russell Offices-City Bus Station (Platform 11): [Wjz4-WZ, Wjz4-WL, Wjz4-Rc, Wjz4-KO, Wjz4_wS, Wjz4_jm, Wjz4T-X, Wjz5MEL, Wjz5MsT, Wjz5MsD, Wjz5Nht]  
City Bus Station (Platform 11)-Macarthur / Northbourne Ave: [Wjz5O3Q, Wjz5Oci, Wjz5P8n, Wjz5P8K, Wjz5PdJ, Wjz5Pl0, Wjz5Qi2, Wjz5Qgn, Wjz5Qmu, Wjz5QmR]  
short_name: "701"  
stop_times: [[442p, 450p, 502p, 509p, 512p, 522p, 527p, 534p, 540p], ["-", "-", 520p, 527p, 529p, 539p, 543p, 550p, 554p], [525p, 533p, 543p, 550p, 552p, 602p, 606p, 613p, 617p], [542p, 550p, 600p, 607p, 609p, 619p, 623p, 630p, 634p]]  
-  
time_points: [Woden Bus Station (Platform 14), Canberra Hospital, Garran Shops, Hughes Shops, Deakin Shops, Parliament House, Kings Ave / National Circuit, City Bus Station (Platform 4), National Museum of Australia, Burton and Garran Hall Daley Road, O'Connor Shops, Calvary Hospital, Belconnen Community Bus Station, Westfield Bus Station, Cohen Street Bus Station]  
long_name: To Cohen Street Bus Station  
between_stops:  
Westfield Bus Station-Cohen Street Bus Station: []  
Belconnen Community Bus Station-Westfield Bus Station: []  
Woden Bus Station (Platform 14)-Canberra Hospital: [Wjz3mAg, Wjz3mPO, Wjz3mWn]  
Kings Ave / National Circuit-City Bus Station (Platform 4): [Wjz4Quk, Wjz4RwH, Wjz4RFJ, Wjz4Rs-, Wjz4S1U, Wjz5FOn]  
short_name: "3"  
stop_times: [[612a, 619a, 621a, 625a, 630a, 634a, 638a, 650a, 701a, 706a, 711a, 718a, 730a, 732a, 737a], [642a, 649a, 651a, 655a, 700a, 704a, 708a, 720a, 731a, 736a, 741a, 750a, 803a, 805a, 810a], [712a, 719a, 721a, 725a, 730a, 734a, 740a, 752a, 803a, 808a, 813a, 822a, 835a, 837a, 842a], [738a, 746a, 749a, 754a, 802a, 806a, 812a, 824a, 835a, 840a, 845a, 854a, 907a, 909a, 914a], [808a, 816a, 819a, 824a, 832a, 836a, 842a, 854a, 905a, 910a, 915a, 924a, 936a, 938a, 943a], [838a, 846a, 849a, 854a, 902a, 906a, 912a, 924a, 935a, 940a, 945a, 952a, 1004a, 1006a, 1011a], [912a, 920a, 923a, 928a, 934a, 938a, 942a, 954a, 1005a, 1010a, 1015a, 1022a, 1034a, 1036a, 1041a], [942a, 949a, 951a, 955a, 1000a, 1004a, 1008a, 1020a, 1031a, 1036a, 1041a, 1048a, 1100a, 1102a, 1107a], [1012a, 1019a, 1021a, 1025a, 1030a, 1034a, 1038a, 1050a, 1101a, 1106a, 1111a, 1118a, 1130a, 1132a, 1137a], [1042a, 1049a, 1051a, 1055a, 1100a, 1104a, 1108a, 1120a, 1131a, 1136a, 1141a, 1148a, 1200p, 1202p, 1207p], [1112a, 1119a, 1121a, 1125a, 1130a, 1134a, 1138a, 1150a, 1201p, 1206p, 1211p, 1218p, 1230p, 1232p, 1237p], [1142a, 1149a, 1151a, 1155a, 1200p, 1204p, 1208p, 1220p, 1231p, 1236p, 1241p, 1248p, 100p, 102p, 107p], [1212p, 1219p, 1221p, 1225p, 1230p, 1234p, 1238p, 1250p, 101p, 106p, 111p, 118p, 130p, 132p, 137p], [1242p, 1249p, 1251p, 1255p, 100p, 104p, 108p, 120p, 131p, 136p, 141p, 148p, 200p, 202p, 207p], [112p, 119p, 121p, 125p, 130p, 134p, 138p, 150p, 201p, 206p, 211p, 218p, 230p, 232p, 237p], [142p, 149p, 151p, 155p, 200p, 204p, 208p, 220p, 231p, 236p, 241p, 248p, 300p, 302p, 307p], [212p, 219p, 221p, 225p, 230p, 234p, 238p, 250p, 301p, 307p, 313p, 321p, 334p, 336p, 341p], [242p, 249p, 251p, 255p, 300p, 304p, 308p, 320p, 331p, 337p, 343p, 351p, 404p, 406p, 411p], [309p, 317p, 319p, 324p, 330p, 334p, 338p, 350p, 401p, 407p, 413p, 421p, 434p, 436p, 441p], [339p, 347p, 349p, 354p, 400p, 404p, 408p, 420p, 431p, 437p, 443p, 451p, 504p, 506p, 511p], [409p, 417p, 419p, 424p, 430p, 434p, 438p, 450p, 501p, 507p, 513p, 521p, 534p, 536p, 541p], [439p, 447p, 449p, 454p, 500p, 504p, 508p, 520p, 531p, 537p, 543p, 551p, 604p, 606p, 611p], [511p, 519p, 521p, 526p, 532p, 536p, 540p, 552p, 603p, 609p, 615p, 623p, 636p, 638p, 643p], [539p, 547p, 549p, 554p, 600p, 604p, 608p, 620p, 631p, 636p, 641p, 648p, 700p, 702p, 707p], [608p, 616p, 618p, 623p, 629p, 632p, 636p, 648p, 659p, 704p, 709p, 716p, 728p, 730p, 735p], [643p, 649p, 651p, 655p, 700p, 703p, 707p, 719p, 730p, 735p, 740p, 747p, 759p, 801p, 806p], [713p, 719p, 721p, 725p, 730p, 733p, 737p, 749p, 800p, 805p, 810p, 817p, 829p, 831p, 836p], [813p, 819p, 821p, 825p, 830p, 833p, 837p, 849p, 900p, 905p, 910p, 917p, 929p, 931p, 936p], [913p, 919p, 921p, 925p, 930p, 933p, 937p, 949p, 1000p, 1005p, 1010p, 1017p, 1029p, 1031p, 1036p], [1013p, 1019p, 1021p, 1025p, 1030p, 1033p, 1037p, 1049p, 1100p, 1105p, 1110p, 1117p, 1129p, 1131p, 1136p], [1113p, 1119p, 1121p, 1125p, 1130p, 1133p, 1137p, 1147p, "-", "-", "-", "-", "-", "-", "-"]]  
-  
time_points: [Spence Terminus, Evatt Shops, Copland College, McKellar Shops, Cohen Street Bus Station (Platform 3), Westfield Bus Station (Platform 1), Belconnen Community Bus Station (Platform 1), City Bus Station (Platform 1), Woden Bus Station (Platform 6), Tuggeranong Bus Station]  
long_name: To Tuggeranong Bus Station long_name: To Tuggeranong Bus Station
between_stops: between_stops:
Cohen Street Bus Station (Platform 3)-Westfield Bus Station (Platform 1): [] Cohen Street Bus Station (Platform 3)-Westfield Bus Station (Platform 1): []
Westfield Bus Station (Platform 1)-Belconnen Community Bus Station (Platform 1): [] Westfield Bus Station (Platform 1)-Belconnen Community Bus Station (Platform 1): []
Woden Bus Station (Platform 6)-Tuggeranong Bus Station: [Wjz3kAx, Wjz3kyX, Wjz3kwU, Wjz3iFK, Wjz3hL_, Wjz3gK-, Wjz3gQn, Wjz3gMq, Wjz2nLE, Wjz2mTK, Wjz2mGO, Wjz2lDC, Wjz239F, Wjz238T, Wjz213q] Woden Bus Station (Platform 6)-Tuggeranong Bus Station: [Wjz3kAx, Wjz3kyX, Wjz3kwU, Wjz3iFK, Wjz3hL_, Wjz3gK-, Wjz3gQn, Wjz3gMq, Wjz2nLE, Wjz2mTK, Wjz2mGO, Wjz2lDC, Wjz239F, Wjz238T, Wjz213q]
City Bus Station (Platform 1)-Woden Bus Station (Platform 6): [Wjz5Nht, Wjz4KO9, Wjz4KNu, Wjz3eZ4, Wjz3eRR, Wjz3m3b, Wjz3m3b] City Bus Station (Platform 1)-Woden Bus Station (Platform 6): [Wjz5Nht, Wjz4KO9, Wjz4KNu, Wjz3eZ4, Wjz3eRR, Wjz3m3b, Wjz3m3b]
Belconnen Community Bus Station (Platform 1)-City Bus Station (Platform 1): [Wjz681S, Wjz689c, Wjz68W3, Wjz68W5, Wjz6gia, Wjz6giR, Wjz5nw6, Wjz5nwb, Wjz5mbS, Wjz5maK, Wjz5G6B, Wjz5G6U, Wjz5GNG, Wjz5GMT, Wjz5FSY, Wjz5F-1] Belconnen Community Bus Station (Platform 1)-City Bus Station (Platform 1): [Wjz681S, Wjz689c, Wjz68W3, Wjz68W5, Wjz6gia, Wjz6giR, Wjz5nw6, Wjz5nwb, Wjz5mbS, Wjz5maK, Wjz5G6B, Wjz5G6U, Wjz5GNG, Wjz5GMT, Wjz5FSY, Wjz5F-1]
short_name: 12 312 short_name: 12 312
stop_times: [[624a, 629a, 632a, 636a, 646a, 648a, 652a, "-", "-", "-"], [653a, 658a, 701a, 705a, 715a, 717a, 721a, 742a, 759a, 816a], [723a, 728a, 731a, 735a, 745a, 747a, 751a, 813a, 830a, 847a], [734a, 739a, 743a, 747a, 757a, 759a, 803a, 825a, 842a, 859a], [749a, 754a, 758a, 802a, 812a, 814a, 818a, 840a, 857a, 914a], [807a, 812a, 816a, 820a, 830a, 832a, 836a, 858a, 915a, 932a], [827a, 832a, 836a, 840a, 850a, 852a, 856a, 918a, 935a, 950a], [852a, 857a, 901a, 905a, 915a, 917a, 921a, 942a, 959a, 1014a], [922a, 927a, 931a, 935a, 945a, 947a, 951a, 1011a, 1028a, 1043a], [953a, 958a, 1001a, 1005a, 1015a, 1017a, 1021a, 1041a, 1058a, 1113a], [1023a, 1028a, 1031a, 1035a, 1045a, 1047a, 1051a, 1111a, 1128a, 1143a], [1053a, 1058a, 1101a, 1105a, 1115a, 1117a, 1121a, 1141a, 1158a, 1213p], [1123a, 1128a, 1131a, 1135a, 1145a, 1147a, 1151a, 1211p, 1228p, 1243p], [1153a, 1158a, 1201p, 1205p, 1215p, 1217p, 1221p, 1241p, 1258p, 113p], [1223p, 1228p, 1231p, 1235p, 1245p, 1247p, 1251p, 111p, 128p, 143p], [1253p, 1258p, 101p, 105p, 115p, 117p, 121p, 141p, 158p, 213p], [123p, 128p, 131p, 135p, 145p, 147p, 151p, 211p, 228p, 243p], [153p, 158p, 201p, 205p, 215p, 217p, 221p, 241p, 258p, 316p], [223p, 228p, 231p, 235p, 245p, 247p, 251p, 312p, 329p, 348p], [253p, 258p, 301p, 305p, 315p, 317p, 321p, 343p, 400p, 419p], [322p, 327p, 331p, 335p, 345p, 347p, 351p, 413p, 430p, 449p], [342p, 347p, 351p, 355p, 405p, 407p, 411p, 433p, 450p, 509p], [412p, 417p, 421p, 425p, 435p, 437p, 441p, 503p, 520p, 539p], [432p, 437p, 441p, 445p, 455p, 457p, 501p, 523p, 540p, 559p], [457p, 502p, 506p, 510p, 520p, 522p, 526p, 548p, 605p, 624p], [522p, 527p, 531p, 535p, 545p, 547p, 551p, 613p, 630p, 645p], [552p, 557p, 601p, 605p, 615p, 617p, 621p, 641p, 655p, 710p], [622p, 627p, 631p, 635p, 645p, 647p, 651p, 710p, 724p, 739p], [711p, 716p, 719p, 723p, 733p, 735p, 739p, "-", "-", "-"], [811p, 816p, 819p, 823p, 833p, 835p, 839p, "-", "-", "-"], [911p, 916p, 919p, 923p, 933p, 935p, 939p, "-", "-", "-"], [1011p, 1016p, 1019p, 1023p, 1033p, 1035p, 1039p, "-", "-", "-"]] stop_times: [[624a, 629a, 632a, 636a, 646a, 648a, 652a, "-", "-", "-"], [653a, 658a, 701a, 705a, 715a, 717a, 721a, 742a, 759a, 816a], [723a, 728a, 731a, 735a, 745a, 747a, 751a, 813a, 830a, 847a], [734a, 739a, 743a, 747a, 757a, 759a, 803a, 825a, 842a, 859a], [749a, 754a, 758a, 802a, 812a, 814a, 818a, 840a, 857a, 914a], [807a, 812a, 816a, 820a, 830a, 832a, 836a, 858a, 915a, 932a], [827a, 832a, 836a, 840a, 850a, 852a, 856a, 918a, 935a, 950a], [852a, 857a, 901a, 905a, 915a, 917a, 921a, 942a, 959a, 1014a], [922a, 927a, 931a, 935a, 945a, 947a, 951a, 1011a, 1028a, 1043a], [953a, 958a, 1001a, 1005a, 1015a, 1017a, 1021a, 1041a, 1058a, 1113a], [1023a, 1028a, 1031a, 1035a, 1045a, 1047a, 1051a, 1111a, 1128a, 1143a], [1053a, 1058a, 1101a, 1105a, 1115a, 1117a, 1121a, 1141a, 1158a, 1213p], [1123a, 1128a, 1131a, 1135a, 1145a, 1147a, 1151a, 1211p, 1228p, 1243p], [1153a, 1158a, 1201p, 1205p, 1215p, 1217p, 1221p, 1241p, 1258p, 113p], [1223p, 1228p, 1231p, 1235p, 1245p, 1247p, 1251p, 111p, 128p, 143p], [1253p, 1258p, 101p, 105p, 115p, 117p, 121p, 141p, 158p, 213p], [123p, 128p, 131p, 135p, 145p, 147p, 151p, 211p, 228p, 243p], [153p, 158p, 201p, 205p, 215p, 217p, 221p, 241p, 258p, 316p], [223p, 228p, 231p, 235p, 245p, 247p, 251p, 312p, 329p, 348p], [253p, 258p, 301p, 305p, 315p, 317p, 321p, 343p, 400p, 419p], [322p, 327p, 331p, 335p, 345p, 347p, 351p, 413p, 430p, 449p], [342p, 347p, 351p, 355p, 405p, 407p, 411p, 433p, 450p, 509p], [412p, 417p, 421p, 425p, 435p, 437p, 441p, 503p, 520p, 539p], [432p, 437p, 441p, 445p, 455p, 457p, 501p, 523p, 540p, 559p], [457p, 502p, 506p, 510p, 520p, 522p, 526p, 548p, 605p, 624p], [522p, 527p, 531p, 535p, 545p, 547p, 551p, 613p, 630p, 645p], [552p, 557p, 601p, 605p, 615p, 617p, 621p, 641p, 655p, 710p], [622p, 627p, 631p, 635p, 645p, 647p, 651p, 710p, 724p, 739p], [711p, 716p, 719p, 723p, 733p, 735p, 739p, "-", "-", "-"], [811p, 816p, 819p, 823p, 833p, 835p, 839p, "-", "-", "-"], [911p, 916p, 919p, 923p, 933p, 935p, 939p, "-", "-", "-"], [1011p, 1016p, 1019p, 1023p, 1033p, 1035p, 1039p, "-", "-", "-"]]
- -
time_points: [Cohen Street Bus Station (Platform 2), Westfield Bus Station (Platform 1), Belconnen Community Bus Station (Platform 3), Calvary Hospital, O'Connor Shops, Burton and Garran Hall Daley Road, National Museum of Australia, City Bus Station (Platform 2), Kings Ave / National Circuit, Deakin Shops, Hughes Shops, Garran Shops, Canberra Hospital, Woden Bus Station] time_points: [Cohen Street Bus Station (Platform 2), Westfield Bus Station (Platform 1), Belconnen Community Bus Station (Platform 3), Calvary Hospital, O'Connor, Burton and Garran Hall Daley Road, National Museum of Australia, City Bus Station (Platform 2), Kings Ave / National Circuit, Deakin, Hughes, Garran, Canberra Hospital, Woden Bus Station]
long_name: To Woden Bus Station long_name: To Woden Bus Station
between_stops: between_stops:
Westfield Bus Station (Platform 1)-Belconnen Community Bus Station (Platform 3): [] Westfield Bus Station (Platform 1)-Belconnen Community Bus Station (Platform 3): []
City Bus Station (Platform 2)-Kings Ave / National Circuit: [Wjz5FOn, Wjz4S1U, Wjz4Rs-, Wjz4RFJ, Wjz4RwH, Wjz4Quk] City Bus Station (Platform 2)-Kings Ave / National Circuit: [Wjz5FOn, Wjz4S1U, Wjz4Rs-, Wjz4RFJ, Wjz4RwH, Wjz4Quk]
Cohen Street Bus Station (Platform 2)-Westfield Bus Station (Platform 1): [] Cohen Street Bus Station (Platform 2)-Westfield Bus Station (Platform 1): []
Canberra Hospital-Woden Bus Station: [Wjz3mWn, Wjz3mPO, Wjz3mAg] Canberra Hospital-Woden Bus Station: [Wjz3mWn, Wjz3mPO, Wjz3mAg]
short_name: "934" short_name: "934"
stop_times_sunday: [[829a, 831a, 835a, 852a, 859a, 904a, 909a, 919a, 928a, 937a, 942a, 946a, 948a, 955a], [929a, 931a, 935a, 952a, 959a, 1004a, 1009a, 1019a, 1028a, 1037a, 1042a, 1046a, 1048a, 1055a], [1029a, 1031a, 1035a, 1052a, 1059a, 1104a, 1109a, 1119a, 1128a, 1137a, 1142a, 1146a, 1148a, 1155a], [1129a, 1131a, 1135a, 1152a, 1159a, 1204p, 1209p, 1219p, 1228p, 1237p, 1242p, 1246p, 1248p, 1255p], [1229p, 1231p, 1235p, 1252p, 1259p, 104p, 109p, 119p, 128p, 137p, 142p, 146p, 148p, 155p], [129p, 131p, 135p, 152p, 159p, 204p, 209p, 219p, 228p, 237p, 242p, 246p, 248p, 255p], [229p, 231p, 235p, 252p, 259p, 304p, 309p, 319p, 328p, 337p, 342p, 346p, 348p, 355p], [329p, 331p, 335p, 352p, 359p, 404p, 409p, 419p, 428p, 437p, 442p, 446p, 448p, 455p], [429p, 431p, 435p, 452p, 459p, 504p, 509p, 519p, 528p, 537p, 542p, 546p, 548p, 555p], [529p, 531p, 535p, 552p, 559p, 604p, 609p, 619p, 628p, 637p, 642p, 646p, 648p, 655p], [629p, 631p, 635p, 652p, 659p, 704p, 709p, 719p, 728p, 737p, 742p, 746p, 748p, 755p]] stop_times_sunday: [[829a, 831a, 835a, 852a, 859a, 904a, 909a, 919a, 928a, 937a, 942a, 946a, 948a, 955a], [929a, 931a, 935a, 952a, 959a, 1004a, 1009a, 1019a, 1028a, 1037a, 1042a, 1046a, 1048a, 1055a], [1029a, 1031a, 1035a, 1052a, 1059a, 1104a, 1109a, 1119a, 1128a, 1137a, 1142a, 1146a, 1148a, 1155a], [1129a, 1131a, 1135a, 1152a, 1159a, 1204p, 1209p, 1219p, 1228p, 1237p, 1242p, 1246p, 1248p, 1255p], [1229p, 1231p, 1235p, 1252p, 1259p, 104p, 109p, 119p, 128p, 137p, 142p, 146p, 148p, 155p], [129p, 131p, 135p, 152p, 159p, 204p, 209p, 219p, 228p, 237p, 242p, 246p, 248p, 255p], [229p, 231p, 235p, 252p, 259p, 304p, 309p, 319p, 328p, 337p, 342p, 346p, 348p, 355p], [329p, 331p, 335p, 352p, 359p, 404p, 409p, 419p, 428p, 437p, 442p, 446p, 448p, 455p], [429p, 431p, 435p, 452p, 459p, 504p, 509p, 519p, 528p, 537p, 542p, 546p, 548p, 555p], [529p, 531p, 535p, 552p, 559p, 604p, 609p, 619p, 628p, 637p, 642p, 646p, 648p, 655p], [629p, 631p, 635p, 652p, 659p, 704p, 709p, 719p, 728p, 737p, 742p, 746p, 748p, 755p]]
- -
time_points: [Cohen Street Bus Station (Platform 1), Westfield Bus Station (Platform 2), Belconnen Community Bus Station (Platform 2), Chuculba / William Slim Dr, Gungahlin Marketplace, Kosciuszko / Everard, Flemington Rd / Sandford St, Northbourne Avenue / Antill St, Macarthur / Northbourne Ave, City Bus Station] time_points: [City Bus Station (Platform 8), Macarthur / Northbourne Ave, Northbourne Avenue / Antill St, Flemington Rd / Sandford St, Hibberson / Kate Crace, Gungahlin Marketplace]
long_name: To City Bus Station long_name: To Gungahlin Marketplace
between_stops: between_stops:
Northbourne Avenue / Antill St-Macarthur / Northbourne Ave: [Wjz5SDc, Wjz5Sux, Wjz5SrO, Wjz5Sqk, Wjz5RvC, Wjz5Rsi, Wjz5RkN] Macarthur / Northbourne Ave-Northbourne Avenue / Antill St: [Wjz5RkN, Wjz5Rsi, Wjz5RvC, Wjz5Sqk, Wjz5SrO, Wjz5Sux, Wjz5SDc]
Belconnen Community Bus Station (Platform 2)-Chuculba / William Slim Dr: [Wjz69gA, Wjz69ht, Wjz69uI, Wjz69vO, Wjz6mip] City Bus Station (Platform 8)-Macarthur / Northbourne Ave: [Wjz5O3Q, Wjz5Oci, Wjz5P8n, Wjz5P8K, Wjz5PdJ, Wjz5Pl0, Wjz5Qi2, Wjz5Qgn, Wjz5Qmu, Wjz5QmR]
Cohen Street Bus Station (Platform 1)-Westfield Bus Station (Platform 2): [] short_name: "50"
Macarthur / Northbourne Ave-City Bus Station: [Wjz5QmR, Wjz5Qmu, Wjz5Qgn, Wjz5Qi2, Wjz5Pl0, Wjz5PdJ, Wjz5P8K, Wjz5P8n, Wjz5Oci, Wjz5O3Q] stop_times: [[700p, 706p, 708p, 715p, 718p, 721p], [730p, 736p, 738p, 745p, 748p, 751p], [800p, 806p, 808p, 815p, 818p, 821p], [830p, 836p, 838p, 845p, 848p, 851p], [900p, 906p, 908p, 915p, 918p, 921p], [930p, 936p, 938p, 945p, 948p, 951p], [1000p, 1006p, 1008p, 1015p, 1018p, 1021p], [1030p, 1036p, 1038p, 1045p, 1048p, 1051p], [1100p, 1106p, 1108p, 1115p, 1118p, 1121p]]
Westfield Bus Station (Platform 2)-Belconnen Community Bus Station (Platform 2): [] -
short_name: "56" time_points: [Woden Bus Station (Platform 14), Canberra Hospital, Garran, Hughes, Deakin, Kings Ave / National Circuit, City Bus Station (Platform 4), National Museum of Australia, Burton and Garran Hall Daley Road, O'Connor, Calvary Hospital, Belconnen Community Bus Station, Westfield Bus Station, Cohen Street Bus Station]
stop_times: [[534a, 536a, 540a, 554a, 605a, 615a, 622a, 628a, 630a, 637a], [614a, 616a, 620a, 634a, 645a, 655a, 702a, 708a, 710a, 717a], [634a, 636a, 640a, 654a, 705a, 715a, 722a, 728a, 730a, 737a], ["-", "-", "-", "-", 723a, 732a, 739a, 745a, 750a, 805a], [658a, 700a, 704a, 718a, 729a, 739a, 746a, 757a, 802a, 818a], [717a, 719a, 723a, 737a, 748a, 802a, 810a, 821a, 826a, 842a], [739a, 741a, 745a, 800a, 811a, 825a, 833a, 844a, 849a, 902a], [802a, 804a, 808a, 823a, 834a, 848a, 856a, 904a, 906a, 913a], [847a, 849a, 853a, 907a, 917a, 927a, 934a, 940a, 942a, 949a], [930a, 932a, 936a, 950a, 1000a, 1010a, 1017a, 1023a, 1025a, 1032a], [1030a, 1032a, 1036a, 1050a, 1100a, 1110a, 1117a, 1123a, 1125a, 1132a], [1130a, 1132a, 1136a, 1150a, 1200p, 1210p, 1217p, 1223p, 1225p, 1232p], [1230p, 1232p, 1236p, 1250p, 100p, 110p, 117p, 123p, 125p, 132p], [130p, 132p, 136p, 150p, 200p, 210p, 217p, 223p, 225p, 232p], [235p, 237p, 241p, 255p, 305p, 315p, 322p, 328p, 330p, 337p], [312p, 314p, 318p, 332p, 342p, 352p, 359p, 406p, 408p, 416p], [340p, 342p, 346p, 400p, 411p, 423p, 431p, 438p, 440p, 448p], [420p, 422p, 426p, 441p, 452p, 504p, 512p, 519p, 521p, 529p], [440p, 442p, 446p, 501p, 512p, 524p, 532p, 539p, 541p, 549p], [456p, 458p, 502p, 517p, 528p, 540p, 548p, 555p, 557p, 604p], [516p, 518p, 522p, 537p, 548p, 600p, 607p, 613p, 615p, 621p], [536p, 538p, 542p, 557p, 607p, 617p, 624p, 630p, 632p, 638p], [555p, 557p, 601p, 615p, 625p, 635p, 642p, 648p, 650p, 656p], [629p, 631p, 635p, 649p, 659p, 709p, 716p, 722p, 724p, 730p], [729p, 731p, 735p, 749p, 759p, 809p, 816p, 822p, 824p, 830p], [829p, 831p, 835p, 849p, 859p, 909p, 916p, 922p, 924p, 930p], [929p, 931p, 935p, 949p, 959p, 1009p, 1016p, 1022p, 1024p, 1030p], [1029p, 1031p, 1035p, 1049p, 1059p, 1109p, 1116p, 1122p, 1124p, 1130p]]  
-  
time_points: [Bimberi Centre, Northbourne Avenue / Antill St, Macarthur / Northbourne Ave, City Bus Station]  
long_name: To City Bus Station  
between_stops:  
Northbourne Avenue / Antill St-Macarthur / Northbourne Ave: [Wjz5SDc, Wjz5Sux, Wjz5SrO, Wjz5Sqk, Wjz5RvC, Wjz5Rsi, Wjz5RkN]  
Macarthur / Northbourne Ave-City Bus Station: [Wjz5QmR, Wjz5Qmu, Wjz5Qgn, Wjz5Qi2, Wjz5Pl0, Wjz5PdJ, Wjz5P8K, Wjz5P8n, Wjz5Oci, Wjz5O3Q]  
short_name: "82"  
stop_times: [[715p, 724p, 726p, 733p]]  
-  
time_points: [City West, City Bus Station (Platform 10), Russell Offices, Chisholm Shops, Isabella Shops, Calwell Shops]  
long_name: To Calwell Shops  
between_stops:  
City West-City Bus Station (Platform 10): []  
City Bus Station (Platform 10)-Russell Offices: [Wjz5Nht, Wjz5MsD, Wjz5MsT, Wjz5MEL, Wjz4T-X, Wjz4_jm, Wjz4_wS, Wjz4-KO, Wjz4-Rc, Wjz4-WL, Wjz4-WZ]  
short_name: "768"  
stop_times: [[447p, 453p, 502p, 526p, 537p, 545p], [519p, 525p, 534p, 558p, 609p, 617p]]  
-  
time_points: [Woden Bus Station (Platform 14), Canberra Hospital, Garran Shops, Hughes Shops, Deakin Shops, Kings Ave / National Circuit, City Bus Station (Platform 4), National Museum of Australia, Burton and Garran Hall Daley Road, O'Connor Shops, Calvary Hospital, Belconnen Community Bus Station, Westfield Bus Station, Cohen Street Bus Station]  
long_name: To Cohen Street Bus Station long_name: To Cohen Street Bus Station
between_stops: between_stops:
Westfield Bus Station-Cohen Street Bus Station: [] Westfield Bus Station-Cohen Street Bus Station: []
Belconnen Community Bus Station-Westfield Bus Station: [] Belconnen Community Bus Station-Westfield Bus Station: []
Woden Bus Station (Platform 14)-Canberra Hospital: [Wjz3mAg, Wjz3mPO, Wjz3mWn] Woden Bus Station (Platform 14)-Canberra Hospital: [Wjz3mAg, Wjz3mPO, Wjz3mWn]
Kings Ave / National Circuit-City Bus Station (Platform 4): [Wjz4Quk, Wjz4RwH, Wjz4RFJ, Wjz4Rs-, Wjz4S1U, Wjz5FOn] Kings Ave / National Circuit-City Bus Station (Platform 4): [Wjz4Quk, Wjz4RwH, Wjz4RFJ, Wjz4Rs-, Wjz4S1U, Wjz5FOn]
short_name: "934" short_name: "934"
stop_times_sunday: [[813a, 820a, 822a, 826a, 831a, 840a, 852a, 859a, 904a, 909a, 916a, 933a, 935a, 940a], [913a, 920a, 922a, 926a, 931a, 940a, 952a, 959a, 1004a, 1009a, 1016a, 1033a, 1035a, 1040a], [1013a, 1020a, 1022a, 1026a, 1031a, 1040a, 1052a, 1059a, 1104a, 1109a, 1116a, 1133a, 1135a, 1140a], [1113a, 1120a, 1122a, 1126a, 1131a, 1140a, 1152a, 1159a, 1204p, 1209p, 1216p, 1233p, 1235p, 1240p], [1213p, 1220p, 1222p, 1226p, 1231p, 1240p, 1252p, 1259p, 104p, 109p, 116p, 133p, 135p, 140p], [113p, 120p, 122p, 126p, 131p, 140p, 152p, 159p, 204p, 209p, 216p, 233p, 235p, 240p], [213p, 220p, 222p, 226p, 231p, 240p, 252p, 259p, 304p, 309p, 316p, 333p, 335p, 340p], [313p, 320p, 322p, 326p, 331p, 340p, 352p, 359p, 404p, 409p, 416p, 433p, 435p, 440p], [413p, 420p, 422p, 426p, 431p, 440p, 452p, 459p, 504p, 509p, 516p, 533p, 535p, 540p], [513p, 520p, 522p, 526p, 531p, 540p, 552p, 559p, 604p, 609p, 616p, 633p, 635p, 640p], [613p, 620p, 622p, 626p, 631p, 640p, 652p, 659p, 704p, 709p, 716p, 733p, 735p, 740p]] stop_times_sunday: [[813a, 820a, 822a, 826a, 831a, 840a, 852a, 859a, 904a, 909a, 916a, 933a, 935a, 940a], [913a, 920a, 922a, 926a, 931a, 940a, 952a, 959a, 1004a, 1009a, 1016a, 1033a, 1035a, 1040a], [1013a, 1020a, 1022a, 1026a, 1031a, 1040a, 1052a, 1059a, 1104a, 1109a, 1116a, 1133a, 1135a, 1140a], [1113a, 1120a, 1122a, 1126a, 1131a, 1140a, 1152a, 1159a, 1204p, 1209p, 1216p, 1233p, 1235p, 1240p], [1213p, 1220p, 1222p, 1226p, 1231p, 1240p, 1252p, 1259p, 104p, 109p, 116p, 133p, 135p, 140p], [113p, 120p, 122p, 126p, 131p, 140p, 152p, 159p, 204p, 209p, 216p, 233p, 235p, 240p], [213p, 220p, 222p, 226p, 231p, 240p, 252p, 259p, 304p, 309p, 316p, 333p, 335p, 340p], [313p, 320p, 322p, 326p, 331p, 340p, 352p, 359p, 404p, 409p, 416p, 433p, 435p, 440p], [413p, 420p, 422p, 426p, 431p, 440p, 452p, 459p, 504p, 509p, 516p, 533p, 535p, 540p], [513p, 520p, 522p, 526p, 531p, 540p, 552p, 559p, 604p, 609p, 616p, 633p, 635p, 640p], [613p, 620p, 622p, 626p, 631p, 640p, 652p, 659p, 704p, 709p, 716p, 733p, 735p, 740p]]
- -
time_points: [Tuggeranong Bus Station (Platform 3), Taverner St / Erindale Dr, Livingston Shops / Kambah, Athllon / Sulwood Kambah, Woden Bus Station, City Bus Station, City West] time_points: [Tuggeranong Bus Station (Platform 3), Taverner St / Erindale Dr, Kambah / Livingston St, Athllon / Sulwood Kambah, Woden Bus Station, City Bus Station, City West]
long_name: To City West long_name: To City West
between_stops: between_stops:
City Bus Station-City West: [] City Bus Station-City West: []
Athllon / Sulwood Kambah-Woden Bus Station: [Wjz2mTK, Wjz3gMq, Wjz3gQn, Wjz3gK-, Wjz3hL_, Wjz3iFK, Wjz3kwU, Wjz3kyX, Wjz3kAx] Athllon / Sulwood Kambah-Woden Bus Station: [Wjz2mTK, Wjz3gMq, Wjz3gQn, Wjz3gK-, Wjz3hL_, Wjz3iFK, Wjz3kwU, Wjz3kyX, Wjz3kAx]
Woden Bus Station-City Bus Station: [Wjz3m3b, Wjz3m3b, Wjz3eRR, Wjz3eZ4, Wjz4KNu, Wjz4KO9, Wjz5Nht] Woden Bus Station-City Bus Station: [Wjz3m3b, Wjz3m3b, Wjz3eRR, Wjz3eZ4, Wjz4KNu, Wjz4KO9, Wjz5Nht]
short_name: 61 161 short_name: 61 161
stop_times: [[630a, 641a, 646a, 651a, 658a, "-", "-"], [700a, 712a, 717a, 722a, 733a, "-", "-"], [726a, 739a, 746a, 751a, 805a, 819a, 822a], [740a, 754a, 759a, 804a, 813a, "-", "-"], [800a, 814a, 819a, 825a, 839a, "-", "-"], [837a, 851a, 856a, 901a, 910a, "-", "-"], [900a, 914a, 919a, 924a, 933a, "-", "-"], [930a, 943a, 948a, 953a, 1001a, "-", "-"], [1030a, 1043a, 1048a, 1053a, 1101a, "-", "-"], [1130a, 1143a, 1148a, 1153a, 1201p, "-", "-"], [1230p, 1243p, 1248p, 1253p, 101p, "-", "-"], [130p, 143p, 148p, 153p, 201p, "-", "-"], [230p, 243p, 248p, 253p, 301p, "-", "-"], [330p, 344p, 349p, 354p, 403p, "-", "-"], [400p, 414p, 419p, 424p, 433p, "-", "-"], [430p, 444p, 449p, 454p, 503p, "-", "-"], [500p, 514p, 519p, 524p, 533p, "-", "-"], [530p, 544p, 549p, 554p, 603p, "-", "-"], [600p, 614p, 619p, 624p, 633p, "-", "-"], [630p, 643p, 648p, 653p, 701p, "-", "-"], [730p, 743p, 748p, 753p, 801p, "-", "-"], [830p, 843p, 848p, 853p, 901p, "-", "-"], [930p, 943p, 948p, 953p, 1001p, "-", "-"], [1030p, 1043p, 1048p, 1053p, 1101p, "-", "-"], [1130p, 1143p, 1148p, 1153p, "-", "-", "-"], []] stop_times: [[630a, 641a, 646a, 651a, 658a, "-", "-"], [700a, 712a, 717a, 722a, 733a, "-", "-"], [726a, 739a, 746a, 751a, 805a, 819a, 822a], [740a, 754a, 759a, 804a, 813a, "-", "-"], [800a, 814a, 819a, 825a, 839a, "-", "-"], [837a, 851a, 856a, 901a, 910a, "-", "-"], [900a, 914a, 919a, 924a, 933a, "-", "-"], [930a, 943a, 948a, 953a, 1001a, "-", "-"], [1030a, 1043a, 1048a, 1053a, 1101a, "-", "-"], [1130a, 1143a, 1148a, 1153a, 1201p, "-", "-"], [1230p, 1243p, 1248p, 1253p, 101p, "-", "-"], [130p, 143p, 148p, 153p, 201p, "-", "-"], [230p, 243p, 248p, 253p, 301p, "-", "-"], [330p, 344p, 349p, 354p, 403p, "-", "-"], [400p, 414p, 419p, 424p, 433p, "-", "-"], [430p, 444p, 449p, 454p, 503p, "-", "-"], [500p, 514p, 519p, 524p, 533p, "-", "-"], [530p, 544p, 549p, 554p, 603p, "-", "-"], [600p, 614p, 619p, 624p, 633p, "-", "-"], [630p, 643p, 648p, 653p, 701p, "-", "-"], [730p, 743p, 748p, 753p, 801p, "-", "-"], [830p, 843p, 848p, 853p, 901p, "-", "-"], [930p, 943p, 948p, 953p, 1001p, "-", "-"], [1030p, 1043p, 1048p, 1053p, 1101p, "-", "-"], [1130p, 1143p, 1148p, 1153p, "-", "-", "-"], []]
- -
time_points: [Cohen Street Bus Station (Platform 2), Westfield Bus Station (Platform 1), Belconnen Community Bus Station (Platform 1), City Bus Station (Platform 1), Woden Bus Station (Platform 6), Tuggeranong Bus Station (Platform 4), Bonython Primary School, St Clare of Assisi, Conder Primary, Lanyon Market Place] time_points: [Cohen Street Bus Station (Platform 2), Westfield Bus Station (Platform 1), Belconnen Community Bus Station (Platform 1), City Bus Station (Platform 1), Woden Bus Station (Platform 6), Tuggeranong Bus Station (Platform 4), Bonython Primary School, St Clare of Assisi Primary, Conder Primary, Lanyon Market Place]
long_name: To Lanyon Market Place long_name: To Lanyon Market Place
between_stops: between_stops:
Woden Bus Station (Platform 6)-Tuggeranong Bus Station (Platform 4): [Wjz3kAx, Wjz3kyX, Wjz3kwU, Wjz3iFK, Wjz3hL_, Wjz3gK-, Wjz3gQn, Wjz3gMq, Wjz2nLE, Wjz2mTK, Wjz2mGO, Wjz2lDC, Wjz239F, Wjz238T, Wjz213q] Woden Bus Station (Platform 6)-Tuggeranong Bus Station (Platform 4): [Wjz3kAx, Wjz3kyX, Wjz3kwU, Wjz3iFK, Wjz3hL_, Wjz3gK-, Wjz3gQn, Wjz3gMq, Wjz2nLE, Wjz2mTK, Wjz2mGO, Wjz2lDC, Wjz239F, Wjz238T, Wjz213q]
Cohen Street Bus Station (Platform 2)-Westfield Bus Station (Platform 1): [] Cohen Street Bus Station (Platform 2)-Westfield Bus Station (Platform 1): []
Westfield Bus Station (Platform 1)-Belconnen Community Bus Station (Platform 1): [] Westfield Bus Station (Platform 1)-Belconnen Community Bus Station (Platform 1): []
City Bus Station (Platform 1)-Woden Bus Station (Platform 6): [Wjz5Nht, Wjz4KO9, Wjz4KNu, Wjz3eZ4, Wjz3eRR, Wjz3m3b, Wjz3m3b] City Bus Station (Platform 1)-Woden Bus Station (Platform 6): [Wjz5Nht, Wjz4KO9, Wjz4KNu, Wjz3eZ4, Wjz3eRR, Wjz3m3b, Wjz3m3b]
Belconnen Community Bus Station (Platform 1)-City Bus Station (Platform 1): [Wjz681S, Wjz689c, Wjz68W3, Wjz68W5, Wjz6gia, Wjz6giR, Wjz5nw6, Wjz5nwb, Wjz5mbS, Wjz5maK, Wjz5G6B, Wjz5G6U, Wjz5GNG, Wjz5GMT, Wjz5FSY, Wjz5F-1] Belconnen Community Bus Station (Platform 1)-City Bus Station (Platform 1): [Wjz681S, Wjz689c, Wjz68W3, Wjz68W5, Wjz6gia, Wjz6giR, Wjz5nw6, Wjz5nwb, Wjz5mbS, Wjz5maK, Wjz5G6B, Wjz5G6U, Wjz5GNG, Wjz5GMT, Wjz5FSY, Wjz5F-1]
short_name: 19 319 short_name: 19 319
stop_times: [["-", "-", "-", "-", "-", 705a, 711a, 716a, 725a, 731a], ["-", "-", "-", "-", "-", 740a, 747a, 754a, 803a, 810a], [700a, 702a, 706a, 726a, 743a, 801a, 808a, 815a, 824a, 831a], [730a, 732a, 736a, 758a, 815a, 833a, 840a, 847a, 856a, 903a], ["-", "-", "-", "-", "-", 901a, 908a, 915a, 924a, 930a], ["-", "-", "-", "-", "-", 930a, 936a, 941a, 950a, 956a], [900a, 902a, 906a, 928a, 945a, 1001a, 1007a, 1012a, 1021a, 1027a], [930a, 932a, 936a, 956a, 1013a, 1029a, 1035a, 1040a, 1049a, 1055a], [1000a, 1002a, 1006a, 1026a, 1043a, 1059a, 1105a, 1110a, 1119a, 1125a], [1030a, 1032a, 1036a, 1056a, 1113a, 1129a, 1135a, 1140a, 1149a, 1155a], [1100a, 1102a, 1106a, 1126a, 1143a, 1159a, 1205p, 1210p, 1219p, 1225p], [1130a, 1132a, 1136a, 1156a, 1213p, 1229p, 1235p, 1240p, 1249p, 1255p], [1200p, 1202p, 1206p, 1226p, 1243p, 1259p, 105p, 110p, 119p, 125p], [1230p, 1232p, 1236p, 1256p, 113p, 129p, 135p, 140p, 149p, 155p], [100p, 102p, 106p, 126p, 143p, 159p, 205p, 210p, 219p, 225p], [130p, 132p, 136p, 156p, 213p, 229p, 235p, 240p, 249p, 255p], [200p, 202p, 206p, 226p, 243p, 259p, 306p, 313p, 322p, 329p], [230p, 232p, 236p, 256p, 313p, 333p, 340p, 347p, 356p, 403p], ["-", "-", "-", "-", 332p, 352p, 359p, 406p, 415p, 422p], [300p, 302p, 306p, 328p, 345p, 405p, 412p, 419p, 428p, 435p], [330p, 332p, 336p, 358p, 415p, 435p, 442p, 449p, 458p, 505p], [400p, 402p, 406p, 428p, 445p, 505p, 512p, 519p, 528p, 535p], [430p, 432p, 436p, 458p, 515p, 535p, 542p, 549p, 558p, 605p], [450p, 452p, 456p, 518p, 535p, 555p, 602p, 609p, 618p, 625p], [510p, 512p, 516p, 538p, 555p, 615p, 622p, 629p, 638p, 644p], [530p, 532p, 536p, 558p, 615p, 634p, 640p, 645p, 654p, 700p], [600p, 602p, 606p, 628p, 642p, 658p, 704p, 709p, 718p, 724p], [630p, 632p, 636p, 655p, 709p, 725p, 731p, 736p, 745p, 751p], ["-", "-", "-", "-", "-", 818p, 824p, 829p, 838p, 844p], ["-", "-", "-", "-", "-", 918p, 924p, 929p, 938p, 944p], ["-", "-", "-", "-", "-", 1018p, 1024p, 1029p, 1038p, 1044p], ["-", "-", "-", "-", "-", 1118p, 1124p, 1129p, 1138p, 1144p]] stop_times: [["-", "-", "-", "-", "-", 705a, 711a, 716a, 725a, 731a], ["-", "-", "-", "-", "-", 740a, 747a, 754a, 803a, 810a], [700a, 702a, 706a, 726a, 743a, 801a, 808a, 815a, 824a, 831a], [730a, 732a, 736a, 758a, 815a, 833a, 840a, 847a, 856a, 903a], ["-", "-", "-", "-", "-", 901a, 908a, 915a, 924a, 930a], ["-", "-", "-", "-", "-", 930a, 936a, 941a, 950a, 956a], [900a, 902a, 906a, 928a, 945a, 1001a, 1007a, 1012a, 1021a, 1027a], [930a, 932a, 936a, 956a, 1013a, 1029a, 1035a, 1040a, 1049a, 1055a], [1000a, 1002a, 1006a, 1026a, 1043a, 1059a, 1105a, 1110a, 1119a, 1125a], [1030a, 1032a, 1036a, 1056a, 1113a, 1129a, 1135a, 1140a, 1149a, 1155a], [1100a, 1102a, 1106a, 1126a, 1143a, 1159a, 1205p, 1210p, 1219p, 1225p], [1130a, 1132a, 1136a, 1156a, 1213p, 1229p, 1235p, 1240p, 1249p, 1255p], [1200p, 1202p, 1206p, 1226p, 1243p, 1259p, 105p, 110p, 119p, 125p], [1230p, 1232p, 1236p, 1256p, 113p, 129p, 135p, 140p, 149p, 155p], [100p, 102p, 106p, 126p, 143p, 159p, 205p, 210p, 219p, 225p], [130p, 132p, 136p, 156p, 213p, 229p, 235p, 240p, 249p, 255p], [200p, 202p, 206p, 226p, 243p, 259p, 306p, 313p, 322p, 329p], [230p, 232p, 236p, 256p, 313p, 333p, 340p, 347p, 356p, 403p], ["-", "-", "-", "-", 332p, 352p, 359p, 406p, 415p, 422p], [300p, 302p, 306p, 328p, 345p, 405p, 412p, 419p, 428p, 435p], [330p, 332p, 336p, 358p, 415p, 435p, 442p, 449p, 458p, 505p], [400p, 402p, 406p, 428p, 445p, 505p, 512p, 519p, 528p, 535p], [430p, 432p, 436p, 458p, 515p, 535p, 542p, 549p, 558p, 605p], [450p, 452p, 456p, 518p, 535p, 555p, 602p, 609p, 618p, 625p], [510p, 512p, 516p, 538p, 555p, 615p, 622p, 629p, 638p, 644p], [530p, 532p, 536p, 558p, 615p, 634p, 640p, 645p, 654p, 700p], [600p, 602p, 606p, 628p, 642p, 658p, 704p, 709p, 718p, 724p], [630p, 632p, 636p, 655p, 709p, 725p, 731p, 736p, 745p, 751p], ["-", "-", "-", "-", "-", 818p, 824p, 829p, 838p, 844p], ["-", "-", "-", "-", "-", 918p, 924p, 929p, 938p, 944p], ["-", "-", "-", "-", "-", 1018p, 1024p, 1029p, 1038p, 1044p], ["-", "-", "-", "-", "-", 1118p, 1124p, 1129p, 1138p, 1144p]]
- -
time_points: [Woden Bus Station (Platform 15), Southlands Mawson, Farrer Primary School, Isaacs Shops, Pearce Shops, Woden Bus Station] time_points: [Woden Bus Station (Platform 15), Southlands Mawson, Farrer Primary School, Isaacs, Pearce, Woden Bus Station]
long_name: To Woden Bus Station long_name: To Woden Bus Station
between_stops: {} between_stops: {}
short_name: "924" short_name: "924"
stop_times_sunday: [[1010a, 1019a, 1024a, 1029a, 1033a, 1041a], [1210p, 1219p, 1224p, 1229p, 1233p, 1241p], [210p, 219p, 224p, 229p, 233p, 241p], [410p, 419p, 424p, 429p, 433p, 441p], [610p, 619p, 624p, 629p, 633p, 641p]] stop_times_sunday: [[1010a, 1019a, 1024a, 1029a, 1033a, 1041a], [1210p, 1219p, 1224p, 1229p, 1233p, 1241p], [210p, 219p, 224p, 229p, 233p, 241p], [410p, 419p, 424p, 429p, 433p, 441p], [610p, 619p, 624p, 629p, 633p, 641p]]
- -
time_points: [City Bus Station (Platform 8), St Thomas More's Campbell, Hospice / Menindee Dr, ADFA, City Bus Station] time_points: [City Bus Station (Platform 8), St Thomas More's Campbell, Hospice / Menindee Dr, ADFA, City Bus Station]
long_name: To City Bus Station long_name: To City Bus Station
between_stops: between_stops:
ADFA-City Bus Station: [Wjzcend, Wjzd8br, Wjzd0CK, Wjz5VUU, Wjz5VFA, Wjz5VAq, Wjz5V64, Wjz5NRJ, Wjz5NAQ] ADFA-City Bus Station: [Wjzcend, Wjzd8br, Wjzd0CK, Wjz5VUU, Wjz5VFA, Wjz5VAq, Wjz5V64, Wjz5NRJ, Wjz5NAQ]
short_name: "930" short_name: "930"
stop_times_sunday: [[1001a, 1013a, 1020a, 1027a, 1041a], [1201p, 1213p, 1220p, 1227p, 1241p], [201p, 213p, 220p, 227p, 241p], [401p, 413p, 420p, 427p, 441p], [601p, 613p, 620p, 627p, 641p]] stop_times_sunday: [[1001a, 1013a, 1020a, 1027a, 1041a], [1201p, 1213p, 1220p, 1227p, 1241p], [201p, 213p, 220p, 227p, 241p], [401p, 413p, 420p, 427p, 441p], [601p, 613p, 620p, 627p, 641p]]
- -
time_points: [City Bus Station (Platform 4), Macarthur / Miller O'Connor, Lyneham Shops Wattle Street, North Lyneham, Dickson Shops, Hackett Shops, Ainslie Shops, City Bus Station] time_points: [Fraser East Terminus, Fraser, Charnwood, Flynn, Northbourne Avenue / Antill St, Macarthur / Northbourne Ave, City Bus Station (Platform 10), Russell Offices, National Circ / Canberra Ave]
long_name: To City Bus Station  
between_stops: {}  
   
short_name: "936"  
stop_times: [[818a, 827a, 830a, 835a, 844a, 849a, 857a, 909a], [918a, 927a, 930a, 935a, 944a, 949a, 957a, 1009a], [1018a, 1027a, 1030a, 1035a, 1044a, 1049a, 1057a, 1109a], [1118a, 1127a, 1130a, 1135a, 1144a, 1149a, 1157a, 1209p], [1218p, 1227p, 1230p, 1235p, 1244p, 1249p, 1257p, 109p], [118p, 127p, 130p, 135p, 144p, 149p, 157p, 209p], [218p, 227p, 230p, 235p, 244p, 249p, 257p, 309p], [318p, 327p, 330p, 335p, 344p, 349p, 357p, 409p], [418p, 427p, 430p, 435p, 444p, 449p, 457p, 509p], [518p, 527p, 530p, 535p, 544p, 549p, 557p, 609p], [618p, 627p, 630p, 635p, 644p, 649p, 657p, 709p], [718p, 727p, 730p, 735p, 744p, 749p, 757p, 809p]]  
-  
time_points: [Woden Bus Station (Platform 4), Alexander Maconochie Centre]  
long_name: To Alexander Maconochie Centre Hume  
between_stops: {}  
   
short_name: "988"  
stop_times: [[920a, 940a], [1255p, 115p], [455p, 515p]]  
-  
time_points: [Alexander Maconochie Centre, Woden Bus Station]  
long_name: To Woden Bus Station  
between_stops: {}  
   
short_name: "988"  
stop_times: [[1130a, 1150a], [320p, 340p], [730p, 750p]]  
-  
time_points: [Fraser East Terminus, Fraser Shops, Charnwood Shops, Flynn, Northbourne Avenue / Antill St, Macarthur / Northbourne Ave, City Bus Station (Platform 10), Russell Offices, National Circ / Canberra Ave]  
long_name: To National Circ / Canberra Ave long_name: To National Circ / Canberra Ave
between_stops: between_stops:
Northbourne Avenue / Antill St-Macarthur / Northbourne Ave: [Wjz5SDc, Wjz5Sux, Wjz5SrO, Wjz5Sqk, Wjz5RvC, Wjz5Rsi, Wjz5RkN] Northbourne Avenue / Antill St-Macarthur / Northbourne Ave: [Wjz5SDc, Wjz5Sux, Wjz5SrO, Wjz5Sqk, Wjz5RvC, Wjz5Rsi, Wjz5RkN]
Macarthur / Northbourne Ave-City Bus Station (Platform 10): [Wjz5QmR, Wjz5Qmu, Wjz5Qgn, Wjz5Qi2, Wjz5Pl0, Wjz5PdJ, Wjz5P8K, Wjz5P8n, Wjz5Oci, Wjz5O3Q] Macarthur / Northbourne Ave-City Bus Station (Platform 10): [Wjz5QmR, Wjz5Qmu, Wjz5Qgn, Wjz5Qi2, Wjz5Pl0, Wjz5PdJ, Wjz5P8K, Wjz5P8n, Wjz5Oci, Wjz5O3Q]
City Bus Station (Platform 10)-Russell Offices: [Wjz5Nht, Wjz5MsD, Wjz5MsT, Wjz5MEL, Wjz4T-X, Wjz4_jm, Wjz4_wS, Wjz4-KO, Wjz4-Rc, Wjz4-WL, Wjz4-WZ] City Bus Station (Platform 10)-Russell Offices: [Wjz5Nht, Wjz5MsD, Wjz5MsT, Wjz5MEL, Wjz4T-X, Wjz4_jm, Wjz4_wS, Wjz4-KO, Wjz4-Rc, Wjz4-WL, Wjz4-WZ]
short_name: "702" short_name: "702"
stop_times: [[658a, 703a, 709a, 714a, 727a, 730a, 745a, 754a, 802a], [735a, 740a, 746a, 751a, 805a, 810a, 826a, 835a, 843a], [754a, 759a, 806a, 811a, 828a, 833a, 849a, 858a, 906a]] stop_times: [[658a, 703a, 709a, 714a, 727a, 730a, 745a, 754a, 802a], [735a, 740a, 746a, 751a, 805a, 810a, 826a, 835a, 843a], [754a, 759a, 806a, 811a, 828a, 833a, 849a, 858a, 906a]]
- -
time_points: [Kippax, Holt Shops, West Macgregor, Higgins Shops, Belconnen Way, Cohen Street Bus Station, Westfield Bus Station, Belconnen Community Bus Station] time_points: [Kippax, Holt, West Macgregor, Higgins, Belconnen Way, Cohen Street Bus Station, Westfield Bus Station, Belconnen Community Bus Station]
long_name: To Belconnen Community Bus Station long_name: To Belconnen Community Bus Station
between_stops: between_stops:
Westfield Bus Station-Belconnen Community Bus Station: [] Westfield Bus Station-Belconnen Community Bus Station: []
Cohen Street Bus Station-Westfield Bus Station: [] Cohen Street Bus Station-Westfield Bus Station: []
short_name: "44" short_name: "44"
stop_times: [[605a, 607a, 616a, 625a, 630a, 635a, 637a, 641a], [638a, 640a, 649a, 658a, 703a, 708a, 710a, 714a], [705a, 707a, 716a, 725a, 730a, 736a, 738a, 742a], ["-", "-", "-", 732a, 739a, 745a, 747a, 751a], [738a, 741a, 750a, 759a, 806a, 812a, 814a, 818a], [808a, 811a, 820a, 829a, 836a, 842a, 844a, 848a], [842a, 845a, 854a, 903a, 910a, 916a, 918a, 922a], [912a, 915a, 924a, 933a, 939a, 945a, 947a, 951a], [938a, 940a, 949a, 958a, 1004a, 1010a, 1012a, 1016a], [1037a, 1039a, 1048a, 1057a, 1103a, 1109a, 1111a, 1115a], [1137a, 1139a, 1148a, 1157a, 1203p, 1209p, 1211p, 1215p], [1237p, 1239p, 1248p, 1257p, 103p, 109p, 111p, 115p], [137p, 139p, 148p, 157p, 203p, 209p, 211p, 215p], [237p, 239p, 248p, 257p, 304p, 310p, 312p, 316p], [313p, 315p, 324p, 333p, 340p, 346p, 348p, 352p], [348p, 350p, 359p, 408p, 415p, 421p, 423p, 427p], [420p, 422p, 431p, 440p, 447p, 453p, 455p, 459p], [452p, 454p, 503p, 512p, 519p, 525p, 527p, 531p], [523p, 525p, 534p, 543p, 550p, 556p, 558p, 602p], [600p, 602p, 611p, 620p, 627p, 633p, 635p, 639p], [628p, 630p, 639p, 648p, 654p, 659p, 701p, 705p], [642p, 644p, 653p, 702p, 708p, 713p, 715p, 719p], [737p, 739p, 748p, 757p, 803p, 808p, 810p, 814p], [837p, 839p, 848p, 857p, 903p, 908p, 910p, 914p], [937p, 939p, 948p, 957p, 1003p, 1008p, 1010p, 1014p], [1037p, 1039p, 1048p, 1057p, 1103p, 1108p, 1110p, 1114p]] stop_times: [[605a, 607a, 616a, 625a, 630a, 635a, 637a, 641a], [638a, 640a, 649a, 658a, 703a, 708a, 710a, 714a], [705a, 707a, 716a, 725a, 730a, 736a, 738a, 742a], ["-", "-", "-", 732a, 739a, 745a, 747a, 751a], [738a, 741a, 750a, 759a, 806a, 812a, 814a, 818a], [808a, 811a, 820a, 829a, 836a, 842a, 844a, 848a], [842a, 845a, 854a, 903a, 910a, 916a, 918a, 922a], [912a, 915a, 924a, 933a, 939a, 945a, 947a, 951a], [938a, 940a, 949a, 958a, 1004a, 1010a, 1012a, 1016a], [1037a, 1039a, 1048a, 1057a, 1103a, 1109a, 1111a, 1115a], [1137a, 1139a, 1148a, 1157a, 1203p, 1209p, 1211p, 1215p], [1237p, 1239p, 1248p, 1257p, 103p, 109p, 111p, 115p], [137p, 139p, 148p, 157p, 203p, 209p, 211p, 215p], [237p, 239p, 248p, 257p, 304p, 310p, 312p, 316p], [313p, 315p, 324p, 333p, 340p, 346p, 348p, 352p], [348p, 350p, 359p, 408p, 415p, 421p, 423p, 427p], [420p, 422p, 431p, 440p, 447p, 453p, 455p, 459p], [452p, 454p, 503p, 512p, 519p, 525p, 527p, 531p], [523p, 525p, 534p, 543p, 550p, 556p, 558p, 602p], [600p, 602p, 611p, 620p, 627p, 633p, 635p, 639p], [628p, 630p, 639p, 648p, 654p, 659p, 701p, 705p], [642p, 644p, 653p, 702p, 708p, 713p, 715p, 719p], [737p, 739p, 748p, 757p, 803p, 808p, 810p, 814p], [837p, 839p, 848p, 857p, 903p, 908p, 910p, 914p], [937p, 939p, 948p, 957p, 1003p, 1008p, 1010p, 1014p], [1037p, 1039p, 1048p, 1057p, 1103p, 1108p, 1110p, 1114p]]
- -
time_points: [Woden Bus Station (Platform 5), Kambah Village, Kambah High, Tuggeranong Bus Station] time_points: [Woden Bus Station (Platform 5), Kambah Village, Kambah High, Tuggeranong Bus Station]
long_name: To Tuggeranong Bus Station long_name: To Tuggeranong Bus Station
between_stops: {} between_stops: {}
stop_times_saturday: [[851a, 902a, 910a, 917a], [951a, 1002a, 1010a, 1017a], [1051a, 1102a, 1110a, 1117a], [1151a, 1202p, 1210p, 1217p], [1251p, 102p, 110p, 117p], [151p, 202p, 210p, 217p], [251p, 302p, 310p, 317p], [351p, 402p, 410p, 417p], [451p, 502p, 510p, 517p], [551p, 602p, 610p, 617p], [651p, 702p, 710p, 717p], [751p, 802p, 810p, 817p], [851p, 902p, 910p, 917p], [951p, 1002p, 1010p, 1017p], [1051p, 1102p, 1110p, 1117p]] stop_times_saturday: [[851a, 902a, 910a, 917a], [951a, 1002a, 1010a, 1017a], [1051a, 1102a, 1110a, 1117a], [1151a, 1202p, 1210p, 1217p], [1251p, 102p, 110p, 117p], [151p, 202p, 210p, 217p], [251p, 302p, 310p, 317p], [351p, 402p, 410p, 417p], [451p, 502p, 510p, 517p], [551p, 602p, 610p, 617p], [651p, 702p, 710p, 717p], [751p, 802p, 810p, 817p], [851p, 902p, 910p, 917p], [951p, 1002p, 1010p, 1017p], [1051p, 1102p, 1110p, 1117p]]
short_name: "962" short_name: "962"
- -
time_points: [Cohen Street Bus Station (Platform 2), Westfield Bus Station (Platform 1), Belconnen Community Bus Station (Platform 3), Jamison Centre, Cook Shops, Aranda, Caswell Drive, City Bus Station] time_points: [Tuggeranong Bus Station (Platform 7), Heagney / Clift Richardson, Chisholm, Erindale Centre, Tuggeranong Bus Station]
long_name: To City Bus Station long_name: To Tuggeranong Bus Station
between_stops: between_stops: {}
Westfield Bus Station (Platform 1)-Belconnen Community Bus Station (Platform 3): []  
Cohen Street Bus Station (Platform 2)-Westfield Bus Station (Platform 1): [] short_name: "968"
short_name: "942" stop_times_sunday: [[1003a, 1016a, 1024a, 1038a, 1048a], [1203p, 1216p, 1224p, 1238p, 1248p], [203p, 216p, 224p, 238p, 248p], [403p, 416p, 424p, 438p, 448p], [603p, 616p, 624p, 638p, 648p]]
stop_times: [[815a, 817a, 821a, 830a, 839a, 843a, 844a, 855a], [915a, 917a, 921a, 930a, 939a, 943a, 944a, 955a], [1015a, 1017a, 1021a, 1030a, 1039a, 1043a, 1044a, 1055a], [1115a, 1117a, 1121a, 1130a, 1139a, 1143a, 1144a, 1155a], [1215p, 1217p, 1221p, 1230p, 1239p, 1243p, 1244p, 1255p], [115p, 117p, 121p, 130p, 139p, 143p, 144p, 155p], [215p, 217p, 221p, 230p, 239p, 243p, 244p, 255p], [315p, 317p, 321p, 330p, 339p, 343p, 344p, 355p], [415p, 417p, 421p, 430p, 439p, 443p, 444p, 455p], [515p, 517p, 521p, 530p, 539p, 543p, 544p, 555p], [615p, 617p, 621p, 630p, 639p, 643p, 644p, 655p]] -
- time_points: [City West, City Bus Station (Platform 10), Russell Offices, Mentone View / Tharwa Drive, Tharwa Drive / Pockett Ave, Woodcock / Clare Dennis]
time_points: [Cohen Street Bus Station (Platform 2), Westfield Bus Station (Platform 1), Belconnen Community Bus Station (Platform 3), Calvary Hospital, O'Connor Shops, Burton and Garran Hall Daley Road, National Museum of Australia, City Bus Station (Platform 2), Kings Ave / National Circuit, Deakin Shops, Hughes Shops, Garran Shops, Canberra Hospital, Woden Bus Station] long_name: To Woodcock / Clare Dennis
long_name: To Woden Bus Station between_stops:
between_stops: City West-City Bus Station (Platform 10): []
Westfield Bus Station (Platform 1)-Belconnen Community Bus Station (Platform 3): [] City Bus Station (Platform 10)-Russell Offices: [Wjz5Nht, Wjz5MsD, Wjz5MsT, Wjz5MEL, Wjz4T-X, Wjz4_jm, Wjz4_wS, Wjz4-KO, Wjz4-Rc, Wjz4-WL, Wjz4-WZ]
City Bus Station (Platform 2)-Kings Ave / National Circuit: [Wjz5FOn, Wjz4S1U, Wjz4Rs-, Wjz4RFJ, Wjz4RwH, Wjz4Quk] short_name: "788"
Cohen Street Bus Station (Platform 2)-Westfield Bus Station (Platform 1): [] stop_times: [[426p, 432p, 441p, 512p, 526p, 536p], [502p, 507p, 518p, 552p, 606p, 615p], [532p, 538p, 547p, 618p, 632p, 642p]]
Canberra Hospital-Woden Bus Station: [Wjz3mWn, Wjz3mPO, Wjz3mAg] -
short_name: "934" time_points: [Belconnen Community Bus Station (Platform 4), Westfield Bus Station (Platform 2), Cohen Street Bus Station (Platform 6), Kippax, Macgregor, Charnwood, Fraser West Terminus, Charnwood, Macgregor, Kippax, Cohen Street Bus Station, Westfield Bus Station, Belconnen Community Bus Station]
stop_times: [[829a, 831a, 835a, 852a, 859a, 904a, 909a, 919a, 928a, 937a, 942a, 946a, 948a, 955a], [929a, 931a, 935a, 952a, 959a, 1004a, 1009a, 1019a, 1028a, 1037a, 1042a, 1046a, 1048a, 1055a], [1029a, 1031a, 1035a, 1052a, 1059a, 1104a, 1109a, 1119a, 1128a, 1137a, 1142a, 1146a, 1148a, 1155a], [1129a, 1131a, 1135a, 1152a, 1159a, 1204p, 1209p, 1219p, 1228p, 1237p, 1242p, 1246p, 1248p, 1255p], [1229p, 1231p, 1235p, 1252p, 1259p, 104p, 109p, 119p, 128p, 137p, 142p, 146p, 148p, 155p], [129p, 131p, 135p, 152p, 159p, 204p, 209p, 219p, 228p, 237p, 242p, 246p, 248p, 255p], [229p, 231p, 235p, 252p, 259p, 304p, 309p, 319p, 328p, 337p, 342p, 346p, 348p, 355p], [329p, 331p, 335p, 352p, 359p, 404p, 409p, 419p, 428p, 437p, 442p, 446p, 448p, 455p], [429p, 431p, 435p, 452p, 459p, 504p, 509p, 519p, 528p, 537p, 542p, 546p, 548p, 555p], [529p, 531p, 535p, 552p, 559p, 604p, 609p, 619p, 628p, 637p, 642p, 646p, 648p, 655p], [629p, 631p, 635p, 652p, 659p, 704p, 709p, 719p, 728p, 737p, 742p, 746p, 748p, 755p]]  
-  
time_points: [Belconnen Community Bus Station (Platform 4), Westfield Bus Station (Platform 2), Cohen Street Bus Station (Platform 6), Melba Shops, Spence Terminus, Melba Shops, Cohen Street Bus Station, Westfield Bus Station, Belconnen Community Bus Station]  
long_name: To Belconnen Community Bus Station long_name: To Belconnen Community Bus Station
between_stops: between_stops:
Belconnen Community Bus Station (Platform 4)-Westfield Bus Station (Platform 2): [] Belconnen Community Bus Station (Platform 4)-Westfield Bus Station (Platform 2): []
Westfield Bus Station-Belconnen Community Bus Station: [] Westfield Bus Station-Belconnen Community Bus Station: []
Cohen Street Bus Station-Westfield Bus Station: [] Cohen Street Bus Station-Westfield Bus Station: []
Westfield Bus Station (Platform 2)-Cohen Street Bus Station (Platform 6): [] Westfield Bus Station (Platform 2)-Cohen Street Bus Station (Platform 6): []
short_name: "906" short_name: "905"
stop_times_sunday: [[852a, 854a, 858a, 911a, 925a, 938a, 953a, 955a, 959a], [952a, 954a, 958a, 1011a, 1025a, 1038a, 1053a, 1055a, 1059a], [1052a, 1054a, 1058a, 1111a, 1125a, 1138a, 1153a, 1155a, 1159a], [1152a, 1154a, 1158a, 1211p, 1225p, 1238p, 1253p, 1255p, 1259p], [1252p, 1254p, 1258p, 111p, 125p, 138p, 153p, 155p, 159p], [152p, 154p, 158p, 211p, 225p, 238p, 253p, 255p, 259p], [252p, 254p, 258p, 311p, 325p, 338p, 353p, 355p, 359p], [352p, 354p, 358p, 411p, 425p, 438p, 453p, 455p, 459p], [452p, 454p, 458p, 511p, 525p, 538p, 553p, 555p, 559p], [552p, 554p, 558p, 611p, 625p, 638p, 652p, 654p, 658p]] stop_times_sunday: [["-", "-", "-", "-", "-", "-", 857a, 909a, 916a, 923a, 936a, 938a, 942a], [914a, 916a, 920a, 933a, 939a, 946a, 957a, 1009a, 1016a, 1023a, 1036a, 1038a, 1042a], [1014a, 1016a, 1020a, 1033a, 1039a, 1046a, 1057a, 1109a, 1116a, 1123a, 1136a, 1138a, 1142a], [1114a, 1116a, 1120a, 1133a, 1139a, 1146a, 1157a, 1209p, 1216p, 1223p, 1236p, 1238p, 1242p], [1214p, 1216p, 1220p, 1233p, 1239p, 1246p, 1257p, 109p, 116p, 123p, 136p, 138p, 142p], [114p, 116p, 120p, 133p, 139p, 146p, 157p, 209p, 216p, 223p, 236p, 238p, 242p], [214p, 216p, 220p, 233p, 239p, 246p, 257p, 309p, 316p, 323p, 336p, 338p, 342p], [314p, 316p, 320p, 333p, 339p, 346p, 357p, 409p, 416p, 423p, 436p, 438p, 442p], [414p, 416p, 420p, 433p, 439p, 446p, 457p, 509p, 516p, 523p, 536p, 538p, 542p], [514p, 516p, 520p, 533p, 539p, 546p, 557p, 609p, 616p, 623p, 636p, 638p, 642p], [614p, 616p, 620p, 633p, 639p, 646p, 656p, "-", "-", "-", "-", "-", "-"]]
- -
time_points: [City Bus Station (Platform 9), Russell Offices, Kings Ave / National Circuit, Kingston, Narrabundah College, Canberra Hospital, Woden Bus Station] time_points: [City Bus Station (Platform 9), Russell Offices, Kings Ave / National Circuit, Kingston, Narrabundah College, Canberra Hospital, Woden Bus Station]
long_name: To Woden Bus Station long_name: To Woden Bus Station
between_stops: between_stops:
Canberra Hospital-Woden Bus Station: [Wjz3mWn, Wjz3mPO, Wjz3mAg] Canberra Hospital-Woden Bus Station: [Wjz3mWn, Wjz3mPO, Wjz3mAg]
stop_times_saturday: [[746a, 754a, 758a, 802a, 817a, 827a, 834a], [846a, 854a, 858a, 902a, 917a, 927a, 934a], [946a, 954a, 958a, 1002a, 1017a, 1027a, 1034a], [1046a, 1054a, 1058a, 1102a, 1117a, 1127a, 1134a], [1146a, 1154a, 1158a, 1202p, 1217p, 1227p, 1234p], [1246p, 1254p, 1258p, 102p, 117p, 127p, 134p], [146p, 154p, 158p, 202p, 217p, 227p, 234p], [246p, 254p, 258p, 302p, 317p, 327p, 334p], [346p, 354p, 358p, 402p, 417p, 427p, 434p], [446p, 454p, 458p, 502p, 517p, 527p, 534p], [546p, 554p, 558p, 602p, 617p, 627p, 634p], [646p, 654p, 658p, 702p, 715p, 724p, 731p], [746p, 753p, 757p, 801p, 814p, 823p, 830p], [846p, 853p, 857p, 901p, 914p, 923p, 930p], [946p, 953p, 957p, 1001p, 1014p, 1023p, 1030p], [1046p, 1053p, 1057p, 1101p, 1114p, 1123p, 1130p]] stop_times_saturday: [[746a, 754a, 758a, 802a, 817a, 827a, 834a], [846a, 854a, 858a, 902a, 917a, 927a, 934a], [946a, 954a, 958a, 1002a, 1017a, 1027a, 1034a], [1046a, 1054a, 1058a, 1102a, 1117a, 1127a, 1134a], [1146a, 1154a, 1158a, 1202p, 1217p, 1227p, 1234p], [1246p, 1254p, 1258p, 102p, 117p, 127p, 134p], [146p, 154p, 158p, 202p, 217p, 227p, 234p], [246p, 254p, 258p, 302p, 317p, 327p, 334p], [346p, 354p, 358p, 402p, 417p, 427p, 434p], [446p, 454p, 458p, 502p, 517p, 527p, 534p], [546p, 554p, 558p, 602p, 617p, 627p, 634p], [646p, 654p, 658p, 702p, 715p, 724p, 731p], [746p, 753p, 757p, 801p, 814p, 823p, 830p], [846p, 853p, 857p, 901p, 914p, 923p, 930p], [946p, 953p, 957p, 1001p, 1014p, 1023p, 1030p], [1046p, 1053p, 1057p, 1101p, 1114p, 1123p, 1130p]]
short_name: "938" short_name: "938"
- -
time_points: [Tuggeranong Bus Station (Platform 8), Woden Bus Station (Platform 9), City Bus Station (Platform 3), Belconnen Community Bus Station (Platform 4), Westfield Bus Station (Platform 2), Cohen Street Bus Station (Platform 6), McKellar Shops, Copland College, Evatt Shops, Spence Terminus] time_points: [Tuggeranong Bus Station (Platform 8), Woden Bus Station (Platform 9), City Bus Station (Platform 3), Belconnen Community Bus Station (Platform 4), Westfield Bus Station (Platform 2), Cohen Street Bus Station (Platform 6), McKellar, Copland College, Evatt, Spence Terminus]
long_name: To Spence Terminus long_name: To Spence Terminus
between_stops: between_stops:
Belconnen Community Bus Station (Platform 4)-Westfield Bus Station (Platform 2): [] Belconnen Community Bus Station (Platform 4)-Westfield Bus Station (Platform 2): []
Tuggeranong Bus Station (Platform 8)-Woden Bus Station (Platform 9): [Wjz213q, Wjz238T, Wjz239F, Wjz2lDC, Wjz2mGO, Wjz2mTK, Wjz2nLE, Wjz3gMq, Wjz3gQn, Wjz3gK-, Wjz3hL_, Wjz3iFK, Wjz3kwU, Wjz3kyX, Wjz3kAx] Tuggeranong Bus Station (Platform 8)-Woden Bus Station (Platform 9): [Wjz213q, Wjz238T, Wjz239F, Wjz2lDC, Wjz2mGO, Wjz2mTK, Wjz2nLE, Wjz3gMq, Wjz3gQn, Wjz3gK-, Wjz3hL_, Wjz3iFK, Wjz3kwU, Wjz3kyX, Wjz3kAx]
Westfield Bus Station (Platform 2)-Cohen Street Bus Station (Platform 6): [] Westfield Bus Station (Platform 2)-Cohen Street Bus Station (Platform 6): []
City Bus Station (Platform 3)-Belconnen Community Bus Station (Platform 4): [Wjz5F-1, Wjz5FSY, Wjz5GMT, Wjz5GNG, Wjz5G6U, Wjz5G6B, Wjz5maK, Wjz5mbS, Wjz5nwb, Wjz5nw6, Wjz6giR, Wjz6gia, Wjz68W3, Wjz68W5, Wjz689c, Wjz681S] City Bus Station (Platform 3)-Belconnen Community Bus Station (Platform 4): [Wjz5F-1, Wjz5FSY, Wjz5GMT, Wjz5GNG, Wjz5G6U, Wjz5G6B, Wjz5maK, Wjz5mbS, Wjz5nwb, Wjz5nw6, Wjz6giR, Wjz6gia, Wjz68W3, Wjz68W5, Wjz689c, Wjz681S]
Woden Bus Station (Platform 9)-City Bus Station (Platform 3): [Wjz3m3b, Wjz3m3b, Wjz3eRR, Wjz3eZ4, Wjz4KNu, Wjz4KO9, Wjz5Nht] Woden Bus Station (Platform 9)-City Bus Station (Platform 3): [Wjz3m3b, Wjz3m3b, Wjz3eRR, Wjz3eZ4, Wjz4KNu, Wjz4KO9, Wjz5Nht]
short_name: 12 312 short_name: 12 312
stop_times: [["-", "-", "-", 723a, 725a, 729a, 737a, 741a, 746a, 753a], ["-", "-", "-", 805a, 807a, 811a, 819a, 823a, 828a, 835a], [726a, 745a, 803a, 824a, 826a, 830a, 838a, 842a, 847a, 854a], [826a, 845a, 903a, 924a, 926a, 930a, 937a, 941a, 945a, 952a], [901a, 920a, 937a, 957a, 959a, 1003a, 1010a, 1014a, 1018a, 1025a], [931a, 949a, 1005a, 1025a, 1027a, 1031a, 1038a, 1042a, 1046a, 1053a], [1001a, 1019a, 1035a, 1055a, 1057a, 1101a, 1108a, 1112a, 1116a, 1123a], [1031a, 1049a, 1105a, 1125a, 1127a, 1131a, 1138a, 1142a, 1146a, 1153a], [1101a, 1119a, 1135a, 1155a, 1157a, 1201p, 1208p, 1212p, 1216p, 1223p], [1131a, 1149a, 1205p, 1225p, 1227p, 1231p, 1238p, 1242p, 1246p, 1253p], [1201p, 1219p, 1235p, 1255p, 1257p, 101p, 108p, 112p, 116p, 123p], [1231p, 1249p, 105p, 125p, 127p, 131p, 138p, 142p, 146p, 153p], [101p, 119p, 135p, 155p, 157p, 201p, 208p, 212p, 216p, 223p], [131p, 149p, 205p, 225p, 227p, 231p, 238p, 242p, 246p, 253p], [201p, 219p, 235p, 255p, 257p, 301p, 309p, 313p, 318p, 325p], [231p, 249p, 305p, 326p, 328p, 332p, 340p, 344p, 349p, 356p], [259p, 318p, 336p, 357p, 359p, 403p, 411p, 415p, 420p, 427p], [331p, 350p, 408p, 429p, 431p, 435p, 443p, 447p, 452p, 459p], [356p, 415p, 433p, 454p, 456p, 500p, 508p, 512p, 517p, 524p], [416p, 435p, 453p, 514p, 516p, 520p, 528p, 532p, 537p, 544p], [436p, 455p, 513p, 534p, 536p, 540p, 548p, 552p, 557p, 604p], [456p, 515p, 533p, 554p, 556p, 600p, 608p, 612p, 617p, 624p], [516p, 535p, 553p, 614p, 616p, 620p, 628p, 632p, 636p, 643p], [536p, 555p, 613p, 634p, 636p, 640p, 647p, 651p, 655p, 702p], [636p, 653p, 708p, 728p, 730p, 734p, 741p, 745p, 749p, 756p], ["-", "-", "-", 834p, 836p, 840p, 847p, 851p, 855p, 902p], ["-", "-", "-", 934p, 936p, 940p, 947p, 951p, 955p, 1002p], ["-", "-", "-", 1034p, 1036p, 1040p, 1047p, 1051p, 1055p, 1102p], ["-", "-", "-", 1134p, 1136p, 1140p, 1147p, 1151p, 1155p, 1202a]] stop_times: [["-", "-", "-", 723a, 725a, 729a, 737a, 741a, 746a, 753a], ["-", "-", "-", 805a, 807a, 811a, 819a, 823a, 828a, 835a], [726a, 745a, 803a, 824a, 826a, 830a, 838a, 842a, 847a, 854a], [826a, 845a, 903a, 924a, 926a, 930a, 937a, 941a, 945a, 952a], [901a, 920a, 937a, 957a, 959a, 1003a, 1010a, 1014a, 1018a, 1025a], [931a, 949a, 1005a, 1025a, 1027a, 1031a, 1038a, 1042a, 1046a, 1053a], [1001a, 1019a, 1035a, 1055a, 1057a, 1101a, 1108a, 1112a, 1116a, 1123a], [1031a, 1049a, 1105a, 1125a, 1127a, 1131a, 1138a, 1142a, 1146a, 1153a], [1101a, 1119a, 1135a, 1155a, 1157a, 1201p, 1208p, 1212p, 1216p, 1223p], [1131a, 1149a, 1205p, 1225p, 1227p, 1231p, 1238p, 1242p, 1246p, 1253p], [1201p, 1219p, 1235p, 1255p, 1257p, 101p, 108p, 112p, 116p, 123p], [1231p, 1249p, 105p, 125p, 127p, 131p, 138p, 142p, 146p, 153p], [101p, 119p, 135p, 155p, 157p, 201p, 208p, 212p, 216p, 223p], [131p, 149p, 205p, 225p, 227p, 231p, 238p, 242p, 246p, 253p], [201p, 219p, 235p, 255p, 257p, 301p, 309p, 313p, 318p, 325p], [231p, 249p, 305p, 326p, 328p, 332p, 340p, 344p, 349p, 356p], [259p, 318p, 336p, 357p, 359p, 403p, 411p, 415p, 420p, 427p], [331p, 350p, 408p, 429p, 431p, 435p, 443p, 447p, 452p, 459p], [356p, 415p, 433p, 454p, 456p, 500p, 508p, 512p, 517p, 524p], [416p, 435p, 453p, 514p, 516p, 520p, 528p, 532p, 537p, 544p], [436p, 455p, 513p, 534p, 536p, 540p, 548p, 552p, 557p, 604p], [456p, 515p, 533p, 554p, 556p, 600p, 608p, 612p, 617p, 624p], [516p, 535p, 553p, 614p, 616p, 620p, 628p, 632p, 636p, 643p], [536p, 555p, 613p, 634p, 636p, 640p, 647p, 651p, 655p, 702p], [636p, 653p, 708p, 728p, 730p, 734p, 741p, 745p, 749p, 756p], ["-", "-", "-", 834p, 836p, 840p, 847p, 851p, 855p, 902p], ["-", "-", "-", 934p, 936p, 940p, 947p, 951p, 955p, 1002p], ["-", "-", "-", 1034p, 1036p, 1040p, 1047p, 1051p, 1055p, 1102p], ["-", "-", "-", 1134p, 1136p, 1140p, 1147p, 1151p, 1155p, 1202a]]
- -
time_points: [City West, City Bus Station (Platform 10), Hughes Shops, Garran Shops, Southlands Mawson, Farrer Terminus] time_points: [City West, City Bus Station (Platform 10), Hughes, Garran, Southlands Mawson, Farrer Terminus]
long_name: To Farrer Terminus long_name: To Farrer Terminus
between_stops: between_stops:
City West-City Bus Station (Platform 10): [] City West-City Bus Station (Platform 10): []
short_name: "720" short_name: "720"
stop_times: [[440p, 446p, 504p, 510p, 523p, 529p], [510p, 516p, 534p, 540p, 553p, 559p], [540p, 546p, 604p, 610p, 623p, 629p]] stop_times: [[440p, 446p, 504p, 510p, 523p, 529p], [510p, 516p, 534p, 540p, 553p, 559p], [540p, 546p, 604p, 610p, 623p, 629p]]
- -
time_points: [Tuggeranong Bus Station (Platform 4), Isabella Shops, Calwell Shops, Theodore, Outtrim / Duggan, Tuggeranong Bus Station] time_points: [City West, City Bus Station (Platform 10), Russell Offices, Kings Ave / National Circuit, Woden Bus Station (Platform 5), Erindale / Sternberg Cres, Bugden Sternberg, Chisholm, Calwell, Tuggeranong Bus Station]
long_name: To Tuggeranong Bus Station  
between_stops: {}  
   
short_name: "912"  
stop_times: [[1015a, 1025a, 1030a, 1039a, 1046a, 1055a], [1215p, 1225p, 1230p, 1239p, 1246p, 1255p], [215p, 225p, 230p, 239p, 246p, 255p], [415p, 425p, 430p, 439p, 446p, 455p], [615p, 625p, 630p, 639p, 646p, 655p]]  
-  
time_points: [City West, City Bus Station (Platform 10), Russell Offices, Kings Ave / National Circuit, Woden Bus Station (Platform 5), Erindale / Sternberg Cres, Bugden Sternberg, Chisholm Shops, Calwell Shops, Tuggeranong Bus Station]  
long_name: To Tuggeranong Bus Station long_name: To Tuggeranong Bus Station
between_stops: between_stops:
Russell Offices-Kings Ave / National Circuit: [Wjz4RFJ, Wjz4RwH, Wjz4Quk] Russell Offices-Kings Ave / National Circuit: [Wjz4RFJ, Wjz4RwH, Wjz4Quk]
City West-City Bus Station (Platform 10): [] City West-City Bus Station (Platform 10): []
City Bus Station (Platform 10)-Russell Offices: [Wjz5Nht, Wjz5MsD, Wjz5MsT, Wjz5MEL, Wjz4T-X, Wjz4_jm, Wjz4_wS, Wjz4-KO, Wjz4-Rc, Wjz4-WL, Wjz4-WZ] City Bus Station (Platform 10)-Russell Offices: [Wjz5Nht, Wjz5MsD, Wjz5MsT, Wjz5MEL, Wjz4T-X, Wjz4_jm, Wjz4_wS, Wjz4-KO, Wjz4-Rc, Wjz4-WL, Wjz4-WZ]
short_name: 67 267 short_name: 67 267
stop_times: [["-", "-", "-", "-", "-", "-", 601a, 608a, 618a, 632a], ["-", "-", "-", "-", 617a, 626a, 626a, 633a, 643a, 657a], ["-", "-", "-", "-", 647a, 656a, 656a, 703a, 713a, 727a], ["-", "-", "-", "-", 717a, 726a, 726a, 734a, 746a, 803a], ["-", "-", "-", "-", 747a, 804a, 804a, 813a, 825a, 842a], ["-", "-", "-", "-", 817a, 834a, 834a, 843a, 855a, 912a], ["-", "-", "-", "-", 847a, 904a, 904a, 913a, 925a, 941a], ["-", "-", "-", "-", 917a, 933a, 933a, 940a, 949a, 1004a], ["-", "-", "-", "-", 1017a, 1030a, 1030a, 1037a, 1046a, 1101a], ["-", "-", "-", "-", 1117a, 1130a, 1130a, 1137a, 1146a, 1201p], ["-", "-", "-", "-", 1217p, 1230p, 1230p, 1237p, 1246p, 101p], ["-", "-", "-", "-", 117p, 130p, 130p, 137p, 146p, 201p], ["-", "-", "-", "-", 217p, 230p, 230p, 237p, 246p, 301p], ["-", "-", "-", "-", 247p, 300p, 300p, 310p, 325p, 341p], ["-", "-", "-", "-", 317p, 334p, 334p, 344p, 359p, 415p], ["-", "-", "-", "-", 347p, 404p, 404p, 414p, 429p, 445p], ["-", "-", "-", "-", 417p, 434p, 434p, 444p, 459p, 515p], ["-", "-", "-", "-", 447p, 504p, 504p, 514p, 529p, 545p], [430p, 436p, 445p, 448p, 503p, 520p, 520p, 530p, 545p, 601p], [500p, 506p, 515p, 518p, 533p, 550p, 550p, 600p, 615p, 631p], [544p, 550p, 559p, 602p, 617p, 633p, 633p, 640p, 649p, 704p], ["-", "-", "-", "-", 717p, 730p, 730p, 737p, 746p, 801p], ["-", "-", "-", "-", 817p, 830p, 830p, 837p, 846p, 901p], ["-", "-", "-", "-", 917p, 930p, 930p, 937p, 946p, 1001p], ["-", "-", "-", "-", 1017p, 1030p, 1030p, 1037p, 1046p, 1101p], ["-", "-", "-", "-", 1117p, 1130p, 1130p, 1137p, 1146p, 1201a]] stop_times: [["-", "-", "-", "-", "-", "-", 601a, 608a, 618a, 632a], ["-", "-", "-", "-", 617a, 626a, 626a, 633a, 643a, 657a], ["-", "-", "-", "-", 647a, 656a, 656a, 703a, 713a, 727a], ["-", "-", "-", "-", 717a, 726a, 726a, 734a, 746a, 803a], ["-", "-", "-", "-", 747a, 804a, 804a, 813a, 825a, 842a], ["-", "-", "-", "-", 817a, 834a, 834a, 843a, 855a, 912a], ["-", "-", "-", "-", 847a, 904a, 904a, 913a, 925a, 941a], ["-", "-", "-", "-", 917a, 933a, 933a, 940a, 949a, 1004a], ["-", "-", "-", "-", 1017a, 1030a, 1030a, 1037a, 1046a, 1101a], ["-", "-", "-", "-", 1117a, 1130a, 1130a, 1137a, 1146a, 1201p], ["-", "-", "-", "-", 1217p, 1230p, 1230p, 1237p, 1246p, 101p], ["-", "-", "-", "-", 117p, 130p, 130p, 137p, 146p, 201p], ["-", "-", "-", "-", 217p, 230p, 230p, 237p, 246p, 301p], ["-", "-", "-", "-", 247p, 300p, 300p, 310p, 325p, 341p], ["-", "-", "-", "-", 317p, 334p, 334p, 344p, 359p, 415p], ["-", "-", "-", "-", 347p, 404p, 404p, 414p, 429p, 445p], ["-", "-", "-", "-", 417p, 434p, 434p, 444p, 459p, 515p], ["-", "-", "-", "-", 447p, 504p, 504p, 514p, 529p, 545p], [430p, 436p, 445p, 448p, 503p, 520p, 520p, 530p, 545p, 601p], [500p, 506p, 515p, 518p, 533p, 550p, 550p, 600p, 615p, 631p], [544p, 550p, 559p, 602p, 617p, 633p, 633p, 640p, 649p, 704p], ["-", "-", "-", "-", 717p, 730p, 730p, 737p, 746p, 801p], ["-", "-", "-", "-", 817p, 830p, 830p, 837p, 846p, 901p], ["-", "-", "-", "-", 917p, 930p, 930p, 937p, 946p, 1001p], ["-", "-", "-", "-", 1017p, 1030p, 1030p, 1037p, 1046p, 1101p], ["-", "-", "-", "-", 1117p, 1130p, 1130p, 1137p, 1146p, 1201a]]
- -
time_points: [Tuggeranong Bus Station (Platform 3), MacKillop College Isabella Campus, Theodore, Calwell Shops, Erindale Centre, Woden Bus Station (Platform 9), City Bus Station] time_points: [City Bus Station (Platform 8), Macarthur / Northbourne Ave, Northbourne Avenue / Antill St, Flemington Rd / Sandford St, Kosciuszko / Everard, Gungahlin Marketplace, Chuculba / William Slim Dr, Belconnen Community Bus Station, Westfield Bus Station, Cohen Street Bus Station]
  long_name: To Cohen Street Bus Station
  between_stops:
  Westfield Bus Station-Cohen Street Bus Station: []
  Macarthur / Northbourne Ave-Northbourne Avenue / Antill St: [Wjz5RkN, Wjz5Rsi, Wjz5RvC, Wjz5Sqk, Wjz5SrO, Wjz5Sux, Wjz5SDc]
  City Bus Station (Platform 8)-Macarthur / Northbourne Ave: [Wjz5O3Q, Wjz5Oci, Wjz5P8n, Wjz5P8K, Wjz5PdJ, Wjz5Pl0, Wjz5Qi2, Wjz5Qgn, Wjz5Qmu, Wjz5QmR]
  Belconnen Community Bus Station-Westfield Bus Station: []
  Chuculba / William Slim Dr-Belconnen Community Bus Station: [Wjz6mip, Wjz69vO, Wjz69uI, Wjz69ht, Wjz69gA]
  short_name: "56"
  stop_times: [["-", "-", "-", "-", 602a, 612a, 623a, 639a, 641a, 646a], ["-", "-", "-", "-", 636a, 646a, 657a, 713a, 715a, 720a], ["-", "-", "-", "-", 706a, 716a, 727a, 743a, 745a, 750a], [651a, 657a, 659a, 705a, 712a, 722a, 733a, 749a, 751a, 756a], ["-", "-", "-", "-", 726a, 736a, 747a, 804a, 806a, 811a], ["-", "-", "-", "-", 743a, 755a, 806a, 823a, 825a, 830a], [741a, 747a, 749a, 755a, 803a, 815a, 826a, 843a, 845a, 850a], [801a, 808a, 810a, 816a, 824a, 836a, 847a, 904a, 906a, 911a], [821a, 828a, 830a, 836a, 844a, 856a, 906a, 922a, 924a, 929a], [851a, 858a, 900a, 906a, 913a, 925a, 935a, 951a, 953a, 958a], [1004a, 1010a, 1012a, 1018a, 1025a, 1037a, 1047a, 1103a, 1105a, 1110a], [1104a, 1110a, 1112a, 1118a, 1125a, 1137a, 1147a, 1203p, 1205p, 1210p], [1204p, 1210p, 1212p, 1218p, 1225p, 1237p, 1247p, 103p, 105p, 110p], [104p, 110p, 112p, 118p, 125p, 137p, 147p, 203p, 205p, 210p], [204p, 210p, 212p, 218p, 225p, 237p, 247p, 303p, 305p, 310p], [302p, 309p, 311p, 318p, 326p, 338p, 349p, 406p, 408p, 413p], [358p, 405p, 407p, 414p, 422p, 434p, 445p, 502p, 504p, 509p], [409p, 416p, 418p, 425p, 433p, 445p, 456p, 513p, 515p, 520p], [429p, 436p, 438p, 445p, 453p, 505p, 516p, 533p, 535p, 540p], [449p, 456p, 458p, 505p, 513p, 525p, 536p, 553p, 555p, 600p], [510p, 517p, 519p, 526p, 534p, 546p, 557p, 613p, 615p, 620p], [530p, 537p, 539p, 546p, 554p, 605p, 615p, 631p, 633p, 638p], [550p, 557p, 559p, 604p, 611p, 621p, 631p, 647p, 649p, 654p], [610p, 616p, 618p, 623p, 630p, 640p, 650p, 706p, 708p, 713p], [704p, 710p, 712p, 717p, 724p, 734p, 744p, 800p, 802p, 807p], [804p, 810p, 812p, 817p, 824p, 834p, 844p, 900p, 902p, 907p], [904p, 910p, 912p, 917p, 924p, 934p, 944p, 1000p, 1002p, 1007p], [1004p, 1010p, 1012p, 1017p, 1024p, 1034p, 1044p, 1100p, 1102p, 1107p], [1104p, 1110p, 1112p, 1117p, 1124p, 1134p, 1144p, 1200a, 1202a, 1207a]]
  -
  time_points: [Geoscience Australia, Narrabundah Terminus, Narrabundah College, Manuka / Captain Cook Cres, Kingston, Kings Ave / National Circuit, Russell Offices, City Bus Station]
  long_name: To City Bus Station
  between_stops:
  Kings Ave / National Circuit-Russell Offices: [Wjz4Quk, Wjz4RwH, Wjz4RFJ]
  Russell Offices-City Bus Station: [Wjz4-WZ, Wjz4-WL, Wjz4-Rc, Wjz4-KO, Wjz4_wS, Wjz4_jm, Wjz4T-X, Wjz5MEL, Wjz5MsT, Wjz5MsD, Wjz5Nht]
  short_name: "4"
  stop_times: [[712a, "-", 715a, 722a, 725a, 729a, 734a, 743a], [744a, "-", 747a, 756a, 800a, 805a, 810a, 819a], [817a, "-", 820a, 829a, 833a, 838a, 843a, 852a], [847a, "-", 850a, 859a, 903a, 908a, 913a, 922a], [917a, "-", 920a, 929a, 932a, 936a, 940a, 948a], [948a, "-", 951a, 958a, 1001a, 1005a, 1009a, 1017a], [1018a, "-", 1021a, 1028a, 1031a, 1035a, 1039a, 1047a], [1048a, "-", 1051a, 1058a, 1101a, 1105a, 1109a, 1117a], [1118a, "-", 1121a, 1128a, 1131a, 1135a, 1139a, 1147a], [1148a, "-", 1151a, 1158a, 1201p, 1205p, 1209p, 1217p], [1218p, "-", 1221p, 1228p, 1231p, 1235p, 1239p, 1247p], [1248p, "-", 1251p, 1258p, 101p, 105p, 109p, 117p], [118p, "-", 121p, 128p, 131p, 135p, 139p, 147p], [148p, "-", 151p, 158p, 201p, 205p, 209p, 217p], [218p, "-", 221p, 228p, 231p, 235p, 239p, 247p], [246p, "-", 249p, 256p, 259p, 304p, 309p, 318p], [314p, "-", 317p, 326p, 330p, 335p, 340p, 349p], [346p, "-", 349p, 358p, 402p, 407p, 412p, 421p], [417p, "-", 420p, 429p, 433p, 438p, 443p, 452p], [448p, "-", 451p, 500p, 504p, 509p, 514p, 523p], [518p, "-", 521p, 530p, 534p, 539p, 544p, 553p], [548p, "-", 551p, 600p, 604p, 609p, 614p, 623p], ["-", 617p, 620p, 629p, 632p, 636p, 640p, 648p], ["-", 650p, 653p, 658p, 701p, 705p, 709p, 717p], ["-", 743p, 746p, 751p, 754p, 758p, 802p, 810p], ["-", 843p, 846p, 851p, 854p, 858p, 902p, 910p], ["-", 943p, 946p, 951p, 954p, 958p, 1002p, 1010p], ["-", 1043p, 1046p, 1051p, 1054p, 1058p, 1102p, 1110p]]
  -
  time_points: [Bimberi Centre, Northbourne Avenue / Antill St, Macarthur / Northbourne Ave, City Bus Station]
  long_name: To City Bus Station
  between_stops:
  Macarthur / Northbourne Ave-City Bus Station: [Wjz5QmR, Wjz5Qmu, Wjz5Qgn, Wjz5Qi2, Wjz5Pl0, Wjz5PdJ, Wjz5P8K, Wjz5P8n, Wjz5Oci, Wjz5O3Q]
  short_name: "982"
  stop_times_sunday: [[715p, 724p, 726p, 733p]]
  -
  time_points: [Woden Bus Station (Platform 11), Athllon / Sulwood Kambah, Erindale Centre, Tuggeranong Bus Station]
  long_name: To Tuggeranong Bus Station
  between_stops:
  Woden Bus Station (Platform 11)-Athllon / Sulwood Kambah: [Wjz3kAx, Wjz3kyX, Wjz3kwU, Wjz3iFK, Wjz3hL_, Wjz3gK-, Wjz3gQn, Wjz3gMq, Wjz2mTK]
  short_name: "961"
  stop_times_sunday: [[931a, 940a, 950a, 1003a], [1031a, 1040a, 1050a, 1103a], [1131a, 1140a, 1150a, 1203p], [1231p, 1240p, 1250p, 103p], [131p, 140p, 150p, 203p], [231p, 240p, 250p, 303p], [331p, 340p, 350p, 403p], [431p, 440p, 450p, 503p], [531p, 540p, 550p, 603p], [628p, 637p, 647p, 700p]]
  -
  time_points: [Cooleman Court, Rivett, Chapman, Fisher, Waramanga, Woden Bus Station]
  long_name: To Woden Bus Station
  between_stops: {}
   
  short_name: "927"
  stop_times_sunday: [[855a, 903a, 906a, 916a, 919a, 926a], [955a, 1003a, 1006a, 1016a, 1019a, 1026a], [1055a, 1103a, 1106a, 1116a, 1119a, 1126a], [1155a, 1203p, 1206p, 1216p, 1219p, 1226p], [1255p, 103p, 106p, 116p, 119p, 126p], [155p, 203p, 206p, 216p, 219p, 226p], [255p, 303p, 306p, 316p, 319p, 326p], [355p, 403p, 406p, 416p, 419p, 426p], [455p, 503p, 506p, 516p, 519p, 526p], [555p, 603p, 606p, 616p, 619p, 626p], [655p, 703p, 706p, 716p, 719p, 726p]]
  -
  time_points: [Tuggeranong Bus Station (Platform 7), Erindale Centre, Gowrie, Chisholm, Gowrie, Erindale Centre, Tuggeranong Bus Station]
  long_name: To Tuggeranong Bus Station
  between_stops: {}
   
  short_name: "966"
  stop_times_sunday: [[908a, 920a, 927a, 936a, 948a, 957a, 1010a], [1008a, 1020a, 1027a, 1036a, 1048a, 1057a, 1110a], [1108a, 1120a, 1127a, 1136a, 1148a, 1157a, 1210p], [1208p, 1220p, 1227p, 1236p, 1248p, 1257p, 110p], [108p, 120p, 127p, 136p, 148p, 157p, 210p], [208p, 220p, 227p, 236p, 248p, 257p, 310p], [308p, 320p, 327p, 336p, 348p, 357p, 410p], [408p, 420p, 427p, 436p, 448p, 457p, 510p], [508p, 520p, 527p, 536p, 548p, 557p, 610p], [608p, 620p, 627p, 636p, 648p, 657p, 710p], [705p, 717p, 724p, 733p, 745p, 754p, 807p]]
  -
  time_points: [Sydney Ave, Russell Offices, City Bus Station (Platform 11), Belconnen Way, Macgregor, Dunlop, Fraser West Terminus]
  long_name: To Fraser West Terminus
  between_stops:
  Russell Offices-City Bus Station (Platform 11): [Wjz4-WZ, Wjz4-WL, Wjz4-Rc, Wjz4-KO, Wjz4_wS, Wjz4_jm, Wjz4T-X, Wjz5MEL, Wjz5MsT, Wjz5MsD, Wjz5Nht]
  short_name: "703"
  stop_times: [[440p, 448p, 458p, 516p, 527p, 534p, 541p], ["-", "-", 515p, 533p, 544p, 551p, 558p], ["-", "-", 526p, 544p, 555p, 602p, 609p], [520p, 528p, 538p, 556p, 607p, 614p, 621p], [545p, 553p, 603p, 621p, 632p, 639p, 646p]]
  -
  time_points: [Cooleman Court, Duffy Primary, CIT Weston, Lyons, Woden Bus Station (Platform 10), Kings Ave / National Circuit, Russell Offices, Brindabella Business Park, Fairbairn Park]
  long_name: To Fairbairn Park
  between_stops:
  Brindabella Business Park-Fairbairn Park: [WjzcrK3, WjzcrrQ, WjzcrEu, WjzcJ0K, WjzcBHZ, WjzcJ38]
  Kings Ave / National Circuit-Russell Offices: [Wjz4Quk, Wjz4RwH, Wjz4RFJ]
  short_name: "28"
  stop_times: [[615a, 624a, 630a, 634a, 638a, 652a, 655a, 709a, 719a], [637a, 646a, 652a, 656a, 700a, 714a, 717a, 731a, 741a], [705a, 714a, 720a, 724a, 728a, 742a, 746a, 800a, 810a], [745a, 757a, 805a, 810a, 815a, 829a, 833a, 847a, 857a], [815a, 827a, 835a, 840a, 844a, "-", "-", "-", "-"], [844a, 856a, 904a, 909a, 913a, "-", "-", "-", "-"], [926a, 938a, 945a, 949a, 953a, "-", "-", "-", "-"], [1026a, 1038a, 1045a, 1049a, 1053a, "-", "-", "-", "-"], [1126a, 1138a, 1145a, 1149a, 1153a, "-", "-", "-", "-"], [1226p, 1238p, 1245p, 1249p, 1253p, "-", "-", "-", "-"], [126p, 138p, 145p, 149p, 153p, "-", "-", "-", "-"], [226p, 238p, 245p, 249p, 253p, "-", "-", "-", "-"], [326p, 338p, 346p, 351p, 354p, "-", "-", "-", "-"], [356p, 408p, 416p, 421p, 425p, "-", "-", "-", "-"], [415p, 427p, 435p, 440p, 444p, "-", "-", "-", "-"], [515p, 527p, 535p, 540p, 544p, "-", "-", "-", "-"], [615p, 627p, 634p, 638p, 641p, "-", "-", "-", "-"], [700p, 709p, 715p, 719p, 722p, "-", "-", "-", "-"], [800p, 809p, 815p, 819p, 822p, "-", "-", "-", "-"], [900p, 909p, 915p, 919p, 922p, "-", "-", "-", "-"], [1000p, 1009p, 1015p, 1019p, 1022p, "-", "-", "-", "-"]]
  -
  time_points: [Cohen Street Bus Station (Platform 1), Westfield Bus Station (Platform 1), Belconnen Community Bus Station (Platform 2), Chuculba / William Slim Dr, Ngunnawal Primary, Shoalhaven / Katherine Ave, Gungahlin Marketplace, Anthony Rolfe Av / Moonlight Av, Flemington Rd / Nullabor Ave, Flemington Rd / Sandford St, Macarthur / Northbourne Ave, City Bus Station]
  long_name: To City Bus Station
  between_stops:
  Belconnen Community Bus Station (Platform 2)-Chuculba / William Slim Dr: [Wjz69gA, Wjz69ht, Wjz69uI, Wjz69vO, Wjz6mip]
  Macarthur / Northbourne Ave-City Bus Station: [Wjz5QmR, Wjz5Qmu, Wjz5Qgn, Wjz5Qi2, Wjz5Pl0, Wjz5PdJ, Wjz5P8K, Wjz5P8n, Wjz5Oci, Wjz5O3Q]
  Cohen Street Bus Station (Platform 1)-Westfield Bus Station (Platform 1): []
  Westfield Bus Station (Platform 1)-Belconnen Community Bus Station (Platform 2): []
  stop_times_saturday: [["-", "-", "-", 708a, 719a, 727a, 735a, 744a, 751a, 758a, 806a, 813a], [752a, 754a, 758a, 808a, 819a, 827a, 835a, 844a, 851a, 858a, 906a, 913a], [852a, 854a, 858a, 908a, 919a, 927a, 935a, 944a, 951a, 958a, 1006a, 1013a], [952a, 954a, 958a, 1008a, 1019a, 1027a, 1035a, 1044a, 1051a, 1058a, 1106a, 1113a], [1052a, 1054a, 1058a, 1108a, 1119a, 1127a, 1135a, 1144a, 1151a, 1158a, 1206p, 1213p], [1152a, 1154a, 1158a, 1208p, 1219p, 1227p, 1235p, 1244p, 1251p, 1258p, 106p, 113p], [1252p, 1254p, 1258p, 108p, 119p, 127p, 135p, 144p, 151p, 158p, 206p, 213p], [152p, 154p, 158p, 208p, 219p, 227p, 235p, 244p, 251p, 258p, 306p, 313p], [252p, 254p, 258p, 308p, 319p, 327p, 335p, 344p, 351p, 358p, 406p, 413p], [352p, 354p, 358p, 408p, 419p, 427p, 435p, 444p, 451p, 458p, 506p, 513p], [452p, 454p, 458p, 508p, 519p, 527p, 535p, 544p, 551p, 558p, 606p, 613p], [552p, 554p, 558p, 608p, 619p, 627p, 635p, 644p, 651p, 658p, 706p, 713p], [652p, 654p, 658p, 708p, 719p, 727p, 735p, 744p, 751p, 758p, 806p, 813p], [752p, 754p, 758p, 808p, 819p, 827p, 835p, 844p, 851p, 858p, 906p, 913p], [852p, 854p, 858p, 908p, 919p, 927p, 935p, 944p, 951p, 958p, 1006p, 1013p], [952p, 954p, 958p, 1008p, 1019p, 1027p, 1035p, 1044p, 1051p, 1058p, 1106p, 1113p], [1052p, 1054p, 1058p, 1108p, 1119p, 1127p, 1135p, 1144p, 1151p, "-", "-", "-"]]
  short_name: "958"
  -
  time_points: [City Bus Station (Platform 8), Ainslie, Hackett, Dickson / Antill St, North Lyneham, Lyneham, Macarthur / Miller O'Connor, City Bus Station]
  long_name: To City Bus Station
  between_stops:
  Ainslie-Hackett: [Wjz5YKO, Wjz5ZO1, Wjz5ZZQ, Wjzd68O, Wjzd6iW, Wjzd6lW, Wjzd6Cq, Wjzd6Pn, Wjzd6XP, WjzdeeQ]
  short_name: "937"
  stop_times_sunday: [[859a, 911a, 919a, 925a, 934a, 939a, 942a, 951a], [959a, 1011a, 1019a, 1025a, 1034a, 1039a, 1042a, 1051a], [1059a, 1111a, 1119a, 1125a, 1134a, 1139a, 1142a, 1151a], [1159a, 1211p, 1219p, 1225p, 1234p, 1239p, 1242p, 1251p], [1259p, 111p, 119p, 125p, 134p, 139p, 142p, 151p], [159p, 211p, 219p, 225p, 234p, 239p, 242p, 251p], [259p, 311p, 319p, 325p, 334p, 339p, 342p, 351p], [359p, 411p, 419p, 425p, 434p, 439p, 442p, 451p], [459p, 511p, 519p, 525p, 534p, 539p, 542p, 551p], [559p, 611p, 619p, 625p, 634p, 639p, 642p, 651p], [659p, 711p, 719p, 725p, 734p, 739p, 742p, 751p]]
  -
  time_points: [Cohen Street Bus Station (Platform 1), Westfield Bus Station (Platform 1), Belconnen Community Bus Station (Platform 2), Chuculba / William Slim Dr, Gungahlin Marketplace, Anthony Rolfe Av / Moonlight Av, Flemington Rd / Nullabor Ave, Flemington Rd / Sandford St, Northbourne Avenue / Antill St, Macarthur / Northbourne Ave]
  long_name: To Macarthur / Northbourne Ave
  between_stops:
  Northbourne Avenue / Antill St-Macarthur / Northbourne Ave: [Wjz5SDc, Wjz5Sux, Wjz5SrO, Wjz5Sqk, Wjz5RvC, Wjz5Rsi, Wjz5RkN]
  Belconnen Community Bus Station (Platform 2)-Chuculba / William Slim Dr: [Wjz69gA, Wjz69ht, Wjz69uI, Wjz69vO, Wjz6mip]
  Cohen Street Bus Station (Platform 1)-Westfield Bus Station (Platform 1): []
  Westfield Bus Station (Platform 1)-Belconnen Community Bus Station (Platform 2): []
  short_name: "58"
  stop_times: [["-", "-", "-", 543a, 554a, 602a, 609a, 615a, 621a, 623a], ["-", "-", "-", 623a, 634a, 642a, 649a, 655a, 701a, 703a], ["-", "-", "-", "-", 654a, 702a, 709a, 715a, 721a, 723a], ["-", "-", "-", "-", 713a, 721a, 728a, 734a, 740a, 742a], ["-", "-", "-", "-", 723a, 731a, 738a, 744a, 754a, 759a], ["-", "-", "-", "-", 740a, 748a, 755a, 803a, 814a, 819a], [723a, 725a, 729a, 743a, 754a, 803a, 810a, 818a, 829a, 834a], [744a, 746a, 750a, 805a, 816a, 825a, 832a, 840a, 851a, 856a], [825a, 827a, 831a, 846a, 857a, 905a, 912a, 919a, 925a, 927a], [905a, 907a, 911a, 925a, 935a, 943a, 950a, 957a, 1003a, 1005a], [1005a, 1007a, 1011a, 1025a, 1035a, 1043a, 1050a, 1057a, 1103a, 1105a], [1105a, 1107a, 1111a, 1125a, 1135a, 1143a, 1150a, 1157a, 1203p, 1205p], [1205p, 1207p, 1211p, 1225p, 1235p, 1243p, 1250p, 1257p, 103p, 105p], [105p, 107p, 111p, 125p, 135p, 143p, 150p, 157p, 203p, 205p], [205p, 207p, 211p, 225p, 235p, 243p, 250p, 257p, 303p, 305p], [307p, 309p, 313p, 327p, 337p, 345p, 352p, 359p, 406p, 408p], [405p, 407p, 411p, 426p, 437p, 446p, 453p, 501p, 508p, 510p], [425p, 427p, 431p, 446p, 457p, 506p, 513p, 521p, 528p, 530p], [445p, 447p, 451p, 506p, 517p, 526p, 533p, 541p, 548p, 550p], [505p, 507p, 511p, 526p, 537p, 546p, 553p, 601p, 607p, 609p], [525p, 527p, 531p, 546p, 557p, 605p, 612p, 618p, 624p, 626p], [545p, 547p, 551p, 606p, 616p, 624p, 631p, 637p, 643p, 645p], [604p, 606p, 610p, 624p, 634p, 642p, 649p, 655p, 701p, 703p], [704p, 706p, 710p, 724p, 734p, 742p, 749p, 755p, 801p, 803p], [804p, 806p, 810p, 824p, 834p, 842p, 849p, 855p, 901p, 903p], [904p, 906p, 910p, 924p, 934p, 942p, 949p, 955p, 1001p, 1003p], [1004p, 1006p, 1010p, 1024p, 1034p, 1042p, 1049p, 1055p, 1101p, 1103p], []]
  -
  time_points: [Cohen Street Bus Station (Platform 2), Westfield Bus Station (Platform 1), Belconnen Community Bus Station (Platform 1), City Bus Station (Platform 1), Woden Bus Station (Platform 6), Erindale Centre, Tuggeranong Bus Station]
  long_name: To Tuggeranong Bus Station
  between_stops:
  Cohen Street Bus Station (Platform 2)-Westfield Bus Station (Platform 1): []
  Westfield Bus Station (Platform 1)-Belconnen Community Bus Station (Platform 1): []
  stop_times_saturday: [[631a, 633a, 637a, 657a, 714a, 729a, 735a], [646a, 648a, 652a, 712a, 729a, 744a, 750a], [701a, 703a, 707a, 727a, 744a, 759a, 805a], [716a, 718a, 722a, 742a, 759a, 814a, 820a], [731a, 733a, 737a, 757a, 814a, 829a, 835a], [746a, 748a, 752a, 812a, 829a, 844a, 850a], [801a, 803a, 807a, 827a, 844a, 859a, 905a], [816a, 818a, 822a, 842a, 859a, 914a, 920a], [831a, 833a, 837a, 857a, 914a, 929a, 935a], [846a, 848a, 852a, 912a, 929a, 944a, 950a], [901a, 903a, 907a, 927a, 944a, 959a, 1005a], [916a, 918a, 922a, 942a, 959a, 1014a, 1020a], [931a, 933a, 937a, 957a, 1014a, 1029a, 1035a], [946a, 948a, 952a, 1012a, 1029a, 1044a, 1050a], [1001a, 1003a, 1007a, 1027a, 1044a, 1059a, 1105a], [1016a, 1018a, 1022a, 1042a, 1059a, 1114a, 1120a], [1031a, 1033a, 1037a, 1057a, 1114a, 1129a, 1135a], [1046a, 1048a, 1052a, 1112a, 1129a, 1144a, 1150a], [1053a, 1055a, 1059a, 1119a, 1134a, "-", "-"], [1101a, 1103a, 1107a, 1127a, 1144a, 1159a, 1205p], [1116a, 1118a, 1122a, 1142a, 1159a, 1214p, 1220p], [1123a, 1125a, 1129a, 1149a, 1204p, "-", "-"], [1131a, 1133a, 1137a, 1157a, 1214p, 1229p, 1235p], [1146a, 1148a, 1152a, 1212p, 1229p, 1244p, 1250p], [1153a, 1155a, 1159a, 1219p, 1234p, "-", "-"], [1201p, 1203p, 1207p, 1227p, 1244p, 1259p, 105p], [1216p, 1218p, 1222p, 1242p, 1259p, 114p, 120p], [1223p, 1225p, 1229p, 1249p, 104p, "-", "-"], [1231p, 1233p, 1237p, 1257p, 114p, 129p, 135p], [1246p, 1248p, 1252p, 112p, 129p, 144p, 150p], [1253p, 1255p, 1259p, 119p, 134p, "-", "-"], [101p, 103p, 107p, 127p, 144p, 159p, 205p], [116p, 118p, 122p, 142p, 159p, 214p, 220p], [123p, 125p, 129p, 149p, 204p, "-", "-"], [131p, 133p, 137p, 157p, 214p, 229p, 235p], [146p, 148p, 152p, 212p, 229p, 244p, 250p], [153p, 155p, 159p, 219p, 234p, "-", "-"], [201p, 203p, 207p, 227p, 244p, 259p, 305p], [216p, 218p, 222p, 242p, 259p, 314p, 320p], [223p, 225p, 229p, 249p, 304p, "-", "-"], [231p, 233p, 237p, 257p, 314p, 329p, 335p], [246p, 248p, 252p, 312p, 329p, 344p, 350p], [253p, 255p, 259p, 319p, 334p, "-", "-"], [301p, 303p, 307p, 327p, 344p, 359p, 405p], [316p, 318p, 322p, 342p, 359p, 414p, 420p], [323p, 325p, 329p, 349p, 404p, "-", "-"], [331p, 333p, 337p, 357p, 414p, 429p, 435p], [346p, 348p, 352p, 412p, 429p, 444p, 450p], [353p, 355p, 359p, 419p, 434p, "-", "-"], [401p, 403p, 407p, 427p, 444p, 459p, 505p], [416p, 418p, 422p, 442p, 459p, 514p, 520p], [431p, 433p, 437p, 457p, 514p, 529p, 535p], [446p, 448p, 452p, 512p, 529p, 544p, 550p], [501p, 503p, 507p, 527p, 544p, 559p, 605p], [516p, 518p, 522p, 542p, 559p, 614p, 620p], [531p, 533p, 537p, 557p, 614p, 629p, 635p], [546p, 548p, 552p, 612p, 629p, 643p, 649p], [601p, 603p, 607p, 627p, 642p, 656p, 702p], [616p, 618p, 622p, 641p, 655p, 709p, 715p], [631p, 633p, 637p, 656p, 710p, 724p, 730p], [646p, 648p, 652p, 711p, 725p, 739p, 745p], [701p, 703p, 707p, 726p, 740p, 754p, 800p], [716p, 718p, 722p, 741p, 755p, 809p, 815p], [731p, 733p, 737p, 756p, 810p, 824p, 830p], [746p, 748p, 752p, 811p, 825p, 839p, 845p], [801p, 803p, 807p, 826p, 840p, 854p, 900p], [816p, 818p, 822p, 841p, 855p, 909p, 915p], [831p, 833p, 837p, 856p, 910p, 924p, 930p], [846p, 848p, 852p, 911p, 925p, 939p, 945p], [901p, 903p, 907p, 926p, 940p, 954p, 1000p], [916p, 918p, 922p, 941p, 955p, 1009p, 1015p], [931p, 933p, 937p, 956p, 1010p, 1024p, 1030p], [946p, 948p, 952p, 1011p, 1025p, 1039p, 1045p], [1001p, 1003p, 1007p, 1026p, 1040p, 1054p, 1100p], [1016p, 1018p, 1022p, 1041p, 1055p, 1109p, 1115p], [1031p, 1033p, 1037p, 1056p, 1110p, 1124p, 1130p], [1046p, 1048p, 1052p, 1111p, 1125p, 1139p, 1145p], [1101p, 1103p, 1107p, 1126p, 1140p, 1154p, 1200a]]
  short_name: "900"
  -
  time_points: [City Bus Station (Platform 9), National Zoo and Aquarium, Black Mountain Telstra Tower, Botanic Gardens, City Bus Station]
  long_name: To City Bus Station
  between_stops: {}
   
  stop_times_saturday: [[1020a, 1034a, 1042a, 1048a, 1055a], [1150a, 1204p, 1212p, 1218p, 1225p], [120p, 134p, 142p, 148p, 155p], [250p, 304p, 312p, 318p, 325p], [420p, 434p, 442p, 448p, 455p]]
  short_name: "981"
  -
  time_points: [Tuggeranong Bus Station (Platform 5), MacKillop College Isabella Campus, Gowrie, Erindale Centre, Woden Bus Station (Platform 10), Kings Ave / National Circuit, Russell Offices, City Bus Station, City West]
  long_name: To City West
  between_stops:
  City Bus Station-City West: []
  Kings Ave / National Circuit-Russell Offices: [Wjz4Quk, Wjz4RwH, Wjz4RFJ]
  Russell Offices-City Bus Station: [Wjz4-WZ, Wjz4-WL, Wjz4-Rc, Wjz4-KO, Wjz4_wS, Wjz4_jm, Wjz4T-X, Wjz5MEL, Wjz5MsT, Wjz5MsD, Wjz5Nht]
  short_name: 65 265
  stop_times: [[535a, 541a, 552a, 557a, 611a, "-", "-", "-", "-"], [635a, 641a, 652a, 657a, 711a, "-", "-", "-", "-"], [653a, 700a, 712a, 721a, 737a, 752a, 756a, 805a, 808a], [720a, 726a, 734a, 743a, 801a, 815a, 819a, 829a, 832a], [730a, 739a, 756a, 805a, 822a, "-", "-", "-", "-"], [745a, 754a, 811a, 820a, 842a, "-", "-", "-", "-"], [815a, 824a, 841a, 850a, 907a, "-", "-", "-", "-"], [845a, 854a, 911a, 920a, 936a, "-", "-", "-", "-"], [945a, 952a, 1005a, 1012a, 1027a, "-", "-", "-", "-"], [1045a, 1052a, 1105a, 1112a, 1127a, "-", "-", "-", "-"], [1145a, 1152a, 1205p, 1212p, 1227p, "-", "-", "-", "-"], [1245p, 1252p, 105p, 112p, 127p, "-", "-", "-", "-"], [145p, 152p, 205p, 212p, 227p, "-", "-", "-", "-"], [245p, 252p, 305p, 312p, 331p, "-", "-", "-", "-"], [315p, 324p, 337p, 344p, 403p, "-", "-", "-", "-"], [345p, 354p, 407p, 414p, 433p, "-", "-", "-", "-"], [420p, 429p, 442p, 449p, 508p, "-", "-", "-", "-"], [445p, 454p, 507p, 514p, 533p, "-", "-", "-", "-"], [515p, 524p, 537p, 544p, 603p, "-", "-", "-", "-"], [545p, 554p, 607p, 614p, 633p, "-", "-", "-", "-"], [615p, 624p, 636p, 641p, 657p, "-", "-", "-", "-"], [641p, 647p, 659p, 704p, 720p, "-", "-", "-", "-"], [741p, 747p, 759p, 804p, 820p, "-", "-", "-", "-"], [841p, 847p, 859p, 904p, 920p, "-", "-", "-", "-"], [941p, 947p, 959p, 1004p, 1020p, "-", "-", "-", "-"], [1041p, 1047p, 1059p, 1104p, 1120p, "-", "-", "-", "-"]]
  -
  time_points: [Belconnen Community Bus Station (Platform 4), Westfield Bus Station (Platform 2), Cohen Street Bus Station (Platform 6), McKellar, Evatt, Spence Terminus, Evatt, McKellar, Cohen Street Bus Station, Westfield Bus Station, Belconnen Community Bus Station]
  long_name: To Belconnen Community Bus Station
  between_stops:
  Belconnen Community Bus Station (Platform 4)-Westfield Bus Station (Platform 2): []
  Westfield Bus Station-Belconnen Community Bus Station: []
  Cohen Street Bus Station-Westfield Bus Station: []
  Westfield Bus Station (Platform 2)-Cohen Street Bus Station (Platform 6): []
  short_name: "902"
  stop_times_sunday: [[851a, 853a, 857a, 904a, 912a, 918a, 923a, 931a, 939a, 941a, 945a], [951a, 953a, 957a, 1004a, 1012a, 1018a, 1023a, 1031a, 1039a, 1041a, 1045a], [1051a, 1053a, 1057a, 1104a, 1112a, 1118a, 1123a, 1131a, 1139a, 1141a, 1145a], [1151a, 1153a, 1157a, 1204p, 1212p, 1218p, 1223p, 1231p, 1239p, 1241p, 1245p], [1251p, 1253p, 1257p, 104p, 112p, 118p, 123p, 131p, 139p, 141p, 145p], [151p, 153p, 157p, 204p, 212p, 218p, 223p, 231p, 239p, 241p, 245p], [251p, 253p, 257p, 304p, 312p, 318p, 323p, 331p, 339p, 341p, 345p], [351p, 353p, 357p, 404p, 412p, 418p, 423p, 431p, 439p, 441p, 445p], [451p, 453p, 457p, 504p, 512p, 518p, 523p, 531p, 539p, 541p, 545p], [551p, 553p, 557p, 604p, 612p, 618p, 623p, 631p, 638p, 640p, 644p], [651p, 653p, 657p, 703p, 710p, 716p, 721p, 729p, 736p, 738p, 742p]]
  -
  time_points: [Cohen Street Bus Station (Platform 1), Westfield Bus Station (Platform 1), Belconnen Community Bus Station (Platform 2), Chuculba / William Slim Dr, Federation Square, Nicholls Primary, Ngunnawal Primary, Gungahlin Marketplace]
  long_name: To Gungahlin Market Place
  between_stops:
  Belconnen Community Bus Station (Platform 2)-Chuculba / William Slim Dr: [Wjz69gA, Wjz69ht, Wjz69uI, Wjz69vO, Wjz6mip]
  Cohen Street Bus Station (Platform 1)-Westfield Bus Station (Platform 1): []
  Westfield Bus Station (Platform 1)-Belconnen Community Bus Station (Platform 2): []
  stop_times_saturday: [[822a, 824a, 828a, 836a, 841a, 846a, 856a, 906a], [920a, 922a, 926a, 934a, 939a, 944a, 954a, 1004a], [1020a, 1022a, 1026a, 1034a, 1039a, 1044a, 1054a, 1104a], [1120a, 1122a, 1126a, 1134a, 1139a, 1144a, 1154a, 1204p], [1220p, 1222p, 1226p, 1234p, 1239p, 1244p, 1254p, 104p], [120p, 122p, 126p, 134p, 139p, 144p, 154p, 204p], [220p, 222p, 226p, 234p, 239p, 244p, 254p, 304p], [320p, 322p, 326p, 334p, 339p, 344p, 354p, 404p], [420p, 422p, 426p, 434p, 439p, 444p, 454p, 504p], [520p, 522p, 526p, 534p, 539p, 544p, 554p, 604p], [620p, 622p, 626p, 634p, 639p, 644p, 654p, 704p], [720p, 722p, 726p, 734p, 739p, 744p, 754p, 804p], [820p, 822p, 826p, 834p, 839p, 844p, 854p, 904p], [920p, 922p, 926p, 934p, 939p, 944p, 954p, 1004p], [1020p, 1022p, 1026p, 1034p, 1039p, 1044p, 1054p, 1104p]]
  short_name: "951"
  -
  time_points: [Woden Bus Station, Geoscience Australia, Eye Hospital, Fyshwick Direct Factory Outlet, Canberra Times, Railway Station Kingston, Causeway, Kings Ave / National Circuit, Russell Offices, City Bus Station]
  long_name: To City Bus Station
  between_stops:
  Kings Ave / National Circuit-Russell Offices: [Wjz4Quk, Wjz4RwH, Wjz4RFJ]
  Russell Offices-City Bus Station: [Wjz4-WZ, Wjz4-WL, Wjz4-Rc, Wjz4-KO, Wjz4_wS, Wjz4_jm, Wjz4T-X, Wjz5MEL, Wjz5MsT, Wjz5MsD, Wjz5Nht]
  short_name: "80"
  stop_times: [[547a, 602a, 611a, 616a, 625a, 632a, 634a, 638a, 642a, 650a], [606a, 621a, 630a, 635a, 644a, 651a, 653a, 657a, 701a, 709a], [633a, 648a, 657a, 702a, 711a, 718a, 720a, 724a, 728a, 738a], [701a, 716a, 725a, 730a, 741a, 749a, 753a, 800a, 804a, 815a], [731a, 747a, 756a, 803a, 814a, 822a, 826a, 833a, 837a, 848a], [801a, 817a, 826a, 833a, 844a, 852a, 856a, 903a, 907a, 918a], [834a, 850a, 859a, 906a, 917a, 925a, 929a, 933a, 937a, 945a], [909a, 924a, 934a, 939a, 948a, 955a, 957a, 1001a, 1005a, 1013a], [940a, 955a, 1004a, 1009a, 1018a, 1025a, 1027a, 1031a, 1035a, 1043a], [1040a, 1055a, 1104a, 1109a, 1118a, 1125a, 1127a, 1131a, 1135a, 1143a], ["-", "-", "-", "-", "-", 1131a, 1133a, 1137a, 1141a, 1149a], [1140a, 1155a, 1204p, 1209p, 1218p, 1225p, 1227p, 1231p, 1235p, 1243p], [1240p, 1255p, 104p, 109p, 118p, 125p, 127p, 131p, 135p, 143p], [140p, 155p, 204p, 209p, 218p, 225p, 227p, 231p, 235p, 243p], [240p, 255p, 304p, 309p, 318p, 325p, 327p, 331p, 335p, 343p], [340p, 356p, 406p, 412p, 422p, 429p, 431p, 436p, 441p, 450p], [408p, 424p, 434p, 440p, 450p, 457p, 459p, 504p, 509p, 518p], [438p, 454p, 504p, 510p, 520p, 527p, 529p, 534p, 539p, 548p], [508p, 524p, 534p, 540p, 550p, 557p, 559p, 604p, 609p, 618p], [538p, 554p, 604p, 610p, 620p, 627p, 629p, 633p, 637p, 645p], [557p, 613p, 623p, 629p, 637p, 643p, 645p, 649p, 653p, 701p], ["-", "-", "-", "-", "-", 727p, 729p, 733p, 737p, 745p], ["-", "-", "-", "-", "-", 827p, 829p, 833p, 837p, 845p], ["-", "-", "-", "-", "-", 927p, 929p, 933p, 937p, 945p], ["-", "-", "-", "-", "-", 1027p, 1029p, 1033p, 1037p, 1045p]]
  -
  time_points: [Gungahlin Marketplace, Hibberson / Kate Crace, Flemington Rd / Sandford St, Northbourne Avenue / Antill St, Macarthur / Northbourne Ave, City Bus Station]
  long_name: To City Bus Station
  between_stops:
  Northbourne Avenue / Antill St-Macarthur / Northbourne Ave: [Wjz5SDc, Wjz5Sux, Wjz5SrO, Wjz5Sqk, Wjz5RvC, Wjz5Rsi, Wjz5RkN]
  Macarthur / Northbourne Ave-City Bus Station: [Wjz5QmR, Wjz5Qmu, Wjz5Qgn, Wjz5Qi2, Wjz5Pl0, Wjz5PdJ, Wjz5P8K, Wjz5P8n, Wjz5Oci, Wjz5O3Q]
  short_name: "50"
  stop_times: [[700p, 703p, 706p, 713p, 715p, 722p], [730p, 733p, 736p, 743p, 745p, 752p], [800p, 803p, 806p, 813p, 815p, 822p], [830p, 833p, 836p, 843p, 845p, 852p], [900p, 903p, 906p, 913p, 915p, 922p], [930p, 933p, 936p, 943p, 945p, 952p], [1000p, 1003p, 1006p, 1013p, 1015p, 1022p], [1030p, 1033p, 1036p, 1043p, 1045p, 1052p], [1100p, 1103p, 1106p, 1113p, 1115p, 1122p]]
  -
  time_points: [Kippax, Higgins, Hawker College, Hawker, Weetangera, Cohen Street Bus Station, Westfield Bus Station, Belconnen Community Bus Station]
  long_name: To Belconnen Community Bus Station
  between_stops:
  Westfield Bus Station-Belconnen Community Bus Station: []
  Cohen Street Bus Station-Westfield Bus Station: []
  short_name: "17"
  stop_times: [[601a, 606a, 612a, 617a, 620a, 625a, 627a, 631a], [631a, 636a, 642a, 647a, 650a, 655a, 657a, 701a], [701a, 706a, 712a, 717a, 720a, 725a, 727a, 731a], [721a, 726a, 732a, 737a, 740a, 746a, 748a, 752a], [741a, 747a, 753a, 758a, 801a, 807a, 809a, 813a], [801a, 807a, 813a, 818a, 821a, 827a, 829a, 833a], [821a, 827a, 833a, 838a, 841a, 847a, 849a, 853a], [841a, 847a, 853a, 858a, 901a, 907a, 909a, 913a], [925a, 931a, 937a, 942a, 945a, 950a, 952a, 956a], [956a, 1001a, 1007a, 1012a, 1015a, 1020a, 1022a, 1026a], [1026a, 1031a, 1037a, 1042a, 1045a, 1050a, 1052a, 1056a], [1056a, 1101a, 1107a, 1112a, 1115a, 1120a, 1122a, 1126a], [1126a, 1131a, 1137a, 1142a, 1145a, 1150a, 1152a, 1156a], [1156a, 1201p, 1207p, 1212p, 1215p, 1220p, 1222p, 1226p], [1226p, 1231p, 1237p, 1242p, 1245p, 1250p, 1252p, 1256p], [1256p, 101p, 107p, 112p, 115p, 120p, 122p, 126p], ["-", "-", 122p, 127p, 130p, 135p, 137p, 141p], [126p, 131p, 137p, 142p, 145p, 150p, 152p, 156p], [156p, 201p, 207p, 212p, 215p, 220p, 222p, 226p], [226p, 231p, 237p, 242p, 245p, 250p, 252p, 256p], ["-", "-", 252p, 257p, 300p, 306p, 308p, 312p], [256p, 301p, 307p, 312p, 315p, 321p, 323p, 327p], ["-", "-", 325p, 330p, 333p, 339p, 341p, 345p], [326p, 332p, 338p, 343p, 346p, 352p, 354p, 358p], [347p, 353p, 359p, 404p, 407p, 413p, 415p, 419p], ["-", "-", 403p, 408p, 411p, 417p, 419p, 423p], [417p, 423p, 429p, 434p, 437p, 443p, 445p, 449p], [447p, 453p, 459p, 504p, 507p, 513p, 515p, 519p], [517p, 523p, 529p, 534p, 537p, 543p, 545p, 549p], [547p, 553p, 559p, 604p, 607p, 613p, 615p, 619p], [617p, 623p, 629p, 634p, 637p, 641p, 643p, 647p], [719p, 724p, 730p, 735p, 738p, 742p, 744p, 748p], [819p, 824p, 830p, 835p, 838p, 842p, 844p, 848p], [919p, 924p, 930p, 935p, 938p, 942p, 944p, 948p], [1019p, 1024p, 1030p, 1035p, 1038p, 1042p, 1044p, 1048p], [1119p, 1124p, 1130p, 1135p, 1138p, 1142p, 1144p, 1148p], []]
  -
  time_points: [Cohen Street Bus Station (Platform 1), Westfield Bus Station (Platform 1), Belconnen Community Bus Station (Platform 3), University of Canberra, Giralang, Kaleen Village / Marybrynong, North Lyneham, Northbourne Avenue / Antill St, Macarthur / Northbourne Ave, City Bus Station]
  long_name: To City Bus Station
  between_stops:
  Northbourne Avenue / Antill St-Macarthur / Northbourne Ave: [Wjz5SDc, Wjz5Sux, Wjz5SrO, Wjz5Sqk, Wjz5RvC, Wjz5Rsi, Wjz5RkN]
  Macarthur / Northbourne Ave-City Bus Station: [Wjz5QmR, Wjz5Qmu, Wjz5Qgn, Wjz5Qi2, Wjz5Pl0, Wjz5PdJ, Wjz5P8K, Wjz5P8n, Wjz5Oci, Wjz5O3Q]
  Westfield Bus Station (Platform 1)-Belconnen Community Bus Station (Platform 3): []
  Cohen Street Bus Station (Platform 1)-Westfield Bus Station (Platform 1): []
  Belconnen Community Bus Station (Platform 3)-University of Canberra: [Wjz689c, Wjz681S]
  short_name: "30"
  stop_times: [[546a, 548a, 552a, 557a, 605a, 612a, 618a, 621a, 623a, 630a], [615a, 617a, 621a, 626a, 634a, 641a, 647a, 650a, 652a, 659a], [631a, 633a, 637a, 642a, 650a, 657a, 703a, 706a, 708a, 715a], [656a, 658a, 702a, 707a, 715a, 722a, 728a, 731a, 736a, 752a], ["-", "-", "-", "-", 729a, 738a, 746a, 750a, 755a, 811a], [724a, 726a, 730a, 735a, 743a, 752a, 800a, 804a, 809a, 825a], ["-", "-", "-", "-", 803a, 812a, 824a, 828a, 833a, 848a], [756a, 758a, 802a, 807a, 815a, 824a, 834a, 838a, 843a, 858a], ["-", "-", "-", "-", 829a, 838a, 846a, 850a, 855a, 911a], [824a, 826a, 830a, 835a, 843a, 852a, 900a, 904a, 909a, 925a], [853a, 855a, 859a, 904a, 912a, 921a, 929a, 932a, 934a, 941a], [953a, 955a, 959a, 1004a, 1011a, 1019a, 1027a, 1030a, 1032a, 1039a], [1053a, 1055a, 1059a, 1104a, 1111a, 1119a, 1127a, 1130a, 1132a, 1139a], [1153a, 1155a, 1159a, 1204p, 1211p, 1219p, 1227p, 1230p, 1232p, 1239p], [1253p, 1255p, 1259p, 104p, 111p, 119p, 127p, 130p, 132p, 139p], [153p, 155p, 159p, 204p, 211p, 219p, 227p, 230p, 232p, 239p], [242p, 244p, 248p, 253p, 300p, 308p, 316p, 320p, 322p, 330p], [307p, 309p, 313p, 318p, 327p, 335p, 343p, 347p, 349p, 357p], [331p, 333p, 337p, 342p, 351p, 359p, 407p, 411p, 413p, 421p], [401p, 403p, 407p, 412p, 421p, 429p, 437p, 441p, 443p, 451p], [431p, 433p, 437p, 442p, 451p, 459p, 507p, 511p, 513p, 521p], [501p, 503p, 507p, 512p, 521p, 529p, 537p, 541p, 543p, 551p], [531p, 533p, 537p, 542p, 551p, 559p, 607p, 611p, 613p, 621p], [552p, 554p, 558p, 603p, 612p, 620p, 628p, 632p, 634p, 640p], [652p, 654p, 658p, 703p, 711p, 718p, 724p, 727p, 729p, 735p], [752p, 754p, 758p, 803p, 811p, 818p, 824p, 827p, 829p, 835p], [852p, 854p, 858p, 903p, 911p, 918p, 924p, 927p, 929p, 935p], [952p, 954p, 958p, 1003p, 1011p, 1018p, 1024p, 1027p, 1029p, 1035p], [1052p, 1054p, 1058p, 1103p, 1111p, 1118p, 1124p, 1127p, 1129p, 1135p]]
  -
  time_points: [Tuggeranong Bus Station (Platform 7), Bonython Primary School, Lanyon Market Place, Conder Primary, Tharwa Drive / Knoke Ave, Gordon Primary, Woodcock / Clare Dennis, Bonython Primary School, Tuggeranong Bus Station]
  long_name: To Tuggeranong Bus Station
  between_stops: {}
   
  short_name: "914"
  stop_times_sunday: [[1025a, 1034a, 1040a, 1047a, 1050a, 1054a, 1059a, 1102a, 1112a], [1225p, 1234p, 1240p, 1247p, 1250p, 1254p, 1259p, 102p, 112p], [225p, 234p, 240p, 247p, 250p, 254p, 259p, 302p, 312p], [425p, 434p, 440p, 447p, 450p, 454p, 459p, 502p, 512p], [625p, 634p, 640p, 647p, 650p, 654p, 659p, 702p, 712p]]
  -
  time_points: [City Bus Station (Platform 9), Russell Offices, Kings Ave / National Circuit, Causeway, Railway Station Kingston, Newcastle Street after Isa Street, Fyshwick Direct Factory Outlet, Eye Hospital, Geoscience Australia, Woden Bus Station]
  long_name: To Woden Bus Station
  between_stops:
  Russell Offices-Kings Ave / National Circuit: [Wjz4RFJ, Wjz4RwH, Wjz4Quk]
  City Bus Station (Platform 9)-Russell Offices: [Wjz5Nht, Wjz5MsD, Wjz5MsT, Wjz5MEL, Wjz4T-X, Wjz4_jm, Wjz4_wS, Wjz4-KO, Wjz4-Rc, Wjz4-WL, Wjz4-WZ]
  short_name: "80"
  stop_times: [[550a, 558a, 602a, 606a, 609a, 617a, 626a, 631a, 640a, 656a], [617a, 625a, 629a, 633a, 636a, 644a, 653a, 658a, 707a, 723a], [648a, 656a, 700a, 704a, 707a, 715a, 724a, 729a, 737a, 753a], [719a, 727a, 731a, 738a, 741a, 750a, 804a, 810a, 818a, 834a], [751a, 800a, 803a, 810a, 813a, 822a, 836a, 842a, 850a, 906a], [828a, 837a, 840a, 847a, 850a, 859a, 913a, 919a, 927a, 945a], [859a, 907a, 911a, 915a, 918a, 930a, 939a, 944a, 952a, 1010a], [928a, 936a, 940a, 944a, 947a, 955a, 1004a, 1009a, 1017a, 1035a], [1028a, 1036a, 1040a, 1044a, 1047a, 1055a, 1104a, 1109a, 1117a, 1135a], [1128a, 1136a, 1140a, 1144a, 1147a, 1155a, 1204p, 1209p, 1217p, 1235p], [1228p, 1236p, 1240p, 1244p, 1247p, 1255p, 104p, 109p, 117p, 135p], [128p, 136p, 140p, 144p, 147p, 155p, 204p, 209p, 217p, 235p], [228p, 236p, 240p, 244p, 247p, 255p, 304p, 309p, 318p, 334p], [330p, 339p, 344p, 349p, 352p, 400p, 410p, 416p, 426p, 444p], [400p, 409p, 414p, 419p, 422p, 430p, 440p, 446p, 456p, 514p], [434p, 443p, 448p, 453p, 456p, 504p, 514p, 520p, 530p, 548p], [504p, 513p, 518p, 523p, 526p, 534p, 544p, 550p, 600p, 618p], [534p, 543p, 548p, 553p, 556p, 604p, 614p, 620p, 630p, 645p], [604p, 613p, 618p, 623p, 626p, 633p, 641p, 646p, 654p, 709p], [702p, 710p, 714p, 718p, 720p, "-", "-", "-", "-", "-"], [800p, 808p, 812p, 816p, 818p, "-", "-", "-", "-", "-"], [900p, 908p, 912p, 916p, 918p, "-", "-", "-", "-", "-"], [1000p, 1008p, 1012p, 1016p, 1018p, "-", "-", "-", "-", "-"], [1100p, 1108p, 1112p, 1116p, 1118p, "-", "-", "-", "-", "-"]]
  -
  time_points: [City West, City Bus Station (Platform 10), ACTEW AGL House, Woodcock / Clare Dennis, Tharwa Drive / Knoke Ave, Lanyon Market Place]
  long_name: To Lanyon Market Place
  between_stops:
  City West-City Bus Station (Platform 10): []
  ACTEW AGL House-Woodcock / Clare Dennis: [Wjz34Gq, Wjz33LB, Wjz33KX, Wjz33GY, Wjz33EK, WjrXUAm, WjrXUsW, WjrXUoV, WjrW_uo, Wjz2a26]
  City Bus Station (Platform 10)-ACTEW AGL House: [Wjz5Nht]
  short_name: "787"
  stop_times: [[516p, 522p, 524p, 556p, 607p, 609p], [535p, 541p, 543p, 615p, 626p, 628p]]
  -
  time_points: [Fairbairn Park, Brindabella Business Park, Russell Offices, Dickson College, Gungahlin Marketplace]
  long_name: To Gungahlin Marketplace
  between_stops:
  Fairbairn Park-Brindabella Business Park: [WjzcJ38, WjzcBHZ, WjzcJ0K, WjzcrEu, WjzcrrQ, WjzcrK3]
  short_name: "757"
  stop_times: [[433p, 443p, 457p, 510p, 524p], [508p, 518p, 532p, 543p, 556p], [538p, 548p, 602p, 613p, 626p]]
  -
  time_points: [Woden Bus Station (Platform 14), Canberra Hospital, Garran, Hughes, Deakin, Kings Ave / National Circuit, City Bus Station (Platform 4), National Museum of Australia, Burton and Garran Hall Daley Road, O'Connor, Calvary Hospital, Belconnen Community Bus Station, Westfield Bus Station, Cohen Street Bus Station]
  long_name: To Cohen Street Bus Station
  between_stops:
  Westfield Bus Station-Cohen Street Bus Station: []
  Belconnen Community Bus Station-Westfield Bus Station: []
  Woden Bus Station (Platform 14)-Canberra Hospital: [Wjz3mAg, Wjz3mPO, Wjz3mWn]
  Kings Ave / National Circuit-City Bus Station (Platform 4): [Wjz4Quk, Wjz4RwH, Wjz4RFJ, Wjz4Rs-, Wjz4S1U, Wjz5FOn]
  stop_times_saturday: [["-", "-", "-", "-", "-", "-", 752a, 759a, 804a, 809a, 816a, 833a, 835a, 840a], [813a, 820a, 822a, 826a, 831a, 840a, 852a, 859a, 904a, 909a, 916a, 933a, 935a, 940a], [913a, 920a, 922a, 926a, 931a, 940a, 952a, 959a, 1004a, 1009a, 1016a, 1033a, 1035a, 1040a], [1013a, 1020a, 1022a, 1026a, 1031a, 1040a, 1052a, 1059a, 1104a, 1109a, 1116a, 1133a, 1135a, 1140a], [1113a, 1120a, 1122a, 1126a, 1131a, 1140a, 1152a, 1159a, 1204p, 1209p, 1216p, 1233p, 1235p, 1240p], [1213p, 1220p, 1222p, 1226p, 1231p, 1240p, 1252p, 1259p, 104p, 109p, 116p, 133p, 135p, 140p], [113p, 120p, 122p, 126p, 131p, 140p, 152p, 159p, 204p, 209p, 216p, 233p, 235p, 240p], [213p, 220p, 222p, 226p, 231p, 240p, 252p, 259p, 304p, 309p, 316p, 333p, 335p, 340p], [313p, 320p, 322p, 326p, 331p, 340p, 352p, 359p, 404p, 409p, 416p, 433p, 435p, 440p], [413p, 420p, 422p, 426p, 431p, 440p, 452p, 459p, 504p, 509p, 516p, 533p, 535p, 540p], [513p, 520p, 522p, 526p, 531p, 540p, 552p, 559p, 604p, 609p, 616p, 633p, 635p, 640p], [613p, 620p, 622p, 626p, 631p, 640p, 652p, 659p, 704p, 709p, 716p, 733p, 735p, 740p], [713p, 720p, 722p, 726p, 731p, 740p, 752p, 759p, 804p, 809p, 816p, 833p, 835p, 840p], [813p, 820p, 822p, 826p, 831p, 840p, 852p, 859p, 904p, 909p, 916p, 933p, 935p, 940p], [913p, 920p, 922p, 926p, 931p, 940p, 952p, 959p, 1004p, 1009p, 1016p, 1033p, 1035p, 1040p], [1013p, 1020p, 1022p, 1026p, 1031p, 1040p, 1052p, 1059p, 1104p, 1109p, 1116p, 1133p, 1135p, 1140p], [1113p, 1120p, 1122p, 1126p, 1131p, 1140p, 1150p, "-", "-", "-", "-", "-", "-", "-"]]
  short_name: "934"
  -
  time_points: [City Bus Station (Platform 8), Macarthur / Northbourne Ave, Flemington Rd / Sandford St, Flemington Rd / Nullabor Ave, Anthony Rolfe Av / Moonlight Av, Gungahlin Marketplace, Shoalhaven / Katherine Ave, Ngunnawal Primary, Chuculba / William Slim Dr, Belconnen Community Bus Station, Westfield Bus Station, Cohen Street Bus Station]
  long_name: To Cohen Street Bus Station
  between_stops:
  Westfield Bus Station-Cohen Street Bus Station: []
  City Bus Station (Platform 8)-Macarthur / Northbourne Ave: [Wjz5O3Q, Wjz5Oci, Wjz5P8n, Wjz5P8K, Wjz5PdJ, Wjz5Pl0, Wjz5Qi2, Wjz5Qgn, Wjz5Qmu, Wjz5QmR]
  Belconnen Community Bus Station-Westfield Bus Station: []
  Chuculba / William Slim Dr-Belconnen Community Bus Station: [Wjz6mip, Wjz69vO, Wjz69uI, Wjz69ht, Wjz69gA]
  short_name: "958"
  stop_times_sunday: [[900a, 906a, 914a, 921a, 928a, 937a, 945a, 953a, 1004a, 1014a, 1016a, 1021a], [1000a, 1006a, 1014a, 1021a, 1028a, 1037a, 1045a, 1053a, 1104a, 1114a, 1116a, 1121a], [1100a, 1106a, 1114a, 1121a, 1128a, 1137a, 1145a, 1153a, 1204p, 1214p, 1216p, 1221p], [1200p, 1206p, 1214p, 1221p, 1228p, 1237p, 1245p, 1253p, 104p, 114p, 116p, 121p], [100p, 106p, 114p, 121p, 128p, 137p, 145p, 153p, 204p, 214p, 216p, 221p], [200p, 206p, 214p, 221p, 228p, 237p, 245p, 253p, 304p, 314p, 316p, 321p], [300p, 306p, 314p, 321p, 328p, 337p, 345p, 353p, 404p, 414p, 416p, 421p], [400p, 406p, 414p, 421p, 428p, 437p, 445p, 453p, 504p, 514p, 516p, 521p], [500p, 506p, 514p, 521p, 528p, 537p, 545p, 553p, 604p, 614p, 616p, 621p], [600p, 606p, 614p, 621p, 628p, 637p, 645p, 653p, 704p, 714p, 716p, 721p], [700p, 706p, 714p, 721p, 728p, 737p, 745p, 753p, 804p, 814p, 816p, 821p]]
  -
  time_points: [Woden Bus Station (Platform 15), Southlands Mawson, Farrer Primary School, Isaacs, Canberra Hospital, Woden Bus Station]
  long_name: To Woden Bus Station
  between_stops:
  Canberra Hospital-Woden Bus Station: [Wjz3mWn, Wjz3mPO, Wjz3mAg]
  stop_times_saturday: [[810a, 819a, 824a, 829a, 833a, 841a], [1010a, 1019a, 1024a, 1029a, 1033a, 1041a], [1210p, 1219p, 1224p, 1229p, 1233p, 1241p], [210p, 219p, 224p, 229p, 233p, 241p], [410p, 419p, 424p, 429p, 433p, 441p], [610p, 619p, 624p, 629p, 633p, 641p], [813p, 821p, 826p, 830p, 834p, 841p], [1013p, 1021p, 1026p, 1030p, 1034p, 1041p]]
  short_name: "924"
  -
  time_points: [City Bus Station (Platform 8), St Thomas More's Campbell, Hospice / Menindee Dr, ADFA, City Bus Station]
  long_name: To City Bus Station
  between_stops:
  ADFA-City Bus Station: [Wjzcend, Wjzd8br, Wjzd0CK, Wjz5VUU, Wjz5VFA, Wjz5VAq, Wjz5V64, Wjz5NRJ, Wjz5NAQ]
  stop_times_saturday: [[1001a, 1013a, 1020a, 1027a, 1041a], [1201p, 1213p, 1220p, 1227p, 1241p], [201p, 213p, 220p, 227p, 241p], [401p, 413p, 420p, 427p, 441p], [601p, 613p, 620p, 627p, 641p], [801p, 813p, 820p, 827p, 841p], [901p, 913p, 920p, 927p, 941p], [1001p, 1013p, 1020p, 1027p, 1041p], [1101p, 1113p, 1120p, 1127p, 1141p]]
  short_name: "930"
  -
  time_points: [Campbell Park Offices, ADFA, Russell Offices, Kings Ave / National Circuit, Woden Bus Station (Platform 3), Cooleman Court, Canberra College Weston Campus, Chapman, Weston Creek Terminus]
  long_name: To Weston Creek Terminus
  between_stops:
  Russell Offices-Kings Ave / National Circuit: [Wjz4RFJ, Wjz4RwH, Wjz4Quk]
  ADFA-Russell Offices: [Wjzcend, Wjzce4H, Wjzce7O, Wjzc54R, Wjzc55s, Wjzc60i, Wjzc60A]
  Campbell Park Offices-ADFA: [Wjzce7O, Wjzce4H, Wjzcend]
  short_name: 26 226
  stop_times: [["-", "-", "-", "-", 718a, 725a, 727a, 731a, 735a], ["-", "-", "-", "-", 818a, 828a, 832a, 837a, 841a], ["-", "-", "-", "-", 858a, 908a, 912a, 917a, 921a], ["-", "-", "-", "-", 958a, 1007a, 1010a, 1015a, 1019a], ["-", "-", "-", "-", 1058a, 1107a, 1110a, 1115a, 1119a], ["-", "-", "-", "-", 1158a, 1207p, 1210p, 1215p, 1219p], ["-", "-", "-", "-", 1258p, 107p, 110p, 115p, 119p], ["-", "-", "-", "-", 158p, 207p, 210p, 215p, 219p], ["-", "-", "-", "-", 258p, 309p, 313p, 319p, 324p], ["-", "-", "-", "-", 328p, 340p, 344p, 350p, 355p], ["-", "-", "-", "-", 354p, 406p, 410p, 416p, 421p], ["-", "-", "-", "-", 418p, 430p, 434p, 440p, 445p], ["-", "-", "-", "-", 448p, 500p, 504p, 510p, 515p], [452p, 456p, 500p, 503p, 518p, 530p, 534p, 540p, 545p], [522p, 526p, 530p, 533p, 548p, 600p, 604p, 610p, 615p], ["-", "-", "-", "-", 618p, 630p, 632p, 636p, 640p], ["-", "-", "-", "-", 650p, 657p, 659p, 703p, 707p], ["-", "-", "-", "-", 750p, 757p, 759p, 803p, 807p], ["-", "-", "-", "-", 850p, 857p, 859p, 903p, 907p], ["-", "-", "-", "-", 950p, 957p, 959p, 1003p, 1007p], ["-", "-", "-", "-", 1050p, 1057p, 1059p, 1103p, 1107p]]
  -
  time_points: [Cooleman Court, Duffy, Holder, Weston Primary, Woden Bus Station]
  long_name: To Woden Bus Station
  between_stops: {}
   
  short_name: "925"
  stop_times_sunday: [[924a, 931a, 934a, 937a, 946a], [1024a, 1031a, 1034a, 1037a, 1046a], [1124a, 1131a, 1134a, 1137a, 1146a], [1224p, 1231p, 1234p, 1237p, 1246p], [124p, 131p, 134p, 137p, 146p], [224p, 231p, 234p, 237p, 246p], [324p, 331p, 334p, 337p, 346p], [424p, 431p, 434p, 437p, 446p], [524p, 531p, 534p, 537p, 546p], [624p, 631p, 634p, 637p, 646p]]
  -
  time_points: [City West, City Bus Station (Platform 10), Holder, Duffy Primary, Rivett, Cooleman Court]
  long_name: To Cooleman Court
  between_stops:
  City West-City Bus Station (Platform 10): []
  short_name: "729"
  stop_times: [[445p, 451p, 513p, 518p, 526p, 532p], [515p, 521p, 543p, 548p, 556p, 602p]]
  -
  time_points: [Tuggeranong Bus Station (Platform 7), Heagney / Clift Richardson, Chisholm, Erindale Centre, Tuggeranong Bus Station]
  long_name: To Tuggeranong Bus Station
  between_stops: {}
   
  stop_times_saturday: [[803a, 816a, 824a, 838a, 848a], [1003a, 1016a, 1024a, 1038a, 1048a], [1203p, 1216p, 1224p, 1238p, 1248p], [203p, 216p, 224p, 238p, 248p], [403p, 416p, 424p, 438p, 448p], [603p, 616p, 624p, 638p, 648p], [803p, 816p, 824p, 838p, 848p], [1003p, 1016p, 1024p, 1038p, 1048p]]
  short_name: "968"
  -
  time_points: [Fraser West Terminus, Dunlop, Macgregor, Belconnen Way, City Bus Station (Platform 10), Russell Offices, National Circ / Canberra Ave]
  long_name: To National Circ / Canberra Ave
  between_stops:
  City Bus Station (Platform 10)-Russell Offices: [Wjz5Nht, Wjz5MsD, Wjz5MsT, Wjz5MEL, Wjz4T-X, Wjz4_jm, Wjz4_wS, Wjz4-KO, Wjz4-Rc, Wjz4-WL, Wjz4-WZ]
  short_name: "703"
  stop_times: [[653a, 700a, 706a, 718a, 737a, 746a, 754a], [710a, 717a, 723a, 735a, 753a, "-", "-"], [723a, 730a, 736a, 748a, 806a, "-", "-"], [738a, 745a, 751a, 803a, 834a, 843a, 851a], [758a, 806a, 813a, 827a, 849a, 858a, 906a]]
  -
  time_points: [Belconnen Community Bus Station (Platform 4), Westfield Bus Station (Platform 2), Cohen Street Bus Station (Platform 6), Melba, Spence Terminus, Melba, Cohen Street Bus Station, Westfield Bus Station, Belconnen Community Bus Station]
  long_name: To Belconnen Community Bus Station
  between_stops:
  Belconnen Community Bus Station (Platform 4)-Westfield Bus Station (Platform 2): []
  Westfield Bus Station-Belconnen Community Bus Station: []
  Cohen Street Bus Station-Westfield Bus Station: []
  Westfield Bus Station (Platform 2)-Cohen Street Bus Station (Platform 6): []
  stop_times_saturday: [["-", "-", "-", "-", 725a, 738a, 753a, 755a, 759a], [752a, 754a, 758a, 811a, 825a, 838a, 853a, 855a, 859a], [852a, 854a, 858a, 911a, 925a, 938a, 953a, 955a, 959a], [952a, 954a, 958a, 1011a, 1025a, 1038a, 1053a, 1055a, 1059a], [1052a, 1054a, 1058a, 1111a, 1125a, 1138a, 1153a, 1155a, 1159a], [1152a, 1154a, 1158a, 1211p, 1225p, 1238p, 1253p, 1255p, 1259p], [1252p, 1254p, 1258p, 111p, 125p, 138p, 153p, 155p, 159p], [152p, 154p, 158p, 211p, 225p, 238p, 253p, 255p, 259p], [252p, 254p, 258p, 311p, 325p, 338p, 353p, 355p, 359p], [352p, 354p, 358p, 411p, 425p, 438p, 453p, 455p, 459p], [452p, 454p, 458p, 511p, 525p, 538p, 553p, 555p, 559p], [552p, 554p, 558p, 611p, 625p, 638p, 652p, 654p, 658p], [651p, 653p, 657p, 709p, 723p, 736p, 750p, 752p, 756p], [751p, 753p, 757p, 809p, 823p, 836p, 850p, 852p, 856p], [855p, 857p, 901p, 913p, 927p, 940p, 954p, 956p, 1000p], [955p, 957p, 1001p, 1013p, 1027p, 1040p, 1054p, 1056p, 1100p], [1055p, 1057p, 1101p, 1113p, 1127p, 1140p, 1154p, 1156p, 1200a]]
  short_name: "906"
  -
  time_points: [Alexander Maconochie Centre, Woden Bus Station]
  long_name: To Woden Bus Station
  between_stops:
  Alexander Maconochie Centre-Woden Bus Station: [Wjz3kAx, Wjz3dXS]
  short_name: "988"
  stop_times_sunday: [[1130a, 1150a], [320p, 340p], [730p, 750p]]
  -
  time_points: [City Bus Station (Platform 9), Russell Offices, Kings Ave / National Circuit, Kingston, Manuka / Captain Cook Cres, Narrabundah College, Narrabundah Terminus, Geoscience Australia]
  long_name: To Geoscience Australia
  between_stops:
  Russell Offices-Kings Ave / National Circuit: [Wjz4RFJ, Wjz4RwH, Wjz4Quk]
  City Bus Station (Platform 9)-Russell Offices: [Wjz5Nht, Wjz5MsD, Wjz5MsT, Wjz5MEL, Wjz4T-X, Wjz4_jm, Wjz4_wS, Wjz4-KO, Wjz4-Rc, Wjz4-WL, Wjz4-WZ]
  short_name: "4"
  stop_times: [[633a, 641a, 645a, 649a, 652a, 700a, "-", 703a], [703a, 711a, 715a, 719a, 722a, 730a, "-", 733a], [733a, 742a, 747a, 752a, 755a, 805a, "-", 808a], [803a, 812a, 817a, 822a, 825a, 835a, "-", 838a], [818a, 827a, 832a, 837a, 840a, 850a, "-", 853a], [833a, 842a, 847a, 852a, 855a, 905a, "-", 908a], [903a, 912a, 917a, 922a, 925a, 935a, "-", 938a], [933a, 941a, 945a, 949a, 952a, 1001a, "-", 1004a], [1003a, 1011a, 1015a, 1019a, 1022a, 1031a, "-", 1034a], [1033a, 1041a, 1045a, 1049a, 1052a, 1101a, "-", 1104a], [1103a, 1111a, 1115a, 1119a, 1122a, 1131a, "-", 1134a], [1133a, 1141a, 1145a, 1149a, 1152a, 1201p, "-", 1204p], [1203p, 1211p, 1215p, 1219p, 1222p, 1231p, "-", 1234p], [1233p, 1241p, 1245p, 1249p, 1252p, 101p, "-", 104p], [103p, 111p, 115p, 119p, 122p, 131p, "-", 134p], [133p, 141p, 145p, 149p, 152p, 201p, "-", 204p], [203p, 211p, 215p, 219p, 222p, 231p, "-", 234p], [233p, 241p, 245p, 249p, 252p, 301p, "-", 304p], [303p, 312p, 317p, 322p, 325p, 334p, "-", 337p], [333p, 342p, 347p, 352p, 355p, 404p, "-", 407p], [405p, 414p, 419p, 424p, 427p, 436p, "-", 439p], [439p, 448p, 453p, 458p, 501p, 510p, "-", 513p], [509p, 518p, 523p, 528p, 531p, 540p, "-", 543p], [539p, 548p, 553p, 558p, 601p, 610p, 613p, "-"], [616p, 625p, 630p, 634p, 637p, 642p, 645p, "-"], [707p, 715p, 719p, 723p, 726p, 731p, 734p, "-"], [810p, 818p, 822p, 826p, 829p, 834p, 837p, "-"], [910p, 918p, 922p, 926p, 929p, 934p, 937p, "-"], [1010p, 1018p, 1022p, 1026p, 1029p, 1034p, 1037p, "-"], [1110p, 1118p, 1122p, 1126p, 1129p, 1134p, 1137p, "-"]]
  -
  time_points: [Woden Bus Station (Platform 14), Canberra Hospital, Red Hill, Manuka, Kings Ave / National Circuit, City Bus Station (Platform 4), Lyneham / Wattle St, North Lyneham, Dickson / Antill St]
  long_name: To Dickson
  between_stops:
  Woden Bus Station (Platform 14)-Canberra Hospital: [Wjz3mAg, Wjz3mPO, Wjz3mWn]
  short_name: "6"
  stop_times: [[618a, 626a, 638a, 645a, 650a, 701a, 713a, 719a, 725a], [653a, 701a, 713a, 720a, 725a, 737a, 751a, 759a, 806a], [723a, 731a, 745a, 753a, 758a, 812a, 826a, 834a, 841a], [753a, 803a, 817a, 825a, 830a, 844a, 858a, 906a, 913a], [823a, 833a, 847a, 855a, 900a, 914a, 928a, 936a, 943a], [853a, 903a, 917a, 925a, 930a, 944a, 956a, 1004a, 1011a], [923a, 933a, 945a, 952a, 957a, 1011a, 1023a, 1031a, 1038a], [1023a, 1033a, 1045a, 1052a, 1057a, 1111a, 1123a, 1131a, 1138a], [1123a, 1133a, 1145a, 1152a, 1157a, 1211p, 1223p, 1231p, 1238p], [1223p, 1233p, 1245p, 1252p, 1257p, 111p, 123p, 131p, 138p], [123p, 133p, 145p, 152p, 157p, 211p, 223p, 231p, 238p], [223p, 233p, 245p, 252p, 257p, 311p, 325p, 333p, 340p], ["-", "-", "-", "-", "-", 344p, 358p, 406p, 413p], [323p, 333p, 347p, 355p, 400p, 414p, 428p, 436p, 443p], [353p, 403p, 417p, 425p, 430p, 444p, 458p, 506p, 513p], [423p, 433p, 447p, 455p, 500p, 514p, 528p, 536p, 543p], [453p, 503p, 517p, 525p, 530p, 544p, 558p, 606p, 613p], [516p, 526p, 540p, 548p, 553p, 607p, 621p, 629p, 635p], [553p, 603p, 617p, 625p, 630p, 640p, 650p, 656p, 702p], [630p, 638p, 648p, 655p, 700p, 710p, 720p, 726p, 732p], [730p, 738p, 748p, 755p, 800p, 810p, 820p, 826p, 832p], [830p, 838p, 848p, 855p, 900p, 910p, 920p, 926p, 932p], [930p, 938p, 948p, 955p, 1000p, 1010p, 1020p, 1026p, 1032p], [1030p, 1038p, 1048p, 1055p, 1100p, 1110p, 1120p, 1126p, 1132p]]
  -
  time_points: [Cohen Street Bus Station (Platform 2), Westfield Bus Station (Platform 1), Belconnen Community Bus Station (Platform 2), City Bus Station (Platform 10), Russell Offices, National Circ / Canberra Ave]
  long_name: To National Circ / Canberra Ave
  between_stops:
  Westfield Bus Station (Platform 1)-Belconnen Community Bus Station (Platform 2): []
  Cohen Street Bus Station (Platform 2)-Westfield Bus Station (Platform 1): []
  City Bus Station (Platform 10)-Russell Offices: [Wjz5Nht, Wjz5MsD, Wjz5MsT, Wjz5MEL, Wjz4T-X, Wjz4_jm, Wjz4_wS, Wjz4-KO, Wjz4-Rc, Wjz4-WL, Wjz4-WZ]
  Belconnen Community Bus Station (Platform 2)-City Bus Station (Platform 10): [Wjz681S, Wjz689c, Wjz68W3, Wjz68W5, Wjz6gia, Wjz6giR, Wjz5nw6, Wjz5nwb, Wjz5mbS, Wjz5maK, Wjz5G6B, Wjz5G6U, Wjz5GNG, Wjz5GMT, Wjz5FSY, Wjz5F-1]
  short_name: "710"
  stop_times: [[658a, 700a, 704a, 723a, 732a, 740a], [728a, 730a, 734a, 753a, 802a, 810a], [743a, 745a, 749a, 808a, 817a, 825a], [758a, 800a, 804a, 823a, 832a, 840a], [813a, 815a, 819a, 838a, 847a, 855a]]
  -
  time_points: [Tuggeranong Bus Station (Platform 4), Kambah High, Kambah Village, Woden Bus Station]
  long_name: To Woden Bus Station
  between_stops: {}
   
  short_name: "962"
  stop_times_sunday: [[924a, 931a, 939a, 952a], [1024a, 1031a, 1039a, 1052a], [1124a, 1131a, 1139a, 1152a], [1224p, 1231p, 1239p, 1252p], [124p, 131p, 139p, 152p], [224p, 231p, 239p, 252p], [324p, 331p, 339p, 352p], [424p, 431p, 439p, 452p], [524p, 531p, 539p, 552p], [624p, 631p, 638p, 649p]]
  -
  time_points: [City West, City Bus Station (Platform 10), Russell Offices, Chisholm, Calwell, Theodore, Tharwa Drive]
  long_name: To Tharwa Drive
  between_stops:
  City West-City Bus Station (Platform 10): []
  City Bus Station (Platform 10)-Russell Offices: [Wjz5Nht, Wjz5MsD, Wjz5MsT, Wjz5MEL, Wjz4T-X, Wjz4_jm, Wjz4_wS, Wjz4-KO, Wjz4-Rc, Wjz4-WL, Wjz4-WZ]
  short_name: "769"
  stop_times: [[427p, 433p, 442p, 507p, 517p, 527p, 532p], [500p, 506p, 515p, 540p, 550p, 600p, 605p], [537p, 543p, 552p, 617p, 627p, 637p, 642p]]
  -
  time_points: [Tharwa Drive, Theodore, Calwell, Chisholm, Russell Offices, City Bus Station (Platform 11), City West]
  long_name: To City West
  between_stops:
  Russell Offices-City Bus Station (Platform 11): [Wjz4-WZ, Wjz4-WL, Wjz4-Rc, Wjz4-KO, Wjz4_wS, Wjz4_jm, Wjz4T-X, Wjz5MEL, Wjz5MsT, Wjz5MsD, Wjz5Nht]
  City Bus Station (Platform 11)-City West: []
  short_name: "769"
  stop_times: [[641a, 646a, 656a, 706a, 733a, 743a, 747a], [721a, 726a, 736a, 746a, 813a, 823a, 827a], [741a, 746a, 756a, 806a, 833a, 843a, 847a]]
  -
  time_points: [City Bus Station (Platform 8), Macarthur / Northbourne Ave, Northbourne Avenue / Antill St, Bimberi Centre]
  long_name: To Bimberi Centre
  between_stops:
  City Bus Station (Platform 8)-Macarthur / Northbourne Ave: [Wjz5O3Q, Wjz5Oci, Wjz5P8n, Wjz5P8K, Wjz5PdJ, Wjz5Pl0, Wjz5Qi2, Wjz5Qgn, Wjz5Qmu, Wjz5QmR]
  stop_times_saturday: [[632a, 638a, 640a, 650a], [342p, 348p, 350p, 400p]]
  short_name: "982"
  -
  time_points: [Woden Bus Station (Platform 4), Belconnen Community Bus Station, Westfield Bus Station, Cohen Street Bus Station]
  long_name: To Cohen Street Bus Station
  between_stops:
  Westfield Bus Station-Cohen Street Bus Station: []
  Belconnen Community Bus Station-Westfield Bus Station: []
  short_name: "749"
  stop_times: [[753a, 820a, 822a, 827a], [436p, 505p, 507p, 512p], [510p, 539p, 541p, 546p], [540p, 609p, 611p, 616p]]
  -
  time_points: [Bimberi Centre, Northbourne Avenue / Antill St, Macarthur / Northbourne Ave, City Bus Station]
  long_name: To City Bus Station
  between_stops:
  Macarthur / Northbourne Ave-City Bus Station: [Wjz5QmR, Wjz5Qmu, Wjz5Qgn, Wjz5Qi2, Wjz5Pl0, Wjz5PdJ, Wjz5P8K, Wjz5P8n, Wjz5Oci, Wjz5O3Q]
  stop_times_saturday: [[715p, 724p, 726p, 733p]]
  short_name: "982"
  -
  time_points: [City Bus Station (Platform 9), Newcastle Street after Isa Street, Lithgow St Terminus Fyshwick]
  long_name: To Lithgow St Terminus
  between_stops: {}
   
  short_name: "780"
  stop_times: [[648a, 707a, 723a], [719a, 738a, 754a]]
  -
  time_points: [Cooleman Court, Rivett, Chapman, Fisher, Waramanga, Woden Bus Station]
  long_name: To Woden Bus Station
  between_stops: {}
   
  stop_times_saturday: [[755a, 803a, 806a, 816a, 819a, 826a], [855a, 903a, 906a, 916a, 919a, 926a], [955a, 1003a, 1006a, 1016a, 1019a, 1026a], [1055a, 1103a, 1106a, 1116a, 1119a, 1126a], [1155a, 1203p, 1206p, 1216p, 1219p, 1226p], [1255p, 103p, 106p, 116p, 119p, 126p], [155p, 203p, 206p, 216p, 219p, 226p], [255p, 303p, 306p, 316p, 319p, 326p], [355p, 403p, 406p, 416p, 419p, 426p], [455p, 503p, 506p, 516p, 519p, 526p], [555p, 603p, 606p, 616p, 619p, 626p], [655p, 703p, 706p, 716p, 719p, 726p], [755p, 803p, 806p, 816p, 819p, 826p], [855p, 903p, 906p, 916p, 919p, 926p], [955p, 1003p, 1006p, 1016p, 1019p, 1026p], [1055p, 1103p, 1106p, 1116p, 1119p, 1126p]]
  short_name: "927"
  -
  time_points: [Woden Bus Station (Platform 14), Canberra Hospital, Garran, Hughes, Deakin, Parliament House, Kings Ave / National Circuit, City Bus Station (Platform 4), National Museum of Australia, Burton and Garran Hall Daley Road, O'Connor, Calvary Hospital, Belconnen Community Bus Station, Westfield Bus Station, Cohen Street Bus Station]
  long_name: To Cohen Street Bus Station
  between_stops:
  Westfield Bus Station-Cohen Street Bus Station: []
  Parliament House-Kings Ave / National Circuit: [Wjz4INj, Wjz4P6x]
  Belconnen Community Bus Station-Westfield Bus Station: []
  Woden Bus Station (Platform 14)-Canberra Hospital: [Wjz3mAg, Wjz3mPO, Wjz3mWn]
  Kings Ave / National Circuit-City Bus Station (Platform 4): [Wjz4Quk, Wjz4RwH, Wjz4RFJ, Wjz4Rs-, Wjz4S1U, Wjz5FOn]
  short_name: "3"
  stop_times: [[612a, 619a, 621a, 625a, 630a, 634a, 638a, 650a, 701a, 706a, 711a, 718a, 730a, 732a, 737a], [642a, 649a, 651a, 655a, 700a, 704a, 708a, 720a, 731a, 736a, 741a, 750a, 803a, 805a, 810a], [712a, 719a, 721a, 725a, 730a, 734a, 740a, 752a, 803a, 808a, 813a, 822a, 835a, 837a, 842a], [738a, 746a, 749a, 754a, 802a, 806a, 812a, 824a, 835a, 840a, 845a, 854a, 907a, 909a, 914a], [808a, 816a, 819a, 824a, 832a, 836a, 842a, 854a, 905a, 910a, 915a, 924a, 936a, 938a, 943a], [838a, 846a, 849a, 854a, 902a, 906a, 912a, 924a, 935a, 940a, 945a, 952a, 1004a, 1006a, 1011a], [912a, 920a, 923a, 928a, 934a, 938a, 942a, 954a, 1005a, 1010a, 1015a, 1022a, 1034a, 1036a, 1041a], [942a, 949a, 951a, 955a, 1000a, 1004a, 1008a, 1020a, 1031a, 1036a, 1041a, 1048a, 1100a, 1102a, 1107a], [1012a, 1019a, 1021a, 1025a, 1030a, 1034a, 1038a, 1050a, 1101a, 1106a, 1111a, 1118a, 1130a, 1132a, 1137a], [1042a, 1049a, 1051a, 1055a, 1100a, 1104a, 1108a, 1120a, 1131a, 1136a, 1141a, 1148a, 1200p, 1202p, 1207p], [1112a, 1119a, 1121a, 1125a, 1130a, 1134a, 1138a, 1150a, 1201p, 1206p, 1211p, 1218p, 1230p, 1232p, 1237p], [1142a, 1149a, 1151a, 1155a, 1200p, 1204p, 1208p, 1220p, 1231p, 1236p, 1241p, 1248p, 100p, 102p, 107p], [1212p, 1219p, 1221p, 1225p, 1230p, 1234p, 1238p, 1250p, 101p, 106p, 111p, 118p, 130p, 132p, 137p], [1242p, 1249p, 1251p, 1255p, 100p, 104p, 108p, 120p, 131p, 136p, 141p, 148p, 200p, 202p, 207p], [112p, 119p, 121p, 125p, 130p, 134p, 138p, 150p, 201p, 206p, 211p, 218p, 230p, 232p, 237p], [142p, 149p, 151p, 155p, 200p, 204p, 208p, 220p, 231p, 236p, 241p, 248p, 300p, 302p, 307p], [212p, 219p, 221p, 225p, 230p, 234p, 238p, 250p, 301p, 307p, 313p, 321p, 334p, 336p, 341p], [242p, 249p, 251p, 255p, 300p, 304p, 308p, 320p, 331p, 337p, 343p, 351p, 404p, 406p, 411p], [309p, 317p, 319p, 324p, 330p, 334p, 338p, 350p, 401p, 407p, 413p, 421p, 434p, 436p, 441p], [339p, 347p, 349p, 354p, 400p, 404p, 408p, 420p, 431p, 437p, 443p, 451p, 504p, 506p, 511p], [409p, 417p, 419p, 424p, 430p, 434p, 438p, 450p, 501p, 507p, 513p, 521p, 534p, 536p, 541p], [439p, 447p, 449p, 454p, 500p, 504p, 508p, 520p, 531p, 537p, 543p, 551p, 604p, 606p, 611p], [511p, 519p, 521p, 526p, 532p, 536p, 540p, 552p, 603p, 609p, 615p, 623p, 636p, 638p, 643p], [539p, 547p, 549p, 554p, 600p, 604p, 608p, 620p, 631p, 636p, 641p, 648p, 700p, 702p, 707p], [608p, 616p, 618p, 623p, 629p, 632p, 636p, 648p, 659p, 704p, 709p, 716p, 728p, 730p, 735p], [643p, 649p, 651p, 655p, 700p, 703p, 707p, 719p, 730p, 735p, 740p, 747p, 759p, 801p, 806p], [713p, 719p, 721p, 725p, 730p, 733p, 737p, 749p, 800p, 805p, 810p, 817p, 829p, 831p, 836p], [813p, 819p, 821p, 825p, 830p, 833p, 837p, 849p, 900p, 905p, 910p, 917p, 929p, 931p, 936p], [913p, 919p, 921p, 925p, 930p, 933p, 937p, 949p, 1000p, 1005p, 1010p, 1017p, 1029p, 1031p, 1036p], [1013p, 1019p, 1021p, 1025p, 1030p, 1033p, 1037p, 1049p, 1100p, 1105p, 1110p, 1117p, 1129p, 1131p, 1136p], [1113p, 1119p, 1121p, 1125p, 1130p, 1133p, 1137p, 1147p, "-", "-", "-", "-", "-", "-", "-"]]
  -
  time_points: [Belconnen Community Bus Station (Platform 4), Westfield Bus Station (Platform 2), Cohen Street Bus Station (Platform 6), Melba, Spence Terminus, Melba, Cohen Street Bus Station, Westfield Bus Station, Belconnen Community Bus Station]
  long_name: To Belconnen Community Bus Station
  between_stops:
  Belconnen Community Bus Station (Platform 4)-Westfield Bus Station (Platform 2): []
  Westfield Bus Station-Belconnen Community Bus Station: []
  Cohen Street Bus Station-Westfield Bus Station: []
  Westfield Bus Station (Platform 2)-Cohen Street Bus Station (Platform 6): []
  short_name: "906"
  stop_times_sunday: [[852a, 854a, 858a, 911a, 925a, 938a, 953a, 955a, 959a], [952a, 954a, 958a, 1011a, 1025a, 1038a, 1053a, 1055a, 1059a], [1052a, 1054a, 1058a, 1111a, 1125a, 1138a, 1153a, 1155a, 1159a], [1152a, 1154a, 1158a, 1211p, 1225p, 1238p, 1253p, 1255p, 1259p], [1252p, 1254p, 1258p, 111p, 125p, 138p, 153p, 155p, 159p], [152p, 154p, 158p, 211p, 225p, 238p, 253p, 255p, 259p], [252p, 254p, 258p, 311p, 325p, 338p, 353p, 355p, 359p], [352p, 354p, 358p, 411p, 425p, 438p, 453p, 455p, 459p], [452p, 454p, 458p, 511p, 525p, 538p, 553p, 555p, 559p], [552p, 554p, 558p, 611p, 625p, 638p, 652p, 654p, 658p]]
  -
  time_points: [Gungahlin Marketplace, Flemington Rd / Sandford St, Northbourne Avenue / Antill St, Macarthur / Northbourne Ave, City Bus Station (Platform 9), Russell Offices, Kings Ave / National Circuit, Railway Station Kingston, Fyshwick Direct Factory Outlet]
  long_name: To Fyshwick DirectFactory Outlet
  between_stops:
  Russell Offices-Kings Ave / National Circuit: [Wjz4RFJ, Wjz4RwH, Wjz4Quk]
  Northbourne Avenue / Antill St-Macarthur / Northbourne Ave: [Wjz5SDc, Wjz5Sux, Wjz5SrO, Wjz5Sqk, Wjz5RvC, Wjz5Rsi, Wjz5RkN]
  Macarthur / Northbourne Ave-City Bus Station (Platform 9): [Wjz5QmR, Wjz5Qmu, Wjz5Qgn, Wjz5Qi2, Wjz5Pl0, Wjz5PdJ, Wjz5P8K, Wjz5P8n, Wjz5Oci, Wjz5O3Q]
  City Bus Station (Platform 9)-Russell Offices: [Wjz5Nht, Wjz5MsD, Wjz5MsT, Wjz5MEL, Wjz4T-X, Wjz4_jm, Wjz4_wS, Wjz4-KO, Wjz4-Rc, Wjz4-WL, Wjz4-WZ]
  short_name: "200"
  stop_times: [[701a, 709a, 715a, 718a, 723a, 732a, 736a, 742a, 749a], [716a, 724a, 731a, 737a, 747a, 757a, 801a, 807a, 814a], [731a, 740a, 749a, 755a, 805a, 815a, 819a, 825a, 832a], [746a, 755a, 804a, 810a, 820a, 830a, 834a, 840a, 847a], [801a, 810a, 819a, 825a, 835a, 845a, 849a, 855a, 902a], [816a, 825a, 834a, 840a, 850a, 900a, 904a, 910a, 917a], [831a, 840a, 849a, 855a, 902a, 910a, 914a, 920a, 927a], [846a, 855a, 902a, 905a, 910a, 918a, 922a, 928a, 935a], [901a, 909a, 915a, 918a, 923a, 931a, 935a, 941a, 948a], [916a, 924a, 930a, 933a, 938a, 946a, 950a, 956a, 1003a], [931a, 939a, 945a, 948a, 953a, 1001a, 1005a, 1011a, 1018a], [946a, 954a, 1000a, 1003a, 1008a, 1016a, 1020a, 1026a, 1033a], [1001a, 1009a, 1015a, 1018a, 1023a, 1031a, 1035a, 1041a, 1048a], [1016a, 1024a, 1030a, 1033a, 1038a, 1046a, 1050a, 1056a, 1103a], [1031a, 1039a, 1045a, 1048a, 1053a, 1101a, 1105a, 1111a, 1118a], [1046a, 1054a, 1100a, 1103a, 1108a, 1116a, 1120a, 1126a, 1133a], [1101a, 1109a, 1115a, 1118a, 1123a, 1131a, 1135a, 1141a, 1148a], [1116a, 1124a, 1130a, 1133a, 1138a, 1146a, 1150a, 1156a, 1203p], [1131a, 1139a, 1145a, 1148a, 1153a, 1201p, 1205p, 1211p, 1218p], [1146a, 1154a, 1200p, 1203p, 1208p, 1216p, 1220p, 1226p, 1233p], [1201p, 1209p, 1215p, 1218p, 1223p, 1231p, 1235p, 1241p, 1248p], [1216p, 1224p, 1230p, 1233p, 1238p, 1246p, 1250p, 1256p, 103p], [1233p, 1241p, 1247p, 1250p, 1255p, 103p, 107p, 113p, 120p], [1246p, 1254p, 100p, 103p, 108p, 116p, 120p, 126p, 133p], [101p, 109p, 115p, 118p, 123p, 131p, 135p, 141p, 148p], [116p, 124p, 130p, 133p, 138p, 146p, 150p, 156p, 203p], [131p, 139p, 145p, 148p, 153p, 201p, 205p, 211p, 218p], [146p, 154p, 200p, 203p, 208p, 216p, 220p, 226p, 233p], [201p, 209p, 215p, 218p, 223p, 231p, 235p, 241p, 248p], [216p, 224p, 230p, 233p, 238p, 246p, 250p, 256p, 303p], [231p, 239p, 245p, 248p, 253p, 301p, 305p, 311p, 318p], [246p, 254p, 300p, 303p, 308p, 316p, 320p, 326p, 333p], [301p, 309p, 315p, 318p, 323p, 331p, 335p, 341p, 348p], [316p, 324p, 330p, 333p, 338p, 346p, 350p, 356p, 404p], [331p, 339p, 345p, 348p, 353p, 402p, 407p, 415p, 424p], [346p, 354p, 400p, 403p, 412p, 422p, 427p, 435p, 444p], [401p, 410p, 417p, 420p, 429p, 439p, 444p, 452p, 501p], [416p, 425p, 432p, 435p, 444p, 454p, 459p, 507p, 516p], [431p, 440p, 447p, 450p, 459p, 509p, 514p, 522p, 531p], [446p, 455p, 502p, 505p, 514p, 524p, 529p, 537p, 546p], [501p, 510p, 517p, 520p, 529p, 539p, 544p, 552p, 600p], [516p, 525p, 532p, 535p, 544p, 554p, 559p, 605p, 612p], [531p, 540p, 547p, 550p, 559p, 607p, 611p, 617p, 624p], [546p, 555p, 601p, 604p, 609p, 617p, 621p, 627p, 634p], [601p, 609p, 615p, 618p, 623p, 631p, 635p, 641p, 648p], [616p, 624p, 630p, 633p, 638p, 646p, 650p, 656p, 703p], [631p, 639p, 645p, 648p, 653p, 701p, 705p, 711p, 718p], [646p, 654p, 700p, 703p, 708p, 716p, 720p, 726p, 733p]]
  -
  time_points: [Dickson / Antill St, Lyneham / Wattle St, Macarthur / Miller O'Connor, City Bus Station]
  long_name: To City Bus Station
  between_stops: {}
   
  short_name: "8"
  stop_times: [[626a, 632a, 637a, 644a], [657a, 703a, 708a, 715a], [724a, 730a, 737a, 746a], [757a, 804a, 811a, 820a], [831a, 838a, 845a, 854a], [904a, 911a, 918a, 927a], [1009a, 1015a, 1020a, 1027a], [1109a, 1115a, 1120a, 1127a], [1209p, 1215p, 1220p, 1227p], [109p, 115p, 120p, 127p], [209p, 215p, 220p, 227p], [302p, 309p, 316p, 325p], [332p, 339p, 346p, 355p], [408p, 415p, 422p, 431p], [437p, 444p, 451p, 500p], [507p, 514p, 521p, 530p], [537p, 544p, 551p, 600p], [646p, 652p, 657p, 702p], [746p, 752p, 757p, 802p], [846p, 852p, 857p, 902p], [946p, 952p, 957p, 1002p], [1046p, 1052p, 1057p, 1102p]]
  -
  time_points: [Woden Bus Station (Platform 16), Weston Primary, Holder, Duffy, Cooleman Court]
  long_name: To Cooleman Court
  between_stops: {}
   
  short_name: "925"
  stop_times_sunday: [[957a, 1007a, 1009a, 1011a, 1019a], [1057a, 1107a, 1109a, 1111a, 1119a], [1157a, 1207p, 1209p, 1211p, 1219p], [1257p, 107p, 109p, 111p, 119p], [157p, 207p, 209p, 211p, 219p], [257p, 307p, 309p, 311p, 319p], [357p, 407p, 409p, 411p, 419p], [457p, 507p, 509p, 511p, 519p], [557p, 607p, 609p, 611p, 619p], [657p, 707p, 709p, 711p, 719p]]
  -
  time_points: [Gungahlin Marketplace, Nicholls Primary, Federation Square, Chuculba / William Slim Dr, Belconnen Community Bus Station, Westfield Bus Station, Cohen Street Bus Station]
  long_name: To Cohen Street Bus Station
  between_stops:
  Westfield Bus Station-Cohen Street Bus Station: []
  Belconnen Community Bus Station-Westfield Bus Station: []
  Chuculba / William Slim Dr-Belconnen Community Bus Station: [Wjz6mip, Wjz69vO, Wjz69uI, Wjz69ht, Wjz69gA]
  short_name: "952"
  stop_times_sunday: [[839a, 847a, 900a, 905a, 918a, 920a, 925a], [939a, 947a, 1000a, 1005a, 1018a, 1020a, 1025a], [1039a, 1047a, 1100a, 1105a, 1118a, 1120a, 1125a], [1139a, 1147a, 1200p, 1205p, 1218p, 1220p, 1225p], [1239p, 1247p, 100p, 105p, 118p, 120p, 125p], [139p, 147p, 200p, 205p, 218p, 220p, 225p], [239p, 247p, 300p, 305p, 318p, 320p, 325p], [339p, 347p, 400p, 405p, 418p, 420p, 425p], [439p, 447p, 500p, 505p, 518p, 520p, 525p], [539p, 547p, 600p, 605p, 618p, 620p, 625p], [639p, 647p, 700p, 705p, 718p, 720p, 725p]]
  -
  time_points: [Cohen Street Bus Station (Platform 2), Westfield Bus Station (Platform 1), Belconnen Community Bus Station (Platform 3), Calvary Hospital, O'Connor, Burton and Garran Hall Daley Road, National Museum of Australia, City Bus Station (Platform 2), Kings Ave / National Circuit, Parliament House, Deakin, Hughes, Garran, Canberra Hospital, Woden Bus Station]
  long_name: To Woden Bus Station
  between_stops:
  Westfield Bus Station (Platform 1)-Belconnen Community Bus Station (Platform 3): []
  Kings Ave / National Circuit-Parliament House: [Wjz4P6x, Wjz4IrL]
  City Bus Station (Platform 2)-Kings Ave / National Circuit: [Wjz5FOn, Wjz4S1U, Wjz4Rs-, Wjz4RFJ, Wjz4RwH, Wjz4Quk]
  Cohen Street Bus Station (Platform 2)-Westfield Bus Station (Platform 1): []
  Canberra Hospital-Woden Bus Station: [Wjz3mWn, Wjz3mPO, Wjz3mAg]
  short_name: "3"
  stop_times: [["-", "-", "-", "-", "-", "-", "-", 618a, 627a, 631a, 636a, 640a, 644a, 646a, 653a], ["-", "-", "-", "-", "-", "-", "-", 648a, 657a, 701a, 706a, 710a, 714a, 716a, 723a], [628a, 630a, 634a, 651a, 657a, 701a, 705a, 718a, 727a, 731a, 736a, 742a, 746a, 748a, 758a], [656a, 658a, 702a, 719a, 725a, 729a, 734a, 748a, 758a, 803a, 808a, 814a, 818a, 820a, 830a], [721a, 723a, 727a, 746a, 754a, 759a, 804a, 818a, 828a, 833a, 838a, 844a, 848a, 850a, 900a], [745a, 747a, 751a, 810a, 819a, 827a, 832a, 848a, 853a, 901a, 906a, 908a, 912a, 914a, 924a], [821a, 823a, 827a, 846a, 854a, 859a, 904a, 918a, 928a, 932a, 937a, 942a, 946a, 948a, 955a], [851a, 853a, 857a, 916a, 924a, 929a, 934a, 948a, 958a, 1002a, 1007a, 1012a, 1016a, 1018a, 1025a], [924a, 926a, 930a, 947a, 954a, 959a, 1004a, 1018a, 1028a, 1032a, 1037a, 1042a, 1046a, 1048a, 1055a], [954a, 956a, 1000a, 1017a, 1024a, 1029a, 1034a, 1048a, 1058a, 1102a, 1107a, 1112a, 1116a, 1118a, 1125a], [1024a, 1026a, 1030a, 1047a, 1054a, 1059a, 1104a, 1118a, 1128a, 1132a, 1137a, 1142a, 1146a, 1148a, 1155a], [1054a, 1056a, 1100a, 1117a, 1124a, 1129a, 1134a, 1148a, 1158a, 1202p, 1207p, 1212p, 1216p, 1218p, 1225p], [1124a, 1126a, 1130a, 1147a, 1154a, 1159a, 1204p, 1218p, 1228p, 1232p, 1237p, 1242p, 1246p, 1248p, 1255p], [1154a, 1156a, 1200p, 1217p, 1224p, 1229p, 1234p, 1248p, 1258p, 102p, 107p, 112p, 116p, 118p, 125p], [1224p, 1226p, 1230p, 1247p, 1254p, 1259p, 104p, 118p, 128p, 132p, 137p, 142p, 146p, 148p, 155p], [1254p, 1256p, 100p, 117p, 124p, 129p, 134p, 148p, 158p, 202p, 207p, 212p, 216p, 218p, 225p], [124p, 126p, 130p, 147p, 154p, 159p, 204p, 218p, 228p, 232p, 237p, 242p, 246p, 248p, 255p], [154p, 156p, 200p, 217p, 224p, 229p, 234p, 248p, 258p, 303p, 308p, 314p, 318p, 320p, 329p], [229p, 231p, 235p, 248p, 258p, 303p, 310p, 324p, 334p, 339p, 344p, 350p, 354p, 356p, 405p], [250p, 252p, 256p, 315p, 323p, 328p, 334p, 348p, 358p, 403p, 408p, 414p, 418p, 420p, 429p], [317p, 319p, 323p, 342p, 350p, 355p, 401p, 415p, 425p, 430p, 435p, 441p, 445p, 447p, 456p], [346p, 348p, 352p, 411p, 419p, 424p, 430p, 444p, 454p, 459p, 504p, 510p, 514p, 516p, 525p], [418p, 420p, 424p, 443p, 451p, 456p, 502p, 516p, 526p, 531p, 536p, 542p, 546p, 548p, 557p], [445p, 447p, 451p, 510p, 518p, 523p, 529p, 543p, 553p, 558p, 603p, 609p, 613p, 615p, 624p], [515p, 517p, 521p, 540p, 548p, 553p, 559p, 613p, 623p, 628p, 632p, 637p, 641p, 643p, 650p], [547p, 549p, 553p, 612p, 620p, 625p, 631p, 644p, 653p, 658p, 702p, 707p, 711p, 713p, 720p], [620p, 622p, 626p, 643p, 650p, 655p, 700p, 713p, 722p, 727p, 731p, 736p, 740p, 742p, 749p], [723p, 725p, 729p, 746p, 753p, 758p, 803p, 816p, 825p, 830p, 834p, 839p, 843p, 845p, 852p], [825p, 827p, 831p, 848p, 855p, 900p, 905p, 918p, 927p, 932p, 936p, 941p, 945p, 947p, 954p], [925p, 927p, 931p, 948p, 955p, 1000p, 1005p, 1018p, 1027p, 1032p, 1036p, 1041p, 1045p, 1047p, 1054p], [1025p, 1027p, 1031p, 1048p, 1055p, 1100p, 1105p, 1116p, "-", "-", "-", "-", "-", "-", "-"]]
  -
  time_points: [Tuggeranong Bus Station (Platform 8), Woden Bus Station (Platform 9), City Bus Station (Platform 3), Belconnen Community Bus Station (Platform 4), Westfield Bus Station (Platform 2), Cohen Street Bus Station (Platform 6), St Francis Xavier Florey, Charnwood Tillyard Dr, Fraser, Fraser West Terminus]
  long_name: To Fraser West Terminus
  between_stops:
  Belconnen Community Bus Station (Platform 4)-Westfield Bus Station (Platform 2): []
  Tuggeranong Bus Station (Platform 8)-Woden Bus Station (Platform 9): [Wjz213q, Wjz238T, Wjz239F, Wjz2lDC, Wjz2mGO, Wjz2mTK, Wjz2nLE, Wjz3gMq, Wjz3gQn, Wjz3gK-, Wjz3hL_, Wjz3iFK, Wjz3kwU, Wjz3kyX, Wjz3kAx]
  Westfield Bus Station (Platform 2)-Cohen Street Bus Station (Platform 6): []
  City Bus Station (Platform 3)-Belconnen Community Bus Station (Platform 4): [Wjz5F-1, Wjz5FSY, Wjz5GMT, Wjz5GNG, Wjz5G6U, Wjz5G6B, Wjz5maK, Wjz5mbS, Wjz5nwb, Wjz5nw6, Wjz6giR, Wjz6gia, Wjz68W3, Wjz68W5, Wjz689c, Wjz681S]
  Woden Bus Station (Platform 9)-City Bus Station (Platform 3): [Wjz3m3b, Wjz3m3b, Wjz3eRR, Wjz3eZ4, Wjz4KNu, Wjz4KO9, Wjz5Nht]
  short_name: 14 314
  stop_times: [["-", "-", "-", 706a, 708a, 712a, 717a, 722a, 726a, 735a], ["-", "-", "-", 722a, 724a, 728a, 734a, 739a, 744a, 753a], [706a, 724a, 741a, 802a, 804a, 808a, 814a, 819a, 824a, 833a], [746a, 805a, 823a, 844a, 846a, 850a, 856a, 901a, 906a, 915a], [805a, 824a, 842a, 903a, 905a, 909a, 915a, 920a, 925a, 934a], [843a, 902a, 920a, 940a, 942a, 946a, 951a, 956a, 1000a, 1009a], [916a, 935a, 951a, 1011a, 1013a, 1017a, 1022a, 1027a, 1031a, 1040a], [946a, 1004a, 1020a, 1040a, 1042a, 1046a, 1051a, 1056a, 1100a, 1109a], [1016a, 1034a, 1050a, 1110a, 1112a, 1116a, 1121a, 1126a, 1130a, 1139a], [1046a, 1104a, 1120a, 1140a, 1142a, 1146a, 1151a, 1156a, 1200p, 1209p], [1116a, 1134a, 1150a, 1210p, 1212p, 1216p, 1221p, 1226p, 1230p, 1239p], [1146a, 1204p, 1220p, 1240p, 1242p, 1246p, 1251p, 1256p, 100p, 109p], [1216p, 1234p, 1250p, 110p, 112p, 116p, 121p, 126p, 130p, 139p], [1246p, 104p, 120p, 140p, 142p, 146p, 151p, 156p, 200p, 209p], [116p, 134p, 150p, 210p, 212p, 216p, 221p, 226p, 230p, 239p], [146p, 204p, 220p, 240p, 242p, 246p, 251p, 256p, 300p, 310p], [216p, 234p, 250p, 311p, 313p, 317p, 323p, 328p, 333p, 343p], [245p, 303p, 321p, 342p, 344p, 348p, 354p, 359p, 404p, 414p], ["-", "-", 340p, 345p, 347p, 351p, 357p, 402p, 407p, 417p], [321p, 340p, 358p, 419p, 421p, 425p, 431p, 436p, 441p, 451p], [351p, 410p, 428p, 449p, 451p, 455p, 501p, 506p, 511p, 521p], [421p, 440p, 458p, 519p, 521p, 525p, 531p, 536p, 541p, 551p], [451p, 510p, 528p, 549p, 551p, 555p, 601p, 606p, 611p, 621p], [511p, 530p, 548p, 609p, 611p, 615p, 621p, 626p, 631p, 640p], [531p, 550p, 608p, 629p, 631p, 635p, 640p, 645p, 649p, 658p], [551p, 610p, 628p, 648p, 650p, 654p, 659p, 704p, 708p, 717p], [621p, 639p, 654p, 714p, 716p, 720p, 725p, 730p, 734p, 743p], ["-", "-", "-", 804p, 806p, 810p, 815p, 820p, 824p, 833p], ["-", "-", "-", 904p, 906p, 910p, 915p, 920p, 924p, 933p], ["-", "-", "-", 1004p, 1006p, 1010p, 1015p, 1020p, 1024p, 1033p], ["-", "-", "-", 1104p, 1106p, 1110p, 1115p, 1120p, 1124p, 1133p], []]
  -
  time_points: [Kippax, Latham Post Office, Florey, Cohen Street Bus Station, Westfield Bus Station, Belconnen Community Bus Station]
  long_name: To Belconnen Community Bus Station
  between_stops:
  Westfield Bus Station-Belconnen Community Bus Station: []
  Cohen Street Bus Station-Westfield Bus Station: []
  short_name: "16"
  stop_times: [[610a, 619a, 625a, 630a, 632a, 636a], [640a, 649a, 655a, 700a, 702a, 706a], [711a, 720a, 726a, 731a, 733a, 737a], [730a, 741a, 747a, 753a, 755a, 759a], [750a, 801a, 807a, 813a, 815a, 819a], [810a, 821a, 827a, 833a, 835a, 839a], [830a, 841a, 847a, 853a, 855a, 859a], [851a, 902a, 908a, 914a, 916a, 920a], [916a, 927a, 933a, 938a, 940a, 944a], [946a, 955a, 1001a, 1006a, 1008a, 1012a], [1011a, 1020a, 1026a, 1031a, 1033a, 1037a], [1046a, 1055a, 1101a, 1106a, 1108a, 1112a], [1111a, 1120a, 1126a, 1131a, 1133a, 1137a], [1146a, 1155a, 1201p, 1206p, 1208p, 1212p], [1211p, 1220p, 1226p, 1231p, 1233p, 1237p], [1246p, 1255p, 101p, 106p, 108p, 112p], [111p, 120p, 126p, 131p, 133p, 137p], [146p, 155p, 201p, 206p, 208p, 212p], [211p, 220p, 226p, 231p, 233p, 237p], [246p, 255p, 301p, 307p, 309p, 313p], [311p, 322p, 328p, 334p, 336p, 340p], [341p, 352p, 358p, 404p, 406p, 410p], [407p, 418p, 424p, 430p, 432p, 436p], [431p, 442p, 448p, 454p, 456p, 500p], [456p, 507p, 513p, 519p, 521p, 525p], [526p, 537p, 543p, 549p, 551p, 555p], [555p, 606p, 612p, 618p, 620p, 624p], [655p, 704p, 710p, 714p, 716p, 720p], [755p, 804p, 810p, 814p, 816p, 820p], [855p, 904p, 910p, 914p, 916p, 920p], [955p, 1004p, 1010p, 1014p, 1016p, 1020p], [1055p, 1104p, 1110p, 1114p, 1116p, 1120p]]
  -
  time_points: [Cohen Street Bus Station (Platform 2), Westfield Bus Station (Platform 1), Belconnen Community Bus Station (Platform 3), Jamison Centre, Cook, Aranda, Caswell Drive, City Bus Station]
  long_name: To City Bus Station
  between_stops:
  Westfield Bus Station (Platform 1)-Belconnen Community Bus Station (Platform 3): []
  Cohen Street Bus Station (Platform 2)-Westfield Bus Station (Platform 1): []
  short_name: "942"
  stop_times_sunday: [[815a, 817a, 821a, 830a, 839a, 843a, 844a, 855a], [915a, 917a, 921a, 930a, 939a, 943a, 944a, 955a], [1015a, 1017a, 1021a, 1030a, 1039a, 1043a, 1044a, 1055a], [1115a, 1117a, 1121a, 1130a, 1139a, 1143a, 1144a, 1155a], [1215p, 1217p, 1221p, 1230p, 1239p, 1243p, 1244p, 1255p], [115p, 117p, 121p, 130p, 139p, 143p, 144p, 155p], [215p, 217p, 221p, 230p, 239p, 243p, 244p, 255p], [315p, 317p, 321p, 330p, 339p, 343p, 344p, 355p], [415p, 417p, 421p, 430p, 439p, 443p, 444p, 455p], [515p, 517p, 521p, 530p, 539p, 543p, 544p, 555p], [615p, 617p, 621p, 630p, 639p, 643p, 644p, 655p]]
  -
  time_points: [City Bus Station (Platform 8), Dickson / Antill St, Watson, Watson Terminus, Watson, Dickson / Antill St, City Bus Station]
  long_name: To City Bus Station
  between_stops: {}
   
  stop_times_saturday: [["-", "-", "-", 708a, 713a, 719a, 734a], ["-", "-", "-", 808a, 813a, 819a, 834a], [846a, 903a, 908a, 915a, 920a, 926a, 941a], [946a, 1003a, 1008a, 1015a, 1020a, 1026a, 1041a], [1046a, 1103a, 1108a, 1115a, 1120a, 1126a, 1141a], [1146a, 1203p, 1208p, 1215p, 1220p, 1226p, 1241p], [1246p, 103p, 108p, 115p, 120p, 126p, 141p], [146p, 203p, 208p, 215p, 220p, 226p, 241p], [246p, 303p, 308p, 315p, 320p, 326p, 341p], [346p, 403p, 408p, 415p, 420p, 426p, 441p], [446p, 503p, 508p, 515p, 520p, 526p, 541p], [546p, 603p, 608p, 615p, 620p, 626p, 641p], [646p, 703p, 708p, 715p, 720p, 726p, 741p], [746p, 803p, 808p, 815p, 820p, 826p, 841p], [846p, 903p, 908p, 915p, 920p, 926p, 941p], [946p, 1003p, 1008p, 1015p, 1020p, 1026p, 1041p], [1046p, 1103p, 1108p, 1115p, 1120p, 1126p, 1141p]]
  short_name: "939"
  -
  time_points: [Bimberi Centre, Northbourne Avenue / Antill St, Macarthur / Northbourne Ave, City Bus Station]
  long_name: To City Bus Station
  between_stops:
  Northbourne Avenue / Antill St-Macarthur / Northbourne Ave: [Wjz5SDc, Wjz5Sux, Wjz5SrO, Wjz5Sqk, Wjz5RvC, Wjz5Rsi, Wjz5RkN]
  Macarthur / Northbourne Ave-City Bus Station: [Wjz5QmR, Wjz5Qmu, Wjz5Qgn, Wjz5Qi2, Wjz5Pl0, Wjz5PdJ, Wjz5P8K, Wjz5P8n, Wjz5Oci, Wjz5O3Q]
  short_name: "82"
  stop_times: [[715p, 724p, 726p, 733p]]
  -
  time_points: [Tuggeranong Bus Station (Platform 7), Erindale Centre, Chisholm, Heagney / Clift Richardson, Tuggeranong Bus Station]
  long_name: To Tuggeranong Bus Station
  between_stops: {}
   
  short_name: "967"
  stop_times_sunday: [[903a, 914a, 928a, 937a, 950a], [1103a, 1114a, 1128a, 1137a, 1150a], [103p, 114p, 128p, 137p, 150p], [303p, 314p, 328p, 337p, 350p], [503p, 514p, 528p, 537p, 550p], [703p, 714p, 728p, 737p, 750p]]
  -
  time_points: [Belconnen Community Bus Station (Platform 6), Westfield Bus Station (Platform 2), Cohen Street Bus Station (Platform 5), Higgins, Kippax, Higgins, Cohen Street Bus Station, Westfield Bus Station, Belconnen Community Bus Station]
  long_name: To Belconnen Community Bus Station
  between_stops:
  Belconnen Community Bus Station (Platform 6)-Westfield Bus Station (Platform 2): []
  Westfield Bus Station-Belconnen Community Bus Station: []
  Cohen Street Bus Station-Westfield Bus Station: []
  Westfield Bus Station (Platform 2)-Cohen Street Bus Station (Platform 5): []
  short_name: "904"
  stop_times_sunday: [[819a, 821a, 825a, 846a, 857a, 907a, 928a, 930a, 934a], [919a, 921a, 925a, 946a, 957a, 1007a, 1028a, 1030a, 1034a], [1019a, 1021a, 1025a, 1046a, 1057a, 1107a, 1128a, 1130a, 1134a], [1119a, 1121a, 1125a, 1146a, 1157a, 1207p, 1228p, 1230p, 1234p], [1219p, 1221p, 1225p, 1246p, 1257p, 107p, 128p, 130p, 134p], [119p, 121p, 125p, 146p, 157p, 207p, 228p, 230p, 234p], [219p, 221p, 225p, 246p, 257p, 307p, 328p, 330p, 334p], [319p, 321p, 325p, 346p, 357p, 407p, 428p, 430p, 434p], [419p, 421p, 425p, 446p, 457p, 507p, 528p, 530p, 534p], [519p, 521p, 525p, 546p, 557p, 607p, 628p, 630p, 634p], [619p, 621p, 625p, 645p, 656p, 706p, 726p, 728p, 732p]]
  -
  time_points: [Woden Bus Station (Platform 3), Waramanga, Fisher, Chapman, Rivett, Cooleman Court]
  long_name: To Cooleman Court
  between_stops: {}
   
  stop_times_saturday: [[920a, 929a, 932a, 942a, 945a, 950a], [1020a, 1029a, 1032a, 1042a, 1045a, 1050a], [1120a, 1129a, 1132a, 1142a, 1145a, 1150a], [1220p, 1229p, 1232p, 1242p, 1245p, 1250p], [120p, 129p, 132p, 142p, 145p, 150p], [220p, 229p, 232p, 242p, 245p, 250p], [320p, 329p, 332p, 342p, 345p, 350p], [420p, 429p, 432p, 442p, 445p, 450p], [520p, 529p, 532p, 542p, 545p, 550p], [620p, 629p, 632p, 642p, 645p, 650p], [720p, 729p, 732p, 742p, 745p, 750p], [820p, 829p, 832p, 842p, 845p, 850p], [920p, 929p, 932p, 942p, 945p, 950p], [1020p, 1029p, 1032p, 1042p, 1045p, 1050p], [1120p, 1129p, 1132p, 1142p, 1145p, 1150p]]
  short_name: "927"
  -
  time_points: [Tuggeranong Bus Station (Platform 4), Isabella, Calwell, Theodore, Outtrim / Duggan, Tuggeranong Bus Station]
  long_name: To Tuggeranong Bus Station
  between_stops: {}
   
  short_name: "912"
  stop_times_sunday: [[1015a, 1025a, 1030a, 1039a, 1046a, 1055a], [1215p, 1225p, 1230p, 1239p, 1246p, 1255p], [215p, 225p, 230p, 239p, 246p, 255p], [415p, 425p, 430p, 439p, 446p, 455p], [615p, 625p, 630p, 639p, 646p, 655p]]
  -
  time_points: [City Bus Station (Platform 7), Kings Ave / National Circuit, Manuka, Red Hill, Narrabundah Terminus, Red Hill, Manuka, Kings Ave / National Circuit, City Bus Station]
  long_name: To City Bus Station
  between_stops:
  City Bus Station (Platform 7)-Kings Ave / National Circuit: [Wjz5FOn, Wjz4S1U, Wjz4Rs-, Wjz4RFJ, Wjz4RwH, Wjz4Quk]
  Kings Ave / National Circuit-City Bus Station: [Wjz4Quk, Wjz4RwH, Wjz4RFJ, Wjz4Rs-, Wjz4S1U, Wjz5FOn]
  short_name: "935"
  stop_times_sunday: [["-", "-", "-", "-", 824a, 833a, 839a, 843a, 852a], [856a, 903a, 907a, 914a, 924a, 933a, 939a, 943a, 952a], [956a, 1003a, 1007a, 1014a, 1024a, 1033a, 1039a, 1043a, 1052a], [1056a, 1103a, 1107a, 1114a, 1124a, 1133a, 1139a, 1143a, 1152a], [1156a, 1203p, 1207p, 1214p, 1224p, 1233p, 1239p, 1243p, 1252p], [1256p, 103p, 107p, 114p, 124p, 133p, 139p, 143p, 152p], [156p, 203p, 207p, 214p, 224p, 233p, 239p, 243p, 252p], [256p, 303p, 307p, 314p, 324p, 333p, 339p, 343p, 352p], [356p, 403p, 407p, 414p, 424p, 433p, 439p, 443p, 452p], [456p, 503p, 507p, 514p, 524p, 533p, 539p, 543p, 552p], [556p, 603p, 607p, 614p, 624p, 633p, 639p, 643p, 652p], [656p, 703p, 707p, 714p, 724p, 733p, 739p, 743p, 752p]]
  -
  time_points: [Tuggeranong Bus Station (Platform 8), Woden Bus Station (Platform 9), City Bus Station (Platform 3), Belconnen Community Bus Station (Platform 4), Westfield Bus Station (Platform 2), Cohen Street Bus Station (Platform 6), Copland College, Melba, Spence, Spence Terminus]
  long_name: To Spence Terminus
  between_stops:
  Belconnen Community Bus Station (Platform 4)-Westfield Bus Station (Platform 2): []
  Tuggeranong Bus Station (Platform 8)-Woden Bus Station (Platform 9): [Wjz213q, Wjz238T, Wjz239F, Wjz2lDC, Wjz2mGO, Wjz2mTK, Wjz2nLE, Wjz3gMq, Wjz3gQn, Wjz3gK-, Wjz3hL_, Wjz3iFK, Wjz3kwU, Wjz3kyX, Wjz3kAx]
  Westfield Bus Station (Platform 2)-Cohen Street Bus Station (Platform 6): []
  City Bus Station (Platform 3)-Belconnen Community Bus Station (Platform 4): [Wjz5F-1, Wjz5FSY, Wjz5GMT, Wjz5GNG, Wjz5G6U, Wjz5G6B, Wjz5maK, Wjz5mbS, Wjz5nwb, Wjz5nw6, Wjz6giR, Wjz6gia, Wjz68W3, Wjz68W5, Wjz689c, Wjz681S]
  Woden Bus Station (Platform 9)-City Bus Station (Platform 3): [Wjz3m3b, Wjz3m3b, Wjz3eRR, Wjz3eZ4, Wjz4KNu, Wjz4KO9, Wjz5Nht]
  short_name: 15 315
  stop_times: [["-", "-", "-", 723a, 725a, 729a, 737a, 741a, 749a, 755a], ["-", "-", "-", 803a, 805a, 809a, 817a, 821a, 829a, 835a], [731a, 750a, 808a, 829a, 831a, 835a, 843a, 847a, 855a, 901a], [831a, 850a, 908a, 929a, 931a, 935a, 942a, 945a, 951a, 957a], [911a, 930a, 946a, 1006a, 1008a, 1012a, 1019a, 1022a, 1028a, 1034a], [941a, 959a, 1015a, 1035a, 1037a, 1041a, 1048a, 1051a, 1057a, 1103a], [1011a, 1029a, 1045a, 1105a, 1107a, 1111a, 1118a, 1121a, 1127a, 1133a], [1041a, 1059a, 1115a, 1135a, 1137a, 1141a, 1148a, 1151a, 1157a, 1203p], [1111a, 1129a, 1145a, 1205p, 1207p, 1211p, 1218p, 1221p, 1227p, 1233p], [1141a, 1159a, 1215p, 1235p, 1237p, 1241p, 1248p, 1251p, 1257p, 103p], [1211p, 1229p, 1245p, 105p, 107p, 111p, 118p, 121p, 127p, 133p], [1241p, 1259p, 115p, 135p, 137p, 141p, 148p, 151p, 157p, 203p], [111p, 129p, 145p, 205p, 207p, 211p, 218p, 221p, 227p, 233p], [141p, 159p, 215p, 235p, 237p, 241p, 248p, 251p, 257p, 303p], [211p, 229p, 245p, 305p, 307p, 311p, 319p, 323p, 331p, 337p], [240p, 258p, 316p, 337p, 339p, 343p, 351p, 355p, 403p, 409p], ["-", "-", "-", 357p, 359p, 403p, 411p, 415p, 423p, 429p], [311p, 330p, 348p, 409p, 411p, 415p, 423p, 427p, 435p, 441p], [341p, 400p, 418p, 439p, 441p, 445p, 453p, 457p, 505p, 511p], [411p, 430p, 448p, 509p, 511p, 515p, 523p, 527p, 535p, 541p], [441p, 500p, 518p, 539p, 541p, 545p, 553p, 557p, 605p, 611p], [501p, 520p, 538p, 559p, 601p, 605p, 613p, 617p, 625p, 631p], [521p, 540p, 558p, 619p, 621p, 625p, 633p, 636p, 642p, 648p], [601p, 620p, 636p, 656p, 658p, 702p, 709p, 712p, 718p, 724p], ["-", "-", "-", 728p, 730p, 734p, 741p, 744p, 750p, 756p], ["-", "-", "-", 804p, 806p, 810p, 817p, 820p, 826p, 832p], ["-", "-", "-", 904p, 906p, 910p, 917p, 920p, 926p, 932p], ["-", "-", "-", 1004p, 1006p, 1010p, 1017p, 1020p, 1026p, 1032p], ["-", "-", "-", 1104p, 1106p, 1110p, 1117p, 1120p, 1126p, 1132p], []]
  -
  time_points: [Woden Bus Station (Platform 4), Alexander Maconochie Centre]
  long_name: To Alexander Machonochie Centre Hume
  between_stops:
  Woden Bus Station (Platform 4)-Alexander Maconochie Centre: [Wjz3dXS, Wjz3kAx]
  short_name: "988"
  stop_times_sunday: [[920a, 940a], [1255p, 115p], [455p, 515p]]
  -
  time_points: [City Bus Station (Platform 8), Macarthur / Northbourne Ave, Northbourne Avenue / Antill St, Flemington Rd / Sandford St, Hibberson / Kate Crace, Gungahlin Marketplace, Ngunnawal Primary, Nicholls Primary, Federation Square, Chuculba / William Slim Dr, Belconnen Community Bus Station, Westfield Bus Station, Cohen Street Bus Station]
  long_name: To Cohen Street Bus Station
  between_stops:
  Westfield Bus Station-Cohen Street Bus Station: []
  Macarthur / Northbourne Ave-Northbourne Avenue / Antill St: [Wjz5RkN, Wjz5Rsi, Wjz5RvC, Wjz5Sqk, Wjz5SrO, Wjz5Sux, Wjz5SDc]
  City Bus Station (Platform 8)-Macarthur / Northbourne Ave: [Wjz5O3Q, Wjz5Oci, Wjz5P8n, Wjz5P8K, Wjz5PdJ, Wjz5Pl0, Wjz5Qi2, Wjz5Qgn, Wjz5Qmu, Wjz5QmR]
  Belconnen Community Bus Station-Westfield Bus Station: []
  Chuculba / William Slim Dr-Belconnen Community Bus Station: [Wjz6mip, Wjz69vO, Wjz69uI, Wjz69ht, Wjz69gA]
  short_name: "52"
  stop_times: [["-", "-", "-", "-", 715a, 718a, 724a, 732a, 740a, 745a, 758a, 800a, 805a], ["-", "-", "-", "-", 735a, 738a, 744a, 753a, 801a, 806a, 819a, 821a, 826a], ["-", "-", "-", "-", 755a, 758a, 804a, 813a, 821a, 826a, 839a, 841a, 846a], ["-", "-", "-", "-", 815a, 818a, 824a, 833a, 841a, 846a, 859a, 901a, 906a], ["-", "-", "-", "-", 835a, 838a, 844a, 853a, 901a, 906a, 918a, 920a, 925a], ["-", "-", "-", "-", 855a, 858a, 904a, 912a, 920a, 925a, 937a, 939a, 944a], ["-", "-", "-", "-", 915a, 918a, 924a, 932a, 940a, 945a, 957a, 959a, 1004a], ["-", "-", "-", "-", 942a, 945a, 951a, 959a, 1007a, 1012a, 1024a, 1026a, 1031a], ["-", "-", "-", "-", 1005a, 1008a, 1014a, 1022a, 1030a, 1035a, 1047a, 1049a, 1054a], ["-", "-", "-", "-", 1105a, 1108a, 1114a, 1122a, 1130a, 1135a, 1147a, 1149a, 1154a], ["-", "-", "-", "-", 1205p, 1208p, 1214p, 1222p, 1230p, 1235p, 1247p, 1249p, 1254p], ["-", "-", "-", "-", 105p, 108p, 114p, 122p, 130p, 135p, 147p, 149p, 154p], ["-", "-", "-", "-", 205p, 208p, 214p, 222p, 230p, 235p, 247p, 249p, 254p], ["-", "-", "-", "-", 301p, 304p, 310p, 318p, 326p, 331p, 343p, 345p, 350p], ["-", "-", "-", "-", 340p, 343p, 349p, 357p, 405p, 410p, 423p, 425p, 430p], [345p, 351p, 353p, 401p, 406p, 409p, 415p, 424p, 432p, 437p, 450p, 452p, 457p], [400p, 407p, 409p, 418p, 423p, 426p, 432p, 441p, 449p, 454p, 507p, 509p, 514p], [418p, 425p, 427p, 436p, 441p, 444p, 450p, 459p, 507p, 512p, 525p, 527p, 532p], [440p, 447p, 449p, 458p, 503p, 506p, 512p, 521p, 529p, 534p, 547p, 549p, 554p], [459p, 506p, 508p, 517p, 522p, 525p, 531p, 540p, 548p, 553p, 606p, 608p, 613p], [515p, 522p, 524p, 533p, 538p, 541p, 547p, 556p, 604p, 609p, 621p, 623p, 628p], [540p, 547p, 549p, 558p, 602p, 605p, 611p, 619p, 627p, 632p, 644p, 646p, 651p], [600p, 606p, 608p, 615p, 618p, 621p, 627p, 635p, 643p, 648p, 700p, 702p, 707p], ["-", "-", "-", "-", 705p, 708p, 714p, 722p, 730p, 735p, 747p, 749p, 754p], ["-", "-", "-", "-", 805p, 808p, 814p, 822p, 830p, 835p, 847p, 849p, 854p], ["-", "-", "-", "-", 905p, 908p, 914p, 922p, 930p, 935p, 947p, 949p, 954p], ["-", "-", "-", "-", 1005p, 1008p, 1014p, 1022p, 1030p, 1035p, 1047p, 1049p, 1054p], ["-", "-", "-", "-", 1105p, 1108p, 1114p, 1122p, 1130p, "-", "-", "-", "-"]]
  -
  time_points: [Fairbairn Park, Brindabella Business Park, Russell Offices, Kings Ave / National Circuit, Woden Bus Station (Platform 16), Lyons, CIT Weston, Duffy Primary, Cooleman Court]
  long_name: To Cooleman Court
  between_stops:
  Russell Offices-Kings Ave / National Circuit: [Wjz4RFJ, Wjz4RwH, Wjz4Quk]
  Fairbairn Park-Brindabella Business Park: [WjzcJ38, WjzcBHZ, WjzcJ0K, WjzcrEu, WjzcrrQ, WjzcrK3]
  short_name: "28"
  stop_times: [["-", "-", "-", "-", 742a, 746a, 751a, 759a, 811a], ["-", "-", "-", "-", 845a, 849a, 854a, 902a, 914a], ["-", "-", "-", "-", 952a, 956a, 1000a, 1007a, 1019a], ["-", "-", "-", "-", 1052a, 1056a, 1100a, 1107a, 1119a], ["-", "-", "-", "-", 1152a, 1156a, 1200p, 1207p, 1219p], ["-", "-", "-", "-", 1252p, 1256p, 100p, 107p, 119p], ["-", "-", "-", "-", 152p, 156p, 200p, 207p, 219p], ["-", "-", "-", "-", 252p, 256p, 300p, 308p, 320p], ["-", "-", "-", "-", 312p, 316p, 321p, 329p, 341p], ["-", "-", "-", "-", 342p, 346p, 351p, 359p, 411p], ["-", "-", "-", "-", 412p, 416p, 421p, 429p, 441p], ["-", "-", "-", "-", 442p, 446p, 451p, 459p, 511p], [429p, 439p, 453p, 456p, 511p, 515p, 520p, 528p, 540p], [449p, 459p, 513p, 516p, 531p, 535p, 540p, 548p, 600p], [519p, 529p, 543p, 546p, 601p, 605p, 610p, 618p, 630p], [549p, 559p, 613p, 616p, 631p, 634p, 638p, 645p, 654p], ["-", "-", "-", "-", 732p, 735p, 739p, 746p, 755p], ["-", "-", "-", "-", 832p, 835p, 839p, 846p, 855p], ["-", "-", "-", "-", 932p, 935p, 939p, 946p, 955p], ["-", "-", "-", "-", 1032p, 1035p, 1039p, 1046p, 1055p]]
  -
  time_points: [Tuggeranong Bus Station (Platform 7), Erindale Centre, Chisholm, Heagney / Clift Richardson, Tuggeranong Bus Station]
  long_name: To Tuggeranong Bus Station
  between_stops: {}
   
  stop_times_saturday: [[903a, 914a, 928a, 937a, 950a], [1103a, 1114a, 1128a, 1137a, 1150a], [103p, 114p, 128p, 137p, 150p], [303p, 314p, 328p, 337p, 350p], [503p, 514p, 528p, 537p, 550p], [703p, 714p, 728p, 737p, 750p], [903p, 914p, 928p, 937p, 950p], [1103p, 1114p, 1128p, 1137p, 1150p]]
  short_name: "967"
  -
  time_points: [City Bus Station (Platform 8), Macarthur / Northbourne Ave, Northbourne Avenue / Antill St, Flemington Rd / Sandford St, Flemington Rd / Nullabor Ave, Anthony Rolfe Av / Moonlight Av, Gungahlin Marketplace, Chuculba / William Slim Dr, Belconnen Community Bus Station, Westfield Bus Station, Cohen Street Bus Station]
  long_name: To Cohen Street Bus Station
  between_stops:
  Westfield Bus Station-Cohen Street Bus Station: []
  Macarthur / Northbourne Ave-Northbourne Avenue / Antill St: [Wjz5RkN, Wjz5Rsi, Wjz5RvC, Wjz5Sqk, Wjz5SrO, Wjz5Sux, Wjz5SDc]
  City Bus Station (Platform 8)-Macarthur / Northbourne Ave: [Wjz5O3Q, Wjz5Oci, Wjz5P8n, Wjz5P8K, Wjz5PdJ, Wjz5Pl0, Wjz5Qi2, Wjz5Qgn, Wjz5Qmu, Wjz5QmR]
  Belconnen Community Bus Station-Westfield Bus Station: []
  Chuculba / William Slim Dr-Belconnen Community Bus Station: [Wjz6mip, Wjz69vO, Wjz69uI, Wjz69ht, Wjz69gA]
  short_name: "58"
  stop_times: [["-", "-", "-", "-", 551a, 558a, 606a, "-", "-", "-", "-"], ["-", "-", "-", "-", 624a, 631a, 639a, "-", "-", "-", "-"], [631a, 637a, 639a, 645a, 651a, 658a, 706a, 717a, 733a, 735a, 740a], [711a, 717a, 719a, 725a, 731a, 738a, 746a, 757a, 814a, 816a, 821a], [727a, 733a, 735a, 741a, 748a, 757a, 806a, 817a, 834a, 836a, 841a], [745a, 752a, 754a, 800a, 808a, 817a, 826a, 837a, 854a, 856a, 901a], [805a, 812a, 814a, 820a, 828a, 837a, 846a, 857a, 913a, 915a, 920a], [917a, 923a, 925a, 931a, 938a, 945a, 953a, 1003a, 1019a, 1021a, 1026a], [1017a, 1023a, 1025a, 1031a, 1038a, 1045a, 1053a, 1103a, 1119a, 1121a, 1126a], [1117a, 1123a, 1125a, 1131a, 1138a, 1145a, 1153a, 1203p, 1219p, 1221p, 1226p], [1217p, 1223p, 1225p, 1231p, 1238p, 1245p, 1253p, 103p, 119p, 121p, 126p], [117p, 123p, 125p, 131p, 138p, 145p, 153p, 203p, 219p, 221p, 226p], [217p, 223p, 225p, 231p, 238p, 245p, 253p, 303p, 320p, 322p, 327p], [328p, 335p, 337p, 344p, 352p, 401p, 410p, 421p, 438p, 440p, 445p], [419p, 426p, 428p, 435p, 443p, 452p, 501p, 512p, 529p, 531p, 536p], [439p, 446p, 448p, 455p, 503p, 512p, 521p, 532p, 549p, 551p, 556p], [500p, 507p, 509p, 516p, 524p, 533p, 542p, 553p, 609p, 611p, 616p], [520p, 527p, 529p, 536p, 544p, 553p, 602p, 612p, 628p, 630p, 635p], [540p, 547p, 549p, 556p, 603p, 610p, 618p, 628p, 644p, 646p, 651p], [600p, 606p, 608p, 613p, 619p, 626p, 634p, 644p, 700p, 702p, 707p], [631p, 637p, 639p, 644p, 650p, 657p, 705p, 715p, 731p, 733p, 738p], [717p, 723p, 725p, 730p, 736p, 743p, 751p, 801p, 817p, 819p, 824p], [817p, 823p, 825p, 830p, 836p, 843p, 851p, 901p, 917p, 919p, 924p], [917p, 923p, 925p, 930p, 936p, 943p, 951p, 1001p, 1017p, 1019p, 1024p], [1017p, 1023p, 1025p, 1030p, 1036p, 1043p, 1051p, 1101p, 1117p, 1119p, 1124p], [1117p, 1123p, 1125p, 1130p, 1136p, 1143p, 1151p, 1201a, 1217a, 1219a, 1224a], []]
  -
  time_points: [Campbell Park Offices, ADFA, Russell Offices, Kings Ave / National Circuit, Woden Bus Station (Platform 16), Weston Primary, Holder, Cooleman Court]
  long_name: To Cooleman Court
  between_stops:
  Russell Offices-Kings Ave / National Circuit: [Wjz4RFJ, Wjz4RwH, Wjz4Quk]
  ADFA-Russell Offices: [Wjzcend, Wjzce4H, Wjzce7O, Wjzc54R, Wjzc55s, Wjzc60i, Wjzc60A]
  Campbell Park Offices-ADFA: [Wjzce7O, Wjzce4H, Wjzcend]
  short_name: 25 225
  stop_times: [["-", "-", "-", "-", 712a, 720a, 723a, 734a], ["-", "-", "-", "-", 807a, 819a, 823a, 835a], ["-", "-", "-", "-", 842a, 854a, 858a, 910a], ["-", "-", "-", "-", 940a, 949a, 952a, 1002a], ["-", "-", "-", "-", 1040a, 1049a, 1052a, 1102a], ["-", "-", "-", "-", 1140a, 1149a, 1152a, 1202p], ["-", "-", "-", "-", 1240p, 1249p, 1252p, 102p], ["-", "-", "-", "-", 140p, 149p, 152p, 202p], ["-", "-", "-", "-", 240p, 249p, 252p, 306p], ["-", "-", "-", "-", 342p, 352p, 356p, 408p], ["-", "-", "-", "-", 412p, 422p, 426p, 438p], [417p, 421p, 425p, 428p, 443p, 453p, 457p, 509p], [447p, 451p, 455p, 458p, 513p, 523p, 527p, 539p], [517p, 521p, 525p, 528p, 543p, 553p, 557p, 609p], ["-", "-", "-", "-", 612p, 622p, 626p, 637p], ["-", "-", "-", "-", 656p, 704p, 707p, 717p], ["-", "-", "-", "-", 756p, 804p, 807p, 817p], ["-", "-", "-", "-", 856p, 904p, 907p, 917p], ["-", "-", "-", "-", 956p, 1004p, 1007p, 1017p], ["-", "-", "-", "-", 1056p, 1104p, 1107p, 1117p]]
  -
  time_points: [Belconnen Community Bus Station (Platform 5), Westfield Bus Station (Platform 2), Cohen Street Bus Station (Platform 5), Belconnen Way, Higgins, West Macgregor, Holt, Kippax]
  long_name: To Kippax
  between_stops:
  Belconnen Community Bus Station (Platform 5)-Westfield Bus Station (Platform 2): []
  Westfield Bus Station (Platform 2)-Cohen Street Bus Station (Platform 5): []
  short_name: "44"
  stop_times: [[725a, 727a, 731a, 737a, 744a, 756a, 801a, 804a], [754a, 756a, 800a, 806a, 813a, 825a, 830a, 833a], [854a, 856a, 900a, 906a, 913a, 925a, 930a, 933a], [955a, 957a, 1001a, 1006a, 1012a, 1024a, 1029a, 1032a], [1055a, 1057a, 1101a, 1106a, 1112a, 1124a, 1129a, 1132a], [1155a, 1157a, 1201p, 1206p, 1212p, 1224p, 1229p, 1232p], [1255p, 1257p, 101p, 106p, 112p, 124p, 129p, 132p], [155p, 157p, 201p, 206p, 212p, 224p, 229p, 232p], [305p, 307p, 311p, 317p, 324p, 336p, 341p, 344p], [337p, 339p, 343p, 349p, 356p, 408p, 413p, 416p], [411p, 413p, 417p, 423p, 430p, 442p, 447p, 450p], [442p, 444p, 448p, 454p, 501p, 513p, 518p, 521p], [516p, 518p, 522p, 528p, 535p, 547p, 552p, 555p], [547p, 549p, 553p, 559p, 606p, 618p, 623p, 626p], [619p, 621p, 625p, 631p, 637p, 649p, 654p, 657p], [654p, 656p, 700p, 705p, 711p, 723p, 728p, 731p], [754p, 756p, 800p, 805p, 811p, 823p, 828p, 831p], [854p, 856p, 900p, 905p, 911p, 923p, 928p, 931p], [954p, 956p, 1000p, 1005p, 1011p, 1023p, 1028p, 1031p], [1054p, 1056p, 1100p, 1105p, 1111p, 1123p, 1128p, 1131p]]
  -
  time_points: [Tuggeranong Bus Station (Platform 7), Bonython Primary School, Woodcock / Clare Dennis, Gordon Primary, Tharwa Drive / Knoke Ave, Conder Primary, Lanyon Market Place, Bonython Primary School, Tuggeranong Bus Station]
  long_name: To Tuggeranong Bus Station
  between_stops: {}
   
  stop_times_saturday: [[725a, 733a, 737a, 741a, 744a, 749a, 800a, 805a, 813a], [925a, 933a, 937a, 941a, 944a, 949a, 1000a, 1005a, 1013a], [1125a, 1133a, 1137a, 1141a, 1144a, 1149a, 1200p, 1205p, 1213p], [125p, 133p, 137p, 141p, 144p, 149p, 200p, 205p, 213p], [325p, 333p, 337p, 341p, 344p, 349p, 400p, 405p, 413p], [525p, 533p, 537p, 541p, 544p, 549p, 600p, 605p, 613p], [725p, 733p, 737p, 741p, 744p, 749p, 800p, 805p, 813p], [928p, 936p, 940p, 944p, 947p, 952p, 1003p, 1008p, 1016p], [1128p, 1136p, 1140p, 1144p, 1147p, 1152p, 1203a, "-", "-"]]
  short_name: "913"
  -
  time_points: [Dickson / Antill St, Hackett, Ainslie, Olims Hotel, City Bus Station (Platform 2), Kings Ave / National Circuit, Parliament House, Deakin, Yarralumla, John James Hospital, Curtin, Woden Bus Station]
  long_name: To Woden Bus Station
  between_stops:
  Ainslie-Olims Hotel: [Wjz5YAK, Wjz5Yq4, Wjz5XnQ, Wjz5XrS, Wjz5XwW, Wjz5W3H, Wjz5W8l]
  Olims Hotel-City Bus Station (Platform 2): [Wjz5V64, Wjz5NRJ, Wjz5NHD, Wjz5NAQ]
  Kings Ave / National Circuit-Parliament House: [Wjz4P6x, Wjz4IrL]
  City Bus Station (Platform 2)-Kings Ave / National Circuit: [Wjz5FOn, Wjz4S1U, Wjz4Rs-, Wjz4RFJ, Wjz4RwH, Wjz4Quk]
  Hackett-Ainslie: [WjzdeeQ, Wjzd6XP, Wjzd6Pn, Wjzd6Cq, Wjzd6lW, Wjzd6iW, Wjzd68O, Wjz5ZZQ, Wjz5ZO1, Wjz5YKO]
  short_name: "2"
  stop_times: [[634a, 639a, 647a, 653a, 700a, 709a, 713a, 718a, 722a, 725a, 729a, 741a], [701a, 706a, 714a, 720a, 727a, 737a, 742a, 747a, 751a, 754a, 758a, 810a], [710a, 715a, 723a, 729a, 736a, 746a, 751a, 756a, 800a, 803a, 807a, 819a], [724a, 729a, 743a, 749a, 756a, 806a, 811a, 816a, 820a, 823a, 827a, 839a], [739a, 748a, 803a, 809a, 816a, 826a, 831a, 836a, 840a, 843a, 847a, 859a], [758a, 807a, 822a, 828a, 835a, 845a, 850a, 855a, 859a, 902a, 906a, 918a], [809a, 818a, 833a, 839a, 846a, 856a, 901a, 906a, 910a, 913a, 917a, 929a], [822a, 831a, 846a, 852a, 859a, 909a, 914a, 919a, 923a, 926a, 930a, 942a], [839a, 848a, 903a, 909a, 916a, 926a, 931a, 936a, 940a, 943a, 947a, 959a], [856a, 905a, 920a, 926a, 933a, 943a, 948a, 953a, 957a, 1000a, 1004a, 1016a], [936a, 941a, 949a, 955a, 1002a, 1012a, 1017a, 1022a, 1026a, 1029a, 1033a, 1045a], [1006a, 1011a, 1019a, 1025a, 1032a, 1042a, 1047a, 1052a, 1056a, 1059a, 1103a, 1115a], [1036a, 1041a, 1049a, 1055a, 1102a, 1112a, 1117a, 1122a, 1126a, 1129a, 1133a, 1145a], [1106a, 1111a, 1119a, 1125a, 1132a, 1142a, 1147a, 1152a, 1156a, 1159a, 1203p, 1215p], [1136a, 1141a, 1149a, 1155a, 1202p, 1212p, 1217p, 1222p, 1226p, 1229p, 1233p, 1245p], [1206p, 1211p, 1219p, 1225p, 1232p, 1242p, 1247p, 1252p, 1256p, 1259p, 103p, 115p], [1236p, 1241p, 1249p, 1255p, 102p, 112p, 117p, 122p, 126p, 129p, 133p, 145p], [106p, 111p, 119p, 125p, 132p, 142p, 147p, 152p, 156p, 159p, 203p, 215p], [136p, 141p, 149p, 155p, 202p, 212p, 217p, 222p, 226p, 229p, 233p, 245p], [206p, 211p, 219p, 225p, 232p, 242p, 247p, 252p, 256p, 259p, 303p, 315p], [236p, 241p, 249p, 255p, 302p, 313p, 318p, 323p, 327p, 330p, 334p, 346p], [249p, 254p, 302p, 308p, 315p, 326p, 331p, 336p, 340p, 343p, 347p, 359p], [306p, 311p, 319p, 325p, 334p, 345p, 350p, 355p, 359p, 402p, 406p, 418p], [312p, 317p, 325p, 331p, 339p, "-", "-", "-", "-", "-", "-", "-"], [319p, 326p, 334p, 340p, 347p, 358p, 403p, 408p, 412p, 415p, 419p, 431p], [332p, 339p, 347p, 353p, 400p, 411p, 416p, 421p, 425p, 428p, 432p, 444p], [349p, 356p, 404p, 410p, 417p, 428p, 433p, 438p, 442p, 445p, 449p, 501p], [402p, 409p, 417p, 423p, 430p, 441p, 446p, 451p, 455p, 458p, 502p, 514p], [419p, 426p, 434p, 440p, 447p, 458p, 503p, 508p, 512p, 515p, 519p, 531p], [432p, 439p, 447p, 453p, 500p, 511p, 516p, 521p, 525p, 528p, 532p, 544p], [449p, 456p, 504p, 510p, 517p, 528p, 533p, 538p, 542p, 545p, 549p, 601p], [502p, 509p, 517p, 523p, 530p, 541p, 546p, 551p, 555p, 558p, 602p, 614p], [519p, 526p, 534p, 540p, 547p, 558p, 603p, 608p, 612p, 615p, 619p, 631p], [532p, 539p, 547p, 553p, 600p, 611p, 616p, 621p, 625p, 628p, 632p, 643p], [549p, 556p, 604p, 610p, 617p, 628p, 633p, 637p, 641p, 644p, 648p, 659p], [603p, 610p, 618p, 624p, 631p, 640p, 645p, 649p, 653p, 656p, 700p, 711p], [626p, 632p, 638p, 643p, 649p, 658p, 703p, 707p, 711p, 714p, 718p, 729p], [726p, 731p, 737p, 742p, 748p, 757p, 802p, 806p, 810p, 813p, 817p, 828p], [826p, 831p, 837p, 842p, 848p, 857p, 902p, 906p, 910p, 913p, 917p, 928p], [926p, 931p, 937p, 942p, 948p, 957p, 1002p, 1006p, 1010p, 1013p, 1017p, 1028p], [1026p, 1031p, 1037p, 1042p, 1048p, 1057p, 1102p, 1106p, 1110p, 1113p, 1117p, 1128p], [1126p, 1131p, 1137p, 1142p, 1147p, "-", "-", "-", "-", "-", "-", "-"], [], [], []]
  -
  time_points: [Lanyon Market Place, Tharwa Drive / Knoke Ave, Woodcock / Clare Dennis, City West, City Bus Station (Platform 10), ACTEW AGL House]
  long_name: To ACTEW AGL House
  between_stops:
  City West-City Bus Station (Platform 10): []
  City Bus Station (Platform 10)-ACTEW AGL House: [Wjz5Nht]
  short_name: "787"
  stop_times: [[647a, 650a, 702a, 728a, 732a, 734a], [720a, 723a, 735a, 801a, 805a, 807a]]
  -
  time_points: [Woden Bus Station (Platform 15), Pearce, Southlands Mawson, Torrens, Chifley, Lyons, Woden Bus Station]
  long_name: To Woden Bus Station
  between_stops: {}
   
  stop_times_saturday: [[833a, 839a, 843a, 849a, 854a, 858a, 901a], [1033a, 1039a, 1043a, 1049a, 1054a, 1058a, 1101a], [1233p, 1239p, 1243p, 1249p, 1254p, 1258p, 101p], [233p, 239p, 243p, 249p, 254p, 258p, 301p], [433p, 439p, 443p, 449p, 454p, 458p, 501p], [633p, 639p, 643p, 649p, 654p, 658p, 701p], [833p, 839p, 843p, 849p, 854p, 858p, 901p], [1033p, 1039p, 1043p, 1049p, 1054p, 1058p, 1101p]]
  short_name: "922"
  -
  time_points: [Sydney Ave, Russell Offices, City Bus Station (Platform 11), Aranda, Macquarie, Hawker, Hawker College, Higgins, Kippax]
  long_name: To Kippax
  between_stops:
  Russell Offices-City Bus Station (Platform 11): [Wjz4-WZ, Wjz4-WL, Wjz4-Rc, Wjz4-KO, Wjz4_wS, Wjz4_jm, Wjz4T-X, Wjz5MEL, Wjz5MsT, Wjz5MsD, Wjz5Nht]
  short_name: "704"
  stop_times: [[506p, 514p, 524p, 533p, 542p, 550p, 555p, 600p, 606p]]
  -
  time_points: [Woden Bus Station (Platform 14), Canberra Hospital, Narrabundah College, Kingston, Kings Ave / National Circuit, Russell Offices, City Bus Station]
  long_name: To City Bus Station
  between_stops:
  Woden Bus Station (Platform 14)-Canberra Hospital: [Wjz3mAg, Wjz3mPO, Wjz3mWn]
  stop_times_saturday: [[800a, 808a, 818a, 833a, 837a, 841a, 849a], [900a, 908a, 918a, 933a, 937a, 941a, 949a], [1000a, 1008a, 1018a, 1033a, 1037a, 1041a, 1049a], [1100a, 1108a, 1118a, 1133a, 1137a, 1141a, 1149a], [1200p, 1208p, 1218p, 1233p, 1237p, 1241p, 1249p], [100p, 108p, 118p, 133p, 137p, 141p, 149p], [200p, 208p, 218p, 233p, 237p, 241p, 249p], [300p, 308p, 318p, 333p, 337p, 341p, 349p], [400p, 408p, 418p, 433p, 437p, 441p, 449p], [500p, 508p, 518p, 533p, 537p, 541p, 549p], [600p, 608p, 618p, 633p, 637p, 641p, 649p], [700p, 707p, 716p, 729p, 733p, 737p, 744p], [800p, 807p, 816p, 829p, 833p, 837p, 844p], [900p, 907p, 916p, 929p, 933p, 937p, 944p], [1000p, 1007p, 1016p, 1029p, 1033p, 1037p, 1044p], [1100p, 1107p, 1116p, 1129p, 1133p, 1137p, 1144p]]
  short_name: "938"
  -
  time_points: [Woden Bus Station (Platform 5), Kambah Village, Kambah High, Tuggeranong Bus Station]
  long_name: To Tuggeranong Bus Station
  between_stops: {}
   
  short_name: "962"
  stop_times_sunday: [[951a, 1002a, 1010a, 1017a], [1051a, 1102a, 1110a, 1117a], [1151a, 1202p, 1210p, 1217p], [1251p, 102p, 110p, 117p], [151p, 202p, 210p, 217p], [251p, 302p, 310p, 317p], [351p, 402p, 410p, 417p], [451p, 502p, 510p, 517p], [551p, 602p, 610p, 617p], [651p, 702p, 710p, 717p]]
  -
  time_points: [Cohen Street Bus Station (Platform 2), Westfield Bus Station (Platform 1), Belconnen Community Bus Station (Platform 3), University of Canberra, Gwydir Square Kaleen, Kaleen Village / Marybrynong, Giralang, Southwell Park, Macarthur / Northbourne Ave, City Bus Station (Platform 9), Yarralumla, John James Hospital, Curtin, Woden Bus Station]
  long_name: To Woden Bus Station
  between_stops:
  Westfield Bus Station (Platform 1)-Belconnen Community Bus Station (Platform 3): []
  Cohen Street Bus Station (Platform 2)-Westfield Bus Station (Platform 1): []
  Belconnen Community Bus Station (Platform 3)-University of Canberra: [Wjz681S, Wjz689c, Wjz68Ip, Wjz68IH, Wjz68Y0, Wjz68Yy]
  stop_times_saturday: [[746a, 748a, 752a, 757a, 803a, 808a, 810a, 825a, 830a, 838a, 850a, 853a, 857a, 908a], [846a, 848a, 852a, 857a, 903a, 908a, 910a, 925a, 930a, 938a, 950a, 953a, 957a, 1008a], [946a, 948a, 952a, 957a, 1003a, 1008a, 1010a, 1025a, 1030a, 1038a, 1050a, 1053a, 1057a, 1108a], [1046a, 1048a, 1052a, 1057a, 1103a, 1108a, 1110a, 1125a, 1130a, 1138a, 1150a, 1153a, 1157a, 1208p], [1146a, 1148a, 1152a, 1157a, 1203p, 1208p, 1210p, 1225p, 1230p, 1238p, 1250p, 1253p, 1257p, 108p], [1246p, 1248p, 1252p, 1257p, 103p, 108p, 110p, 125p, 130p, 138p, 150p, 153p, 157p, 208p], [146p, 148p, 152p, 157p, 203p, 208p, 210p, 225p, 230p, 238p, 250p, 253p, 257p, 308p], [246p, 248p, 252p, 257p, 303p, 308p, 310p, 325p, 330p, 338p, 350p, 353p, 357p, 408p], [346p, 348p, 352p, 357p, 403p, 408p, 410p, 425p, 430p, 438p, 450p, 453p, 457p, 508p], [446p, 448p, 452p, 457p, 503p, 508p, 510p, 525p, 530p, 538p, 550p, 553p, 557p, 608p], [546p, 548p, 552p, 557p, 603p, 608p, 610p, 625p, 630p, 637p, 649p, 652p, 655p, 705p], [645p, 647p, 651p, 656p, 701p, 706p, 708p, 723p, 728p, 735p, 747p, 750p, 753p, 803p], [745p, 747p, 751p, 756p, 801p, 806p, 808p, 823p, 828p, 835p, 847p, 850p, 853p, 903p], [845p, 847p, 851p, 856p, 901p, 906p, 908p, 923p, 928p, 935p, 947p, 950p, 953p, 1003p], [945p, 947p, 951p, 956p, 1001p, 1006p, 1008p, 1023p, 1028p, 1035p, 1047p, 1050p, 1053p, 1103p], [1045p, 1047p, 1051p, 1056p, 1101p, 1106p, 1108p, 1123p, 1128p, 1134p, "-", "-", "-", "-"]]
  short_name: "932"
  -
  time_points: [Woden Bus Station (Platform 4), Curtin, John James Hospital, Yarralumla, City Bus Station (Platform 8), Macarthur / Northbourne Ave, Southwell Park, Giralang, Kaleen Village / Marybrynong, Gwydir Square Kaleen, University of Canberra, Belconnen Community Bus Station, Westfield Bus Station, Cohen Street Bus Station]
  long_name: To Cohen Street Bus Station
  between_stops:
  Westfield Bus Station-Cohen Street Bus Station: []
  Belconnen Community Bus Station-Westfield Bus Station: []
  University of Canberra-Belconnen Community Bus Station: [Wjz68Yy, Wjz68Y0, Wjz68IH, Wjz68Ip, Wjz689c, Wjz681S]
  stop_times_saturday: [[739a, 750a, 753a, 756a, 809a, 815a, 819a, 828a, 836a, 841a, 847a, 850a, 852a, 857a], [839a, 850a, 853a, 856a, 909a, 915a, 919a, 928a, 936a, 941a, 947a, 950a, 952a, 957a], [939a, 950a, 953a, 956a, 1009a, 1015a, 1019a, 1028a, 1036a, 1041a, 1047a, 1050a, 1052a, 1057a], [1039a, 1050a, 1053a, 1056a, 1109a, 1115a, 1119a, 1128a, 1136a, 1141a, 1147a, 1150a, 1152a, 1157a], [1139a, 1150a, 1153a, 1156a, 1209p, 1215p, 1219p, 1228p, 1236p, 1241p, 1247p, 1250p, 1252p, 1257p], [1239p, 1250p, 1253p, 1256p, 109p, 115p, 119p, 128p, 136p, 141p, 147p, 150p, 152p, 157p], [139p, 150p, 153p, 156p, 209p, 215p, 219p, 228p, 236p, 241p, 247p, 250p, 252p, 257p], [239p, 250p, 253p, 256p, 309p, 315p, 319p, 328p, 336p, 341p, 347p, 350p, 352p, 357p], [339p, 350p, 353p, 356p, 409p, 415p, 419p, 428p, 436p, 441p, 447p, 450p, 452p, 457p], [439p, 450p, 453p, 456p, 509p, 515p, 519p, 528p, 536p, 541p, 547p, 550p, 552p, 557p], [539p, 550p, 553p, 556p, 609p, 615p, 619p, 628p, 635p, 640p, 645p, 648p, 650p, 655p], [639p, 648p, 651p, 654p, 707p, 712p, 716p, 725p, 732p, 737p, 742p, 745p, 747p, 752p], [739p, 748p, 751p, 754p, 807p, 812p, 816p, 825p, 832p, 837p, 842p, 845p, 847p, 852p], [839p, 848p, 851p, 854p, 907p, 912p, 916p, 925p, 932p, 937p, 942p, 945p, 947p, 952p], [939p, 948p, 951p, 954p, 1007p, 1012p, 1016p, 1025p, 1032p, 1037p, 1042p, 1045p, 1047p, 1052p], [1039p, 1048p, 1051p, 1054p, 1107p, 1112p, 1116p, 1125p, 1132p, 1137p, 1142p, 1145p, 1147p, 1152p], [1139p, 1150p, 1153p, 1156p, 1208a, "-", "-", "-", "-", "-", "-", "-", "-"]]
  short_name: "932"
  -
  time_points: [Tuggeranong Bus Station (Platform 5), Erindale Centre, Athllon / Sulwood Kambah, Woden Bus Station]
  long_name: To Woden Bus Station
  between_stops:
  Athllon / Sulwood Kambah-Woden Bus Station: [Wjz2mTK, Wjz3gMq, Wjz3gQn, Wjz3gK-, Wjz3hL_, Wjz3iFK, Wjz3kwU, Wjz3kyX, Wjz3kAx]
  stop_times_saturday: [[825a, 837a, 849a, 858a], [925a, 937a, 949a, 958a], [1025a, 1037a, 1049a, 1058a], [1125a, 1137a, 1149a, 1158a], [1225p, 1237p, 1249p, 1258p], [125p, 137p, 149p, 158p], [225p, 237p, 249p, 258p], [325p, 337p, 349p, 358p], [425p, 437p, 449p, 458p], [525p, 537p, 549p, 558p], [625p, 637p, 649p, 658p], [725p, 737p, 749p, 758p], [825p, 837p, 849p, 858p], [925p, 937p, 949p, 958p], [1025p, 1037p, 1049p, 1058p], [1125p, 1137p, 1149p, "-"]]
  short_name: "964"
  -
  time_points: [City Bus Station (Platform 9), Russell Offices, Kings Ave / National Circuit, Kingston, Narrabundah College, Pearce, Woden Bus Station]
  long_name: To Woden Bus Station
  between_stops: {}
   
  short_name: "938"
  stop_times_sunday: [[846a, 854a, 858a, 902a, 917a, 927a, 934a], [946a, 954a, 958a, 1002a, 1017a, 1027a, 1034a], [1046a, 1054a, 1058a, 1102a, 1117a, 1127a, 1134a], [1146a, 1154a, 1158a, 1202p, 1217p, 1227p, 1234p], [1246p, 1254p, 1258p, 102p, 117p, 127p, 134p], [146p, 154p, 158p, 202p, 217p, 227p, 234p], [246p, 254p, 258p, 302p, 317p, 327p, 334p], [346p, 354p, 358p, 402p, 417p, 427p, 434p], [446p, 454p, 458p, 502p, 517p, 527p, 534p], [546p, 554p, 558p, 602p, 617p, 627p, 634p], [646p, 654p, 658p, 702p, 715p, 724p, 731p]]
  -
  time_points: [Spence Terminus, Spence, Melba, Copland College, Cohen Street Bus Station (Platform 3), Westfield Bus Station (Platform 1), Belconnen Community Bus Station (Platform 1), City Bus Station (Platform 1), Woden Bus Station (Platform 6), Tuggeranong Bus Station]
  long_name: To Tuggeranong Bus Station
  between_stops:
  Cohen Street Bus Station (Platform 3)-Westfield Bus Station (Platform 1): []
  Westfield Bus Station (Platform 1)-Belconnen Community Bus Station (Platform 1): []
  Woden Bus Station (Platform 6)-Tuggeranong Bus Station: [Wjz3kAx, Wjz3kyX, Wjz3kwU, Wjz3iFK, Wjz3hL_, Wjz3gK-, Wjz3gQn, Wjz3gMq, Wjz2nLE, Wjz2mTK, Wjz2mGO, Wjz2lDC, Wjz239F, Wjz238T, Wjz213q]
  City Bus Station (Platform 1)-Woden Bus Station (Platform 6): [Wjz5Nht, Wjz4KO9, Wjz4KNu, Wjz3eZ4, Wjz3eRR, Wjz3m3b, Wjz3m3b]
  Belconnen Community Bus Station (Platform 1)-City Bus Station (Platform 1): [Wjz681S, Wjz689c, Wjz68W3, Wjz68W5, Wjz6gia, Wjz6giR, Wjz5nw6, Wjz5nwb, Wjz5mbS, Wjz5maK, Wjz5G6B, Wjz5G6U, Wjz5GNG, Wjz5GMT, Wjz5FSY, Wjz5F-1]
  short_name: 15 315
  stop_times: [[533a, 538a, 543a, 546a, 556a, 558a, 602a, "-", "-", "-"], [603a, 608a, 613a, 616a, 626a, 628a, 632a, "-", "-", "-"], [632a, 637a, 642a, 645a, 655a, 657a, 701a, 721a, 738a, 755a], [702a, 707a, 712a, 715a, 725a, 727a, 731a, 753a, 810a, 827a], [728a, 733a, 739a, 743a, 753a, 755a, 759a, 821a, 838a, 855a], [750a, 755a, 801a, 805a, 815a, 817a, 821a, 843a, 900a, 917a], ["-", "-", 818a, 822a, 832a, 834a, 838a, 858a, "-", "-"], [810a, 815a, 821a, 825a, 835a, 837a, 841a, 903a, 920a, 936a], [830a, 835a, 841a, 845a, 855a, 857a, 901a, 923a, 940a, 955a], [900a, 905a, 911a, 915a, 925a, 927a, 931a, 951a, 1008a, 1023a], [932a, 937a, 942a, 945a, 955a, 957a, 1001a, 1021a, 1038a, 1053a], [1002a, 1007a, 1012a, 1015a, 1025a, 1027a, 1031a, 1051a, 1108a, 1123a], [1032a, 1037a, 1042a, 1045a, 1055a, 1057a, 1101a, 1121a, 1138a, 1153a], [1102a, 1107a, 1112a, 1115a, 1125a, 1127a, 1131a, 1151a, 1208p, 1223p], [1132a, 1137a, 1142a, 1145a, 1155a, 1157a, 1201p, 1221p, 1238p, 1253p], [1202p, 1207p, 1212p, 1215p, 1225p, 1227p, 1231p, 1251p, 108p, 123p], [1232p, 1237p, 1242p, 1245p, 1255p, 1257p, 101p, 121p, 138p, 153p], [102p, 107p, 112p, 115p, 125p, 127p, 131p, 151p, 208p, 223p], [132p, 137p, 142p, 145p, 155p, 157p, 201p, 221p, 238p, 253p], [202p, 207p, 212p, 215p, 225p, 227p, 231p, 251p, 308p, 327p], [233p, 238p, 243p, 246p, 256p, 258p, 302p, 324p, 341p, 400p], [300p, 305p, 311p, 315p, 325p, 327p, 331p, 353p, 410p, 429p], [330p, 335p, 341p, 345p, 355p, 357p, 401p, 423p, 440p, 459p], [400p, 405p, 411p, 415p, 425p, 427p, 431p, 453p, 510p, 529p], [440p, 445p, 451p, 455p, 505p, 507p, 511p, 533p, 550p, 609p], [530p, 535p, 541p, 545p, 555p, 557p, 601p, 623p, 638p, 653p], [600p, 605p, 611p, 615p, 625p, 627p, 631p, 650p, 704p, 719p], [623p, 628p, 633p, 636p, 645p, 647p, 651p, "-", "-", "-"], [656p, 701p, 706p, 709p, 718p, 720p, 724p, "-", "-", "-"], [740p, 745p, 750p, 753p, 802p, 804p, 808p, "-", "-", "-"], [840p, 845p, 850p, 853p, 902p, 904p, 908p, "-", "-", "-"], [940p, 945p, 950p, 953p, 1002p, 1004p, 1008p, "-", "-", "-"], [1040p, 1045p, 1050p, 1053p, 1102p, 1104p, 1108p, "-", "-", "-"], []]
  -
  time_points: [Tuggeranong Bus Station (Platform 7), Bonython Primary School, Woodcock / Clare Dennis, Gordon Primary, Tharwa Drive / Knoke Ave, Conder Primary, Lanyon Market Place, Bonython Primary School, Tuggeranong Bus Station]
  long_name: To Tuggeranong Bus Station
  between_stops: {}
   
  short_name: "913"
  stop_times_sunday: [[925a, 933a, 937a, 941a, 944a, 949a, 1000a, 1005a, 1013a], [1125a, 1133a, 1137a, 1141a, 1144a, 1149a, 1200p, 1205p, 1213p], [125p, 133p, 137p, 141p, 144p, 149p, 200p, 205p, 213p], [325p, 333p, 337p, 341p, 344p, 349p, 400p, 405p, 413p], [525p, 533p, 537p, 541p, 544p, 549p, 600p, 605p, 613p], [725p, 733p, 737p, 741p, 744p, 749p, 800p, 805p, 813p]]
  -
  time_points: [Woden Bus Station (Platform 2), Brindabella Gardens Nursing Home, Saint Andrews Village Hughes, Canberra Hospital, Woden Bus Station]
  long_name: To Woden Bus Station
  between_stops:
  Canberra Hospital-Woden Bus Station: [Wjz3mWn, Wjz3mPO, Wjz3mAg]
  short_name: "76"
  stop_times: [[1000a, 1007a, 1015a, 1020a, 1028a], [1200p, 1207p, 1215p, 1220p, 1228p], [200p, 207p, 215p, 220p, 228p]]
  -
  time_points: [Calwell, Isabella, Chisholm, Russell Offices, City Bus Station (Platform 11), City West]
  long_name: To City West
  between_stops:
  Russell Offices-City Bus Station (Platform 11): [Wjz4-WZ, Wjz4-WL, Wjz4-Rc, Wjz4-KO, Wjz4_wS, Wjz4_jm, Wjz4T-X, Wjz5MEL, Wjz5MsT, Wjz5MsD, Wjz5Nht]
  City Bus Station (Platform 11)-City West: []
  short_name: "768"
  stop_times: [[707a, 715a, 726a, 751a, 800a, 804a], [737a, 748a, 801a, 833a, 845a, 848a]]
  -
  time_points: [City Bus Station (Platform 8), Macarthur / Northbourne Ave, Northbourne Avenue / Antill St, Bimberi Centre]
  long_name: To Bimberi Centre
  between_stops:
  Macarthur / Northbourne Ave-Northbourne Avenue / Antill St: [Wjz5RkN, Wjz5Rsi, Wjz5RvC, Wjz5Sqk, Wjz5SrO, Wjz5Sux, Wjz5SDc]
  City Bus Station (Platform 8)-Macarthur / Northbourne Ave: [Wjz5O3Q, Wjz5Oci, Wjz5P8n, Wjz5P8K, Wjz5PdJ, Wjz5Pl0, Wjz5Qi2, Wjz5Qgn, Wjz5Qmu, Wjz5QmR]
  short_name: "82"
  stop_times: [[632a, 638a, 640a, 650a], [342p, 348p, 350p, 400p]]
  -
  time_points: [Gungahlin Marketplace, Dickson College, Russell Offices, Brindabella Business Park, Fairbairn Park]
  long_name: To Fairbairn Park
  between_stops:
  Brindabella Business Park-Fairbairn Park: [WjzcrK3, WjzcrrQ, WjzcrEu, WjzcJ0K, WjzcBHZ, WjzcJ38]
  short_name: "757"
  stop_times: [[650a, 700a, 711a, 725a, 735a], [710a, 720a, 731a, 745a, 755a], [730a, 740a, 751a, 805a, 815a]]
  -
  time_points: [Sydney Ave, Russell Offices, City Bus Station (Platform 11), Belconnen Community Bus Station, Westfield Bus Station, Cohen Street Bus Station]
  long_name: To Cohen Street Bus Station
  between_stops:
  Westfield Bus Station-Cohen Street Bus Station: []
  Russell Offices-City Bus Station (Platform 11): [Wjz4-WZ, Wjz4-WL, Wjz4-Rc, Wjz4-KO, Wjz4_wS, Wjz4_jm, Wjz4T-X, Wjz5MEL, Wjz5MsT, Wjz5MsD, Wjz5Nht]
  Belconnen Community Bus Station-Westfield Bus Station: []
  City Bus Station (Platform 11)-Belconnen Community Bus Station: [Wjz5F-1, Wjz5FSY, Wjz5GMT, Wjz5GNG, Wjz5G6U, Wjz5G6B, Wjz5maK, Wjz5mbS, Wjz5nwb, Wjz5nw6, Wjz6giR, Wjz6gia, Wjz68W3, Wjz68W5, Wjz689c, Wjz681S]
  short_name: "710"
  stop_times: [[407p, 415p, 425p, 445p, 447p, 452p], [427p, 435p, 445p, 505p, 507p, 512p], [445p, 453p, 503p, 523p, 525p, 530p], [507p, 515p, 525p, 545p, 547p, 552p], [527p, 535p, 545p, 605p, 607p, 612p]]
  -
  time_points: [City Bus Station (Platform 4), Caswell Drive, Aranda, Cook, Jamison Centre, Belconnen Community Bus Station, Westfield Bus Station, Cohen Street Bus Station]
  long_name: To Cohen Street Bus Station
  between_stops:
  Westfield Bus Station-Cohen Street Bus Station: []
  Belconnen Community Bus Station-Westfield Bus Station: []
  short_name: "942"
  stop_times_sunday: [[914a, 923a, 924a, 927a, 936a, 945a, 947a, 952a], [1014a, 1023a, 1024a, 1027a, 1036a, 1045a, 1047a, 1052a], [1114a, 1123a, 1124a, 1127a, 1136a, 1145a, 1147a, 1152a], [1214p, 1223p, 1224p, 1227p, 1236p, 1245p, 1247p, 1252p], [114p, 123p, 124p, 127p, 136p, 145p, 147p, 152p], [214p, 223p, 224p, 227p, 236p, 245p, 247p, 252p], [314p, 323p, 324p, 327p, 336p, 345p, 347p, 352p], [414p, 423p, 424p, 427p, 436p, 445p, 447p, 452p], [514p, 523p, 524p, 527p, 536p, 545p, 547p, 552p], [614p, 623p, 624p, 627p, 636p, 645p, 647p, 652p]]
  -
  time_points: [Tuggeranong Bus Station (Platform 3), Erindale Centre, Athllon / Sulwood Kambah, Woden Bus Station]
  long_name: To Woden Bus Station
  between_stops:
  Athllon / Sulwood Kambah-Woden Bus Station: [Wjz2mTK, Wjz3gMq, Wjz3gQn, Wjz3gK-, Wjz3hL_, Wjz3iFK, Wjz3kwU, Wjz3kyX, Wjz3kAx]
  short_name: "961"
  stop_times_sunday: [[942a, 956a, 1006a, 1015a], [1042a, 1056a, 1106a, 1115a], [1142a, 1156a, 1206p, 1215p], [1242p, 1256p, 106p, 115p], [142p, 156p, 206p, 215p], [242p, 256p, 306p, 315p], [342p, 356p, 406p, 415p], [442p, 456p, 506p, 515p], [542p, 556p, 606p, 615p]]
  -
  time_points: [City Bus Station (Platform 1), Woden Bus Station (Platform 11), Erindale Centre, Calwell, Theodore, MacKillop College Isabella Campus, Tuggeranong Bus Station]
  long_name: To Tuggeranong Bus Station
  between_stops:
  City Bus Station (Platform 1)-Woden Bus Station (Platform 11): [Wjz5Nht, Wjz4KO9, Wjz4KNu, Wjz3eZ4, Wjz3eRR, Wjz3m3b, Wjz3m3b]
  short_name: 11 111
  stop_times: [["-", "-", "-", 546a, 556a, 609a, 616a], ["-", "-", "-", 606a, 616a, 629a, 636a], ["-", "-", "-", 626a, 636a, 649a, 656a], ["-", "-", "-", 646a, 656a, 709a, 716a], ["-", "-", "-", 706a, 716a, 729a, 736a], ["-", "-", "-", 725a, 735a, 749a, 756a], ["-", "-", "-", 745a, 755a, 809a, 816a], ["-", "-", "-", 805a, 815a, 829a, 836a], ["-", "-", "-", 825a, 835a, 849a, 856a], ["-", "-", "-", 845a, 855a, 909a, 916a], ["-", "-", "-", 917a, 927a, 940a, 946a], ["-", 930a, 942a, 948a, 957a, 1010a, 1016a], ["-", 1000a, 1012a, 1018a, 1027a, 1040a, 1046a], ["-", 1030a, 1042a, 1048a, 1057a, 1110a, 1116a], ["-", 1100a, 1112a, 1118a, 1127a, 1140a, 1146a], ["-", 1130a, 1142a, 1148a, 1157a, 1210p, 1216p], ["-", 1200p, 1212p, 1218p, 1227p, 1240p, 1246p], ["-", 1230p, 1242p, 1248p, 1257p, 110p, 116p], ["-", 100p, 112p, 118p, 127p, 140p, 146p], ["-", 130p, 142p, 148p, 157p, 210p, 216p], ["-", 200p, 212p, 218p, 227p, 240p, 246p], ["-", 230p, 242p, 248p, 257p, 311p, 318p], ["-", 300p, 314p, 321p, 331p, 345p, 352p], ["-", 320p, 334p, 341p, 351p, 405p, 412p], ["-", 340p, 354p, 401p, 411p, 425p, 432p], ["-", 400p, 414p, 421p, 431p, 445p, 452p], ["-", 425p, 439p, 446p, 456p, 510p, 517p], ["-", 440p, 454p, 501p, 511p, 525p, 532p], ["-", 500p, 514p, 521p, 531p, 545p, 552p], [456p, 513p, 527p, 534p, 544p, 558p, 605p], [516p, 533p, 547p, 554p, 604p, 618p, 625p], [534p, 551p, 605p, 612p, 622p, 636p, 641p], [556p, 613p, 627p, 633p, 642p, 655p, 701p], [616p, 633p, 645p, 651p, 700p, 713p, 719p], ["-", 733p, 745p, 751p, 800p, 813p, 819p], ["-", 833p, 845p, 851p, 900p, 913p, 919p], ["-", 933p, 945p, 951p, 1000p, 1013p, 1019p], ["-", 1033p, 1045p, 1051p, 1100p, 1113p, 1119p]]
  -
  time_points: [Fraser West Terminus, Fraser, Charnwood Tillyard Dr, St Francis Xavier Florey, Cohen Street Bus Station (Platform 3), Westfield Bus Station (Platform 1), Belconnen Community Bus Station (Platform 1), City Bus Station (Platform 1), Woden Bus Station (Platform 6), Tuggeranong Bus Station]
  long_name: To Tuggeranong Bus Station
  between_stops:
  Cohen Street Bus Station (Platform 3)-Westfield Bus Station (Platform 1): []
  Westfield Bus Station (Platform 1)-Belconnen Community Bus Station (Platform 1): []
  Woden Bus Station (Platform 6)-Tuggeranong Bus Station: [Wjz3kAx, Wjz3kyX, Wjz3kwU, Wjz3iFK, Wjz3hL_, Wjz3gK-, Wjz3gQn, Wjz3gMq, Wjz2nLE, Wjz2mTK, Wjz2mGO, Wjz2lDC, Wjz239F, Wjz238T, Wjz213q]
  City Bus Station (Platform 1)-Woden Bus Station (Platform 6): [Wjz5Nht, Wjz4KO9, Wjz4KNu, Wjz3eZ4, Wjz3eRR, Wjz3m3b, Wjz3m3b]
  Belconnen Community Bus Station (Platform 1)-City Bus Station (Platform 1): [Wjz681S, Wjz689c, Wjz68W3, Wjz68W5, Wjz6gia, Wjz6giR, Wjz5nw6, Wjz5nwb, Wjz5mbS, Wjz5maK, Wjz5G6B, Wjz5G6U, Wjz5GNG, Wjz5GMT, Wjz5FSY, Wjz5F-1]
  short_name: 14 314
  stop_times: [[611a, 618a, 622a, 627a, 636a, 638a, 642a, "-", "-", "-"], [640a, 647a, 651a, 656a, 705a, 707a, 711a, 731a, 748a, 805a], [709a, 716a, 720a, 725a, 734a, 736a, 740a, 803a, 820a, 837a], [732a, 740a, 745a, 750a, 800a, 802a, 806a, 828a, 845a, 902a], [752a, 800a, 805a, 810a, 820a, 822a, 826a, 848a, 905a, 922a], [812a, 820a, 825a, 830a, 840a, 842a, 846a, 908a, 925a, 941a], [837a, 845a, 850a, 855a, 905a, 907a, 911a, 933a, 950a, 1005a], [908a, 916a, 921a, 926a, 935a, 937a, 941a, 1001a, 1018a, 1033a], [940a, 947a, 951a, 956a, 1005a, 1007a, 1011a, 1031a, 1048a, 1103a], [1010a, 1017a, 1021a, 1026a, 1035a, 1037a, 1041a, 1101a, 1118a, 1133a], [1040a, 1047a, 1051a, 1056a, 1105a, 1107a, 1111a, 1131a, 1148a, 1203p], [1110a, 1117a, 1121a, 1126a, 1135a, 1137a, 1141a, 1201p, 1218p, 1233p], [1140a, 1147a, 1151a, 1156a, 1205p, 1207p, 1211p, 1231p, 1248p, 103p], [1210p, 1217p, 1221p, 1226p, 1235p, 1237p, 1241p, 101p, 118p, 133p], [1240p, 1247p, 1251p, 1256p, 105p, 107p, 111p, 131p, 148p, 203p], [110p, 117p, 121p, 126p, 135p, 137p, 141p, 201p, 218p, 233p], [140p, 147p, 151p, 156p, 205p, 207p, 211p, 231p, 248p, 304p], [210p, 217p, 221p, 226p, 235p, 237p, 241p, 301p, 318p, 337p], [239p, 246p, 250p, 255p, 305p, 307p, 311p, 333p, 350p, 409p], [308p, 315p, 320p, 325p, 335p, 337p, 341p, 403p, 420p, 439p], [348p, 355p, 400p, 405p, 415p, 417p, 421p, 443p, 500p, 519p], [418p, 425p, 430p, 435p, 445p, 447p, 451p, 513p, 530p, 549p], [450p, 457p, 502p, 507p, 517p, 519p, 523p, "-", "-", "-"], [538p, 545p, 550p, 555p, 605p, 607p, 611p, 632p, 646p, 701p], [609p, 616p, 621p, 626p, 635p, 637p, 641p, 700p, 714p, 729p], [637p, 644p, 648p, 653p, 701p, 703p, 707p, "-", "-", "-"], [707p, 714p, 718p, 723p, 731p, 733p, 737p, "-", "-", "-"], [745p, 752p, 756p, 801p, 809p, 811p, 815p, "-", "-", "-"], [839p, 846p, 850p, 855p, 903p, 905p, 909p, "-", "-", "-"], [939p, 946p, 950p, 955p, 1003p, 1005p, 1009p, "-", "-", "-"], [1039p, 1046p, 1050p, 1055p, 1103p, 1105p, 1109p, "-", "-", "-"]]
  -
  time_points: [Tuggeranong Bus Station (Platform 8), Erindale Centre, Woden Bus Station (Platform 9), City Bus Station (Platform 3), Belconnen Community Bus Station, Westfield Bus Station, Cohen Street Bus Station]
  long_name: To Cohen Street Bus Station
  between_stops:
  Westfield Bus Station-Cohen Street Bus Station: []
  Belconnen Community Bus Station-Westfield Bus Station: []
  stop_times_saturday: [[630a, 641a, 657a, 713a, 730a, 732a, 737a], [645a, 656a, 712a, 728a, 745a, 747a, 752a], [700a, 711a, 727a, 743a, 800a, 802a, 807a], [715a, 726a, 742a, 758a, 815a, 817a, 822a], [730a, 741a, 757a, 813a, 830a, 832a, 837a], [745a, 756a, 812a, 828a, 845a, 847a, 852a], [800a, 811a, 827a, 843a, 900a, 902a, 907a], [815a, 826a, 842a, 858a, 915a, 917a, 922a], [830a, 841a, 857a, 913a, 930a, 932a, 937a], [845a, 856a, 912a, 928a, 945a, 947a, 952a], [900a, 911a, 927a, 943a, 1000a, 1002a, 1007a], [915a, 926a, 942a, 958a, 1015a, 1017a, 1022a], [930a, 941a, 957a, 1013a, 1030a, 1032a, 1037a], [945a, 956a, 1012a, 1028a, 1045a, 1047a, 1052a], [1000a, 1011a, 1027a, 1043a, 1100a, 1102a, 1107a], [1015a, 1026a, 1042a, 1058a, 1115a, 1117a, 1122a], [1030a, 1041a, 1057a, 1113a, 1130a, 1132a, 1137a], [1045a, 1056a, 1112a, 1128a, 1145a, 1147a, 1152a], [1100a, 1111a, 1127a, 1143a, 1200p, 1202p, 1207p], [1115a, 1126a, 1142a, 1158a, 1215p, 1217p, 1222p], ["-", "-", 1149a, 1205p, 1222p, 1224p, 1229p], [1130a, 1141a, 1157a, 1213p, 1230p, 1232p, 1237p], [1145a, 1156a, 1212p, 1228p, 1245p, 1247p, 1252p], ["-", "-", 1219p, 1235p, 1252p, 1254p, 1259p], [1200p, 1211p, 1227p, 1243p, 100p, 102p, 107p], [1215p, 1226p, 1242p, 1258p, 115p, 117p, 122p], ["-", "-", 1249p, 105p, 122p, 124p, 129p], [1230p, 1241p, 1257p, 113p, 130p, 132p, 137p], [1245p, 1256p, 112p, 128p, 145p, 147p, 152p], ["-", "-", 119p, 135p, 152p, 154p, 159p], [100p, 111p, 127p, 143p, 200p, 202p, 207p], [115p, 126p, 142p, 158p, 215p, 217p, 222p], ["-", "-", 149p, 205p, 222p, 224p, 229p], [130p, 141p, 157p, 213p, 230p, 232p, 237p], [145p, 156p, 212p, 228p, 245p, 247p, 252p], ["-", "-", 219p, 235p, 252p, 254p, 259p], [200p, 211p, 227p, 243p, 300p, 302p, 307p], [215p, 226p, 242p, 258p, 315p, 317p, 322p], ["-", "-", 249p, 305p, 322p, 324p, 329p], [230p, 241p, 257p, 313p, 330p, 332p, 337p], [245p, 256p, 312p, 328p, 345p, 347p, 352p], ["-", "-", 319p, 335p, 352p, 354p, 359p], [300p, 311p, 327p, 343p, 400p, 402p, 407p], [315p, 326p, 342p, 358p, 415p, 417p, 422p], ["-", "-", 349p, 405p, 422p, 424p, 429p], [330p, 341p, 357p, 413p, 430p, 432p, 437p], [345p, 356p, 412p, 428p, 445p, 447p, 452p], ["-", "-", 419p, 435p, 452p, 454p, 459p], [400p, 411p, 427p, 443p, 500p, 502p, 507p], [415p, 426p, 442p, 458p, 515p, 517p, 522p], ["-", "-", 449p, 505p, 522p, 524p, 529p], [430p, 441p, 457p, 513p, 530p, 532p, 537p], [445p, 456p, 512p, 528p, 545p, 547p, 552p], [500p, 511p, 527p, 543p, 600p, 602p, 607p], [515p, 526p, 542p, 558p, 615p, 617p, 622p], [530p, 541p, 557p, 613p, 630p, 632p, 637p], [545p, 556p, 612p, 628p, 645p, 647p, 652p], [600p, 611p, 627p, 642p, 659p, 701p, 706p], [615p, 626p, 641p, 656p, 713p, 715p, 720p], [630p, 640p, 655p, 710p, 727p, 729p, 734p], [645p, 655p, 710p, 725p, 742p, 744p, 749p], [700p, 710p, 725p, 740p, 757p, 759p, 804p], [715p, 725p, 740p, 755p, 812p, 814p, 819p], [730p, 740p, 755p, 810p, 827p, 829p, 834p], [745p, 755p, 810p, 825p, 842p, 844p, 849p], [800p, 810p, 825p, 840p, 857p, 859p, 904p], [815p, 825p, 840p, 855p, 912p, 914p, 919p], [830p, 840p, 855p, 910p, 927p, 929p, 934p], [845p, 855p, 910p, 925p, 942p, 944p, 949p], [900p, 910p, 925p, 940p, 957p, 959p, 1004p], [915p, 925p, 940p, 955p, 1012p, 1014p, 1019p], [930p, 940p, 955p, 1010p, 1027p, 1029p, 1034p], [945p, 955p, 1010p, 1025p, 1042p, 1044p, 1049p], [1000p, 1010p, 1025p, 1040p, 1057p, 1059p, 1104p], [1015p, 1025p, 1040p, 1055p, 1112p, 1114p, 1119p], [1030p, 1040p, 1055p, 1110p, 1127p, 1129p, 1134p], [1045p, 1055p, 1110p, 1125p, 1142p, 1144p, 1149p], [1100p, 1110p, 1125p, 1140p, 1157p, 1159p, 1204a], [1115p, 1125p, 1140p, 1155p, 1212a, 1214a, 1219a]]
  short_name: "900"
  -
  time_points: [Tuggeranong Bus Station (Platform 7), Erindale Centre, Gowrie, Chisholm, Gowrie, Erindale Centre, Tuggeranong Bus Station]
  long_name: To Tuggeranong Bus Station
  between_stops: {}
   
  stop_times_saturday: [["-", "-", "-", 736a, 748a, 757a, 810a], [808a, 820a, 827a, 836a, 848a, 857a, 910a], [908a, 920a, 927a, 936a, 948a, 957a, 1010a], [1008a, 1020a, 1027a, 1036a, 1048a, 1057a, 1110a], [1108a, 1120a, 1127a, 1136a, 1148a, 1157a, 1210p], [1208p, 1220p, 1227p, 1236p, 1248p, 1257p, 110p], [108p, 120p, 127p, 136p, 148p, 157p, 210p], [208p, 220p, 227p, 236p, 248p, 257p, 310p], [308p, 320p, 327p, 336p, 348p, 357p, 410p], [408p, 420p, 427p, 436p, 448p, 457p, 510p], [508p, 520p, 527p, 536p, 548p, 557p, 610p], [608p, 620p, 627p, 636p, 648p, 657p, 710p], [705p, 717p, 724p, 733p, 745p, 754p, 807p], [803p, 815p, 822p, 831p, 843p, 852p, 905p], [903p, 915p, 922p, 931p, 943p, 952p, 1005p], [1003p, 1015p, 1022p, 1031p, 1043p, 1052p, 1105p], [1103p, 1115p, 1122p, 1131p, "-", "-", "-"]]
  short_name: "966"
  -
  time_points: [Belconnen Community Bus Station (Platform 4), Westfield Bus Station (Platform 2), Cohen Street Bus Station (Platform 6), Kippax, Fraser West Terminus, Kippax, Cohen Street Bus Station, Westfield Bus Station, Belconnen Community Bus Station]
  long_name: To Belconnen Community Bus Station
  between_stops:
  Belconnen Community Bus Station (Platform 4)-Westfield Bus Station (Platform 2): []
  Westfield Bus Station-Belconnen Community Bus Station: []
  Cohen Street Bus Station-Westfield Bus Station: []
  Westfield Bus Station (Platform 2)-Cohen Street Bus Station (Platform 6): []
  stop_times_saturday: [["-", "-", "-", "-", 734a, 748a, 802a, 808a, 810a], [759a, 801a, 805a, 819a, 834a, 848a, 902a, 908a, 910a], [859a, 901a, 905a, 919a, 934a, 948a, 1002a, 1008a, 1010a], [959a, 1001a, 1005a, 1019a, 1034a, 1048a, 1102a, 1108a, 1110a], [1059a, 1101a, 1105a, 1119a, 1134a, 1148a, 1202p, 1208p, 1210p], [1159a, 1201p, 1205p, 1219p, 1234p, 1248p, 102p, 108p, 110p], [1259p, 101p, 105p, 119p, 134p, 148p, 202p, 208p, 210p], [159p, 201p, 205p, 219p, 234p, 248p, 302p, 308p, 310p], [259p, 301p, 305p, 319p, 334p, 348p, 402p, 408p, 410p], [359p, 401p, 405p, 419p, 434p, 448p, 502p, 508p, 510p], [459p, 501p, 505p, 519p, 534p, 548p, 602p, 608p, 610p], [559p, 601p, 605p, 619p, 634p, 648p, 701p, 707p, 709p], [658p, 700p, 704p, 717p, 732p, 746p, 759p, 805p, 807p], [758p, 800p, 804p, 817p, 832p, 846p, 859p, 905p, 907p], [858p, 900p, 904p, 917p, 932p, 946p, 959p, 1005p, 1007p], [958p, 1000p, 1004p, 1017p, 1032p, 1046p, 1059p, 1105p, 1107p], [1058p, 1100p, 1104p, 1117p, 1132p, "-", "-", "-", "-"]]
  short_name: "903"
  -
  time_points: [Woden Bus Station (Platform 4), Alexander Maconochie Centre]
  long_name: To Alexander Maconochie Centre Hume
  between_stops:
  Woden Bus Station (Platform 4)-Alexander Maconochie Centre: [Wjz3dXS, Wjz3kAx]
  stop_times_saturday: [[920a, 940a], [1255p, 115p], [455p, 515p]]
  short_name: "988"
  -
  time_points: [Weston Creek Terminus, Chapman, Canberra College Weston Campus, Cooleman Court, Woden Bus Station (Platform 10), Kings Ave / National Circuit, Russell Offices, ADFA, Campbell Park Offices]
  long_name: To Campbell Park Offices
  between_stops:
  Kings Ave / National Circuit-Russell Offices: [Wjz4Quk, Wjz4RwH, Wjz4RFJ]
  ADFA-Campbell Park Offices: [Wjzcend, Wjzce4H, Wjzce7O]
  Russell Offices-ADFA: [Wjzc60A, Wjzc60i, Wjzc55s, Wjzc54R, Wjzce7O, Wjzce4H, Wjzcend]
  short_name: 26 226
  stop_times: [[615a, 619a, 623a, 625a, 632a, "-", "-", "-", "-"], [657a, 701a, 705a, 707a, 715a, 729a, 733a, 737a, 741a], [716a, 720a, 724a, 726a, 736a, 750a, 754a, 758a, 802a], [747a, 752a, 758a, 802a, 815a, 829a, 833a, 837a, 841a], [800a, 805a, 811a, 815a, 827a, "-", "-", "-", "-"], [820a, 825a, 831a, 835a, 847a, "-", "-", "-", "-"], [850a, 855a, 901a, 905a, 917a, "-", "-", "-", "-"], [925a, 930a, 935a, 938a, 948a, "-", "-", "-", "-"], [1025a, 1029a, 1034a, 1037a, 1047a, "-", "-", "-", "-"], [1125a, 1129a, 1134a, 1137a, 1147a, "-", "-", "-", "-"], [1225p, 1229p, 1234p, 1237p, 1247p, "-", "-", "-", "-"], [125p, 129p, 134p, 137p, 147p, "-", "-", "-", "-"], [225p, 229p, 234p, 237p, 247p, "-", "-", "-", "-"], [255p, 259p, 305p, 308p, 317p, "-", "-", "-", "-"], [320p, 324p, 330p, 333p, 342p, "-", "-", "-", "-"], [420p, 424p, 430p, 433p, 442p, "-", "-", "-", "-"], [520p, 524p, 530p, 533p, 542p, "-", "-", "-", "-"], [620p, 624p, 630p, 632p, 639p, "-", "-", "-", "-"], [714p, 718p, 722p, 724p, 731p, "-", "-", "-", "-"], [814p, 818p, 822p, 824p, 831p, "-", "-", "-", "-"], [914p, 918p, 922p, 924p, 931p, "-", "-", "-", "-"], [1014p, 1018p, 1022p, 1024p, 1031p, "-", "-", "-", "-"]]
  -
  time_points: [Woden Bus Station (Platform 4), Curtin, John James Hospital, Yarralumla, Deakin, Parliament House, Kings Ave / National Circuit, City Bus Station (Platform 5), Olims Hotel, Ainslie, Hackett, Dickson / Antill St]
  long_name: To Dickson
  between_stops:
  Ainslie-Hackett: [Wjz5YKO, Wjz5ZO1, Wjz5ZZQ, Wjzd68O, Wjzd6iW, Wjzd6lW, Wjzd6Cq, Wjzd6Pn, Wjzd6XP, WjzdeeQ]
  Olims Hotel-Ainslie: [Wjz5W8l, Wjz5W3H, Wjz5XwW, Wjz5XrS, Wjz5XnQ, Wjz5Yq4, Wjz5YAK]
  City Bus Station (Platform 5)-Olims Hotel: [Wjz5NAQ, Wjz5NHD, Wjz5NRJ, Wjz5V64]
  Parliament House-Kings Ave / National Circuit: [Wjz4INj, Wjz4P6x]
  Kings Ave / National Circuit-City Bus Station (Platform 5): [Wjz4Quk, Wjz4RwH, Wjz4RFJ, Wjz4Rs-, Wjz4S1U, Wjz5FOn]
  short_name: "2"
  stop_times: [["-", "-", "-", "-", "-", "-", "-", 703a, 712a, 717a, 725a, 733a], [653a, 704a, 708a, 711a, 715a, 719a, 723a, 733a, 742a, 748a, 756a, 805a], [708a, 719a, 723a, 726a, 730a, 734a, 738a, 749a, 758a, 804a, 812a, 821a], [719a, 730a, 734a, 737a, 741a, 745a, 749a, 800a, 809a, 815a, 823a, 833a], [738a, 749a, 754a, 758a, 803a, 808a, 814a, 830a, 838a, 845a, 853a, 859a], [753a, 804a, 808a, 812a, 817a, 823a, 826a, 843a, 849a, 854a, 902a, 910a], [808a, 819a, 823a, 826a, 830a, 834a, 838a, 849a, 858a, 904a, 912a, 921a], [823a, 834a, 838a, 841a, 845a, 849a, 853a, 904a, 913a, 919a, 927a, 935a], [838a, 849a, 853a, 856a, 900a, 904a, 908a, 918a, "-", "-", "-", "-"], [851a, 902a, 906a, 909a, 913a, 917a, 921a, 932a, 941a, 946a, 954a, 1001a], [921a, 932a, 936a, 939a, 943a, 947a, 951a, 1002a, 1011a, 1016a, 1024a, 1031a], [951a, 1002a, 1006a, 1009a, 1013a, 1017a, 1021a, 1032a, 1041a, 1046a, 1054a, 1101a], [1021a, 1032a, 1036a, 1039a, 1043a, 1047a, 1051a, 1102a, 1111a, 1116a, 1124a, 1131a], [1051a, 1102a, 1106a, 1109a, 1113a, 1117a, 1121a, 1132a, 1141a, 1146a, 1154a, 1201p], [1121a, 1132a, 1136a, 1139a, 1143a, 1147a, 1151a, 1202p, 1211p, 1216p, 1224p, 1231p], [1151a, 1202p, 1206p, 1209p, 1213p, 1217p, 1221p, 1232p, 1241p, 1246p, 1254p, 101p], [1221p, 1232p, 1236p, 1239p, 1243p, 1247p, 1251p, 102p, 111p, 116p, 124p, 131p], [1251p, 102p, 106p, 109p, 113p, 117p, 121p, 132p, 141p, 146p, 154p, 201p], [121p, 132p, 136p, 139p, 143p, 147p, 151p, 202p, 211p, 216p, 224p, 231p], [151p, 202p, 206p, 209p, 213p, 217p, 221p, 232p, 241p, 246p, 254p, 301p], [216p, 227p, 231p, 234p, 238p, 242p, 246p, 257p, 306p, 312p, 320p, 328p], [238p, 249p, 253p, 256p, 300p, 304p, 308p, 319p, 328p, 334p, 342p, 351p], [253p, 304p, 308p, 311p, 315p, 319p, 323p, 334p, 343p, 349p, 357p, 406p], [308p, 318p, 322p, 325p, 329p, 333p, 337p, 348p, 357p, 403p, 411p, 420p], [323p, 333p, 337p, 340p, 344p, 348p, 352p, 403p, 412p, 418p, 426p, 435p], [338p, 348p, 352p, 355p, 359p, 403p, 407p, 418p, 427p, 433p, 441p, 450p], [353p, 403p, 407p, 410p, 414p, 418p, 422p, 433p, 442p, 448p, 456p, 505p], [408p, 418p, 422p, 425p, 429p, 433p, 437p, 448p, 457p, 503p, 511p, 520p], [423p, 433p, 437p, 440p, 444p, 448p, 452p, 503p, 512p, 518p, 526p, 535p], [438p, 448p, 452p, 455p, 459p, 503p, 507p, 518p, 527p, 533p, 541p, 550p], [453p, 503p, 507p, 510p, 514p, 518p, 522p, 533p, 542p, 548p, 556p, 605p], [508p, 518p, 522p, 525p, 529p, 533p, 537p, 548p, 557p, 603p, 611p, 620p], [523p, 533p, 537p, 540p, 544p, 548p, 552p, 603p, 612p, 618p, 626p, 633p], [538p, 548p, 552p, 555p, 559p, 603p, 607p, 618p, 627p, 633p, 639p, 645p], [553p, 603p, 607p, 610p, 614p, 618p, 622p, 633p, 640p, 645p, 651p, 657p], [640p, 650p, 653p, 656p, 700p, 703p, 707p, 717p, 724p, 729p, 735p, 741p], [740p, 750p, 753p, 756p, 800p, 803p, 807p, 817p, 824p, 829p, 835p, 841p], [840p, 850p, 853p, 856p, 900p, 903p, 907p, 917p, 924p, 929p, 935p, 941p], [940p, 950p, 953p, 956p, 1000p, 1003p, 1007p, 1017p, 1024p, 1029p, 1035p, 1041p], [1040p, 1050p, 1053p, 1056p, 1100p, 1103p, 1107p, 1117p, 1124p, 1129p, 1135p, 1141p]]
  -
  time_points: [Gungahlin Marketplace, Ngunnawal Primary, Nicholls Primary, Federation Square, Chuculba / William Slim Dr, Belconnen Community Bus Station, Westfield Bus Station, Cohen Street Bus Station]
  long_name: To Cohen Street Bus Station
  between_stops:
  Westfield Bus Station-Cohen Street Bus Station: []
  Belconnen Community Bus Station-Westfield Bus Station: []
  Chuculba / William Slim Dr-Belconnen Community Bus Station: [Wjz6mip, Wjz69vO, Wjz69uI, Wjz69ht, Wjz69gA]
  short_name: "951"
  stop_times_sunday: [[912a, 921a, 931a, 937a, 942a, 950a, 952a, 957a], [1012a, 1021a, 1031a, 1037a, 1042a, 1050a, 1052a, 1057a], [1112a, 1121a, 1131a, 1137a, 1142a, 1150a, 1152a, 1157a], [1212p, 1221p, 1231p, 1237p, 1242p, 1250p, 1252p, 1257p], [112p, 121p, 131p, 137p, 142p, 150p, 152p, 157p], [212p, 221p, 231p, 237p, 242p, 250p, 252p, 257p], [312p, 321p, 331p, 337p, 342p, 350p, 352p, 357p], [412p, 421p, 431p, 437p, 442p, 450p, 452p, 457p], [512p, 521p, 531p, 537p, 542p, 550p, 552p, 557p], [612p, 621p, 631p, 637p, 642p, 650p, 652p, 657p]]
  -
  time_points: [Lanyon Market Place, Tharwa Drive / Pockett Ave, Mentone View / Tharwa Drive, City West, City Bus Station (Platform 10), ACTEW AGL House]
  long_name: To ACTEW AGL House
  between_stops:
  City West-City Bus Station (Platform 10): []
  City Bus Station (Platform 10)-ACTEW AGL House: [Wjz5Nht]
  short_name: "785"
  stop_times: [[652a, 655a, 713a, 743a, 747a, 749a], [725a, 728a, 746a, 816a, 820a, 822a], [745a, 748a, 806a, 836a, 840a, 842a]]
  -
  time_points: [Woden Bus Station (Platform 15), Canberra Hospital, Isaacs, Farrer Terminus, Southlands Mawson, Chifley, Lyons, Woden Bus Station]
  long_name: To Woden Bus Station
  between_stops:
  Woden Bus Station (Platform 15)-Canberra Hospital: [Wjz3mAg, Wjz3mPO, Wjz3mWn]
  short_name: "24"
  stop_times: [["-", "-", "-", 703a, 709a, 715a, 720a, 724a], [702a, 708a, 715a, 720a, 726a, 732a, 737a, 742a], [739a, 746a, 754a, 800a, 806a, 813a, 818a, 823a], [809a, 816a, 824a, 830a, 836a, 843a, 848a, 853a], [839a, 846a, 854a, 900a, 906a, 913a, 918a, 923a], [956a, 1002a, 1009a, 1014a, 1020a, 1026a, 1031a, 1035a], [1056a, 1102a, 1109a, 1114a, 1120a, 1126a, 1131a, 1135a], [1156a, 1202p, 1209p, 1214p, 1220p, 1226p, 1231p, 1235p], [1256p, 102p, 109p, 114p, 120p, 126p, 131p, 135p], [156p, 202p, 209p, 214p, 220p, 226p, 231p, 235p], [256p, 302p, 310p, 316p, 322p, 329p, 334p, 339p], [339p, 346p, 354p, 400p, 406p, 413p, 418p, 423p], [409p, 416p, 424p, 430p, 436p, 443p, 448p, 453p], [439p, 446p, 454p, 500p, 506p, 513p, 518p, 523p], [509p, 516p, 524p, 530p, 536p, 543p, 548p, 553p], [538p, 545p, 553p, 559p, 605p, 612p, 617p, 622p], [608p, 615p, 623p, 629p, 635p, 641p, 646p, 650p], [659p, 705p, 712p, 717p, 723p, 729p, 734p, 738p], [759p, 805p, 812p, 817p, 823p, 829p, 834p, 838p], [859p, 905p, 912p, 917p, 923p, 929p, 934p, 938p], [959p, 1005p, 1012p, 1017p, 1023p, 1029p, 1034p, 1038p], [1059p, 1105p, 1112p, 1117p, 1123p, 1129p, 1134p, 1138p]]
  -
  time_points: [Cohen Street Bus Station (Platform 2), Westfield Bus Station (Platform 1), Belconnen Community Bus Station (Platform 3), University of Canberra, Gwydir Square Kaleen, Kaleen Village / Marybrynong, Giralang, Southwell Park, Macarthur / Northbourne Ave, City Bus Station (Platform 9), Yarralumla, John James Hospital, Curtin, Woden Bus Station]
  long_name: To Woden Bus Station
  between_stops:
  Westfield Bus Station (Platform 1)-Belconnen Community Bus Station (Platform 3): []
  Cohen Street Bus Station (Platform 2)-Westfield Bus Station (Platform 1): []
  Belconnen Community Bus Station (Platform 3)-University of Canberra: [Wjz681S, Wjz689c, Wjz68Ip, Wjz68IH, Wjz68Y0, Wjz68Yy]
  short_name: "932"
  stop_times_sunday: [[746a, 748a, 752a, 757a, 803a, 808a, 810a, 825a, 830a, 838a, 850a, 853a, 857a, 908a], [846a, 848a, 852a, 857a, 903a, 908a, 910a, 925a, 930a, 938a, 950a, 953a, 957a, 1008a], [946a, 948a, 952a, 957a, 1003a, 1008a, 1010a, 1025a, 1030a, 1038a, 1050a, 1053a, 1057a, 1108a], [1046a, 1048a, 1052a, 1057a, 1103a, 1108a, 1110a, 1125a, 1130a, 1138a, 1150a, 1153a, 1157a, 1208p], [1146a, 1148a, 1152a, 1157a, 1203p, 1208p, 1210p, 1225p, 1230p, 1238p, 1250p, 1253p, 1257p, 108p], [1246p, 1248p, 1252p, 1257p, 103p, 108p, 110p, 125p, 130p, 138p, 150p, 153p, 157p, 208p], [146p, 148p, 152p, 157p, 203p, 208p, 210p, 225p, 230p, 238p, 250p, 253p, 257p, 308p], [246p, 248p, 252p, 257p, 303p, 308p, 310p, 325p, 330p, 338p, 350p, 353p, 357p, 408p], [346p, 348p, 352p, 357p, 403p, 408p, 410p, 425p, 430p, 438p, 450p, 453p, 457p, 508p], [446p, 448p, 452p, 457p, 503p, 508p, 510p, 525p, 530p, 538p, 550p, 553p, 557p, 608p], [546p, 548p, 552p, 557p, 603p, 608p, 610p, 625p, 630p, 637p, 649p, 652p, 655p, 705p]]
  -
  time_points: [Woden Bus Station (Platform 15), Lyons, Chifley, Southlands Mawson, Farrer Terminus, Isaacs, Canberra Hospital, Woden Bus Station]
  long_name: To Woden Bus Station
  between_stops:
  Canberra Hospital-Woden Bus Station: [Wjz3mWn, Wjz3mPO, Wjz3mAg]
  short_name: "23"
  stop_times: [[607a, 609a, 613a, 622a, 628a, 634a, 642a, 647a], [644a, 646a, 650a, 659a, 705a, 711a, 719a, 724a], [714a, 716a, 720a, 729a, 736a, 742a, 752a, 757a], [744a, 748a, 753a, 801a, 808a, 814a, 824a, 829a], [814a, 818a, 823a, 831a, 838a, 844a, 854a, 859a], [844a, 848a, 853a, 901a, 908a, 914a, 924a, 929a], [926a, 930a, 934a, 943a, 949a, 955a, 1003a, 1008a], [1026a, 1028a, 1032a, 1041a, 1047a, 1053a, 1101a, 1106a], [1126a, 1128a, 1132a, 1141a, 1147a, 1153a, 1201p, 1206p], [1226p, 1228p, 1232p, 1241p, 1247p, 1253p, 101p, 106p], [126p, 128p, 132p, 141p, 147p, 153p, 201p, 206p], [226p, 228p, 232p, 241p, 247p, 253p, 301p, 306p], [314p, 318p, 323p, 331p, 338p, 344p, 354p, 359p], [344p, 348p, 353p, 401p, 408p, 414p, 424p, 429p], [414p, 418p, 423p, 431p, 438p, 444p, 454p, 459p], [444p, 448p, 453p, 501p, 508p, 514p, 524p, 529p], [514p, 518p, 523p, 531p, 538p, 544p, 554p, 559p], [544p, 548p, 553p, 601p, 608p, 614p, 624p, 629p], [626p, 630p, 634p, 643p, 649p, 655p, 703p, 708p], [726p, 728p, 732p, 741p, 747p, 753p, 801p, 806p], [826p, 828p, 832p, 841p, 847p, 853p, 901p, 906p], [926p, 928p, 932p, 941p, 947p, 953p, 1001p, 1006p], [1026p, 1028p, 1032p, 1041p, 1047p, 1053p, 1101p, 1106p], [1126p, 1128p, 1132p, 1141p, "-", "-", "-", "-"]]
  -
  time_points: [City West, City Bus Station (Platform 1), Woden Bus Station (Platform 11), Athllon / Sulwood Kambah, Kambah / Livingston St, Taverner St / Erindale Dr, Tuggeranong Bus Station]
  long_name: To Tuggeranong Bus Station
  between_stops:
  Woden Bus Station (Platform 11)-Athllon / Sulwood Kambah: [Wjz3kAx, Wjz3kyX, Wjz3kwU, Wjz3iFK, Wjz3hL_, Wjz3gK-, Wjz3gQn, Wjz3gMq, Wjz2mTK]
  City Bus Station (Platform 1)-Woden Bus Station (Platform 11): [Wjz5Nht, Wjz4KO9, Wjz4KNu, Wjz3eZ4, Wjz3eRR, Wjz3m3b, Wjz3m3b]
  City West-City Bus Station (Platform 1): []
  short_name: 61 161
  stop_times: [["-", "-", 642a, 649a, 654a, 659a, 710a], ["-", "-", 712a, 719a, 724a, 729a, 743a], ["-", "-", 742a, 751a, 756a, 801a, 815a], ["-", "-", 812a, 821a, 826a, 831a, 845a], ["-", "-", 842a, 859a, 905a, 909a, 920a], ["-", "-", 912a, 921a, 926a, 931a, 944a], ["-", "-", 1012a, 1020a, 1025a, 1030a, 1043a], ["-", "-", 1112a, 1120a, 1125a, 1130a, 1143a], ["-", "-", 1212p, 1220p, 1225p, 1230p, 1243p], ["-", "-", 112p, 120p, 125p, 130p, 143p], ["-", "-", 212p, 220p, 225p, 230p, 243p], ["-", "-", 320p, 329p, 334p, 339p, 353p], ["-", "-", 342p, 351p, 356p, 401p, 415p], ["-", "-", 412p, 421p, 426p, 431p, 445p], ["-", "-", 442p, 451p, 456p, 501p, 515p], ["-", "-", 512p, 521p, 526p, 531p, 545p], [520p, 526p, 542p, 551p, 556p, 601p, 615p], ["-", "-", 612p, 621p, 626p, 631p, 644p], ["-", "-", 712p, 720p, 725p, 730p, 743p], ["-", "-", 810p, 818p, 823p, 828p, 841p], ["-", "-", 910p, 918p, 923p, 928p, 941p], ["-", "-", 1010p, 1018p, 1023p, 1028p, 1041p], ["-", "-", 1112p, 1120p, 1125p, 1130p, 1143p], []]
  -
  time_points: [Cohen Street Bus Station (Platform 2), Westfield Bus Station (Platform 1), Belconnen Community Bus Station (Platform 2), William Webb / Ginninderra Drive, Chuculba / William Slim Dr, Gungahlin Marketplace, Kosciuszko / Everard, Flemington Rd / Sandford St, Macarthur / Northbourne Ave, City Bus Station]
  long_name: To City Bus Station
  between_stops:
  Macarthur / Northbourne Ave-City Bus Station: [Wjz5QmR, Wjz5Qmu, Wjz5Qgn, Wjz5Qi2, Wjz5Pl0, Wjz5PdJ, Wjz5P8K, Wjz5P8n, Wjz5Oci, Wjz5O3Q]
  Westfield Bus Station (Platform 1)-Belconnen Community Bus Station (Platform 2): []
  Cohen Street Bus Station (Platform 2)-Westfield Bus Station (Platform 1): []
  short_name: "956"
  stop_times_sunday: [[841a, 843a, 847a, 853a, 858a, 908a, 918a, 925a, 933a, 940a], [941a, 943a, 947a, 953a, 958a, 1008a, 1018a, 1025a, 1033a, 1040a], [1041a, 1043a, 1047a, 1053a, 1058a, 1108a, 1118a, 1125a, 1133a, 1140a], [1141a, 1143a, 1147a, 1153a, 1158a, 1208p, 1218p, 1225p, 1233p, 1240p], [1241p, 1243p, 1247p, 1253p, 1258p, 108p, 118p, 125p, 133p, 140p], [141p, 143p, 147p, 153p, 158p, 208p, 218p, 225p, 233p, 240p], [241p, 243p, 247p, 253p, 258p, 308p, 318p, 325p, 333p, 340p], [341p, 343p, 347p, 353p, 358p, 408p, 418p, 425p, 433p, 440p], [441p, 443p, 447p, 453p, 458p, 508p, 518p, 525p, 533p, 540p], [541p, 543p, 547p, 553p, 558p, 608p, 618p, 625p, 633p, 640p], [641p, 643p, 647p, 653p, 658p, 708p, 718p, 725p, 733p, 740p]]
  -
  time_points: [City Bus Station (Platform 7), Russell Offices, Brindabella Business Park, Fairbairn Park]
  long_name: To Fairbairn Park
  between_stops:
  Brindabella Business Park-Fairbairn Park: [WjzcrK3, WjzcrrQ, WjzcrEu, WjzcJ0K, WjzcBHZ, WjzcJ38]
  City Bus Station (Platform 7)-Russell Offices: [Wjz5Nht, Wjz5MsD, Wjz5MsT, Wjz5MEL, Wjz4T-X, Wjz4_jm, Wjz4_wS, Wjz4-KO, Wjz4-Rc, Wjz4-WL, Wjz4-WZ]
  short_name: "737"
  stop_times: [[643a, 651a, 705a, "-"], [658a, 706a, 720a, "-"], [718a, 726a, 740a, "-"], [738a, 746a, 800a, "-"], [758a, 806a, 820a, 830a], [818a, 826a, 840a, 850a]]
  -
  time_points: [Cohen Street Bus Station (Platform 1), Westfield Bus Station (Platform 1), Belconnen Community Bus Station (Platform 3), University of Canberra, Australian Institute of Sport, National Hockey Centre Lyneham, Macarthur / Northbourne Ave, City Bus Station (Platform 9), Russell Offices, Railway Station Kingston, Newcastle Street after Isa Street, Fyshwick Direct Factory Outlet, Lithgow St Terminus Fyshwick]
  long_name: To Lithgow St Terminus Fyshwick
  between_stops:
  Westfield Bus Station (Platform 1)-Belconnen Community Bus Station (Platform 3): []
  Cohen Street Bus Station (Platform 1)-Westfield Bus Station (Platform 1): []
  Belconnen Community Bus Station (Platform 3)-University of Canberra: [Wjz681S, Wjz689c, Wjz68Ip, Wjz68IH, Wjz68Y0, Wjz68Yy]
  stop_times_saturday: [[720a, 722a, 726a, 734a, 740a, 745a, 751a, 759a, 808a, 814a, 822a, 831a, 840a], [820a, 822a, 826a, 834a, 840a, 845a, 851a, 859a, 908a, 914a, 922a, 931a, 940a], [920a, 922a, 926a, 934a, 940a, 945a, 951a, 959a, 1008a, 1014a, 1022a, 1031a, 1040a], [1020a, 1022a, 1026a, 1034a, 1040a, 1045a, 1051a, 1059a, 1108a, 1114a, 1122a, 1131a, 1140a], [1120a, 1122a, 1126a, 1134a, 1140a, 1145a, 1151a, 1159a, 1208p, 1214p, 1222p, 1231p, 1240p], [1220p, 1222p, 1226p, 1234p, 1240p, 1245p, 1251p, 1259p, 108p, 114p, 122p, 131p, 140p], [120p, 122p, 126p, 134p, 140p, 145p, 151p, 159p, 208p, 214p, 222p, 231p, 240p], [220p, 222p, 226p, 234p, 240p, 245p, 251p, 259p, 308p, 314p, 322p, 331p, 340p], [320p, 322p, 326p, 334p, 340p, 345p, 351p, 359p, 408p, 414p, 422p, 431p, 440p], ["-", "-", "-", "-", "-", "-", "-", 415p, 424p, 430p, "-", "-", "-"], [420p, 422p, 426p, 434p, 440p, 445p, 451p, 459p, 508p, 514p, 522p, 531p, 540p], [520p, 522p, 526p, 534p, 540p, 545p, 551p, 558p, "-", "-", "-", "-", "-"], [615p, 617p, 621p, 629p, 635p, 640p, 645p, 652p, "-", "-", "-", "-", "-"], [725p, 727p, 732p, 739p, 745p, 750p, 755p, 802p, "-", "-", "-", "-", "-"], [834p, 836p, 841p, 848p, 854p, 859p, 904p, 911p, "-", "-", "-", "-", "-"], [945p, 947p, 952p, 959p, 1005p, 1010p, 1015p, 1022p, "-", "-", "-", "-", "-"], [1057p, 1059p, 1104p, 1111p, 1117p, 1122p, 1127p, 1134p, "-", "-", "-", "-", "-"]]
  short_name: "980"
  -
  time_points: [Tuggeranong Bus Station (Platform 3), MacKillop College Isabella Campus, Theodore, Calwell, Erindale Centre, Woden Bus Station (Platform 9), City Bus Station]
long_name: To City Bus Station long_name: To City Bus Station
between_stops: between_stops:
Woden Bus Station (Platform 9)-City Bus Station: [Wjz3m3b, Wjz3m3b, Wjz3eRR, Wjz3eZ4, Wjz4KNu, Wjz4KO9, Wjz5Nht] Woden Bus Station (Platform 9)-City Bus Station: [Wjz3m3b, Wjz3m3b, Wjz3eRR, Wjz3eZ4, Wjz4KNu, Wjz4KO9, Wjz5Nht]
short_name: 11 111 short_name: 11 111
stop_times: [[621a, 627a, 641a, 651a, 657a, 713a, 729a], [641a, 647a, 701a, 711a, 717a, 733a, 751a], [701a, 707a, 721a, 731a, 737a, 754a, 812a], [721a, 727a, 742a, 752a, 758a, 815a, 833a], [741a, 748a, 803a, 813a, 819a, 836a, 857a], [801a, 808a, 823a, 833a, 839a, 856a, 914a], [821a, 828a, 843a, 853a, 859a, 914a, "-"], [841a, 848a, 903a, 913a, 919a, 933a, "-"], [921a, 927a, 940a, 949a, 955a, 1007a, "-"], [951a, 957a, 1010a, 1019a, 1025a, 1037a, "-"], [1021a, 1027a, 1040a, 1049a, 1055a, 1107a, "-"], [1051a, 1057a, 1110a, 1119a, 1125a, 1137a, "-"], [1121a, 1127a, 1140a, 1149a, 1155a, 1207p, "-"], [1151a, 1157a, 1210p, 1219p, 1225p, 1237p, "-"], [1221p, 1227p, 1240p, 1249p, 1255p, 107p, "-"], [1251p, 1257p, 110p, 119p, 125p, 137p, "-"], [121p, 127p, 140p, 149p, 155p, 207p, "-"], [151p, 157p, 210p, 219p, 225p, 237p, "-"], [221p, 227p, 240p, 249p, 255p, 307p, "-"], [251p, 257p, 310p, 319p, 325p, 339p, "-"], [323p, 330p, 345p, 355p, 401p, 421p, "-"], [340p, 347p, 402p, 412p, 418p, 433p, "-"], [400p, 407p, 422p, 432p, 438p, 453p, "-"], [418p, 425p, 440p, 450p, 456p, 511p, "-"], [441p, 448p, 503p, 513p, 519p, "-", "-"], [501p, 508p, 523p, 533p, 539p, "-", "-"], [521p, 528p, 543p, 553p, 559p, 614p, "-"], [541p, 548p, 603p, 613p, 619p, "-", "-"], [601p, 608p, 623p, 633p, 639p, "-", "-"], [625p, 632p, 645p, 654p, 700p, 712p, "-"], [725p, 731p, 744p, 753p, 759p, 811p, "-"], [825p, 831p, 844p, 853p, 859p, 911p, "-"], [925p, 931p, 944p, 953p, 959p, 1011p, "-"], [1025p, 1031p, 1044p, 1053p, 1059p, 1111p, "-"], [1125p, 1131p, 1144p, 1153p, 1159p, "-", "-"]] stop_times: [[621a, 627a, 641a, 651a, 657a, 713a, 729a], [641a, 647a, 701a, 711a, 717a, 733a, 751a], [701a, 707a, 721a, 731a, 737a, 754a, 812a], [721a, 727a, 742a, 752a, 758a, 815a, 833a], [741a, 748a, 803a, 813a, 819a, 836a, 857a], [801a, 808a, 823a, 833a, 839a, 856a, 914a], [821a, 828a, 843a, 853a, 859a, 914a, "-"], [841a, 848a, 903a, 913a, 919a, 933a, "-"], [921a, 927a, 940a, 949a, 955a, 1007a, "-"], [951a, 957a, 1010a, 1019a, 1025a, 1037a, "-"], [1021a, 1027a, 1040a, 1049a, 1055a, 1107a, "-"], [1051a, 1057a, 1110a, 1119a, 1125a, 1137a, "-"], [1121a, 1127a, 1140a, 1149a, 1155a, 1207p, "-"], [1151a, 1157a, 1210p, 1219p, 1225p, 1237p, "-"], [1221p, 1227p, 1240p, 1249p, 1255p, 107p, "-"], [1251p, 1257p, 110p, 119p, 125p, 137p, "-"], [121p, 127p, 140p, 149p, 155p, 207p, "-"], [151p, 157p, 210p, 219p, 225p, 237p, "-"], [221p, 227p, 240p, 249p, 255p, 307p, "-"], [251p, 257p, 310p, 319p, 325p, 339p, "-"], [323p, 330p, 345p, 355p, 401p, 421p, "-"], [340p, 347p, 402p, 412p, 418p, 433p, "-"], [400p, 407p, 422p, 432p, 438p, 453p, "-"], [418p, 425p, 440p, 450p, 456p, 511p, "-"], [441p, 448p, 503p, 513p, 519p, "-", "-"], [501p, 508p, 523p, 533p, 539p, "-", "-"], [521p, 528p, 543p, 553p, 559p, 614p, "-"], [541p, 548p, 603p, 613p, 619p, "-", "-"], [601p, 608p, 623p, 633p, 639p, "-", "-"], [625p, 632p, 645p, 654p, 700p, 712p, "-"], [725p, 731p, 744p, 753p, 759p, 811p, "-"], [825p, 831p, 844p, 853p, 859p, 911p, "-"], [925p, 931p, 944p, 953p, 959p, 1011p, "-"], [1025p, 1031p, 1044p, 1053p, 1059p, 1111p, "-"], [1125p, 1131p, 1144p, 1153p, 1159p, "-", "-"]]
- -
time_points: [City Bus Station (Platform 7), Kings Ave / National Circuit, Manuka, Red Hill Shops, Narrabundah Terminus, Red Hill Shops, Manuka, Kings Ave / National Circuit, City Bus Station] time_points: [City Bus Station (Platform 8), Ainslie, Hackett, Dickson / Antill St, North Lyneham, Lyneham, Macarthur / Miller O'Connor, City Bus Station]
long_name: To City Bus Station long_name: To City Bus Station
between_stops: between_stops:
City Bus Station (Platform 7)-Kings Ave / National Circuit: [Wjz5FOn, Wjz4S1U, Wjz4Rs-, Wjz4RFJ, Wjz4RwH, Wjz4Quk] Ainslie-Hackett: [Wjz5YKO, Wjz5ZO1, Wjz5ZZQ, Wjzd68O, Wjzd6iW, Wjzd6lW, Wjzd6Cq, Wjzd6Pn, Wjzd6XP, WjzdeeQ]
Kings Ave / National Circuit-City Bus Station: [Wjz4Quk, Wjz4RwH, Wjz4RFJ, Wjz4Rs-, Wjz4S1U, Wjz5FOn] stop_times_saturday: [[759a, 811a, 819a, 825a, 834a, 839a, 842a, 851a], [859a, 911a, 919a, 925a, 934a, 939a, 942a, 951a], [959a, 1011a, 1019a, 1025a, 1034a, 1039a, 1042a, 1051a], [1059a, 1111a, 1119a, 1125a, 1134a, 1139a, 1142a, 1151a], [1159a, 1211p, 1219p, 1225p, 1234p, 1239p, 1242p, 1251p], [1259p, 111p, 119p, 125p, 134p, 139p, 142p, 151p], [159p, 211p, 219p, 225p, 234p, 239p, 242p, 251p], [259p, 311p, 319p, 325p, 334p, 339p, 342p, 351p], [359p, 411p, 419p, 425p, 434p, 439p, 442p, 451p], [459p, 511p, 519p, 525p, 534p, 539p, 542p, 551p], [559p, 611p, 619p, 625p, 634p, 639p, 642p, 651p], [659p, 711p, 719p, 725p, 734p, 739p, 742p, 751p], [749p, 801p, 809p, 815p, 824p, 829p, 832p, 841p], [849p, 901p, 909p, 915p, 924p, 929p, 932p, 941p], [949p, 1001p, 1009p, 1015p, 1024p, 1029p, 1032p, 1041p], [1049p, 1101p, 1109p, 1115p, 1124p, 1129p, 1132p, 1141p]]
short_name: "935" short_name: "937"
stop_times: [["-", "-", "-", "-", 824a, 833a, 839a, 843a, 852a], [856a, 903a, 907a, 914a, 924a, 933a, 939a, 943a, 952a], [956a, 1003a, 1007a, 1014a, 1024a, 1033a, 1039a, 1043a, 1052a], [1056a, 1103a, 1107a, 1114a, 1124a, 1133a, 1139a, 1143a, 1152a], [1156a, 1203p, 1207p, 1214p, 1224p, 1233p, 1239p, 1243p, 1252p], [1256p, 103p, 107p, 114p, 124p, 133p, 139p, 143p, 152p], [156p, 203p, 207p, 214p, 224p, 233p, 239p, 243p, 252p], [256p, 303p, 307p, 314p, 324p, 333p, 339p, 343p, 352p], [356p, 403p, 407p, 414p, 424p, 433p, 439p, 443p, 452p], [456p, 503p, 507p, 514p, 524p, 533p, 539p, 543p, 552p], [556p, 603p, 607p, 614p, 624p, 633p, 639p, 643p, 652p], [656p, 703p, 707p, 714p, 724p, 733p, 739p, 743p, 752p]] -
- time_points: [Woden Bus Station (Platform 11), Athllon / Sulwood Kambah, Erindale Centre, Tuggeranong Bus Station]
time_points: [Bimberi Centre, Northbourne Avenue / Antill St, Macarthur / Northbourne Ave, City Bus Station] long_name: To Tuggeranong Bus Station
long_name: To City Bus Station between_stops:
between_stops: Woden Bus Station (Platform 11)-Athllon / Sulwood Kambah: [Wjz3kAx, Wjz3kyX, Wjz3kwU, Wjz3iFK, Wjz3hL_, Wjz3gK-, Wjz3gQn, Wjz3gMq, Wjz2mTK]
  stop_times_saturday: [[905a, 914a, 926a, 937a], [1005a, 1014a, 1026a, 1037a], [1105a, 1114a, 1126a, 1137a], [1205p, 1214p, 1226p, 1237p], [105p, 114p, 126p, 137p], [205p, 214p, 226p, 237p], [305p, 314p, 326p, 337p], [405p, 414p, 426p, 437p], [505p, 514p, 526p, 537p], [605p, 614p, 626p, 637p], [705p, 714p, 726p, 737p], [805p, 814p, 826p, 837p], [905p, 914p, 926p, 937p], [1005p, 1014p, 1026p, 1037p], [1105p, 1114p, 1126p, 1137p]]
  short_name: "964"
  -
  time_points: [Cohen Street Bus Station (Platform 1), Westfield Bus Station (Platform 1), Belconnen Community Bus Station (Platform 2), Chuculba / William Slim Dr, Federation Square, Nicholls Primary, Gungahlin Marketplace]
  long_name: To Gungahlin Market Place
  between_stops:
  Belconnen Community Bus Station (Platform 2)-Chuculba / William Slim Dr: [Wjz69gA, Wjz69ht, Wjz69uI, Wjz69vO, Wjz6mip]
  Cohen Street Bus Station (Platform 1)-Westfield Bus Station (Platform 1): []
  Westfield Bus Station (Platform 1)-Belconnen Community Bus Station (Platform 2): []
  short_name: "952"
  stop_times_sunday: [[945a, 947a, 951a, 1004a, 1009a, 1022a, 1031a], [1045a, 1047a, 1051a, 1104a, 1109a, 1122a, 1131a], [1145a, 1147a, 1151a, 1204p, 1209p, 1222p, 1231p], [1245p, 1247p, 1251p, 104p, 109p, 122p, 131p], [145p, 147p, 151p, 204p, 209p, 222p, 231p], [245p, 247p, 251p, 304p, 309p, 322p, 331p], [345p, 347p, 351p, 404p, 409p, 422p, 431p], [445p, 447p, 451p, 504p, 509p, 522p, 531p], [545p, 547p, 551p, 604p, 609p, 622p, 631p], [645p, 647p, 651p, 704p, 709p, 722p, 731p]]
  -
  time_points: [Tuggeranong Bus Station (Platform 7), Bonython Primary School, Deamer / Clift Richardson, Proctor / Mead, Erindale Centre, Woden Bus Station]
  long_name: To Woden Bus Station
  between_stops: {}
   
  short_name: "66"
  stop_times: [[612a, 618a, 625a, 631a, 638a, 652a], [641a, 647a, 654a, 700a, 712a, 727a], [706a, 714a, 723a, 732a, 744a, 800a], [736a, 744a, 753a, 802a, 814a, 830a], [806a, 814a, 823a, 832a, 844a, 900a], [836a, 844a, 853a, 902a, 914a, 930a], [909a, 917a, 926a, 933a, 941a, 956a], [1012a, 1018a, 1026a, 1032a, 1040a, 1055a], [1112a, 1118a, 1126a, 1132a, 1140a, 1155a], [1212p, 1218p, 1226p, 1232p, 1240p, 1255p], [112p, 118p, 126p, 132p, 140p, 155p], [212p, 218p, 226p, 232p, 240p, 255p], [312p, 319p, 327p, 334p, 345p, 400p], [412p, 419p, 427p, 434p, 445p, 500p], [442p, 449p, 457p, 504p, 515p, 530p], [512p, 519p, 527p, 534p, 545p, 600p], [542p, 549p, 557p, 604p, 615p, 630p], [613p, 620p, 628p, 634p, 642p, 657p], [714p, 720p, 728p, 734p, 742p, 757p], [814p, 820p, 828p, 834p, 842p, 857p], [914p, 920p, 928p, 934p, 942p, 957p], [1014p, 1020p, 1028p, 1034p, 1042p, 1057p], [1114p, 1120p, 1128p, 1134p, 1142p, "-"]]
  -
  time_points: [City Bus Station (Platform 8), Macarthur / Northbourne Ave, Dickson / Antill St, Watson, Watson Terminus, Watson, Dickson / Antill St, Northbourne Avenue / Antill St, Macarthur / Northbourne Ave, City Bus Station]
  long_name: To City Bus Station
  between_stops:
  City Bus Station (Platform 8)-Macarthur / Northbourne Ave: [Wjz5O3Q, Wjz5Oci, Wjz5P8n, Wjz5P8K, Wjz5PdJ, Wjz5Pl0, Wjz5Qi2, Wjz5Qgn, Wjz5Qmu, Wjz5QmR]
Macarthur / Northbourne Ave-City Bus Station: [Wjz5QmR, Wjz5Qmu, Wjz5Qgn, Wjz5Qi2, Wjz5Pl0, Wjz5PdJ, Wjz5P8K, Wjz5P8n, Wjz5Oci, Wjz5O3Q] Macarthur / Northbourne Ave-City Bus Station: [Wjz5QmR, Wjz5Qmu, Wjz5Qgn, Wjz5Qi2, Wjz5Pl0, Wjz5PdJ, Wjz5P8K, Wjz5P8n, Wjz5Oci, Wjz5O3Q]
short_name: "982" short_name: "39"
stop_times_sunday: [[715p, 724p, 726p, 733p]] stop_times: [["-", "-", "-", 549a, 555a, 601a, 606a, 607a, 610a, 617a], [609a, 615a, 618a, 624a, 630a, 636a, 641a, 642a, 645a, 652a], [639a, 645a, 648a, 654a, 700a, 706a, 711a, 712a, 715a, 722a], ["-", "-", "-", 707a, 713a, 719a, 724a, 725a, 728a, 741a], [703a, 709a, 712a, 718a, 724a, 730a, 736a, 737a, 742a, 757a], ["-", "-", "-", 726a, 732a, 738a, 744a, 745a, 750a, 805a], [718a, 724a, 727a, 734a, 740a, 746a, 752a, 753a, 758a, 813a], ["-", "-", "-", 742a, 748a, 754a, 800a, 801a, 806a, 821a], [733a, 739a, 742a, 749a, 755a, 801a, 807a, 808a, 813a, 828a], ["-", "-", "-", 756a, 802a, 808a, 814a, 815a, 820a, 835a], [748a, 754a, 757a, 804a, 810a, 816a, 822a, 823a, 828a, 843a], [758a, 804a, 807a, 814a, 820a, 826a, 832a, 833a, 838a, 853a], ["-", "-", "-", 824a, 830a, 836a, 842a, 843a, 848a, 903a], [818a, 824a, 827a, 834a, 840a, 846a, 852a, 853a, 858a, 913a], [833a, 839a, 842a, 849a, 855a, 901a, 907a, 908a, 913a, 928a], [910a, 918a, 924a, 929a, 935a, 942a, 949a, 952a, 954a, 1001a], [940a, 946a, 949a, 954a, 1000a, 1005a, 1010a, 1011a, 1013a, 1019a], [1010a, 1016a, 1019a, 1024a, 1030a, 1035a, 1040a, 1041a, 1043a, 1049a], [1040a, 1046a, 1049a, 1054a, 1100a, 1105a, 1110a, 1111a, 1113a, 1119a], [1110a, 1116a, 1119a, 1124a, 1130a, 1135a, 1140a, 1141a, 1143a, 1149a], [1140a, 1146a, 1149a, 1154a, 1200p, 1205p, 1210p, 1211p, 1213p, 1219p], [1210p, 1216p, 1219p, 1224p, 1230p, 1235p, 1240p, 1241p, 1243p, 1249p], [1240p, 1246p, 1249p, 1254p, 100p, 105p, 110p, 111p, 113p, 119p], [110p, 116p, 119p, 124p, 130p, 135p, 140p, 141p, 143p, 149p], [140p, 146p, 149p, 154p, 200p, 205p, 210p, 211p, 213p, 219p], [210p, 216p, 219p, 224p, 230p, 235p, 240p, 241p, 243p, 249p], [240p, 246p, 249p, 254p, 300p, 307p, 313p, 314p, 317p, 324p], [309p, 315p, 318p, 324p, 330p, 337p, 343p, 344p, 347p, 354p], [328p, 334p, 337p, 343p, 349p, 356p, 402p, 403p, 406p, 413p], [358p, 404p, 407p, 413p, 419p, 426p, 432p, 433p, 436p, 443p], [417p, 423p, 426p, 432p, 438p, 445p, 451p, 452p, 455p, 502p], [432p, 438p, 441p, 447p, 453p, 500p, 506p, 507p, 510p, 517p], [447p, 453p, 456p, 502p, 508p, 515p, 521p, 522p, 525p, 532p], [506p, 512p, 515p, 521p, 527p, 534p, 540p, 541p, 544p, 551p], [512p, 518p, 521p, 527p, 533p, 540p, "-", "-", "-", "-"], [521p, 527p, 530p, 536p, 542p, 549p, 555p, 556p, 559p, 606p], [536p, 542p, 545p, 551p, 557p, 604p, 610p, 611p, 614p, 621p], [546p, 552p, 555p, 601p, 607p, 614p, "-", "-", "-", "-"], [555p, 601p, 604p, 610p, 616p, 623p, 629p, 630p, 632p, 638p], [610p, 616p, 619p, 625p, 631p, 636p, 641p, 642p, 644p, 650p], [710p, 716p, 719p, 724p, 730p, 735p, 740p, 741p, 743p, 749p], [810p, 816p, 819p, 824p, 830p, 835p, 840p, 841p, 843p, 849p], [910p, 916p, 919p, 924p, 930p, 935p, 940p, 941p, 943p, 949p], [1010p, 1016p, 1019p, 1024p, 1030p, 1035p, 1040p, 1041p, 1043p, 1049p], [1110p, 1116p, 1119p, 1124p, 1130p, 1135p, "-", "-", "-", "-"]]
- -
time_points: [Fraser West Terminus, Charnwood Shops, Scullin Shops, Page Shops, Cohen Street Bus Station (Platform 3), Westfield Bus Station (Platform 1), Belconnen Community Bus Station (Platform 1), City Bus Station (Platform 1), Woden Bus Station (Platform 6), Tuggeranong Bus Station] time_points: [Tuggeranong Bus Station (Platform 8), Woden Bus Station (Platform 9), City Bus Station (Platform 3), Belconnen Community Bus Station (Platform 4), Westfield Bus Station (Platform 2), Cohen Street Bus Station (Platform 6), Page, Scullin, Charnwood, Fraser West Terminus]
  long_name: To Fraser West Terminus
  between_stops:
  Belconnen Community Bus Station (Platform 4)-Westfield Bus Station (Platform 2): []
  Tuggeranong Bus Station (Platform 8)-Woden Bus Station (Platform 9): [Wjz213q, Wjz238T, Wjz239F, Wjz2lDC, Wjz2mGO, Wjz2mTK, Wjz2nLE, Wjz3gMq, Wjz3gQn, Wjz3gK-, Wjz3hL_, Wjz3iFK, Wjz3kwU, Wjz3kyX, Wjz3kAx]
  Westfield Bus Station (Platform 2)-Cohen Street Bus Station (Platform 6): []
  City Bus Station (Platform 3)-Belconnen Community Bus Station (Platform 4): [Wjz5F-1, Wjz5FSY, Wjz5GMT, Wjz5GNG, Wjz5G6U, Wjz5G6B, Wjz5maK, Wjz5mbS, Wjz5nwb, Wjz5nw6, Wjz6giR, Wjz6gia, Wjz68W3, Wjz68W5, Wjz689c, Wjz681S]
  Woden Bus Station (Platform 9)-City Bus Station (Platform 3): [Wjz3m3b, Wjz3m3b, Wjz3eRR, Wjz3eZ4, Wjz4KNu, Wjz4KO9, Wjz5Nht]
  short_name: 13 313
  stop_times: [["-", "-", "-", 733a, 735a, 739a, 742a, 746a, 755a, 803a], [710a, 728a, 746a, 807a, 809a, 813a, 816a, 820a, 829a, 837a], [751a, 810a, 828a, 849a, 851a, 855a, 858a, 902a, 911a, 919a], [811a, 830a, 848a, 909a, 911a, 915a, 918a, 922a, 931a, 938a], [850a, 909a, 927a, 947a, 949a, 953a, 955a, 959a, 1007a, 1014a], [921a, 940a, 956a, 1016a, 1018a, 1022a, 1024a, 1028a, 1036a, 1043a], [951a, 1009a, 1025a, 1045a, 1047a, 1051a, 1053a, 1057a, 1105a, 1112a], [1021a, 1039a, 1055a, 1115a, 1117a, 1121a, 1123a, 1127a, 1135a, 1142a], [1051a, 1109a, 1125a, 1145a, 1147a, 1151a, 1153a, 1157a, 1205p, 1212p], [1121a, 1139a, 1155a, 1215p, 1217p, 1221p, 1223p, 1227p, 1235p, 1242p], [1151a, 1209p, 1225p, 1245p, 1247p, 1251p, 1253p, 1257p, 105p, 112p], [1221p, 1239p, 1255p, 115p, 117p, 121p, 123p, 127p, 135p, 142p], [1251p, 109p, 125p, 145p, 147p, 151p, 153p, 157p, 205p, 212p], [121p, 139p, 155p, 215p, 217p, 221p, 223p, 227p, 235p, 242p], [151p, 209p, 225p, 245p, 247p, 251p, 253p, 257p, 306p, 313p], [221p, 239p, 255p, 316p, 318p, 322p, 325p, 330p, 340p, 347p], [250p, 308p, 326p, 347p, 349p, 353p, 356p, 401p, 411p, 418p], [316p, 335p, 353p, 414p, 416p, 420p, 423p, 428p, 438p, 445p], [346p, 405p, 423p, 444p, 446p, 450p, 453p, 458p, 508p, 515p], [406p, 425p, 443p, 504p, 506p, 510p, 513p, 518p, 528p, 535p], [426p, 445p, 503p, 524p, 526p, 530p, 533p, 538p, 548p, 555p], [446p, 505p, 523p, 544p, 546p, 550p, 553p, 558p, 608p, 615p], [526p, 545p, 603p, 624p, 626p, 630p, 632p, 636p, 644p, 651p], [556p, 615p, 632p, 652p, 654p, 658p, 700p, 704p, 712p, 719p], [656p, 713p, 728p, 748p, 750p, 754p, 756p, 800p, 808p, 815p], ["-", "-", "-", 835p, 837p, 841p, 843p, 847p, 855p, 902p], ["-", "-", "-", 935p, 937p, 941p, 943p, 947p, 955p, 1002p], ["-", "-", "-", 1034p, 1036p, 1040p, 1042p, 1046p, 1054p, 1101p]]
  -
  time_points: [Woden Bus Station (Platform 2), Canberra Hospital, Saint Andrews Village Hughes, Brindabella Gardens Nursing Home, Woden Bus Station]
  long_name: To Woden Bus Station
  between_stops: {}
   
  short_name: "77"
  stop_times: [[1100a, 1108a, 1113a, 1121a, 1128a], [100p, 108p, 113p, 121p, 128p]]
  -
  time_points: [City Bus Station (Platform 8), Macarthur / Northbourne Ave, Northbourne Avenue / Antill St, Flemington Rd / Sandford St, Hibberson / Kate Crace, Gungahlin Marketplace, Ngunnawal Primary, Nicholls Primary, Federation Square, Chuculba / William Slim Dr, Belconnen Community Bus Station, Westfield Bus Station, Cohen Street Bus Station]
  long_name: To Cohen Street Bus Station
  between_stops:
  Westfield Bus Station-Cohen Street Bus Station: []
  Macarthur / Northbourne Ave-Northbourne Avenue / Antill St: [Wjz5RkN, Wjz5Rsi, Wjz5RvC, Wjz5Sqk, Wjz5SrO, Wjz5Sux, Wjz5SDc]
  City Bus Station (Platform 8)-Macarthur / Northbourne Ave: [Wjz5O3Q, Wjz5Oci, Wjz5P8n, Wjz5P8K, Wjz5PdJ, Wjz5Pl0, Wjz5Qi2, Wjz5Qgn, Wjz5Qmu, Wjz5QmR]
  Belconnen Community Bus Station-Westfield Bus Station: []
  Chuculba / William Slim Dr-Belconnen Community Bus Station: [Wjz6mip, Wjz69vO, Wjz69uI, Wjz69ht, Wjz69gA]
  short_name: "51"
  stop_times: [["-", "-", "-", "-", 701a, 704a, 713a, 723a, 733a, 738a, 750a, 752a, 757a], ["-", "-", "-", "-", 721a, 724a, 733a, 743a, 753a, 758a, 811a, 813a, 818a], ["-", "-", "-", "-", 741a, 744a, 753a, 803a, 813a, 818a, 831a, 833a, 838a], ["-", "-", "-", "-", 800a, 803a, 812a, 822a, 832a, 837a, 850a, 852a, 857a], ["-", "-", "-", "-", 821a, 824a, 833a, 843a, 853a, 858a, 908a, 910a, 915a], ["-", "-", "-", "-", 840a, 843a, 852a, 902a, 911a, 916a, 925a, 927a, 932a], ["-", "-", "-", "-", 940a, 943a, 952a, 1001a, 1010a, 1015a, 1024a, 1026a, 1031a], ["-", "-", "-", "-", 1040a, 1043a, 1052a, 1101a, 1110a, 1115a, 1124a, 1126a, 1131a], ["-", "-", "-", "-", 1140a, 1143a, 1152a, 1201p, 1210p, 1215p, 1224p, 1226p, 1231p], ["-", "-", "-", "-", 1240p, 1243p, 1252p, 101p, 110p, 115p, 124p, 126p, 131p], ["-", "-", "-", "-", 140p, 143p, 152p, 201p, 210p, 215p, 224p, 226p, 231p], ["-", "-", "-", "-", 240p, 243p, 252p, 301p, 310p, 315p, 324p, 326p, 331p], ["-", "-", "-", "-", 307p, 310p, 319p, 328p, 337p, 342p, 351p, 353p, 358p], [332p, 338p, 340p, 348p, 351p, 354p, 403p, 413p, 423p, 428p, 438p, 440p, 445p], [406p, 412p, 414p, 423p, 428p, 431p, 440p, 450p, 500p, 505p, 515p, 517p, 522p], [428p, 434p, 436p, 445p, 450p, 453p, 502p, 512p, 522p, 527p, 537p, 539p, 544p], [444p, 450p, 452p, 501p, 506p, 509p, 518p, 528p, 538p, 543p, 553p, 555p, 600p], [511p, 517p, 519p, 528p, 533p, 536p, 545p, 555p, 605p, 610p, 619p, 621p, 626p], [529p, 535p, 537p, 546p, 551p, 554p, 603p, 612p, 621p, 626p, 635p, 637p, 642p], [538p, 544p, 546p, 555p, 600p, 603p, 612p, 621p, 630p, 635p, 644p, 646p, 651p], [554p, 600p, 602p, 609p, 612p, 615p, 624p, 633p, 642p, 647p, 656p, 658p, 703p], [616p, 620p, 622p, 629p, 632p, 635p, 644p, 653p, 702p, 707p, 716p, 718p, 723p], ["-", "-", "-", "-", 740p, 743p, 752p, 801p, 810p, 815p, 824p, 826p, 831p], ["-", "-", "-", "-", 840p, 843p, 852p, 901p, 910p, 915p, 924p, 926p, 931p], ["-", "-", "-", "-", 940p, 943p, 952p, 1001p, 1010p, 1015p, 1024p, 1026p, 1031p], ["-", "-", "-", "-", 1040p, 1043p, 1052p, 1101p, 1110p, 1115p, 1124p, 1126p, 1131p], ["-", "-", "-", "-", 1140p, 1143p, 1152p, 1201a, 1210a, 1215a, 1224a, 1226a, 1231a]]
  -
  time_points: [City Bus Station (Platform 8), ADFA, Hospice / Menindee Dr, St Thomas More's Campbell, City Bus Station]
  long_name: To City Bus Station
  between_stops:
  ADFA-Hospice / Menindee Dr: [WjzceHt, WjzceFT, WjzcdDs, Wjzcdsn, Wjzcdi7, Wjzcd2U, Wjzcd8D]
  short_name: "931"
  stop_times_sunday: [[901a, 915a, 922a, 929a, 941a], [1101a, 1115a, 1122a, 1129a, 1141a], [101p, 115p, 122p, 129p, 141p], [301p, 315p, 322p, 329p, 341p], [501p, 515p, 522p, 529p, 541p], [701p, 715p, 722p, 729p, 741p]]
  -
  time_points: [Tuggeranong Bus Station (Platform 3), Kambah High, Mount Neighbour School, Woden Bus Station]
  long_name: To Woden Bus Station
  between_stops: {}
   
  short_name: "960"
  stop_times_sunday: [[755a, 805a, 811a, 823a], [855a, 905a, 911a, 923a], [955a, 1005a, 1011a, 1023a], [1055a, 1105a, 1111a, 1123a], [1155a, 1205p, 1211p, 1223p], [1255p, 105p, 111p, 123p], [155p, 205p, 211p, 223p], [255p, 305p, 311p, 323p], [355p, 405p, 411p, 423p], [455p, 505p, 511p, 523p], [555p, 605p, 611p, 623p], [655p, 705p, 711p, 721p]]
  -
  time_points: [Woden Bus Station (Platform 15), Pearce, Isaacs, Farrer Primary School, Southlands Mawson, Woden Bus Station]
  long_name: To Woden Bus Station
  between_stops: {}
   
  short_name: "923"
  stop_times_sunday: [[910a, 916a, 921a, 927a, 933a, 943a], [1110a, 1116a, 1121a, 1127a, 1133a, 1143a], [110p, 116p, 121p, 127p, 133p, 143p], [310p, 316p, 321p, 327p, 333p, 343p], [510p, 516p, 521p, 527p, 533p, 543p]]
  -
  time_points: [Woden Bus Station (Platform 15), Lyons, Chifley, Torrens, Southlands Mawson, Pearce, Woden Bus Station]
  long_name: To Woden Bus Station
  between_stops: {}
   
  stop_times_saturday: [[933a, 936a, 940a, 945a, 951a, 955a, 1001a], [1133a, 1136a, 1140a, 1145a, 1151a, 1155a, 1201p], [133p, 136p, 140p, 145p, 151p, 155p, 201p], [333p, 336p, 340p, 345p, 351p, 355p, 401p], [533p, 536p, 540p, 545p, 551p, 555p, 601p], [733p, 736p, 740p, 745p, 751p, 755p, 801p], [933p, 936p, 940p, 945p, 951p, 955p, 1001p], [1133p, 1136p, 1140p, 1145p, 1151p, 1155p, "-"]]
  short_name: "921"
  -
  time_points: [Cohen Street Bus Station (Platform 2), Westfield Bus Station (Platform 1), Belconnen Community Bus Station (Platform 2), Woden Bus Station]
  long_name: To Woden Bus Station
  between_stops:
  Westfield Bus Station (Platform 1)-Belconnen Community Bus Station (Platform 2): []
  Cohen Street Bus Station (Platform 2)-Westfield Bus Station (Platform 1): []
  short_name: "749"
  stop_times: [[659a, 701a, 705a, 730a], [734a, 736a, 740a, 810a], [804a, 806a, 810a, 840a], [456p, 458p, 502p, 535p]]
  -
  time_points: [Woden Bus Station (Platform 5), Mount Neighbour School, Kambah High, Tuggeranong Bus Station]
  long_name: To Tuggeranong Bus Station
  between_stops: {}
   
  stop_times_saturday: [[850a, 902a, 908a, 918a], [950a, 1002a, 1008a, 1018a], [1050a, 1102a, 1108a, 1118a], [1150a, 1202p, 1208p, 1218p], [1250p, 102p, 108p, 118p], [150p, 202p, 208p, 218p], [250p, 302p, 308p, 318p], [350p, 402p, 408p, 418p], [450p, 502p, 508p, 518p], [550p, 602p, 608p, 618p], [650p, 702p, 708p, 717p], [750p, 800p, 806p, 815p], [850p, 900p, 906p, 915p], [950p, 1000p, 1006p, 1015p], [1050p, 1100p, 1106p, 1115p]]
  short_name: "960"
  -
  time_points: [Woden Bus Station (Platform 15), Southlands Mawson, Torrens, Pearce, Woden Bus Station]
  long_name: To Woden Bus Station
  between_stops: {}
   
  short_name: "22"
  stop_times: [[635a, 648a, 656a, 659a, 707a], [705a, 718a, 726a, 729a, 738a], [735a, 749a, 758a, 801a, 810a], [805a, 819a, 828a, 831a, 840a], [843a, 857a, 906a, 909a, 918a], [943a, 956a, 1004a, 1007a, 1015a], [1043a, 1056a, 1104a, 1107a, 1115a], [1143a, 1156a, 1204p, 1207p, 1215p], [1243p, 1256p, 104p, 107p, 115p], [143p, 156p, 204p, 207p, 215p], [243p, 256p, 305p, 308p, 317p], [313p, 327p, 336p, 339p, 348p], [335p, 349p, 358p, 401p, 410p], [405p, 419p, 428p, 431p, 440p], [435p, 449p, 458p, 501p, 510p], [505p, 519p, 528p, 531p, 540p], [535p, 549p, 558p, 601p, 610p], [605p, 619p, 628p, 631p, 639p], [638p, 651p, 659p, 702p, 710p], [738p, 751p, 759p, 802p, 810p], [838p, 851p, 859p, 902p, 910p], [938p, 951p, 959p, 1002p, 1010p], [1038p, 1051p, 1059p, 1102p, 1110p]]
  -
  time_points: [Lithgow St Terminus Fyshwick, Fyshwick Direct Factory Outlet, Canberra Times, Railway Station Kingston, Russell Offices, City Bus Station (Platform 8), Macarthur / Northbourne Ave, National Hockey Centre Lyneham, Australian Institute of Sport, University of Canberra, Belconnen Community Bus Station, Westfield Bus Station, Cohen Street Bus Station]
  long_name: To Cohen Street Bus Station
  between_stops:
  Westfield Bus Station-Cohen Street Bus Station: []
  Belconnen Community Bus Station-Westfield Bus Station: []
  University of Canberra-Belconnen Community Bus Station: [Wjz68Yy, Wjz68Y0, Wjz68IH, Wjz68Ip, Wjz689c, Wjz681S]
  stop_times_saturday: [["-", "-", "-", "-", "-", 809a, 815a, 820a, 824a, 830a, 837a, 839a, 844a], [845a, 853a, 904a, 911a, 917a, 928a, 934a, 939a, 943a, 949a, 956a, 958a, 1003a], [945a, 953a, 1004a, 1011a, 1017a, 1028a, 1034a, 1039a, 1043a, 1049a, 1056a, 1058a, 1103a], [1045a, 1053a, 1104a, 1111a, 1117a, 1128a, 1134a, 1139a, 1143a, 1149a, 1156a, 1158a, 1203p], ["-", "-", "-", 1130a, 1136a, 1146a, "-", "-", "-", "-", "-", "-", "-"], [1145a, 1153a, 1204p, 1211p, 1217p, 1228p, 1234p, 1239p, 1243p, 1249p, 1256p, 1258p, 103p], [1245p, 1253p, 104p, 111p, 117p, 128p, 134p, 139p, 143p, 149p, 156p, 158p, 203p], [145p, 153p, 204p, 211p, 217p, 228p, 234p, 239p, 243p, 249p, 256p, 258p, 303p], [245p, 253p, 304p, 311p, 317p, 328p, 334p, 339p, 343p, 349p, 356p, 358p, 403p], [345p, 353p, 404p, 411p, 417p, 428p, 434p, 439p, 443p, 449p, 456p, 458p, 503p], ["-", "-", "-", 440p, 446p, 456p, "-", "-", "-", "-", "-", "-", "-"], [445p, 453p, 504p, 511p, 517p, 528p, 534p, 539p, 543p, 549p, 556p, 558p, 603p], [545p, 553p, 604p, 611p, 617p, 628p, 634p, 639p, 643p, 649p, 656p, 658p, 703p], ["-", "-", "-", "-", "-", 657p, 703p, 708p, 712p, 718p, 725p, 727p, 732p], ["-", "-", "-", "-", "-", 807p, 813p, 818p, 822p, 828p, 835p, 837p, 842p], ["-", "-", "-", "-", "-", 917p, 923p, 928p, 932p, 938p, 945p, 947p, 952p], ["-", "-", "-", "-", "-", 1028p, 1034p, 1039p, 1043p, 1049p, 1056p, 1058p, 1103p], ["-", "-", "-", "-", "-", 1140p, 1146p, 1151p, 1155p, 1201a, 1208a, 1210a, 1215a]]
  short_name: "980"
  -
  time_points: [Campbell Park Offices, ADFA, Russell Offices, Kings Ave / National Circuit, Woden Bus Station (Platform 10), Athllon / Sulwood Kambah, Wanniassa High, Erindale Centre, Monash Goodwin Village, Tuggeranong Bus Station]
  long_name: To Tuggeranong Bus Station
  between_stops:
  Russell Offices-Kings Ave / National Circuit: [Wjz4RFJ, Wjz4RwH, Wjz4Quk]
  Woden Bus Station (Platform 10)-Athllon / Sulwood Kambah: [Wjz3kAx, Wjz3kyX, Wjz3kwU, Wjz3iFK, Wjz3hL_, Wjz3gK-, Wjz3gQn, Wjz3gMq, Wjz2mTK]
  ADFA-Russell Offices: [Wjzcend, Wjzce4H, Wjzce7O, Wjzc54R, Wjzc55s, Wjzc60i, Wjzc60A]
  Campbell Park Offices-ADFA: [Wjzce7O, Wjzce4H, Wjzcend]
  short_name: "63"
  stop_times: [["-", "-", "-", "-", "-", "-", 615a, 619a, 623a, 631a], ["-", "-", "-", "-", "-", "-", 645a, 649a, 653a, 701a], ["-", "-", "-", "-", 703a, 710a, 715a, 719a, 723a, 731a], ["-", "-", "-", "-", 723a, 730a, 736a, 741a, 746a, 756a], ["-", "-", "-", "-", 803a, 812a, 818a, 823a, 828a, 838a], ["-", "-", "-", "-", 823a, 832a, 838a, 843a, 848a, 858a], ["-", "-", "-", "-", 903a, 912a, 918a, 923a, 928a, 937a], ["-", "-", "-", "-", 1003a, 1011a, 1017a, 1022a, 1026a, 1035a], ["-", "-", "-", "-", 1103a, 1111a, 1117a, 1122a, 1126a, 1135a], ["-", "-", "-", "-", 1203p, 1211p, 1217p, 1222p, 1226p, 1235p], ["-", "-", "-", "-", 103p, 111p, 117p, 122p, 126p, 135p], ["-", "-", "-", "-", 203p, 211p, 217p, 222p, 226p, 235p], ["-", "-", "-", "-", 303p, 312p, 318p, 323p, 328p, 338p], ["-", "-", "-", "-", 323p, 332p, 338p, 343p, 348p, 358p], ["-", "-", "-", "-", 403p, 412p, 418p, 423p, 428p, 438p], ["-", "-", "-", "-", 423p, 432p, 438p, 443p, 448p, 458p], [437p, 441p, 445p, 448p, 503p, 512p, 518p, 523p, 528p, 538p], [457p, 501p, 505p, 508p, 523p, 532p, 538p, 543p, 548p, 558p], [537p, 541p, 545p, 548p, 603p, 612p, 618p, 623p, 628p, 637p], [557p, 601p, 605p, 608p, 623p, 632p, 638p, 643p, 647p, 656p], ["-", "-", "-", "-", 703p, 711p, 717p, 722p, 726p, 735p], ["-", "-", "-", "-", 803p, 811p, 817p, 822p, 826p, 835p], ["-", "-", "-", "-", 903p, 911p, 917p, 922p, 926p, 935p], ["-", "-", "-", "-", 1003p, 1011p, 1017p, 1022p, 1026p, 1035p], ["-", "-", "-", "-", 1103p, 1111p, 1117p, 1122p, 1126p, 1135p], []]
  -
  time_points: [City West, City Bus Station (Platform 1), Woden Bus Station (Platform 12), Erindale / Sternberg Cres, Gowrie, Erindale Dr / Charleston St Monash]
  long_name: To Erindale Dr / Charleston St Monash
  between_stops:
  City Bus Station (Platform 1)-Woden Bus Station (Platform 12): [Wjz5Nht, Wjz4KO9, Wjz4KNu, Wjz3eZ4, Wjz3eRR, Wjz3m3b, Wjz3m3b]
  City West-City Bus Station (Platform 1): []
  short_name: "170"
  stop_times: [[500p, 505p, 521p, 536p, 546p, 556p]]
  -
  time_points: [Tuggeranong Bus Station (Platform 7), Bonython Primary School, Lanyon Market Place, Conder Primary, Tharwa Drive / Pockett Ave, Gordon Primary, Woodcock / Clare Dennis, Bonython Primary School, Tuggeranong Bus Station]
  long_name: To Tuggeranong Bus Station
  between_stops: {}
   
  stop_times_saturday: [[625a, 634a, 640a, 647a, 650a, 654a, 659a, 702a, 712a], [825a, 834a, 840a, 847a, 850a, 854a, 859a, 902a, 912a], [1025a, 1034a, 1040a, 1047a, 1050a, 1054a, 1059a, 1102a, 1112a], [1225p, 1234p, 1240p, 1247p, 1250p, 1254p, 1259p, 102p, 112p], [225p, 234p, 240p, 247p, 250p, 254p, 259p, 302p, 312p], [425p, 434p, 440p, 447p, 450p, 454p, 459p, 502p, 512p], [625p, 634p, 640p, 647p, 650p, 654p, 659p, 702p, 712p], [828p, 837p, 843p, 850p, 853p, 857p, 902p, 905p, 915p], [1028p, 1037p, 1043p, 1050p, 1053p, 1057p, 1102p, 1105p, 1115p]]
  short_name: "914"
  -
  time_points: [Belconnen Community Bus Station (Platform 4), Westfield Bus Station (Platform 2), Cohen Street Bus Station (Platform 6), Kippax, Macgregor, Charnwood, Fraser West Terminus, Charnwood, Macgregor, Kippax, Cohen Street Bus Station, Westfield Bus Station, Belconnen Community Bus Station]
  long_name: To Belconnen Community Bus Station
  between_stops:
  Belconnen Community Bus Station (Platform 4)-Westfield Bus Station (Platform 2): []
  Westfield Bus Station-Belconnen Community Bus Station: []
  Cohen Street Bus Station-Westfield Bus Station: []
  Westfield Bus Station (Platform 2)-Cohen Street Bus Station (Platform 6): []
  stop_times_saturday: [["-", "-", "-", "-", "-", "-", 757a, 809a, 816a, 823a, 836a, 838a, 842a], [814a, 816a, 820a, 833a, 839a, 846a, 857a, 909a, 916a, 923a, 936a, 938a, 942a], [914a, 916a, 920a, 933a, 939a, 946a, 957a, 1009a, 1016a, 1023a, 1036a, 1038a, 1042a], [1014a, 1016a, 1020a, 1033a, 1039a, 1046a, 1057a, 1109a, 1116a, 1123a, 1136a, 1138a, 1142a], [1114a, 1116a, 1120a, 1133a, 1139a, 1146a, 1157a, 1209p, 1216p, 1223p, 1236p, 1238p, 1242p], [1214p, 1216p, 1220p, 1233p, 1239p, 1246p, 1257p, 109p, 116p, 123p, 136p, 138p, 142p], [114p, 116p, 120p, 133p, 139p, 146p, 157p, 209p, 216p, 223p, 236p, 238p, 242p], [214p, 216p, 220p, 233p, 239p, 246p, 257p, 309p, 316p, 323p, 336p, 338p, 342p], [314p, 316p, 320p, 333p, 339p, 346p, 357p, 409p, 416p, 423p, 436p, 438p, 442p], [414p, 416p, 420p, 433p, 439p, 446p, 457p, 509p, 516p, 523p, 536p, 538p, 542p], [514p, 516p, 520p, 533p, 539p, 546p, 557p, 609p, 616p, 623p, 636p, 638p, 642p], [614p, 616p, 620p, 633p, 639p, 646p, 656p, 707p, 714p, 721p, 733p, 735p, 739p], [713p, 715p, 719p, 731p, 737p, 744p, 754p, 805p, 812p, 819p, 831p, 833p, 837p], [813p, 815p, 819p, 831p, 837p, 844p, 854p, 905p, 912p, 919p, 931p, 933p, 937p], [913p, 915p, 919p, 931p, 937p, 944p, 954p, 1005p, 1012p, 1019p, 1031p, 1033p, 1037p], [1013p, 1015p, 1019p, 1031p, 1037p, 1044p, 1054p, "-", "-", "-", "-", "-", "-"], [1113p, 1115p, 1119p, 1131p, 1137p, 1144p, 1154p, "-", "-", "-", "-", "-", "-"]]
  short_name: "905"
  -
  time_points: [City Bus Station (Platform 4), Macarthur / Miller O'Connor, Lyneham / Wattle St, Dickson / Antill St]
  long_name: To Dickson
  between_stops: {}
   
  short_name: "8"
  stop_times: [[655a, 702a, 707a, 713a], [714a, 721a, 726a, 732a], [741a, 750a, 757a, 804a], [811a, 820a, 827a, 834a], [841a, 850a, 857a, 904a], [915a, 924a, 931a, 937a], [946a, 953a, 958a, 1004a], [1018a, 1025a, 1030a, 1036a], [1046a, 1053a, 1058a, 1104a], [1146a, 1153a, 1158a, 1204p], [1246p, 1253p, 1258p, 104p], [146p, 153p, 158p, 204p], [246p, 253p, 258p, 305p], [311p, 320p, 327p, 334p], [346p, 355p, 402p, 409p], [411p, 420p, 427p, 434p], [444p, 453p, 500p, 507p], [523p, 532p, 539p, 546p], [553p, 602p, 609p, 616p], [623p, 631p, 636p, 642p], [650p, 655p, 700p, 706p], [705p, 710p, 715p, 721p], [805p, 810p, 815p, 821p], [905p, 910p, 915p, 921p], [1005p, 1010p, 1015p, 1021p], [1105p, 1110p, 1115p, 1121p]]
  -
  time_points: [Cohen Street Bus Station (Platform 1), Westfield Bus Station (Platform 1), Belconnen Community Bus Station (Platform 2), Chuculba / William Slim Dr, Mirrabei Drive / Dam Wall, Paul Coe / Mirrabei Dr, Katherine Ave / Horse Park Drive, Gungahlin Marketplace, Hibberson / Kate Crace, Flemington Rd / Sandford St, Northbourne Avenue / Antill St]
  long_name: To Northbourne Avenue / Antill St
  between_stops:
  Belconnen Community Bus Station (Platform 2)-Chuculba / William Slim Dr: [Wjz69gA, Wjz69ht, Wjz69uI, Wjz69vO, Wjz6mip]
  Cohen Street Bus Station (Platform 1)-Westfield Bus Station (Platform 1): []
  Westfield Bus Station (Platform 1)-Belconnen Community Bus Station (Platform 2): []
  short_name: "59"
  stop_times: [["-", "-", "-", "-", 537a, 541a, 547a, 603a, 606a, "-", "-"], ["-", "-", "-", "-", 612a, 616a, 622a, 638a, 641a, "-", "-"], ["-", "-", "-", "-", 646a, 650a, 656a, 711a, 714a, 717a, 724a], ["-", "-", "-", "-", 702a, 706a, 712a, 727a, 730a, 733a, 740a], ["-", "-", "-", "-", 712a, 716a, 722a, 737a, 740a, 743a, 753a], ["-", "-", "-", "-", 733a, 737a, 743a, 758a, 801a, 806a, 817a], ["-", "-", "-", "-", 809a, 813a, 819a, 834a, 837a, 842a, 853a], ["-", "-", "-", "-", 820a, 824a, 830a, 845a, 848a, 853a, 903a], ["-", "-", "-", "-", 849a, 853a, 859a, 914a, 917a, 920a, 927a], [900a, 902a, 906a, 923a, "-", 933a, 939a, 955a, 958a, "-", "-"], [1000a, 1002a, 1006a, 1023a, "-", 1033a, 1039a, 1055a, 1058a, "-", "-"], [1100a, 1102a, 1106a, 1123a, "-", 1133a, 1139a, 1155a, 1158a, "-", "-"], [1200p, 1202p, 1206p, 1223p, "-", 1233p, 1239p, 1255p, 1258p, "-", "-"], [100p, 102p, 106p, 123p, "-", 133p, 139p, 155p, 158p, "-", "-"], [200p, 202p, 206p, 223p, "-", 233p, 239p, 255p, 258p, "-", "-"], [240p, 242p, 246p, 303p, "-", 313p, 319p, 335p, 338p, "-", "-"], [318p, 320p, 324p, 342p, "-", 352p, 358p, 414p, 417p, "-", "-"], [333p, 335p, 339p, 357p, "-", 407p, 413p, 429p, 432p, "-", "-"], [348p, 350p, 354p, 412p, "-", 422p, 428p, 444p, 447p, "-", "-"], [403p, 405p, 409p, 427p, "-", 437p, 443p, 459p, 502p, "-", "-"], [418p, 420p, 424p, 442p, "-", 452p, 458p, 514p, 517p, "-", "-"], [433p, 435p, 439p, 457p, "-", 507p, 513p, 529p, 532p, "-", "-"], [448p, 450p, 454p, 512p, "-", 522p, 528p, 544p, 547p, "-", "-"], [503p, 505p, 509p, 527p, "-", 537p, 543p, 559p, 602p, "-", "-"], [518p, 520p, 524p, 542p, "-", 552p, 558p, 614p, 617p, "-", "-"], [530p, 532p, 536p, 554p, "-", 604p, 610p, 626p, 629p, "-", "-"], [548p, 550p, 554p, 611p, "-", 620p, 626p, 642p, 645p, "-", "-"], [603p, 605p, 609p, 626p, "-", 635p, 641p, 657p, 700p, "-", "-"], [703p, 705p, 709p, 726p, "-", 735p, 741p, 757p, 800p, "-", "-"], [803p, 805p, 809p, 826p, "-", 835p, 841p, 857p, 900p, "-", "-"], [903p, 905p, 909p, 926p, "-", 935p, 941p, 957p, 1000p, "-", "-"], [1003p, 1005p, 1009p, 1026p, "-", 1035p, 1041p, 1057p, 1100p, "-", "-"], [1103p, 1105p, 1109p, 1126p, "-", 1135p, 1141p, 1157p, 1200a, "-", "-"]]
  -
  time_points: [Campbell Park Offices, ADFA, Russell Offices, Kings Ave / National Circuit, Woden Bus Station (Platform 3), Waramanga, Fisher, Rivett, Cooleman Court]
  long_name: To Cooleman Court
  between_stops:
  Russell Offices-Kings Ave / National Circuit: [Wjz4RFJ, Wjz4RwH, Wjz4Quk]
  ADFA-Russell Offices: [Wjzcend, Wjzce4H, Wjzce7O, Wjzc54R, Wjzc55s, Wjzc60i, Wjzc60A]
  Campbell Park Offices-ADFA: [Wjzce7O, Wjzce4H, Wjzcend]
  short_name: 27 227
  stop_times: [["-", "-", "-", "-", 821a, 829a, 833a, 840a, 845a], ["-", "-", "-", "-", 854a, 902a, 906a, 913a, 918a], ["-", "-", "-", "-", 954a, 1001a, 1005a, 1013a, 1019a], ["-", "-", "-", "-", 1054a, 1101a, 1105a, 1113a, 1119a], ["-", "-", "-", "-", 1154a, 1201p, 1205p, 1213p, 1219p], ["-", "-", "-", "-", 1254p, 101p, 105p, 113p, 119p], ["-", "-", "-", "-", 154p, 201p, 205p, 213p, 219p], ["-", "-", "-", "-", 254p, 302p, 307p, 314p, 322p], ["-", "-", "-", "-", 321p, 333p, 338p, 345p, 353p], ["-", "-", "-", "-", 351p, 403p, 408p, 415p, 423p], ["-", "-", "-", "-", 421p, 433p, 438p, 445p, 453p], [427p, 431p, 435p, 438p, 453p, 505p, 510p, 517p, 525p], ["-", "-", "-", "-", 521p, 533p, 538p, 545p, 553p], [527p, 531p, 535p, 538p, 553p, 605p, 610p, 617p, 625p], ["-", "-", "-", "-", 635p, 641p, 644p, 650p, 655p], ["-", "-", "-", "-", 735p, 741p, 744p, 750p, 755p], ["-", "-", "-", "-", 835p, 841p, 844p, 850p, 855p], ["-", "-", "-", "-", 935p, 941p, 944p, 950p, 955p], ["-", "-", "-", "-", 1035p, 1041p, 1044p, 1050p, 1055p]]
  -
  time_points: [Belconnen Community Bus Station (Platform 5), Westfield Bus Station (Platform 2), Cohen Street Bus Station (Platform 5), Kippax, Macgregor, Charnwood, Macgregor, Kippax, Cohen Street Bus Station, Westfield Bus Station, Belconnen Community Bus Station]
  long_name: To Belconnen Community Bus Station
  between_stops:
  Belconnen Community Bus Station (Platform 5)-Westfield Bus Station (Platform 2): []
  Westfield Bus Station-Belconnen Community Bus Station: []
  Cohen Street Bus Station-Westfield Bus Station: []
  Westfield Bus Station (Platform 2)-Cohen Street Bus Station (Platform 5): []
  short_name: "43"
  stop_times: [["-", "-", "-", "-", 621a, 629a, 638a, 643a, 648a, 650a, 654a], ["-", "-", "-", "-", 640a, 648a, 657a, 702a, 707a, 709a, 713a], [644a, 646a, 650a, 655a, 700a, 708a, 717a, 722a, 727a, 729a, 733a], ["-", "-", "-", "-", 720a, 728a, 739a, 744a, 752a, 754a, 758a], ["-", "-", "-", "-", 741a, 749a, 800a, 805a, 813a, 815a, 819a], ["-", "-", "-", "-", 802a, 810a, 821a, 826a, 834a, 836a, 840a], ["-", "-", "-", "-", 824a, 832a, 843a, 848a, 856a, 858a, 902a], [823a, 825a, 829a, 837a, 842a, 850a, 901a, 906a, 914a, 916a, 920a], [843a, 845a, 849a, 857a, 902a, 910a, 921a, 926a, 933a, 935a, 939a], [903a, 905a, 909a, 917a, 922a, 930a, 939a, 944a, 952a, 954a, 958a], [1003a, 1005a, 1009a, 1015a, 1020a, 1028a, 1037a, 1042a, 1048a, 1050a, 1054a], [1103a, 1105a, 1109a, 1115a, 1120a, 1128a, 1137a, 1142a, 1148a, 1150a, 1154a], [1203p, 1205p, 1209p, 1215p, 1220p, 1228p, 1237p, 1242p, 1248p, 1250p, 1254p], [103p, 105p, 109p, 115p, 120p, 128p, 137p, 142p, 148p, 150p, 154p], [203p, 205p, 209p, 215p, 220p, 228p, 237p, 242p, 248p, 250p, 254p], [254p, 256p, 300p, 308p, 313p, 321p, 332p, 337p, 345p, 347p, 351p], [323p, 325p, 329p, 337p, 342p, 350p, 401p, 406p, 414p, 416p, 420p], [343p, 345p, 349p, 357p, 402p, 410p, 421p, 426p, 434p, 436p, 440p], [403p, 405p, 409p, 417p, 422p, 430p, 441p, 446p, 454p, 456p, 500p], [423p, 425p, 429p, 437p, 442p, 450p, 501p, 506p, 514p, 516p, 520p], [443p, 445p, 449p, 457p, 502p, 510p, 521p, 526p, 534p, 536p, 540p], [503p, 505p, 509p, 517p, 522p, 530p, 541p, 546p, 554p, 556p, 600p], [523p, 525p, 529p, 537p, 542p, 550p, 601p, 606p, 614p, 616p, 620p], [602p, 604p, 608p, 616p, 621p, 629p, 638p, 643p, 648p, 650p, 654p], [702p, 704p, 708p, 713p, 718p, 726p, 735p, 740p, 745p, 747p, 751p], [802p, 804p, 808p, 813p, 818p, 826p, 835p, 840p, 845p, 847p, 851p], [902p, 904p, 908p, 913p, 918p, 926p, 935p, 940p, 945p, 947p, 951p], [1002p, 1004p, 1008p, 1013p, 1018p, 1026p, 1035p, 1040p, 1045p, 1047p, 1051p], [1102p, 1104p, 1108p, 1113p, 1118p, 1126p, 1135p, "-", "-", "-", "-"], []]
  -
  time_points: [City Bus Station (Platform 8), Macarthur / Northbourne Ave, Flemington Rd / Sandford St, Flemington Rd / Nullabor Ave, Anthony Rolfe Av / Moonlight Av, Gungahlin Marketplace, Shoalhaven / Katherine Ave, Ngunnawal Primary, Chuculba / William Slim Dr, Belconnen Community Bus Station, Westfield Bus Station, Cohen Street Bus Station]
  long_name: To Cohen Street Bus Station
  between_stops:
  Westfield Bus Station-Cohen Street Bus Station: []
  City Bus Station (Platform 8)-Macarthur / Northbourne Ave: [Wjz5O3Q, Wjz5Oci, Wjz5P8n, Wjz5P8K, Wjz5PdJ, Wjz5Pl0, Wjz5Qi2, Wjz5Qgn, Wjz5Qmu, Wjz5QmR]
  Belconnen Community Bus Station-Westfield Bus Station: []
  Chuculba / William Slim Dr-Belconnen Community Bus Station: [Wjz6mip, Wjz69vO, Wjz69uI, Wjz69ht, Wjz69gA]
  stop_times_saturday: [["-", "-", "-", 723a, 730a, 739a, 747a, 755a, 806a, 816a, 818a, 823a], [800a, 806a, 814a, 821a, 828a, 837a, 845a, 853a, 904a, 914a, 916a, 921a], [900a, 906a, 914a, 921a, 928a, 937a, 945a, 953a, 1004a, 1014a, 1016a, 1021a], [1000a, 1006a, 1014a, 1021a, 1028a, 1037a, 1045a, 1053a, 1104a, 1114a, 1116a, 1121a], [1100a, 1106a, 1114a, 1121a, 1128a, 1137a, 1145a, 1153a, 1204p, 1214p, 1216p, 1221p], [1200p, 1206p, 1214p, 1221p, 1228p, 1237p, 1245p, 1253p, 104p, 114p, 116p, 121p], [100p, 106p, 114p, 121p, 128p, 137p, 145p, 153p, 204p, 214p, 216p, 221p], [200p, 206p, 214p, 221p, 228p, 237p, 245p, 253p, 304p, 314p, 316p, 321p], [300p, 306p, 314p, 321p, 328p, 337p, 345p, 353p, 404p, 414p, 416p, 421p], [400p, 406p, 414p, 421p, 428p, 437p, 445p, 453p, 504p, 514p, 516p, 521p], [500p, 506p, 514p, 521p, 528p, 537p, 545p, 553p, 604p, 614p, 616p, 621p], [600p, 606p, 614p, 621p, 628p, 637p, 645p, 653p, 704p, 714p, 716p, 721p], [700p, 706p, 714p, 721p, 728p, 737p, 745p, 753p, 804p, 814p, 816p, 821p], [800p, 806p, 814p, 821p, 828p, 837p, 845p, 853p, 904p, 914p, 916p, 921p], [900p, 906p, 914p, 921p, 928p, 937p, 945p, 953p, 1004p, 1014p, 1016p, 1021p], [1000p, 1006p, 1014p, 1021p, 1028p, 1037p, 1045p, 1053p, 1104p, 1114p, 1116p, 1121p], [1100p, 1106p, 1114p, 1121p, 1128p, 1137p, "-", "-", "-", "-", "-", "-"]]
  short_name: "958"
  -
  time_points: [Cooleman Court, Stromlo High Waramanga, Woden Bus Station]
  long_name: To Woden Bus Station
  between_stops: {}
   
  short_name: "75"
  stop_times: [[925a, 934a, 947a], [1125a, 1134a, 1147a], [125p, 134p, 147p]]
  -
  time_points: [Belconnen Community Bus Station (Platform 4), Westfield Bus Station (Platform 2), Cohen Street Bus Station (Platform 6), McKellar, Evatt, Spence Terminus, Evatt, McKellar, Cohen Street Bus Station, Westfield Bus Station, Belconnen Community Bus Station]
  long_name: To Belconnen Community Bus Station
  between_stops:
  Belconnen Community Bus Station (Platform 4)-Westfield Bus Station (Platform 2): []
  Westfield Bus Station-Belconnen Community Bus Station: []
  Cohen Street Bus Station-Westfield Bus Station: []
  Westfield Bus Station (Platform 2)-Cohen Street Bus Station (Platform 6): []
  stop_times_saturday: [["-", "-", "-", "-", "-", 718a, 723a, 731a, 739a, 741a, 745a], ["-", "-", "-", "-", "-", 818a, 823a, 831a, 839a, 841a, 845a], [851a, 853a, 857a, 904a, 912a, 918a, 923a, 931a, 939a, 941a, 945a], [951a, 953a, 957a, 1004a, 1012a, 1018a, 1023a, 1031a, 1039a, 1041a, 1045a], [1051a, 1053a, 1057a, 1104a, 1112a, 1118a, 1123a, 1131a, 1139a, 1141a, 1145a], [1151a, 1153a, 1157a, 1204p, 1212p, 1218p, 1223p, 1231p, 1239p, 1241p, 1245p], [1251p, 1253p, 1257p, 104p, 112p, 118p, 123p, 131p, 139p, 141p, 145p], [151p, 153p, 157p, 204p, 212p, 218p, 223p, 231p, 239p, 241p, 245p], [251p, 253p, 257p, 304p, 312p, 318p, 323p, 331p, 339p, 341p, 345p], [351p, 353p, 357p, 404p, 412p, 418p, 423p, 431p, 439p, 441p, 445p], [451p, 453p, 457p, 504p, 512p, 518p, 523p, 531p, 539p, 541p, 545p], [551p, 553p, 557p, 604p, 612p, 618p, 623p, 631p, 638p, 640p, 644p], [650p, 652p, 656p, 702p, 709p, 715p, 720p, 728p, 735p, 737p, 741p], [750p, 752p, 756p, 802p, 809p, 815p, 820p, 828p, 835p, 837p, 841p], [850p, 852p, 856p, 902p, 909p, 915p, 920p, 928p, 935p, 937p, 941p], [950p, 952p, 956p, 1002p, 1009p, 1015p, 1020p, 1028p, 1035p, 1037p, 1041p], [1050p, 1052p, 1056p, 1102p, 1109p, 1115p, 1120p, 1128p, 1135p, 1137p, 1141p]]
  short_name: "902"
  -
  time_points: [Lanyon Market Place, Gordon Primary, Woodcock / Clare Dennis, Tuggeranong Bus Station (Platform 8), Woden Bus Station (Platform 9), City Bus Station (Platform 3), Belconnen Community Bus Station, Westfield Bus Station, Cohen Street Bus Station]
  long_name: To Cohen Street Bus Station
  between_stops:
  Westfield Bus Station-Cohen Street Bus Station: []
  City Bus Station (Platform 3)-Belconnen Community Bus Station: [Wjz5F-1, Wjz5FSY, Wjz5GMT, Wjz5GNG, Wjz5G6U, Wjz5G6B, Wjz5maK, Wjz5mbS, Wjz5nwb, Wjz5nw6, Wjz6giR, Wjz6gia, Wjz68W3, Wjz68W5, Wjz689c, Wjz681S]
  Belconnen Community Bus Station-Westfield Bus Station: []
  Tuggeranong Bus Station (Platform 8)-Woden Bus Station (Platform 9): [Wjz213q, Wjz238T, Wjz239F, Wjz2lDC, Wjz2mGO, Wjz2mTK, Wjz2nLE, Wjz3gMq, Wjz3gQn, Wjz3gK-, Wjz3hL_, Wjz3iFK, Wjz3kwU, Wjz3kyX, Wjz3kAx]
  Woden Bus Station (Platform 9)-City Bus Station (Platform 3): [Wjz3m3b, Wjz3m3b, Wjz3eRR, Wjz3eZ4, Wjz4KNu, Wjz4KO9, Wjz5Nht]
  short_name: 18 318
  stop_times: [[545a, 554a, 558a, 608a, 626a, 642a, 702a, 704a, 709a], [612a, 621a, 625a, 635a, 653a, 709a, 729a, 731a, 736a], [635a, 644a, 648a, 658a, 716a, 732a, 753a, 755a, 800a], [657a, 706a, 710a, 720a, 738a, 756a, 817a, 819a, 824a], [716a, 725a, 729a, 741a, 800a, 818a, 839a, 841a, 846a], [733a, 743a, 748a, 800a, 819a, 837a, 858a, 900a, 905a], ["-", "-", 750a, 758a, "-", "-", "-", "-", "-"], [753a, 803a, 808a, 820a, 839a, 857a, 918a, 920a, 925a], [813a, 823a, 828a, 840a, 859a, 917a, 938a, 940a, 945a], [838a, 848a, 853a, 905a, 924a, 941a, 1001a, 1003a, 1008a], [909a, 919a, 924a, 935a, 953a, 1009a, 1029a, 1031a, 1036a], [943a, 952a, 956a, 1006a, 1024a, 1040a, 1100a, 1102a, 1107a], [1013a, 1022a, 1026a, 1036a, 1054a, 1110a, 1130a, 1132a, 1137a], [1043a, 1052a, 1056a, 1106a, 1124a, 1140a, 1200p, 1202p, 1207p], [1113a, 1122a, 1126a, 1136a, 1154a, 1210p, 1230p, 1232p, 1237p], [1143a, 1152a, 1156a, 1206p, 1224p, 1240p, 100p, 102p, 107p], [1213p, 1222p, 1226p, 1236p, 1254p, 110p, 130p, 132p, 137p], [1243p, 1252p, 1256p, 106p, 124p, 140p, 200p, 202p, 207p], [113p, 122p, 126p, 136p, 154p, 210p, 230p, 232p, 237p], [143p, 152p, 156p, 206p, 224p, 240p, 300p, 302p, 307p], [212p, 221p, 225p, 235p, 253p, 310p, 331p, 333p, 338p], [241p, 250p, 254p, 305p, 324p, 342p, 403p, 405p, 410p], [308p, 318p, 323p, 335p, 354p, 412p, 433p, 435p, 440p], [333p, 343p, 348p, 400p, 419p, 437p, 458p, 500p, 505p], [402p, 412p, 417p, 429p, 448p, 506p, 527p, 529p, 534p], [439p, 449p, 454p, 506p, 525p, 543p, 604p, 606p, 611p], [515p, 525p, 530p, 540p, "-", "-", "-", "-", "-"], [545p, 555p, 600p, 610p, "-", "-", "-", "-", "-"], [617p, 627p, 632p, 642p, 659p, 714p, 734p, 736p, 741p], [713p, 722p, 726p, 734p, "-", "-", "-", "-", "-"], [814p, 823p, 827p, 835p, "-", "-", "-", "-", "-"], [914p, 923p, 927p, 935p, "-", "-", "-", "-", "-"], [1014p, 1023p, 1027p, 1035p, "-", "-", "-", "-", "-"], [1114p, 1123p, 1127p, 1135p, "-", "-", "-", "-", "-"], []]
  -
  time_points: [Belconnen Community Bus Station (Platform 6), Westfield Bus Station (Platform 2), Cohen Street Bus Station (Platform 5), Florey, Latham Post Office, Kippax]
  long_name: To Kippax
  between_stops:
  Belconnen Community Bus Station (Platform 6)-Westfield Bus Station (Platform 2): []
  Westfield Bus Station (Platform 2)-Cohen Street Bus Station (Platform 5): []
  short_name: "16"
  stop_times: [[700a, 702a, 706a, 711a, 717a, 727a], [800a, 802a, 806a, 812a, 818a, 830a], [826a, 828a, 832a, 838a, 844a, 856a], [913a, 915a, 919a, 925a, 931a, 941a], [939a, 941a, 945a, 950a, 956a, 1006a], [1014a, 1016a, 1020a, 1025a, 1031a, 1041a], [1039a, 1041a, 1045a, 1050a, 1056a, 1106a], [1114a, 1116a, 1120a, 1125a, 1131a, 1141a], [1139a, 1141a, 1145a, 1150a, 1156a, 1206p], [1214p, 1216p, 1220p, 1225p, 1231p, 1241p], [1239p, 1241p, 1245p, 1250p, 1256p, 106p], [114p, 116p, 120p, 125p, 131p, 141p], [139p, 141p, 145p, 150p, 156p, 206p], [214p, 216p, 220p, 225p, 231p, 241p], [238p, 240p, 244p, 249p, 255p, 306p], [307p, 309p, 313p, 319p, 325p, 337p], [326p, 328p, 332p, 338p, 344p, 356p], [356p, 358p, 402p, 408p, 414p, 426p], [426p, 428p, 432p, 438p, 444p, 456p], [446p, 448p, 452p, 458p, 504p, 516p], [506p, 508p, 512p, 518p, 524p, 536p], [526p, 528p, 532p, 538p, 544p, 556p], [546p, 548p, 552p, 558p, 604p, 616p], [601p, 603p, 607p, 613p, 619p, 631p], [622p, 624p, 628p, 633p, 639p, 649p], [721p, 723p, 727p, 731p, 737p, 747p], [821p, 823p, 827p, 831p, 837p, 847p], [921p, 923p, 927p, 931p, 937p, 947p], [1021p, 1023p, 1027p, 1031p, 1037p, 1047p], [1121p, 1123p, 1127p, 1131p, 1137p, 1147p]]
  -
  time_points: [Cohen Street Bus Station (Platform 1), Westfield Bus Station (Platform 1), Belconnen Community Bus Station (Platform 2), Chuculba / William Slim Dr, Federation Square, Nicholls Primary, Ngunnawal Primary, Gungahlin Marketplace, Hibberson / Kate Crace, Flemington Rd / Sandford St, Northbourne Avenue / Antill St, Macarthur / Northbourne Ave, City Bus Station]
  long_name: To City Bus Station
  between_stops:
  Northbourne Avenue / Antill St-Macarthur / Northbourne Ave: [Wjz5SDc, Wjz5Sux, Wjz5SrO, Wjz5Sqk, Wjz5RvC, Wjz5Rsi, Wjz5RkN]
  Belconnen Community Bus Station (Platform 2)-Chuculba / William Slim Dr: [Wjz69gA, Wjz69ht, Wjz69uI, Wjz69vO, Wjz6mip]
  Macarthur / Northbourne Ave-City Bus Station: [Wjz5QmR, Wjz5Qmu, Wjz5Qgn, Wjz5Qi2, Wjz5Pl0, Wjz5PdJ, Wjz5P8K, Wjz5P8n, Wjz5Oci, Wjz5O3Q]
  Cohen Street Bus Station (Platform 1)-Westfield Bus Station (Platform 1): []
  Westfield Bus Station (Platform 1)-Belconnen Community Bus Station (Platform 2): []
  short_name: "52"
  stop_times: [["-", "-", "-", "-", 539a, 547a, 555a, 602a, 605a, "-", "-", "-", "-"], ["-", "-", "-", "-", 618a, 626a, 634a, 641a, 644a, "-", "-", "-", "-"], [628a, 630a, 634a, 647a, 652a, 700a, 708a, 714a, 717a, 720a, 727a, 729a, 741a], ["-", "-", "-", "-", 708a, 716a, 724a, 730a, 733a, 736a, 743a, 745a, 800a], ["-", "-", "-", "-", 723a, 731a, 739a, 745a, 748a, 753a, 804a, 809a, 824a], [723a, 725a, 729a, 742a, 747a, 755a, 803a, 810a, 813a, 818a, 829a, 834a, 849a], [739a, 741a, 745a, 759a, 804a, 812a, 820a, 827a, 830a, 835a, 846a, 851a, 903a], [803a, 805a, 809a, 823a, 828a, 836a, 844a, 851a, 854a, 859a, 906a, 908a, 915a], [834a, 836a, 840a, 854a, 859a, 907a, 915a, 921a, 924a, 927a, 934a, 936a, 943a], [912a, 914a, 918a, 931a, 936a, 944a, 952a, 959a, 1002a, "-", "-", "-", "-"], [1012a, 1014a, 1018a, 1031a, 1036a, 1044a, 1052a, 1059a, 1102a, "-", "-", "-", "-"], [1112a, 1114a, 1118a, 1131a, 1136a, 1144a, 1152a, 1159a, 1202p, "-", "-", "-", "-"], [1212p, 1214p, 1218p, 1231p, 1236p, 1244p, 1252p, 1259p, 102p, "-", "-", "-", "-"], [112p, 114p, 118p, 131p, 136p, 144p, 152p, 159p, 202p, "-", "-", "-", "-"], [212p, 214p, 218p, 231p, 236p, 244p, 252p, 259p, 302p, "-", "-", "-", "-"], [229p, 231p, 235p, 248p, 253p, 301p, 309p, 316p, 319p, "-", "-", "-", "-"], [312p, 314p, 318p, 331p, 336p, 344p, 352p, 359p, 402p, "-", "-", "-", "-"], [352p, 354p, 358p, 412p, 417p, 426p, 434p, 442p, 445p, "-", "-", "-", "-"], [412p, 414p, 418p, 432p, 437p, 446p, 454p, 502p, 505p, "-", "-", "-", "-"], [432p, 434p, 438p, 452p, 457p, 506p, 514p, 522p, 525p, "-", "-", "-", "-"], [452p, 454p, 458p, 512p, 517p, 526p, 534p, 542p, 545p, "-", "-", "-", "-"], [512p, 514p, 518p, 532p, 537p, 546p, 554p, 602p, 605p, "-", "-", "-", "-"], [532p, 534p, 538p, 552p, 557p, 605p, 613p, 620p, 623p, "-", "-", "-", "-"], [611p, 613p, 617p, 630p, 635p, 643p, 651p, 658p, 701p, "-", "-", "-", "-"], [711p, 713p, 717p, 730p, 735p, 743p, 751p, 758p, 801p, "-", "-", "-", "-"], [811p, 813p, 817p, 830p, 835p, 843p, 851p, 858p, 901p, "-", "-", "-", "-"], [911p, 913p, 917p, 930p, 935p, 943p, 951p, 958p, 1001p, "-", "-", "-", "-"], [1011p, 1013p, 1017p, 1030p, 1035p, 1043p, 1051p, 1058p, 1101p, "-", "-", "-", "-"]]
  -
  time_points: [Tuggeranong Bus Station (Platform 5), Monash Primary, MacKillop College Wanniassa Campus, Athllon / Sulwood Kambah, Woden Bus Station]
  long_name: To Woden Bus Station
  between_stops:
  Athllon / Sulwood Kambah-Woden Bus Station: [Wjz2mTK, Wjz3gMq, Wjz3gQn, Wjz3gK-, Wjz3hL_, Wjz3iFK, Wjz3kwU, Wjz3kyX, Wjz3kAx]
  short_name: "64"
  stop_times: [[605a, 612a, 616a, 623a, 631a], [635a, 642a, 646a, 653a, 701a], [705a, 712a, 716a, 723a, 731a], [735a, 744a, 749a, 756a, 806a], [805a, 814a, 819a, 826a, 836a], [825a, 834a, 839a, 846a, 856a], [905a, 914a, 919a, 926a, 935a], [935a, 943a, 947a, 954a, 1003a], [1035a, 1043a, 1047a, 1054a, 1103a], [1135a, 1143a, 1147a, 1154a, 1203p], [1235p, 1243p, 1247p, 1254p, 103p], [135p, 143p, 147p, 154p, 203p], [235p, 243p, 247p, 254p, 303p], [305p, 314p, 319p, 326p, 336p], [335p, 344p, 349p, 356p, 406p], [435p, 444p, 449p, 456p, 506p], [505p, 514p, 519p, 526p, 536p], [535p, 544p, 549p, 556p, 606p], [636p, 644p, 648p, 655p, 704p], [739p, 747p, 751p, 758p, 807p], [839p, 847p, 851p, 858p, 907p], [939p, 947p, 951p, 958p, 1007p], [1039p, 1047p, 1051p, 1058p, "-"], [], []]
  -
  time_points: [Cohen Street Bus Station (Platform 2), Westfield Bus Station (Platform 1), Belconnen Community Bus Station (Platform 3), Australian Institute of Sport, Dickson / Antill St, Merici College, City Bus Station]
  long_name: To City Bus Station
  between_stops:
  Westfield Bus Station (Platform 1)-Belconnen Community Bus Station (Platform 3): []
  Cohen Street Bus Station (Platform 2)-Westfield Bus Station (Platform 1): []
  short_name: "7"
  stop_times: [[541a, 543a, 547a, 600a, 608a, 615a, 623a], [611a, 613a, 617a, 630a, 638a, 645a, 653a], [641a, 643a, 647a, 700a, 708a, 715a, 723a], [711a, 713a, 717a, 730a, 739a, 746a, 755a], [741a, 743a, 747a, 804a, 817a, 824a, 837a], [811a, 813a, 817a, 832a, 841a, 848a, 903a], [841a, 843a, 847a, 902a, 911a, 918a, 927a], [915a, 917a, 921a, 935a, 943a, 950a, 958a], [946a, 948a, 952a, 1005a, 1013a, 1020a, 1028a], [1016a, 1018a, 1022a, 1035a, 1043a, 1050a, 1058a], [1046a, 1048a, 1052a, 1105a, 1113a, 1120a, 1128a], [1116a, 1118a, 1122a, 1135a, 1143a, 1150a, 1158a], [1146a, 1148a, 1152a, 1205p, 1213p, 1220p, 1228p], [1216p, 1218p, 1222p, 1235p, 1243p, 1250p, 1258p], [1246p, 1248p, 1252p, 105p, 113p, 120p, 128p], [116p, 118p, 122p, 135p, 143p, 150p, 158p], [146p, 148p, 152p, 205p, 213p, 220p, 228p], [216p, 218p, 222p, 235p, 243p, 250p, 258p], [246p, 248p, 252p, 306p, 314p, 321p, 330p], [311p, 313p, 317p, 332p, 340p, 347p, 356p], [341p, 343p, 347p, 402p, 410p, 417p, 426p], [411p, 413p, 417p, 432p, 440p, 447p, 456p], [441p, 443p, 447p, 502p, 510p, 517p, 526p], [511p, 513p, 517p, 532p, 540p, 547p, 601p], [541p, 543p, 547p, 602p, 610p, 617p, 626p], [646p, 648p, 652p, 705p, 713p, 719p, 727p], [746p, 748p, 752p, 805p, 813p, 819p, 827p], [846p, 848p, 852p, 905p, 913p, 919p, 927p], [946p, 948p, 952p, 1005p, 1013p, 1019p, 1027p], [1046p, 1048p, 1052p, 1105p, 1113p, 1119p, 1127p]]
  -
  time_points: [Tuggeranong Bus Station (Platform 3), Erindale Centre, Athllon / Sulwood Kambah, Woden Bus Station]
  long_name: To Woden Bus Station
  between_stops:
  Athllon / Sulwood Kambah-Woden Bus Station: [Wjz2mTK, Wjz3gMq, Wjz3gQn, Wjz3gK-, Wjz3hL_, Wjz3iFK, Wjz3kwU, Wjz3kyX, Wjz3kAx]
  stop_times_saturday: [[842a, 856a, 906a, 915a], [942a, 956a, 1006a, 1015a], [1042a, 1056a, 1106a, 1115a], [1142a, 1156a, 1206p, 1215p], [1242p, 1256p, 106p, 115p], [142p, 156p, 206p, 215p], [242p, 256p, 306p, 315p], [342p, 356p, 406p, 415p], [442p, 456p, 506p, 515p], [542p, 556p, 606p, 615p], [642p, 656p, 706p, 715p], [742p, 756p, 806p, 815p], [842p, 856p, 906p, 915p], [942p, 956p, 1006p, 1015p], [1042p, 1056p, 1106p, 1115p]]
  short_name: "961"
  -
  time_points: [Woden Bus Station (Platform 4), Alexander Maconochie Centre]
  long_name: To Alexander Maconochie Centre
  between_stops:
  Woden Bus Station (Platform 4)-Alexander Maconochie Centre: [Wjz3dXS, Wjz3kAx]
  short_name: "88"
  stop_times: [[920a, 940a], [1255p, 115p], [455p, 515p]]
  -
  time_points: [Alexander Maconochie Centre, Woden Bus Station]
  long_name: To Woden Bus Station
  between_stops:
  Alexander Maconochie Centre-Woden Bus Station: [Wjz3kAx, Wjz3dXS]
  short_name: "88"
  stop_times: [[1150a, 1210p], [320p, 340p], [730p, 750p]]
  -
  time_points: [City Bus Station (Platform 8), Dickson / Antill St, Watson, Watson Terminus, Watson, Dickson / Antill St, City Bus Station]
  long_name: To City Bus Station
  between_stops: {}
   
  short_name: "939"
  stop_times_sunday: [[846a, 903a, 908a, 915a, 920a, 926a, 941a], [946a, 1003a, 1008a, 1015a, 1020a, 1026a, 1041a], [1046a, 1103a, 1108a, 1115a, 1120a, 1126a, 1141a], [1146a, 1203p, 1208p, 1215p, 1220p, 1226p, 1241p], [1246p, 103p, 108p, 115p, 120p, 126p, 141p], [146p, 203p, 208p, 215p, 220p, 226p, 241p], [246p, 303p, 308p, 315p, 320p, 326p, 341p], [346p, 403p, 408p, 415p, 420p, 426p, 441p], [446p, 503p, 508p, 515p, 520p, 526p, 541p], [546p, 603p, 608p, 615p, 620p, 626p, 641p], [646p, 703p, 708p, 715p, 720p, 726p, 741p]]
  -
  time_points: [City Bus Station (Platform 4), Macarthur / Miller O'Connor, Lyneham, North Lyneham, Dickson / Antill St, Hackett, Ainslie, City Bus Station]
  long_name: To City Bus Station
  between_stops:
  Ainslie-City Bus Station: [Wjz5YAK, Wjz5Yq4, Wjz5XnQ, Wjz5XrS, Wjz5XwW, Wjz5W3H, Wjz5W8l, Wjz5V64, Wjz5NRJ, Wjz5NHD]
  short_name: "936"
  stop_times_sunday: [[818a, 827a, 830a, 835a, 844a, 849a, 857a, 909a], [918a, 927a, 930a, 935a, 944a, 949a, 957a, 1009a], [1018a, 1027a, 1030a, 1035a, 1044a, 1049a, 1057a, 1109a], [1118a, 1127a, 1130a, 1135a, 1144a, 1149a, 1157a, 1209p], [1218p, 1227p, 1230p, 1235p, 1244p, 1249p, 1257p, 109p], [118p, 127p, 130p, 135p, 144p, 149p, 157p, 209p], [218p, 227p, 230p, 235p, 244p, 249p, 257p, 309p], [318p, 327p, 330p, 335p, 344p, 349p, 357p, 409p], [418p, 427p, 430p, 435p, 444p, 449p, 457p, 509p], [518p, 527p, 530p, 535p, 544p, 549p, 557p, 609p], [618p, 627p, 630p, 635p, 644p, 649p, 657p, 709p], [718p, 727p, 730p, 735p, 744p, 749p, 757p, 809p]]
  -
  time_points: [Cohen Street Bus Station (Platform 1), Westfield Bus Station (Platform 1), Belconnen Community Bus Station (Platform 3), University of Canberra, Australian Institute of Sport, National Hockey Centre Lyneham, Macarthur / Northbourne Ave, City Bus Station (Platform 9), Russell Offices, Railway Station Kingston, Newcastle Street after Isa Street, Fyshwick Direct Factory Outlet, Lithgow St Terminus Fyshwick]
  long_name: To Lithgow St Terminus Fyshwick
  between_stops:
  Westfield Bus Station (Platform 1)-Belconnen Community Bus Station (Platform 3): []
  Cohen Street Bus Station (Platform 1)-Westfield Bus Station (Platform 1): []
  Belconnen Community Bus Station (Platform 3)-University of Canberra: [Wjz681S, Wjz689c, Wjz68Ip, Wjz68IH, Wjz68Y0, Wjz68Yy]
  short_name: "980"
  stop_times_sunday: [[820a, 822a, 826a, 834a, 840a, 845a, 851a, 859a, 908a, 914a, 922a, 931a, 940a], [920a, 922a, 926a, 934a, 940a, 945a, 951a, 959a, 1008a, 1014a, 1022a, 1031a, 1040a], [1020a, 1022a, 1026a, 1034a, 1040a, 1045a, 1051a, 1059a, 1108a, 1114a, 1122a, 1131a, 1140a], [1120a, 1122a, 1126a, 1134a, 1140a, 1145a, 1151a, 1159a, 1208p, 1214p, 1222p, 1231p, 1240p], [1220p, 1222p, 1226p, 1234p, 1240p, 1245p, 1251p, 1259p, 108p, 114p, 122p, 131p, 140p], [120p, 122p, 126p, 134p, 140p, 145p, 151p, 159p, 208p, 214p, 222p, 231p, 240p], [220p, 222p, 226p, 234p, 240p, 245p, 251p, 259p, 308p, 314p, 322p, 331p, 340p], [320p, 322p, 326p, 334p, 340p, 345p, 351p, 359p, 408p, 414p, 422p, 431p, 440p], ["-", "-", "-", "-", "-", "-", "-", 415p, 424p, 430p, "-", "-", "-"], [420p, 422p, 426p, 434p, 440p, 445p, 451p, 459p, 508p, 514p, 522p, 531p, 540p], [520p, 522p, 526p, 534p, 540p, 545p, 551p, 558p, "-", "-", "-", "-", "-"], [615p, 617p, 621p, 629p, 635p, 640p, 645p, 652p, "-", "-", "-", "-", "-"]]
  -
  time_points: [Belconnen Community Bus Station (Platform 5), Westfield Bus Station (Platform 2), Cohen Street Bus Station (Platform 5), Florey, Page, Hawker, Cook, Jamison Centre, Calvary Hospital, Belconnen Community Bus Station, Westfield Bus Station, Cohen Street Bus Station]
  long_name: To Cohen Street Bus Station
  between_stops:
  Westfield Bus Station-Cohen Street Bus Station: []
  Belconnen Community Bus Station (Platform 5)-Westfield Bus Station (Platform 2): []
  Belconnen Community Bus Station-Westfield Bus Station: []
  Westfield Bus Station (Platform 2)-Cohen Street Bus Station (Platform 5): []
  short_name: "73"
  stop_times: [[916a, 918a, 922a, 927a, 933a, 937a, 944a, 947a, 954a, 1005a, 1007a, 1012a], [1046a, 1048a, 1052a, 1057a, 1103a, 1107a, 1114a, 1117a, 1124a, 1135a, 1137a, 1142a], [1216p, 1218p, 1222p, 1227p, 1233p, 1237p, 1244p, 1247p, 1254p, 105p, 107p, 112p], [146p, 148p, 152p, 157p, 203p, 207p, 214p, 217p, 224p, 235p, 237p, 242p]]
  -
  time_points: [Woden Bus Station (Platform 3), Waramanga, Fisher, Chapman, Rivett, Cooleman Court]
  long_name: To Cooleman Court
  between_stops: {}
   
  short_name: "927"
  stop_times_sunday: [[920a, 929a, 932a, 942a, 945a, 950a], [1020a, 1029a, 1032a, 1042a, 1045a, 1050a], [1120a, 1129a, 1132a, 1142a, 1145a, 1150a], [1220p, 1229p, 1232p, 1242p, 1245p, 1250p], [120p, 129p, 132p, 142p, 145p, 150p], [220p, 229p, 232p, 242p, 245p, 250p], [320p, 329p, 332p, 342p, 345p, 350p], [420p, 429p, 432p, 442p, 445p, 450p], [520p, 529p, 532p, 542p, 545p, 550p], [620p, 629p, 632p, 642p, 645p, 650p]]
  -
  time_points: [Gungahlin Marketplace, Ngunnawal Primary, Nicholls Primary, Federation Square, Chuculba / William Slim Dr, Belconnen Community Bus Station, Westfield Bus Station, Cohen Street Bus Station]
  long_name: To Cohen Street Bus Station
  between_stops:
  Westfield Bus Station-Cohen Street Bus Station: []
  Belconnen Community Bus Station-Westfield Bus Station: []
  Chuculba / William Slim Dr-Belconnen Community Bus Station: [Wjz6mip, Wjz69vO, Wjz69uI, Wjz69ht, Wjz69gA]
  stop_times_saturday: [[812a, 821a, 831a, 837a, 842a, 850a, 852a, 857a], [912a, 921a, 931a, 937a, 942a, 950a, 952a, 957a], [1012a, 1021a, 1031a, 1037a, 1042a, 1050a, 1052a, 1057a], [1112a, 1121a, 1131a, 1137a, 1142a, 1150a, 1152a, 1157a], [1212p, 1221p, 1231p, 1237p, 1242p, 1250p, 1252p, 1257p], [112p, 121p, 131p, 137p, 142p, 150p, 152p, 157p], [212p, 221p, 231p, 237p, 242p, 250p, 252p, 257p], [312p, 321p, 331p, 337p, 342p, 350p, 352p, 357p], [412p, 421p, 431p, 437p, 442p, 450p, 452p, 457p], [512p, 521p, 531p, 537p, 542p, 550p, 552p, 557p], [612p, 621p, 631p, 637p, 642p, 650p, 652p, 657p], [712p, 721p, 731p, 737p, 742p, 750p, 752p, 757p], [812p, 821p, 831p, 837p, 842p, 850p, 852p, 857p], [912p, 921p, 931p, 937p, 942p, 950p, 952p, 957p], [1012p, 1021p, 1031p, 1037p, 1042p, 1050p, 1052p, 1057p], [1112p, 1121p, 1131p, 1137p, 1142p, 1150p, 1152p, 1157p]]
  short_name: "951"
  -
  time_points: [Woden Bus Station (Platform 2), Stromlo High Waramanga, Cooleman Court]
  long_name: To Cooleman Court
  between_stops: {}
   
  short_name: "75"
  stop_times: [[1055a, 1108a, 1117a], [1255p, 108p, 117p]]
  -
  time_points: [City Bus Station (Platform 4), Caswell Drive, Aranda, Cook, Jamison Centre, Belconnen Community Bus Station, Westfield Bus Station, Cohen Street Bus Station]
  long_name: To Cohen Street Bus Station
  between_stops:
  Westfield Bus Station-Cohen Street Bus Station: []
  Belconnen Community Bus Station-Westfield Bus Station: []
  stop_times_saturday: [[814a, 823a, 824a, 827a, 836a, 845a, 847a, 852a], [914a, 923a, 924a, 927a, 936a, 945a, 947a, 952a], [1014a, 1023a, 1024a, 1027a, 1036a, 1045a, 1047a, 1052a], [1114a, 1123a, 1124a, 1127a, 1136a, 1145a, 1147a, 1152a], [1214p, 1223p, 1224p, 1227p, 1236p, 1245p, 1247p, 1252p], [114p, 123p, 124p, 127p, 136p, 145p, 147p, 152p], [214p, 223p, 224p, 227p, 236p, 245p, 247p, 252p], [314p, 323p, 324p, 327p, 336p, 345p, 347p, 352p], [414p, 423p, 424p, 427p, 436p, 445p, 447p, 452p], [514p, 523p, 524p, 527p, 536p, 545p, 547p, 552p], [614p, 623p, 624p, 627p, 636p, 645p, 647p, 652p], [714p, 723p, 724p, 727p, 736p, 745p, 747p, 752p], [814p, 823p, 824p, 827p, 836p, 845p, 847p, 852p], [914p, 923p, 924p, 927p, 936p, 945p, 947p, 952p], [1014p, 1023p, 1024p, 1027p, 1036p, 1045p, 1047p, 1052p], [1114p, 1123p, 1124p, 1127p, 1136p, 1145p, 1147p, 1152p]]
  short_name: "942"
  -
  time_points: [Woden Bus Station (Platform 14), Pearce, Narrabundah College, Kingston, Kings Ave / National Circuit, Russell Offices, City Bus Station]
  long_name: To City Bus Station
  between_stops: {}
   
  short_name: "938"
  stop_times_sunday: [[800a, 808a, 818a, 833a, 837a, 841a, 849a], [900a, 908a, 918a, 933a, 937a, 941a, 949a], [1000a, 1008a, 1018a, 1033a, 1037a, 1041a, 1049a], [1100a, 1108a, 1118a, 1133a, 1137a, 1141a, 1149a], [1200p, 1208p, 1218p, 1233p, 1237p, 1241p, 1249p], [100p, 108p, 118p, 133p, 137p, 141p, 149p], [200p, 208p, 218p, 233p, 237p, 241p, 249p], [300p, 308p, 318p, 333p, 337p, 341p, 349p], [400p, 408p, 418p, 433p, 437p, 441p, 449p], [500p, 508p, 518p, 533p, 537p, 541p, 549p], [600p, 608p, 618p, 633p, 637p, 641p, 649p], [700p, 707p, 716p, 729p, 733p, 737p, 744p]]
  -
  time_points: [City West, City Bus Station (Platform 1), Woden Bus Station (Platform 5), Mount Neighbour School, Kambah High, Tuggeranong Bus Station]
  long_name: To Tuggeranong Bus Station
  between_stops:
  City Bus Station (Platform 1)-Woden Bus Station (Platform 5): [Wjz5Nht, Wjz4KO9, Wjz4KNu, Wjz3eZ4, Wjz3eRR, Wjz3m3b, Wjz3m3b]
  City West-City Bus Station (Platform 1): []
  short_name: 60 160
  stop_times: [["-", "-", 647a, 701a, 708a, 718a], ["-", "-", 717a, 731a, 739a, 750a], ["-", "-", 747a, 801a, 809a, 820a], ["-", "-", 817a, 831a, 839a, 850a], ["-", "-", 847a, 901a, 909a, 920a], ["-", "-", 947a, 1001a, 1009a, 1019a], ["-", "-", 1047a, 1101a, 1109a, 1119a], ["-", "-", 1147a, 1201p, 1209p, 1219p], ["-", "-", 1247p, 101p, 109p, 119p], ["-", "-", 147p, 201p, 209p, 219p], ["-", "-", 247p, 301p, 309p, 320p], ["-", "-", 317p, 331p, 339p, 350p], ["-", "-", 347p, 401p, 409p, 420p], ["-", "-", 417p, 431p, 439p, 450p], ["-", "-", 447p, 501p, 509p, 520p], [455p, 501p, 517p, 531p, 539p, 550p], [531p, 537p, 553p, 607p, 615p, 626p], [555p, 601p, 617p, 631p, 638p, 647p], ["-", "-", 647p, 701p, 708p, 717p], ["-", "-", 743p, 757p, 804p, 813p], ["-", "-", 843p, 857p, 904p, 913p], ["-", "-", 943p, 957p, 1004p, 1013p], ["-", "-", 1043p, 1057p, 1104p, 1113p], []]
  -
  time_points: [Woden Bus Station, Curtin, City West, City Bus Station, ACTEW AGL House]
  long_name: To ACTEW AGL House
  between_stops:
  City Bus Station-ACTEW AGL House: [Wjz5Nht]
  City West-City Bus Station: []
  short_name: "732"
  stop_times: [[715a, 724a, 738a, 742a, 744a], [748a, 803a, 815a, 819a, 821a], [818a, 827a, 841a, 845a, 847a]]
  -
  time_points: [City Bus Station (Platform 4), Macarthur / Miller O'Connor, Lyneham, North Lyneham, Dickson / Antill St, Hackett, Ainslie, City Bus Station]
  long_name: To City Bus Station
  between_stops:
  Ainslie-City Bus Station: [Wjz5YAK, Wjz5Yq4, Wjz5XnQ, Wjz5XrS, Wjz5XwW, Wjz5W3H, Wjz5W8l, Wjz5V64, Wjz5NRJ, Wjz5NHD]
  stop_times_saturday: [[718a, 727a, 730a, 735a, 744a, 749a, 757a, 809a], [818a, 827a, 830a, 835a, 844a, 849a, 857a, 909a], [918a, 927a, 930a, 935a, 944a, 949a, 957a, 1009a], [1018a, 1027a, 1030a, 1035a, 1044a, 1049a, 1057a, 1109a], [1118a, 1127a, 1130a, 1135a, 1144a, 1149a, 1157a, 1209p], [1218p, 1227p, 1230p, 1235p, 1244p, 1249p, 1257p, 109p], [118p, 127p, 130p, 135p, 144p, 149p, 157p, 209p], [218p, 227p, 230p, 235p, 244p, 249p, 257p, 309p], [318p, 327p, 330p, 335p, 344p, 349p, 357p, 409p], [418p, 427p, 430p, 435p, 444p, 449p, 457p, 509p], [518p, 527p, 530p, 535p, 544p, 549p, 557p, 609p], [618p, 627p, 630p, 635p, 644p, 649p, 657p, 709p], [718p, 727p, 730p, 735p, 744p, 749p, 757p, 809p], [818p, 827p, 830p, 835p, 844p, 849p, 857p, 909p], [918p, 927p, 930p, 935p, 944p, 949p, 957p, 1009p], [1018p, 1027p, 1030p, 1035p, 1044p, 1049p, 1057p, 1109p], [1118p, 1127p, 1130p, 1135p, 1144p, "-", "-", "-"]]
  short_name: "936"
  -
  time_points: [Fairbairn Park, Brindabella Business Park, Chisholm, Tuggeranong Bus Station]
  long_name: To Tuggeranong Bus Station
  between_stops:
  Fairbairn Park-Brindabella Business Park: [WjzcJ38, WjzcBHZ, WjzcJ0K, WjzcrEu, WjzcrrQ, WjzcrK3]
  short_name: "786"
  stop_times: [[445p, 455p, 520p, 533p], [515p, 525p, 550p, 603p], [545p, 555p, 620p, 633p]]
  -
  time_points: [City West, City Bus Station (Platform 10), Russell Offices, Chisholm, Isabella, Calwell]
  long_name: To Calwell
  between_stops:
  City West-City Bus Station (Platform 10): []
  City Bus Station (Platform 10)-Russell Offices: [Wjz5Nht, Wjz5MsD, Wjz5MsT, Wjz5MEL, Wjz4T-X, Wjz4_jm, Wjz4_wS, Wjz4-KO, Wjz4-Rc, Wjz4-WL, Wjz4-WZ]
  short_name: "768"
  stop_times: [[447p, 453p, 502p, 526p, 537p, 545p], [519p, 525p, 534p, 558p, 609p, 617p]]
  -
  time_points: [Tuggeranong Bus Station (Platform 5), Monash Goodwin Village, Erindale Centre, Wanniassa High, Athllon / Sulwood Kambah, Woden Bus Station (Platform 10), Kings Ave / National Circuit, Russell Offices, ADFA, Campbell Park Offices]
  long_name: To Campbell Park Offices
  between_stops:
  Kings Ave / National Circuit-Russell Offices: [Wjz4Quk, Wjz4RwH, Wjz4RFJ]
  Athllon / Sulwood Kambah-Woden Bus Station (Platform 10): [Wjz2mTK, Wjz3gMq, Wjz3gQn, Wjz3gK-, Wjz3hL_, Wjz3iFK, Wjz3kwU, Wjz3kyX, Wjz3kAx]
  ADFA-Campbell Park Offices: [Wjzcend, Wjzce4H, Wjzce7O]
  Russell Offices-ADFA: [Wjzc60A, Wjzc60i, Wjzc55s, Wjzc54R, Wjzce7O, Wjzce4H, Wjzcend]
  short_name: "63"
  stop_times: [[611a, 619a, 623a, 628a, 633a, 640a, "-", "-", "-", "-"], [640a, 648a, 652a, 657a, 702a, 710a, 724a, 727a, 731a, 735a], [712a, 720a, 724a, 729a, 735a, 745a, 759a, 803a, 807a, 811a], [744a, 754a, 759a, 804a, 810a, 820a, 834a, 838a, 842a, 846a], [810a, 820a, 825a, 830a, 836a, 845a, "-", "-", "-", "-"], [845a, 855a, 900a, 905a, 911a, 920a, "-", "-", "-", "-"], [945a, 954a, 958a, 1003a, 1009a, 1017a, "-", "-", "-", "-"], [1045a, 1054a, 1058a, 1103a, 1109a, 1117a, "-", "-", "-", "-"], [1145a, 1154a, 1158a, 1203p, 1209p, 1217p, "-", "-", "-", "-"], [1245p, 1254p, 1258p, 103p, 109p, 117p, "-", "-", "-", "-"], [145p, 154p, 158p, 203p, 209p, 217p, "-", "-", "-", "-"], [245p, 254p, 258p, 303p, 309p, 318p, "-", "-", "-", "-"], [314p, 324p, 329p, 334p, 340p, 349p, "-", "-", "-", "-"], [345p, 355p, 400p, 405p, 411p, 420p, "-", "-", "-", "-"], [415p, 425p, 430p, 435p, 441p, 450p, "-", "-", "-", "-"], [445p, 455p, 500p, 505p, 511p, 520p, "-", "-", "-", "-"], [515p, 525p, 530p, 535p, 541p, 550p, "-", "-", "-", "-"], [545p, 555p, 600p, 605p, 611p, 620p, "-", "-", "-", "-"], [645p, 654p, 658p, 703p, 709p, 717p, "-", "-", "-", "-"], [745p, 754p, 758p, 803p, 809p, 817p, "-", "-", "-", "-"], [845p, 854p, 858p, 903p, 909p, 917p, "-", "-", "-", "-"], [945p, 954p, 958p, 1003p, 1009p, 1017p, "-", "-", "-", "-"], [1045p, 1054p, 1058p, 1103p, 1109p, "-", "-", "-", "-", "-"], []]
  -
  time_points: [Woden Bus Station (Platform 15), Pearce, Southlands Mawson, Torrens, Chifley, Lyons, Woden Bus Station]
  long_name: To Woden Bus Station
  between_stops: {}
   
  short_name: "922"
  stop_times_sunday: [[1033a, 1039a, 1043a, 1049a, 1054a, 1058a, 1101a], [1233p, 1239p, 1243p, 1249p, 1254p, 1258p, 101p], [233p, 239p, 243p, 249p, 254p, 258p, 301p], [433p, 439p, 443p, 449p, 454p, 458p, 501p], [633p, 639p, 643p, 649p, 654p, 658p, 701p]]
  -
  time_points: [Cohen Street Bus Station (Platform 1), Westfield Bus Station (Platform 1), Belconnen Community Bus Station (Platform 2), Chuculba / William Slim Dr, Federation Square, Nicholls Primary, Gungahlin Marketplace]
  long_name: To Gungahlin Market Place
  between_stops:
  Belconnen Community Bus Station (Platform 2)-Chuculba / William Slim Dr: [Wjz69gA, Wjz69ht, Wjz69uI, Wjz69vO, Wjz6mip]
  Cohen Street Bus Station (Platform 1)-Westfield Bus Station (Platform 1): []
  Westfield Bus Station (Platform 1)-Belconnen Community Bus Station (Platform 2): []
  stop_times_saturday: [[0945a, 0947a, 0951a, 1004a, 1009a, 1022a, 1031a], [1045a, 1047a, 1051a, 1104a, 1109a, 1122a, 1131a], [1145a, 1147a, 1151a, 1204p, 1209p, 1222p, 1231p], [1245p, 1247p, 1251p, 0104p, 0109p, 0122p, 0131p], [0145p, 0147p, 0151p, 0204p, 0209p, 0222p, 0231p], [0245p, 0247p, 0251p, 0304p, 0309p, 0322p, 0331p], [0345p, 0347p, 0351p, 0404p, 0409p, 0422p, 0431p], [0445p, 0447p, 0451p, 0504p, 0509p, 0522p, 0531p], [0545p, 0547p, 0551p, 0604p, 0609p, 0622p, 0631p], [0645p, 0647p, 0651p, 0704p, 0709p, 0722p, 0731p]]
  short_name: "952"
  -
  time_points: [Cohen Street Bus Station (Platform 1), Westfield Bus Station (Platform 1), Belconnen Community Bus Station (Platform 3), Gwydir Square Kaleen, Kaleen Village / Marybrynong, Giralang, Kaleen Village / Marybrynong, Gwydir Square Kaleen, Belconnen Community Bus Station, Westfield Bus Station, Cohen Street Bus Station]
  long_name: To Cohen Street Bus Station
  between_stops:
  Westfield Bus Station-Cohen Street Bus Station: []
  Belconnen Community Bus Station-Westfield Bus Station: []
  Westfield Bus Station (Platform 1)-Belconnen Community Bus Station (Platform 3): []
  Cohen Street Bus Station (Platform 1)-Westfield Bus Station (Platform 1): []
  short_name: "71"
  stop_times: [[927a, 929a, 933a, 943a, 948a, 957a, 959a, 1004a, 1014a, 1016a, 1021a], [1027a, 1029a, 1033a, 1043a, 1048a, 1057a, 1059a, 1104a, 1114a, 1116a, 1121a], [1127a, 1129a, 1133a, 1143a, 1148a, 1157a, 1159a, 1204p, 1214p, 1216p, 1221p], [1227p, 1229p, 1233p, 1243p, 1248p, 1257p, 1259p, 104p, 114p, 116p, 121p], [127p, 129p, 133p, 143p, 148p, 157p, 159p, 204p, 214p, 216p, 221p]]
  -
  time_points: [Erindale Dr / Charleston St Monash, Gowrie, Erindale / Sternberg Cres, Woden Bus Station (Platform 9), City Bus Station, City West]
  long_name: To City West
  between_stops:
  City Bus Station-City West: []
  Woden Bus Station (Platform 9)-City Bus Station: [Wjz3m3b, Wjz3m3b, Wjz3eRR, Wjz3eZ4, Wjz4KNu, Wjz4KO9, Wjz5Nht]
  short_name: "170"
  stop_times: [[710a, 720a, 732a, 749a, 804a, 806a], [728a, 738a, 750a, 807a, 822a, 824a]]
  -
  time_points: [Woden Bus Station (Platform 4), Curtin, John James Hospital, Yarralumla, City Bus Station (Platform 8), Macarthur / Northbourne Ave, Southwell Park, Giralang, Kaleen Village / Marybrynong, Gwydir Square Kaleen, University of Canberra, Belconnen Community Bus Station, Westfield Bus Station, Cohen Street Bus Station]
  long_name: To Cohen Street Bus Station
  between_stops:
  Westfield Bus Station-Cohen Street Bus Station: []
  Belconnen Community Bus Station-Westfield Bus Station: []
  University of Canberra-Belconnen Community Bus Station: [Wjz68Yy, Wjz68Y0, Wjz68IH, Wjz68Ip, Wjz689c, Wjz681S]
  short_name: "932"
  stop_times_sunday: [[839a, 850a, 853a, 856a, 909a, 915a, 919a, 928a, 936a, 941a, 947a, 950a, 952a, 957a], [939a, 950a, 953a, 956a, 1009a, 1015a, 1019a, 1028a, 1036a, 1041a, 1047a, 1050a, 1052a, 1057a], [1039a, 1050a, 1053a, 1056a, 1109a, 1115a, 1119a, 1128a, 1136a, 1141a, 1147a, 1150a, 1152a, 1157a], [1139a, 1150a, 1153a, 1156a, 1209p, 1215p, 1219p, 1228p, 1236p, 1241p, 1247p, 1250p, 1252p, 1257p], [1239p, 1250p, 1253p, 1256p, 109p, 115p, 119p, 128p, 136p, 141p, 147p, 150p, 152p, 157p], [139p, 150p, 153p, 156p, 209p, 215p, 219p, 228p, 236p, 241p, 247p, 250p, 252p, 257p], [239p, 250p, 253p, 256p, 309p, 315p, 319p, 328p, 336p, 341p, 347p, 350p, 352p, 357p], [339p, 350p, 353p, 356p, 409p, 415p, 419p, 428p, 436p, 441p, 447p, 450p, 452p, 457p], [439p, 450p, 453p, 456p, 509p, 515p, 519p, 528p, 536p, 541p, 547p, 550p, 552p, 557p], [539p, 550p, 553p, 556p, 609p, 615p, 619p, 628p, 635p, 640p, 645p, 648p, 650p, 655p], [639p, 648p, 651p, 654p, 707p, 712p, 716p, 725p, 732p, 737p, 742p, 745p, 747p, 752p]]
  -
  time_points: [Tuggeranong Bus Station (Platform 5), Erindale Centre, Athllon / Sulwood Kambah, Woden Bus Station]
  long_name: To Woden Bus Station
  between_stops:
  Athllon / Sulwood Kambah-Woden Bus Station: [Wjz2mTK, Wjz3gMq, Wjz3gQn, Wjz3gK-, Wjz3hL_, Wjz3iFK, Wjz3kwU, Wjz3kyX, Wjz3kAx]
  short_name: "964"
  stop_times_sunday: [[925a, 937a, 949a, 958a], [1025a, 1037a, 1049a, 1058a], [1125a, 1137a, 1149a, 1158a], [1225p, 1237p, 1249p, 1258p], [125p, 137p, 149p, 158p], [225p, 237p, 249p, 258p], [325p, 337p, 349p, 358p], [425p, 437p, 449p, 458p], [525p, 537p, 549p, 558p], [625p, 637p, 649p, 658p]]
  -
  time_points: [Spence Terminus, Spence, Copland College, William Webb / Ginninderra Drive, Northbourne Avenue / Antill St, Macarthur / Northbourne Ave, City Bus Station (Platform 10), Russell Offices, National Circ / Canberra Ave]
  long_name: To National Circ / Canberra Ave
  between_stops:
  Northbourne Avenue / Antill St-Macarthur / Northbourne Ave: [Wjz5SDc, Wjz5Sux, Wjz5SrO, Wjz5Sqk, Wjz5RvC, Wjz5Rsi, Wjz5RkN]
  Macarthur / Northbourne Ave-City Bus Station (Platform 10): [Wjz5QmR, Wjz5Qmu, Wjz5Qgn, Wjz5Qi2, Wjz5Pl0, Wjz5PdJ, Wjz5P8K, Wjz5P8n, Wjz5Oci, Wjz5O3Q]
  City Bus Station (Platform 10)-Russell Offices: [Wjz5Nht, Wjz5MsD, Wjz5MsT, Wjz5MEL, Wjz4T-X, Wjz4_jm, Wjz4_wS, Wjz4-KO, Wjz4-Rc, Wjz4-WL, Wjz4-WZ]
  short_name: "701"
  stop_times: [[658a, 703a, 710a, 714a, 724a, 726a, 737a, 746a, 754a], [731a, 736a, 743a, 747a, 805a, 810a, 826a, 835a, 843a], [745a, 750a, 757a, 801a, 819a, 824a, 840a, 849a, 857a]]
  -
  time_points: [Woden Bus Station (Platform 15), Canberra Hospital, Isaacs, Farrer Primary School, Southlands Mawson, Woden Bus Station]
  long_name: To Woden Bus Station
  between_stops: {}
   
  stop_times_saturday: [[910a, 916a, 921a, 927a, 933a, 943a], [1110a, 1116a, 1121a, 1127a, 1133a, 1143a], [110p, 116p, 121p, 127p, 133p, 143p], [310p, 316p, 321p, 327p, 333p, 343p], [510p, 516p, 521p, 527p, 533p, 543p], [713p, 718p, 723p, 728p, 734p, 743p], [913p, 918p, 923p, 928p, 934p, 943p], [1113p, 1118p, 1123p, 1128p, 1134p, 1143p]]
  short_name: "923"
  -
  time_points: [Tuggeranong Bus Station (Platform 3), Kambah High, Mount Neighbour School, Woden Bus Station]
  long_name: To Woden Bus Station
  between_stops: {}
   
  stop_times_saturday: [[755a, 805a, 811a, 823a], [855a, 905a, 911a, 923a], [955a, 1005a, 1011a, 1023a], [1055a, 1105a, 1111a, 1123a], [1155a, 1205p, 1211p, 1223p], [1255p, 105p, 111p, 123p], [155p, 205p, 211p, 223p], [255p, 305p, 311p, 323p], [355p, 405p, 411p, 423p], [455p, 505p, 511p, 523p], [555p, 605p, 611p, 623p], [655p, 705p, 711p, 721p], [755p, 804p, 810p, 820p], [855p, 904p, 910p, 920p], [955p, 1004p, 1010p, 1020p], [1055p, 1104p, 1110p, 1120p]]
  short_name: "960"
  -
  time_points: [Cooleman Court, Rivett, Fisher, Waramanga, Woden Bus Station (Platform 10), Kings Ave / National Circuit, Russell Offices, ADFA, Campbell Park Offices]
  long_name: To Campbell Park Offices
  between_stops:
  Kings Ave / National Circuit-Russell Offices: [Wjz4Quk, Wjz4RwH, Wjz4RFJ]
  ADFA-Campbell Park Offices: [Wjzcend, Wjzce4H, Wjzce7O]
  Russell Offices-ADFA: [Wjzc60A, Wjzc60i, Wjzc55s, Wjzc54R, Wjzce7O, Wjzce4H, Wjzcend]
  short_name: 27 227
  stop_times: [[629a, 635a, 643a, 647a, 655a, 709a, 712a, 716a, 720a], [654a, 700a, 708a, 712a, 720a, 734a, 738a, 742a, 746a], ["-", "-", 728a, 735a, 746a, "-", "-", "-", "-"], [722a, 728a, 736a, 743a, 755a, 809a, 813a, 817a, 821a], [740a, 746a, 754a, 801a, 812a, "-", "-", "-", "-"], [748a, 754a, 802a, 809a, 820a, "-", "-", "-", "-"], [823a, 829a, 837a, 844a, 855a, "-", "-", "-", "-"], [853a, 859a, 907a, 914a, 925a, "-", "-", "-", "-"], [925a, 931a, 938a, 942a, 949a, "-", "-", "-", "-"], [1025a, 1031a, 1038a, 1042a, 1049a, "-", "-", "-", "-"], [1125a, 1131a, 1138a, 1142a, 1149a, "-", "-", "-", "-"], [1225p, 1231p, 1238p, 1242p, 1249p, "-", "-", "-", "-"], [125p, 131p, 138p, 142p, 149p, "-", "-", "-", "-"], [225p, 231p, 238p, 242p, 249p, "-", "-", "-", "-"], [325p, 330p, 337p, 341p, 349p, "-", "-", "-", "-"], [355p, 400p, 407p, 411p, 419p, "-", "-", "-", "-"], [425p, 430p, 437p, 441p, 449p, "-", "-", "-", "-"], [525p, 530p, 537p, 541p, 549p, "-", "-", "-", "-"], [625p, 630p, 637p, 640p, 647p, "-", "-", "-", "-"], [700p, 705p, 712p, 715p, 722p, "-", "-", "-", "-"], [800p, 805p, 812p, 815p, 822p, "-", "-", "-", "-"], [900p, 905p, 912p, 915p, 922p, "-", "-", "-", "-"], [1000p, 1005p, 1012p, 1015p, 1022p, "-", "-", "-", "-"]]
  -
  time_points: [Tuggeranong Bus Station (Platform 3), Kambah High, Mount Neighbour School, Woden Bus Station (Platform 9), City Bus Station, City West]
  long_name: To City West
  between_stops:
  City Bus Station-City West: []
  Woden Bus Station (Platform 9)-City Bus Station: [Wjz3m3b, Wjz3m3b, Wjz3eRR, Wjz3eZ4, Wjz4KNu, Wjz4KO9, Wjz5Nht]
  short_name: 60 160
  stop_times: [[606a, 615a, 621a, 632a, "-", "-"], [706a, 715a, 721a, 734a, 749a, 752a], [730a, 740a, 748a, 802a, "-", "-"], [738a, 748a, 756a, 811a, 826a, 829a], [752a, 802a, 810a, 824a, "-", "-"], [808a, 818a, 826a, 841a, 856a, 859a], [836a, 846a, 854a, 908a, "-", "-"], [906a, 916a, 924a, 937a, "-", "-"], [1006a, 1016a, 1024a, 1036a, "-", "-"], [1106a, 1116a, 1124a, 1136a, "-", "-"], [1206p, 1216p, 1224p, 1236p, "-", "-"], [106p, 116p, 124p, 136p, "-", "-"], [206p, 216p, 224p, 236p, "-", "-"], [236p, 246p, 254p, 307p, "-", "-"], [306p, 316p, 324p, 338p, "-", "-"], [336p, 346p, 354p, 408p, "-", "-"], [406p, 416p, 424p, 438p, "-", "-"], [436p, 446p, 454p, 508p, "-", "-"], [506p, 516p, 524p, 538p, "-", "-"], [536p, 546p, 554p, 608p, "-", "-"], [606p, 616p, 624p, 637p, "-", "-"], [706p, 716p, 722p, 734p, "-", "-"], [806p, 816p, 822p, 834p, "-", "-"], [906p, 916p, 922p, 934p, "-", "-"], [1006p, 1016p, 1022p, 1034p, "-", "-"], [1106p, 1116p, 1122p, 1134p, "-", "-"]]
  -
  time_points: [Woden Bus Station (Platform 14), Canberra Hospital, Narrabundah College, Manuka / Captain Cook Cres, Kingston, Kings Ave / National Circuit, Russell Offices, City Bus Station]
  long_name: To City Bus Station
  between_stops:
  Kings Ave / National Circuit-Russell Offices: [Wjz4Quk, Wjz4RwH, Wjz4RFJ]
  Woden Bus Station (Platform 14)-Canberra Hospital: [Wjz3mAg, Wjz3mPO, Wjz3mWn]
  Russell Offices-City Bus Station: [Wjz4-WZ, Wjz4-WL, Wjz4-Rc, Wjz4-KO, Wjz4_wS, Wjz4_jm, Wjz4T-X, Wjz5MEL, Wjz5MsT, Wjz5MsD, Wjz5Nht]
  short_name: "5"
  stop_times: [[615a, 621a, 630a, 636a, 640a, 644a, 648a, 653a], [641a, 649a, 659a, 711a, 714a, 718a, 722a, 730a], [659a, 707a, 717a, 729a, 732a, 738a, 743a, 752a], [713a, 721a, 731a, 744a, 747a, 753a, 758a, 807a], [729a, 737a, 747a, 800a, 803a, 809a, 814a, 823a], [747a, 755a, 805a, 818a, 821a, 827a, 832a, 841a], [806a, 814a, 824a, 837a, 840a, 846a, 851a, 900a], [825a, 833a, 843a, 856a, 859a, 905a, 910a, 919a], [844a, 852a, 902a, 915a, 918a, 924a, 929a, 937a], [914a, 922a, 932a, 944a, 947a, 951a, 955a, 1003a], [944a, 952a, 1002a, 1014a, 1017a, 1021a, 1025a, 1033a], [1014a, 1022a, 1032a, 1044a, 1047a, 1051a, 1055a, 1103a], [1044a, 1052a, 1102a, 1114a, 1117a, 1121a, 1125a, 1133a], [1114a, 1122a, 1132a, 1144a, 1147a, 1151a, 1155a, 1203p], [1144a, 1152a, 1202p, 1214p, 1217p, 1221p, 1225p, 1233p], [1214p, 1222p, 1232p, 1244p, 1247p, 1251p, 1255p, 103p], [1244p, 1252p, 102p, 114p, 117p, 121p, 125p, 133p], [114p, 122p, 132p, 144p, 147p, 151p, 155p, 203p], [144p, 152p, 202p, 214p, 217p, 221p, 225p, 233p], [214p, 222p, 232p, 244p, 247p, 251p, 255p, 303p], [244p, 252p, 302p, 315p, 318p, 324p, 329p, 338p], [314p, 322p, 332p, 345p, 348p, 354p, 359p, 408p], [342p, 350p, 400p, 413p, 416p, 422p, 427p, 436p], [413p, 421p, 431p, 444p, 447p, 453p, 458p, 507p], [447p, 455p, 505p, 518p, 521p, 527p, 532p, 541p], [518p, 526p, 536p, 549p, 552p, 558p, 603p, 612p], [548p, 556p, 606p, 619p, 622p, 628p, 632p, 640p], [648p, 655p, 704p, 716p, 719p, 723p, 727p, 735p], [748p, 755p, 804p, 816p, 819p, 823p, 827p, 835p], [848p, 855p, 904p, 916p, 919p, 923p, 927p, 935p], [948p, 955p, 1004p, 1016p, 1019p, 1023p, 1027p, 1035p], [1048p, 1055p, 1104p, 1116p, 1119p, 1123p, 1127p, 1135p]]
  -
  time_points: [City Bus Station (Platform 8), Macarthur / Northbourne Ave, North Lyneham, Gwydir Square Kaleen, University of Canberra, Belconnen Community Bus Station, Westfield Bus Station, Cohen Street Bus Station]
  long_name: To Cohen Street Bus Station
  between_stops:
  Westfield Bus Station-Cohen Street Bus Station: []
  City Bus Station (Platform 8)-Macarthur / Northbourne Ave: [Wjz5O3Q, Wjz5Oci, Wjz5P8n, Wjz5P8K, Wjz5PdJ, Wjz5Pl0, Wjz5Qi2, Wjz5Qgn, Wjz5Qmu, Wjz5QmR]
  Belconnen Community Bus Station-Westfield Bus Station: []
  University of Canberra-Belconnen Community Bus Station: [Wjz681S, Wjz689c]
  short_name: "31"
  stop_times: [["-", "-", 637a, 643a, 648a, 654a, 656a, 701a], ["-", "-", 707a, 713a, 718a, 724a, 726a, 731a], [733a, 740a, 745a, 753a, 800a, 806a, 808a, 813a], [803a, 810a, 815a, 823a, 830a, 836a, 838a, 843a], [829a, 836a, 841a, 849a, 856a, 902a, 904a, 909a], [910a, 917a, 922a, 930a, 936a, 942a, 944a, 949a], [948a, 954a, 959a, 1005a, 1011a, 1017a, 1019a, 1024a], [1048a, 1054a, 1059a, 1105a, 1111a, 1117a, 1119a, 1124a], [1148a, 1154a, 1159a, 1205p, 1211p, 1217p, 1219p, 1224p], [1248p, 1254p, 1259p, 105p, 111p, 117p, 119p, 124p], [148p, 154p, 159p, 205p, 211p, 217p, 219p, 224p], [248p, 254p, 259p, 307p, 315p, 321p, 323p, 328p], [303p, 310p, 315p, 323p, 331p, 337p, 339p, 344p], [333p, 340p, 345p, 353p, 401p, 407p, 409p, 414p], [403p, 410p, 415p, 423p, 431p, 437p, 439p, 444p], [433p, 440p, 445p, 453p, 501p, 507p, 509p, 514p], [503p, 510p, 515p, 523p, 531p, 537p, 539p, 544p], [533p, 540p, 545p, 553p, 601p, 607p, 609p, 614p], [603p, 610p, 615p, 623p, 631p, 637p, 639p, 644p], [648p, 654p, 659p, 705p, 710p, 716p, 718p, 723p], [748p, 754p, 759p, 805p, 810p, 816p, 818p, 823p], [848p, 854p, 859p, 905p, 910p, 916p, 918p, 923p], [948p, 954p, 959p, 1005p, 1010p, 1016p, 1018p, 1023p], [1048p, 1054p, 1059p, 1105p, 1110p, 1116p, 1118p, 1123p]]
  -
  time_points: [Fraser West Terminus, Charnwood, Scullin, Page, Cohen Street Bus Station (Platform 3), Westfield Bus Station (Platform 1), Belconnen Community Bus Station (Platform 1), City Bus Station (Platform 1), Woden Bus Station (Platform 6), Tuggeranong Bus Station]
long_name: To Tuggeranong Bus Station long_name: To Tuggeranong Bus Station
between_stops: between_stops:
Cohen Street Bus Station (Platform 3)-Westfield Bus Station (Platform 1): [] Cohen Street Bus Station (Platform 3)-Westfield Bus Station (Platform 1): []
Westfield Bus Station (Platform 1)-Belconnen Community Bus Station (Platform 1): [] Westfield Bus Station (Platform 1)-Belconnen Community Bus Station (Platform 1): []
Woden Bus Station (Platform 6)-Tuggeranong Bus Station: [Wjz3kAx, Wjz3kyX, Wjz3kwU, Wjz3iFK, Wjz3hL_, Wjz3gK-, Wjz3gQn, Wjz3gMq, Wjz2nLE, Wjz2mTK, Wjz2mGO, Wjz2lDC, Wjz239F, Wjz238T, Wjz213q] Woden Bus Station (Platform 6)-Tuggeranong Bus Station: [Wjz3kAx, Wjz3kyX, Wjz3kwU, Wjz3iFK, Wjz3hL_, Wjz3gK-, Wjz3gQn, Wjz3gMq, Wjz2nLE, Wjz2mTK, Wjz2mGO, Wjz2lDC, Wjz239F, Wjz238T, Wjz213q]
City Bus Station (Platform 1)-Woden Bus Station (Platform 6): [Wjz5Nht, Wjz4KO9, Wjz4KNu, Wjz3eZ4, Wjz3eRR, Wjz3m3b, Wjz3m3b] City Bus Station (Platform 1)-Woden Bus Station (Platform 6): [Wjz5Nht, Wjz4KO9, Wjz4KNu, Wjz3eZ4, Wjz3eRR, Wjz3m3b, Wjz3m3b]
Belconnen Community Bus Station (Platform 1)-City Bus Station (Platform 1): [Wjz681S, Wjz689c, Wjz68W3, Wjz68W5, Wjz6gia, Wjz6giR, Wjz5nw6, Wjz5nwb, Wjz5mbS, Wjz5maK, Wjz5G6B, Wjz5G6U, Wjz5GNG, Wjz5GMT, Wjz5FSY, Wjz5F-1] Belconnen Community Bus Station (Platform 1)-City Bus Station (Platform 1): [Wjz681S, Wjz689c, Wjz68W3, Wjz68W5, Wjz6gia, Wjz6giR, Wjz5nw6, Wjz5nwb, Wjz5mbS, Wjz5maK, Wjz5G6B, Wjz5G6U, Wjz5GNG, Wjz5GMT, Wjz5FSY, Wjz5F-1]
short_name: 13 313 short_name: 13 313
stop_times: [[546a, 550a, 559a, 603a, 610a, 612a, 616a, 636a, 653a, 706a], [616a, 620a, 629a, 633a, 640a, 642a, 646a, 706a, 723a, 738a], [646a, 650a, 659a, 703a, 710a, 712a, 716a, 737a, 754a, 811a], [712a, 716a, 725a, 729a, 737a, 739a, 743a, 806a, 823a, 840a], [737a, 742a, 752a, 757a, 805a, 807a, 811a, 833a, 850a, 907a], [757a, 802a, 812a, 817a, 825a, 827a, 831a, 853a, 910a, 927a], [817a, 822a, 832a, 837a, 845a, 847a, 851a, 913a, 930a, 945a], [842a, 847a, 857a, 902a, 910a, 912a, 916a, 937a, 954a, 1009a], [914a, 919a, 929a, 933a, 940a, 942a, 946a, 1006a, 1023a, 1038a], [946a, 950a, 959a, 1003a, 1010a, 1012a, 1016a, 1036a, 1053a, 1108a], [1016a, 1020a, 1029a, 1033a, 1040a, 1042a, 1046a, 1106a, 1123a, 1138a], [1046a, 1050a, 1059a, 1103a, 1110a, 1112a, 1116a, 1136a, 1153a, 1208p], [1116a, 1120a, 1129a, 1133a, 1140a, 1142a, 1146a, 1206p, 1223p, 1238p], [1146a, 1150a, 1159a, 1203p, 1210p, 1212p, 1216p, 1236p, 1253p, 108p], [1216p, 1220p, 1229p, 1233p, 1240p, 1242p, 1246p, 106p, 123p, 138p], [1246p, 1250p, 1259p, 103p, 110p, 112p, 116p, 136p, 153p, 208p], [116p, 120p, 129p, 133p, 140p, 142p, 146p, 206p, 223p, 238p], [146p, 150p, 159p, 203p, 210p, 212p, 216p, 236p, 253p, 310p], [216p, 220p, 229p, 233p, 240p, 242p, 246p, 307p, 324p, 343p], [245p, 249p, 258p, 302p, 310p, 312p, 316p, 338p, 355p, 414p], [313p, 318p, 328p, 332p, 340p, 342p, 346p, 408p, 425p, 444p], [343p, 348p, 358p, 402p, 410p, 412p, 416p, 438p, 455p, 514p], [418p, 423p, 433p, 437p, 445p, 447p, 451p, "-", "-", "-"], [449p, 454p, 504p, 508p, 516p, 518p, 522p, "-", "-", "-"], [513p, 518p, 528p, 532p, 540p, 542p, 546p, 608p, 625p, 641p], [543p, 548p, 558p, 602p, 610p, 612p, 616p, 636p, 650p, 705p], [615p, 620p, 630p, 634p, 640p, 642p, 646p, 705p, 719p, 734p], [710p, 714p, 723p, 727p, 733p, 735p, 739p, "-", "-", "-"], [810p, 814p, 823p, 827p, 833p, 835p, 839p, "-", "-", "-"], [910p, 914p, 923p, 927p, 933p, 935p, 939p, "-", "-", "-"], [1010p, 1014p, 1023p, 1027p, 1033p, 1035p, 1039p, "-", "-", "-"]] stop_times: [[546a, 550a, 559a, 603a, 610a, 612a, 616a, 636a, 653a, 706a], [616a, 620a, 629a, 633a, 640a, 642a, 646a, 706a, 723a, 738a], [646a, 650a, 659a, 703a, 710a, 712a, 716a, 737a, 754a, 811a], [712a, 716a, 725a, 729a, 737a, 739a, 743a, 806a, 823a, 840a], [737a, 742a, 752a, 757a, 805a, 807a, 811a, 833a, 850a, 907a], [757a, 802a, 812a, 817a, 825a, 827a, 831a, 853a, 910a, 927a], [817a, 822a, 832a, 837a, 845a, 847a, 851a, 913a, 930a, 945a], [842a, 847a, 857a, 902a, 910a, 912a, 916a, 937a, 954a, 1009a], [914a, 919a, 929a, 933a, 940a, 942a, 946a, 1006a, 1023a, 1038a], [946a, 950a, 959a, 1003a, 1010a, 1012a, 1016a, 1036a, 1053a, 1108a], [1016a, 1020a, 1029a, 1033a, 1040a, 1042a, 1046a, 1106a, 1123a, 1138a], [1046a, 1050a, 1059a, 1103a, 1110a, 1112a, 1116a, 1136a, 1153a, 1208p], [1116a, 1120a, 1129a, 1133a, 1140a, 1142a, 1146a, 1206p, 1223p, 1238p], [1146a, 1150a, 1159a, 1203p, 1210p, 1212p, 1216p, 1236p, 1253p, 108p], [1216p, 1220p, 1229p, 1233p, 1240p, 1242p, 1246p, 106p, 123p, 138p], [1246p, 1250p, 1259p, 103p, 110p, 112p, 116p, 136p, 153p, 208p], [116p, 120p, 129p, 133p, 140p, 142p, 146p, 206p, 223p, 238p], [146p, 150p, 159p, 203p, 210p, 212p, 216p, 236p, 253p, 310p], [216p, 220p, 229p, 233p, 240p, 242p, 246p, 307p, 324p, 343p], [245p, 249p, 258p, 302p, 310p, 312p, 316p, 338p, 355p, 414p], [313p, 318p, 328p, 332p, 340p, 342p, 346p, 408p, 425p, 444p], [343p, 348p, 358p, 402p, 410p, 412p, 416p, 438p, 455p, 514p], [418p, 423p, 433p, 437p, 445p, 447p, 451p, "-", "-", "-"], [449p, 454p, 504p, 508p, 516p, 518p, 522p, "-", "-", "-"], [513p, 518p, 528p, 532p, 540p, 542p, 546p, 608p, 625p, 641p], [543p, 548p, 558p, 602p, 610p, 612p, 616p, 636p, 650p, 705p], [615p, 620p, 630p, 634p, 640p, 642p, 646p, 705p, 719p, 734p], [710p, 714p, 723p, 727p, 733p, 735p, 739p, "-", "-", "-"], [810p, 814p, 823p, 827p, 833p, 835p, 839p, "-", "-", "-"], [910p, 914p, 923p, 927p, 933p, 935p, 939p, "-", "-", "-"], [1010p, 1014p, 1023p, 1027p, 1033p, 1035p, 1039p, "-", "-", "-"]]
- -
time_points: [Woden Bus Station (Platform 11), Athllon / Sulwood Kambah, Erindale Centre, Tuggeranong Bus Station] time_points: [Woodcock / Clare Dennis, Tharwa Drive / Pockett Ave, Mentone View / Tharwa Drive, Russell Offices, City Bus Station (Platform 11), City West]
  long_name: To City West
  between_stops:
  Russell Offices-City Bus Station (Platform 11): [Wjz4-WZ, Wjz4-WL, Wjz4-Rc, Wjz4-KO, Wjz4_wS, Wjz4_jm, Wjz4T-X, Wjz5MEL, Wjz5MsT, Wjz5MsD, Wjz5Nht]
  City Bus Station (Platform 11)-City West: []
  short_name: "788"
  stop_times: [[710a, 719a, 734a, 811a, 820a, 824a], [740a, 749a, 804a, 841a, 850a, 854a]]
  -
  time_points: [Cohen Street Bus Station (Platform 2), Westfield Bus Station (Platform 1), Belconnen Community Bus Station (Platform 3), Jamison Centre, Cook, Aranda, Caswell Drive, City Bus Station]
  long_name: To City Bus Station
  between_stops:
  Westfield Bus Station (Platform 1)-Belconnen Community Bus Station (Platform 3): []
  Cohen Street Bus Station (Platform 2)-Westfield Bus Station (Platform 1): []
  stop_times_saturday: [[815a, 817a, 821a, 830a, 839a, 843a, 844a, 855a], [915a, 917a, 921a, 930a, 939a, 943a, 944a, 955a], [1015a, 1017a, 1021a, 1030a, 1039a, 1043a, 1044a, 1055a], [1115a, 1117a, 1121a, 1130a, 1139a, 1143a, 1144a, 1155a], [1215p, 1217p, 1221p, 1230p, 1239p, 1243p, 1244p, 1255p], [115p, 117p, 121p, 130p, 139p, 143p, 144p, 155p], [215p, 217p, 221p, 230p, 239p, 243p, 244p, 255p], [315p, 317p, 321p, 330p, 339p, 343p, 344p, 355p], [415p, 417p, 421p, 430p, 439p, 443p, 444p, 455p], [515p, 517p, 521p, 530p, 539p, 543p, 544p, 555p], [615p, 617p, 621p, 630p, 639p, 643p, 644p, 655p], [715p, 717p, 721p, 730p, 739p, 743p, 744p, 755p], [815p, 817p, 821p, 830p, 839p, 843p, 844p, 855p], [915p, 917p, 921p, 930p, 939p, 943p, 944p, 955p], [1015p, 1017p, 1021p, 1030p, 1039p, 1043p, 1044p, 1055p]]
  short_name: "942"
  -
  time_points: [City West, City Bus Station (Platform 1), Woden Bus Station (Platform 5), Kambah Village, Kambah High, Tuggeranong Bus Station]
long_name: To Tuggeranong Bus Station long_name: To Tuggeranong Bus Station
between_stops: between_stops:
Woden Bus Station (Platform 11)-Athllon / Sulwood Kambah: [Wjz3kAx, Wjz3kyX, Wjz3kwU, Wjz3iFK, Wjz3hL_, Wjz3gK-, Wjz3gQn, Wjz3gMq, Wjz2mTK] City Bus Station (Platform 1)-Woden Bus Station (Platform 5): [Wjz5Nht, Wjz4KO9, Wjz4KNu, Wjz3eZ4, Wjz3eRR, Wjz3m3b, Wjz3m3b]
short_name: "961" City West-City Bus Station (Platform 1): []
stop_times_sunday: [[931a, 940a, 950a, 1003a], [1031a, 1040a, 1050a, 1103a], [1131a, 1140a, 1150a, 1203p], [1231p, 1240p, 1250p, 103p], [131p, 140p, 150p, 203p], [231p, 240p, 250p, 303p], [331p, 340p, 350p, 403p], [431p, 440p, 450p, 503p], [531p, 540p, 550p, 603p], [628p, 637p, 647p, 700p]] short_name: "62"
- stop_times: [["-", "-", "-", 709a, 716a, 723a], ["-", "-", 732a, 744a, 753a, 800a], ["-", "-", 802a, 814a, 823a, 830a], ["-", "-", 832a, 844a, 853a, 900a], ["-", "-", 902a, 914a, 923a, 930a], ["-", "-", 932a, 943a, 951a, 958a], ["-", "-", 1032a, 1043a, 1051a, 1058a], ["-", "-", 1132a, 1143a, 1151a, 1158a], ["-", "-", 1232p, 1243p, 1251p, 1258p], ["-", "-", 132p, 143p, 151p, 158p], ["-", "-", 232p, 243p, 251p, 258p], ["-", "-", 332p, 344p, 353p, 400p], ["-", "-", 402p, 414p, 423p, 430p], ["-", "-", 432p, 444p, 453p, 500p], ["-", "-", 502p, 514p, 523p, 530p], [510p, 516p, 532p, 544p, 553p, 600p], [540p, 546p, 602p, 614p, 623p, 630p], [610p, 616p, 632p, 643p, 651p, 658p], ["-", "-", 732p, 743p, 751p, 758p], ["-", "-", 832p, 843p, 851p, 858p], ["-", "-", 932p, 943p, 951p, 958p], ["-", "-", 1032p, 1043p, 1051p, 1058p], ["-", "-", 1132p, 1143p, 1151p, 1158p]]
time_points: [Cooleman Court, Rivett Shops, Chapman Shops, Fisher Shops, Waramanga Shops, Woden Bus Station] -
long_name: To Woden Bus Station time_points: [City Bus Station (Platform 8), Macarthur / Northbourne Ave, Flemington Rd / Sandford St, Kosciuszko / Everard, Gungahlin Marketplace, Chuculba / William Slim Dr, William Webb / Ginninderra Drive, Belconnen Community Bus Station, Westfield Bus Station, Cohen Street Bus Station]
between_stops: {}  
   
short_name: "927"  
stop_times_sunday: [[855a, 903a, 906a, 916a, 919a, 926a], [955a, 1003a, 1006a, 1016a, 1019a, 1026a], [1055a, 1103a, 1106a, 1116a, 1119a, 1126a], [1155a, 1203p, 1206p, 1216p, 1219p, 1226p], [1255p, 103p, 106p, 116p, 119p, 126p], [155p, 203p, 206p, 216p, 219p, 226p], [255p, 303p, 306p, 316p, 319p, 326p], [355p, 403p, 406p, 416p, 419p, 426p], [455p, 503p, 506p, 516p, 519p, 526p], [555p, 603p, 606p, 616p, 619p, 626p], [655p, 703p, 706p, 716p, 719p, 726p]]  
-  
time_points: [Tuggeranong Bus Station (Platform 7), Erindale Centre, Gowrie Shops, Chisholm Shops, Gowrie Shops, Erindale Centre, Tuggeranong Bus Station]  
long_name: To Tuggeranong Bus Station  
between_stops: {}  
   
short_name: "966"  
stop_times_sunday: [[908a, 920a, 927a, 936a, 948a, 957a, 1010a], [1008a, 1020a, 1027a, 1036a, 1048a, 1057a, 1110a], [1108a, 1120a, 1127a, 1136a, 1148a, 1157a, 1210p], [1208p, 1220p, 1227p, 1236p, 1248p, 1257p, 110p], [108p, 120p, 127p, 136p, 148p, 157p, 210p], [208p, 220p, 227p, 236p, 248p, 257p, 310p], [308p, 320p, 327p, 336p, 348p, 357p, 410p], [408p, 420p, 427p, 436p, 448p, 457p, 510p], [508p, 520p, 527p, 536p, 548p, 557p, 610p], [608p, 620p, 627p, 636p, 648p, 657p, 710p], [705p, 717p, 724p, 733p, 745p, 754p, 807p]]  
-  
time_points: [Fairbairn Park, Brindabella Business Park, Majura Business Park, Campbell Park Offices, ADFA, War Memorial Limestone Ave, City Bus Station (Platform 4), Caswell Drive, Aranda Shops, Cook Shops, Jamison Centre, Belconnen Community Bus Station, Westfield Bus Station, Cohen Street Bus Station]  
long_name: To Cohen Street Bus Station long_name: To Cohen Street Bus Station
between_stops: between_stops:
Westfield Bus Station-Cohen Street Bus Station: [] Westfield Bus Station-Cohen Street Bus Station: []
  City Bus Station (Platform 8)-Macarthur / Northbourne Ave: [Wjz5O3Q, Wjz5Oci, Wjz5P8n, Wjz5P8K, Wjz5PdJ, Wjz5Pl0, Wjz5Qi2, Wjz5Qgn, Wjz5Qmu, Wjz5QmR]
Belconnen Community Bus Station-Westfield Bus Station: [] Belconnen Community Bus Station-Westfield Bus Station: []
ADFA-War Memorial Limestone Ave: [Wjzcend, Wjzce4H, Wjzce7O, Wjzd8br, Wjzd0CK, Wjz5VUU] stop_times_saturday: [[838a, 844a, 852a, 859a, 909a, 919a, 924a, 930a, 932a, 937a], [938a, 944a, 952a, 959a, 1009a, 1019a, 1024a, 1030a, 1032a, 1037a], [1038a, 1044a, 1052a, 1059a, 1109a, 1119a, 1124a, 1130a, 1132a, 1137a], [1138a, 1144a, 1152a, 1159a, 1209p, 1219p, 1224p, 1230p, 1232p, 1237p], [1238p, 1244p, 1252p, 1259p, 109p, 119p, 124p, 130p, 132p, 137p], [138p, 144p, 152p, 159p, 209p, 219p, 224p, 230p, 232p, 237p], [238p, 244p, 252p, 259p, 309p, 319p, 324p, 330p, 332p, 337p], [338p, 344p, 352p, 359p, 409p, 419p, 424p, 430p, 432p, 437p], [438p, 444p, 452p, 459p, 509p, 519p, 524p, 530p, 532p, 537p], [538p, 544p, 552p, 559p, 609p, 619p, 624p, 630p, 632p, 637p], [638p, 644p, 652p, 659p, 709p, 719p, 724p, 730p, 732p, 737p]]
Campbell Park Offices-ADFA: [Wjzce7O, Wjzce4H, Wjzcend] short_name: "956"
War Memorial Limestone Ave-City Bus Station (Platform 4): [Wjz5VFA, Wjz5VAq, Wjz5W8l, Wjz5V64, Wjz5NRJ, Wjz5NHD, Wjz5NAQ]  
Fairbairn Park-Brindabella Business Park: [WjzcJ38, WjzcBHZ, WjzcJ0K, WjzcrEu, WjzcrrQ, WjzcrK3]  
short_name: "10"  
stop_times: [["-", "-", "-", "-", "-", "-", 632a, 642a, 644a, 649a, 659a, 709a, 711a, 716a], ["-", "-", "-", "-", "-", "-", 702a, 712a, 714a, 719a, 729a, 739a, 741a, 746a], ["-", "-", "-", "-", "-", "-", 732a, 742a, 744a, 749a, 759a, 809a, 811a, 816a], ["-", "-", "-", "-", "-", "-", 802a, 812a, 814a, 819a, 829a, 839a, 841a, 846a], ["-", "-", "-", 800a, 803a, 808a, 820a, 830a, 832a, 837a, 847a, 857a, 859a, 904a], ["-", "-", "-", 830a, 833a, 838a, 850a, 900a, 902a, 907a, 917a, 927a, 929a, 934a], ["-", "-", "-", 900a, 903a, 908a, 920a, 930a, 932a, 937a, 947a, 957a, 959a, 1004a], [918a, 928a, 933a, 940a, 943a, 948a, 1000a, 1010a, 1012a, 1017a, 1027a, 1037a, 1039a, 1044a], [948a, 958a, 1003a, 1010a, 1013a, 1018a, 1030a, 1040a, 1042a, 1047a, 1057a, 1107a, 1109a, 1114a], [1018a, 1028a, 1033a, 1040a, 1043a, 1048a, 1100a, 1110a, 1112a, 1117a, 1127a, 1137a, 1139a, 1144a], [1048a, 1058a, 1103a, 1110a, 1113a, 1118a, 1130a, 1140a, 1142a, 1147a, 1157a, 1207p, 1209p, 1214p], [1118a, 1128a, 1133a, 1140a, 1143a, 1148a, 1200p, 1210p, 1212p, 1217p, 1227p, 1237p, 1239p, 1244p], [1148a, 1158a, 1203p, 1210p, 1213p, 1218p, 1230p, 1240p, 1242p, 1247p, 1257p, 107p, 109p, 114p], [1218p, 1228p, 1233p, 1240p, 1243p, 1248p, 100p, 110p, 112p, 117p, 127p, 137p, 139p, 144p], [1248p, 1258p, 103p, 110p, 113p, 118p, 130p, 140p, 142p, 147p, 157p, 207p, 209p, 214p], [118p, 128p, 133p, 140p, 143p, 148p, 200p, 210p, 212p, 217p, 227p, 237p, 239p, 244p], [148p, 158p, 203p, 210p, 213p, 218p, 230p, 240p, 242p, 247p, 257p, 307p, 309p, 314p], [218p, 228p, 233p, 240p, 243p, 248p, 300p, 310p, 313p, 318p, 328p, 338p, 340p, 345p], [248p, 258p, 303p, 310p, 314p, 319p, 331p, 341p, 344p, 349p, 359p, 409p, 411p, 416p], [318p, 328p, 333p, 340p, 344p, 349p, 401p, 411p, 414p, 419p, 429p, 439p, 441p, 446p], ["-", "-", "-", "-", "-", "-", 416p, 426p, 429p, 434p, 444p, 454p, 456p, 501p], [348p, 358p, 403p, 410p, 414p, 419p, 431p, 441p, 444p, 449p, 459p, 509p, 511p, 516p], ["-", "-", "-", "-", "-", "-", 446p, 456p, 459p, 504p, 514p, 524p, 526p, 531p], ["-", "-", 431p, 441p, 445p, 450p, 502p, 512p, 515p, 520p, 530p, 537p, 539p, 544p], ["-", "-", "-", "-", "-", "-", 516p, 526p, 529p, 534p, 544p, 554p, 556p, 601p], ["-", "-", 458p, 511p, 515p, 520p, 532p, 542p, 545p, 550p, 600p, 610p, 612p, 617p], ["-", "-", "-", "-", "-", "-", 546p, 556p, 559p, 604p, 614p, 624p, 626p, 631p], ["-", "-", "-", 540p, 544p, 549p, 601p, 611p, 614p, 619p, 629p, 639p, 641p, 646p], ["-", "-", "-", "-", "-", "-", 616p, 626p, 629p, 634p, 644p, 654p, 656p, 701p], ["-", "-", "-", 611p, 615p, 620p, 632p, 642p, 644p, 649p, 659p, 709p, 711p, 716p], ["-", "-", "-", "-", "-", "-", 736p, 746p, 748p, 753p, 803p, 813p, 815p, 820p], ["-", "-", "-", "-", "-", "-", 836p, 846p, 848p, 853p, 903p, 913p, 915p, 920p], ["-", "-", "-", "-", "-", "-", 936p, 946p, 948p, 953p, 1003p, 1013p, 1015p, 1020p], ["-", "-", "-", "-", "-", "-", 1036p, 1046p, 1048p, 1053p, 1103p, 1113p, 1115p, 1120p], ["-", "-", "-", "-", "-", "-", 1136p, 1146p, 1148p, 1153p, 1203a, 1213a, 1215a, 1220a]]  
-  
time_points: [Tuggeranong Bus Station (Platform 4), Kambah High, Kambah Village, Woden Bus Station]  
long_name: To Woden Bus Station  
between_stops: {}  
   
short_name: "962"  
stop_times: [[924a, 931a, 939a, 952a], [1024a, 1031a, 1039a, 1052a], [1124a, 1131a, 1139a, 1152a], [1224p, 1231p, 1239p, 1252p], [124p, 131p, 139p, 152p], [224p, 231p, 239p, 252p], [324p, 331p, 339p, 352p], [424p, 431p, 439p, 452p], [524p, 531p, 539p, 552p], [624p, 631p, 638p, 649p]]  
-  
time_points: [Gungahlin Marketplace, Flemington Rd / Sandford St, Northbourne Avenue / Antill St, Macarthur / Northbourne Ave, City Bus Station (Platform 9), Russell Offices, Kings Ave / National Circuit, Railway Station Kingston, Fyshwick Direct Factory Outlet]  
long_name: To Fyshwick DirectFactory Outlet  
between_stops:  
Russell Offices-Kings Ave / National Circuit: [Wjz4RFJ, Wjz4RwH, Wjz4Quk]  
Northbourne Avenue / Antill St-Macarthur / Northbourne Ave: [Wjz5SDc, Wjz5Sux, Wjz5SrO, Wjz5Sqk, Wjz5RvC, Wjz5Rsi, Wjz5RkN]  
Macarthur / Northbourne Ave-City Bus Station (Platform 9): [Wjz5QmR, Wjz5Qmu, Wjz5Qgn, Wjz5Qi2, Wjz5Pl0, Wjz5PdJ, Wjz5P8K, Wjz5P8n, Wjz5Oci, Wjz5O3Q]  
City Bus Station (Platform 9)-Russell Offices: [Wjz5Nht, Wjz5MsD, Wjz5MsT, Wjz5MEL, Wjz4T-X, Wjz4_jm, Wjz4_wS, Wjz4-KO, Wjz4-Rc, Wjz4-WL, Wjz4-WZ]  
short_name: "200"  
stop_times: [[701a, 709a, 715a, 718a, 723a, 732a, 736a, 742a, 749a], [716a, 724a, 731a, 737a, 747a, 757a, 801a, 807a, 814a], [731a, 740a, 749a, 755a, 805a, 815a, 819a, 825a, 832a], [746a, 755a, 804a, 810a, 820a, 830a, 834a, 840a, 847a], [801a, 810a, 819a, 825a, 835a, 845a, 849a, 855a, 902a], [816a, 825a, 834a, 840a, 850a, 900a, 904a, 910a, 917a], [831a, 840a, 849a, 855a, 902a, 910a, 914a, 920a, 927a], [846a, 855a, 902a, 905a, 910a, 918a, 922a, 928a, 935a], [901a, 909a, 915a, 918a, 923a, 931a, 935a, 941a, 948a], [916a, 924a, 930a, 933a, 938a, 946a, 950a, 956a, 1003a], [931a, 939a, 945a, 948a, 953a, 1001a, 1005a, 1011a, 1018a], [946a, 954a, 1000a, 1003a, 1008a, 1016a, 1020a, 1026a, 1033a], [1001a, 1009a, 1015a, 1018a, 1023a, 1031a, 1035a, 1041a, 1048a], [1016a, 1024a, 1030a, 1033a, 1038a, 1046a, 1050a, 1056a, 1103a], [1031a, 1039a, 1045a, 1048a, 1053a, 1101a, 1105a, 1111a, 1118a], [1046a, 1054a, 1100a, 1103a, 1108a, 1116a, 1120a, 1126a, 1133a], [1101a, 1109a, 1115a, 1118a, 1123a, 1131a, 1135a, 1141a, 1148a], [1116a, 1124a, 1130a, 1133a, 1138a, 1146a, 1150a, 1156a, 1203p], [1131a, 1139a, 1145a, 1148a, 1153a, 1201p, 1205p, 1211p, 1218p], [1146a, 1154a, 1200p, 1203p, 1208p, 1216p, 1220p, 1226p, 1233p], [1201p, 1209p, 1215p, 1218p, 1223p, 1231p, 1235p, 1241p, 1248p], [1216p, 1224p, 1230p, 1233p, 1238p, 1246p, 1250p, 1256p, 103p], [1233p, 1241p, 1247p, 1250p, 1255p, 103p, 107p, 113p, 120p], [1246p, 1254p, 100p, 103p, 108p, 116p, 120p, 126p, 133p], [101p, 109p, 115p, 118p, 123p, 131p, 135p, 141p, 148p], [116p, 124p, 130p, 133p, 138p, 146p, 150p, 156p, 203p], [131p, 139p, 145p, 148p, 153p, 201p, 205p, 211p, 218p], [146p, 154p, 200p, 203p, 208p, 216p, 220p, 226p, 233p], [201p, 209p, 215p, 218p, 223p, 231p, 235p, 241p, 248p], [216p, 224p, 230p, 233p, 238p, 246p, 250p, 256p, 303p], [231p, 239p, 245p, 248p, 253p, 301p, 305p, 311p, 318p], [246p, 254p, 300p, 303p, 308p, 316p, 320p, 326p, 333p], [301p, 309p, 315p, 318p, 323p, 331p, 335p, 341p, 348p], [316p, 324p, 330p, 333p, 338p, 346p, 350p, 356p, 404p], [331p, 339p, 345p, 348p, 353p, 402p, 407p, 415p, 424p], [346p, 354p, 400p, 403p, 412p, 422p, 427p, 435p, 444p], [401p, 410p, 417p, 420p, 429p, 439p, 444p, 452p, 501p], [416p, 425p, 432p, 435p, 444p, 454p, 459p, 507p, 516p], [431p, 440p, 447p, 450p, 459p, 509p, 514p, 522p, 531p], [446p, 455p, 502p, 505p, 514p, 524p, 529p, 537p, 546p], [501p, 510p, 517p, 520p, 529p, 539p, 544p, 552p, 600p], [516p, 525p, 532p, 535p, 544p, 554p, 559p, 605p, 612p], [531p, 540p, 547p, 550p, 559p, 607p, 611p, 617p, 624p], [546p, 555p, 601p, 604p, 609p, 617p, 621p, 627p, 634p], [601p, 609p, 615p, 618p, 623p, 631p, 635p, 641p, 648p], [616p, 624p, 630p, 633p, 638p, 646p, 650p, 656p, 703p], [631p, 639p, 645p, 648p, 653p, 701p, 705p, 711p, 718p], [646p, 654p, 700p, 703p, 708p, 716p, 720p, 726p, 733p]]  
-  
time_points: [Dickson, Lyneham Shops Wattle Street, Macarthur / Miller O'Connor, City Bus Station]  
long_name: To City Bus Station  
between_stops: {}  
   
short_name: "8"  
stop_times: [[626a, 632a, 637a, 644a], [657a, 703a, 708a, 715a], [724a, 730a, 737a, 746a], [757a, 804a, 811a, 820a], [831a, 838a, 845a, 854a], [904a, 911a, 918a, 927a], [1009a, 1015a, 1020a, 1027a], [1109a, 1115a, 1120a, 1127a], [1209p, 1215p, 1220p, 1227p], [109p, 115p, 120p, 127p], [209p, 215p, 220p, 227p], [302p, 309p, 316p, 325p], [332p, 339p, 346p, 355p], [408p, 415p, 422p, 431p], [437p, 444p, 451p, 500p], [507p, 514p, 521p, 530p], [537p, 544p, 551p, 600p], [646p, 652p, 657p, 702p], [746p, 752p, 757p, 802p], [846p, 852p, 857p, 902p], [946p, 952p, 957p, 1002p], [1046p, 1052p, 1057p, 1102p]]  
-  
time_points: [Woden Bus Station (Platform 16), Weston Primary, Holder, Duffy, Cooleman Court]  
long_name: To Cooleman Court  
between_stops: {}  
   
short_name: "925"  
stop_times_sunday: [[957a, 1007a, 1009a, 1011a, 1019a], [1057a, 1107a, 1109a, 1111a, 1119a], [1157a, 1207p, 1209p, 1211p, 1219p], [1257p, 107p, 109p, 111p, 119p], [157p, 207p, 209p, 211p, 219p], [257p, 307p, 309p, 311p, 319p], [357p, 407p, 409p, 411p, 419p], [457p, 507p, 509p, 511p, 519p], [557p, 607p, 609p, 611p, 619p], [657p, 707p, 709p, 711p, 719p]]  
-  
time_points: [Woden Bus Station (Platform 15), Southlands Mawson, Farrer Primary School, Isaacs Shops, Pearce Shops, Woden Bus Station]  
long_name: To Woden Bus Station  
between_stops: {}  
   
short_name: "924"  
stop_times: [[1010a, 1019a, 1024a, 1029a, 1033a, 1041a], [1210p, 1219p, 1224p, 1229p, 1233p, 1241p], [210p, 219p, 224p, 229p, 233p, 241p], [410p, 419p, 424p, 429p, 433p, 441p], [610p, 619p, 624p, 629p, 633p, 641p]]  
-  
time_points: [City Bus Station (Platform 8), St Thomas More's Campbell, Hospice / Menindee Dr, ADFA, City Bus Station]  
long_name: To City Bus Station  
between_stops:  
ADFA-City Bus Station: [Wjzcend, Wjzd8br, Wjzd0CK, Wjz5VUU, Wjz5VFA, Wjz5VAq, Wjz5V64, Wjz5NRJ, Wjz5NAQ]  
short_name: "930"  
stop_times: [[1001a, 1013a, 1020a, 1027a, 1041a], [1201p, 1213p, 1220p, 1227p, 1241p], [201p, 213p, 220p, 227p, 241p], [401p, 413p, 420p, 427p, 441p], [601p, 613p, 620p, 627p, 641p]]  
-  
time_points: [Belconnen Community Bus Station (Platform 4), Westfield Bus Station (Platform 2), Cohen Street Bus Station (Platform 6), Melba Shops, Spence Terminus, Melba Shops, Cohen Street Bus Station, Westfield Bus Station, Belconnen Community Bus Station]  
long_name: To Belconnen Community Bus Station  
between_stops:  
Belconnen Community Bus Station (Platform 4)-Westfield Bus Station (Platform 2): []  
Westfield Bus Station-Belconnen Community Bus Station: []  
Cohen Street Bus Station-Westfield Bus Station: []  
Westfield Bus Station (Platform 2)-Cohen Street Bus Station (Platform 6): []  
stop_times_saturday: [["-", "-", "-", "-", 725a, 738a, 753a, 755a, 759a], [752a, 754a, 758a, 811a, 825a, 838a, 853a, 855a, 859a], [852a, 854a, 858a, 911a, 925a, 938a, 953a, 955a, 959a], [952a, 954a, 958a, 1011a, 1025a, 1038a, 1053a, 1055a, 1059a], [1052a, 1054a, 1058a, 1111a, 1125a, 1138a, 1153a, 1155a, 1159a], [1152a, 1154a, 1158a, 1211p, 1225p, 1238p, 1253p, 1255p, 1259p], [1252p, 1254p, 1258p, 111p, 125p, 138p, 153p, 155p, 159p], [152p, 154p, 158p, 211p, 225p, 238p, 253p, 255p, 259p], [252p, 254p, 258p, 311p, 325p, 338p, 353p, 355p, 359p], [352p, 354p, 358p, 411p, 425p, 438p, 453p, 455p, 459p], [452p, 454p, 458p, 511p, 525p, 538p, 553p, 555p, 559p], [552p, 554p, 558p, 611p, 625p, 638p, 652p, 654p, 658p], [651p, 653p, 657p, 709p, 723p, 736p, 750p, 752p, 756p], [751p, 753p, 757p, 809p, 823p, 836p, 850p, 852p, 856p], [855p, 857p, 901p, 913p, 927p, 940p, 954p, 956p, 1000p], [955p, 957p, 1001p, 1013p, 1027p, 1040p, 1054p, 1056p, 1100p], [1055p, 1057p, 1101p, 1113p, 1127p, 1140p, 1154p, 1156p, 1200a]]  
short_name: "906"  
-  
time_points: [Alexander Maconochie Centre, Woden Bus Station]  
long_name: To Woden Bus Station  
between_stops: {}  
   
short_name: "88"  
stop_times: [[1150a, 1210p], [320p, 340p], [730p, 750p]]  
-  
time_points: [Belconnen Community Bus Station (Platform 4), Westfield Bus Station (Platform 2), Cohen Street Bus Station (Platform 6), Melba Shops, Spence Terminus, Melba Shops, Cohen Street Bus Station, Westfield Bus Station, Belconnen Community Bus Station]  
long_name: To Belconnen Community Bus Station  
between_stops:  
Belconnen Community Bus Station (Platform 4)-Westfield Bus Station (Platform 2): []  
Westfield Bus Station-Belconnen Community Bus Station: []  
Cohen Street Bus Station-Westfield Bus Station: []  
Westfield Bus Station (Platform 2)-Cohen Street Bus Station (Platform 6): []  
short_name: "906"  
stop_times: [[852a, 854a, 858a, 911a, 925a, 938a, 953a, 955a, 959a], [952a, 954a, 958a, 1011a, 1025a, 1038a, 1053a, 1055a, 1059a], [1052a, 1054a, 1058a, 1111a, 1125a, 1138a, 1153a, 1155a, 1159a], [1152a, 1154a, 1158a, 1211p, 1225p, 1238p, 1253p, 1255p, 1259p], [1252p, 1254p, 1258p, 111p, 125p, 138p, 153p, 155p, 159p], [152p, 154p, 158p, 211p, 225p, 238p, 253p, 255p, 259p], [252p, 254p, 258p, 311p, 325p, 338p, 353p, 355p, 359p], [352p, 354p, 358p, 411p, 425p, 438p, 453p, 455p, 459p], [452p, 454p, 458p, 511p, 525p, 538p, 553p, 555p, 559p], [552p, 554p, 558p, 611p, 625p, 638p, 652p, 654p, 658p]]  
-  
time_points: [Woden Bus Station (Platform 14), Canberra Hospital, Red Hill, Manuka, Kings Ave / National Circuit, City Bus Station (Platform 4), Lyneham Shops Wattle Street, North Lyneham, Dickson]  
long_name: To Dickson  
between_stops:  
Woden Bus Station (Platform 14)-Canberra Hospital: [Wjz3mAg, Wjz3mPO, Wjz3mWn]  
short_name: "6"  
stop_times: [[618a, 626a, 638a, 645a, 650a, 701a, 713a, 719a, 725a], [653a, 701a, 713a, 720a, 725a, 737a, 751a, 759a, 806a], [723a, 731a, 745a, 753a, 758a, 812a, 826a, 834a, 841a], [753a, 803a, 817a, 825a, 830a, 844a, 858a, 906a, 913a], [823a, 833a, 847a, 855a, 900a, 914a, 928a, 936a, 943a], [853a, 903a, 917a, 925a, 930a, 944a, 956a, 1004a, 1011a], [923a, 933a, 945a, 952a, 957a, 1011a, 1023a, 1031a, 1038a], [1023a, 1033a, 1045a, 1052a, 1057a, 1111a, 1123a, 1131a, 1138a], [1123a, 1133a, 1145a, 1152a, 1157a, 1211p, 1223p, 1231p, 1238p], [1223p, 1233p, 1245p, 1252p, 1257p, 111p, 123p, 131p, 138p], [123p, 133p, 145p, 152p, 157p, 211p, 223p, 231p, 238p], [223p, 233p, 245p, 252p, 257p, 311p, 325p, 333p, 340p], ["-", "-", "-", "-", "-", 344p, 358p, 406p, 413p], [323p, 333p, 347p, 355p, 400p, 414p, 428p, 436p, 443p], [353p, 403p, 417p, 425p, 430p, 444p, 458p, 506p, 513p], [423p, 433p, 447p, 455p, 500p, 514p, 528p, 536p, 543p], [453p, 503p, 517p, 525p, 530p, 544p, 558p, 606p, 613p], [516p, 526p, 540p, 548p, 553p, 607p, 621p, 629p, 635p], [553p, 603p, 617p, 625p, 630p, 640p, 650p, 656p, 702p], [630p, 638p, 648p, 655p, 700p, 710p, 720p, 726p, 732p], [730p, 738p, 748p, 755p, 800p, 810p, 820p, 826p, 832p], [830p, 838p, 848p, 855p, 900p, 910p, 920p, 926p, 932p], [930p, 938p, 948p, 955p, 1000p, 1010p, 1020p, 1026p, 1032p], [1030p, 1038p, 1048p, 1055p, 1100p, 1110p, 1120p, 1126p, 1132p]]  
-  
time_points: [Kippax, Latham Post Office, Florey Shops, Cohen Street Bus Station, Westfield Bus Station, Belconnen Community Bus Station]  
long_name: To Belconnen Community Bus Station  
between_stops:  
Westfield Bus Station-Belconnen Community Bus Station: []  
Cohen Street Bus Station-Westfield Bus Station: []  
short_name: "16"  
stop_times: [[610a, 619a, 625a, 630a, 632a, 636a], [640a, 649a, 655a, 700a, 702a, 706a], [711a, 720a, 726a, 731a, 733a, 737a], [730a, 741a, 747a, 753a, 755a, 759a], [750a, 801a, 807a, 813a, 815a, 819a], [810a, 821a, 827a, 833a, 835a, 839a], [830a, 841a, 847a, 853a, 855a, 859a], [851a, 902a, 908a, 914a, 916a, 920a], [916a, 927a, 933a, 938a, 940a, 944a], [946a, 955a, 1001a, 1006a, 1008a, 1012a], [1011a, 1020a, 1026a, 1031a, 1033a, 1037a], [1046a, 1055a, 1101a, 1106a, 1108a, 1112a], [1111a, 1120a, 1126a, 1131a, 1133a, 1137a], [1146a, 1155a, 1201p, 1206p, 1208p, 1212p], [1211p, 1220p, 1226p, 1231p, 1233p, 1237p], [1246p, 1255p, 101p, 106p, 108p, 112p], [111p, 120p, 126p, 131p, 133p, 137p], [146p, 155p, 201p, 206p, 208p, 212p], [211p, 220p, 226p, 231p, 233p, 237p], [246p, 255p, 301p, 307p, 309p, 313p], [311p, 322p, 328p, 334p, 336p, 340p], [341p, 352p, 358p, 404p, 406p, 410p], [407p, 418p, 424p, 430p, 432p, 436p], [431p, 442p, 448p, 454p, 456p, 500p], [456p, 507p, 513p, 519p, 521p, 525p], [526p, 537p, 543p, 549p, 551p, 555p], [555p, 606p, 612p, 618p, 620p, 624p], [655p, 704p, 710p, 714p, 716p, 720p], [755p, 804p, 810p, 814p, 816p, 820p], [855p, 904p, 910p, 914p, 916p, 920p], [955p, 1004p, 1010p, 1014p, 1016p, 1020p], [1055p, 1104p, 1110p, 1114p, 1116p, 1120p]]  
-  
time_points: [Cohen Street Bus Station (Platform 2), Westfield Bus Station (Platform 1), Belconnen Community Bus Station (Platform 3), Calvary Hospital, O'Connor Shops, Burton and Garran Hall Daley Road, National Museum of Australia, City Bus Station (Platform 2), Kings Ave / National Circuit, Parliament House, Deakin Shops, Hughes Shops, Garran Shops, Canberra Hospital, Woden Bus Station]  
long_name: To Woden Bus Station  
between_stops:  
Westfield Bus Station (Platform 1)-Belconnen Community Bus Station (Platform 3): []  
City Bus Station (Platform 2)-Kings Ave / National Circuit: [Wjz5FOn, Wjz4S1U, Wjz4Rs-, Wjz4RFJ, Wjz4RwH, Wjz4Quk]  
Cohen Street Bus Station (Platform 2)-Westfield Bus Station (Platform 1): []  
Canberra Hospital-Woden Bus Station: [Wjz3mWn, Wjz3mPO, Wjz3mAg]  
short_name: "3"  
stop_times: [["-", "-", "-", "-", "-", "-", "-", 618a, 627a, 631a, 636a, 640a, 644a, 646a, 653a], ["-", "-", "-", "-", "-", "-", "-", 648a, 657a, 701a, 706a, 710a, 714a, 716a, 723a], [628a, 630a, 634a, 651a, 657a, 701a, 705a, 718a, 727a, 731a, 736a, 742a, 746a, 748a, 758a], [656a, 658a, 702a, 719a, 725a, 729a, 734a, 748a, 758a, 803a, 808a, 814a, 818a, 820a, 830a], [721a, 723a, 727a, 746a, 754a, 759a, 804a, 818a, 828a, 833a, 838a, 844a, 848a, 850a, 900a], [745a, 747a, 751a, 810a, 819a, 827a, 832a, 848a, 853a, 901a, 906a, 908a, 912a, 914a, 924a], [821a, 823a, 827a, 846a, 854a, 859a, 904a, 918a, 928a, 932a, 937a, 942a, 946a, 948a, 955a], [851a, 853a, 857a, 916a, 924a, 929a, 934a, 948a, 958a, 1002a, 1007a, 1012a, 1016a, 1018a, 1025a], [924a, 926a, 930a, 947a, 954a, 959a, 1004a, 1018a, 1028a, 1032a, 1037a, 1042a, 1046a, 1048a, 1055a], [954a, 956a, 1000a, 1017a, 1024a, 1029a, 1034a, 1048a, 1058a, 1102a, 1107a, 1112a, 1116a, 1118a, 1125a], [1024a, 1026a, 1030a, 1047a, 1054a, 1059a, 1104a, 1118a, 1128a, 1132a, 1137a, 1142a, 1146a, 1148a, 1155a], [1054a, 1056a, 1100a, 1117a, 1124a, 1129a, 1134a, 1148a, 1158a, 1202p, 1207p, 1212p, 1216p, 1218p, 1225p], [1124a, 1126a, 1130a, 1147a, 1154a, 1159a, 1204p, 1218p, 1228p, 1232p, 1237p, 1242p, 1246p, 1248p, 1255p], [1154a, 1156a, 1200p, 1217p, 1224p, 1229p, 1234p, 1248p, 1258p, 102p, 107p, 112p, 116p, 118p, 125p], [1224p, 1226p, 1230p, 1247p, 1254p, 1259p, 104p, 118p, 128p, 132p, 137p, 142p, 146p, 148p, 155p], [1254p, 1256p, 100p, 117p, 124p, 129p, 134p, 148p, 158p, 202p, 207p, 212p, 216p, 218p, 225p], [124p, 126p, 130p, 147p, 154p, 159p, 204p, 218p, 228p, 232p, 237p, 242p, 246p, 248p, 255p], [154p, 156p, 200p, 217p, 224p, 229p, 234p, 248p, 258p, 303p, 308p, 314p, 318p, 320p, 329p], [229p, 231p, 235p, 248p, 258p, 303p, 310p, 324p, 334p, 339p, 344p, 350p, 354p, 356p, 405p], [250p, 252p, 256p, 315p, 323p, 328p, 334p, 348p, 358p, 403p, 408p, 414p, 418p, 420p, 429p], [317p, 319p, 323p, 342p, 350p, 355p, 401p, 415p, 425p, 430p, 435p, 441p, 445p, 447p, 456p], [346p, 348p, 352p, 411p, 419p, 424p, 430p, 444p, 454p, 459p, 504p, 510p, 514p, 516p, 525p], [418p, 420p, 424p, 443p, 451p, 456p, 502p, 516p, 526p, 531p, 536p, 542p, 546p, 548p, 557p], [445p, 447p, 451p, 510p, 518p, 523p, 529p, 543p, 553p, 558p, 603p, 609p, 613p, 615p, 624p], [515p, 517p, 521p, 540p, 548p, 553p, 559p, 613p, 623p, 628p, 632p, 637p, 641p, 643p, 650p], [547p, 549p, 553p, 612p, 620p, 625p, 631p, 644p, 653p, 658p, 702p, 707p, 711p, 713p, 720p], [620p, 622p, 626p, 643p, 650p, 655p, 700p, 713p, 722p, 727p, 731p, 736p, 740p, 742p, 749p], [723p, 725p, 729p, 746p, 753p, 758p, 803p, 816p, 825p, 830p, 834p, 839p, 843p, 845p, 852p], [825p, 827p, 831p, 848p, 855p, 900p, 905p, 918p, 927p, 932p, 936p, 941p, 945p, 947p, 954p], [925p, 927p, 931p, 948p, 955p, 1000p, 1005p, 1018p, 1027p, 1032p, 1036p, 1041p, 1045p, 1047p, 1054p], [1025p, 1027p, 1031p, 1048p, 1055p, 1100p, 1105p, 1116p, "-", "-", "-", "-", "-", "-", "-"]]  
-  
time_points: [City Bus Station (Platform 8), Dickson Shops, Watson Shops, Watson Terminus, Watson Shops, Dickson Shops, City Bus Station]  
long_name: To City Bus Station  
between_stops: {}  
   
stop_times_saturday: [["-", "-", "-", 708a, 713a, 719a, 734a], ["-", "-", "-", 808a, 813a, 819a, 834a], [846a, 903a, 908a, 915a, 920a, 926a, 941a], [946a, 1003a, 1008a, 1015a, 1020a, 1026a, 1041a], [1046a, 1103a, 1108a, 1115a, 1120a, 1126a, 1141a], [1146a, 1203p, 1208p, 1215p, 1220p, 1226p, 1241p], [1246p, 103p, 108p, 115p, 120p, 126p, 141p], [146p, 203p, 208p, 215p, 220p, 226p, 241p], [246p, 303p, 308p, 315p, 320p, 326p, 341p], [346p, 403p, 408p, 415p, 420p, 426p, 441p], [446p, 503p, 508p, 515p, 520p, 526p, 541p], [546p, 603p, 608p, 615p, 620p, 626p, 641p], [646p, 703p, 708p, 715p, 720p, 726p, 741p], [746p, 803p, 808p, 815p, 820p, 826p, 841p], [846p, 903p, 908p, 915p, 920p, 926p, 941p], [946p, 1003p, 1008p, 1015p, 1020p, 1026p, 1041p], [1046p, 1103p, 1108p, 1115p, 1120p, 1126p, 1141p]]  
short_name: "939"  
- -
time_points: [Woden Bus Station (Platform 11), Athllon / Sulwood Kambah, Erindale Centre, Tuggeranong Bus Station] time_points: [Woden Bus Station (Platform 11), Athllon / Sulwood Kambah, Erindale Centre, Tuggeranong Bus Station]
long_name: To Tuggeranong Bus Station long_name: To Tuggeranong Bus Station
between_stops: between_stops:
Woden Bus Station (Platform 11)-Athllon / Sulwood Kambah: [Wjz3kAx, Wjz3kyX, Wjz3kwU, Wjz3iFK, Wjz3hL_, Wjz3gK-, Wjz3gQn, Wjz3gMq, Wjz2mTK] Woden Bus Station (Platform 11)-Athllon / Sulwood Kambah: [Wjz3kAx, Wjz3kyX, Wjz3kwU, Wjz3iFK, Wjz3hL_, Wjz3gK-, Wjz3gQn, Wjz3gMq, Wjz2mTK]
stop_times_saturday: [[831a, 840a, 850a, 903a], [931a, 940a, 950a, 1003a], [1031a, 1040a, 1050a, 1103a], [1131a, 1140a, 1150a, 1203p], [1231p, 1240p, 1250p, 103p], [131p, 140p, 150p, 203p], [231p, 240p, 250p, 303p], [331p, 340p, 350p, 403p], [431p, 440p, 450p, 503p], [531p, 540p, 550p, 603p], [628p, 637p, 647p, 700p], [726p, 735p, 745p, 758p], [826p, 835p, 845p, 858p], [926p, 935p, 945p, 958p], [1026p, 1035p, 1045p, 1058p], [1126p, 1135p, 1145p, 1158p]] stop_times_saturday: [[831a, 840a, 850a, 903a], [931a, 940a, 950a, 1003a], [1031a, 1040a, 1050a, 1103a], [1131a, 1140a, 1150a, 1203p], [1231p, 1240p, 1250p, 103p], [131p, 140p, 150p, 203p], [231p, 240p, 250p, 303p], [331p, 340p, 350p, 403p], [431p, 440p, 450p, 503p], [531p, 540p, 550p, 603p], [628p, 637p, 647p, 700p], [726p, 735p, 745p, 758p], [826p, 835p, 845p, 858p], [926p, 935p, 945p, 958p], [1026p, 1035p, 1045p, 1058p], [1126p, 1135p, 1145p, 1158p]]
short_name: "961" short_name: "961"
- -
time_points: [Tuggeranong Bus Station (Platform 7), Heagney / Clift Richardson, Chisholm Shops, Erindale Centre, Tuggeranong Bus Station] time_points: [Cohen Street Bus Station (Platform 2), Westfield Bus Station (Platform 1), Belconnen Community Bus Station (Platform 2), William Webb / Ginninderra Drive, Chuculba / William Slim Dr, Gungahlin Marketplace, Kosciuszko / Everard, Flemington Rd / Sandford St, Macarthur / Northbourne Ave, City Bus Station]
long_name: To Tuggeranong Bus Station long_name: To City Bus Station
between_stops: {} between_stops:
  Macarthur / Northbourne Ave-City Bus Station: [Wjz5QmR, Wjz5Qmu, Wjz5Qgn, Wjz5Qi2, Wjz5Pl0, Wjz5PdJ, Wjz5P8K, Wjz5P8n, Wjz5Oci, Wjz5O3Q]
short_name: "968" Westfield Bus Station (Platform 1)-Belconnen Community Bus Station (Platform 2): []
stop_times: [[1003a, 1016a, 1024a, 1038a, 1048a], [1203p, 1216p, 1224p, 1238p, 1248p], [203p, 216p, 224p, 238p, 248p], [403p, 416p, 424p, 438p, 448p], [603p, 616p, 624p, 638p, 648p]] Cohen Street Bus Station (Platform 2)-Westfield Bus Station (Platform 1): []
- stop_times_saturday: [[841a, 843a, 847a, 853a, 858a, 908a, 918a, 925a, 933a, 940a], [941a, 943a, 947a, 953a, 958a, 1008a, 1018a, 1025a, 1033a, 1040a], [1041a, 1043a, 1047a, 1053a, 1058a, 1108a, 1118a, 1125a, 1133a, 1140a], [1141a, 1143a, 1147a, 1153a, 1158a, 1208p, 1218p, 1225p, 1233p, 1240p], [1241p, 1243p, 1247p, 1253p, 1258p, 108p, 118p, 125p, 133p, 140p], [141p, 143p, 147p, 153p, 158p, 208p, 218p, 225p, 233p, 240p], [241p, 243p, 247p, 253p, 258p, 308p, 318p, 325p, 333p, 340p], [341p, 343p, 347p, 353p, 358p, 408p, 418p, 425p, 433p, 440p], [441p, 443p, 447p, 453p, 458p, 508p, 518p, 525p, 533p, 540p], [541p, 543p, 547p, 553p, 558p, 608p, 618p, 625p, 633p, 640p], [641p, 643p, 647p, 653p, 658p, 708p, 718p, 725p, 733p, 740p]]
time_points: [Belconnen Community Bus Station (Platform 6), Westfield Bus Station (Platform 2), Cohen Street Bus Station (Platform 5), Charnwood Shops, Fraser East Terminus, Charnwood Shops, Cohen Street Bus Station, Westfield Bus Station, Belconnen Community Bus Station] short_name: "956"
long_name: To Belconnen Community Bus Station -
between_stops: time_points: [City Bus Station (Platform 5), Merici College, Dickson / Antill St, Australian Institute of Sport, Belconnen Community Bus Station, Westfield Bus Station, Cohen Street Bus Station]
Belconnen Community Bus Station (Platform 6)-Westfield Bus Station (Platform 2): []  
Westfield Bus Station-Belconnen Community Bus Station: []  
Cohen Street Bus Station-Westfield Bus Station: []  
Westfield Bus Station (Platform 2)-Cohen Street Bus Station (Platform 5): []  
short_name: "907"  
stop_times: [[848a, 850a, 854a, 908a, 916a, 923a, 937a, 939a, 943a], [948a, 950a, 954a, 1008a, 1016a, 1023a, 1037a, 1039a, 1043a], [1048a, 1050a, 1054a, 1108a, 1116a, 1123a, 1137a, 1139a, 1143a], [1148a, 1150a, 1154a, 1208p, 1216p, 1223p, 1237p, 1239p, 1243p], [1248p, 1250p, 1254p, 108p, 116p, 123p, 137p, 139p, 143p], [148p, 150p, 154p, 208p, 216p, 223p, 237p, 239p, 243p], [248p, 250p, 254p, 308p, 316p, 323p, 337p, 339p, 343p], [348p, 350p, 354p, 408p, 416p, 423p, 437p, 439p, 443p], [448p, 450p, 454p, 508p, 516p, 523p, 537p, 539p, 543p], [548p, 550p, 554p, 608p, 616p, 623p, 637p, 639p, 643p], [647p, 649p, 653p, 706p, 714p, 721p, 734p, 736p, 740p]]  
-  
time_points: [Woden Bus Station (Platform 3), Waramanga Shops, Fisher Shops, Chapman Shops, Rivett Shops, Cooleman Court]  
long_name: To Cooleman Court  
between_stops: {}  
   
stop_times_saturday: [[920a, 929a, 932a, 942a, 945a, 950a], [1020a, 1029a, 1032a, 1042a, 1045a, 1050a], [1120a, 1129a, 1132a, 1142a, 1145a, 1150a], [1220p, 1229p, 1232p, 1242p, 1245p, 1250p], [120p, 129p, 132p, 142p, 145p, 150p], [220p, 229p, 232p, 242p, 245p, 250p], [320p, 329p, 332p, 342p, 345p, 350p], [420p, 429p, 432p, 442p, 445p, 450p], [520p, 529p, 532p, 542p, 545p, 550p], [620p, 629p, 632p, 642p, 645p, 650p], [720p, 729p, 732p, 742p, 745p, 750p], [820p, 829p, 832p, 842p, 845p, 850p], [920p, 929p, 932p, 942p, 945p, 950p], [1020p, 1029p, 1032p, 1042p, 1045p, 1050p], [1120p, 1129p, 1132p, 1142p, 1145p, 1150p]]  
short_name: "927"  
-  
time_points: [Tuggeranong Bus Station (Platform 4), Isabella Shops, Calwell Shops, Theodore, Outtrim / Duggan, Tuggeranong Bus Station]  
long_name: To Tuggeranong Bus Station  
between_stops: {}  
   
short_name: "912"  
stop_times_sunday: [[1015a, 1025a, 1030a, 1039a, 1046a, 1055a], [1215p, 1225p, 1230p, 1239p, 1246p, 1255p], [215p, 225p, 230p, 239p, 246p, 255p], [415p, 425p, 430p, 439p, 446p, 455p], [615p, 625p, 630p, 639p, 646p, 655p]]  
-  
time_points: [City Bus Station (Platform 7), Kings Ave / National Circuit, Manuka, Red Hill Shops, Narrabundah Terminus, Red Hill Shops, Manuka, Kings Ave / National Circuit, City Bus Station]  
long_name: To City Bus Station  
between_stops:  
City Bus Station (Platform 7)-Kings Ave / National Circuit: [Wjz5FOn, Wjz4S1U, Wjz4Rs-, Wjz4RFJ, Wjz4RwH, Wjz4Quk]  
Kings Ave / National Circuit-City Bus Station: [Wjz4Quk, Wjz4RwH, Wjz4RFJ, Wjz4Rs-, Wjz4S1U, Wjz5FOn]  
short_name: "935"  
stop_times_sunday: [["-", "-", "-", "-", 824a, 833a, 839a, 843a, 852a], [856a, 903a, 907a, 914a, 924a, 933a, 939a, 943a, 952a], [956a, 1003a, 1007a, 1014a, 1024a, 1033a, 1039a, 1043a, 1052a], [1056a, 1103a, 1107a, 1114a, 1124a, 1133a, 1139a, 1143a, 1152a], [1156a, 1203p, 1207p, 1214p, 1224p, 1233p, 1239p, 1243p, 1252p], [1256p, 103p, 107p, 114p, 124p, 133p, 139p, 143p, 152p], [156p, 203p, 207p, 214p, 224p, 233p, 239p, 243p, 252p], [256p, 303p, 307p, 314p, 324p, 333p, 339p, 343p, 352p], [356p, 403p, 407p, 414p, 424p, 433p, 439p, 443p, 452p], [456p, 503p, 507p, 514p, 524p, 533p, 539p, 543p, 552p], [556p, 603p, 607p, 614p, 624p, 633p, 639p, 643p, 652p], [656p, 703p, 707p, 714p, 724p, 733p, 739p, 743p, 752p]]  
-  
time_points: [Woden Bus Station (Platform 5), Kambah Village, Kambah High, Tuggeranong Bus Station]  
long_name: To Tuggeranong Bus Station  
between_stops: {}  
   
short_name: "962"  
stop_times_sunday: [[951a, 1002a, 1010a, 1017a], [1051a, 1102a, 1110a, 1117a], [1151a, 1202p, 1210p, 1217p], [1251p, 102p, 110p, 117p], [151p, 202p, 210p, 217p], [251p, 302p, 310p, 317p], [351p, 402p, 410p, 417p], [451p, 502p, 510p, 517p], [551p, 602p, 610p, 617p], [651p, 702p, 710p, 717p]]  
-  
time_points: [Tuggeranong Bus Station (Platform 8), Woden Bus Station (Platform 9), City Bus Station (Platform 3), Belconnen Community Bus Station (Platform 4), Westfield Bus Station (Platform 2), Cohen Street Bus Station (Platform 6), Copland College, Melba Shops, Spence Shops, Spence Terminus]  
long_name: To Spence Terminus  
between_stops:  
Belconnen Community Bus Station (Platform 4)-Westfield Bus Station (Platform 2): []  
Tuggeranong Bus Station (Platform 8)-Woden Bus Station (Platform 9): [Wjz213q, Wjz238T, Wjz239F, Wjz2lDC, Wjz2mGO, Wjz2mTK, Wjz2nLE, Wjz3gMq, Wjz3gQn, Wjz3gK-, Wjz3hL_, Wjz3iFK, Wjz3kwU, Wjz3kyX, Wjz3kAx]  
Westfield Bus Station (Platform 2)-Cohen Street Bus Station (Platform 6): []  
City Bus Station (Platform 3)-Belconnen Community Bus Station (Platform 4): [Wjz5F-1, Wjz5FSY, Wjz5GMT, Wjz5GNG, Wjz5G6U, Wjz5G6B, Wjz5maK, Wjz5mbS, Wjz5nwb, Wjz5nw6, Wjz6giR, Wjz6gia, Wjz68W3, Wjz68W5, Wjz689c, Wjz681S]  
Woden Bus Station (Platform 9)-City Bus Station (Platform 3): [Wjz3m3b, Wjz3m3b, Wjz3eRR, Wjz3eZ4, Wjz4KNu, Wjz4KO9, Wjz5Nht]  
short_name: 15 315  
stop_times: [["-", "-", "-", 723a, 725a, 729a, 737a, 741a, 749a, 755a], ["-", "-", "-", 803a, 805a, 809a, 817a, 821a, 829a, 835a], [731a, 750a, 808a, 829a, 831a, 835a, 843a, 847a, 855a, 901a], [831a, 850a, 908a, 929a, 931a, 935a, 942a, 945a, 951a, 957a], [911a, 930a, 946a, 1006a, 1008a, 1012a, 1019a, 1022a, 1028a, 1034a], [941a, 959a, 1015a, 1035a, 1037a, 1041a, 1048a, 1051a, 1057a, 1103a], [1011a, 1029a, 1045a, 1105a, 1107a, 1111a, 1118a, 1121a, 1127a, 1133a], [1041a, 1059a, 1115a, 1135a, 1137a, 1141a, 1148a, 1151a, 1157a, 1203p], [1111a, 1129a, 1145a, 1205p, 1207p, 1211p, 1218p, 1221p, 1227p, 1233p], [1141a, 1159a, 1215p, 1235p, 1237p, 1241p, 1248p, 1251p, 1257p, 103p], [1211p, 1229p, 1245p, 105p, 107p, 111p, 118p, 121p, 127p, 133p], [1241p, 1259p, 115p, 135p, 137p, 141p, 148p, 151p, 157p, 203p], [111p, 129p, 145p, 205p, 207p, 211p, 218p, 221p, 227p, 233p], [141p, 159p, 215p, 235p, 237p, 241p, 248p, 251p, 257p, 303p], [211p, 229p, 245p, 305p, 307p, 311p, 319p, 323p, 331p, 337p], [240p, 258p, 316p, 337p, 339p, 343p, 351p, 355p, 403p, 409p], ["-", "-", "-", 357p, 359p, 403p, 411p, 415p, 423p, 429p], [311p, 330p, 348p, 409p, 411p, 415p, 423p, 427p, 435p, 441p], [341p, 400p, 418p, 439p, 441p, 445p, 453p, 457p, 505p, 511p], [411p, 430p, 448p, 509p, 511p, 515p, 523p, 527p, 535p, 541p], [441p, 500p, 518p, 539p, 541p, 545p, 553p, 557p, 605p, 611p], [501p, 520p, 538p, 559p, 601p, 605p, 613p, 617p, 625p, 631p], [521p, 540p, 558p, 619p, 621p, 625p, 633p, 636p, 642p, 648p], [601p, 620p, 636p, 656p, 658p, 702p, 709p, 712p, 718p, 724p], ["-", "-", "-", 728p, 730p, 734p, 741p, 744p, 750p, 756p], ["-", "-", "-", 804p, 806p, 810p, 817p, 820p, 826p, 832p], ["-", "-", "-", 904p, 906p, 910p, 917p, 920p, 926p, 932p], ["-", "-", "-", 1004p, 1006p, 1010p, 1017p, 1020p, 1026p, 1032p], ["-", "-", "-", 1104p, 1106p, 1110p, 1117p, 1120p, 1126p, 1132p], []]  
-  
time_points: [City Bus Station (Platform 4), Caswell Drive, Aranda, Cook Shops, Jamison Centre, Belconnen Community Bus Station, Westfield Bus Station, Cohen Street Bus Station]  
long_name: To Cohen Street Bus Station long_name: To Cohen Street Bus Station
between_stops: between_stops:
Westfield Bus Station-Cohen Street Bus Station: [] Westfield Bus Station-Cohen Street Bus Station: []
Belconnen Community Bus Station-Westfield Bus Station: [] Belconnen Community Bus Station-Westfield Bus Station: []
stop_times_saturday: [[814a, 823a, 824a, 827a, 836a, 845a, 847a, 852a], [914a, 923a, 924a, 927a, 936a, 945a, 947a, 952a], [1014a, 1023a, 1024a, 1027a, 1036a, 1045a, 1047a, 1052a], [1114a, 1123a, 1124a, 1127a, 1136a, 1145a, 1147a, 1152a], [1214p, 1223p, 1224p, 1227p, 1236p, 1245p, 1247p, 1252p], [114p, 123p, 124p, 127p, 136p, 145p, 147p, 152p], [214p, 223p, 224p, 227p, 236p, 245p, 247p, 252p], [314p, 323p, 324p, 327p, 336p, 345p, 347p, 352p], [414p, 423p, 424p, 427p, 436p, 445p, 447p, 452p], [514p, 523p, 524p, 527p, 536p, 545p, 547p, 552p], [614p, 623p, 624p, 627p, 636p, 645p, 647p, 652p], [714p, 723p, 724p, 727p, 736p, 745p, 747p, 752p], [814p, 823p, 824p, 827p, 836p, 845p, 847p, 852p], [914p, 923p, 924p, 927p, 936p, 945p, 947p, 952p], [1014p, 1023p, 1024p, 1027p, 1036p, 1045p, 1047p, 1052p], [1114p, 1123p, 1124p, 1127p, 1136p, 1145p, 1147p, 1152p]] short_name: "7"
short_name: "942" stop_times: [[632a, 639a, 646a, 654a, 708a, 710a, 715a], [701a, 708a, 715a, 723a, 738a, 740a, 745a], [731a, 739a, 746a, 754a, 810a, 812a, 817a], [801a, 809a, 816a, 824a, 840a, 842a, 847a], [829a, 837a, 844a, 852a, 908a, 910a, 915a], [858a, 906a, 913a, 921a, 936a, 938a, 943a], [930a, 937a, 944a, 952a, 1006a, 1008a, 1013a], [1000a, 1007a, 1014a, 1022a, 1036a, 1038a, 1043a], [1030a, 1037a, 1044a, 1052a, 1106a, 1108a, 1113a], [1100a, 1107a, 1114a, 1122a, 1136a, 1138a, 1143a], [1130a, 1137a, 1144a, 1152a, 1206p, 1208p, 1213p], [1200p, 1207p, 1214p, 1222p, 1236p, 1238p, 1243p], [1230p, 1237p, 1244p, 1252p, 106p, 108p, 113p], [100p, 107p, 114p, 122p, 136p, 138p, 143p], [130p, 137p, 144p, 152p, 206p, 208p, 213p], [200p, 207p, 214p, 222p, 236p, 238p, 243p], [230p, 237p, 244p, 252p, 307p, 309p, 314p], [259p, 307p, 314p, 323p, 339p, 341p, 346p], [331p, 339p, 346p, 355p, 411p, 413p, 418p], [401p, 409p, 416p, 425p, 441p, 443p, 448p], [431p, 439p, 446p, 455p, 511p, 513p, 518p], [501p, 509p, 516p, 525p, 541p, 543p, 548p], [531p, 539p, 546p, 555p, 611p, 613p, 618p], [631p, 637p, 644p, 652p, 706p, 708p, 713p], [731p, 737p, 744p, 752p, 806p, 808p, 813p], [831p, 837p, 844p, 852p, 906p, 908p, 913p], [931p, 937p, 944p, 952p, 1006p, 1008p, 1013p], [1031p, 1037p, 1044p, 1052p, 1106p, 1108p, 1113p]]
- -
time_points: [Woden Bus Station (Platform 4), Alexander Maconochie Centre] time_points: [Gungahlin Marketplace, Manning Clarke / Oodgeroo, Hoskins Street / Oodgeroo Ave, Flemington Rd / Sandford St, Northbourne Avenue / Antill St, Macarthur / Northbourne Ave, City Bus Station]
long_name: To Alexander Machonochie Centre Hume long_name: To City Bus Station
between_stops: {} between_stops:
  Northbourne Avenue / Antill St-Macarthur / Northbourne Ave: [Wjz5SDc, Wjz5Sux, Wjz5SrO, Wjz5Sqk, Wjz5RvC, Wjz5Rsi, Wjz5RkN]
short_name: "988" Macarthur / Northbourne Ave-City Bus Station: [Wjz5QmR, Wjz5Qmu, Wjz5Qgn, Wjz5Qi2, Wjz5Pl0, Wjz5PdJ, Wjz5P8K, Wjz5P8n, Wjz5Oci, Wjz5O3Q]
stop_times_sunday: [[920a, 940a], [1255p, 115p], [455p, 515p]] short_name: "57"
- stop_times: [[600a, 607a, 610a, 618a, 624a, 626a, 632a], [630a, 637a, 640a, 648a, 654a, 656a, 702a], [700a, 707a, 710a, 718a, 724a, 726a, 732a], [736a, 743a, 746a, 754a, 805a, 810a, 825a], [806a, 813a, 816a, 824a, 835a, 840a, 855a], [836a, 843a, 846a, 854a, 903a, 905a, 911a], [936a, 943a, 946a, 954a, 1000a, 1002a, 1008a], [1036a, 1043a, 1046a, 1054a, 1100a, 1102a, 1108a], [1136a, 1143a, 1146a, 1154a, 1200p, 1202p, 1208p], [1236p, 1243p, 1246p, 1254p, 100p, 102p, 108p], [136p, 143p, 146p, 154p, 200p, 202p, 208p], [236p, 243p, 246p, 254p, 300p, 302p, 308p], [336p, 343p, 346p, 354p, 400p, 402p, 409p], [407p, 414p, 417p, 425p, 432p, 434p, 441p], [437p, 444p, 447p, 455p, 502p, 504p, 511p], [507p, 514p, 517p, 525p, 532p, 534p, 541p], [537p, 544p, 547p, 555p, 602p, 604p, 609p], [636p, 643p, 646p, 654p, 700p, 702p, 707p]]
time_points: [City Bus Station (Platform 8), Macarthur / Northbourne Ave, Northbourne Avenue / Antill St, Flemington Rd / Sandford St, Hibberson / Kate Crace, Gungahlin Marketplace, Ngunnawal Primary, Nicholls Primary, Federation Square, Chuculba / William Slim Dr, Belconnen Community Bus Station, Westfield Bus Station, Cohen Street Bus Station] -
long_name: To Cohen Street Bus Station time_points: [Cohen Street Bus Station (Platform 2), Westfield Bus Station (Platform 1), Belconnen Community Bus Station (Platform 3), Jamison Centre, Cook, Aranda, Caswell Drive, City Bus Station (Platform 7), War Memorial / Limestone Ave, ADFA, Campbell Park Offices, Majura Business Park, Brindabella Business Park, Fairbairn Park]
between_stops: long_name: To Fairbairn Park
Westfield Bus Station-Cohen Street Bus Station: [] between_stops:
Macarthur / Northbourne Ave-Northbourne Avenue / Antill St: [Wjz5RkN, Wjz5Rsi, Wjz5RvC, Wjz5Sqk, Wjz5SrO, Wjz5Sux, Wjz5SDc] Brindabella Business Park-Fairbairn Park: [WjzcrK3, WjzcrrQ, WjzcrEu, WjzcJ0K, WjzcBHZ, WjzcJ38]
City Bus Station (Platform 8)-Macarthur / Northbourne Ave: [Wjz5O3Q, Wjz5Oci, Wjz5P8n, Wjz5P8K, Wjz5PdJ, Wjz5Pl0, Wjz5Qi2, Wjz5Qgn, Wjz5Qmu, Wjz5QmR] Westfield Bus Station (Platform 1)-Belconnen Community Bus Station (Platform 3): []
Belconnen Community Bus Station-Westfield Bus Station: [] Cohen Street Bus Station (Platform 2)-Westfield Bus Station (Platform 1): []
Chuculba / William Slim Dr-Belconnen Community Bus Station: [Wjz6mip, Wjz69vO, Wjz69uI, Wjz69ht, Wjz69gA] ADFA-Campbell Park Offices: [Wjzcend, Wjzce4H, Wjzce7O]
short_name: "52" short_name: "10"
stop_times: [["-", "-", "-", "-", 715a, 718a, 724a, 732a, 740a, 745a, 758a, 800a, 805a], ["-", "-", "-", "-", 735a, 738a, 744a, 753a, 801a, 806a, 819a, 821a, 826a], ["-", "-", "-", "-", 755a, 758a, 804a, 813a, 821a, 826a, 839a, 841a, 846a], ["-", "-", "-", "-", 815a, 818a, 824a, 833a, 841a, 846a, 859a, 901a, 906a], ["-", "-", "-", "-", 835a, 838a, 844a, 853a, 901a, 906a, 918a, 920a, 925a], ["-", "-", "-", "-", 855a, 858a, 904a, 912a, 920a, 925a, 937a, 939a, 944a], ["-", "-", "-", "-", 915a, 918a, 924a, 932a, 940a, 945a, 957a, 959a, 1004a], ["-", "-", "-", "-", 942a, 945a, 951a, 959a, 1007a, 1012a, 1024a, 1026a, 1031a], ["-", "-", "-", "-", 1005a, 1008a, 1014a, 1022a, 1030a, 1035a, 1047a, 1049a, 1054a], ["-", "-", "-", "-", 1105a, 1108a, 1114a, 1122a, 1130a, 1135a, 1147a, 1149a, 1154a], ["-", "-", "-", "-", 1205p, 1208p, 1214p, 1222p, 1230p, 1235p, 1247p, 1249p, 1254p], ["-", "-", "-", "-", 105p, 108p, 114p, 122p, 130p, 135p, 147p, 149p, 154p], ["-", "-", "-", "-", 205p, 208p, 214p, 222p, 230p, 235p, 247p, 249p, 254p], ["-", "-", "-", "-", 301p, 304p, 310p, 318p, 326p, 331p, 343p, 345p, 350p], ["-", "-", "-", "-", 340p, 343p, 349p, 357p, 405p, 410p, 423p, 425p, 430p], [345p, 351p, 353p, 401p, 406p, 409p, 415p, 424p, 432p, 437p, 450p, 452p, 457p], [400p, 407p, 409p, 418p, 423p, 426p, 432p, 441p, 449p, 454p, 507p, 509p, 514p], [418p, 425p, 427p, 436p, 441p, 444p, 450p, 459p, 507p, 512p, 525p, 527p, 532p], [440p, 447p, 449p, 458p, 503p, 506p, 512p, 521p, 529p, 534p, 547p, 549p, 554p], [459p, 506p, 508p, 517p, 522p, 525p, 531p, 540p, 548p, 553p, 606p, 608p, 613p], [515p, 522p, 524p, 533p, 538p, 541p, 547p, 556p, 604p, 609p, 621p, 623p, 628p], [540p, 547p, 549p, 558p, 602p, 605p, 611p, 619p, 627p, 632p, 644p, 646p, 651p], [600p, 606p, 608p, 615p, 618p, 621p, 627p, 635p, 643p, 648p, 700p, 702p, 707p], ["-", "-", "-", "-", 705p, 708p, 714p, 722p, 730p, 735p, 747p, 749p, 754p], ["-", "-", "-", "-", 805p, 808p, 814p, 822p, 830p, 835p, 847p, 849p, 854p], ["-", "-", "-", "-", 905p, 908p, 914p, 922p, 930p, 935p, 947p, 949p, 954p], ["-", "-", "-", "-", 1005p, 1008p, 1014p, 1022p, 1030p, 1035p, 1047p, 1049p, 1054p], ["-", "-", "-", "-", 1105p, 1108p, 1114p, 1122p, 1130p, "-", "-", "-", "-"]] stop_times: [[550a, 552a, 556a, 605a, 615a, 620a, 623a, 634a, "-", "-", "-", "-", "-", "-"], [621a, 623a, 627a, 636a, 646a, 651a, 654a, 705a, "-", "-", "-", "-", "-", "-"], [651a, 653a, 657a, 706a, 716a, 721a, 724a, 736a, 746a, 752a, 756a, 803a, "-", "-"], ["-", "-", "-", "-", 724a, 729a, 732a, 743a, "-", "-", "-", "-", "-", "-"], [706a, 708a, 712a, 721a, 731a, 736a, 739a, 750a, "-", "-", "-", "-", "-", "-"], [721a, 723a, 727a, 736a, 746a, 751a, 754a, 806a, 816a, 822a, 826a, 833a, "-", "-"], ["-", "-", "-", "-", 754a, 759a, 802a, 813a, "-", "-", "-", "-", "-", "-"], [736a, 738a, 742a, 751a, 801a, 806a, 809a, 820a, "-", "-", "-", "-", "-", "-"], [751a, 753a, 757a, 806a, 816a, 821a, 824a, 836a, 846a, 852a, 856a, "-", "-", "-"], [806a, 808a, 812a, 821a, 831a, 836a, 839a, 851a, 901a, 907a, 911a, 918a, 927a, 937a], [821a, 823a, 827a, 836a, 846a, 851a, 854a, 905a, "-", "-", "-", "-", "-", "-"], [836a, 838a, 842a, 851a, 901a, 906a, 909a, 921a, 931a, 937a, 940a, 947a, 956a, 1006a], [851a, 853a, 857a, 906a, 916a, 921a, 924a, 936a, 946a, 952a, 955a, 1002a, 1011a, 1021a], [913a, 915a, 919a, 928a, 938a, 943a, 945a, 957a, 1007a, 1013a, 1016a, 1023a, 1032a, 1042a], [943a, 945a, 949a, 958a, 1008a, 1013a, 1015a, 1027a, 1037a, 1043a, 1046a, 1053a, 1102a, 1112a], [1013a, 1015a, 1019a, 1028a, 1038a, 1043a, 1045a, 1057a, 1107a, 1113a, 1116a, 1123a, 1132a, 1142a], [1043a, 1045a, 1049a, 1058a, 1108a, 1113a, 1115a, 1127a, 1137a, 1143a, 1146a, 1153a, 1202p, 1212p], [1113a, 1115a, 1119a, 1128a, 1138a, 1143a, 1145a, 1157a, 1207p, 1213p, 1216p, 1223p, 1232p, 1242p], [1143a, 1145a, 1149a, 1158a, 1208p, 1213p, 1215p, 1227p, 1237p, 1243p, 1246p, 1253p, 102p, 112p], [1213p, 1215p, 1219p, 1228p, 1238p, 1243p, 1245p, 1257p, 107p, 113p, 116p, 123p, 132p, 142p], [1243p, 1245p, 1249p, 1258p, 108p, 113p, 115p, 127p, 137p, 143p, 146p, 153p, 202p, 212p], [113p, 115p, 119p, 128p, 138p, 143p, 145p, 157p, 207p, 213p, 216p, 223p, 232p, 242p], [143p, 145p, 149p, 158p, 208p, 213p, 215p, 227p, 237p, 243p, 246p, 253p, 302p, 312p], [213p, 215p, 219p, 228p, 238p, 243p, 245p, 257p, 307p, 313p, 317p, 324p, 333p, 343p], [253p, 255p, 259p, 308p, 318p, 323p, 325p, 337p, 347p, 353p, 357p, 404p, 413p, 423p], [323p, 325p, 329p, 338p, 348p, 353p, 355p, 407p, 417p, 423p, 427p, 434p, 443p, 453p], [338p, 340p, 344p, 353p, 403p, 408p, 410p, 421p, "-", "-", "-", "-", "-", "-"], [353p, 355p, 359p, 408p, 418p, 423p, 425p, 437p, 447p, 453p, 457p, "-", "-", "-"], [408p, 410p, 414p, 423p, 433p, 438p, 440p, 451p, "-", "-", "-", "-", "-", "-"], [423p, 425p, 429p, 438p, 448p, 453p, 455p, 506p, "-", "-", "-", "-", "-", "-"], [438p, 440p, 444p, 453p, 503p, 508p, 510p, 521p, "-", "-", "-", "-", "-", "-"], [453p, 455p, 459p, 508p, 518p, 523p, 525p, 536p, "-", "-", "-", "-", "-", "-"], [508p, 510p, 514p, 523p, 533p, 538p, 540p, 551p, "-", "-", "-", "-", "-", "-"], [523p, 525p, 529p, 538p, 548p, 553p, 555p, 606p, "-", "-", "-", "-", "-", "-"], [538p, 540p, 544p, 553p, 603p, 608p, 610p, 621p, "-", "-", "-", "-", "-", "-"], [617p, 619p, 623p, 632p, 642p, 647p, 649p, 700p, "-", "-", "-", "-", "-", "-"], [716p, 718p, 722p, 731p, 741p, 746p, 748p, 759p, "-", "-", "-", "-", "-", "-"], [816p, 818p, 822p, 831p, 841p, 846p, 848p, 859p, "-", "-", "-", "-", "-", "-"], [916p, 918p, 922p, 931p, 941p, 946p, 948p, 959p, "-", "-", "-", "-", "-", "-"], [1016p, 1018p, 1022p, 1031p, 1041p, 1046p, 1048p, 1059p, "-", "-", "-", "-", "-", "-"], [1116p, 1118p, 1122p, 1131p, 1141p, 1146p, 1148p, "-", "-", "-", "-", "-", "-", "-"]]
- -
time_points: [Woden Bus Station (Platform 11), Athllon / Sulwood Kambah, Erindale Centre, Tuggeranong Bus Station] time_points: [City West, City Bus Station (Platform 10), Curtin, Woden Bus Station]
long_name: To Tuggeranong Bus Station long_name: To Woden Bus Station
between_stops: between_stops:
Woden Bus Station (Platform 11)-Athllon / Sulwood Kambah: [Wjz3kAx, Wjz3kyX, Wjz3kwU, Wjz3iFK, Wjz3hL_, Wjz3gK-, Wjz3gQn, Wjz3gMq, Wjz2mTK] City West-City Bus Station (Platform 10): []
short_name: "961" short_name: "732"
stop_times: [[931a, 940a, 950a, 1003a], [1031a, 1040a, 1050a, 1103a], [1131a, 1140a, 1150a, 1203p], [1231p, 1240p, 1250p, 103p], [131p, 140p, 150p, 203p], [231p, 240p, 250p, 303p], [331p, 340p, 350p, 403p], [431p, 440p, 450p, 503p], [531p, 540p, 550p, 603p], [628p, 637p, 647p, 700p]] stop_times: [[435p, 441p, 453p, 503p], [505p, 511p, 523p, 533p], [535p, 541p, 553p, 603p]]
- -
time_points: [Fairbairn Park, Brindabella Business Park, Russell Offices, Kings Ave / National Circuit, Woden Bus Station (Platform 16), Lyons Shops, CIT Weston, Duffy Primary, Cooleman Court] time_points: [Sydney Ave, Russell Offices, City Bus Station (Platform 11), Macarthur / Northbourne Ave, Northbourne Avenue / Antill St, Flynn, Charnwood, Fraser, Fraser East Terminus]
long_name: To Cooleman Court  
between_stops:  
Russell Offices-Kings Ave / National Circuit: [Wjz4RFJ, Wjz4RwH, Wjz4Quk]  
Fairbairn Park-Brindabella Business Park: [WjzcJ38, WjzcBHZ, WjzcJ0K, WjzcrEu, WjzcrrQ, WjzcrK3]  
short_name: "28"  
stop_times: [["-", "-", "-", "-", 742a, 746a, 751a, 759a, 811a], ["-", "-", "-", "-", 845a, 849a, 854a, 902a, 914a], ["-", "-", "-", "-", 952a, 956a, 1000a, 1007a, 1019a], ["-", "-", "-", "-", 1052a, 1056a, 1100a, 1107a, 1119a], ["-", "-", "-", "-", 1152a, 1156a, 1200p, 1207p, 1219p], ["-", "-", "-", "-", 1252p, 1256p, 100p, 107p, 119p], ["-", "-", "-", "-", 152p, 156p, 200p, 207p, 219p], ["-", "-", "-", "-", 252p, 256p, 300p, 308p, 320p], ["-", "-", "-", "-", 312p, 316p, 321p, 329p, 341p], ["-", "-", "-", "-", 342p, 346p, 351p, 359p, 411p], ["-", "-", "-", "-", 412p, 416p, 421p, 429p, 441p], ["-", "-", "-", "-", 442p, 446p, 451p, 459p, 511p], [429p, 439p, 453p, 456p, 511p, 515p, 520p, 528p, 540p], [449p, 459p, 513p, 516p, 531p, 535p, 540p, 548p, 600p], [519p, 529p, 543p, 546p, 601p, 605p, 610p, 618p, 630p], [549p, 559p, 613p, 616p, 631p, 634p, 638p, 645p, 654p], ["-", "-", "-", "-", 732p, 735p, 739p, 746p, 755p], ["-", "-", "-", "-", 832p, 835p, 839p, 846p, 855p], ["-", "-", "-", "-", 932p, 935p, 939p, 946p, 955p], ["-", "-", "-", "-", 1032p, 1035p, 1039p, 1046p, 1055p]]  
-  
time_points: [Tuggeranong Bus Station (Platform 7), Erindale Centre, Chisholm Shops, Heagney / Clift Richardson, Tuggeranong Bus Station]  
long_name: To Tuggeranong Bus Station  
between_stops: {}  
   
stop_times_saturday: [[903a, 914a, 928a, 937a, 950a], [1103a, 1114a, 1128a, 1137a, 1150a], [103p, 114p, 128p, 137p, 150p], [303p, 314p, 328p, 337p, 350p], [503p, 514p, 528p, 537p, 550p], [703p, 714p, 728p, 737p, 750p], [903p, 914p, 928p, 937p, 950p], [1103p, 1114p, 1128p, 1137p, 1150p]]  
short_name: "967"  
-  
time_points: [Sydney Ave, Russell Offices, City Bus Station (Platform 11), Macarthur / Northbourne Ave, Northbourne Avenue / Antill St, Flynn, Charnwood Shops, Fraser Shops, Fraser East Terminus]  
long_name: To Fraser East Terminus long_name: To Fraser East Terminus
between_stops: between_stops:
Macarthur / Northbourne Ave-Northbourne Avenue / Antill St: [Wjz5RkN, Wjz5Rsi, Wjz5RvC, Wjz5Sqk, Wjz5SrO, Wjz5Sux, Wjz5SDc] Macarthur / Northbourne Ave-Northbourne Avenue / Antill St: [Wjz5RkN, Wjz5Rsi, Wjz5RvC, Wjz5Sqk, Wjz5SrO, Wjz5Sux, Wjz5SDc]
Russell Offices-City Bus Station (Platform 11): [Wjz4-WZ, Wjz4-WL, Wjz4-Rc, Wjz4-KO, Wjz4_wS, Wjz4_jm, Wjz4T-X, Wjz5MEL, Wjz5MsT, Wjz5MsD, Wjz5Nht] Russell Offices-City Bus Station (Platform 11): [Wjz4-WZ, Wjz4-WL, Wjz4-Rc, Wjz4-KO, Wjz4_wS, Wjz4_jm, Wjz4T-X, Wjz5MEL, Wjz5MsT, Wjz5MsD, Wjz5Nht]
City Bus Station (Platform 11)-Macarthur / Northbourne Ave: [Wjz5O3Q, Wjz5Oci, Wjz5P8n, Wjz5P8K, Wjz5PdJ, Wjz5Pl0, Wjz5Qi2, Wjz5Qgn, Wjz5Qmu, Wjz5QmR] City Bus Station (Platform 11)-Macarthur / Northbourne Ave: [Wjz5O3Q, Wjz5Oci, Wjz5P8n, Wjz5P8K, Wjz5PdJ, Wjz5Pl0, Wjz5Qi2, Wjz5Qgn, Wjz5Qmu, Wjz5QmR]
short_name: "702" short_name: "702"
stop_times: [[450p, 458p, 508p, 513p, 515p, 527p, 532p, 538p, 542p], ["-", "-", 530p, 535p, 537p, 549p, 554p, 600p, 604p], [535p, 543p, 553p, 558p, 600p, 612p, 617p, 623p, 627p]] stop_times: [[450p, 458p, 508p, 513p, 515p, 527p, 532p, 538p, 542p], ["-", "-", 530p, 535p, 537p, 549p, 554p, 600p, 604p], [535p, 543p, 553p, 558p, 600p, 612p, 617p, 623p, 627p]]
- -
time_points: [Campbell Park Offices, ADFA, Russell Offices, Kings Ave / National Circuit, Woden Bus Station (Platform 16), Weston Primary, Holder Shops, Cooleman Court]  
long_name: To Cooleman Court  
between_stops:  
Russell Offices-Kings Ave / National Circuit: [Wjz4RFJ, Wjz4RwH, Wjz4Quk]  
ADFA-Russell Offices: [Wjzcend, Wjzce4H, Wjzce7O, Wjzc54R, Wjzc55s, Wjzc60i, Wjzc60A]  
Campbell Park Offices-ADFA: [Wjzce7O, Wjzce4H, Wjzcend]  
short_name: 25 225  
stop_times: [["-", "-", "-", "-", 712a, 720a, 723a, 734a], ["-", "-", "-", "-", 807a, 819a, 823a, 835a], ["-", "-", "-", "-", 842a, 854a, 858a, 910a], ["-", "-", "-", "-", 940a, 949a, 952a, 1002a], ["-", "-", "-", "-", 1040a, 1049a, 1052a, 1102a], ["-", "-", "-", "-", 1140a, 1149a, 1152a, 1202p], ["-", "-", "-", "-", 1240p, 1249p, 1252p, 102p], ["-", "-", "-", "-", 140p, 149p, 152p, 202p], ["-", "-", "-", "-", 240p, 249p, 252p, 306p], ["-", "-", "-", "-", 342p, 352p, 356p, 408p], ["-", "-", "-", "-", 412p, 422p, 426p, 438p], [417p, 421p, 425p, 428p, 443p, 453p, 457p, 509p], [447p, 451p, 455p, 458p, 513p, 523p, 527p, 539p], [517p, 521p, 525p, 528p, 543p, 553p, 557p, 609p], ["-", "-", "-", "-", 612p, 622p, 626p, 637p], ["-", "-", "-", "-", 656p, 704p, 707p, 717p], ["-", "-", "-", "-", 756p, 804p, 807p, 817p], ["-", "-", "-", "-", 856p, 904p, 907p, 917p], ["-", "-", "-", "-", 956p, 1004p, 1007p, 1017p], ["-", "-", "-", "-", 1056p, 1104p, 1107p, 1117p]]  
-  
time_points: [Belconnen Community Bus Station (Platform 5), Westfield Bus Station (Platform 2), Cohen Street Bus Station (Platform 5), Belconnen Way, Higgins Shops, West Macgregor, Holt Shops, Kippax]  
long_name: To Kippax  
between_stops:  
Belconnen Community Bus Station (Platform 5)-Westfield Bus Station (Platform 2): []  
Westfield Bus Station (Platform 2)-Cohen Street Bus Station (Platform 5): []  
short_name: "44"  
stop_times: [[725a, 727a, 731a, 737a, 744a, 756a, 801a, 804a], [754a, 756a, 800a, 806a, 813a, 825a, 830a, 833a], [854a, 856a, 900a, 906a, 913a, 925a, 930a, 933a], [955a, 957a, 1001a, 1006a, 1012a, 1024a, 1029a, 1032a], [1055a, 1057a, 1101a, 1106a, 1112a, 1124a, 1129a, 1132a], [1155a, 1157a, 1201p, 1206p, 1212p, 1224p, 1229p, 1232p], [1255p, 1257p, 101p, 106p, 112p, 124p, 129p, 132p], [155p, 157p, 201p, 206p, 212p, 224p, 229p, 232p], [305p, 307p, 311p, 317p, 324p, 336p, 341p, 344p], [337p, 339p, 343p, 349p, 356p, 408p, 413p, 416p], [411p, 413p, 417p, 423p, 430p, 442p, 447p, 450p], [442p, 444p, 448p, 454p, 501p, 513p, 518p, 521p], [516p, 518p, 522p, 528p, 535p, 547p, 552p, 555p], [547p, 549p, 553p, 559p, 606p, 618p, 623p, 626p], [619p, 621p, 625p, 631p, 637p, 649p, 654p, 657p], [654p, 656p, 700p, 705p, 711p, 723p, 728p, 731p], [754p, 756p, 800p, 805p, 811p, 823p, 828p, 831p], [854p, 856p, 900p, 905p, 911p, 923p, 928p, 931p], [954p, 956p, 1000p, 1005p, 1011p, 1023p, 1028p, 1031p], [1054p, 1056p, 1100p, 1105p, 1111p, 1123p, 1128p, 1131p]]  
-  
time_points: [Tuggeranong Bus Station (Platform 7), Bonython Primary School, Woodcock / Clare Dennis, Gordon Primary, Tharwa Drive / Knoke Ave, Conder Primary, Lanyon Market Place, Bonython Primary School, Tuggeranong Bus Station]  
long_name: To Tuggeranong Bus Station  
between_stops: {}  
   
stop_times_saturday: [[725a, 733a, 737a, 741a, 744a, 749a, 800a, 805a, 813a], [925a, 933a, 937a, 941a, 944a, 949a, 1000a, 1005a, 1013a], [1125a, 1133a, 1137a, 1141a, 1144a, 1149a, 1200p, 1205p, 1213p], [125p, 133p, 137p, 141p, 144p, 149p, 200p, 205p, 213p], [325p, 333p, 337p, 341p, 344p, 349p, 400p, 405p, 413p], [525p, 533p, 537p, 541p, 544p, 549p, 600p, 605p, 613p], [725p, 733p, 737p, 741p, 744p, 749p, 800p, 805p, 813p], [928p, 936p, 940p, 944p, 947p, 952p, 1003p, 1008p, 1016p], [1128p, 1136p, 1140p, 1144p, 1147p, 1152p, 1203a, "-", "-"]]  
short_name: "913"  
-  
time_points: [Dickson Shops, Hackett Shops, Ainslie Shops, Olims Hotel, City Bus Station (Platform 2), Kings Ave / National Circuit, Parliament House, Deakin, Yarralumla Shops, John James Hospital, Curtin Shops, Woden Bus Station]  
long_name: To Woden Bus Station  
between_stops:  
Olims Hotel-City Bus Station (Platform 2): [Wjz5V64, Wjz5NRJ, Wjz5NHD, Wjz5NAQ]  
City Bus Station (Platform 2)-Kings Ave / National Circuit: [Wjz5FOn, Wjz4S1U, Wjz4Rs-, Wjz4RFJ, Wjz4RwH, Wjz4Quk]  
Ainslie Shops-Olims Hotel: [Wjz5YAK, Wjz5Yq4, Wjz5XnQ, Wjz5XrS, Wjz5XwW, Wjz5W3H, Wjz5W8l]  
short_name: "2"  
stop_times: [[634a, 639a, 647a, 653a, 700a, 709a, 713a, 718a, 722a, 725a, 729a, 741a], [701a, 706a, 714a, 720a, 727a, 737a, 742a, 747a, 751a, 754a, 758a, 810a], [710a, 715a, 723a, 729a, 736a, 746a, 751a, 756a, 800a, 803a, 807a, 819a], [724a, 729a, 743a, 749a, 756a, 806a, 811a, 816a, 820a, 823a, 827a, 839a], [739a, 748a, 803a, 809a, 816a, 826a, 831a, 836a, 840a, 843a, 847a, 859a], [758a, 807a, 822a, 828a, 835a, 845a, 850a, 855a, 859a, 902a, 906a, 918a], [809a, 818a, 833a, 839a, 846a, 856a, 901a, 906a, 910a, 913a, 917a, 929a], [822a, 831a, 846a, 852a, 859a, 909a, 914a, 919a, 923a, 926a, 930a, 942a], [839a, 848a, 903a, 909a, 916a, 926a, 931a, 936a, 940a, 943a, 947a, 959a], [856a, 905a, 920a, 926a, 933a, 943a, 948a, 953a, 957a, 1000a, 1004a, 1016a], [936a, 941a, 949a, 955a, 1002a, 1012a, 1017a, 1022a, 1026a, 1029a, 1033a, 1045a], [1006a, 1011a, 1019a, 1025a, 1032a, 1042a, 1047a, 1052a, 1056a, 1059a, 1103a, 1115a], [1036a, 1041a, 1049a, 1055a, 1102a, 1112a, 1117a, 1122a, 1126a, 1129a, 1133a, 1145a], [1106a, 1111a, 1119a, 1125a, 1132a, 1142a, 1147a, 1152a, 1156a, 1159a, 1203p, 1215p], [1136a, 1141a, 1149a, 1155a, 1202p, 1212p, 1217p, 1222p, 1226p, 1229p, 1233p, 1245p], [1206p, 1211p, 1219p, 1225p, 1232p, 1242p, 1247p, 1252p, 1256p, 1259p, 103p, 115p], [1236p, 1241p, 1249p, 1255p, 102p, 112p, 117p, 122p, 126p, 129p, 133p, 145p], [106p, 111p, 119p, 125p, 132p, 142p, 147p, 152p, 156p, 159p, 203p, 215p], [136p, 141p, 149p, 155p, 202p, 212p, 217p, 222p, 226p, 229p, 233p, 245p], [206p, 211p, 219p, 225p, 232p, 242p, 247p, 252p, 256p, 259p, 303p, 315p], [236p, 241p, 249p, 255p, 302p, 313p, 318p, 323p, 327p, 330p, 334p, 346p], [249p, 254p, 302p, 308p, 315p, 326p, 331p, 336p, 340p, 343p, 347p, 359p], [306p, 311p, 319p, 325p, 334p, 345p, 350p, 355p, 359p, 402p, 406p, 418p], [312p, 317p, 325p, 331p, 339p, "-", "-", "-", "-", "-", "-", "-"], [319p, 326p, 334p, 340p, 347p, 358p, 403p, 408p, 412p, 415p, 419p, 431p], [332p, 339p, 347p, 353p, 400p, 411p, 416p, 421p, 425p, 428p, 432p, 444p], [349p, 356p, 404p, 410p, 417p, 428p, 433p, 438p, 442p, 445p, 449p, 501p], [402p, 409p, 417p, 423p, 430p, 441p, 446p, 451p, 455p, 458p, 502p, 514p], [419p, 426p, 434p, 440p, 447p, 458p, 503p, 508p, 512p, 515p, 519p, 531p], [432p, 439p, 447p, 453p, 500p, 511p, 516p, 521p, 525p, 528p, 532p, 544p], [449p, 456p, 504p, 510p, 517p, 528p, 533p, 538p, 542p, 545p, 549p, 601p], [502p, 509p, 517p, 523p, 530p, 541p, 546p, 551p, 555p, 558p, 602p, 614p], [519p, 526p, 534p, 540p, 547p, 558p, 603p, 608p, 612p, 615p, 619p, 631p], [532p, 539p, 547p, 553p, 600p, 611p, 616p, 621p, 625p, 628p, 632p, 643p], [549p, 556p, 604p, 610p, 617p, 628p, 633p, 637p, 641p, 644p, 648p, 659p], [603p, 610p, 618p, 624p, 631p, 640p, 645p, 649p, 653p, 656p, 700p, 711p], [626p, 632p, 638p, 643p, 649p, 658p, 703p, 707p, 711p, 714p, 718p, 729p], [726p, 731p, 737p, 742p, 748p, 757p, 802p, 806p, 810p, 813p, 817p, 828p], [826p, 831p, 837p, 842p, 848p, 857p, 902p, 906p, 910p, 913p, 917p, 928p], [926p, 931p, 937p, 942p, 948p, 957p, 1002p, 1006p, 1010p, 1013p, 1017p, 1028p], [1026p, 1031p, 1037p, 1042p, 1048p, 1057p, 1102p, 1106p, 1110p, 1113p, 1117p, 1128p], [1126p, 1131p, 1137p, 1142p, 1147p, "-", "-", "-", "-", "-", "-", "-"], [], [], []]  
-  
time_points: [Lanyon Market Place, Tharwa Drive / Knoke Ave, Woodcock / Clare Dennis, City West, City Bus Station (Platform 10), ACTEW AGL House]  
long_name: To ACTEW AGL House  
between_stops:  
City West-City Bus Station (Platform 10): []  
City Bus Station (Platform 10)-ACTEW AGL House: [Wjz5Nht]  
short_name: "787"  
stop_times: [[647a, 650a, 702a, 728a, 732a, 734a], [720a, 723a, 735a, 801a, 805a, 807a]]  
-  
time_points: [Woden Bus Station (Platform 15), Pearce Shops, Southlands Mawson, Torrens Shops, Chifley Shops, Lyons Shops, Woden Bus Station]  
long_name: To Woden Bus Station  
between_stops: {}  
   
stop_times_saturday: [[833a, 839a, 843a, 849a, 854a, 858a, 901a], [1033a, 1039a, 1043a, 1049a, 1054a, 1058a, 1101a], [1233p, 1239p, 1243p, 1249p, 1254p, 1258p, 101p], [233p, 239p, 243p, 249p, 254p, 258p, 301p], [433p, 439p, 443p, 449p, 454p, 458p, 501p], [633p, 639p, 643p, 649p, 654p, 658p, 701p], [833p, 839p, 843p, 849p, 854p, 858p, 901p], [1033p, 1039p, 1043p, 1049p, 1054p, 1058p, 1101p]]  
short_name: "922"  
-  
time_points: [Sydney Ave, Russell Offices, City Bus Station (Platform 11), Aranda Shops, Macquarie, Hawker Shops, Hawker College, Higgins, Kippax]  
long_name: To Kippax  
between_stops:  
Russell Offices-City Bus Station (Platform 11): [Wjz4-WZ, Wjz4-WL, Wjz4-Rc, Wjz4-KO, Wjz4_wS, Wjz4_jm, Wjz4T-X, Wjz5MEL, Wjz5MsT, Wjz5MsD, Wjz5Nht]  
short_name: "704"  
stop_times: [[506p, 514p, 524p, 533p, 542p, 550p, 555p, 600p, 606p]]  
-  
time_points: [Woden Bus Station (Platform 14), Canberra Hospital, Narrabundah College, Kingston, Kings Ave / National Circuit, Russell Offices, City Bus Station]  
long_name: To City Bus Station  
between_stops:  
Woden Bus Station (Platform 14)-Canberra Hospital: [Wjz3mAg, Wjz3mPO, Wjz3mWn]  
stop_times_saturday: [[800a, 808a, 818a, 833a, 837a, 841a, 849a], [900a, 908a, 918a, 933a, 937a, 941a, 949a], [1000a, 1008a, 1018a, 1033a, 1037a, 1041a, 1049a], [1100a, 1108a, 1118a, 1133a, 1137a, 1141a, 1149a], [1200p, 1208p, 1218p, 1233p, 1237p, 1241p, 1249p], [100p, 108p, 118p, 133p, 137p, 141p, 149p], [200p, 208p, 218p, 233p, 237p, 241p, 249p], [300p, 308p, 318p, 333p, 337p, 341p, 349p], [400p, 408p, 418p, 433p, 437p, 441p, 449p], [500p, 508p, 518p, 533p, 537p, 541p, 549p], [600p, 608p, 618p, 633p, 637p, 641p, 649p], [700p, 707p, 716p, 729p, 733p, 737p, 744p], [800p, 807p, 816p, 829p, 833p, 837p, 844p], [900p, 907p, 916p, 929p, 933p, 937p, 944p], [1000p, 1007p, 1016p, 1029p, 1033p, 1037p, 1044p], [1100p, 1107p, 1116p, 1129p, 1133p, 1137p, 1144p]]  
short_name: "938"  
-  
time_points: [City Bus Station (Platform 8), Dickson Shops, Watson Shops, Watson Terminus, Watson Shops, Dickson Shops, City Bus Station]  
long_name: To City Bus Station  
between_stops: {}  
   
short_name: "939"  
stop_times: [[846a, 903a, 908a, 915a, 920a, 926a, 941a], [946a, 1003a, 1008a, 1015a, 1020a, 1026a, 1041a], [1046a, 1103a, 1108a, 1115a, 1120a, 1126a, 1141a], [1146a, 1203p, 1208p, 1215p, 1220p, 1226p, 1241p], [1246p, 103p, 108p, 115p, 120p, 126p, 141p], [146p, 203p, 208p, 215p, 220p, 226p, 241p], [246p, 303p, 308p, 315p, 320p, 326p, 341p], [346p, 403p, 408p, 415p, 420p, 426p, 441p], [446p, 503p, 508p, 515p, 520p, 526p, 541p], [546p, 603p, 608p, 615p, 620p, 626p, 641p], [646p, 703p, 708p, 715p, 720p, 726p, 741p]]  
-  
time_points: [Cohen Street Bus Station (Platform 2), Westfield Bus Station (Platform 1), Belconnen Community Bus Station (Platform 3), University of Canberra, Gwydir Square Kaleen, Kaleen Village / Marybrynong, Giralang Shops, Southwell Park, Macarthur / Northbourne Ave, City Bus Station (Platform 9), Yarralumla Shops, John James Hospital, Curtin Shops, Woden Bus Station]  
long_name: To Woden Bus Station  
between_stops:  
Westfield Bus Station (Platform 1)-Belconnen Community Bus Station (Platform 3): []  
Cohen Street Bus Station (Platform 2)-Westfield Bus Station (Platform 1): []  
Belconnen Community Bus Station (Platform 3)-University of Canberra: [Wjz681S, Wjz689c, Wjz68Ip, Wjz68IH, Wjz68Y0, Wjz68Yy]  
stop_times_saturday: [[746a, 748a, 752a, 757a, 803a, 808a, 810a, 825a, 830a, 838a, 850a, 853a, 857a, 908a], [846a, 848a, 852a, 857a, 903a, 908a, 910a, 925a, 930a, 938a, 950a, 953a, 957a, 1008a], [946a, 948a, 952a, 957a, 1003a, 1008a, 1010a, 1025a, 1030a, 1038a, 1050a, 1053a, 1057a, 1108a], [1046a, 1048a, 1052a, 1057a, 1103a, 1108a, 1110a, 1125a, 1130a, 1138a, 1150a, 1153a, 1157a, 1208p], [1146a, 1148a, 1152a, 1157a, 1203p, 1208p, 1210p, 1225p, 1230p, 1238p, 1250p, 1253p, 1257p, 108p], [1246p, 1248p, 1252p, 1257p, 103p, 108p, 110p, 125p, 130p, 138p, 150p, 153p, 157p, 208p], [146p, 148p, 152p, 157p, 203p, 208p, 210p, 225p, 230p, 238p, 250p, 253p, 257p, 308p], [246p, 248p, 252p, 257p, 303p, 308p, 310p, 325p, 330p, 338p, 350p, 353p, 357p, 408p], [346p, 348p, 352p, 357p, 403p, 408p, 410p, 425p, 430p, 438p, 450p, 453p, 457p, 508p], [446p, 448p, 452p, 457p, 503p, 508p, 510p, 525p, 530p, 538p, 550p, 553p, 557p, 608p], [546p, 548p, 552p, 557p, 603p, 608p, 610p, 625p, 630p, 637p, 649p, 652p, 655p, 705p], [645p, 647p, 651p, 656p, 701p, 706p, 708p, 723p, 728p, 735p, 747p, 750p, 753p, 803p], [745p, 747p, 751p, 756p, 801p, 806p, 808p, 823p, 828p, 835p, 847p, 850p, 853p, 903p], [845p, 847p, 851p, 856p, 901p, 906p, 908p, 923p, 928p, 935p, 947p, 950p, 953p, 1003p], [945p, 947p, 951p, 956p, 1001p, 1006p, 1008p, 1023p, 1028p, 1035p, 1047p, 1050p, 1053p, 1103p], [1045p, 1047p, 1051p, 1056p, 1101p, 1106p, 1108p, 1123p, 1128p, 1134p, "-", "-", "-", "-"]]  
short_name: "932"  
-  
time_points: [Woden Bus Station (Platform 4), Curtin Shops, John James Hospital, Yarralumla Shops, City Bus Station (Platform 8), Macarthur / Northbourne Ave, Southwell Park, Giralang Shops, Kaleen Village / Marybrynong, Gwydir Square Kaleen, University of Canberra, Belconnen Community Bus Station, Westfield Bus Station, Cohen Street Bus Station]  
long_name: To Cohen Street Bus Station  
between_stops:  
Westfield Bus Station-Cohen Street Bus Station: []  
Belconnen Community Bus Station-Westfield Bus Station: []  
University of Canberra-Belconnen Community Bus Station: [Wjz68Yy, Wjz68Y0, Wjz68IH, Wjz68Ip, Wjz689c, Wjz681S]  
stop_times_saturday: [[739a, 750a, 753a, 756a, 809a, 815a, 819a, 828a, 836a, 841a, 847a, 850a, 852a, 857a], [839a, 850a, 853a, 856a, 909a, 915a, 919a, 928a, 936a, 941a, 947a, 950a, 952a, 957a], [939a, 950a, 953a, 956a, 1009a, 1015a, 1019a, 1028a, 1036a, 1041a, 1047a, 1050a, 1052a, 1057a], [1039a, 1050a, 1053a, 1056a, 1109a, 1115a, 1119a, 1128a, 1136a, 1141a, 1147a, 1150a, 1152a, 1157a], [1139a, 1150a, 1153a, 1156a, 1209p, 1215p, 1219p, 1228p, 1236p, 1241p, 1247p, 1250p, 1252p, 1257p], [1239p, 1250p, 1253p, 1256p, 109p, 115p, 119p, 128p, 136p, 141p, 147p, 150p, 152p, 157p], [139p, 150p, 153p, 156p, 209p, 215p, 219p, 228p, 236p, 241p, 247p, 250p, 252p, 257p], [239p, 250p, 253p, 256p, 309p, 315p, 319p, 328p, 336p, 341p, 347p, 350p, 352p, 357p], [339p, 350p, 353p, 356p, 409p, 415p, 419p, 428p, 436p, 441p, 447p, 450p, 452p, 457p], [439p, 450p, 453p, 456p, 509p, 515p, 519p, 528p, 536p, 541p, 547p, 550p, 552p, 557p], [539p, 550p, 553p, 556p, 609p, 615p, 619p, 628p, 635p, 640p, 645p, 648p, 650p, 655p], [639p, 648p, 651p, 654p, 707p, 712p, 716p, 725p, 732p, 737p, 742p, 745p, 747p, 752p], [739p, 748p, 751p, 754p, 807p, 812p, 816p, 825p, 832p, 837p, 842p, 845p, 847p, 852p], [839p, 848p, 851p, 854p, 907p, 912p, 916p, 925p, 932p, 937p, 942p, 945p, 947p, 952p], [939p, 948p, 951p, 954p, 1007p, 1012p, 1016p, 1025p, 1032p, 1037p, 1042p, 1045p, 1047p, 1052p], [1039p, 1048p, 1051p, 1054p, 1107p, 1112p, 1116p, 1125p, 1132p, 1137p, 1142p, 1145p, 1147p, 1152p], [1139p, 1150p, 1153p, 1156p, 1208a, "-", "-", "-", "-", "-", "-", "-", "-"]]  
short_name: "932"  
-  
time_points: [Tuggeranong Bus Station (Platform 7), Erindale Centre, Chisholm Shops, Heagney / Clift Richardson, Tuggeranong Bus Station]  
long_name: To Tuggeranong Bus Station  
between_stops: {}  
   
short_name: "967"  
stop_times: [[903a, 914a, 928a, 937a, 950a], [1103a, 1114a, 1128a, 1137a, 1150a], [103p, 114p, 128p, 137p, 150p], [303p, 314p, 328p, 337p, 350p], [503p, 514p, 528p, 537p, 550p], [703p, 714p, 728p, 737p, 750p]]  
-  
time_points: [Tuggeranong Bus Station (Platform 5), Erindale Centre, Athllon / Sulwood Kambah, Woden Bus Station]  
long_name: To Woden Bus Station  
between_stops:  
Athllon / Sulwood Kambah-Woden Bus Station: [Wjz2mTK, Wjz3gMq, Wjz3gQn, Wjz3gK-, Wjz3hL_, Wjz3iFK, Wjz3kwU, Wjz3kyX, Wjz3kAx]  
stop_times_saturday: [[825a, 837a, 849a, 858a], [925a, 937a, 949a, 958a], [1025a, 1037a, 1049a, 1058a], [1125a, 1137a, 1149a, 1158a], [1225p, 1237p, 1249p, 1258p], [125p, 137p, 149p, 158p], [225p, 237p, 249p, 258p], [325p, 337p, 349p, 358p], [425p, 437p, 449p, 458p], [525p, 537p, 549p, 558p], [625p, 637p, 649p, 658p], [725p, 737p, 749p, 758p], [825p, 837p, 849p, 858p], [925p, 937p, 949p, 958p], [1025p, 1037p, 1049p, 1058p], [1125p, 1137p, 1149p, "-"]]  
short_name: "964"  
-  
time_points: [Spence Terminus, Spence Shops, Melba Shops, Copland College, Cohen Street Bus Station (Platform 3), Westfield Bus Station (Platform 1), Belconnen Community Bus Station (Platform 1), City Bus Station (Platform 1), Woden Bus Station (Platform 6), Tuggeranong Bus Station]  
long_name: To Tuggeranong Bus Station  
between_stops:  
Cohen Street Bus Station (Platform 3)-Westfield Bus Station (Platform 1): []  
Westfield Bus Station (Platform 1)-Belconnen Community Bus Station (Platform 1): []  
Woden Bus Station (Platform 6)-Tuggeranong Bus Station: [Wjz3kAx, Wjz3kyX, Wjz3kwU, Wjz3iFK, Wjz3hL_, Wjz3gK-, Wjz3gQn, Wjz3gMq, Wjz2nLE, Wjz2mTK, Wjz2mGO, Wjz2lDC, Wjz239F, Wjz238T, Wjz213q]  
City Bus Station (Platform 1)-Woden Bus Station (Platform 6): [Wjz5Nht, Wjz4KO9, Wjz4KNu, Wjz3eZ4, Wjz3eRR, Wjz3m3b, Wjz3m3b]  
Belconnen Community Bus Station (Platform 1)-City Bus Station (Platform 1): [Wjz681S, Wjz689c, Wjz68W3, Wjz68W5, Wjz6gia, Wjz6giR, Wjz5nw6, Wjz5nwb, Wjz5mbS, Wjz5maK, Wjz5G6B, Wjz5G6U, Wjz5GNG, Wjz5GMT, Wjz5FSY, Wjz5F-1]  
short_name: 15 315  
stop_times: [[533a, 538a, 543a, 546a, 556a, 558a, 602a, "-", "-", "-"], [603a, 608a, 613a, 616a, 626a, 628a, 632a, "-", "-", "-"], [632a, 637a, 642a, 645a, 655a, 657a, 701a, 721a, 738a, 755a], [702a, 707a, 712a, 715a, 725a, 727a, 731a, 753a, 810a, 827a], [728a, 733a, 739a, 743a, 753a, 755a, 759a, 821a, 838a, 855a], [750a, 755a, 801a, 805a, 815a, 817a, 821a, 843a, 900a, 917a], ["-", "-", 818a, 822a, 832a, 834a, 838a, 858a, "-", "-"], [810a, 815a, 821a, 825a, 835a, 837a, 841a, 903a, 920a, 936a], [830a, 835a, 841a, 845a, 855a, 857a, 901a, 923a, 940a, 955a], [900a, 905a, 911a, 915a, 925a, 927a, 931a, 951a, 1008a, 1023a], [932a, 937a, 942a, 945a, 955a, 957a, 1001a, 1021a, 1038a, 1053a], [1002a, 1007a, 1012a, 1015a, 1025a, 1027a, 1031a, 1051a, 1108a, 1123a], [1032a, 1037a, 1042a, 1045a, 1055a, 1057a, 1101a, 1121a, 1138a, 1153a], [1102a, 1107a, 1112a, 1115a, 1125a, 1127a, 1131a, 1151a, 1208p, 1223p], [1132a, 1137a, 1142a, 1145a, 1155a, 1157a, 1201p, 1221p, 1238p, 1253p], [1202p, 1207p, 1212p, 1215p, 1225p, 1227p, 1231p, 1251p, 108p, 123p], [1232p, 1237p, 1242p, 1245p, 1255p, 1257p, 101p, 121p, 138p, 153p], [102p, 107p, 112p, 115p, 125p, 127p, 131p, 151p, 208p, 223p], [132p, 137p, 142p, 145p, 155p, 157p, 201p, 221p, 238p, 253p], [202p, 207p, 212p, 215p, 225p, 227p, 231p, 251p, 308p, 327p], [233p, 238p, 243p, 246p, 256p, 258p, 302p, 324p, 341p, 400p], [300p, 305p, 311p, 315p, 325p, 327p, 331p, 353p, 410p, 429p], [330p, 335p, 341p, 345p, 355p, 357p, 401p, 423p, 440p, 459p], [400p, 405p, 411p, 415p, 425p, 427p, 431p, 453p, 510p, 529p], [440p, 445p, 451p, 455p, 505p, 507p, 511p, 533p, 550p, 609p], [530p, 535p, 541p, 545p, 555p, 557p, 601p, 623p, 638p, 653p], [600p, 605p, 611p, 615p, 625p, 627p, 631p, 650p, 704p, 719p], [623p, 628p, 633p, 636p, 645p, 647p, 651p, "-", "-", "-"], [656p, 701p, 706p, 709p, 718p, 720p, 724p, "-", "-", "-"], [740p, 745p, 750p, 753p, 802p, 804p, 808p, "-", "-", "-"], [840p, 845p, 850p, 853p, 902p, 904p, 908p, "-", "-", "-"], [940p, 945p, 950p, 953p, 1002p, 1004p, 1008p, "-", "-", "-"], [1040p, 1045p, 1050p, 1053p, 1102p, 1104p, 1108p, "-", "-", "-"], []]  
-  
time_points: [City Bus Station (Platform 9), Russell Offices, Kings Ave / National Circuit, Kingston, Narrabundah College, Pearce Shops, Woden Bus Station]  
long_name: To Woden Bus Station  
between_stops: {}  
   
short_name: "938"  
stop_times_sunday: [[846a, 854a, 858a, 902a, 917a, 927a, 934a], [946a, 954a, 958a, 1002a, 1017a, 1027a, 1034a], [1046a, 1054a, 1058a, 1102a, 1117a, 1127a, 1134a], [1146a, 1154a, 1158a, 1202p, 1217p, 1227p, 1234p], [1246p, 1254p, 1258p, 102p, 117p, 127p, 134p], [146p, 154p, 158p, 202p, 217p, 227p, 234p], [246p, 254p, 258p, 302p, 317p, 327p, 334p], [346p, 354p, 358p, 402p, 417p, 427p, 434p], [446p, 454p, 458p, 502p, 517p, 527p, 534p], [546p, 554p, 558p, 602p, 617p, 627p, 634p], [646p, 654p, 658p, 702p, 715p, 724p, 731p]]  
-  
time_points: [Cohen Street Bus Station (Platform 1), Westfield Bus Station (Platform 1), Belconnen Community Bus Station (Platform 3), University of Canberra, Australian Institute of Sport, National Hockey Centre Lyneham, Macarthur / Northbourne Ave, City Bus Station (Platform 9), Russell Offices, Railway Station Kingston, Newcastle Street after Isa Street, Fyshwick Direct Factory Outlet, Lithgow St Terminus Fyshwick]  
long_name: To Lithgow St Terminus Fyshwick  
between_stops:  
Westfield Bus Station (Platform 1)-Belconnen Community Bus Station (Platform 3): []  
Cohen Street Bus Station (Platform 1)-Westfield Bus Station (Platform 1): []  
Belconnen Community Bus Station (Platform 3)-University of Canberra: [Wjz681S, Wjz689c, Wjz68Ip, Wjz68IH, Wjz68Y0, Wjz68Yy]  
short_name: "980"  
stop_times: [[820a, 822a, 826a, 834a, 840a, 845a, 851a, 859a, 908a, 914a, 922a, 931a, 940a], [920a, 922a, 926a, 934a, 940a, 945a, 951a, 959a, 1008a, 1014a, 1022a, 1031a, 1040a], [1020a, 1022a, 1026a, 1034a, 1040a, 1045a, 1051a, 1059a, 1108a, 1114a, 1122a, 1131a, 1140a], [1120a, 1122a, 1126a, 1134a, 1140a, 1145a, 1151a, 1159a, 1208p, 1214p, 1222p, 1231p, 1240p], [1220p, 1222p, 1226p, 1234p, 1240p, 1245p, 1251p, 1259p, 108p, 114p, 122p, 131p, 140p], [120p, 122p, 126p, 134p, 140p, 145p, 151p, 159p, 208p, 214p, 222p, 231p, 240p], [220p, 222p, 226p, 234p, 240p, 245p, 251p, 259p, 308p, 314p, 322p, 331p, 340p], [320p, 322p, 326p, 334p, 340p, 345p, 351p, 359p, 408p, 414p, 422p, 431p, 440p], ["-", "-", "-", "-", "-", "-", "-", 415p, 424p, 430p, "-", "-", "-"], [420p, 422p, 426p, 434p, 440p, 445p, 451p, 459p, 508p, 514p, 522p, 531p, 540p], [520p, 522p, 526p, 534p, 540p, 545p, 551p, 558p, "-", "-", "-", "-", "-"], [615p, 617p, 621p, 629p, 635p, 640p, 645p, 652p, "-", "-", "-", "-", "-"]]  
-  
time_points: [Woden Bus Station (Platform 3), Waramanga Shops, Fisher Shops, Chapman Shops, Rivett Shops, Cooleman Court]  
long_name: To Cooleman Court  
between_stops: {}  
   
short_name: "927"  
stop_times: [[920a, 929a, 932a, 942a, 945a, 950a], [1020a, 1029a, 1032a, 1042a, 1045a, 1050a], [1120a, 1129a, 1132a, 1142a, 1145a, 1150a], [1220p, 1229p, 1232p, 1242p, 1245p, 1250p], [120p, 129p, 132p, 142p, 145p, 150p], [220p, 229p, 232p, 242p, 245p, 250p], [320p, 329p, 332p, 342p, 345p, 350p], [420p, 429p, 432p, 442p, 445p, 450p], [520p, 529p, 532p, 542p, 545p, 550p], [620p, 629p, 632p, 642p, 645p, 650p]]  
-  
time_points: [Belconnen Community Bus Station (Platform 4), Westfield Bus Station (Platform 2), Cohen Street Bus Station (Platform 6), Kippax, Macgregor, Charnwood Shops, Fraser West Terminus, Charnwood Shops, Macgregor, Kippax, Cohen Street Bus Station, Westfield Bus Station, Belconnen Community Bus Station]  
long_name: To Belconnen Community Bus Station  
between_stops:  
Belconnen Community Bus Station (Platform 4)-Westfield Bus Station (Platform 2): []  
Westfield Bus Station-Belconnen Community Bus Station: []  
Cohen Street Bus Station-Westfield Bus Station: []  
Westfield Bus Station (Platform 2)-Cohen Street Bus Station (Platform 6): []  
stop_times_saturday: [["-", "-", "-", "-", "-", "-", 757a, 809a, 816a, 823a, 836a, 838a, 842a], [814a, 816a, 820a, 833a, 839a, 846a, 857a, 909a, 916a, 923a, 936a, 938a, 942a], [914a, 916a, 920a, 933a, 939a, 946a, 957a, 1009a, 1016a, 1023a, 1036a, 1038a, 1042a], [1014a, 1016a, 1020a, 1033a, 1039a, 1046a, 1057a, 1109a, 1116a, 1123a, 1136a, 1138a, 1142a], [1114a, 1116a, 1120a, 1133a, 1139a, 1146a, 1157a, 1209p, 1216p, 1223p, 1236p, 1238p, 1242p], [1214p, 1216p, 1220p, 1233p, 1239p, 1246p, 1257p, 109p, 116p, 123p, 136p, 138p, 142p], [114p, 116p, 120p, 133p, 139p, 146p, 157p, 209p, 216p, 223p, 236p, 238p, 242p], [214p, 216p, 220p, 233p, 239p, 246p, 257p, 309p, 316p, 323p, 336p, 338p, 342p], [314p, 316p, 320p, 333p, 339p, 346p, 357p, 409p, 416p, 423p, 436p, 438p, 442p], [414p, 416p, 420p, 433p, 439p, 446p, 457p, 509p, 516p, 523p, 536p, 538p, 542p], [514p, 516p, 520p, 533p, 539p, 546p, 557p, 609p, 616p, 623p, 636p, 638p, 642p], [614p, 616p, 620p, 633p, 639p, 646p, 656p, 707p, 714p, 721p, 733p, 735p, 739p], [713p, 715p, 719p, 731p, 737p, 744p, 754p, 805p, 812p, 819p, 831p, 833p, 837p], [813p, 815p, 819p, 831p, 837p, 844p, 854p, 905p, 912p, 919p, 931p, 933p, 937p], [913p, 915p, 919p, 931p, 937p, 944p, 954p, 1005p, 1012p, 1019p, 1031p, 1033p, 1037p], [1013p, 1015p, 1019p, 1031p, 1037p, 1044p, 1054p, "-", "-", "-", "-", "-", "-"], [1113p, 1115p, 1119p, 1131p, 1137p, 1144p, 1154p, "-", "-", "-", "-", "-", "-"]]  
short_name: "905"  
-  
time_points: [Woden Bus Station (Platform 2), Brindabella Gardens Nursing Home, Saint Andrews Village Hughes, Canberra Hospital, Woden Bus Station]  
long_name: To Woden Bus Station  
between_stops:  
Canberra Hospital-Woden Bus Station: [Wjz3mWn, Wjz3mPO, Wjz3mAg]  
short_name: "76"  
stop_times: [[1000a, 1007a, 1015a, 1020a, 1028a], [1200p, 1207p, 1215p, 1220p, 1228p], [200p, 207p, 215p, 220p, 228p]]  
-  
time_points: [Calwell Shops, Isabella Shops, Chisholm Shops, Russell Offices, City Bus Station (Platform 11), City West]  
long_name: To City West  
between_stops:  
Russell Offices-City Bus Station (Platform 11): [Wjz4-WZ, Wjz4-WL, Wjz4-Rc, Wjz4-KO, Wjz4_wS, Wjz4_jm, Wjz4T-X, Wjz5MEL, Wjz5MsT, Wjz5MsD, Wjz5Nht]  
City Bus Station (Platform 11)-City West: []  
short_name: "768"  
stop_times: [[707a, 715a, 726a, 751a, 800a, 804a], [737a, 748a, 801a, 833a, 845a, 848a]]  
-  
time_points: [Woden Bus Station (Platform 14), Pearce Shops, Narrabundah College, Kingston, Kings Ave / National Circuit, Russell Offices, City Bus Station]  
long_name: To City Bus Station  
between_stops: {}  
   
short_name: "938"  
stop_times: [[800a, 808a, 818a, 833a, 837a, 841a, 849a], [900a, 908a, 918a, 933a, 937a, 941a, 949a], [1000a, 1008a, 1018a, 1033a, 1037a, 1041a, 1049a], [1100a, 1108a, 1118a, 1133a, 1137a, 1141a, 1149a], [1200p, 1208p, 1218p, 1233p, 1237p, 1241p, 1249p], [100p, 108p, 118p, 133p, 137p, 141p, 149p], [200p, 208p, 218p, 233p, 237p, 241p, 249p], [300p, 308p, 318p, 333p, 337p, 341p, 349p], [400p, 408p, 418p, 433p, 437p, 441p, 449p], [500p, 508p, 518p, 533p, 537p, 541p, 549p], [600p, 608p, 618p, 633p, 637p, 641p, 649p], [700p, 707p, 716p, 729p, 733p, 737p, 744p]]  
-  
time_points: [Belconnen Community Bus Station (Platform 6), Westfield Bus Station (Platform 2), Cohen Street Bus Station (Platform 5), Higgins Shops, Kippax, Higgins Shops, Cohen Street Bus Station, Westfield Bus Station, Belconnen Community Bus Station]  
long_name: To Belconnen Community Bus Station  
between_stops:  
Belconnen Community Bus Station (Platform 6)-Westfield Bus Station (Platform 2): []  
Westfield Bus Station-Belconnen Community Bus Station: []  
Cohen Street Bus Station-Westfield Bus Station: []  
Westfield Bus Station (Platform 2)-Cohen Street Bus Station (Platform 5): []  
short_name: "904"  
stop_times_sunday: [[819a, 821a, 825a, 846a, 857a, 907a, 928a, 930a, 934a], [919a, 921a, 925a, 946a, 957a, 1007a, 1028a, 1030a, 1034a], [1019a, 1021a, 1025a, 1046a, 1057a, 1107a, 1128a, 1130a, 1134a], [1119a, 1121a, 1125a, 1146a, 1157a, 1207p, 1228p, 1230p, 1234p], [1219p, 1221p, 1225p, 1246p, 1257p, 107p, 128p, 130p, 134p], [119p, 121p, 125p, 146p, 157p, 207p, 228p, 230p, 234p], [219p, 221p, 225p, 246p, 257p, 307p, 328p, 330p, 334p], [319p, 321p, 325p, 346p, 357p, 407p, 428p, 430p, 434p], [419p, 421p, 425p, 446p, 457p, 507p, 528p, 530p, 534p], [519p, 521p, 525p, 546p, 557p, 607p, 628p, 630p, 634p], [619p, 621p, 625p, 645p, 656p, 706p, 726p, 728p, 732p]]  
-  
time_points: [City Bus Station (Platform 8), Macarthur / Northbourne Ave, Northbourne Avenue / Antill St, Bimberi Centre]  
long_name: To Bimberi Centre  
between_stops:  
Macarthur / Northbourne Ave-Northbourne Avenue / Antill St: [Wjz5RkN, Wjz5Rsi, Wjz5RvC, Wjz5Sqk, Wjz5SrO, Wjz5Sux, Wjz5SDc]  
City Bus Station (Platform 8)-Macarthur / Northbourne Ave: [Wjz5O3Q, Wjz5Oci, Wjz5P8n, Wjz5P8K, Wjz5PdJ, Wjz5Pl0, Wjz5Qi2, Wjz5Qgn, Wjz5Qmu, Wjz5QmR]  
short_name: "82"  
stop_times: [[632a, 638a, 640a, 650a], [342p, 348p, 350p, 400p]]  
-  
time_points: [Belconnen Community Bus Station (Platform 4), Westfield Bus Station (Platform 2), Cohen Street Bus Station (Platform 6), McKellar Shops, Evatt Shops, Spence Terminus, Evatt Shops, McKellar Shops, Cohen Street Bus Station, Westfield Bus Station, Belconnen Community Bus Station]  
long_name: To Belconnen Community Bus Station  
between_stops:  
Belconnen Community Bus Station (Platform 4)-Westfield Bus Station (Platform 2): []  
Westfield Bus Station-Belconnen Community Bus Station: []  
Cohen Street Bus Station-Westfield Bus Station: []  
Westfield Bus Station (Platform 2)-Cohen Street Bus Station (Platform 6): []  
short_name: "902"  
stop_times_sunday: [[851a, 853a, 857a, 904a, 912a, 918a, 923a, 931a, 939a, 941a, 945a], [951a, 953a, 957a, 1004a, 1012a, 1018a, 1023a, 1031a, 1039a, 1041a, 1045a], [1051a, 1053a, 1057a, 1104a, 1112a, 1118a, 1123a, 1131a, 1139a, 1141a, 1145a], [1151a, 1153a, 1157a, 1204p, 1212p, 1218p, 1223p, 1231p, 1239p, 1241p, 1245p], [1251p, 1253p, 1257p, 104p, 112p, 118p, 123p, 131p, 139p, 141p, 145p], [151p, 153p, 157p, 204p, 212p, 218p, 223p, 231p, 239p, 241p, 245p], [251p, 253p, 257p, 304p, 312p, 318p, 323p, 331p, 339p, 341p, 345p], [351p, 353p, 357p, 404p, 412p, 418p, 423p, 431p, 439p, 441p, 445p], [451p, 453p, 457p, 504p, 512p, 518p, 523p, 531p, 539p, 541p, 545p], [551p, 553p, 557p, 604p, 612p, 618p, 623p, 631p, 638p, 640p, 644p], [651p, 653p, 657p, 703p, 710p, 716p, 721p, 729p, 736p, 738p, 742p]]  
-  
time_points: [Tuggeranong Bus Station (Platform 7), Bonython Primary School, Woodcock / Clare Dennis, Gordon Primary, Tharwa Drive / Knoke Ave, Conder Primary, Lanyon Market Place, Bonython Primary School, Tuggeranong Bus Station]  
long_name: To Tuggeranong Bus Station  
between_stops: {}  
   
short_name: "913"  
stop_times: [[925a, 933a, 937a, 941a, 944a, 949a, 1000a, 1005a, 1013a], [1125a, 1133a, 1137a, 1141a, 1144a, 1149a, 1200p, 1205p, 1213p], [125p, 133p, 137p, 141p, 144p, 149p, 200p, 205p, 213p], [325p, 333p, 337p, 341p, 344p, 349p, 400p, 405p, 413p], [525p, 533p, 537p, 541p, 544p, 549p, 600p, 605p, 613p], [725p, 733p, 737p, 741p, 744p, 749p, 800p, 805p, 813p]]  
-  
time_points: [Tuggeranong Bus Station (Platform 5), MacKillop College Isabella Campus, Gowrie, Erindale Centre, Woden Bus Station (Platform 10), Kings Ave / National Circuit, Russell Offices, City Bus Station, City West]  
long_name: To City West  
between_stops:  
City Bus Station-City West: []  
Kings Ave / National Circuit-Russell Offices: [Wjz4Quk, Wjz4RwH, Wjz4RFJ]  
Russell Offices-City Bus Station: [Wjz4-WZ, Wjz4-WL, Wjz4-Rc, Wjz4-KO, Wjz4_wS, Wjz4_jm, Wjz4T-X, Wjz5MEL, Wjz5MsT, Wjz5MsD, Wjz5Nht]  
short_name: 65 265  
stop_times: [[535a, 541a, 552a, 557a, 611a, "-", "-", "-", "-"], [635a, 641a, 652a, 657a, 711a, "-", "-", "-", "-"], [653a, 700a, 712a, 721a, 737a, 752a, 756a, 805a, 808a], [720a, 726a, 734a, 743a, 801a, 815a, 819a, 829a, 832a], [730a, 739a, 756a, 805a, 822a, "-", "-", "-", "-"], [745a, 754a, 811a, 820a, 842a, "-", "-", "-", "-"], [815a, 824a, 841a, 850a, 907a, "-", "-", "-", "-"], [845a, 854a, 911a, 920a, 936a, "-", "-", "-", "-"], [945a, 952a, 1005a, 1012a, 1027a, "-", "-", "-", "-"], [1045a, 1052a, 1105a, 1112a, 1127a, "-", "-", "-", "-"], [1145a, 1152a, 1205p, 1212p, 1227p, "-", "-", "-", "-"], [1245p, 1252p, 105p, 112p, 127p, "-", "-", "-", "-"], [145p, 152p, 205p, 212p, 227p, "-", "-", "-", "-"], [245p, 252p, 305p, 312p, 331p, "-", "-", "-", "-"], [315p, 324p, 337p, 344p, 403p, "-", "-", "-", "-"], [345p, 354p, 407p, 414p, 433p, "-", "-", "-", "-"], [420p, 429p, 442p, 449p, 508p, "-", "-", "-", "-"], [445p, 454p, 507p, 514p, 533p, "-", "-", "-", "-"], [515p, 524p, 537p, 544p, 603p, "-", "-", "-", "-"], [545p, 554p, 607p, 614p, 633p, "-", "-", "-", "-"], [615p, 624p, 636p, 641p, 657p, "-", "-", "-", "-"], [641p, 647p, 659p, 704p, 720p, "-", "-", "-", "-"], [741p, 747p, 759p, 804p, 820p, "-", "-", "-", "-"], [841p, 847p, 859p, 904p, 920p, "-", "-", "-", "-"], [941p, 947p, 959p, 1004p, 1020p, "-", "-", "-", "-"], [1041p, 1047p, 1059p, 1104p, 1120p, "-", "-", "-", "-"]]  
-  
time_points: [Sydney Ave, Russell Offices, City Bus Station (Platform 11), Belconnen Community Bus Station, Westfield Bus Station, Cohen Street Bus Station]  
long_name: To Cohen Street Bus Station  
between_stops:  
Westfield Bus Station-Cohen Street Bus Station: []  
Russell Offices-City Bus Station (Platform 11): [Wjz4-WZ, Wjz4-WL, Wjz4-Rc, Wjz4-KO, Wjz4_wS, Wjz4_jm, Wjz4T-X, Wjz5MEL, Wjz5MsT, Wjz5MsD, Wjz5Nht]  
Belconnen Community Bus Station-Westfield Bus Station: []  
City Bus Station (Platform 11)-Belconnen Community Bus Station: [Wjz5F-1, Wjz5FSY, Wjz5GMT, Wjz5GNG, Wjz5G6U, Wjz5G6B, Wjz5maK, Wjz5mbS, Wjz5nwb, Wjz5nw6, Wjz6giR, Wjz6gia, Wjz68W3, Wjz68W5, Wjz689c, Wjz681S]  
short_name: "710"  
stop_times: [[407p, 415p, 425p, 445p, 447p, 452p], [427p, 435p, 445p, 505p, 507p, 512p], [445p, 453p, 503p, 523p, 525p, 530p], [507p, 515p, 525p, 545p, 547p, 552p], [527p, 535p, 545p, 605p, 607p, 612p]]  
-  
time_points: [City Bus Station (Platform 8), Macarthur / Northbourne Ave, Northbourne Avenue / Antill St, Flemington Rd / Sandford St, Flemington Rd / Nullabor Ave, Anthony Rolfe Av / Moonlight Av, Gungahlin Marketplace, Chuculba / William Slim Dr, Belconnen Community Bus Station, Westfield Bus Station, Cohen Street Bus Station]  
long_name: To Cohen Street Bus Station  
between_stops:  
Westfield Bus Station-Cohen Street Bus Station: []  
Macarthur / Northbourne Ave-Northbourne Avenue / Antill St: [Wjz5RkN, Wjz5Rsi, Wjz5RvC, Wjz5Sqk, Wjz5SrO, Wjz5Sux, Wjz5SDc]  
City Bus Station (Platform 8)-Macarthur / Northbourne Ave: [Wjz5O3Q, Wjz5Oci, Wjz5P8n, Wjz5P8K, Wjz5PdJ, Wjz5Pl0, Wjz5Qi2, Wjz5Qgn, Wjz5Qmu, Wjz5QmR]  
Belconnen Community Bus Station-Westfield Bus Station: []  
Chuculba / William Slim Dr-Belconnen Community Bus Station: [Wjz6mip, Wjz69vO, Wjz69uI, Wjz69ht, Wjz69gA]  
short_name: "58"  
stop_times: [["-", "-", "-", "-", 551a, 558a, 606a, "-", "-", "-", "-"], ["-", "-", "-", "-", 624a, 631a, 639a, "-", "-", "-", "-"], [631a, 637a, 639a, 645a, 651a, 658a, 706a, 717a, 733a, 735a, 740a], [711a, 717a, 719a, 725a, 731a, 738a, 746a, 757a, 814a, 816a, 821a], [727a, 733a, 735a, 741a, 748a, 757a, 806a, 817a, 834a, 836a, 841a], [745a, 752a, 754a, 800a, 808a, 817a, 826a, 837a, 854a, 856a, 901a], [805a, 812a, 814a, 820a, 828a, 837a, 846a, 857a, 913a, 915a, 920a], [917a, 923a, 925a, 931a, 938a, 945a, 953a, 1003a, 1019a, 1021a, 1026a], [1017a, 1023a, 1025a, 1031a, 1038a, 1045a, 1053a, 1103a, 1119a, 1121a, 1126a], [1117a, 1123a, 1125a, 1131a, 1138a, 1145a, 1153a, 1203p, 1219p, 1221p, 1226p], [1217p, 1223p, 1225p, 1231p, 1238p, 1245p, 1253p, 103p, 119p, 121p, 126p], [117p, 123p, 125p, 131p, 138p, 145p, 153p, 203p, 219p, 221p, 226p], [217p, 223p, 225p, 231p, 238p, 245p, 253p, 303p, 320p, 322p, 327p], [328p, 335p, 337p, 344p, 352p, 401p, 410p, 421p, 438p, 440p, 445p], [419p, 426p, 428p, 435p, 443p, 452p, 501p, 512p, 529p, 531p, 536p], [439p, 446p, 448p, 455p, 503p, 512p, 521p, 532p, 549p, 551p, 556p], [500p, 507p, 509p, 516p, 524p, 533p, 542p, 553p, 609p, 611p, 616p], [520p, 527p, 529p, 536p, 544p, 553p, 602p, 612p, 628p, 630p, 635p], [540p, 547p, 549p, 556p, 603p, 610p, 618p, 628p, 644p, 646p, 651p], [600p, 606p, 608p, 613p, 619p, 626p, 634p, 644p, 700p, 702p, 707p], [631p, 637p, 639p, 644p, 650p, 657p, 705p, 715p, 731p, 733p, 738p], [717p, 723p, 725p, 730p, 736p, 743p, 751p, 801p, 817p, 819p, 824p], [817p, 823p, 825p, 830p, 836p, 843p, 851p, 901p, 917p, 919p, 924p], [917p, 923p, 925p, 930p, 936p, 943p, 951p, 1001p, 1017p, 1019p, 1024p], [1017p, 1023p, 1025p, 1030p, 1036p, 1043p, 1051p, 1101p, 1117p, 1119p, 1124p], [1117p, 1123p, 1125p, 1130p, 1136p, 1143p, 1151p, 1201a, 1217a, 1219a, 1224a], []]  
-  
time_points: [City Bus Station (Platform 4), Caswell Drive, Aranda, Cook Shops, Jamison Centre, Belconnen Community Bus Station, Westfield Bus Station, Cohen Street Bus Station]  
long_name: To Cohen Street Bus Station  
between_stops:  
Westfield Bus Station-Cohen Street Bus Station: []  
Belconnen Community Bus Station-Westfield Bus Station: []  
short_name: "942"  
stop_times_sunday: [[914a, 923a, 924a, 927a, 936a, 945a, 947a, 952a], [1014a, 1023a, 1024a, 1027a, 1036a, 1045a, 1047a, 1052a], [1114a, 1123a, 1124a, 1127a, 1136a, 1145a, 1147a, 1152a], [1214p, 1223p, 1224p, 1227p, 1236p, 1245p, 1247p, 1252p], [114p, 123p, 124p, 127p, 136p, 145p, 147p, 152p], [214p, 223p, 224p, 227p, 236p, 245p, 247p, 252p], [314p, 323p, 324p, 327p, 336p, 345p, 347p, 352p], [414p, 423p, 424p, 427p, 436p, 445p, 447p, 452p], [514p, 523p, 524p, 527p, 536p, 545p, 547p, 552p], [614p, 623p, 624p, 627p, 636p, 645p, 647p, 652p]]  
-  
time_points: [Tuggeranong Bus Station (Platform 3), Erindale Centre, Athllon / Sulwood Kambah, Woden Bus Station]  
long_name: To Woden Bus Station  
between_stops:  
Athllon / Sulwood Kambah-Woden Bus Station: [Wjz2mTK, Wjz3gMq, Wjz3gQn, Wjz3gK-, Wjz3hL_, Wjz3iFK, Wjz3kwU, Wjz3kyX, Wjz3kAx]  
short_name: "961"  
stop_times_sunday: [[942a, 956a, 1006a, 1015a], [1042a, 1056a, 1106a, 1115a], [1142a, 1156a, 1206p, 1215p], [1242p, 1256p, 106p, 115p], [142p, 156p, 206p, 215p], [242p, 256p, 306p, 315p], [342p, 356p, 406p, 415p], [442p, 456p, 506p, 515p], [542p, 556p, 606p, 615p]]  
-  
time_points: [Woden Bus Station (Platform 4), Curtin Shops, John James Hospital, Yarralumla Shops, Deakin, Parliament House, Kings Ave / National Circuit, City Bus Station (Platform 5), Olims Hotel, Ainslie Shops, Hackett Shops, Dickson Shops]  
long_name: To Dickson Shops  
between_stops:  
City Bus Station (Platform 5)-Olims Hotel: [Wjz5NAQ, Wjz5NHD, Wjz5NRJ, Wjz5V64]  
Kings Ave / National Circuit-City Bus Station (Platform 5): [Wjz4Quk, Wjz4RwH, Wjz4RFJ, Wjz4Rs-, Wjz4S1U, Wjz5FOn]  
Olims Hotel-Ainslie Shops: [Wjz5W8l, Wjz5W3H, Wjz5XwW, Wjz5XrS, Wjz5XnQ, Wjz5Yq4, Wjz5YAK]  
short_name: "2"  
stop_times: [["-", "-", "-", "-", "-", "-", "-", 703a, 712a, 717a, 725a, 733a], [653a, 704a, 708a, 711a, 715a, 719a, 723a, 733a, 742a, 748a, 756a, 805a], [708a, 719a, 723a, 726a, 730a, 734a, 738a, 749a, 758a, 804a, 812a, 821a], [719a, 730a, 734a, 737a, 741a, 745a, 749a, 800a, 809a, 815a, 823a, 833a], [738a, 749a, 754a, 758a, 803a, 808a, 814a, 830a, 838a, 845a, 853a, 859a], [753a, 804a, 808a, 812a, 817a, 823a, 826a, 843a, 849a, 854a, 902a, 910a], [808a, 819a, 823a, 826a, 830a, 834a, 838a, 849a, 858a, 904a, 912a, 921a], [823a, 834a, 838a, 841a, 845a, 849a, 853a, 904a, 913a, 919a, 927a, 935a], [838a, 849a, 853a, 856a, 900a, 904a, 908a, 918a, "-", "-", "-", "-"], [851a, 902a, 906a, 909a, 913a, 917a, 921a, 932a, 941a, 946a, 954a, 1001a], [921a, 932a, 936a, 939a, 943a, 947a, 951a, 1002a, 1011a, 1016a, 1024a, 1031a], [951a, 1002a, 1006a, 1009a, 1013a, 1017a, 1021a, 1032a, 1041a, 1046a, 1054a, 1101a], [1021a, 1032a, 1036a, 1039a, 1043a, 1047a, 1051a, 1102a, 1111a, 1116a, 1124a, 1131a], [1051a, 1102a, 1106a, 1109a, 1113a, 1117a, 1121a, 1132a, 1141a, 1146a, 1154a, 1201p], [1121a, 1132a, 1136a, 1139a, 1143a, 1147a, 1151a, 1202p, 1211p, 1216p, 1224p, 1231p], [1151a, 1202p, 1206p, 1209p, 1213p, 1217p, 1221p, 1232p, 1241p, 1246p, 1254p, 101p], [1221p, 1232p, 1236p, 1239p, 1243p, 1247p, 1251p, 102p, 111p, 116p, 124p, 131p], [1251p, 102p, 106p, 109p, 113p, 117p, 121p, 132p, 141p, 146p, 154p, 201p], [121p, 132p, 136p, 139p, 143p, 147p, 151p, 202p, 211p, 216p, 224p, 231p], [151p, 202p, 206p, 209p, 213p, 217p, 221p, 232p, 241p, 246p, 254p, 301p], [216p, 227p, 231p, 234p, 238p, 242p, 246p, 257p, 306p, 312p, 320p, 328p], [238p, 249p, 253p, 256p, 300p, 304p, 308p, 319p, 328p, 334p, 342p, 351p], [253p, 304p, 308p, 311p, 315p, 319p, 323p, 334p, 343p, 349p, 357p, 406p], [308p, 318p, 322p, 325p, 329p, 333p, 337p, 348p, 357p, 403p, 411p, 420p], [323p, 333p, 337p, 340p, 344p, 348p, 352p, 403p, 412p, 418p, 426p, 435p], [338p, 348p, 352p, 355p, 359p, 403p, 407p, 418p, 427p, 433p, 441p, 450p], [353p, 403p, 407p, 410p, 414p, 418p, 422p, 433p, 442p, 448p, 456p, 505p], [408p, 418p, 422p, 425p, 429p, 433p, 437p, 448p, 457p, 503p, 511p, 520p], [423p, 433p, 437p, 440p, 444p, 448p, 452p, 503p, 512p, 518p, 526p, 535p], [438p, 448p, 452p, 455p, 459p, 503p, 507p, 518p, 527p, 533p, 541p, 550p], [453p, 503p, 507p, 510p, 514p, 518p, 522p, 533p, 542p, 548p, 556p, 605p], [508p, 518p, 522p, 525p, 529p, 533p, 537p, 548p, 557p, 603p, 611p, 620p], [523p, 533p, 537p, 540p, 544p, 548p, 552p, 603p, 612p, 618p, 626p, 633p], [538p, 548p, 552p, 555p, 559p, 603p, 607p, 618p, 627p, 633p, 639p, 645p], [553p, 603p, 607p, 610p, 614p, 618p, 622p, 633p, 640p, 645p, 651p, 657p], [640p, 650p, 653p, 656p, 700p, 703p, 707p, 717p, 724p, 729p, 735p, 741p], [740p, 750p, 753p, 756p, 800p, 803p, 807p, 817p, 824p, 829p, 835p, 841p], [840p, 850p, 853p, 856p, 900p, 903p, 907p, 917p, 924p, 929p, 935p, 941p], [940p, 950p, 953p, 956p, 1000p, 1003p, 1007p, 1017p, 1024p, 1029p, 1035p, 1041p], [1040p, 1050p, 1053p, 1056p, 1100p, 1103p, 1107p, 1117p, 1124p, 1129p, 1135p, 1141p]]  
-  
time_points: [Lanyon Market Place, Gordon Primary, Woodcock / Clare Dennis, Tuggeranong Bus Station (Platform 8), Woden Bus Station (Platform 9), City Bus Station (Platform 3), Belconnen Community Bus Station, Westfield Bus Station, Cohen Street Bus Station]  
long_name: To Cohen Street Bus Station  
between_stops:  
Westfield Bus Station-Cohen Street Bus Station: []  
City Bus Station (Platform 3)-Belconnen Community Bus Station: [Wjz5F-1, Wjz5FSY, Wjz5GMT, Wjz5GNG, Wjz5G6U, Wjz5G6B, Wjz5maK, Wjz5mbS, Wjz5nwb, Wjz5nw6, Wjz6giR, Wjz6gia, Wjz68W3, Wjz68W5, Wjz689c, Wjz681S]  
Belconnen Community Bus Station-Westfield Bus Station: []  
Tuggeranong Bus Station (Platform 8)-Woden Bus Station (Platform 9): [Wjz213q, Wjz238T, Wjz239F, Wjz2lDC, Wjz2mGO, Wjz2mTK, Wjz2nLE, Wjz3gMq, Wjz3gQn, Wjz3gK-, Wjz3hL_, Wjz3iFK, Wjz3kwU, Wjz3kyX, Wjz3kAx]  
Woden Bus Station (Platform 9)-City Bus Station (Platform 3): [Wjz3m3b, Wjz3m3b, Wjz3eRR, Wjz3eZ4, Wjz4KNu, Wjz4KO9, Wjz5Nht]  
short_name: 18 318  
stop_times: [[545a, 554a, 558a, 608a, 626a, 642a, 702a, 704a, 709a], [612a, 621a, 625a, 635a, 653a, 709a, 729a, 731a, 736a], [635a, 644a, 648a, 658a, 716a, 732a, 753a, 755a, 800a], [657a, 706a, 710a, 720a, 738a, 756a, 817a, 819a, 824a], [716a, 725a, 729a, 741a, 800a, 818a, 839a, 841a, 846a], [733a, 743a, 748a, 800a, 819a, 837a, 858a, 900a, 905a], ["-", "-", 750a, 758a, "-", "-", "-", "-", "-"], [753a, 803a, 808a, 820a, 839a, 857a, 918a, 920a, 925a], [813a, 823a, 828a, 840a, 859a, 917a, 938a, 940a, 945a], [838a, 848a, 853a, 905a, 924a, 941a, 1001a, 1003a, 1008a], [909a, 919a, 924a, 935a, 953a, 1009a, 1029a, 1031a, 1036a], [943a, 952a, 956a, 1006a, 1024a, 1040a, 1100a, 1102a, 1107a], [1013a, 1022a, 1026a, 1036a, 1054a, 1110a, 1130a, 1132a, 1137a], [1043a, 1052a, 1056a, 1106a, 1124a, 1140a, 1200p, 1202p, 1207p], [1113a, 1122a, 1126a, 1136a, 1154a, 1210p, 1230p, 1232p, 1237p], [1143a, 1152a, 1156a, 1206p, 1224p, 1240p, 100p, 102p, 107p], [1213p, 1222p, 1226p, 1236p, 1254p, 110p, 130p, 132p, 137p], [1243p, 1252p, 1256p, 106p, 124p, 140p, 200p, 202p, 207p], [113p, 122p, 126p, 136p, 154p, 210p, 230p, 232p, 237p], [143p, 152p, 156p, 206p, 224p, 240p, 300p, 302p, 307p], [212p, 221p, 225p, 235p, 253p, 310p, 331p, 333p, 338p], [241p, 250p, 254p, 305p, 324p, 342p, 403p, 405p, 410p], [308p, 318p, 323p, 335p, 354p, 412p, 433p, 435p, 440p], [333p, 343p, 348p, 400p, 419p, 437p, 458p, 500p, 505p], [402p, 412p, 417p, 429p, 448p, 506p, 527p, 529p, 534p], [439p, 449p, 454p, 506p, 525p, 543p, 604p, 606p, 611p], [515p, 525p, 530p, 540p, "-", "-", "-", "-", "-"], [545p, 555p, 600p, 610p, "-", "-", "-", "-", "-"], [617p, 627p, 632p, 642p, 659p, 714p, 734p, 736p, 741p], [713p, 722p, 726p, 734p, "-", "-", "-", "-", "-"], [814p, 823p, 827p, 835p, "-", "-", "-", "-", "-"], [914p, 923p, 927p, 935p, "-", "-", "-", "-", "-"], [1014p, 1023p, 1027p, 1035p, "-", "-", "-", "-", "-"], [1114p, 1123p, 1127p, 1135p, "-", "-", "-", "-", "-"], []]  
-  
time_points: [Fraser West Terminus, Fraser Shops, Charnwood Tillyard Dr, St Francis Xavier Florey, Cohen Street Bus Station (Platform 3), Westfield Bus Station (Platform 1), Belconnen Community Bus Station (Platform 1), City Bus Station (Platform 1), Woden Bus Station (Platform 6), Tuggeranong Bus Station]  
long_name: To Tuggeranong Bus Station  
between_stops:  
Cohen Street Bus Station (Platform 3)-Westfield Bus Station (Platform 1): []  
Westfield Bus Station (Platform 1)-Belconnen Community Bus Station (Platform 1): []  
Woden Bus Station (Platform 6)-Tuggeranong Bus Station: [Wjz3kAx, Wjz3kyX, Wjz3kwU, Wjz3iFK, Wjz3hL_, Wjz3gK-, Wjz3gQn, Wjz3gMq, Wjz2nLE, Wjz2mTK, Wjz2mGO, Wjz2lDC, Wjz239F, Wjz238T, Wjz213q]  
City Bus Station (Platform 1)-Woden Bus Station (Platform 6): [Wjz5Nht, Wjz4KO9, Wjz4KNu, Wjz3eZ4, Wjz3eRR, Wjz3m3b, Wjz3m3b]  
Belconnen Community Bus Station (Platform 1)-City Bus Station (Platform 1): [Wjz681S, Wjz689c, Wjz68W3, Wjz68W5, Wjz6gia, Wjz6giR, Wjz5nw6, Wjz5nwb, Wjz5mbS, Wjz5maK, Wjz5G6B, Wjz5G6U, Wjz5GNG, Wjz5GMT, Wjz5FSY, Wjz5F-1]  
short_name: 14 314  
stop_times: [[611a, 618a, 622a, 627a, 636a, 638a, 642a, "-", "-", "-"], [640a, 647a, 651a, 656a, 705a, 707a, 711a, 731a, 748a, 805a], [709a, 716a, 720a, 725a, 734a, 736a, 740a, 803a, 820a, 837a], [732a, 740a, 745a, 750a, 800a, 802a, 806a, 828a, 845a, 902a], [752a, 800a, 805a, 810a, 820a, 822a, 826a, 848a, 905a, 922a], [812a, 820a, 825a, 830a, 840a, 842a, 846a, 908a, 925a, 941a], [837a, 845a, 850a, 855a, 905a, 907a, 911a, 933a, 950a, 1005a], [908a, 916a, 921a, 926a, 935a, 937a, 941a, 1001a, 1018a, 1033a], [940a, 947a, 951a, 956a, 1005a, 1007a, 1011a, 1031a, 1048a, 1103a], [1010a, 1017a, 1021a, 1026a, 1035a, 1037a, 1041a, 1101a, 1118a, 1133a], [1040a, 1047a, 1051a, 1056a, 1105a, 1107a, 1111a, 1131a, 1148a, 1203p], [1110a, 1117a, 1121a, 1126a, 1135a, 1137a, 1141a, 1201p, 1218p, 1233p], [1140a, 1147a, 1151a, 1156a, 1205p, 1207p, 1211p, 1231p, 1248p, 103p], [1210p, 1217p, 1221p, 1226p, 1235p, 1237p, 1241p, 101p, 118p, 133p], [1240p, 1247p, 1251p, 1256p, 105p, 107p, 111p, 131p, 148p, 203p], [110p, 117p, 121p, 126p, 135p, 137p, 141p, 201p, 218p, 233p], [140p, 147p, 151p, 156p, 205p, 207p, 211p, 231p, 248p, 304p], [210p, 217p, 221p, 226p, 235p, 237p, 241p, 301p, 318p, 337p], [239p, 246p, 250p, 255p, 305p, 307p, 311p, 333p, 350p, 409p], [308p, 315p, 320p, 325p, 335p, 337p, 341p, 403p, 420p, 439p], [348p, 355p, 400p, 405p, 415p, 417p, 421p, 443p, 500p, 519p], [418p, 425p, 430p, 435p, 445p, 447p, 451p, 513p, 530p, 549p], [450p, 457p, 502p, 507p, 517p, 519p, 523p, "-", "-", "-"], [538p, 545p, 550p, 555p, 605p, 607p, 611p, 632p, 646p, 701p], [609p, 616p, 621p, 626p, 635p, 637p, 641p, 700p, 714p, 729p], [637p, 644p, 648p, 653p, 701p, 703p, 707p, "-", "-", "-"], [707p, 714p, 718p, 723p, 731p, 733p, 737p, "-", "-", "-"], [745p, 752p, 756p, 801p, 809p, 811p, 815p, "-", "-", "-"], [839p, 846p, 850p, 855p, 903p, 905p, 909p, "-", "-", "-"], [939p, 946p, 950p, 955p, 1003p, 1005p, 1009p, "-", "-", "-"], [1039p, 1046p, 1050p, 1055p, 1103p, 1105p, 1109p, "-", "-", "-"]]  
-  
time_points: [Woden Bus Station (Platform 4), Curtin Shops, John James Hospital, Yarralumla Shops, City Bus Station (Platform 8), Macarthur / Northbourne Ave, Southwell Park, Giralang Shops, Kaleen Village / Marybrynong, Gwydir Square Kaleen, University of Canberra, Belconnen Community Bus Station, Westfield Bus Station, Cohen Street Bus Station]  
long_name: To Cohen Street Bus Station  
between_stops:  
Westfield Bus Station-Cohen Street Bus Station: []  
Belconnen Community Bus Station-Westfield Bus Station: []  
University of Canberra-Belconnen Community Bus Station: [Wjz68Yy, Wjz68Y0, Wjz68IH, Wjz68Ip, Wjz689c, Wjz681S]  
short_name: "932"  
stop_times: [[839a, 850a, 853a, 856a, 909a, 915a, 919a, 928a, 936a, 941a, 947a, 950a, 952a, 957a], [939a, 950a, 953a, 956a, 1009a, 1015a, 1019a, 1028a, 1036a, 1041a, 1047a, 1050a, 1052a, 1057a], [1039a, 1050a, 1053a, 1056a, 1109a, 1115a, 1119a, 1128a, 1136a, 1141a, 1147a, 1150a, 1152a, 1157a], [1139a, 1150a, 1153a, 1156a, 1209p, 1215p, 1219p, 1228p, 1236p, 1241p, 1247p, 1250p, 1252p, 1257p], [1239p, 1250p, 1253p, 1256p, 109p, 115p, 119p, 128p, 136p, 141p, 147p, 150p, 152p, 157p], [139p, 150p, 153p, 156p, 209p, 215p, 219p, 228p, 236p, 241p, 247p, 250p, 252p, 257p], [239p, 250p, 253p, 256p, 309p, 315p, 319p, 328p, 336p, 341p, 347p, 350p, 352p, 357p], [339p, 350p, 353p, 356p, 409p, 415p, 419p, 428p, 436p, 441p, 447p, 450p, 452p, 457p], [439p, 450p, 453p, 456p, 509p, 515p, 519p, 528p, 536p, 541p, 547p, 550p, 552p, 557p], [539p, 550p, 553p, 556p, 609p, 615p, 619p, 628p, 635p, 640p, 645p, 648p, 650p, 655p], [639p, 648p, 651p, 654p, 707p, 712p, 716p, 725p, 732p, 737p, 742p, 745p, 747p, 752p]]  
-  
time_points: [Tuggeranong Bus Station (Platform 8), Erindale Centre, Woden Bus Station (Platform 9), City Bus Station (Platform 3), Belconnen Community Bus Station, Westfield Bus Station, Cohen Street Bus Station]  
long_name: To Cohen Street Bus Station  
between_stops:  
Westfield Bus Station-Cohen Street Bus Station: []  
Belconnen Community Bus Station-Westfield Bus Station: []  
stop_times_saturday: [[630a, 641a, 657a, 713a, 730a, 732a, 737a], [645a, 656a, 712a, 728a, 745a, 747a, 752a], [700a, 711a, 727a, 743a, 800a, 802a, 807a], [715a, 726a, 742a, 758a, 815a, 817a, 822a], [730a, 741a, 757a, 813a, 830a, 832a, 837a], [745a, 756a, 812a, 828a, 845a, 847a, 852a], [800a, 811a, 827a, 843a, 900a, 902a, 907a], [815a, 826a, 842a, 858a, 915a, 917a, 922a], [830a, 841a, 857a, 913a, 930a, 932a, 937a], [845a, 856a, 912a, 928a, 945a, 947a, 952a], [900a, 911a, 927a, 943a, 1000a, 1002a, 1007a], [915a, 926a, 942a, 958a, 1015a, 1017a, 1022a], [930a, 941a, 957a, 1013a, 1030a, 1032a, 1037a], [945a, 956a, 1012a, 1028a, 1045a, 1047a, 1052a], [1000a, 1011a, 1027a, 1043a, 1100a, 1102a, 1107a], [1015a, 1026a, 1042a, 1058a, 1115a, 1117a, 1122a], [1030a, 1041a, 1057a, 1113a, 1130a, 1132a, 1137a], [1045a, 1056a, 1112a, 1128a, 1145a, 1147a, 1152a], [1100a, 1111a, 1127a, 1143a, 1200p, 1202p, 1207p], [1115a, 1126a, 1142a, 1158a, 1215p, 1217p, 1222p], ["-", "-", 1149a, 1205p, 1222p, 1224p, 1229p], [1130a, 1141a, 1157a, 1213p, 1230p, 1232p, 1237p], [1145a, 1156a, 1212p, 1228p, 1245p, 1247p, 1252p], ["-", "-", 1219p, 1235p, 1252p, 1254p, 1259p], [1200p, 1211p, 1227p, 1243p, 100p, 102p, 107p], [1215p, 1226p, 1242p, 1258p, 115p, 117p, 122p], ["-", "-", 1249p, 105p, 122p, 124p, 129p], [1230p, 1241p, 1257p, 113p, 130p, 132p, 137p], [1245p, 1256p, 112p, 128p, 145p, 147p, 152p], ["-", "-", 119p, 135p, 152p, 154p, 159p], [100p, 111p, 127p, 143p, 200p, 202p, 207p], [115p, 126p, 142p, 158p, 215p, 217p, 222p], ["-", "-", 149p, 205p, 222p, 224p, 229p], [130p, 141p, 157p, 213p, 230p, 232p, 237p], [145p, 156p, 212p, 228p, 245p, 247p, 252p], ["-", "-", 219p, 235p, 252p, 254p, 259p], [200p, 211p, 227p, 243p, 300p, 302p, 307p], [215p, 226p, 242p, 258p, 315p, 317p, 322p], ["-", "-", 249p, 305p, 322p, 324p, 329p], [230p, 241p, 257p, 313p, 330p, 332p, 337p], [245p, 256p, 312p, 328p, 345p, 347p, 352p], ["-", "-", 319p, 335p, 352p, 354p, 359p], [300p, 311p, 327p, 343p, 400p, 402p, 407p], [315p, 326p, 342p, 358p, 415p, 417p, 422p], ["-", "-", 349p, 405p, 422p, 424p, 429p], [330p, 341p, 357p, 413p, 430p, 432p, 437p], [345p, 356p, 412p, 428p, 445p, 447p, 452p], ["-", "-", 419p, 435p, 452p, 454p, 459p], [400p, 411p, 427p, 443p, 500p, 502p, 507p], [415p, 426p, 442p, 458p, 515p, 517p, 522p], ["-", "-", 449p, 505p, 522p, 524p, 529p], [430p, 441p, 457p, 513p, 530p, 532p, 537p], [445p, 456p, 512p, 528p, 545p, 547p, 552p], [500p, 511p, 527p, 543p, 600p, 602p, 607p], [515p, 526p, 542p, 558p, 615p, 617p, 622p], [530p, 541p, 557p, 613p, 630p, 632p, 637p], [545p, 556p, 612p, 628p, 645p, 647p, 652p], [600p, 611p, 627p, 642p, 659p, 701p, 706p], [615p, 626p, 641p, 656p, 713p, 715p, 720p], [630p, 640p, 655p, 710p, 727p, 729p, 734p], [645p, 655p, 710p, 725p, 742p, 744p, 749p], [700p, 710p, 725p, 740p, 757p, 759p, 804p], [715p, 725p, 740p, 755p, 812p, 814p, 819p], [730p, 740p, 755p, 810p, 827p, 829p, 834p], [745p, 755p, 810p, 825p, 842p, 844p, 849p], [800p, 810p, 825p, 840p, 857p, 859p, 904p], [815p, 825p, 840p, 855p, 912p, 914p, 919p], [830p, 840p, 855p, 910p, 927p, 929p, 934p], [845p, 855p, 910p, 925p, 942p, 944p, 949p], [900p, 910p, 925p, 940p, 957p, 959p, 1004p], [915p, 925p, 940p, 955p, 1012p, 1014p, 1019p], [930p, 940p, 955p, 1010p, 1027p, 1029p, 1034p], [945p, 955p, 1010p, 1025p, 1042p, 1044p, 1049p], [1000p, 1010p, 1025p, 1040p, 1057p, 1059p, 1104p], [1015p, 1025p, 1040p, 1055p, 1112p, 1114p, 1119p], [1030p, 1040p, 1055p, 1110p, 1127p, 1129p, 1134p], [1045p, 1055p, 1110p, 1125p, 1142p, 1144p, 1149p], [1100p, 1110p, 1125p, 1140p, 1157p, 1159p, 1204a], [1115p, 1125p, 1140p, 1155p, 1212a, 1214a, 1219a]]  
short_name: "900"  
-  
time_points: [City Bus Station (Platform 9), National Zoo and Aquarium, Black Mountain Telstra Tower, Botanic Gardens, City Bus Station] time_points: [City Bus Station (Platform 9), National Zoo and Aquarium, Black Mountain Telstra Tower, Botanic Gardens, City Bus Station]
long_name: To City Bus Station long_name: To City Interchange
between_stops: {} between_stops: {}
   
stop_times_saturday: [[1020a, 1034a, 1042a, 1048a, 1055a], [1150a, 1204p, 1212p, 1218p, 1225p], [120p, 134p, 142p, 148p, 155p], [250p, 304p, 312p, 318p, 325p], [420p, 434p, 442p, 448p, 455p]] short_name: "81"
short_name: "981" stop_times: [[920a, 934a, 942a, 948a, 955a], [1020a, 1034a, 1042a, 1048a, 1055a], [1120a, 1134a, 1142a, 1148a, 1155a], [1220p, 1234p, 1242p, 1248p, 1255p], [120p, 134p, 142p, 148p, 155p], [220p, 234p, 242p, 248p, 255p], [320p, 334p, 342p, 348p, 355p], [420p, 434p, 442p, 448p, 455p]]
- -
time_points: [Tuggeranong Bus Station (Platform 5), Erindale Centre, Athllon / Sulwood Kambah, Woden Bus Station] time_points: [Cohen Street Bus Station (Platform 2), Westfield Bus Station (Platform 1), Belconnen Community Bus Station (Platform 2), Tuggeranong Bus Station (Platform 7), Centrelink Tuggeranong]
long_name: To Woden Bus Station long_name: To Centrelink Tuggeranong
between_stops: between_stops:
Athllon / Sulwood Kambah-Woden Bus Station: [Wjz2mTK, Wjz3gMq, Wjz3gQn, Wjz3gK-, Wjz3hL_, Wjz3iFK, Wjz3kwU, Wjz3kyX, Wjz3kAx]  
short_name: "964"  
stop_times: [[925a, 937a, 949a, 958a], [1025a, 1037a, 1049a, 1058a], [1125a, 1137a, 1149a, 1158a], [1225p, 1237p, 1249p, 1258p], [125p, 137p, 149p, 158p], [225p, 237p, 249p, 258p], [325p, 337p, 349p, 358p], [425p, 437p, 449p, 458p], [525p, 537p, 549p, 558p], [625p, 637p, 649p, 658p]]  
-  
time_points: [Belconnen Community Bus Station (Platform 4), Westfield Bus Station (Platform 2), Cohen Street Bus Station (Platform 6), Kippax, Fraser West Terminus, Kippax, Cohen Street Bus Station, Westfield Bus Station, Belconnen Community Bus Station]  
long_name: To Belconnen Community Bus Station  
between_stops:  
Belconnen Community Bus Station (Platform 4)-Westfield Bus Station (Platform 2): []  
Westfield Bus Station-Belconnen Community Bus Station: []  
Cohen Street Bus Station-Westfield Bus Station: []  
Westfield Bus Station (Platform 2)-Cohen Street Bus Station (Platform 6): []  
stop_times_saturday: [["-", "-", "-", "-", 734a, 748a, 802a, 808a, 810a], [759a, 801a, 805a, 819a, 834a, 848a, 902a, 908a, 910a], [859a, 901a, 905a, 919a, 934a, 948a, 1002a, 1008a, 1010a], [959a, 1001a, 1005a, 1019a, 1034a, 1048a, 1102a, 1108a, 1110a], [1059a, 1101a, 1105a, 1119a, 1134a, 1148a, 1202p, 1208p, 1210p], [1159a, 1201p, 1205p, 1219p, 1234p, 1248p, 102p, 108p, 110p], [1259p, 101p, 105p, 119p, 134p, 148p, 202p, 208p, 210p], [159p, 201p, 205p, 219p, 234p, 248p, 302p, 308p, 310p], [259p, 301p, 305p, 319p, 334p, 348p, 402p, 408p, 410p], [359p, 401p, 405p, 419p, 434p, 448p, 502p, 508p, 510p], [459p, 501p, 505p, 519p, 534p, 548p, 602p, 608p, 610p], [559p, 601p, 605p, 619p, 634p, 648p, 701p, 707p, 709p], [658p, 700p, 704p, 717p, 732p, 746p, 759p, 805p, 807p], [758p, 800p, 804p, 817p, 832p, 846p, 859p, 905p, 907p], [858p, 900p, 904p, 917p, 932p, 946p, 959p, 1005p, 1007p], [958p, 1000p, 1004p, 1017p, 1032p, 1046p, 1059p, 1105p, 1107p], [1058p, 1100p, 1104p, 1117p, 1132p, "-", "-", "-", "-"]]  
short_name: "903"  
-  
time_points: [Woden Bus Station (Platform 4), Alexander Maconochie Centre]  
long_name: To Alexander Maconochie Centre Hume  
between_stops: {}  
   
stop_times_saturday: [[920a, 940a], [1255p, 115p], [455p, 515p]]  
short_name: "988"  
-  
time_points: [Weston Creek Terminus, Chapman Shops, Canberra College Weston Campus, Cooleman Court, Woden Bus Station (Platform 10), Kings Ave / National Circuit, Russell Offices, ADFA, Campbell Park Offices]  
long_name: To Campbell Park Offices  
between_stops:  
Kings Ave / National Circuit-Russell Offices: [Wjz4Quk, Wjz4RwH, Wjz4RFJ]  
ADFA-Campbell Park Offices: [Wjzcend, Wjzce4H, Wjzce7O]  
Russell Offices-ADFA: [Wjzc60A, Wjzc60i, Wjzc55s, Wjzc54R, Wjzce7O, Wjzce4H, Wjzcend]  
short_name: 26 226  
stop_times: [[615a, 619a, 623a, 625a, 632a, "-", "-", "-", "-"], [657a, 701a, 705a, 707a, 715a, 729a, 733a, 737a, 741a], [716a, 720a, 724a, 726a, 736a, 750a, 754a, 758a, 802a], [747a, 752a, 758a, 802a, 815a, 829a, 833a, 837a, 841a], [800a, 805a, 811a, 815a, 827a, "-", "-", "-", "-"], [820a, 825a, 831a, 835a, 847a, "-", "-", "-", "-"], [850a, 855a, 901a, 905a, 917a, "-", "-", "-", "-"], [925a, 930a, 935a, 938a, 948a, "-", "-", "-", "-"], [1025a, 1029a, 1034a, 1037a, 1047a, "-", "-", "-", "-"], [1125a, 1129a, 1134a, 1137a, 1147a, "-", "-", "-", "-"], [1225p, 1229p, 1234p, 1237p, 1247p, "-", "-", "-", "-"], [125p, 129p, 134p, 137p, 147p, "-", "-", "-", "-"], [225p, 229p, 234p, 237p, 247p, "-", "-", "-", "-"], [255p, 259p, 305p, 308p, 317p, "-", "-", "-", "-"], [320p, 324p, 330p, 333p, 342p, "-", "-", "-", "-"], [420p, 424p, 430p, 433p, 442p, "-", "-", "-", "-"], [520p, 524p, 530p, 533p, 542p, "-", "-", "-", "-"], [620p, 624p, 630p, 632p, 639p, "-", "-", "-", "-"], [714p, 718p, 722p, 724p, 731p, "-", "-", "-", "-"], [814p, 818p, 822p, 824p, 831p, "-", "-", "-", "-"], [914p, 918p, 922p, 924p, 931p, "-", "-", "-", "-"], [1014p, 1018p, 1022p, 1024p, 1031p, "-", "-", "-", "-"]]  
-  
time_points: [Gungahlin Marketplace, Ngunnawal Primary, Nicholls Primary, Federation Square, Chuculba / William Slim Dr, Belconnen Community Bus Station, Westfield Bus Station, Cohen Street Bus Station]  
long_name: To Cohen Street Bus Station  
between_stops:  
Westfield Bus Station-Cohen Street Bus Station: []  
Belconnen Community Bus Station-Westfield Bus Station: []  
Chuculba / William Slim Dr-Belconnen Community Bus Station: [Wjz6mip, Wjz69vO, Wjz69uI, Wjz69ht, Wjz69gA]  
short_name: "951"  
stop_times_sunday: [[912a, 921a, 931a, 937a, 942a, 950a, 952a, 957a], [1012a, 1021a, 1031a, 1037a, 1042a, 1050a, 1052a, 1057a], [1112a, 1121a, 1131a, 1137a, 1142a, 1150a, 1152a, 1157a], [1212p, 1221p, 1231p, 1237p, 1242p, 1250p, 1252p, 1257p], [112p, 121p, 131p, 137p, 142p, 150p, 152p, 157p], [212p, 221p, 231p, 237p, 242p, 250p, 252p, 257p], [312p, 321p, 331p, 337p, 342p, 350p, 352p, 357p], [412p, 421p, 431p, 437p, 442p, 450p, 452p, 457p], [512p, 521p, 531p, 537p, 542p, 550p, 552p, 557p], [612p, 621p, 631p, 637p, 642p, 650p, 652p, 657p]]  
-  
time_points: [Cohen Street Bus Station (Platform 2), Westfield Bus Station (Platform 1), Belconnen Community Bus Station (Platform 3), University of Canberra, Gwydir Square Kaleen, Kaleen Village / Marybrynong, Giralang Shops, Southwell Park, Macarthur / Northbourne Ave, City Bus Station (Platform 9), Yarralumla Shops, John James Hospital, Curtin Shops, Woden Bus Station]  
long_name: To Woden Bus Station  
between_stops:  
Westfield Bus Station (Platform 1)-Belconnen Community Bus Station (Platform 3): []  
Cohen Street Bus Station (Platform 2)-Westfield Bus Station (Platform 1): []  
Belconnen Community Bus Station (Platform 3)-University of Canberra: [Wjz681S, Wjz689c, Wjz68Ip, Wjz68IH, Wjz68Y0, Wjz68Yy]  
short_name: "932"  
stop_times_sunday: [[746a, 748a, 752a, 757a, 803a, 808a, 810a, 825a, 830a, 838a, 850a, 853a, 857a, 908a], [846a, 848a, 852a, 857a, 903a, 908a, 910a, 925a, 930a, 938a, 950a, 953a, 957a, 1008a], [946a, 948a, 952a, 957a, 1003a, 1008a, 1010a, 1025a, 1030a, 1038a, 1050a, 1053a, 1057a, 1108a], [1046a, 1048a, 1052a, 1057a, 1103a, 1108a, 1110a, 1125a, 1130a, 1138a, 1150a, 1153a, 1157a, 1208p], [1146a, 1148a, 1152a, 1157a, 1203p, 1208p, 1210p, 1225p, 1230p, 1238p, 1250p, 1253p, 1257p, 108p], [1246p, 1248p, 1252p, 1257p, 103p, 108p, 110p, 125p, 130p, 138p, 150p, 153p, 157p, 208p], [146p, 148p, 152p, 157p, 203p, 208p, 210p, 225p, 230p, 238p, 250p, 253p, 257p, 308p], [246p, 248p, 252p, 257p, 303p, 308p, 310p, 325p, 330p, 338p, 350p, 353p, 357p, 408p], [346p, 348p, 352p, 357p, 403p, 408p, 410p, 425p, 430p, 438p, 450p, 453p, 457p, 508p], [446p, 448p, 452p, 457p, 503p, 508p, 510p, 525p, 530p, 538p, 550p, 553p, 557p, 608p], [546p, 548p, 552p, 557p, 603p, 608p, 610p, 625p, 630p, 637p, 649p, 652p, 655p, 705p]]  
-  
time_points: [Cohen Street Bus Station (Platform 1), Westfield Bus Station (Platform 1), Belconnen Community Bus Station (Platform 3), Copland College, Tillyard / Spalding, Charnwood Shops, Kerrigan / Lhotsky, Charnwood Shops, Tillyard / Spalding, Copland College, Belconnen Community Bus Station, Westfield Bus Station, Cohen Street Bus Station]  
long_name: To Cohen Street Bus Station  
between_stops:  
Westfield Bus Station-Cohen Street Bus Station: []  
Belconnen Community Bus Station-Westfield Bus Station: []  
Westfield Bus Station (Platform 1)-Belconnen Community Bus Station (Platform 3): []  
Cohen Street Bus Station (Platform 1)-Westfield Bus Station (Platform 1): []  
short_name: "45"  
stop_times: [["-", "-", "-", "-", "-", 627a, 632a, 638a, 640a, 648a, 658a, 700a, 705a], ["-", "-", "-", "-", "-", 657a, 702a, 708a, 710a, 718a, 728a, 730a, 735a], ["-", "-", "-", "-", "-", 729a, 734a, 740a, 742a, 750a, 800a, 802a, 807a], ["-", "-", "-", "-", "-", 759a, 804a, 810a, 812a, 820a, 830a, 832a, 837a], ["-", "-", "-", "-", "-", 822a, 827a, 833a, 835a, 843a, 853a, 855a, 900a], ["-", "-", "-", "-", "-", 844a, 849a, 855a, 857a, 905a, 915a, 917a, 922a], [828a, 830a, 834a, 842a, 850a, 852a, 857a, 903a, 905a, 913a, 923a, 925a, 930a], [858a, 900a, 904a, 912a, 920a, 922a, 927a, 933a, 935a, 943a, 953a, 955a, 1000a], [921a, 923a, 927a, 935a, 943a, 945a, 950a, 956a, 958a, 1006a, 1016a, 1018a, 1023a], [1021a, 1023a, 1027a, 1035a, 1043a, 1045a, 1050a, 1056a, 1058a, 1106a, 1116a, 1118a, 1123a], [1121a, 1123a, 1127a, 1135a, 1143a, 1145a, 1150a, 1156a, 1158a, 1206p, 1216p, 1218p, 1223p], [1221p, 1223p, 1227p, 1235p, 1243p, 1245p, 1250p, 1256p, 1258p, 106p, 116p, 118p, 123p], [121p, 123p, 127p, 135p, 143p, 145p, 150p, 156p, 158p, 206p, 216p, 218p, 223p], [221p, 223p, 227p, 235p, 243p, 245p, 250p, 256p, 258p, 306p, 316p, 318p, 323p], [258p, 300p, 304p, 312p, 320p, 322p, 327p, 333p, 335p, 343p, 353p, 355p, 400p], [328p, 330p, 334p, 342p, 350p, 352p, 357p, 403p, 405p, 413p, 423p, 425p, 430p], [358p, 400p, 404p, 412p, 420p, 422p, 427p, 433p, 435p, 443p, 453p, 455p, 500p], [428p, 430p, 434p, 442p, 450p, 452p, 457p, 503p, 505p, 513p, 523p, 525p, 530p], [458p, 500p, 504p, 512p, 520p, 522p, 527p, 533p, 535p, 543p, 553p, 555p, 600p], [528p, 530p, 534p, 542p, 550p, 552p, 557p, 603p, 605p, 613p, 623p, 625p, 630p], [558p, 600p, 604p, 612p, 620p, 622p, 627p, 633p, 635p, 643p, 652p, 654p, 659p], [621p, 623p, 627p, 634p, 642p, 644p, 649p, 655p, 657p, 705p, 714p, 716p, 721p], [720p, 722p, 726p, 733p, 741p, 743p, 748p, 754p, 756p, 804p, 813p, 815p, 820p], [820p, 822p, 826p, 833p, 841p, 843p, 848p, 854p, 856p, 904p, 913p, 915p, 920p], [920p, 922p, 926p, 933p, 941p, 943p, 948p, 954p, 956p, 1004p, 1013p, 1015p, 1020p], [1020p, 1022p, 1026p, 1033p, 1041p, 1043p, 1048p, 1054p, 1056p, 1104p, 1113p, 1115p, 1120p], [1120p, 1122p, 1126p, 1133p, 1141p, 1143p, 1148p, 1154p, "-", "-", "-", "-", "-"], []]  
-  
time_points: [City West, City Bus Station (Platform 1), Woden Bus Station (Platform 11), Athllon / Sulwood Kambah, Livingston Shops / Kambah, Taverner St / Erindale Dr, Tuggeranong Bus Station]  
long_name: To Tuggeranong Bus Station  
between_stops:  
Woden Bus Station (Platform 11)-Athllon / Sulwood Kambah: [Wjz3kAx, Wjz3kyX, Wjz3kwU, Wjz3iFK, Wjz3hL_, Wjz3gK-, Wjz3gQn, Wjz3gMq, Wjz2mTK]  
City Bus Station (Platform 1)-Woden Bus Station (Platform 11): [Wjz5Nht, Wjz4KO9, Wjz4KNu, Wjz3eZ4, Wjz3eRR, Wjz3m3b, Wjz3m3b]  
City West-City Bus Station (Platform 1): []  
short_name: 61 161  
stop_times: [["-", "-", 642a, 649a, 654a, 659a, 710a], ["-", "-", 712a, 719a, 724a, 729a, 743a], ["-", "-", 742a, 751a, 756a, 801a, 815a], ["-", "-", 812a, 821a, 826a, 831a, 845a], ["-", "-", 842a, 859a, 905a, 909a, 920a], ["-", "-", 912a, 921a, 926a, 931a, 944a], ["-", "-", 1012a, 1020a, 1025a, 1030a, 1043a], ["-", "-", 1112a, 1120a, 1125a, 1130a, 1143a], ["-", "-", 1212p, 1220p, 1225p, 1230p, 1243p], ["-", "-", 112p, 120p, 125p, 130p, 143p], ["-", "-", 212p, 220p, 225p, 230p, 243p], ["-", "-", 320p, 329p, 334p, 339p, 353p], ["-", "-", 342p, 351p, 356p, 401p, 415p], ["-", "-", 412p, 421p, 426p, 431p, 445p], ["-", "-", 442p, 451p, 456p, 501p, 515p], ["-", "-", 512p, 521p, 526p, 531p, 545p], [520p, 526p, 542p, 551p, 556p, 601p, 615p], ["-", "-", 612p, 621p, 626p, 631p, 644p], ["-", "-", 712p, 720p, 725p, 730p, 743p], ["-", "-", 810p, 818p, 823p, 828p, 841p], ["-", "-", 910p, 918p, 923p, 928p, 941p], ["-", "-", 1010p, 1018p, 1023p, 1028p, 1041p], ["-", "-", 1112p, 1120p, 1125p, 1130p, 1143p], []]  
-  
time_points: [Cohen Street Bus Station (Platform 2), Westfield Bus Station (Platform 1), Belconnen Community Bus Station (Platform 2), William Webb / Ginninderra Drive, Chuculba / William Slim Dr, Gungahlin Marketplace, Kosciuszko / Everard, Flemington Rd / Sandford St, Macarthur / Northbourne Ave, City Bus Station]  
long_name: To City Bus Station  
between_stops:  
Macarthur / Northbourne Ave-City Bus Station: [Wjz5QmR, Wjz5Qmu, Wjz5Qgn, Wjz5Qi2, Wjz5Pl0, Wjz5PdJ, Wjz5P8K, Wjz5P8n, Wjz5Oci, Wjz5O3Q]  
Westfield Bus Station (Platform 1)-Belconnen Community Bus Station (Platform 2): [] Westfield Bus Station (Platform 1)-Belconnen Community Bus Station (Platform 2): []
Cohen Street Bus Station (Platform 2)-Westfield Bus Station (Platform 1): [] Cohen Street Bus Station (Platform 2)-Westfield Bus Station (Platform 1): []
short_name: "956" short_name: "705"
stop_times_sunday: [[841a, 843a, 847a, 853a, 858a, 908a, 918a, 925a, 933a, 940a], [941a, 943a, 947a, 953a, 958a, 1008a, 1018a, 1025a, 1033a, 1040a], [1041a, 1043a, 1047a, 1053a, 1058a, 1108a, 1118a, 1125a, 1133a, 1140a], [1141a, 1143a, 1147a, 1153a, 1158a, 1208p, 1218p, 1225p, 1233p, 1240p], [1241p, 1243p, 1247p, 1253p, 1258p, 108p, 118p, 125p, 133p, 140p], [141p, 143p, 147p, 153p, 158p, 208p, 218p, 225p, 233p, 240p], [241p, 243p, 247p, 253p, 258p, 308p, 318p, 325p, 333p, 340p], [341p, 343p, 347p, 353p, 358p, 408p, 418p, 425p, 433p, 440p], [441p, 443p, 447p, 453p, 458p, 508p, 518p, 525p, 533p, 540p], [541p, 543p, 547p, 553p, 558p, 608p, 618p, 625p, 633p, 640p], [641p, 643p, 647p, 653p, 658p, 708p, 718p, 725p, 733p, 740p]] stop_times: [[715a, 717a, 721a, 800a, 802a], [741a, 743a, 747a, 826a, 828a], [807a, 809a, 813a, 852a, 854a], [439p, 441p, 445p, 523p, "-"], [505p, 507p, 511p, 549p, "-"], [532p, 534p, 538p, 616p, "-"]]
-  
time_points: [City Bus Station (Platform 7), Russell Offices, Brindabella Business Park, Fairbairn Park]  
long_name: To Fairbairn Park  
between_stops:  
Brindabella Business Park-Fairbairn Park: [WjzcrK3, WjzcrrQ, WjzcrEu, WjzcJ0K, WjzcBHZ, WjzcJ38]  
City Bus Station (Platform 7)-Russell Offices: [Wjz5Nht, Wjz5MsD, Wjz5MsT, Wjz5MEL, Wjz4T-X, Wjz4_jm, Wjz4_wS, Wjz4-KO, Wjz4-Rc, Wjz4-WL, Wjz4-WZ]  
short_name: "737"  
stop_times: [[643a, 651a, 705a, "-"], [658a, 706a, 720a, "-"], [718a, 726a, 740a, "-"], [738a, 746a, 800a, "-"], [758a, 806a, 820a, 830a], [818a, 826a, 840a, 850a]]  
-  
time_points: [Cohen Street Bus Station (Platform 2), Westfield Bus Station (Platform 1), Belconnen Community Bus Station (Platform 3), Jamison Centre, Cook Shops, Aranda, Caswell Drive, City Bus Station]  
long_name: To City Bus Station  
between_stops:  
Westfield Bus Station (Platform 1)-Belconnen Community Bus Station (Platform 3): []  
Cohen Street Bus Station (Platform 2)-Westfield Bus Station (Platform 1): []  
stop_times_saturday: [[815a, 817a, 821a, 830a, 839a, 843a, 844a, 855a], [915a, 917a, 921a, 930a, 939a, 943a, 944a, 955a], [1015a, 1017a, 1021a, 1030a, 1039a, 1043a, 1044a, 1055a], [1115a, 1117a, 1121a, 1130a, 1139a, 1143a, 1144a, 1155a], [1215p, 1217p, 1221p, 1230p, 1239p, 1243p, 1244p, 1255p], [115p, 117p, 121p, 130p, 139p, 143p, 144p, 155p], [215p, 217p, 221p, 230p, 239p, 243p, 244p, 255p], [315p, 317p, 321p, 330p, 339p, 343p, 344p, 355p], [415p, 417p, 421p, 430p, 439p, 443p, 444p, 455p], [515p, 517p, 521p, 530p, 539p, 543p, 544p, 555p], [615p, 617p, 621p, 630p, 639p, 643p, 644p, 655p], [715p, 717p, 721p, 730p, 739p, 743p, 744p, 755p], [815p, 817p, 821p, 830p, 839p, 843p, 844p, 855p], [915p, 917p, 921p, 930p, 939p, 943p, 944p, 955p], [1015p, 1017p, 1021p, 1030p, 1039p, 1043p, 1044p, 1055p]]  
short_name: "942"  
-  
time_points: [Cohen Street Bus Station (Platform 1), Westfield Bus Station (Platform 1), Belconnen Community Bus Station (Platform 3), University of Canberra, Australian Institute of Sport, National Hockey Centre Lyneham, Macarthur / Northbourne Ave, City Bus Station (Platform 9), Russell Offices, Railway Station Kingston, Newcastle Street after Isa Street, Fyshwick Direct Factory Outlet, Lithgow St Terminus Fyshwick]  
long_name: To Lithgow St Terminus Fyshwick  
between_stops:  
Westfield Bus Station (Platform 1)-Belconnen Community Bus Station (Platform 3): []  
Cohen Street Bus Station (Platform 1)-Westfield Bus Station (Platform 1): []  
Belconnen Community Bus Station (Platform 3)-University of Canberra: [Wjz681S, Wjz689c, Wjz68Ip, Wjz68IH, Wjz68Y0, Wjz68Yy]  
stop_times_saturday: [[720a, 722a, 726a, 734a, 740a, 745a, 751a, 759a, 808a, 814a, 822a, 831a, 840a], [820a, 822a, 826a, 834a, 840a, 845a, 851a, 859a, 908a, 914a, 922a, 931a, 940a], [920a, 922a, 926a, 934a, 940a, 945a, 951a, 959a, 1008a, 1014a, 1022a, 1031a, 1040a], [1020a, 1022a, 1026a, 1034a, 1040a, 1045a, 1051a, 1059a, 1108a, 1114a, 1122a, 1131a, 1140a], [1120a, 1122a, 1126a, 1134a, 1140a, 1145a, 1151a, 1159a, 1208p, 1214p, 1222p, 1231p, 1240p], [1220p, 1222p, 1226p, 1234p, 1240p, 1245p, 1251p, 1259p, 108p, 114p, 122p, 131p, 140p], [120p, 122p, 126p, 134p, 140p, 145p, 151p, 159p, 208p, 214p, 222p, 231p, 240p], [220p, 222p, 226p, 234p, 240p, 245p, 251p, 259p, 308p, 314p, 322p, 331p, 340p], [320p, 322p, 326p, 334p, 340p, 345p, 351p, 359p, 408p, 414p, 422p, 431p, 440p], ["-", "-", "-", "-", "-", "-", "-", 415p, 424p, 430p, "-", "-", "-"], [420p, 422p, 426p, 434p, 440p, 445p, 451p, 459p, 508p, 514p, 522p, 531p, 540p], [520p, 522p, 526p, 534p, 540p, 545p, 551p, 558p, "-", "-", "-", "-", "-"], [615p, 617p, 621p, 629p, 635p, 640p, 645p, 652p, "-", "-", "-", "-", "-"], [725p, 727p, 732p, 739p, 745p, 750p, 755p, 802p, "-", "-", "-", "-", "-"], [834p, 836p, 841p, 848p, 854p, 859p, 904p, 911p, "-", "-", "-", "-", "-"], [945p, 947p, 952p, 959p, 1005p, 1010p, 1015p, 1022p, "-", "-", "-", "-", "-"], [1057p, 1059p, 1104p, 1111p, 1117p, 1122p, 1127p, 1134p, "-", "-", "-", "-", "-"]]  
short_name: "980"  
-  
time_points: [Tuggeranong Bus Station (Platform 3), Kambah High, Mount Neighbour School, Woden Bus Station]  
long_name: To Woden Bus Station  
between_stops: {}  
   
short_name: "960"  
stop_times_sunday: [[755a, 805a, 811a, 823a], [855a, 905a, 911a, 923a], [955a, 1005a, 1011a, 1023a], [1055a, 1105a, 1111a, 1123a], [1155a, 1205p, 1211p, 1223p], [1255p, 105p, 111p, 123p], [155p, 205p, 211p, 223p], [255p, 305p, 311p, 323p], [355p, 405p, 411p, 423p], [455p, 505p, 511p, 523p], [555p, 605p, 611p, 623p], [655p, 705p, 711p, 721p]]  
-  
time_points: [City West, City Bus Station (Platform 10), Russell Offices, Mentone View / Tharwa Drive, Tharwa Dr / Pockett Ave, Woodcock / Clare Dennis]  
long_name: To Woodcock / Clare Dennis  
between_stops:  
City West-City Bus Station (Platform 10): []  
City Bus Station (Platform 10)-Russell Offices: [Wjz5Nht, Wjz5MsD, Wjz5MsT, Wjz5MEL, Wjz4T-X, Wjz4_jm, Wjz4_wS, Wjz4-KO, Wjz4-Rc, Wjz4-WL, Wjz4-WZ]  
short_name: "788"  
stop_times: [[426p, 432p, 441p, 512p, 526p, 536p], [502p, 507p, 518p, 552p, 606p, 615p], [532p, 538p, 547p, 618p, 632p, 642p]]  
-  
time_points: [City Bus Station (Platform 8), Ainslie Shops, Hackett Shops, Dickson Shops, North Lyneham, Lyneham Shops Wattle Street, Macarthur / Miller O'Connor, City Bus Station]  
long_name: To City Bus Station  
between_stops: {}  
   
stop_times_saturday: [[759a, 811a, 819a, 825a, 834a, 839a, 842a, 851a], [859a, 911a, 919a, 925a, 934a, 939a, 942a, 951a], [959a, 1011a, 1019a, 1025a, 1034a, 1039a, 1042a, 1051a], [1059a, 1111a, 1119a, 1125a, 1134a, 1139a, 1142a, 1151a], [1159a, 1211p, 1219p, 1225p, 1234p, 1239p, 1242p, 1251p], [1259p, 111p, 119p, 125p, 134p, 139p, 142p, 151p], [159p, 211p, 219p, 225p, 234p, 239p, 242p, 251p], [259p, 311p, 319p, 325p, 334p, 339p, 342p, 351p], [359p, 411p, 419p, 425p, 434p, 439p, 442p, 451p], [459p, 511p, 519p, 525p, 534p, 539p, 542p, 551p], [559p, 611p, 619p, 625p, 634p, 639p, 642p, 651p], [659p, 711p, 719p, 725p, 734p, 739p, 742p, 751p], [749p, 801p, 809p, 815p, 824p, 829p, 832p, 841p], [849p, 901p, 909p, 915p, 924p, 929p, 932p, 941p], [949p, 1001p, 1009p, 1015p, 1024p, 1029p, 1032p, 1041p], [1049p, 1101p, 1109p, 1115p, 1124p, 1129p, 1132p, 1141p]]  
short_name: "937"  
-  
time_points: [Woden Bus Station (Platform 11), Athllon / Sulwood Kambah, Erindale Centre, Tuggeranong Bus Station]  
long_name: To Tuggeranong Bus Station  
between_stops:  
Woden Bus Station (Platform 11)-Athllon / Sulwood Kambah: [Wjz3kAx, Wjz3kyX, Wjz3kwU, Wjz3iFK, Wjz3hL_, Wjz3gK-, Wjz3gQn, Wjz3gMq, Wjz2mTK]  
stop_times_saturday: [[905a, 914a, 926a, 937a], [1005a, 1014a, 1026a, 1037a], [1105a, 1114a, 1126a, 1137a], [1205p, 1214p, 1226p, 1237p], [105p, 114p, 126p, 137p], [205p, 214p, 226p, 237p], [305p, 314p, 326p, 337p], [405p, 414p, 426p, 437p], [505p, 514p, 526p, 537p], [605p, 614p, 626p, 637p], [705p, 714p, 726p, 737p], [805p, 814p, 826p, 837p], [905p, 914p, 926p, 937p], [1005p, 1014p, 1026p, 1037p], [1105p, 1114p, 1126p, 1137p]]  
short_name: "964"  
-  
time_points: [Cohen Street Bus Station (Platform 1), Westfield Bus Station (Platform 1), Belconnen Community Bus Station (Platform 2), Chuculba / William Slim Dr, Federation Square, Nicholls Primary, Gungahlin Marketplace]  
long_name: To Gungahlin Market Place  
between_stops:  
Belconnen Community Bus Station (Platform 2)-Chuculba / William Slim Dr: [Wjz69gA, Wjz69ht, Wjz69uI, Wjz69vO, Wjz6mip]  
Cohen Street Bus Station (Platform 1)-Westfield Bus Station (Platform 1): []  
Westfield Bus Station (Platform 1)-Belconnen Community Bus Station (Platform 2): []  
short_name: "952"  
stop_times_sunday: [[945a, 947a, 951a, 1004a, 1009a, 1022a, 1031a], [1045a, 1047a, 1051a, 1104a, 1109a, 1122a, 1131a], [1145a, 1147a, 1151a, 1204p, 1209p, 1222p, 1231p], [1245p, 1247p, 1251p, 104p, 109p, 122p, 131p], [145p, 147p, 151p, 204p, 209p, 222p, 231p], [245p, 247p, 251p, 304p, 309p, 322p, 331p], [345p, 347p, 351p, 404p, 409p, 422p, 431p], [445p, 447p, 451p, 504p, 509p, 522p, 531p], [545p, 547p, 551p, 604p, 609p, 622p, 631p], [645p, 647p, 651p, 704p, 709p, 722p, 731p]]  
-  
time_points: [Tuggeranong Bus Station (Platform 7), Bonython Primary School, Deamer / Clift Richardson, Proctor / Mead, Erindale Centre, Woden Bus Station]  
long_name: To Woden Bus Station  
between_stops: {}  
   
short_name: "66"  
stop_times: [[612a, 618a, 625a, 631a, 638a, 652a], [641a, 647a, 654a, 700a, 712a, 727a], [706a, 714a, 723a, 732a, 744a, 800a], [736a, 744a, 753a, 802a, 814a, 830a], [806a, 814a, 823a, 832a, 844a, 900a], [836a, 844a, 853a, 902a, 914a, 930a], [909a, 917a, 926a, 933a, 941a, 956a], [1012a, 1018a, 1026a, 1032a, 1040a, 1055a], [1112a, 1118a, 1126a, 1132a, 1140a, 1155a], [1212p, 1218p, 1226p, 1232p, 1240p, 1255p], [112p, 118p, 126p, 132p, 140p, 155p], [212p, 218p, 226p, 232p, 240p, 255p], [312p, 319p, 327p, 334p, 345p, 400p], [412p, 419p, 427p, 434p, 445p, 500p], [442p, 449p, 457p, 504p, 515p, 530p], [512p, 519p, 527p, 534p, 545p, 600p], [542p, 549p, 557p, 604p, 615p, 630p], [613p, 620p, 628p, 634p, 642p, 657p], [714p, 720p, 728p, 734p, 742p, 757p], [814p, 820p, 828p, 834p, 842p, 857p], [914p, 920p, 928p, 934p, 942p, 957p], [1014p, 1020p, 1028p, 1034p, 1042p, 1057p], [1114p, 1120p, 1128p, 1134p, 1142p, "-"]]  
-  
time_points: [City Bus Station (Platform 8), Macarthur / Northbourne Ave, Dickson Shops, Watson, Watson Terminus, Watson, Dickson Shops, Northbourne Avenue / Antill St, Macarthur / Northbourne Ave, City Bus Station]  
long_name: To City Bus Station  
between_stops:  
City Bus Station (Platform 8)-Macarthur / Northbourne Ave: [Wjz5O3Q, Wjz5Oci, Wjz5P8n, Wjz5P8K, Wjz5PdJ, Wjz5Pl0, Wjz5Qi2, Wjz5Qgn, Wjz5Qmu, Wjz5QmR]  
Macarthur / Northbourne Ave-City Bus Station: [Wjz5QmR, Wjz5Qmu, Wjz5Qgn, Wjz5Qi2, Wjz5Pl0, Wjz5PdJ, Wjz5P8K, Wjz5P8n, Wjz5Oci, Wjz5O3Q]  
short_name: "39"  
stop_times: [["-", "-", "-", 549a, 555a, 601a, 606a, 607a, 610a, 617a], [609a, 615a, 618a, 624a, 630a, 636a, 641a, 642a, 645a, 652a], [639a, 645a, 648a, 654a, 700a, 706a, 711a, 712a, 715a, 722a], ["-", "-", "-", 707a, 713a, 719a, 724a, 725a, 728a, 741a], [703a, 709a, 712a, 718a, 724a, 730a, 736a, 737a, 742a, 757a], ["-", "-", "-", 726a, 732a, 738a, 744a, 745a, 750a, 805a], [718a, 724a, 727a, 734a, 740a, 746a, 752a, 753a, 758a, 813a], ["-", "-", "-", 742a, 748a, 754a, 800a, 801a, 806a, 821a], [733a, 739a, 742a, 749a, 755a, 801a, 807a, 808a, 813a, 828a], ["-", "-", "-", 756a, 802a, 808a, 814a, 815a, 820a, 835a], [748a, 754a, 757a, 804a, 810a, 816a, 822a, 823a, 828a, 843a], [758a, 804a, 807a, 814a, 820a, 826a, 832a, 833a, 838a, 853a], ["-", "-", "-", 824a, 830a, 836a, 842a, 843a, 848a, 903a], [818a, 824a, 827a, 834a, 840a, 846a, 852a, 853a, 858a, 913a], [833a, 839a, 842a, 849a, 855a, 901a, 907a, 908a, 913a, 928a], [910a, 918a, 924a, 929a, 935a, 942a, 949a, 952a, 954a, 1001a], [940a, 946a, 949a, 954a, 1000a, 1005a, 1010a, 1011a, 1013a, 1019a], [1010a, 1016a, 1019a, 1024a, 1030a, 1035a, 1040a, 1041a, 1043a, 1049a], [1040a, 1046a, 1049a, 1054a, 1100a, 1105a, 1110a, 1111a, 1113a, 1119a], [1110a, 1116a, 1119a, 1124a, 1130a, 1135a, 1140a, 1141a, 1143a, 1149a], [1140a, 1146a, 1149a, 1154a, 1200p, 1205p, 1210p, 1211p, 1213p, 1219p], [1210p, 1216p, 1219p, 1224p, 1230p, 1235p, 1240p, 1241p, 1243p, 1249p], [1240p, 1246p, 1249p, 1254p, 100p, 105p, 110p, 111p, 113p, 119p], [110p, 116p, 119p, 124p, 130p, 135p, 140p, 141p, 143p, 149p], [140p, 146p, 149p, 154p, 200p, 205p, 210p, 211p, 213p, 219p], [210p, 216p, 219p, 224p, 230p, 235p, 240p, 241p, 243p, 249p], [240p, 246p, 249p, 254p, 300p, 307p, 313p, 314p, 317p, 324p], [309p, 315p, 318p, 324p, 330p, 337p, 343p, 344p, 347p, 354p], [328p, 334p, 337p, 343p, 349p, 356p, 402p, 403p, 406p, 413p], [358p, 404p, 407p, 413p, 419p, 426p, 432p, 433p, 436p, 443p], [417p, 423p, 426p, 432p, 438p, 445p, 451p, 452p, 455p, 502p], [432p, 438p, 441p, 447p, 453p, 500p, 506p, 507p, 510p, 517p], [447p, 453p, 456p, 502p, 508p, 515p, 521p, 522p, 525p, 532p], [506p, 512p, 515p, 521p, 527p, 534p, 540p, 541p, 544p, 551p], [512p, 518p, 521p, 527p, 533p, 540p, "-", "-", "-", "-"], [521p, 527p, 530p, 536p, 542p, 549p, 555p, 556p, 559p, 606p], [536p, 542p, 545p, 551p, 557p, 604p, 610p, 611p, 614p, 621p], [546p, 552p, 555p, 601p, 607p, 614p, "-", "-", "-", "-"], [555p, 601p, 604p, 610p, 616p, 623p, 629p, 630p, 632p, 638p], [610p, 616p, 619p, 625p, 631p, 636p, 641p, 642p, 644p, 650p], [710p, 716p, 719p, 724p, 730p, 735p, 740p, 741p, 743p, 749p], [810p, 816p, 819p, 824p, 830p, 835p, 840p, 841p, 843p, 849p], [910p, 916p, 919p, 924p, 930p, 935p, 940p, 941p, 943p, 949p], [1010p, 1016p, 1019p, 1024p, 1030p, 1035p, 1040p, 1041p, 1043p, 1049p], [1110p, 1116p, 1119p, 1124p, 1130p, 1135p, "-", "-", "-", "-"]]  
-  
time_points: [Tuggeranong Bus Station (Platform 8), Woden Bus Station (Platform 9), City Bus Station (Platform 3), Belconnen Community Bus Station (Platform 4), Westfield Bus Station (Platform 2), Cohen Street Bus Station (Platform 6), Page Shops, Scullin Shops, Charnwood Shops, Fraser West Terminus]  
long_name: To Fraser West Terminus  
between_stops:  
Belconnen Community Bus Station (Platform 4)-Westfield Bus Station (Platform 2): []  
Tuggeranong Bus Station (Platform 8)-Woden Bus Station (Platform 9): [Wjz213q, Wjz238T, Wjz239F, Wjz2lDC, Wjz2mGO, Wjz2mTK, Wjz2nLE, Wjz3gMq, Wjz3gQn, Wjz3gK-, Wjz3hL_, Wjz3iFK, Wjz3kwU, Wjz3kyX, Wjz3kAx]  
Westfield Bus Station (Platform 2)-Cohen Street Bus Station (Platform 6): []  
City Bus Station (Platform 3)-Belconnen Community Bus Station (Platform 4): [Wjz5F-1, Wjz5FSY, Wjz5GMT, Wjz5GNG, Wjz5G6U, Wjz5G6B, Wjz5maK, Wjz5mbS, Wjz5nwb, Wjz5nw6, Wjz6giR, Wjz6gia, Wjz68W3, Wjz68W5, Wjz689c, Wjz681S]  
Woden Bus Station (Platform 9)-City Bus Station (Platform 3): [Wjz3m3b, Wjz3m3b, Wjz3eRR, Wjz3eZ4, Wjz4KNu, Wjz4KO9, Wjz5Nht]  
short_name: 13 313  
stop_times: [["-", "-", "-", 733a, 735a, 739a, 742a, 746a, 755a, 803a], [710a, 728a, 746a, 807a, 809a, 813a, 816a, 820a, 829a, 837a], [751a, 810a, 828a, 849a, 851a, 855a, 858a, 902a, 911a, 919a], [811a, 830a, 848a, 909a, 911a, 915a, 918a, 922a, 931a, 938a], [850a, 909a, 927a, 947a, 949a, 953a, 955a, 959a, 1007a, 1014a], [921a, 940a, 956a, 1016a, 1018a, 1022a, 1024a, 1028a, 1036a, 1043a], [951a, 1009a, 1025a, 1045a, 1047a, 1051a, 1053a, 1057a, 1105a, 1112a], [1021a, 1039a, 1055a, 1115a, 1117a, 1121a, 1123a, 1127a, 1135a, 1142a], [1051a, 1109a, 1125a, 1145a, 1147a, 1151a, 1153a, 1157a, 1205p, 1212p], [1121a, 1139a, 1155a, 1215p, 1217p, 1221p, 1223p, 1227p, 1235p, 1242p], [1151a, 1209p, 1225p, 1245p, 1247p, 1251p, 1253p, 1257p, 105p, 112p], [1221p, 1239p, 1255p, 115p, 117p, 121p, 123p, 127p, 135p, 142p], [1251p, 109p, 125p, 145p, 147p, 151p, 153p, 157p, 205p, 212p], [121p, 139p, 155p, 215p, 217p, 221p, 223p, 227p, 235p, 242p], [151p, 209p, 225p, 245p, 247p, 251p, 253p, 257p, 306p, 313p], [221p, 239p, 255p, 316p, 318p, 322p, 325p, 330p, 340p, 347p], [250p, 308p, 326p, 347p, 349p, 353p, 356p, 401p, 411p, 418p], [316p, 335p, 353p, 414p, 416p, 420p, 423p, 428p, 438p, 445p], [346p, 405p, 423p, 444p, 446p, 450p, 453p, 458p, 508p, 515p], [406p, 425p, 443p, 504p, 506p, 510p, 513p, 518p, 528p, 535p], [426p, 445p, 503p, 524p, 526p, 530p, 533p, 538p, 548p, 555p], [446p, 505p, 523p, 544p, 546p, 550p, 553p, 558p, 608p, 615p], [526p, 545p, 603p, 624p, 626p, 630p, 632p, 636p, 644p, 651p], [556p, 615p, 632p, 652p, 654p, 658p, 700p, 704p, 712p, 719p], [656p, 713p, 728p, 748p, 750p, 754p, 756p, 800p, 808p, 815p], ["-", "-", "-", 835p, 837p, 841p, 843p, 847p, 855p, 902p], ["-", "-", "-", 935p, 937p, 941p, 943p, 947p, 955p, 1002p], ["-", "-", "-", 1034p, 1036p, 1040p, 1042p, 1046p, 1054p, 1101p]]  
-  
time_points: [Woden Bus Station (Platform 2), Canberra Hospital, Saint Andrews Village Hughes, Brindabella Gardens Nursing Home, Woden Bus Station]  
long_name: To Woden Bus Station  
between_stops: {}  
   
short_name: "77"  
stop_times: [[1100a, 1108a, 1113a, 1121a, 1128a], [100p, 108p, 113p, 121p, 128p]]  
-  
time_points: [City Bus Station (Platform 8), Macarthur / Northbourne Ave, Northbourne Avenue / Antill St, Flemington Rd / Sandford St, Hibberson / Kate Crace, Gungahlin Marketplace, Ngunnawal Primary, Nicholls Primary, Federation Square, Chuculba / William Slim Dr, Belconnen Community Bus Station, Westfield Bus Station, Cohen Street Bus Station]  
long_name: To Cohen Street Bus Station  
between_stops:  
Westfield Bus Station-Cohen Street Bus Station: []  
Macarthur / Northbourne Ave-Northbourne Avenue / Antill St: [Wjz5RkN, Wjz5Rsi, Wjz5RvC, Wjz5Sqk, Wjz5SrO, Wjz5Sux, Wjz5SDc]  
City Bus Station (Platform 8)-Macarthur / Northbourne Ave: [Wjz5O3Q, Wjz5Oci, Wjz5P8n, Wjz5P8K, Wjz5PdJ, Wjz5Pl0, Wjz5Qi2, Wjz5Qgn, Wjz5Qmu, Wjz5QmR]  
Belconnen Community Bus Station-Westfield Bus Station: []  
Chuculba / William Slim Dr-Belconnen Community Bus Station: [Wjz6mip, Wjz69vO, Wjz69uI, Wjz69ht, Wjz69gA]  
short_name: "51"  
stop_times: [["-", "-", "-", "-", 701a, 704a, 713a, 723a, 733a, 738a, 750a, 752a, 757a], ["-", "-", "-", "-", 721a, 724a, 733a, 743a, 753a, 758a, 811a, 813a, 818a], ["-", "-", "-", "-", 741a, 744a, 753a, 803a, 813a, 818a, 831a, 833a, 838a], ["-", "-", "-", "-", 800a, 803a, 812a, 822a, 832a, 837a, 850a, 852a, 857a], ["-", "-", "-", "-", 821a, 824a, 833a, 843a, 853a, 858a, 908a, 910a, 915a], ["-", "-", "-", "-", 840a, 843a, 852a, 902a, 911a, 916a, 925a, 927a, 932a], ["-", "-", "-", "-", 940a, 943a, 952a, 1001a, 1010a, 1015a, 1024a, 1026a, 1031a], ["-", "-", "-", "-", 1040a, 1043a, 1052a, 1101a, 1110a, 1115a, 1124a, 1126a, 1131a], ["-", "-", "-", "-", 1140a, 1143a, 1152a, 1201p, 1210p, 1215p, 1224p, 1226p, 1231p], ["-", "-", "-", "-", 1240p, 1243p, 1252p, 101p, 110p, 115p, 124p, 126p, 131p], ["-", "-", "-", "-", 140p, 143p, 152p, 201p, 210p, 215p, 224p, 226p, 231p], ["-", "-", "-", "-", 240p, 243p, 252p, 301p, 310p, 315p, 324p, 326p, 331p], ["-", "-", "-", "-", 307p, 310p, 319p, 328p, 337p, 342p, 351p, 353p, 358p], [332p, 338p, 340p, 348p, 351p, 354p, 403p, 413p, 423p, 428p, 438p, 440p, 445p], [406p, 412p, 414p, 423p, 428p, 431p, 440p, 450p, 500p, 505p, 515p, 517p, 522p], [428p, 434p, 436p, 445p, 450p, 453p, 502p, 512p, 522p, 527p, 537p, 539p, 544p], [444p, 450p, 452p, 501p, 506p, 509p, 518p, 528p, 538p, 543p, 553p, 555p, 600p], [511p, 517p, 519p, 528p, 533p, 536p, 545p, 555p, 605p, 610p, 619p, 621p, 626p], [529p, 535p, 537p, 546p, 551p, 554p, 603p, 612p, 621p, 626p, 635p, 637p, 642p], [538p, 544p, 546p, 555p, 600p, 603p, 612p, 621p, 630p, 635p, 644p, 646p, 651p], [554p, 600p, 602p, 609p, 612p, 615p, 624p, 633p, 642p, 647p, 656p, 658p, 703p], [616p, 620p, 622p, 629p, 632p, 635p, 644p, 653p, 702p, 707p, 716p, 718p, 723p], ["-", "-", "-", "-", 740p, 743p, 752p, 801p, 810p, 815p, 824p, 826p, 831p], ["-", "-", "-", "-", 840p, 843p, 852p, 901p, 910p, 915p, 924p, 926p, 931p], ["-", "-", "-", "-", 940p, 943p, 952p, 1001p, 1010p, 1015p, 1024p, 1026p, 1031p], ["-", "-", "-", "-", 1040p, 1043p, 1052p, 1101p, 1110p, 1115p, 1124p, 1126p, 1131p], ["-", "-", "-", "-", 1140p, 1143p, 1152p, 1201a, 1210a, 1215a, 1224a, 1226a, 1231a]]  
- -
time_points: [Tuggeranong Bus Station (Platform 8), Erindale Centre, Woden Bus Station (Platform 9), City Bus Station (Platform 3), Belconnen Community Bus Station, Westfield Bus Station, Cohen Street Bus Station] time_points: [Tuggeranong Bus Station (Platform 8), Erindale Centre, Woden Bus Station (Platform 9), City Bus Station (Platform 3), Belconnen Community Bus Station, Westfield Bus Station, Cohen Street Bus Station]
long_name: To Cohen Street Bus Station long_name: To Cohen Street Bus Station
between_stops: between_stops:
Westfield Bus Station-Cohen Street Bus Station: [] Westfield Bus Station-Cohen Street Bus Station: []
Belconnen Community Bus Station-Westfield Bus Station: [] Belconnen Community Bus Station-Westfield Bus Station: []
short_name: "900" short_name: "900"
stop_times: [[730a, 741a, 757a, 813a, 830a, 832a, 837a], [745a, 756a, 812a, 828a, 845a, 847a, 852a], [800a, 811a, 827a, 843a, 900a, 902a, 907a], [815a, 826a, 842a, 858a, 915a, 917a, 922a], [830a, 841a, 857a, 913a, 930a, 932a, 937a], [845a, 856a, 912a, 928a, 945a, 947a, 952a], [900a, 911a, 927a, 943a, 1000a, 1002a, 1007a], [915a, 926a, 942a, 958a, 1015a, 1017a, 1022a], [930a, 941a, 957a, 1013a, 1030a, 1032a, 1037a], [945a, 956a, 1012a, 1028a, 1045a, 1047a, 1052a], [1000a, 1011a, 1027a, 1043a, 1100a, 1102a, 1107a], [1015a, 1026a, 1042a, 1058a, 1115a, 1117a, 1122a], [1030a, 1041a, 1057a, 1113a, 1130a, 1132a, 1137a], [1045a, 1056a, 1112a, 1128a, 1145a, 1147a, 1152a], [1100a, 1111a, 1127a, 1143a, 1200p, 1202p, 1207p], [1115a, 1126a, 1142a, 1158a, 1215p, 1217p, 1222p], [1130a, 1141a, 1157a, 1213p, 1230p, 1232p, 1237p], [1145a, 1156a, 1212p, 1228p, 1245p, 1247p, 1252p], [1200p, 1211p, 1227p, 1243p, 100p, 102p, 107p], [1215p, 1226p, 1242p, 1258p, 115p, 117p, 122p], [1230p, 1241p, 1257p, 113p, 130p, 132p, 137p], [1245p, 1256p, 112p, 128p, 145p, 147p, 152p], [100p, 111p, 127p, 143p, 200p, 202p, 207p], [115p, 126p, 142p, 158p, 215p, 217p, 222p], [130p, 141p, 157p, 213p, 230p, 232p, 237p], [145p, 156p, 212p, 228p, 245p, 247p, 252p], [200p, 211p, 227p, 243p, 300p, 302p, 307p], [215p, 226p, 242p, 258p, 315p, 317p, 322p], [230p, 241p, 257p, 313p, 330p, 332p, 337p], [245p, 256p, 312p, 328p, 345p, 347p, 352p], [300p, 311p, 327p, 343p, 400p, 402p, 407p], [315p, 326p, 342p, 358p, 415p, 417p, 422p], [330p, 341p, 357p, 413p, 430p, 432p, 437p], [345p, 356p, 412p, 428p, 445p, 447p, 452p], [400p, 411p, 427p, 443p, 500p, 502p, 507p], [415p, 426p, 442p, 458p, 515p, 517p, 522p], [430p, 441p, 457p, 513p, 530p, 532p, 537p], [445p, 456p, 512p, 528p, 545p, 547p, 552p], [500p, 511p, 527p, 543p, 600p, 602p, 607p], [515p, 526p, 542p, 558p, 615p, 617p, 622p], [530p, 541p, 557p, 613p, 630p, 632p, 637p], [545p, 556p, 612p, 628p, 645p, 647p, 652p], [600p, 611p, 627p, 642p, 659p, 701p, 706p], [615p, 626p, 641p, 656p, 713p, 715p, 720p], [630p, 640p, 655p, 710p, 727p, 729p, 734p], [645p, 655p, 710p, 725p, 742p, 744p, 749p], [700p, 710p, 725p, 740p, 757p, 759p, 804p], [715p, 725p, 740p, 755p, 812p, 814p, 819p]] stop_times_sunday: [[730a, 741a, 757a, 813a, 830a, 832a, 837a], [745a, 756a, 812a, 828a, 845a, 847a, 852a], [800a, 811a, 827a, 843a, 900a, 902a, 907a], [815a, 826a, 842a, 858a, 915a, 917a, 922a], [830a, 841a, 857a, 913a, 930a, 932a, 937a], [845a, 856a, 912a, 928a, 945a, 947a, 952a], [900a, 911a, 927a, 943a, 1000a, 1002a, 1007a], [915a, 926a, 942a, 958a, 1015a, 1017a, 1022a], [930a, 941a, 957a, 1013a, 1030a, 1032a, 1037a], [945a, 956a, 1012a, 1028a, 1045a, 1047a, 1052a], [1000a, 1011a, 1027a, 1043a, 1100a, 1102a, 1107a], [1015a, 1026a, 1042a, 1058a, 1115a, 1117a, 1122a], [1030a, 1041a, 1057a, 1113a, 1130a, 1132a, 1137a], [1045a, 1056a, 1112a, 1128a, 1145a, 1147a, 1152a], [1100a, 1111a, 1127a, 1143a, 1200p, 1202p, 1207p], [1115a, 1126a, 1142a, 1158a, 1215p, 1217p, 1222p], [1130a, 1141a, 1157a, 1213p, 1230p, 1232p, 1237p], [1145a, 1156a, 1212p, 1228p, 1245p, 1247p, 1252p], [1200p, 1211p, 1227p, 1243p, 100p, 102p, 107p], [1215p, 1226p, 1242p, 1258p, 115p, 117p, 122p], [1230p, 1241p, 1257p, 113p, 130p, 132p, 137p], [1245p, 1256p, 112p, 128p, 145p, 147p, 152p], [100p, 111p, 127p, 143p, 200p, 202p, 207p], [115p, 126p, 142p, 158p, 215p, 217p, 222p], [130p, 141p, 157p, 213p, 230p, 232p, 237p], [145p, 156p, 212p, 228p, 245p, 247p, 252p], [200p, 211p, 227p, 243p, 300p, 302p, 307p], [215p, 226p, 242p, 258p, 315p, 317p, 322p], [230p, 241p, 257p, 313p, 330p, 332p, 337p], [245p, 256p, 312p, 328p, 345p, 347p, 352p], [300p, 311p, 327p, 343p, 400p, 402p, 407p], [315p, 326p, 342p, 358p, 415p, 417p, 422p], [330p, 341p, 357p, 413p, 430p, 432p, 437p], [345p, 356p, 412p, 428p, 445p, 447p, 452p], [400p, 411p, 427p, 443p, 500p, 502p, 507p], [415p, 426p, 442p, 458p, 515p, 517p, 522p], [430p, 441p, 457p, 513p, 530p, 532p, 537p], [445p, 456p, 512p, 528p, 545p, 547p, 552p], [500p, 511p, 527p, 543p, 600p, 602p, 607p], [515p, 526p, 542p, 558p, 615p, 617p, 622p], [530p, 541p, 557p, 613p, 630p, 632p, 637p], [545p, 556p, 612p, 628p, 645p, 647p, 652p], [600p, 611p, 627p, 642p, 659p, 701p, 706p], [615p, 626p, 641p, 656p, 713p, 715p, 720p], [630p, 640p, 655p, 710p, 727p, 729p, 734p], [645p, 655p, 710p, 725p, 742p, 744p, 749p], [700p, 710p, 725p, 740p, 757p, 759p, 804p], [715p, 725p, 740p, 755p, 812p, 814p, 819p]]
- -
time_points: [Tuggeranong Bus Station (Platform 7), Erindale Centre, Gowrie Shops, Chisholm Shops, Gowrie Shops, Erindale Centre, Tuggeranong Bus Station] time_points: [City Bus Station (Platform 9), National Zoo and Aquarium, Black Mountain Telstra Tower, Botanic Gardens, City Bus Station]
long_name: To Tuggeranong Bus Station long_name: To City Bus Station
between_stops: {} between_stops: {}
   
short_name: "966" short_name: "981"
stop_times: [[908a, 920a, 927a, 936a, 948a, 957a, 1010a], [1008a, 1020a, 1027a, 1036a, 1048a, 1057a, 1110a], [1108a, 1120a, 1127a, 1136a, 1148a, 1157a, 1210p], [1208p, 1220p, 1227p, 1236p, 1248p, 1257p, 110p], [108p, 120p, 127p, 136p, 148p, 157p, 210p], [208p, 220p, 227p, 236p, 248p, 257p, 310p], [308p, 320p, 327p, 336p, 348p, 357p, 410p], [408p, 420p, 427p, 436p, 448p, 457p, 510p], [508p, 520p, 527p, 536p, 548p, 557p, 610p], [608p, 620p, 627p, 636p, 648p, 657p, 710p], [705p, 717p, 724p, 733p, 745p, 754p, 807p]] stop_times_sunday: [[1020a, 1034a, 1042a, 1048a, 1055a], [1150a, 1204p, 1212p, 1218p, 1225p], [120p, 134p, 142p, 148p, 155p], [250p, 304p, 312p, 318p, 325p], [420p, 434p, 442p, 448p, 455p]]
-  
time_points: [City Bus Station (Platform 8), ADFA, Hospice / Menindee Dr, St Thomas More's Campbell, City Bus Station]  
long_name: To City Bus Station  
between_stops: {}  
   
short_name: "931"  
stop_times_sunday: [[901a, 915a, 922a, 929a, 941a], [1101a, 1115a, 1122a, 1129a, 1141a], [101p, 115p, 122p, 129p, 141p], [301p, 315p, 322p, 329p, 341p], [501p, 515p, 522p, 529p, 541p], [701p, 715p, 722p, 729p, 741p]]  
-  
time_points: [City Bus Station (Platform 8), Macarthur / Northbourne Ave, North Lyneham, Gwydir Square Kaleen, University of Canberra, Belconnen Community Bus Station, Westfield Bus Station, Cohen Street Bus Station]  
long_name: To Cohen Street Bus Station  
between_stops:  
Westfield Bus Station-Cohen Street Bus Station: []  
City Bus Station (Platform 8)-Macarthur / Northbourne Ave: [Wjz5O3Q, Wjz5Oci, Wjz5P8n, Wjz5P8K, Wjz5PdJ, Wjz5Pl0, Wjz5Qi2, Wjz5Qgn, Wjz5Qmu, Wjz5QmR]  
Belconnen Community Bus Station-Westfield Bus Station: []  
University of Canberra-Belconnen Community Bus Station: [Wjz681S, Wjz689c]  
short_name: "31"  
stop_times: [["-", "-", 637a, 643a, 648a, 654a, 656a, 701a], ["-", "-", 707a, 713a, 718a, 724a, 726a, 731a], [733a, 740a, 745a, 753a, 800a, 806a, 808a, 813a], [803a, 810a, 815a, 823a, 830a, 836a, 838a, 843a], [829a, 836a, 841a, 849a, 856a, 902a, 904a, 909a], [910a, 917a, 922a, 930a, 936a, 942a, 944a, 949a], [948a, 954a, 959a, 1005a, 1011a, 1017a, 1019a, 1024a], [1048a, 1054a, 1059a, 1105a, 1111a, 1117a, 1119a, 1124a], [1148a, 1154a, 1159a, 1205p, 1211p, 1217p, 1219p, 1224p], [1248p, 1254p, 1259p, 105p, 111p, 117p, 119p, 124p], [148p, 154p, 159p, 205p, 211p, 217p, 219p, 224p], [248p, 254p, 259p, 307p, 315p, 321p, 323p, 328p], [303p, 310p, 315p, 323p, 331p, 337p, 339p, 344p], [333p, 340p, 345p, 353p, 401p, 407p, 409p, 414p], [403p, 410p, 415p, 423p, 431p, 437p, 439p, 444p], [433p, 440p, 445p, 453p, 501p, 507p, 509p, 514p], [503p, 510p, 515p, 523p, 531p, 537p, 539p, 544p], [533p, 540p, 545p, 553p, 601p, 607p, 609p, 614p], [603p, 610p, 615p, 623p, 631p, 637p, 639p, 644p], [648p, 654p, 659p, 705p, 710p, 716p, 718p, 723p], [748p, 754p, 759p, 805p, 810p, 816p, 818p, 823p], [848p, 854p, 859p, 905p, 910p, 916p, 918p, 923p], [948p, 954p, 959p, 1005p, 1010p, 1016p, 1018p, 1023p], [1048p, 1054p, 1059p, 1105p, 1110p, 1116p, 1118p, 1123p]]  
-  
time_points: [Belconnen Community Bus Station (Platform 4), Westfield Bus Station (Platform 2), Cohen Street Bus Station (Platform 6), Kippax, Fraser West Terminus, Kippax, Cohen Street Bus Station, Westfield Bus Station, Belconnen Community Bus Station]  
long_name: To Belconnen Community Bus Station  
between_stops:  
Belconnen Community Bus Station (Platform 4)-Westfield Bus Station (Platform 2): []  
Westfield Bus Station-Belconnen Community Bus Station: []  
Cohen Street Bus Station-Westfield Bus Station: []  
Westfield Bus Station (Platform 2)-Cohen Street Bus Station (Platform 6): []  
short_name: "903"  
stop_times: [[859a, 901a, 905a, 919a, 934a, 948a, 1002a, 1004a, 1008a], [959a, 1001a, 1005a, 1019a, 1034a, 1048a, 1102a, 1104a, 1108a], [1059a, 1101a, 1105a, 1119a, 1134a, 1148a, 1202p, 1204p, 1208p], [1159a, 1201p, 1205p, 1219p, 1234p, 1248p, 102p, 104p, 108p], [1259p, 101p, 105p, 119p, 134p, 148p, 202p, 204p, 208p], [159p, 201p, 205p, 219p, 234p, 248p, 302p, 304p, 308p], [259p, 301p, 305p, 319p, 334p, 348p, 402p, 404p, 408p], [359p, 401p, 405p, 419p, 434p, 448p, 502p, 504p, 508p], [459p, 501p, 505p, 519p, 534p, 548p, 602p, 604p, 608p], [559p, 601p, 605p, 619p, 634p, 648p, 701p, 703p, 707p]]  
-  
time_points: [Woden Bus Station (Platform 15), Pearce Shops, Isaacs Shops, Farrer Primary School, Southlands Mawson, Woden Bus Station]  
long_name: To Woden Bus Station  
between_stops: {}  
   
short_name: "923"  
stop_times_sunday: [[910a, 916a, 921a, 927a, 933a, 943a], [1110a, 1116a, 1121a, 1127a, 1133a, 1143a], [110p, 116p, 121p, 127p, 133p, 143p], [310p, 316p, 321p, 327p, 333p, 343p], [510p, 516p, 521p, 527p, 533p, 543p]]  
-  
time_points: [Woden Bus Station (Platform 15), Lyons Shops, Chifley Shops, Torrens Shops, Southlands Mawson, Pearce Shops, Woden Bus Station]  
long_name: To Woden Bus Station  
between_stops: {}  
   
stop_times_saturday: [[933a, 936a, 940a, 945a, 951a, 955a, 1001a], [1133a, 1136a, 1140a, 1145a, 1151a, 1155a, 1201p], [133p, 136p, 140p, 145p, 151p, 155p, 201p], [333p, 336p, 340p, 345p, 351p, 355p, 401p], [533p, 536p, 540p, 545p, 551p, 555p, 601p], [733p, 736p, 740p, 745p, 751p, 755p, 801p], [933p, 936p, 940p, 945p, 951p, 955p, 1001p], [1133p, 1136p, 1140p, 1145p, 1151p, 1155p, "-"]]  
short_name: "921"  
-  
time_points: [City Bus Station (Platform 4), Caswell Drive, Aranda, Cook Shops, Jamison Centre, Belconnen Community Bus Station, Westfield Bus Station, Cohen Street Bus Station]  
long_name: To Cohen Street Bus Station  
between_stops:  
Westfield Bus Station-Cohen Street Bus Station: []  
Belconnen Community Bus Station-Westfield Bus Station: []  
short_name: "942"  
stop_times: [[914a, 923a, 924a, 927a, 936a, 945a, 947a, 952a], [1014a, 1023a, 1024a, 1027a, 1036a, 1045a, 1047a, 1052a], [1114a, 1123a, 1124a, 1127a, 1136a, 1145a, 1147a, 1152a], [1214p, 1223p, 1224p, 1227p, 1236p, 1245p, 1247p, 1252p], [114p, 123p, 124p, 127p, 136p, 145p, 147p, 152p], [214p, 223p, 224p, 227p, 236p, 245p, 247p, 252p], [314p, 323p, 324p, 327p, 336p, 345p, 347p, 352p], [414p, 423p, 424p, 427p, 436p, 445p, 447p, 452p], [514p, 523p, 524p, 527p, 536p, 545p, 547p, 552p], [614p, 623p, 624p, 627p, 636p, 645p, 647p, 652p]]  
-  
time_points: [Woden Bus Station (Platform 5), Mount Neighbour School, Kambah High, Tuggeranong Bus Station]  
long_name: To Tuggeranong Bus Station  
between_stops: {}  
   
stop_times_saturday: [[850a, 902a, 908a, 918a], [950a, 1002a, 1008a, 1018a], [1050a, 1102a, 1108a, 1118a], [1150a, 1202p, 1208p, 1218p], [1250p, 102p, 108p, 118p], [150p, 202p, 208p, 218p], [250p, 302p, 308p, 318p], [350p, 402p, 408p, 418p], [450p, 502p, 508p, 518p], [550p, 602p, 608p, 618p], [650p, 702p, 708p, 717p], [750p, 800p, 806p, 815p], [850p, 900p, 906p, 915p], [950p, 1000p, 1006p, 1015p], [1050p, 1100p, 1106p, 1115p]]  
short_name: "960"  
-  
time_points: [Woden Bus Station (Platform 15), Southlands Mawson, Torrens Shops, Pearce, Woden Bus Station]  
long_name: To Woden Bus Station  
between_stops: {}  
   
short_name: "22"  
stop_times: [[635a, 648a, 656a, 659a, 707a], [705a, 718a, 726a, 729a, 738a], [735a, 749a, 758a, 801a, 810a], [805a, 819a, 828a, 831a, 840a], [843a, 857a, 906a, 909a, 918a], [943a, 956a, 1004a, 1007a, 1015a], [1043a, 1056a, 1104a, 1107a, 1115a], [1143a, 1156a, 1204p, 1207p, 1215p], [1243p, 1256p, 104p, 107p, 115p], [143p, 156p, 204p, 207p, 215p], [243p, 256p, 305p, 308p, 317p], [313p, 327p, 336p, 339p, 348p], [335p, 349p, 358p, 401p, 410p], [405p, 419p, 428p, 431p, 440p], [435p, 449p, 458p, 501p, 510p], [505p, 519p, 528p, 531p, 540p], [535p, 549p, 558p, 601p, 610p], [605p, 619p, 628p, 631p, 639p], [638p, 651p, 659p, 702p, 710p], [738p, 751p, 759p, 802p, 810p], [838p, 851p, 859p, 902p, 910p], [938p, 951p, 959p, 1002p, 1010p], [1038p, 1051p, 1059p, 1102p, 1110p]]  
-  
time_points: [Lithgow St Terminus Fyshwick, Fyshwick Direct Factory Outlet, Canberra Times, Railway Station Kingston, Russell Offices, City Bus Station (Platform 8), Macarthur / Northbourne Ave, National Hockey Centre Lyneham, Australian Institute of Sport, University of Canberra, Belconnen Community Bus Station, Westfield Bus Station, Cohen Street Bus Station]  
long_name: To Cohen Street Bus Station  
between_stops:  
Westfield Bus Station-Cohen Street Bus Station: []  
Belconnen Community Bus Station-Westfield Bus Station: []  
University of Canberra-Belconnen Community Bus Station: [Wjz68Yy, Wjz68Y0, Wjz68IH, Wjz68Ip, Wjz689c, Wjz681S]  
stop_times_saturday: [["-", "-", "-", "-", "-", 809a, 815a, 820a, 824a, 830a, 837a, 839a, 844a], [845a, 853a, 904a, 911a, 917a, 928a, 934a, 939a, 943a, 949a, 956a, 958a, 1003a], [945a, 953a, 1004a, 1011a, 1017a, 1028a, 1034a, 1039a, 1043a, 1049a, 1056a, 1058a, 1103a], [1045a, 1053a, 1104a, 1111a, 1117a, 1128a, 1134a, 1139a, 1143a, 1149a, 1156a, 1158a, 1203p], ["-", "-", "-", 1130a, 1136a, 1146a, "-", "-", "-", "-", "-", "-", "-"], [1145a, 1153a, 1204p, 1211p, 1217p, 1228p, 1234p, 1239p, 1243p, 1249p, 1256p, 1258p, 103p], [1245p, 1253p, 104p, 111p, 117p, 128p, 134p, 139p, 143p, 149p, 156p, 158p, 203p], [145p, 153p, 204p, 211p, 217p, 228p, 234p, 239p, 243p, 249p, 256p, 258p, 303p], [245p, 253p, 304p, 311p, 317p, 328p, 334p, 339p, 343p, 349p, 356p, 358p, 403p], [345p, 353p, 404p, 411p, 417p, 428p, 434p, 439p, 443p, 449p, 456p, 458p, 503p], ["-", "-", "-", 440p, 446p, 456p, "-", "-", "-", "-", "-", "-", "-"], [445p, 453p, 504p, 511p, 517p, 528p, 534p, 539p, 543p, 549p, 556p, 558p, 603p], [545p, 553p, 604p, 611p, 617p, 628p, 634p, 639p, 643p, 649p, 656p, 658p, 703p], ["-", "-", "-", "-", "-", 657p, 703p, 708p, 712p, 718p, 725p, 727p, 732p], ["-", "-", "-", "-", "-", 807p, 813p, 818p, 822p, 828p, 835p, 837p, 842p], ["-", "-", "-", "-", "-", 917p, 923p, 928p, 932p, 938p, 945p, 947p, 952p], ["-", "-", "-", "-", "-", 1028p, 1034p, 1039p, 1043p, 1049p, 1056p, 1058p, 1103p], ["-", "-", "-", "-", "-", 1140p, 1146p, 1151p, 1155p, 1201a, 1208a, 1210a, 1215a]]  
short_name: "980"  
-  
time_points: [Cohen Street Bus Station (Platform 2), Westfield Bus Station (Platform 1), Belconnen Community Bus Station (Platform 3), Jamison Centre, Cook Shops, Aranda, Caswell Drive, City Bus Station]  
long_name: To City Bus Station  
between_stops:  
Westfield Bus Station (Platform 1)-Belconnen Community Bus Station (Platform 3): []  
Cohen Street Bus Station (Platform 2)-Westfield Bus Station (Platform 1): []  
short_name: "942"  
stop_times_sunday: [[815a, 817a, 821a, 830a, 839a, 843a, 844a, 855a], [915a, 917a, 921a, 930a, 939a, 943a, 944a, 955a], [1015a, 1017a, 1021a, 1030a, 1039a, 1043a, 1044a, 1055a], [1115a, 1117a, 1121a, 1130a, 1139a, 1143a, 1144a, 1155a], [1215p, 1217p, 1221p, 1230p, 1239p, 1243p, 1244p, 1255p], [115p, 117p, 121p, 130p, 139p, 143p, 144p, 155p], [215p, 217p, 221p, 230p, 239p, 243p, 244p, 255p], [315p, 317p, 321p, 330p, 339p, 343p, 344p, 355p], [415p, 417p, 421p, 430p, 439p, 443p, 444p, 455p], [515p, 517p, 521p, 530p, 539p, 543p, 544p, 555p], [615p, 617p, 621p, 630p, 639p, 643p, 644p, 655p]]  
-  
time_points: [City West, City Bus Station (Platform 1), Woden Bus Station (Platform 12), Erindale / Sternberg Cres, Gowrie, Erindale Dr / Charleston St Monash]  
long_name: To Erindale Dr / Charleston St Monash  
between_stops:  
City Bus Station (Platform 1)-Woden Bus Station (Platform 12): [Wjz5Nht, Wjz4KO9, Wjz4KNu, Wjz3eZ4, Wjz3eRR, Wjz3m3b, Wjz3m3b]  
City West-City Bus Station (Platform 1): []  
short_name: "170"  
stop_times: [[500p, 505p, 521p, 536p, 546p, 556p]]  
-  
time_points: [Tuggeranong Bus Station (Platform 7), Bonython Primary School, Lanyon Market Place, Conder Primary, Tharwa Dr / Pockett Ave, Gordon Primary, Woodcock / Clare Dennis, Bonython Primary School, Tuggeranong Bus Station]  
long_name: To Tuggeranong Bus Station  
between_stops: {}  
   
stop_times_saturday: [[625a, 634a, 640a, 647a, 650a, 654a, 659a, 702a, 712a], [825a, 834a, 840a, 847a, 850a, 854a, 859a, 902a, 912a], [1025a, 1034a, 1040a, 1047a, 1050a, 1054a, 1059a, 1102a, 1112a], [1225p, 1234p, 1240p, 1247p, 1250p, 1254p, 1259p, 102p, 112p], [225p, 234p, 240p, 247p, 250p, 254p, 259p, 302p, 312p], [425p, 434p, 440p, 447p, 450p, 454p, 459p, 502p, 512p], [625p, 634p, 640p, 647p, 650p, 654p, 659p, 702p, 712p], [828p, 837p, 843p, 850p, 853p, 857p, 902p, 905p, 915p], [1028p, 1037p, 1043p, 1050p, 1053p, 1057p, 1102p, 1105p, 1115p]]  
short_name: "914"  
-  
time_points: [City Bus Station (Platform 4), Macarthur / Miller O'Connor, Lyneham Shops Wattle Street, Dickson]  
long_name: To Dickson  
between_stops: {}  
   
short_name: "8"  
stop_times: [[655a, 702a, 707a, 713a], [714a, 721a, 726a, 732a], [741a, 750a, 757a, 804a], [811a, 820a, 827a, 834a], [841a, 850a, 857a, 904a], [915a, 924a, 931a, 937a], [946a, 953a, 958a, 1004a], [1018a, 1025a, 1030a, 1036a], [1046a, 1053a, 1058a, 1104a], [1146a, 1153a, 1158a, 1204p], [1246p, 1253p, 1258p, 104p], [146p, 153p, 158p, 204p], [246p, 253p, 258p, 305p], [311p, 320p, 327p, 334p], [346p, 355p, 402p, 409p], [411p, 420p, 427p, 434p], [444p, 453p, 500p, 507p], [523p, 532p, 539p, 546p], [553p, 602p, 609p, 616p], [623p, 631p, 636p, 642p], [650p, 655p, 700p, 706p], [705p, 710p, 715p, 721p], [805p, 810p, 815p, 821p], [905p, 910p, 915p, 921p], [1005p, 1010p, 1015p, 1021p], [1105p, 1110p, 1115p, 1121p]]  
-  
time_points: [Tuggeranong Bus Station (Platform 5), Monash Primary, MacKillop College Wanniassa Campus, Athllon / Sulwood Kambah, Woden Bus Station]  
long_name: To Woden Bus Station  
between_stops:  
Athllon / Sulwood Kambah-Woden Bus Station: [Wjz2mTK, Wjz3gMq, Wjz3gQn, Wjz3gK-, Wjz3hL_, Wjz3iFK, Wjz3kwU, Wjz3kyX, Wjz3kAx]  
short_name: "64"  
stop_times: [[605a, 612a, 616a, 623a, 631a], [635a, 642a, 646a, 653a, 701a], [705a, 712a, 716a, 723a, 731a], [735a, 744a, 749a, 756a, 806a], [805a, 814a, 819a, 826a, 836a], [825a, 834a, 839a, 846a, 856a], [905a, 914a, 919a, 926a, 935a], [935a, 943a, 947a, 954a, 1003a], [1035a, 1043a, 1047a, 1054a, 1103a], [1135a, 1143a, 1147a, 1154a, 1203p], [1235p, 1243p, 1247p, 1254p, 103p], [135p, 143p, 147p, 154p, 203p], [235p, 243p, 247p, 254p, 303p], [305p, 314p, 319p, 326p, 336p], [335p, 344p, 349p, 356p, 406p], [435p, 444p, 449p, 456p, 506p], [505p, 514p, 519p, 526p, 536p], [535p, 544p, 549p, 556p, 606p], [636p, 644p, 648p, 655p, 704p], [739p, 747p, 751p, 758p, 807p], [839p, 847p, 851p, 858p, 907p], [939p, 947p, 951p, 958p, 1007p], [1039p, 1047p, 1051p, 1058p, "-"], [], []]  
-  
time_points: [City Bus Station (Platform 9), Russell Offices, Kings Ave / National Circuit, Kingston, Narrabundah College, Pearce Shops, Woden Bus Station]  
long_name: To Woden Bus Station  
between_stops: {}  
   
short_name: "938"  
stop_times: [[846a, 854a, 858a, 902a, 917a, 927a, 934a], [946a, 954a, 958a, 1002a, 1017a, 1027a, 1034a], [1046a, 1054a, 1058a, 1102a, 1117a, 1127a, 1134a], [1146a, 1154a, 1158a, 1202p, 1217p, 1227p, 1234p], [1246p, 1254p, 1258p, 102p, 117p, 127p, 134p], [146p, 154p, 158p, 202p, 217p, 227p, 234p], [246p, 254p, 258p, 302p, 317p, 327p, 334p], [346p, 354p, 358p, 402p, 417p, 427p, 434p], [446p, 454p, 458p, 502p, 517p, 527p, 534p], [546p, 554p, 558p, 602p, 617p, 627p, 634p], [646p, 654p, 658p, 702p, 715p, 724p, 731p]]  
-  
time_points: [Campbell Park Offices, ADFA, Russell Offices, Kings Ave / National Circuit, Woden Bus Station (Platform 3), Waramanga Shops, Fisher Shops, Rivett Shops, Cooleman Court]  
long_name: To Cooleman Court  
between_stops:  
Russell Offices-Kings Ave / National Circuit: [Wjz4RFJ, Wjz4RwH, Wjz4Quk]  
ADFA-Russell Offices: [Wjzcend, Wjzce4H, Wjzce7O, Wjzc54R, Wjzc55s, Wjzc60i, Wjzc60A]  
Campbell Park Offices-ADFA: [Wjzce7O, Wjzce4H, Wjzcend]  
short_name: 27 227  
stop_times: [["-", "-", "-", "-", 821a, 829a, 833a, 840a, 845a], ["-", "-", "-", "-", 854a, 902a, 906a, 913a, 918a], ["-", "-", "-", "-", 954a, 1001a, 1005a, 1013a, 1019a], ["-", "-", "-", "-", 1054a, 1101a, 1105a, 1113a, 1119a], ["-", "-", "-", "-", 1154a, 1201p, 1205p, 1213p, 1219p], ["-", "-", "-", "-", 1254p, 101p, 105p, 113p, 119p], ["-", "-", "-", "-", 154p, 201p, 205p, 213p, 219p], ["-", "-", "-", "-", 254p, 302p, 307p, 314p, 322p], ["-", "-", "-", "-", 321p, 333p, 338p, 345p, 353p], ["-", "-", "-", "-", 351p, 403p, 408p, 415p, 423p], ["-", "-", "-", "-", 421p, 433p, 438p, 445p, 453p], [427p, 431p, 435p, 438p, 453p, 505p, 510p, 517p, 525p], ["-", "-", "-", "-", 521p, 533p, 538p, 545p, 553p], [527p, 531p, 535p, 538p, 553p, 605p, 610p, 617p, 625p], ["-", "-", "-", "-", 635p, 641p, 644p, 650p, 655p], ["-", "-", "-", "-", 735p, 741p, 744p, 750p, 755p], ["-", "-", "-", "-", 835p, 841p, 844p, 850p, 855p], ["-", "-", "-", "-", 935p, 941p, 944p, 950p, 955p], ["-", "-", "-", "-", 1035p, 1041p, 1044p, 1050p, 1055p]]  
-  
time_points: [Belconnen Community Bus Station (Platform 5), Westfield Bus Station (Platform 2), Cohen Street Bus Station (Platform 5), Kippax, Macgregor Shops, Charnwood Shops, Macgregor Shops, Kippax, Cohen Street Bus Station, Westfield Bus Station, Belconnen Community Bus Station]  
long_name: To Belconnen Community Bus Station  
between_stops:  
Belconnen Community Bus Station (Platform 5)-Westfield Bus Station (Platform 2): []  
Westfield Bus Station-Belconnen Community Bus Station: []  
Cohen Street Bus Station-Westfield Bus Station: []  
Westfield Bus Station (Platform 2)-Cohen Street Bus Station (Platform 5): []  
short_name: "43"  
stop_times: [["-", "-", "-", "-", 621a, 629a, 638a, 643a, 648a, 650a, 654a], ["-", "-", "-", "-", 640a, 648a, 657a, 702a, 707a, 709a, 713a], [644a, 646a, 650a, 655a, 700a, 708a, 717a, 722a, 727a, 729a, 733a], ["-", "-", "-", "-", 720a, 728a, 739a, 744a, 752a, 754a, 758a], ["-", "-", "-", "-", 741a, 749a, 800a, 805a, 813a, 815a, 819a], ["-", "-", "-", "-", 802a, 810a, 821a, 826a, 834a, 836a, 840a], ["-", "-", "-", "-", 824a, 832a, 843a, 848a, 856a, 858a, 902a], [823a, 825a, 829a, 837a, 842a, 850a, 901a, 906a, 914a, 916a, 920a], [843a, 845a, 849a, 857a, 902a, 910a, 921a, 926a, 933a, 935a, 939a], [903a, 905a, 909a, 917a, 922a, 930a, 939a, 944a, 952a, 954a, 958a], [1003a, 1005a, 1009a, 1015a, 1020a, 1028a, 1037a, 1042a, 1048a, 1050a, 1054a], [1103a, 1105a, 1109a, 1115a, 1120a, 1128a, 1137a, 1142a, 1148a, 1150a, 1154a], [1203p, 1205p, 1209p, 1215p, 1220p, 1228p, 1237p, 1242p, 1248p, 1250p, 1254p], [103p, 105p, 109p, 115p, 120p, 128p, 137p, 142p, 148p, 150p, 154p], [203p, 205p, 209p, 215p, 220p, 228p, 237p, 242p, 248p, 250p, 254p], [254p, 256p, 300p, 308p, 313p, 321p, 332p, 337p, 345p, 347p, 351p], [323p, 325p, 329p, 337p, 342p, 350p, 401p, 406p, 414p, 416p, 420p], [343p, 345p, 349p, 357p, 402p, 410p, 421p, 426p, 434p, 436p, 440p], [403p, 405p, 409p, 417p, 422p, 430p, 441p, 446p, 454p, 456p, 500p], [423p, 425p, 429p, 437p, 442p, 450p, 501p, 506p, 514p, 516p, 520p], [443p, 445p, 449p, 457p, 502p, 510p, 521p, 526p, 534p, 536p, 540p], [503p, 505p, 509p, 517p, 522p, 530p, 541p, 546p, 554p, 556p, 600p], [523p, 525p, 529p, 537p, 542p, 550p, 601p, 606p, 614p, 616p, 620p], [602p, 604p, 608p, 616p, 621p, 629p, 638p, 643p, 648p, 650p, 654p], [702p, 704p, 708p, 713p, 718p, 726p, 735p, 740p, 745p, 747p, 751p], [802p, 804p, 808p, 813p, 818p, 826p, 835p, 840p, 845p, 847p, 851p], [902p, 904p, 908p, 913p, 918p, 926p, 935p, 940p, 945p, 947p, 951p], [1002p, 1004p, 1008p, 1013p, 1018p, 1026p, 1035p, 1040p, 1045p, 1047p, 1051p], [1102p, 1104p, 1108p, 1113p, 1118p, 1126p, 1135p, "-", "-", "-", "-"], []]  
-  
time_points: [Fairbairn Park, Brindabella Business Park, Russell Offices, City Bus Station]  
long_name: To City Bus Station  
between_stops:  
Fairbairn Park-Brindabella Business Park: [WjzcJ38, WjzcBHZ, WjzcJ0K, WjzcrEu, WjzcrrQ, WjzcrK3]  
Russell Offices-City Bus Station: [Wjz4-WZ, Wjz4-WL, Wjz4-Rc, Wjz4-KO, Wjz4_wS, Wjz4_jm, Wjz4T-X, Wjz5MEL, Wjz5MsT, Wjz5MsD, Wjz5Nht]  
short_name: "737"  
stop_times: [[431p, 441p, 455p, 513p], [445p, 455p, 509p, 527p], [505p, 515p, 529p, 547p], [525p, 535p, 549p, 607p], [545p, 555p, 609p, 627p]]  
-  
time_points: [City Bus Station (Platform 8), Macarthur / Northbourne Ave, Flemington Rd / Sandford St, Flemington Rd / Nullabor Ave, Anthony Rolfe Av / Moonlight Av, Gungahlin Marketplace, Shoalhaven / Katherine Ave, Ngunnawal Primary, Chuculba / William Slim Dr, Belconnen Community Bus Station, Westfield Bus Station, Cohen Street Bus Station]  
long_name: To Cohen Street Bus Station  
between_stops:  
Westfield Bus Station-Cohen Street Bus Station: []  
City Bus Station (Platform 8)-Macarthur / Northbourne Ave: [Wjz5O3Q, Wjz5Oci, Wjz5P8n, Wjz5P8K, Wjz5PdJ, Wjz5Pl0, Wjz5Qi2, Wjz5Qgn, Wjz5Qmu, Wjz5QmR]  
Belconnen Community Bus Station-Westfield Bus Station: []  
Chuculba / William Slim Dr-Belconnen Community Bus Station: [Wjz6mip, Wjz69vO, Wjz69uI, Wjz69ht, Wjz69gA]  
stop_times_saturday: [["-", "-", "-", 723a, 730a, 739a, 747a, 755a, 806a, 816a, 818a, 823a], [800a, 806a, 814a, 821a, 828a, 837a, 845a, 853a, 904a, 914a, 916a, 921a], [900a, 906a, 914a, 921a, 928a, 937a, 945a, 953a, 1004a, 1014a, 1016a, 1021a], [1000a, 1006a, 1014a, 1021a, 1028a, 1037a, 1045a, 1053a, 1104a, 1114a, 1116a, 1121a], [1100a, 1106a, 1114a, 1121a, 1128a, 1137a, 1145a, 1153a, 1204p, 1214p, 1216p, 1221p], [1200p, 1206p, 1214p, 1221p, 1228p, 1237p, 1245p, 1253p, 104p, 114p, 116p, 121p], [100p, 106p, 114p, 121p, 128p, 137p, 145p, 153p, 204p, 214p, 216p, 221p], [200p, 206p, 214p, 221p, 228p, 237p, 245p, 253p, 304p, 314p, 316p, 321p], [300p, 306p, 314p, 321p, 328p, 337p, 345p, 353p, 404p, 414p, 416p, 421p], [400p, 406p, 414p, 421p, 428p, 437p, 445p, 453p, 504p, 514p, 516p, 521p], [500p, 506p, 514p, 521p, 528p, 537p, 545p, 553p, 604p, 614p, 616p, 621p], [600p, 606p, 614p, 621p, 628p, 637p, 645p, 653p, 704p, 714p, 716p, 721p], [700p, 706p, 714p, 721p, 728p, 737p, 745p, 753p, 804p, 814p, 816p, 821p], [800p, 806p, 814p, 821p, 828p, 837p, 845p, 853p, 904p, 914p, 916p, 921p], [900p, 906p, 914p, 921p, 928p, 937p, 945p, 953p, 1004p, 1014p, 1016p, 1021p], [1000p, 1006p, 1014p, 1021p, 1028p, 1037p, 1045p, 1053p, 1104p, 1114p, 1116p, 1121p], [1100p, 1106p, 1114p, 1121p, 1128p, 1137p, "-", "-", "-", "-", "-", "-"]]  
short_name: "958"  
-  
time_points: [Woden Bus Station (Platform 11), Athllon / Sulwood Kambah, Erindale Centre, Tuggeranong Bus Station]  
long_name: To Tuggeranong Bus Station  
between_stops:  
Woden Bus Station (Platform 11)-Athllon / Sulwood Kambah: [Wjz3kAx, Wjz3kyX, Wjz3kwU, Wjz3iFK, Wjz3hL_, Wjz3gK-, Wjz3gQn, Wjz3gMq, Wjz2mTK]  
short_name: "964"  
stop_times: [[905a, 914a, 926a, 937a], [1005a, 1014a, 1026a, 1037a], [1105a, 1114a, 1126a, 1137a], [1205p, 1214p, 1226p, 1237p], [105p, 114p, 126p, 137p], [205p, 214p, 226p, 237p], [305p, 314p, 326p, 337p], [405p, 414p, 426p, 437p], [505p, 514p, 526p, 537p], [605p, 614p, 626p, 637p], [705p, 714p, 726p, 737p]]  
-  
time_points: [City Bus Station (Platform 9), Russell Offices, Kings Ave / National Circuit, Causeway, Railway Station Kingston, Newcastle Street after Isa Street, Fyshwick Direct Factory Outlet, Eye Hospital, Geoscience Australia, Woden Bus Station]  
long_name: To Woden Bus Station  
between_stops:  
Russell Offices-Kings Ave / National Circuit: [Wjz4RFJ, Wjz4RwH, Wjz4Quk]  
City Bus Station (Platform 9)-Russell Offices: [Wjz5Nht, Wjz5MsD, Wjz5MsT, Wjz5MEL, Wjz4T-X, Wjz4_jm, Wjz4_wS, Wjz4-KO, Wjz4-Rc, Wjz4-WL, Wjz4-WZ]  
short_name: "80"  
stop_times: [[550a, 558a, 602a, 606a, 609a, 617a, 626a, 631a, 640a, 656a], [617a, 625a, 629a, 633a, 636a, 644a, 653a, 658a, 707a, 723a], [648a, 656a, 700a, 704a, 707a, 715a, 724a, 729a, 737a, 753a], [719a, 727a, 731a, 738a, 741a, 750a, 804a, 810a, 818a, 834a], [751a, 800a, 803a, 810a, 813a, 822a, 836a, 842a, 850a, 906a], [828a, 837a, 840a, 847a, 850a, 859a, 913a, 919a, 927a, 945a], [859a, 907a, 911a, 915a, 918a, 930a, 939a, 944a, 952a, 1010a], [928a, 936a, 940a, 944a, 947a, 955a, 1004a, 1009a, 1017a, 1035a], [1028a, 1036a, 1040a, 1044a, 1047a, 1055a, 1104a, 1109a, 1117a, 1135a], [1128a, 1136a, 1140a, 1144a, 1147a, 1155a, 1204p, 1209p, 1217p, 1235p], [1228p, 1236p, 1240p, 1244p, 1247p, 1255p, 104p, 109p, 117p, 135p], [128p, 136p, 140p, 144p, 147p, 155p, 204p, 209p, 217p, 235p], [228p, 236p, 240p, 244p, 247p, 255p, 304p, 309p, 318p, 334p], [330p, 339p, 344p, 349p, 352p, 400p, 410p, 416p, 426p, 444p], [400p, 409p, 414p, 419p, 422p, 430p, 440p, 446p, 456p, 514p], [434p, 443p, 448p, 453p, 456p, 504p, 514p, 520p, 530p, 548p], [504p, 513p, 518p, 523p, 526p, 534p, 544p, 550p, 600p, 618p], [534p, 543p, 548p, 553p, 556p, 604p, 614p, 620p, 630p, 645p], [604p, 613p, 618p, 623p, 626p, 633p, 641p, 646p, 654p, 709p], [702p, 710p, 714p, 718p, 720p, "-", "-", "-", "-", "-"], [800p, 808p, 812p, 816p, 818p, "-", "-", "-", "-", "-"], [900p, 908p, 912p, 916p, 918p, "-", "-", "-", "-", "-"], [1000p, 1008p, 1012p, 1016p, 1018p, "-", "-", "-", "-", "-"], [1100p, 1108p, 1112p, 1116p, 1118p, "-", "-", "-", "-", "-"]]  
-  
time_points: [Woden Bus Station (Platform 4), Alexander Maconochie Centre]  
long_name: To Alexander Machonochie Centre Hume  
between_stops: {}  
   
short_name: "988"  
stop_times: [[920a, 940a], [1255p, 115p], [455p, 515p]]  
-  
time_points: [Tuggeranong Bus Station (Platform 7), Chisholm Shops, Brindabella Business Park, Fairbairn Park]  
long_name: To Fairbairn Park  
between_stops:  
Brindabella Business Park-Fairbairn Park: [WjzcrK3, WjzcrrQ, WjzcrEu, WjzcJ0K, WjzcBHZ, WjzcJ38]  
short_name: "786"  
stop_times: [[646a, 656a, 716a, 726a], [706a, 716a, 736a, 746a], [727a, 737a, 804a, 814a]]  
-  
time_points: [City Bus Station (Platform 8), Macarthur / Northbourne Ave, Northbourne Avenue / Antill St, Flemington Rd / Sandford St, Hoskins Street / Oodgeroo Ave, Manning Clarke / Oodgeroo, Gungahlin Marketplace]  
long_name: To Gungahlin Marketplace  
between_stops:  
Macarthur / Northbourne Ave-Northbourne Avenue / Antill St: [Wjz5RkN, Wjz5Rsi, Wjz5RvC, Wjz5Sqk, Wjz5SrO, Wjz5Sux, Wjz5SDc]  
City Bus Station (Platform 8)-Macarthur / Northbourne Ave: [Wjz5O3Q, Wjz5Oci, Wjz5P8n, Wjz5P8K, Wjz5PdJ, Wjz5Pl0, Wjz5Qi2, Wjz5Qgn, Wjz5Qmu, Wjz5QmR]  
short_name: "57"  
stop_times: [[655a, 701a, 703a, 709a, 717a, 720a, 724a], [725a, 731a, 733a, 739a, 747a, 750a, 754a], [755a, 802a, 804a, 810a, 818a, 821a, 825a], [825a, 832a, 834a, 840a, 848a, 851a, 855a], [855a, 902a, 904a, 910a, 918a, 921a, 925a], [957a, 1003a, 1005a, 1011a, 1019a, 1022a, 1026a], [1055a, 1101a, 1103a, 1109a, 1117a, 1120a, 1124a], [1155a, 1201p, 1203p, 1209p, 1217p, 1220p, 1224p], [1255p, 101p, 103p, 109p, 117p, 120p, 124p], [155p, 201p, 203p, 209p, 217p, 220p, 224p], [255p, 301p, 303p, 310p, 318p, 321p, 325p], [355p, 402p, 404p, 411p, 419p, 422p, 426p], [425p, 432p, 434p, 441p, 449p, 452p, 456p], [455p, 502p, 504p, 511p, 519p, 522p, 526p], [525p, 532p, 534p, 541p, 549p, 552p, 556p], [555p, 602p, 604p, 609p, 617p, 620p, 624p], [625p, 631p, 633p, 638p, 646p, 649p, 653p], [655p, 701p, 703p, 708p, 716p, 719p, 723p]]  
-  
time_points: [Belconnen Community Bus Station (Platform 6), Westfield Bus Station (Platform 2), Cohen Street Bus Station (Platform 5), Charnwood Shops, Fraser East Terminus, Charnwood Shops, Cohen Street Bus Station, Westfield Bus Station, Belconnen Community Bus Station]  
long_name: To Belconnen Community Bus Station  
between_stops:  
Belconnen Community Bus Station (Platform 6)-Westfield Bus Station (Platform 2): []  
Westfield Bus Station-Belconnen Community Bus Station: []  
Cohen Street Bus Station-Westfield Bus Station: []  
Westfield Bus Station (Platform 2)-Cohen Street Bus Station (Platform 5): []  
stop_times_saturday: [["-", "-", "-", 708a, 716a, 723a, 737a, 739a, 743a], ["-", "-", "-", 808a, 816a, 823a, 837a, 839a, 843a], [848a, 850a, 854a, 908a, 916a, 923a, 937a, 939a, 943a], [948a, 950a, 954a, 1008a, 1016a, 1023a, 1037a, 1039a, 1043a], [1048a, 1050a, 1054a, 1108a, 1116a, 1123a, 1137a, 1139a, 1143a], [1148a, 1150a, 1154a, 1208p, 1216p, 1223p, 1237p, 1239p, 1243p], [1248p, 1250p, 1254p, 108p, 116p, 123p, 137p, 139p, 143p], [148p, 150p, 154p, 208p, 216p, 223p, 237p, 239p, 243p], [248p, 250p, 254p, 308p, 316p, 323p, 337p, 339p, 343p], [348p, 350p, 354p, 408p, 416p, 423p, 437p, 439p, 443p], [448p, 450p, 454p, 508p, 516p, 523p, 537p, 539p, 543p], [548p, 550p, 554p, 608p, 616p, 623p, 637p, 639p, 643p], [647p, 649p, 653p, 706p, 714p, 721p, 734p, 736p, 740p], [747p, 749p, 753p, 806p, 814p, 821p, 834p, 836p, 840p], [847p, 849p, 853p, 906p, 914p, 921p, 934p, 936p, 940p], [947p, 949p, 953p, 1006p, 1014p, 1021p, 1034p, 1036p, 1040p], [1047p, 1049p, 1053p, 1106p, 1114p, 1121p, 1134p, 1136p, 1140p]]  
short_name: "907"  
-  
time_points: [City Bus Station (Platform 8), Ainslie Shops, Hackett Shops, Dickson Shops, North Lyneham, Lyneham Shops Wattle Street, Macarthur / Miller O'Connor, City Bus Station]  
long_name: To City Bus Station  
between_stops: {}  
   
short_name: "937"  
stop_times: [[859a, 911a, 919a, 925a, 934a, 939a, 942a, 951a], [959a, 1011a, 1019a, 1025a, 1034a, 1039a, 1042a, 1051a], [1059a, 1111a, 1119a, 1125a, 1134a, 1139a, 1142a, 1151a], [1159a, 1211p, 1219p, 1225p, 1234p, 1239p, 1242p, 1251p], [1259p, 111p, 119p, 125p, 134p, 139p, 142p, 151p], [159p, 211p, 219p, 225p, 234p, 239p, 242p, 251p], [259p, 311p, 319p, 325p, 334p, 339p, 342p, 351p], [359p, 411p, 419p, 425p, 434p, 439p, 442p, 451p], [459p, 511p, 519p, 525p, 534p, 539p, 542p, 551p], [559p, 611p, 619p, 625p, 634p, 639p, 642p, 651p], [659p, 711p, 719p, 725p, 734p, 739p, 742p, 751p]]  
-  
time_points: [Cooleman Court, Duffy, Holder, Weston Primary, Woden Bus Station]  
long_name: To Woden Bus Station  
between_stops: {}  
   
stop_times_saturday: [[824a, 831a, 834a, 837a, 846a], [924a, 931a, 934a, 937a, 946a], [1024a, 1031a, 1034a, 1037a, 1046a], [1124a, 1131a, 1134a, 1137a, 1146a], [1224p, 1231p, 1234p, 1237p, 1246p], [124p, 131p, 134p, 137p, 146p], [224p, 231p, 234p, 237p, 246p], [324p, 331p, 334p, 337p, 346p], [424p, 431p, 434p, 437p, 446p], [524p, 531p, 534p, 537p, 546p], [624p, 631p, 634p, 637p, 646p], [724p, 731p, 734p, 737p, 746p], [824p, 831p, 834p, 837p, 846p], [924p, 931p, 934p, 937p, 946p], [1024p, 1031p, 1034p, 1037p, 1046p]]  
short_name: "925"  
-  
time_points: [Woden Bus Station (Platform 15), Lyons Shops, Chifley Shops, Torrens Shops, Southlands Mawson, Pearce Shops, Woden Bus Station]  
long_name: To Woden Bus Station  
between_stops: {}  
   
short_name: "921"  
stop_times_sunday: [[933a, 936a, 940a, 945a, 951a, 955a, 1001a], [1133a, 1136a, 1140a, 1145a, 1151a, 1155a, 1201p], [133p, 136p, 140p, 145p, 151p, 155p, 201p], [333p, 336p, 340p, 345p, 351p, 355p, 401p], [533p, 536p, 540p, 545p, 551p, 555p, 601p]]  
-  
time_points: [Tuggeranong Bus Station (Platform 4), Isabella Shops, Theodore, Calwell Shops, Outtrim / Duggan, Tuggeranong Bus Station]  
long_name: To Tuggeranong Bus Station  
between_stops: {}  
   
short_name: "915"  
stop_times_sunday: [[915a, 925a, 934a, 943a, 946a, 955a], [1115a, 1125a, 1134a, 1143a, 1146a, 1155a], [115p, 125p, 134p, 143p, 146p, 155p], [315p, 325p, 334p, 343p, 346p, 355p], [515p, 525p, 534p, 543p, 546p, 555p], [715p, 725p, 734p, 743p, 746p, 755p]]  
-  
time_points: [Cohen Street Bus Station (Platform 1), Westfield Bus Station (Platform 1), Belconnen Community Bus Station (Platform 2), Chuculba / William Slim Dr, Mirrabei Drive / Dam Wall, Paul Coe / Mirrabei Dr, Katherine Ave / Horse Park Drive, Gungahlin Marketplace, Hibberson / Kate Crace, Flemington Rd / Sandford St, Northbourne Avenue / Antill St]  
long_name: To Northbourne Avenue / Antill St  
between_stops:  
Belconnen Community Bus Station (Platform 2)-Chuculba / William Slim Dr: [Wjz69gA, Wjz69ht, Wjz69uI, Wjz69vO, Wjz6mip]  
Cohen Street Bus Station (Platform 1)-Westfield Bus Station (Platform 1): []  
Westfield Bus Station (Platform 1)-Belconnen Community Bus Station (Platform 2): []  
short_name: "59"  
stop_times: [["-", "-", "-", "-", 537a, 541a, 547a, 603a, 606a, "-", "-"], ["-", "-", "-", "-", 612a, 616a, 622a, 638a, 641a, "-", "-"], ["-", "-", "-", "-", 646a, 650a, 656a, 711a, 714a, 717a, 724a], ["-", "-", "-", "-", 702a, 706a, 712a, 727a, 730a, 733a, 740a], ["-", "-", "-", "-", 712a, 716a, 722a, 737a, 740a, 743a, 753a], ["-", "-", "-", "-", 733a, 737a, 743a, 758a, 801a, 806a, 817a], ["-", "-", "-", "-", 809a, 813a, 819a, 834a, 837a, 842a, 853a], ["-", "-", "-", "-", 820a, 824a, 830a, 845a, 848a, 853a, 903a], ["-", "-", "-", "-", 849a, 853a, 859a, 914a, 917a, 920a, 927a], [900a, 902a, 906a, 923a, "-", 933a, 939a, 955a, 958a, "-", "-"], [1000a, 1002a, 1006a, 1023a, "-", 1033a, 1039a, 1055a, 1058a, "-", "-"], [1100a, 1102a, 1106a, 1123a, "-", 1133a, 1139a, 1155a, 1158a, "-", "-"], [1200p, 1202p, 1206p, 1223p, "-", 1233p, 1239p, 1255p, 1258p, "-", "-"], [100p, 102p, 106p, 123p, "-", 133p, 139p, 155p, 158p, "-", "-"], [200p, 202p, 206p, 223p, "-", 233p, 239p, 255p, 258p, "-", "-"], [240p, 242p, 246p, 303p, "-", 313p, 319p, 335p, 338p, "-", "-"], [318p, 320p, 324p, 342p, "-", 352p, 358p, 414p, 417p, "-", "-"], [333p, 335p, 339p, 357p, "-", 407p, 413p, 429p, 432p, "-", "-"], [348p, 350p, 354p, 412p, "-", 422p, 428p, 444p, 447p, "-", "-"], [403p, 405p, 409p, 427p, "-", 437p, 443p, 459p, 502p, "-", "-"], [418p, 420p, 424p, 442p, "-", 452p, 458p, 514p, 517p, "-", "-"], [433p, 435p, 439p, 457p, "-", 507p, 513p, 529p, 532p, "-", "-"], [448p, 450p, 454p, 512p, "-", 522p, 528p, 544p, 547p, "-", "-"], [503p, 505p, 509p, 527p, "-", 537p, 543p, 559p, 602p, "-", "-"], [518p, 520p, 524p, 542p, "-", 552p, 558p, 614p, 617p, "-", "-"], [530p, 532p, 536p, 554p, "-", 604p, 610p, 626p, 629p, "-", "-"], [548p, 550p, 554p, 611p, "-", 620p, 626p, 642p, 645p, "-", "-"], [603p, 605p, 609p, 626p, "-", 635p, 641p, 657p, 700p, "-", "-"], [703p, 705p, 709p, 726p, "-", 735p, 741p, 757p, 800p, "-", "-"], [803p, 805p, 809p, 826p, "-", 835p, 841p, 857p, 900p, "-", "-"], [903p, 905p, 909p, 926p, "-", 935p, 941p, 957p, 1000p, "-", "-"], [1003p, 1005p, 1009p, 1026p, "-", 1035p, 1041p, 1057p, 1100p, "-", "-"], [1103p, 1105p, 1109p, 1126p, "-", 1135p, 1141p, 1157p, 1200a, "-", "-"]]  
-  
time_points: [Woden Bus Station (Platform 15), Lyons Shops, Chifley Shops, Torrens Shops, Southlands Mawson, Pearce Shops, Woden Bus Station]  
long_name: To Woden Bus Station  
between_stops: {}  
   
short_name: "921"  
stop_times: [[933a, 936a, 940a, 945a, 951a, 955a, 1001a], [1133a, 1136a, 1140a, 1145a, 1151a, 1155a, 1201p], [133p, 136p, 140p, 145p, 151p, 155p, 201p], [333p, 336p, 340p, 345p, 351p, 355p, 401p], [533p, 536p, 540p, 545p, 551p, 555p, 601p]]  
-  
time_points: [Centrelink Tuggeranong, Tuggeranong Bus Station (Platform 7), Belconnen Community Bus Station, Westfield Bus Station, Cohen Street Bus Station]  
long_name: To Cohen Street Bus Station  
between_stops:  
Westfield Bus Station-Cohen Street Bus Station: []  
Belconnen Community Bus Station-Westfield Bus Station: []  
short_name: "705"  
stop_times: [["-", 723a, 752a, 754a, 759a], ["-", 749a, 818a, 820a, 825a], ["-", 814a, 848a, 850a, 855a], [442p, 447p, 516p, 518p, 523p], [507p, 512p, 541p, 543p, 548p], [535p, 540p, 609p, 611p, 616p]]  
-  
time_points: [Farrer Terminus, Southlands Mawson, Garran Shops, Hughes Shops, City West, City Bus Station, ACTEW AGL House]  
long_name: To ACTEW AGL House  
between_stops:  
City Bus Station-ACTEW AGL House: [Wjz5Nht]  
City West-City Bus Station: []  
short_name: "720"  
stop_times: [[710a, 716a, 728a, 734a, 752a, 756a, 757a], [740a, 746a, 758a, 804a, 822a, 826a, 827a], [816a, 822a, 834a, 840a, 858a, 902a, 903a], [840a, 846a, 858a, 904a, 922a, 926a, 927a]]  
-  
time_points: [City Bus Station (Platform 8), Macarthur / Northbourne Ave, Northbourne Avenue / Antill St, North Lyneham, Kaleen Village / Marybrynong, Giralang, University of Canberra, Belconnen Community Bus Station, Westfield Bus Station, Cohen Street Bus Station]  
long_name: To Cohen Street Bus Station  
between_stops:  
Westfield Bus Station-Cohen Street Bus Station: []  
Macarthur / Northbourne Ave-Northbourne Avenue / Antill St: [Wjz5RkN, Wjz5Rsi, Wjz5RvC, Wjz5Sqk, Wjz5SrO, Wjz5Sux, Wjz5SDc]  
City Bus Station (Platform 8)-Macarthur / Northbourne Ave: [Wjz5O3Q, Wjz5Oci, Wjz5P8n, Wjz5P8K, Wjz5PdJ, Wjz5Pl0, Wjz5Qi2, Wjz5Qgn, Wjz5Qmu, Wjz5QmR]  
Belconnen Community Bus Station-Westfield Bus Station: []  
University of Canberra-Belconnen Community Bus Station: [Wjz681S, Wjz689c]  
short_name: "30"  
stop_times: [[603a, 609a, 611a, 614a, 621a, 628a, 635a, 641a, 643a, 648a], [634a, 640a, 642a, 645a, 652a, 659a, 706a, 712a, 714a, 719a], [701a, 707a, 709a, 712a, 719a, 726a, 735a, 741a, 743a, 748a], [726a, 732a, 734a, 737a, 745a, 753a, 805a, 811a, 813a, 818a], [759a, 806a, 808a, 811a, 819a, 827a, 839a, 845a, 847a, 852a], [829a, 836a, 838a, 841a, 849a, 857a, 909a, 915a, 917a, 922a], [859a, 906a, 908a, 911a, 919a, 927a, 935a, 941a, 943a, 948a], [933a, 939a, 941a, 944a, 951a, 958a, 1005a, 1011a, 1013a, 1018a], [1002a, 1008a, 1010a, 1013a, 1020a, 1027a, 1034a, 1040a, 1042a, 1047a], [1102a, 1108a, 1110a, 1113a, 1120a, 1127a, 1134a, 1140a, 1142a, 1147a], [1202p, 1208p, 1210p, 1213p, 1220p, 1227p, 1234p, 1240p, 1242p, 1247p], [102p, 108p, 110p, 113p, 120p, 127p, 134p, 140p, 142p, 147p], [202p, 208p, 210p, 213p, 220p, 227p, 234p, 240p, 242p, 247p], [302p, 309p, 311p, 316p, 324p, 332p, 344p, 350p, 352p, 357p], [334p, 341p, 343p, 348p, 356p, 404p, 416p, 422p, 424p, 429p], [359p, 406p, 408p, 413p, 421p, 429p, 441p, 447p, 449p, 454p], [429p, 436p, 438p, 443p, 451p, 459p, 511p, 517p, 519p, 524p], [459p, 506p, 508p, 513p, 521p, 529p, 541p, 547p, 549p, 554p], [514p, 521p, 523p, 528p, 536p, 544p, 556p, 602p, 604p, 609p], [529p, 536p, 538p, 543p, 551p, 559p, 611p, 617p, 619p, 624p], [544p, 551p, 553p, 558p, 606p, 614p, 626p, 632p, 634p, 639p], [559p, 606p, 608p, 613p, 621p, 629p, 636p, 642p, 644p, 649p], [633p, 639p, 641p, 644p, 651p, 658p, 705p, 711p, 713p, 718p], [702p, 708p, 710p, 713p, 720p, 727p, 734p, 740p, 742p, 747p], [802p, 808p, 810p, 813p, 820p, 827p, 834p, 840p, 842p, 847p], [902p, 908p, 910p, 913p, 920p, 927p, 934p, 940p, 942p, 947p], [1002p, 1008p, 1010p, 1013p, 1020p, 1027p, 1034p, 1040p, 1042p, 1047p], [1102p, 1108p, 1110p, 1113p, 1120p, 1127p, 1134p, 1140p, 1142p, 1147p]]  
-  
time_points: [Tuggeranong Bus Station (Platform 4), Kambah High, Kambah Village, Woden Bus Station]  
long_name: To Woden Bus Station  
between_stops: {}  
   
stop_times_saturday: [[824a, 831a, 839a, 852a], [924a, 931a, 939a, 952a], [1024a, 1031a, 1039a, 1052a], [1124a, 1131a, 1139a, 1152a], [1224p, 1231p, 1239p, 1252p], [124p, 131p, 139p, 152p], [224p, 231p, 239p, 252p], [324p, 331p, 339p, 352p], [424p, 431p, 439p, 452p], [524p, 531p, 539p, 552p], [624p, 631p, 638p, 649p], [724p, 730p, 737p, 748p], [824p, 830p, 837p, 848p], [924p, 930p, 937p, 948p], [1024p, 1030p, 1037p, 1048p], [1124p, 1130p, 1137p, 1148p]]  
short_name: "962"  
-  
time_points: [Belconnen Community Bus Station (Platform 4), Westfield Bus Station (Platform 2), Cohen Street Bus Station (Platform 6), McKellar Shops, Evatt Shops, Spence Terminus, Evatt Shops, McKellar Shops, Cohen Street Bus Station, Westfield Bus Station, Belconnen Community Bus Station]  
long_name: To Belconnen Community Bus Station  
between_stops:  
Belconnen Community Bus Station (Platform 4)-Westfield Bus Station (Platform 2): []  
Westfield Bus Station-Belconnen Community Bus Station: []  
Cohen Street Bus Station-Westfield Bus Station: []  
Westfield Bus Station (Platform 2)-Cohen Street Bus Station (Platform 6): []  
stop_times_saturday: [["-", "-", "-", "-", "-", 718a, 723a, 731a, 739a, 741a, 745a], ["-", "-", "-", "-", "-", 818a, 823a, 831a, 839a, 841a, 845a], [851a, 853a, 857a, 904a, 912a, 918a, 923a, 931a, 939a, 941a, 945a], [951a, 953a, 957a, 1004a, 1012a, 1018a, 1023a, 1031a, 1039a, 1041a, 1045a], [1051a, 1053a, 1057a, 1104a, 1112a, 1118a, 1123a, 1131a, 1139a, 1141a, 1145a], [1151a, 1153a, 1157a, 1204p, 1212p, 1218p, 1223p, 1231p, 1239p, 1241p, 1245p], [1251p, 1253p, 1257p, 104p, 112p, 118p, 123p, 131p, 139p, 141p, 145p], [151p, 153p, 157p, 204p, 212p, 218p, 223p, 231p, 239p, 241p, 245p], [251p, 253p, 257p, 304p, 312p, 318p, 323p, 331p, 339p, 341p, 345p], [351p, 353p, 357p, 404p, 412p, 418p, 423p, 431p, 439p, 441p, 445p], [451p, 453p, 457p, 504p, 512p, 518p, 523p, 531p, 539p, 541p, 545p], [551p, 553p, 557p, 604p, 612p, 618p, 623p, 631p, 638p, 640p, 644p], [650p, 652p, 656p, 702p, 709p, 715p, 720p, 728p, 735p, 737p, 741p], [750p, 752p, 756p, 802p, 809p, 815p, 820p, 828p, 835p, 837p, 841p], [850p, 852p, 856p, 902p, 909p, 915p, 920p, 928p, 935p, 937p, 941p], [950p, 952p, 956p, 1002p, 1009p, 1015p, 1020p, 1028p, 1035p, 1037p, 1041p], [1050p, 1052p, 1056p, 1102p, 1109p, 1115p, 1120p, 1128p, 1135p, 1137p, 1141p]]  
short_name: "902"  
-  
time_points: [Lithgow St Terminus Fyshwick, Canberra Times, City Bus Station]  
long_name: To City Bus Station  
between_stops: {}  
   
short_name: "780"  
stop_times: [[405p, 421p, 440p], [435p, 451p, 510p]]  
-  
time_points: [Kippax, Higgins, Hawker College, Hawker Shops, Macquarie, Aranda Shops, City Bus Station (Platform 10), Russell Offices, National Circ / Canberra Ave]  
long_name: To National Circ / Canberra Ave  
between_stops:  
City Bus Station (Platform 10)-Russell Offices: [Wjz5Nht, Wjz5MsD, Wjz5MsT, Wjz5MEL, Wjz4T-X, Wjz4_jm, Wjz4_wS, Wjz4-KO, Wjz4-Rc, Wjz4-WL, Wjz4-WZ]  
short_name: "704"  
stop_times: [[738a, 744a, 749a, 754a, 803a, 812a, 825a, 833a, 840a], [753a, 759a, 804a, 809a, 818a, 827a, 840a, 848a, 855a]]  
-  
time_points: [Cohen Street Bus Station (Platform 2), Westfield Bus Station (Platform 1), Belconnen Community Bus Station (Platform 1), City Bus Station (Platform 1), Woden Bus Station (Platform 6), Erindale Centre, Tuggeranong Bus Station]  
long_name: To Tuggeranong Bus Station  
between_stops:  
Cohen Street Bus Station (Platform 2)-Westfield Bus Station (Platform 1): []  
Westfield Bus Station (Platform 1)-Belconnen Community Bus Station (Platform 1): []  
short_name: "900"  
stop_times: [[731a, 733a, 737a, 757a, 814a, 829a, 835a], [746a, 748a, 752a, 812a, 829a, 844a, 850a], [801a, 803a, 807a, 827a, 844a, 859a, 905a], [816a, 818a, 822a, 842a, 859a, 914a, 920a], [831a, 833a, 837a, 857a, 914a, 929a, 935a], [846a, 848a, 852a, 912a, 929a, 944a, 950a], [901a, 903a, 907a, 927a, 944a, 959a, 1005a], [916a, 918a, 922a, 942a, 959a, 1014a, 1020a], [931a, 933a, 937a, 957a, 1014a, 1029a, 1035a], [946a, 948a, 952a, 1012a, 1029a, 1044a, 1050a], [1001a, 1003a, 1007a, 1027a, 1044a, 1059a, 1105a], [1016a, 1018a, 1022a, 1042a, 1059a, 1114a, 1120a], [1031a, 1033a, 1037a, 1057a, 1114a, 1129a, 1135a], [1046a, 1048a, 1052a, 1112a, 1129a, 1144a, 1150a], [1101a, 1103a, 1107a, 1127a, 1144a, 1159a, 1205p], [1116a, 1118a, 1122a, 1142a, 1159a, 1214p, 1220p], [1131a, 1133a, 1137a, 1157a, 1214p, 1229p, 1235p], [1146a, 1148a, 1152a, 1212p, 1229p, 1244p, 1250p], [1201p, 1203p, 1207p, 1227p, 1244p, 1259p, 105p], [1216p, 1218p, 1222p, 1242p, 1259p, 114p, 120p], [1231p, 1233p, 1237p, 1257p, 114p, 129p, 135p], [1246p, 1248p, 1252p, 112p, 129p, 144p, 150p], [101p, 103p, 107p, 127p, 144p, 159p, 205p], [116p, 118p, 122p, 142p, 159p, 214p, 220p], [131p, 133p, 137p, 157p, 214p, 229p, 235p], [146p, 148p, 152p, 212p, 229p, 244p, 250p], [201p, 203p, 207p, 227p, 244p, 259p, 305p], [216p, 218p, 222p, 242p, 259p, 314p, 320p], [231p, 233p, 237p, 257p, 314p, 329p, 335p], [246p, 248p, 252p, 312p, 329p, 344p, 350p], [301p, 303p, 307p, 327p, 344p, 359p, 405p], [316p, 318p, 322p, 342p, 359p, 414p, 420p], [331p, 333p, 337p, 357p, 414p, 429p, 435p], [346p, 348p, 352p, 412p, 429p, 444p, 450p], [401p, 403p, 407p, 427p, 444p, 459p, 505p], [416p, 418p, 422p, 442p, 459p, 514p, 520p], [431p, 433p, 437p, 457p, 514p, 529p, 535p], [446p, 448p, 452p, 512p, 529p, 544p, 550p], [501p, 503p, 507p, 527p, 544p, 559p, 605p], [516p, 518p, 522p, 542p, 559p, 614p, 620p], [531p, 533p, 537p, 557p, 614p, 629p, 635p], [546p, 548p, 552p, 612p, 629p, 643p, 649p], [601p, 603p, 607p, 627p, 642p, 656p, 702p], [616p, 618p, 622p, 641p, 655p, 709p, 715p], [631p, 633p, 637p, 656p, 710p, 724p, 730p], [646p, 648p, 652p, 711p, 725p, 739p, 745p], [701p, 703p, 707p, 726p, 740p, 754p, 800p]]  
-  
time_points: [Cooleman Court, Stromlo High Waramanga, Woden Bus Station]  
long_name: To Woden Bus Station  
between_stops: {}  
   
short_name: "75"  
stop_times: [[925a, 934a, 947a], [1125a, 1134a, 1147a], [125p, 134p, 147p]]  
-  
time_points: [Gungahlin Marketplace, Ngunnawal Primary, Nicholls Primary, Federation Square, Chuculba / William Slim Dr, Belconnen Community Bus Station, Westfield Bus Station, Cohen Street Bus Station]  
long_name: To Cohen Street Bus Station  
between_stops:  
Westfield Bus Station-Cohen Street Bus Station: []  
Belconnen Community Bus Station-Westfield Bus Station: []  
Chuculba / William Slim Dr-Belconnen Community Bus Station: [Wjz6mip, Wjz69vO, Wjz69uI, Wjz69ht, Wjz69gA]  
short_name: "951"  
stop_times: [[912a, 921a, 931a, 937a, 942a, 950a, 952a, 957a], [1012a, 1021a, 1031a, 1037a, 1042a, 1050a, 1052a, 1057a], [1112a, 1121a, 1131a, 1137a, 1142a, 1150a, 1152a, 1157a], [1212p, 1221p, 1231p, 1237p, 1242p, 1250p, 1252p, 1257p], [112p, 121p, 131p, 137p, 142p, 150p, 152p, 157p], [212p, 221p, 231p, 237p, 242p, 250p, 252p, 257p], [312p, 321p, 331p, 337p, 342p, 350p, 352p, 357p], [412p, 421p, 431p, 437p, 442p, 450p, 452p, 457p], [512p, 521p, 531p, 537p, 542p, 550p, 552p, 557p], [612p, 621p, 631p, 637p, 642p, 650p, 652p, 657p]]  
-  
time_points: [Lanyon Market Place, Conder Primary, St Clare of Assisi, Bonython Primary School, Tuggeranong Bus Station (Platform 8), Woden Bus Station (Platform 9), City Bus Station (Platform 3), Belconnen Community Bus Station, Westfield Bus Station, Cohen Street Bus Station]  
long_name: To Cohen Street Bus Station  
between_stops:  
Westfield Bus Station-Cohen Street Bus Station: []  
City Bus Station (Platform 3)-Belconnen Community Bus Station: [Wjz5F-1, Wjz5FSY, Wjz5GMT, Wjz5GNG, Wjz5G6U, Wjz5G6B, Wjz5maK, Wjz5mbS, Wjz5nwb, Wjz5nw6, Wjz6giR, Wjz6gia, Wjz68W3, Wjz68W5, Wjz689c, Wjz681S]  
Belconnen Community Bus Station-Westfield Bus Station: []  
Tuggeranong Bus Station (Platform 8)-Woden Bus Station (Platform 9): [Wjz213q, Wjz238T, Wjz239F, Wjz2lDC, Wjz2mGO, Wjz2mTK, Wjz2nLE, Wjz3gMq, Wjz3gQn, Wjz3gK-, Wjz3hL_, Wjz3iFK, Wjz3kwU, Wjz3kyX, Wjz3kAx]  
Woden Bus Station (Platform 9)-City Bus Station (Platform 3): [Wjz3m3b, Wjz3m3b, Wjz3eRR, Wjz3eZ4, Wjz4KNu, Wjz4KO9, Wjz5Nht]  
short_name: 19 319  
stop_times: [[556a, 602a, 608a, 614a, 625a, 643a, 659a, 719a, 721a, 726a], [622a, 628a, 634a, 640a, 651a, 709a, 725a, 746a, 748a, 753a], [646a, 652a, 658a, 704a, 715a, 733a, 751a, 812a, 814a, 819a], [706a, 712a, 718a, 724a, 735a, 754a, 812a, 833a, 835a, 840a], [723a, 729a, 735a, 743a, 755a, 814a, 832a, 853a, 855a, 900a], [735a, 742a, 752a, 800a, 810a, "-", "-", "-", "-", "-"], [742a, 749a, 755a, 803a, 815a, 834a, 852a, 913a, 915a, 920a], [802a, 809a, 815a, 823a, 835a, 854a, 912a, 933a, 935a, 940a], [822a, 829a, 835a, 843a, 855a, 914a, 932a, 952a, 954a, 959a], [853a, 900a, 906a, 914a, 926a, 944a, 1000a, 1020a, 1022a, 1027a], [926a, 933a, 939a, 945a, 956a, 1014a, 1030a, 1050a, 1052a, 1057a], [957a, 1003a, 1009a, 1015a, 1026a, 1044a, 1100a, 1120a, 1122a, 1127a], [1027a, 1033a, 1039a, 1045a, 1056a, 1114a, 1130a, 1150a, 1152a, 1157a], [1057a, 1103a, 1109a, 1115a, 1126a, 1144a, 1200p, 1220p, 1222p, 1227p], [1127a, 1133a, 1139a, 1145a, 1156a, 1214p, 1230p, 1250p, 1252p, 1257p], [1157a, 1203p, 1209p, 1215p, 1226p, 1244p, 100p, 120p, 122p, 127p], [1227p, 1233p, 1239p, 1245p, 1256p, 114p, 130p, 150p, 152p, 157p], [1257p, 103p, 109p, 115p, 126p, 144p, 200p, 220p, 222p, 227p], [127p, 133p, 139p, 145p, 156p, 214p, 230p, 250p, 252p, 257p], [157p, 203p, 209p, 215p, 226p, 244p, 300p, 321p, 323p, 328p], [226p, 232p, 238p, 244p, 255p, 314p, 332p, 353p, 355p, 400p], [253p, 259p, 305p, 313p, 325p, 344p, 402p, 423p, 425p, 430p], [320p, 327p, 337p, 345p, 355p, "-", "-", "-", "-", "-"], [352p, 359p, 409p, 417p, 427p, "-", "-", "-", "-", "-"], [424p, 431p, 441p, 449p, 459p, "-", "-", "-", "-", "-"], [454p, 501p, 511p, 519p, 529p, "-", "-", "-", "-", "-"], [524p, 531p, 541p, 549p, 559p, "-", "-", "-", "-", "-"], [556p, 603p, 613p, 621p, 631p, "-", "-", "-", "-", "-"], [654p, 700p, 710p, 716p, 725p, "-", "-", "-", "-", "-"], [754p, 800p, 810p, 816p, 825p, "-", "-", "-", "-", "-"], [849p, 855p, 905p, 911p, 920p, "-", "-", "-", "-", "-"], [949p, 955p, 1005p, 1011p, 1020p, "-", "-", "-", "-", "-"], [1049p, 1055p, 1105p, 1111p, 1120p, "-", "-", "-", "-", "-"], []]  
-  
time_points: [Belconnen Community Bus Station (Platform 6), Westfield Bus Station (Platform 2), Cohen Street Bus Station (Platform 5), Florey Shops, Latham Post Office, Kippax]  
long_name: To Kippax  
between_stops:  
Belconnen Community Bus Station (Platform 6)-Westfield Bus Station (Platform 2): []  
Westfield Bus Station (Platform 2)-Cohen Street Bus Station (Platform 5): []  
short_name: "16"  
stop_times: [[700a, 702a, 706a, 711a, 717a, 727a], [800a, 802a, 806a, 812a, 818a, 830a], [826a, 828a, 832a, 838a, 844a, 856a], [913a, 915a, 919a, 925a, 931a, 941a], [939a, 941a, 945a, 950a, 956a, 1006a], [1014a, 1016a, 1020a, 1025a, 1031a, 1041a], [1039a, 1041a, 1045a, 1050a, 1056a, 1106a], [1114a, 1116a, 1120a, 1125a, 1131a, 1141a], [1139a, 1141a, 1145a, 1150a, 1156a, 1206p], [1214p, 1216p, 1220p, 1225p, 1231p, 1241p], [1239p, 1241p, 1245p, 1250p, 1256p, 106p], [114p, 116p, 120p, 125p, 131p, 141p], [139p, 141p, 145p, 150p, 156p, 206p], [214p, 216p, 220p, 225p, 231p, 241p], [238p, 240p, 244p, 249p, 255p, 306p], [307p, 309p, 313p, 319p, 325p, 337p], [326p, 328p, 332p, 338p, 344p, 356p], [356p, 358p, 402p, 408p, 414p, 426p], [426p, 428p, 432p, 438p, 444p, 456p], [446p, 448p, 452p, 458p, 504p, 516p], [506p, 508p, 512p, 518p, 524p, 536p], [526p, 528p, 532p, 538p, 544p, 556p], [546p, 548p, 552p, 558p, 604p, 616p], [601p, 603p, 607p, 613p, 619p, 631p], [622p, 624p, 628p, 633p, 639p, 649p], [721p, 723p, 727p, 731p, 737p, 747p], [821p, 823p, 827p, 831p, 837p, 847p], [921p, 923p, 927p, 931p, 937p, 947p], [1021p, 1023p, 1027p, 1031p, 1037p, 1047p], [1121p, 1123p, 1127p, 1131p, 1137p, 1147p]]  
-  
time_points: [Cohen Street Bus Station (Platform 1), Westfield Bus Station (Platform 1), Belconnen Community Bus Station (Platform 2), Chuculba / William Slim Dr, Federation Square, Nicholls Primary, Ngunnawal Primary, Gungahlin Marketplace, Hibberson / Kate Crace, Flemington Rd / Sandford St, Northbourne Avenue / Antill St, Macarthur / Northbourne Ave, City Bus Station]  
long_name: To City Bus Station  
between_stops:  
Northbourne Avenue / Antill St-Macarthur / Northbourne Ave: [Wjz5SDc, Wjz5Sux, Wjz5SrO, Wjz5Sqk, Wjz5RvC, Wjz5Rsi, Wjz5RkN]  
Belconnen Community Bus Station (Platform 2)-Chuculba / William Slim Dr: [Wjz69gA, Wjz69ht, Wjz69uI, Wjz69vO, Wjz6mip]  
Macarthur / Northbourne Ave-City Bus Station: [Wjz5QmR, Wjz5Qmu, Wjz5Qgn, Wjz5Qi2, Wjz5Pl0, Wjz5PdJ, Wjz5P8K, Wjz5P8n, Wjz5Oci, Wjz5O3Q]  
Cohen Street Bus Station (Platform 1)-Westfield Bus Station (Platform 1): []  
Westfield Bus Station (Platform 1)-Belconnen Community Bus Station (Platform 2): []  
short_name: "52"  
stop_times: [["-", "-", "-", "-", 539a, 547a, 555a, 602a, 605a, "-", "-", "-", "-"], ["-", "-", "-", "-", 618a, 626a, 634a, 641a, 644a, "-", "-", "-", "-"], [628a, 630a, 634a, 647a, 652a, 700a, 708a, 714a, 717a, 720a, 727a, 729a, 741a], ["-", "-", "-", "-", 708a, 716a, 724a, 730a, 733a, 736a, 743a, 745a, 800a], ["-", "-", "-", "-", 723a, 731a, 739a, 745a, 748a, 753a, 804a, 809a, 824a], [723a, 725a, 729a, 742a, 747a, 755a, 803a, 810a, 813a, 818a, 829a, 834a, 849a], [739a, 741a, 745a, 759a, 804a, 812a, 820a, 827a, 830a, 835a, 846a, 851a, 903a], [803a, 805a, 809a, 823a, 828a, 836a, 844a, 851a, 854a, 859a, 906a, 908a, 915a], [834a, 836a, 840a, 854a, 859a, 907a, 915a, 921a, 924a, 927a, 934a, 936a, 943a], [912a, 914a, 918a, 931a, 936a, 944a, 952a, 959a, 1002a, "-", "-", "-", "-"], [1012a, 1014a, 1018a, 1031a, 1036a, 1044a, 1052a, 1059a, 1102a, "-", "-", "-", "-"], [1112a, 1114a, 1118a, 1131a, 1136a, 1144a, 1152a, 1159a, 1202p, "-", "-", "-", "-"], [1212p, 1214p, 1218p, 1231p, 1236p, 1244p, 1252p, 1259p, 102p, "-", "-", "-", "-"], [112p, 114p, 118p, 131p, 136p, 144p, 152p, 159p, 202p, "-", "-", "-", "-"], [212p, 214p, 218p, 231p, 236p, 244p, 252p, 259p, 302p, "-", "-", "-", "-"], [229p, 231p, 235p, 248p, 253p, 301p, 309p, 316p, 319p, "-", "-", "-", "-"], [312p, 314p, 318p, 331p, 336p, 344p, 352p, 359p, 402p, "-", "-", "-", "-"], [352p, 354p, 358p, 412p, 417p, 426p, 434p, 442p, 445p, "-", "-", "-", "-"], [412p, 414p, 418p, 432p, 437p, 446p, 454p, 502p, 505p, "-", "-", "-", "-"], [432p, 434p, 438p, 452p, 457p, 506p, 514p, 522p, 525p, "-", "-", "-", "-"], [452p, 454p, 458p, 512p, 517p, 526p, 534p, 542p, 545p, "-", "-", "-", "-"], [512p, 514p, 518p, 532p, 537p, 546p, 554p, 602p, 605p, "-", "-", "-", "-"], [532p, 534p, 538p, 552p, 557p, 605p, 613p, 620p, 623p, "-", "-", "-", "-"], [611p, 613p, 617p, 630p, 635p, 643p, 651p, 658p, 701p, "-", "-", "-", "-"], [711p, 713p, 717p, 730p, 735p, 743p, 751p, 758p, 801p, "-", "-", "-", "-"], [811p, 813p, 817p, 830p, 835p, 843p, 851p, 858p, 901p, "-", "-", "-", "-"], [911p, 913p, 917p, 930p, 935p, 943p, 951p, 958p, 1001p, "-", "-", "-", "-"], [1011p, 1013p, 1017p, 1030p, 1035p, 1043p, 1051p, 1058p, 1101p, "-", "-", "-", "-"]]  
-  
time_points: [Woden Bus Station (Platform 5), Kambah Village, Kambah High, Tuggeranong Bus Station]  
long_name: To Tuggeranong Bus Station  
between_stops: {}  
   
short_name: "962"  
stop_times: [[951a, 1002a, 1010a, 1017a], [1051a, 1102a, 1110a, 1117a], [1151a, 1202p, 1210p, 1217p], [1251p, 102p, 110p, 117p], [151p, 202p, 210p, 217p], [251p, 302p, 310p, 317p], [351p, 402p, 410p, 417p], [451p, 502p, 510p, 517p], [551p, 602p, 610p, 617p], [651p, 702p, 710p, 717p]]  
-  
time_points: [Belconnen Community Bus Station (Platform 4), Westfield Bus Station (Platform 2), Cohen Street Bus Station (Platform 6), Kippax, Macgregor, Charnwood Shops, Fraser West Terminus, Charnwood Shops, Macgregor, Kippax, Cohen Street Bus Station, Westfield Bus Station, Belconnen Community Bus Station]  
long_name: To Belconnen Community Bus Station  
between_stops:  
Belconnen Community Bus Station (Platform 4)-Westfield Bus Station (Platform 2): []  
Westfield Bus Station-Belconnen Community Bus Station: []  
Cohen Street Bus Station-Westfield Bus Station: []  
Westfield Bus Station (Platform 2)-Cohen Street Bus Station (Platform 6): []  
short_name: "905"  
stop_times: [["-", "-", "-", "-", "-", "-", 857a, 909a, 916a, 923a, 936a, 938a, 942a], [914a, 916a, 920a, 933a, 939a, 946a, 957a, 1009a, 1016a, 1023a, 1036a, 1038a, 1042a], [1014a, 1016a, 1020a, 1033a, 1039a, 1046a, 1057a, 1109a, 1116a, 1123a, 1136a, 1138a, 1142a], [1114a, 1116a, 1120a, 1133a, 1139a, 1146a, 1157a, 1209p, 1216p, 1223p, 1236p, 1238p, 1242p], [1214p, 1216p, 1220p, 1233p, 1239p, 1246p, 1257p, 109p, 116p, 123p, 136p, 138p, 142p], [114p, 116p, 120p, 133p, 139p, 146p, 157p, 209p, 216p, 223p, 236p, 238p, 242p], [214p, 216p, 220p, 233p, 239p, 246p, 257p, 309p, 316p, 323p, 336p, 338p, 342p], [314p, 316p, 320p, 333p, 339p, 346p, 357p, 409p, 416p, 423p, 436p, 438p, 442p], [414p, 416p, 420p, 433p, 439p, 446p, 457p, 509p, 516p, 523p, 536p, 538p, 542p], [514p, 516p, 520p, 533p, 539p, 546p, 557p, 609p, 616p, 623p, 636p, 638p, 642p], [614p, 616p, 620p, 633p, 639p, 646p, 656p, "-", "-", "-", "-", "-", "-"]]  
-  
time_points: [Cohen Street Bus Station (Platform 2), Westfield Bus Station (Platform 1), Belconnen Community Bus Station (Platform 3), Australian Institute of Sport, Dickson, Merici College, City Bus Station]  
long_name: To City Bus Station  
between_stops:  
Westfield Bus Station (Platform 1)-Belconnen Community Bus Station (Platform 3): []  
Cohen Street Bus Station (Platform 2)-Westfield Bus Station (Platform 1): []  
short_name: "7"  
stop_times: [[541a, 543a, 547a, 600a, 608a, 615a, 623a], [611a, 613a, 617a, 630a, 638a, 645a, 653a], [641a, 643a, 647a, 700a, 708a, 715a, 723a], [711a, 713a, 717a, 730a, 739a, 746a, 755a], [741a, 743a, 747a, 804a, 817a, 824a, 837a], [811a, 813a, 817a, 832a, 841a, 848a, 903a], [841a, 843a, 847a, 902a, 911a, 918a, 927a], [915a, 917a, 921a, 935a, 943a, 950a, 958a], [946a, 948a, 952a, 1005a, 1013a, 1020a, 1028a], [1016a, 1018a, 1022a, 1035a, 1043a, 1050a, 1058a], [1046a, 1048a, 1052a, 1105a, 1113a, 1120a, 1128a], [1116a, 1118a, 1122a, 1135a, 1143a, 1150a, 1158a], [1146a, 1148a, 1152a, 1205p, 1213p, 1220p, 1228p], [1216p, 1218p, 1222p, 1235p, 1243p, 1250p, 1258p], [1246p, 1248p, 1252p, 105p, 113p, 120p, 128p], [116p, 118p, 122p, 135p, 143p, 150p, 158p], [146p, 148p, 152p, 205p, 213p, 220p, 228p], [216p, 218p, 222p, 235p, 243p, 250p, 258p], [246p, 248p, 252p, 306p, 314p, 321p, 330p], [311p, 313p, 317p, 332p, 340p, 347p, 356p], [341p, 343p, 347p, 402p, 410p, 417p, 426p], [411p, 413p, 417p, 432p, 440p, 447p, 456p], [441p, 443p, 447p, 502p, 510p, 517p, 526p], [511p, 513p, 517p, 532p, 540p, 547p, 601p], [541p, 543p, 547p, 602p, 610p, 617p, 626p], [646p, 648p, 652p, 705p, 713p, 719p, 727p], [746p, 748p, 752p, 805p, 813p, 819p, 827p], [846p, 848p, 852p, 905p, 913p, 919p, 927p], [946p, 948p, 952p, 1005p, 1013p, 1019p, 1027p], [1046p, 1048p, 1052p, 1105p, 1113p, 1119p, 1127p]]  
-  
time_points: [Tuggeranong Bus Station (Platform 3), Erindale Centre, Athllon / Sulwood Kambah, Woden Bus Station]  
long_name: To Woden Bus Station  
between_stops:  
Athllon / Sulwood Kambah-Woden Bus Station: [Wjz2mTK, Wjz3gMq, Wjz3gQn, Wjz3gK-, Wjz3hL_, Wjz3iFK, Wjz3kwU, Wjz3kyX, Wjz3kAx]  
stop_times_saturday: [[842a, 856a, 906a, 915a], [942a, 956a, 1006a, 1015a], [1042a, 1056a, 1106a, 1115a], [1142a, 1156a, 1206p, 1215p], [1242p, 1256p, 106p, 115p], [142p, 156p, 206p, 215p], [242p, 256p, 306p, 315p], [342p, 356p, 406p, 415p], [442p, 456p, 506p, 515p], [542p, 556p, 606p, 615p], [642p, 656p, 706p, 715p], [742p, 756p, 806p, 815p], [842p, 856p, 906p, 915p], [942p, 956p, 1006p, 1015p], [1042p, 1056p, 1106p, 1115p]]  
short_name: "961"  
-  
time_points: [Gungahlin Marketplace, Nicholls Primary, Federation Square, Chuculba / William Slim Dr, Belconnen Community Bus Station, Westfield Bus Station, Cohen Street Bus Station]  
long_name: To Cohen Street Bus Station  
between_stops:  
Westfield Bus Station-Cohen Street Bus Station: []  
Belconnen Community Bus Station-Westfield Bus Station: []  
Chuculba / William Slim Dr-Belconnen Community Bus Station: [Wjz6mip, Wjz69vO, Wjz69uI, Wjz69ht, Wjz69gA]  
short_name: "952"  
stop_times_sunday: [[839a, 847a, 900a, 905a, 918a, 920a, 925a], [939a, 947a, 1000a, 1005a, 1018a, 1020a, 1025a], [1039a, 1047a, 1100a, 1105a, 1118a, 1120a, 1125a], [1139a, 1147a, 1200p, 1205p, 1218p, 1220p, 1225p], [1239p, 1247p, 100p, 105p, 118p, 120p, 125p], [139p, 147p, 200p, 205p, 218p, 220p, 225p], [239p, 247p, 300p, 305p, 318p, 320p, 325p], [339p, 347p, 400p, 405p, 418p, 420p, 425p], [439p, 447p, 500p, 505p, 518p, 520p, 525p], [539p, 547p, 600p, 605p, 618p, 620p, 625p], [639p, 647p, 700p, 705p, 718p, 720p, 725p]]  
-  
time_points: [Woden Bus Station (Platform 4), Alexander Maconochie Centre]  
long_name: To Alexander Maconochie Centre  
between_stops: {}  
   
short_name: "88"  
stop_times: [[920a, 940a], [1255p, 115p], [455p, 515p]]  
-  
time_points: [Tuggeranong Bus Station (Platform 5), Monash Goodwin Village, Erindale Centre, Wanniassa High, Athllon / Sulwood Kambah, Woden Bus Station (Platform 10), Kings Ave / National Circuit, Russell Offices, ADFA, Campbell Park Offices]  
long_name: To Campbell Park Offices  
between_stops:  
Kings Ave / National Circuit-Russell Offices: [Wjz4Quk, Wjz4RwH, Wjz4RFJ]  
Athllon / Sulwood Kambah-Woden Bus Station (Platform 10): [Wjz2mTK, Wjz3gMq, Wjz3gQn, Wjz3gK-, Wjz3hL_, Wjz3iFK, Wjz3kwU, Wjz3kyX, Wjz3kAx]  
ADFA-Campbell Park Offices: [Wjzcend, Wjzce4H, Wjzce7O]  
Russell Offices-ADFA: [Wjzc60A, Wjzc60i, Wjzc55s, Wjzc54R, Wjzce7O, Wjzce4H, Wjzcend]  
short_name: "63"  
stop_times: [[611a, 619a, 623a, 628a, 633a, 640a, "-", "-", "-", "-"], [640a, 648a, 652a, 657a, 702a, 710a, 724a, 727a, 731a, 735a], [712a, 720a, 724a, 729a, 735a, 745a, 759a, 803a, 807a, 811a], [744a, 754a, 759a, 804a, 810a, 820a, 834a, 838a, 842a, 846a], [810a, 820a, 825a, 830a, 836a, 845a, "-", "-", "-", "-"], [845a, 855a, 900a, 905a, 911a, 920a, "-", "-", "-", "-"], [945a, 954a, 958a, 1003a, 1009a, 1017a, "-", "-", "-", "-"], [1045a, 1054a, 1058a, 1103a, 1109a, 1117a, "-", "-", "-", "-"], [1145a, 1154a, 1158a, 1203p, 1209p, 1217p, "-", "-", "-", "-"], [1245p, 1254p, 1258p, 103p, 109p, 117p, "-", "-", "-", "-"], [145p, 154p, 158p, 203p, 209p, 217p, "-", "-", "-", "-"], [245p, 254p, 258p, 303p, 309p, 318p, "-", "-", "-", "-"], [314p, 324p, 329p, 334p, 340p, 349p, "-", "-", "-", "-"], [345p, 355p, 400p, 405p, 411p, 420p, "-", "-", "-", "-"], [415p, 425p, 430p, 435p, 441p, 450p, "-", "-", "-", "-"], [445p, 455p, 500p, 505p, 511p, 520p, "-", "-", "-", "-"], [515p, 525p, 530p, 535p, 541p, 550p, "-", "-", "-", "-"], [545p, 555p, 600p, 605p, 611p, 620p, "-", "-", "-", "-"], [645p, 654p, 658p, 703p, 709p, 717p, "-", "-", "-", "-"], [745p, 754p, 758p, 803p, 809p, 817p, "-", "-", "-", "-"], [845p, 854p, 858p, 903p, 909p, 917p, "-", "-", "-", "-"], [945p, 954p, 958p, 1003p, 1009p, 1017p, "-", "-", "-", "-"], [1045p, 1054p, 1058p, 1103p, 1109p, "-", "-", "-", "-", "-"], []]  
-  
time_points: [Tuggeranong Bus Station (Platform 7), Erindale Centre, Chisholm Shops, Heagney / Clift Richardson, Tuggeranong Bus Station]  
long_name: To Tuggeranong Bus Station  
between_stops: {}  
   
short_name: "967"  
stop_times_sunday: [[903a, 914a, 928a, 937a, 950a], [1103a, 1114a, 1128a, 1137a, 1150a], [103p, 114p, 128p, 137p, 150p], [303p, 314p, 328p, 337p, 350p], [503p, 514p, 528p, 537p, 550p], [703p, 714p, 728p, 737p, 750p]]  
-  
time_points: [City Bus Station (Platform 4), Macarthur / Miller O'Connor, Lyneham Shops Wattle Street, North Lyneham, Dickson Shops, Hackett Shops, Ainslie Shops, City Bus Station]  
long_name: To City Bus Station  
between_stops: {}  
   
short_name: "936"  
stop_times_sunday: [[818a, 827a, 830a, 835a, 844a, 849a, 857a, 909a], [918a, 927a, 930a, 935a, 944a, 949a, 957a, 1009a], [1018a, 1027a, 1030a, 1035a, 1044a, 1049a, 1057a, 1109a], [1118a, 1127a, 1130a, 1135a, 1144a, 1149a, 1157a, 1209p], [1218p, 1227p, 1230p, 1235p, 1244p, 1249p, 1257p, 109p], [118p, 127p, 130p, 135p, 144p, 149p, 157p, 209p], [218p, 227p, 230p, 235p, 244p, 249p, 257p, 309p], [318p, 327p, 330p, 335p, 344p, 349p, 357p, 409p], [418p, 427p, 430p, 435p, 444p, 449p, 457p, 509p], [518p, 527p, 530p, 535p, 544p, 549p, 557p, 609p], [618p, 627p, 630p, 635p, 644p, 649p, 657p, 709p], [718p, 727p, 730p, 735p, 744p, 749p, 757p, 809p]]  
-  
time_points: [Belconnen Community Bus Station (Platform 5), Westfield Bus Station (Platform 2), Cohen Street Bus Station (Platform 5), Florey Shops, Page Shops, Hawker Shops, Cook Shops, Jamison Centre, Calvary Hospital, Belconnen Community Bus Station, Westfield Bus Station, Cohen Street Bus Station]  
long_name: To Cohen Street Bus Station  
between_stops:  
Westfield Bus Station-Cohen Street Bus Station: []  
Belconnen Community Bus Station (Platform 5)-Westfield Bus Station (Platform 2): []  
Belconnen Community Bus Station-Westfield Bus Station: []  
Westfield Bus Station (Platform 2)-Cohen Street Bus Station (Platform 5): []  
short_name: "73"  
stop_times: [[916a, 918a, 922a, 927a, 933a, 937a, 944a, 947a, 954a, 1005a, 1007a, 1012a], [1046a, 1048a, 1052a, 1057a, 1103a, 1107a, 1114a, 1117a, 1124a, 1135a, 1137a, 1142a], [1216p, 1218p, 1222p, 1227p, 1233p, 1237p, 1244p, 1247p, 1254p, 105p, 107p, 112p], [146p, 148p, 152p, 157p, 203p, 207p, 214p, 217p, 224p, 235p, 237p, 242p]]  
-  
time_points: [Woden Bus Station (Platform 3), Waramanga Shops, Fisher Shops, Chapman Shops, Rivett Shops, Cooleman Court]  
long_name: To Cooleman Court  
between_stops: {}  
   
short_name: "927"  
stop_times_sunday: [[920a, 929a, 932a, 942a, 945a, 950a], [1020a, 1029a, 1032a, 1042a, 1045a, 1050a], [1120a, 1129a, 1132a, 1142a, 1145a, 1150a], [1220p, 1229p, 1232p, 1242p, 1245p, 1250p], [120p, 129p, 132p, 142p, 145p, 150p], [220p, 229p, 232p, 242p, 245p, 250p], [320p, 329p, 332p, 342p, 345p, 350p], [420p, 429p, 432p, 442p, 445p, 450p], [520p, 529p, 532p, 542p, 545p, 550p], [620p, 629p, 632p, 642p, 645p, 650p]]  
-  
time_points: [Gungahlin Marketplace, Ngunnawal Primary, Nicholls Primary, Federation Square, Chuculba / William Slim Dr, Belconnen Community Bus Station, Westfield Bus Station, Cohen Street Bus Station]  
long_name: To Cohen Street Bus Station  
between_stops:  
Westfield Bus Station-Cohen Street Bus Station: []  
Belconnen Community Bus Station-Westfield Bus Station: []  
Chuculba / William Slim Dr-Belconnen Community Bus Station: [Wjz6mip, Wjz69vO, Wjz69uI, Wjz69ht, Wjz69gA]  
stop_times_saturday: [[812a, 821a, 831a, 837a, 842a, 850a, 852a, 857a], [912a, 921a, 931a, 937a, 942a, 950a, 952a, 957a], [1012a, 1021a, 1031a, 1037a, 1042a, 1050a, 1052a, 1057a], [1112a, 1121a, 1131a, 1137a, 1142a, 1150a, 1152a, 1157a], [1212p, 1221p, 1231p, 1237p, 1242p, 1250p, 1252p, 1257p], [112p, 121p, 131p, 137p, 142p, 150p, 152p, 157p], [212p, 221p, 231p, 237p, 242p, 250p, 252p, 257p], [312p, 321p, 331p, 337p, 342p, 350p, 352p, 357p], [412p, 421p, 431p, 437p, 442p, 450p, 452p, 457p], [512p, 521p, 531p, 537p, 542p, 550p, 552p, 557p], [612p, 621p, 631p, 637p, 642p, 650p, 652p, 657p], [712p, 721p, 731p, 737p, 742p, 750p, 752p, 757p], [812p, 821p, 831p, 837p, 842p, 850p, 852p, 857p], [912p, 921p, 931p, 937p, 942p, 950p, 952p, 957p], [1012p, 1021p, 1031p, 1037p, 1042p, 1050p, 1052p, 1057p], [1112p, 1121p, 1131p, 1137p, 1142p, 1150p, 1152p, 1157p]]  
short_name: "951"  
-  
time_points: [Belconnen Community Bus Station (Platform 4), Westfield Bus Station (Platform 2), Cohen Street Bus Station (Platform 6), Kippax, Macgregor, Charnwood Shops, Fraser West Terminus, Charnwood Shops, Macgregor, Kippax, Cohen Street Bus Station, Westfield Bus Station, Belconnen Community Bus Station]  
long_name: To Belconnen Community Bus Station  
between_stops:  
Belconnen Community Bus Station (Platform 4)-Westfield Bus Station (Platform 2): []  
Westfield Bus Station-Belconnen Community Bus Station: []  
Cohen Street Bus Station-Westfield Bus Station: []  
Westfield Bus Station (Platform 2)-Cohen Street Bus Station (Platform 6): []  
short_name: "905"  
stop_times_sunday: [["-", "-", "-", "-", "-", "-", 857a, 909a, 916a, 923a, 936a, 938a, 942a], [914a, 916a, 920a, 933a, 939a, 946a, 957a, 1009a, 1016a, 1023a, 1036a, 1038a, 1042a], [1014a, 1016a, 1020a, 1033a, 1039a, 1046a, 1057a, 1109a, 1116a, 1123a, 1136a, 1138a, 1142a], [1114a, 1116a, 1120a, 1133a, 1139a, 1146a, 1157a, 1209p, 1216p, 1223p, 1236p, 1238p, 1242p], [1214p, 1216p, 1220p, 1233p, 1239p, 1246p, 1257p, 109p, 116p, 123p, 136p, 138p, 142p], [114p, 116p, 120p, 133p, 139p, 146p, 157p, 209p, 216p, 223p, 236p, 238p, 242p], [214p, 216p, 220p, 233p, 239p, 246p, 257p, 309p, 316p, 323p, 336p, 338p, 342p], [314p, 316p, 320p, 333p, 339p, 346p, 357p, 409p, 416p, 423p, 436p, 438p, 442p], [414p, 416p, 420p, 433p, 439p, 446p, 457p, 509p, 516p, 523p, 536p, 538p, 542p], [514p, 516p, 520p, 533p, 539p, 546p, 557p, 609p, 616p, 623p, 636p, 638p, 642p], [614p, 616p, 620p, 633p, 639p, 646p, 656p, "-", "-", "-", "-", "-", "-"]]  
-  
time_points: [Woden Bus Station (Platform 2), Stromlo High Waramanga, Cooleman Court]  
long_name: To Cooleman Court  
between_stops: {}  
   
short_name: "75"  
stop_times: [[1055a, 1108a, 1117a], [1255p, 108p, 117p]]  
-  
time_points: [Tuggeranong Bus Station (Platform 7), Erindale Centre, Gowrie Shops, Chisholm Shops, Gowrie Shops, Erindale Centre, Tuggeranong Bus Station]  
long_name: To Tuggeranong Bus Station  
between_stops: {}  
   
stop_times_saturday: [["-", "-", "-", 736a, 748a, 757a, 810a], [808a, 820a, 827a, 836a, 848a, 857a, 910a], [908a, 920a, 927a, 936a, 948a, 957a, 1010a], [1008a, 1020a, 1027a, 1036a, 1048a, 1057a, 1110a], [1108a, 1120a, 1127a, 1136a, 1148a, 1157a, 1210p], [1208p, 1220p, 1227p, 1236p, 1248p, 1257p, 110p], [108p, 120p, 127p, 136p, 148p, 157p, 210p], [208p, 220p, 227p, 236p, 248p, 257p, 310p], [308p, 320p, 327p, 336p, 348p, 357p, 410p], [408p, 420p, 427p, 436p, 448p, 457p, 510p], [508p, 520p, 527p, 536p, 548p, 557p, 610p], [608p, 620p, 627p, 636p, 648p, 657p, 710p], [705p, 717p, 724p, 733p, 745p, 754p, 807p], [803p, 815p, 822p, 831p, 843p, 852p, 905p], [903p, 915p, 922p, 931p, 943p, 952p, 1005p], [1003p, 1015p, 1022p, 1031p, 1043p, 1052p, 1105p], [1103p, 1115p, 1122p, 1131p, "-", "-", "-"]]  
short_name: "966"  
-  
time_points: [Woden Bus Station (Platform 14), Pearce Shops, Narrabundah College, Kingston, Kings Ave / National Circuit, Russell Offices, City Bus Station]  
long_name: To City Bus Station  
between_stops: {}  
   
short_name: "938"  
stop_times_sunday: [[800a, 808a, 818a, 833a, 837a, 841a, 849a], [900a, 908a, 918a, 933a, 937a, 941a, 949a], [1000a, 1008a, 1018a, 1033a, 1037a, 1041a, 1049a], [1100a, 1108a, 1118a, 1133a, 1137a, 1141a, 1149a], [1200p, 1208p, 1218p, 1233p, 1237p, 1241p, 1249p], [100p, 108p, 118p, 133p, 137p, 141p, 149p], [200p, 208p, 218p, 233p, 237p, 241p, 249p], [300p, 308p, 318p, 333p, 337p, 341p, 349p], [400p, 408p, 418p, 433p, 437p, 441p, 449p], [500p, 508p, 518p, 533p, 537p, 541p, 549p], [600p, 608p, 618p, 633p, 637p, 641p, 649p], [700p, 707p, 716p, 729p, 733p, 737p, 744p]]  
-  
time_points: [City West, City Bus Station (Platform 1), Woden Bus Station (Platform 5), Mount Neighbour School, Kambah High, Tuggeranong Bus Station]  
long_name: To Tuggeranong Bus Station  
between_stops:  
City Bus Station (Platform 1)-Woden Bus Station (Platform 5): [Wjz5Nht, Wjz4KO9, Wjz4KNu, Wjz3eZ4, Wjz3eRR, Wjz3m3b, Wjz3m3b]  
City West-City Bus Station (Platform 1): []  
short_name: 60 160  
stop_times: [["-", "-", 647a, 701a, 708a, 718a], ["-", "-", 717a, 731a, 739a, 750a], ["-", "-", 747a, 801a, 809a, 820a], ["-", "-", 817a, 831a, 839a, 850a], ["-", "-", 847a, 901a, 909a, 920a], ["-", "-", 947a, 1001a, 1009a, 1019a], ["-", "-", 1047a, 1101a, 1109a, 1119a], ["-", "-", 1147a, 1201p, 1209p, 1219p], ["-", "-", 1247p, 101p, 109p, 119p], ["-", "-", 147p, 201p, 209p, 219p], ["-", "-", 247p, 301p, 309p, 320p], ["-", "-", 317p, 331p, 339p, 350p], ["-", "-", 347p, 401p, 409p, 420p], ["-", "-", 417p, 431p, 439p, 450p], ["-", "-", 447p, 501p, 509p, 520p], [455p, 501p, 517p, 531p, 539p, 550p], [531p, 537p, 553p, 607p, 615p, 626p], [555p, 601p, 617p, 631p, 638p, 647p], ["-", "-", 647p, 701p, 708p, 717p], ["-", "-", 743p, 757p, 804p, 813p], ["-", "-", 843p, 857p, 904p, 913p], ["-", "-", 943p, 957p, 1004p, 1013p], ["-", "-", 1043p, 1057p, 1104p, 1113p], []]  
-  
time_points: [Woden Bus Station, Curtin, City West, City Bus Station, ACTEW AGL House]  
long_name: To ACTEW AGL House  
between_stops:  
City Bus Station-ACTEW AGL House: [Wjz5Nht]  
City West-City Bus Station: []  
short_name: "732"  
stop_times: [[715a, 724a, 738a, 742a, 744a], [748a, 803a, 815a, 819a, 821a], [818a, 827a, 841a, 845a, 847a]]  
-  
time_points: [Belconnen Community Bus Station (Platform 6), Westfield Bus Station (Platform 2), Cohen Street Bus Station (Platform 5), Higgins Shops, Kippax, Higgins Shops, Cohen Street Bus Station, Westfield Bus Station, Belconnen Community Bus Station]  
long_name: To Belconnen Community Bus Station  
between_stops:  
Belconnen Community Bus Station (Platform 6)-Westfield Bus Station (Platform 2): []  
Westfield Bus Station-Belconnen Community Bus Station: []  
Cohen Street Bus Station-Westfield Bus Station: []  
Westfield Bus Station (Platform 2)-Cohen Street Bus Station (Platform 5): []  
short_name: "904"  
stop_times: [[819a, 821a, 825a, 846a, 857a, 907a, 928a, 930a, 934a], [919a, 921a, 925a, 946a, 957a, 1007a, 1028a, 1030a, 1034a], [1019a, 1021a, 1025a, 1046a, 1057a, 1107a, 1128a, 1130a, 1134a], [1119a, 1121a, 1125a, 1146a, 1157a, 1207p, 1228p, 1230p, 1234p], [1219p, 1221p, 1225p, 1246p, 1257p, 107p, 128p, 130p, 134p], [119p, 121p, 125p, 146p, 157p, 207p, 228p, 230p, 234p], [219p, 221p, 225p, 246p, 257p, 307p, 328p, 330p, 334p], [319p, 321p, 325p, 346p, 357p, 407p, 428p, 430p, 434p], [419p, 421p, 425p, 446p, 457p, 507p, 528p, 530p, 534p], [519p, 521p, 525p, 546p, 557p, 607p, 628p, 630p, 634p], [619p, 621p, 625p, 645p, 656p, 706p, 726p, 728p, 732p]]  
-  
time_points: [City Bus Station (Platform 8), Macarthur / Northbourne Ave, Northbourne Avenue / Antill St, Bimberi Centre]  
long_name: To Bimberi Centre  
between_stops:  
City Bus Station (Platform 8)-Macarthur / Northbourne Ave: [Wjz5O3Q, Wjz5Oci, Wjz5P8n, Wjz5P8K, Wjz5PdJ, Wjz5Pl0, Wjz5Qi2, Wjz5Qgn, Wjz5Qmu, Wjz5QmR]  
short_name: "982"  
stop_times: [[342p, 348p, 350p, 400p]]  
-  
time_points: [City Bus Station (Platform 8), ADFA, Hospice / Menindee Dr, St Thomas More's Campbell, City Bus Station]  
long_name: To City Bus Station  
between_stops: {}  
   
stop_times_saturday: [[801a, 815a, 822a, 829a, 841a], [901a, 915a, 922a, 929a, 941a], [1101a, 1115a, 1122a, 1129a, 1141a], [101p, 115p, 122p, 129p, 141p], [301p, 315p, 322p, 329p, 341p], [501p, 515p, 522p, 529p, 541p], [701p, 715p, 722p, 729p, 741p]]  
short_name: "931"  
-  
time_points: [Belconnen Community Bus Station (Platform 4), Westfield Bus Station (Platform 2), Cohen Street Bus Station (Platform 6), McKellar Shops, Evatt Shops, Spence Terminus, Evatt Shops, McKellar Shops, Cohen Street Bus Station, Westfield Bus Station, Belconnen Community Bus Station]  
long_name: To Belconnen Community Bus Station  
between_stops:  
Belconnen Community Bus Station (Platform 4)-Westfield Bus Station (Platform 2): []  
Westfield Bus Station-Belconnen Community Bus Station: []  
Cohen Street Bus Station-Westfield Bus Station: []  
Westfield Bus Station (Platform 2)-Cohen Street Bus Station (Platform 6): []  
short_name: "902"  
stop_times: [[851a, 853a, 857a, 904a, 912a, 918a, 923a, 931a, 939a, 941a, 945a], [951a, 953a, 957a, 1004a, 1012a, 1018a, 1023a, 1031a, 1039a, 1041a, 1045a], [1051a, 1053a, 1057a, 1104a, 1112a, 1118a, 1123a, 1131a, 1139a, 1141a, 1145a], [1151a, 1153a, 1157a, 1204p, 1212p, 1218p, 1223p, 1231p, 1239p, 1241p, 1245p], [1251p, 1253p, 1257p, 104p, 112p, 118p, 123p, 131p, 139p, 141p, 145p], [151p, 153p, 157p, 204p, 212p, 218p, 223p, 231p, 239p, 241p, 245p], [251p, 253p, 257p, 304p, 312p, 318p, 323p, 331p, 339p, 341p, 345p], [351p, 353p, 357p, 404p, 412p, 418p, 423p, 431p, 439p, 441p, 445p], [451p, 453p, 457p, 504p, 512p, 518p, 523p, 531p, 539p, 541p, 545p], [551p, 553p, 557p, 604p, 612p, 618p, 623p, 631p, 638p, 640p, 644p], [651p, 653p, 657p, 703p, 710p, 716p, 721p, 729p, 736p, 738p, 742p]]  
-  
time_points: [Tuggeranong Bus Station (Platform 7), Bonython Primary School, Woodcock / Clare Dennis, Gordon Primary, Tharwa Drive / Knoke Ave, Conder Primary, Lanyon Market Place, Bonython Primary School, Tuggeranong Bus Station]  
long_name: To Tuggeranong Bus Station  
between_stops: {}  
   
short_name: "913"  
stop_times_sunday: [[925a, 933a, 937a, 941a, 944a, 949a, 1000a, 1005a, 1013a], [1125a, 1133a, 1137a, 1141a, 1144a, 1149a, 1200p, 1205p, 1213p], [125p, 133p, 137p, 141p, 144p, 149p, 200p, 205p, 213p], [325p, 333p, 337p, 341p, 344p, 349p, 400p, 405p, 413p], [525p, 533p, 537p, 541p, 544p, 549p, 600p, 605p, 613p], [725p, 733p, 737p, 741p, 744p, 749p, 800p, 805p, 813p]]  
- -
time_points: [City Bus Station (Platform 9), Russell Offices, Kings Ave / National Circuit, Kingston, Manuka / Captain Cook Cres, Narrabundah College, Canberra Hospital, Woden Bus Station] time_points: [City Bus Station (Platform 9), Russell Offices, Kings Ave / National Circuit, Kingston, Manuka / Captain Cook Cres, Narrabundah College, Canberra Hospital, Woden Bus Station]
long_name: To Woden Bus Station long_name: To Woden Bus Station
between_stops: between_stops:
Russell Offices-Kings Ave / National Circuit: [Wjz4RFJ, Wjz4RwH, Wjz4Quk] Russell Offices-Kings Ave / National Circuit: [Wjz4RFJ, Wjz4RwH, Wjz4Quk]
City Bus Station (Platform 9)-Russell Offices: [Wjz5Nht, Wjz5MsD, Wjz5MsT, Wjz5MEL, Wjz4T-X, Wjz4_jm, Wjz4_wS, Wjz4-KO, Wjz4-Rc, Wjz4-WL, Wjz4-WZ] City Bus Station (Platform 9)-Russell Offices: [Wjz5Nht, Wjz5MsD, Wjz5MsT, Wjz5MEL, Wjz4T-X, Wjz4_jm, Wjz4_wS, Wjz4-KO, Wjz4-Rc, Wjz4-WL, Wjz4-WZ]
Canberra Hospital-Woden Bus Station: [Wjz3mWn, Wjz3mPO, Wjz3mAg] Canberra Hospital-Woden Bus Station: [Wjz3mWn, Wjz3mPO, Wjz3mAg]
short_name: "5" short_name: "5"
stop_times: [[630a, 638a, 642a, 646a, 649a, 701a, 711a, 719a], [650a, 658a, 702a, 706a, 709a, 721a, 731a, 739a], [710a, 718a, 722a, 726a, 729a, 741a, 752a, 800a], [728a, 736a, 740a, 744a, 747a, 800a, 812a, 820a], [741a, 750a, 755a, 800a, 803a, 816a, 828a, 836a], [756a, 805a, 810a, 815a, 818a, 831a, 843a, 851a], [811a, 820a, 825a, 830a, 833a, 846a, 858a, 906a], [828a, 837a, 842a, 847a, 850a, 903a, 913a, 921a], [846a, 855a, 900a, 904a, 907a, 919a, 929a, 937a], [919a, 927a, 931a, 935a, 938a, 950a, 1000a, 1008a], [947a, 955a, 959a, 1003a, 1006a, 1018a, 1028a, 1036a], [1017a, 1025a, 1029a, 1033a, 1036a, 1048a, 1058a, 1106a], [1047a, 1055a, 1059a, 1103a, 1106a, 1118a, 1128a, 1136a], [1117a, 1125a, 1129a, 1133a, 1136a, 1148a, 1158a, 1206p], [1147a, 1155a, 1159a, 1203p, 1206p, 1218p, 1228p, 1236p], [1217p, 1225p, 1229p, 1233p, 1236p, 1248p, 1258p, 106p], [1247p, 1255p, 1259p, 103p, 106p, 118p, 128p, 136p], [117p, 125p, 129p, 133p, 136p, 148p, 158p, 206p], [147p, 155p, 159p, 203p, 206p, 218p, 228p, 236p], [217p, 225p, 229p, 233p, 236p, 248p, 258p, 306p], [247p, 255p, 259p, 303p, 306p, 318p, 328p, 336p], [317p, 325p, 329p, 333p, 336p, 348p, 358p, 411p], [347p, 355p, 359p, 404p, 407p, 420p, 432p, 440p], [417p, 426p, 431p, 436p, 439p, 452p, 504p, 512p], [444p, 453p, 458p, 503p, 506p, 519p, 531p, 539p], [524p, 533p, 538p, 543p, 546p, 559p, 608p, 616p], [554p, 603p, 607p, 611p, 614p, 626p, 635p, 643p], [635p, 643p, 647p, 651p, 654p, 706p, 715p, 723p], [706p, 714p, 718p, 722p, 725p, 737p, 746p, 754p], [735p, 743p, 747p, 751p, 754p, 806p, 815p, 823p], [835p, 843p, 847p, 851p, 854p, 906p, 915p, 923p], [930p, 938p, 942p, 946p, 949p, 1001p, 1010p, 1018p], [1030p, 1038p, 1042p, 1046p, 1049p, 1101p, 1110p, 1118p]] stop_times: [[630a, 638a, 642a, 646a, 649a, 701a, 711a, 719a], [650a, 658a, 702a, 706a, 709a, 721a, 731a, 739a], [710a, 718a, 722a, 726a, 729a, 741a, 752a, 800a], [728a, 736a, 740a, 744a, 747a, 800a, 812a, 820a], [741a, 750a, 755a, 800a, 803a, 816a, 828a, 836a], [756a, 805a, 810a, 815a, 818a, 831a, 843a, 851a], [811a, 820a, 825a, 830a, 833a, 846a, 858a, 906a], [828a, 837a, 842a, 847a, 850a, 903a, 913a, 921a], [846a, 855a, 900a, 904a, 907a, 919a, 929a, 937a], [919a, 927a, 931a, 935a, 938a, 950a, 1000a, 1008a], [947a, 955a, 959a, 1003a, 1006a, 1018a, 1028a, 1036a], [1017a, 1025a, 1029a, 1033a, 1036a, 1048a, 1058a, 1106a], [1047a, 1055a, 1059a, 1103a, 1106a, 1118a, 1128a, 1136a], [1117a, 1125a, 1129a, 1133a, 1136a, 1148a, 1158a, 1206p], [1147a, 1155a, 1159a, 1203p, 1206p, 1218p, 1228p, 1236p], [1217p, 1225p, 1229p, 1233p, 1236p, 1248p, 1258p, 106p], [1247p, 1255p, 1259p, 103p, 106p, 118p, 128p, 136p], [117p, 125p, 129p, 133p, 136p, 148p, 158p, 206p], [147p, 155p, 159p, 203p, 206p, 218p, 228p, 236p], [217p, 225p, 229p, 233p, 236p, 248p, 258p, 306p], [247p, 255p, 259p, 303p, 306p, 318p, 328p, 336p], [317p, 325p, 329p, 333p, 336p, 348p, 358p, 411p], [347p, 355p, 359p, 404p, 407p, 420p, 432p, 440p], [417p, 426p, 431p, 436p, 439p, 452p, 504p, 512p], [444p, 453p, 458p, 503p, 506p, 519p, 531p, 539p], [524p, 533p, 538p, 543p, 546p, 559p, 608p, 616p], [554p, 603p, 607p, 611p, 614p, 626p, 635p, 643p], [635p, 643p, 647p, 651p, 654p, 706p, 715p, 723p], [706p, 714p, 718p, 722p, 725p, 737p, 746p, 754p], [735p, 743p, 747p, 751p, 754p, 806p, 815p, 823p], [835p, 843p, 847p, 851p, 854p, 906p, 915p, 923p], [930p, 938p, 942p, 946p, 949p, 1001p, 1010p, 1018p], [1030p, 1038p, 1042p, 1046p, 1049p, 1101p, 1110p, 1118p]]
-  
time_points: [Tuggeranong Bus Station (Platform 3), Erindale Centre, Athllon / Sulwood Kambah, Woden Bus Station]  
long_name: To Woden Bus Station  
between_stops:  
Athllon / Sulwood Kambah-Woden Bus Station: [Wjz2mTK, Wjz3gMq, Wjz3gQn, Wjz3gK-, Wjz3hL_, Wjz3iFK, Wjz3kwU, Wjz3kyX, Wjz3kAx]  
short_name: "961"  
stop_times: [[942a, 956a, 1006a, 1015a], [1042a, 1056a, 1106a, 1115a], [1142a, 1156a, 1206p, 1215p], [1242p, 1256p, 106p, 115p], [142p, 156p, 206p, 215p], [242p, 256p, 306p, 315p], [342p, 356p, 406p, 415p], [442p, 456p, 506p, 515p], [542p, 556p, 606p, 615p]]  
-  
time_points: [Cohen Street Bus Station (Platform 1), Westfield Bus Station (Platform 1), Belconnen Community Bus Station (Platform 2), Chuculba / William Slim Dr, Federation Square, Nicholls Primary, Gungahlin Marketplace]  
long_name: To Gungahlin Market Place  
between_stops:  
Belconnen Community Bus Station (Platform 2)-Chuculba / William Slim Dr: [Wjz69gA, Wjz69ht, Wjz69uI, Wjz69vO, Wjz6mip]  
Cohen Street Bus Station (Platform 1)-Westfield Bus Station (Platform 1): []  
Westfield Bus Station (Platform 1)-Belconnen Community Bus Station (Platform 2): []  
stop_times_saturday: [[0945a, 0947a, 0951a, 1004a, 1009a, 1022a, 1031a], [1045a, 1047a, 1051a, 1104a, 1109a, 1122a, 1131a], [1145a, 1147a, 1151a, 1204p, 1209p, 1222p, 1231p], [1245p, 1247p, 1251p, 0104p, 0109p, 0122p, 0131p], [0145p, 0147p, 0151p, 0204p, 0209p, 0222p, 0231p], [0245p, 0247p, 0251p, 0304p, 0309p, 0322p, 0331p], [0345p, 0347p, 0351p, 0404p, 0409p, 0422p, 0431p], [0445p, 0447p, 0451p, 0504p, 0509p, 0522p, 0531p], [0545p, 0547p, 0551p, 0604p, 0609p, 0622p, 0631p], [0645p, 0647p, 0651p, 0704p, 0709p, 0722p, 0731p]]  
short_name: "952"  
-  
time_points: [Cohen Street Bus Station (Platform 1), Westfield Bus Station (Platform 1), Belconnen Community Bus Station (Platform 3), Gwydir Square Kaleen, Kaleen Village / Marybrynong, Giralang, Kaleen Village / Marybrynong, Gwydir Square Kaleen, Belconnen Community Bus Station, Westfield Bus Station, Cohen Street Bus Station]  
long_name: To Cohen Street Bus Station  
between_stops:  
Westfield Bus Station-Cohen Street Bus Station: []  
Belconnen Community Bus Station-Westfield Bus Station: []  
Westfield Bus Station (Platform 1)-Belconnen Community Bus Station (Platform 3): []  
Cohen Street Bus Station (Platform 1)-Westfield Bus Station (Platform 1): []  
short_name: "71"  
stop_times: [[927a, 929a, 933a, 943a, 948a, 957a, 959a, 1004a, 1014a, 1016a, 1021a], [1027a, 1029a, 1033a, 1043a, 1048a, 1057a, 1059a, 1104a, 1114a, 1116a, 1121a], [1127a, 1129a, 1133a, 1143a, 1148a, 1157a, 1159a, 1204p, 1214p, 1216p, 1221p], [1227p, 1229p, 1233p, 1243p, 1248p, 1257p, 1259p, 104p, 114p, 116p, 121p], [127p, 129p, 133p, 143p, 148p, 157p, 159p, 204p, 214p, 216p, 221p]]  
-  
time_points: [Erindale Dr / Charleston St Monash, Gowrie, Erindale / Sternberg Cres, Woden Bus Station (Platform 9), City Bus Station, City West]  
long_name: To City West  
between_stops:  
City Bus Station-City West: []  
Woden Bus Station (Platform 9)-City Bus Station: [Wjz3m3b, Wjz3m3b, Wjz3eRR, Wjz3eZ4, Wjz4KNu, Wjz4KO9, Wjz5Nht]  
short_name: "170"  
stop_times: [[710a, 720a, 732a, 749a, 804a, 806a], [728a, 738a, 750a, 807a, 822a, 824a]]  
-  
time_points: [Woden Bus Station (Platform 4), Curtin Shops, John James Hospital, Yarralumla Shops, City Bus Station (Platform 8), Macarthur / Northbourne Ave, Southwell Park, Giralang Shops, Kaleen Village / Marybrynong, Gwydir Square Kaleen, University of Canberra, Belconnen Community Bus Station, Westfield Bus Station, Cohen Street Bus Station]  
long_name: To Cohen Street Bus Station  
between_stops:  
Westfield Bus Station-Cohen Street Bus Station: []  
Belconnen Community Bus Station-Westfield Bus Station: []  
University of Canberra-Belconnen Community Bus Station: [Wjz68Yy, Wjz68Y0, Wjz68IH, Wjz68Ip, Wjz689c, Wjz681S]  
short_name: "932"  
stop_times_sunday: [[839a, 850a, 853a, 856a, 909a, 915a, 919a, 928a, 936a, 941a, 947a, 950a, 952a, 957a], [939a, 950a, 953a, 956a, 1009a, 1015a, 1019a, 1028a, 1036a, 1041a, 1047a, 1050a, 1052a, 1057a], [1039a, 1050a, 1053a, 1056a, 1109a, 1115a, 1119a, 1128a, 1136a, 1141a, 1147a, 1150a, 1152a, 1157a], [1139a, 1150a, 1153a, 1156a, 1209p, 1215p, 1219p, 1228p, 1236p, 1241p, 1247p, 1250p, 1252p, 1257p], [1239p, 1250p, 1253p, 1256p, 109p, 115p, 119p, 128p, 136p, 141p, 147p, 150p, 152p, 157p], [139p, 150p, 153p, 156p, 209p, 215p, 219p, 228p, 236p, 241p, 247p, 250p, 252p, 257p], [239p, 250p, 253p, 256p, 309p, 315p, 319p, 328p, 336p, 341p, 347p, 350p, 352p, 357p], [339p, 350p, 353p, 356p, 409p, 415p, 419p, 428p, 436p, 441p, 447p, 450p, 452p, 457p], [439p, 450p, 453p, 456p, 509p, 515p, 519p, 528p, 536p, 541p, 547p, 550p, 552p, 557p], [539p, 550p, 553p, 556p, 609p, 615p, 619p, 628p, 635p, 640p, 645p, 648p, 650p, 655p], [639p, 648p, 651p, 654p, 707p, 712p, 716p, 725p, 732p, 737p, 742p, 745p, 747p, 752p]]  
-  
time_points: [Gungahlin Marketplace, Nicholls Primary, Federation Square, Chuculba / William Slim Dr, Belconnen Community Bus Station, Westfield Bus Station, Cohen Street Bus Station]  
long_name: To Cohen Street Bus Station  
between_stops:  
Westfield Bus Station-Cohen Street Bus Station: []  
Belconnen Community Bus Station-Westfield Bus Station: []  
Chuculba / William Slim Dr-Belconnen Community Bus Station: [Wjz6mip, Wjz69vO, Wjz69uI, Wjz69ht, Wjz69gA]  
short_name: "952"  
stop_times: [[839a, 847a, 900a, 905a, 918a, 920a, 925a], [939a, 947a, 1000a, 1005a, 1018a, 1020a, 1025a], [1039a, 1047a, 1100a, 1105a, 1118a, 1120a, 1125a], [1139a, 1147a, 1200p, 1205p, 1218p, 1220p, 1225p], [1239p, 1247p, 100p, 105p, 118p, 120p, 125p], [139p, 147p, 200p, 205p, 218p, 220p, 225p], [239p, 247p, 300p, 305p, 318p, 320p, 325p], [339p, 347p, 400p, 405p, 418p, 420p, 425p], [439p, 447p, 500p, 505p, 518p, 520p, 525p], [539p, 547p, 600p, 605p, 618p, 620p, 625p], [639p, 647p, 700p, 705p, 718p, 720p, 725p]]  
-  
time_points: [Tuggeranong Bus Station (Platform 5), Erindale Centre, Athllon / Sulwood Kambah, Woden Bus Station]  
long_name: To Woden Bus Station  
between_stops:  
Athllon / Sulwood Kambah-Woden Bus Station: [Wjz2mTK, Wjz3gMq, Wjz3gQn, Wjz3gK-, Wjz3hL_, Wjz3iFK, Wjz3kwU, Wjz3kyX, Wjz3kAx]  
short_name: "964"  
stop_times_sunday: [[925a, 937a, 949a, 958a], [1025a, 1037a, 1049a, 1058a], [1125a, 1137a, 1149a, 1158a], [1225p, 1237p, 1249p, 1258p], [125p, 137p, 149p, 158p], [225p, 237p, 249p, 258p], [325p, 337p, 349p, 358p], [425p, 437p, 449p, 458p], [525p, 537p, 549p, 558p], [625p, 637p, 649p, 658p]]  
-  
time_points: [Spence Terminus, Spence Shops, Copland College, William Webb / Ginninderra Drive, Northbourne Avenue / Antill St, Macarthur / Northbourne Ave, City Bus Station (Platform 10), Russell Offices, National Circ / Canberra Ave]  
long_name: To National Circ / Canberra Ave  
between_stops:  
Northbourne Avenue / Antill St-Macarthur / Northbourne Ave: [Wjz5SDc, Wjz5Sux, Wjz5SrO, Wjz5Sqk, Wjz5RvC, Wjz5Rsi, Wjz5RkN]  
Macarthur / Northbourne Ave-City Bus Station (Platform 10): [Wjz5QmR, Wjz5Qmu, Wjz5Qgn, Wjz5Qi2, Wjz5Pl0, Wjz5PdJ, Wjz5P8K, Wjz5P8n, Wjz5Oci, Wjz5O3Q]  
City Bus Station (Platform 10)-Russell Offices: [Wjz5Nht, Wjz5MsD, Wjz5MsT, Wjz5MEL, Wjz4T-X, Wjz4_jm, Wjz4_wS, Wjz4-KO, Wjz4-Rc, Wjz4-WL, Wjz4-WZ]  
short_name: "701"  
stop_times: [[658a, 703a, 710a, 714a, 724a, 726a, 737a, 746a, 754a], [731a, 736a, 743a, 747a, 805a, 810a, 826a, 835a, 843a], [745a, 750a, 757a, 801a, 819a, 824a, 840a, 849a, 857a]]  
-  
time_points: [Woden Bus Station (Platform 15), Canberra Hospital, Isaacs Shops, Farrer Primary School, Southlands Mawson, Woden Bus Station]  
long_name: To Woden Bus Station  
between_stops: {}  
   
stop_times_saturday: [[910a, 916a, 921a, 927a, 933a, 943a], [1110a, 1116a, 1121a, 1127a, 1133a, 1143a], [110p, 116p, 121p, 127p, 133p, 143p], [310p, 316p, 321p, 327p, 333p, 343p], [510p, 516p, 521p, 527p, 533p, 543p], [713p, 718p, 723p, 728p, 734p, 743p], [913p, 918p, 923p, 928p, 934p, 943p], [1113p, 1118p, 1123p, 1128p, 1134p, 1143p]]  
short_name: "923"  
-  
time_points: [Tuggeranong Bus Station (Platform 3), Kambah High, Mount Neighbour School, Woden Bus Station]  
long_name: To Woden Bus Station  
between_stops: {}  
   
stop_times_saturday: [[755a, 805a, 811a, 823a], [855a, 905a, 911a, 923a], [955a, 1005a, 1011a, 1023a], [1055a, 1105a, 1111a, 1123a], [1155a, 1205p, 1211p, 1223p], [1255p, 105p, 111p, 123p], [155p, 205p, 211p, 223p], [255p, 305p, 311p, 323p], [355p, 405p, 411p, 423p], [455p, 505p, 511p, 523p], [555p, 605p, 611p, 623p], [655p, 705p, 711p, 721p], [755p, 804p, 810p, 820p], [855p, 904p, 910p, 920p], [955p, 1004p, 1010p, 1020p], [1055p, 1104p, 1110p, 1120p]]  
short_name: "960"  
-  
time_points: [Cooleman Court, Rivett Shops, Fisher Shops, Waramanga Shops, Woden Bus Station (Platform 10), Kings Ave / National Circuit, Russell Offices, ADFA, Campbell Park Offices]  
long_name: To Campbell Park Offices  
between_stops:  
Kings Ave / National Circuit-Russell Offices: [Wjz4Quk, Wjz4RwH, Wjz4RFJ]  
ADFA-Campbell Park Offices: [Wjzcend, Wjzce4H, Wjzce7O]  
Russell Offices-ADFA: [Wjzc60A, Wjzc60i, Wjzc55s, Wjzc54R, Wjzce7O, Wjzce4H, Wjzcend]  
short_name: 27 227  
stop_times: [[629a, 635a, 643a, 647a, 655a, 709a, 712a, 716a, 720a], [654a, 700a, 708a, 712a, 720a, 734a, 738a, 742a, 746a], ["-", "-", 728a, 735a, 746a, "-", "-", "-", "-"], [722a, 728a, 736a, 743a, 755a, 809a, 813a, 817a, 821a], [740a, 746a, 754a, 801a, 812a, "-", "-", "-", "-"], [748a, 754a, 802a, 809a, 820a, "-", "-", "-", "-"], [823a, 829a, 837a, 844a, 855a, "-", "-", "-", "-"], [853a, 859a, 907a, 914a, 925a, "-", "-", "-", "-"], [925a, 931a, 938a, 942a, 949a, "-", "-", "-", "-"], [1025a, 1031a, 1038a, 1042a, 1049a, "-", "-", "-", "-"], [1125a, 1131a, 1138a, 1142a, 1149a, "-", "-", "-", "-"], [1225p, 1231p, 1238p, 1242p, 1249p, "-", "-", "-", "-"], [125p, 131p, 138p, 142p, 149p, "-", "-", "-", "-"], [225p, 231p, 238p, 242p, 249p, "-", "-", "-", "-"], [325p, 330p, 337p, 341p, 349p, "-", "-", "-", "-"], [355p, 400p, 407p, 411p, 419p, "-", "-", "-", "-"], [425p, 430p, 437p, 441p, 449p, "-", "-", "-", "-"], [525p, 530p, 537p, 541p, 549p, "-", "-", "-", "-"], [625p, 630p, 637p, 640p, 647p, "-", "-", "-", "-"], [700p, 705p, 712p, 715p, 722p, "-", "-", "-", "-"], [800p, 805p, 812p, 815p, 822p, "-", "-", "-", "-"], [900p, 905p, 912p, 915p, 922p, "-", "-", "-", "-"], [1000p, 1005p, 1012p, 1015p, 1022p, "-", "-", "-", "-"]]  
-  
time_points: [Tuggeranong Bus Station (Platform 3), Kambah High, Mount Neighbour School, Woden Bus Station (Platform 9), City Bus Station, City West]  
long_name: To City West  
between_stops:  
City Bus Station-City West: []  
Woden Bus Station (Platform 9)-City Bus Station: [Wjz3m3b, Wjz3m3b, Wjz3eRR, Wjz3eZ4, Wjz4KNu, Wjz4KO9, Wjz5Nht]  
short_name: 60 160  
stop_times: [[606a, 615a, 621a, 632a, "-", "-"], [706a, 715a, 721a, 734a, 749a, 752a], [730a, 740a, 748a, 802a, "-", "-"], [738a, 748a, 756a, 811a, 826a, 829a], [752a, 802a, 810a, 824a, "-", "-"], [808a, 818a, 826a, 841a, 856a, 859a], [836a, 846a, 854a, 908a, "-", "-"], [906a, 916a, 924a, 937a, "-", "-"], [1006a, 1016a, 1024a, 1036a, "-", "-"], [1106a, 1116a, 1124a, 1136a, "-", "-"], [1206p, 1216p, 1224p, 1236p, "-", "-"], [106p, 116p, 124p, 136p, "-", "-"], [206p, 216p, 224p, 236p, "-", "-"], [236p, 246p, 254p, 307p, "-", "-"], [306p, 316p, 324p, 338p, "-", "-"], [336p, 346p, 354p, 408p, "-", "-"], [406p, 416p, 424p, 438p, "-", "-"], [436p, 446p, 454p, 508p, "-", "-"], [506p, 516p, 524p, 538p, "-", "-"], [536p, 546p, 554p, 608p, "-", "-"], [606p, 616p, 624p, 637p, "-", "-"], [706p, 716p, 722p, 734p, "-", "-"], [806p, 816p, 822p, 834p, "-", "-"], [906p, 916p, 922p, 934p, "-", "-"], [1006p, 1016p, 1022p, 1034p, "-", "-"], [1106p, 1116p, 1122p, 1134p, "-", "-"]]  
-  
time_points: [Woden Bus Station (Platform 14), Canberra Hospital, Narrabundah College, Manuka / Captain Cook Cres, Kingston, Kings Ave / National Circuit, Russell Offices, City Bus Station]  
long_name: To City Bus Station  
between_stops:  
Kings Ave / National Circuit-Russell Offices: [Wjz4Quk, Wjz4RwH, Wjz4RFJ]  
Woden Bus Station (Platform 14)-Canberra Hospital: [Wjz3mAg, Wjz3mPO, Wjz3mWn]  
Russell Offices-City Bus Station: [Wjz4-WZ, Wjz4-WL, Wjz4-Rc, Wjz4-KO, Wjz4_wS, Wjz4_jm, Wjz4T-X, Wjz5MEL, Wjz5MsT, Wjz5MsD, Wjz5Nht]  
short_name: "5"  
stop_times: [[615a, 621a, 630a, 636a, 640a, 644a, 648a, 653a], [641a, 649a, 659a, 711a, 714a, 718a, 722a, 730a], [659a, 707a, 717a, 729a, 732a, 738a, 743a, 752a], [713a, 721a, 731a, 744a, 747a, 753a, 758a, 807a], [729a, 737a, 747a, 800a, 803a, 809a, 814a, 823a], [747a, 755a, 805a, 818a, 821a, 827a, 832a, 841a], [806a, 814a, 824a, 837a, 840a, 846a, 851a, 900a], [825a, 833a, 843a, 856a, 859a, 905a, 910a, 919a], [844a, 852a, 902a, 915a, 918a, 924a, 929a, 937a], [914a, 922a, 932a, 944a, 947a, 951a, 955a, 1003a], [944a, 952a, 1002a, 1014a, 1017a, 1021a, 1025a, 1033a], [1014a, 1022a, 1032a, 1044a, 1047a, 1051a, 1055a, 1103a], [1044a, 1052a, 1102a, 1114a, 1117a, 1121a, 1125a, 1133a], [1114a, 1122a, 1132a, 1144a, 1147a, 1151a, 1155a, 1203p], [1144a, 1152a, 1202p, 1214p, 1217p, 1221p, 1225p, 1233p], [1214p, 1222p, 1232p, 1244p, 1247p, 1251p, 1255p, 103p], [1244p, 1252p, 102p, 114p, 117p, 121p, 125p, 133p], [114p, 122p, 132p, 144p, 147p, 151p, 155p, 203p], [144p, 152p, 202p, 214p, 217p, 221p, 225p, 233p], [214p, 222p, 232p, 244p, 247p, 251p, 255p, 303p], [244p, 252p, 302p, 315p, 318p, 324p, 329p, 338p], [314p, 322p, 332p, 345p, 348p, 354p, 359p, 408p], [342p, 350p, 400p, 413p, 416p, 422p, 427p, 436p], [413p, 421p, 431p, 444p, 447p, 453p, 458p, 507p], [447p, 455p, 505p, 518p, 521p, 527p, 532p, 541p], [518p, 526p, 536p, 549p, 552p, 558p, 603p, 612p], [548p, 556p, 606p, 619p, 622p, 628p, 632p, 640p], [648p, 655p, 704p, 716p, 719p, 723p, 727p, 735p], [748p, 755p, 804p, 816p, 819p, 823p, 827p, 835p], [848p, 855p, 904p, 916p, 919p, 923p, 927p, 935p], [948p, 955p, 1004p, 1016p, 1019p, 1023p, 1027p, 1035p], [1048p, 1055p, 1104p, 1116p, 1119p, 1123p, 1127p, 1135p]]  
-  
time_points: [Woden Bus Station (Platform 16), Weston Primary, Holder, Duffy, Cooleman Court]  
long_name: To Cooleman Court  
between_stops: {}  
   
short_name: "925"  
stop_times: [[957a, 1007a, 1009a, 1011a, 1019a], [1057a, 1107a, 1109a, 1111a, 1119a], [1157a, 1207p, 1209p, 1211p, 1219p], [1257p, 107p, 109p, 111p, 119p], [157p, 207p, 209p, 211p, 219p], [257p, 307p, 309p, 311p, 319p], [357p, 407p, 409p, 411p, 419p], [457p, 507p, 509p, 511p, 519p], [557p, 607p, 609p, 611p, 619p], [657p, 707p, 709p, 711p, 719p]]  
-  
time_points: [Cohen Street Bus Station (Platform 2), Westfield Bus Station (Platform 1), Belconnen Community Bus Station (Platform 2), Woden Bus Station]  
long_name: To Woden Bus Station  
between_stops:  
Westfield Bus Station (Platform 1)-Belconnen Community Bus Station (Platform 2): []  
Cohen Street Bus Station (Platform 2)-Westfield Bus Station (Platform 1): []  
short_name: "749"  
stop_times: [[659a, 701a, 705a, 730a], [734a, 736a, 740a, 810a], [804a, 806a, 810a, 840a], [456p, 458p, 502p, 535p]]  
-  
time_points: [Cohen Street Bus Station (Platform 2), Westfield Bus Station (Platform 1), Belconnen Community Bus Station (Platform 3), University of Canberra, Gwydir Square Kaleen, Kaleen Village / Marybrynong, Giralang Shops, Southwell Park, Macarthur / Northbourne Ave, City Bus Station (Platform 9), Yarralumla Shops, John James Hospital, Curtin Shops, Woden Bus Station]  
long_name: To Woden Bus Station  
between_stops:  
Westfield Bus Station (Platform 1)-Belconnen Community Bus Station (Platform 3): []  
Cohen Street Bus Station (Platform 2)-Westfield Bus Station (Platform 1): []  
Belconnen Community Bus Station (Platform 3)-University of Canberra: [Wjz681S, Wjz689c, Wjz68Ip, Wjz68IH, Wjz68Y0, Wjz68Yy]  
short_name: "932"  
stop_times: [[746a, 748a, 752a, 757a, 803a, 808a, 810a, 825a, 830a, 838a, 850a, 853a, 857a, 908a], [846a, 848a, 852a, 857a, 903a, 908a, 910a, 925a, 930a, 938a, 950a, 953a, 957a, 1008a], [946a, 948a, 952a, 957a, 1003a, 1008a, 1010a, 1025a, 1030a, 1038a, 1050a, 1053a, 1057a, 1108a], [1046a, 1048a, 1052a, 1057a, 1103a, 1108a, 1110a, 1125a, 1130a, 1138a, 1150a, 1153a, 1157a, 1208p], [1146a, 1148a, 1152a, 1157a, 1203p, 1208p, 1210p, 1225p, 1230p, 1238p, 1250p, 1253p, 1257p, 108p], [1246p, 1248p, 1252p, 1257p, 103p, 108p, 110p, 125p, 130p, 138p, 150p, 153p, 157p, 208p], [146p, 148p, 152p, 157p, 203p, 208p, 210p, 225p, 230p, 238p, 250p, 253p, 257p, 308p], [246p, 248p, 252p, 257p, 303p, 308p, 310p, 325p, 330p, 338p, 350p, 353p, 357p, 408p], [346p, 348p, 352p, 357p, 403p, 408p, 410p, 425p, 430p, 438p, 450p, 453p, 457p, 508p], [446p, 448p, 452p, 457p, 503p, 508p, 510p, 525p, 530p, 538p, 550p, 553p, 557p, 608p], [546p, 548p, 552p, 557p, 603p, 608p, 610p, 625p, 630p, 637p, 649p, 652p, 655p, 705p]]  
-  
time_points: [Cohen Street Bus Station (Platform 2), Westfield Bus Station (Platform 1), Belconnen Community Bus Station (Platform 2), William Webb / Ginninderra Drive, Chuculba / William Slim Dr, Gungahlin Marketplace, Kosciuszko / Everard, Flemington Rd / Sandford St, Macarthur / Northbourne Ave, City Bus Station]  
long_name: To City Bus Station  
between_stops:  
Macarthur / Northbourne Ave-City Bus Station: [Wjz5QmR, Wjz5Qmu, Wjz5Qgn, Wjz5Qi2, Wjz5Pl0, Wjz5PdJ, Wjz5P8K, Wjz5P8n, Wjz5Oci, Wjz5O3Q]  
Westfield Bus Station (Platform 1)-Belconnen Community Bus Station (Platform 2): []  
Cohen Street Bus Station (Platform 2)-Westfield Bus Station (Platform 1): []  
stop_times_saturday: [[841a, 843a, 847a, 853a, 858a, 908a, 918a, 925a, 933a, 940a], [941a, 943a, 947a, 953a, 958a, 1008a, 1018a, 1025a, 1033a, 1040a], [1041a, 1043a, 1047a, 1053a, 1058a, 1108a, 1118a, 1125a, 1133a, 1140a], [1141a, 1143a, 1147a, 1153a, 1158a, 1208p, 1218p, 1225p, 1233p, 1240p], [1241p, 1243p, 1247p, 1253p, 1258p, 108p, 118p, 125p, 133p, 140p], [141p, 143p, 147p, 153p, 158p, 208p, 218p, 225p, 233p, 240p], [241p, 243p, 247p, 253p, 258p, 308p, 318p, 325p, 333p, 340p], [341p, 343p, 347p, 353p, 358p, 408p, 418p, 425p, 433p, 440p], [441p, 443p, 447p, 453p, 458p, 508p, 518p, 525p, 533p, 540p], [541p, 543p, 547p, 553p, 558p, 608p, 618p, 625p, 633p, 640p], [641p, 643p, 647p, 653p, 658p, 708p, 718p, 725p, 733p, 740p]]  
short_name: "956"  
-  
time_points: [City Bus Station (Platform 1), Woden Bus Station (Platform 11), Erindale Centre, Calwell Shops, Theodore, MacKillop College Isabella Campus, Tuggeranong Bus Station]  
long_name: To Tuggeranong Bus Station  
between_stops:  
City Bus Station (Platform 1)-Woden Bus Station (Platform 11): [Wjz5Nht, Wjz4KO9, Wjz4KNu, Wjz3eZ4, Wjz3eRR, Wjz3m3b, Wjz3m3b]  
short_name: 11 111  
stop_times: [["-", "-", "-", 546a, 556a, 609a, 616a], ["-", "-", "-", 606a, 616a, 629a, 636a], ["-", "-", "-", 626a, 636a, 649a, 656a], ["-", "-", "-", 646a, 656a, 709a, 716a], ["-", "-", "-", 706a, 716a, 729a, 736a], ["-", "-", "-", 725a, 735a, 749a, 756a], ["-", "-", "-", 745a, 755a, 809a, 816a], ["-", "-", "-", 805a, 815a, 829a, 836a], ["-", "-", "-", 825a, 835a, 849a, 856a], ["-", "-", "-", 845a, 855a, 909a, 916a], ["-", "-", "-", 917a, 927a, 940a, 946a], ["-", 930a, 942a, 948a, 957a, 1010a, 1016a], ["-", 1000a, 1012a, 1018a, 1027a, 1040a, 1046a], ["-", 1030a, 1042a, 1048a, 1057a, 1110a, 1116a], ["-", 1100a, 1112a, 1118a, 1127a, 1140a, 1146a], ["-", 1130a, 1142a, 1148a, 1157a, 1210p, 1216p], ["-", 1200p, 1212p, 1218p, 1227p, 1240p, 1246p], ["-", 1230p, 1242p, 1248p, 1257p, 110p, 116p], ["-", 100p, 112p, 118p, 127p, 140p, 146p], ["-", 130p, 142p, 148p, 157p, 210p, 216p], ["-", 200p, 212p, 218p, 227p, 240p, 246p], ["-", 230p, 242p, 248p, 257p, 311p, 318p], ["-", 300p, 314p, 321p, 331p, 345p, 352p], ["-", 320p, 334p, 341p, 351p, 405p, 412p], ["-", 340p, 354p, 401p, 411p, 425p, 432p], ["-", 400p, 414p, 421p, 431p, 445p, 452p], ["-", 425p, 439p, 446p, 456p, 510p, 517p], ["-", 440p, 454p, 501p, 511p, 525p, 532p], ["-", 500p, 514p, 521p, 531p, 545p, 552p], [456p, 513p, 527p, 534p, 544p, 558p, 605p], [516p, 533p, 547p, 554p, 604p, 618p, 625p], [534p, 551p, 605p, 612p, 622p, 636p, 641p], [556p, 613p, 627p, 633p, 642p, 655p, 701p], [616p, 633p, 645p, 651p, 700p, 713p, 719p], ["-", 733p, 745p, 751p, 800p, 813p, 819p], ["-", 833p, 845p, 851p, 900p, 913p, 919p], ["-", 933p, 945p, 951p, 1000p, 1013p, 1019p], ["-", 1033p, 1045p, 1051p, 1100p, 1113p, 1119p]]  
-  
time_points: [City West, City Bus Station (Platform 1), Woden Bus Station (Platform 5), Kambah Village, Kambah High, Tuggeranong Bus Station]  
long_name: To Tuggeranong Bus Station  
between_stops:  
City Bus Station (Platform 1)-Woden Bus Station (Platform 5): [Wjz5Nht, Wjz4KO9, Wjz4KNu, Wjz3eZ4, Wjz3eRR, Wjz3m3b, Wjz3m3b]  
City West-City Bus Station (Platform 1): []  
short_name: "62"  
stop_times: [["-", "-", "-", 709a, 716a, 723a], ["-", "-", 732a, 744a, 753a, 800a], ["-", "-", 802a, 814a, 823a, 830a], ["-", "-", 832a, 844a, 853a, 900a], ["-", "-", 902a, 914a, 923a, 930a], ["-", "-", 932a, 943a, 951a, 958a], ["-", "-", 1032a, 1043a, 1051a, 1058a], ["-", "-", 1132a, 1143a, 1151a, 1158a], ["-", "-", 1232p, 1243p, 1251p, 1258p], ["-", "-", 132p, 143p, 151p, 158p], ["-", "-", 232p, 243p, 251p, 258p], ["-", "-", 332p, 344p, 353p, 400p], ["-", "-", 402p, 414p, 423p, 430p], ["-", "-", 432p, 444p, 453p, 500p], ["-", "-", 502p, 514p, 523p, 530p], [510p, 516p, 532p, 544p, 553p, 600p], [540p, 546p, 602p, 614p, 623p, 630p], [610p, 616p, 632p, 643p, 651p, 658p], ["-", "-", 732p, 743p, 751p, 758p], ["-", "-", 832p, 843p, 851p, 858p], ["-", "-", 932p, 943p, 951p, 958p], ["-", "-", 1032p, 1043p, 1051p, 1058p], ["-", "-", 1132p, 1143p, 1151p, 1158p]]  
-  
time_points: [City Bus Station (Platform 8), Macarthur / Northbourne Ave, Flemington Rd / Sandford St, Kosciuszko / Everard, Gungahlin Marketplace, Chuculba / William Slim Dr, William Webb / Ginninderra Drive, Belconnen Community Bus Station, Westfield Bus Station, Cohen Street Bus Station]  
long_name: To Cohen Street Bus Station  
between_stops:  
Westfield Bus Station-Cohen Street Bus Station: []  
City Bus Station (Platform 8)-Macarthur / Northbourne Ave: [Wjz5O3Q, Wjz5Oci, Wjz5P8n, Wjz5P8K, Wjz5PdJ, Wjz5Pl0, Wjz5Qi2, Wjz5Qgn, Wjz5Qmu, Wjz5QmR]  
Belconnen Community Bus Station-Westfield Bus Station: []  
stop_times_saturday: [[838a, 844a, 852a, 859a, 909a, 919a, 924a, 930a, 932a, 937a], [938a, 944a, 952a, 959a, 1009a, 1019a, 1024a, 1030a, 1032a, 1037a], [1038a, 1044a, 1052a, 1059a, 1109a, 1119a, 1124a, 1130a, 1132a, 1137a], [1138a, 1144a, 1152a, 1159a, 1209p, 1219p, 1224p, 1230p, 1232p, 1237p], [1238p, 1244p, 1252p, 1259p, 109p, 119p, 124p, 130p, 132p, 137p], [138p, 144p, 152p, 159p, 209p, 219p, 224p, 230p, 232p, 237p], [238p, 244p, 252p, 259p, 309p, 319p, 324p, 330p, 332p, 337p], [338p, 344p, 352p, 359p, 409p, 419p, 424p, 430p, 432p, 437p], [438p, 444p, 452p, 459p, 509p, 519p, 524p, 530p, 532p, 537p], [538p, 544p, 552p, 559p, 609p, 619p, 624p, 630p, 632p, 637p], [638p, 644p, 652p, 659p, 709p, 719p, 724p, 730p, 732p, 737p]]  
short_name: "956"  
-  
time_points: [Tuggeranong Bus Station (Platform 8), Woden Bus Station (Platform 9), City Bus Station (Platform 3), Belconnen Community Bus Station (Platform 4), Westfield Bus Station (Platform 2), Cohen Street Bus Station (Platform 6), St Francis Xavier Florey, Charnwood Tillyard Dr, Fraser Shops, Fraser West Terminus]  
long_name: To Fraser West Terminus  
between_stops:  
Belconnen Community Bus Station (Platform 4)-Westfield Bus Station (Platform 2): []  
Tuggeranong Bus Station (Platform 8)-Woden Bus Station (Platform 9): [Wjz213q, Wjz238T, Wjz239F, Wjz2lDC, Wjz2mGO, Wjz2mTK, Wjz2nLE, Wjz3gMq, Wjz3gQn, Wjz3gK-, Wjz3hL_, Wjz3iFK, Wjz3kwU, Wjz3kyX, Wjz3kAx]  
Westfield Bus Station (Platform 2)-Cohen Street Bus Station (Platform 6): []  
City Bus Station (Platform 3)-Belconnen Community Bus Station (Platform 4): [Wjz5F-1, Wjz5FSY, Wjz5GMT, Wjz5GNG, Wjz5G6U, Wjz5G6B, Wjz5maK, Wjz5mbS, Wjz5nwb, Wjz5nw6, Wjz6giR, Wjz6gia, Wjz68W3, Wjz68W5, Wjz689c, Wjz681S]  
Woden Bus Station (Platform 9)-City Bus Station (Platform 3): [Wjz3m3b, Wjz3m3b, Wjz3eRR, Wjz3eZ4, Wjz4KNu, Wjz4KO9, Wjz5Nht]  
short_name: 14 314  
stop_times: [["-", "-", "-", 706a, 708a, 712a, 717a, 722a, 726a, 735a], ["-", "-", "-", 722a, 724a, 728a, 734a, 739a, 744a, 753a], [706a, 724a, 741a, 802a, 804a, 808a, 814a, 819a, 824a, 833a], [746a, 805a, 823a, 844a, 846a, 850a, 856a, 901a, 906a, 915a], [805a, 824a, 842a, 903a, 905a, 909a, 915a, 920a, 925a, 934a], [843a, 902a, 920a, 940a, 942a, 946a, 951a, 956a, 1000a, 1009a], [916a, 935a, 951a, 1011a, 1013a, 1017a, 1022a, 1027a, 1031a, 1040a], [946a, 1004a, 1020a, 1040a, 1042a, 1046a, 1051a, 1056a, 1100a, 1109a], [1016a, 1034a, 1050a, 1110a, 1112a, 1116a, 1121a, 1126a, 1130a, 1139a], [1046a, 1104a, 1120a, 1140a, 1142a, 1146a, 1151a, 1156a, 1200p, 1209p], [1116a, 1134a, 1150a, 1210p, 1212p, 1216p, 1221p, 1226p, 1230p, 1239p], [1146a, 1204p, 1220p, 1240p, 1242p, 1246p, 1251p, 1256p, 100p, 109p], [1216p, 1234p, 1250p, 110p, 112p, 116p, 121p, 126p, 130p, 139p], [1246p, 104p, 120p, 140p, 142p, 146p, 151p, 156p, 200p, 209p], [116p, 134p, 150p, 210p, 212p, 216p, 221p, 226p, 230p, 239p], [146p, 204p, 220p, 240p, 242p, 246p, 251p, 256p, 300p, 310p], [216p, 234p, 250p, 311p, 313p, 317p, 323p, 328p, 333p, 343p], [245p, 303p, 321p, 342p, 344p, 348p, 354p, 359p, 404p, 414p], ["-", "-", 340p, 345p, 347p, 351p, 357p, 402p, 407p, 417p], [321p, 340p, 358p, 419p, 421p, 425p, 431p, 436p, 441p, 451p], [351p, 410p, 428p, 449p, 451p, 455p, 501p, 506p, 511p, 521p], [421p, 440p, 458p, 519p, 521p, 525p, 531p, 536p, 541p, 551p], [451p, 510p, 528p, 549p, 551p, 555p, 601p, 606p, 611p, 621p], [511p, 530p, 548p, 609p, 611p, 615p, 621p, 626p, 631p, 640p], [531p, 550p, 608p, 629p, 631p, 635p, 640p, 645p, 649p, 658p], [551p, 610p, 628p, 648p, 650p, 654p, 659p, 704p, 708p, 717p], [621p, 639p, 654p, 714p, 716p, 720p, 725p, 730p, 734p, 743p], ["-", "-", "-", 804p, 806p, 810p, 815p, 820p, 824p, 833p], ["-", "-", "-", 904p, 906p, 910p, 915p, 920p, 924p, 933p], ["-", "-", "-", 1004p, 1006p, 1010p, 1015p, 1020p, 1024p, 1033p], ["-", "-", "-", 1104p, 1106p, 1110p, 1115p, 1120p, 1124p, 1133p], []]  
-  
time_points: [City Bus Station (Platform 5), Merici College, Dickson, Australian Institute of Sport, Belconnen Community Bus Station, Westfield Bus Station, Cohen Street Bus Station]  
long_name: To Cohen Street Bus Station  
between_stops:  
Westfield Bus Station-Cohen Street Bus Station: []  
Belconnen Community Bus Station-Westfield Bus Station: []  
short_name: "7"  
stop_times: [[632a, 639a, 646a, 654a, 708a, 710a, 715a], [701a, 708a, 715a, 723a, 738a, 740a, 745a], [731a, 739a, 746a, 754a, 810a, 812a, 817a], [801a, 809a, 816a, 824a, 840a, 842a, 847a], [829a, 837a, 844a, 852a, 908a, 910a, 915a], [858a, 906a, 913a, 921a, 936a, 938a, 943a], [930a, 937a, 944a, 952a, 1006a, 1008a, 1013a], [1000a, 1007a, 1014a, 1022a, 1036a, 1038a, 1043a], [1030a, 1037a, 1044a, 1052a, 1106a, 1108a, 1113a], [1100a, 1107a, 1114a, 1122a, 1136a, 1138a, 1143a], [1130a, 1137a, 1144a, 1152a, 1206p, 1208p, 1213p], [1200p, 1207p, 1214p, 1222p, 1236p, 1238p, 1243p], [1230p, 1237p, 1244p, 1252p, 106p, 108p, 113p], [100p, 107p, 114p, 122p, 136p, 138p, 143p], [130p, 137p, 144p, 152p, 206p, 208p, 213p], [200p, 207p, 214p, 222p, 236p, 238p, 243p], [230p, 237p, 244p, 252p, 307p, 309p, 314p], [259p, 307p, 314p, 323p, 339p, 341p, 346p], [331p, 339p, 346p, 355p, 411p, 413p, 418p], [401p, 409p, 416p, 425p, 441p, 443p, 448p], [431p, 439p, 446p, 455p, 511p, 513p, 518p], [501p, 509p, 516p, 525p, 541p, 543p, 548p], [531p, 539p, 546p, 555p, 611p, 613p, 618p], [631p, 637p, 644p, 652p, 706p, 708p, 713p], [731p, 737p, 744p, 752p, 806p, 808p, 813p], [831p, 837p, 844p, 852p, 906p, 908p, 913p], [931p, 937p, 944p, 952p, 1006p, 1008p, 1013p], [1031p, 1037p, 1044p, 1052p, 1106p, 1108p, 1113p]]  
-  
time_points: [Gungahlin Marketplace, Manning Clarke / Oodgeroo, Hoskins Street / Oodgeroo Ave, Flemington Rd / Sandford St, Northbourne Avenue / Antill St, Macarthur / Northbourne Ave, City Bus Station]  
long_name: To City Bus Station  
between_stops:  
Northbourne Avenue / Antill St-Macarthur / Northbourne Ave: [Wjz5SDc, Wjz5Sux, Wjz5SrO, Wjz5Sqk, Wjz5RvC, Wjz5Rsi, Wjz5RkN]  
Macarthur / Northbourne Ave-City Bus Station: [Wjz5QmR, Wjz5Qmu, Wjz5Qgn, Wjz5Qi2, Wjz5Pl0, Wjz5PdJ, Wjz5P8K, Wjz5P8n, Wjz5Oci, Wjz5O3Q]  
short_name: "57"  
stop_times: [[600a, 607a, 610a, 618a, 624a, 626a, 632a], [630a, 637a, 640a, 648a, 654a, 656a, 702a], [700a, 707a, 710a, 718a, 724a, 726a, 732a], [736a, 743a, 746a, 754a, 805a, 810a, 825a], [806a, 813a, 816a, 824a, 835a, 840a, 855a], [836a, 843a, 846a, 854a, 903a, 905a, 911a], [936a, 943a, 946a, 954a, 1000a, 1002a, 1008a], [1036a, 1043a, 1046a, 1054a, 1100a, 1102a, 1108a], [1136a, 1143a, 1146a, 1154a, 1200p, 1202p, 1208p], [1236p, 1243p, 1246p, 1254p, 100p, 102p, 108p], [136p, 143p, 146p, 154p, 200p, 202p, 208p], [236p, 243p, 246p, 254p, 300p, 302p, 308p], [336p, 343p, 346p, 354p, 400p, 402p, 409p], [407p, 414p, 417p, 425p, 432p, 434p, 441p], [437p, 444p, 447p, 455p, 502p, 504p, 511p], [507p, 514p, 517p, 525p, 532p, 534p, 541p], [537p, 544p, 547p, 555p, 602p, 604p, 609p], [636p, 643p, 646p, 654p, 700p, 702p, 707p]]  
-  
time_points: [Cohen Street Bus Station (Platform 2), Westfield Bus Station (Platform 1), Belconnen Community Bus Station (Platform 3), Jamison Centre, Cook Shops, Aranda Shops, Caswell Drive, City Bus Station (Platform 7), War Memorial Limestone Ave, ADFA, Campbell Park Offices, Majura Business Park, Brindabella Business Park, Fairbairn Park]  
long_name: To Fairbairn Park  
between_stops:  
City Bus Station (Platform 7)-War Memorial Limestone Ave: [Wjz5NAQ, Wjz5NHD, Wjz5NRJ, Wjz5V64, Wjz5W8l, Wjz5VAq, Wjz5VFA]  
Brindabella Business Park-Fairbairn Park: [WjzcrK3, WjzcrrQ, WjzcrEu, WjzcJ0K, WjzcBHZ, WjzcJ38]  
Westfield Bus Station (Platform 1)-Belconnen Community Bus Station (Platform 3): []  
War Memorial Limestone Ave-ADFA: [Wjz5VUU, Wjzd0CK, Wjzd8br, Wjzce7O, Wjzce4H, Wjzcend]  
Cohen Street Bus Station (Platform 2)-Westfield Bus Station (Platform 1): []  
ADFA-Campbell Park Offices: [Wjzcend, Wjzce4H, Wjzce7O]  
short_name: "10"  
stop_times: [[550a, 552a, 556a, 605a, 615a, 620a, 623a, 634a, "-", "-", "-", "-", "-", "-"], [621a, 623a, 627a, 636a, 646a, 651a, 654a, 705a, "-", "-", "-", "-", "-", "-"], [651a, 653a, 657a, 706a, 716a, 721a, 724a, 736a, 746a, 752a, 756a, 803a, "-", "-"], ["-", "-", "-", "-", 724a, 729a, 732a, 743a, "-", "-", "-", "-", "-", "-"], [706a, 708a, 712a, 721a, 731a, 736a, 739a, 750a, "-", "-", "-", "-", "-", "-"], [721a, 723a, 727a, 736a, 746a, 751a, 754a, 806a, 816a, 822a, 826a, 833a, "-", "-"], ["-", "-", "-", "-", 754a, 759a, 802a, 813a, "-", "-", "-", "-", "-", "-"], [736a, 738a, 742a, 751a, 801a, 806a, 809a, 820a, "-", "-", "-", "-", "-", "-"], [751a, 753a, 757a, 806a, 816a, 821a, 824a, 836a, 846a, 852a, 856a, "-", "-", "-"], [806a, 808a, 812a, 821a, 831a, 836a, 839a, 851a, 901a, 907a, 911a, 918a, 927a, 937a], [821a, 823a, 827a, 836a, 846a, 851a, 854a, 905a, "-", "-", "-", "-", "-", "-"], [836a, 838a, 842a, 851a, 901a, 906a, 909a, 921a, 931a, 937a, 940a, 947a, 956a, 1006a], [851a, 853a, 857a, 906a, 916a, 921a, 924a, 936a, 946a, 952a, 955a, 1002a, 1011a, 1021a], [913a, 915a, 919a, 928a, 938a, 943a, 945a, 957a, 1007a, 1013a, 1016a, 1023a, 1032a, 1042a], [943a, 945a, 949a, 958a, 1008a, 1013a, 1015a, 1027a, 1037a, 1043a, 1046a, 1053a, 1102a, 1112a], [1013a, 1015a, 1019a, 1028a, 1038a, 1043a, 1045a, 1057a, 1107a, 1113a, 1116a, 1123a, 1132a, 1142a], [1043a, 1045a, 1049a, 1058a, 1108a, 1113a, 1115a, 1127a, 1137a, 1143a, 1146a, 1153a, 1202p, 1212p], [1113a, 1115a, 1119a, 1128a, 1138a, 1143a, 1145a, 1157a, 1207p, 1213p, 1216p, 1223p, 1232p, 1242p], [1143a, 1145a, 1149a, 1158a, 1208p, 1213p, 1215p, 1227p, 1237p, 1243p, 1246p, 1253p, 102p, 112p], [1213p, 1215p, 1219p, 1228p, 1238p, 1243p, 1245p, 1257p, 107p, 113p, 116p, 123p, 132p, 142p], [1243p, 1245p, 1249p, 1258p, 108p, 113p, 115p, 127p, 137p, 143p, 146p, 153p, 202p, 212p], [113p, 115p, 119p, 128p, 138p, 143p, 145p, 157p, 207p, 213p, 216p, 223p, 232p, 242p], [143p, 145p, 149p, 158p, 208p, 213p, 215p, 227p, 237p, 243p, 246p, 253p, 302p, 312p], [213p, 215p, 219p, 228p, 238p, 243p, 245p, 257p, 307p, 313p, 317p, 324p, 333p, 343p], [253p, 255p, 259p, 308p, 318p, 323p, 325p, 337p, 347p, 353p, 357p, 404p, 413p, 423p], [323p, 325p, 329p, 338p, 348p, 353p, 355p, 407p, 417p, 423p, 427p, 434p, 443p, 453p], [338p, 340p, 344p, 353p, 403p, 408p, 410p, 421p, "-", "-", "-", "-", "-", "-"], [353p, 355p, 359p, 408p, 418p, 423p, 425p, 437p, 447p, 453p, 457p, "-", "-", "-"], [408p, 410p, 414p, 423p, 433p, 438p, 440p, 451p, "-", "-", "-", "-", "-", "-"], [423p, 425p, 429p, 438p, 448p, 453p, 455p, 506p, "-", "-", "-", "-", "-", "-"], [438p, 440p, 444p, 453p, 503p, 508p, 510p, 521p, "-", "-", "-", "-", "-", "-"], [453p, 455p, 459p, 508p, 518p, 523p, 525p, 536p, "-", "-", "-", "-", "-", "-"], [508p, 510p, 514p, 523p, 533p, 538p, 540p, 551p, "-", "-", "-", "-", "-", "-"], [523p, 525p, 529p, 538p, 548p, 553p, 555p, 606p, "-", "-", "-", "-", "-", "-"], [538p, 540p, 544p, 553p, 603p, 608p, 610p, 621p, "-", "-", "-", "-", "-", "-"], [617p, 619p, 623p, 632p, 642p, 647p, 649p, 700p, "-", "-", "-", "-", "-", "-"], [716p, 718p, 722p, 731p, 741p, 746p, 748p, 759p, "-", "-", "-", "-", "-", "-"], [816p, 818p, 822p, 831p, 841p, 846p, 848p, 859p, "-", "-", "-", "-", "-", "-"], [916p, 918p, 922p, 931p, 941p, 946p, 948p, 959p, "-", "-", "-", "-", "-", "-"], [1016p, 1018p, 1022p, 1031p, 1041p, 1046p, 1048p, 1059p, "-", "-", "-", "-", "-", "-"], [1116p, 1118p, 1122p, 1131p, 1141p, 1146p, 1148p, "-", "-", "-", "-", "-", "-", "-"]]  
-  
time_points: [City West, City Bus Station (Platform 10), Curtin, Woden Bus Station]  
long_name: To Woden Bus Station  
between_stops:  
City West-City Bus Station (Platform 10): []  
short_name: "732"  
stop_times: [[435p, 441p, 453p, 503p], [505p, 511p, 523p, 533p], [535p, 541p, 553p, 603p]]  
-  
time_points: [Tuggeranong Bus Station (Platform 8), Erindale Centre, Woden Bus Station (Platform 9), City Bus Station (Platform 3), Belconnen Community Bus Station, Westfield Bus Station, Cohen Street Bus Station]  
long_name: To Cohen Street Bus Station  
between_stops:  
Westfield Bus Station-Cohen Street Bus Station: []  
Belconnen Community Bus Station-Westfield Bus Station: []  
short_name: "900"  
stop_times_sunday: [[730a, 741a, 757a, 813a, 830a, 832a, 837a], [745a, 756a, 812a, 828a, 845a, 847a, 852a], [800a, 811a, 827a, 843a, 900a, 902a, 907a], [815a, 826a, 842a, 858a, 915a, 917a, 922a], [830a, 841a, 857a, 913a, 930a, 932a, 937a], [845a, 856a, 912a, 928a, 945a, 947a, 952a], [900a, 911a, 927a, 943a, 1000a, 1002a, 1007a], [915a, 926a, 942a, 958a, 1015a, 1017a, 1022a], [930a, 941a, 957a, 1013a, 1030a, 1032a, 1037a], [945a, 956a, 1012a, 1028a, 1045a, 1047a, 1052a], [1000a, 1011a, 1027a, 1043a, 1100a, 1102a, 1107a], [1015a, 1026a, 1042a, 1058a, 1115a, 1117a, 1122a], [1030a, 1041a, 1057a, 1113a, 1130a, 1132a, 1137a], [1045a, 1056a, 1112a, 1128a, 1145a, 1147a, 1152a], [1100a, 1111a, 1127a, 1143a, 1200p, 1202p, 1207p], [1115a, 1126a, 1142a, 1158a, 1215p, 1217p, 1222p], [1130a, 1141a, 1157a, 1213p, 1230p, 1232p, 1237p], [1145a, 1156a, 1212p, 1228p, 1245p, 1247p, 1252p], [1200p, 1211p, 1227p, 1243p, 100p, 102p, 107p], [1215p, 1226p, 1242p, 1258p, 115p, 117p, 122p], [1230p, 1241p, 1257p, 113p, 130p, 132p, 137p], [1245p, 1256p, 112p, 128p, 145p, 147p, 152p], [100p, 111p, 127p, 143p, 200p, 202p, 207p], [115p, 126p, 142p, 158p, 215p, 217p, 222p], [130p, 141p, 157p, 213p, 230p, 232p, 237p], [145p, 156p, 212p, 228p, 245p, 247p, 252p], [200p, 211p, 227p, 243p, 300p, 302p, 307p], [215p, 226p, 242p, 258p, 315p, 317p, 322p], [230p, 241p, 257p, 313p, 330p, 332p, 337p], [245p, 256p, 312p, 328p, 345p, 347p, 352p], [300p, 311p, 327p, 343p, 400p, 402p, 407p], [315p, 326p, 342p, 358p, 415p, 417p, 422p], [330p, 341p, 357p, 413p, 430p, 432p, 437p], [345p, 356p, 412p, 428p, 445p, 447p, 452p], [400p, 411p, 427p, 443p, 500p, 502p, 507p], [415p, 426p, 442p, 458p, 515p, 517p, 522p], [430p, 441p, 457p, 513p, 530p, 532p, 537p], [445p, 456p, 512p, 528p, 545p, 547p, 552p], [500p, 511p, 527p, 543p, 600p, 602p, 607p], [515p, 526p, 542p, 558p, 615p, 617p, 622p], [530p, 541p, 557p, 613p, 630p, 632p, 637p], [545p, 556p, 612p, 628p, 645p, 647p, 652p], [600p, 611p, 627p, 642p, 659p, 701p, 706p], [615p, 626p, 641p, 656p, 713p, 715p, 720p], [630p, 640p, 655p, 710p, 727p, 729p, 734p], [645p, 655p, 710p, 725p, 742p, 744p, 749p], [700p, 710p, 725p, 740p, 757p, 759p, 804p], [715p, 725p, 740p, 755p, 812p, 814p, 819p]]  
-  
time_points: [Cohen Street Bus Station (Platform 1), Westfield Bus Station (Platform 1), Belconnen Community Bus Station (Platform 2), Chuculba / William Slim Dr, Federation Square, Nicholls Primary, Gungahlin Marketplace]  
long_name: To Gungahlin Market Place  
between_stops:  
Belconnen Community Bus Station (Platform 2)-Chuculba / William Slim Dr: [Wjz69gA, Wjz69ht, Wjz69uI, Wjz69vO, Wjz6mip]  
Cohen Street Bus Station (Platform 1)-Westfield Bus Station (Platform 1): []  
Westfield Bus Station (Platform 1)-Belconnen Community Bus Station (Platform 2): []  
short_name: "952"  
stop_times: [[945a, 947a, 951a, 1004a, 1009a, 1022a, 1031a], [1045a, 1047a, 1051a, 1104a, 1109a, 1122a, 1131a], [1145a, 1147a, 1151a, 1204p, 1209p, 1222p, 1231p], [1245p, 1247p, 1251p, 104p, 109p, 122p, 131p], [145p, 147p, 151p, 204p, 209p, 222p, 231p], [245p, 247p, 251p, 304p, 309p, 322p, 331p], [345p, 347p, 351p, 404p, 409p, 422p, 431p], [445p, 447p, 451p, 504p, 509p, 522p, 531p], [545p, 547p, 551p, 604p, 609p, 622p, 631p], [645p, 647p, 651p, 704p, 709p, 722p, 731p]]  
-  
time_points: [City Bus Station (Platform 9), National Zoo and Aquarium, Black Mountain Telstra Tower, Botanic Gardens, City Bus Station]  
long_name: To City Interchange  
between_stops: {}  
   
short_name: "81"  
stop_times: [[920a, 934a, 942a, 948a, 955a], [1020a, 1034a, 1042a, 1048a, 1055a], [1120a, 1134a, 1142a, 1148a, 1155a], [1220p, 1234p, 1242p, 1248p, 1255p], [120p, 134p, 142p, 148p, 155p], [220p, 234p, 242p, 248p, 255p], [320p, 334p, 342p, 348p, 355p], [420p, 434p, 442p, 448p, 455p]]  
-  
time_points: [Bimberi Centre, Northbourne Avenue / Antill St, Macarthur / Northbourne Ave, City Bus Station]  
long_name: To City Bus Station  
between_stops:  
Macarthur / Northbourne Ave-City Bus Station: [Wjz5QmR, Wjz5Qmu, Wjz5Qgn, Wjz5Qi2, Wjz5Pl0, Wjz5PdJ, Wjz5P8K, Wjz5P8n, Wjz5Oci, Wjz5O3Q]  
short_name: "982"  
stop_times: [[715p, 724p, 726p, 733p]]  
-  
time_points: [Cohen Street Bus Station (Platform 2), Westfield Bus Station (Platform 1), Belconnen Community Bus Station (Platform 2), Tuggeranong Bus Station (Platform 7), Centrelink Tuggeranong]  
long_name: To Centrelink Tuggeranong  
between_stops:  
Westfield Bus Station (Platform 1)-Belconnen Community Bus Station (Platform 2): []  
Cohen Street Bus Station (Platform 2)-Westfield Bus Station (Platform 1): []  
short_name: "705"  
stop_times: [[715a, 717a, 721a, 800a, 802a], [741a, 743a, 747a, 826a, 828a], [807a, 809a, 813a, 852a, 854a], [439p, 441p, 445p, 523p, "-"], [505p, 507p, 511p, 549p, "-"], [532p, 534p, 538p, 616p, "-"]]  
-  
time_points: [Geoscience Australia, Narrabundah Terminus, Narrabundah College, Manuka / Captain Cook Cres, Kingston, Kings Ave / National Circuit, Russell Offices, City Bus Station]  
long_name: To City Bus Station  
between_stops:  
Kings Ave / National Circuit-Russell Offices: [Wjz4Quk, Wjz4RwH, Wjz4RFJ]  
Russell Offices-City Bus Station: [Wjz4-WZ, Wjz4-WL, Wjz4-Rc, Wjz4-KO, Wjz4_wS, Wjz4_jm, Wjz4T-X, Wjz5MEL, Wjz5MsT, Wjz5MsD, Wjz5Nht]  
short_name: "4"  
stop_times: [[712a, "-", 715a, 722a, 725a, 729a, 734a, 743a], [744a, "-", 747a, 756a, 800a, 805a, 810a, 819a], [817a, "-", 820a, 829a, 833a, 838a, 843a, 852a], [847a, "-", 850a, 859a, 903a, 908a, 913a, 922a], [917a, "-", 920a, 929a, 932a, 936a, 940a, 948a], [948a, "-", 951a, 958a, 1001a, 1005a, 1009a, 1017a], [1018a, "-", 1021a, 1028a, 1031a, 1035a, 1039a, 1047a], [1048a, "-", 1051a, 1058a, 1101a, 1105a, 1109a, 1117a], [1118a, "-", 1121a, 1128a, 1131a, 1135a, 1139a, 1147a], [1148a, "-", 1151a, 1158a, 1201p, 1205p, 1209p, 1217p], [1218p, "-", 1221p, 1228p, 1231p, 1235p, 1239p, 1247p], [1248p, "-", 1251p, 1258p, 101p, 105p, 109p, 117p], [118p, "-", 121p, 128p, 131p, 135p, 139p, 147p], [148p, "-", 151p, 158p, 201p, 205p, 209p, 217p], [218p, "-", 221p, 228p, 231p, 235p, 239p, 247p], [246p, "-", 249p, 256p, 259p, 304p, 309p, 318p], [314p, "-", 317p, 326p, 330p, 335p, 340p, 349p], [346p, "-", 349p, 358p, 402p, 407p, 412p, 421p], [417p, "-", 420p, 429p, 433p, 438p, 443p, 452p], [448p, "-", 451p, 500p, 504p, 509p, 514p, 523p], [518p, "-", 521p, 530p, 534p, 539p, 544p, 553p], [548p, "-", 551p, 600p, 604p, 609p, 614p, 623p], ["-", 617p, 620p, 629p, 632p, 636p, 640p, 648p], ["-", 650p, 653p, 658p, 701p, 705p, 709p, 717p], ["-", 743p, 746p, 751p, 754p, 758p, 802p, 810p], ["-", 843p, 846p, 851p, 854p, 858p, 902p, 910p], ["-", 943p, 946p, 951p, 954p, 958p, 1002p, 1010p], ["-", 1043p, 1046p, 1051p, 1054p, 1058p, 1102p, 1110p]]  
-  
time_points: [City Bus Station (Platform 9), National Zoo and Aquarium, Black Mountain Telstra Tower, Botanic Gardens, City Bus Station]  
long_name: To City Bus Station  
between_stops: {}  
   
short_name: "981"  
stop_times_sunday: [[1020a, 1034a, 1042a, 1048a, 1055a], [1150a, 1204p, 1212p, 1218p, 1225p], [120p, 134p, 142p, 148p, 155p], [250p, 304p, 312p, 318p, 325p], [420p, 434p, 442p, 448p, 455p]]  
-  
time_points: [Tuggeranong Bus Station (Platform 3), Kambah High, Mount Neighbour School, Woden Bus Station]  
long_name: To Woden Bus Station  
between_stops: {}  
   
short_name: "960"  
stop_times: [[755a, 805a, 811a, 823a], [855a, 905a, 911a, 923a], [955a, 1005a, 1011a, 1023a], [1055a, 1105a, 1111a, 1123a], [1155a, 1205p, 1211p, 1223p], [1255p, 105p, 111p, 123p], [155p, 205p, 211p, 223p], [255p, 305p, 311p, 323p], [355p, 405p, 411p, 423p], [455p, 505p, 511p, 523p], [555p, 605p, 611p, 623p], [655p, 705p, 711p, 721p]]  
- -
time_points: [Belconnen Community Bus Station (Platform 4), Westfield Bus Station (Platform 2), Cohen Street Bus Station (Platform 6), Kippax, Fraser West Terminus, Kippax, Cohen Street Bus Station, Westfield Bus Station, Belconnen Community Bus Station] time_points: [Belconnen Community Bus Station (Platform 4), Westfield Bus Station (Platform 2), Cohen Street Bus Station (Platform 6), Kippax, Fraser West Terminus, Kippax, Cohen Street Bus Station, Westfield Bus Station, Belconnen Community Bus Station]
long_name: To Belconnen Community Bus Station long_name: To Belconnen Community Bus Station
between_stops: between_stops:
Belconnen Community Bus Station (Platform 4)-Westfield Bus Station (Platform 2): [] Belconnen Community Bus Station (Platform 4)-Westfield Bus Station (Platform 2): []
Westfield Bus Station-Belconnen Community Bus Station: [] Westfield Bus Station-Belconnen Community Bus Station: []
Cohen Street Bus Station-Westfield Bus Station: [] Cohen Street Bus Station-Westfield Bus Station: []
Westfield Bus Station (Platform 2)-Cohen Street Bus Station (Platform 6): [] Westfield Bus Station (Platform 2)-Cohen Street Bus Station (Platform 6): []
short_name: "903" short_name: "903"
stop_times_sunday: [[859a, 901a, 905a, 919a, 934a, 948a, 1002a, 1004a, 1008a], [959a, 1001a, 1005a, 1019a, 1034a, 1048a, 1102a, 1104a, 1108a], [1059a, 1101a, 1105a, 1119a, 1134a, 1148a, 1202p, 1204p, 1208p], [1159a, 1201p, 1205p, 1219p, 1234p, 1248p, 102p, 104p, 108p], [1259p, 101p, 105p, 119p, 134p, 148p, 202p, 204p, 208p], [159p, 201p, 205p, 219p, 234p, 248p, 302p, 304p, 308p], [259p, 301p, 305p, 319p, 334p, 348p, 402p, 404p, 408p], [359p, 401p, 405p, 419p, 434p, 448p, 502p, 504p, 508p], [459p, 501p, 505p, 519p, 534p, 548p, 602p, 604p, 608p], [559p, 601p, 605p, 619p, 634p, 648p, 701p, 703p, 707p]] stop_times_sunday: [[859a, 901a, 905a, 919a, 934a, 948a, 1002a, 1004a, 1008a], [959a, 1001a, 1005a, 1019a, 1034a, 1048a, 1102a, 1104a, 1108a], [1059a, 1101a, 1105a, 1119a, 1134a, 1148a, 1202p, 1204p, 1208p], [1159a, 1201p, 1205p, 1219p, 1234p, 1248p, 102p, 104p, 108p], [1259p, 101p, 105p, 119p, 134p, 148p, 202p, 204p, 208p], [159p, 201p, 205p, 219p, 234p, 248p, 302p, 304p, 308p], [259p, 301p, 305p, 319p, 334p, 348p, 402p, 404p, 408p], [359p, 401p, 405p, 419p, 434p, 448p, 502p, 504p, 508p], [459p, 501p, 505p, 519p, 534p, 548p, 602p, 604p, 608p], [559p, 601p, 605p, 619p, 634p, 648p, 701p, 703p, 707p]]
- -
time_points: [Woden Bus Station (Platform 15), Pearce Shops, Isaacs Shops, Farrer Primary School, Southlands Mawson, Woden Bus Station] time_points: [Tuggeranong Bus Station (Platform 4), Isabella, Theodore, Calwell, Outtrim / Duggan, Tuggeranong Bus Station]
long_name: To Woden Bus Station long_name: To Tuggeranong Bus Station
between_stops: {} between_stops: {}
   
short_name: "923" stop_times_saturday: [[715a, 725a, 734a, 743a, 746a, 755a], [915a, 925a, 934a, 943a, 946a, 955a], [1115a, 1125a, 1134a, 1143a, 1146a, 1155a], [115p, 125p, 134p, 143p, 146p, 155p], [315p, 325p, 334p, 343p, 346p, 355p], [515p, 525p, 534p, 543p, 546p, 555p], [715p, 725p, 734p, 743p, 746p, 755p], [918p, 928p, 937p, 946p, 949p, 958p], [1118p, 1128p, 1137p, 1146p, 1149p, "-"]]
stop_times: [[910a, 916a, 921a, 927a, 933a, 943a], [1110a, 1116a, 1121a, 1127a, 1133a, 1143a], [110p, 116p, 121p, 127p, 133p, 143p], [310p, 316p, 321p, 327p, 333p, 343p], [510p, 516p, 521p, 527p, 533p, 543p]] short_name: "915"
- -
time_points: [Cohen Street Bus Station (Platform 1), Westfield Bus Station (Platform 1), Belconnen Community Bus Station (Platform 2), Chuculba / William Slim Dr, Federation Square, Nicholls Primary, Ngunnawal Primary, Gungahlin Marketplace, Hibberson / Kate Crace, Flemington Rd / Sandford St, Northbourne Avenue / Antill St, Macarthur / Northbourne Ave, City Bus Station] time_points: [Cohen Street Bus Station (Platform 1), Westfield Bus Station (Platform 1), Belconnen Community Bus Station (Platform 2), Chuculba / William Slim Dr, Federation Square, Nicholls Primary, Ngunnawal Primary, Gungahlin Marketplace, Hibberson / Kate Crace, Flemington Rd / Sandford St, Northbourne Avenue / Antill St, Macarthur / Northbourne Ave, City Bus Station]
long_name: To City Bus Station long_name: To City Bus Station
between_stops: between_stops:
Northbourne Avenue / Antill St-Macarthur / Northbourne Ave: [Wjz5SDc, Wjz5Sux, Wjz5SrO, Wjz5Sqk, Wjz5RvC, Wjz5Rsi, Wjz5RkN] Northbourne Avenue / Antill St-Macarthur / Northbourne Ave: [Wjz5SDc, Wjz5Sux, Wjz5SrO, Wjz5Sqk, Wjz5RvC, Wjz5Rsi, Wjz5RkN]
Belconnen Community Bus Station (Platform 2)-Chuculba / William Slim Dr: [Wjz69gA, Wjz69ht, Wjz69uI, Wjz69vO, Wjz6mip] Belconnen Community Bus Station (Platform 2)-Chuculba / William Slim Dr: [Wjz69gA, Wjz69ht, Wjz69uI, Wjz69vO, Wjz6mip]
Macarthur / Northbourne Ave-City Bus Station: [Wjz5QmR, Wjz5Qmu, Wjz5Qgn, Wjz5Qi2, Wjz5Pl0, Wjz5PdJ, Wjz5P8K, Wjz5P8n, Wjz5Oci, Wjz5O3Q] Macarthur / Northbourne Ave-City Bus Station: [Wjz5QmR, Wjz5Qmu, Wjz5Qgn, Wjz5Qi2, Wjz5Pl0, Wjz5PdJ, Wjz5P8K, Wjz5P8n, Wjz5Oci, Wjz5O3Q]
Cohen Street Bus Station (Platform 1)-Westfield Bus Station (Platform 1): [] Cohen Street Bus Station (Platform 1)-Westfield Bus Station (Platform 1): []
Westfield Bus Station (Platform 1)-Belconnen Community Bus Station (Platform 2): [] Westfield Bus Station (Platform 1)-Belconnen Community Bus Station (Platform 2): []
short_name: "51" short_name: "51"
stop_times: [["-", "-", "-", "-", 531a, 540a, 549a, 559a, 602a, "-", "-", "-", "-"], ["-", "-", "-", "-", 616a, 625a, 634a, 644a, 647a, "-", "-", "-", "-"], [618a, 620a, 624a, 634a, 639a, 648a, 657a, 706a, 709a, 712a, 719a, 721a, 728a], ["-", "-", "-", "-", 656a, 705a, 714a, 723a, 726a, 729a, 736a, 738a, 745a], [652a, 654a, 658a, 708a, 713a, 722a, 731a, 740a, 743a, 747a, 758a, 802a, 818a], ["-", "-", "-", 721a, 726a, 735a, 744a, 753a, 756a, 801a, 812a, 817a, 832a], [732a, 734a, 738a, 748a, 753a, 803a, 813a, 822a, 825a, 830a, 841a, 846a, 900a], [749a, 751a, 755a, 806a, 811a, 821a, 831a, 840a, 843a, 848a, 859a, 902a, 909a], ["-", "-", "-", "-", 829a, 839a, 849a, 858a, 901a, 904a, 911a, 913a, 927a], [838a, 840a, 844a, 855a, 900a, 909a, 918a, 927a, 930a, 933a, 940a, 942a, 949a], [909a, 911a, 915a, 925a, 930a, 939a, 948a, 958a, 1001a, "-", "-", "-", "-"], [939a, 941a, 945a, 955a, 1000a, 1009a, 1018a, 1028a, 1031a, "-", "-", "-", "-"], [1039a, 1041a, 1045a, 1055a, 1100a, 1109a, 1118a, 1128a, 1131a, "-", "-", "-", "-"], [1139a, 1141a, 1145a, 1155a, 1200p, 1209p, 1218p, 1228p, 1231p, "-", "-", "-", "-"], [1239p, 1241p, 1245p, 1255p, 100p, 109p, 118p, 128p, 131p, "-", "-", "-", "-"], [139p, 141p, 145p, 155p, 200p, 209p, 218p, 228p, 231p, "-", "-", "-", "-"], [239p, 241p, 245p, 255p, 300p, 309p, 318p, 328p, 331p, "-", "-", "-", "-"], [334p, 336p, 340p, 350p, 355p, 405p, 415p, 425p, 428p, "-", "-", "-", "-"], [414p, 416p, 420p, 431p, 436p, 447p, 457p, 507p, 510p, "-", "-", "-", "-"], [434p, 436p, 440p, 451p, 456p, 507p, 517p, 527p, 530p, "-", "-", "-", "-"], [454p, 456p, 500p, 511p, 516p, 527p, 537p, 547p, 550p, "-", "-", "-", "-"], [513p, 515p, 519p, 530p, 535p, 546p, 556p, 606p, 609p, "-", "-", "-", "-"], [534p, 536p, 540p, 551p, 556p, 606p, 615p, 625p, 628p, "-", "-", "-", "-"], [638p, 640p, 644p, 654p, 659p, 708p, 717p, 727p, 730p, "-", "-", "-", "-"], [738p, 740p, 744p, 754p, 759p, 808p, 817p, 827p, 830p, "-", "-", "-", "-"], [838p, 840p, 844p, 854p, 859p, 908p, 917p, 927p, 930p, "-", "-", "-", "-"], [938p, 940p, 944p, 954p, 959p, 1008p, 1017p, 1027p, 1030p, "-", "-", "-", "-"], [1038p, 1040p, 1044p, 1054p, 1059p, 1108p, 1117p, 1127p, 1130p, "-", "-", "-", "-"]] stop_times: [["-", "-", "-", "-", 531a, 540a, 549a, 559a, 602a, "-", "-", "-", "-"], ["-", "-", "-", "-", 616a, 625a, 634a, 644a, 647a, "-", "-", "-", "-"], [618a, 620a, 624a, 634a, 639a, 648a, 657a, 706a, 709a, 712a, 719a, 721a, 728a], ["-", "-", "-", "-", 656a, 705a, 714a, 723a, 726a, 729a, 736a, 738a, 745a], [652a, 654a, 658a, 708a, 713a, 722a, 731a, 740a, 743a, 747a, 758a, 802a, 818a], ["-", "-", "-", 721a, 726a, 735a, 744a, 753a, 756a, 801a, 812a, 817a, 832a], [732a, 734a, 738a, 748a, 753a, 803a, 813a, 822a, 825a, 830a, 841a, 846a, 900a], [749a, 751a, 755a, 806a, 811a, 821a, 831a, 840a, 843a, 848a, 859a, 902a, 909a], ["-", "-", "-", "-", 829a, 839a, 849a, 858a, 901a, 904a, 911a, 913a, 927a], [838a, 840a, 844a, 855a, 900a, 909a, 918a, 927a, 930a, 933a, 940a, 942a, 949a], [909a, 911a, 915a, 925a, 930a, 939a, 948a, 958a, 1001a, "-", "-", "-", "-"], [939a, 941a, 945a, 955a, 1000a, 1009a, 1018a, 1028a, 1031a, "-", "-", "-", "-"], [1039a, 1041a, 1045a, 1055a, 1100a, 1109a, 1118a, 1128a, 1131a, "-", "-", "-", "-"], [1139a, 1141a, 1145a, 1155a, 1200p, 1209p, 1218p, 1228p, 1231p, "-", "-", "-", "-"], [1239p, 1241p, 1245p, 1255p, 100p, 109p, 118p, 128p, 131p, "-", "-", "-", "-"], [139p, 141p, 145p, 155p, 200p, 209p, 218p, 228p, 231p, "-", "-", "-", "-"], [239p, 241p, 245p, 255p, 300p, 309p, 318p, 328p, 331p, "-", "-", "-", "-"], [334p, 336p, 340p, 350p, 355p, 405p, 415p, 425p, 428p, "-", "-", "-", "-"], [414p, 416p, 420p, 431p, 436p, 447p, 457p, 507p, 510p, "-", "-", "-", "-"], [434p, 436p, 440p, 451p, 456p, 507p, 517p, 527p, 530p, "-", "-", "-", "-"], [454p, 456p, 500p, 511p, 516p, 527p, 537p, 547p, 550p, "-", "-", "-", "-"], [513p, 515p, 519p, 530p, 535p, 546p, 556p, 606p, 609p, "-", "-", "-", "-"], [534p, 536p, 540p, 551p, 556p, 606p, 615p, 625p, 628p, "-", "-", "-", "-"], [638p, 640p, 644p, 654p, 659p, 708p, 717p, 727p, 730p, "-", "-", "-", "-"], [738p, 740p, 744p, 754p, 759p, 808p, 817p, 827p, 830p, "-", "-", "-", "-"], [838p, 840p, 844p, 854p, 859p, 908p, 917p, 927p, 930p, "-", "-", "-", "-"], [938p, 940p, 944p, 954p, 959p, 1008p, 1017p, 1027p, 1030p, "-", "-", "-", "-"], [1038p, 1040p, 1044p, 1054p, 1059p, 1108p, 1117p, 1127p, 1130p, "-", "-", "-", "-"]]
- -
time_points: [City Bus Station (Platform 8), Macarthur / Northbourne Ave, Flemington Rd / Sandford St, Kosciuszko / Everard, Gungahlin Marketplace, Chuculba / William Slim Dr, William Webb / Ginninderra Drive, Belconnen Community Bus Station, Westfield Bus Station, Cohen Street Bus Station] time_points: [City Bus Station (Platform 8), Macarthur / Northbourne Ave, Flemington Rd / Sandford St, Kosciuszko / Everard, Gungahlin Marketplace, Chuculba / William Slim Dr, William Webb / Ginninderra Drive, Belconnen Community Bus Station, Westfield Bus Station, Cohen Street Bus Station]
long_name: To Cohen Street Bus Station long_name: To Cohen Street Bus Station
between_stops: between_stops:
Westfield Bus Station-Cohen Street Bus Station: [] Westfield Bus Station-Cohen Street Bus Station: []
City Bus Station (Platform 8)-Macarthur / Northbourne Ave: [Wjz5O3Q, Wjz5Oci, Wjz5P8n, Wjz5P8K, Wjz5PdJ, Wjz5Pl0, Wjz5Qi2, Wjz5Qgn, Wjz5Qmu, Wjz5QmR] City Bus Station (Platform 8)-Macarthur / Northbourne Ave: [Wjz5O3Q, Wjz5Oci, Wjz5P8n, Wjz5P8K, Wjz5PdJ, Wjz5Pl0, Wjz5Qi2, Wjz5Qgn, Wjz5Qmu, Wjz5QmR]
Belconnen Community Bus Station-Westfield Bus Station: [] Belconnen Community Bus Station-Westfield Bus Station: []
short_name: "956" short_name: "956"
stop_times_sunday: [[838a, 844a, 852a, 859a, 909a, 919a, 924a, 930a, 932a, 937a], [938a, 944a, 952a, 959a, 1009a, 1019a, 1024a, 1030a, 1032a, 1037a], [1038a, 1044a, 1052a, 1059a, 1109a, 1119a, 1124a, 1130a, 1132a, 1137a], [1138a, 1144a, 1152a, 1159a, 1209p, 1219p, 1224p, 1230p, 1232p, 1237p], [1238p, 1244p, 1252p, 1259p, 109p, 119p, 124p, 130p, 132p, 137p], [138p, 144p, 152p, 159p, 209p, 219p, 224p, 230p, 232p, 237p], [238p, 244p, 252p, 259p, 309p, 319p, 324p, 330p, 332p, 337p], [338p, 344p, 352p, 359p, 409p, 419p, 424p, 430p, 432p, 437p], [438p, 444p, 452p, 459p, 509p, 519p, 524p, 530p, 532p, 537p], [538p, 544p, 552p, 559p, 609p, 619p, 624p, 630p, 632p, 637p], [638p, 644p, 652p, 659p, 709p, 719p, 724p, 730p, 732p, 737p]] stop_times_sunday: [[838a, 844a, 852a, 859a, 909a, 919a, 924a, 930a, 932a, 937a], [938a, 944a, 952a, 959a, 1009a, 1019a, 1024a, 1030a, 1032a, 1037a], [1038a, 1044a, 1052a, 1059a, 1109a, 1119a, 1124a, 1130a, 1132a, 1137a], [1138a, 1144a, 1152a, 1159a, 1209p, 1219p, 1224p, 1230p, 1232p, 1237p], [1238p, 1244p, 1252p, 1259p, 109p, 119p, 124p, 130p, 132p, 137p], [138p, 144p, 152p, 159p, 209p, 219p, 224p, 230p, 232p, 237p], [238p, 244p, 252p, 259p, 309p, 319p, 324p, 330p, 332p, 337p], [338p, 344p, 352p, 359p, 409p, 419p, 424p, 430p, 432p, 437p], [438p, 444p, 452p, 459p, 509p, 519p, 524p, 530p, 532p, 537p], [538p, 544p, 552p, 559p, 609p, 619p, 624p, 630p, 632p, 637p], [638p, 644p, 652p, 659p, 709p, 719p, 724p, 730p, 732p, 737p]]
- -
time_points: [Lithgow St Terminus Fyshwick, Fyshwick Direct Factory Outlet, Canberra Times, Railway Station Kingston, Russell Offices, City Bus Station (Platform 8), Macarthur / Northbourne Ave, National Hockey Centre Lyneham, Australian Institute of Sport, University of Canberra, Belconnen Community Bus Station, Westfield Bus Station, Cohen Street Bus Station] time_points: [Lithgow St Terminus Fyshwick, Fyshwick Direct Factory Outlet, Canberra Times, Railway Station Kingston, Russell Offices, City Bus Station (Platform 8), Macarthur / Northbourne Ave, National Hockey Centre Lyneham, Australian Institute of Sport, University of Canberra, Belconnen Community Bus Station, Westfield Bus Station, Cohen Street Bus Station]
long_name: To Cohen Street Bus Station long_name: To Cohen Street Bus Station
between_stops: between_stops:
Westfield Bus Station-Cohen Street Bus Station: [] Westfield Bus Station-Cohen Street Bus Station: []
Belconnen Community Bus Station-Westfield Bus Station: [] Belconnen Community Bus Station-Westfield Bus Station: []
University of Canberra-Belconnen Community Bus Station: [Wjz68Yy, Wjz68Y0, Wjz68IH, Wjz68Ip, Wjz689c, Wjz681S] University of Canberra-Belconnen Community Bus Station: [Wjz68Yy, Wjz68Y0, Wjz68IH, Wjz68Ip, Wjz689c, Wjz681S]
short_name: "980" short_name: "980"
stop_times_sunday: [[845a, 853a, 904a, 911a, 917a, 928a, 934a, 939a, 943a, 949a, 956a, 958a, 1003a], [945a, 953a, 1004a, 1011a, 1017a, 1028a, 1034a, 1039a, 1043a, 1049a, 1056a, 1058a, 1103a], [1045a, 1053a, 1104a, 1111a, 1117a, 1128a, 1134a, 1139a, 1143a, 1149a, 1156a, 1158a, 1203p], ["-", "-", "-", 1130a, 1136a, 1146a, "-", "-", "-", "-", "-", "-", "-"], [1145a, 1153a, 1204p, 1211p, 1217p, 1228p, 1234p, 1239p, 1243p, 1249p, 1256p, 1258p, 103p], [1245p, 1253p, 104p, 111p, 117p, 128p, 134p, 139p, 143p, 149p, 156p, 158p, 203p], [145p, 153p, 204p, 211p, 217p, 228p, 234p, 239p, 243p, 249p, 256p, 258p, 303p], [245p, 253p, 304p, 311p, 317p, 328p, 334p, 339p, 343p, 349p, 356p, 358p, 403p], [345p, 353p, 404p, 411p, 417p, 428p, 434p, 439p, 443p, 449p, 456p, 458p, 503p], ["-", "-", "-", 440p, 446p, 456p, "-", "-", "-", "-", "-", "-", "-"], [445p, 453p, 504p, 511p, 517p, 528p, 534p, 539p, 543p, 549p, 556p, 558p, 603p], [545p, 553p, 604p, 611p, 617p, 628p, 634p, 639p, 643p, 649p, 656p, 658p, 703p]] stop_times_sunday: [[845a, 853a, 904a, 911a, 917a, 928a, 934a, 939a, 943a, 949a, 956a, 958a, 1003a], [945a, 953a, 1004a, 1011a, 1017a, 1028a, 1034a, 1039a, 1043a, 1049a, 1056a, 1058a, 1103a], [1045a, 1053a, 1104a, 1111a, 1117a, 1128a, 1134a, 1139a, 1143a, 1149a, 1156a, 1158a, 1203p], ["-", "-", "-", 1130a, 1136a, 1146a, "-", "-", "-", "-", "-", "-", "-"], [1145a, 1153a, 1204p, 1211p, 1217p, 1228p, 1234p, 1239p, 1243p, 1249p, 1256p, 1258p, 103p], [1245p, 1253p, 104p, 111p, 117p, 128p, 134p, 139p, 143p, 149p, 156p, 158p, 203p], [145p, 153p, 204p, 211p, 217p, 228p, 234p, 239p, 243p, 249p, 256p, 258p, 303p], [245p, 253p, 304p, 311p, 317p, 328p, 334p, 339p, 343p, 349p, 356p, 358p, 403p], [345p, 353p, 404p, 411p, 417p, 428p, 434p, 439p, 443p, 449p, 456p, 458p, 503p], ["-", "-", "-", 440p, 446p, 456p, "-", "-", "-", "-", "-", "-", "-"], [445p, 453p, 504p, 511p, 517p, 528p, 534p, 539p, 543p, 549p, 556p, 558p, 603p], [545p, 553p, 604p, 611p, 617p, 628p, 634p, 639p, 643p, 649p, 656p, 658p, 703p]]
- -
time_points: [Cohen Street Bus Station (Platform 1), Westfield Bus Station (Platform 1), Belconnen Community Bus Station (Platform 3), University of Canberra, Gwydir Square Kaleen, North Lyneham, Macarthur / Northbourne Ave, City Bus Station] time_points: [Cohen Street Bus Station (Platform 1), Westfield Bus Station (Platform 1), Belconnen Community Bus Station (Platform 3), University of Canberra, Gwydir Square Kaleen, North Lyneham, Macarthur / Northbourne Ave, City Bus Station]
long_name: To City Bus Station long_name: To City Bus Station
between_stops: between_stops:
Macarthur / Northbourne Ave-City Bus Station: [Wjz5QmR, Wjz5Qmu, Wjz5Qgn, Wjz5Qi2, Wjz5Pl0, Wjz5PdJ, Wjz5P8K, Wjz5P8n, Wjz5Oci, Wjz5O3Q] Macarthur / Northbourne Ave-City Bus Station: [Wjz5QmR, Wjz5Qmu, Wjz5Qgn, Wjz5Qi2, Wjz5Pl0, Wjz5PdJ, Wjz5P8K, Wjz5P8n, Wjz5Oci, Wjz5O3Q]
Westfield Bus Station (Platform 1)-Belconnen Community Bus Station (Platform 3): [] Westfield Bus Station (Platform 1)-Belconnen Community Bus Station (Platform 3): []
Cohen Street Bus Station (Platform 1)-Westfield Bus Station (Platform 1): [] Cohen Street Bus Station (Platform 1)-Westfield Bus Station (Platform 1): []
Belconnen Community Bus Station (Platform 3)-University of Canberra: [Wjz689c, Wjz681S] Belconnen Community Bus Station (Platform 3)-University of Canberra: [Wjz689c, Wjz681S]
short_name: "31" short_name: "31"
stop_times: [[612a, 614a, 618a, 623a, 628a, 635a, 640a, 647a], [642a, 644a, 648a, 653a, 658a, 705a, 710a, 717a], [709a, 711a, 715a, 720a, 725a, 733a, 741a, 757a], [738a, 740a, 744a, 749a, 756a, 805a, 813a, 829a], [808a, 810a, 814a, 819a, 826a, 835a, 843a, 859a], [838a, 840a, 844a, 849a, 856a, 905a, 913a, 929a], [927a, 929a, 933a, 938a, 944a, 952a, 957a, 1004a], [1027a, 1029a, 1033a, 1038a, 1044a, 1052a, 1057a, 1104a], [1127a, 1129a, 1133a, 1138a, 1144a, 1152a, 1157a, 1204p], [1227p, 1229p, 1233p, 1238p, 1244p, 1252p, 1257p, 104p], [127p, 129p, 133p, 138p, 144p, 152p, 157p, 204p], [227p, 229p, 233p, 238p, 244p, 252p, 257p, 305p], [312p, 314p, 318p, 323p, 329p, 337p, 342p, 350p], [342p, 344p, 348p, 353p, 359p, 407p, 412p, 420p], [412p, 414p, 418p, 423p, 429p, 437p, 442p, 450p], [442p, 444p, 448p, 453p, 459p, 507p, 512p, 520p], [512p, 514p, 518p, 523p, 529p, 537p, 542p, 550p], [542p, 544p, 548p, 553p, 559p, 607p, 612p, 620p], [626p, 628p, 632p, 637p, 642p, 649p, 654p, 700p], [726p, 728p, 732p, 737p, 742p, 749p, 754p, 800p], [826p, 828p, 832p, 837p, 842p, 849p, 854p, 900p], [926p, 928p, 932p, 937p, 942p, 949p, 954p, 1000p], [1026p, 1028p, 1032p, 1037p, 1042p, 1049p, 1054p, 1100p]] stop_times: [[612a, 614a, 618a, 623a, 628a, 635a, 640a, 647a], [642a, 644a, 648a, 653a, 658a, 705a, 710a, 717a], [709a, 711a, 715a, 720a, 725a, 733a, 741a, 757a], [738a, 740a, 744a, 749a, 756a, 805a, 813a, 829a], [808a, 810a, 814a, 819a, 826a, 835a, 843a, 859a], [838a, 840a, 844a, 849a, 856a, 905a, 913a, 929a], [927a, 929a, 933a, 938a, 944a, 952a, 957a, 1004a], [1027a, 1029a, 1033a, 1038a, 1044a, 1052a, 1057a, 1104a], [1127a, 1129a, 1133a, 1138a, 1144a, 1152a, 1157a, 1204p], [1227p, 1229p, 1233p, 1238p, 1244p, 1252p, 1257p, 104p], [127p, 129p, 133p, 138p, 144p, 152p, 157p, 204p], [227p, 229p, 233p, 238p, 244p, 252p, 257p, 305p], [312p, 314p, 318p, 323p, 329p, 337p, 342p, 350p], [342p, 344p, 348p, 353p, 359p, 407p, 412p, 420p], [412p, 414p, 418p, 423p, 429p, 437p, 442p, 450p], [442p, 444p, 448p, 453p, 459p, 507p, 512p, 520p], [512p, 514p, 518p, 523p, 529p, 537p, 542p, 550p], [542p, 544p, 548p, 553p, 559p, 607p, 612p, 620p], [626p, 628p, 632p, 637p, 642p, 649p, 654p, 700p], [726p, 728p, 732p, 737p, 742p, 749p, 754p, 800p], [826p, 828p, 832p, 837p, 842p, 849p, 854p, 900p], [926p, 928p, 932p, 937p, 942p, 949p, 954p, 1000p], [1026p, 1028p, 1032p, 1037p, 1042p, 1049p, 1054p, 1100p]]
- -
time_points: [Tuggeranong Bus Station (Platform 4), Kambah High, Kambah Village, Woden Bus Station (Platform 9), City Bus Station, City West] time_points: [Tuggeranong Bus Station (Platform 4), Kambah High, Kambah Village, Woden Bus Station (Platform 9), City Bus Station, City West]
long_name: To City West long_name: To City West
between_stops: between_stops:
City Bus Station-City West: [] City Bus Station-City West: []
Woden Bus Station (Platform 9)-City Bus Station: [Wjz3m3b, Wjz3m3b, Wjz3eRR, Wjz3eZ4, Wjz4KNu, Wjz4KO9, Wjz5Nht] Woden Bus Station (Platform 9)-City Bus Station: [Wjz3m3b, Wjz3m3b, Wjz3eRR, Wjz3eZ4, Wjz4KNu, Wjz4KO9, Wjz5Nht]
short_name: "62" short_name: "62"
stop_times: [[609a, 616a, 624a, 637a, "-", "-"], [639a, 646a, 654a, 707a, "-", "-"], [709a, 716a, 725a, 740a, 755a, 758a], [736a, 743a, 752a, 807a, 822a, 825a], [754a, 801a, 810a, 824a, "-", "-"], [809a, 816a, 825a, 840a, 855a, 858a], [839a, 846a, 855a, 909a, "-", "-"], [939a, 946a, 954a, 1007a, "-", "-"], [1039a, 1046a, 1054a, 1107a, "-", "-"], [1139a, 1146a, 1154a, 1207p, "-", "-"], [1239p, 1246p, 1254p, 107p, "-", "-"], [139p, 146p, 154p, 207p, "-", "-"], [239p, 246p, 254p, 308p, "-", "-"], [309p, 316p, 325p, 339p, "-", "-"], [339p, 346p, 355p, 409p, "-", "-"], [409p, 416p, 425p, 439p, "-", "-"], [439p, 446p, 455p, 509p, "-", "-"], [509p, 516p, 525p, 539p, "-", "-"], [539p, 546p, 555p, 609p, "-", "-"], [609p, 616p, 625p, 637p, "-", "-"], [639p, 645p, 652p, 703p, "-", "-"], [739p, 745p, 752p, 803p, "-", "-"], [839p, 845p, 852p, 903p, "-", "-"], [940p, 946p, 953p, 1004p, "-", "-"], [1040p, 1046p, 1053p, 1104p, "-", "-"]] stop_times: [[609a, 616a, 624a, 637a, "-", "-"], [639a, 646a, 654a, 707a, "-", "-"], [709a, 716a, 725a, 740a, 755a, 758a], [736a, 743a, 752a, 807a, 822a, 825a], [754a, 801a, 810a, 824a, "-", "-"], [809a, 816a, 825a, 840a, 855a, 858a], [839a, 846a, 855a, 909a, "-", "-"], [939a, 946a, 954a, 1007a, "-", "-"], [1039a, 1046a, 1054a, 1107a, "-", "-"], [1139a, 1146a, 1154a, 1207p, "-", "-"], [1239p, 1246p, 1254p, 107p, "-", "-"], [139p, 146p, 154p, 207p, "-", "-"], [239p, 246p, 254p, 308p, "-", "-"], [309p, 316p, 325p, 339p, "-", "-"], [339p, 346p, 355p, 409p, "-", "-"], [409p, 416p, 425p, 439p, "-", "-"], [439p, 446p, 455p, 509p, "-", "-"], [509p, 516p, 525p, 539p, "-", "-"], [539p, 546p, 555p, 609p, "-", "-"], [609p, 616p, 625p, 637p, "-", "-"], [639p, 645p, 652p, 703p, "-", "-"], [739p, 745p, 752p, 803p, "-", "-"], [839p, 845p, 852p, 903p, "-", "-"], [940p, 946p, 953p, 1004p, "-", "-"], [1040p, 1046p, 1053p, 1104p, "-", "-"]]
- -
time_points: [City Bus Station (Platform 8), Ainslie Shops, Hackett Shops, Dickson Shops, North Lyneham, Lyneham Shops Wattle Street, Macarthur / Miller O'Connor, City Bus Station] time_points: [Belconnen Community Bus Station (Platform 6), Westfield Bus Station (Platform 2), Cohen Street Bus Station (Platform 5), Charnwood, Fraser East Terminus, Charnwood, Cohen Street Bus Station, Westfield Bus Station, Belconnen Community Bus Station]
long_name: To City Bus Station  
between_stops: {}  
   
short_name: "937"  
stop_times_sunday: [[859a, 911a, 919a, 925a, 934a, 939a, 942a, 951a], [959a, 1011a, 1019a, 1025a, 1034a, 1039a, 1042a, 1051a], [1059a, 1111a, 1119a, 1125a, 1134a, 1139a, 1142a, 1151a], [1159a, 1211p, 1219p, 1225p, 1234p, 1239p, 1242p, 1251p], [1259p, 111p, 119p, 125p, 134p, 139p, 142p, 151p], [159p, 211p, 219p, 225p, 234p, 239p, 242p, 251p], [259p, 311p, 319p, 325p, 334p, 339p, 342p, 351p], [359p, 411p, 419p, 425p, 434p, 439p, 442p, 451p], [459p, 511p, 519p, 525p, 534p, 539p, 542p, 551p], [559p, 611p, 619p, 625p, 634p, 639p, 642p, 651p], [659p, 711p, 719p, 725p, 734p, 739p, 742p, 751p]]  
-  
time_points: [Belconnen Community Bus Station (Platform 6), Westfield Bus Station (Platform 2), Cohen Street Bus Station (Platform 5), Charnwood Shops, Fraser East Terminus, Charnwood Shops, Cohen Street Bus Station, Westfield Bus Station, Belconnen Community Bus Station]  
long_name: To Belconnen Community Bus Station long_name: To Belconnen Community Bus Station
between_stops: between_stops:
Belconnen Community Bus Station (Platform 6)-Westfield Bus Station (Platform 2): [] Belconnen Community Bus Station (Platform 6)-Westfield Bus Station (Platform 2): []
Westfield Bus Station-Belconnen Community Bus Station: [] Westfield Bus Station-Belconnen Community Bus Station: []
Cohen Street Bus Station-Westfield Bus Station: [] Cohen Street Bus Station-Westfield Bus Station: []
Westfield Bus Station (Platform 2)-Cohen Street Bus Station (Platform 5): [] Westfield Bus Station (Platform 2)-Cohen Street Bus Station (Platform 5): []
short_name: "907" short_name: "907"
stop_times_sunday: [[848a, 850a, 854a, 908a, 916a, 923a, 937a, 939a, 943a], [948a, 950a, 954a, 1008a, 1016a, 1023a, 1037a, 1039a, 1043a], [1048a, 1050a, 1054a, 1108a, 1116a, 1123a, 1137a, 1139a, 1143a], [1148a, 1150a, 1154a, 1208p, 1216p, 1223p, 1237p, 1239p, 1243p], [1248p, 1250p, 1254p, 108p, 116p, 123p, 137p, 139p, 143p], [148p, 150p, 154p, 208p, 216p, 223p, 237p, 239p, 243p], [248p, 250p, 254p, 308p, 316p, 323p, 337p, 339p, 343p], [348p, 350p, 354p, 408p, 416p, 423p, 437p, 439p, 443p], [448p, 450p, 454p, 508p, 516p, 523p, 537p, 539p, 543p], [548p, 550p, 554p, 608p, 616p, 623p, 637p, 639p, 643p], [647p, 649p, 653p, 706p, 714p, 721p, 734p, 736p, 740p]] stop_times_sunday: [[848a, 850a, 854a, 908a, 916a, 923a, 937a, 939a, 943a], [948a, 950a, 954a, 1008a, 1016a, 1023a, 1037a, 1039a, 1043a], [1048a, 1050a, 1054a, 1108a, 1116a, 1123a, 1137a, 1139a, 1143a], [1148a, 1150a, 1154a, 1208p, 1216p, 1223p, 1237p, 1239p, 1243p], [1248p, 1250p, 1254p, 108p, 116p, 123p, 137p, 139p, 143p], [148p, 150p, 154p, 208p, 216p, 223p, 237p, 239p, 243p], [248p, 250p, 254p, 308p, 316p, 323p, 337p, 339p, 343p], [348p, 350p, 354p, 408p, 416p, 423p, 437p, 439p, 443p], [448p, 450p, 454p, 508p, 516p, 523p, 537p, 539p, 543p], [548p, 550p, 554p, 608p, 616p, 623p, 637p, 639p, 643p], [647p, 649p, 653p, 706p, 714p, 721p, 734p, 736p, 740p]]
- -
time_points: [Cohen Street Bus Station (Platform 2), Westfield Bus Station (Platform 1), Belconnen Community Bus Station (Platform 2), William Webb / Ginninderra Drive, Chuculba / William Slim Dr, Gungahlin Marketplace, Kosciuszko / Everard, Flemington Rd / Sandford St, Macarthur / Northbourne Ave, City Bus Station]  
long_name: To City Bus Station  
between_stops:  
Macarthur / Northbourne Ave-City Bus Station: [Wjz5QmR, Wjz5Qmu, Wjz5Qgn, Wjz5Qi2, Wjz5Pl0, Wjz5PdJ, Wjz5P8K, Wjz5P8n, Wjz5Oci, Wjz5O3Q]  
Westfield Bus Station (Platform 1)-Belconnen Community Bus Station (Platform 2): []  
Cohen Street Bus Station (Platform 2)-Westfield Bus Station (Platform 1): []  
short_name: "956"  
stop_times: [[841a, 843a, 847a, 853a, 858a, 908a, 918a, 925a, 933a, 940a], [941a, 943a, 947a, 953a, 958a, 1008a, 1018a, 1025a, 1033a, 1040a], [1041a, 1043a, 1047a, 1053a, 1058a, 1108a, 1118a, 1125a, 1133a, 1140a], [1141a, 1143a, 1147a, 1153a, 1158a, 1208p, 1218p, 1225p, 1233p, 1240p], [1241p, 1243p, 1247p, 1253p, 1258p, 108p, 118p, 125p, 133p, 140p], [141p, 143p, 147p, 153p, 158p, 208p, 218p, 225p, 233p, 240p], [241p, 243p, 247p, 253p, 258p, 308p, 318p, 325p, 333p, 340p], [341p, 343p, 347p, 353p, 358p, 408p, 418p, 425p, 433p, 440p], [441p, 443p, 447p, 453p, 458p, 508p, 518p, 525p, 533p, 540p], [541p, 543p, 547p, 553p, 558p, 608p, 618p, 625p, 633p, 640p], [641p, 643p, 647p, 653p, 658p, 708p, 718p, 725p, 733p, 740p]]  
-  
time_points: [City Bus Station (Platform 8), Macarthur / Northbourne Ave, Northbourne Avenue / Antill St, Bimberi Centre] time_points: [City Bus Station (Platform 8), Macarthur / Northbourne Ave, Northbourne Avenue / Antill St, Bimberi Centre]
long_name: To Bimberi Centre long_name: To Bimberi Centre
between_stops: between_stops:
City Bus Station (Platform 8)-Macarthur / Northbourne Ave: [Wjz5O3Q, Wjz5Oci, Wjz5P8n, Wjz5P8K, Wjz5PdJ, Wjz5Pl0, Wjz5Qi2, Wjz5Qgn, Wjz5Qmu, Wjz5QmR] City Bus Station (Platform 8)-Macarthur / Northbourne Ave: [Wjz5O3Q, Wjz5Oci, Wjz5P8n, Wjz5P8K, Wjz5PdJ, Wjz5Pl0, Wjz5Qi2, Wjz5Qgn, Wjz5Qmu, Wjz5QmR]
short_name: "982" short_name: "982"
stop_times_sunday: [[342p, 348p, 350p, 400p]] stop_times_sunday: [[342p, 348p, 350p, 400p]]
- -
time_points: [Woden Bus Station (Platform 15), Pearce, Torrens Shops, Southlands Mawson, Woden Bus Station] time_points: [Woden Bus Station (Platform 15), Pearce, Torrens, Southlands Mawson, Woden Bus Station]
long_name: To Woden Bus Station long_name: To Woden Bus Station
between_stops: {} between_stops: {}
short_name: "21" short_name: "21"
stop_times: [[657a, 703a, 706a, 712a, 724a], [727a, 734a, 737a, 744a, 757a], [757a, 804a, 807a, 814a, 827a], [827a, 834a, 837a, 844a, 857a], [904a, 911a, 914a, 921a, 934a], [1004a, 1010a, 1013a, 1019a, 1031a], [1104a, 1110a, 1113a, 1119a, 1131a], [1204p, 1210p, 1213p, 1219p, 1231p], [104p, 110p, 113p, 119p, 131p], [204p, 210p, 213p, 219p, 231p], [304p, 311p, 314p, 321p, 334p], [327p, 334p, 337p, 344p, 357p], [357p, 404p, 407p, 414p, 427p], [427p, 434p, 437p, 444p, 457p], [457p, 504p, 507p, 514p, 527p], [527p, 534p, 537p, 544p, 557p], [557p, 604p, 607p, 614p, 627p], [627p, 633p, 636p, 642p, 654p], [720p, 726p, 729p, 735p, 747p], [820p, 826p, 829p, 835p, 847p], [920p, 926p, 929p, 935p, 947p], [1020p, 1026p, 1029p, 1035p, 1047p], [1120p, 1126p, 1129p, 1135p, "-"]] stop_times: [[657a, 703a, 706a, 712a, 724a], [727a, 734a, 737a, 744a, 757a], [757a, 804a, 807a, 814a, 827a], [827a, 834a, 837a, 844a, 857a], [904a, 911a, 914a, 921a, 934a], [1004a, 1010a, 1013a, 1019a, 1031a], [1104a, 1110a, 1113a, 1119a, 1131a], [1204p, 1210p, 1213p, 1219p, 1231p], [104p, 110p, 113p, 119p, 131p], [204p, 210p, 213p, 219p, 231p], [304p, 311p, 314p, 321p, 334p], [327p, 334p, 337p, 344p, 357p], [357p, 404p, 407p, 414p, 427p], [427p, 434p, 437p, 444p, 457p], [457p, 504p, 507p, 514p, 527p], [527p, 534p, 537p, 544p, 557p], [557p, 604p, 607p, 614p, 627p], [627p, 633p, 636p, 642p, 654p], [720p, 726p, 729p, 735p, 747p], [820p, 826p, 829p, 835p, 847p], [920p, 926p, 929p, 935p, 947p], [1020p, 1026p, 1029p, 1035p, 1047p], [1120p, 1126p, 1129p, 1135p, "-"]]
- -
time_points: [Fairbairn Park, Brindabella Business Park, Chisholm Shops, Tuggeranong Bus Station] time_points: [City Bus Station (Platform 8), ADFA, Hospice / Menindee Dr, St Thomas More's Campbell, City Bus Station]
long_name: To Tuggeranong Bus Station long_name: To City Bus Station
between_stops: between_stops:
Fairbairn Park-Brindabella Business Park: [WjzcJ38, WjzcBHZ, WjzcJ0K, WjzcrEu, WjzcrrQ, WjzcrK3] ADFA-Hospice / Menindee Dr: [WjzceHt, WjzceFT, WjzcdDs, Wjzcdsn, Wjzcdi7, Wjzcd2U, Wjzcd8D]
short_name: "786" stop_times_saturday: [[801a, 815a, 822a, 829a, 841a], [901a, 915a, 922a, 929a, 941a], [1101a, 1115a, 1122a, 1129a, 1141a], [101p, 115p, 122p, 129p, 141p], [301p, 315p, 322p, 329p, 341p], [501p, 515p, 522p, 529p, 541p], [701p, 715p, 722p, 729p, 741p]]
stop_times: [[445p, 455p, 520p, 533p], [515p, 525p, 550p, 603p], [545p, 555p, 620p, 633p]] short_name: "931"
-  
time_points: [Cohen Street Bus Station (Platform 1), Westfield Bus Station (Platform 1), Belconnen Community Bus Station (Platform 3), University of Canberra, Australian Institute of Sport, National Hockey Centre Lyneham, Macarthur / Northbourne Ave, City Bus Station (Platform 9), Russell Offices, Railway Station Kingston, Newcastle Street after Isa Street, Fyshwick Direct Factory Outlet, Lithgow St Terminus Fyshwick]  
long_name: To Lithgow St Terminus Fyshwick  
between_stops:  
Westfield Bus Station (Platform 1)-Belconnen Community Bus Station (Platform 3): []  
Cohen Street Bus Station (Platform 1)-Westfield Bus Station (Platform 1): []  
Belconnen Community Bus Station (Platform 3)-University of Canberra: [Wjz681S, Wjz689c, Wjz68Ip, Wjz68IH, Wjz68Y0, Wjz68Yy]  
short_name: "980"  
stop_times_sunday: [[820a, 822a, 826a, 834a, 840a, 845a, 851a, 859a, 908a, 914a, 922a, 931a, 940a], [920a, 922a, 926a, 934a, 940a, 945a, 951a, 959a, 1008a, 1014a, 1022a, 1031a, 1040a], [1020a, 1022a, 1026a, 1034a, 1040a, 1045a, 1051a, 1059a, 1108a, 1114a, 1122a, 1131a, 1140a], [1120a, 1122a, 1126a, 1134a, 1140a, 1145a, 1151a, 1159a, 1208p, 1214p, 1222p, 1231p, 1240p], [1220p, 1222p, 1226p, 1234p, 1240p, 1245p, 1251p, 1259p, 108p, 114p, 122p, 131p, 140p], [120p, 122p, 126p, 134p, 140p, 145p, 151p, 159p, 208p, 214p, 222p, 231p, 240p], [220p, 222p, 226p, 234p, 240p, 245p, 251p, 259p, 308p, 314p, 322p, 331p, 340p], [320p, 322p, 326p, 334p, 340p, 345p, 351p, 359p, 408p, 414p, 422p, 431p, 440p], ["-", "-", "-", "-", "-", "-", "-", 415p, 424p, 430p, "-", "-", "-"], [420p, 422p, 426p, 434p, 440p, 445p, 451p, 459p, 508p, 514p, 522p, 531p, 540p], [520p, 522p, 526p, 534p, 540p, 545p, 551p, 558p, "-", "-", "-", "-", "-"], [615p, 617p, 621p, 629p, 635p, 640p, 645p, 652p, "-", "-", "-", "-", "-"]]  
-  
time_points: [Woodcock / Clare Dennis, Tharwa Dr / Pockett Ave, Mentone View / Tharwa Drive, Russell Offices, City Bus Station (Platform 11), City West]  
long_name: To City West  
between_stops:  
Russell Offices-City Bus Station (Platform 11): [Wjz4-WZ, Wjz4-WL, Wjz4-Rc, Wjz4-KO, Wjz4_wS, Wjz4_jm, Wjz4T-X, Wjz5MEL, Wjz5MsT, Wjz5MsD, Wjz5Nht]  
City Bus Station (Platform 11)-City West: []  
short_name: "788"  
stop_times: [[710a, 719a, 734a, 811a, 820a, 824a], [740a, 749a, 804a, 841a, 850a, 854a]]  
- -
time_points: [Cohen Street Bus Station (Platform 2), Westfield Bus Station (Platform 1), Belconnen Community Bus Station (Platform 1), City Bus Station (Platform 1), Woden Bus Station (Platform 6), Tuggeranong Bus Station (Platform 7), Woodcock / Clare Dennis, Gordon Primary, Lanyon Market Place] time_points: [Cohen Street Bus Station (Platform 2), Westfield Bus Station (Platform 1), Belconnen Community Bus Station (Platform 1), City Bus Station (Platform 1), Woden Bus Station (Platform 6), Tuggeranong Bus Station (Platform 7), Woodcock / Clare Dennis, Gordon Primary, Lanyon Market Place]
long_name: To Lanyon Market Place long_name: To Lanyon Market Place
between_stops: between_stops:
Woden Bus Station (Platform 6)-Tuggeranong Bus Station (Platform 7): [Wjz3kAx, Wjz3kyX, Wjz3kwU, Wjz3iFK, Wjz3hL_, Wjz3gK-, Wjz3gQn, Wjz3gMq, Wjz2nLE, Wjz2mTK, Wjz2mGO, Wjz2lDC, Wjz239F, Wjz238T, Wjz213q] Woden Bus Station (Platform 6)-Tuggeranong Bus Station (Platform 7): [Wjz3kAx, Wjz3kyX, Wjz3kwU, Wjz3iFK, Wjz3hL_, Wjz3gK-, Wjz3gQn, Wjz3gMq, Wjz2nLE, Wjz2mTK, Wjz2mGO, Wjz2lDC, Wjz239F, Wjz238T, Wjz213q]
Cohen Street Bus Station (Platform 2)-Westfield Bus Station (Platform 1): [] Cohen Street Bus Station (Platform 2)-Westfield Bus Station (Platform 1): []
Westfield Bus Station (Platform 1)-Belconnen Community Bus Station (Platform 1): [] Westfield Bus Station (Platform 1)-Belconnen Community Bus Station (Platform 1): []
City Bus Station (Platform 1)-Woden Bus Station (Platform 6): [Wjz5Nht, Wjz4KO9, Wjz4KNu, Wjz3eZ4, Wjz3eRR, Wjz3m3b, Wjz3m3b] City Bus Station (Platform 1)-Woden Bus Station (Platform 6): [Wjz5Nht, Wjz4KO9, Wjz4KNu, Wjz3eZ4, Wjz3eRR, Wjz3m3b, Wjz3m3b]
Belconnen Community Bus Station (Platform 1)-City Bus Station (Platform 1): [Wjz681S, Wjz689c, Wjz68W3, Wjz68W5, Wjz6gia, Wjz6giR, Wjz5nw6, Wjz5nwb, Wjz5mbS, Wjz5maK, Wjz5G6B, Wjz5G6U, Wjz5GNG, Wjz5GMT, Wjz5FSY, Wjz5F-1] Belconnen Community Bus Station (Platform 1)-City Bus Station (Platform 1): [Wjz681S, Wjz689c, Wjz68W3, Wjz68W5, Wjz6gia, Wjz6giR, Wjz5nw6, Wjz5nwb, Wjz5mbS, Wjz5maK, Wjz5G6B, Wjz5G6U, Wjz5GNG, Wjz5GMT, Wjz5FSY, Wjz5F-1]
short_name: 18 318 short_name: 18 318
stop_times: [["-", "-", "-", "-", "-", 714a, 722a, 726a, 736a], ["-", "-", "-", "-", "-", 740a, 750a, 755a, 805a], [720a, 722a, 726a, 748a, 805a, 823a, 833a, 838a, 848a], [749a, 751a, 755a, 817a, 834a, 852a, 902a, 907a, 917a], ["-", "-", "-", "-", "-", 916a, 926a, 931a, 940a], ["-", "-", "-", "-", "-", 949a, 957a, 1001a, 1010a], [920a, 922a, 926a, 946a, 1003a, 1019a, 1027a, 1031a, 1040a], [950a, 952a, 956a, 1016a, 1033a, 1049a, 1057a, 1101a, 1110a], [1020a, 1022a, 1026a, 1046a, 1103a, 1119a, 1127a, 1131a, 1140a], [1050a, 1052a, 1056a, 1116a, 1133a, 1149a, 1157a, 1201p, 1210p], [1120a, 1122a, 1126a, 1146a, 1203p, 1219p, 1227p, 1231p, 1240p], [1150a, 1152a, 1156a, 1216p, 1233p, 1249p, 1257p, 101p, 110p], [1220p, 1222p, 1226p, 1246p, 103p, 119p, 127p, 131p, 140p], [1250p, 1252p, 1256p, 116p, 133p, 149p, 157p, 201p, 210p], [120p, 122p, 126p, 146p, 203p, 219p, 227p, 231p, 240p], [150p, 152p, 156p, 216p, 233p, 249p, 257p, 301p, 310p], [220p, 222p, 226p, 246p, 303p, 323p, 331p, 335p, 344p], [250p, 252p, 256p, 318p, 335p, 355p, 403p, 407p, 416p], [320p, 322p, 326p, 348p, 405p, 425p, 433p, 437p, 446p], [350p, 352p, 356p, 418p, 435p, 455p, 503p, 507p, 516p], [420p, 422p, 426p, 448p, 505p, 525p, 533p, 537p, 546p], [440p, 442p, 446p, 508p, 525p, 545p, 553p, 557p, 606p], [500p, 502p, 506p, 528p, 545p, 605p, 613p, 617p, 626p], [515p, 517p, 521p, 543p, 600p, 620p, 628p, 632p, 641p], [550p, 552p, 556p, 618p, 634p, 650p, 658p, 702p, 711p], [620p, 622p, 626p, 645p, 659p, 715p, 723p, 727p, 736p], [650p, 652p, 656p, 715p, 729p, 745p, 753p, 757p, 806p], ["-", "-", "-", "-", "-", 848p, 856p, 900p, 909p], ["-", "-", "-", "-", "-", 948p, 956p, 1000p, 1009p], ["-", "-", "-", "-", "-", 1048p, 1056p, 1100p, 1109p]] stop_times: [["-", "-", "-", "-", "-", 714a, 722a, 726a, 736a], ["-", "-", "-", "-", "-", 740a, 750a, 755a, 805a], [720a, 722a, 726a, 748a, 805a, 823a, 833a, 838a, 848a], [749a, 751a, 755a, 817a, 834a, 852a, 902a, 907a, 917a], ["-", "-", "-", "-", "-", 916a, 926a, 931a, 940a], ["-", "-", "-", "-", "-", 949a, 957a, 1001a, 1010a], [920a, 922a, 926a, 946a, 1003a, 1019a, 1027a, 1031a, 1040a], [950a, 952a, 956a, 1016a, 1033a, 1049a, 1057a, 1101a, 1110a], [1020a, 1022a, 1026a, 1046a, 1103a, 1119a, 1127a, 1131a, 1140a], [1050a, 1052a, 1056a, 1116a, 1133a, 1149a, 1157a, 1201p, 1210p], [1120a, 1122a, 1126a, 1146a, 1203p, 1219p, 1227p, 1231p, 1240p], [1150a, 1152a, 1156a, 1216p, 1233p, 1249p, 1257p, 101p, 110p], [1220p, 1222p, 1226p, 1246p, 103p, 119p, 127p, 131p, 140p], [1250p, 1252p, 1256p, 116p, 133p, 149p, 157p, 201p, 210p], [120p, 122p, 126p, 146p, 203p, 219p, 227p, 231p, 240p], [150p, 152p, 156p, 216p, 233p, 249p, 257p, 301p, 310p], [220p, 222p, 226p, 246p, 303p, 323p, 331p, 335p, 344p], [250p, 252p, 256p, 318p, 335p, 355p, 403p, 407p, 416p], [320p, 322p, 326p, 348p, 405p, 425p, 433p, 437p, 446p], [350p, 352p, 356p, 418p, 435p, 455p, 503p, 507p, 516p], [420p, 422p, 426p, 448p, 505p, 525p, 533p, 537p, 546p], [440p, 442p, 446p, 508p, 525p, 545p, 553p, 557p, 606p], [500p, 502p, 506p, 528p, 545p, 605p, 613p, 617p, 626p], [515p, 517p, 521p, 543p, 600p, 620p, 628p, 632p, 641p], [550p, 552p, 556p, 618p, 634p, 650p, 658p, 702p, 711p], [620p, 622p, 626p, 645p, 659p, 715p, 723p, 727p, 736p], [650p, 652p, 656p, 715p, 729p, 745p, 753p, 757p, 806p], ["-", "-", "-", "-", "-", 848p, 856p, 900p, 909p], ["-", "-", "-", "-", "-", 948p, 956p, 1000p, 1009p], ["-", "-", "-", "-", "-", 1048p, 1056p, 1100p, 1109p]]
- -
time_points: [Dickson, North Lyneham, Lyneham Shops Wattle Street, City Bus Station (Platform 4), Kings Ave / National Circuit, Manuka, Red Hill, Canberra Hospital, Woden Bus Station] time_points: [Dickson / Antill St, North Lyneham, Lyneham / Wattle St, City Bus Station (Platform 4), Kings Ave / National Circuit, Manuka, Red Hill, Canberra Hospital, Woden Bus Station]
long_name: To Woden Bus Station long_name: To Woden Bus Station
between_stops: between_stops:
Canberra Hospital-Woden Bus Station: [Wjz3mWn, Wjz3mPO, Wjz3mAg] Canberra Hospital-Woden Bus Station: [Wjz3mWn, Wjz3mPO, Wjz3mAg]
short_name: "6" short_name: "6"
stop_times: [["-", "-", "-", 650a, 658a, 703a, 710a, 720a, 728a], [648a, 655a, 701a, 715a, 723a, 728a, 736a, 750a, 758a], [718a, 725a, 731a, 747a, 759a, 804a, 812a, 826a, 834a], [748a, 756a, 804a, 820a, 830a, 838a, 846a, 903a, 911a], [818a, 827a, 836a, 852a, 904a, 909a, 917a, 931a, 939a], [848a, 856a, 903a, 919a, 931a, 936a, 943a, 955a, 1003a], [918a, 926a, 933a, 947a, 957a, 1002a, 1009a, 1021a, 1029a], [948a, 956a, 1003a, 1017a, 1027a, 1032a, 1039a, 1051a, 1059a], [1048a, 1056a, 1103a, 1117a, 1127a, 1132a, 1139a, 1151a, 1159a], [1148a, 1156a, 1203p, 1217p, 1227p, 1232p, 1239p, 1251p, 1259p], [1248p, 1256p, 103p, 117p, 127p, 132p, 139p, 151p, 159p], [148p, 156p, 203p, 217p, 227p, 232p, 239p, 251p, 259p], [248p, 256p, 303p, 319p, 331p, 336p, 344p, 358p, 406p], [318p, 326p, 333p, 349p, 401p, 406p, 414p, 428p, 436p], [348p, 356p, 403p, 419p, 431p, 436p, 444p, 458p, 506p], [418p, 426p, 433p, 449p, 501p, 506p, 514p, 528p, 536p], [448p, 456p, 503p, 519p, 531p, 536p, 544p, 558p, 606p], [518p, 526p, 533p, 549p, 601p, 606p, 614p, 628p, 636p], [548p, 556p, 603p, 619p, 631p, 636p, 643p, 653p, 701p], [640p, 647p, 653p, 705p, 713p, 718p, 725p, 735p, 743p], [740p, 747p, 753p, 805p, 813p, 818p, 825p, 835p, 843p], [840p, 847p, 853p, 905p, 913p, 918p, 925p, 935p, 943p], [940p, 947p, 953p, 1005p, 1013p, 1018p, 1025p, 1035p, 1043p], [1040p, 1047p, 1053p, 1105p, 1113p, 1118p, 1125p, 1135p, 1143p]] stop_times: [["-", "-", "-", 650a, 658a, 703a, 710a, 720a, 728a], [648a, 655a, 701a, 715a, 723a, 728a, 736a, 750a, 758a], [718a, 725a, 731a, 747a, 759a, 804a, 812a, 826a, 834a], [748a, 756a, 804a, 820a, 830a, 838a, 846a, 903a, 911a], [818a, 827a, 836a, 852a, 904a, 909a, 917a, 931a, 939a], [848a, 856a, 903a, 919a, 931a, 936a, 943a, 955a, 1003a], [918a, 926a, 933a, 947a, 957a, 1002a, 1009a, 1021a, 1029a], [948a, 956a, 1003a, 1017a, 1027a, 1032a, 1039a, 1051a, 1059a], [1048a, 1056a, 1103a, 1117a, 1127a, 1132a, 1139a, 1151a, 1159a], [1148a, 1156a, 1203p, 1217p, 1227p, 1232p, 1239p, 1251p, 1259p], [1248p, 1256p, 103p, 117p, 127p, 132p, 139p, 151p, 159p], [148p, 156p, 203p, 217p, 227p, 232p, 239p, 251p, 259p], [248p, 256p, 303p, 319p, 331p, 336p, 344p, 358p, 406p], [318p, 326p, 333p, 349p, 401p, 406p, 414p, 428p, 436p], [348p, 356p, 403p, 419p, 431p, 436p, 444p, 458p, 506p], [418p, 426p, 433p, 449p, 501p, 506p, 514p, 528p, 536p], [448p, 456p, 503p, 519p, 531p, 536p, 544p, 558p, 606p], [518p, 526p, 533p, 549p, 601p, 606p, 614p, 628p, 636p], [548p, 556p, 603p, 619p, 631p, 636p, 643p, 653p, 701p], [640p, 647p, 653p, 705p, 713p, 718p, 725p, 735p, 743p], [740p, 747p, 753p, 805p, 813p, 818p, 825p, 835p, 843p], [840p, 847p, 853p, 905p, 913p, 918p, 925p, 935p, 943p], [940p, 947p, 953p, 1005p, 1013p, 1018p, 1025p, 1035p, 1043p], [1040p, 1047p, 1053p, 1105p, 1113p, 1118p, 1125p, 1135p, 1143p]]
- -
time_points: [City Bus Station (Platform 7), Kings Ave / National Circuit, Manuka, Red Hill, Narrabundah, Red Hill, Manuka, Kings Ave / National Circuit, City Bus Station] time_points: [City Bus Station (Platform 7), Kings Ave / National Circuit, Manuka, Red Hill, Narrabundah, Red Hill, Manuka, Kings Ave / National Circuit, City Bus Station]
long_name: To City Bus Station long_name: To City Bus Station
between_stops: between_stops:
City Bus Station (Platform 7)-Kings Ave / National Circuit: [Wjz5FOn, Wjz4S1U, Wjz4Rs-, Wjz4RFJ, Wjz4RwH, Wjz4Quk] City Bus Station (Platform 7)-Kings Ave / National Circuit: [Wjz5FOn, Wjz4S1U, Wjz4Rs-, Wjz4RFJ, Wjz4RwH, Wjz4Quk]
Kings Ave / National Circuit-City Bus Station: [Wjz4Quk, Wjz4RwH, Wjz4RFJ, Wjz4Rs-, Wjz4S1U, Wjz5FOn] Kings Ave / National Circuit-City Bus Station: [Wjz4Quk, Wjz4RwH, Wjz4RFJ, Wjz4Rs-, Wjz4S1U, Wjz5FOn]
stop_times_saturday: [[756a, 803a, 807a, 814a, 824a, 833a, 839a, 843a, 852a], [856a, 903a, 907a, 914a, 924a, 933a, 939a, 943a, 952a], [956a, 1003a, 1007a, 1014a, 1024a, 1033a, 1039a, 1043a, 1052a], [1056a, 1103a, 1107a, 1114a, 1124a, 1133a, 1139a, 1143a, 1152a], [1156a, 1203p, 1207p, 1214p, 1224p, 1233p, 1239p, 1243p, 1252p], [1256p, 103p, 107p, 114p, 124p, 133p, 139p, 143p, 152p], [156p, 203p, 207p, 214p, 224p, 233p, 239p, 243p, 252p], [256p, 303p, 307p, 314p, 324p, 333p, 339p, 343p, 352p], [356p, 403p, 407p, 414p, 424p, 433p, 439p, 443p, 452p], [456p, 503p, 507p, 514p, 524p, 533p, 539p, 543p, 552p], [556p, 603p, 607p, 614p, 624p, 633p, 639p, 643p, 652p], [656p, 703p, 707p, 714p, 724p, 733p, 739p, 743p, 752p], [756p, 803p, 807p, 814p, 824p, 833p, 839p, 843p, 852p], [856p, 903p, 907p, 914p, 924p, 933p, 939p, 943p, 952p], [956p, 1003p, 1007p, 1014p, 1024p, 1033p, 1039p, 1043p, 1052p], [1056p, 1103p, 1107p, 1114p, 1124p, "-", "-", "-", "-"]] stop_times_saturday: [[756a, 803a, 807a, 814a, 824a, 833a, 839a, 843a, 852a], [856a, 903a, 907a, 914a, 924a, 933a, 939a, 943a, 952a], [956a, 1003a, 1007a, 1014a, 1024a, 1033a, 1039a, 1043a, 1052a], [1056a, 1103a, 1107a, 1114a, 1124a, 1133a, 1139a, 1143a, 1152a], [1156a, 1203p, 1207p, 1214p, 1224p, 1233p, 1239p, 1243p, 1252p], [1256p, 103p, 107p, 114p, 124p, 133p, 139p, 143p, 152p], [156p, 203p, 207p, 214p, 224p, 233p, 239p, 243p, 252p], [256p, 303p, 307p, 314p, 324p, 333p, 339p, 343p, 352p], [356p, 403p, 407p, 414p, 424p, 433p, 439p, 443p, 452p], [456p, 503p, 507p, 514p, 524p, 533p, 539p, 543p, 552p], [556p, 603p, 607p, 614p, 624p, 633p, 639p, 643p, 652p], [656p, 703p, 707p, 714p, 724p, 733p, 739p, 743p, 752p], [756p, 803p, 807p, 814p, 824p, 833p, 839p, 843p, 852p], [856p, 903p, 907p, 914p, 924p, 933p, 939p, 943p, 952p], [956p, 1003p, 1007p, 1014p, 1024p, 1033p, 1039p, 1043p, 1052p], [1056p, 1103p, 1107p, 1114p, 1124p, "-", "-", "-", "-"]]
short_name: "935" short_name: "935"
- -
time_points: [Woden Bus Station (Platform 5), Mount Neighbour School, Kambah High, Tuggeranong Bus Station] time_points: [Woden Bus Station (Platform 5), Mount Neighbour School, Kambah High, Tuggeranong Bus Station]
long_name: To Tuggeranong Bus Station long_name: To Tuggeranong Bus Station
between_stops: {} between_stops: {}
short_name: "960" short_name: "960"
stop_times_sunday: [[850a, 902a, 908a, 918a], [950a, 1002a, 1008a, 1018a], [1050a, 1102a, 1108a, 1118a], [1150a, 1202p, 1208p, 1218p], [1250p, 102p, 108p, 118p], [150p, 202p, 208p, 218p], [250p, 302p, 308p, 318p], [350p, 402p, 408p, 418p], [450p, 502p, 508p, 518p], [550p, 602p, 608p, 618p], [650p, 702p, 708p, 717p]] stop_times_sunday: [[850a, 902a, 908a, 918a], [950a, 1002a, 1008a, 1018a], [1050a, 1102a, 1108a, 1118a], [1150a, 1202p, 1208p, 1218p], [1250p, 102p, 108p, 118p], [150p, 202p, 208p, 218p], [250p, 302p, 308p, 318p], [350p, 402p, 408p, 418p], [450p, 502p, 508p, 518p], [550p, 602p, 608p, 618p], [650p, 702p, 708p, 717p]]
- -
time_points: [Woden Bus Station (Platform 11), Athllon / Sulwood Kambah, Erindale Centre, Tuggeranong Bus Station] time_points: [Woden Bus Station (Platform 11), Athllon / Sulwood Kambah, Erindale Centre, Tuggeranong Bus Station]
long_name: To Tuggeranong Bus Station long_name: To Tuggeranong Bus Station
between_stops: between_stops:
Woden Bus Station (Platform 11)-Athllon / Sulwood Kambah: [Wjz3kAx, Wjz3kyX, Wjz3kwU, Wjz3iFK, Wjz3hL_, Wjz3gK-, Wjz3gQn, Wjz3gMq, Wjz2mTK] Woden Bus Station (Platform 11)-Athllon / Sulwood Kambah: [Wjz3kAx, Wjz3kyX, Wjz3kwU, Wjz3iFK, Wjz3hL_, Wjz3gK-, Wjz3gQn, Wjz3gMq, Wjz2mTK]
short_name: "964" short_name: "964"
stop_times_sunday: [[905a, 914a, 926a, 937a], [1005a, 1014a, 1026a, 1037a], [1105a, 1114a, 1126a, 1137a], [1205p, 1214p, 1226p, 1237p], [105p, 114p, 126p, 137p], [205p, 214p, 226p, 237p], [305p, 314p, 326p, 337p], [405p, 414p, 426p, 437p], [505p, 514p, 526p, 537p], [605p, 614p, 626p, 637p], [705p, 714p, 726p, 737p]] stop_times_sunday: [[905a, 914a, 926a, 937a], [1005a, 1014a, 1026a, 1037a], [1105a, 1114a, 1126a, 1137a], [1205p, 1214p, 1226p, 1237p], [105p, 114p, 126p, 137p], [205p, 214p, 226p, 237p], [305p, 314p, 326p, 337p], [405p, 414p, 426p, 437p], [505p, 514p, 526p, 537p], [605p, 614p, 626p, 637p], [705p, 714p, 726p, 737p]]
-  
time_points: [Sydney Ave, Russell Offices, City Bus Station (Platform 11), Belconnen Way, Macgregor Shops, Dunlop, Fraser West Terminus]  
long_name: To Fraser West Terminus  
between_stops:  
Russell Offices-City Bus Station (Platform 11): [Wjz4-WZ, Wjz4-WL, Wjz4-Rc, Wjz4-KO, Wjz4_wS, Wjz4_jm, Wjz4T-X, Wjz5MEL, Wjz5MsT, Wjz5MsD, Wjz5Nht]  
short_name: "703"  
stop_times: [[440p, 448p, 458p, 516p, 527p, 534p, 541p], ["-", "-", 515p, 533p, 544p, 551p, 558p], ["-", "-", 526p, 544p, 555p, 602p, 609p], [520p, 528p, 538p, 556p, 607p, 614p, 621p], [545p, 553p, 603p, 621p, 632p, 639p, 646p]]  
-  
time_points: [Cooleman Court, Duffy Primary, CIT Weston, Lyons Shops, Woden Bus Station (Platform 10), Kings Ave / National Circuit, Russell Offices, Brindabella Business Park, Fairbairn Park]  
long_name: To Fairbairn Park  
between_stops:  
Brindabella Business Park-Fairbairn Park: [WjzcrK3, WjzcrrQ, WjzcrEu, WjzcJ0K, WjzcBHZ, WjzcJ38]  
Kings Ave / National Circuit-Russell Offices: [Wjz4Quk, Wjz4RwH, Wjz4RFJ]  
short_name: "28"  
stop_times: [[615a, 624a, 630a, 634a, 638a, 652a, 655a, 709a, 719a], [637a, 646a, 652a, 656a, 700a, 714a, 717a, 731a, 741a], [705a, 714a, 720a, 724a, 728a, 742a, 746a, 800a, 810a], [745a, 757a, 805a, 810a, 815a, 829a, 833a, 847a, 857a], [815a, 827a, 835a, 840a, 844a, "-", "-", "-", "-"], [844a, 856a, 904a, 909a, 913a, "-", "-", "-", "-"], [926a, 938a, 945a, 949a, 953a, "-", "-", "-", "-"], [1026a, 1038a, 1045a, 1049a, 1053a, "-", "-", "-", "-"], [1126a, 1138a, 1145a, 1149a, 1153a, "-", "-", "-", "-"], [1226p, 1238p, 1245p, 1249p, 1253p, "-", "-", "-", "-"], [126p, 138p, 145p, 149p, 153p, "-", "-", "-", "-"], [226p, 238p, 245p, 249p, 253p, "-", "-", "-", "-"], [326p, 338p, 346p, 351p, 354p, "-", "-", "-", "-"], [356p, 408p, 416p, 421p, 425p, "-", "-", "-", "-"], [415p, 427p, 435p, 440p, 444p, "-", "-", "-", "-"], [515p, 527p, 535p, 540p, 544p, "-", "-", "-", "-"], [615p, 627p, 634p, 638p, 641p, "-", "-", "-", "-"], [700p, 709p, 715p, 719p, 722p, "-", "-", "-", "-"], [800p, 809p, 815p, 819p, 822p, "-", "-", "-", "-"], [900p, 909p, 915p, 919p, 922p, "-", "-", "-", "-"], [1000p, 1009p, 1015p, 1019p, 1022p, "-", "-", "-", "-"]]  
-  
time_points: [Cohen Street Bus Station (Platform 1), Westfield Bus Station (Platform 1), Belconnen Community Bus Station (Platform 2), Chuculba / William Slim Dr, Ngunnawal Primary, Shoalhaven / Katherine Ave, Gungahlin Marketplace, Anthony Rolfe Av / Moonlight Av, Flemington Rd / Nullabor Ave, Flemington Rd / Sandford St, Macarthur / Northbourne Ave, City Bus Station]  
long_name: To City Bus Station  
between_stops:  
Belconnen Community Bus Station (Platform 2)-Chuculba / William Slim Dr: [Wjz69gA, Wjz69ht, Wjz69uI, Wjz69vO, Wjz6mip]  
Macarthur / Northbourne Ave-City Bus Station: [Wjz5QmR, Wjz5Qmu, Wjz5Qgn, Wjz5Qi2, Wjz5Pl0, Wjz5PdJ, Wjz5P8K, Wjz5P8n, Wjz5Oci, Wjz5O3Q]  
Cohen Street Bus Station (Platform 1)-Westfield Bus Station (Platform 1): []  
Westfield Bus Station (Platform 1)-Belconnen Community Bus Station (Platform 2): []  
stop_times_saturday: [["-", "-", "-", 708a, 719a, 727a, 735a, 744a, 751a, 758a, 806a, 813a], [752a, 754a, 758a, 808a, 819a, 827a, 835a, 844a, 851a, 858a, 906a, 913a], [852a, 854a, 858a, 908a, 919a, 927a, 935a, 944a, 951a, 958a, 1006a, 1013a], [952a, 954a, 958a, 1008a, 1019a, 1027a, 1035a, 1044a, 1051a, 1058a, 1106a, 1113a], [1052a, 1054a, 1058a, 1108a, 1119a, 1127a, 1135a, 1144a, 1151a, 1158a, 1206p, 1213p], [1152a, 1154a, 1158a, 1208p, 1219p, 1227p, 1235p, 1244p, 1251p, 1258p, 106p, 113p], [1252p, 1254p, 1258p, 108p, 119p, 127p, 135p, 144p, 151p, 158p, 206p, 213p], [152p, 154p, 158p, 208p, 219p, 227p, 235p, 244p, 251p, 258p, 306p, 313p], [252p, 254p, 258p, 308p, 319p, 327p, 335p, 344p, 351p, 358p, 406p, 413p], [352p, 354p, 358p, 408p, 419p, 427p, 435p, 444p, 451p, 458p, 506p, 513p], [452p, 454p, 458p, 508p, 519p, 527p, 535p, 544p, 551p, 558p, 606p, 613p], [552p, 554p, 558p, 608p, 619p, 627p, 635p, 644p, 651p, 658p, 706p, 713p], [652p, 654p, 658p, 708p, 719p, 727p, 735p, 744p, 751p, 758p, 806p, 813p], [752p, 754p, 758p, 808p, 819p, 827p, 835p, 844p, 851p, 858p, 906p, 913p], [852p, 854p, 858p, 908p, 919p, 927p, 935p, 944p, 951p, 958p, 1006p, 1013p], [952p, 954p, 958p, 1008p, 1019p, 1027p, 1035p, 1044p, 1051p, 1058p, 1106p, 1113p], [1052p, 1054p, 1058p, 1108p, 1119p, 1127p, 1135p, 1144p, 1151p, "-", "-", "-"]]  
short_name: "958"  
-  
time_points: [City Bus Station (Platform 8), Macarthur / Northbourne Ave, Northbourne Avenue / Antill St, Flemington Rd / Sandford St, Kosciuszko / Everard, Gungahlin Marketplace, Chuculba / William Slim Dr, Belconnen Community Bus Station, Westfield Bus Station, Cohen Street Bus Station]  
long_name: To Cohen Street Bus Station  
between_stops:  
Westfield Bus Station-Cohen Street Bus Station: []  
Macarthur / Northbourne Ave-Northbourne Avenue / Antill St: [Wjz5RkN, Wjz5Rsi, Wjz5RvC, Wjz5Sqk, Wjz5SrO, Wjz5Sux, Wjz5SDc]  
City Bus Station (Platform 8)-Macarthur / Northbourne Ave: [Wjz5O3Q, Wjz5Oci, Wjz5P8n, Wjz5P8K, Wjz5PdJ, Wjz5Pl0, Wjz5Qi2, Wjz5Qgn, Wjz5Qmu, Wjz5QmR]  
Belconnen Community Bus Station-Westfield Bus Station: []  
Chuculba / William Slim Dr-Belconnen Community Bus Station: [Wjz6mip, Wjz69vO, Wjz69uI, Wjz69ht, Wjz69gA]  
short_name: "56"  
stop_times: [["-", "-", "-", "-", 602a, 612a, 623a, 639a, 641a, 646a], ["-", "-", "-", "-", 636a, 646a, 657a, 713a, 715a, 720a], ["-", "-", "-", "-", 706a, 716a, 727a, 743a, 745a, 750a], [651a, 657a, 659a, 705a, 712a, 722a, 733a, 749a, 751a, 756a], ["-", "-", "-", "-", 726a, 736a, 747a, 804a, 806a, 811a], ["-", "-", "-", "-", 743a, 755a, 806a, 823a, 825a, 830a], [741a, 747a, 749a, 755a, 803a, 815a, 826a, 843a, 845a, 850a], [801a, 808a, 810a, 816a, 824a, 836a, 847a, 904a, 906a, 911a], [821a, 828a, 830a, 836a, 844a, 856a, 906a, 922a, 924a, 929a], [851a, 858a, 900a, 906a, 913a, 925a, 935a, 951a, 953a, 958a], [1004a, 1010a, 1012a, 1018a, 1025a, 1037a, 1047a, 1103a, 1105a, 1110a], [1104a, 1110a, 1112a, 1118a, 1125a, 1137a, 1147a, 1203p, 1205p, 1210p], [1204p, 1210p, 1212p, 1218p, 1225p, 1237p, 1247p, 103p, 105p, 110p], [104p, 110p, 112p, 118p, 125p, 137p, 147p, 203p, 205p, 210p], [204p, 210p, 212p, 218p, 225p, 237p, 247p, 303p, 305p, 310p], [302p, 309p, 311p, 318p, 326p, 338p, 349p, 406p, 408p, 413p], [358p, 405p, 407p, 414p, 422p, 434p, 445p, 502p, 504p, 509p], [409p, 416p, 418p, 425p, 433p, 445p, 456p, 513p, 515p, 520p], [429p, 436p, 438p, 445p, 453p, 505p, 516p, 533p, 535p, 540p], [449p, 456p, 458p, 505p, 513p, 525p, 536p, 553p, 555p, 600p], [510p, 517p, 519p, 526p, 534p, 546p, 557p, 613p, 615p, 620p], [530p, 537p, 539p, 546p, 554p, 605p, 615p, 631p, 633p, 638p], [550p, 557p, 559p, 604p, 611p, 621p, 631p, 647p, 649p, 654p], [610p, 616p, 618p, 623p, 630p, 640p, 650p, 706p, 708p, 713p], [704p, 710p, 712p, 717p, 724p, 734p, 744p, 800p, 802p, 807p], [804p, 810p, 812p, 817p, 824p, 834p, 844p, 900p, 902p, 907p], [904p, 910p, 912p, 917p, 924p, 934p, 944p, 1000p, 1002p, 1007p], [1004p, 1010p, 1012p, 1017p, 1024p, 1034p, 1044p, 1100p, 1102p, 1107p], [1104p, 1110p, 1112p, 1117p, 1124p, 1134p, 1144p, 1200a, 1202a, 1207a]]  
-  
time_points: [Cohen Street Bus Station (Platform 1), Westfield Bus Station (Platform 1), Belconnen Community Bus Station (Platform 2), Chuculba / William Slim Dr, Gungahlin Marketplace, Anthony Rolfe Av / Moonlight Av, Flemington Rd / Nullabor Ave, Flemington Rd / Sandford St, Northbourne Avenue / Antill St, Macarthur / Northbourne Ave]  
long_name: To Macarthur / Northbourne Ave  
between_stops:  
Northbourne Avenue / Antill St-Macarthur / Northbourne Ave: [Wjz5SDc, Wjz5Sux, Wjz5SrO, Wjz5Sqk, Wjz5RvC, Wjz5Rsi, Wjz5RkN]  
Belconnen Community Bus Station (Platform 2)-Chuculba / William Slim Dr: [Wjz69gA, Wjz69ht, Wjz69uI, Wjz69vO, Wjz6mip]  
Cohen Street Bus Station (Platform 1)-Westfield Bus Station (Platform 1): []  
Westfield Bus Station (Platform 1)-Belconnen Community Bus Station (Platform 2): []  
short_name: "58"  
stop_times: [["-", "-", "-", 543a, 554a, 602a, 609a, 615a, 621a, 623a], ["-", "-", "-", 623a, 634a, 642a, 649a, 655a, 701a, 703a], ["-", "-", "-", "-", 654a, 702a, 709a, 715a, 721a, 723a], ["-", "-", "-", "-", 713a, 721a, 728a, 734a, 740a, 742a], ["-", "-", "-", "-", 723a, 731a, 738a, 744a, 754a, 759a], ["-", "-", "-", "-", 740a, 748a, 755a, 803a, 814a, 819a], [723a, 725a, 729a, 743a, 754a, 803a, 810a, 818a, 829a, 834a], [744a, 746a, 750a, 805a, 816a, 825a, 832a, 840a, 851a, 856a], [825a, 827a, 831a, 846a, 857a, 905a, 912a, 919a, 925a, 927a], [905a, 907a, 911a, 925a, 935a, 943a, 950a, 957a, 1003a, 1005a], [1005a, 1007a, 1011a, 1025a, 1035a, 1043a, 1050a, 1057a, 1103a, 1105a], [1105a, 1107a, 1111a, 1125a, 1135a, 1143a, 1150a, 1157a, 1203p, 1205p], [1205p, 1207p, 1211p, 1225p, 1235p, 1243p, 1250p, 1257p, 103p, 105p], [105p, 107p, 111p, 125p, 135p, 143p, 150p, 157p, 203p, 205p], [205p, 207p, 211p, 225p, 235p, 243p, 250p, 257p, 303p, 305p], [307p, 309p, 313p, 327p, 337p, 345p, 352p, 359p, 406p, 408p], [405p, 407p, 411p, 426p, 437p, 446p, 453p, 501p, 508p, 510p], [425p, 427p, 431p, 446p, 457p, 506p, 513p, 521p, 528p, 530p], [445p, 447p, 451p, 506p, 517p, 526p, 533p, 541p, 548p, 550p], [505p, 507p, 511p, 526p, 537p, 546p, 553p, 601p, 607p, 609p], [525p, 527p, 531p, 546p, 557p, 605p, 612p, 618p, 624p, 626p], [545p, 547p, 551p, 606p, 616p, 624p, 631p, 637p, 643p, 645p], [604p, 606p, 610p, 624p, 634p, 642p, 649p, 655p, 701p, 703p], [704p, 706p, 710p, 724p, 734p, 742p, 749p, 755p, 801p, 803p], [804p, 806p, 810p, 824p, 834p, 842p, 849p, 855p, 901p, 903p], [904p, 906p, 910p, 924p, 934p, 942p, 949p, 955p, 1001p, 1003p], [1004p, 1006p, 1010p, 1024p, 1034p, 1042p, 1049p, 1055p, 1101p, 1103p], []]  
-  
time_points: [Cohen Street Bus Station (Platform 2), Westfield Bus Station (Platform 1), Belconnen Community Bus Station (Platform 1), City Bus Station (Platform 1), Woden Bus Station (Platform 6), Erindale Centre, Tuggeranong Bus Station]  
long_name: To Tuggeranong Bus Station  
between_stops:  
Cohen Street Bus Station (Platform 2)-Westfield Bus Station (Platform 1): []  
Westfield Bus Station (Platform 1)-Belconnen Community Bus Station (Platform 1): []  
stop_times_saturday: [[631a, 633a, 637a, 657a, 714a, 729a, 735a], [646a, 648a, 652a, 712a, 729a, 744a, 750a], [701a, 703a, 707a, 727a, 744a, 759a, 805a], [716a, 718a, 722a, 742a, 759a, 814a, 820a], [731a, 733a, 737a, 757a, 814a, 829a, 835a], [746a, 748a, 752a, 812a, 829a, 844a, 850a], [801a, 803a, 807a, 827a, 844a, 859a, 905a], [816a, 818a, 822a, 842a, 859a, 914a, 920a], [831a, 833a, 837a, 857a, 914a, 929a, 935a], [846a, 848a, 852a, 912a, 929a, 944a, 950a], [901a, 903a, 907a, 927a, 944a, 959a, 1005a], [916a, 918a, 922a, 942a, 959a, 1014a, 1020a], [931a, 933a, 937a, 957a, 1014a, 1029a, 1035a], [946a, 948a, 952a, 1012a, 1029a, 1044a, 1050a], [1001a, 1003a, 1007a, 1027a, 1044a, 1059a, 1105a], [1016a, 1018a, 1022a, 1042a, 1059a, 1114a, 1120a], [1031a, 1033a, 1037a, 1057a, 1114a, 1129a, 1135a], [1046a, 1048a, 1052a, 1112a, 1129a, 1144a, 1150a], [1053a, 1055a, 1059a, 1119a, 1134a, "-", "-"], [1101a, 1103a, 1107a, 1127a, 1144a, 1159a, 1205p], [1116a, 1118a, 1122a, 1142a, 1159a, 1214p, 1220p], [1123a, 1125a, 1129a, 1149a, 1204p, "-", "-"], [1131a, 1133a, 1137a, 1157a, 1214p, 1229p, 1235p], [1146a, 1148a, 1152a, 1212p, 1229p, 1244p, 1250p], [1153a, 1155a, 1159a, 1219p, 1234p, "-", "-"], [1201p, 1203p, 1207p, 1227p, 1244p, 1259p, 105p], [1216p, 1218p, 1222p, 1242p, 1259p, 114p, 120p], [1223p, 1225p, 1229p, 1249p, 104p, "-", "-"], [1231p, 1233p, 1237p, 1257p, 114p, 129p, 135p], [1246p, 1248p, 1252p, 112p, 129p, 144p, 150p], [1253p, 1255p, 1259p, 119p, 134p, "-", "-"], [101p, 103p, 107p, 127p, 144p, 159p, 205p], [116p, 118p, 122p, 142p, 159p, 214p, 220p], [123p, 125p, 129p, 149p, 204p, "-", "-"], [131p, 133p, 137p, 157p, 214p, 229p, 235p], [146p, 148p, 152p, 212p, 229p, 244p, 250p], [153p, 155p, 159p, 219p, 234p, "-", "-"], [201p, 203p, 207p, 227p, 244p, 259p, 305p], [216p, 218p, 222p, 242p, 259p, 314p, 320p], [223p, 225p, 229p, 249p, 304p, "-", "-"], [231p, 233p, 237p, 257p, 314p, 329p, 335p], [246p, 248p, 252p, 312p, 329p, 344p, 350p], [253p, 255p, 259p, 319p, 334p, "-", "-"], [301p, 303p, 307p, 327p, 344p, 359p, 405p], [316p, 318p, 322p, 342p, 359p, 414p, 420p], [323p, 325p, 329p, 349p, 404p, "-", "-"], [331p, 333p, 337p, 357p, 414p, 429p, 435p], [346p, 348p, 352p, 412p, 429p, 444p, 450p], [353p, 355p, 359p, 419p, 434p, "-", "-"], [401p, 403p, 407p, 427p, 444p, 459p, 505p], [416p, 418p, 422p, 442p, 459p, 514p, 520p], [431p, 433p, 437p, 457p, 514p, 529p, 535p], [446p, 448p, 452p, 512p, 529p, 544p, 550p], [501p, 503p, 507p, 527p, 544p, 559p, 605p], [516p, 518p, 522p, 542p, 559p, 614p, 620p], [531p, 533p, 537p, 557p, 614p, 629p, 635p], [546p, 548p, 552p, 612p, 629p, 643p, 649p], [601p, 603p, 607p, 627p, 642p, 656p, 702p], [616p, 618p, 622p, 641p, 655p, 709p, 715p], [631p, 633p, 637p, 656p, 710p, 724p, 730p], [646p, 648p, 652p, 711p, 725p, 739p, 745p], [701p, 703p, 707p, 726p, 740p, 754p, 800p], [716p, 718p, 722p, 741p, 755p, 809p, 815p], [731p, 733p, 737p, 756p, 810p, 824p, 830p], [746p, 748p, 752p, 811p, 825p, 839p, 845p], [801p, 803p, 807p, 826p, 840p, 854p, 900p], [816p, 818p, 822p, 841p, 855p, 909p, 915p], [831p, 833p, 837p, 856p, 910p, 924p, 930p], [846p, 848p, 852p, 911p, 925p, 939p, 945p], [901p, 903p, 907p, 926p, 940p, 954p, 1000p], [916p, 918p, 922p, 941p, 955p, 1009p, 1015p], [931p, 933p, 937p, 956p, 1010p, 1024p, 1030p], [946p, 948p, 952p, 1011p, 1025p, 1039p, 1045p], [1001p, 1003p, 1007p, 1026p, 1040p, 1054p, 1100p], [1016p, 1018p, 1022p, 1041p, 1055p, 1109p, 1115p], [1031p, 1033p, 1037p, 1056p, 1110p, 1124p, 1130p], [1046p, 1048p, 1052p, 1111p, 1125p, 1139p, 1145p], [1101p, 1103p, 1107p, 1126p, 1140p, 1154p, 1200a]]  
short_name: "900"  
-  
time_points: [Tuggeranong Bus Station (Platform 4), Isabella Shops, Theodore, Calwell Shops, Outtrim / Duggan, Tuggeranong Bus Station]  
long_name: To Tuggeranong Bus Station  
between_stops: {}  
   
short_name: "915"  
stop_times: [[915a, 925a, 934a, 943a, 946a, 955a], [1115a, 1125a, 1134a, 1143a, 1146a, 1155a], [115p, 125p, 134p, 143p, 146p, 155p], [315p, 325p, 334p, 343p, 346p, 355p], [515p, 525p, 534p, 543p, 546p, 555p], [715p, 725p, 734p, 743p, 746p, 755p]]  
-  
time_points: [Lithgow St Terminus Fyshwick, Fyshwick Direct Factory Outlet, Canberra Times, Railway Station Kingston, Russell Offices, City Bus Station (Platform 8), Macarthur / Northbourne Ave, National Hockey Centre Lyneham, Australian Institute of Sport, University of Canberra, Belconnen Community Bus Station, Westfield Bus Station, Cohen Street Bus Station]  
long_name: To Cohen Street Bus Station  
between_stops:  
Westfield Bus Station-Cohen Street Bus Station: []  
Belconnen Community Bus Station-Westfield Bus Station: []  
University of Canberra-Belconnen Community Bus Station: [Wjz68Yy, Wjz68Y0, Wjz68IH, Wjz68Ip, Wjz689c, Wjz681S]  
short_name: "980"  
stop_times: [[845a, 853a, 904a, 911a, 917a, 928a, 934a, 939a, 943a, 949a, 956a, 958a, 1003a], [945a, 953a, 1004a, 1011a, 1017a, 1028a, 1034a, 1039a, 1043a, 1049a, 1056a, 1058a, 1103a], [1045a, 1053a, 1104a, 1111a, 1117a, 1128a, 1134a, 1139a, 1143a, 1149a, 1156a, 1158a, 1203p], ["-", "-", "-", 1130a, 1136a, 1146a, "-", "-", "-", "-", "-", "-", "-"], [1145a, 1153a, 1204p, 1211p, 1217p, 1228p, 1234p, 1239p, 1243p, 1249p, 1256p, 1258p, 103p], [1245p, 1253p, 104p, 111p, 117p, 128p, 134p, 139p, 143p, 149p, 156p, 158p, 203p], [145p, 153p, 204p, 211p, 217p, 228p, 234p, 239p, 243p, 249p, 256p, 258p, 303p], [245p, 253p, 304p, 311p, 317p, 328p, 334p, 339p, 343p, 349p, 356p, 358p, 403p], [345p, 353p, 404p, 411p, 417p, 428p, 434p, 439p, 443p, 449p, 456p, 458p, 503p], ["-", "-", "-", 440p, 446p, 456p, "-", "-", "-", "-", "-", "-", "-"], [445p, 453p, 504p, 511p, 517p, 528p, 534p, 539p, 543p, 549p, 556p, 558p, 603p], [545p, 553p, 604p, 611p, 617p, 628p, 634p, 639p, 643p, 649p, 656p, 658p, 703p]]  
-  
time_points: [Tuggeranong Bus Station (Platform 4), Isabella Shops, Theodore, Calwell Shops, Outtrim / Duggan, Tuggeranong Bus Station]  
long_name: To Tuggeranong Bus Station  
between_stops: {}  
   
stop_times_saturday: [[715a, 725a, 734a, 743a, 746a, 755a], [915a, 925a, 934a, 943a, 946a, 955a], [1115a, 1125a, 1134a, 1143a, 1146a, 1155a], [115p, 125p, 134p, 143p, 146p, 155p], [315p, 325p, 334p, 343p, 346p, 355p], [515p, 525p, 534p, 543p, 546p, 555p], [715p, 725p, 734p, 743p, 746p, 755p], [918p, 928p, 937p, 946p, 949p, 958p], [1118p, 1128p, 1137p, 1146p, 1149p, "-"]]  
short_name: "915"  
-  
time_points: [Cooleman Court, Rivett Shops, Chapman Shops, Fisher Shops, Waramanga Shops, Woden Bus Station]  
long_name: To Woden Bus Station  
between_stops: {}  
   
short_name: "927"  
stop_times: [[855a, 903a, 906a, 916a, 919a, 926a], [955a, 1003a, 1006a, 1016a, 1019a, 1026a], [1055a, 1103a, 1106a, 1116a, 1119a, 1126a], [1155a, 1203p, 1206p, 1216p, 1219p, 1226p], [1255p, 103p, 106p, 116p, 119p, 126p], [155p, 203p, 206p, 216p, 219p, 226p], [255p, 303p, 306p, 316p, 319p, 326p], [355p, 403p, 406p, 416p, 419p, 426p], [455p, 503p, 506p, 516p, 519p, 526p], [555p, 603p, 606p, 616p, 619p, 626p], [655p, 703p, 706p, 716p, 719p, 726p]]  
-  
time_points: [Cohen Street Bus Station (Platform 1), Westfield Bus Station (Platform 1), Belconnen Community Bus Station (Platform 2), Chuculba / William Slim Dr, Federation Square, Nicholls Primary, Ngunnawal Primary, Gungahlin Marketplace]  
long_name: To Gungahlin Market Place  
between_stops:  
Belconnen Community Bus Station (Platform 2)-Chuculba / William Slim Dr: [Wjz69gA, Wjz69ht, Wjz69uI, Wjz69vO, Wjz6mip]  
Cohen Street Bus Station (Platform 1)-Westfield Bus Station (Platform 1): []  
Westfield Bus Station (Platform 1)-Belconnen Community Bus Station (Platform 2): []  
stop_times_saturday: [[822a, 824a, 828a, 836a, 841a, 846a, 856a, 906a], [920a, 922a, 926a, 934a, 939a, 944a, 954a, 1004a], [1020a, 1022a, 1026a, 1034a, 1039a, 1044a, 1054a, 1104a], [1120a, 1122a, 1126a, 1134a, 1139a, 1144a, 1154a, 1204p], [1220p, 1222p, 1226p, 1234p, 1239p, 1244p, 1254p, 104p], [120p, 122p, 126p, 134p, 139p, 144p, 154p, 204p], [220p, 222p, 226p, 234p, 239p, 244p, 254p, 304p], [320p, 322p, 326p, 334p, 339p, 344p, 354p, 404p], [420p, 422p, 426p, 434p, 439p, 444p, 454p, 504p], [520p, 522p, 526p, 534p, 539p, 544p, 554p, 604p], [620p, 622p, 626p, 634p, 639p, 644p, 654p, 704p], [720p, 722p, 726p, 734p, 739p, 744p, 754p, 804p], [820p, 822p, 826p, 834p, 839p, 844p, 854p, 904p], [920p, 922p, 926p, 934p, 939p, 944p, 954p, 1004p], [1020p, 1022p, 1026p, 1034p, 1039p, 1044p, 1054p, 1104p]]  
short_name: "951"  
-  
time_points: [Woden Bus Station, Geoscience Australia, Eye Hospital, Fyshwick Direct Factory Outlet, Canberra Times, Railway Station Kingston, Causeway, Kings Ave / National Circuit, Russell Offices, City Bus Station]  
long_name: To City Bus Station  
between_stops:  
Kings Ave / National Circuit-Russell Offices: [Wjz4Quk, Wjz4RwH, Wjz4RFJ]  
Russell Offices-City Bus Station: [Wjz4-WZ, Wjz4-WL, Wjz4-Rc, Wjz4-KO, Wjz4_wS, Wjz4_jm, Wjz4T-X, Wjz5MEL, Wjz5MsT, Wjz5MsD, Wjz5Nht]  
short_name: "80"  
stop_times: [[547a, 602a, 611a, 616a, 625a, 632a, 634a, 638a, 642a, 650a], [606a, 621a, 630a, 635a, 644a, 651a, 653a, 657a, 701a, 709a], [633a, 648a, 657a, 702a, 711a, 718a, 720a, 724a, 728a, 738a], [701a, 716a, 725a, 730a, 741a, 749a, 753a, 800a, 804a, 815a], [731a, 747a, 756a, 803a, 814a, 822a, 826a, 833a, 837a, 848a], [801a, 817a, 826a, 833a, 844a, 852a, 856a, 903a, 907a, 918a], [834a, 850a, 859a, 906a, 917a, 925a, 929a, 933a, 937a, 945a], [909a, 924a, 934a, 939a, 948a, 955a, 957a, 1001a, 1005a, 1013a], [940a, 955a, 1004a, 1009a, 1018a, 1025a, 1027a, 1031a, 1035a, 1043a], [1040a, 1055a, 1104a, 1109a, 1118a, 1125a, 1127a, 1131a, 1135a, 1143a], ["-", "-", "-", "-", "-", 1131a, 1133a, 1137a, 1141a, 1149a], [1140a, 1155a, 1204p, 1209p, 1218p, 1225p, 1227p, 1231p, 1235p, 1243p], [1240p, 1255p, 104p, 109p, 118p, 125p, 127p, 131p, 135p, 143p], [140p, 155p, 204p, 209p, 218p, 225p, 227p, 231p, 235p, 243p], [240p, 255p, 304p, 309p, 318p, 325p, 327p, 331p, 335p, 343p], [340p, 356p, 406p, 412p, 422p, 429p, 431p, 436p, 441p, 450p], [408p, 424p, 434p, 440p, 450p, 457p, 459p, 504p, 509p, 518p], [438p, 454p, 504p, 510p, 520p, 527p, 529p, 534p, 539p, 548p], [508p, 524p, 534p, 540p, 550p, 557p, 559p, 604p, 609p, 618p], [538p, 554p, 604p, 610p, 620p, 627p, 629p, 633p, 637p, 645p], [557p, 613p, 623p, 629p, 637p, 643p, 645p, 649p, 653p, 701p], ["-", "-", "-", "-", "-", 727p, 729p, 733p, 737p, 745p], ["-", "-", "-", "-", "-", 827p, 829p, 833p, 837p, 845p], ["-", "-", "-", "-", "-", 927p, 929p, 933p, 937p, 945p], ["-", "-", "-", "-", "-", 1027p, 1029p, 1033p, 1037p, 1045p]]  
-  
time_points: [Gungahlin Marketplace, Hibberson / Kate Crace, Flemington Rd / Sandford St, Northbourne Avenue / Antill St, Macarthur / Northbourne Ave, City Bus Station]  
long_name: To City Bus Station  
between_stops:  
Northbourne Avenue / Antill St-Macarthur / Northbourne Ave: [Wjz5SDc, Wjz5Sux, Wjz5SrO, Wjz5Sqk, Wjz5RvC, Wjz5Rsi, Wjz5RkN]  
Macarthur / Northbourne Ave-City Bus Station: [Wjz5QmR, Wjz5Qmu, Wjz5Qgn, Wjz5Qi2, Wjz5Pl0, Wjz5PdJ, Wjz5P8K, Wjz5P8n, Wjz5Oci, Wjz5O3Q]  
short_name: "50"  
stop_times: [[700p, 703p, 706p, 713p, 715p, 722p], [730p, 733p, 736p, 743p, 745p, 752p], [800p, 803p, 806p, 813p, 815p, 822p], [830p, 833p, 836p, 843p, 845p, 852p], [900p, 903p, 906p, 913p, 915p, 922p], [930p, 933p, 936p, 943p, 945p, 952p], [1000p, 1003p, 1006p, 1013p, 1015p, 1022p], [1030p, 1033p, 1036p, 1043p, 1045p, 1052p], [1100p, 1103p, 1106p, 1113p, 1115p, 1122p]]  
-  
time_points: [Kippax, Higgins, Hawker College, Hawker Shops, Weetangera Shops, Cohen Street Bus Station, Westfield Bus Station, Belconnen Community Bus Station]  
long_name: To Belconnen Community Bus Station  
between_stops:  
Westfield Bus Station-Belconnen Community Bus Station: []  
Cohen Street Bus Station-Westfield Bus Station: []  
short_name: "17"  
stop_times: [[601a, 606a, 612a, 617a, 620a, 625a, 627a, 631a], [631a, 636a, 642a, 647a, 650a, 655a, 657a, 701a], [701a, 706a, 712a, 717a, 720a, 725a, 727a, 731a], [721a, 726a, 732a, 737a, 740a, 746a, 748a, 752a], [741a, 747a, 753a, 758a, 801a, 807a, 809a, 813a], [801a, 807a, 813a, 818a, 821a, 827a, 829a, 833a], [821a, 827a, 833a, 838a, 841a, 847a, 849a, 853a], [841a, 847a, 853a, 858a, 901a, 907a, 909a, 913a], [925a, 931a, 937a, 942a, 945a, 950a, 952a, 956a], [956a, 1001a, 1007a, 1012a, 1015a, 1020a, 1022a, 1026a], [1026a, 1031a, 1037a, 1042a, 1045a, 1050a, 1052a, 1056a], [1056a, 1101a, 1107a, 1112a, 1115a, 1120a, 1122a, 1126a], [1126a, 1131a, 1137a, 1142a, 1145a, 1150a, 1152a, 1156a], [1156a, 1201p, 1207p, 1212p, 1215p, 1220p, 1222p, 1226p], [1226p, 1231p, 1237p, 1242p, 1245p, 1250p, 1252p, 1256p], [1256p, 101p, 107p, 112p, 115p, 120p, 122p, 126p], ["-", "-", 122p, 127p, 130p, 135p, 137p, 141p], [126p, 131p, 137p, 142p, 145p, 150p, 152p, 156p], [156p, 201p, 207p, 212p, 215p, 220p, 222p, 226p], [226p, 231p, 237p, 242p, 245p, 250p, 252p, 256p], ["-", "-", 252p, 257p, 300p, 306p, 308p, 312p], [256p, 301p, 307p, 312p, 315p, 321p, 323p, 327p], ["-", "-", 325p, 330p, 333p, 339p, 341p, 345p], [326p, 332p, 338p, 343p, 346p, 352p, 354p, 358p], [347p, 353p, 359p, 404p, 407p, 413p, 415p, 419p], ["-", "-", 403p, 408p, 411p, 417p, 419p, 423p], [417p, 423p, 429p, 434p, 437p, 443p, 445p, 449p], [447p, 453p, 459p, 504p, 507p, 513p, 515p, 519p], [517p, 523p, 529p, 534p, 537p, 543p, 545p, 549p], [547p, 553p, 559p, 604p, 607p, 613p, 615p, 619p], [617p, 623p, 629p, 634p, 637p, 641p, 643p, 647p], [719p, 724p, 730p, 735p, 738p, 742p, 744p, 748p], [819p, 824p, 830p, 835p, 838p, 842p, 844p, 848p], [919p, 924p, 930p, 935p, 938p, 942p, 944p, 948p], [1019p, 1024p, 1030p, 1035p, 1038p, 1042p, 1044p, 1048p], [1119p, 1124p, 1130p, 1135p, 1138p, 1142p, 1144p, 1148p], []]  
-  
time_points: [Cohen Street Bus Station (Platform 1), Westfield Bus Station (Platform 1), Belconnen Community Bus Station (Platform 3), University of Canberra, Giralang, Kaleen Village / Marybrynong, North Lyneham, Northbourne Avenue / Antill St, Macarthur / Northbourne Ave, City Bus Station]  
long_name: To City Bus Station  
between_stops:  
Northbourne Avenue / Antill St-Macarthur / Northbourne Ave: [Wjz5SDc, Wjz5Sux, Wjz5SrO, Wjz5Sqk, Wjz5RvC, Wjz5Rsi, Wjz5RkN]  
Macarthur / Northbourne Ave-City Bus Station: [Wjz5QmR, Wjz5Qmu, Wjz5Qgn, Wjz5Qi2, Wjz5Pl0, Wjz5PdJ, Wjz5P8K, Wjz5P8n, Wjz5Oci, Wjz5O3Q]  
Westfield Bus Station (Platform 1)-Belconnen Community Bus Station (Platform 3): []  
Cohen Street Bus Station (Platform 1)-Westfield Bus Station (Platform 1): []  
Belconnen Community Bus Station (Platform 3)-University of Canberra: [Wjz689c, Wjz681S]  
short_name: "30"  
stop_times: [[546a, 548a, 552a, 557a, 605a, 612a, 618a, 621a, 623a, 630a], [615a, 617a, 621a, 626a, 634a, 641a, 647a, 650a, 652a, 659a], [631a, 633a, 637a, 642a, 650a, 657a, 703a, 706a, 708a, 715a], [656a, 658a, 702a, 707a, 715a, 722a, 728a, 731a, 736a, 752a], ["-", "-", "-", "-", 729a, 738a, 746a, 750a, 755a, 811a], [724a, 726a, 730a, 735a, 743a, 752a, 800a, 804a, 809a, 825a], ["-", "-", "-", "-", 803a, 812a, 824a, 828a, 833a, 848a], [756a, 758a, 802a, 807a, 815a, 824a, 834a, 838a, 843a, 858a], ["-", "-", "-", "-", 829a, 838a, 846a, 850a, 855a, 911a], [824a, 826a, 830a, 835a, 843a, 852a, 900a, 904a, 909a, 925a], [853a, 855a, 859a, 904a, 912a, 921a, 929a, 932a, 934a, 941a], [953a, 955a, 959a, 1004a, 1011a, 1019a, 1027a, 1030a, 1032a, 1039a], [1053a, 1055a, 1059a, 1104a, 1111a, 1119a, 1127a, 1130a, 1132a, 1139a], [1153a, 1155a, 1159a, 1204p, 1211p, 1219p, 1227p, 1230p, 1232p, 1239p], [1253p, 1255p, 1259p, 104p, 111p, 119p, 127p, 130p, 132p, 139p], [153p, 155p, 159p, 204p, 211p, 219p, 227p, 230p, 232p, 239p], [242p, 244p, 248p, 253p, 300p, 308p, 316p, 320p, 322p, 330p], [307p, 309p, 313p, 318p, 327p, 335p, 343p, 347p, 349p, 357p], [331p, 333p, 337p, 342p, 351p, 359p, 407p, 411p, 413p, 421p], [401p, 403p, 407p, 412p, 421p, 429p, 437p, 441p, 443p, 451p], [431p, 433p, 437p, 442p, 451p, 459p, 507p, 511p, 513p, 521p], [501p, 503p, 507p, 512p, 521p, 529p, 537p, 541p, 543p, 551p], [531p, 533p, 537p, 542p, 551p, 559p, 607p, 611p, 613p, 621p], [552p, 554p, 558p, 603p, 612p, 620p, 628p, 632p, 634p, 640p], [652p, 654p, 658p, 703p, 711p, 718p, 724p, 727p, 729p, 735p], [752p, 754p, 758p, 803p, 811p, 818p, 824p, 827p, 829p, 835p], [852p, 854p, 858p, 903p, 911p, 918p, 924p, 927p, 929p, 935p], [952p, 954p, 958p, 1003p, 1011p, 1018p, 1024p, 1027p, 1029p, 1035p], [1052p, 1054p, 1058p, 1103p, 1111p, 1118p, 1124p, 1127p, 1129p, 1135p]]  
-  
time_points: [Tuggeranong Bus Station (Platform 7), Bonython Primary School, Lanyon Market Place, Conder Primary, Tharwa Drive / Knoke Ave, Gordon Primary, Woodcock / Clare Dennis, Bonython Primary School, Tuggeranong Bus Station]  
long_name: To Tuggeranong Bus Station  
between_stops: {}  
   
short_name: "914"  
stop_times_sunday: [[1025a, 1034a, 1040a, 1047a, 1050a, 1054a, 1059a, 1102a, 1112a], [1225p, 1234p, 1240p, 1247p, 1250p, 1254p, 1259p, 102p, 112p], [225p, 234p, 240p, 247p, 250p, 254p, 259p, 302p, 312p], [425p, 434p, 440p, 447p, 450p, 454p, 459p, 502p, 512p], [625p, 634p, 640p, 647p, 650p, 654p, 659p, 702p, 712p]]  
-  
time_points: [Woden Bus Station (Platform 15), Southlands Mawson, Farrer Primary School, Isaacs Shops, Canberra Hospital, Woden Bus Station]  
long_name: To Woden Bus Station  
between_stops:  
Canberra Hospital-Woden Bus Station: [Wjz3mWn, Wjz3mPO, Wjz3mAg]  
stop_times_saturday: [[810a, 819a, 824a, 829a, 833a, 841a], [1010a, 1019a, 1024a, 1029a, 1033a, 1041a], [1210p, 1219p, 1224p, 1229p, 1233p, 1241p], [210p, 219p, 224p, 229p, 233p, 241p], [410p, 419p, 424p, 429p, 433p, 441p], [610p, 619p, 624p, 629p, 633p, 641p], [813p, 821p, 826p, 830p, 834p, 841p], [1013p, 1021p, 1026p, 1030p, 1034p, 1041p]]  
short_name: "924"  
-  
time_points: [Lanyon Market Place, Tharwa Dr / Pockett Ave, Mentone View / Tharwa Drive, City West, City Bus Station (Platform 10), ACTEW AGL House]  
long_name: To ACTEW AGL House  
between_stops:  
City West-City Bus Station (Platform 10): []  
City Bus Station (Platform 10)-ACTEW AGL House: [Wjz5Nht]  
short_name: "785"  
stop_times: [[652a, 655a, 713a, 743a, 747a, 749a], [725a, 728a, 746a, 816a, 820a, 822a], [745a, 748a, 806a, 836a, 840a, 842a]]  
-  
time_points: [City West, City Bus Station (Platform 10), ACTEW AGL House, Woodcock / Clare Dennis, Tharwa Drive / Knoke Ave, Lanyon Market Place]  
long_name: To Lanyon Market Place  
between_stops:  
City West-City Bus Station (Platform 10): []  
ACTEW AGL House-Woodcock / Clare Dennis: [Wjz34Gq, Wjz33LB, Wjz33KX, Wjz33GY, Wjz33EK, WjrXUAm, WjrXUsW, WjrXUoV, WjrW_uo, Wjz2a26]  
City Bus Station (Platform 10)-ACTEW AGL House: [Wjz5Nht]  
short_name: "787"  
stop_times: [[516p, 522p, 524p, 556p, 607p, 609p], [535p, 541p, 543p, 615p, 626p, 628p]]  
-  
time_points: [City Bus Station (Platform 8), ADFA, Hospice / Menindee Dr, St Thomas More's Campbell, City Bus Station]  
long_name: To City Bus Station  
between_stops: {}  
   
short_name: "931"  
stop_times: [[901a, 915a, 922a, 929a, 941a], [1101a, 1115a, 1122a, 1129a, 1141a], [101p, 115p, 122p, 129p, 141p], [301p, 315p, 322p, 329p, 341p], [501p, 515p, 522p, 529p, 541p], [701p, 715p, 722p, 729p, 741p]]  
-  
time_points: [Woden Bus Station (Platform 15), Pearce Shops, Southlands Mawson, Torrens Shops, Chifley Shops, Lyons Shops, Woden Bus Station]  
long_name: To Woden Bus Station  
between_stops: {}  
   
short_name: "922"  
stop_times_sunday: [[1033a, 1039a, 1043a, 1049a, 1054a, 1058a, 1101a], [1233p, 1239p, 1243p, 1249p, 1254p, 1258p, 101p], [233p, 239p, 243p, 249p, 254p, 258p, 301p], [433p, 439p, 443p, 449p, 454p, 458p, 501p], [633p, 639p, 643p, 649p, 654p, 658p, 701p]]  
-  
time_points: [Fairbairn Park, Brindabella Business Park, Russell Offices, Dickson College, Gungahlin Marketplace]  
long_name: To Gungahlin Marketplace  
between_stops:  
Fairbairn Park-Brindabella Business Park: [WjzcJ38, WjzcBHZ, WjzcJ0K, WjzcrEu, WjzcrrQ, WjzcrK3]  
short_name: "757"  
stop_times: [[433p, 443p, 457p, 510p, 524p], [508p, 518p, 532p, 543p, 556p], [538p, 548p, 602p, 613p, 626p]]  
-  
time_points: [City Bus Station (Platform 9), National Zoo and Aquarium, Black Mountain Telstra Tower, Botanic Gardens, City Bus Station]  
long_name: To City Bus Station  
between_stops: {}  
   
short_name: "981"  
stop_times: [[1020a, 1034a, 1042a, 1048a, 1055a], [1150a, 1204p, 1212p, 1218p, 1225p], [120p, 134p, 142p, 148p, 155p], [250p, 304p, 312p, 318p, 325p], [420p, 434p, 442p, 448p, 455p]]  
-  
time_points: [Woden Bus Station (Platform 14), Canberra Hospital, Garran Shops, Hughes Shops, Deakin Shops, Kings Ave / National Circuit, City Bus Station (Platform 4), National Museum of Australia, Burton and Garran Hall Daley Road, O'Connor Shops, Calvary Hospital, Belconnen Community Bus Station, Westfield Bus Station, Cohen Street Bus Station]  
long_name: To Cohen Street Bus Station  
between_stops:  
Westfield Bus Station-Cohen Street Bus Station: []  
Belconnen Community Bus Station-Westfield Bus Station: []  
Woden Bus Station (Platform 14)-Canberra Hospital: [Wjz3mAg, Wjz3mPO, Wjz3mWn]  
Kings Ave / National Circuit-City Bus Station (Platform 4): [Wjz4Quk, Wjz4RwH, Wjz4RFJ, Wjz4Rs-, Wjz4S1U, Wjz5FOn]  
stop_times_saturday: [["-", "-", "-", "-", "-", "-", 752a, 759a, 804a, 809a, 816a, 833a, 835a, 840a], [813a, 820a, 822a, 826a, 831a, 840a, 852a, 859a, 904a, 909a, 916a, 933a, 935a, 940a], [913a, 920a, 922a, 926a, 931a, 940a, 952a, 959a, 1004a, 1009a, 1016a, 1033a, 1035a, 1040a], [1013a, 1020a, 1022a, 1026a, 1031a, 1040a, 1052a, 1059a, 1104a, 1109a, 1116a, 1133a, 1135a, 1140a], [1113a, 1120a, 1122a, 1126a, 1131a, 1140a, 1152a, 1159a, 1204p, 1209p, 1216p, 1233p, 1235p, 1240p], [1213p, 1220p, 1222p, 1226p, 1231p, 1240p, 1252p, 1259p, 104p, 109p, 116p, 133p, 135p, 140p], [113p, 120p, 122p, 126p, 131p, 140p, 152p, 159p, 204p, 209p, 216p, 233p, 235p, 240p], [213p, 220p, 222p, 226p, 231p, 240p, 252p, 259p, 304p, 309p, 316p, 333p, 335p, 340p], [313p, 320p, 322p, 326p, 331p, 340p, 352p, 359p, 404p, 409p, 416p, 433p, 435p, 440p], [413p, 420p, 422p, 426p, 431p, 440p, 452p, 459p, 504p, 509p, 516p, 533p, 535p, 540p], [513p, 520p, 522p, 526p, 531p, 540p, 552p, 559p, 604p, 609p, 616p, 633p, 635p, 640p], [613p, 620p, 622p, 626p, 631p, 640p, 652p, 659p, 704p, 709p, 716p, 733p, 735p, 740p], [713p, 720p, 722p, 726p, 731p, 740p, 752p, 759p, 804p, 809p, 816p, 833p, 835p, 840p], [813p, 820p, 822p, 826p, 831p, 840p, 852p, 859p, 904p, 909p, 916p, 933p, 935p, 940p], [913p, 920p, 922p, 926p, 931p, 940p, 952p, 959p, 1004p, 1009p, 1016p, 1033p, 1035p, 1040p], [1013p, 1020p, 1022p, 1026p, 1031p, 1040p, 1052p, 1059p, 1104p, 1109p, 1116p, 1133p, 1135p, 1140p], [1113p, 1120p, 1122p, 1126p, 1131p, 1140p, 1150p, "-", "-", "-", "-", "-", "-", "-"]]  
short_name: "934"  
-  
time_points: [City Bus Station (Platform 8), Macarthur / Northbourne Ave, Flemington Rd / Sandford St, Flemington Rd / Nullabor Ave, Anthony Rolfe Av / Moonlight Av, Gungahlin Marketplace, Shoalhaven / Katherine Ave, Ngunnawal Primary, Chuculba / William Slim Dr, Belconnen Community Bus Station, Westfield Bus Station, Cohen Street Bus Station]  
long_name: To Cohen Street Bus Station  
between_stops:  
Westfield Bus Station-Cohen Street Bus Station: []  
City Bus Station (Platform 8)-Macarthur / Northbourne Ave: [Wjz5O3Q, Wjz5Oci, Wjz5P8n, Wjz5P8K, Wjz5PdJ, Wjz5Pl0, Wjz5Qi2, Wjz5Qgn, Wjz5Qmu, Wjz5QmR]  
Belconnen Community Bus Station-Westfield Bus Station: []  
Chuculba / William Slim Dr-Belconnen Community Bus Station: [Wjz6mip, Wjz69vO, Wjz69uI, Wjz69ht, Wjz69gA]  
short_name: "958"  
stop_times_sunday: [[900a, 906a, 914a, 921a, 928a, 937a, 945a, 953a, 1004a, 1014a, 1016a, 1021a], [1000a, 1006a, 1014a, 1021a, 1028a, 1037a, 1045a, 1053a, 1104a, 1114a, 1116a, 1121a], [1100a, 1106a, 1114a, 1121a, 1128a, 1137a, 1145a, 1153a, 1204p, 1214p, 1216p, 1221p], [1200p, 1206p, 1214p, 1221p, 1228p, 1237p, 1245p, 1253p, 104p, 114p, 116p, 121p], [100p, 106p, 114p, 121p, 128p, 137p, 145p, 153p, 204p, 214p, 216p, 221p], [200p, 206p, 214p, 221p, 228p, 237p, 245p, 253p, 304p, 314p, 316p, 321p], [300p, 306p, 314p, 321p, 328p, 337p, 345p, 353p, 404p, 414p, 416p, 421p], [400p, 406p, 414p, 421p, 428p, 437p, 445p, 453p, 504p, 514p, 516p, 521p], [500p, 506p, 514p, 521p, 528p, 537p, 545p, 553p, 604p, 614p, 616p, 621p], [600p, 606p, 614p, 621p, 628p, 637p, 645p, 653p, 704p, 714p, 716p, 721p], [700p, 706p, 714p, 721p, 728p, 737p, 745p, 753p, 804p, 814p, 816p, 821p]]  
-  
time_points: [Woden Bus Station (Platform 5), Mount Neighbour School, Kambah High, Tuggeranong Bus Station]  
long_name: To Tuggeranong Bus Station  
between_stops: {}  
   
short_name: "960"  
stop_times: [[850a, 902a, 908a, 918a], [950a, 1002a, 1008a, 1018a], [1050a, 1102a, 1108a, 1118a], [1150a, 1202p, 1208p, 1218p], [1250p, 102p, 108p, 118p], [150p, 202p, 208p, 218p], [250p, 302p, 308p, 318p], [350p, 402p, 408p, 418p], [450p, 502p, 508p, 518p], [550p, 602p, 608p, 618p], [650p, 702p, 708p, 717p]]  
-  
time_points: [City Bus Station (Platform 8), St Thomas More's Campbell, Hospice / Menindee Dr, ADFA, City Bus Station]  
long_name: To City Bus Station  
between_stops:  
ADFA-City Bus Station: [Wjzcend, Wjzd8br, Wjzd0CK, Wjz5VUU, Wjz5VFA, Wjz5VAq, Wjz5V64, Wjz5NRJ, Wjz5NAQ]  
stop_times_saturday: [[1001a, 1013a, 1020a, 1027a, 1041a], [1201p, 1213p, 1220p, 1227p, 1241p], [201p, 213p, 220p, 227p, 241p], [401p, 413p, 420p, 427p, 441p], [601p, 613p, 620p, 627p, 641p], [801p, 813p, 820p, 827p, 841p], [901p, 913p, 920p, 927p, 941p], [1001p, 1013p, 1020p, 1027p, 1041p], [1101p, 1113p, 1120p, 1127p, 1141p]]  
short_name: "930"  
-  
time_points: [Campbell Park Offices, ADFA, Russell Offices, Kings Ave / National Circuit, Woden Bus Station (Platform 10), Athllon / Sulwood Kambah, Wanniassa High, Erindale Centre, Monash Goodwin Village, Tuggeranong Bus Station]  
long_name: To Tuggeranong Bus Station  
between_stops:  
Russell Offices-Kings Ave / National Circuit: [Wjz4RFJ, Wjz4RwH, Wjz4Quk]  
Woden Bus Station (Platform 10)-Athllon / Sulwood Kambah: [Wjz3kAx, Wjz3kyX, Wjz3kwU, Wjz3iFK, Wjz3hL_, Wjz3gK-, Wjz3gQn, Wjz3gMq, Wjz2mTK]  
ADFA-Russell Offices: [Wjzcend, Wjzce4H, Wjzce7O, Wjzc54R, Wjzc55s, Wjzc60i, Wjzc60A]  
Campbell Park Offices-ADFA: [Wjzce7O, Wjzce4H, Wjzcend]  
short_name: "63"  
stop_times: [["-", "-", "-", "-", "-", "-", 615a, 619a, 623a, 631a], ["-", "-", "-", "-", "-", "-", 645a, 649a, 653a, 701a], ["-", "-", "-", "-", 703a, 710a, 715a, 719a, 723a, 731a], ["-", "-", "-", "-", 723a, 730a, 736a, 741a, 746a, 756a], ["-", "-", "-", "-", 803a, 812a, 818a, 823a, 828a, 838a], ["-", "-", "-", "-", 823a, 832a, 838a, 843a, 848a, 858a], ["-", "-", "-", "-", 903a, 912a, 918a, 923a, 928a, 937a], ["-", "-", "-", "-", 1003a, 1011a, 1017a, 1022a, 1026a, 1035a], ["-", "-", "-", "-", 1103a, 1111a, 1117a, 1122a, 1126a, 1135a], ["-", "-", "-", "-", 1203p, 1211p, 1217p, 1222p, 1226p, 1235p], ["-", "-", "-", "-", 103p, 111p, 117p, 122p, 126p, 135p], ["-", "-", "-", "-", 203p, 211p, 217p, 222p, 226p, 235p], ["-", "-", "-", "-", 303p, 312p, 318p, 323p, 328p, 338p], ["-", "-", "-", "-", 323p, 332p, 338p, 343p, 348p, 358p], ["-", "-", "-", "-", 403p, 412p, 418p, 423p, 428p, 438p], ["-", "-", "-", "-", 423p, 432p, 438p, 443p, 448p, 458p], [437p, 441p, 445p, 448p, 503p, 512p, 518p, 523p, 528p, 538p], [457p, 501p, 505p, 508p, 523p, 532p, 538p, 543p, 548p, 558p], [537p, 541p, 545p, 548p, 603p, 612p, 618p, 623p, 628p, 637p], [557p, 601p, 605p, 608p, 623p, 632p, 638p, 643p, 647p, 656p], ["-", "-", "-", "-", 703p, 711p, 717p, 722p, 726p, 735p], ["-", "-", "-", "-", 803p, 811p, 817p, 822p, 826p, 835p], ["-", "-", "-", "-", 903p, 911p, 917p, 922p, 926p, 935p], ["-", "-", "-", "-", 1003p, 1011p, 1017p, 1022p, 1026p, 1035p], ["-", "-", "-", "-", 1103p, 1111p, 1117p, 1122p, 1126p, 1135p], []]  
-  
time_points: [City Bus Station (Platform 4), Macarthur / Miller O'Connor, Lyneham Shops Wattle Street, North Lyneham, Dickson Shops, Hackett Shops, Ainslie Shops, City Bus Station]  
long_name: To City Bus Station  
between_stops: {}  
   
stop_times_saturday: [[718a, 727a, 730a, 735a, 744a, 749a, 757a, 809a], [818a, 827a, 830a, 835a, 844a, 849a, 857a, 909a], [918a, 927a, 930a, 935a, 944a, 949a, 957a, 1009a], [1018a, 1027a, 1030a, 1035a, 1044a, 1049a, 1057a, 1109a], [1118a, 1127a, 1130a, 1135a, 1144a, 1149a, 1157a, 1209p], [1218p, 1227p, 1230p, 1235p, 1244p, 1249p, 1257p, 109p], [118p, 127p, 130p, 135p, 144p, 149p, 157p, 209p], [218p, 227p, 230p, 235p, 244p, 249p, 257p, 309p], [318p, 327p, 330p, 335p, 344p, 349p, 357p, 409p], [418p, 427p, 430p, 435p, 444p, 449p, 457p, 509p], [518p, 527p, 530p, 535p, 544p, 549p, 557p, 609p], [618p, 627p, 630p, 635p, 644p, 649p, 657p, 709p], [718p, 727p, 730p, 735p, 744p, 749p, 757p, 809p], [818p, 827p, 830p, 835p, 844p, 849p, 857p, 909p], [918p, 927p, 930p, 935p, 944p, 949p, 957p, 1009p], [1018p, 1027p, 1030p, 1035p, 1044p, 1049p, 1057p, 1109p], [1118p, 1127p, 1130p, 1135p, 1144p, "-", "-", "-"]]  
short_name: "936"  
-  
time_points: [Cohen Street Bus Station (Platform 1), Westfield Bus Station (Platform 1), Belconnen Community Bus Station (Platform 2), Chuculba / William Slim Dr, Ngunnawal Primary, Shoalhaven / Katherine Ave, Gungahlin Marketplace, Anthony Rolfe Av / Moonlight Av, Flemington Rd / Nullabor Ave, Flemington Rd / Sandford St, Macarthur / Northbourne Ave, City Bus Station]  
long_name: To City Bus Station  
between_stops:  
Belconnen Community Bus Station (Platform 2)-Chuculba / William Slim Dr: [Wjz69gA, Wjz69ht, Wjz69uI, Wjz69vO, Wjz6mip]  
Macarthur / Northbourne Ave-City Bus Station: [Wjz5QmR, Wjz5Qmu, Wjz5Qgn, Wjz5Qi2, Wjz5Pl0, Wjz5PdJ, Wjz5P8K, Wjz5P8n, Wjz5Oci, Wjz5O3Q]  
Cohen Street Bus Station (Platform 1)-Westfield Bus Station (Platform 1): []  
Westfield Bus Station (Platform 1)-Belconnen Community Bus Station (Platform 2): []  
short_name: "958"  
stop_times: [[852a, 854a, 858a, 908a, 919a, 927a, 935a, 944a, 951a, 958a, 1006a, 1013a], [952a, 954a, 958a, 1008a, 1019a, 1027a, 1035a, 1044a, 1051a, 1058a, 1106a, 1113a], [1052a, 1054a, 1058a, 1108a, 1119a, 1127a, 1135a, 1144a, 1151a, 1158a, 1206p, 1213p], [1152a, 1154a, 1158a, 1208p, 1219p, 1227p, 1235p, 1244p, 1251p, 1258p, 106p, 113p], [1252p, 1254p, 1258p, 108p, 119p, 127p, 135p, 144p, 151p, 158p, 206p, 213p], [152p, 154p, 158p, 208p, 219p, 227p, 235p, 244p, 251p, 258p, 306p, 313p], [252p, 254p, 258p, 308p, 319p, 327p, 335p, 344p, 351p, 358p, 406p, 413p], [352p, 354p, 358p, 408p, 419p, 427p, 435p, 444p, 451p, 458p, 506p, 513p], [452p, 454p, 458p, 508p, 519p, 527p, 535p, 544p, 551p, 558p, 606p, 613p], [552p, 554p, 558p, 608p, 619p, 627p, 635p, 644p, 651p, 658p, 706p, 713p], [652p, 654p, 658p, 708p, 719p, 727p, 735p, 744p, 751p, 758p, 806p, 813p]]  
-  
time_points: [Campbell Park Offices, ADFA, Russell Offices, Kings Ave / National Circuit, Woden Bus Station (Platform 3), Cooleman Court, Canberra College Weston Campus, Chapman Shops, Weston Creek Terminus]  
long_name: To Weston Creek Terminus  
between_stops:  
Russell Offices-Kings Ave / National Circuit: [Wjz4RFJ, Wjz4RwH, Wjz4Quk]  
ADFA-Russell Offices: [Wjzcend, Wjzce4H, Wjzce7O, Wjzc54R, Wjzc55s, Wjzc60i, Wjzc60A]  
Campbell Park Offices-ADFA: [Wjzce7O, Wjzce4H, Wjzcend]  
short_name: 26 226  
stop_times: [["-", "-", "-", "-", 718a, 725a, 727a, 731a, 735a], ["-", "-", "-", "-", 818a, 828a, 832a, 837a, 841a], ["-", "-", "-", "-", 858a, 908a, 912a, 917a, 921a], ["-", "-", "-", "-", 958a, 1007a, 1010a, 1015a, 1019a], ["-", "-", "-", "-", 1058a, 1107a, 1110a, 1115a, 1119a], ["-", "-", "-", "-", 1158a, 1207p, 1210p, 1215p, 1219p], ["-", "-", "-", "-", 1258p, 107p, 110p, 115p, 119p], ["-", "-", "-", "-", 158p, 207p, 210p, 215p, 219p], ["-", "-", "-", "-", 258p, 309p, 313p, 319p, 324p], ["-", "-", "-", "-", 328p, 340p, 344p, 350p, 355p], ["-", "-", "-", "-", 354p, 406p, 410p, 416p, 421p], ["-", "-", "-", "-", 418p, 430p, 434p, 440p, 445p], ["-", "-", "-", "-", 448p, 500p, 504p, 510p, 515p], [452p, 456p, 500p, 503p, 518p, 530p, 534p, 540p, 545p], [522p, 526p, 530p, 533p, 548p, 600p, 604p, 610p, 615p], ["-", "-", "-", "-", 618p, 630p, 632p, 636p, 640p], ["-", "-", "-", "-", 650p, 657p, 659p, 703p, 707p], ["-", "-", "-", "-", 750p, 757p, 759p, 803p, 807p], ["-", "-", "-", "-", 850p, 857p, 859p, 903p, 907p], ["-", "-", "-", "-", 950p, 957p, 959p, 1003p, 1007p], ["-", "-", "-", "-", 1050p, 1057p, 1059p, 1103p, 1107p]]  
-  
time_points: [Cooleman Court, Duffy, Holder, Weston Primary, Woden Bus Station]  
long_name: To Woden Bus Station  
between_stops: {}  
   
short_name: "925"  
stop_times_sunday: [[924a, 931a, 934a, 937a, 946a], [1024a, 1031a, 1034a, 1037a, 1046a], [1124a, 1131a, 1134a, 1137a, 1146a], [1224p, 1231p, 1234p, 1237p, 1246p], [124p, 131p, 134p, 137p, 146p], [224p, 231p, 234p, 237p, 246p], [324p, 331p, 334p, 337p, 346p], [424p, 431p, 434p, 437p, 446p], [524p, 531p, 534p, 537p, 546p], [624p, 631p, 634p, 637p, 646p]]  
-  
time_points: [City West, City Bus Station (Platform 10), Holder Shops, Duffy Primary, Rivett Shops, Cooleman Court]  
long_name: To Cooleman Court  
between_stops:  
City West-City Bus Station (Platform 10): []  
short_name: "729"  
stop_times: [[445p, 451p, 513p, 518p, 526p, 532p], [515p, 521p, 543p, 548p, 556p, 602p]]  
-  
time_points: [Tuggeranong Bus Station (Platform 7), Heagney / Clift Richardson, Chisholm Shops, Erindale Centre, Tuggeranong Bus Station]  
long_name: To Tuggeranong Bus Station  
between_stops: {}  
   
stop_times_saturday: [[803a, 816a, 824a, 838a, 848a], [1003a, 1016a, 1024a, 1038a, 1048a], [1203p, 1216p, 1224p, 1238p, 1248p], [203p, 216p, 224p, 238p, 248p], [403p, 416p, 424p, 438p, 448p], [603p, 616p, 624p, 638p, 648p], [803p, 816p, 824p, 838p, 848p], [1003p, 1016p, 1024p, 1038p, 1048p]]  
short_name: "968"  
-  
time_points: [Fraser West Terminus, Dunlop, Macgregor Shops, Belconnen Way, City Bus Station (Platform 10), Russell Offices, National Circ / Canberra Ave]  
long_name: To National Circ / Canberra Ave  
between_stops:  
City Bus Station (Platform 10)-Russell Offices: [Wjz5Nht, Wjz5MsD, Wjz5MsT, Wjz5MEL, Wjz4T-X, Wjz4_jm, Wjz4_wS, Wjz4-KO, Wjz4-Rc, Wjz4-WL, Wjz4-WZ]  
short_name: "703"  
stop_times: [[653a, 700a, 706a, 718a, 737a, 746a, 754a], [710a, 717a, 723a, 735a, 753a, "-", "-"], [723a, 730a, 736a, 748a, 806a, "-", "-"], [738a, 745a, 751a, 803a, 834a, 843a, 851a], [758a, 806a, 813a, 827a, 849a, 858a, 906a]]  
-  
time_points: [City Bus Station (Platform 8), Macarthur / Northbourne Ave, Flemington Rd / Sandford St, Flemington Rd / Nullabor Ave, Anthony Rolfe Av / Moonlight Av, Gungahlin Marketplace, Shoalhaven / Katherine Ave, Ngunnawal Primary, Chuculba / William Slim Dr, Belconnen Community Bus Station, Westfield Bus Station, Cohen Street Bus Station]  
long_name: To Cohen Street Bus Station  
between_stops:  
Westfield Bus Station-Cohen Street Bus Station: []  
City Bus Station (Platform 8)-Macarthur / Northbourne Ave: [Wjz5O3Q, Wjz5Oci, Wjz5P8n, Wjz5P8K, Wjz5PdJ, Wjz5Pl0, Wjz5Qi2, Wjz5Qgn, Wjz5Qmu, Wjz5QmR]  
Belconnen Community Bus Station-Westfield Bus Station: []  
Chuculba / William Slim Dr-Belconnen Community Bus Station: [Wjz6mip, Wjz69vO, Wjz69uI, Wjz69ht, Wjz69gA]  
short_name: "958"  
stop_times: [[900a, 906a, 914a, 921a, 928a, 937a, 945a, 953a, 1004a, 1014a, 1016a, 1021a], [1000a, 1006a, 1014a, 1021a, 1028a, 1037a, 1045a, 1053a, 1104a, 1114a, 1116a, 1121a], [1100a, 1106a, 1114a, 1121a, 1128a, 1137a, 1145a, 1153a, 1204p, 1214p, 1216p, 1221p], [1200p, 1206p, 1214p, 1221p, 1228p, 1237p, 1245p, 1253p, 104p, 114p, 116p, 121p], [100p, 106p, 114p, 121p, 128p, 137p, 145p, 153p, 204p, 214p, 216p, 221p], [200p, 206p, 214p, 221p, 228p, 237p, 245p, 253p, 304p, 314p, 316p, 321p], [300p, 306p, 314p, 321p, 328p, 337p, 345p, 353p, 404p, 414p, 416p, 421p], [400p, 406p, 414p, 421p, 428p, 437p, 445p, 453p, 504p, 514p, 516p, 521p], [500p, 506p, 514p, 521p, 528p, 537p, 545p, 553p, 604p, 614p, 616p, 621p], [600p, 606p, 614p, 621p, 628p, 637p, 645p, 653p, 704p, 714p, 716p, 721p], [700p, 706p, 714p, 721p, 728p, 737p, 745p, 753p, 804p, 814p, 816p, 821p]]  
-  
time_points: [Alexander Maconochie Centre, Woden Bus Station]  
long_name: To Woden Bus Station  
between_stops: {}  
   
short_name: "988"  
stop_times_sunday: [[1130a, 1150a], [320p, 340p], [730p, 750p]]  
-  
time_points: [City Bus Station (Platform 9), Russell Offices, Kings Ave / National Circuit, Kingston, Manuka / Captain Cook Cres, Narrabundah College, Narrabundah Terminus, Geoscience Australia]  
long_name: To Geoscience Australia  
between_stops:  
Russell Offices-Kings Ave / National Circuit: [Wjz4RFJ, Wjz4RwH, Wjz4Quk]  
City Bus Station (Platform 9)-Russell Offices: [Wjz5Nht, Wjz5MsD, Wjz5MsT, Wjz5MEL, Wjz4T-X, Wjz4_jm, Wjz4_wS, Wjz4-KO, Wjz4-Rc, Wjz4-WL, Wjz4-WZ]  
short_name: "4"  
stop_times: [[633a, 641a, 645a, 649a, 652a, 700a, "-", 703a], [703a, 711a, 715a, 719a, 722a, 730a, "-", 733a], [733a, 742a, 747a, 752a, 755a, 805a, "-", 808a], [803a, 812a, 817a, 822a, 825a, 835a, "-", 838a], [818a, 827a, 832a, 837a, 840a, 850a, "-", 853a], [833a, 842a, 847a, 852a, 855a, 905a, "-", 908a], [903a, 912a, 917a, 922a, 925a, 935a, "-", 938a], [933a, 941a, 945a, 949a, 952a, 1001a, "-", 1004a], [1003a, 1011a, 1015a, 1019a, 1022a, 1031a, "-", 1034a], [1033a, 1041a, 1045a, 1049a, 1052a, 1101a, "-", 1104a], [1103a, 1111a, 1115a, 1119a, 1122a, 1131a, "-", 1134a], [1133a, 1141a, 1145a, 1149a, 1152a, 1201p, "-", 1204p], [1203p, 1211p, 1215p, 1219p, 1222p, 1231p, "-", 1234p], [1233p, 1241p, 1245p, 1249p, 1252p, 101p, "-", 104p], [103p, 111p, 115p, 119p, 122p, 131p, "-", 134p], [133p, 141p, 145p, 149p, 152p, 201p, "-", 204p], [203p, 211p, 215p, 219p, 222p, 231p, "-", 234p], [233p, 241p, 245p, 249p, 252p, 301p, "-", 304p], [303p, 312p, 317p, 322p, 325p, 334p, "-", 337p], [333p, 342p, 347p, 352p, 355p, 404p, "-", 407p], [405p, 414p, 419p, 424p, 427p, 436p, "-", 439p], [439p, 448p, 453p, 458p, 501p, 510p, "-", 513p], [509p, 518p, 523p, 528p, 531p, 540p, "-", 543p], [539p, 548p, 553p, 558p, 601p, 610p, 613p, "-"], [616p, 625p, 630p, 634p, 637p, 642p, 645p, "-"], [707p, 715p, 719p, 723p, 726p, 731p, 734p, "-"], [810p, 818p, 822p, 826p, 829p, 834p, 837p, "-"], [910p, 918p, 922p, 926p, 929p, 934p, 937p, "-"], [1010p, 1018p, 1022p, 1026p, 1029p, 1034p, 1037p, "-"], [1110p, 1118p, 1122p, 1126p, 1129p, 1134p, 1137p, "-"]]  
-  
time_points: [Woden Bus Station (Platform 14), Canberra Hospital, Garran Shops, Hughes Shops, Deakin Shops, Kings Ave / National Circuit, City Bus Station (Platform 4), National Museum of Australia, Burton and Garran Hall Daley Road, O'Connor Shops, Calvary Hospital, Belconnen Community Bus Station, Westfield Bus Station, Cohen Street Bus Station]  
long_name: To Cohen Street Bus Station  
between_stops:  
Westfield Bus Station-Cohen Street Bus Station: []  
Belconnen Community Bus Station-Westfield Bus Station: []  
Woden Bus Station (Platform 14)-Canberra Hospital: [Wjz3mAg, Wjz3mPO, Wjz3mWn]  
Kings Ave / National Circuit-City Bus Station (Platform 4): [Wjz4Quk, Wjz4RwH, Wjz4RFJ, Wjz4Rs-, Wjz4S1U, Wjz5FOn]  
short_name: "934"  
stop_times: [[813a, 820a, 822a, 826a, 831a, 840a, 852a, 859a, 904a, 909a, 916a, 933a, 935a, 940a], [913a, 920a, 922a, 926a, 931a, 940a, 952a, 959a, 1004a, 1009a, 1016a, 1033a, 1035a, 1040a], [1013a, 1020a, 1022a, 1026a, 1031a, 1040a, 1052a, 1059a, 1104a, 1109a, 1116a, 1133a, 1135a, 1140a], [1113a, 1120a, 1122a, 1126a, 1131a, 1140a, 1152a, 1159a, 1204p, 1209p, 1216p, 1233p, 1235p, 1240p], [1213p, 1220p, 1222p, 1226p, 1231p, 1240p, 1252p, 1259p, 104p, 109p, 116p, 133p, 135p, 140p], [113p, 120p, 122p, 126p, 131p, 140p, 152p, 159p, 204p, 209p, 216p, 233p, 235p, 240p], [213p, 220p, 222p, 226p, 231p, 240p, 252p, 259p, 304p, 309p, 316p, 333p, 335p, 340p], [313p, 320p, 322p, 326p, 331p, 340p, 352p, 359p, 404p, 409p, 416p, 433p, 435p, 440p], [413p, 420p, 422p, 426p, 431p, 440p, 452p, 459p, 504p, 509p, 516p, 533p, 535p, 540p], [513p, 520p, 522p, 526p, 531p, 540p, 552p, 559p, 604p, 609p, 616p, 633p, 635p, 640p], [613p, 620p, 622p, 626p, 631p, 640p, 652p, 659p, 704p, 709p, 716p, 733p, 735p, 740p]]  
-  
time_points: [Cohen Street Bus Station (Platform 2), Westfield Bus Station (Platform 1), Belconnen Community Bus Station (Platform 2), City Bus Station (Platform 10), Russell Offices, National Circ / Canberra Ave]  
long_name: To National Circ / Canberra Ave  
between_stops:  
Westfield Bus Station (Platform 1)-Belconnen Community Bus Station (Platform 2): []  
Cohen Street Bus Station (Platform 2)-Westfield Bus Station (Platform 1): []  
City Bus Station (Platform 10)-Russell Offices: [Wjz5Nht, Wjz5MsD, Wjz5MsT, Wjz5MEL, Wjz4T-X, Wjz4_jm, Wjz4_wS, Wjz4-KO, Wjz4-Rc, Wjz4-WL, Wjz4-WZ]  
Belconnen Community Bus Station (Platform 2)-City Bus Station (Platform 10): [Wjz681S, Wjz689c, Wjz68W3, Wjz68W5, Wjz6gia, Wjz6giR, Wjz5nw6, Wjz5nwb, Wjz5mbS, Wjz5maK, Wjz5G6B, Wjz5G6U, Wjz5GNG, Wjz5GMT, Wjz5FSY, Wjz5F-1]  
short_name: "710"  
stop_times: [[658a, 700a, 704a, 723a, 732a, 740a], [728a, 730a, 734a, 753a, 802a, 810a], [743a, 745a, 749a, 808a, 817a, 825a], [758a, 800a, 804a, 823a, 832a, 840a], [813a, 815a, 819a, 838a, 847a, 855a]]  
-  
time_points: [Tuggeranong Bus Station (Platform 4), Kambah High, Kambah Village, Woden Bus Station]  
long_name: To Woden Bus Station  
between_stops: {}  
   
short_name: "962"  
stop_times_sunday: [[924a, 931a, 939a, 952a], [1024a, 1031a, 1039a, 1052a], [1124a, 1131a, 1139a, 1152a], [1224p, 1231p, 1239p, 1252p], [124p, 131p, 139p, 152p], [224p, 231p, 239p, 252p], [324p, 331p, 339p, 352p], [424p, 431p, 439p, 452p], [524p, 531p, 539p, 552p], [624p, 631p, 638p, 649p]]  
-  
time_points: [Cohen Street Bus Station (Platform 1), Westfield Bus Station (Platform 1), Belconnen Community Bus Station (Platform 2), Chuculba / William Slim Dr, Federation Square, Nicholls Primary, Ngunnawal Primary, Gungahlin Marketplace]  
long_name: To Gungahlin Market Place  
between_stops:  
Belconnen Community Bus Station (Platform 2)-Chuculba / William Slim Dr: [Wjz69gA, Wjz69ht, Wjz69uI, Wjz69vO, Wjz6mip]  
Cohen Street Bus Station (Platform 1)-Westfield Bus Station (Platform 1): []  
Westfield Bus Station (Platform 1)-Belconnen Community Bus Station (Platform 2): []  
short_name: "951"  
stop_times: [[920a, 922a, 926a, 934a, 939a, 944a, 954a, 1004a], [1020a, 1022a, 1026a, 1034a, 1039a, 1044a, 1054a, 1104a], [1120a, 1122a, 1126a, 1134a, 1139a, 1144a, 1154a, 1204p], [1220p, 1222p, 1226p, 1234p, 1239p, 1244p, 1254p, 104p], [120p, 122p, 126p, 134p, 139p, 144p, 154p, 204p], [220p, 222p, 226p, 234p, 239p, 244p, 254p, 304p], [320p, 322p, 326p, 334p, 339p, 344p, 354p, 404p], [420p, 422p, 426p, 434p, 439p, 444p, 454p, 504p], [520p, 522p, 526p, 534p, 539p, 544p, 554p, 604p], [620p, 622p, 626p, 634p, 639p, 644p, 654p, 704p]]  
-  
time_points: [City West, City Bus Station (Platform 10), Russell Offices, Chisholm Shops, Calwell Shops, Theodore, Tharwa Drive]  
long_name: To Tharwa Drive  
between_stops:  
City West-City Bus Station (Platform 10): []  
City Bus Station (Platform 10)-Russell Offices: [Wjz5Nht, Wjz5MsD, Wjz5MsT, Wjz5MEL, Wjz4T-X, Wjz4_jm, Wjz4_wS, Wjz4-KO, Wjz4-Rc, Wjz4-WL, Wjz4-WZ]  
short_name: "769"  
stop_times: [[427p, 433p, 442p, 507p, 517p, 527p, 532p], [500p, 506p, 515p, 540p, 550p, 600p, 605p], [537p, 543p, 552p, 617p, 627p, 637p, 642p]]  
-  
time_points: [Tharwa Drive, Theodore, Calwell Shops, Chisholm Shops, Russell Offices, City Bus Station (Platform 11), City West]  
long_name: To City West  
between_stops:  
Russell Offices-City Bus Station (Platform 11): [Wjz4-WZ, Wjz4-WL, Wjz4-Rc, Wjz4-KO, Wjz4_wS, Wjz4_jm, Wjz4T-X, Wjz5MEL, Wjz5MsT, Wjz5MsD, Wjz5Nht]  
City Bus Station (Platform 11)-City West: []  
short_name: "769"  
stop_times: [[641a, 646a, 656a, 706a, 733a, 743a, 747a], [721a, 726a, 736a, 746a, 813a, 823a, 827a], [741a, 746a, 756a, 806a, 833a, 843a, 847a]]  
-  
time_points: [City Bus Station (Platform 8), Macarthur / Northbourne Ave, Northbourne Avenue / Antill St, Bimberi Centre]  
long_name: To Bimberi Centre  
between_stops:  
City Bus Station (Platform 8)-Macarthur / Northbourne Ave: [Wjz5O3Q, Wjz5Oci, Wjz5P8n, Wjz5P8K, Wjz5PdJ, Wjz5Pl0, Wjz5Qi2, Wjz5Qgn, Wjz5Qmu, Wjz5QmR]  
stop_times_saturday: [[632a, 638a, 640a, 650a], [342p, 348p, 350p, 400p]]  
short_name: "982"  
-  
time_points: [City Bus Station (Platform 8), Macarthur / Northbourne Ave, Northbourne Avenue / Antill St, Flemington Rd / Sandford St, Hibberson / Kate Crace, Gungahlin Marketplace]  
long_name: To Gungahlin Marketplace  
between_stops:  
Macarthur / Northbourne Ave-Northbourne Avenue / Antill St: [Wjz5RkN, Wjz5Rsi, Wjz5RvC, Wjz5Sqk, Wjz5SrO, Wjz5Sux, Wjz5SDc]  
City Bus Station (Platform 8)-Macarthur / Northbourne Ave: [Wjz5O3Q, Wjz5Oci, Wjz5P8n, Wjz5P8K, Wjz5PdJ, Wjz5Pl0, Wjz5Qi2, Wjz5Qgn, Wjz5Qmu, Wjz5QmR]  
short_name: "50"  
stop_times: [[700p, 706p, 708p, 715p, 718p, 721p], [730p, 736p, 738p, 745p, 748p, 751p], [800p, 806p, 808p, 815p, 818p, 821p], [830p, 836p, 838p, 845p, 848p, 851p], [900p, 906p, 908p, 915p, 918p, 921p], [930p, 936p, 938p, 945p, 948p, 951p], [1000p, 1006p, 1008p, 1015p, 1018p, 1021p], [1030p, 1036p, 1038p, 1045p, 1048p, 1051p], [1100p, 1106p, 1108p, 1115p, 1118p, 1121p]]  
-  
time_points: [Bimberi Centre, Northbourne Avenue / Antill St, Macarthur / Northbourne Ave, City Bus Station]  
long_name: To City Bus Station  
between_stops:  
Macarthur / Northbourne Ave-City Bus Station: [Wjz5QmR, Wjz5Qmu, Wjz5Qgn, Wjz5Qi2, Wjz5Pl0, Wjz5PdJ, Wjz5P8K, Wjz5P8n, Wjz5Oci, Wjz5O3Q]  
stop_times_saturday: [[715p, 724p, 726p, 733p]]  
short_name: "982"  
-  
time_points: [City Bus Station (Platform 8), Dickson Shops, Watson Shops, Watson Terminus, Watson Shops, Dickson Shops, City Bus Station]  
long_name: To City Bus Station  
between_stops: {}  
   
short_name: "939"  
stop_times_sunday: [[846a, 903a, 908a, 915a, 920a, 926a, 941a], [946a, 1003a, 1008a, 1015a, 1020a, 1026a, 1041a], [1046a, 1103a, 1108a, 1115a, 1120a, 1126a, 1141a], [1146a, 1203p, 1208p, 1215p, 1220p, 1226p, 1241p], [1246p, 103p, 108p, 115p, 120p, 126p, 141p], [146p, 203p, 208p, 215p, 220p, 226p, 241p], [246p, 303p, 308p, 315p, 320p, 326p, 341p], [346p, 403p, 408p, 415p, 420p, 426p, 441p], [446p, 503p, 508p, 515p, 520p, 526p, 541p], [546p, 603p, 608p, 615p, 620p, 626p, 641p], [646p, 703p, 708p, 715p, 720p, 726p, 741p]]  
-  
time_points: [City Bus Station (Platform 9), Newcastle Street after Isa Street, Lithgow St Terminus Fyshwick]  
long_name: To Lithgow St Terminus  
between_stops: {}  
   
short_name: "780"  
stop_times: [[648a, 707a, 723a], [719a, 738a, 754a]]  
-  
time_points: [Cooleman Court, Rivett Shops, Chapman Shops, Fisher Shops, Waramanga Shops, Woden Bus Station]  
long_name: To Woden Bus Station  
between_stops: {}  
   
stop_times_saturday: [[755a, 803a, 806a, 816a, 819a, 826a], [855a, 903a, 906a, 916a, 919a, 926a], [955a, 1003a, 1006a, 1016a, 1019a, 1026a], [1055a, 1103a, 1106a, 1116a, 1119a, 1126a], [1155a, 1203p, 1206p, 1216p, 1219p, 1226p], [1255p, 103p, 106p, 116p, 119p, 126p], [155p, 203p, 206p, 216p, 219p, 226p], [255p, 303p, 306p, 316p, 319p, 326p], [355p, 403p, 406p, 416p, 419p, 426p], [455p, 503p, 506p, 516p, 519p, 526p], [555p, 603p, 606p, 616p, 619p, 626p], [655p, 703p, 706p, 716p, 719p, 726p], [755p, 803p, 806p, 816p, 819p, 826p], [855p, 903p, 906p, 916p, 919p, 926p], [955p, 1003p, 1006p, 1016p, 1019p, 1026p], [1055p, 1103p, 1106p, 1116p, 1119p, 1126p]]  
short_name: "927"  
   
options: options:
start_date: 20101115 start_date: 20101115
end_date: 20111231 end_date: 20111231
remove_date: 20111231 remove_date: 20111231
agency_name: ACT Internal Omnibus Network (ACTION) agency_name: ACT Internal Omnibus Network (ACTION)
agency_url: http://www.action.act.gov.au/ agency_url: http://www.action.act.gov.au/
agency_timezone: Australia/Sydney agency_timezone: Australia/Sydney
   
   
stops: stops:
- { name: ACTEW AGL House,stop_code: ACTEW AGL House, lat: -35.282374, lng: 149.132047} - { name: ACTEW AGL House,stop_code: ACTEW AGL House, lat: -35.282374, lng: 149.132047}
- { name: ADFA,stop_code: ADFA, lat: -35.2937972, lng: 149.1643403} - { name: ADFA,stop_code: ADFA, lat: -35.2937972, lng: 149.1643403}
- { name: Ainslie,stop_code: Ainslie, lat: -35.2620105, lng: 149.1443302} - { name: Ainslie,stop_code: Ainslie, lat: -35.2620105, lng: 149.1443302}
- { name: Ainslie Shops,stop_code: Ainslie Shops, lat: -35.26198, lng: 149.14535}  
- { name: Alexander Maconochie Centre,stop_code: Alexander Maconochie Centre, lat: -35.3720651, lng: 149.1696618} - { name: Alexander Maconochie Centre,stop_code: Alexander Maconochie Centre, lat: -35.3720651, lng: 149.1696618}
- { name: Alpen & Clifford St,stop_code: Alpen & Clifford St, lat: -35.20562, lng: 149.06259} - { name: Alpen & Clifford St,stop_code: Alpen & Clifford St, lat: -35.20562, lng: 149.06259}
- { name: Anthony Rolfe Av / Moonlight Av,stop_code: Anthony Rolfe Av / Moonlight Av, lat: -35.1856021, lng: 149.1543639} - { name: Anthony Rolfe Av / Moonlight Av,stop_code: Anthony Rolfe Av / Moonlight Av, lat: -35.1856021, lng: 149.1543639}
- { name: Aranda,stop_code: Aranda, lat: -35.257534, lng: 149.0762963} - { name: Aranda,stop_code: Aranda, lat: -35.257534, lng: 149.0762963}
- { name: Aranda Shops,stop_code: Aranda Shops, lat: -35.25753, lng: 149.0763}  
- { name: Athllon / Sulwood Kambah,stop_code: Athllon / Sulwood Kambah, lat: -35.38442, lng: 149.09328} - { name: Athllon / Sulwood Kambah,stop_code: Athllon / Sulwood Kambah, lat: -35.38442, lng: 149.09328}
- { name: Australian Institute of Sport,stop_code: Australian Institute of Sport, lat: -35.246351, lng: 149.101478} - { name: Australian Institute of Sport,stop_code: Australian Institute of Sport, lat: -35.246351, lng: 149.101478}
- { name: Belconnen Community Bus Station,stop_code: Belconnen Community Bus Station, lat: -35.23987, lng: 149.0619} - { name: Belconnen Community Bus Station,stop_code: Belconnen Community Bus Station, lat: -35.23987, lng: 149.0619}
- { name: Belconnen Community Bus Station (Platform 1),stop_code: Belconnen Community Bus Station (Platform 1), lat: -35.23982, lng: 149.06978} - { name: Belconnen Community Bus Station (Platform 1),stop_code: Belconnen Community Bus Station (Platform 1), lat: -35.23982, lng: 149.06978}
- { name: Belconnen Community Bus Station (Platform 2),stop_code: Belconnen Community Bus Station (Platform 2), lat: -35.23982, lng: 149.06926} - { name: Belconnen Community Bus Station (Platform 2),stop_code: Belconnen Community Bus Station (Platform 2), lat: -35.23982, lng: 149.06926}
- { name: Belconnen Community Bus Station (Platform 3),stop_code: Belconnen Community Bus Station (Platform 3), lat: -35.23986, lng: 149.06847} - { name: Belconnen Community Bus Station (Platform 3),stop_code: Belconnen Community Bus Station (Platform 3), lat: -35.23986, lng: 149.06847}
- { name: Belconnen Community Bus Station (Platform 4),stop_code: Belconnen Community Bus Station (Platform 4), lat: -35.23994, lng: 149.06887} - { name: Belconnen Community Bus Station (Platform 4),stop_code: Belconnen Community Bus Station (Platform 4), lat: -35.23994, lng: 149.06887}
- { name: Belconnen Community Bus Station (Platform 5),stop_code: Belconnen Community Bus Station (Platform 5), lat: -35.23994, lng: 149.06928} - { name: Belconnen Community Bus Station (Platform 5),stop_code: Belconnen Community Bus Station (Platform 5), lat: -35.23994, lng: 149.06928}
- { name: Belconnen Community Bus Station (Platform 6),stop_code: Belconnen Community Bus Station (Platform 6), lat: -35.23994, lng: 149.0698} - { name: Belconnen Community Bus Station (Platform 6),stop_code: Belconnen Community Bus Station (Platform 6), lat: -35.23994, lng: 149.0698}
- { name: Belconnen Way,stop_code: Belconnen Way, lat: -35.24809, lng: 149.06765} - { name: Belconnen Way,stop_code: Belconnen Way, lat: -35.24809, lng: 149.06765}
- { name: Bimberi Centre,stop_code: Bimberi Centre, lat: -35.2219941, lng: 149.1546928} - { name: Bimberi Centre,stop_code: Bimberi Centre, lat: -35.2219941, lng: 149.1546928}
- { name: Black Mountain Telstra Tower,stop_code: Black Mountain Telstra Tower, lat: -35.2748058, lng: 149.0972461} - { name: Black Mountain Telstra Tower,stop_code: Black Mountain Telstra Tower, lat: -35.2748058, lng: 149.0972461}
- { name: Bonython,stop_code: Bonython, lat: -35.4297416, lng: 149.0814517} - { name: Bonython,stop_code: Bonython, lat: -35.4297416, lng: 149.0814517}
- { name: Bonython Primary School,stop_code: Bonython Primary School, lat: -35.431019, lng: 149.0831217} - { name: Bonython Primary School,stop_code: Bonython Primary School, lat: -35.431019, lng: 149.0831217}
- { name: Botanic Gardens,stop_code: Botanic Gardens, lat: -35.278643, lng: 149.1093237} - { name: Botanic Gardens,stop_code: Botanic Gardens, lat: -35.278643, lng: 149.1093237}
- { name: Brindabella Business Park,stop_code: Brindabella Business Park, lat: -35.314496, lng: 149.189145} - { name: Brindabella Business Park,stop_code: Brindabella Business Park, lat: -35.314496, lng: 149.189145}
- { name: Brindabella Gardens Nursing Home,stop_code: Brindabella Gardens Nursing Home, lat: -35.3294459, lng: 149.0806116} - { name: Brindabella Gardens Nursing Home,stop_code: Brindabella Gardens Nursing Home, lat: -35.3294459, lng: 149.0806116}
- { name: Bugden Sternberg,stop_code: Bugden Sternberg, lat: -35.4017223, lng: 149.0992172} - { name: Bugden Sternberg,stop_code: Bugden Sternberg, lat: -35.4017223, lng: 149.0992172}
- { name: Burton and Garran Hall Daley Road,stop_code: Burton and Garran Hall Daley Road, lat: -35.2753671, lng: 149.1172822} - { name: Burton and Garran Hall Daley Road,stop_code: Burton and Garran Hall Daley Road, lat: -35.2753671, lng: 149.1172822}
- { name: Calvary Hospital,stop_code: Calvary Hospital, lat: -35.25212, lng: 149.09088} - { name: Calvary Hospital,stop_code: Calvary Hospital, lat: -35.25212, lng: 149.09088}
- { name: Calwell Shops,stop_code: Calwell Shops, lat: -35.43524, lng: 149.113942} - { name: Calwell,stop_code: Calwell, lat: -35.43524, lng: 149.113942}
- { name: Cameron Ave Bus Station,stop_code: Cameron Ave Bus Station, lat: -35.2410195, lng: 149.0722506} - { name: Cameron Ave Bus Station,stop_code: Cameron Ave Bus Station, lat: -35.2410195, lng: 149.0722506}
- { name: Cameron Ave Bus Station (Platform 1),stop_code: Cameron Ave Bus Station (Platform 1), lat: -35.2410195, lng: 149.0722506} - { name: Cameron Ave Bus Station (Platform 1),stop_code: Cameron Ave Bus Station (Platform 1), lat: -35.2410195, lng: 149.0722506}
- { name: Cameron Ave Bus Station (Platform 2),stop_code: Cameron Ave Bus Station (Platform 2), lat: -35.2410108, lng: 149.0717142} - { name: Cameron Ave Bus Station (Platform 2),stop_code: Cameron Ave Bus Station (Platform 2), lat: -35.2410108, lng: 149.0717142}
- { name: Cameron Ave Bus Station (Platform 3),stop_code: Cameron Ave Bus Station (Platform 3), lat: -35.2410064, lng: 149.0710758} - { name: Cameron Ave Bus Station (Platform 3),stop_code: Cameron Ave Bus Station (Platform 3), lat: -35.2410064, lng: 149.0710758}
- { name: Cameron Ave Bus Station (Platform 4),stop_code: Cameron Ave Bus Station (Platform 4), lat: -35.2411773, lng: 149.0709793} - { name: Cameron Ave Bus Station (Platform 4),stop_code: Cameron Ave Bus Station (Platform 4), lat: -35.2411773, lng: 149.0709793}
- { name: Cameron Ave Bus Station (Platform 5),stop_code: Cameron Ave Bus Station (Platform 5), lat: -35.241186, lng: 149.0720789} - { name: Cameron Ave Bus Station (Platform 5),stop_code: Cameron Ave Bus Station (Platform 5), lat: -35.241186, lng: 149.0720789}
- { name: Campbell Park Offices,stop_code: Campbell Park Offices, lat: -35.28368, lng: 149.17045} - { name: Campbell Park Offices,stop_code: Campbell Park Offices, lat: -35.28368, lng: 149.17045}
- { name: Canberra College Weston Campus,stop_code: Canberra College Weston Campus, lat: -35.3490278, lng: 149.0486277} - { name: Canberra College Weston Campus,stop_code: Canberra College Weston Campus, lat: -35.3490278, lng: 149.0486277}
- { name: Canberra Hospital,stop_code: Canberra Hospital, lat: -35.3459462, lng: 149.1012001} - { name: Canberra Hospital,stop_code: Canberra Hospital, lat: -35.3459462, lng: 149.1012001}
- { name: Canberra Times,stop_code: Canberra Times, lat: -35.3245431, lng: 149.1705533} - { name: Canberra Times,stop_code: Canberra Times, lat: -35.3245431, lng: 149.1705533}
- { name: Caswell Drive,stop_code: Caswell Drive, lat: -35.25922, lng: 149.08576} - { name: Caswell Drive,stop_code: Caswell Drive, lat: -35.25922, lng: 149.08576}
- { name: Causeway,stop_code: Causeway, lat: -35.31615, lng: 149.15058} - { name: Causeway,stop_code: Causeway, lat: -35.31615, lng: 149.15058}
- { name: Centrelink Tuggeranong,stop_code: Centrelink Tuggeranong, lat: -35.4207496, lng: 149.0700973} - { name: Centrelink Tuggeranong,stop_code: Centrelink Tuggeranong, lat: -35.4207496, lng: 149.0700973}
- { name: Chapman,stop_code: Chapman, lat: -35.3557877, lng: 149.0408111} - { name: Chapman,stop_code: Chapman, lat: -35.3557877, lng: 149.0408111}
- { name: Chapman Shops,stop_code: Chapman Shops, lat: -35.35579, lng: 149.04082}  
- { name: Charnwood,stop_code: Charnwood, lat: -35.2052138, lng: 149.0337266} - { name: Charnwood,stop_code: Charnwood, lat: -35.2052138, lng: 149.0337266}
- { name: Charnwood Shops,stop_code: Charnwood Shops, lat: -35.20472, lng: 149.03336}  
- { name: Charnwood Tillyard Dr,stop_code: Charnwood Tillyard Dr, lat: -35.20295, lng: 149.04027} - { name: Charnwood Tillyard Dr,stop_code: Charnwood Tillyard Dr, lat: -35.20295, lng: 149.04027}
- { name: Chifley,stop_code: Chifley, lat: -35.350985, lng: 149.077319} - { name: Chifley,stop_code: Chifley, lat: -35.350985, lng: 149.077319}
- { name: Chifley Shops,stop_code: Chifley Shops, lat: -35.35099, lng: 149.07732} - { name: Chisholm,stop_code: Chisholm, lat: -35.41341, lng: 149.12833}
- { name: Chisholm Shops,stop_code: Chisholm Shops, lat: -35.41341, lng: 149.12833}  
- { name: Chuculba / William Slim Dr,stop_code: Chuculba / William Slim Dr, lat: -35.208931, lng: 149.088499} - { name: Chuculba / William Slim Dr,stop_code: Chuculba / William Slim Dr, lat: -35.208931, lng: 149.088499}
- { name: CIT Weston,stop_code: CIT Weston, lat: -35.330234, lng: 149.058632} - { name: CIT Weston,stop_code: CIT Weston, lat: -35.330234, lng: 149.058632}
- { name: City Bus Station,stop_code: City Bus Station, lat: -35.2794346, lng: 149.1305879} - { name: City Bus Station,stop_code: City Bus Station, lat: -35.2794346, lng: 149.1305879}
- { name: City Bus Station (Platform 1),stop_code: City Bus Station (Platform 1), lat: -35.2794346, lng: 149.1305879} - { name: City Bus Station (Platform 1),stop_code: City Bus Station (Platform 1), lat: -35.2794346, lng: 149.1305879}
- { name: City Bus Station (Platform 10),stop_code: City Bus Station (Platform 10), lat: -35.2793571, lng: 149.1293659} - { name: City Bus Station (Platform 10),stop_code: City Bus Station (Platform 10), lat: -35.2793571, lng: 149.1293659}
- { name: City Bus Station (Platform 11),stop_code: City Bus Station (Platform 11), lat: -35.2787905, lng: 149.1288627} - { name: City Bus Station (Platform 11),stop_code: City Bus Station (Platform 11), lat: -35.2787905, lng: 149.1288627}
- { name: City Bus Station (Platform 2),stop_code: City Bus Station (Platform 2), lat: -35.278907, lng: 149.130612} - { name: City Bus Station (Platform 2),stop_code: City Bus Station (Platform 2), lat: -35.278907, lng: 149.130612}
- { name: City Bus Station (Platform 3),stop_code: City Bus Station (Platform 3), lat: -35.2787886, lng: 149.1304779} - { name: City Bus Station (Platform 3),stop_code: City Bus Station (Platform 3), lat: -35.2787886, lng: 149.1304779}
- { name: City Bus Station (Platform 4),stop_code: City Bus Station (Platform 4), lat: -35.2785658, lng: 149.1301727} - { name: City Bus Station (Platform 4),stop_code: City Bus Station (Platform 4), lat: -35.2785658, lng: 149.1301727}
- { name: City Bus Station (Platform 5),stop_code: City Bus Station (Platform 5), lat: -35.2785242, lng: 149.1297348} - { name: City Bus Station (Platform 5),stop_code: City Bus Station (Platform 5), lat: -35.2785242, lng: 149.1297348}
- { name: City Bus Station (Platform 7),stop_code: City Bus Station (Platform 7), lat: -35.27843, lng: 149.130345} - { name: City Bus Station (Platform 7),stop_code: City Bus Station (Platform 7), lat: -35.27843, lng: 149.130345}
- { name: City Bus Station (Platform 8),stop_code: City Bus Station (Platform 8), lat: -35.2778798, lng: 149.1305995} - { name: City Bus Station (Platform 8),stop_code: City Bus Station (Platform 8), lat: -35.2778798, lng: 149.1305995}
- { name: City Bus Station (Platform 9),stop_code: City Bus Station (Platform 9), lat: -35.2783224, lng: 149.130726} - { name: City Bus Station (Platform 9),stop_code: City Bus Station (Platform 9), lat: -35.2783224, lng: 149.130726}
- { name: City West,stop_code: City West, lat: -35.2788605, lng: 149.1257969} - { name: City West,stop_code: City West, lat: -35.2788605, lng: 149.1257969}
- { name: Cnr Kerrigan/Lhotsky,stop_code: Cnr Kerrigan/Lhotsky, lat: -35.1995716, lng: 149.0285277} - { name: Cnr Kerrigan/Lhotsky,stop_code: Cnr Kerrigan/Lhotsky, lat: -35.1995716, lng: 149.0285277}
- { name: Cnr Tillyard Dr & Spalding St,stop_code: Cnr Tillyard Dr & Spalding St, lat: -35.2040477, lng: 149.0393052} - { name: Cnr Tillyard Dr & Spalding St,stop_code: Cnr Tillyard Dr & Spalding St, lat: -35.2040477, lng: 149.0393052}
- { name: Cohen Street Bus Station,stop_code: Cohen Street Bus Station, lat: -35.2394775, lng: 149.0602031} - { name: Cohen Street Bus Station,stop_code: Cohen Street Bus Station, lat: -35.2394775, lng: 149.0602031}
- { name: Cohen Street Bus Station (Platform 1),stop_code: Cohen Street Bus Station (Platform 1), lat: -35.2394775, lng: 149.0602031} - { name: Cohen Street Bus Station (Platform 1),stop_code: Cohen Street Bus Station (Platform 1), lat: -35.2394775, lng: 149.0602031}
- { name: Cohen Street Bus Station (Platform 2),stop_code: Cohen Street Bus Station (Platform 2), lat: -35.2396467, lng: 149.0602152} - { name: Cohen Street Bus Station (Platform 2),stop_code: Cohen Street Bus Station (Platform 2), lat: -35.2396467, lng: 149.0602152}
- { name: Cohen Street Bus Station (Platform 3),stop_code: Cohen Street Bus Station (Platform 3), lat: -35.239764, lng: 149.0604531} - { name: Cohen Street Bus Station (Platform 3),stop_code: Cohen Street Bus Station (Platform 3), lat: -35.239764, lng: 149.0604531}
- { name: Cohen Street Bus Station (Platform 4),stop_code: Cohen Street Bus Station (Platform 4), lat: -35.239844, lng: 149.0600683} - { name: Cohen Street Bus Station (Platform 4),stop_code: Cohen Street Bus Station (Platform 4), lat: -35.239844, lng: 149.0600683}
- { name: Cohen Street Bus Station (Platform 5),stop_code: Cohen Street Bus Station (Platform 5), lat: -35.2401211, lng: 149.0597102} - { name: Cohen Street Bus Station (Platform 5),stop_code: Cohen Street Bus Station (Platform 5), lat: -35.2401211, lng: 149.0597102}
- { name: Cohen Street Bus Station (Platform 6),stop_code: Cohen Street Bus Station (Platform 6), lat: -35.2400028, lng: 149.060315} - { name: Cohen Street Bus Station (Platform 6),stop_code: Cohen Street Bus Station (Platform 6), lat: -35.2400028, lng: 149.060315}
- { name: Conder Primary,stop_code: Conder Primary, lat: -35.4643475, lng: 149.0986908} - { name: Conder Primary,stop_code: Conder Primary, lat: -35.4643475, lng: 149.0986908}
- { name: Cook,stop_code: Cook, lat: -35.2596, lng: 149.0638} - { name: Cook,stop_code: Cook, lat: -35.2596, lng: 149.0638}
- { name: Cook Shops,stop_code: Cook Shops, lat: -35.25898, lng: 149.06343}  
- { name: Cooleman Court,stop_code: Cooleman Court, lat: -35.34147, lng: 149.05338} - { name: Cooleman Court,stop_code: Cooleman Court, lat: -35.34147, lng: 149.05338}
- { name: Copland College,stop_code: Copland College, lat: -35.2127018, lng: 149.0596387} - { name: Copland College,stop_code: Copland College, lat: -35.2127018, lng: 149.0596387}
- { name: Curtin,stop_code: Curtin, lat: -35.3248779, lng: 149.081441} - { name: Curtin,stop_code: Curtin, lat: -35.3248779, lng: 149.081441}
- { name: Curtin Shops,stop_code: Curtin Shops, lat: -35.32515, lng: 149.08224}  
- { name: Deakin,stop_code: Deakin, lat: -35.3158608, lng: 149.1084563} - { name: Deakin,stop_code: Deakin, lat: -35.3158608, lng: 149.1084563}
- { name: Deakin Shops,stop_code: Deakin Shops, lat: -35.31473, lng: 149.10771}  
- { name: Deamer / Clift Richardson,stop_code: Deamer / Clift Richardson, lat: -35.4319597, lng: 149.1187876} - { name: Deamer / Clift Richardson,stop_code: Deamer / Clift Richardson, lat: -35.4319597, lng: 149.1187876}
- { name: Dickson,stop_code: Dickson, lat: -35.2498434, lng: 149.1391218} - { name: Dickson / Antill St,stop_code: Dickson / Antill St, lat: -35.2489, lng: 149.14012}
- { name: Dickson College,stop_code: Dickson College, lat: -35.24923, lng: 149.15315} - { name: Dickson College,stop_code: Dickson College, lat: -35.24923, lng: 149.15315}
- { name: Dickson Cowper St,stop_code: Dickson Cowper St, lat: -35.250297, lng: 149.141336} - { name: Dickson / Cowper St,stop_code: Dickson / Cowper St, lat: -35.250297, lng: 149.141336}
- { name: Dickson Shops,stop_code: Dickson Shops, lat: -35.25045, lng: 149.14044}  
- { name: Dickson Shops/Antill St,stop_code: Dickson Shops/Antill St, lat: -35.2251335, lng: 149.1658895}  
- { name: Duffy,stop_code: Duffy, lat: -35.3366908, lng: 149.0324311} - { name: Duffy,stop_code: Duffy, lat: -35.3366908, lng: 149.0324311}
- { name: Duffy Primary,stop_code: Duffy Primary, lat: -35.334219, lng: 149.033656} - { name: Duffy Primary,stop_code: Duffy Primary, lat: -35.334219, lng: 149.033656}
- { name: Dunlop,stop_code: Dunlop, lat: -35.1942693, lng: 149.0206702} - { name: Dunlop,stop_code: Dunlop, lat: -35.1942693, lng: 149.0206702}
- { name: Erindale Centre,stop_code: Erindale Centre, lat: -35.4038881, lng: 149.0992283} - { name: Erindale Centre,stop_code: Erindale Centre, lat: -35.4038881, lng: 149.0992283}
- { name: Erindale Dr / Charleston St Monash,stop_code: Erindale Dr / Charleston St Monash, lat: -35.414616, lng: 149.07888} - { name: Erindale Dr / Charleston St Monash,stop_code: Erindale Dr / Charleston St Monash, lat: -35.414616, lng: 149.07888}
- { name: Erindale / Sternberg Cres,stop_code: Erindale / Sternberg Cres, lat: -35.4014472, lng: 149.0956545} - { name: Erindale / Sternberg Cres,stop_code: Erindale / Sternberg Cres, lat: -35.4014472, lng: 149.0956545}
- { name: Evatt,stop_code: Evatt, lat: -35.2091093, lng: 149.0735343} - { name: Evatt,stop_code: Evatt, lat: -35.2091093, lng: 149.0735343}
- { name: Evatt Shops,stop_code: Evatt Shops, lat: -35.21203, lng: 149.06505}  
- { name: Eye Hospital,stop_code: Eye Hospital, lat: -35.3341884, lng: 149.1656213} - { name: Eye Hospital,stop_code: Eye Hospital, lat: -35.3341884, lng: 149.1656213}
- { name: Fairbairn Park,stop_code: Fairbairn Park, lat: -35.3001773, lng: 149.2041185} - { name: Fairbairn Park,stop_code: Fairbairn Park, lat: -35.3001773, lng: 149.2041185}
- { name: Farrer Primary School,stop_code: Farrer Primary School, lat: -35.37887, lng: 149.10641} - { name: Farrer Primary School,stop_code: Farrer Primary School, lat: -35.37887, lng: 149.10641}
- { name: Farrer Terminus,stop_code: Farrer Terminus, lat: -35.3771794, lng: 149.1046948} - { name: Farrer Terminus,stop_code: Farrer Terminus, lat: -35.3771794, lng: 149.1046948}
- { name: Federation Square,stop_code: Federation Square, lat: -35.1908726, lng: 149.0848153} - { name: Federation Square,stop_code: Federation Square, lat: -35.1908726, lng: 149.0848153}
- { name: Fisher,stop_code: Fisher, lat: -35.3605627, lng: 149.0576481} - { name: Fisher,stop_code: Fisher, lat: -35.3605627, lng: 149.0576481}
- { name: Fisher Shops,stop_code: Fisher Shops, lat: -35.36056, lng: 149.05765}  
- { name: Flemington Rd,stop_code: Flemington Rd, lat: -35.20756, lng: 149.14778} - { name: Flemington Rd,stop_code: Flemington Rd, lat: -35.20756, lng: 149.14778}
- { name: Flemington Rd / Nullabor Ave,stop_code: Flemington Rd / Nullabor Ave, lat: -35.2008585, lng: 149.1493407} - { name: Flemington Rd / Nullabor Ave,stop_code: Flemington Rd / Nullabor Ave, lat: -35.2008585, lng: 149.1493407}
- { name: Flemington Rd / Sandford St,stop_code: Flemington Rd / Sandford St, lat: -35.221231, lng: 149.144645} - { name: Flemington Rd / Sandford St,stop_code: Flemington Rd / Sandford St, lat: -35.221231, lng: 149.144645}
- { name: Florey Shops,stop_code: Florey Shops, lat: -35.2258544, lng: 149.0546214} - { name: Florey,stop_code: Florey, lat: -35.2258544, lng: 149.0546214}
- { name: Flynn,stop_code: Flynn, lat: -35.2019283, lng: 149.0478356} - { name: Flynn,stop_code: Flynn, lat: -35.2019283, lng: 149.0478356}
- { name: Fraser,stop_code: Fraser, lat: -35.1896539, lng: 149.0435012} - { name: Fraser,stop_code: Fraser, lat: -35.1896539, lng: 149.0435012}
- { name: Fraser East Terminus,stop_code: Fraser East Terminus, lat: -35.1896539, lng: 149.0435012} - { name: Fraser East Terminus,stop_code: Fraser East Terminus, lat: -35.1896539, lng: 149.0435012}
- { name: Fraser Shops,stop_code: Fraser Shops, lat: -35.18966, lng: 149.0435}  
- { name: Fraser West Terminus,stop_code: Fraser West Terminus, lat: -35.191513, lng: 149.038006} - { name: Fraser West Terminus,stop_code: Fraser West Terminus, lat: -35.191513, lng: 149.038006}
- { name: Fyshwick Direct Factory Outlet,stop_code: Fyshwick Direct Factory Outlet, lat: -35.3359862, lng: 149.1796322} - { name: Fyshwick Direct Factory Outlet,stop_code: Fyshwick Direct Factory Outlet, lat: -35.3359862, lng: 149.1796322}
- { name: Fyshwick Terminus,stop_code: Fyshwick Terminus, lat: -35.3285202, lng: 149.1785592} - { name: Fyshwick Terminus,stop_code: Fyshwick Terminus, lat: -35.3285202, lng: 149.1785592}
- { name: Garran,stop_code: Garran, lat: -35.3423286, lng: 149.10811} - { name: Garran,stop_code: Garran, lat: -35.3423286, lng: 149.10811}
- { name: Garran Shops,stop_code: Garran Shops, lat: -35.34236, lng: 149.1082}  
- { name: Geoscience Australia,stop_code: Geoscience Australia, lat: -35.3429702, lng: 149.1583893} - { name: Geoscience Australia,stop_code: Geoscience Australia, lat: -35.3429702, lng: 149.1583893}
- { name: Giralang,stop_code: Giralang, lat: -35.2115608, lng: 149.0960692} - { name: Giralang,stop_code: Giralang, lat: -35.2115608, lng: 149.0960692}
- { name: Giralang Shops,stop_code: Giralang Shops, lat: -35.2115608, lng: 149.0960692}  
- { name: Gordon Primary,stop_code: Gordon Primary, lat: -35.455517, lng: 149.086978} - { name: Gordon Primary,stop_code: Gordon Primary, lat: -35.455517, lng: 149.086978}
- { name: Gowrie,stop_code: Gowrie, lat: -35.4120264, lng: 149.1110804} - { name: Gowrie,stop_code: Gowrie, lat: -35.4120264, lng: 149.1110804}
- { name: Gowrie Shops,stop_code: Gowrie Shops, lat: -35.4120264, lng: 149.1110804}  
- { name: Gungahlin Marketplace,stop_code: Gungahlin Marketplace, lat: -35.1769532, lng: 149.1319017} - { name: Gungahlin Marketplace,stop_code: Gungahlin Marketplace, lat: -35.1769532, lng: 149.1319017}
- { name: Gwydir Square Kaleen,stop_code: Gwydir Square Kaleen, lat: -35.2338677, lng: 149.1031998} - { name: Gwydir Square Kaleen,stop_code: Gwydir Square Kaleen, lat: -35.2338677, lng: 149.1031998}
- { name: Hackett,stop_code: Hackett, lat: -35.2481617, lng: 149.1626094} - { name: Hackett,stop_code: Hackett, lat: -35.2481617, lng: 149.1626094}
- { name: Hackett Shops,stop_code: Hackett Shops, lat: -35.24825, lng: 149.16271}  
- { name: Hawker,stop_code: Hawker, lat: -35.2437386, lng: 149.0432804} - { name: Hawker,stop_code: Hawker, lat: -35.2437386, lng: 149.0432804}
- { name: Hawker College,stop_code: Hawker College, lat: -35.2454598, lng: 149.0324251} - { name: Hawker College,stop_code: Hawker College, lat: -35.2454598, lng: 149.0324251}
- { name: Hawker Shops,stop_code: Hawker Shops, lat: -35.24398, lng: 149.04361}  
- { name: Heagney / Clift Richardson,stop_code: Heagney / Clift Richardson, lat: -35.4251299, lng: 149.11375} - { name: Heagney / Clift Richardson,stop_code: Heagney / Clift Richardson, lat: -35.4251299, lng: 149.11375}
- { name: Hibberson / Kate Crace,stop_code: Hibberson / Kate Crace, lat: -35.1861642, lng: 149.1391756} - { name: Hibberson / Kate Crace,stop_code: Hibberson / Kate Crace, lat: -35.1861642, lng: 149.1391756}
- { name: Higgins,stop_code: Higgins, lat: -35.2313901, lng: 149.0271811} - { name: Higgins,stop_code: Higgins, lat: -35.2313901, lng: 149.0271811}
- { name: Higgins Shops,stop_code: Higgins Shops, lat: -35.23136, lng: 149.02611}  
- { name: Holder,stop_code: Holder, lat: -35.3378123, lng: 149.0449433} - { name: Holder,stop_code: Holder, lat: -35.3378123, lng: 149.0449433}
- { name: Holder Shops,stop_code: Holder Shops, lat: -35.33781, lng: 149.04494}  
- { name: Holt,stop_code: Holt, lat: -35.223099, lng: 149.0126269} - { name: Holt,stop_code: Holt, lat: -35.223099, lng: 149.0126269}
- { name: Holt Shops,stop_code: Holt Shops, lat: -35.2231, lng: 149.01263}  
- { name: Hoskins Street / Oodgeroo Ave,stop_code: Hoskins Street / Oodgeroo Ave, lat: -35.201095, lng: 149.139941} - { name: Hoskins Street / Oodgeroo Ave,stop_code: Hoskins Street / Oodgeroo Ave, lat: -35.201095, lng: 149.139941}
- { name: Hospice / Menindee Dr,stop_code: Hospice / Menindee Dr, lat: -35.303557, lng: 149.151627} - { name: Hospice / Menindee Dr,stop_code: Hospice / Menindee Dr, lat: -35.303557, lng: 149.151627}
- { name: Hughes,stop_code: Hughes, lat: -35.3339223, lng: 149.093854} - { name: Hughes,stop_code: Hughes, lat: -35.3339223, lng: 149.093854}
- { name: Hughes Shops,stop_code: Hughes Shops, lat: -35.3335, lng: 149.09392}  
- { name: Isaacs,stop_code: Isaacs, lat: -35.3669823, lng: 149.1119217} - { name: Isaacs,stop_code: Isaacs, lat: -35.3669823, lng: 149.1119217}
- { name: Isaacs Shops,stop_code: Isaacs Shops, lat: -35.36698, lng: 149.11192} - { name: Isabella,stop_code: Isabella, lat: -35.4285703, lng: 149.0916837}
- { name: Isabella Shops,stop_code: Isabella Shops, lat: -35.4285703, lng: 149.0916837}  
- { name: Jamison Centre,stop_code: Jamison Centre, lat: -35.2527268, lng: 149.0713712} - { name: Jamison Centre,stop_code: Jamison Centre, lat: -35.2527268, lng: 149.0713712}
- { name: John James Hospital,stop_code: John James Hospital, lat: -35.3200295, lng: 149.0955996} - { name: John James Hospital,stop_code: John James Hospital, lat: -35.3200295, lng: 149.0955996}
- { name: Kaleen Village / Marybrynong,stop_code: Kaleen Village / Marybrynong, lat: -35.2274031, lng: 149.1075421} - { name: Kaleen Village / Marybrynong,stop_code: Kaleen Village / Marybrynong, lat: -35.2274031, lng: 149.1075421}
- { name: Kambah High,stop_code: Kambah High, lat: -35.3847749, lng: 149.0720245} - { name: Kambah High,stop_code: Kambah High, lat: -35.3847749, lng: 149.0720245}
  - { name: Kambah / Livingston St,stop_code: Kambah / Livingston St, lat: -35.3883359, lng: 149.0811471}
- { name: Kambah Village,stop_code: Kambah Village, lat: -35.3800314, lng: 149.0576581} - { name: Kambah Village,stop_code: Kambah Village, lat: -35.3800314, lng: 149.0576581}
- { name: Katherine Ave / Horse Park Drive,stop_code: Katherine Ave / Horse Park Drive, lat: -35.1680901, lng: 149.1321801} - { name: Katherine Ave / Horse Park Drive,stop_code: Katherine Ave / Horse Park Drive, lat: -35.1680901, lng: 149.1321801}
- { name: Kerrigan / Lhotsky,stop_code: Kerrigan / Lhotsky, lat: -35.193801, lng: 149.035689} - { name: Kerrigan / Lhotsky,stop_code: Kerrigan / Lhotsky, lat: -35.193801, lng: 149.035689}
- { name: Kings Ave / National Circuit,stop_code: Kings Ave / National Circuit, lat: -35.305004, lng: 149.13262} - { name: Kings Ave / National Circuit,stop_code: Kings Ave / National Circuit, lat: -35.305004, lng: 149.13262}
- { name: Kingston,stop_code: Kingston, lat: -35.3197448, lng: 149.1375261} - { name: Kingston,stop_code: Kingston, lat: -35.3197448, lng: 149.1375261}
- { name: Kippax,stop_code: Kippax, lat: -35.22225, lng: 149.0195627} - { name: Kippax,stop_code: Kippax, lat: -35.22225, lng: 149.0195627}
- { name: Kippax Centre,stop_code: Kippax Centre, lat: -35.22172, lng: 149.01995} - { name: Kippax Centre,stop_code: Kippax Centre, lat: -35.22172, lng: 149.01995}
- { name: Kosciuszko / Everard,stop_code: Kosciuszko / Everard, lat: -35.188901, lng: 149.1216937} - { name: Kosciuszko / Everard,stop_code: Kosciuszko / Everard, lat: -35.188901, lng: 149.1216937}
- { name: Lanyon Market Place,stop_code: Lanyon Market Place, lat: -35.4573, lng: 149.09199} - { name: Lanyon Market Place,stop_code: Lanyon Market Place, lat: -35.4573, lng: 149.09199}
  - { name: Latham,stop_code: Latham, lat: -35.21848, lng: 149.03214}
- { name: Latham Post Office,stop_code: Latham Post Office, lat: -35.21906, lng: 149.03223} - { name: Latham Post Office,stop_code: Latham Post Office, lat: -35.21906, lng: 149.03223}
- { name: Latham Shops,stop_code: Latham Shops, lat: -35.21848, lng: 149.03214}  
- { name: Lathlain St Bus Station,stop_code: Lathlain St Bus Station, lat: -35.2396657, lng: 149.0633993} - { name: Lathlain St Bus Station,stop_code: Lathlain St Bus Station, lat: -35.2396657, lng: 149.0633993}
- { name: Lathlain St Bus Station (Platform 1),stop_code: Lathlain St Bus Station (Platform 1), lat: -35.2408973, lng: 149.0639887} - { name: Lathlain St Bus Station (Platform 1),stop_code: Lathlain St Bus Station (Platform 1), lat: -35.2408973, lng: 149.0639887}
- { name: Lathlain St Bus Station (Platform 2),stop_code: Lathlain St Bus Station (Platform 2), lat: -35.2406038, lng: 149.0638922} - { name: Lathlain St Bus Station (Platform 2),stop_code: Lathlain St Bus Station (Platform 2), lat: -35.2406038, lng: 149.0638922}
- { name: Lathlain St Bus Station (Platform 3),stop_code: Lathlain St Bus Station (Platform 3), lat: -35.2400517, lng: 149.0637152} - { name: Lathlain St Bus Station (Platform 3),stop_code: Lathlain St Bus Station (Platform 3), lat: -35.2400517, lng: 149.0637152}
- { name: Lathlain St Bus Station (Platform 4),stop_code: Lathlain St Bus Station (Platform 4), lat: -35.2396657, lng: 149.0633993} - { name: Lathlain St Bus Station (Platform 4),stop_code: Lathlain St Bus Station (Platform 4), lat: -35.2396657, lng: 149.0633993}
- { name: Lathlain St Bus Station (Platform 5),stop_code: Lathlain St Bus Station (Platform 5), lat: -35.2405468, lng: 149.0636669} - { name: Lathlain St Bus Station (Platform 5),stop_code: Lathlain St Bus Station (Platform 5), lat: -35.2405468, lng: 149.0636669}
- { name: Lathlain St Bus Station (Platform 6),stop_code: Lathlain St Bus Station (Platform 6), lat: -35.2410486, lng: 149.0638326} - { name: Lathlain St Bus Station (Platform 6),stop_code: Lathlain St Bus Station (Platform 6), lat: -35.2410486, lng: 149.0638326}
- { name: Lewis Luxton/Woodcock Dr,stop_code: Lewis Luxton/Woodcock Dr, lat: -35.4422566, lng: 149.0854375} - { name: Lewis Luxton/Woodcock Dr,stop_code: Lewis Luxton/Woodcock Dr, lat: -35.4422566, lng: 149.0854375}
- { name: Lithgow St Terminus Fyshwick,stop_code: Lithgow St Terminus Fyshwick, lat: -35.3296912, lng: 149.1668153} - { name: Lithgow St Terminus Fyshwick,stop_code: Lithgow St Terminus Fyshwick, lat: -35.3296912, lng: 149.1668153}
- { name: Livingston Shops Kambah,stop_code: Livingston Shops Kambah, lat: -35.3883359, lng: 149.0811471}  
- { name: Livingston Shops / Kambah,stop_code: Livingston Shops / Kambah, lat: -35.390246, lng: 149.07822}  
- { name: Lyneham,stop_code: Lyneham, lat: -35.2523304, lng: 149.1246184} - { name: Lyneham,stop_code: Lyneham, lat: -35.2523304, lng: 149.1246184}
- { name: Lyneham High,stop_code: Lyneham High, lat: -35.2524016, lng: 149.130254} - { name: Lyneham High,stop_code: Lyneham High, lat: -35.2524016, lng: 149.130254}
- { name: Lyneham Shops Wattle Street,stop_code: Lyneham Shops Wattle Street, lat: -35.25205, lng: 149.12524} - { name: Lyneham / Wattle St,stop_code: Lyneham / Wattle St, lat: -35.25205, lng: 149.12524}
- { name: Lyons,stop_code: Lyons, lat: -35.3415779, lng: 149.0765703} - { name: Lyons,stop_code: Lyons, lat: -35.3415779, lng: 149.0765703}
- { name: Lyons Shops,stop_code: Lyons Shops, lat: -35.34019, lng: 149.0771}  
- { name: Macarthur / Miller O'Connor,stop_code: Macarthur / Miller O'Connor, lat: -35.2587584, lng: 149.1153561} - { name: Macarthur / Miller O'Connor,stop_code: Macarthur / Miller O'Connor, lat: -35.2587584, lng: 149.1153561}
- { name: Macarthur / Northbourne Ave,stop_code: Macarthur / Northbourne Ave, lat: -35.26051, lng: 149.13224} - { name: Macarthur / Northbourne Ave,stop_code: Macarthur / Northbourne Ave, lat: -35.26051, lng: 149.13224}
- { name: Macgregor,stop_code: Macgregor, lat: -35.2100645, lng: 149.0122952} - { name: Macgregor,stop_code: Macgregor, lat: -35.2100645, lng: 149.0122952}
- { name: Macgregor Shops,stop_code: Macgregor Shops, lat: -35.2100645, lng: 149.0122952}  
- { name: MacKillop College Isabella Campus,stop_code: MacKillop College Isabella Campus, lat: -35.42597, lng: 149.09172} - { name: MacKillop College Isabella Campus,stop_code: MacKillop College Isabella Campus, lat: -35.42597, lng: 149.09172}
- { name: MacKillop College Wanniassa Campus,stop_code: MacKillop College Wanniassa Campus, lat: -35.4056, lng: 149.089774} - { name: MacKillop College Wanniassa Campus,stop_code: MacKillop College Wanniassa Campus, lat: -35.4056, lng: 149.089774}
- { name: Macquarie,stop_code: Macquarie, lat: -35.2483414, lng: 149.0600666} - { name: Macquarie,stop_code: Macquarie, lat: -35.2483414, lng: 149.0600666}
- { name: Majura Business Park,stop_code: Majura Business Park, lat: -35.2987, lng: 149.18561} - { name: Majura Business Park,stop_code: Majura Business Park, lat: -35.2987, lng: 149.18561}
- { name: Manning Clarke / Oodgeroo,stop_code: Manning Clarke / Oodgeroo, lat: -35.193236, lng: 149.146534} - { name: Manning Clarke / Oodgeroo,stop_code: Manning Clarke / Oodgeroo, lat: -35.193236, lng: 149.146534}
- { name: Manuka,stop_code: Manuka, lat: -35.3200096, lng: 149.1341344} - { name: Manuka,stop_code: Manuka, lat: -35.3200096, lng: 149.1341344}
- { name: Manuka / Captain Cook Cres,stop_code: Manuka / Captain Cook Cres, lat: -35.3217, lng: 149.13445} - { name: Manuka / Captain Cook Cres,stop_code: Manuka / Captain Cook Cres, lat: -35.3217, lng: 149.13445}
- { name: McKellar,stop_code: McKellar, lat: -35.2174267, lng: 149.0742108} - { name: McKellar,stop_code: McKellar, lat: -35.2174267, lng: 149.0742108}
- { name: McKellar Shops,stop_code: McKellar Shops, lat: -35.2182, lng: 149.07555}  
- { name: Melba,stop_code: Melba, lat: -35.2083104, lng: 149.0485366} - { name: Melba,stop_code: Melba, lat: -35.2083104, lng: 149.0485366}
- { name: Melba Shops,stop_code: Melba Shops, lat: -35.21004, lng: 149.05302}  
- { name: Mentone View / Tharwa Drive,stop_code: Mentone View / Tharwa Drive, lat: -35.45144, lng: 149.0919} - { name: Mentone View / Tharwa Drive,stop_code: Mentone View / Tharwa Drive, lat: -35.45144, lng: 149.0919}
- { name: Merici College,stop_code: Merici College, lat: -35.266525, lng: 149.137037} - { name: Merici College,stop_code: Merici College, lat: -35.266525, lng: 149.137037}
- { name: Mirrabei Drive / Dam Wall,stop_code: Mirrabei Drive / Dam Wall, lat: -35.177453, lng: 149.124291} - { name: Mirrabei Drive / Dam Wall,stop_code: Mirrabei Drive / Dam Wall, lat: -35.177453, lng: 149.124291}
- { name: Monash,stop_code: Monash, lat: -35.4190254, lng: 149.0834805} - { name: Monash,stop_code: Monash, lat: -35.4190254, lng: 149.0834805}
- { name: Monash Goodwin Village,stop_code: Monash Goodwin Village, lat: -35.421084, lng: 149.097438} - { name: Monash Goodwin Village,stop_code: Monash Goodwin Village, lat: -35.421084, lng: 149.097438}
- { name: Monash Primary,stop_code: Monash Primary, lat: -35.414879, lng: 149.089411} - { name: Monash Primary,stop_code: Monash Primary, lat: -35.414879, lng: 149.089411}
- { name: Mount Neighbour School,stop_code: Mount Neighbour School, lat: -35.382445, lng: 149.051518} - { name: Mount Neighbour School,stop_code: Mount Neighbour School, lat: -35.382445, lng: 149.051518}
- { name: Narrabundah,stop_code: Narrabundah, lat: -35.332605, lng: 149.154049} - { name: Narrabundah,stop_code: Narrabundah, lat: -35.332605, lng: 149.154049}
- { name: Narrabundah College,stop_code: Narrabundah College, lat: -35.3362106, lng: 149.1471005} - { name: Narrabundah College,stop_code: Narrabundah College, lat: -35.3362106, lng: 149.1471005}
- { name: Narrabundah Terminus,stop_code: Narrabundah Terminus, lat: -35.332605, lng: 149.154049} - { name: Narrabundah Terminus,stop_code: Narrabundah Terminus, lat: -35.332605, lng: 149.154049}
- { name: National Circ / Canberra Ave,stop_code: National Circ / Canberra Ave, lat: -35.31407, lng: 149.13011} - { name: National Circ / Canberra Ave,stop_code: National Circ / Canberra Ave, lat: -35.31407, lng: 149.13011}
- { name: National Hockey Centre Lyneham,stop_code: National Hockey Centre Lyneham, lat: -35.2446729, lng: 149.1288303} - { name: National Hockey Centre Lyneham,stop_code: National Hockey Centre Lyneham, lat: -35.2446729, lng: 149.1288303}
- { name: National Museum of Australia,stop_code: National Museum of Australia, lat: -35.29248, lng: 149.1205367} - { name: National Museum of Australia,stop_code: National Museum of Australia, lat: -35.29248, lng: 149.1205367}
- { name: National Zoo and Aquarium,stop_code: National Zoo and Aquarium, lat: -35.29915, lng: 149.07025} - { name: National Zoo and Aquarium,stop_code: National Zoo and Aquarium, lat: -35.29915, lng: 149.07025}
- { name: Newcastle Street after Isa Street,stop_code: Newcastle Street after Isa Street, lat: -35.3255, lng: 149.173291} - { name: Newcastle Street after Isa Street,stop_code: Newcastle Street after Isa Street, lat: -35.3255, lng: 149.173291}
- { name: Ngunnawal Primary,stop_code: Ngunnawal Primary, lat: -35.1688551, lng: 149.1112569} - { name: Ngunnawal Primary,stop_code: Ngunnawal Primary, lat: -35.1688551, lng: 149.1112569}
- { name: Nicholls Primary,stop_code: Nicholls Primary, lat: -35.1905592, lng: 149.0876716} - { name: Nicholls Primary,stop_code: Nicholls Primary, lat: -35.1905592, lng: 149.0876716}
- { name: Northbourne Avenue / Antill St,stop_code: Northbourne Avenue / Antill St, lat: -35.248287, lng: 149.134241} - { name: Northbourne Avenue / Antill St,stop_code: Northbourne Avenue / Antill St, lat: -35.248287, lng: 149.134241}
- { name: North Lyneham,stop_code: North Lyneham, lat: -35.2385618, lng: 149.1221188} - { name: North Lyneham,stop_code: North Lyneham, lat: -35.2385618, lng: 149.1221188}
- { name: O'Connor,stop_code: O'Connor, lat: -35.2640376, lng: 149.1226107} - { name: O'Connor,stop_code: O'Connor, lat: -35.2640376, lng: 149.1226107}
- { name: O'Connor Shops,stop_code: O'Connor Shops, lat: -35.2640376, lng: 149.1226107}  
- { name: Olims Hotel,stop_code: Olims Hotel, lat: -35.27597, lng: 149.1428} - { name: Olims Hotel,stop_code: Olims Hotel, lat: -35.27597, lng: 149.1428}
- { name: Outtrim / Duggan,stop_code: Outtrim / Duggan, lat: -35.435871, lng: 149.097692} - { name: Outtrim / Duggan,stop_code: Outtrim / Duggan, lat: -35.435871, lng: 149.097692}
- { name: Page Shops,stop_code: Page Shops, lat: -35.2360695, lng: 149.0536554} - { name: Page,stop_code: Page, lat: -35.2360695, lng: 149.0536554}
- { name: Parliament House,stop_code: Parliament House, lat: -35.3081571, lng: 149.1244592} - { name: Parliament House,stop_code: Parliament House, lat: -35.3081571, lng: 149.1244592}
- { name: Paul Coe / Mirrabei Dr,stop_code: Paul Coe / Mirrabei Dr, lat: -35.17467, lng: 149.12005} - { name: Paul Coe / Mirrabei Dr,stop_code: Paul Coe / Mirrabei Dr, lat: -35.17467, lng: 149.12005}
- { name: Pearce,stop_code: Pearce, lat: -35.3625413, lng: 149.0815935} - { name: Pearce,stop_code: Pearce, lat: -35.3625413, lng: 149.0815935}
- { name: Pearce Shops,stop_code: Pearce Shops, lat: -35.3625413, lng: 149.0815935}  
- { name: Police College Weston,stop_code: Police College Weston, lat: -35.33018, lng: 149.05458} - { name: Police College Weston,stop_code: Police College Weston, lat: -35.33018, lng: 149.05458}
- { name: Proctor / Mead,stop_code: Proctor / Mead, lat: -35.415305, lng: 149.127204} - { name: Proctor / Mead,stop_code: Proctor / Mead, lat: -35.415305, lng: 149.127204}
- { name: Railway Station Kingston,stop_code: Railway Station Kingston, lat: -35.319602, lng: 149.149083} - { name: Railway Station Kingston,stop_code: Railway Station Kingston, lat: -35.319602, lng: 149.149083}
- { name: Red Hill,stop_code: Red Hill, lat: -35.336505, lng: 149.131645} - { name: Red Hill,stop_code: Red Hill, lat: -35.336505, lng: 149.131645}
- { name: Red Hill Shops,stop_code: Red Hill Shops, lat: -35.336505, lng: 149.131645}  
- { name: Rivett,stop_code: Rivett, lat: -35.3473758, lng: 149.0365438} - { name: Rivett,stop_code: Rivett, lat: -35.3473758, lng: 149.0365438}
- { name: Rivett Shops,stop_code: Rivett Shops, lat: -35.34737, lng: 149.03654}  
- { name: Russell Offices,stop_code: Russell Offices, lat: -35.2973294, lng: 149.1508803} - { name: Russell Offices,stop_code: Russell Offices, lat: -35.2973294, lng: 149.1508803}
- { name: Sainsbury Street,stop_code: Sainsbury Street, lat: -35.3885, lng: 149.09643} - { name: Sainsbury Street,stop_code: Sainsbury Street, lat: -35.3885, lng: 149.09643}
- { name: Saint Andrews Village Hughes,stop_code: Saint Andrews Village Hughes, lat: -35.328097, lng: 149.088685} - { name: Saint Andrews Village Hughes,stop_code: Saint Andrews Village Hughes, lat: -35.328097, lng: 149.088685}
- { name: Scullin Shops,stop_code: Scullin Shops, lat: -35.23356, lng: 149.04056} - { name: Scullin,stop_code: Scullin, lat: -35.23356, lng: 149.04056}
- { name: Shoalhaven / Katherine Ave,stop_code: Shoalhaven / Katherine Ave, lat: -35.16823, lng: 149.12791} - { name: Shoalhaven / Katherine Ave,stop_code: Shoalhaven / Katherine Ave, lat: -35.16823, lng: 149.12791}
- { name: Southlands Mawson,stop_code: Southlands Mawson, lat: -35.3650685, lng: 149.0945962} - { name: Southlands Mawson,stop_code: Southlands Mawson, lat: -35.3650685, lng: 149.0945962}
- { name: Southwell Park,stop_code: Southwell Park, lat: -35.24573, lng: 149.1321} - { name: Southwell Park,stop_code: Southwell Park, lat: -35.24573, lng: 149.1321}
- { name: Spence,stop_code: Spence, lat: -35.194735, lng: 149.062352} - { name: Spence,stop_code: Spence, lat: -35.194735, lng: 149.062352}
- { name: Spence Shops,stop_code: Spence Shops, lat: -35.19968, lng: 149.06763}  
- { name: Spence Terminus,stop_code: Spence Terminus, lat: -35.199684, lng: 149.0676196} - { name: Spence Terminus,stop_code: Spence Terminus, lat: -35.199684, lng: 149.0676196}
- { name: St Clare of Assisi,stop_code: St Clare of Assisi, lat: -35.46063, lng: 149.09627}  
- { name: St Clare of Assisi Primary,stop_code: St Clare of Assisi Primary, lat: -35.4606284, lng: 149.0962704} - { name: St Clare of Assisi Primary,stop_code: St Clare of Assisi Primary, lat: -35.4606284, lng: 149.0962704}
- { name: St Francis Xavier Florey,stop_code: St Francis Xavier Florey, lat: -35.223951, lng: 149.0406888} - { name: St Francis Xavier Florey,stop_code: St Francis Xavier Florey, lat: -35.223951, lng: 149.0406888}
- { name: Stromlo High Waramanga,stop_code: Stromlo High Waramanga, lat: -35.3551186, lng: 149.0547624} - { name: Stromlo High Waramanga,stop_code: Stromlo High Waramanga, lat: -35.3551186, lng: 149.0547624}
- { name: St Thomas More's Campbell,stop_code: St Thomas More's Campbell, lat: -35.286717, lng: 149.156836} - { name: St Thomas More's Campbell,stop_code: St Thomas More's Campbell, lat: -35.286717, lng: 149.156836}
- { name: Sydney Ave,stop_code: Sydney Ave, lat: -35.31193, lng: 149.13105} - { name: Sydney Ave,stop_code: Sydney Ave, lat: -35.31193, lng: 149.13105}
- { name: Taverner St / Erindale Dr,stop_code: Taverner St / Erindale Dr, lat: -35.4059104, lng: 149.0809317} - { name: Taverner St / Erindale Dr,stop_code: Taverner St / Erindale Dr, lat: -35.4059104, lng: 149.0809317}
- { name: Tharwa Drive,stop_code: Tharwa Drive, lat: -35.458251, lng: 149.091652} - { name: Tharwa Drive,stop_code: Tharwa Drive, lat: -35.458251, lng: 149.091652}
- { name: Tharwa Drive / Knoke Ave,stop_code: Tharwa Drive / Knoke Ave, lat: -35.47281, lng: 149.08926} - { name: Tharwa Drive / Knoke Ave,stop_code: Tharwa Drive / Knoke Ave, lat: -35.47281, lng: 149.08926}
- { name: Tharwa Dr / Pockett Ave,stop_code: Tharwa Dr / Pockett Ave, lat: -35.47348, lng: 149.09178} - { name: Tharwa Drive / Pockett Ave,stop_code: Tharwa Drive / Pockett Ave, lat: -35.47348, lng: 149.09178}
- { name: Theodore,stop_code: Theodore, lat: -35.4464808, lng: 149.1234651} - { name: Theodore,stop_code: Theodore, lat: -35.4464808, lng: 149.1234651}
- { name: Tillyard / Spalding,stop_code: Tillyard / Spalding, lat: -35.199204, lng: 149.044556} - { name: Tillyard / Spalding,stop_code: Tillyard / Spalding, lat: -35.199204, lng: 149.044556}
- { name: Torrens Shops,stop_code: Torrens Shops, lat: -35.3730889, lng: 149.087327} - { name: Torrens,stop_code: Torrens, lat: -35.3730889, lng: 149.087327}
- { name: Tuggeranong Bus Station,stop_code: Tuggeranong Bus Station, lat: -35.41465, lng: 149.06537} - { name: Tuggeranong Bus Station,stop_code: Tuggeranong Bus Station, lat: -35.41465, lng: 149.06537}
- { name: Tuggeranong Bus Station (Platform 3),stop_code: Tuggeranong Bus Station (Platform 3), lat: -35.4147569, lng: 149.0657435} - { name: Tuggeranong Bus Station (Platform 3),stop_code: Tuggeranong Bus Station (Platform 3), lat: -35.4147569, lng: 149.0657435}
- { name: Tuggeranong Bus Station (Platform 4),stop_code: Tuggeranong Bus Station (Platform 4), lat: -35.4144924, lng: 149.0655423} - { name: Tuggeranong Bus Station (Platform 4),stop_code: Tuggeranong Bus Station (Platform 4), lat: -35.4144924, lng: 149.0655423}
- { name: Tuggeranong Bus Station (Platform 5),stop_code: Tuggeranong Bus Station (Platform 5), lat: -35.414217, lng: 149.0653492} - { name: Tuggeranong Bus Station (Platform 5),stop_code: Tuggeranong Bus Station (Platform 5), lat: -35.414217, lng: 149.0653492}
- { name: Tuggeranong Bus Station (Platform 7),stop_code: Tuggeranong Bus Station (Platform 7), lat: -35.4146761, lng: 149.0654565} - { name: Tuggeranong Bus Station (Platform 7),stop_code: Tuggeranong Bus Station (Platform 7), lat: -35.4146761, lng: 149.0654565}
- { name: Tuggeranong Bus Station (Platform 8),stop_code: Tuggeranong Bus Station (Platform 8), lat: -35.4149428, lng: 149.0656523} - { name: Tuggeranong Bus Station (Platform 8),stop_code: Tuggeranong Bus Station (Platform 8), lat: -35.4149428, lng: 149.0656523}
- { name: University of Canberra,stop_code: University of Canberra, lat: -35.2423222, lng: 149.0831522} - { name: University of Canberra,stop_code: University of Canberra, lat: -35.2423222, lng: 149.0831522}
- { name: Wanniassa High,stop_code: Wanniassa High, lat: -35.3952462, lng: 149.0852655} - { name: Wanniassa High,stop_code: Wanniassa High, lat: -35.3952462, lng: 149.0852655}
- { name: Waramanga,stop_code: Waramanga, lat: -35.3526825, lng: 149.0594712} - { name: Waramanga,stop_code: Waramanga, lat: -35.3526825, lng: 149.0594712}
- { name: Waramanga Shops,stop_code: Waramanga Shops, lat: -35.35268, lng: 149.05948} - { name: War Memorial / Limestone Ave,stop_code: War Memorial / Limestone Ave, lat: -35.280477, lng: 149.149085}
- { name: War Memorial Limestone Ave,stop_code: War Memorial Limestone Ave, lat: -35.280477, lng: 149.149085}  
- { name: Watson,stop_code: Watson, lat: -35.2389399, lng: 149.1535345} - { name: Watson,stop_code: Watson, lat: -35.2389399, lng: 149.1535345}
- { name: Watson Shops,stop_code: Watson Shops, lat: -35.2389399, lng: 149.1535345}  
- { name: Watson Terminus,stop_code: Watson Terminus, lat: -35.2374698, lng: 149.1534553} - { name: Watson Terminus,stop_code: Watson Terminus, lat: -35.2374698, lng: 149.1534553}
- { name: Weetangera Shops,stop_code: Weetangera Shops, lat: -35.248393, lng: 149.0506342} - { name: Weetangera,stop_code: Weetangera, lat: -35.248393, lng: 149.0506342}
- { name: Westfield Bus Station,stop_code: Westfield Bus Station, lat: -35.23875, lng: 149.0638} - { name: Westfield Bus Station,stop_code: Westfield Bus Station, lat: -35.23875, lng: 149.0638}
- { name: Westfield Bus Station (Platform 1),stop_code: Westfield Bus Station (Platform 1), lat: -35.23872, lng: 149.06387} - { name: Westfield Bus Station (Platform 1),stop_code: Westfield Bus Station (Platform 1), lat: -35.23872, lng: 149.06387}
- { name: Westfield Bus Station (Platform 2),stop_code: Westfield Bus Station (Platform 2), lat: -35.23882, lng: 149.0637} - { name: Westfield Bus Station (Platform 2),stop_code: Westfield Bus Station (Platform 2), lat: -35.23882, lng: 149.0637}
- { name: West Macgregor,stop_code: West Macgregor, lat: -35.21207, lng: 149.00165} - { name: West Macgregor,stop_code: West Macgregor, lat: -35.21207, lng: 149.00165}
- { name: Weston Creek Terminus,stop_code: Weston Creek Terminus, lat: -35.342728, lng: 149.0524906} - { name: Weston Creek Terminus,stop_code: Weston Creek Terminus, lat: -35.342728, lng: 149.0524906}
- { name: Weston Primary,stop_code: Weston Primary, lat: -35.3305221, lng: 149.0524281} - { name: Weston Primary,stop_code: Weston Primary, lat: -35.3305221, lng: 149.0524281}
- { name: William Webb / Ginninderra Drive,stop_code: William Webb / Ginninderra Drive, lat: -35.222395, lng: 149.0706} - { name: William Webb / Ginninderra Drive,stop_code: William Webb / Ginninderra Drive, lat: -35.222395, lng: 149.0706}
- { name: Woden Bus Station,stop_code: Woden Bus Station, lat: -35.34433, lng: 149.08742} - { name: Woden Bus Station,stop_code: Woden Bus Station, lat: -35.34433, lng: 149.08742}
- { name: Woden Bus Station (Platform 10),stop_code: Woden Bus Station (Platform 10), lat: -35.3439501, lng: 149.0877369} - { name: Woden Bus Station (Platform 10),stop_code: Woden Bus Station (Platform 10), lat: -35.3439501, lng: 149.0877369}
- { name: Woden Bus Station (Platform 11),stop_code: Woden Bus Station (Platform 11), lat: -35.3439129, lng: 149.0876216} - { name: Woden Bus Station (Platform 11),stop_code: Woden Bus Station (Platform 11), lat: -35.3439129, lng: 149.0876216}
- { name: Woden Bus Station (Platform 12),stop_code: Woden Bus Station (Platform 12), lat: -35.3442094, lng: 149.0876444} - { name: Woden Bus Station (Platform 12),stop_code: Woden Bus Station (Platform 12), lat: -35.3442094, lng: 149.0876444}
- { name: Woden Bus Station (Platform 14),stop_code: Woden Bus Station (Platform 14), lat: -35.34438, lng: 149.0872662} - { name: Woden Bus Station (Platform 14),stop_code: Woden Bus Station (Platform 14), lat: -35.34438, lng: 149.0872662}
- { name: Woden Bus Station (Platform 15),stop_code: Woden Bus Station (Platform 15), lat: -35.3444271, lng: 149.0869631} - { name: Woden Bus Station (Platform 15),stop_code: Woden Bus Station (Platform 15), lat: -35.3444271, lng: 149.0869631}
- { name: Woden Bus Station (Platform 16),stop_code: Woden Bus Station (Platform 16), lat: -35.344484, lng: 149.0866144} - { name: Woden Bus Station (Platform 16),stop_code: Woden Bus Station (Platform 16), lat: -35.344484, lng: 149.0866144}
- { name: Woden Bus Station (Platform 2),stop_code: Woden Bus Station (Platform 2), lat: -35.3447574, lng: 149.0862912} - { name: Woden Bus Station (Platform 2),stop_code: Woden Bus Station (Platform 2), lat: -35.3447574, lng: 149.0862912}
- { name: Woden Bus Station (Platform 3),stop_code: Woden Bus Station (Platform 3), lat: -35.344566, lng: 149.086774} - { name: Woden Bus Station (Platform 3),stop_code: Woden Bus Station (Platform 3), lat: -35.344566, lng: 149.086774}
- { name: Woden Bus Station (Platform 4),stop_code: Woden Bus Station (Platform 4), lat: -35.3445222, lng: 149.0870436} - { name: Woden Bus Station (Platform 4),stop_code: Woden Bus Station (Platform 4), lat: -35.3445222, lng: 149.0870436}
- { name: Woden Bus Station (Platform 5),stop_code: Woden Bus Station (Platform 5), lat: -35.3444741, lng: 149.0873533} - { name: Woden Bus Station (Platform 5),stop_code: Woden Bus Station (Platform 5), lat: -35.3444741, lng: 149.0873533}
- { name: Woden Bus Station (Platform 6),stop_code: Woden Bus Station (Platform 6), lat: -35.34445, lng: 149.0875371} - { name: Woden Bus Station (Platform 6),stop_code: Woden Bus Station (Platform 6), lat: -35.34445, lng: 149.0875371}
- { name: Woden Bus Station (Platform 9),stop_code: Woden Bus Station (Platform 9), lat: -35.3442083, lng: 149.0877771} - { name: Woden Bus Station (Platform 9),stop_code: Woden Bus Station (Platform 9), lat: -35.3442083, lng: 149.0877771}
- { name: Woodcock / Clare Dennis,stop_code: Woodcock / Clare Dennis, lat: -35.4422566, lng: 149.0854375} - { name: Woodcock / Clare Dennis,stop_code: Woodcock / Clare Dennis, lat: -35.4422566, lng: 149.0854375}
- { name: Yarralumla Shops,stop_code: Yarralumla Shops, lat: -35.30725, lng: 149.0972} - { name: Yarralumla,stop_code: Yarralumla, lat: -35.30725, lng: 149.0972}
- { name: Andrea Place,stop_code: Wjz1ceG, lat: -35.4375289, lng: 149.0757996} - { name: Andrea Place,stop_code: Wjz1ceG, lat: -35.4375289, lng: 149.0757996}
- { name: Tarlton Place,stop_code: Wjz1kvl, lat: -35.4366017, lng: 149.0890756} - { name: Tarlton Place,stop_code: Wjz1kvl, lat: -35.4366017, lng: 149.0890756}
- { name: Don Dunstan Drive,stop_code: Wjz16U7, lat: -35.4302659, lng: 149.0722593} - { name: Don Dunstan Drive,stop_code: Wjz16U7, lat: -35.4302659, lng: 149.0722593}
- { name: Salmon Place,stop_code: WjrWY3_, lat: -35.3952466, lng: 149.0527528} - { name: Salmon Place,stop_code: WjrWY3_, lat: -35.3952466, lng: 149.0527528}
- { name: Crozier Circuit,stop_code: WjrWSUa, lat: -35.3867455, lng: 149.0504459} - { name: Crozier Circuit,stop_code: WjrWSUa, lat: -35.3867455, lng: 149.0504459}
- { name: Mouat Street,stop_code: Wjz5LYB, lat: -35.2464052, lng: 149.1278592} - { name: Mouat Street,stop_code: Wjz5LYB, lat: -35.2464052, lng: 149.1278592}
- { name: Mackennal Street,stop_code: Wjz5LsC, lat: -35.2463364, lng: 149.1223897} - { name: Mackennal Street,stop_code: Wjz5LsC, lat: -35.2463364, lng: 149.1223897}
- { name: Clianthus Street,stop_code: Wjz5Krx, lat: -35.2529666, lng: 149.1223781} - { name: Clianthus Street,stop_code: Wjz5Krx, lat: -35.2529666, lng: 149.1223781}
- { name: Way Street,stop_code: Wjz5BWh, lat: -35.2591172, lng: 149.1164155} - { name: Way Street,stop_code: Wjz5BWh, lat: -35.2591172, lng: 149.1164155}
- { name: Cockle Street,stop_code: Wjz5AGB, lat: -35.2642702, lng: 149.1141435} - { name: Cockle Street,stop_code: Wjz5AGB, lat: -35.2642702, lng: 149.1141435}
- { name: Froggatt Street,stop_code: Wjz5H0p, lat: -35.2714838, lng: 149.1180142} - { name: Froggatt Street,stop_code: Wjz5H0p, lat: -35.2714838, lng: 149.1180142}
- { name: McClintock Street,stop_code: Wjz6ElH, lat: -35.2404264, lng: 149.1210434} - { name: McClintock Street,stop_code: Wjz6ElH, lat: -35.2404264, lng: 149.1210434}
- { name: Cossington Smith Crescent,stop_code: Wjz6FEI, lat: -35.2382959, lng: 149.1252507} - { name: Cossington Smith Crescent,stop_code: Wjz6FEI, lat: -35.2382959, lng: 149.1252507}
- { name: Dumas Street,stop_code: Wjz6cz2, lat: -35.2199304, lng: 149.0791416} - { name: Dumas Street,stop_code: Wjz6cz2, lat: -35.2199304, lng: 149.0791416}
- { name: Buggy Crescent,stop_code: Wjz64OE, lat: -35.2207286, lng: 149.0717368} - { name: Buggy Crescent,stop_code: Wjz64OE, lat: -35.2207286, lng: 149.0717368}
- { name: Owen Dixon Drive,stop_code: Wjz6eWi, lat: -35.2096321, lng: 149.0835148} - { name: Owen Dixon Drive,stop_code: Wjz6eWi, lat: -35.2096321, lng: 149.0835148}
- { name: Baldwin Drive,stop_code: Wjz6kCT, lat: -35.217402, lng: 149.0910262} - { name: Baldwin Drive,stop_code: Wjz6kCT, lat: -35.217402, lng: 149.0910262}
- { name: Jacob Place,stop_code: Wjr-TRM, lat: -35.2021703, lng: 149.0498418} - { name: Jacob Place,stop_code: Wjr-TRM, lat: -35.2021703, lng: 149.0498418}
- { name: Love Street,stop_code: Wjr_MMi, lat: -35.200018, lng: 149.0491234} - { name: Love Street,stop_code: Wjr_MMi, lat: -35.200018, lng: 149.0491234}
- { name: Box Place,stop_code: Wjr-IeY, lat: -35.2176259, lng: 149.032238} - { name: Box Place,stop_code: Wjr-IeY, lat: -35.2176259, lng: 149.032238}
- { name: Macrossan Crescent,stop_code: Wjr-J8t, lat: -35.2161747, lng: 149.0315719} - { name: Macrossan Crescent,stop_code: Wjr-J8t, lat: -35.2161747, lng: 149.0315719}
- { name: Want Place,stop_code: Wjr-Jm9, lat: -35.2124379, lng: 149.0325045} - { name: Want Place,stop_code: Wjr-Jm9, lat: -35.2124379, lng: 149.0325045}
- { name: Fellows Street,stop_code: Wjr-J44, lat: -35.2135626, lng: 149.0296181} - { name: Fellows Street,stop_code: Wjr-J44, lat: -35.2135626, lng: 149.0296181}
- { name: Osburn Drive,stop_code: Wjr-BB3, lat: -35.2129096, lng: 149.0241561} - { name: Osburn Drive,stop_code: Wjr-BB3, lat: -35.2129096, lng: 149.0241561}
- { name: Solomon Crescent,stop_code: Wjr-AY4, lat: -35.2190044, lng: 149.0282415} - { name: Solomon Crescent,stop_code: Wjr-AY4, lat: -35.2190044, lng: 149.0282415}
- { name: Onslow Street,stop_code: Wjr-IcO, lat: -35.2191858, lng: 149.0319716} - { name: Onslow Street,stop_code: Wjr-IcO, lat: -35.2191858, lng: 149.0319716}
- { name: Onslow Street,stop_code: Wjr-IqS, lat: -35.2202741, lng: 149.034858} - { name: Onslow Street,stop_code: Wjr-IqS, lat: -35.2202741, lng: 149.034858}
- { name: Kingsford Smith Drive,stop_code: Wjr-H-a, lat: -35.2232851, lng: 149.039343} - { name: Kingsford Smith Drive,stop_code: Wjr-H-a, lat: -35.2232851, lng: 149.039343}
- { name: Krefft Street,stop_code: Wjr-Q4G, lat: -35.2192221, lng: 149.0415189} - { name: Krefft Street,stop_code: Wjr-Q4G, lat: -35.2192221, lng: 149.0415189}
- { name: Maribyrnong Avenue,stop_code: Wjz6zon, lat: -35.2269858, lng: 149.1109391} - { name: Maribyrnong Avenue,stop_code: Wjz6zon, lat: -35.2269858, lng: 149.1109391}
- { name: Maribyrnong Avenue,stop_code: Wjz6ytu, lat: -35.2291622, lng: 149.1110812} - { name: Maribyrnong Avenue,stop_code: Wjz6ytu, lat: -35.2291622, lng: 149.1110812}
- { name: Belconnen Way,stop_code: Wjz5mpm, lat: -35.2538531, lng: 149.0889493} - { name: Belconnen Way,stop_code: Wjz5mpm, lat: -35.2538531, lng: 149.0889493}
- { name: Belconnen Way,stop_code: Wjz5mxf, lat: -35.2538241, lng: 149.0902637} - { name: Belconnen Way,stop_code: Wjz5mxf, lat: -35.2538241, lng: 149.0902637}
- { name: Belconnen Way,stop_code: Wjr-MNh, lat: -35.2433401, lng: 149.0492618} - { name: Belconnen Way,stop_code: Wjr-MNh, lat: -35.2433401, lng: 149.0492618}
- { name: Belconnen Way,stop_code: Wjr-Mqd, lat: -35.2422956, lng: 149.0448568} - { name: Belconnen Way,stop_code: Wjr-Mqd, lat: -35.2422956, lng: 149.0448568}
- { name: Belconnen Way,stop_code: Wjr-EYe, lat: -35.2408449, lng: 149.0394925} - { name: Belconnen Way,stop_code: Wjr-EYe, lat: -35.2408449, lng: 149.0394925}
- { name: Belconnen Way,stop_code: Wjr-EA_, lat: -35.2407288, lng: 149.0362953} - { name: Belconnen Way,stop_code: Wjr-EA_, lat: -35.2407288, lng: 149.0362953}
- { name: Mackinolty Street,stop_code: Wjr-Fw4, lat: -35.2382916, lng: 149.035194} - { name: Mackinolty Street,stop_code: Wjr-Fw4, lat: -35.2382916, lng: 149.035194}
- { name: Challinor Crescent,stop_code: Wjr-Vnf, lat: -35.2331848, lng: 149.054555} - { name: Challinor Crescent,stop_code: Wjr-Vnf, lat: -35.2331848, lng: 149.054555}
- { name: Lightfoot Crescent,stop_code: Wjr-Ws2, lat: -35.230167, lng: 149.0557628} - { name: Lightfoot Crescent,stop_code: Wjr-Ws2, lat: -35.230167, lng: 149.0557628}
- { name: Nanson Place,stop_code: Wjr-PyX, lat: -35.2259882, lng: 149.0472724} - { name: Nanson Place,stop_code: Wjr-PyX, lat: -35.2259882, lng: 149.0472724}
- { name: Kulgera Street,stop_code: WjrZKZn, lat: -35.2510294, lng: 149.0396391} - { name: Kulgera Street,stop_code: WjrZKZn, lat: -35.2510294, lng: 149.0396391}
- { name: King Edward Terrace,stop_code: Wjz4S1U, lat: -35.2983385, lng: 149.1296979} - { name: King Edward Terrace,stop_code: Wjz4S1U, lat: -35.2983385, lng: 149.1296979}
- { name: King George Terrace,stop_code: Wjz4RbQ, lat: -35.3021238, lng: 149.1308574} - { name: King George Terrace,stop_code: Wjz4RbQ, lat: -35.3021238, lng: 149.1308574}
- { name: James Street,stop_code: Wjz3fCx, lat: -35.333256, lng: 149.0798309} - { name: James Street,stop_code: Wjz3fCx, lat: -35.333256, lng: 149.0798309}
- { name: Kent Street,stop_code: Wjz4peM, lat: -35.322342, lng: 149.0979263} - { name: Kent Street,stop_code: Wjz4peM, lat: -35.322342, lng: 149.0979263}
- { name: Fuller Street,stop_code: Wjz4qgy, lat: -35.3208475, lng: 149.098981} - { name: Fuller Street,stop_code: Wjz4qgy, lat: -35.3208475, lng: 149.098981}
- { name: Hopetoun Circuit,stop_code: Wjz4A7o, lat: -35.3052441, lng: 149.107042} - { name: Hopetoun Circuit,stop_code: Wjz4A7o, lat: -35.3052441, lng: 149.107042}
- { name: De Chair Street,stop_code: Wjz4qTw, lat: -35.3162151, lng: 149.1045086} - { name: De Chair Street,stop_code: Wjz4qTw, lat: -35.3162151, lng: 149.1045086}
- { name: Macgregor Street,stop_code: Wjz4qs0, lat: -35.3182278, lng: 149.09964} - { name: Macgregor Street,stop_code: Wjz4qs0, lat: -35.3182278, lng: 149.09964}
- { name: Stonehaven Crescent,stop_code: Wjz4yzk, lat: -35.3186155, lng: 149.1123352} - { name: Stonehaven Crescent,stop_code: Wjz4yzk, lat: -35.3186155, lng: 149.1123352}
- { name: Dominion Circuit,stop_code: Wjz4H0P, lat: -35.3152936, lng: 149.1185178} - { name: Dominion Circuit,stop_code: Wjz4H0P, lat: -35.3152936, lng: 149.1185178}
- { name: Schlich Street,stop_code: Wjz4tpE, lat: -35.3038329, lng: 149.1005569} - { name: Schlich Street,stop_code: Wjz4tpE, lat: -35.3038329, lng: 149.1005569}
- { name: Weston Street,stop_code: Wjz4z67, lat: -35.3107704, lng: 149.1065979} - { name: Weston Street,stop_code: Wjz4z67, lat: -35.3107704, lng: 149.1065979}
- { name: Musgrave Street,stop_code: Wjz4tUp, lat: -35.3044055, lng: 149.1056974} - { name: Musgrave Street,stop_code: Wjz4tUp, lat: -35.3044055, lng: 149.1056974}
- { name: Hopetoun Circuit,stop_code: Wjz4A2c, lat: -35.3082791, lng: 149.1066534} - { name: Hopetoun Circuit,stop_code: Wjz4A2c, lat: -35.3082791, lng: 149.1066534}
- { name: Lienhop Street,stop_code: Wjz1HTi, lat: -35.4423392, lng: 149.1260397} - { name: Lienhop Street,stop_code: Wjz1HTi, lat: -35.4423392, lng: 149.1260397}
- { name: Hartung Crescent,stop_code: Wjz1zN3, lat: -35.4464057, lng: 149.1147796} - { name: Hartung Crescent,stop_code: Wjz1zN3, lat: -35.4464057, lng: 149.1147796}
- { name: Lawrence Wackett Crescent,stop_code: Wjz1HEb, lat: -35.4471149, lng: 149.1245306} - { name: Lawrence Wackett Crescent,stop_code: Wjz1HEb, lat: -35.4471149, lng: 149.1245306}
- { name: Callister Crescent,stop_code: Wjz1xWZ, lat: -35.4565002, lng: 149.1174205} - { name: Callister Crescent,stop_code: Wjz1xWZ, lat: -35.4565002, lng: 149.1174205}
- { name: Chippindall Circuit,stop_code: Wjz1Gjj, lat: -35.4504956, lng: 149.1205257} - { name: Chippindall Circuit,stop_code: Wjz1Gjj, lat: -35.4504956, lng: 149.1205257}
- { name: Fidge Street,stop_code: Wjz1rQ6, lat: -35.4440887, lng: 149.1038388} - { name: Fidge Street,stop_code: Wjz1rQ6, lat: -35.4440887, lng: 149.1038388}
- { name: Weavers Crescent,stop_code: Wjz1xRC, lat: -35.4544199, lng: 149.1154761} - { name: Weavers Crescent,stop_code: Wjz1xRC, lat: -35.4544199, lng: 149.1154761}
- { name: Kiddle Crescent,stop_code: Wjz1CdY, lat: -35.4270927, lng: 149.1090734} - { name: Kiddle Crescent,stop_code: Wjz1CdY, lat: -35.4270927, lng: 149.1090734}
- { name: Fairley Crescent,stop_code: Wjz1G89, lat: -35.4527651, lng: 149.1190457} - { name: Fairley Crescent,stop_code: Wjz1G89, lat: -35.4527651, lng: 149.1190457}
- { name: Fairley Crescent,stop_code: Wjz1F5W, lat: -35.4547272, lng: 149.1186974} - { name: Fairley Crescent,stop_code: Wjz1F5W, lat: -35.4547272, lng: 149.1186974}
- { name: Muscio Place,stop_code: Wjz2EdX, lat: -35.416214, lng: 149.120065} - { name: Muscio Place,stop_code: Wjz2EdX, lat: -35.416214, lng: 149.120065}
- { name: Clift Crescent,stop_code: Wjz1CRl, lat: -35.4269745, lng: 149.1151677} - { name: Clift Crescent,stop_code: Wjz1CRl, lat: -35.4269745, lng: 149.1151677}
- { name: Southern Close,stop_code: Wjz1K49, lat: -35.428009, lng: 149.1176708} - { name: Southern Close,stop_code: Wjz1K49, lat: -35.428009, lng: 149.1176708}
- { name: Clift Crescent,stop_code: Wjz1J4T, lat: -35.4330044, lng: 149.1185777} - { name: Clift Crescent,stop_code: Wjz1J4T, lat: -35.4330044, lng: 149.1185777}
- { name: Prichard Circuit,stop_code: Wjz1K89, lat: -35.4308171, lng: 149.1191218} - { name: Prichard Circuit,stop_code: Wjz1K89, lat: -35.4308171, lng: 149.1191218}
- { name: Twamley Crescent,stop_code: Wjz1JD7, lat: -35.4309354, lng: 149.1230759} - { name: Twamley Crescent,stop_code: Wjz1JD7, lat: -35.4309354, lng: 149.1230759}
- { name: Monaro Highway,stop_code: Wjz1JTP, lat: -35.4312901, lng: 149.126776} - { name: Monaro Highway,stop_code: Wjz1JTP, lat: -35.4312901, lng: 149.126776}
- { name: Deamer Crescent,stop_code: Wjz1S5I, lat: -35.4271223, lng: 149.1292791} - { name: Deamer Crescent,stop_code: Wjz1S5I, lat: -35.4271223, lng: 149.1292791}
- { name: Monaro Highway,stop_code: Wjz1SfM, lat: -35.4260286, lng: 149.1309478} - { name: Monaro Highway,stop_code: Wjz1SfM, lat: -35.4260286, lng: 149.1309478}
- { name: Henry Melville Crescent,stop_code: Wjz1TLL, lat: -35.4199685, lng: 149.1361715} - { name: Henry Melville Crescent,stop_code: Wjz1TLL, lat: -35.4199685, lng: 149.1361715}
- { name: Muntz Street,stop_code: Wjz1Lxu, lat: -35.4241367, lng: 149.1234749} - { name: Muntz Street,stop_code: Wjz1Lxu, lat: -35.4241367, lng: 149.1234749}
- { name: Mofflin Street,stop_code: Wjz1Liw, lat: -35.4239889, lng: 149.1208993} - { name: Mofflin Street,stop_code: Wjz1Liw, lat: -35.4239889, lng: 149.1208993}
- { name: Tuck Place,stop_code: Wjz1DLm, lat: -35.4200572, lng: 149.1136804} - { name: Tuck Place,stop_code: Wjz1DLm, lat: -35.4200572, lng: 149.1136804}
- { name: Proctor Street,stop_code: Wjz2M5R, lat: -35.4160071, lng: 149.129533} - { name: Proctor Street,stop_code: Wjz2M5R, lat: -35.4160071, lng: 149.129533}
- { name: Hynes Place,stop_code: Wjz2wY-, lat: -35.4166279, lng: 149.1173443} - { name: Hynes Place,stop_code: Wjz2wY-, lat: -35.4166279, lng: 149.1173443}
- { name: Sweet Place,stop_code: Wjz2EL2, lat: -35.4149132, lng: 149.1244544} - { name: Sweet Place,stop_code: Wjz2EL2, lat: -35.4149132, lng: 149.1244544}
- { name: Schoales Place,stop_code: WjrXZiM, lat: -35.3470777, lng: 149.0553331} - { name: Schoales Place,stop_code: WjrXZiM, lat: -35.3470777, lng: 149.0553331}
- { name: Logue Place,stop_code: WjrXRW0, lat: -35.3471147, lng: 149.0502999} - { name: Logue Place,stop_code: WjrXRW0, lat: -35.3471147, lng: 149.0502999}
- { name: Finlayson Place,stop_code: Wjz2NPZ, lat: -35.4118681, lng: 149.1378765} - { name: Finlayson Place,stop_code: Wjz2NPZ, lat: -35.4118681, lng: 149.1378765}
- { name: Namatjira Drive,stop_code: WjrXZz3, lat: -35.3461161, lng: 149.0570563} - { name: Namatjira Drive,stop_code: WjrXZz3, lat: -35.3461161, lng: 149.0570563}
- { name: Wark Street,stop_code: Wjz3nLq, lat: -35.3325054, lng: 149.0919265} - { name: Wark Street,stop_code: Wjz3nLq, lat: -35.3325054, lng: 149.0919265}
- { name: McCulloch Street,stop_code: Wjz49Y5, lat: -35.3233291, lng: 149.0831296} - { name: McCulloch Street,stop_code: Wjz49Y5, lat: -35.3233291, lng: 149.0831296}
- { name: Novar Street,stop_code: Wjz4shf, lat: -35.3086912, lng: 149.0984092} - { name: Novar Street,stop_code: Wjz4shf, lat: -35.3086912, lng: 149.0984092}
- { name: Novar Street,stop_code: Wjz4rk2, lat: -35.3126013, lng: 149.0982349} - { name: Novar Street,stop_code: Wjz4rk2, lat: -35.3126013, lng: 149.0982349}
- { name: Denison Street,stop_code: Wjz4hPC, lat: -35.323921, lng: 149.0935136} - { name: Denison Street,stop_code: Wjz4hPC, lat: -35.323921, lng: 149.0935136}
- { name: Jensen Street,stop_code: Wjz4gou, lat: -35.3314972, lng: 149.0892541} - { name: Jensen Street,stop_code: Wjz4gou, lat: -35.3314972, lng: 149.0892541}
- { name: Denison Street,stop_code: Wjz4hMe, lat: -35.3259558, lng: 149.0929241} - { name: Denison Street,stop_code: Wjz4hMe, lat: -35.3259558, lng: 149.0929241}
- { name: Yarra Glen,stop_code: Wjz4gt5, lat: -35.3281248, lng: 149.0887511} - { name: Yarra Glen,stop_code: Wjz4gt5, lat: -35.3281248, lng: 149.0887511}
- { name: Carruthers Street,stop_code: Wjz49Wd, lat: -35.324698, lng: 149.0833563} - { name: Carruthers Street,stop_code: Wjz49Wd, lat: -35.324698, lng: 149.0833563}
- { name: Shiels Place,stop_code: Wjz4arc, lat: -35.3185933, lng: 149.0779149} - { name: Shiels Place,stop_code: Wjz4arc, lat: -35.3185933, lng: 149.0779149}
- { name: Heysen Street,stop_code: WjrYUG8, lat: -35.3306155, lng: 149.058622} - { name: Heysen Street,stop_code: WjrYUG8, lat: -35.3306155, lng: 149.058622}
- { name: Dunstan Street,stop_code: Wjz4aH6, lat: -35.3184453, lng: 149.0804542} - { name: Dunstan Street,stop_code: Wjz4aH6, lat: -35.3184453, lng: 149.0804542}
- { name: Mair Place,stop_code: Wjz48dZ, lat: -35.3281016, lng: 149.0761465} - { name: Mair Place,stop_code: Wjz48dZ, lat: -35.3281016, lng: 149.0761465}
- { name: Jennings Street,stop_code: Wjz499S, lat: -35.3252899, lng: 149.0759651} - { name: Jennings Street,stop_code: Wjz499S, lat: -35.3252899, lng: 149.0759651}
- { name: O'Loghlen Street,stop_code: Wjr-IMR, lat: -35.2216889, lng: 149.0389433} - { name: O'Loghlen Street,stop_code: Wjr-IMR, lat: -35.2216889, lng: 149.0389433}
- { name: Carruthers Street,stop_code: Wjz48qI, lat: -35.3302472, lng: 149.0785498} - { name: Carruthers Street,stop_code: Wjz48qI, lat: -35.3302472, lng: 149.0785498}
- { name: Heysen Street,stop_code: WjrYUj0, lat: -35.3299526, lng: 149.0543559} - { name: Heysen Street,stop_code: WjrYUj0, lat: -35.3299526, lng: 149.0543559}
- { name: Heysen Street,stop_code: Wjz37Lm, lat: -35.3321544, lng: 149.0697369} - { name: Heysen Street,stop_code: Wjz37Lm, lat: -35.3321544, lng: 149.0697369}
- { name: Burnie Street,stop_code: Wjz3d3K, lat: -35.3459087, lng: 149.0743512} - { name: Burnie Street,stop_code: Wjz3d3K, lat: -35.3459087, lng: 149.0743512}
- { name: Derwent Street,stop_code: Wjz3ee-, lat: -35.3383098, lng: 149.0761505} - { name: Derwent Street,stop_code: Wjz3ee-, lat: -35.3383098, lng: 149.0761505}
- { name: Anne Place,stop_code: Wjz3fa8, lat: -35.3360845, lng: 149.0750477} - { name: Anne Place,stop_code: Wjz3fa8, lat: -35.3360845, lng: 149.0750477}
- { name: McInnes Street,stop_code: WjrX-Lw, lat: -35.3381915, lng: 149.0592024} - { name: McInnes Street,stop_code: WjrX-Lw, lat: -35.3381915, lng: 149.0592024}
- { name: Lycett Street,stop_code: WjrX_xY, lat: -35.3364869, lng: 149.0583028} - { name: Lycett Street,stop_code: WjrX_xY, lat: -35.3364869, lng: 149.0583028}
- { name: Meldrum Street,stop_code: WjrX_iU, lat: -35.3361318, lng: 149.0556038} - { name: Meldrum Street,stop_code: WjrX_iU, lat: -35.3361318, lng: 149.0556038}
- { name: Namatjira Drive,stop_code: WjrX-m2, lat: -35.3386886, lng: 149.0543559} - { name: Namatjira Drive,stop_code: WjrX-m2, lat: -35.3386886, lng: 149.0543559}
- { name: Mather Street,stop_code: WjrX-sE, lat: -35.3402511, lng: 149.0565615} - { name: Mather Street,stop_code: WjrX-sE, lat: -35.3402511, lng: 149.0565615}
- { name: Buvelot Street,stop_code: Wjz354b, lat: -35.345459, lng: 149.062772} - { name: Buvelot Street,stop_code: Wjz354b, lat: -35.345459, lng: 149.062772}
- { name: Gask Place,stop_code: Wjz1et6, lat: -35.4269117, lng: 149.0777759} - { name: Gask Place,stop_code: Wjz1et6, lat: -35.4269117, lng: 149.0777759}
- { name: Drumston Street,stop_code: Wjz1nxQ, lat: -35.4243695, lng: 149.0911255} - { name: Drumston Street,stop_code: Wjz1nxQ, lat: -35.4243695, lng: 149.0911255}
- { name: Athllon Drive,stop_code: Wjz1f8Y, lat: -35.4250198, lng: 149.076216} - { name: Athllon Drive,stop_code: Wjz1f8Y, lat: -35.4250198, lng: 149.076216}
- { name: Anketell Street,stop_code: Wjz1f2H, lat: -35.4237487, lng: 149.0744748} - { name: Anketell Street,stop_code: Wjz1f2H, lat: -35.4237487, lng: 149.0744748}
- { name: Lake Tuggeranong cycle track,stop_code: Wjz1f7q, lat: -35.4203787, lng: 149.0740032} - { name: Lake Tuggeranong cycle track,stop_code: Wjz1f7q, lat: -35.4203787, lng: 149.0740032}
- { name: Forlonge Street,stop_code: Wjz2bHS, lat: -35.400824, lng: 149.0814035} - { name: Forlonge Street,stop_code: Wjz2bHS, lat: -35.400824, lng: 149.0814035}
- { name: Derham Court,stop_code: Wjz2aLs, lat: -35.4037395, lng: 149.081019} - { name: Derham Court,stop_code: Wjz2aLs, lat: -35.4037395, lng: 149.081019}
- { name: Mortimer Lewis Drive,stop_code: Wjz2a26, lat: -35.4069683, lng: 149.0736259} - { name: Mortimer Lewis Drive,stop_code: Wjz2a26, lat: -35.4069683, lng: 149.0736259}
- { name: Nunan Crescent,stop_code: Wjz29Ya, lat: -35.4114741, lng: 149.0833189} - { name: Nunan Crescent,stop_code: Wjz29Ya, lat: -35.4114741, lng: 149.0833189}
- { name: William Webb Drive,stop_code: Wjz6e8G, lat: -35.2110071, lng: 149.0758577} - { name: William Webb Drive,stop_code: Wjz6e8G, lat: -35.2110071, lng: 149.0758577}
- { name: Evelyn Owen Crescent,stop_code: Wjr_w0L, lat: -35.1995769, lng: 149.0194714} - { name: Evelyn Owen Crescent,stop_code: Wjr_w0L, lat: -35.1995769, lng: 149.0194714}
- { name: Cusack Place,stop_code: Wjr_Ow3, lat: -35.1889085, lng: 149.0461463} - { name: Cusack Place,stop_code: Wjr_Ow3, lat: -35.1889085, lng: 149.0461463}
- { name: Binns Street,stop_code: Wjr_GGq, lat: -35.1875953, lng: 149.0370811} - { name: Binns Street,stop_code: Wjr_GGq, lat: -35.1875953, lng: 149.0370811}
- { name: Clubbe Crescent,stop_code: Wjr-uUb, lat: -35.2108896, lng: 149.0174054} - { name: Clubbe Crescent,stop_code: Wjr-uUb, lat: -35.2108896, lng: 149.0174054}
- { name: Southern Cross Drive,stop_code: Wjr-s5D, lat: -35.2180783, lng: 149.0083939} - { name: Southern Cross Drive,stop_code: Wjr-s5D, lat: -35.2180783, lng: 149.0083939}
- { name: Higgins Place,stop_code: Wjr-yOB, lat: -35.2313222, lng: 149.0276235} - { name: Higgins Place,stop_code: Wjr-yOB, lat: -35.2313222, lng: 149.0276235}
- { name: Southern Cross Drive,stop_code: Wjr-Hoi, lat: -35.2274077, lng: 149.0341216} - { name: Southern Cross Drive,stop_code: Wjr-Hoi, lat: -35.2274077, lng: 149.0341216}
- { name: Wollongong Street,stop_code: WjzcgD0, lat: -35.3271927, lng: 149.1779495} - { name: Wollongong Street,stop_code: WjzcgD0, lat: -35.3271927, lng: 149.1779495}
- { name: Taubman Street,stop_code: Wjzbfpl, lat: -35.3363832, lng: 149.1658515} - { name: Taubman Street,stop_code: Wjzbfpl, lat: -35.3363832, lng: 149.1658515}
- { name: Wiluna Street,stop_code: Wjzc8l0, lat: -35.3285713, lng: 149.1642018} - { name: Wiluna Street,stop_code: Wjzc8l0, lat: -35.3285713, lng: 149.1642018}
- { name: Whyalla Street,stop_code: Wjzbnmb, lat: -35.3331064, lng: 149.1753196} - { name: Whyalla Street,stop_code: Wjzbnmb, lat: -35.3331064, lng: 149.1753196}
- { name: Allen Street,stop_code: Wjz3_3L, lat: -35.3347817, lng: 149.1404124} - { name: Allen Street,stop_code: Wjz3_3L, lat: -35.3347817, lng: 149.1404124}
- { name: Goyder Street,stop_code: Wjz3-aW, lat: -35.3414521, lng: 149.1420263} - { name: Goyder Street,stop_code: Wjz3-aW, lat: -35.3414521, lng: 149.1420263}
- { name: Alfred Place,stop_code: Wjza_-f, lat: -35.3767042, lng: 149.237157} - { name: Alfred Place,stop_code: Wjza_-f, lat: -35.3767042, lng: 149.237157}
- { name: Farrer Place,stop_code: WjzbXms, lat: -35.3550134, lng: 149.2306199} - { name: Farrer Place,stop_code: WjzbXms, lat: -35.3550134, lng: 149.2306199}
- { name: Bazley Street,stop_code: Wjr_Vbj, lat: -35.1923583, lng: 149.0533723} - { name: Bazley Street,stop_code: Wjr_Vbj, lat: -35.1923583, lng: 149.0533723}
- { name: Tuggeranong Parkway,stop_code: Wjz33GY, lat: -35.3577485, lng: 149.0706526} - { name: Tuggeranong Parkway,stop_code: Wjz33GY, lat: -35.3577485, lng: 149.0706526}
- { name: Kalgoorlie Crescent,stop_code: WjrXXFn, lat: -35.3581997, lng: 149.0587995} - { name: Kalgoorlie Crescent,stop_code: WjrXXFn, lat: -35.3581997, lng: 149.0587995}
- { name: Jarrahdale Street,stop_code: WjrXWQ8, lat: -35.3621767, lng: 149.0600261} - { name: Jarrahdale Street,stop_code: WjrXWQ8, lat: -35.3621767, lng: 149.0600261}
- { name: Kapunda Street,stop_code: WjrXW7A, lat: -35.3597972, lng: 149.0523061} - { name: Kapunda Street,stop_code: WjrXW7A, lat: -35.3597972, lng: 149.0523061}
- { name: Nannine Place,stop_code: WjrXXq3, lat: -35.3578077, lng: 149.0557251} - { name: Nannine Place,stop_code: WjrXXq3, lat: -35.3578077, lng: 149.0557251}
- { name: Greenvale Street,stop_code: WjrXXd0, lat: -35.3559956, lng: 149.0529772} - { name: Greenvale Street,stop_code: WjrXXd0, lat: -35.3559956, lng: 149.0529772}
- { name: Hindmarsh Drive,stop_code: Wjz35av, lat: -35.3464684, lng: 149.064395} - { name: Hindmarsh Drive,stop_code: Wjz35av, lat: -35.3464684, lng: 149.064395}
- { name: Bangalay Crescent,stop_code: WjrXIDX, lat: -35.348916, lng: 149.0363428} - { name: Bangalay Crescent,stop_code: WjrXIDX, lat: -35.348916, lng: 149.0363428}
- { name: Buvelot Street,stop_code: WjrX-FV, lat: -35.3422149, lng: 149.0596338} - { name: Buvelot Street,stop_code: WjrX-FV, lat: -35.3422149, lng: 149.0596338}
- { name: Chevalier Street,stop_code: Wjz356k, lat: -35.3440169, lng: 149.0629513} - { name: Chevalier Street,stop_code: Wjz356k, lat: -35.3440169, lng: 149.0629513}
- { name: Larakia Street,stop_code: Wjz358l, lat: -35.3480588, lng: 149.0643043} - { name: Larakia Street,stop_code: Wjz358l, lat: -35.3480588, lng: 149.0643043}
- { name: Tiwi Place,stop_code: Wjz348u, lat: -35.3534586, lng: 149.0644857} - { name: Tiwi Place,stop_code: Wjz348u, lat: -35.3534586, lng: 149.0644857}
- { name: Bidia Place,stop_code: Wjz337w, lat: -35.354642, lng: 149.0633068} - { name: Bidia Place,stop_code: Wjz337w, lat: -35.354642, lng: 149.0633068}
- { name: Dalabon Crescent,stop_code: WjrXXK9, lat: -35.355219, lng: 149.0585637} - { name: Dalabon Crescent,stop_code: WjrXXK9, lat: -35.355219, lng: 149.0585637}
- { name: Kalgoorlie Crescent,stop_code: WjrXXyQ, lat: -35.3576967, lng: 149.0580467} - { name: Kalgoorlie Crescent,stop_code: WjrXXyQ, lat: -35.3576967, lng: 149.0580467}
- { name: Tristania Street,stop_code: WjrXRks, lat: -35.3453958, lng: 149.0438991} - { name: Tristania Street,stop_code: WjrXRks, lat: -35.3453958, lng: 149.0438991}
- { name: Damala Street,stop_code: WjrXYL4, lat: -35.3488355, lng: 149.0584095} - { name: Damala Street,stop_code: WjrXYL4, lat: -35.3488355, lng: 149.0584095}
- { name: Somerset Street,stop_code: WjrXLaD, lat: -35.3355436, lng: 149.0316183} - { name: Somerset Street,stop_code: WjrXLaD, lat: -35.3355436, lng: 149.0316183}
- { name: Frayne Place,stop_code: WjrXQZX, lat: -35.3502779, lng: 149.0514717} - { name: Frayne Place,stop_code: WjrXQZX, lat: -35.3502779, lng: 149.0514717}
- { name: Dixon Drive,stop_code: WjrXTgl, lat: -35.3370298, lng: 149.0436997} - { name: Dixon Drive,stop_code: WjrXTgl, lat: -35.3370298, lng: 149.0436997}
- { name: Hyndes Crescent,stop_code: WjrXTqY, lat: -35.3357893, lng: 149.0460156} - { name: Hyndes Crescent,stop_code: WjrXTqY, lat: -35.3357893, lng: 149.0460156}
- { name: Nelumbo Street,stop_code: WjrXQ65, lat: -35.349419, lng: 149.040696} - { name: Nelumbo Street,stop_code: WjrXQ65, lat: -35.349419, lng: 149.040696}
- { name: Hindmarsh Drive,stop_code: WjrXKoe, lat: -35.3424911, lng: 149.0339533} - { name: Hindmarsh Drive,stop_code: WjrXKoe, lat: -35.3424911, lng: 149.0339533}
- { name: Burrinjuck Crescent,stop_code: WjrXLR-, lat: -35.3335487, lng: 149.0390846} - { name: Burrinjuck Crescent,stop_code: WjrXLR-, lat: -35.3335487, lng: 149.0390846}
- { name: Warragamba Avenue,stop_code: WjrYEpn, lat: -35.3306598, lng: 149.0341649} - { name: Warragamba Avenue,stop_code: WjrYEpn, lat: -35.3306598, lng: 149.0341649}
- { name: Tantangara Street,stop_code: WjrXKBE, lat: -35.3395611, lng: 149.0360582} - { name: Tantangara Street,stop_code: WjrXKBE, lat: -35.3395611, lng: 149.0360582}
- { name: Counsel Street,stop_code: WjrYMbF, lat: -35.3298385, lng: 149.0428712} - { name: Counsel Street,stop_code: WjrYMbF, lat: -35.3298385, lng: 149.0428712}
- { name: Hyndes Crescent,stop_code: WjrYMrj, lat: -35.3296313, lng: 149.0450622} - { name: Hyndes Crescent,stop_code: WjrYMrj, lat: -35.3296313, lng: 149.0450622}
- { name: Mulley Street,stop_code: WjrYMHm, lat: -35.3294538, lng: 149.0477466} - { name: Mulley Street,stop_code: WjrYMHm, lat: -35.3294538, lng: 149.0477466}
- { name: Mulley Street,stop_code: WjrYMGB, lat: -35.3301626, lng: 149.0481758} - { name: Mulley Street,stop_code: WjrYMGB, lat: -35.3301626, lng: 149.0481758}
- { name: Dixon Drive,stop_code: WjrXTSe, lat: -35.3328347, lng: 149.0489873} - { name: Dixon Drive,stop_code: WjrXTSe, lat: -35.3328347, lng: 149.0489873}
- { name: Calder Crescent,stop_code: WjrXTIp, lat: -35.3346742, lng: 149.0480789} - { name: Calder Crescent,stop_code: WjrXTIp, lat: -35.3346742, lng: 149.0480789}
- { name: Woodger Place,stop_code: Wjr_V2c, lat: -35.192985, lng: 149.0517177} - { name: Woodger Place,stop_code: Wjr_V2c, lat: -35.192985, lng: 149.0517177}
- { name: Watt Place,stop_code: Wjz2ve3, lat: -35.3770117, lng: 149.0968721} - { name: Watt Place,stop_code: Wjz2ve3, lat: -35.3770117, lng: 149.0968721}
- { name: Pearce Avenue,stop_code: WjzcBHZ, lat: -35.3020154, lng: 149.2024041} - { name: Pearce Avenue,stop_code: WjzcBHZ, lat: -35.3020154, lng: 149.2024041}
- { name: Duffy Place,stop_code: WjrXLgs, lat: -35.3371612, lng: 149.0328459} - { name: Duffy Place,stop_code: WjrXLgs, lat: -35.3371612, lng: 149.0328459}
- { name: Renmark Street,stop_code: WjrXKfG, lat: -35.338018, lng: 149.0318393} - { name: Renmark Street,stop_code: WjrXKfG, lat: -35.338018, lng: 149.0318393}
- { name: Anstey Street,stop_code: Wjz3aaB, lat: -35.3631322, lng: 149.0756066} - { name: Anstey Street,stop_code: Wjz3aaB, lat: -35.3631322, lng: 149.0756066}
- { name: Lhotsky Street,stop_code: Wjr-L1H, lat: -35.2046871, lng: 149.0304447} - { name: Lhotsky Street,stop_code: Wjr-L1H, lat: -35.2046871, lng: 149.0304447}
- { name: McCay Place,stop_code: Wjz39PE, lat: -35.3683683, lng: 149.0827167} - { name: McCay Place,stop_code: Wjz39PE, lat: -35.3683683, lng: 149.0827167}
- { name: Hodgson Crescent,stop_code: Wjz3h5c, lat: -35.3666525, lng: 149.0847118} - { name: Hodgson Crescent,stop_code: Wjz3h5c, lat: -35.3666525, lng: 149.0847118}
- { name: Collings Street,stop_code: Wjz3j2F, lat: -35.3580142, lng: 149.0853648} - { name: Collings Street,stop_code: Wjz3j2F, lat: -35.3580142, lng: 149.0853648}
- { name: Marr Street,stop_code: Wjz3it1, lat: -35.3614164, lng: 149.0886297} - { name: Marr Street,stop_code: Wjz3it1, lat: -35.3614164, lng: 149.0886297}
- { name: Pialligo Avenue,stop_code: Wjzcrp_, lat: -35.3142011, lng: 149.1887666} - { name: Pialligo Avenue,stop_code: Wjzcrp_, lat: -35.3142011, lng: 149.1887666}
- { name: Brindabella Circuit,stop_code: WjzcrK3, lat: -35.3111478, lng: 149.190364} - { name: Brindabella Circuit,stop_code: WjzcrK3, lat: -35.3111478, lng: 149.190364}
- { name: Dakota Drive,stop_code: Wjzcuw1, lat: -35.2989793, lng: 149.188937} - { name: Dakota Drive,stop_code: Wjzcuw1, lat: -35.2989793, lng: 149.188937}
- { name: Fairbairn Avenue,stop_code: WjzcJ0K, lat: -35.3040486, lng: 149.2062653} - { name: Fairbairn Avenue,stop_code: WjzcJ0K, lat: -35.3040486, lng: 149.2062653}
- { name: Anthony Rolfe Avenue,stop_code: Wjzf3oM, lat: -35.1836894, lng: 149.1556666} - { name: Anthony Rolfe Avenue,stop_code: Wjzf3oM, lat: -35.1836894, lng: 149.1556666}
- { name: Binns Street,stop_code: Wjr_Gxf, lat: -35.1878657, lng: 149.0352296} - { name: Binns Street,stop_code: Wjr_Gxf, lat: -35.1878657, lng: 149.0352296}
- { name: Lhotsky Street,stop_code: Wjr_Es4, lat: -35.1970405, lng: 149.0338265} - { name: Lhotsky Street,stop_code: Wjr_Es4, lat: -35.1970405, lng: 149.0338265}
- { name: Rogers Street,stop_code: Wjr_FTN, lat: -35.1897508, lng: 149.038952} - { name: Rogers Street,stop_code: Wjr_FTN, lat: -35.1897508, lng: 149.038952}
- { name: Kerrigan Street,stop_code: Wjr_xLL, lat: -35.1892698, lng: 149.0264062} - { name: Kerrigan Street,stop_code: Wjr_xLL, lat: -35.1892698, lng: 149.0264062}
- { name: Bandt Place,stop_code: Wjr_xnT, lat: -35.1892671, lng: 149.0223682} - { name: Bandt Place,stop_code: Wjr_xnT, lat: -35.1892671, lng: 149.0223682}
- { name: Filshie Close,stop_code: Wjr_FXR, lat: -35.1922038, lng: 149.0402464} - { name: Filshie Close,stop_code: Wjr_FXR, lat: -35.1922038, lng: 149.0402464}
- { name: Donnison Place,stop_code: Wjr_E1y, lat: -35.1992571, lng: 149.0303603} - { name: Donnison Place,stop_code: Wjr_E1y, lat: -35.1992571, lng: 149.0303603}
- { name: Edlington Street,stop_code: Wjr_NDY, lat: -35.1895167, lng: 149.04724} - { name: Edlington Street,stop_code: Wjr_NDY, lat: -35.1895167, lng: 149.04724}
- { name: Nish Place,stop_code: Wjr_Vt9, lat: -35.191134, lng: 149.055871} - { name: Nish Place,stop_code: Wjr_Vt9, lat: -35.191134, lng: 149.055871}
- { name: Shrivell Circuit,stop_code: Wjr_wm3, lat: -35.195762, lng: 149.0214528} - { name: Shrivell Circuit,stop_code: Wjr_wm3, lat: -35.195762, lng: 149.0214528}
- { name: O'Reilly Street,stop_code: Wjr-thp, lat: -35.2158247, lng: 149.0109263} - { name: O'Reilly Street,stop_code: Wjr-thp, lat: -35.2158247, lng: 149.0109263}
- { name: Eddison Place,stop_code: Wjr_NFt, lat: -35.1935465, lng: 149.0479464} - { name: Eddison Place,stop_code: Wjr_NFt, lat: -35.1935465, lng: 149.0479464}
- { name: Garrad Court,stop_code: Wjr_MjV, lat: -35.1979805, lng: 149.0445264} - { name: Garrad Court,stop_code: Wjr_MjV, lat: -35.1979805, lng: 149.0445264}
- { name: Covington Crescent,stop_code: Wjr-Tf_, lat: -35.2002734, lng: 149.0432168} - { name: Covington Crescent,stop_code: Wjr-Tf_, lat: -35.2002734, lng: 149.0432168}
- { name: Moyes Crescent,stop_code: Wjr-zOn, lat: -35.2256125, lng: 149.0272189} - { name: Moyes Crescent,stop_code: Wjr-zOn, lat: -35.2256125, lng: 149.0272189}
- { name: Noakes Court,stop_code: Wjr-Lzm, lat: -35.2030997, lng: 149.0354829} - { name: Noakes Court,stop_code: Wjr-Lzm, lat: -35.2030997, lng: 149.0354829}
- { name: Hirschfeld Crescent,stop_code: Wjr-tbm, lat: -35.2140927, lng: 149.0093105} - { name: Hirschfeld Crescent,stop_code: Wjr-tbm, lat: -35.2140927, lng: 149.0093105}
- { name: Florey Drive,stop_code: Wjr-DNK, lat: -35.2044788, lng: 149.0277602} - { name: Florey Drive,stop_code: Wjr-DNK, lat: -35.2044788, lng: 149.0277602}
- { name: Lhotsky Street,stop_code: Wjr-DQE, lat: -35.2029293, lng: 149.0277662} - { name: Lhotsky Street,stop_code: Wjr-DQE, lat: -35.2029293, lng: 149.0277662}
- { name: Ginninderra Drive,stop_code: Wjr_oP1, lat: -35.1980445, lng: 149.0158736} - { name: Ginninderra Drive,stop_code: Wjr_oP1, lat: -35.1980445, lng: 149.0158736}
- { name: Krefft Street,stop_code: Wjr-Pk6, lat: -35.2243699, lng: 149.0432872} - { name: Krefft Street,stop_code: Wjr-Pk6, lat: -35.2243699, lng: 149.0432872}
- { name: Kerrigan Street,stop_code: Wjr_o_j, lat: -35.1950629, lng: 149.0175978} - { name: Kerrigan Street,stop_code: Wjr_o_j, lat: -35.1950629, lng: 149.0175978}
- { name: Lance Hill Avenue,stop_code: Wjr_wjn, lat: -35.1975263, lng: 149.0216638} - { name: Lance Hill Avenue,stop_code: Wjr_wjn, lat: -35.1975263, lng: 149.0216638}
- { name: Florey Drive,stop_code: Wjr-CS2, lat: -35.2068071, lng: 149.0268212} - { name: Florey Drive,stop_code: Wjr-CS2, lat: -35.2068071, lng: 149.0268212}
- { name: Kerrigan Street,stop_code: Wjr_oJA, lat: -35.1964177, lng: 149.0152805} - { name: Kerrigan Street,stop_code: Wjr_oJA, lat: -35.1964177, lng: 149.0152805}
- { name: Rossell Place,stop_code: Wjr-KJQ, lat: -35.2073355, lng: 149.037506} - { name: Rossell Place,stop_code: Wjr-KJQ, lat: -35.2073355, lng: 149.037506}
- { name: O'Reilly Street,stop_code: Wjr-smi, lat: -35.2178617, lng: 149.0106876} - { name: O'Reilly Street,stop_code: Wjr-smi, lat: -35.2178617, lng: 149.0106876}
- { name: Archdall Street,stop_code: Wjr-vJY, lat: -35.2019113, lng: 149.0157184} - { name: Archdall Street,stop_code: Wjr-vJY, lat: -35.2019113, lng: 149.0157184}
- { name: Cumpston Place,stop_code: Wjr-BL8, lat: -35.2118565, lng: 149.025622} - { name: Cumpston Place,stop_code: Wjr-BL8, lat: -35.2118565, lng: 149.025622}
- { name: Nulsen Circuit,stop_code: Wjr-S6B, lat: -35.2066123, lng: 149.0412991} - { name: Nulsen Circuit,stop_code: Wjr-S6B, lat: -35.2066123, lng: 149.0412991}
- { name: Tulloch Place,stop_code: Wjr-RnT, lat: -35.2112095, lng: 149.0444601} - { name: Tulloch Place,stop_code: Wjr-RnT, lat: -35.2112095, lng: 149.0444601}
- { name: Grigson Place,stop_code: Wjr-s_F, lat: -35.2172009, lng: 149.0180976} - { name: Grigson Place,stop_code: Wjr-s_F, lat: -35.2172009, lng: 149.0180976}
- { name: Hampton Gardens,stop_code: Wjr-rQJ, lat: -35.2244007, lng: 149.0167658} - { name: Hampton Gardens,stop_code: Wjr-rQJ, lat: -35.2244007, lng: 149.0167658}
- { name: Rentoul Place,stop_code: Wjr-Rs8, lat: -35.2139046, lng: 149.0449606} - { name: Rentoul Place,stop_code: Wjr-Rs8, lat: -35.2139046, lng: 149.0449606}
- { name: Krefft Street,stop_code: Wjr-Q8c, lat: -35.2217975, lng: 149.042121} - { name: Krefft Street,stop_code: Wjr-Q8c, lat: -35.2217975, lng: 149.042121}
- { name: Dalley Crescent,stop_code: Wjr-AHx, lat: -35.2199899, lng: 149.0262529} - { name: Dalley Crescent,stop_code: Wjr-AHx, lat: -35.2199899, lng: 149.0262529}
- { name: Southern Cross Drive,stop_code: Wjr-z_L, lat: -35.222191, lng: 149.0291286} - { name: Southern Cross Drive,stop_code: Wjr-z_L, lat: -35.222191, lng: 149.0291286}
- { name: Starke Street,stop_code: Wjr-sV3, lat: -35.2212162, lng: 149.0172455} - { name: Starke Street,stop_code: Wjr-sV3, lat: -35.2212162, lng: 149.0172455}
- { name: Drake Brockman Drive,stop_code: Wjr-qcc, lat: -35.230013, lng: 149.0092125} - { name: Drake Brockman Drive,stop_code: Wjr-qcc, lat: -35.230013, lng: 149.0092125}
- { name: Southern Cross Drive,stop_code: Wjr-st9, lat: -35.2186471, lng: 149.0119654} - { name: Southern Cross Drive,stop_code: Wjr-st9, lat: -35.2186471, lng: 149.0119654}
- { name: Messenger Street,stop_code: Wjr-jRn, lat: -35.2235756, lng: 149.0053113} - { name: Messenger Street,stop_code: Wjr-jRn, lat: -35.2235756, lng: 149.0053113}
- { name: Armstrong Crescent,stop_code: Wjr-syd, lat: -35.2203046, lng: 149.0133355} - { name: Armstrong Crescent,stop_code: Wjr-syd, lat: -35.2203046, lng: 149.0133355}
- { name: Holt Place,stop_code: Wjr-rjD, lat: -35.2249706, lng: 149.0111289} - { name: Holt Place,stop_code: Wjr-rjD, lat: -35.2249706, lng: 149.0111289}
- { name: Drake Brockman Drive,stop_code: Wjr-qyr, lat: -35.2315106, lng: 149.0137011} - { name: Drake Brockman Drive,stop_code: Wjr-qyr, lat: -35.2315106, lng: 149.0137011}
- { name: Ashburner Street,stop_code: Wjr-yrh, lat: -35.2309899, lng: 149.0230231} - { name: Ashburner Street,stop_code: Wjr-yrh, lat: -35.2309899, lng: 149.0230231}
- { name: Grout Place,stop_code: Wjr-jNB, lat: -35.2265208, lng: 149.0056756} - { name: Grout Place,stop_code: Wjr-jNB, lat: -35.2265208, lng: 149.0056756}
- { name: Starke Street,stop_code: Wjr-yni, lat: -35.2281496, lng: 149.0217011} - { name: Starke Street,stop_code: Wjr-yni, lat: -35.2281496, lng: 149.0217011}
- { name: Hardwick Crescent,stop_code: Wjr-zom, lat: -35.2270626, lng: 149.0231771} - { name: Hardwick Crescent,stop_code: Wjr-zom, lat: -35.2270626, lng: 149.0231771}
- { name: Davidson Street,stop_code: Wjr-ywh, lat: -35.2330631, lng: 149.0245222} - { name: Davidson Street,stop_code: Wjr-ywh, lat: -35.2330631, lng: 149.0245222}
- { name: Kriewaldt Circuit,stop_code: Wjr-yJZ, lat: -35.2292857, lng: 149.0266955} - { name: Kriewaldt Circuit,stop_code: Wjr-yJZ, lat: -35.2292857, lng: 149.0266955}
- { name: Kriewaldt Circuit,stop_code: Wjr-ySy, lat: -35.228821, lng: 149.0276438} - { name: Kriewaldt Circuit,stop_code: Wjr-ySy, lat: -35.228821, lng: 149.0276438}
- { name: Starke Street,stop_code: Wjr-zWb, lat: -35.2259772, lng: 149.0283569} - { name: Starke Street,stop_code: Wjr-zWb, lat: -35.2259772, lng: 149.0283569}
- { name: Chave Street,stop_code: Wjr-zC9, lat: -35.2234474, lng: 149.0242983} - { name: Chave Street,stop_code: Wjr-zC9, lat: -35.2234474, lng: 149.0242983}
- { name: Dethridge Street,stop_code: Wjr-GeX, lat: -35.2287693, lng: 149.0321955} - { name: Dethridge Street,stop_code: Wjr-GeX, lat: -35.2287693, lng: 149.0321955}
- { name: Davidson Street,stop_code: Wjr-xLK, lat: -35.2332476, lng: 149.0263679} - { name: Davidson Street,stop_code: Wjr-xLK, lat: -35.2332476, lng: 149.0263679}
- { name: Drake Brockman Drive,stop_code: Wjr-xxu, lat: -35.2373929, lng: 149.0246092} - { name: Drake Brockman Drive,stop_code: Wjr-xxu, lat: -35.2373929, lng: 149.0246092}
- { name: Tanumbirini Street,stop_code: Wjr-Ekp, lat: -35.2412759, lng: 149.032879} - { name: Tanumbirini Street,stop_code: Wjr-Ekp, lat: -35.2412759, lng: 149.032879}
- { name: Crawford Street,stop_code: WjzbYue, lat: -35.3493054, lng: 149.2316145} - { name: Crawford Street,stop_code: WjzbYue, lat: -35.3493054, lng: 149.2316145}
- { name: Antill Street,stop_code: WjzbYD0, lat: -35.3491814, lng: 149.232803} - { name: Antill Street,stop_code: WjzbYD0, lat: -35.3491814, lng: 149.232803}
- { name: Alinga Street,stop_code: Wjz5FSY, lat: -35.2780524, lng: 149.1269928} - { name: Alinga Street,stop_code: Wjz5FSY, lat: -35.2780524, lng: 149.1269928}
- { name: Uriarra Road,stop_code: WjzbRdA, lat: -35.3446934, lng: 149.2184308} - { name: Uriarra Road,stop_code: WjzbRdA, lat: -35.3446934, lng: 149.2184308}
- { name: Pound Street,stop_code: Wjzj5cC, lat: -35.3451754, lng: 149.2404108} - { name: Pound Street,stop_code: Wjzj5cC, lat: -35.3451754, lng: 149.2404108}
- { name: Uriarra Road,stop_code: WjzbRBx, lat: -35.3449879, lng: 149.2226535} - { name: Uriarra Road,stop_code: WjzbRBx, lat: -35.3449879, lng: 149.2226535}
- { name: Alinga Street,stop_code: Wjz5Neo, lat: -35.27843, lng: 149.130345} - { name: Alinga Street,stop_code: Wjz5Neo, lat: -35.27843, lng: 149.130345}
- { name: Redwood Avenue,stop_code: WjzaJ9a, lat: -35.391582, lng: 149.2069701} - { name: Redwood Avenue,stop_code: WjzaJ9a, lat: -35.391582, lng: 149.2069701}
- { name: Canberra Avenue,stop_code: WjzbPQW, lat: -35.3565184, lng: 149.2259167} - { name: Canberra Avenue,stop_code: WjzbPQW, lat: -35.3565184, lng: 149.2259167}
- { name: Kenneth Place,stop_code: WjzbVBj, lat: -35.3667378, lng: 149.233235} - { name: Kenneth Place,stop_code: WjzbVBj, lat: -35.3667378, lng: 149.233235}
- { name: Cooma Street,stop_code: WjzbVCw, lat: -35.3663608, lng: 149.2335824} - { name: Cooma Street,stop_code: WjzbVCw, lat: -35.3663608, lng: 149.2335824}
- { name: Gibbs Place,stop_code: Wjz9JIL, lat: -35.4330525, lng: 149.2131844} - { name: Gibbs Place,stop_code: Wjz9JIL, lat: -35.4330525, lng: 149.2131844}
- { name: Parkview Crescent,stop_code: WjzaK0g, lat: -35.3868815, lng: 149.2056751} - { name: Parkview Crescent,stop_code: WjzaK0g, lat: -35.3868815, lng: 149.2056751}
- { name: Dixon Place,stop_code: WjzaDIK, lat: -35.3781802, lng: 149.2021825} - { name: Dixon Place,stop_code: WjzaDIK, lat: -35.3781802, lng: 149.2021825}
- { name: Rutledge Street,stop_code: WjzbXBT, lat: -35.3553953, lng: 149.2338714} - { name: Rutledge Street,stop_code: WjzbXBT, lat: -35.3553953, lng: 149.2338714}
- { name: Brindabella Circuit,stop_code: WjzcrrQ, lat: -35.3131274, lng: 149.188611} - { name: Brindabella Circuit,stop_code: WjzcrrQ, lat: -35.3131274, lng: 149.188611}
- { name: Benjamin Way,stop_code: Wjz57tg, lat: -35.2461188, lng: 149.0669661} - { name: Benjamin Way,stop_code: Wjz57tg, lat: -35.2461188, lng: 149.0669661}
- { name: Greene Place,stop_code: Wjz57T_, lat: -35.2441569, lng: 149.0719751} - { name: Greene Place,stop_code: Wjz57T_, lat: -35.2441569, lng: 149.0719751}
- { name: Gatehouse Place,stop_code: Wjz5f2j, lat: -35.2479775, lng: 149.0739202} - { name: Gatehouse Place,stop_code: Wjz5f2j, lat: -35.2479775, lng: 149.0739202}
- { name: Crisp Circuit,stop_code: Wjz688N, lat: -35.2439868, lng: 149.0759082} - { name: Crisp Circuit,stop_code: Wjz688N, lat: -35.2439868, lng: 149.0759082}
- { name: Cobbett Place,stop_code: Wjz68g-, lat: -35.2436119, lng: 149.0775571} - { name: Cobbett Place,stop_code: Wjz68g-, lat: -35.2436119, lng: 149.0775571}
- { name: Braybrooke Street,stop_code: Wjz5vjd, lat: -35.2470998, lng: 149.0983861} - { name: Braybrooke Street,stop_code: Wjz5vjd, lat: -35.2470998, lng: 149.0983861}
- { name: Watkin Street,stop_code: Wjz5v68, lat: -35.2454993, lng: 149.0956677} - { name: Watkin Street,stop_code: Wjz5v68, lat: -35.2454993, lng: 149.0956677}
- { name: Dunlop Court,stop_code: Wjz6gQ0, lat: -35.2413491, lng: 149.0928379} - { name: Dunlop Court,stop_code: Wjz6gQ0, lat: -35.2413491, lng: 149.0928379}
- { name: Eardley Street,stop_code: Wjz6gJc, lat: -35.2402968, lng: 149.0916132} - { name: Eardley Street,stop_code: Wjz6gJc, lat: -35.2402968, lng: 149.0916132}
- { name: Leverrier Crescent,stop_code: Wjz6oEz, lat: -35.243821, lng: 149.1030282} - { name: Leverrier Crescent,stop_code: Wjz6oEz, lat: -35.243821, lng: 149.1030282}
- { name: Krantzcke Circuit,stop_code: Wjz7pfP, lat: -35.189616, lng: 149.0978803} - { name: Krantzcke Circuit,stop_code: Wjz7pfP, lat: -35.189616, lng: 149.0978803}
- { name: Temperley Street,stop_code: Wjz7p2n, lat: -35.1926501, lng: 149.0958323} - { name: Temperley Street,stop_code: Wjz7p2n, lat: -35.1926501, lng: 149.0958323}
- { name: Temperley Street,stop_code: Wjz7iV0, lat: -35.1885169, lng: 149.0941253} - { name: Temperley Street,stop_code: Wjz7iV0, lat: -35.1885169, lng: 149.0941253}
- { name: Temperley Street,stop_code: Wjz7iG_, lat: -35.1872252, lng: 149.0926713} - { name: Temperley Street,stop_code: Wjz7iG_, lat: -35.1872252, lng: 149.0926713}
- { name: Curran Drive,stop_code: Wjz7ilp, lat: -35.1856235, lng: 149.0877402} - { name: Curran Drive,stop_code: Wjz7ilp, lat: -35.1856235, lng: 149.0877402}
- { name: Ayers Fowler Street,stop_code: Wjz7i7r, lat: -35.1841251, lng: 149.0850218} - { name: Ayers Fowler Street,stop_code: Wjz7i7r, lat: -35.1841251, lng: 149.0850218}
- { name: McClelland Avenue,stop_code: Wjz7jsi, lat: -35.1807665, lng: 149.0890046} - { name: McClelland Avenue,stop_code: Wjz7jsi, lat: -35.1807665, lng: 149.0890046}
- { name: Whiteside Court,stop_code: Wjz7qfu, lat: -35.1838151, lng: 149.0974127} - { name: Whiteside Court,stop_code: Wjz7qfu, lat: -35.1838151, lng: 149.0974127}
- { name: Oldershaw Court,stop_code: Wjz7qvq, lat: -35.1841768, lng: 149.1001944} - { name: Oldershaw Court,stop_code: Wjz7qvq, lat: -35.1841768, lng: 149.1001944}
- { name: Ryder Place,stop_code: Wjz7qkM, lat: -35.1864502, lng: 149.0992461} - { name: Ryder Place,stop_code: Wjz7qkM, lat: -35.1864502, lng: 149.0992461}
- { name: Lexcen Avenue,stop_code: Wjz7qwq, lat: -35.1890336, lng: 149.101522} - { name: Lexcen Avenue,stop_code: Wjz7qwq, lat: -35.1890336, lng: 149.101522}
- { name: Anne Clark Avenue,stop_code: Wjz7rOj, lat: -35.1820066, lng: 149.104114} - { name: Anne Clark Avenue,stop_code: Wjz7rOj, lat: -35.1820066, lng: 149.104114}
- { name: Biddell Place,stop_code: Wjz7rMm, lat: -35.1831434, lng: 149.104114} - { name: Biddell Place,stop_code: Wjz7rMm, lat: -35.1831434, lng: 149.104114}
- { name: Lexcen Avenue,stop_code: Wjz7q-_, lat: -35.1844351, lng: 149.1063899} - { name: Lexcen Avenue,stop_code: Wjz7q-_, lat: -35.1844351, lng: 149.1063899}
- { name: Quist Place,stop_code: Wjz7yfG, lat: -35.1841768, lng: 149.108729} - { name: Quist Place,stop_code: Wjz7yfG, lat: -35.1841768, lng: 149.108729}
- { name: Kelleway Avenue,stop_code: Wjz7r-a, lat: -35.1793714, lng: 149.1053784} - { name: Kelleway Avenue,stop_code: Wjz7r-a, lat: -35.1793714, lng: 149.1053784}
- { name: Wanganeen Avenue,stop_code: Wjz7Add, lat: -35.1743073, lng: 149.10816} - { name: Wanganeen Avenue,stop_code: Wjz7Add, lat: -35.1743073, lng: 149.10816}
- { name: Bimbiang Crescent,stop_code: Wjz7tOr, lat: -35.1710517, lng: 149.1042404} - { name: Bimbiang Crescent,stop_code: Wjz7tOr, lat: -35.1710517, lng: 149.1042404}
- { name: Bargang Crescent,stop_code: Wjz7txI, lat: -35.1716718, lng: 149.1018381} - { name: Bargang Crescent,stop_code: Wjz7txI, lat: -35.1716718, lng: 149.1018381}
- { name: Horse Park Drive,stop_code: Wjz7tug, lat: -35.1685711, lng: 149.0999415} - { name: Horse Park Drive,stop_code: Wjz7tug, lat: -35.1685711, lng: 149.0999415}
- { name: Horse Park Drive,stop_code: Wjz7tvK, lat: -35.1673308, lng: 149.1005105} - { name: Horse Park Drive,stop_code: Wjz7tvK, lat: -35.1673308, lng: 149.1005105}
- { name: Warabin Crescent,stop_code: Wjz7tLG, lat: -35.1677443, lng: 149.1032921} - { name: Warabin Crescent,stop_code: Wjz7tLG, lat: -35.1677443, lng: 149.1032921}
- { name: Wanganeen Avenue,stop_code: Wjz7Bg7, lat: -35.1720853, lng: 149.109298} - { name: Wanganeen Avenue,stop_code: Wjz7Bg7, lat: -35.1720853, lng: 149.109298}
- { name: Bunburung Close,stop_code: Wjz7BqG, lat: -35.1711551, lng: 149.1115106} - { name: Bunburung Close,stop_code: Wjz7BqG, lat: -35.1711551, lng: 149.1115106}
- { name: Unaipon Avenue,stop_code: Wjz7BC3, lat: -35.1683127, lng: 149.1120164} - { name: Unaipon Avenue,stop_code: Wjz7BC3, lat: -35.1683127, lng: 149.1120164}
- { name: Gurubun Close,stop_code: Wjz7BJK, lat: -35.1687262, lng: 149.1142923} - { name: Gurubun Close,stop_code: Wjz7BJK, lat: -35.1687262, lng: 149.1142923}
- { name: Deumonga Court,stop_code: Wjz7BED, lat: -35.1720853, lng: 149.1141026} - { name: Deumonga Court,stop_code: Wjz7BED, lat: -35.1720853, lng: 149.1141026}
- { name: Mirrabei Drive,stop_code: Wjz7BWN, lat: -35.1712067, lng: 149.1171372} - { name: Mirrabei Drive,stop_code: Wjz7BWN, lat: -35.1712067, lng: 149.1171372}
- { name: Ferguson Circuit,stop_code: Wjz7AGv, lat: -35.1762193, lng: 149.113913} - { name: Ferguson Circuit,stop_code: Wjz7AGv, lat: -35.1762193, lng: 149.113913}
- { name: Taggerty Street,stop_code: Wjz7AEw, lat: -35.1781829, lng: 149.1141659} - { name: Taggerty Street,stop_code: Wjz7AEw, lat: -35.1781829, lng: 149.1141659}
- { name: Tipiloura Street,stop_code: Wjz7CqJ, lat: -35.1654186, lng: 149.1114474} - { name: Tipiloura Street,stop_code: Wjz7CqJ, lat: -35.1654186, lng: 149.1114474}
- { name: Windradyne Street,stop_code: Wjz7CA3, lat: -35.16423, lng: 149.1119532} - { name: Windradyne Street,stop_code: Wjz7CA3, lat: -35.16423, lng: 149.1119532}
- { name: Mirrabei Drive,stop_code: Wjz7CKg, lat: -35.1630413, lng: 149.1137233} - { name: Mirrabei Drive,stop_code: Wjz7CKg, lat: -35.1630413, lng: 149.1137233}
- { name: Naas Close,stop_code: Wjz7IDY, lat: -35.1730154, lng: 149.1242809} - { name: Naas Close,stop_code: Wjz7IDY, lat: -35.1730154, lng: 149.1242809}
- { name: Paul Coe Crescent,stop_code: Wjz7Ikc, lat: -35.1750825, lng: 149.1204878} - { name: Paul Coe Crescent,stop_code: Wjz7Ikc, lat: -35.1750825, lng: 149.1204878}
- { name: Milari Street,stop_code: Wjz7HfF, lat: -35.178803, lng: 149.1197924} - { name: Milari Street,stop_code: Wjz7HfF, lat: -35.178803, lng: 149.1197924}
- { name: Paul Coe Crescent,stop_code: Wjz7IoZ, lat: -35.1777695, lng: 149.1227637} - { name: Paul Coe Crescent,stop_code: Wjz7IoZ, lat: -35.1777695, lng: 149.1227637}
- { name: Shoalhaven Avenue,stop_code: Wjz7IuJ, lat: -35.1736356, lng: 149.1225108} - { name: Shoalhaven Avenue,stop_code: Wjz7IuJ, lat: -35.1736356, lng: 149.1225108}
- { name: Tyenna Close,stop_code: Wjz7JP1, lat: -35.1705349, lng: 149.1257982} - { name: Tyenna Close,stop_code: Wjz7JP1, lat: -35.1705349, lng: 149.1257982}
- { name: Katherine Avenue,stop_code: Wjz7R6d, lat: -35.1681577, lng: 149.1286431} - { name: Katherine Avenue,stop_code: Wjz7R6d, lat: -35.1681577, lng: 149.1286431}
- { name: Timboram Street,stop_code: Wjz7R5z, lat: -35.1690363, lng: 149.1291488} - { name: Timboram Street,stop_code: Wjz7R5z, lat: -35.1690363, lng: 149.1291488}
- { name: Carstairs Circuit,stop_code: Wjz7RHe, lat: -35.1700698, lng: 149.135534} - { name: Carstairs Circuit,stop_code: Wjz7RHe, lat: -35.1700698, lng: 149.135534}
- { name: Horse Park Drive,stop_code: Wjz7SN-, lat: -35.1660013, lng: 149.1378981} - { name: Horse Park Drive,stop_code: Wjz7SN-, lat: -35.1660013, lng: 149.1378981}
- { name: Boreham Lane,stop_code: Wjz7PIc, lat: -35.1805599, lng: 149.135534} - { name: Boreham Lane,stop_code: Wjz7PIc, lat: -35.1805599, lng: 149.135534}
- { name: Swain Street,stop_code: Wjz7Pjj, lat: -35.1813349, lng: 149.1316144} - { name: Swain Street,stop_code: Wjz7Pjj, lat: -35.1813349, lng: 149.1316144}
- { name: Gundaroo Drive,stop_code: Wjz7yNW, lat: -35.1883262, lng: 149.1159763} - { name: Gundaroo Drive,stop_code: Wjz7yNW, lat: -35.1883262, lng: 149.1159763}
- { name: Hibberson Street,stop_code: Wjz7OBc, lat: -35.1853732, lng: 149.1341431} - { name: Hibberson Street,stop_code: Wjz7OBc, lat: -35.1853732, lng: 149.1341431}
- { name: Sarre Street,stop_code: Wjz7PNV, lat: -35.1828992, lng: 149.1380246} - { name: Sarre Street,stop_code: Wjz7PNV, lat: -35.1828992, lng: 149.1380246}
- { name: Gundaroo Drive,stop_code: Wjz7X3O, lat: -35.1814007, lng: 149.1404901} - { name: Gundaroo Drive,stop_code: Wjz7X3O, lat: -35.1814007, lng: 149.1404901}
- { name: Tesselaar Street,stop_code: Wjz7Xiv, lat: -35.1817108, lng: 149.1427028} - { name: Tesselaar Street,stop_code: Wjz7Xiv, lat: -35.1817108, lng: 149.1427028}
- { name: Sarson Street,stop_code: Wjzf31y, lat: -35.1828475, lng: 149.151111} - { name: Sarson Street,stop_code: Wjzf31y, lat: -35.1828475, lng: 149.151111}
- { name: Kalianna Street,stop_code: Wjzf2hJ, lat: -35.1880144, lng: 149.154019} - { name: Kalianna Street,stop_code: Wjzf2hJ, lat: -35.1880144, lng: 149.154019}
- { name: Nimbera Street,stop_code: Wjzf1X3, lat: -35.1923543, lng: 149.1600249} - { name: Nimbera Street,stop_code: Wjzf1X3, lat: -35.1923543, lng: 149.1600249}
- { name: Mapleton Avenue,stop_code: Wjzf91m, lat: -35.1934909, lng: 149.1618582} - { name: Mapleton Avenue,stop_code: Wjzf91m, lat: -35.1934909, lng: 149.1618582}
- { name: Elabana Street,stop_code: Wjzf0EJ, lat: -35.1997419, lng: 149.1581283} - { name: Elabana Street,stop_code: Wjzf0EJ, lat: -35.1997419, lng: 149.1581283}
- { name: Cudgewa Lane,stop_code: Wjze7Cp, lat: -35.2014466, lng: 149.1565478} - { name: Cudgewa Lane,stop_code: Wjze7Cp, lat: -35.2014466, lng: 149.1565478}
- { name: Oodgeroo Avenue,stop_code: Wjz6_7M, lat: -35.2008784, lng: 149.1404901} - { name: Oodgeroo Avenue,stop_code: Wjz6_7M, lat: -35.2008784, lng: 149.1404901}
- { name: Hoskins Street,stop_code: Wjz6TZN, lat: -35.2021182, lng: 149.1392257} - { name: Hoskins Street,stop_code: Wjz6TZN, lat: -35.2021182, lng: 149.1392257}
- { name: The Valley Avenue,stop_code: Wjz7GPB, lat: -35.1867085, lng: 149.1264936} - { name: The Valley Avenue,stop_code: Wjz7GPB, lat: -35.1867085, lng: 149.1264936}
- { name: The Valley Avenue,stop_code: Wjz7Gxm, lat: -35.188002, lng: 149.1234035} - { name: The Valley Avenue,stop_code: Wjz7Gxm, lat: -35.188002, lng: 149.1234035}
- { name: Kosciuszko Avenue,stop_code: Wjz7F5C, lat: -35.1906966, lng: 149.118141} - { name: Kosciuszko Avenue,stop_code: Wjz7F5C, lat: -35.1906966, lng: 149.118141}
- { name: Burrowa Street,stop_code: Wjz7xJz, lat: -35.191011, lng: 149.1141277} - { name: Burrowa Street,stop_code: Wjz7xJz, lat: -35.191011, lng: 149.1141277}
- { name: Kosciuszko Avenue,stop_code: Wjz7wZg, lat: -35.1967555, lng: 149.1165529} - { name: Kosciuszko Avenue,stop_code: Wjz7wZg, lat: -35.1967555, lng: 149.1165529}
- { name: Kosciuszko Avenue,stop_code: Wjz7EjH, lat: -35.1978404, lng: 149.1211679} - { name: Kosciuszko Avenue,stop_code: Wjz7EjH, lat: -35.1978404, lng: 149.1211679}
- { name: Kosciuszko Avenue,stop_code: Wjz7Ezf, lat: -35.1975304, lng: 149.1231277} - { name: Kosciuszko Avenue,stop_code: Wjz7Ezf, lat: -35.1975304, lng: 149.1231277}
- { name: Everard Street,stop_code: Wjz7FNw, lat: -35.193955, lng: 149.126474} - { name: Everard Street,stop_code: Wjz7FNw, lat: -35.193955, lng: 149.126474}
- { name: Vicars Street,stop_code: Wjz6-16, lat: -35.20994, lng: 149.1394383} - { name: Vicars Street,stop_code: Wjz6-16, lat: -35.20994, lng: 149.1394383}
- { name: McEacharn Place,stop_code: Wjz6Zb2, lat: -35.214395, lng: 149.1408607} - { name: McEacharn Place,stop_code: Wjz6Zb2, lat: -35.214395, lng: 149.1408607}
- { name: Brookes Street,stop_code: Wjz6Z8D, lat: -35.216009, lng: 149.1414929} - { name: Brookes Street,stop_code: Wjz6Z8D, lat: -35.216009, lng: 149.1414929}
- { name: Grimwade Street,stop_code: Wjz6QPM, lat: -35.2200763, lng: 149.1377788} - { name: Grimwade Street,stop_code: Wjz6QPM, lat: -35.2200763, lng: 149.1377788}
- { name: Brookes Street,stop_code: Wjz6Yc1, lat: -35.2193016, lng: 149.1407817} - { name: Brookes Street,stop_code: Wjz6Yc1, lat: -35.2193016, lng: 149.1407817}
- { name: Darling Street,stop_code: Wjz6YiM, lat: -35.2207864, lng: 149.1433105} - { name: Darling Street,stop_code: Wjz6YiM, lat: -35.2207864, lng: 149.1433105}
- { name: Flemington Road,stop_code: Wjz6XiO, lat: -35.226071, lng: 149.143256} - { name: Flemington Road,stop_code: Wjz6XiO, lat: -35.226071, lng: 149.143256}
- { name: Well Station Road,stop_code: Wjze2eG, lat: -35.2288072, lng: 149.1527323} - { name: Well Station Road,stop_code: Wjze2eG, lat: -35.2288072, lng: 149.1527323}
- { name: Well Station Road,stop_code: Wjze3gN, lat: -35.2275265, lng: 149.154199} - { name: Well Station Road,stop_code: Wjze3gN, lat: -35.2275265, lng: 149.154199}
- { name: Federal Highway,stop_code: Wjze3Vq, lat: -35.2267416, lng: 149.1606727} - { name: Federal Highway,stop_code: Wjze3Vq, lat: -35.2267416, lng: 149.1606727}
- { name: Federal Highway,stop_code: Wjzebjj, lat: -35.2253369, lng: 149.1645164} - { name: Federal Highway,stop_code: Wjzebjj, lat: -35.2253369, lng: 149.1645164}
- { name: Antill Street,stop_code: Wjze8v0, lat: -35.2393099, lng: 149.1654981} - { name: Antill Street,stop_code: Wjze8v0, lat: -35.2393099, lng: 149.1654981}
- { name: Fison Street,stop_code: Wjze8bf, lat: -35.2414165, lng: 149.1630705} - { name: Fison Street,stop_code: Wjze8bf, lat: -35.2414165, lng: 149.1630705}
- { name: Dobbie Place,stop_code: Wjze0Pi, lat: -35.2418709, lng: 149.1591256} - { name: Dobbie Place,stop_code: Wjze0Pi, lat: -35.2418709, lng: 149.1591256}
- { name: Knox Street,stop_code: Wjze0vR, lat: -35.2388968, lng: 149.1555853} - { name: Knox Street,stop_code: Wjze0vR, lat: -35.2388968, lng: 149.1555853}
- { name: Dickinson Street,stop_code: Wjze1c2, lat: -35.2356747, lng: 149.1518427} - { name: Dickinson Street,stop_code: Wjze1c2, lat: -35.2356747, lng: 149.1518427}
- { name: Harvey Street,stop_code: Wjze1gi, lat: -35.2384424, lng: 149.1535117} - { name: Harvey Street,stop_code: Wjze1gi, lat: -35.2384424, lng: 149.1535117}
- { name: Bradfield Street,stop_code: Wjz6UYK, lat: -35.2407969, lng: 149.1499714} - { name: Bradfield Street,stop_code: Wjz6UYK, lat: -35.2407969, lng: 149.1499714}
- { name: Atherton Street,stop_code: Wjz6Upu, lat: -35.2429035, lng: 149.1442058} - { name: Atherton Street,stop_code: Wjz6Upu, lat: -35.2429035, lng: 149.1442058}
- { name: Melba Street,stop_code: Wjz6Ugw, lat: -35.2441014, lng: 149.142992} - { name: Melba Street,stop_code: Wjz6Ugw, lat: -35.2441014, lng: 149.142992}
- { name: Melba Street,stop_code: Wjz5_ie, lat: -35.2476948, lng: 149.1423851} - { name: Melba Street,stop_code: Wjz5_ie, lat: -35.2476948, lng: 149.1423851}
- { name: Antill Street,stop_code: Wjz5_y0, lat: -35.2482318, lng: 149.1449139} - { name: Antill Street,stop_code: Wjz5_y0, lat: -35.2482318, lng: 149.1449139}
- { name: Antill Street,stop_code: Wjzd73N, lat: -35.2474057, lng: 149.1515393} - { name: Antill Street,stop_code: Wjzd73N, lat: -35.2474057, lng: 149.1515393}
- { name: Antill Street,stop_code: Wjzd7sL, lat: -35.2462079, lng: 149.1554841} - { name: Antill Street,stop_code: Wjzd7sL, lat: -35.2462079, lng: 149.1554841}
- { name: Madigan Street,stop_code: Wjzd7_6, lat: -35.2443079, lng: 149.1601371} - { name: Madigan Street,stop_code: Wjzd7_6, lat: -35.2443079, lng: 149.1601371}
- { name: Madigan Street,stop_code: Wjzdfaz, lat: -35.2479426, lng: 149.1635256} - { name: Madigan Street,stop_code: Wjzdfaz, lat: -35.2479426, lng: 149.1635256}
- { name: Madigan Street,stop_code: Wjzd6XP, lat: -35.2527713, lng: 149.1610527} - { name: Madigan Street,stop_code: Wjzd6XP, lat: -35.2527713, lng: 149.1610527}
- { name: Phillip Avenue,stop_code: Wjzd6Pn, lat: -35.2524079, lng: 149.1590701} - { name: Phillip Avenue,stop_code: Wjzd6Pn, lat: -35.2524079, lng: 149.1590701}
- { name: Salomons Place,stop_code: Wjzd6lW, lat: -35.2515158, lng: 149.1544172} - { name: Salomons Place,stop_code: Wjzd6lW, lat: -35.2515158, lng: 149.1544172}
- { name: Agnew Street,stop_code: Wjzd6iW, lat: -35.2535643, lng: 149.1544576} - { name: Agnew Street,stop_code: Wjzd6iW, lat: -35.2535643, lng: 149.1544576}
- { name: Bourke Street,stop_code: Wjz4Pt5, lat: -35.3116531, lng: 149.1326324} - { name: Bourke Street,stop_code: Wjz4Pt5, lat: -35.3116531, lng: 149.1326324}
- { name: Nyrang Street,stop_code: Wjzc1qE, lat: -35.3251161, lng: 149.1555115} - { name: Nyrang Street,stop_code: Wjzc1qE, lat: -35.3251161, lng: 149.1555115}
- { name: Bunda Street,stop_code: Wjz5NeC, lat: -35.2778798, lng: 149.1305995} - { name: Bunda Street,stop_code: Wjz5NeC, lat: -35.2778798, lng: 149.1305995}
- { name: Justinian Street,stop_code: Wjz3mWn, lat: -35.3409621, lng: 149.0945298} - { name: Justinian Street,stop_code: Wjz3mWn, lat: -35.3409621, lng: 149.0945298}
- { name: Wisdom Street,stop_code: Wjz3mQ4, lat: -35.3398419, lng: 149.0928819} - { name: Wisdom Street,stop_code: Wjz3mQ4, lat: -35.3398419, lng: 149.0928819}
- { name: Robson Street,stop_code: Wjz3C9Q, lat: -35.3419855, lng: 149.108934} - { name: Robson Street,stop_code: Wjz3C9Q, lat: -35.3419855, lng: 149.108934}
- { name: Ingamells Street,stop_code: Wjz3uJV, lat: -35.339486, lng: 149.1035524} - { name: Ingamells Street,stop_code: Wjz3uJV, lat: -35.339486, lng: 149.1035524}
- { name: Robson Street,stop_code: Wjz3C9J, lat: -35.3418945, lng: 149.1087966} - { name: Robson Street,stop_code: Wjz3C9J, lat: -35.3418945, lng: 149.1087966}
- { name: Wisdom Street,stop_code: Wjz3n-4, lat: -35.3330183, lng: 149.0941258} - { name: Wisdom Street,stop_code: Wjz3n-4, lat: -35.3330183, lng: 149.0941258}
- { name: Kent Street,stop_code: Wjz4qia, lat: -35.3194535, lng: 149.0984183} - { name: Kent Street,stop_code: Wjz4qia, lat: -35.3194535, lng: 149.0984183}
- { name: Kent Street,stop_code: Wjz4gXk, lat: -35.3296011, lng: 149.0945736} - { name: Kent Street,stop_code: Wjz4gXk, lat: -35.3296011, lng: 149.0945736}
- { name: McCaughey Street,stop_code: Wjz5Guy, lat: -35.2727878, lng: 149.1223747} - { name: McCaughey Street,stop_code: Wjz5Guy, lat: -35.2727878, lng: 149.1223747}
- { name: McCaughey Street,stop_code: Wjz5Iw8, lat: -35.2660466, lng: 149.1231132} - { name: McCaughey Street,stop_code: Wjz5Iw8, lat: -35.2660466, lng: 149.1231132}
- { name: Macpherson Street,stop_code: Wjz5Imu, lat: -35.2614148, lng: 149.1208459} - { name: Macpherson Street,stop_code: Wjz5Imu, lat: -35.2614148, lng: 149.1208459}
- { name: Macarthur Avenue,stop_code: Wjz5Jpu, lat: -35.2594072, lng: 149.1221624} - { name: Macarthur Avenue,stop_code: Wjz5Jpu, lat: -35.2594072, lng: 149.1221624}
- { name: Karri Street,stop_code: Wjz5JuJ, lat: -35.2560391, lng: 149.1225279} - { name: Karri Street,stop_code: Wjz5JuJ, lat: -35.2560391, lng: 149.1225279}
- { name: Jarrah Street,stop_code: Wjz5KgT, lat: -35.2544701, lng: 149.1213129} - { name: Jarrah Street,stop_code: Wjz5KgT, lat: -35.2544701, lng: 149.1213129}
- { name: Fawkner Street,stop_code: Wjz5OIf, lat: -35.2737328, lng: 149.1354944} - { name: Fawkner Street,stop_code: Wjz5OIf, lat: -35.2737328, lng: 149.1354944}
- { name: Ainslie Avenue,stop_code: Wjz5V64, lat: -35.2780918, lng: 149.1394963} - { name: Ainslie Avenue,stop_code: Wjz5V64, lat: -35.2780918, lng: 149.1394963}
- { name: Ainslie Avenue,stop_code: Wjz5NRJ, lat: -35.2787111, lng: 149.1375365} - { name: Ainslie Avenue,stop_code: Wjz5NRJ, lat: -35.2787111, lng: 149.1375365}
- { name: Gooreen Street,stop_code: Wjz5Vls, lat: -35.2787911, lng: 149.1427895} - { name: Gooreen Street,stop_code: Wjz5Vls, lat: -35.2787911, lng: 149.1427895}
- { name: Limestone Avenue,stop_code: Wjz5VAq, lat: -35.2796604, lng: 149.14553} - { name: Limestone Avenue,stop_code: Wjz5VAq, lat: -35.2796604, lng: 149.14553}
- { name: Fairbairn Avenue,stop_code: Wjz5VUU, lat: -35.2825429, lng: 149.15037} - { name: Fairbairn Avenue,stop_code: Wjz5VUU, lat: -35.2825429, lng: 149.15037}
- { name: Fairbairn Avenue,stop_code: Wjzd8br, lat: -35.2857037, lng: 149.16333} - { name: Fairbairn Avenue,stop_code: Wjzd8br, lat: -35.2857037, lng: 149.16333}
- { name: Glossop Crescent,stop_code: Wjzd0yM, lat: -35.2866868, lng: 149.1570161} - { name: Glossop Crescent,stop_code: Wjzd0yM, lat: -35.2866868, lng: 149.1570161}
- { name: Savige Street,stop_code: Wjzd02s, lat: -35.286331, lng: 149.1509776} - { name: Savige Street,stop_code: Wjzd02s, lat: -35.286331, lng: 149.1509776}
- { name: Chowne Street,stop_code: Wjz5UHK, lat: -35.2854924, lng: 149.1472635} - { name: Chowne Street,stop_code: Wjz5UHK, lat: -35.2854924, lng: 149.1472635}
- { name: Euree Street,stop_code: Wjz5Vg4, lat: -35.2821666, lng: 149.1422877} - { name: Euree Street,stop_code: Wjz5Vg4, lat: -35.2821666, lng: 149.1422877}
- { name: White Crescent,stop_code: Wjzd0EU, lat: -35.2880133, lng: 149.158501} - { name: White Crescent,stop_code: Wjzd0EU, lat: -35.2880133, lng: 149.158501}
- { name: Chauvel Street,stop_code: Wjzc7si, lat: -35.2905765, lng: 149.1549056} - { name: Chauvel Street,stop_code: Wjzc7si, lat: -35.2905765, lng: 149.1549056}
- { name: Bungey Street,stop_code: Wjzc7bs, lat: -35.2911202, lng: 149.1523397} - { name: Bungey Street,stop_code: Wjzc7bs, lat: -35.2911202, lng: 149.1523397}
- { name: Constitution Avenue,stop_code: Wjz4_wS, lat: -35.2930129, lng: 149.145973} - { name: Constitution Avenue,stop_code: Wjz4_wS, lat: -35.2930129, lng: 149.145973}
- { name: Wendouree Drive,stop_code: Wjz4_jm, lat: -35.2909901, lng: 149.1425844} - { name: Wendouree Drive,stop_code: Wjz4_jm, lat: -35.2909901, lng: 149.1425844}
- { name: Parkes Way,stop_code: Wjz5MEL, lat: -35.2874399, lng: 149.1362625} - { name: Parkes Way,stop_code: Wjz5MEL, lat: -35.2874399, lng: 149.1362625}
- { name: General Bridges Drive,stop_code: Wjzce4H, lat: -35.2960675, lng: 149.1623594} - { name: General Bridges Drive,stop_code: Wjzce4H, lat: -35.2960675, lng: 149.1623594}
- { name: Vowels Road,stop_code: WjzceFT, lat: -35.2977187, lng: 149.1693894} - { name: Vowels Road,stop_code: WjzceFT, lat: -35.2977187, lng: 149.1693894}
- { name: Vowels Road,stop_code: WjzcdDs, lat: -35.299411, lng: 149.1675181} - { name: Vowels Road,stop_code: WjzcdDs, lat: -35.299411, lng: 149.1675181}
- { name: Morshead Drive,stop_code: Wjzcdi7, lat: -35.3025893, lng: 149.1642813} - { name: Morshead Drive,stop_code: Wjzcdi7, lat: -35.3025893, lng: 149.1642813}
- { name: Morshead Drive,stop_code: Wjzcd8D, lat: -35.3039101, lng: 149.1635732} - { name: Morshead Drive,stop_code: Wjzcd8D, lat: -35.3039101, lng: 149.1635732}
- { name: Menindee Drive,stop_code: Wjzc59p, lat: -35.3037863, lng: 149.1523455} - { name: Menindee Drive,stop_code: Wjzc59p, lat: -35.3037863, lng: 149.1523455}
- { name: Menindee Drive,stop_code: Wjzc45R, lat: -35.3061389, lng: 149.1514351} - { name: Menindee Drive,stop_code: Wjzc45R, lat: -35.3061389, lng: 149.1514351}
- { name: Canberra Avenue,stop_code: Wjz4VKr, lat: -35.3221513, lng: 149.1468833} - { name: Canberra Avenue,stop_code: Wjz4VKr, lat: -35.3221513, lng: 149.1468833}
- { name: Canberra Avenue,stop_code: Wjz4VRQ, lat: -35.3226878, lng: 149.148704} - { name: Canberra Avenue,stop_code: Wjz4VRQ, lat: -35.3226878, lng: 149.148704}
- { name: Wickham Crescent,stop_code: Wjz4FEJ, lat: -35.3260887, lng: 149.125286} - { name: Wickham Crescent,stop_code: Wjz4FEJ, lat: -35.3260887, lng: 149.125286}
- { name: Vancouver Street,stop_code: Wjz4ECF, lat: -35.3278218, lng: 149.1238193} - { name: Vancouver Street,stop_code: Wjz4ECF, lat: -35.3278218, lng: 149.1238193}
- { name: Friendship Street,stop_code: Wjz3LP9, lat: -35.3353724, lng: 149.1259941} - { name: Friendship Street,stop_code: Wjz3LP9, lat: -35.3353724, lng: 149.1259941}
- { name: Quiros Street,stop_code: Wjz3LN9, lat: -35.3367339, lng: 149.1259435} - { name: Quiros Street,stop_code: Wjz3LN9, lat: -35.3367339, lng: 149.1259435}
- { name: Bremer Street,stop_code: Wjz4MAz, lat: -35.3290192, lng: 149.1346333} - { name: Bremer Street,stop_code: Wjz4MAz, lat: -35.3290192, lng: 149.1346333}
- { name: Favenc Circle,stop_code: Wjz4Ue5, lat: -35.327397, lng: 149.140921} - { name: Favenc Circle,stop_code: Wjz4Ue5, lat: -35.327397, lng: 149.140921}
- { name: Stuart Street,stop_code: Wjz4Ujk, lat: -35.3295839, lng: 149.1425394} - { name: Stuart Street,stop_code: Wjz4Ujk, lat: -35.3295839, lng: 149.1425394}
- { name: Captain Cook Crescent,stop_code: Wjz3_Ji, lat: -35.3339111, lng: 149.146681} - { name: Captain Cook Crescent,stop_code: Wjz3_Ji, lat: -35.3339111, lng: 149.146681}
- { name: McKinlay Place,stop_code: Wjz4UwD, lat: -35.3313913, lng: 149.1456952} - { name: McKinlay Place,stop_code: Wjz4UwD, lat: -35.3313913, lng: 149.1456952}
- { name: McKinlay Street,stop_code: Wjz4VEF, lat: -35.3264205, lng: 149.1472235} - { name: McKinlay Street,stop_code: Wjz4VEF, lat: -35.3264205, lng: 149.1472235}
- { name: Leeton Street,stop_code: Wjzc1n0, lat: -35.3216636, lng: 149.1532292} - { name: Leeton Street,stop_code: Wjzc1n0, lat: -35.3216636, lng: 149.1532292}
- { name: Boolimba Crescent,stop_code: Wjzc090, lat: -35.3312849, lng: 149.15186} - { name: Boolimba Crescent,stop_code: Wjzc090, lat: -35.3312849, lng: 149.15186}
- { name: Iluka Street,stop_code: Wjzb7nW, lat: -35.3324815, lng: 149.1544899} - { name: Iluka Street,stop_code: Wjzb7nW, lat: -35.3324815, lng: 149.1544899}
- { name: Mugga Way,stop_code: Wjz3Kxb, lat: -35.342056, lng: 149.1231366} - { name: Mugga Way,stop_code: Wjz3Kxb, lat: -35.342056, lng: 149.1231366}
- { name: Mugga Way,stop_code: Wjz3JDp, lat: -35.3435515, lng: 149.1235159} - { name: Mugga Way,stop_code: Wjz3JDp, lat: -35.3435515, lng: 149.1235159}
- { name: Mugga Way,stop_code: Wjz3JJs, lat: -35.344686, lng: 149.1248435} - { name: Mugga Way,stop_code: Wjz3JJs, lat: -35.344686, lng: 149.1248435}
- { name: Beagle Street,stop_code: Wjz3Rdo, lat: -35.3450469, lng: 149.1304068} - { name: Beagle Street,stop_code: Wjz3Rdo, lat: -35.3450469, lng: 149.1304068}
- { name: Monaro Crescent,stop_code: Wjz3ShE, lat: -35.3422498, lng: 149.1321257} - { name: Monaro Crescent,stop_code: Wjz3ShE, lat: -35.3422498, lng: 149.1321257}
- { name: Astrolabe Street,stop_code: Wjz3T8Z, lat: -35.337043, lng: 149.1311337} - { name: Astrolabe Street,stop_code: Wjz3T8Z, lat: -35.337043, lng: 149.1311337}
- { name: Bell Street,stop_code: Wjz4MpW, lat: -35.3311406, lng: 149.1338209} - { name: Bell Street,stop_code: Wjz4MpW, lat: -35.3311406, lng: 149.1338209}
- { name: Goyder Street,stop_code: Wjz3-Jb, lat: -35.3392754, lng: 149.1466095} - { name: Goyder Street,stop_code: Wjz3-Jb, lat: -35.3392754, lng: 149.1466095}
- { name: Narupai Street,stop_code: Wjzb6cp, lat: -35.3401203, lng: 149.1523581} - { name: Narupai Street,stop_code: Wjzb6cp, lat: -35.3401203, lng: 149.1523581}
- { name: Kyeema Street,stop_code: Wjzb7wf, lat: -35.3368722, lng: 149.1561338} - { name: Kyeema Street,stop_code: Wjzb7wf, lat: -35.3368722, lng: 149.1561338}
- { name: Matina Street,stop_code: Wjzb7HN, lat: -35.335349, lng: 149.1583716} - { name: Matina Street,stop_code: Wjzb7HN, lat: -35.335349, lng: 149.1583716}
- { name: Kootara Crescent,stop_code: Wjzb7S4, lat: -35.3330282, lng: 149.1586877} - { name: Kootara Crescent,stop_code: Wjzb7S4, lat: -35.3330282, lng: 149.1586877}
- { name: Goyder Street,stop_code: Wjz3SUA, lat: -35.3426508, lng: 149.1388551} - { name: Goyder Street,stop_code: Wjz3SUA, lat: -35.3426508, lng: 149.1388551}
- { name: Narrabundah Lane,stop_code: Wjzb4vx, lat: -35.3490259, lng: 149.1553622} - { name: Narrabundah Lane,stop_code: Wjzb4vx, lat: -35.3490259, lng: 149.1553622}
- { name: Dalby Street,stop_code: Wjzc1tq, lat: -35.3228774, lng: 149.1550358} - { name: Dalby Street,stop_code: Wjzc1tq, lat: -35.3228774, lng: 149.1550358}
- { name: Canberra Avenue,stop_code: Wjzbfnr, lat: -35.332383, lng: 149.1647873} - { name: Canberra Avenue,stop_code: Wjzbfnr, lat: -35.332383, lng: 149.1647873}
- { name: Newcastle Street,stop_code: Wjzc9WV, lat: -35.3250576, lng: 149.1722805} - { name: Newcastle Street,stop_code: Wjzc9WV, lat: -35.3250576, lng: 149.1722805}
- { name: Albany Street,stop_code: WjzchQP, lat: -35.3235189, lng: 149.1817987} - { name: Albany Street,stop_code: WjzchQP, lat: -35.3235189, lng: 149.1817987}
- { name: Townsville Street,stop_code: Wjzcod5, lat: -35.3281204, lng: 149.1848684} - { name: Townsville Street,stop_code: Wjzcod5, lat: -35.3281204, lng: 149.1848684}
- { name: Townsville Street,stop_code: Wjzcoab, lat: -35.3303968, lng: 149.1849583} - { name: Townsville Street,stop_code: Wjzcoab, lat: -35.3303968, lng: 149.1849583}
- { name: Townsville Street,stop_code: WjzcgX_, lat: -35.3293219, lng: 149.1833416} - { name: Townsville Street,stop_code: WjzcgX_, lat: -35.3293219, lng: 149.1833416}
- { name: Jindalee Crescent,stop_code: Wjz3r_u, lat: -35.3540946, lng: 149.1057023} - { name: Jindalee Crescent,stop_code: Wjz3r_u, lat: -35.3540946, lng: 149.1057023}
- { name: Arrellah Place,stop_code: Wjz3rQi, lat: -35.3565695, lng: 149.104185} - { name: Arrellah Place,stop_code: Wjz3rQi, lat: -35.3565695, lng: 149.104185}
- { name: Coreen Place,stop_code: Wjz3z0c, lat: -35.3591474, lng: 149.106777} - { name: Coreen Place,stop_code: Wjz3z0c, lat: -35.3591474, lng: 149.106777}
- { name: Bromby Street,stop_code: Wjz3y4z, lat: -35.3619315, lng: 149.1072828} - { name: Bromby Street,stop_code: Wjz3y4z, lat: -35.3619315, lng: 149.1072828}
- { name: Yamba Drive,stop_code: Wjz3pZQ, lat: -35.366623, lng: 149.1062713} - { name: Yamba Drive,stop_code: Wjz3pZQ, lat: -35.366623, lng: 149.1062713}
- { name: Beasley Street,stop_code: Wjz3x3A, lat: -35.3680664, lng: 149.1072196} - { name: Beasley Street,stop_code: Wjz3x3A, lat: -35.3680664, lng: 149.1072196}
- { name: Bee Place,stop_code: Wjz3xwa, lat: -35.3702316, lng: 149.1122771} - { name: Bee Place,stop_code: Wjz3xwa, lat: -35.3702316, lng: 149.1122771}
- { name: Yamba Drive,stop_code: Wjz3wrK, lat: -35.3733761, lng: 149.1115817} - { name: Yamba Drive,stop_code: Wjz3wrK, lat: -35.3733761, lng: 149.1115817}
- { name: Dookie Street,stop_code: Wjz3woC, lat: -35.3754381, lng: 149.1112656} - { name: Dookie Street,stop_code: Wjz3woC, lat: -35.3754381, lng: 149.1112656}
- { name: Shepherdson Place,stop_code: Wjz2DPD, lat: -35.378737, lng: 149.1155013} - { name: Shepherdson Place,stop_code: Wjz2DPD, lat: -35.378737, lng: 149.1155013}
- { name: Pudney Street,stop_code: Wjz2DEs, lat: -35.3811081, lng: 149.1139208} - { name: Pudney Street,stop_code: Wjz2DEs, lat: -35.3811081, lng: 149.1139208}
- { name: Woodgate Street,stop_code: Wjz2C5I, lat: -35.3831852, lng: 149.1074202} - { name: Woodgate Street,stop_code: Wjz2C5I, lat: -35.3831852, lng: 149.1074202}
- { name: Muresk Street,stop_code: Wjz2uSZ, lat: -35.3823742, lng: 149.1050643} - { name: Muresk Street,stop_code: Wjz2uSZ, lat: -35.3823742, lng: 149.1050643}
- { name: Longerenong Street,stop_code: Wjz2vL4, lat: -35.3762782, lng: 149.1023627} - { name: Longerenong Street,stop_code: Wjz2vL4, lat: -35.3762782, lng: 149.1023627}
- { name: Pridham Street,stop_code: Wjz3oih, lat: -35.3744422, lng: 149.0986886} - { name: Pridham Street,stop_code: Wjz3oih, lat: -35.3744422, lng: 149.0986886}
- { name: Lambrigg Street,stop_code: Wjz3oeM, lat: -35.3718451, lng: 149.0980006} - { name: Lambrigg Street,stop_code: Wjz3oeM, lat: -35.3718451, lng: 149.0980006}
- { name: Beasley Street,stop_code: Wjz3hXO, lat: -35.3681696, lng: 149.0952079} - { name: Beasley Street,stop_code: Wjz3hXO, lat: -35.3681696, lng: 149.0952079}
- { name: Wilkins Street,stop_code: Wjz3peD, lat: -35.3657466, lng: 149.0976102} - { name: Wilkins Street,stop_code: Wjz3peD, lat: -35.3657466, lng: 149.0976102}
- { name: Prior Place,stop_code: Wjz3oge, lat: -35.3754535, lng: 149.0983799} - { name: Prior Place,stop_code: Wjz3oge, lat: -35.3754535, lng: 149.0983799}
- { name: Athllon Drive,stop_code: Wjz2nLE, lat: -35.3766237, lng: 149.0922366} - { name: Athllon Drive,stop_code: Wjz2nLE, lat: -35.3766237, lng: 149.0922366}
- { name: Brookman Street,stop_code: Wjz2nug, lat: -35.3773453, lng: 149.0890124} - { name: Brookman Street,stop_code: Wjz2nug, lat: -35.3773453, lng: 149.0890124}
- { name: Batchelor Street,stop_code: Wjz3gcu, lat: -35.3726637, lng: 149.0864364} - { name: Batchelor Street,stop_code: Wjz3gcu, lat: -35.3726637, lng: 149.0864364}
- { name: Gouger Street,stop_code: Wjz3gB5, lat: -35.3720623, lng: 149.0900243} - { name: Gouger Street,stop_code: Wjz3gB5, lat: -35.3720623, lng: 149.0900243}
- { name: Garratt Street,stop_code: Wjz2k5E, lat: -35.3945084, lng: 149.0853457} - { name: Garratt Street,stop_code: Wjz2k5E, lat: -35.3945084, lng: 149.0853457}
- { name: Sternberg Crescent,stop_code: Wjz2cKo, lat: -35.3937869, lng: 149.0809204} - { name: Sternberg Crescent,stop_code: Wjz2cKo, lat: -35.3937869, lng: 149.0809204}
- { name: Fincham Crescent,stop_code: Wjz2crQ, lat: -35.3954875, lng: 149.0787077} - { name: Fincham Crescent,stop_code: Wjz2crQ, lat: -35.3954875, lng: 149.0787077}
- { name: Byrne Street,stop_code: Wjz2kbO, lat: -35.3956421, lng: 149.0869894} - { name: Byrne Street,stop_code: Wjz2kbO, lat: -35.3956421, lng: 149.0869894}
- { name: Athllon Drive,stop_code: Wjz2lDC, lat: -35.3870716, lng: 149.090679} - { name: Athllon Drive,stop_code: Wjz2lDC, lat: -35.3870716, lng: 149.090679}
- { name: Sulwood Drive,stop_code: Wjz2u2j, lat: -35.3853192, lng: 149.095863} - { name: Sulwood Drive,stop_code: Wjz2u2j, lat: -35.3853192, lng: 149.095863}
- { name: Sulwood Drive,stop_code: Wjz2ugd, lat: -35.3865047, lng: 149.0985182} - { name: Sulwood Drive,stop_code: Wjz2ugd, lat: -35.3865047, lng: 149.0985182}
- { name: Sulwood Drive,stop_code: Wjz2tyn, lat: -35.3904732, lng: 149.1013631} - { name: Sulwood Drive,stop_code: Wjz2tyn, lat: -35.3904732, lng: 149.1013631}
- { name: Sulwood Drive,stop_code: Wjz2sLr, lat: -35.3928439, lng: 149.1028803} - { name: Sulwood Drive,stop_code: Wjz2sLr, lat: -35.3928439, lng: 149.1028803}
- { name: Lansell Circuit,stop_code: Wjz2qJ7, lat: -35.4048663, lng: 149.1024781} - { name: Lansell Circuit,stop_code: Wjz2qJ7, lat: -35.4048663, lng: 149.1024781}
- { name: Grattan Court,stop_code: Wjz2r9X, lat: -35.4024569, lng: 149.098142} - { name: Grattan Court,stop_code: Wjz2r9X, lat: -35.4024569, lng: 149.098142}
- { name: Wheeler Crescent,stop_code: Wjz2jFF, lat: -35.4026479, lng: 149.0922959} - { name: Wheeler Crescent,stop_code: Wjz2jFF, lat: -35.4026479, lng: 149.0922959}
- { name: Snowden Place,stop_code: Wjz2isR, lat: -35.4057431, lng: 149.0896883} - { name: Snowden Place,stop_code: Wjz2isR, lat: -35.4057431, lng: 149.0896883}
- { name: Sturdee Crescent,stop_code: Wjz2iVd, lat: -35.4077519, lng: 149.0942596} - { name: Sturdee Crescent,stop_code: Wjz2iVd, lat: -35.4077519, lng: 149.0942596}
- { name: Crocker Place,stop_code: Wjz2q9z, lat: -35.4079064, lng: 149.0976735} - { name: Crocker Place,stop_code: Wjz2q9z, lat: -35.4079064, lng: 149.0976735}
- { name: Bugden Avenue,stop_code: Wjz2F6d, lat: -35.4098598, lng: 149.1177053} - { name: Bugden Avenue,stop_code: Wjz2F6d, lat: -35.4098598, lng: 149.1177053}
- { name: Bugden Avenue,stop_code: Wjz2xyM, lat: -35.4130074, lng: 149.113099} - { name: Bugden Avenue,stop_code: Wjz2xyM, lat: -35.4130074, lng: 149.113099}
- { name: Woods Place,stop_code: Wjz2pVO, lat: -35.4135227, lng: 149.1062081} - { name: Woods Place,stop_code: Wjz2pVO, lat: -35.4135227, lng: 149.1062081}
- { name: Stacy Street,stop_code: Wjz2oQE, lat: -35.4171292, lng: 149.1046908} - { name: Stacy Street,stop_code: Wjz2oQE, lat: -35.4171292, lng: 149.1046908}
- { name: Gilday Place,stop_code: Wjz2Gff, lat: -35.403475, lng: 149.1191048} - { name: Gilday Place,stop_code: Wjz2Gff, lat: -35.403475, lng: 149.1191048}
- { name: Demaine Crescent,stop_code: Wjz2Gu5, lat: -35.404351, lng: 149.1216336} - { name: Demaine Crescent,stop_code: Wjz2Gu5, lat: -35.404351, lng: 149.1216336}
- { name: Coyne Street,stop_code: Wjz2FDo, lat: -35.4095553, lng: 149.1235301} - { name: Coyne Street,stop_code: Wjz2FDo, lat: -35.4095553, lng: 149.1235301}
- { name: Coyne Street,stop_code: Wjz2F_q, lat: -35.4093651, lng: 149.1276548} - { name: Coyne Street,stop_code: Wjz2F_q, lat: -35.4093651, lng: 149.1276548}
- { name: Akhurst Grove,stop_code: Wjz1cz3, lat: -35.4395376, lng: 149.079087} - { name: Akhurst Grove,stop_code: Wjz1cz3, lat: -35.4395376, lng: 149.079087}
- { name: Andrea Place,stop_code: Wjz1d0X, lat: -35.4360866, lng: 149.0748513} - { name: Andrea Place,stop_code: Wjz1d0X, lat: -35.4360866, lng: 149.0748513}
- { name: Andrea Place,stop_code: Wjz15Xb, lat: -35.4340778, lng: 149.0723858} - { name: Andrea Place,stop_code: Wjz15Xb, lat: -35.4340778, lng: 149.0723858}
- { name: Harcus Close,stop_code: Wjz1klr, lat: -35.4381985, lng: 149.087748} - { name: Harcus Close,stop_code: Wjz1klr, lat: -35.4381985, lng: 149.087748}
- { name: Woodcock Drive,stop_code: Wjz1kyn, lat: -35.4398982, lng: 149.0904032} - { name: Woodcock Drive,stop_code: Wjz1kyn, lat: -35.4398982, lng: 149.0904032}
- { name: Stella Hume Street,stop_code: Wjz16Q9, lat: -35.4280509, lng: 149.0709317} - { name: Stella Hume Street,stop_code: Wjz16Q9, lat: -35.4280509, lng: 149.0709317}
- { name: Ragless Circuit,stop_code: WjrWXL8, lat: -35.3985958, lng: 149.0586576} - { name: Ragless Circuit,stop_code: WjrWXL8, lat: -35.3985958, lng: 149.0586576}
- { name: Learmonth Drive,stop_code: WjrWXIP, lat: -35.4004264, lng: 149.0594265} - { name: Learmonth Drive,stop_code: WjrWXIP, lat: -35.4004264, lng: 149.0594265}
- { name: Meredith Circuit,stop_code: WjrWQRL, lat: -35.3938608, lng: 149.049706} - { name: Meredith Circuit,stop_code: WjrWQRL, lat: -35.3938608, lng: 149.049706}
- { name: Bateman Street,stop_code: WjrWRWi, lat: -35.3908805, lng: 149.0506492} - { name: Bateman Street,stop_code: WjrWRWi, lat: -35.3908805, lng: 149.0506492}
- { name: Boddington Crescent,stop_code: WjrWSX9, lat: -35.3847561, lng: 149.0504459} - { name: Boddington Crescent,stop_code: WjrWSX9, lat: -35.3847561, lng: 149.0504459}
- { name: Eagle Circuit,stop_code: WjrWSBZ, lat: -35.383041, lng: 149.0472484} - { name: Eagle Circuit,stop_code: WjrWSBZ, lat: -35.383041, lng: 149.0472484}
- { name: Archibald Street,stop_code: Wjz5LLF, lat: -35.2446872, lng: 149.1252507} - { name: Archibald Street,stop_code: Wjz5LLF, lat: -35.2446872, lng: 149.1252507}
- { name: Archibald Street,stop_code: Wjz5LDv, lat: -35.2442061, lng: 149.1235678} - { name: Archibald Street,stop_code: Wjz5LDv, lat: -35.2442061, lng: 149.1235678}
- { name: Tharwa Drive,stop_code: Wjz1gBy, lat: -35.4601891, lng: 149.0907826} - { name: Tharwa Drive,stop_code: Wjz1gBy, lat: -35.4601891, lng: 149.0907826}
- { name: Pocket Avenue,stop_code: Wjz0v3X, lat: -35.4670374, lng: 149.0967252} - { name: Pocket Avenue,stop_code: Wjz0v3X, lat: -35.4670374, lng: 149.0967252}
- { name: Troughton Street,stop_code: Wjz0unz, lat: -35.4697663, lng: 149.0990011} - { name: Troughton Street,stop_code: Wjz0unz, lat: -35.4697663, lng: 149.0990011}
- { name: Paperbark Street,stop_code: Wjz0uQv, lat: -35.4714653, lng: 149.1043747} - { name: Paperbark Street,stop_code: Wjz0uQv, lat: -35.4714653, lng: 149.1043747}
- { name: Wollemi Place,stop_code: Wjz0C4B, lat: -35.4716198, lng: 149.1071563} - { name: Wollemi Place,stop_code: Wjz0C4B, lat: -35.4716198, lng: 149.1071563}
- { name: Kallista Place,stop_code: Wjz0Cpn, lat: -35.4735247, lng: 149.1110759} - { name: Kallista Place,stop_code: Wjz0Cpn, lat: -35.4735247, lng: 149.1110759}
- { name: Wollemi Place,stop_code: Wjz0Bv9, lat: -35.4753782, lng: 149.1107598} - { name: Wollemi Place,stop_code: Wjz0Bv9, lat: -35.4753782, lng: 149.1107598}
- { name: Galbraith Close,stop_code: Wjz0t_T, lat: -35.4749148, lng: 149.1061448} - { name: Galbraith Close,stop_code: Wjz0t_T, lat: -35.4749148, lng: 149.1061448}
- { name: Bellchambers Crescent,stop_code: Wjz0tno, lat: -35.4754811, lng: 149.0988746} - { name: Bellchambers Crescent,stop_code: Wjz0tno, lat: -35.4754811, lng: 149.0988746}
- { name: Forsythe Street,stop_code: Wjz0u92, lat: -35.4739881, lng: 149.0969148} - { name: Forsythe Street,stop_code: Wjz0u92, lat: -35.4739881, lng: 149.0969148}
- { name: Menzies Court,stop_code: Wjz0lYC, lat: -35.4770256, lng: 149.0948286} - { name: Menzies Court,stop_code: Wjz0lYC, lat: -35.4770256, lng: 149.0948286}
- { name: Olive Pink Crescent,stop_code: Wjz0t9g, lat: -35.4795997, lng: 149.0972309} - { name: Olive Pink Crescent,stop_code: Wjz0t9g, lat: -35.4795997, lng: 149.0972309}
- { name: Tharwa Drive,stop_code: Wjz0kHU, lat: -35.4837695, lng: 149.0925527} - { name: Tharwa Drive,stop_code: Wjz0kHU, lat: -35.4837695, lng: 149.0925527}
- { name: Tharwa Drive,stop_code: Wjz0klX, lat: -35.4821222, lng: 149.0884434} - { name: Tharwa Drive,stop_code: Wjz0klX, lat: -35.4821222, lng: 149.0884434}
- { name: Tharwa Drive,stop_code: Wjz0lcW, lat: -35.477386, lng: 149.0870526} - { name: Tharwa Drive,stop_code: Wjz0lcW, lat: -35.477386, lng: 149.0870526}
- { name: McVilly Close,stop_code: Wjz0eVg, lat: -35.4740911, lng: 149.0835756} - { name: McVilly Close,stop_code: Wjz0eVg, lat: -35.4740911, lng: 149.0835756}
- { name: Robert Lewis Court,stop_code: Wjz0m65, lat: -35.4702811, lng: 149.0845871} - { name: Robert Lewis Court,stop_code: Wjz0m65, lat: -35.4702811, lng: 149.0845871}
- { name: Hickenbotham Street,stop_code: Wjz0n3A, lat: -35.4669344, lng: 149.0852193} - { name: Hickenbotham Street,stop_code: Wjz0n3A, lat: -35.4669344, lng: 149.0852193}
- { name: Oxenham Circuit,stop_code: Wjz1gnx, lat: -35.4589532, lng: 149.0880641} - { name: Oxenham Circuit,stop_code: Wjz1gnx, lat: -35.4589532, lng: 149.0880641}
- { name: Knoke Avenue,stop_code: Wjz1h9y, lat: -35.4574599, lng: 149.0866733} - { name: Knoke Avenue,stop_code: Wjz1h9y, lat: -35.4574599, lng: 149.0866733}
- { name: McGilvray Close,stop_code: Wjz1h4G, lat: -35.4554516, lng: 149.0853457} - { name: McGilvray Close,stop_code: Wjz1h4G, lat: -35.4554516, lng: 149.0853457}
- { name: Woodcock Drive,stop_code: Wjz1heN, lat: -35.4541126, lng: 149.0869262} - { name: Woodcock Drive,stop_code: Wjz1heN, lat: -35.4541126, lng: 149.0869262}
- { name: Donohoe Place,stop_code: Wjz1ic5, lat: -35.4496838, lng: 149.0858515} - { name: Donohoe Place,stop_code: Wjz1ic5, lat: -35.4496838, lng: 149.0858515}
- { name: Dempsey Place,stop_code: Wjz1bTA, lat: -35.4422159, lng: 149.0824376} - { name: Dempsey Place,stop_code: Wjz1bTA, lat: -35.4422159, lng: 149.0824376}
- { name: Akhurst Grove,stop_code: Wjz1cI3, lat: -35.438868, lng: 149.0804778} - { name: Akhurst Grove,stop_code: Wjz1cI3, lat: -35.438868, lng: 149.0804778}
- { name: Mackennal Street,stop_code: Wjz5Lh-, lat: -35.248398, lng: 149.12138} - { name: Mackennal Street,stop_code: Wjz5Lh-, lat: -35.248398, lng: 149.12138}
- { name: Dyson Street,stop_code: Wjz5Kve, lat: -35.2497723, lng: 149.1218849} - { name: Dyson Street,stop_code: Wjz5Kve, lat: -35.2497723, lng: 149.1218849}
- { name: Miller Street,stop_code: Wjz5CW3, lat: -35.2534813, lng: 149.1160707} - { name: Miller Street,stop_code: Wjz5CW3, lat: -35.2534813, lng: 149.1160707}
- { name: Miller Street,stop_code: Wjz5BPB, lat: -35.2580866, lng: 149.1154899} - { name: Miller Street,stop_code: Wjz5BPB, lat: -35.2580866, lng: 149.1154899}
- { name: Fairfax Street,stop_code: Wjz5BaH, lat: -35.2589798, lng: 149.1087583} - { name: Fairfax Street,stop_code: Wjz5BaH, lat: -35.2589798, lng: 149.1087583}
- { name: Miller Street,stop_code: Wjz5ASf, lat: -35.2613846, lng: 149.1149009} - { name: Miller Street,stop_code: Wjz5ASf, lat: -35.2613846, lng: 149.1149009}
- { name: David Street,stop_code: Wjz5zJi, lat: -35.2679801, lng: 149.113807} - { name: David Street,stop_code: Wjz5zJi, lat: -35.2679801, lng: 149.113807}
- { name: Nicholson Crescent,stop_code: Wjz5zOq, lat: -35.2700411, lng: 149.1153216} - { name: Nicholson Crescent,stop_code: Wjz5zOq, lat: -35.2700411, lng: 149.1153216}
- { name: Boldrewood Street,stop_code: Wjz5GeU, lat: -35.2729264, lng: 149.1200337} - { name: Boldrewood Street,stop_code: Wjz5GeU, lat: -35.2729264, lng: 149.1200337}
- { name: Colville Street,stop_code: Wjz6EBY, lat: -35.2403577, lng: 149.1242409} - { name: Colville Street,stop_code: Wjz6EBY, lat: -35.2403577, lng: 149.1242409}
- { name: Northbourne Avenue,stop_code: Wjz6Myj, lat: -35.2424881, lng: 149.1344225} - { name: Northbourne Avenue,stop_code: Wjz6Myj, lat: -35.2424881, lng: 149.1344225}
- { name: Federal Highway,stop_code: Wjz6Vj2, lat: -35.2363715, lng: 149.1421638} - { name: Federal Highway,stop_code: Wjz6Vj2, lat: -35.2363715, lng: 149.1421638}
- { name: Claxton Crescent,stop_code: Wjz6Fze, lat: -35.2360279, lng: 149.123147} - { name: Claxton Crescent,stop_code: Wjz6Fze, lat: -35.2360279, lng: 149.123147}
- { name: Barsdell Place,stop_code: Wjz6cjg, lat: -35.2200412, lng: 149.0766172} - { name: Barsdell Place,stop_code: Wjz6cjg, lat: -35.2200412, lng: 149.0766172}
- { name: Bean Crescent,stop_code: Wjz6c8c, lat: -35.2217598, lng: 149.0751026} - { name: Bean Crescent,stop_code: Wjz6c8c, lat: -35.2217598, lng: 149.0751026}
- { name: Grover Crescent,stop_code: Wjz64Yc, lat: -35.2190101, lng: 149.0723258} - { name: Grover Crescent,stop_code: Wjz64Yc, lat: -35.2190101, lng: 149.0723258}
- { name: Bennetts Close,stop_code: Wjz6c7A, lat: -35.2169478, lng: 149.074177} - { name: Bennetts Close,stop_code: Wjz6c7A, lat: -35.2169478, lng: 149.074177}
- { name: Pirani Place,stop_code: Wjz6eGq, lat: -35.2096321, lng: 149.0809063} - { name: Pirani Place,stop_code: Wjz6eGq, lat: -35.2096321, lng: 149.0809063}
- { name: William Webb Drive,stop_code: Wjz6eoG, lat: -35.2110071, lng: 149.0784661} - { name: William Webb Drive,stop_code: Wjz6eoG, lat: -35.2110071, lng: 149.0784661}
- { name: Gleadow Street,stop_code: Wjz65_2, lat: -35.2116258, lng: 149.0722394} - { name: Gleadow Street,stop_code: Wjz65_2, lat: -35.2116258, lng: 149.0722394}
- { name: William Webb Drive,stop_code: Wjz64CB, lat: -35.2176067, lng: 149.0687895} - { name: William Webb Drive,stop_code: Wjz64CB, lat: -35.2176067, lng: 149.0687895}
- { name: Kerrigan Street,stop_code: Wjr_F9a, lat: -35.1938253, lng: 149.031231} - { name: Kerrigan Street,stop_code: Wjr_F9a, lat: -35.1938253, lng: 149.031231}
- { name: Tillyard Drive,stop_code: Wjr_NaX, lat: -35.1930428, lng: 149.043112} - { name: Tillyard Drive,stop_code: Wjr_NaX, lat: -35.1930428, lng: 149.043112}
- { name: Reuther Street,stop_code: Wjr_M6A, lat: -35.1956738, lng: 149.0413435} - { name: Reuther Street,stop_code: Wjr_M6A, lat: -35.1956738, lng: 149.0413435}
- { name: Shakespeare Crescent,stop_code: Wjr_FV4, lat: -35.1935916, lng: 149.039268} - { name: Shakespeare Crescent,stop_code: Wjr_FV4, lat: -35.1935916, lng: 149.039268}
- { name: Lawrence Close,stop_code: Wjr-CnE, lat: -35.206318, lng: 149.0223041} - { name: Lawrence Close,stop_code: Wjr-CnE, lat: -35.206318, lng: 149.0223041}
- { name: Pockley Close,stop_code: Wjr-D1B, lat: -35.2045158, lng: 149.0193788} - { name: Pockley Close,stop_code: Wjr-D1B, lat: -35.2045158, lng: 149.0193788}
- { name: Osburn Drive,stop_code: Wjr-ux-, lat: -35.2099601, lng: 149.0143872} - { name: Osburn Drive,stop_code: Wjr-ux-, lat: -35.2099601, lng: 149.0143872}
- { name: Spofforth Street,stop_code: Wjr-kZV, lat: -35.2186221, lng: 149.0075381} - { name: Spofforth Street,stop_code: Wjr-kZV, lat: -35.2186221, lng: 149.0075381}
- { name: Fullagar Crescent,stop_code: Wjr-yDR, lat: -35.2278849, lng: 149.0252438} - { name: Fullagar Crescent,stop_code: Wjr-yDR, lat: -35.2278849, lng: 149.0252438}
- { name: Dethridge Street,stop_code: Wjr-G49, lat: -35.2302721, lng: 149.0298424} - { name: Dethridge Street,stop_code: Wjr-G49, lat: -35.2302721, lng: 149.0298424}
- { name: Hodges Street,stop_code: Wjr-GcG, lat: -35.2301944, lng: 149.0319226} - { name: Hodges Street,stop_code: Wjr-GcG, lat: -35.2301944, lng: 149.0319226}
- { name: Southern Cross Drive,stop_code: Wjr-Hi1, lat: -35.2261454, lng: 149.032398} - { name: Southern Cross Drive,stop_code: Wjr-Hi1, lat: -35.2261454, lng: 149.032398}
- { name: Albany Street,stop_code: WjzcgLt, lat: -35.3267279, lng: 149.1797667} - { name: Albany Street,stop_code: WjzcgLt, lat: -35.3267279, lng: 149.1797667}
- { name: Collie Street,stop_code: Wjzcgzn, lat: -35.3293028, lng: 149.178368} - { name: Collie Street,stop_code: Wjzcgzn, lat: -35.3293028, lng: 149.178368}
- { name: Faulding Street,stop_code: WjzbfzE, lat: -35.3354178, lng: 149.1678599} - { name: Faulding Street,stop_code: WjzbfzE, lat: -35.3354178, lng: 149.1678599}
- { name: Wormald Street,stop_code: Wjzbfr6, lat: -35.3349204, lng: 149.1655287} - { name: Wormald Street,stop_code: Wjzbfr6, lat: -35.3349204, lng: 149.1655287}
- { name: Lithgow Street,stop_code: Wjzc8im, lat: -35.3300635, lng: 149.1644887} - { name: Lithgow Street,stop_code: Wjzc8im, lat: -35.3300635, lng: 149.1644887}
- { name: Ipswich Street,stop_code: Wjzc8c1, lat: -35.3291272, lng: 149.1628031} - { name: Ipswich Street,stop_code: Wjzc8c1, lat: -35.3291272, lng: 149.1628031}
- { name: Whyalla Street,stop_code: Wjzbn5y, lat: -35.3338671, lng: 149.1730601} - { name: Whyalla Street,stop_code: Wjzbn5y, lat: -35.3338671, lng: 149.1730601}
- { name: Hamelin Crescent,stop_code: Wjz3TZj, lat: -35.3338162, lng: 149.1384399} - { name: Hamelin Crescent,stop_code: Wjz3TZj, lat: -35.3338162, lng: 149.1384399}
- { name: Sprent Street,stop_code: Wjz3_o2, lat: -35.3372978, lng: 149.1435685} - { name: Sprent Street,stop_code: Wjz3_o2, lat: -35.3372978, lng: 149.1435685}
- { name: Goyder Street,stop_code: Wjz3-r-, lat: -35.3403989, lng: 149.1448954} - { name: Goyder Street,stop_code: Wjz3-r-, lat: -35.3403989, lng: 149.1448954}
- { name: Jerrabomberra Avenue,stop_code: Wjzb5vw, lat: -35.3436462, lng: 149.155296} - { name: Jerrabomberra Avenue,stop_code: Wjzb5vw, lat: -35.3436462, lng: 149.155296}
- { name: Northbourne Avenue,stop_code: Wjz5N7c, lat: -35.2774279, lng: 149.1287001} - { name: Northbourne Avenue,stop_code: Wjz5N7c, lat: -35.2774279, lng: 149.1287001}
- { name: Crawford Street,stop_code: WjzbYnD, lat: -35.3485475, lng: 149.2307657} - { name: Crawford Street,stop_code: WjzbYnD, lat: -35.3485475, lng: 149.2307657}
- { name: Uriarra Road,stop_code: WjzbZ3m, lat: -35.3459335, lng: 149.227726} - { name: Uriarra Road,stop_code: WjzbZ3m, lat: -35.3459335, lng: 149.227726}
- { name: Farrer Place,stop_code: WjzbXmQ, lat: -35.3550126, lng: 149.2311068} - { name: Farrer Place,stop_code: WjzbXmQ, lat: -35.3550126, lng: 149.2311068}
- { name: Crawford Street,stop_code: WjzbYzg, lat: -35.3519226, lng: 149.2332104} - { name: Crawford Street,stop_code: WjzbYzg, lat: -35.3519226, lng: 149.2332104}
- { name: Yass Road,stop_code: Wjzj5BH, lat: -35.3447463, lng: 149.2446946} - { name: Yass Road,stop_code: Wjzj5BH, lat: -35.3447463, lng: 149.2446946}
- { name: Endurance Avenue,stop_code: Wjzj6z9, lat: -35.3407864, lng: 149.2440483} - { name: Endurance Avenue,stop_code: Wjzj6z9, lat: -35.3407864, lng: 149.2440483}
- { name: Erin Street,stop_code: WjzbZqS, lat: -35.3465484, lng: 149.2325494} - { name: Erin Street,stop_code: WjzbZqS, lat: -35.3465484, lng: 149.2325494}
- { name: Alinga Street,stop_code: Wjz5F-1, lat: -35.2783161, lng: 149.1271286} - { name: Alinga Street,stop_code: Wjz5F-1, lat: -35.2783161, lng: 149.1271286}
- { name: Crawford Street,stop_code: WjzbZ77, lat: -35.3430401, lng: 149.2274615} - { name: Crawford Street,stop_code: WjzbZ77, lat: -35.3430401, lng: 149.2274615}
- { name: Alinga Street,stop_code: Wjz5N6V, lat: -35.2783725, lng: 149.1297843} - { name: Alinga Street,stop_code: Wjz5N6V, lat: -35.2783725, lng: 149.1297843}
- { name: Uriarra Road,stop_code: WjzbRdl, lat: -35.3446304, lng: 149.2181472} - { name: Uriarra Road,stop_code: WjzbRdl, lat: -35.3446304, lng: 149.2181472}
- { name: Uriarra Road,stop_code: WjzbJSj, lat: -35.3441148, lng: 149.2140644} - { name: Uriarra Road,stop_code: WjzbJSj, lat: -35.3441148, lng: 149.2140644}
- { name: Uriarra Road,stop_code: WjzbZ3n, lat: -35.3458022, lng: 149.2277877} - { name: Uriarra Road,stop_code: WjzbZ3n, lat: -35.3458022, lng: 149.2277877}
- { name: Uriarra Road,stop_code: WjzbRBs, lat: -35.344722, lng: 149.2224303} - { name: Uriarra Road,stop_code: WjzbRBs, lat: -35.344722, lng: 149.2224303}
- { name: Alinga Street,stop_code: Wjz5N5_, lat: -35.2785242, lng: 149.1297348} - { name: Alinga Street,stop_code: Wjz5N5_, lat: -35.2785242, lng: 149.1297348}
- { name: Alinga Street,stop_code: Wjz5Ndm, lat: -35.2785658, lng: 149.1301727} - { name: Alinga Street,stop_code: Wjz5Ndm, lat: -35.2785658, lng: 149.1301727}
- { name: Woodhill Link,stop_code: WjzaArS, lat: -35.3953167, lng: 149.1995002} - { name: Woodhill Link,stop_code: WjzaArS, lat: -35.3953167, lng: 149.1995002}
- { name: Nicholii Loop,stop_code: WjzaAXA, lat: -35.3954806, lng: 149.2047447} - { name: Nicholii Loop,stop_code: WjzaAXA, lat: -35.3954806, lng: 149.2047447}
- { name: Mariners Court,stop_code: WjzaAdv, lat: -35.3938794, lng: 149.1962366} - { name: Mariners Court,stop_code: WjzaAdv, lat: -35.3938794, lng: 149.1962366}
- { name: Canberra Avenue,stop_code: WjzbBu_, lat: -35.3437537, lng: 149.1997253} - { name: Canberra Avenue,stop_code: WjzbBu_, lat: -35.3437537, lng: 149.1997253}
- { name: Broughton Place,stop_code: WjzbPXf, lat: -35.3567667, lng: 149.2261434} - { name: Broughton Place,stop_code: WjzbPXf, lat: -35.3567667, lng: 149.2261434}
- { name: Tharwa Road,stop_code: WjzbPpi, lat: -35.3586252, lng: 149.2208441} - { name: Tharwa Road,stop_code: WjzbPpi, lat: -35.3586252, lng: 149.2208441}
- { name: Hayes Street,stop_code: WjzbWDe, lat: -35.3596366, lng: 149.2330229} - { name: Hayes Street,stop_code: WjzbWDe, lat: -35.3596366, lng: 149.2330229}
- { name: Cooma Street,stop_code: WjzbXwk, lat: -35.3591416, lng: 149.2331706} - { name: Cooma Street,stop_code: WjzbXwk, lat: -35.3591416, lng: 149.2331706}
- { name: Cooma Street,stop_code: WjzbVxf, lat: -35.369131, lng: 149.233084} - { name: Cooma Street,stop_code: WjzbVxf, lat: -35.369131, lng: 149.233084}
- { name: Cooma Street,stop_code: WjzbVy2, lat: -35.3689098, lng: 149.232863} - { name: Cooma Street,stop_code: WjzbVy2, lat: -35.3689098, lng: 149.232863}
- { name: Old Cooma Road,stop_code: Wjz9JdV, lat: -35.4328562, lng: 149.2080577} - { name: Old Cooma Road,stop_code: Wjz9JdV, lat: -35.4328562, lng: 149.2080577}
- { name: Cooma Street,stop_code: WjzbXAb, lat: -35.3564366, lng: 149.2330826} - { name: Cooma Street,stop_code: WjzbXAb, lat: -35.3564366, lng: 149.2330826}
- { name: Kinlyside Avenue,stop_code: WjzbwuF, lat: -35.3717405, lng: 149.1994726} - { name: Kinlyside Avenue,stop_code: WjzbwuF, lat: -35.3717405, lng: 149.1994726}
- { name: Darmody Place,stop_code: WjzbwDR, lat: -35.37069, lng: 149.2008683} - { name: Darmody Place,stop_code: WjzbwDR, lat: -35.37069, lng: 149.2008683}
- { name: Halloran Drive,stop_code: WjzbwMd, lat: -35.3755316, lng: 149.2028602} - { name: Halloran Drive,stop_code: WjzbwMd, lat: -35.3755316, lng: 149.2028602}
- { name: Maloney Street,stop_code: WjzbG5c, lat: -35.3611934, lng: 149.2054955} - { name: Maloney Street,stop_code: WjzbG5c, lat: -35.3611934, lng: 149.2054955}
- { name: Kendall Avenue North,stop_code: WjzbJRl, lat: -35.3445935, lng: 149.2139248} - { name: Kendall Avenue North,stop_code: WjzbJRl, lat: -35.3445935, lng: 149.2139248}
- { name: Canberra Avenue,stop_code: WjzbfPy, lat: -35.3352335, lng: 149.1703836} - { name: Canberra Avenue,stop_code: WjzbfPy, lat: -35.3352335, lng: 149.1703836}
- { name: Flinders Way,stop_code: Wjz4OqF, lat: -35.3195494, lng: 149.1335622} - { name: Flinders Way,stop_code: Wjz4OqF, lat: -35.3195494, lng: 149.1335622}
- { name: Burbury Close,stop_code: Wjz4Pk_, lat: -35.3121631, lng: 149.1324213} - { name: Burbury Close,stop_code: Wjz4Pk_, lat: -35.3121631, lng: 149.1324213}
- { name: Mort Street,stop_code: Wjz5NeF, lat: -35.2783224, lng: 149.130726} - { name: Mort Street,stop_code: Wjz5NeF, lat: -35.2783224, lng: 149.130726}
- { name: East Row,stop_code: Wjz5Ndz, lat: -35.2788601, lng: 149.130649} - { name: East Row,stop_code: Wjz5Ndz, lat: -35.2788601, lng: 149.130649}
- { name: East Row,stop_code: Wjz5NcA, lat: -35.2794346, lng: 149.1305879} - { name: East Row,stop_code: Wjz5NcA, lat: -35.2794346, lng: 149.1305879}
- { name: East Row,stop_code: Wjz5Nds, lat: -35.2787886, lng: 149.1304779} - { name: East Row,stop_code: Wjz5Nds, lat: -35.2787886, lng: 149.1304779}
- { name: Justinian Street,stop_code: Wjz3mPO, lat: -35.3407241, lng: 149.0937831} - { name: Justinian Street,stop_code: Wjz3mPO, lat: -35.3407241, lng: 149.0937831}
- { name: Wisdom Street,stop_code: Wjz3mI_, lat: -35.3396179, lng: 149.0925471} - { name: Wisdom Street,stop_code: Wjz3mI_, lat: -35.3396179, lng: 149.0925471}
- { name: Birdwood Street,stop_code: Wjz3vrf, lat: -35.3348497, lng: 149.099817} - { name: Birdwood Street,stop_code: Wjz3vrf, lat: -35.3348497, lng: 149.099817}
- { name: McNicoll Street,stop_code: Wjz3vqN, lat: -35.3360119, lng: 149.1006409} - { name: McNicoll Street,stop_code: Wjz3vqN, lat: -35.3360119, lng: 149.1006409}
- { name: Ingamells Street,stop_code: Wjz3C4O, lat: -35.3400601, lng: 149.1074834} - { name: Ingamells Street,stop_code: Wjz3C4O, lat: -35.3400601, lng: 149.1074834}
- { name: Ingamells Street,stop_code: Wjz3uQf, lat: -35.339661, lng: 149.1040329} - { name: Ingamells Street,stop_code: Wjz3uQf, lat: -35.339661, lng: 149.1040329}
- { name: Ingamells Street,stop_code: Wjz3C4q, lat: -35.3400391, lng: 149.106977} - { name: Ingamells Street,stop_code: Wjz3C4q, lat: -35.3400391, lng: 149.106977}
- { name: Dennis Street,stop_code: Wjz3B5o, lat: -35.344996, lng: 149.1070285} - { name: Dennis Street,stop_code: Wjz3B5o, lat: -35.344996, lng: 149.1070285}
- { name: Yamba Drive,stop_code: Wjz3lVM, lat: -35.3477625, lng: 149.0952366} - { name: Yamba Drive,stop_code: Wjz3lVM, lat: -35.3477625, lng: 149.0952366}
- { name: Yamba Drive,stop_code: Wjz3lVG, lat: -35.3476365, lng: 149.095065} - { name: Yamba Drive,stop_code: Wjz3lVG, lat: -35.3476365, lng: 149.095065}
- { name: Kent Street,stop_code: Wjz3n-H, lat: -35.3331304, lng: 149.0950356} - { name: Kent Street,stop_code: Wjz3n-H, lat: -35.3331304, lng: 149.0950356}
- { name: Yamba Drive,stop_code: Wjz3mAg, lat: -35.3402021, lng: 149.0903851} - { name: Yamba Drive,stop_code: Wjz3mAg, lat: -35.3402021, lng: 149.0903851}
- { name: Kent Street,stop_code: Wjz4q8_, lat: -35.3203709, lng: 149.0981179} - { name: Kent Street,stop_code: Wjz4q8_, lat: -35.3203709, lng: 149.0981179}
- { name: Kent Street,stop_code: Wjz4p1K, lat: -35.325336, lng: 149.0963669} - { name: Kent Street,stop_code: Wjz4p1K, lat: -35.325336, lng: 149.0963669}
- { name: Kent Street,stop_code: Wjz4p2R, lat: -35.3247128, lng: 149.0966244} - { name: Kent Street,stop_code: Wjz4p2R, lat: -35.3247128, lng: 149.0966244}
- { name: Kent Street,stop_code: Wjz4gYg, lat: -35.329258, lng: 149.0944878} - { name: Kent Street,stop_code: Wjz4gYg, lat: -35.329258, lng: 149.0944878}
- { name: Barry Drive,stop_code: Wjz5G6U, lat: -35.2729086, lng: 149.1187429} - { name: Barry Drive,stop_code: Wjz5G6U, lat: -35.2729086, lng: 149.1187429}
- { name: McCaughey Street,stop_code: Wjz5Hw8, lat: -35.2715996, lng: 149.1231371} - { name: McCaughey Street,stop_code: Wjz5Hw8, lat: -35.2715996, lng: 149.1231371}
- { name: McCaughey Street,stop_code: Wjz5HDd, lat: -35.2662951, lng: 149.1231711} - { name: McCaughey Street,stop_code: Wjz5HDd, lat: -35.2662951, lng: 149.1231711}
- { name: Macpherson Street,stop_code: Wjz5Iqp, lat: -35.2646152, lng: 149.1221727} - { name: Macpherson Street,stop_code: Wjz5Iqp, lat: -35.2646152, lng: 149.1221727}
- { name: Bluebell Street,stop_code: Wjz5IjX, lat: -35.2637604, lng: 149.1215219} - { name: Bluebell Street,stop_code: Wjz5IjX, lat: -35.2637604, lng: 149.1215219}
- { name: Macarthur Avenue,stop_code: Wjz5Jpp, lat: -35.2597672, lng: 149.1221194} - { name: Macarthur Avenue,stop_code: Wjz5Jpp, lat: -35.2597672, lng: 149.1221194}
- { name: Hovea Street,stop_code: Wjz5Jyz, lat: -35.258945, lng: 149.123718} - { name: Hovea Street,stop_code: Wjz5Jyz, lat: -35.258945, lng: 149.123718}
- { name: Hovea Street,stop_code: Wjz5JzP, lat: -35.2582197, lng: 149.123961} - { name: Hovea Street,stop_code: Wjz5JzP, lat: -35.2582197, lng: 149.123961}
- { name: Scrivener Street,stop_code: Wjz5Juf, lat: -35.2558204, lng: 149.1217923} - { name: Scrivener Street,stop_code: Wjz5Juf, lat: -35.2558204, lng: 149.1217923}
- { name: Brigalow Street,stop_code: Wjz5KgQ, lat: -35.2547172, lng: 149.1212395} - { name: Brigalow Street,stop_code: Wjz5KgQ, lat: -35.2547172, lng: 149.1212395}
- { name: Northbourne Avenue,stop_code: Wjz5N5k, lat: -35.2787905, lng: 149.1288627} - { name: Northbourne Avenue,stop_code: Wjz5N5k, lat: -35.2787905, lng: 149.1288627}
- { name: Northbourne Avenue,stop_code: Wjz5N4J, lat: -35.2793571, lng: 149.1293659} - { name: Northbourne Avenue,stop_code: Wjz5N4J, lat: -35.2793571, lng: 149.1293659}
- { name: Tillyard Drive,stop_code: Wjr-LNq, lat: -35.2048275, lng: 149.0383141} - { name: Tillyard Drive,stop_code: Wjr-LNq, lat: -35.2048275, lng: 149.0383141}
- { name: College Street,stop_code: Wjz68W5, lat: -35.2423221, lng: 149.0831522} - { name: College Street,stop_code: Wjz68W5, lat: -35.2423221, lng: 149.0831522}
- { name: College Street,stop_code: Wjz6gia, lat: -35.2425616, lng: 149.0874888} - { name: College Street,stop_code: Wjz6gia, lat: -35.2425616, lng: 149.0874888}
- { name: Haydon Drive,stop_code: Wjz5maK, lat: -35.2532079, lng: 149.0867657} - { name: Haydon Drive,stop_code: Wjz5maK, lat: -35.2532079, lng: 149.0867657}
- { name: Marcus Clarke Street,stop_code: Wjz5GMT, lat: -35.2764151, lng: 149.1267199} - { name: Marcus Clarke Street,stop_code: Wjz5GMT, lat: -35.2764151, lng: 149.1267199}
- { name: Flynn Drive,stop_code: Wjz4KNu, lat: -35.2978611, lng: 149.1263289} - { name: Flynn Drive,stop_code: Wjz4KNu, lat: -35.2978611, lng: 149.1263289}
- { name: Beaconsfield Street,stop_code: WjzbnGh, lat: -35.3359862, lng: 149.1796321} - { name: Beaconsfield Street,stop_code: WjzbnGh, lat: -35.3359862, lng: 149.1796321}
- { name: Flinders Way,stop_code: Wjz4Ox0, lat: -35.3203301, lng: 149.1339648} - { name: Flinders Way,stop_code: Wjz4Ox0, lat: -35.3203301, lng: 149.1339648}
- { name: Flinders Way,stop_code: Wjz4OpP, lat: -35.320064, lng: 149.1335699} - { name: Flinders Way,stop_code: Wjz4OpP, lat: -35.320064, lng: 149.1335699}
- { name: Captain Cook Crescent,stop_code: Wjz4NDP, lat: -35.3214366, lng: 149.1350462} - { name: Captain Cook Crescent,stop_code: Wjz4NDP, lat: -35.3214366, lng: 149.1350462}
- { name: Dominion Circuit,stop_code: Wjz4Pa9, lat: -35.314076, lng: 149.1301281} - { name: Dominion Circuit,stop_code: Wjz4Pa9, lat: -35.314076, lng: 149.1301281}
- { name: Summit Track,stop_code: Wjz5qbi, lat: -35.2748058, lng: 149.0972461} - { name: Summit Track,stop_code: Wjz5qbi, lat: -35.2748058, lng: 149.0972461}
- { name: Alpen Street,stop_code: Wjr-_Ua, lat: -35.2054509, lng: 149.0613315} - { name: Alpen Street,stop_code: Wjr-_Ua, lat: -35.2054509, lng: 149.0613315}
- { name: Keenan Street,stop_code: Wjz66kP, lat: -35.2081588, lng: 149.066382} - { name: Keenan Street,stop_code: Wjz66kP, lat: -35.2081588, lng: 149.066382}
- { name: Copland Drive,stop_code: Wjz66lY, lat: -35.2073806, lng: 149.0665685} - { name: Copland Drive,stop_code: Wjz66lY, lat: -35.2073806, lng: 149.0665685}
- { name: Meagher Place,stop_code: Wjz664q, lat: -35.2082119, lng: 149.0631086} - { name: Meagher Place,stop_code: Wjz664q, lat: -35.2082119, lng: 149.0631086}
- { name: Meagher Place,stop_code: Wjz664g, lat: -35.2083936, lng: 149.0629132} - { name: Meagher Place,stop_code: Wjz664g, lat: -35.2083936, lng: 149.0629132}
- { name: Parkes Place,stop_code: Wjz4Rs-, lat: -35.3012441, lng: 149.1338254} - { name: Parkes Place,stop_code: Wjz4Rs-, lat: -35.3012441, lng: 149.1338254}
- { name: Alpen Street,stop_code: Wjr-_Uj, lat: -35.2054305, lng: 149.0615985} - { name: Alpen Street,stop_code: Wjr-_Uj, lat: -35.2054305, lng: 149.0615985}
- { name: Lennox Crossing,stop_code: Wjz4Lh5, lat: -35.2924038, lng: 149.1201999} - { name: Lennox Crossing,stop_code: Wjz4Lh5, lat: -35.2924038, lng: 149.1201999}
- { name: Russell Drive,stop_code: Wjzc54R, lat: -35.3013866, lng: 149.1515283} - { name: Russell Drive,stop_code: Wjzc54R, lat: -35.3013866, lng: 149.1515283}
- { name: Russell Drive,stop_code: Wjzc60A, lat: -35.2986953, lng: 149.151155} - { name: Russell Drive,stop_code: Wjzc60A, lat: -35.2986953, lng: 149.151155}
- { name: Russell Drive,stop_code: Wjz4-WZ, lat: -35.2972194, lng: 149.1503113} - { name: Russell Drive,stop_code: Wjz4-WZ, lat: -35.2972194, lng: 149.1503113}
- { name: Catchpole Street,stop_code: Wjz56Hh, lat: -35.25291, lng: 149.0697814} - { name: Catchpole Street,stop_code: Wjz56Hh, lat: -35.25291, lng: 149.0697814}
- { name: Russell Drive,stop_code: Wjz4-WL, lat: -35.2970826, lng: 149.149927} - { name: Russell Drive,stop_code: Wjz4-WL, lat: -35.2970826, lng: 149.149927}
- { name: Kings Avenue,stop_code: Wjz4RFJ, lat: -35.3034224, lng: 149.1361467} - { name: Kings Avenue,stop_code: Wjz4RFJ, lat: -35.3034224, lng: 149.1361467}
- { name: Kings Avenue,stop_code: Wjz4RwH, lat: -35.3042846, lng: 149.1348585} - { name: Kings Avenue,stop_code: Wjz4RwH, lat: -35.3042846, lng: 149.1348585}
- { name: Bourke Street,stop_code: Wjz4PuC, lat: -35.3109115, lng: 149.1332413} - { name: Bourke Street,stop_code: Wjz4PuC, lat: -35.3109115, lng: 149.1332413}
- { name: Sydney Avenue,stop_code: Wjz4P6x, lat: -35.3112617, lng: 149.1291119} - { name: Sydney Avenue,stop_code: Wjz4P6x, lat: -35.3112617, lng: 149.1291119}
- { name: Waldock Street,stop_code: Wjz3bdj, lat: -35.3557447, lng: 149.0753424} - { name: Waldock Street,stop_code: Wjz3bdj, lat: -35.3557447, lng: 149.0753424}
- { name: Russell Drive,stop_code: Wjz4-Rc, lat: -35.2952651, lng: 149.1479687} - { name: Russell Drive,stop_code: Wjz4-Rc, lat: -35.2952651, lng: 149.1479687}
- { name: Keenan Street,stop_code: Wjr--W0, lat: -35.2097244, lng: 149.0611869} - { name: Keenan Street,stop_code: Wjr--W0, lat: -35.2097244, lng: 149.0611869}
- { name: Keenan Street,stop_code: Wjr--W9, lat: -35.2096897, lng: 149.061394} - { name: Keenan Street,stop_code: Wjr--W9, lat: -35.2096897, lng: 149.061394}
- { name: Chifley Place,stop_code: Wjz3cal, lat: -35.3521568, lng: 149.0752845} - { name: Chifley Place,stop_code: Wjz3cal, lat: -35.3521568, lng: 149.0752845}
- { name: Waldock Street,stop_code: Wjz3bdl, lat: -35.3556201, lng: 149.075221} - { name: Waldock Street,stop_code: Wjz3bdl, lat: -35.3556201, lng: 149.075221}
- { name: Wilsmore Crescent,stop_code: Wjz3b9v, lat: -35.3581498, lng: 149.0754026} - { name: Wilsmore Crescent,stop_code: Wjz3b9v, lat: -35.3581498, lng: 149.0754026}
- { name: Brinsmead Street,stop_code: Wjz39RI, lat: -35.3666487, lng: 149.0827357} - { name: Brinsmead Street,stop_code: Wjz39RI, lat: -35.3666487, lng: 149.0827357}
- { name: McDonald Street,stop_code: Wjz3ceV, lat: -35.3497899, lng: 149.0761589} - { name: McDonald Street,stop_code: Wjz3ceV, lat: -35.3497899, lng: 149.0761589}
- { name: Kingsford Smith Drive,stop_code: Wjr-RKi, lat: -35.2123821, lng: 149.0478391} - { name: Kingsford Smith Drive,stop_code: Wjr-RKi, lat: -35.2123821, lng: 149.0478391}
- { name: Conley Drive,stop_code: Wjr-RZx, lat: -35.213153, lng: 149.050965} - { name: Conley Drive,stop_code: Wjr-RZx, lat: -35.213153, lng: 149.050965}
- { name: Grainger Circuit,stop_code: Wjr-RT-, lat: -35.2113153, lng: 149.0500244} - { name: Grainger Circuit,stop_code: Wjr-RT-, lat: -35.2113153, lng: 149.0500244}
- { name: Horsley Crescent,stop_code: Wjr-Zk3, lat: -35.2136037, lng: 149.0543575} - { name: Horsley Crescent,stop_code: Wjr-Zk3, lat: -35.2136037, lng: 149.0543575}
- { name: Horsley Crescent,stop_code: Wjr-Zk5, lat: -35.2134943, lng: 149.0543506} - { name: Horsley Crescent,stop_code: Wjr-Zk5, lat: -35.2134943, lng: 149.0543506}
- { name: Verbrugghen Street,stop_code: Wjr-ZJc, lat: -35.2128875, lng: 149.0586429} - { name: Verbrugghen Street,stop_code: Wjr-ZJc, lat: -35.2128875, lng: 149.0586429}
- { name: Copland Drive,stop_code: Wjr-ZRJ, lat: -35.2127453, lng: 149.0607491} - { name: Copland Drive,stop_code: Wjr-ZRJ, lat: -35.2127453, lng: 149.0607491}
- { name: Clifford Crescent,stop_code: Wjz66fw, lat: -35.2063185, lng: 149.0646037} - { name: Clifford Crescent,stop_code: Wjz66fw, lat: -35.2063185, lng: 149.0646037}
- { name: Clifford Crescent,stop_code: Wjz66fx, lat: -35.2062629, lng: 149.0647145} - { name: Clifford Crescent,stop_code: Wjz66fx, lat: -35.2062629, lng: 149.0647145}
- { name: Crossley Close,stop_code: Wjr--Lw, lat: -35.2063011, lng: 149.059093} - { name: Crossley Close,stop_code: Wjr--Lw, lat: -35.2063011, lng: 149.059093}
- { name: Crossley Close,stop_code: Wjr--Ki, lat: -35.2068427, lng: 149.0588291} - { name: Crossley Close,stop_code: Wjr--Ki, lat: -35.2068427, lng: 149.0588291}
- { name: Le Gallienne Street,stop_code: Wjr--md, lat: -35.2066211, lng: 149.0544526} - { name: Le Gallienne Street,stop_code: Wjr--md, lat: -35.2066211, lng: 149.0544526}
- { name: Henslowe Place,stop_code: Wjr--6k, lat: -35.2066759, lng: 149.0519744} - { name: Henslowe Place,stop_code: Wjr--6k, lat: -35.2066759, lng: 149.0519744}
- { name: Pattinson Crescent,stop_code: Wjr-SS5, lat: -35.2065999, lng: 149.0489353} - { name: Pattinson Crescent,stop_code: Wjr-SS5, lat: -35.2065999, lng: 149.0489353}
- { name: Lathlain Street,stop_code: Wjz60d1, lat: -35.2406019, lng: 149.0638958} - { name: Lathlain Street,stop_code: Wjz60d1, lat: -35.2406019, lng: 149.0638958}
- { name: Weedon Close,stop_code: Wjz60c5, lat: -35.2408972, lng: 149.0639885} - { name: Weedon Close,stop_code: Wjz60c5, lat: -35.2408972, lng: 149.0639885}
- { name: Lathlain Street,stop_code: Wjz605_, lat: -35.2400517, lng: 149.0637152} - { name: Lathlain Street,stop_code: Wjz605_, lat: -35.2400517, lng: 149.0637152}
- { name: Lathlain Street,stop_code: Wjz606I, lat: -35.2396656, lng: 149.0633992} - { name: Lathlain Street,stop_code: Wjz606I, lat: -35.2396656, lng: 149.0633992}
- { name: Daley Road,stop_code: Wjz5yXo, lat: -35.2749982, lng: 149.1166312} - { name: Daley Road,stop_code: Wjz5yXo, lat: -35.2749982, lng: 149.1166312}
- { name: Macarthur Avenue,stop_code: Wjz5Jaa, lat: -35.2590481, lng: 149.1191164} - { name: Macarthur Avenue,stop_code: Wjz5Jaa, lat: -35.2590481, lng: 149.1191164}
- { name: Bimbimbie Street,stop_code: Wjz68Yy, lat: -35.2411603, lng: 149.0838439} - { name: Bimbimbie Street,stop_code: Wjz68Yy, lat: -35.2411603, lng: 149.0838439}
- { name: Carandini Street,stop_code: Wjr-_3A, lat: -35.2032823, lng: 149.0522538} - { name: Carandini Street,stop_code: Wjr-_3A, lat: -35.2032823, lng: 149.0522538}
- { name: Kingsford Smith Drive,stop_code: Wjr-_Hp, lat: -35.2034703, lng: 149.0589653} - { name: Kingsford Smith Drive,stop_code: Wjr-_Hp, lat: -35.2034703, lng: 149.0589653}
- { name: Alpen Street,stop_code: Wjr-_Og, lat: -35.2042571, lng: 149.0602273} - { name: Alpen Street,stop_code: Wjr-_Og, lat: -35.2042571, lng: 149.0602273}
- { name: Colborne Place,stop_code: Wjz670_, lat: -35.205061, lng: 149.0637667} - { name: Colborne Place,stop_code: Wjz670_, lat: -35.205061, lng: 149.0637667}
- { name: Hancock Street,stop_code: Wjz67k1, lat: -35.2028461, lng: 149.0653269} - { name: Hancock Street,stop_code: Wjz67k1, lat: -35.2028461, lng: 149.0653269}
- { name: Hancock Street,stop_code: Wjz67kk, lat: -35.2025967, lng: 149.0657125} - { name: Hancock Street,stop_code: Wjz67kk, lat: -35.2025967, lng: 149.0657125}
- { name: Copland Drive,stop_code: Wjr-YdU, lat: -35.2186771, lng: 149.0542242} - { name: Copland Drive,stop_code: Wjr-YdU, lat: -35.2186771, lng: 149.0542242}
- { name: Copland Drive,stop_code: Wjr-YcT, lat: -35.2187393, lng: 149.0539932} - { name: Copland Drive,stop_code: Wjr-YcT, lat: -35.2187393, lng: 149.0539932}
- { name: John Cleland Crescent,stop_code: Wjr-Xno, lat: -35.2227935, lng: 149.0548844} - { name: John Cleland Crescent,stop_code: Wjr-Xno, lat: -35.2227935, lng: 149.0548844}
- { name: John Cleland Crescent,stop_code: Wjr-Xky, lat: -35.2247107, lng: 149.0549856} - { name: John Cleland Crescent,stop_code: Wjr-Xky, lat: -35.2247107, lng: 149.0549856}
- { name: Coulter Drive,stop_code: WjrZ_tn, lat: -35.2455787, lng: 149.0560808} - { name: Coulter Drive,stop_code: WjrZ_tn, lat: -35.2455787, lng: 149.0560808}
- { name: Coulter Drive,stop_code: WjrZ_so, lat: -35.2468109, lng: 149.0562979} - { name: Coulter Drive,stop_code: WjrZ_so, lat: -35.2468109, lng: 149.0562979}
- { name: Wiseman Street,stop_code: Wjz56XB, lat: -35.2526099, lng: 149.0728793} - { name: Wiseman Street,stop_code: Wjz56XB, lat: -35.2526099, lng: 149.0728793}
- { name: Fulton Street,stop_code: Wjz5711, lat: -35.2488233, lng: 149.0625779} - { name: Fulton Street,stop_code: Wjz5711, lat: -35.2488233, lng: 149.0625779}
- { name: Melrose Drive,stop_code: Wjz3eRR, lat: -35.3390911, lng: 149.082759} - { name: Melrose Drive,stop_code: Wjz3eRR, lat: -35.3390911, lng: 149.082759}
- { name: Furzer Street,stop_code: Wjz3m31, lat: -35.3408061, lng: 149.0844784} - { name: Furzer Street,stop_code: Wjz3m31, lat: -35.3408061, lng: 149.0844784}
- { name: Barton Highway,stop_code: Wjz79-a, lat: -35.1903384, lng: 149.0833628} - { name: Barton Highway,stop_code: Wjz79-a, lat: -35.1903384, lng: 149.0833628}
- { name: Akuna Street,stop_code: Wjz5Nht, lat: -35.281465, lng: 149.131837} - { name: Akuna Street,stop_code: Wjz5Nht, lat: -35.281465, lng: 149.131837}
- { name: College Street,stop_code: Wjz6giR, lat: -35.2422899, lng: 149.0883846} - { name: College Street,stop_code: Wjz6giR, lat: -35.2422899, lng: 149.0883846}
- { name: Wisdom Street,stop_code: Wjz3mQ5, lat: -35.339761, lng: 149.0927558} - { name: Wisdom Street,stop_code: Wjz3mQ5, lat: -35.339761, lng: 149.0927558}
- { name: Sharwood Crescent,stop_code: Wjr-ZXo, lat: -35.214551, lng: 149.0617978} - { name: Sharwood Crescent,stop_code: Wjr-ZXo, lat: -35.214551, lng: 149.0617978}
- { name: Deffell Street,stop_code: Wjz652H, lat: -35.2150139, lng: 149.0634241} - { name: Deffell Street,stop_code: Wjz652H, lat: -35.2150139, lng: 149.0634241}
- { name: Callaghan Street,stop_code: Wjz65ik, lat: -35.2149321, lng: 149.0656677} - { name: Callaghan Street,stop_code: Wjz65ik, lat: -35.2149321, lng: 149.0656677}
- { name: Alderman Street,stop_code: Wjz65rA, lat: -35.2142446, lng: 149.0673143} - { name: Alderman Street,stop_code: Wjz65rA, lat: -35.2142446, lng: 149.0673143}
- { name: Norton Street,stop_code: Wjz65Hy, lat: -35.2143691, lng: 149.0701627} - { name: Norton Street,stop_code: Wjz65Hy, lat: -35.2143691, lng: 149.0701627}
- { name: Norton Street,stop_code: Wjz65GS, lat: -35.2147682, lng: 149.0705542} - { name: Norton Street,stop_code: Wjz65GS, lat: -35.2147682, lng: 149.0705542}
- { name: Stenhouse Close,stop_code: Wjz66oO, lat: -35.2109547, lng: 149.067737} - { name: Stenhouse Close,stop_code: Wjz66oO, lat: -35.2109547, lng: 149.067737}
- { name: Pitcairn Street,stop_code: Wjz66Fg, lat: -35.2104421, lng: 149.0698018} - { name: Pitcairn Street,stop_code: Wjz66Fg, lat: -35.2104421, lng: 149.0698018}
- { name: Clancy Street,stop_code: Wjz66XM, lat: -35.2090851, lng: 149.0732672} - { name: Clancy Street,stop_code: Wjz66XM, lat: -35.2090851, lng: 149.0732672}
- { name: Kissane Crescent,stop_code: Wjz6ec7, lat: -35.2077712, lng: 149.0749969} - { name: Kissane Crescent,stop_code: Wjz6ec7, lat: -35.2077712, lng: 149.0749969}
- { name: Primmer Court,stop_code: WjrW_zy, lat: -35.3792073, lng: 149.0577944} - { name: Primmer Court,stop_code: WjrW_zy, lat: -35.3792073, lng: 149.0577944}
- { name: Marconi Crescent,stop_code: WjrW_Qk, lat: -35.3783254, lng: 149.0600973} - { name: Marconi Crescent,stop_code: WjrW_Qk, lat: -35.3783254, lng: 149.0600973}
- { name: Marconi Crescent,stop_code: Wjz27d3, lat: -35.3777767, lng: 149.064033} - { name: Marconi Crescent,stop_code: Wjz27d3, lat: -35.3777767, lng: 149.064033}
- { name: Sinclair Street,stop_code: Wjz27dd, lat: -35.3775909, lng: 149.0640777} - { name: Sinclair Street,stop_code: Wjz27dd, lat: -35.3775909, lng: 149.0640777}
- { name: Marconi Crescent,stop_code: Wjz27k8, lat: -35.3787048, lng: 149.065524} - { name: Marconi Crescent,stop_code: Wjz27k8, lat: -35.3787048, lng: 149.065524}
- { name: Lascelles Circuit,stop_code: Wjz26n5, lat: -35.3816653, lng: 149.0653041} - { name: Lascelles Circuit,stop_code: Wjz26n5, lat: -35.3816653, lng: 149.0653041}
- { name: Summerland Circuit,stop_code: Wjz26tG, lat: -35.3833338, lng: 149.0674908} - { name: Summerland Circuit,stop_code: Wjz26tG, lat: -35.3833338, lng: 149.0674908}
- { name: Summerland Circuit,stop_code: Wjz26P8, lat: -35.3848854, lng: 149.0709314} - { name: Summerland Circuit,stop_code: Wjz26P8, lat: -35.3848854, lng: 149.0709314}
- { name: Summerland Circuit,stop_code: Wjz26Om, lat: -35.385045, lng: 149.0711386} - { name: Summerland Circuit,stop_code: Wjz26Om, lat: -35.385045, lng: 149.0711386}
- { name: Mason Street,stop_code: Wjz26WN, lat: -35.3854988, lng: 149.073226} - { name: Mason Street,stop_code: Wjz26WN, lat: -35.3854988, lng: 149.073226}
- { name: Lee Steere Crescent,stop_code: Wjz2def, lat: -35.3876959, lng: 149.0750942} - { name: Lee Steere Crescent,stop_code: Wjz2def, lat: -35.3876959, lng: 149.0750942}
- { name: Kingsmill Street,stop_code: Wjz2d34, lat: -35.3900029, lng: 149.0734943} - { name: Kingsmill Street,stop_code: Wjz2d34, lat: -35.3900029, lng: 149.0734943}
- { name: Summerland Circuit,stop_code: Wjz25Ox, lat: -35.3909341, lng: 149.0714764} - { name: Summerland Circuit,stop_code: Wjz25Ox, lat: -35.3909341, lng: 149.0714764}
- { name: Summerland Circuit,stop_code: Wjz25NL, lat: -35.3911118, lng: 149.0716052} - { name: Summerland Circuit,stop_code: Wjz25NL, lat: -35.3911118, lng: 149.0716052}
- { name: O'Halloran Circuit,stop_code: Wjz24vP, lat: -35.3928088, lng: 149.0677265} - { name: O'Halloran Circuit,stop_code: Wjz24vP, lat: -35.3928088, lng: 149.0677265}
- { name: O'Halloran Circuit,stop_code: Wjz24lu, lat: -35.3939542, lng: 149.0657865} - { name: O'Halloran Circuit,stop_code: Wjz24lu, lat: -35.3939542, lng: 149.0657865}
- { name: O'Halloran Circuit,stop_code: Wjz24cK, lat: -35.3946419, lng: 149.0647484} - { name: O'Halloran Circuit,stop_code: Wjz24cK, lat: -35.3946419, lng: 149.0647484}
- { name: Pinkerton Circuit,stop_code: Wjz248n, lat: -35.3972727, lng: 149.064345} - { name: Pinkerton Circuit,stop_code: Wjz248n, lat: -35.3972727, lng: 149.064345}
- { name: Ragless Circuit,stop_code: Wjz2347, lat: -35.4000362, lng: 149.0625} - { name: Ragless Circuit,stop_code: Wjz2347, lat: -35.4000362, lng: 149.0625}
- { name: Learmonth Drive,stop_code: WjrWXON, lat: -35.4019182, lng: 149.060886} - { name: Learmonth Drive,stop_code: WjrWXON, lat: -35.4019182, lng: 149.060886}
- { name: Learmonth Drive,stop_code: WjrWXNL, lat: -35.4020721, lng: 149.0607315} - { name: Learmonth Drive,stop_code: WjrWXNL, lat: -35.4020721, lng: 149.0607315}
- { name: Learmonth Drive,stop_code: Wjz230G, lat: -35.4032475, lng: 149.0634951} - { name: Learmonth Drive,stop_code: Wjz230G, lat: -35.4032475, lng: 149.0634951}
- { name: Driver Place,stop_code: Wjz66Cd, lat: -35.2065831, lng: 149.0682105} - { name: Driver Place,stop_code: Wjz66Cd, lat: -35.2065831, lng: 149.0682105}
- { name: Willis Street,stop_code: Wjz67xQ, lat: -35.2046532, lng: 149.0691406} - { name: Willis Street,stop_code: Wjz67xQ, lat: -35.2046532, lng: 149.0691406}
- { name: Kellway Street,stop_code: Wjz66KO, lat: -35.2068138, lng: 149.0704302} - { name: Kellway Street,stop_code: Wjz66KO, lat: -35.2068138, lng: 149.0704302}
- { name: Copland Drive,stop_code: Wjz67yW, lat: -35.2040813, lng: 149.0692143} - { name: Copland Drive,stop_code: Wjz67yW, lat: -35.2040813, lng: 149.0692143}
- { name: Edmunds Place,stop_code: Wjz67nz, lat: -35.2006201, lng: 149.0659965} - { name: Edmunds Place,stop_code: Wjz67nz, lat: -35.2006201, lng: 149.0659965}
- { name: Crofts Crescent,stop_code: Wjz701y, lat: -35.1992909, lng: 149.0633518} - { name: Crofts Crescent,stop_code: Wjz701y, lat: -35.1992909, lng: 149.0633518}
- { name: Standbridge Place,stop_code: Wjz701a, lat: -35.1992794, lng: 149.0628172} - { name: Standbridge Place,stop_code: Wjz701a, lat: -35.1992794, lng: 149.0628172}
- { name: Baddeley Crescent,stop_code: Wjr_UUU, lat: -35.2001327, lng: 149.0624944} - { name: Baddeley Crescent,stop_code: Wjr_UUU, lat: -35.2001327, lng: 149.0624944}
- { name: Goyder Street,stop_code: Wjzb705, lat: -35.3370433, lng: 149.1505109} - { name: Goyder Street,stop_code: Wjzb705, lat: -35.3370433, lng: 149.1505109}
- { name: Kingsford Smith Drive,stop_code: Wjr_UPA, lat: -35.1977713, lng: 149.0605874} - { name: Kingsford Smith Drive,stop_code: Wjr_UPA, lat: -35.1977713, lng: 149.0605874}
- { name: Kingsford Smith Drive,stop_code: Wjr_UTL, lat: -35.1947749, lng: 149.060646} - { name: Kingsford Smith Drive,stop_code: Wjr_UTL, lat: -35.1947749, lng: 149.060646}
- { name: Clarey Crescent,stop_code: Wjz707-, lat: -35.1947883, lng: 149.0637942} - { name: Clarey Crescent,stop_code: Wjz707-, lat: -35.1947883, lng: 149.0637942}
- { name: Clarey Crescent,stop_code: Wjz707Z, lat: -35.1948745, lng: 149.0637273} - { name: Clarey Crescent,stop_code: Wjz707Z, lat: -35.1948745, lng: 149.0637273}
- { name: Healy Street,stop_code: Wjz70lp, lat: -35.1966753, lng: 149.0658519} - { name: Healy Street,stop_code: Wjz70lp, lat: -35.1966753, lng: 149.0658519}
- { name: Boote Street,stop_code: Wjz70zB, lat: -35.1976784, lng: 149.0688026} - { name: Boote Street,stop_code: Wjz70zB, lat: -35.1976784, lng: 149.0688026}
- { name: Scattergood Place,stop_code: Wjz70zz, lat: -35.1978567, lng: 149.0687555} - { name: Scattergood Place,stop_code: Wjz70zz, lat: -35.1978567, lng: 149.0687555}
- { name: Owen Dixon Drive,stop_code: Wjz70IY, lat: -35.1970964, lng: 149.0706179} - { name: Owen Dixon Drive,stop_code: Wjz70IY, lat: -35.1970964, lng: 149.0706179}
- { name: Douglass Street,stop_code: Wjz70Wx, lat: -35.1986717, lng: 149.0728065} - { name: Douglass Street,stop_code: Wjz70Wx, lat: -35.1986717, lng: 149.0728065}
- { name: Copland Drive,stop_code: Wjz67_t, lat: -35.200411, lng: 149.0727116} - { name: Copland Drive,stop_code: Wjz67_t, lat: -35.200411, lng: 149.0727116}
- { name: Emerton Street,stop_code: Wjz67BD, lat: -35.2015929, lng: 149.0686908} - { name: Emerton Street,stop_code: Wjz67BD, lat: -35.2015929, lng: 149.0686908}
- { name: Scattergood Place,stop_code: Wjz67Dq, lat: -35.2006561, lng: 149.0686086} - { name: Scattergood Place,stop_code: Wjz67Dq, lat: -35.2006561, lng: 149.0686086}
- { name: Milne Bay Road,stop_code: Wjzce7O, lat: -35.2940494, lng: 149.162512} - { name: Milne Bay Road,stop_code: Wjzce7O, lat: -35.2940494, lng: 149.162512}
- { name: Bimbimbie Street,stop_code: Wjz68Y0, lat: -35.2413091, lng: 149.0832098} - { name: Bimbimbie Street,stop_code: Wjz68Y0, lat: -35.2413091, lng: 149.0832098}
- { name: Bimbimbie Street,stop_code: Wjz68IH, lat: -35.2411129, lng: 149.0812786} - { name: Bimbimbie Street,stop_code: Wjz68IH, lat: -35.2411129, lng: 149.0812786}
- { name: Bimbimbie Street,stop_code: Wjz68Ip, lat: -35.2412881, lng: 149.0809439} - { name: Bimbimbie Street,stop_code: Wjz68Ip, lat: -35.2412881, lng: 149.0809439}
- { name: Drakeford Drive,stop_code: WjrXUoV, lat: -35.3758661, lng: 149.0568376} - { name: Drakeford Drive,stop_code: WjrXUoV, lat: -35.3758661, lng: 149.0568376}
- { name: Tuggeranong Parkway,stop_code: WjrXUsW, lat: -35.3730527, lng: 149.0568719} - { name: Tuggeranong Parkway,stop_code: WjrXUsW, lat: -35.3730527, lng: 149.0568719}
- { name: Banambila Street,stop_code: Wjz5dQt, lat: -35.2573605, lng: 149.0822652} - { name: Banambila Street,stop_code: Wjz5dQt, lat: -35.2573605, lng: 149.0822652}
- { name: Bindaga Street,stop_code: Wjz5dcJ, lat: -35.2573868, lng: 149.075852} - { name: Bindaga Street,stop_code: Wjz5dcJ, lat: -35.2573868, lng: 149.075852}
- { name: Bandjalong Crescent,stop_code: Wjz5d81, lat: -35.2605056, lng: 149.0749293} - { name: Bandjalong Crescent,stop_code: Wjz5d81, lat: -35.2605056, lng: 149.0749293}
- { name: Cooyong Street,stop_code: Wjz5NAQ, lat: -35.2794375, lng: 149.1349942} - { name: Cooyong Street,stop_code: Wjz5NAQ, lat: -35.2794375, lng: 149.1349942}
- { name: Kambah pool Road,stop_code: WjrXMN9, lat: -35.3751239, lng: 149.0489789} - { name: Kambah pool Road,stop_code: WjrXMN9, lat: -35.3751239, lng: 149.0489789}
- { name: Brierly Street,stop_code: WjrX-3w, lat: -35.340876, lng: 149.0522964} - { name: Brierly Street,stop_code: WjrX-3w, lat: -35.340876, lng: 149.0522964}
- { name: Atkinson Street,stop_code: Wjzj4ju, lat: -35.351369, lng: 149.2416919} - { name: Atkinson Street,stop_code: Wjzj4ju, lat: -35.351369, lng: 149.2416919}
- { name: Gungurra Crescent,stop_code: WjrXJ-g, lat: -35.3443528, lng: 149.0396647} - { name: Gungurra Crescent,stop_code: WjrXJ-g, lat: -35.3443528, lng: 149.0396647}
- { name: Comrie Street,stop_code: Wjz2qnG, lat: -35.4038881, lng: 149.0992283} - { name: Comrie Street,stop_code: Wjz2qnG, lat: -35.4038881, lng: 149.0992283}
- { name: Pethebridge Street,stop_code: Wjz3i6e, lat: -35.3603188, lng: 149.084779} - { name: Pethebridge Street,stop_code: Wjz3i6e, lat: -35.3603188, lng: 149.084779}
- { name: Colbee Court,stop_code: Wjz3k1J, lat: -35.3528521, lng: 149.0854118} - { name: Colbee Court,stop_code: Wjz3k1J, lat: -35.3528521, lng: 149.0854118}
- { name: Divine Court,stop_code: Wjz3kcA, lat: -35.3508773, lng: 149.0866243} - { name: Divine Court,stop_code: Wjz3kcA, lat: -35.3508773, lng: 149.0866243}
- { name: Amy Ackman Street,stop_code: Wjz7ZaH, lat: -35.171087, lng: 149.1418054} - { name: Amy Ackman Street,stop_code: Wjz7ZaH, lat: -35.171087, lng: 149.1418054}
- { name: Amy Ackman Street,stop_code: Wjz7ZaP, lat: -35.1710474, lng: 149.141884} - { name: Amy Ackman Street,stop_code: Wjz7ZaP, lat: -35.1710474, lng: 149.141884}
- { name: Amy Ackman Street,stop_code: Wjz7-xb, lat: -35.1662448, lng: 149.1450965} - { name: Amy Ackman Street,stop_code: Wjz7-xb, lat: -35.1662448, lng: 149.1450965}
- { name: Molonglo Drive,stop_code: WjzcrEu, lat: -35.3150059, lng: 149.190788} - { name: Molonglo Drive,stop_code: WjzcrEu, lat: -35.3150059, lng: 149.190788}
- { name: Lochiel Street,stop_code: WjzbUQX, lat: -35.3729581, lng: 149.2368028} - { name: Lochiel Street,stop_code: WjzbUQX, lat: -35.3729581, lng: 149.2368028}
- { name: Noonan Street,stop_code: Wjzi7mf, lat: -35.3766831, lng: 149.2412565} - { name: Noonan Street,stop_code: Wjzi7mf, lat: -35.3766831, lng: 149.2412565}
- { name: Cooma Street,stop_code: WjzbUCp, lat: -35.3717241, lng: 149.2334526} - { name: Cooma Street,stop_code: WjzbUCp, lat: -35.3717241, lng: 149.2334526}
- { name: Cooma Street,stop_code: WjzbWBs, lat: -35.3611492, lng: 149.2334303} - { name: Cooma Street,stop_code: WjzbWBs, lat: -35.3611492, lng: 149.2334303}
- { name: Cooma Street,stop_code: WjzbWzE, lat: -35.3628765, lng: 149.2337473} - { name: Cooma Street,stop_code: WjzbWzE, lat: -35.3628765, lng: 149.2337473}
- { name: Hambly Place,stop_code: WjzbWyW, lat: -35.363411, lng: 149.2340547} - { name: Hambly Place,stop_code: WjzbWyW, lat: -35.363411, lng: 149.2340547}
- { name: Gundaroo Drive,stop_code: Wjz7oZp, lat: -35.1966204, lng: 149.1057315} - { name: Gundaroo Drive,stop_code: Wjz7oZp, lat: -35.1966204, lng: 149.1057315}
- { name: Gundaroo Drive,stop_code: Wjz7xp9, lat: -35.193896, lng: 149.1108506} - { name: Gundaroo Drive,stop_code: Wjz7xp9, lat: -35.193896, lng: 149.1108506}
- { name: Cowper Street,stop_code: Wjz5-6R, lat: -35.2505265, lng: 149.1404751} - { name: Cowper Street,stop_code: Wjz5-6R, lat: -35.2505265, lng: 149.1404751}
- { name: David Walsh Avenue,stop_code: Wjz7YIc, lat: -35.1751298, lng: 149.1466086} - { name: David Walsh Avenue,stop_code: Wjz7YIc, lat: -35.1751298, lng: 149.1466086}
- { name: Barritt Street,stop_code: WjrWTWO, lat: -35.3798917, lng: 149.0512179} - { name: Barritt Street,stop_code: WjrWTWO, lat: -35.3798917, lng: 149.0512179}
- { name: Barritt Street,stop_code: WjrWTJo, lat: -35.3779591, lng: 149.0479511} - { name: Barritt Street,stop_code: WjrWTJo, lat: -35.3779591, lng: 149.0479511}
- { name: Constitution Avenue,stop_code: Wjz5MsT, lat: -35.2846782, lng: 149.133671} - { name: Constitution Avenue,stop_code: Wjz5MsT, lat: -35.2846782, lng: 149.133671}
- { name: Hindmarsh Drive,stop_code: WjrXBSS, lat: -35.3438051, lng: 149.0278253} - { name: Hindmarsh Drive,stop_code: WjrXBSS, lat: -35.3438051, lng: 149.0278253}
- { name: Hindmarsh Drive,stop_code: WjrXBSJ, lat: -35.3439387, lng: 149.0276931} - { name: Hindmarsh Drive,stop_code: WjrXBSJ, lat: -35.3439387, lng: 149.0276931}
- { name: Mort Street,stop_code: Wjz5Oj2, lat: -35.2748472, lng: 149.131256} - { name: Mort Street,stop_code: Wjz5Oj2, lat: -35.2748472, lng: 149.131256}
- { name: Northbourne Avenue,stop_code: Wjz5P8K, lat: -35.2710632, lng: 149.1307122} - { name: Northbourne Avenue,stop_code: Wjz5P8K, lat: -35.2710632, lng: 149.1307122}
- { name: Northbourne Avenue,stop_code: Wjz5SrO, lat: -35.2528485, lng: 149.1336705} - { name: Northbourne Avenue,stop_code: Wjz5SrO, lat: -35.2528485, lng: 149.1336705}
- { name: Northbourne Avenue,stop_code: Wjz5Rsi, lat: -35.2576771, lng: 149.132889} - { name: Northbourne Avenue,stop_code: Wjz5Rsi, lat: -35.2576771, lng: 149.132889}
- { name: Northbourne Avenue,stop_code: Wjz5QmR, lat: -35.2615172, lng: 149.1322602} - { name: Northbourne Avenue,stop_code: Wjz5QmR, lat: -35.2615172, lng: 149.1322602}
- { name: Northbourne Avenue,stop_code: Wjz5Pl0, lat: -35.2681201, lng: 149.1312} - { name: Northbourne Avenue,stop_code: Wjz5Pl0, lat: -35.2681201, lng: 149.1312}
- { name: Northbourne Avenue,stop_code: Wjz5N5h, lat: -35.2790396, lng: 149.1288222} - { name: Northbourne Avenue,stop_code: Wjz5N5h, lat: -35.2790396, lng: 149.1288222}
- { name: Northbourne Avenue,stop_code: Wjz5O3Q, lat: -35.274617, lng: 149.1295599} - { name: Northbourne Avenue,stop_code: Wjz5O3Q, lat: -35.274617, lng: 149.1295599}
- { name: Northbourne Avenue,stop_code: Wjz5P8n, lat: -35.2710038, lng: 149.1301486} - { name: Northbourne Avenue,stop_code: Wjz5P8n, lat: -35.2710038, lng: 149.1301486}
- { name: Northbourne Avenue,stop_code: Wjz5Qi2, lat: -35.2645608, lng: 149.1311834} - { name: Northbourne Avenue,stop_code: Wjz5Qi2, lat: -35.2645608, lng: 149.1311834}
- { name: Northbourne Avenue,stop_code: Wjz5RkN, lat: -35.2577065, lng: 149.1322899} - { name: Northbourne Avenue,stop_code: Wjz5RkN, lat: -35.2577065, lng: 149.1322899}
- { name: Morisset Street,stop_code: WjzbYAM, lat: -35.3512052, lng: 149.2339748} - { name: Morisset Street,stop_code: WjzbYAM, lat: -35.3512052, lng: 149.2339748}
- { name: Kitchener Street,stop_code: Wjz3uDU, lat: -35.338154, lng: 149.1022456} - { name: Kitchener Street,stop_code: Wjz3uDU, lat: -35.338154, lng: 149.1022456}
- { name: Kitchener Street,stop_code: Wjz3uK7, lat: -35.3382669, lng: 149.1024969} - { name: Kitchener Street,stop_code: Wjz3uK7, lat: -35.3382669, lng: 149.1024969}
- { name: Black Mountain Summit Walk,stop_code: Wjz5xl6, lat: -35.278643, lng: 149.1093237} - { name: Black Mountain Summit Walk,stop_code: Wjz5xl6, lat: -35.278643, lng: 149.1093237}
- { name: Athllon Drive,stop_code: Wjz2mTK, lat: -35.3815863, lng: 149.0936139} - { name: Athllon Drive,stop_code: Wjz2mTK, lat: -35.3815863, lng: 149.0936139}
- { name: Baldwin Drive,stop_code: Wjz6keB, lat: -35.2175697, lng: 149.0866478} - { name: Baldwin Drive,stop_code: Wjz6keB, lat: -35.2175697, lng: 149.0866478}
- { name: Flierl Place,stop_code: Wjr_Mxy, lat: -35.1992913, lng: 149.0468658} - { name: Flierl Place,stop_code: Wjr_Mxy, lat: -35.1992913, lng: 149.0468658}
- { name: Fellows Street,stop_code: Wjr-InZ, lat: -35.2169003, lng: 149.0335258} - { name: Fellows Street,stop_code: Wjr-InZ, lat: -35.2169003, lng: 149.0335258}
- { name: Moyes Crescent,stop_code: Wjr-Alc, lat: -35.2183514, lng: 149.021625} - { name: Moyes Crescent,stop_code: Wjr-Alc, lat: -35.2183514, lng: 149.021625}
- { name: Solomon Crescent,stop_code: Wjr-I4P, lat: -35.2191133, lng: 149.0306838} - { name: Solomon Crescent,stop_code: Wjr-I4P, lat: -35.2191133, lng: 149.0306838}
- { name: Chambers Street,stop_code: Wjr-IGJ, lat: -35.2203467, lng: 149.0373003} - { name: Chambers Street,stop_code: Wjr-IGJ, lat: -35.2203467, lng: 149.0373003}
- { name: Maribyrnong Avenue,stop_code: Wjz6zth, lat: -35.2241129, lng: 149.1109391} - { name: Maribyrnong Avenue,stop_code: Wjz6zth, lat: -35.2241129, lng: 149.1109391}
- { name: Macumba Place,stop_code: Wjz6yir, lat: -35.2314837, lng: 149.1098378} - { name: Macumba Place,stop_code: Wjz6yir, lat: -35.2314837, lng: 149.1098378}
- { name: Glossop Crescent,stop_code: Wjzd0oD, lat: -35.2874406, lng: 149.1552177} - { name: Glossop Crescent,stop_code: Wjzd0oD, lat: -35.2874406, lng: 149.1552177}
- { name: Chewings Street,stop_code: Wjr-N9a, lat: -35.2377693, lng: 149.0421213} - { name: Chewings Street,stop_code: Wjr-N9a, lat: -35.2377693, lng: 149.0421213}
- { name: Hinkler Street,stop_code: Wjr-EuB, lat: -35.2395683, lng: 149.034448} - { name: Hinkler Street,stop_code: Wjr-EuB, lat: -35.2395683, lng: 149.034448}
- { name: Ratcliffe Crescent,stop_code: Wjr-VdI, lat: -35.2348097, lng: 149.0539156} - { name: Ratcliffe Crescent,stop_code: Wjr-VdI, lat: -35.2348097, lng: 149.0539156}
- { name: Krefft Street,stop_code: Wjr-PWf, lat: -35.225611, lng: 149.0504341} - { name: Krefft Street,stop_code: Wjr-PWf, lat: -35.225611, lng: 149.0504341}
- { name: Dungowan Street,stop_code: WjrZKnY, lat: -35.2498968, lng: 149.0336595} - { name: Dungowan Street,stop_code: WjrZKnY, lat: -35.2498968, lng: 149.0336595}
- { name: Capital Circle,stop_code: Wjz4IrL, lat: -35.307326, lng: 149.1225503} - { name: Capital Circle,stop_code: Wjz4IrL, lat: -35.307326, lng: 149.1225503}
- { name: National Circuit,stop_code: Wjz4INj, lat: -35.3091118, lng: 149.1261312} - { name: National Circuit,stop_code: Wjz4INj, lat: -35.3091118, lng: 149.1261312}
- { name: Theodore Street,stop_code: Wjz3fO2, lat: -35.3359729, lng: 149.0817737} - { name: Theodore Street,stop_code: Wjz3fO2, lat: -35.3359729, lng: 149.0817737}
- { name: Newdegate Street,stop_code: Wjz4qtY, lat: -35.3172423, lng: 149.100878} - { name: Newdegate Street,stop_code: Wjz4qtY, lat: -35.3172423, lng: 149.100878}
- { name: Hannah Place,stop_code: Wjz4y7z, lat: -35.3159129, lng: 149.1072689} - { name: Hannah Place,stop_code: Wjz4y7z, lat: -35.3159129, lng: 149.1072689}
- { name: Hopetoun Circuit,stop_code: Wjz4yng, lat: -35.316172, lng: 149.1095953} - { name: Hopetoun Circuit,stop_code: Wjz4yng, lat: -35.316172, lng: 149.1095953}
- { name: Stonehaven Crescent,stop_code: Wjz4yGG, lat: -35.3194308, lng: 149.1142224} - { name: Stonehaven Crescent,stop_code: Wjz4yGG, lat: -35.3194308, lng: 149.1142224}
- { name: Melbourne Avenue,stop_code: Wjz4yQ-, lat: -35.3177825, lng: 149.1159796} - { name: Melbourne Avenue,stop_code: Wjz4yQ-, lat: -35.3177825, lng: 149.1159796}
- { name: Melbourne Avenue,stop_code: Wjz4Hbx, lat: -35.3133913, lng: 149.1195724} - { name: Melbourne Avenue,stop_code: Wjz4Hbx, lat: -35.3133913, lng: 149.1195724}
- { name: Freda Gibson Circuit,stop_code: Wjz1HOf, lat: -35.4453654, lng: 149.1258946} - { name: Freda Gibson Circuit,stop_code: Wjz1HOf, lat: -35.4453654, lng: 149.1258946}
- { name: Burdett Crescent,stop_code: Wjz1GsO, lat: -35.4499519, lng: 149.1226442} - { name: Burdett Crescent,stop_code: Wjz1GsO, lat: -35.4499519, lng: 149.1226442}
- { name: Hartung Crescent,stop_code: Wjz1zWz, lat: -35.4457437, lng: 149.1168111} - { name: Hartung Crescent,stop_code: Wjz1zWz, lat: -35.4457437, lng: 149.1168111}
- { name: Cochrane Crescent,stop_code: Wjz1ySn, lat: -35.4481315, lng: 149.1151569} - { name: Cochrane Crescent,stop_code: Wjz1ySn, lat: -35.4481315, lng: 149.1151569}
- { name: Conlon Crescent,stop_code: Wjz1G32, lat: -35.4506139, lng: 149.1174495} - { name: Conlon Crescent,stop_code: Wjz1G32, lat: -35.4506139, lng: 149.1174495}
- { name: Clift Crescent,stop_code: Wjz1CD8, lat: -35.4260286, lng: 149.1122294} - { name: Clift Crescent,stop_code: Wjz1CD8, lat: -35.4260286, lng: 149.1122294}
- { name: Meeson Street,stop_code: Wjz1Kiu, lat: -35.4289549, lng: 149.1207905} - { name: Meeson Street,stop_code: Wjz1Kiu, lat: -35.4289549, lng: 149.1207905}
- { name: Nina Jones Crescent,stop_code: Wjz1S2v, lat: -35.4289254, lng: 149.1290251} - { name: Nina Jones Crescent,stop_code: Wjz1S2v, lat: -35.4289254, lng: 149.1290251}
- { name: Monaro Highway,stop_code: Wjz1TgM, lat: -35.4253782, lng: 149.1323625} - { name: Monaro Highway,stop_code: Wjz1TgM, lat: -35.4253782, lng: 149.1323625}
- { name: Baskerville Street,stop_code: Wjz1LBV, lat: -35.4218605, lng: 149.1241279} - { name: Baskerville Street,stop_code: Wjz1LBV, lat: -35.4218605, lng: 149.1241279}
- { name: McLorinan Street,stop_code: Wjz1DWq, lat: -35.4238411, lng: 149.1166188} - { name: McLorinan Street,stop_code: Wjz1DWq, lat: -35.4238411, lng: 149.1166188}
- { name: Barry Drive,stop_code: Wjz5G6B, lat: -35.2724804, lng: 149.1181797} - { name: Barry Drive,stop_code: Wjz5G6B, lat: -35.2724804, lng: 149.1181797}
- { name: Chinner Crescent,stop_code: Wjr-SAW, lat: -35.2081966, lng: 149.0473834} - { name: Chinner Crescent,stop_code: Wjr-SAW, lat: -35.2081966, lng: 149.0473834}
- { name: O'Halloran Circuit,stop_code: WjrWYDO, lat: -35.3929049, lng: 149.058196} - { name: O'Halloran Circuit,stop_code: WjrWYDO, lat: -35.3929049, lng: 149.058196}
- { name: Chuculba Crescent,stop_code: Wjz6sdJ, lat: -35.21822, lng: 149.09782} - { name: Chuculba Crescent,stop_code: Wjz6sdJ, lat: -35.21822, lng: 149.09782}
- { name: Tucana Street,stop_code: Wjz6t8_, lat: -35.21601, lng: 149.09817} - { name: Tucana Street,stop_code: Wjz6t8_, lat: -35.21601, lng: 149.09817}
- { name: Tucana Street,stop_code: Wjz6t9w, lat: -35.21597, lng: 149.09763} - { name: Tucana Street,stop_code: Wjz6t9w, lat: -35.21597, lng: 149.09763}
- { name: Canopus Crescent,stop_code: Wjz6t4U, lat: -35.21388, lng: 149.09676} - { name: Canopus Crescent,stop_code: Wjz6t4U, lat: -35.21388, lng: 149.09676}
- { name: Purdie Street,stop_code: Wjz5nw6, lat: -35.2491082, lng: 149.0900504} - { name: Purdie Street,stop_code: Wjz5nw6, lat: -35.2491082, lng: 149.0900504}
- { name: Haydon Drive,stop_code: Wjz6hxB, lat: -35.2374959, lng: 149.0907853} - { name: Haydon Drive,stop_code: Wjz6hxB, lat: -35.2374959, lng: 149.0907853}
- { name: Baldwin Drive,stop_code: Wjz6rsL, lat: -35.2242562, lng: 149.1005043} - { name: Baldwin Drive,stop_code: Wjz6rsL, lat: -35.2242562, lng: 149.1005043}
- { name: Baldwin Drive,stop_code: Wjz6rrI, lat: -35.2252509, lng: 149.1005016} - { name: Baldwin Drive,stop_code: Wjz6rrI, lat: -35.2252509, lng: 149.1005016}
- { name: Bindubi Street,stop_code: Wjz5eb2, lat: -35.252833, lng: 149.0749872} - { name: Bindubi Street,stop_code: Wjz5eb2, lat: -35.252833, lng: 149.0749872}
- { name: Bindubi Street,stop_code: Wjz5ec7, lat: -35.2517641, lng: 149.0750194} - { name: Bindubi Street,stop_code: Wjz5ec7, lat: -35.2517641, lng: 149.0750194}
- { name: Bindubi Street,stop_code: Wjz5d57, lat: -35.256585, lng: 149.0734919} - { name: Bindubi Street,stop_code: Wjz5d57, lat: -35.256585, lng: 149.0734919}
- { name: College Street,stop_code: Wjz681S, lat: -35.2428905, lng: 149.0745728} - { name: College Street,stop_code: Wjz681S, lat: -35.2428905, lng: 149.0745728}
- { name: College Street,stop_code: Wjz689c, lat: -35.2430767, lng: 149.0750449} - { name: College Street,stop_code: Wjz689c, lat: -35.2430767, lng: 149.0750449}
- { name: Gwydir Square,stop_code: Wjz6pLi, lat: -35.2336222, lng: 149.1026958} - { name: Gwydir Square,stop_code: Wjz6pLi, lat: -35.2336222, lng: 149.1026958}
- { name: Maribyrnong Avenue,stop_code: Wjz6y90, lat: -35.2324006, lng: 149.1079069} - { name: Maribyrnong Avenue,stop_code: Wjz6y90, lat: -35.2324006, lng: 149.1079069}
- { name: Moruya Circuit,stop_code: Wjz6Apq, lat: -35.2212504, lng: 149.1111434} - { name: Moruya Circuit,stop_code: Wjz6Apq, lat: -35.2212504, lng: 149.1111434}
- { name: Ellenborough Street,stop_code: Wjz6yzQ, lat: -35.2307289, lng: 149.1130906} - { name: Ellenborough Street,stop_code: Wjz6yzQ, lat: -35.2307289, lng: 149.1130906}
- { name: Mouat Street,stop_code: Wjz5L_c, lat: -35.2444385, lng: 149.1272473} - { name: Mouat Street,stop_code: Wjz5L_c, lat: -35.2444385, lng: 149.1272473}
- { name: Mouat Street,stop_code: Wjz5Ti2, lat: -35.2480353, lng: 149.1313351} - { name: Mouat Street,stop_code: Wjz5Ti2, lat: -35.2480353, lng: 149.1313351}
- { name: Aikman Drive,stop_code: Wjz69ht, lat: -35.2375061, lng: 149.0768646} - { name: Aikman Drive,stop_code: Wjz69ht, lat: -35.2375061, lng: 149.0768646}
- { name: Aikman Drive,stop_code: Wjz69gA, lat: -35.2382334, lng: 149.0769344} - { name: Aikman Drive,stop_code: Wjz69gA, lat: -35.2382334, lng: 149.0769344}
- { name: Broad Place,stop_code: WjrWZsS, lat: -35.3891768, lng: 149.0567055} - { name: Broad Place,stop_code: WjrWZsS, lat: -35.3891768, lng: 149.0567055}
- { name: Boddington Crescent,stop_code: WjrWZA3, lat: -35.3893963, lng: 149.0571767} - { name: Boddington Crescent,stop_code: WjrWZA3, lat: -35.3893963, lng: 149.0571767}
- { name: Baldwin Drive,stop_code: Wjz6iYm, lat: -35.2298806, lng: 149.0944438} - { name: Baldwin Drive,stop_code: Wjz6iYm, lat: -35.2298806, lng: 149.0944438}
- { name: Baldwin Drive,stop_code: Wjz6iYk, lat: -35.2300583, lng: 149.0945448} - { name: Baldwin Drive,stop_code: Wjz6iYk, lat: -35.2300583, lng: 149.0945448}
- { name: Athllon Drive,stop_code: Wjz239F, lat: -35.4026063, lng: 149.0647649} - { name: Athllon Drive,stop_code: Wjz239F, lat: -35.4026063, lng: 149.0647649}
- { name: Anketell Street,stop_code: Wjz213w, lat: -35.4123171, lng: 149.0633299} - { name: Anketell Street,stop_code: Wjz213w, lat: -35.4123171, lng: 149.0633299}
- { name: Anketell Street,stop_code: Wjz20ut, lat: -35.415325, lng: 149.0672593} - { name: Anketell Street,stop_code: Wjz20ut, lat: -35.415325, lng: 149.0672593}
- { name: Athllon Drive,stop_code: Wjz3hL_, lat: -35.3650156, lng: 149.0926464} - { name: Athllon Drive,stop_code: Wjz3hL_, lat: -35.3650156, lng: 149.0926464}
- { name: Athllon Drive,stop_code: Wjz3gQn, lat: -35.3725942, lng: 149.0931105} - { name: Athllon Drive,stop_code: Wjz3gQn, lat: -35.3725942, lng: 149.0931105}
- { name: Athllon Drive,stop_code: Wjz3gMq, lat: -35.3757982, lng: 149.0932419} - { name: Athllon Drive,stop_code: Wjz3gMq, lat: -35.3757982, lng: 149.0932419}
- { name: Athllon Drive,stop_code: Wjz238T, lat: -35.4027681, lng: 149.0650277} - { name: Athllon Drive,stop_code: Wjz238T, lat: -35.4027681, lng: 149.0650277}
- { name: Athllon Drive,stop_code: Wjz3kwU, lat: -35.3539843, lng: 149.0913052} - { name: Athllon Drive,stop_code: Wjz3kwU, lat: -35.3539843, lng: 149.0913052}
- { name: Neales Street,stop_code: Wjz6rp1, lat: -35.2268254, lng: 149.0996755} - { name: Neales Street,stop_code: Wjz6rp1, lat: -35.2268254, lng: 149.0996755}
- { name: Neales Street,stop_code: Wjz6rhW, lat: -35.2267553, lng: 149.0994502} - { name: Neales Street,stop_code: Wjz6rhW, lat: -35.2267553, lng: 149.0994502}
- { name: Maribyrnong Avenue,stop_code: Wjz6qea, lat: -35.2288148, lng: 149.0970523} - { name: Maribyrnong Avenue,stop_code: Wjz6qea, lat: -35.2288148, lng: 149.0970523}
- { name: Marcus Clarke Street,stop_code: Wjz5GNG, lat: -35.2762093, lng: 149.1265723} - { name: Marcus Clarke Street,stop_code: Wjz5GNG, lat: -35.2762093, lng: 149.1265723}
- { name: Liversidge Street,stop_code: Wjz5E4O, lat: -35.2851023, lng: 149.1186022} - { name: Liversidge Street,stop_code: Wjz5E4O, lat: -35.2851023, lng: 149.1186022}
- { name: McDonald Place,stop_code: Wjz5w_S, lat: -35.2827048, lng: 149.117182} - { name: McDonald Place,stop_code: Wjz5w_S, lat: -35.2827048, lng: 149.117182}
- { name: Bowes Street,stop_code: Wjz3leq, lat: -35.344135, lng: 149.0864401} - { name: Bowes Street,stop_code: Wjz3leq, lat: -35.344135, lng: 149.0864401}
- { name: Bradley Street,stop_code: Wjz3ldj, lat: -35.3447574, lng: 149.0862912} - { name: Bradley Street,stop_code: Wjz3ldj, lat: -35.3447574, lng: 149.0862912}
- { name: Bradley Street,stop_code: Wjz3ldh, lat: -35.3449697, lng: 149.0863328} - { name: Bradley Street,stop_code: Wjz3ldh, lat: -35.3449697, lng: 149.0863328}
- { name: Pitman,stop_code: Wjz20ni, lat: -35.4149428, lng: 149.0656523} - { name: Pitman,stop_code: Wjz20ni, lat: -35.4149428, lng: 149.0656523}
- { name: Pitman,stop_code: Wjz20nk, lat: -35.4147569, lng: 149.0657435} - { name: Pitman,stop_code: Wjz20nk, lat: -35.4147569, lng: 149.0657435}
- { name: Callam Street,stop_code: Wjz3lmt, lat: -35.3439501, lng: 149.0877369} - { name: Callam Street,stop_code: Wjz3lmt, lat: -35.3439501, lng: 149.0877369}
- { name: Bowes Street,stop_code: Wjz3ldC, lat: -35.344484, lng: 149.0866144} - { name: Bowes Street,stop_code: Wjz3ldC, lat: -35.344484, lng: 149.0866144}
- { name: Bradley Street,stop_code: Wjz3lm0, lat: -35.34438, lng: 149.0872661} - { name: Bradley Street,stop_code: Wjz3lm0, lat: -35.34438, lng: 149.0872661}
- { name: Bradley Street,stop_code: Wjz3ll7, lat: -35.3444741, lng: 149.0873533} - { name: Bradley Street,stop_code: Wjz3ll7, lat: -35.3444741, lng: 149.0873533}
- { name: Bowes Street,stop_code: Wjz3lml, lat: -35.3439129, lng: 149.0876216} - { name: Bowes Street,stop_code: Wjz3lml, lat: -35.3439129, lng: 149.0876216}
- { name: Callam Street,stop_code: Wjz3lmi, lat: -35.3442093, lng: 149.0876443} - { name: Callam Street,stop_code: Wjz3lmi, lat: -35.3442093, lng: 149.0876443}
- { name: Bowes Street,stop_code: Wjz3leo, lat: -35.344368, lng: 149.0864991} - { name: Bowes Street,stop_code: Wjz3leo, lat: -35.344368, lng: 149.0864991}
- { name: Callam Street,stop_code: Wjz3lmq, lat: -35.3442083, lng: 149.0877771} - { name: Callam Street,stop_code: Wjz3lmq, lat: -35.3442083, lng: 149.0877771}
- { name: Eileen Good Street,stop_code: Wjz21g2, lat: -35.414217, lng: 149.0653492} - { name: Eileen Good Street,stop_code: Wjz21g2, lat: -35.414217, lng: 149.0653492}
- { name: Cohen Street,stop_code: Wjr-UJ-, lat: -35.240121, lng: 149.0597101} - { name: Cohen Street,stop_code: Wjr-UJ-, lat: -35.240121, lng: 149.0597101}
- { name: Pitman,stop_code: Wjz20nd, lat: -35.4146761, lng: 149.0654565} - { name: Pitman,stop_code: Wjz20nd, lat: -35.4146761, lng: 149.0654565}
- { name: Cohen Street,stop_code: Wjr-USa, lat: -35.2398454, lng: 149.0600442} - { name: Cohen Street,stop_code: Wjr-USa, lat: -35.2398454, lng: 149.0600442}
- { name: David Walsh Avenue,stop_code: Wjz7YzW, lat: -35.1759253, lng: 149.1462691} - { name: David Walsh Avenue,stop_code: Wjz7YzW, lat: -35.1759253, lng: 149.1462691}
- { name: Kathner Street,stop_code: WjrXBWn, lat: -35.3465295, lng: 149.0286032} - { name: Kathner Street,stop_code: WjrXBWn, lat: -35.3465295, lng: 149.0286032}
- { name: Cohen Street,stop_code: Wjr-USy, lat: -35.2397639, lng: 149.0604531} - { name: Cohen Street,stop_code: Wjr-USy, lat: -35.2397639, lng: 149.0604531}
- { name: Rene Street,stop_code: WjrXHvw, lat: -35.3546272, lng: 149.0344542} - { name: Rene Street,stop_code: WjrXHvw, lat: -35.3546272, lng: 149.0344542}
- { name: Perry Drive,stop_code: WjrXPbD, lat: -35.356823, lng: 149.0426424} - { name: Perry Drive,stop_code: WjrXPbD, lat: -35.356823, lng: 149.0426424}
- { name: Darwinia Terrace,stop_code: WjrXIbT, lat: -35.351342, lng: 149.0321099} - { name: Darwinia Terrace,stop_code: WjrXIbT, lat: -35.351342, lng: 149.0321099}
- { name: Darwinia Terrace,stop_code: WjrXIqp, lat: -35.352473, lng: 149.0342718} - { name: Darwinia Terrace,stop_code: WjrXIqp, lat: -35.352473, lng: 149.0342718}
- { name: Rafferty Street,stop_code: WjrXIbK, lat: -35.3514081, lng: 149.0319332} - { name: Rafferty Street,stop_code: WjrXIbK, lat: -35.3514081, lng: 149.0319332}
- { name: Kathner Street,stop_code: WjrXBWu, lat: -35.3466197, lng: 149.0287455} - { name: Kathner Street,stop_code: WjrXBWu, lat: -35.3466197, lng: 149.0287455}
- { name: Darwinia Terrace,stop_code: WjrXI5u, lat: -35.3499839, lng: 149.0301495} - { name: Darwinia Terrace,stop_code: WjrXI5u, lat: -35.3499839, lng: 149.0301495}
- { name: Rene Street,stop_code: WjrXHuL, lat: -35.3547054, lng: 149.0346008} - { name: Rene Street,stop_code: WjrXHuL, lat: -35.3547054, lng: 149.0346008}
- { name: Musgrove street,stop_code: WjrXHH7, lat: -35.3568349, lng: 149.0364585} - { name: Musgrove street,stop_code: WjrXHH7, lat: -35.3568349, lng: 149.0364585}
- { name: Musgrove street,stop_code: WjrXHHk, lat: -35.3570187, lng: 149.0369096} - { name: Musgrove street,stop_code: WjrXHHk, lat: -35.3570187, lng: 149.0369096}
- { name: Perry Drive,stop_code: WjrXHYJ, lat: -35.356246, lng: 149.0401055} - { name: Perry Drive,stop_code: WjrXHYJ, lat: -35.356246, lng: 149.0401055}
- { name: Bertel Crescent,stop_code: WjrXPgO, lat: -35.3592839, lng: 149.0444246} - { name: Bertel Crescent,stop_code: WjrXPgO, lat: -35.3592839, lng: 149.0444246}
- { name: Namatjira Drive,stop_code: WjrXPFr, lat: -35.3585046, lng: 149.0479415} - { name: Namatjira Drive,stop_code: WjrXPFr, lat: -35.3585046, lng: 149.0479415}
- { name: Namatjira Drive,stop_code: WjrXPFn, lat: -35.358206, lng: 149.0478792} - { name: Namatjira Drive,stop_code: WjrXPFn, lat: -35.358206, lng: 149.0478792}
- { name: Streeton Drive,stop_code: WjrXPJX, lat: -35.3557253, lng: 149.0486263} - { name: Streeton Drive,stop_code: WjrXPJX, lat: -35.3557253, lng: 149.0486263}
- { name: Fremantle Drive,stop_code: WjrXQO9, lat: -35.352521, lng: 149.0490119} - { name: Fremantle Drive,stop_code: WjrXQO9, lat: -35.352521, lng: 149.0490119}
- { name: Bunbury Street,stop_code: WjrXQTq, lat: -35.348941, lng: 149.0494159} - { name: Bunbury Street,stop_code: WjrXQTq, lat: -35.348941, lng: 149.0494159}
- { name: Bunbury Street,stop_code: WjrXQTy, lat: -35.3489683, lng: 149.0495709} - { name: Bunbury Street,stop_code: WjrXQTy, lat: -35.3489683, lng: 149.0495709}
- { name: McKail Crescent,stop_code: WjrXRFB, lat: -35.3473864, lng: 149.048202} - { name: McKail Crescent,stop_code: WjrXRFB, lat: -35.3473864, lng: 149.048202}
- { name: McKail Crescent,stop_code: WjrXRyK, lat: -35.3465911, lng: 149.0470392} - { name: McKail Crescent,stop_code: WjrXRyK, lat: -35.3465911, lng: 149.0470392}
- { name: Streeton Drive,stop_code: WjrXRBQ, lat: -35.3446963, lng: 149.0471083} - { name: Streeton Drive,stop_code: WjrXRBQ, lat: -35.3446963, lng: 149.0471083}
- { name: Streeton Drive,stop_code: WjrXRBJ, lat: -35.344588, lng: 149.0469995} - { name: Streeton Drive,stop_code: WjrXRBJ, lat: -35.344588, lng: 149.0469995}
- { name: Whitney Place,stop_code: WjrX-90, lat: -35.3423165, lng: 149.0529937} - { name: Whitney Place,stop_code: WjrX-90, lat: -35.3423165, lng: 149.0529937}
- { name: Parkinson Street,stop_code: WjrXZv3, lat: -35.3434037, lng: 149.0557375} - { name: Parkinson Street,stop_code: WjrXZv3, lat: -35.3434037, lng: 149.0557375}
- { name: Corinna Street,stop_code: Wjz3dXS, lat: -35.3459117, lng: 149.0842511} - { name: Corinna Street,stop_code: Wjz3dXS, lat: -35.3459117, lng: 149.0842511}
- { name: Clode Crescent,stop_code: Wjr-uhM, lat: -35.2104818, lng: 149.0114129} - { name: Clode Crescent,stop_code: Wjr-uhM, lat: -35.2104818, lng: 149.0114129}
- { name: Gilmore Crescent,stop_code: Wjz3Bea, lat: -35.3442178, lng: 149.1080098} - { name: Gilmore Crescent,stop_code: Wjz3Bea, lat: -35.3442178, lng: 149.1080098}
- { name: Gonzaga Place,stop_code: Wjz2wGU, lat: -35.4184904, lng: 149.1145873} - { name: Gonzaga Place,stop_code: Wjz2wGU, lat: -35.4184904, lng: 149.1145873}
- { name: Rischbieth Crescent,stop_code: Wjz2MYC, lat: -35.4166279, lng: 149.1388559} - { name: Rischbieth Crescent,stop_code: Wjz2MYC, lat: -35.4166279, lng: 149.1388559}
- { name: Penton Place,stop_code: Wjz2Npv, lat: -35.4131394, lng: 149.1331606} - { name: Penton Place,stop_code: Wjz2Npv, lat: -35.4131394, lng: 149.1331606}
- { name: Carruthers Street,stop_code: Wjz4h1X, lat: -35.3255489, lng: 149.0857143} - { name: Carruthers Street,stop_code: Wjz4h1X, lat: -35.3255489, lng: 149.0857143}
- { name: Bunny Street,stop_code: WjrX_SL, lat: -35.3327937, lng: 149.0607695} - { name: Bunny Street,stop_code: WjrX_SL, lat: -35.3327937, lng: 149.0607695}
- { name: Davenport Street,stop_code: Wjz37Zc, lat: -35.3337407, lng: 149.0723488} - { name: Davenport Street,stop_code: Wjz37Zc, lat: -35.3337407, lng: 149.0723488}
- { name: Isabella Drive,stop_code: Wjz1nzY, lat: -35.4229506, lng: 149.0912343} - { name: Isabella Drive,stop_code: Wjz1nzY, lat: -35.4229506, lng: 149.0912343}
- { name: Lake Tuggeranong cycle track,stop_code: Wjz20Vv, lat: -35.4185754, lng: 149.072661} - { name: Lake Tuggeranong cycle track,stop_code: Wjz20Vv, lat: -35.4185754, lng: 149.072661}
- { name: Taverner Street,stop_code: Wjz2b8J, lat: -35.4029944, lng: 149.0757807} - { name: Taverner Street,stop_code: Wjz2b8J, lat: -35.4029944, lng: 149.0757807}
- { name: Nunan Crescent,stop_code: Wjz29-5, lat: -35.4098244, lng: 149.083123} - { name: Nunan Crescent,stop_code: Wjz29-5, lat: -35.4098244, lng: 149.083123}
- { name: Laurens Street,stop_code: Wjz2i3o, lat: -35.4068322, lng: 149.0850166} - { name: Laurens Street,stop_code: Wjz2i3o, lat: -35.4068322, lng: 149.0850166}
- { name: Taverner Street,stop_code: Wjz2aGG, lat: -35.4073408, lng: 149.0812511} - { name: Taverner Street,stop_code: Wjz2aGG, lat: -35.4073408, lng: 149.0812511}
- { name: Taverner Street,stop_code: Wjz2azE, lat: -35.4068027, lng: 149.0799162} - { name: Taverner Street,stop_code: Wjz2azE, lat: -35.4068027, lng: 149.0799162}
- { name: Clutterbuck Crescent,stop_code: Wjz2arg, lat: -35.4068086, lng: 149.0779936} - { name: Clutterbuck Crescent,stop_code: Wjz2arg, lat: -35.4068086, lng: 149.0779936}
- { name: Connibere Crescent,stop_code: Wjz2aaw, lat: -35.4075241, lng: 149.0756429} - { name: Connibere Crescent,stop_code: Wjz2aaw, lat: -35.4075241, lng: 149.0756429}
- { name: Singleton Crescent,stop_code: Wjz29ea, lat: -35.4101319, lng: 149.0751278} - { name: Singleton Crescent,stop_code: Wjz29ea, lat: -35.4101319, lng: 149.0751278}
- { name: Maconochie Crescent,stop_code: Wjz29yh, lat: -35.4129642, lng: 149.0794301} - { name: Maconochie Crescent,stop_code: Wjz29yh, lat: -35.4129642, lng: 149.0794301}
- { name: Checchi Place,stop_code: Wjz28Yv, lat: -35.4165651, lng: 149.0836163} - { name: Checchi Place,stop_code: Wjz28Yv, lat: -35.4165651, lng: 149.0836163}
- { name: Forwood Street,stop_code: Wjz2haF, lat: -35.4129406, lng: 149.0867361} - { name: Forwood Street,stop_code: Wjz2haF, lat: -35.4129406, lng: 149.0867361}
- { name: Harricks Crescent,stop_code: Wjz2hlp, lat: -35.4109006, lng: 149.0878896} - { name: Harricks Crescent,stop_code: Wjz2hlp, lat: -35.4109006, lng: 149.0878896}
- { name: Michell Street,stop_code: Wjz2hBQ, lat: -35.4106404, lng: 149.0911182} - { name: Michell Street,stop_code: Wjz2hBQ, lat: -35.4106404, lng: 149.0911182}
- { name: Beirne Street,stop_code: Wjz2iEO, lat: -35.40876, lng: 149.0925039} - { name: Beirne Street,stop_code: Wjz2iEO, lat: -35.40876, lng: 149.0925039}
- { name: Amsinck Street,stop_code: Wjz2iPv, lat: -35.4062172, lng: 149.093302} - { name: Amsinck Street,stop_code: Wjz2iPv, lat: -35.4062172, lng: 149.093302}
- { name: Mackinnon Street,stop_code: Wjz2izK, lat: -35.4062764, lng: 149.0909078} - { name: Mackinnon Street,stop_code: Wjz2izK, lat: -35.4062764, lng: 149.0909078}
- { name: Tuggeranong Parkway,stop_code: Wjz34Gq, lat: -35.352423, lng: 149.0699271} - { name: Tuggeranong Parkway,stop_code: Wjz34Gq, lat: -35.352423, lng: 149.0699271}
- { name: Tuggeranong Parkway Onramp,stop_code: Wjz33LB, lat: -35.3542352, lng: 149.0701992} - { name: Tuggeranong Parkway Onramp,stop_code: Wjz33LB, lat: -35.3542352, lng: 149.0701992}
- { name: Tuggeranong Parkway,stop_code: Wjz33EK, lat: -35.3589689, lng: 149.0702445} - { name: Tuggeranong Parkway,stop_code: Wjz33EK, lat: -35.3589689, lng: 149.0702445}
- { name: Yambina Crescent,stop_code: WjrXXMe, lat: -35.3589023, lng: 149.0599784} - { name: Yambina Crescent,stop_code: WjrXXMe, lat: -35.3589023, lng: 149.0599784}
- { name: Araluen Street,stop_code: WjrXWsn, lat: -35.3616093, lng: 149.055979} - { name: Araluen Street,stop_code: WjrXWsn, lat: -35.3616093, lng: 149.055979}
- { name: Guinness Place,stop_code: WjrXGDF, lat: -35.3600413, lng: 149.0360091} - { name: Guinness Place,stop_code: WjrXGDF, lat: -35.3600413, lng: 149.0360091}
- { name: Gulgong Place,stop_code: WjrXXb4, lat: -35.3570754, lng: 149.0530316} - { name: Gulgong Place,stop_code: WjrXXb4, lat: -35.3570754, lng: 149.0530316}
- { name: Larakia Street,stop_code: Wjz34c4, lat: -35.3508697, lng: 149.0639869} - { name: Larakia Street,stop_code: Wjz34c4, lat: -35.3508697, lng: 149.0639869}
- { name: Cedrela Place,stop_code: WjrXR3f, lat: -35.3458397, lng: 149.040861} - { name: Cedrela Place,stop_code: WjrXR3f, lat: -35.3458397, lng: 149.040861}
- { name: Blowering Street,stop_code: WjrXLtK, lat: -35.3335671, lng: 149.0346289} - { name: Blowering Street,stop_code: WjrXLtK, lat: -35.3335671, lng: 149.0346289}
- { name: Mt Taylor Zig Zag,stop_code: Wjz39sA, lat: -35.3673329, lng: 149.0783636} - { name: Mt Taylor Zig Zag,stop_code: Wjz39sA, lat: -35.3673329, lng: 149.0783636}
- { name: Beasley Street,stop_code: Wjz3hu6, lat: -35.3658261, lng: 149.0887408} - { name: Beasley Street,stop_code: Wjz3hu6, lat: -35.3658261, lng: 149.0887408}
- { name: Marr Street,stop_code: Wjz3iuk, lat: -35.3604697, lng: 149.0889561} - { name: Marr Street,stop_code: Wjz3iuk, lat: -35.3604697, lng: 149.0889561}
- { name: Catalina Drive,stop_code: Wjzcuop, lat: -35.2989647, lng: 149.1881172} - { name: Catalina Drive,stop_code: Wjzcuop, lat: -35.2989647, lng: 149.1881172}
- { name: Laverton Avenue,stop_code: WjzcJ38, lat: -35.3024713, lng: 149.2056109} - { name: Laverton Avenue,stop_code: WjzcJ38, lat: -35.3024713, lng: 149.2056109}
- { name: Horse Park Drive,stop_code: Wjz7smv, lat: -35.1734671, lng: 149.0988597} - { name: Horse Park Drive,stop_code: Wjz7smv, lat: -35.1734671, lng: 149.0988597}
- { name: Kerrigan Street,stop_code: Wjr_xY9, lat: -35.1918291, lng: 149.028508} - { name: Kerrigan Street,stop_code: Wjr_xY9, lat: -35.1918291, lng: 149.028508}
- { name: Yabsley Place,stop_code: Wjr_Ej0, lat: -35.1981116, lng: 149.0323079} - { name: Yabsley Place,stop_code: Wjr_Ej0, lat: -35.1981116, lng: 149.0323079}
- { name: Rogers Street,stop_code: Wjr_GVA, lat: -35.188117, lng: 149.0399446} - { name: Rogers Street,stop_code: Wjr_GVA, lat: -35.188117, lng: 149.0399446}
- { name: Foskett Street,stop_code: Wjr_N-q, lat: -35.1903433, lng: 149.0507803} - { name: Foskett Street,stop_code: Wjr_N-q, lat: -35.1903433, lng: 149.0507803}
- { name: Nott Street,stop_code: Wjr_NpJ, lat: -35.1935127, lng: 149.0455536} - { name: Nott Street,stop_code: Wjr_NpJ, lat: -35.1935127, lng: 149.0455536}
- { name: Osburn Drive,stop_code: Wjr-uUL, lat: -35.210513, lng: 149.0180445} - { name: Osburn Drive,stop_code: Wjr-uUL, lat: -35.210513, lng: 149.0180445}
- { name: Commonwealth Avenue,stop_code: Wjz4KO9, lat: -35.2975962, lng: 149.1259252} - { name: Commonwealth Avenue,stop_code: Wjz4KO9, lat: -35.2975962, lng: 149.1259252}
- { name: Cowper Street,stop_code: Wjz5_0v, lat: -35.2490065, lng: 149.1400861} - { name: Cowper Street,stop_code: Wjz5_0v, lat: -35.2490065, lng: 149.1400861}
- { name: Captain Cook Crescent,stop_code: Wjz4NDo, lat: -35.3217168, lng: 149.1344712} - { name: Captain Cook Crescent,stop_code: Wjz4NDo, lat: -35.3217168, lng: 149.1344712}
- { name: Marcus Clarke Street,stop_code: Wjz5FIS, lat: -35.279312, lng: 149.1254166} - { name: Marcus Clarke Street,stop_code: Wjz5FIS, lat: -35.279312, lng: 149.1254166}
- { name: Keenan Street,stop_code: Wjz66kG, lat: -35.2081931, lng: 149.0662542} - { name: Keenan Street,stop_code: Wjz66kG, lat: -35.2081931, lng: 149.0662542}
- { name: Moor Place,stop_code: Wjz66t3, lat: -35.2074684, lng: 149.0667796} - { name: Moor Place,stop_code: Wjz66t3, lat: -35.2074684, lng: 149.0667796}
- { name: Connah Street,stop_code: Wjr-Xhh, lat: -35.2268712, lng: 149.0546156} - { name: Connah Street,stop_code: Wjr-Xhh, lat: -35.2268712, lng: 149.0546156}
- { name: Linger Place,stop_code: Wjr--r_, lat: -35.2084885, lng: 149.0569758} - { name: Linger Place,stop_code: Wjr--r_, lat: -35.2084885, lng: 149.0569758}
- { name: Russell Drive,stop_code: Wjzc55s, lat: -35.3007195, lng: 149.1509863} - { name: Russell Drive,stop_code: Wjzc55s, lat: -35.3007195, lng: 149.1509863}
- { name: Reg Saunders Way,stop_code: Wjz4-YV, lat: -35.2961803, lng: 149.1503194} - { name: Reg Saunders Way,stop_code: Wjz4-YV, lat: -35.2961803, lng: 149.1503194}
- { name: Russell Drive,stop_code: Wjzc60i, lat: -35.2988201, lng: 149.1508684} - { name: Russell Drive,stop_code: Wjzc60i, lat: -35.2988201, lng: 149.1508684}
- { name: National Circuit,stop_code: Wjz4Quk, lat: -35.3055692, lng: 149.1330442} - { name: National Circuit,stop_code: Wjz4Quk, lat: -35.3055692, lng: 149.1330442}
- { name: Melrose Drive,stop_code: Wjz3eZ4, lat: -35.3392098, lng: 149.0831308} - { name: Melrose Drive,stop_code: Wjz3eZ4, lat: -35.3392098, lng: 149.0831308}
- { name: Russell Drive,stop_code: Wjz4-KO, lat: -35.2946955, lng: 149.147399} - { name: Russell Drive,stop_code: Wjz4-KO, lat: -35.2946955, lng: 149.147399}
- { name: Chifley Place,stop_code: Wjz3caw, lat: -35.3525528, lng: 149.0755688} - { name: Chifley Place,stop_code: Wjz3caw, lat: -35.3525528, lng: 149.0755688}
- { name: Carslaw Street,stop_code: Wjz3ceY, lat: -35.3495185, lng: 149.0761236} - { name: Carslaw Street,stop_code: Wjz3ceY, lat: -35.3495185, lng: 149.0761236}
- { name: Threlfall Street,stop_code: Wjz3b9L, lat: -35.3581358, lng: 149.0757975} - { name: Threlfall Street,stop_code: Wjz3b9L, lat: -35.3581358, lng: 149.0757975}
- { name: Boult Place,stop_code: Wjr-SHc, lat: -35.2086969, lng: 149.0476925} - { name: Boult Place,stop_code: Wjr-SHc, lat: -35.2086969, lng: 149.0476925}
- { name: Conley Drive,stop_code: Wjr-RZE, lat: -35.2132014, lng: 149.0511677} - { name: Conley Drive,stop_code: Wjr-RZE, lat: -35.2132014, lng: 149.0511677}
- { name: Grainger Circuit,stop_code: Wjr-R_3, lat: -35.2115401, lng: 149.0502887} - { name: Grainger Circuit,stop_code: Wjr-R_3, lat: -35.2115401, lng: 149.0502887}
- { name: Verbrugghen Street,stop_code: Wjr-ZBY, lat: -35.2128526, lng: 149.0583185} - { name: Verbrugghen Street,stop_code: Wjr-ZBY, lat: -35.2128526, lng: 149.0583185}
- { name: Copland Drive,stop_code: Wjr-ZSE, lat: -35.2124829, lng: 149.0606716} - { name: Copland Drive,stop_code: Wjr-ZSE, lat: -35.2124829, lng: 149.0606716}
- { name: Linger Place,stop_code: Wjr--sV, lat: -35.2083253, lng: 149.0568878} - { name: Linger Place,stop_code: Wjr--sV, lat: -35.2083253, lng: 149.0568878}
- { name: Bishop Place,stop_code: Wjr--m3, lat: -35.2067416, lng: 149.0543264} - { name: Bishop Place,stop_code: Wjr--m3, lat: -35.2067416, lng: 149.0543264}
- { name: Henslowe Place,stop_code: Wjr--6t, lat: -35.2065912, lng: 149.0521439} - { name: Henslowe Place,stop_code: Wjr--6t, lat: -35.2065912, lng: 149.0521439}
- { name: Lathlain Street,stop_code: Wjz605N, lat: -35.2405467, lng: 149.0636668} - { name: Lathlain Street,stop_code: Wjz605N, lat: -35.2405467, lng: 149.0636668}
- { name: Lathlain Street,stop_code: Wjz604Y, lat: -35.2410486, lng: 149.0638326} - { name: Lathlain Street,stop_code: Wjz604Y, lat: -35.2410486, lng: 149.0638326}
- { name: Daley Road,stop_code: Wjz5xHC, lat: -35.2799871, lng: 149.1141335} - { name: Daley Road,stop_code: Wjz5xHC, lat: -35.2799871, lng: 149.1141335}
- { name: Macarthur Avenue,stop_code: Wjz5J9d, lat: -35.2594616, lng: 149.1190821} - { name: Macarthur Avenue,stop_code: Wjz5J9d, lat: -35.2594616, lng: 149.1190821}
- { name: Bainton Crescent,stop_code: Wjr-_kG, lat: -35.2027328, lng: 149.0551853} - { name: Bainton Crescent,stop_code: Wjr-_kG, lat: -35.2027328, lng: 149.0551853}
- { name: Alpen Street,stop_code: Wjr-_Nn, lat: -35.2043934, lng: 149.0601598} - { name: Alpen Street,stop_code: Wjr-_Nn, lat: -35.2043934, lng: 149.0601598}
- { name: Broadby Close,stop_code: Wjz671V, lat: -35.204864, lng: 149.0637204} - { name: Broadby Close,stop_code: Wjz671V, lat: -35.204864, lng: 149.0637204}
- { name: Kingsford Smith Drive,stop_code: Wjr-_zv, lat: -35.2030129, lng: 149.0575605} - { name: Kingsford Smith Drive,stop_code: Wjr-_zv, lat: -35.2030129, lng: 149.0575605}
- { name: John Cleland Crescent,stop_code: Wjr-Yg7, lat: -35.2215188, lng: 149.0543538} - { name: John Cleland Crescent,stop_code: Wjr-Yg7, lat: -35.2215188, lng: 149.0543538}
- { name: John Cleland Crescent,stop_code: Wjr-XyN, lat: -35.226202, lng: 149.0581637} - { name: John Cleland Crescent,stop_code: Wjr-XyN, lat: -35.226202, lng: 149.0581637}
- { name: Wiseman Street,stop_code: Wjz56Xu, lat: -35.2524925, lng: 149.0726439} - { name: Wiseman Street,stop_code: Wjz56Xu, lat: -35.2524925, lng: 149.0726439}
- { name: Fulton Street,stop_code: Wjz571j, lat: -35.2486364, lng: 149.0628845} - { name: Fulton Street,stop_code: Wjz571j, lat: -35.2486364, lng: 149.0628845}
- { name: Launceston Street,stop_code: Wjz3m3b, lat: -35.3406241, lng: 149.0847703} - { name: Launceston Street,stop_code: Wjz3m3b, lat: -35.3406241, lng: 149.0847703}
- { name: O'Hanlon Place,stop_code: Wjz79ZQ, lat: -35.190906, lng: 149.0842116} - { name: O'Hanlon Place,stop_code: Wjz79ZQ, lat: -35.190906, lng: 149.0842116}
- { name: O'Hanlon Place,stop_code: Wjz7hb5, lat: -35.1921368, lng: 149.0859491} - { name: O'Hanlon Place,stop_code: Wjz7hb5, lat: -35.1921368, lng: 149.0859491}
- { name: O'Hanlon Place,stop_code: Wjz7hbe, lat: -35.1921183, lng: 149.0860955} - { name: O'Hanlon Place,stop_code: Wjz7hbe, lat: -35.1921183, lng: 149.0860955}
- { name: Jalanga Crescent,stop_code: Wjz5dCr, lat: -35.2561978, lng: 149.0795805} - { name: Jalanga Crescent,stop_code: Wjz5dCr, lat: -35.2561978, lng: 149.0795805}
- { name: Lyttleton Crescent,stop_code: Wjz54_B, lat: -35.2608235, lng: 149.0728514} - { name: Lyttleton Crescent,stop_code: Wjz54_B, lat: -35.2608235, lng: 149.0728514}
- { name: Lyttleton Crescent,stop_code: Wjz54_n, lat: -35.2606623, lng: 149.072551} - { name: Lyttleton Crescent,stop_code: Wjz54_n, lat: -35.2606623, lng: 149.072551}
- { name: Cambridge Street,stop_code: Wjz54CS, lat: -35.2614333, lng: 149.0690577} - { name: Cambridge Street,stop_code: Wjz54CS, lat: -35.2614333, lng: 149.0690577}
- { name: Templeton Street,stop_code: Wjz551Q, lat: -35.2595831, lng: 149.0636761} - { name: Templeton Street,stop_code: Wjz551Q, lat: -35.2595831, lng: 149.0636761}
- { name: Templeton Street,stop_code: Wjz5592, lat: -35.2596812, lng: 149.0639679} - { name: Templeton Street,stop_code: Wjz5592, lat: -35.2596812, lng: 149.0639679}
- { name: Redfern Street,stop_code: WjrZZB7, lat: -35.2565133, lng: 149.0570071} - { name: Redfern Street,stop_code: WjrZZB7, lat: -35.2565133, lng: 149.0570071}
- { name: Coulter Drive,stop_code: WjrZ_o2, lat: -35.2493991, lng: 149.055711} - { name: Coulter Drive,stop_code: WjrZ_o2, lat: -35.2493991, lng: 149.055711}
- { name: Coulter Drive,stop_code: WjrZ_o4, lat: -35.2492379, lng: 149.0556338} - { name: Coulter Drive,stop_code: WjrZ_o4, lat: -35.2492379, lng: 149.0556338}
- { name: Weetangera Place,stop_code: WjrZTMv, lat: -35.2489575, lng: 149.0493939} - { name: Weetangera Place,stop_code: WjrZTMv, lat: -35.2489575, lng: 149.0493939}
- { name: Gillespie Street,stop_code: WjrZTua, lat: -35.2452775, lng: 149.0448362} - { name: Gillespie Street,stop_code: WjrZTua, lat: -35.2452775, lng: 149.0448362}
- { name: Gillespie Street,stop_code: WjrZTu1, lat: -35.2453967, lng: 149.044759} - { name: Gillespie Street,stop_code: WjrZTu1, lat: -35.2453967, lng: 149.044759}
- { name: Hawker Place,stop_code: Wjr-Mg6, lat: -35.2436162, lng: 149.0432913} - { name: Hawker Place,stop_code: Wjr-Mg6, lat: -35.2436162, lng: 149.0432913}
- { name: Hawker Place,stop_code: Wjr-Mgt, lat: -35.2436863, lng: 149.0438835} - { name: Hawker Place,stop_code: Wjr-Mgt, lat: -35.2436863, lng: 149.0438835}
- { name: Murranji Street,stop_code: WjrZT5e, lat: -35.245649, lng: 149.0408365} - { name: Murranji Street,stop_code: WjrZT5e, lat: -35.245649, lng: 149.0408365}
- { name: Erldunda Circuit,stop_code: WjrZLXY, lat: -35.2471491, lng: 149.0403988} - { name: Erldunda Circuit,stop_code: WjrZLXY, lat: -35.2471491, lng: 149.0403988}
- { name: Murranji Street,stop_code: WjrZT6b, lat: -35.2452004, lng: 149.0407936} - { name: Murranji Street,stop_code: WjrZT6b, lat: -35.2452004, lng: 149.0407936}
- { name: Wisdom Street,stop_code: Wjz3mI-, lat: -35.3396854, lng: 149.092654} - { name: Wisdom Street,stop_code: Wjz3mI-, lat: -35.3396854, lng: 149.092654}
- { name: Hardwick Crescent,stop_code: Wjr-z7J, lat: -35.2223574, lng: 149.0195037} - { name: Hardwick Crescent,stop_code: Wjr-z7J, lat: -35.2223574, lng: 149.0195037}
- { name: Ligertwood Street,stop_code: Wjz65aB, lat: -35.2148653, lng: 149.0646456} - { name: Ligertwood Street,stop_code: Wjz65aB, lat: -35.2148653, lng: 149.0646456}
- { name: Alderman Street,stop_code: Wjz65rQ, lat: -35.2142653, lng: 149.0676927} - { name: Alderman Street,stop_code: Wjz65rQ, lat: -35.2142653, lng: 149.0676927}
- { name: Hatfield Street,stop_code: Wjz66oJ, lat: -35.2107077, lng: 149.0674989} - { name: Hatfield Street,stop_code: Wjz66oJ, lat: -35.2107077, lng: 149.0674989}
- { name: Clancy Street,stop_code: Wjz66WS, lat: -35.2092634, lng: 149.0731992} - { name: Clancy Street,stop_code: Wjz66WS, lat: -35.2092634, lng: 149.0731992}
- { name: Marconi Crescent,stop_code: WjrW_zu, lat: -35.3788924, lng: 149.0576496} - { name: Marconi Crescent,stop_code: WjrW_zu, lat: -35.3788924, lng: 149.0576496}
- { name: Marconi Crescent,stop_code: WjrW_RH, lat: -35.3777568, lng: 149.0607135} - { name: Marconi Crescent,stop_code: WjrW_RH, lat: -35.3777568, lng: 149.0607135}
- { name: Marconi Crescent,stop_code: Wjz27k0, lat: -35.3786939, lng: 149.0653235} - { name: Marconi Crescent,stop_code: Wjz27k0, lat: -35.3786939, lng: 149.0653235}
- { name: Lascelles Circuit,stop_code: Wjz27gg, lat: -35.3814094, lng: 149.0656219} - { name: Lascelles Circuit,stop_code: Wjz27gg, lat: -35.3814094, lng: 149.0656219}
- { name: Summerland Circuit,stop_code: Wjz26tw, lat: -35.38347, lng: 149.0674733} - { name: Summerland Circuit,stop_code: Wjz26tw, lat: -35.38347, lng: 149.0674733}
- { name: Mason Street,stop_code: Wjz26WW, lat: -35.3853577, lng: 149.0733293} - { name: Mason Street,stop_code: Wjz26WW, lat: -35.3853577, lng: 149.0733293}
- { name: Lee Steere Crescent,stop_code: Wjz2df1, lat: -35.3875049, lng: 149.0748933} - { name: Lee Steere Crescent,stop_code: Wjz2df1, lat: -35.3875049, lng: 149.0748933}
- { name: Kingsmill Street,stop_code: Wjz2d32, lat: -35.3901917, lng: 149.0734943} - { name: Kingsmill Street,stop_code: Wjz2d32, lat: -35.3901917, lng: 149.0734943}
- { name: Jenke Circuit,stop_code: Wjz24uT, lat: -35.3931517, lng: 149.0676751} - { name: Jenke Circuit,stop_code: Wjz24uT, lat: -35.3931517, lng: 149.0676751}
- { name: O'Halloran Circuit,stop_code: Wjz24lA, lat: -35.3941231, lng: 149.0659575} - { name: O'Halloran Circuit,stop_code: Wjz24lA, lat: -35.3941231, lng: 149.0659575}
- { name: Pinkerton Circuit,stop_code: Wjz2498, lat: -35.3972167, lng: 149.0640703} - { name: Pinkerton Circuit,stop_code: Wjz2498, lat: -35.3972167, lng: 149.0640703}
- { name: Ragless Circuit,stop_code: Wjz234e, lat: -35.4001412, lng: 149.0627055} - { name: Ragless Circuit,stop_code: Wjz234e, lat: -35.4001412, lng: 149.0627055}
- { name: Learmonth Drive,stop_code: Wjz230Q, lat: -35.4030936, lng: 149.0635466} - { name: Learmonth Drive,stop_code: Wjz230Q, lat: -35.4030936, lng: 149.0635466}
- { name: Lavan Place,stop_code: Wjz66C2, lat: -35.2068343, lng: 149.0681005} - { name: Lavan Place,stop_code: Wjz66C2, lat: -35.2068343, lng: 149.0681005}
- { name: Clancy Street,stop_code: Wjz66Lx, lat: -35.2062279, lng: 149.0700922} - { name: Clancy Street,stop_code: Wjz66Lx, lat: -35.2062279, lng: 149.0700922}
- { name: Edmunds Place,stop_code: Wjz70go, lat: -35.2001419, lng: 149.0658463} - { name: Edmunds Place,stop_code: Wjz70go, lat: -35.2001419, lng: 149.0658463}
- { name: Baddeley Crescent,stop_code: Wjr_UUM, lat: -35.2001188, lng: 149.062303} - { name: Baddeley Crescent,stop_code: Wjr_UUM, lat: -35.2001188, lng: 149.062303}
- { name: Captain Cook Crescent,stop_code: Wjz3_z-, lat: -35.3349223, lng: 149.1461306} - { name: Captain Cook Crescent,stop_code: Wjz3_z-, lat: -35.3349223, lng: 149.1461306}
- { name: Kingsford Smith Drive,stop_code: Wjr_UPL, lat: -35.1975228, lng: 149.0606273} - { name: Kingsford Smith Drive,stop_code: Wjr_UPL, lat: -35.1975228, lng: 149.0606273}
- { name: Kingsford Smith Drive,stop_code: Wjr_UTJ, lat: -35.1949558, lng: 149.0607434} - { name: Kingsford Smith Drive,stop_code: Wjr_UTJ, lat: -35.1949558, lng: 149.0607434}
- { name: Healy Street,stop_code: Wjz70kD, lat: -35.196836, lng: 149.0659887} - { name: Healy Street,stop_code: Wjz70kD, lat: -35.196836, lng: 149.0659887}
- { name: Heagney Crescent,stop_code: Wjz2EXs, lat: -35.4174557, lng: 149.1275741} - { name: Heagney Crescent,stop_code: Wjz2EXs, lat: -35.4174557, lng: 149.1275741}
- { name: Monaro Highway,stop_code: Wjz2V0k, lat: -35.4140263, lng: 149.1397991} - { name: Monaro Highway,stop_code: Wjz2V0k, lat: -35.4140263, lng: 149.1397991}
- { name: Willoughby Crescent,stop_code: Wjz2NH0, lat: -35.4123115, lng: 149.1353734} - { name: Willoughby Crescent,stop_code: Wjz2NH0, lat: -35.4123115, lng: 149.1353734}
- { name: Theodore Street,stop_code: Wjz48Q1, lat: -35.3291744, lng: 149.0818599} - { name: Theodore Street,stop_code: Wjz48Q1, lat: -35.3291744, lng: 149.0818599}
- { name: Martin Street,stop_code: Wjz49Ui, lat: -35.3262888, lng: 149.0835377} - { name: Martin Street,stop_code: Wjz49Ui, lat: -35.3262888, lng: 149.0835377}
- { name: Morgan Crescent,stop_code: Wjz4aMo, lat: -35.3209613, lng: 149.082268} - { name: Morgan Crescent,stop_code: Wjz4aMo, lat: -35.3209613, lng: 149.082268}
- { name: Jenkins Street,stop_code: Wjz49dp, lat: -35.3229961, lng: 149.075421} - { name: Jenkins Street,stop_code: Wjz49dp, lat: -35.3229961, lng: 149.075421}
- { name: Launceston Street,stop_code: Wjz3e8l, lat: -35.3425473, lng: 149.0752509} - { name: Launceston Street,stop_code: Wjz3e8l, lat: -35.3425473, lng: 149.0752509}
- { name: McCubbin Street,stop_code: WjrX_bF, lat: -35.3353506, lng: 149.0538045} - { name: McCubbin Street,stop_code: WjrX_bF, lat: -35.3353506, lng: 149.0538045}
- { name: Namatjira Drive,stop_code: WjrX-oT, lat: -35.3424053, lng: 149.0567937} - { name: Namatjira Drive,stop_code: WjrX-oT, lat: -35.3424053, lng: 149.0567937}
- { name: Mather Street,stop_code: WjrX-zT, lat: -35.3402984, lng: 149.0581286} - { name: Mather Street,stop_code: WjrX-zT, lat: -35.3402984, lng: 149.0581286}
- { name: Nambir Court,stop_code: Wjz1edz, lat: -35.4271482, lng: 149.0757082} - { name: Nambir Court,stop_code: Wjz1edz, lat: -35.4271482, lng: 149.0757082}
- { name: Anketell Street,stop_code: Wjz20Eo, lat: -35.4198466, lng: 149.0699766} - { name: Anketell Street,stop_code: Wjz20Eo, lat: -35.4198466, lng: 149.0699766}
- { name: Laurens Street,stop_code: Wjz2aVu, lat: -35.4076897, lng: 149.0836236} - { name: Laurens Street,stop_code: Wjz2aVu, lat: -35.4076897, lng: 149.0836236}
- { name: Charleston Street,stop_code: Wjz28DH, lat: -35.4148504, lng: 149.0799887} - { name: Charleston Street,stop_code: Wjz28DH, lat: -35.4148504, lng: 149.0799887}
- { name: Mault Place,stop_code: Wjz2g6U, lat: -35.4157965, lng: 149.0857566} - { name: Mault Place,stop_code: Wjz2g6U, lat: -35.4157965, lng: 149.0857566}
- { name: Corlette Crescent,stop_code: Wjz2gvd, lat: -35.4146612, lng: 149.0888256} - { name: Corlette Crescent,stop_code: Wjz2gvd, lat: -35.4146612, lng: 149.0888256}
- { name: Nemarang Crescent,stop_code: Wjz33CI, lat: -35.3549749, lng: 149.0689295} - { name: Nemarang Crescent,stop_code: Wjz33CI, lat: -35.3549749, lng: 149.0689295}
- { name: Tuggeranong Parkway Onramp,stop_code: Wjz33KX, lat: -35.3550858, lng: 149.070698} - { name: Tuggeranong Parkway Onramp,stop_code: Wjz33KX, lat: -35.3550858, lng: 149.070698}
- { name: Wambaya Street,stop_code: Wjz33nk, lat: -35.3543462, lng: 149.0657554} - { name: Wambaya Street,stop_code: Wjz33nk, lat: -35.3543462, lng: 149.0657554}
- { name: Wirangu Place,stop_code: WjrXXI2, lat: -35.3565059, lng: 149.058473} - { name: Wirangu Place,stop_code: WjrXXI2, lat: -35.3565059, lng: 149.058473}
- { name: Walpiri Place,stop_code: WjrXYtm, lat: -35.3499821, lng: 149.0560969} - { name: Walpiri Place,stop_code: WjrXYtm, lat: -35.3499821, lng: 149.0560969}
- { name: Bangalay Crescent,stop_code: WjrXIKK, lat: -35.3493279, lng: 149.0374035} - { name: Bangalay Crescent,stop_code: WjrXIKK, lat: -35.3493279, lng: 149.0374035}
- { name: Hindmarsh Drive,stop_code: WjrXJfw, lat: -35.3436463, lng: 149.031771} - { name: Hindmarsh Drive,stop_code: WjrXJfw, lat: -35.3436463, lng: 149.031771}
- { name: Eppalock Street,stop_code: WjrYEg0, lat: -35.3320285, lng: 149.0323493} - { name: Eppalock Street,stop_code: WjrYEg0, lat: -35.3320285, lng: 149.0323493}
- { name: Warragamba Avenue,stop_code: WjrYEWc, lat: -35.3302839, lng: 149.0394086} - { name: Warragamba Avenue,stop_code: WjrYEWc, lat: -35.3302839, lng: 149.0394086}
- { name: Streeton Drive,stop_code: WjrX_1g, lat: -35.336799, lng: 149.0519909} - { name: Streeton Drive,stop_code: WjrX_1g, lat: -35.336799, lng: 149.0519909}
- { name: Hellyer Street,stop_code: WjrXLY1, lat: -35.3346674, lng: 149.0391656} - { name: Hellyer Street,stop_code: WjrXLY1, lat: -35.3346674, lng: 149.0391656}
- { name: Paloona Place,stop_code: WjrXLEL, lat: -35.3369076, lng: 149.0374236} - { name: Paloona Place,stop_code: WjrXLEL, lat: -35.3369076, lng: 149.0374236}
- { name: Leighton Street,stop_code: Wjz39GV, lat: -35.369019, lng: 149.0816284} - { name: Leighton Street,stop_code: Wjz39GV, lat: -35.369019, lng: 149.0816284}
- { name: Foskett Street,stop_code: Wjr_V6V, lat: -35.1904467, lng: 149.0528033} - { name: Foskett Street,stop_code: Wjr_V6V, lat: -35.1904467, lng: 149.0528033}
- { name: Tillyard Drive,stop_code: Wjr_McO, lat: -35.1972013, lng: 149.0429389} - { name: Tillyard Drive,stop_code: Wjr_McO, lat: -35.1972013, lng: 149.0429389}
- { name: Spalding Street,stop_code: Wjr_MhY, lat: -35.1991196, lng: 149.0445095} - { name: Spalding Street,stop_code: Wjr_MhY, lat: -35.1991196, lng: 149.0445095}
- { name: O'Shanassy Street,stop_code: Wjz4a9o, lat: -35.3203323, lng: 149.0754663} - { name: O'Shanassy Street,stop_code: Wjz4a9o, lat: -35.3203323, lng: 149.0754663}
- { name: Owen Dixon Drive,stop_code: Wjz70IW, lat: -35.197242, lng: 149.0706277} - { name: Owen Dixon Drive,stop_code: Wjz70IW, lat: -35.197242, lng: 149.0706277}
- { name: Sport Way,stop_code: Wjr-DTC, lat: -35.2002855, lng: 149.0276101} - { name: Sport Way,stop_code: Wjr-DTC, lat: -35.2002855, lng: 149.0276101}
- { name: Lance Hill Avenue,stop_code: Wjr_wf4, lat: -35.1950004, lng: 149.0199737} - { name: Lance Hill Avenue,stop_code: Wjr_wf4, lat: -35.1950004, lng: 149.0199737}
- { name: Kerrigan Street,stop_code: Wjr_pVW, lat: -35.1938099, lng: 149.0184155} - { name: Kerrigan Street,stop_code: Wjr_pVW, lat: -35.1938099, lng: 149.0184155}
- { name: Douglass Street,stop_code: Wjz70Wi, lat: -35.1986355, lng: 149.0725952} - { name: Douglass Street,stop_code: Wjz70Wi, lat: -35.1986355, lng: 149.0725952}
- { name: Copland Drive,stop_code: Wjz67_v, lat: -35.2002563, lng: 149.0727607} - { name: Copland Drive,stop_code: Wjz67_v, lat: -35.2002563, lng: 149.0727607}
- { name: Gallipoli Road,stop_code: Wjzcend, lat: -35.2937972, lng: 149.1643403} - { name: Gallipoli Road,stop_code: Wjzcend, lat: -35.2937972, lng: 149.1643403}
- { name: Mileham Street,stop_code: Wjr-vNL, lat: -35.2043835, lng: 149.0167621} - { name: Mileham Street,stop_code: Wjr-vNL, lat: -35.2043835, lng: 149.0167621}
- { name: Ginninderra Drive,stop_code: Wjr-Df8, lat: -35.2008175, lng: 149.0201835} - { name: Ginninderra Drive,stop_code: Wjr-Df8, lat: -35.2008175, lng: 149.0201835}
- { name: Clode Crescent,stop_code: Wjr-te3, lat: -35.2122382, lng: 149.0090273} - { name: Clode Crescent,stop_code: Wjr-te3, lat: -35.2122382, lng: 149.0090273}
- { name: Handcock Crescent,stop_code: Wjr-CsO, lat: -35.2082115, lng: 149.0237453} - { name: Handcock Crescent,stop_code: Wjr-CsO, lat: -35.2082115, lng: 149.0237453}
- { name: Prevost Place,stop_code: Wjr-sKW, lat: -35.2178207, lng: 149.0156953} - { name: Prevost Place,stop_code: Wjr-sKW, lat: -35.2178207, lng: 149.0156953}
- { name: Plowman Place,stop_code: Wjr-S9y, lat: -35.2102797, lng: 149.0426899} - { name: Plowman Place,stop_code: Wjr-S9y, lat: -35.2102797, lng: 149.0426899}
- { name: O'Loghlen Street,stop_code: Wjr-HbC, lat: -35.2250302, lng: 149.0316399} - { name: O'Loghlen Street,stop_code: Wjr-HbC, lat: -35.2250302, lng: 149.0316399}
- { name: Southern Cross Drive,stop_code: Wjr-sQ8, lat: -35.2193706, lng: 149.0159919} - { name: Southern Cross Drive,stop_code: Wjr-sQ8, lat: -35.2193706, lng: 149.0159919}
- { name: Armstrong Crescent,stop_code: Wjr-rv7, lat: -35.2221818, lng: 149.0117611} - { name: Armstrong Crescent,stop_code: Wjr-rv7, lat: -35.2221818, lng: 149.0117611}
- { name: Spofforth Street,stop_code: Wjr-kVk, lat: -35.2210905, lng: 149.0066193} - { name: Spofforth Street,stop_code: Wjr-kVk, lat: -35.2210905, lng: 149.0066193}
- { name: Pickworth Street,stop_code: Wjr-rxG, lat: -35.2267918, lng: 149.0140227} - { name: Pickworth Street,stop_code: Wjr-rxG, lat: -35.2267918, lng: 149.0140227}
- { name: Macnaughton Street,stop_code: Wjr-qZg, lat: -35.2296561, lng: 149.0176617} - { name: Macnaughton Street,stop_code: Wjr-qZg, lat: -35.2296561, lng: 149.0176617}
- { name: Powell Street,stop_code: Wjr-rUs, lat: -35.2272548, lng: 149.0178319} - { name: Powell Street,stop_code: Wjr-rUs, lat: -35.2272548, lng: 149.0178319}
- { name: Beaurepaire Crescent,stop_code: Wjr-rNr, lat: -35.226697, lng: 149.016389} - { name: Beaurepaire Crescent,stop_code: Wjr-rNr, lat: -35.226697, lng: 149.016389}
- { name: Hardwick Crescent,stop_code: Wjr-zcC, lat: -35.2243517, lng: 149.0207165} - { name: Hardwick Crescent,stop_code: Wjr-zcC, lat: -35.2243517, lng: 149.0207165}
- { name: Brazel Street,stop_code: Wjr-G5f, lat: -35.2290792, lng: 149.0298564} - { name: Brazel Street,stop_code: Wjr-G5f, lat: -35.2290792, lng: 149.0298564}
- { name: Wearing Street,stop_code: Wjr-xRd, lat: -35.2347078, lng: 149.0270748} - { name: Wearing Street,stop_code: Wjr-xRd, lat: -35.2347078, lng: 149.0270748}
- { name: Drake Brockman Drive,stop_code: Wjr-wDP, lat: -35.2389936, lng: 149.0252414} - { name: Drake Brockman Drive,stop_code: Wjr-wDP, lat: -35.2389936, lng: 149.0252414}
- { name: Castieau Street,stop_code: Wjr-Gsq, lat: -35.2301636, lng: 149.0342818} - { name: Castieau Street,stop_code: Wjr-Gsq, lat: -35.2301636, lng: 149.0342818}
- { name: Ulm Street,stop_code: Wjr-GyJ, lat: -35.2312775, lng: 149.0359574} - { name: Ulm Street,stop_code: Wjr-GyJ, lat: -35.2312775, lng: 149.0359574}
- { name: Wirraway Crescent,stop_code: Wjr-GFM, lat: -35.2324613, lng: 149.03753} - { name: Wirraway Crescent,stop_code: Wjr-GFM, lat: -35.2324613, lng: 149.03753}
- { name: Ross Smith Crescent,stop_code: Wjr-F_m, lat: -35.233261, lng: 149.039515} - { name: Ross Smith Crescent,stop_code: Wjr-F_m, lat: -35.233261, lng: 149.039515}
- { name: Ross Smith Crescent,stop_code: Wjr-FCU, lat: -35.2344506, lng: 149.0363984} - { name: Ross Smith Crescent,stop_code: Wjr-FCU, lat: -35.2344506, lng: 149.0363984}
- { name: Hinkler Street,stop_code: Wjr-Fzd, lat: -35.2360739, lng: 149.0353153} - { name: Hinkler Street,stop_code: Wjr-Fzd, lat: -35.2360739, lng: 149.0353153}
- { name: Delamere Street,stop_code: Wjr-E8A, lat: -35.2437543, lng: 149.031741} - { name: Delamere Street,stop_code: Wjr-E8A, lat: -35.2437543, lng: 149.031741}
- { name: Tanumbirini Street,stop_code: WjrZLdA, lat: -35.245805, lng: 149.0316615} - { name: Tanumbirini Street,stop_code: WjrZLdA, lat: -35.245805, lng: 149.0316615}
- { name: Southwell Street,stop_code: WjrZSKp, lat: -35.2509203, lng: 149.0480636} - { name: Southwell Street,stop_code: WjrZSKp, lat: -35.2509203, lng: 149.0480636}
- { name: De Salis Street,stop_code: WjrZSWs, lat: -35.2533983, lng: 149.050782} - { name: De Salis Street,stop_code: WjrZSWs, lat: -35.2533983, lng: 149.050782}
- { name: Hannaford Street,stop_code: Wjr-MCk, lat: -35.2396029, lng: 149.0464162} - { name: Hannaford Street,stop_code: Wjr-MCk, lat: -35.2396029, lng: 149.0464162}
- { name: Hannaford Street,stop_code: Wjr-M-x, lat: -35.2399127, lng: 149.0508416} - { name: Hannaford Street,stop_code: Wjr-M-x, lat: -35.2399127, lng: 149.0508416}
- { name: Shumack Street,stop_code: WjrZ-aT, lat: -35.2531402, lng: 149.053943} - { name: Shumack Street,stop_code: WjrZ-aT, lat: -35.2531402, lng: 149.053943}
- { name: Coulter Drive,stop_code: WjrZZeD, lat: -35.2558247, lng: 149.0536901} - { name: Coulter Drive,stop_code: WjrZZeD, lat: -35.2558247, lng: 149.0536901}
- { name: Redfern Street,stop_code: WjrZZlR, lat: -35.2567539, lng: 149.055397} - { name: Redfern Street,stop_code: WjrZZlR, lat: -35.2567539, lng: 149.055397}
- { name: Atkinson Street,stop_code: WjrZZH3, lat: -35.2583026, lng: 149.0584315} - { name: Atkinson Street,stop_code: WjrZZH3, lat: -35.2583026, lng: 149.0584315}
- { name: Skinner Street,stop_code: Wjz54mj, lat: -35.2617096, lng: 149.0656385} - { name: Skinner Street,stop_code: Wjz54mj, lat: -35.2617096, lng: 149.0656385}
- { name: Allman Circuit,stop_code: Wjz55vN, lat: -35.2557214, lng: 149.0677248} - { name: Allman Circuit,stop_code: Wjz55vN, lat: -35.2557214, lng: 149.0677248}
- { name: Redfern Street,stop_code: Wjz557P, lat: -35.2555149, lng: 149.0636155} - { name: Redfern Street,stop_code: Wjz557P, lat: -35.2555149, lng: 149.0636155}
- { name: Goulburn Street,stop_code: WjrZ-WW, lat: -35.2535016, lng: 149.0623511} - { name: Goulburn Street,stop_code: WjrZ-WW, lat: -35.2535016, lng: 149.0623511}
- { name: Roberts Street,stop_code: WjrZ-GZ, lat: -35.2532951, lng: 149.0596327} - { name: Roberts Street,stop_code: WjrZ-GZ, lat: -35.2532951, lng: 149.0596327}
- { name: Erskine Street,stop_code: WjrZ-Jc, lat: -35.2513107, lng: 149.058664} - { name: Erskine Street,stop_code: WjrZ-Jc, lat: -35.2513107, lng: 149.058664}
- { name: Erskine Street,stop_code: WjrZ_Fk, lat: -35.2485228, lng: 149.0588536} - { name: Erskine Street,stop_code: WjrZ_Fk, lat: -35.2485228, lng: 149.0588536}
- { name: Thurlow Place,stop_code: Wjz57Q7, lat: -35.2462221, lng: 149.0708857} - { name: Thurlow Place,stop_code: Wjz57Q7, lat: -35.2462221, lng: 149.0708857}
- { name: Maddison Close,stop_code: Wjz5fm2, lat: -35.2452775, lng: 149.0763507} - { name: Maddison Close,stop_code: Wjz5fm2, lat: -35.2452775, lng: 149.0763507}
- { name: Vowels Crescent,stop_code: Wjz5nUz, lat: -35.2493715, lng: 149.094909} - { name: Vowels Crescent,stop_code: Wjz5nUz, lat: -35.2493715, lng: 149.094909}
- { name: Thynne Street,stop_code: Wjz6gUM, lat: -35.2441052, lng: 149.0951619} - { name: Thynne Street,stop_code: Wjz6gUM, lat: -35.2441052, lng: 149.0951619}
- { name: Leverrier Crescent,stop_code: Wjz5vrT, lat: -35.2469189, lng: 149.1007523} - { name: Leverrier Crescent,stop_code: Wjz5vrT, lat: -35.2469189, lng: 149.1007523}
- { name: Braybrooke Street,stop_code: Wjz6oJz, lat: -35.2403705, lng: 149.1030403} - { name: Braybrooke Street,stop_code: Wjz6oJz, lat: -35.2403705, lng: 149.1030403}
- { name: Temperley Street,stop_code: Wjz7hZW, lat: -35.1910485, lng: 149.0953265} - { name: Temperley Street,stop_code: Wjz7hZW, lat: -35.1910485, lng: 149.0953265}
- { name: Dobbin Circuit,stop_code: Wjz7iKx, lat: -35.1849518, lng: 149.0920391} - { name: Dobbin Circuit,stop_code: Wjz7iKx, lat: -35.1849518, lng: 149.0920391}
- { name: Fitzsimmons Street,stop_code: Wjz7jaJ, lat: -35.1819033, lng: 149.0868551} - { name: Fitzsimmons Street,stop_code: Wjz7jaJ, lat: -35.1819033, lng: 149.0868551}
- { name: Kelleway Avenue,stop_code: Wjz7jW4, lat: -35.181955, lng: 149.0941886} - { name: Kelleway Avenue,stop_code: Wjz7jW4, lat: -35.181955, lng: 149.0941886}
- { name: Whatmore Court,stop_code: Wjz7rzg, lat: -35.1815933, lng: 149.1014588} - { name: Whatmore Court,stop_code: Wjz7rzg, lat: -35.1815933, lng: 149.1014588}
- { name: Whitfield Circuit,stop_code: Wjz7pkV, lat: -35.1918235, lng: 149.0995622} - { name: Whitfield Circuit,stop_code: Wjz7pkV, lat: -35.1918235, lng: 149.0995622}
- { name: Lexcen Avenue,stop_code: Wjz7qSX, lat: -35.1847968, lng: 149.1050623} - { name: Lexcen Avenue,stop_code: Wjz7qSX, lat: -35.1847968, lng: 149.1050623}
- { name: Kelleway Avenue,stop_code: Wjz7rRa, lat: -35.1800948, lng: 149.1039243} - { name: Kelleway Avenue,stop_code: Wjz7rRa, lat: -35.1800948, lng: 149.1039243}
- { name: Jabanungga Avenue,stop_code: Wjz7B0w, lat: -35.1727054, lng: 149.107275} - { name: Jabanungga Avenue,stop_code: Wjz7B0w, lat: -35.1727054, lng: 149.107275}
- { name: Newlop Street,stop_code: Wjz7thn, lat: -35.1713618, lng: 149.0985507} - { name: Newlop Street,stop_code: Wjz7thn, lat: -35.1713618, lng: 149.0985507}
- { name: Bicentennial National Trail,stop_code: Wjz7uxi, lat: -35.1663489, lng: 149.1013956} - { name: Bicentennial National Trail,stop_code: Wjz7uxi, lat: -35.1663489, lng: 149.1013956}
- { name: Mundang Crescent,stop_code: Wjz7tIt, lat: -35.169553, lng: 149.1029128} - { name: Mundang Crescent,stop_code: Wjz7tIt, lat: -35.169553, lng: 149.1029128}
- { name: Wanganeen Avenue,stop_code: Wjz7BsE, lat: -35.1699148, lng: 149.1115106} - { name: Wanganeen Avenue,stop_code: Wjz7BsE, lat: -35.1699148, lng: 149.1115106}
- { name: College Street,stop_code: Wjz68W3, lat: -35.2425008, lng: 149.0831669} - { name: College Street,stop_code: Wjz68W3, lat: -35.2425008, lng: 149.0831669}
- { name: Drakeford Drive,stop_code: WjrW_uo, lat: -35.3773291, lng: 149.056161} - { name: Drakeford Drive,stop_code: WjrW_uo, lat: -35.3773291, lng: 149.056161}
- { name: Tuggeranong Parkway,stop_code: WjrXUAm, lat: -35.3726375, lng: 149.0574471} - { name: Tuggeranong Parkway,stop_code: WjrXUAm, lat: -35.3726375, lng: 149.0574471}
- { name: Banambila Street,stop_code: Wjz5l2U, lat: -35.2592266, lng: 149.0857332} - { name: Banambila Street,stop_code: Wjz5l2U, lat: -35.2592266, lng: 149.0857332}
- { name: Bindel Street,stop_code: Wjz5e8Y, lat: -35.2547235, lng: 149.0761202} - { name: Bindel Street,stop_code: Wjz5e8Y, lat: -35.2547235, lng: 149.0761202}
- { name: Kambah pool Road,stop_code: WjrXMFM, lat: -35.3752866, lng: 149.0485475} - { name: Kambah pool Road,stop_code: WjrXMFM, lat: -35.3752866, lng: 149.0485475}
- { name: Carbeen Street,stop_code: WjrXJZ6, lat: -35.3445279, lng: 149.0392999} - { name: Carbeen Street,stop_code: WjrXJZ6, lat: -35.3445279, lng: 149.0392999}
- { name: Collings Street,stop_code: Wjz3jaF, lat: -35.3579826, lng: 149.0867102} - { name: Collings Street,stop_code: Wjz3jaF, lat: -35.3579826, lng: 149.0867102}
- { name: Paramatta Street,stop_code: Wjz3jei, lat: -35.3551755, lng: 149.0862349} - { name: Paramatta Street,stop_code: Wjz3jei, lat: -35.3551755, lng: 149.0862349}
- { name: Aikman Drive,stop_code: Wjz69uI, lat: -35.2341477, lng: 149.0784965} - { name: Aikman Drive,stop_code: Wjz69uI, lat: -35.2341477, lng: 149.0784965}
- { name: Amy Ackman Street,stop_code: Wjz7-oI, lat: -35.1668191, lng: 149.1443901} - { name: Amy Ackman Street,stop_code: Wjz7-oI, lat: -35.1668191, lng: 149.1443901}
- { name: Barracks Flat Drive,stop_code: WjzbUGB, lat: -35.3740947, lng: 149.2349556} - { name: Barracks Flat Drive,stop_code: WjzbUGB, lat: -35.3740947, lng: 149.2349556}
- { name: Ling Place,stop_code: Wjzj0yX, lat: -35.3742978, lng: 149.2450265} - { name: Ling Place,stop_code: Wjzj0yX, lat: -35.3742978, lng: 149.2450265}
- { name: Knowles Place,stop_code: Wjz5FOn, lat: -35.2806054, lng: 149.1260452} - { name: Knowles Place,stop_code: Wjz5FOn, lat: -35.2806054, lng: 149.1260452}
- { name: Gundaroo Drive,stop_code: Wjz7oYv, lat: -35.196789, lng: 149.1057064} - { name: Gundaroo Drive,stop_code: Wjz7oYv, lat: -35.196789, lng: 149.1057064}
- { name: Gundaroo Drive,stop_code: Wjz7xpa, lat: -35.1938349, lng: 149.1107761} - { name: Gundaroo Drive,stop_code: Wjz7xpa, lat: -35.1938349, lng: 149.1107761}
- { name: Barritt Street,stop_code: WjrW_1f, lat: -35.3801683, lng: 149.051853} - { name: Barritt Street,stop_code: WjrW_1f, lat: -35.3801683, lng: 149.051853}
- { name: Barritt Street,stop_code: WjrWTJq, lat: -35.3778081, lng: 149.0480034} - { name: Barritt Street,stop_code: WjrWTJq, lat: -35.3778081, lng: 149.0480034}
- { name: Constitution Avenue,stop_code: Wjz5MsD, lat: -35.2847121, lng: 149.1333531} - { name: Constitution Avenue,stop_code: Wjz5MsD, lat: -35.2847121, lng: 149.1333531}
- { name: Mort Street,stop_code: Wjz5Ok1, lat: -35.2742265, lng: 149.1312268} - { name: Mort Street,stop_code: Wjz5Ok1, lat: -35.2742265, lng: 149.1312268}
- { name: Northbourne Avenue,stop_code: Wjz5SDc, lat: -35.2499285, lng: 149.1341368} - { name: Northbourne Avenue,stop_code: Wjz5SDc, lat: -35.2499285, lng: 149.1341368}
- { name: Northbourne Avenue,stop_code: Wjz5Qgn, lat: -35.2655006, lng: 149.1316277} - { name: Northbourne Avenue,stop_code: Wjz5Qgn, lat: -35.2655006, lng: 149.1316277}
- { name: Northbourne Avenue,stop_code: Wjz5Sqk, lat: -35.2533948, lng: 149.1329835} - { name: Northbourne Avenue,stop_code: Wjz5Sqk, lat: -35.2533948, lng: 149.1329835}
- { name: Coranderrk Street,stop_code: Wjz5MI3, lat: -35.2850249, lng: 149.1353935} - { name: Coranderrk Street,stop_code: Wjz5MI3, lat: -35.2850249, lng: 149.1353935}
- { name: Launceston Street,stop_code: Wjz3eje, lat: -35.3403963, lng: 149.0765097} - { name: Launceston Street,stop_code: Wjz3eje, lat: -35.3403963, lng: 149.0765097}
- { name: Streeton Drive,stop_code: WjrXQ2W, lat: -35.3523853, lng: 149.0417814} - { name: Streeton Drive,stop_code: WjrXQ2W, lat: -35.3523853, lng: 149.0417814}
- { name: Bangalay Crescent,stop_code: WjrXQeH, lat: -35.3495777, lng: 149.0428125} - { name: Bangalay Crescent,stop_code: WjrXQeH, lat: -35.3495777, lng: 149.0428125}
- { name: Sidaway Street,stop_code: WjrXHZU, lat: -35.3560382, lng: 149.0404158} - { name: Sidaway Street,stop_code: WjrXHZU, lat: -35.3560382, lng: 149.0404158}
- { name: Mirrabei Drive,stop_code: Wjz7BST, lat: -35.167951, lng: 149.1157463} - { name: Mirrabei Drive,stop_code: Wjz7BST, lat: -35.167951, lng: 149.1157463}
- { name: Saunders Street,stop_code: Wjz7AJS, lat: -35.174204, lng: 149.1143555} - { name: Saunders Street,stop_code: Wjz7AJS, lat: -35.174204, lng: 149.1143555}
- { name: Amagula Avenue,stop_code: Wjz7zzB, lat: -35.1811799, lng: 149.1126486} - { name: Amagula Avenue,stop_code: Wjz7zzB, lat: -35.1811799, lng: 149.1126486}
- { name: Windradyne Street,stop_code: Wjz7CD7, lat: -35.1617492, lng: 149.1119532} - { name: Windradyne Street,stop_code: Wjz7CD7, lat: -35.1617492, lng: 149.1119532}
- { name: Mirrabei Drive,stop_code: Wjz7If2, lat: -35.1732221, lng: 149.1188441} - { name: Mirrabei Drive,stop_code: Wjz7If2, lat: -35.1732221, lng: 149.1188441}
- { name: Paul Coe Crescent,stop_code: Wjz7Iax, lat: -35.1766844, lng: 149.1196027} - { name: Paul Coe Crescent,stop_code: Wjz7Iax, lat: -35.1766844, lng: 149.1196027}
- { name: Mirrabei Drive,stop_code: Wjz7IFg, lat: -35.1774595, lng: 149.1246602} - { name: Mirrabei Drive,stop_code: Wjz7IFg, lat: -35.1774595, lng: 149.1246602}
- { name: Shoalhaven Avenue,stop_code: Wjz7J-7, lat: -35.167951, lng: 149.1270626} - { name: Shoalhaven Avenue,stop_code: Wjz7J-7, lat: -35.167951, lng: 149.1270626}
- { name: Proserpine Circuit,stop_code: Wjz7RdE, lat: -35.169243, lng: 149.1307293} - { name: Proserpine Circuit,stop_code: Wjz7RdE, lat: -35.169243, lng: 149.1307293}
- { name: Inglewood Street,stop_code: Wjz7Y0J, lat: -35.177732, lng: 149.1403005} - { name: Inglewood Street,stop_code: Wjz7Y0J, lat: -35.177732, lng: 149.1403005}
- { name: Obrien Place,stop_code: Wjz7GSc, lat: -35.1847451, lng: 149.1258614} - { name: Obrien Place,stop_code: Wjz7GSc, lat: -35.1847451, lng: 149.1258614}
- { name: Anthony Rolfe Avenue,stop_code: Wjz7Ppw, lat: -35.1829884, lng: 149.1332581} - { name: Anthony Rolfe Avenue,stop_code: Wjz7Ppw, lat: -35.1829884, lng: 149.1332581}
- { name: Swain Street,stop_code: Wjz7X2n, lat: -35.1817108, lng: 149.1398579} - { name: Swain Street,stop_code: Wjz7X2n, lat: -35.1817108, lng: 149.1398579}
- { name: Petersilka Street,stop_code: Wjz7XxD, lat: -35.1823825, lng: 149.1457373} - { name: Petersilka Street,stop_code: Wjz7XxD, lat: -35.1823825, lng: 149.1457373}
- { name: Thistle Lane,stop_code: Wjzf2rm, lat: -35.1865677, lng: 149.1549041} - { name: Thistle Lane,stop_code: Wjzf2rm, lat: -35.1865677, lng: 149.1549041}
- { name: Horse Park Drive,stop_code: Wjzf0ZL, lat: -35.1961257, lng: 149.1609099} - { name: Horse Park Drive,stop_code: Wjzf0ZL, lat: -35.1961257, lng: 149.1609099}
- { name: Morris West Street,stop_code: Wjz6_vY, lat: -35.2004651, lng: 149.1448522} - { name: Morris West Street,stop_code: Wjz6_vY, lat: -35.2004651, lng: 149.1448522}
- { name: The Valley Avenue,stop_code: Wjz7Oal, lat: -35.1873286, lng: 149.1301603} - { name: The Valley Avenue,stop_code: Wjz7Oal, lat: -35.1873286, lng: 149.1301603}
- { name: Gungahlin Drive,stop_code: Wjz7Fmf, lat: -35.1899217, lng: 149.1203537} - { name: Gungahlin Drive,stop_code: Wjz7Fmf, lat: -35.1899217, lng: 149.1203537}
- { name: Freeling Crescent,stop_code: Wjz7xO6, lat: -35.1928051, lng: 149.1147348} - { name: Freeling Crescent,stop_code: Wjz7xO6, lat: -35.1928051, lng: 149.1147348}
- { name: Kosciuszko Avenue,stop_code: Wjz7E3Z, lat: -35.1976337, lng: 149.1187656} - { name: Kosciuszko Avenue,stop_code: Wjz7E3Z, lat: -35.1976337, lng: 149.1187656}
- { name: Kosciuszko Avenue,stop_code: Wjz7EJ7, lat: -35.1960839, lng: 149.1244553} - { name: Kosciuszko Avenue,stop_code: Wjz7EJ7, lat: -35.1960839, lng: 149.1244553}
- { name: Hoskins Street,stop_code: Wjz6RQW, lat: -35.2136848, lng: 149.1379368} - { name: Hoskins Street,stop_code: Wjz6RQW, lat: -35.2136848, lng: 149.1379368}
- { name: Hoskins Street,stop_code: Wjz6QTd, lat: -35.2168483, lng: 149.1369095} - { name: Hoskins Street,stop_code: Wjz6QTd, lat: -35.2168483, lng: 149.1369095}
- { name: Sandford Street,stop_code: Wjz6Yaq, lat: -35.2205928, lng: 149.1414139} - { name: Sandford Street,stop_code: Wjz6Yaq, lat: -35.2205928, lng: 149.1414139}
- { name: Flemington Road,stop_code: Wjz6Wse, lat: -35.2298796, lng: 149.1438091} - { name: Flemington Road,stop_code: Wjz6Wse, lat: -35.2298796, lng: 149.1438091}
- { name: Federal Highway,stop_code: Wjze3Fa, lat: -35.2267416, lng: 149.1575876} - { name: Federal Highway,stop_code: Wjze3Fa, lat: -35.2267416, lng: 149.1575876}
- { name: Aspinall Street,stop_code: Wjzeaq_, lat: -35.2311306, lng: 149.1668636} - { name: Aspinall Street,stop_code: Wjzeaq_, lat: -35.2311306, lng: 149.1668636}
- { name: Antill Street,stop_code: Wjze0VY, lat: -35.2430274, lng: 149.1613003} - { name: Antill Street,stop_code: Wjze0VY, lat: -35.2430274, lng: 149.1613003}
- { name: Knox Street,stop_code: Wjze1hB, lat: -35.2374923, lng: 149.1539669} - { name: Knox Street,stop_code: Wjze1hB, lat: -35.2374923, lng: 149.1539669}
- { name: Molesworth Street,stop_code: Wjze17N, lat: -35.2336919, lng: 149.1515898} - { name: Molesworth Street,stop_code: Wjze17N, lat: -35.2336919, lng: 149.1515898}
- { name: Phillip Avenue,stop_code: Wjz6UQw, lat: -35.2413339, lng: 149.1484036} - { name: Phillip Avenue,stop_code: Wjz6UQw, lat: -35.2413339, lng: 149.1484036}
- { name: Melba Street,stop_code: Wjz5_mg, lat: -35.2454644, lng: 149.1425874} - { name: Melba Street,stop_code: Wjz5_mg, lat: -35.2454644, lng: 149.1425874}
- { name: Antill Street,stop_code: Wjz5_O4, lat: -35.24786, lng: 149.147645} - { name: Antill Street,stop_code: Wjz5_O4, lat: -35.24786, lng: 149.147645}
- { name: Antill Street,stop_code: Wjzd7LX, lat: -35.2445144, lng: 149.1586198} - { name: Antill Street,stop_code: Wjzd7LX, lat: -35.2445144, lng: 149.1586198}
- { name: Grayson Street,stop_code: WjzdeeQ, lat: -35.2506237, lng: 149.1639253} - { name: Grayson Street,stop_code: WjzdeeQ, lat: -35.2506237, lng: 149.1639253}
- { name: Stott Street,stop_code: Wjzd6Cq, lat: -35.2507889, lng: 149.1563997} - { name: Stott Street,stop_code: Wjzd6Cq, lat: -35.2507889, lng: 149.1563997}
- { name: Hannan Crescent,stop_code: Wjzd68O, lat: -35.254952, lng: 149.1528797} - { name: Hannan Crescent,stop_code: Wjzd68O, lat: -35.254952, lng: 149.1528797}
- { name: Officer Crescent,stop_code: Wjz5ZZQ, lat: -35.2567691, lng: 149.1500474} - { name: Officer Crescent,stop_code: Wjz5ZZQ, lat: -35.2567691, lng: 149.1500474}
- { name: Officer Crescent,stop_code: Wjz5ZO1, lat: -35.2591479, lng: 149.1477412} - { name: Officer Crescent,stop_code: Wjz5ZO1, lat: -35.2591479, lng: 149.1477412}
- { name: Cowper Street,stop_code: Wjz5-5y, lat: -35.2514497, lng: 149.1400942} - { name: Cowper Street,stop_code: Wjz5-5y, lat: -35.2514497, lng: 149.1400942}
- { name: Morphett Street,stop_code: Wjz5SWN, lat: -35.2535974, lng: 149.1390827} - { name: Morphett Street,stop_code: Wjz5SWN, lat: -35.2535974, lng: 149.1390827}
- { name: Dooring Street,stop_code: Wjz5Z5c, lat: -35.2568022, lng: 149.1396491} - { name: Dooring Street,stop_code: Wjz5Z5c, lat: -35.2568022, lng: 149.1396491}
- { name: Majura Avenue,stop_code: Wjz5Za5, lat: -35.2588175, lng: 149.1409439} - { name: Majura Avenue,stop_code: Wjz5Za5, lat: -35.2588175, lng: 149.1409439}
- { name: Cowper Street,stop_code: Wjz5YfD, lat: -35.2606676, lng: 149.1416317} - { name: Cowper Street,stop_code: Wjz5YfD, lat: -35.2606676, lng: 149.1416317}
- { name: Herbert Crescent,stop_code: Wjz5YKO, lat: -35.2618095, lng: 149.1473796} - { name: Herbert Crescent,stop_code: Wjz5YKO, lat: -35.2618095, lng: 149.1473796}
- { name: Wakefield Gardens,stop_code: Wjz5YAK, lat: -35.2627902, lng: 149.1458623} - { name: Wakefield Gardens,stop_code: Wjz5YAK, lat: -35.2627902, lng: 149.1458623}
- { name: Campbell Street,stop_code: Wjz5Yq4, lat: -35.2643388, lng: 149.1435864} - { name: Campbell Street,stop_code: Wjz5Yq4, lat: -35.2643388, lng: 149.1435864}
- { name: Campbell Street,stop_code: Wjz5XnQ, lat: -35.2664452, lng: 149.1432384} - { name: Campbell Street,stop_code: Wjz5XnQ, lat: -35.2664452, lng: 149.1432384}
- { name: Leslie Street,stop_code: Wjz5XrS, lat: -35.2689744, lng: 149.1446925} - { name: Leslie Street,stop_code: Wjz5XrS, lat: -35.2689744, lng: 149.1446925}
- { name: Campbell Street,stop_code: Wjz5XwW, lat: -35.2714003, lng: 149.1461465} - { name: Campbell Street,stop_code: Wjz5XwW, lat: -35.2714003, lng: 149.1461465}
- { name: Gooreen Street,stop_code: Wjz5W3H, lat: -35.2747063, lng: 149.1403907} - { name: Gooreen Street,stop_code: Wjz5W3H, lat: -35.2747063, lng: 149.1403907}
- { name: Gooreen Street,stop_code: Wjz5W8l, lat: -35.276623, lng: 149.1411209} - { name: Gooreen Street,stop_code: Wjz5W8l, lat: -35.276623, lng: 149.1411209}
- { name: Cox Street,stop_code: Wjz5Ycz, lat: -35.2631, lng: 149.1415634} - { name: Cox Street,stop_code: Wjz5Ycz, lat: -35.2631, lng: 149.1415634}
- { name: Foveaux Street,stop_code: Wjz5Y1_, lat: -35.2648034, lng: 149.1406151} - { name: Foveaux Street,stop_code: Wjz5Y1_, lat: -35.2648034, lng: 149.1406151}
- { name: Limestone Avenue,stop_code: Wjz5QUd, lat: -35.2656089, lng: 149.1383392} - { name: Limestone Avenue,stop_code: Wjz5QUd, lat: -35.2656089, lng: 149.1383392}
- { name: Ipima Street,stop_code: Wjz5PLJ, lat: -35.2663315, lng: 149.136253} - { name: Ipima Street,stop_code: Wjz5PLJ, lat: -35.2663315, lng: 149.136253}
- { name: Ijong Street,stop_code: Wjz5PBC, lat: -35.2675907, lng: 149.1347357} - { name: Ijong Street,stop_code: Wjz5PBC, lat: -35.2675907, lng: 149.1347357}
- { name: Torrens Street,stop_code: Wjz5Pwn, lat: -35.2709457, lng: 149.1344196} - { name: Torrens Street,stop_code: Wjz5Pwn, lat: -35.2709457, lng: 149.1344196}
- { name: Fawkner Street,stop_code: Wjz5OLh, lat: -35.2721844, lng: 149.135684} - { name: Fawkner Street,stop_code: Wjz5OLh, lat: -35.2721844, lng: 149.135684}
- { name: Doonkuna Street,stop_code: Wjz5OOo, lat: -35.2757106, lng: 149.1372297} - { name: Doonkuna Street,stop_code: Wjz5OOo, lat: -35.2757106, lng: 149.1372297}
- { name: Ainslie Avenue,stop_code: Wjz5NHD, lat: -35.2798744, lng: 149.1361266} - { name: Ainslie Avenue,stop_code: Wjz5NHD, lat: -35.2798744, lng: 149.1361266}
- { name: Limestone Avenue,stop_code: Wjz5VFA, lat: -35.2815441, lng: 149.146984} - { name: Limestone Avenue,stop_code: Wjz5VFA, lat: -35.2815441, lng: 149.146984}
- { name: Fairbairn Avenue,stop_code: Wjzd0CK, lat: -35.283446, lng: 149.156771} - { name: Fairbairn Avenue,stop_code: Wjzd0CK, lat: -35.283446, lng: 149.156771}
- { name: White Crescent,stop_code: Wjzc7nq, lat: -35.2885152, lng: 149.1537353} - { name: White Crescent,stop_code: Wjzc7nq, lat: -35.2885152, lng: 149.1537353}
- { name: Anzac Parade,stop_code: Wjz5Urj, lat: -35.285706, lng: 149.144029} - { name: Anzac Parade,stop_code: Wjz5Urj, lat: -35.285706, lng: 149.144029}
- { name: Holmes Crescent,stop_code: Wjzc7Ay, lat: -35.2905765, lng: 149.1566757} - { name: Holmes Crescent,stop_code: Wjzc7Ay, lat: -35.2905765, lng: 149.1566757}
- { name: Borella Street,stop_code: Wjz4_Oj, lat: -35.2918933, lng: 149.1481428} - { name: Borella Street,stop_code: Wjz4_Oj, lat: -35.2918933, lng: 149.1481428}
- { name: Parkes Way,stop_code: Wjz4T-X, lat: -35.2891325, lng: 149.1393476} - { name: Parkes Way,stop_code: Wjz4T-X, lat: -35.2891325, lng: 149.1393476}
- { name: Miles Road,stop_code: WjzceHt, lat: -35.2965216, lng: 149.168833} - { name: Miles Road,stop_code: WjzceHt, lat: -35.2965216, lng: 149.168833}
- { name: Vowels Road,stop_code: Wjzcdsn, lat: -35.3011446, lng: 149.1659502} - { name: Vowels Road,stop_code: Wjzcdsn, lat: -35.3011446, lng: 149.1659502}
- { name: Morshead Drive,stop_code: Wjzcd2U, lat: -35.3031671, lng: 149.1626628} - { name: Morshead Drive,stop_code: Wjzcd2U, lat: -35.3031671, lng: 149.1626628}
- { name: Eyre Street,stop_code: Wjz4WnH, lat: -35.3159201, lng: 149.1430396} - { name: Eyre Street,stop_code: Wjz4WnH, lat: -35.3159201, lng: 149.1430396}
- { name: Canberra Avenue,stop_code: Wjz4Ofi, lat: -35.3160439, lng: 149.1301934} - { name: Canberra Avenue,stop_code: Wjz4Ofi, lat: -35.3160439, lng: 149.1301934}
- { name: Flinders Way,stop_code: Wjz4EG2, lat: -35.3304213, lng: 149.1244262} - { name: Flinders Way,stop_code: Wjz4EG2, lat: -35.3304213, lng: 149.1244262}
- { name: Scarborough Street,stop_code: Wjz3KLn, lat: -35.3376003, lng: 149.1247297} - { name: Scarborough Street,stop_code: Wjz3KLn, lat: -35.3376003, lng: 149.1247297}
- { name: Captain Cook Crescent,stop_code: Wjz4NWF, lat: -35.3250038, lng: 149.138898} - { name: Captain Cook Crescent,stop_code: Wjz4NWF, lat: -35.3250038, lng: 149.138898}
- { name: Carnegie Cresent,stop_code: Wjz3_sf, lat: -35.3341586, lng: 149.1437982} - { name: Carnegie Cresent,stop_code: Wjz3_sf, lat: -35.3341586, lng: 149.1437982}
- { name: McKinlay Street,stop_code: Wjz4UIv, lat: -35.328635, lng: 149.1467867} - { name: McKinlay Street,stop_code: Wjz4UIv, lat: -35.328635, lng: 149.1467867}
- { name: Yamba Place,stop_code: Wjz4UYU, lat: -35.3292631, lng: 149.1503427} - { name: Yamba Place,stop_code: Wjz4UYU, lat: -35.3292631, lng: 149.1503427}
- { name: Mugga Way,stop_code: Wjz3KB0, lat: -35.3395291, lng: 149.1229469} - { name: Mugga Way,stop_code: Wjz3KB0, lat: -35.3395291, lng: 149.1229469}
- { name: Mugga Way,stop_code: Wjz3JQO, lat: -35.3455626, lng: 149.1268033} - { name: Mugga Way,stop_code: Wjz3JQO, lat: -35.3455626, lng: 149.1268033}
- { name: La Perouse Street,stop_code: Wjz3Slx, lat: -35.3394651, lng: 149.131936} - { name: La Perouse Street,stop_code: Wjz3Slx, lat: -35.3394651, lng: 149.131936}
- { name: Caley Crescent,stop_code: Wjz3TJe, lat: -35.3335378, lng: 149.135468} - { name: Caley Crescent,stop_code: Wjz3TJe, lat: -35.3335378, lng: 149.135468}
- { name: Goyder Street,stop_code: Wjzb79X, lat: -35.3365565, lng: 149.1529783} - { name: Goyder Street,stop_code: Wjzb79X, lat: -35.3365565, lng: 149.1529783}
- { name: Sir Harold Raggatt Drive,stop_code: Wjzb6EM, lat: -35.342941, lng: 149.1583643} - { name: Sir Harold Raggatt Drive,stop_code: Wjzb6EM, lat: -35.342941, lng: 149.1583643}
- { name: Toolambi Street,stop_code: Wjzb7Cp, lat: -35.333286, lng: 149.156475} - { name: Toolambi Street,stop_code: Wjzb7Cp, lat: -35.333286, lng: 149.156475}
- { name: Narrabundah Lane,stop_code: Wjz3YW3, lat: -35.3523419, lng: 149.1490844} - { name: Narrabundah Lane,stop_code: Wjz3YW3, lat: -35.3523419, lng: 149.1490844}
- { name: Newcastle Street,stop_code: Wjzc9PB, lat: -35.3239975, lng: 149.1704393} - { name: Newcastle Street,stop_code: Wjzc9PB, lat: -35.3239975, lng: 149.1704393}
- { name: Tennant Street,stop_code: Wjzcp0F, lat: -35.3263698, lng: 149.1843675} - { name: Tennant Street,stop_code: Wjzcp0F, lat: -35.3263698, lng: 149.1843675}
- { name: Tennant Street,stop_code: Wjzcg-_, lat: -35.3272591, lng: 149.1832438} - { name: Tennant Street,stop_code: Wjzcg-_, lat: -35.3272591, lng: 149.1832438}
- { name: Albany Street,stop_code: WjzcgSm, lat: -35.3273624, lng: 149.1809901} - { name: Albany Street,stop_code: WjzcgSm, lat: -35.3273624, lng: 149.1809901}
- { name: Yamba Drive,stop_code: Wjz3rML, lat: -35.3588381, lng: 149.1045644} - { name: Yamba Drive,stop_code: Wjz3rML, lat: -35.3588381, lng: 149.1045644}
- { name: Ellwood Crescent,stop_code: Wjz3y9z, lat: -35.3640453, lng: 149.1086104} - { name: Ellwood Crescent,stop_code: Wjz3y9z, lat: -35.3640453, lng: 149.1086104}
- { name: Julia Flynn Avenue,stop_code: Wjz3xi3, lat: -35.3688397, lng: 149.1093058} - { name: Julia Flynn Avenue,stop_code: Wjz3xi3, lat: -35.3688397, lng: 149.1093058}
- { name: Yamba Drive,stop_code: Wjz2DK6, lat: -35.3767783, lng: 149.1134151} - { name: Yamba Drive,stop_code: Wjz2DK6, lat: -35.3767783, lng: 149.1134151}
- { name: McAlpine Place,stop_code: Wjz2Dgb, lat: -35.381175, lng: 149.10938} - { name: McAlpine Place,stop_code: Wjz2Dgb, lat: -35.381175, lng: 149.10938}
- { name: Pye Place,stop_code: Wjz2vzR, lat: -35.3789646, lng: 149.1019944} - { name: Pye Place,stop_code: Wjz2vzR, lat: -35.3789646, lng: 149.1019944}
- { name: Custance Street,stop_code: Wjz3ops, lat: -35.3749061, lng: 149.1001427} - { name: Custance Street,stop_code: Wjz3ops, lat: -35.3749061, lng: 149.1001427}
- { name: Athllon Drive,stop_code: Wjz3hUs, lat: -35.370077, lng: 149.0946389} - { name: Athllon Drive,stop_code: Wjz3hUs, lat: -35.370077, lng: 149.0946389}
- { name: Ward Place,stop_code: Wjz3gUQ, lat: -35.3755566, lng: 149.0951557} - { name: Ward Place,stop_code: Wjz3gUQ, lat: -35.3755566, lng: 149.0951557}
- { name: Goode Street,stop_code: Wjz2f_R, lat: -35.3761632, lng: 149.0842481} - { name: Goode Street,stop_code: Wjz2f_R, lat: -35.3761632, lng: 149.0842481}
- { name: Hawker Street,stop_code: Wjz3g7D, lat: -35.3705636, lng: 149.085208} - { name: Hawker Street,stop_code: Wjz3g7D, lat: -35.3705636, lng: 149.085208}
- { name: Hyland Place,stop_code: Wjz2c-r, lat: -35.3935292, lng: 149.0837652} - { name: Hyland Place,stop_code: Wjz2c-r, lat: -35.3935292, lng: 149.0837652}
- { name: Beaver Place,stop_code: Wjz2civ, lat: -35.3959622, lng: 149.0767882} - { name: Beaver Place,stop_code: Wjz2civ, lat: -35.3959622, lng: 149.0767882}
- { name: Athllon Drive,stop_code: Wjz2mGO, lat: -35.3853996, lng: 149.0925014} - { name: Athllon Drive,stop_code: Wjz2mGO, lat: -35.3853996, lng: 149.0925014}
- { name: Sulwood Drive,stop_code: Wjz2ttB, lat: -35.3885662, lng: 149.1004148} - { name: Sulwood Drive,stop_code: Wjz2ttB, lat: -35.3885662, lng: 149.1004148}
- { name: Sternberg Crescent,stop_code: Wjz2rN0, lat: -35.4027536, lng: 149.1038057} - { name: Sternberg Crescent,stop_code: Wjz2rN0, lat: -35.4027536, lng: 149.1038057}
- { name: Sternberg Crescent,stop_code: Wjz2jPU, lat: -35.401368, lng: 149.0939538} - { name: Sternberg Crescent,stop_code: Wjz2jPU, lat: -35.401368, lng: 149.0939538}
- { name: Harricks Crescent,stop_code: Wjz2iwA, lat: -35.4085873, lng: 149.0906768} - { name: Harricks Crescent,stop_code: Wjz2iwA, lat: -35.4085873, lng: 149.0906768}
- { name: Leach Street,stop_code: Wjz2pmy, lat: -35.4100705, lng: 149.0990011} - { name: Leach Street,stop_code: Wjz2pmy, lat: -35.4100705, lng: 149.0990011}
- { name: Burston Place,stop_code: Wjz2xq1, lat: -35.4129044, lng: 149.1106334} - { name: Burston Place,stop_code: Wjz2xq1, lat: -35.4129044, lng: 149.1106334}
- { name: Garrick Street,stop_code: Wjz2yQZ, lat: -35.4057423, lng: 149.116007} - { name: Garrick Street,stop_code: Wjz2yQZ, lat: -35.4057423, lng: 149.116007}
- { name: Larcombe Crescent,stop_code: Wjz2G9R, lat: -35.4077654, lng: 149.1199409} - { name: Larcombe Crescent,stop_code: Wjz2G9R, lat: -35.4077654, lng: 149.1199409}
- { name: Halley Street,stop_code: Wjz2N0r, lat: -35.4141264, lng: 149.128949} - { name: Halley Street,stop_code: Wjz2N0r, lat: -35.4141264, lng: 149.128949}
- { name: Proctor Street,stop_code: Wjz2EB6, lat: -35.4159442, lng: 149.1230876} - { name: Proctor Street,stop_code: Wjz2EB6, lat: -35.4159442, lng: 149.1230876}
- { name: Webber Crescent,stop_code: Wjz1BFG, lat: -35.4354872, lng: 149.1142337} - { name: Webber Crescent,stop_code: Wjz1BFG, lat: -35.4354872, lng: 149.1142337}
- { name: Tweddle Place,stop_code: Wjz1CS7, lat: -35.4261448, lng: 149.1147427} - { name: Tweddle Place,stop_code: Wjz1CS7, lat: -35.4261448, lng: 149.1147427}
- { name: Wentcher Place,stop_code: Wjz1Dap, lat: -35.4239297, lng: 149.1084839} - { name: Wentcher Place,stop_code: Wjz1Dap, lat: -35.4239297, lng: 149.1084839}
- { name: Laker Crescent,stop_code: Wjz1Dlj, lat: -35.4217144, lng: 149.1096219} - { name: Laker Crescent,stop_code: Wjz1Dlj, lat: -35.4217144, lng: 149.1096219}
- { name: Kiddle Crescent,stop_code: Wjz1C75, lat: -35.4256297, lng: 149.1065242} - { name: Kiddle Crescent,stop_code: Wjz1C75, lat: -35.4256297, lng: 149.1065242}
- { name: Clift Crescent,stop_code: Wjz1vMs, lat: -35.4250115, lng: 149.1042483} - { name: Clift Crescent,stop_code: Wjz1vMs, lat: -35.4250115, lng: 149.1042483}
- { name: Ashley Drive,stop_code: Wjz1vJN, lat: -35.4218175, lng: 149.1034264} - { name: Ashley Drive,stop_code: Wjz1vJN, lat: -35.4218175, lng: 149.1034264}
- { name: Isabella Drive,stop_code: Wjz2w0e, lat: -35.4193446, lng: 149.106777} - { name: Isabella Drive,stop_code: Wjz2w0e, lat: -35.4193446, lng: 149.106777}
- { name: Barraclough Crescent,stop_code: Wjz2osQ, lat: -35.4167685, lng: 149.1006448} - { name: Barraclough Crescent,stop_code: Wjz2osQ, lat: -35.4167685, lng: 149.1006448}
- { name: Kneeshaw Street,stop_code: Wjz2o8V, lat: -35.4197567, lng: 149.0980528} - { name: Kneeshaw Street,stop_code: Wjz2o8V, lat: -35.4197567, lng: 149.0980528}
- { name: Isabella Drive,stop_code: Wjz1v6h, lat: -35.4211477, lng: 149.0958401} - { name: Isabella Drive,stop_code: Wjz1v6h, lat: -35.4211477, lng: 149.0958401}
- { name: Kerkeri Close,stop_code: Wjz1v2R, lat: -35.423569, lng: 149.0965355} - { name: Kerkeri Close,stop_code: Wjz1v2R, lat: -35.423569, lng: 149.0965355}
- { name: Oakwood Place,stop_code: Wjz1viP, lat: -35.4237236, lng: 149.0993804} - { name: Oakwood Place,stop_code: Wjz1viP, lat: -35.4237236, lng: 149.0993804}
- { name: Johnson Drive,stop_code: Wjz1BrK, lat: -35.4337687, lng: 149.1114553} - { name: Johnson Drive,stop_code: Wjz1BrK, lat: -35.4337687, lng: 149.1114553}
- { name: Costello Circuit,stop_code: Wjz1B9T, lat: -35.4350564, lng: 149.1089897} - { name: Costello Circuit,stop_code: Wjz1B9T, lat: -35.4350564, lng: 149.1089897}
- { name: Johnson Drive,stop_code: Wjz1tYG, lat: -35.4334596, lng: 149.1060816} - { name: Johnson Drive,stop_code: Wjz1tYG, lat: -35.4334596, lng: 149.1060816}
- { name: Johnson Drive,stop_code: Wjz1tR7, lat: -35.4323264, lng: 149.1038057} - { name: Johnson Drive,stop_code: Wjz1tR7, lat: -35.4323264, lng: 149.1038057}
- { name: Carter Crescent,stop_code: Wjz1tE0, lat: -35.4363442, lng: 149.1024781} - { name: Carter Crescent,stop_code: Wjz1tE0, lat: -35.4363442, lng: 149.1024781}
- { name: Outtrim Avenue,stop_code: Wjz1tok, lat: -35.4359836, lng: 149.0999494} - { name: Outtrim Avenue,stop_code: Wjz1tok, lat: -35.4359836, lng: 149.0999494}
- { name: Johnson Drive,stop_code: Wjz1tbe, lat: -35.4337687, lng: 149.0971677} - { name: Johnson Drive,stop_code: Wjz1tbe, lat: -35.4337687, lng: 149.0971677}
- { name: Marengo Place,stop_code: Wjz1lQS, lat: -35.4330991, lng: 149.0938171} - { name: Marengo Place,stop_code: Wjz1lQS, lat: -35.4330991, lng: 149.0938171}
- { name: Heddon Place,stop_code: Wjz1lyA, lat: -35.4346444, lng: 149.0907826} - { name: Heddon Place,stop_code: Wjz1lyA, lat: -35.4346444, lng: 149.0907826}
- { name: Pimpampa Close,stop_code: Wjz1lB8, lat: -35.4329445, lng: 149.0902136} - { name: Pimpampa Close,stop_code: Wjz1lB8, lat: -35.4329445, lng: 149.0902136}
- { name: Abercrombie Circuit,stop_code: Wjz0nS3, lat: -35.4649778, lng: 149.0928056} - { name: Abercrombie Circuit,stop_code: Wjz0nS3, lat: -35.4649778, lng: 149.0928056}
- { name: Youl Court,stop_code: Wjz0uuZ, lat: -35.4702296, lng: 149.1008976} - { name: Youl Court,stop_code: Wjz0uuZ, lat: -35.4702296, lng: 149.1008976}
- { name: Milligan Street,stop_code: Wjz0CcV, lat: -35.4719802, lng: 149.1091794} - { name: Milligan Street,stop_code: Wjz0CcV, lat: -35.4719802, lng: 149.1091794}
- { name: Galbraith Close,stop_code: Wjz0B6Y, lat: -35.4758415, lng: 149.1077253} - { name: Galbraith Close,stop_code: Wjz0B6Y, lat: -35.4758415, lng: 149.1077253}
- { name: Olive Pink Crescent,stop_code: Wjz0tB4, lat: -35.4765623, lng: 149.1010241} - { name: Olive Pink Crescent,stop_code: Wjz0tB4, lat: -35.4765623, lng: 149.1010241}
- { name: Bellchambers Crescent,stop_code: Wjz0mMT, lat: -35.474194, lng: 149.0937539} - { name: Bellchambers Crescent,stop_code: Wjz0mMT, lat: -35.474194, lng: 149.0937539}
- { name: Olive Pink Crescent,stop_code: Wjz0kYJ, lat: -35.482637, lng: 149.0950815} - { name: Olive Pink Crescent,stop_code: Wjz0kYJ, lat: -35.482637, lng: 149.0950815}
- { name: Tharwa Drive,stop_code: Wjz0lhu, lat: -35.4790849, lng: 149.0878745} - { name: Tharwa Drive,stop_code: Wjz0lhu, lat: -35.4790849, lng: 149.0878745}
- { name: Robert Lewis Court,stop_code: Wjz0eRx, lat: -35.4713109, lng: 149.0824376} - { name: Robert Lewis Court,stop_code: Wjz0eRx, lat: -35.4713109, lng: 149.0824376}
- { name: Ferry Place,stop_code: Wjz1gaC, lat: -35.4619398, lng: 149.0865469} - { name: Ferry Place,stop_code: Wjz1gaC, lat: -35.4619398, lng: 149.0865469}
- { name: Charles Place,stop_code: Wjz19V7, lat: -35.4570479, lng: 149.0831962} - { name: Charles Place,stop_code: Wjz19V7, lat: -35.4570479, lng: 149.0831962}
- { name: Gaylard Place,stop_code: Wjz1i2p, lat: -35.4513833, lng: 149.0850928} - { name: Gaylard Place,stop_code: Wjz1i2p, lat: -35.4513833, lng: 149.0850928}
- { name: Clare Dennis Avenue,stop_code: Wjz1jf0, lat: -35.442525, lng: 149.0859147} - { name: Clare Dennis Avenue,stop_code: Wjz1jf0, lat: -35.442525, lng: 149.0859147}
- { name: Northbourne Avenue,stop_code: Wjz5RvC, lat: -35.2552151, lng: 149.1332875} - { name: Northbourne Avenue,stop_code: Wjz5RvC, lat: -35.2552151, lng: 149.1332875}
- { name: Northbourne Avenue,stop_code: Wjz5Oci, lat: -35.2741724, lng: 149.1302168} - { name: Northbourne Avenue,stop_code: Wjz5Oci, lat: -35.2741724, lng: 149.1302168}
- { name: Northbourne Avenue,stop_code: Wjz5N4m, lat: -35.279266, lng: 149.1287817} - { name: Northbourne Avenue,stop_code: Wjz5N4m, lat: -35.279266, lng: 149.1287817}
- { name: Northbourne Avenue,stop_code: Wjz5PdJ, lat: -35.2676612, lng: 149.1306865} - { name: Northbourne Avenue,stop_code: Wjz5PdJ, lat: -35.2676612, lng: 149.1306865}
- { name: Northbourne Avenue,stop_code: Wjz5Qmu, lat: -35.2613932, lng: 149.1316889} - { name: Northbourne Avenue,stop_code: Wjz5Qmu, lat: -35.2613932, lng: 149.1316889}
- { name: Northbourne Avenue,stop_code: Wjz5Sux, lat: -35.2509191, lng: 149.1333899} - { name: Northbourne Avenue,stop_code: Wjz5Sux, lat: -35.2509191, lng: 149.1333899}
- { name: Cameron Avenue,stop_code: Wjz60QI, lat: -35.2410106, lng: 149.0717141} - { name: Cameron Avenue,stop_code: Wjz60QI, lat: -35.2410106, lng: 149.0717141}
- { name: Cameron Avenue,stop_code: Wjz60Y4, lat: -35.2410195, lng: 149.0722506} - { name: Cameron Avenue,stop_code: Wjz60Y4, lat: -35.2410195, lng: 149.0722506}
- { name: Cameron Avenue,stop_code: Wjz60QW, lat: -35.241186, lng: 149.0720789} - { name: Cameron Avenue,stop_code: Wjz60QW, lat: -35.241186, lng: 149.0720789}
- { name: Cameron Avenue,stop_code: Wjz60Qa, lat: -35.2411772, lng: 149.0709792} - { name: Cameron Avenue,stop_code: Wjz60Qa, lat: -35.2411772, lng: 149.0709792}
- { name: Cameron Avenue,stop_code: Wjz60Qc, lat: -35.2410063, lng: 149.0710758} - { name: Cameron Avenue,stop_code: Wjz60Qc, lat: -35.2410063, lng: 149.0710758}
- { name: Wilari Place,stop_code: Wjz6u3h, lat: -35.2089622, lng: 149.095889} - { name: Wilari Place,stop_code: Wjz6u3h, lat: -35.2089622, lng: 149.095889}
- { name: Mirrabucca Crescent,stop_code: Wjz6u32, lat: -35.2088899, lng: 149.09552} - { name: Mirrabucca Crescent,stop_code: Wjz6u32, lat: -35.2088899, lng: 149.09552}
- { name: Buriga Street,stop_code: Wjz6mOx, lat: -35.20966, lng: 149.0935299} - { name: Buriga Street,stop_code: Wjz6mOx, lat: -35.20966, lng: 149.0935299}
- { name: Georgina Crescent,stop_code: Wjz6sHv, lat: -35.21947, lng: 149.10295} - { name: Georgina Crescent,stop_code: Wjz6sHv, lat: -35.21947, lng: 149.10295}
- { name: Staaten Crescent,stop_code: Wjz6sZ1, lat: -35.21859, lng: 149.10511} - { name: Staaten Crescent,stop_code: Wjz6sZ1, lat: -35.21859, lng: 149.10511}
- { name: Chuculba Crescent,stop_code: Wjz6uhX, lat: -35.2101981, lng: 149.0994957} - { name: Chuculba Crescent,stop_code: Wjz6uhX, lat: -35.2101981, lng: 149.0994957}
- { name: Antares Crescent,stop_code: Wjz6uwF, lat: -35.2110747, lng: 149.1018989} - { name: Antares Crescent,stop_code: Wjz6uwF, lat: -35.2110747, lng: 149.1018989}
- { name: Snodgrass Crescent,stop_code: WjrWYHH, lat: -35.3956133, lng: 149.0592665} - { name: Snodgrass Crescent,stop_code: WjrWYHH, lat: -35.3956133, lng: 149.0592665}
- { name: O'Halloran Circuit,stop_code: WjrWYDE, lat: -35.3931009, lng: 149.0580053} - { name: O'Halloran Circuit,stop_code: WjrWYDE, lat: -35.3931009, lng: 149.0580053}
- { name: Snodgrass Crescent,stop_code: WjrWYHE, lat: -35.3958129, lng: 149.0592983} - { name: Snodgrass Crescent,stop_code: WjrWYHE, lat: -35.3958129, lng: 149.0592983}
- { name: Chuculba Crescent,stop_code: Wjz6sdP, lat: -35.21844, lng: 149.0979199} - { name: Chuculba Crescent,stop_code: Wjz6sdP, lat: -35.21844, lng: 149.0979199}
- { name: Canopus Crescent,stop_code: Wjz6t3F, lat: -35.21451, lng: 149.09646} - { name: Canopus Crescent,stop_code: Wjz6t3F, lat: -35.21451, lng: 149.09646}
- { name: Purdie Street,stop_code: Wjz5nwb, lat: -35.2493711, lng: 149.0901523} - { name: Purdie Street,stop_code: Wjz5nwb, lat: -35.2493711, lng: 149.0901523}
- { name: Haydon Drive,stop_code: Wjz5mbS, lat: -35.2525252, lng: 149.0869819} - { name: Haydon Drive,stop_code: Wjz5mbS, lat: -35.2525252, lng: 149.0869819}
- { name: Bindubi Street,stop_code: Wjz5e0m, lat: -35.2546115, lng: 149.0739747} - { name: Bindubi Street,stop_code: Wjz5e0m, lat: -35.2546115, lng: 149.0739747}
- { name: Morphy Place,stop_code: Wjz55V-, lat: -35.2594169, lng: 149.0733684} - { name: Morphy Place,stop_code: Wjz55V-, lat: -35.2594169, lng: 149.0733684}
- { name: Gwydir Square,stop_code: Wjz6pLk, lat: -35.2334807, lng: 149.1028323} - { name: Gwydir Square,stop_code: Wjz6pLk, lat: -35.2334807, lng: 149.1028323}
- { name: William Slim Drive,stop_code: Wjz6mip, lat: -35.2096535, lng: 149.0878294} - { name: William Slim Drive,stop_code: Wjz6mip, lat: -35.2096535, lng: 149.0878294}
- { name: Ellenborough Street,stop_code: Wjz6yzH, lat: -35.2308034, lng: 149.1129136} - { name: Ellenborough Street,stop_code: Wjz6yzH, lat: -35.2308034, lng: 149.1129136}
- { name: Moruya Circuit,stop_code: Wjz6Apy, lat: -35.2213073, lng: 149.1113204} - { name: Moruya Circuit,stop_code: Wjz6Apy, lat: -35.2213073, lng: 149.1113204}
- { name: Aikman Drive,stop_code: Wjz69vO, lat: -35.2336108, lng: 149.0786617} - { name: Aikman Drive,stop_code: Wjz69vO, lat: -35.2336108, lng: 149.0786617}
- { name: Baldwin Drive,stop_code: Wjz6iN7, lat: -35.2318153, lng: 149.0928498} - { name: Baldwin Drive,stop_code: Wjz6iN7, lat: -35.2318153, lng: 149.0928498}
- { name: Baldwin Drive,stop_code: Wjz6iNm, lat: -35.2318811, lng: 149.0930643} - { name: Baldwin Drive,stop_code: Wjz6iNm, lat: -35.2318811, lng: 149.0930643}
- { name: Anketell Street,stop_code: Wjz213q, lat: -35.4121336, lng: 149.063177} - { name: Anketell Street,stop_code: Wjz213q, lat: -35.4121336, lng: 149.063177}
- { name: Athllon Drive,stop_code: Wjz3gK-, lat: -35.3712753, lng: 149.0926679} - { name: Athllon Drive,stop_code: Wjz3gK-, lat: -35.3712753, lng: 149.0926679}
- { name: Athllon Drive,stop_code: Wjz3kAx, lat: -35.3511369, lng: 149.0906806} - { name: Athllon Drive,stop_code: Wjz3kAx, lat: -35.3511369, lng: 149.0906806}
- { name: Athllon Drive,stop_code: Wjz3iFK, lat: -35.3637163, lng: 149.0922629} - { name: Athllon Drive,stop_code: Wjz3iFK, lat: -35.3637163, lng: 149.0922629}
- { name: Maribyrnong Avenue,stop_code: Wjz6qe4, lat: -35.2286658, lng: 149.0969557} - { name: Maribyrnong Avenue,stop_code: Wjz6qe4, lat: -35.2286658, lng: 149.0969557}
- { name: Ashburton Circuit,stop_code: Wjz6zAP, lat: -35.2246234, lng: 149.113116} - { name: Ashburton Circuit,stop_code: Wjz6zAP, lat: -35.2246234, lng: 149.113116}
- { name: Daley Road,stop_code: Wjz5yYV, lat: -35.2742188, lng: 149.1173067} - { name: Daley Road,stop_code: Wjz5yYV, lat: -35.2742188, lng: 149.1173067}
- { name: Bradley Street,stop_code: Wjz3ldS, lat: -35.3445222, lng: 149.0870435} - { name: Bradley Street,stop_code: Wjz3ldS, lat: -35.3445222, lng: 149.0870435}
- { name: Bradley Street,stop_code: Wjz3ldT, lat: -35.3444271, lng: 149.0869631} - { name: Bradley Street,stop_code: Wjz3ldT, lat: -35.3444271, lng: 149.0869631}
- { name: Bradley Street,stop_code: Wjz3ldJ, lat: -35.344566, lng: 149.086774} - { name: Bradley Street,stop_code: Wjz3ldJ, lat: -35.344566, lng: 149.086774}
- { name: Callam Street,stop_code: Wjz3llf, lat: -35.34445, lng: 149.0875371} - { name: Callam Street,stop_code: Wjz3llf, lat: -35.34445, lng: 149.0875371}
- { name: Athllon Drive,stop_code: Wjz3kyX, lat: -35.3523555, lng: 149.0913002} - { name: Athllon Drive,stop_code: Wjz3kyX, lat: -35.3523555, lng: 149.0913002}
- { name: Pitman,stop_code: Wjz20nf, lat: -35.4144924, lng: 149.0655423} - { name: Pitman,stop_code: Wjz20nf, lat: -35.4144924, lng: 149.0655423}
- { name: Eileen Good Street,stop_code: Wjz218U, lat: -35.4143897, lng: 149.0652364} - { name: Eileen Good Street,stop_code: Wjz218U, lat: -35.4143897, lng: 149.0652364}
- { name: Cohen Street,stop_code: Wjr-USo, lat: -35.2400027, lng: 149.0603149} - { name: Cohen Street,stop_code: Wjr-USo, lat: -35.2400027, lng: 149.0603149}
- { name: Spinifex Street,stop_code: Wjzc24u, lat: -35.317722, lng: 149.1510115} - { name: Spinifex Street,stop_code: Wjzc24u, lat: -35.317722, lng: 149.1510115}
- { name: The Causeway,stop_code: Wjz4WZo, lat: -35.3175809, lng: 149.1496027} - { name: The Causeway,stop_code: Wjz4WZo, lat: -35.3175809, lng: 149.1496027}
- { name: Parbery Street,stop_code: Wjz4WY7, lat: -35.3176372, lng: 149.1491419} - { name: Parbery Street,stop_code: Wjz4WY7, lat: -35.3176372, lng: 149.1491419}
- { name: Perry Drive,stop_code: WjrXPbu, lat: -35.3568919, lng: 149.0424224} - { name: Perry Drive,stop_code: WjrXPbu, lat: -35.3568919, lng: 149.0424224}
- { name: Darwinia Terrace,stop_code: WjrXI5s, lat: -35.3501807, lng: 149.0301549} - { name: Darwinia Terrace,stop_code: WjrXI5s, lat: -35.3501807, lng: 149.0301549}
- { name: Darwinia Terrace,stop_code: WjrXIqk, lat: -35.3522608, lng: 149.0341457} - { name: Darwinia Terrace,stop_code: WjrXIqk, lat: -35.3522608, lng: 149.0341457}
- { name: Perry Drive,stop_code: WjrXOn_, lat: -35.359526, lng: 149.0445552} - { name: Perry Drive,stop_code: WjrXOn_, lat: -35.359526, lng: 149.0445552}
- { name: Streeton Drive,stop_code: WjrXPR4, lat: -35.3556673, lng: 149.048857} - { name: Streeton Drive,stop_code: WjrXPR4, lat: -35.3556673, lng: 149.048857}
- { name: Fremantle Drive,stop_code: WjrXQOh, lat: -35.3524926, lng: 149.049231} - { name: Fremantle Drive,stop_code: WjrXQOh, lat: -35.3524926, lng: 149.049231}
- { name: Bunbury Street,stop_code: WjrXRMq, lat: -35.3483271, lng: 149.0492963} - { name: Bunbury Street,stop_code: WjrXRMq, lat: -35.3483271, lng: 149.0492963}
- { name: Fremantle Drive,stop_code: WjrXRzE, lat: -35.3464066, lng: 149.0469632} - { name: Fremantle Drive,stop_code: WjrXRzE, lat: -35.3464066, lng: 149.0469632}
- { name: Parkinson Street,stop_code: WjrX-0-, lat: -35.3424839, lng: 149.052828} - { name: Parkinson Street,stop_code: WjrX-0-, lat: -35.3424839, lng: 149.052828}
- { name: Backler Place,stop_code: WjrXZv5, lat: -35.3432647, lng: 149.0558034} - { name: Backler Place,stop_code: WjrXZv5, lat: -35.3432647, lng: 149.0558034}
- { name: Gilmore Crescent,stop_code: Wjz3BfO, lat: -35.3434784, lng: 149.1088951} - { name: Gilmore Crescent,stop_code: Wjz3BfO, lat: -35.3434784, lng: 149.1088951}
routes: routes:
   
--- ---
time_points: [Fairbairn Park, Brindabella Business Park, Majura Business Park, Campbell Park Offices, ADFA, War Memorial Limestone Ave, City Bus Station (Platform 4), Caswell Drive, Aranda Shops, Cook Shops, Jamison Centre, Belconnen Community Bus Station, Westfield Bus Station, Cohen Street Bus Station] time_points: [Fairbairn Park, Brindabella Business Park, Majura Business Park, Campbell Park Offices, ADFA, War Memorial / Limestone Ave, City Bus Station (Platform 4), Caswell Drive, Aranda, Cook, Jamison Centre, Belconnen Community Bus Station, Westfield Bus Station, Cohen Street Bus Station]
long_name: To Cohen Street Bus Station long_name: To Cohen Street Bus Station
between_stops: between_stops:
Westfield Bus Station-Cohen Street Bus Station: [] Westfield Bus Station-Cohen Street Bus Station: []
Belconnen Community Bus Station-Westfield Bus Station: [] Belconnen Community Bus Station-Westfield Bus Station: []
ADFA-War Memorial Limestone Ave: [Wjzcend, Wjzce4H, Wjzce7O, Wjzd8br, Wjzd0CK, Wjz5VUU]  
Campbell Park Offices-ADFA: [Wjzce7O, Wjzce4H, Wjzcend] Campbell Park Offices-ADFA: [Wjzce7O, Wjzce4H, Wjzcend]
War Memorial Limestone Ave-City Bus Station (Platform 4): [Wjz5VFA, Wjz5VAq, Wjz5W8l, Wjz5V64, Wjz5NRJ, Wjz5NHD, Wjz5NAQ]  
Fairbairn Park-Brindabella Business Park: [WjzcJ38, WjzcBHZ, WjzcJ0K, WjzcrEu, WjzcrrQ, WjzcrK3] Fairbairn Park-Brindabella Business Park: [WjzcJ38, WjzcBHZ, WjzcJ0K, WjzcrEu, WjzcrrQ, WjzcrK3]
short_name: "10" short_name: "10"
stop_times: [["-", "-", "-", "-", "-", "-", 632a, 642a, 644a, 649a, 659a, 709a, 711a, 716a], ["-", "-", "-", "-", "-", "-", 702a, 712a, 714a, 719a, 729a, 739a, 741a, 746a], ["-", "-", "-", "-", "-", "-", 732a, 742a, 744a, 749a, 759a, 809a, 811a, 816a], ["-", "-", "-", "-", "-", "-", 802a, 812a, 814a, 819a, 829a, 839a, 841a, 846a], ["-", "-", "-", 800a, 803a, 808a, 820a, 830a, 832a, 837a, 847a, 857a, 859a, 904a], ["-", "-", "-", 830a, 833a, 838a, 850a, 900a, 902a, 907a, 917a, 927a, 929a, 934a], ["-", "-", "-", 900a, 903a, 908a, 920a, 930a, 932a, 937a, 947a, 957a, 959a, 1004a], [918a, 928a, 933a, 940a, 943a, 948a, 1000a, 1010a, 1012a, 1017a, 1027a, 1037a, 1039a, 1044a], [948a, 958a, 1003a, 1010a, 1013a, 1018a, 1030a, 1040a, 1042a, 1047a, 1057a, 1107a, 1109a, 1114a], [1018a, 1028a, 1033a, 1040a, 1043a, 1048a, 1100a, 1110a, 1112a, 1117a, 1127a, 1137a, 1139a, 1144a], [1048a, 1058a, 1103a, 1110a, 1113a, 1118a, 1130a, 1140a, 1142a, 1147a, 1157a, 1207p, 1209p, 1214p], [1118a, 1128a, 1133a, 1140a, 1143a, 1148a, 1200p, 1210p, 1212p, 1217p, 1227p, 1237p, 1239p, 1244p], [1148a, 1158a, 1203p, 1210p, 1213p, 1218p, 1230p, 1240p, 1242p, 1247p, 1257p, 107p, 109p, 114p], [1218p, 1228p, 1233p, 1240p, 1243p, 1248p, 100p, 110p, 112p, 117p, 127p, 137p, 139p, 144p], [1248p, 1258p, 103p, 110p, 113p, 118p, 130p, 140p, 142p, 147p, 157p, 207p, 209p, 214p], [118p, 128p, 133p, 140p, 143p, 148p, 200p, 210p, 212p, 217p, 227p, 237p, 239p, 244p], [148p, 158p, 203p, 210p, 213p, 218p, 230p, 240p, 242p, 247p, 257p, 307p, 309p, 314p], [218p, 228p, 233p, 240p, 243p, 248p, 300p, 310p, 313p, 318p, 328p, 338p, 340p, 345p], [248p, 258p, 303p, 310p, 314p, 319p, 331p, 341p, 344p, 349p, 359p, 409p, 411p, 416p], [318p, 328p, 333p, 340p, 344p, 349p, 401p, 411p, 414p, 419p, 429p, 439p, 441p, 446p], ["-", "-", "-", "-", "-", "-", 416p, 426p, 429p, 434p, 444p, 454p, 456p, 501p], [348p, 358p, 403p, 410p, 414p, 419p, 431p, 441p, 444p, 449p, 459p, 509p, 511p, 516p], ["-", "-", "-", "-", "-", "-", 446p, 456p, 459p, 504p, 514p, 524p, 526p, 531p], ["-", "-", 431p, 441p, 445p, 450p, 502p, 512p, 515p, 520p, 530p, 537p, 539p, 544p], ["-", "-", "-", "-", "-", "-", 516p, 526p, 529p, 534p, 544p, 554p, 556p, 601p], ["-", "-", 458p, 511p, 515p, 520p, 532p, 542p, 545p, 550p, 600p, 610p, 612p, 617p], ["-", "-", "-", "-", "-", "-", 546p, 556p, 559p, 604p, 614p, 624p, 626p, 631p], ["-", "-", "-", 540p, 544p, 549p, 601p, 611p, 614p, 619p, 629p, 639p, 641p, 646p], ["-", "-", "-", "-", "-", "-", 616p, 626p, 629p, 634p, 644p, 654p, 656p, 701p], ["-", "-", "-", 611p, 615p, 620p, 632p, 642p, 644p, 649p, 659p, 709p, 711p, 716p], ["-", "-", "-", "-", "-", "-", 736p, 746p, 748p, 753p, 803p, 813p, 815p, 820p], ["-", "-", "-", "-", "-", "-", 836p, 846p, 848p, 853p, 903p, 913p, 915p, 920p], ["-", "-", "-", "-", "-", "-", 936p, 946p, 948p, 953p, 1003p, 1013p, 1015p, 1020p], ["-", "-", "-", "-", "-", "-", 1036p, 1046p, 1048p, 1053p, 1103p, 1113p, 1115p, 1120p], ["-", "-", "-", "-", "-", "-", 1136p, 1146p, 1148p, 1153p, 1203a, 1213a, 1215a, 1220a]] stop_times: [["-", "-", "-", "-", "-", "-", 632a, 642a, 644a, 649a, 659a, 709a, 711a, 716a], ["-", "-", "-", "-", "-", "-", 702a, 712a, 714a, 719a, 729a, 739a, 741a, 746a], ["-", "-", "-", "-", "-", "-", 732a, 742a, 744a, 749a, 759a, 809a, 811a, 816a], ["-", "-", "-", "-", "-", "-", 802a, 812a, 814a, 819a, 829a, 839a, 841a, 846a], ["-", "-", "-", 800a, 803a, 808a, 820a, 830a, 832a, 837a, 847a, 857a, 859a, 904a], ["-", "-", "-", 830a, 833a, 838a, 850a, 900a, 902a, 907a, 917a, 927a, 929a, 934a], ["-", "-", "-", 900a, 903a, 908a, 920a, 930a, 932a, 937a, 947a, 957a, 959a, 1004a], [918a, 928a, 933a, 940a, 943a, 948a, 1000a, 1010a, 1012a, 1017a, 1027a, 1037a, 1039a, 1044a], [948a, 958a, 1003a, 1010a, 1013a, 1018a, 1030a, 1040a, 1042a, 1047a, 1057a, 1107a, 1109a, 1114a], [1018a, 1028a, 1033a, 1040a, 1043a, 1048a, 1100a, 1110a, 1112a, 1117a, 1127a, 1137a, 1139a, 1144a], [1048a, 1058a, 1103a, 1110a, 1113a, 1118a, 1130a, 1140a, 1142a, 1147a, 1157a, 1207p, 1209p, 1214p], [1118a, 1128a, 1133a, 1140a, 1143a, 1148a, 1200p, 1210p, 1212p, 1217p, 1227p, 1237p, 1239p, 1244p], [1148a, 1158a, 1203p, 1210p, 1213p, 1218p, 1230p, 1240p, 1242p, 1247p, 1257p, 107p, 109p, 114p], [1218p, 1228p, 1233p, 1240p, 1243p, 1248p, 100p, 110p, 112p, 117p, 127p, 137p, 139p, 144p], [1248p, 1258p, 103p, 110p, 113p, 118p, 130p, 140p, 142p, 147p, 157p, 207p, 209p, 214p], [118p, 128p, 133p, 140p, 143p, 148p, 200p, 210p, 212p, 217p, 227p, 237p, 239p, 244p], [148p, 158p, 203p, 210p, 213p, 218p, 230p, 240p, 242p, 247p, 257p, 307p, 309p, 314p], [218p, 228p, 233p, 240p, 243p, 248p, 300p, 310p, 313p, 318p, 328p, 338p, 340p, 345p], [248p, 258p, 303p, 310p, 314p, 319p, 331p, 341p, 344p, 349p, 359p, 409p, 411p, 416p], [318p, 328p, 333p, 340p, 344p, 349p, 401p, 411p, 414p, 419p, 429p, 439p, 441p, 446p], ["-", "-", "-", "-", "-", "-", 416p, 426p, 429p, 434p, 444p, 454p, 456p, 501p], [348p, 358p, 403p, 410p, 414p, 419p, 431p, 441p, 444p, 449p, 459p, 509p, 511p, 516p], ["-", "-", "-", "-", "-", "-", 446p, 456p, 459p, 504p, 514p, 524p, 526p, 531p], ["-", "-", 431p, 441p, 445p, 450p, 502p, 512p, 515p, 520p, 530p, 537p, 539p, 544p], ["-", "-", "-", "-", "-", "-", 516p, 526p, 529p, 534p, 544p, 554p, 556p, 601p], ["-", "-", 458p, 511p, 515p, 520p, 532p, 542p, 545p, 550p, 600p, 610p, 612p, 617p], ["-", "-", "-", "-", "-", "-", 546p, 556p, 559p, 604p, 614p, 624p, 626p, 631p], ["-", "-", "-", 540p, 544p, 549p, 601p, 611p, 614p, 619p, 629p, 639p, 641p, 646p], ["-", "-", "-", "-", "-", "-", 616p, 626p, 629p, 634p, 644p, 654p, 656p, 701p], ["-", "-", "-", 611p, 615p, 620p, 632p, 642p, 644p, 649p, 659p, 709p, 711p, 716p], ["-", "-", "-", "-", "-", "-", 736p, 746p, 748p, 753p, 803p, 813p, 815p, 820p], ["-", "-", "-", "-", "-", "-", 836p, 846p, 848p, 853p, 903p, 913p, 915p, 920p], ["-", "-", "-", "-", "-", "-", 936p, 946p, 948p, 953p, 1003p, 1013p, 1015p, 1020p], ["-", "-", "-", "-", "-", "-", 1036p, 1046p, 1048p, 1053p, 1103p, 1113p, 1115p, 1120p], ["-", "-", "-", "-", "-", "-", 1136p, 1146p, 1148p, 1153p, 1203a, 1213a, 1215a, 1220a]]
   
--- ---
time_points: [Cohen Street Bus Station (Platform 2), Westfield Bus Station (Platform 1), Belconnen Community Bus Station (Platform 3), Jamison Centre, Cook Shops, Aranda Shops, Caswell Drive, City Bus Station (Platform 7), War Memorial Limestone Ave, ADFA, Campbell Park Offices, Majura Business Park, Brindabella Business Park, Fairbairn Park] time_points: [Cohen Street Bus Station (Platform 2), Westfield Bus Station (Platform 1), Belconnen Community Bus Station (Platform 3), Jamison Centre, Cook, Aranda, Caswell Drive, City Bus Station (Platform 7), War Memorial / Limestone Ave, ADFA, Campbell Park Offices, Majura Business Park, Brindabella Business Park, Fairbairn Park]
long_name: To Fairbairn Park long_name: To Fairbairn Park
between_stops: between_stops:
City Bus Station (Platform 7)-War Memorial Limestone Ave: [Wjz5NAQ, Wjz5NHD, Wjz5NRJ, Wjz5V64, Wjz5W8l, Wjz5VAq, Wjz5VFA]  
Brindabella Business Park-Fairbairn Park: [WjzcrK3, WjzcrrQ, WjzcrEu, WjzcJ0K, WjzcBHZ, WjzcJ38] Brindabella Business Park-Fairbairn Park: [WjzcrK3, WjzcrrQ, WjzcrEu, WjzcJ0K, WjzcBHZ, WjzcJ38]
Westfield Bus Station (Platform 1)-Belconnen Community Bus Station (Platform 3): [] Westfield Bus Station (Platform 1)-Belconnen Community Bus Station (Platform 3): []
War Memorial Limestone Ave-ADFA: [Wjz5VUU, Wjzd0CK, Wjzd8br, Wjzce7O, Wjzce4H, Wjzcend]  
Cohen Street Bus Station (Platform 2)-Westfield Bus Station (Platform 1): [] Cohen Street Bus Station (Platform 2)-Westfield Bus Station (Platform 1): []
ADFA-Campbell Park Offices: [Wjzcend, Wjzce4H, Wjzce7O] ADFA-Campbell Park Offices: [Wjzcend, Wjzce4H, Wjzce7O]
short_name: "10" short_name: "10"
stop_times: [[550a, 552a, 556a, 605a, 615a, 620a, 623a, 634a, "-", "-", "-", "-", "-", "-"], [621a, 623a, 627a, 636a, 646a, 651a, 654a, 705a, "-", "-", "-", "-", "-", "-"], [651a, 653a, 657a, 706a, 716a, 721a, 724a, 736a, 746a, 752a, 756a, 803a, "-", "-"], ["-", "-", "-", "-", 724a, 729a, 732a, 743a, "-", "-", "-", "-", "-", "-"], [706a, 708a, 712a, 721a, 731a, 736a, 739a, 750a, "-", "-", "-", "-", "-", "-"], [721a, 723a, 727a, 736a, 746a, 751a, 754a, 806a, 816a, 822a, 826a, 833a, "-", "-"], ["-", "-", "-", "-", 754a, 759a, 802a, 813a, "-", "-", "-", "-", "-", "-"], [736a, 738a, 742a, 751a, 801a, 806a, 809a, 820a, "-", "-", "-", "-", "-", "-"], [751a, 753a, 757a, 806a, 816a, 821a, 824a, 836a, 846a, 852a, 856a, "-", "-", "-"], [806a, 808a, 812a, 821a, 831a, 836a, 839a, 851a, 901a, 907a, 911a, 918a, 927a, 937a], [821a, 823a, 827a, 836a, 846a, 851a, 854a, 905a, "-", "-", "-", "-", "-", "-"], [836a, 838a, 842a, 851a, 901a, 906a, 909a, 921a, 931a, 937a, 940a, 947a, 956a, 1006a], [851a, 853a, 857a, 906a, 916a, 921a, 924a, 936a, 946a, 952a, 955a, 1002a, 1011a, 1021a], [913a, 915a, 919a, 928a, 938a, 943a, 945a, 957a, 1007a, 1013a, 1016a, 1023a, 1032a, 1042a], [943a, 945a, 949a, 958a, 1008a, 1013a, 1015a, 1027a, 1037a, 1043a, 1046a, 1053a, 1102a, 1112a], [1013a, 1015a, 1019a, 1028a, 1038a, 1043a, 1045a, 1057a, 1107a, 1113a, 1116a, 1123a, 1132a, 1142a], [1043a, 1045a, 1049a, 1058a, 1108a, 1113a, 1115a, 1127a, 1137a, 1143a, 1146a, 1153a, 1202p, 1212p], [1113a, 1115a, 1119a, 1128a, 1138a, 1143a, 1145a, 1157a, 1207p, 1213p, 1216p, 1223p, 1232p, 1242p], [1143a, 1145a, 1149a, 1158a, 1208p, 1213p, 1215p, 1227p, 1237p, 1243p, 1246p, 1253p, 102p, 112p], [1213p, 1215p, 1219p, 1228p, 1238p, 1243p, 1245p, 1257p, 107p, 113p, 116p, 123p, 132p, 142p], [1243p, 1245p, 1249p, 1258p, 108p, 113p, 115p, 127p, 137p, 143p, 146p, 153p, 202p, 212p], [113p, 115p, 119p, 128p, 138p, 143p, 145p, 157p, 207p, 213p, 216p, 223p, 232p, 242p], [143p, 145p, 149p, 158p, 208p, 213p, 215p, 227p, 237p, 243p, 246p, 253p, 302p, 312p], [213p, 215p, 219p, 228p, 238p, 243p, 245p, 257p, 307p, 313p, 317p, 324p, 333p, 343p], [253p, 255p, 259p, 308p, 318p, 323p, 325p, 337p, 347p, 353p, 357p, 404p, 413p, 423p], [323p, 325p, 329p, 338p, 348p, 353p, 355p, 407p, 417p, 423p, 427p, 434p, 443p, 453p], [338p, 340p, 344p, 353p, 403p, 408p, 410p, 421p, "-", "-", "-", "-", "-", "-"], [353p, 355p, 359p, 408p, 418p, 423p, 425p, 437p, 447p, 453p, 457p, "-", "-", "-"], [408p, 410p, 414p, 423p, 433p, 438p, 440p, 451p, "-", "-", "-", "-", "-", "-"], [423p, 425p, 429p, 438p, 448p, 453p, 455p, 506p, "-", "-", "-", "-", "-", "-"], [438p, 440p, 444p, 453p, 503p, 508p, 510p, 521p, "-", "-", "-", "-", "-", "-"], [453p, 455p, 459p, 508p, 518p, 523p, 525p, 536p, "-", "-", "-", "-", "-", "-"], [508p, 510p, 514p, 523p, 533p, 538p, 540p, 551p, "-", "-", "-", "-", "-", "-"], [523p, 525p, 529p, 538p, 548p, 553p, 555p, 606p, "-", "-", "-", "-", "-", "-"], [538p, 540p, 544p, 553p, 603p, 608p, 610p, 621p, "-", "-", "-", "-", "-", "-"], [617p, 619p, 623p, 632p, 642p, 647p, 649p, 700p, "-", "-", "-", "-", "-", "-"], [716p, 718p, 722p, 731p, 741p, 746p, 748p, 759p, "-", "-", "-", "-", "-", "-"], [816p, 818p, 822p, 831p, 841p, 846p, 848p, 859p, "-", "-", "-", "-", "-", "-"], [916p, 918p, 922p, 931p, 941p, 946p, 948p, 959p, "-", "-", "-", "-", "-", "-"], [1016p, 1018p, 1022p, 1031p, 1041p, 1046p, 1048p, 1059p, "-", "-", "-", "-", "-", "-"], [1116p, 1118p, 1122p, 1131p, 1141p, 1146p, 1148p, "-", "-", "-", "-", "-", "-", "-"]] stop_times: [[550a, 552a, 556a, 605a, 615a, 620a, 623a, 634a, "-", "-", "-", "-", "-", "-"], [621a, 623a, 627a, 636a, 646a, 651a, 654a, 705a, "-", "-", "-", "-", "-", "-"], [651a, 653a, 657a, 706a, 716a, 721a, 724a, 736a, 746a, 752a, 756a, 803a, "-", "-"], ["-", "-", "-", "-", 724a, 729a, 732a, 743a, "-", "-", "-", "-", "-", "-"], [706a, 708a, 712a, 721a, 731a, 736a, 739a, 750a, "-", "-", "-", "-", "-", "-"], [721a, 723a, 727a, 736a, 746a, 751a, 754a, 806a, 816a, 822a, 826a, 833a, "-", "-"], ["-", "-", "-", "-", 754a, 759a, 802a, 813a, "-", "-", "-", "-", "-", "-"], [736a, 738a, 742a, 751a, 801a, 806a, 809a, 820a, "-", "-", "-", "-", "-", "-"], [751a, 753a, 757a, 806a, 816a, 821a, 824a, 836a, 846a, 852a, 856a, "-", "-", "-"], [806a, 808a, 812a, 821a, 831a, 836a, 839a, 851a, 901a, 907a, 911a, 918a, 927a, 937a], [821a, 823a, 827a, 836a, 846a, 851a, 854a, 905a, "-", "-", "-", "-", "-", "-"], [836a, 838a, 842a, 851a, 901a, 906a, 909a, 921a, 931a, 937a, 940a, 947a, 956a, 1006a], [851a, 853a, 857a, 906a, 916a, 921a, 924a, 936a, 946a, 952a, 955a, 1002a, 1011a, 1021a], [913a, 915a, 919a, 928a, 938a, 943a, 945a, 957a, 1007a, 1013a, 1016a, 1023a, 1032a, 1042a], [943a, 945a, 949a, 958a, 1008a, 1013a, 1015a, 1027a, 1037a, 1043a, 1046a, 1053a, 1102a, 1112a], [1013a, 1015a, 1019a, 1028a, 1038a, 1043a, 1045a, 1057a, 1107a, 1113a, 1116a, 1123a, 1132a, 1142a], [1043a, 1045a, 1049a, 1058a, 1108a, 1113a, 1115a, 1127a, 1137a, 1143a, 1146a, 1153a, 1202p, 1212p], [1113a, 1115a, 1119a, 1128a, 1138a, 1143a, 1145a, 1157a, 1207p, 1213p, 1216p, 1223p, 1232p, 1242p], [1143a, 1145a, 1149a, 1158a, 1208p, 1213p, 1215p, 1227p, 1237p, 1243p, 1246p, 1253p, 102p, 112p], [1213p, 1215p, 1219p, 1228p, 1238p, 1243p, 1245p, 1257p, 107p, 113p, 116p, 123p, 132p, 142p], [1243p, 1245p, 1249p, 1258p, 108p, 113p, 115p, 127p, 137p, 143p, 146p, 153p, 202p, 212p], [113p, 115p, 119p, 128p, 138p, 143p, 145p, 157p, 207p, 213p, 216p, 223p, 232p, 242p], [143p, 145p, 149p, 158p, 208p, 213p, 215p, 227p, 237p, 243p, 246p, 253p, 302p, 312p], [213p, 215p, 219p, 228p, 238p, 243p, 245p, 257p, 307p, 313p, 317p, 324p, 333p, 343p], [253p, 255p, 259p, 308p, 318p, 323p, 325p, 337p, 347p, 353p, 357p, 404p, 413p, 423p], [323p, 325p, 329p, 338p, 348p, 353p, 355p, 407p, 417p, 423p, 427p, 434p, 443p, 453p], [338p, 340p, 344p, 353p, 403p, 408p, 410p, 421p, "-", "-", "-", "-", "-", "-"], [353p, 355p, 359p, 408p, 418p, 423p, 425p, 437p, 447p, 453p, 457p, "-", "-", "-"], [408p, 410p, 414p, 423p, 433p, 438p, 440p, 451p, "-", "-", "-", "-", "-", "-"], [423p, 425p, 429p, 438p, 448p, 453p, 455p, 506p, "-", "-", "-", "-", "-", "-"], [438p, 440p, 444p, 453p, 503p, 508p, 510p, 521p, "-", "-", "-", "-", "-", "-"], [453p, 455p, 459p, 508p, 518p, 523p, 525p, 536p, "-", "-", "-", "-", "-", "-"], [508p, 510p, 514p, 523p, 533p, 538p, 540p, 551p, "-", "-", "-", "-", "-", "-"], [523p, 525p, 529p, 538p, 548p, 553p, 555p, 606p, "-", "-", "-", "-", "-", "-"], [538p, 540p, 544p, 553p, 603p, 608p, 610p, 621p, "-", "-", "-", "-", "-", "-"], [617p, 619p, 623p, 632p, 642p, 647p, 649p, 700p, "-", "-", "-", "-", "-", "-"], [716p, 718p, 722p, 731p, 741p, 746p, 748p, 759p, "-", "-", "-", "-", "-", "-"], [816p, 818p, 822p, 831p, 841p, 846p, 848p, 859p, "-", "-", "-", "-", "-", "-"], [916p, 918p, 922p, 931p, 941p, 946p, 948p, 959p, "-", "-", "-", "-", "-", "-"], [1016p, 1018p, 1022p, 1031p, 1041p, 1046p, 1048p, 1059p, "-", "-", "-", "-", "-", "-"], [1116p, 1118p, 1122p, 1131p, 1141p, 1146p, 1148p, "-", "-", "-", "-", "-", "-", "-"]]
   
--- ---
time_points: [Tuggeranong Bus Station (Platform 3), MacKillop College Isabella Campus, Theodore, Calwell Shops, Erindale Centre, Woden Bus Station (Platform 9), City Bus Station] time_points: [Tuggeranong Bus Station (Platform 3), MacKillop College Isabella Campus, Theodore, Calwell, Erindale Centre, Woden Bus Station (Platform 9), City Bus Station]
long_name: To City Bus Station long_name: To City Bus Station
between_stops: between_stops:
Woden Bus Station (Platform 9)-City Bus Station: [Wjz3m3b, Wjz3m3b, Wjz3eRR, Wjz3eZ4, Wjz4KNu, Wjz4KO9, Wjz5Nht] Woden Bus Station (Platform 9)-City Bus Station: [Wjz3m3b, Wjz3m3b, Wjz3eRR, Wjz3eZ4, Wjz4KNu, Wjz4KO9, Wjz5Nht]
short_name: 11 111 short_name: 11 111
stop_times: [[621a, 627a, 641a, 651a, 657a, 713a, 729a], [641a, 647a, 701a, 711a, 717a, 733a, 751a], [701a, 707a, 721a, 731a, 737a, 754a, 812a], [721a, 727a, 742a, 752a, 758a, 815a, 833a], [741a, 748a, 803a, 813a, 819a, 836a, 857a], [801a, 808a, 823a, 833a, 839a, 856a, 914a], [821a, 828a, 843a, 853a, 859a, 914a, "-"], [841a, 848a, 903a, 913a, 919a, 933a, "-"], [921a, 927a, 940a, 949a, 955a, 1007a, "-"], [951a, 957a, 1010a, 1019a, 1025a, 1037a, "-"], [1021a, 1027a, 1040a, 1049a, 1055a, 1107a, "-"], [1051a, 1057a, 1110a, 1119a, 1125a, 1137a, "-"], [1121a, 1127a, 1140a, 1149a, 1155a, 1207p, "-"], [1151a, 1157a, 1210p, 1219p, 1225p, 1237p, "-"], [1221p, 1227p, 1240p, 1249p, 1255p, 107p, "-"], [1251p, 1257p, 110p, 119p, 125p, 137p, "-"], [121p, 127p, 140p, 149p, 155p, 207p, "-"], [151p, 157p, 210p, 219p, 225p, 237p, "-"], [221p, 227p, 240p, 249p, 255p, 307p, "-"], [251p, 257p, 310p, 319p, 325p, 339p, "-"], [323p, 330p, 345p, 355p, 401p, 421p, "-"], [340p, 347p, 402p, 412p, 418p, 433p, "-"], [400p, 407p, 422p, 432p, 438p, 453p, "-"], [418p, 425p, 440p, 450p, 456p, 511p, "-"], [441p, 448p, 503p, 513p, 519p, "-", "-"], [501p, 508p, 523p, 533p, 539p, "-", "-"], [521p, 528p, 543p, 553p, 559p, 614p, "-"], [541p, 548p, 603p, 613p, 619p, "-", "-"], [601p, 608p, 623p, 633p, 639p, "-", "-"], [625p, 632p, 645p, 654p, 700p, 712p, "-"], [725p, 731p, 744p, 753p, 759p, 811p, "-"], [825p, 831p, 844p, 853p, 859p, 911p, "-"], [925p, 931p, 944p, 953p, 959p, 1011p, "-"], [1025p, 1031p, 1044p, 1053p, 1059p, 1111p, "-"], [1125p, 1131p, 1144p, 1153p, 1159p, "-", "-"]] stop_times: [[621a, 627a, 641a, 651a, 657a, 713a, 729a], [641a, 647a, 701a, 711a, 717a, 733a, 751a], [701a, 707a, 721a, 731a, 737a, 754a, 812a], [721a, 727a, 742a, 752a, 758a, 815a, 833a], [741a, 748a, 803a, 813a, 819a, 836a, 857a], [801a, 808a, 823a, 833a, 839a, 856a, 914a], [821a, 828a, 843a, 853a, 859a, 914a, "-"], [841a, 848a, 903a, 913a, 919a, 933a, "-"], [921a, 927a, 940a, 949a, 955a, 1007a, "-"], [951a, 957a, 1010a, 1019a, 1025a, 1037a, "-"], [1021a, 1027a, 1040a, 1049a, 1055a, 1107a, "-"], [1051a, 1057a, 1110a, 1119a, 1125a, 1137a, "-"], [1121a, 1127a, 1140a, 1149a, 1155a, 1207p, "-"], [1151a, 1157a, 1210p, 1219p, 1225p, 1237p, "-"], [1221p, 1227p, 1240p, 1249p, 1255p, 107p, "-"], [1251p, 1257p, 110p, 119p, 125p, 137p, "-"], [121p, 127p, 140p, 149p, 155p, 207p, "-"], [151p, 157p, 210p, 219p, 225p, 237p, "-"], [221p, 227p, 240p, 249p, 255p, 307p, "-"], [251p, 257p, 310p, 319p, 325p, 339p, "-"], [323p, 330p, 345p, 355p, 401p, 421p, "-"], [340p, 347p, 402p, 412p, 418p, 433p, "-"], [400p, 407p, 422p, 432p, 438p, 453p, "-"], [418p, 425p, 440p, 450p, 456p, 511p, "-"], [441p, 448p, 503p, 513p, 519p, "-", "-"], [501p, 508p, 523p, 533p, 539p, "-", "-"], [521p, 528p, 543p, 553p, 559p, 614p, "-"], [541p, 548p, 603p, 613p, 619p, "-", "-"], [601p, 608p, 623p, 633p, 639p, "-", "-"], [625p, 632p, 645p, 654p, 700p, 712p, "-"], [725p, 731p, 744p, 753p, 759p, 811p, "-"], [825p, 831p, 844p, 853p, 859p, 911p, "-"], [925p, 931p, 944p, 953p, 959p, 1011p, "-"], [1025p, 1031p, 1044p, 1053p, 1059p, 1111p, "-"], [1125p, 1131p, 1144p, 1153p, 1159p, "-", "-"]]
   
--- ---
time_points: [City Bus Station (Platform 1), Woden Bus Station (Platform 11), Erindale Centre, Calwell Shops, Theodore, MacKillop College Isabella Campus, Tuggeranong Bus Station] time_points: [City Bus Station (Platform 1), Woden Bus Station (Platform 11), Erindale Centre, Calwell, Theodore, MacKillop College Isabella Campus, Tuggeranong Bus Station]
long_name: To Tuggeranong Bus Station long_name: To Tuggeranong Bus Station
between_stops: between_stops:
City Bus Station (Platform 1)-Woden Bus Station (Platform 11): [Wjz5Nht, Wjz4KO9, Wjz4KNu, Wjz3eZ4, Wjz3eRR, Wjz3m3b, Wjz3m3b] City Bus Station (Platform 1)-Woden Bus Station (Platform 11): [Wjz5Nht, Wjz4KO9, Wjz4KNu, Wjz3eZ4, Wjz3eRR, Wjz3m3b, Wjz3m3b]
short_name: 11 111 short_name: 11 111
stop_times: [["-", "-", "-", 546a, 556a, 609a, 616a], ["-", "-", "-", 606a, 616a, 629a, 636a], ["-", "-", "-", 626a, 636a, 649a, 656a], ["-", "-", "-", 646a, 656a, 709a, 716a], ["-", "-", "-", 706a, 716a, 729a, 736a], ["-", "-", "-", 725a, 735a, 749a, 756a], ["-", "-", "-", 745a, 755a, 809a, 816a], ["-", "-", "-", 805a, 815a, 829a, 836a], ["-", "-", "-", 825a, 835a, 849a, 856a], ["-", "-", "-", 845a, 855a, 909a, 916a], ["-", "-", "-", 917a, 927a, 940a, 946a], ["-", 930a, 942a, 948a, 957a, 1010a, 1016a], ["-", 1000a, 1012a, 1018a, 1027a, 1040a, 1046a], ["-", 1030a, 1042a, 1048a, 1057a, 1110a, 1116a], ["-", 1100a, 1112a, 1118a, 1127a, 1140a, 1146a], ["-", 1130a, 1142a, 1148a, 1157a, 1210p, 1216p], ["-", 1200p, 1212p, 1218p, 1227p, 1240p, 1246p], ["-", 1230p, 1242p, 1248p, 1257p, 110p, 116p], ["-", 100p, 112p, 118p, 127p, 140p, 146p], ["-", 130p, 142p, 148p, 157p, 210p, 216p], ["-", 200p, 212p, 218p, 227p, 240p, 246p], ["-", 230p, 242p, 248p, 257p, 311p, 318p], ["-", 300p, 314p, 321p, 331p, 345p, 352p], ["-", 320p, 334p, 341p, 351p, 405p, 412p], ["-", 340p, 354p, 401p, 411p, 425p, 432p], ["-", 400p, 414p, 421p, 431p, 445p, 452p], ["-", 425p, 439p, 446p, 456p, 510p, 517p], ["-", 440p, 454p, 501p, 511p, 525p, 532p], ["-", 500p, 514p, 521p, 531p, 545p, 552p], [456p, 513p, 527p, 534p, 544p, 558p, 605p], [516p, 533p, 547p, 554p, 604p, 618p, 625p], [534p, 551p, 605p, 612p, 622p, 636p, 641p], [556p, 613p, 627p, 633p, 642p, 655p, 701p], [616p, 633p, 645p, 651p, 700p, 713p, 719p], ["-", 733p, 745p, 751p, 800p, 813p, 819p], ["-", 833p, 845p, 851p, 900p, 913p, 919p], ["-", 933p, 945p, 951p, 1000p, 1013p, 1019p], ["-", 1033p, 1045p, 1051p, 1100p, 1113p, 1119p]] stop_times: [["-", "-", "-", 546a, 556a, 609a, 616a], ["-", "-", "-", 606a, 616a, 629a, 636a], ["-", "-", "-", 626a, 636a, 649a, 656a], ["-", "-", "-", 646a, 656a, 709a, 716a], ["-", "-", "-", 706a, 716a, 729a, 736a], ["-", "-", "-", 725a, 735a, 749a, 756a], ["-", "-", "-", 745a, 755a, 809a, 816a], ["-", "-", "-", 805a, 815a, 829a, 836a], ["-", "-", "-", 825a, 835a, 849a, 856a], ["-", "-", "-", 845a, 855a, 909a, 916a], ["-", "-", "-", 917a, 927a, 940a, 946a], ["-", 930a, 942a, 948a, 957a, 1010a, 1016a], ["-", 1000a, 1012a, 1018a, 1027a, 1040a, 1046a], ["-", 1030a, 1042a, 1048a, 1057a, 1110a, 1116a], ["-", 1100a, 1112a, 1118a, 1127a, 1140a, 1146a], ["-", 1130a, 1142a, 1148a, 1157a, 1210p, 1216p], ["-", 1200p, 1212p, 1218p, 1227p, 1240p, 1246p], ["-", 1230p, 1242p, 1248p, 1257p, 110p, 116p], ["-", 100p, 112p, 118p, 127p, 140p, 146p], ["-", 130p, 142p, 148p, 157p, 210p, 216p], ["-", 200p, 212p, 218p, 227p, 240p, 246p], ["-", 230p, 242p, 248p, 257p, 311p, 318p], ["-", 300p, 314p, 321p, 331p, 345p, 352p], ["-", 320p, 334p, 341p, 351p, 405p, 412p], ["-", 340p, 354p, 401p, 411p, 425p, 432p], ["-", 400p, 414p, 421p, 431p, 445p, 452p], ["-", 425p, 439p, 446p, 456p, 510p, 517p], ["-", 440p, 454p, 501p, 511p, 525p, 532p], ["-", 500p, 514p, 521p, 531p, 545p, 552p], [456p, 513p, 527p, 534p, 544p, 558p, 605p], [516p, 533p, 547p, 554p, 604p, 618p, 625p], [534p, 551p, 605p, 612p, 622p, 636p, 641p], [556p, 613p, 627p, 633p, 642p, 655p, 701p], [616p, 633p, 645p, 651p, 700p, 713p, 719p], ["-", 733p, 745p, 751p, 800p, 813p, 819p], ["-", 833p, 845p, 851p, 900p, 913p, 919p], ["-", 933p, 945p, 951p, 1000p, 1013p, 1019p], ["-", 1033p, 1045p, 1051p, 1100p, 1113p, 1119p]]
   
--- ---
time_points: [Tuggeranong Bus Station (Platform 8), Woden Bus Station (Platform 9), City Bus Station (Platform 3), Belconnen Community Bus Station (Platform 4), Westfield Bus Station (Platform 2), Cohen Street Bus Station (Platform 6), McKellar Shops, Copland College, Evatt Shops, Spence Terminus] time_points: [Tuggeranong Bus Station (Platform 8), Woden Bus Station (Platform 9), City Bus Station (Platform 3), Belconnen Community Bus Station (Platform 4), Westfield Bus Station (Platform 2), Cohen Street Bus Station (Platform 6), McKellar, Copland College, Evatt, Spence Terminus]
long_name: To Spence Terminus long_name: To Spence Terminus
between_stops: between_stops:
Belconnen Community Bus Station (Platform 4)-Westfield Bus Station (Platform 2): [] Belconnen Community Bus Station (Platform 4)-Westfield Bus Station (Platform 2): []
Tuggeranong Bus Station (Platform 8)-Woden Bus Station (Platform 9): [Wjz213q, Wjz238T, Wjz239F, Wjz2lDC, Wjz2mGO, Wjz2mTK, Wjz2nLE, Wjz3gMq, Wjz3gQn, Wjz3gK-, Wjz3hL_, Wjz3iFK, Wjz3kwU, Wjz3kyX, Wjz3kAx] Tuggeranong Bus Station (Platform 8)-Woden Bus Station (Platform 9): [Wjz213q, Wjz238T, Wjz239F, Wjz2lDC, Wjz2mGO, Wjz2mTK, Wjz2nLE, Wjz3gMq, Wjz3gQn, Wjz3gK-, Wjz3hL_, Wjz3iFK, Wjz3kwU, Wjz3kyX, Wjz3kAx]
Westfield Bus Station (Platform 2)-Cohen Street Bus Station (Platform 6): [] Westfield Bus Station (Platform 2)-Cohen Street Bus Station (Platform 6): []
City Bus Station (Platform 3)-Belconnen Community Bus Station (Platform 4): [Wjz5F-1, Wjz5FSY, Wjz5GMT, Wjz5GNG, Wjz5G6U, Wjz5G6B, Wjz5maK, Wjz5mbS, Wjz5nwb, Wjz5nw6, Wjz6giR, Wjz6gia, Wjz68W3, Wjz68W5, Wjz689c, Wjz681S] City Bus Station (Platform 3)-Belconnen Community Bus Station (Platform 4): [Wjz5F-1, Wjz5FSY, Wjz5GMT, Wjz5GNG, Wjz5G6U, Wjz5G6B, Wjz5maK, Wjz5mbS, Wjz5nwb, Wjz5nw6, Wjz6giR, Wjz6gia, Wjz68W3, Wjz68W5, Wjz689c, Wjz681S]
Woden Bus Station (Platform 9)-City Bus Station (Platform 3): [Wjz3m3b, Wjz3m3b, Wjz3eRR, Wjz3eZ4, Wjz4KNu, Wjz4KO9, Wjz5Nht] Woden Bus Station (Platform 9)-City Bus Station (Platform 3): [Wjz3m3b, Wjz3m3b, Wjz3eRR, Wjz3eZ4, Wjz4KNu, Wjz4KO9, Wjz5Nht]
short_name: 12 312 short_name: 12 312
stop_times: [["-", "-", "-", 723a, 725a, 729a, 737a, 741a, 746a, 753a], ["-", "-", "-", 805a, 807a, 811a, 819a, 823a, 828a, 835a], [726a, 745a, 803a, 824a, 826a, 830a, 838a, 842a, 847a, 854a], [826a, 845a, 903a, 924a, 926a, 930a, 937a, 941a, 945a, 952a], [901a, 920a, 937a, 957a, 959a, 1003a, 1010a, 1014a, 1018a, 1025a], [931a, 949a, 1005a, 1025a, 1027a, 1031a, 1038a, 1042a, 1046a, 1053a], [1001a, 1019a, 1035a, 1055a, 1057a, 1101a, 1108a, 1112a, 1116a, 1123a], [1031a, 1049a, 1105a, 1125a, 1127a, 1131a, 1138a, 1142a, 1146a, 1153a], [1101a, 1119a, 1135a, 1155a, 1157a, 1201p, 1208p, 1212p, 1216p, 1223p], [1131a, 1149a, 1205p, 1225p, 1227p, 1231p, 1238p, 1242p, 1246p, 1253p], [1201p, 1219p, 1235p, 1255p, 1257p, 101p, 108p, 112p, 116p, 123p], [1231p, 1249p, 105p, 125p, 127p, 131p, 138p, 142p, 146p, 153p], [101p, 119p, 135p, 155p, 157p, 201p, 208p, 212p, 216p, 223p], [131p, 149p, 205p, 225p, 227p, 231p, 238p, 242p, 246p, 253p], [201p, 219p, 235p, 255p, 257p, 301p, 309p, 313p, 318p, 325p], [231p, 249p, 305p, 326p, 328p, 332p, 340p, 344p, 349p, 356p], [259p, 318p, 336p, 357p, 359p, 403p, 411p, 415p, 420p, 427p], [331p, 350p, 408p, 429p, 431p, 435p, 443p, 447p, 452p, 459p], [356p, 415p, 433p, 454p, 456p, 500p, 508p, 512p, 517p, 524p], [416p, 435p, 453p, 514p, 516p, 520p, 528p, 532p, 537p, 544p], [436p, 455p, 513p, 534p, 536p, 540p, 548p, 552p, 557p, 604p], [456p, 515p, 533p, 554p, 556p, 600p, 608p, 612p, 617p, 624p], [516p, 535p, 553p, 614p, 616p, 620p, 628p, 632p, 636p, 643p], [536p, 555p, 613p, 634p, 636p, 640p, 647p, 651p, 655p, 702p], [636p, 653p, 708p, 728p, 730p, 734p, 741p, 745p, 749p, 756p], ["-", "-", "-", 834p, 836p, 840p, 847p, 851p, 855p, 902p], ["-", "-", "-", 934p, 936p, 940p, 947p, 951p, 955p, 1002p], ["-", "-", "-", 1034p, 1036p, 1040p, 1047p, 1051p, 1055p, 1102p], ["-", "-", "-", 1134p, 1136p, 1140p, 1147p, 1151p, 1155p, 1202a]] stop_times: [["-", "-", "-", 723a, 725a, 729a, 737a, 741a, 746a, 753a], ["-", "-", "-", 805a, 807a, 811a, 819a, 823a, 828a, 835a], [726a, 745a, 803a, 824a, 826a, 830a, 838a, 842a, 847a, 854a], [826a, 845a, 903a, 924a, 926a, 930a, 937a, 941a, 945a, 952a], [901a, 920a, 937a, 957a, 959a, 1003a, 1010a, 1014a, 1018a, 1025a], [931a, 949a, 1005a, 1025a, 1027a, 1031a, 1038a, 1042a, 1046a, 1053a], [1001a, 1019a, 1035a, 1055a, 1057a, 1101a, 1108a, 1112a, 1116a, 1123a], [1031a, 1049a, 1105a, 1125a, 1127a, 1131a, 1138a, 1142a, 1146a, 1153a], [1101a, 1119a, 1135a, 1155a, 1157a, 1201p, 1208p, 1212p, 1216p, 1223p], [1131a, 1149a, 1205p, 1225p, 1227p, 1231p, 1238p, 1242p, 1246p, 1253p], [1201p, 1219p, 1235p, 1255p, 1257p, 101p, 108p, 112p, 116p, 123p], [1231p, 1249p, 105p, 125p, 127p, 131p, 138p, 142p, 146p, 153p], [101p, 119p, 135p, 155p, 157p, 201p, 208p, 212p, 216p, 223p], [131p, 149p, 205p, 225p, 227p, 231p, 238p, 242p, 246p, 253p], [201p, 219p, 235p, 255p, 257p, 301p, 309p, 313p, 318p, 325p], [231p, 249p, 305p, 326p, 328p, 332p, 340p, 344p, 349p, 356p], [259p, 318p, 336p, 357p, 359p, 403p, 411p, 415p, 420p, 427p], [331p, 350p, 408p, 429p, 431p, 435p, 443p, 447p, 452p, 459p], [356p, 415p, 433p, 454p, 456p, 500p, 508p, 512p, 517p, 524p], [416p, 435p, 453p, 514p, 516p, 520p, 528p, 532p, 537p, 544p], [436p, 455p, 513p, 534p, 536p, 540p, 548p, 552p, 557p, 604p], [456p, 515p, 533p, 554p, 556p, 600p, 608p, 612p, 617p, 624p], [516p, 535p, 553p, 614p, 616p, 620p, 628p, 632p, 636p, 643p], [536p, 555p, 613p, 634p, 636p, 640p, 647p, 651p, 655p, 702p], [636p, 653p, 708p, 728p, 730p, 734p, 741p, 745p, 749p, 756p], ["-", "-", "-", 834p, 836p, 840p, 847p, 851p, 855p, 902p], ["-", "-", "-", 934p, 936p, 940p, 947p, 951p, 955p, 1002p], ["-", "-", "-", 1034p, 1036p, 1040p, 1047p, 1051p, 1055p, 1102p], ["-", "-", "-", 1134p, 1136p, 1140p, 1147p, 1151p, 1155p, 1202a]]
   
--- ---
time_points: [Spence Terminus, Evatt Shops, Copland College, McKellar Shops, Cohen Street Bus Station (Platform 3), Westfield Bus Station (Platform 1), Belconnen Community Bus Station (Platform 1), City Bus Station (Platform 1), Woden Bus Station (Platform 6), Tuggeranong Bus Station] time_points: [Spence Terminus, Evatt, Copland College, McKellar, Cohen Street Bus Station (Platform 3), Westfield Bus Station (Platform 1), Belconnen Community Bus Station (Platform 1), City Bus Station (Platform 1), Woden Bus Station (Platform 6), Tuggeranong Bus Station]
long_name: To Tuggeranong Bus Station long_name: To Tuggeranong Bus Station
between_stops: between_stops:
Cohen Street Bus Station (Platform 3)-Westfield Bus Station (Platform 1): [] Cohen Street Bus Station (Platform 3)-Westfield Bus Station (Platform 1): []
Westfield Bus Station (Platform 1)-Belconnen Community Bus Station (Platform 1): [] Westfield Bus Station (Platform 1)-Belconnen Community Bus Station (Platform 1): []
Woden Bus Station (Platform 6)-Tuggeranong Bus Station: [Wjz3kAx, Wjz3kyX, Wjz3kwU, Wjz3iFK, Wjz3hL_, Wjz3gK-, Wjz3gQn, Wjz3gMq, Wjz2nLE, Wjz2mTK, Wjz2mGO, Wjz2lDC, Wjz239F, Wjz238T, Wjz213q] Woden Bus Station (Platform 6)-Tuggeranong Bus Station: [Wjz3kAx, Wjz3kyX, Wjz3kwU, Wjz3iFK, Wjz3hL_, Wjz3gK-, Wjz3gQn, Wjz3gMq, Wjz2nLE, Wjz2mTK, Wjz2mGO, Wjz2lDC, Wjz239F, Wjz238T, Wjz213q]
City Bus Station (Platform 1)-Woden Bus Station (Platform 6): [Wjz5Nht, Wjz4KO9, Wjz4KNu, Wjz3eZ4, Wjz3eRR, Wjz3m3b, Wjz3m3b] City Bus Station (Platform 1)-Woden Bus Station (Platform 6): [Wjz5Nht, Wjz4KO9, Wjz4KNu, Wjz3eZ4, Wjz3eRR, Wjz3m3b, Wjz3m3b]
Belconnen Community Bus Station (Platform 1)-City Bus Station (Platform 1): [Wjz681S, Wjz689c, Wjz68W3, Wjz68W5, Wjz6gia, Wjz6giR, Wjz5nw6, Wjz5nwb, Wjz5mbS, Wjz5maK, Wjz5G6B, Wjz5G6U, Wjz5GNG, Wjz5GMT, Wjz5FSY, Wjz5F-1] Belconnen Community Bus Station (Platform 1)-City Bus Station (Platform 1): [Wjz681S, Wjz689c, Wjz68W3, Wjz68W5, Wjz6gia, Wjz6giR, Wjz5nw6, Wjz5nwb, Wjz5mbS, Wjz5maK, Wjz5G6B, Wjz5G6U, Wjz5GNG, Wjz5GMT, Wjz5FSY, Wjz5F-1]
short_name: 12 312 short_name: 12 312
stop_times: [[624a, 629a, 632a, 636a, 646a, 648a, 652a, "-", "-", "-"], [653a, 658a, 701a, 705a, 715a, 717a, 721a, 742a, 759a, 816a], [723a, 728a, 731a, 735a, 745a, 747a, 751a, 813a, 830a, 847a], [734a, 739a, 743a, 747a, 757a, 759a, 803a, 825a, 842a, 859a], [749a, 754a, 758a, 802a, 812a, 814a, 818a, 840a, 857a, 914a], [807a, 812a, 816a, 820a, 830a, 832a, 836a, 858a, 915a, 932a], [827a, 832a, 836a, 840a, 850a, 852a, 856a, 918a, 935a, 950a], [852a, 857a, 901a, 905a, 915a, 917a, 921a, 942a, 959a, 1014a], [922a, 927a, 931a, 935a, 945a, 947a, 951a, 1011a, 1028a, 1043a], [953a, 958a, 1001a, 1005a, 1015a, 1017a, 1021a, 1041a, 1058a, 1113a], [1023a, 1028a, 1031a, 1035a, 1045a, 1047a, 1051a, 1111a, 1128a, 1143a], [1053a, 1058a, 1101a, 1105a, 1115a, 1117a, 1121a, 1141a, 1158a, 1213p], [1123a, 1128a, 1131a, 1135a, 1145a, 1147a, 1151a, 1211p, 1228p, 1243p], [1153a, 1158a, 1201p, 1205p, 1215p, 1217p, 1221p, 1241p, 1258p, 113p], [1223p, 1228p, 1231p, 1235p, 1245p, 1247p, 1251p, 111p, 128p, 143p], [1253p, 1258p, 101p, 105p, 115p, 117p, 121p, 141p, 158p, 213p], [123p, 128p, 131p, 135p, 145p, 147p, 151p, 211p, 228p, 243p], [153p, 158p, 201p, 205p, 215p, 217p, 221p, 241p, 258p, 316p], [223p, 228p, 231p, 235p, 245p, 247p, 251p, 312p, 329p, 348p], [253p, 258p, 301p, 305p, 315p, 317p, 321p, 343p, 400p, 419p], [322p, 327p, 331p, 335p, 345p, 347p, 351p, 413p, 430p, 449p], [342p, 347p, 351p, 355p, 405p, 407p, 411p, 433p, 450p, 509p], [412p, 417p, 421p, 425p, 435p, 437p, 441p, 503p, 520p, 539p], [432p, 437p, 441p, 445p, 455p, 457p, 501p, 523p, 540p, 559p], [457p, 502p, 506p, 510p, 520p, 522p, 526p, 548p, 605p, 624p], [522p, 527p, 531p, 535p, 545p, 547p, 551p, 613p, 630p, 645p], [552p, 557p, 601p, 605p, 615p, 617p, 621p, 641p, 655p, 710p], [622p, 627p, 631p, 635p, 645p, 647p, 651p, 710p, 724p, 739p], [711p, 716p, 719p, 723p, 733p, 735p, 739p, "-", "-", "-"], [811p, 816p, 819p, 823p, 833p, 835p, 839p, "-", "-", "-"], [911p, 916p, 919p, 923p, 933p, 935p, 939p, "-", "-", "-"], [1011p, 1016p, 1019p, 1023p, 1033p, 1035p, 1039p, "-", "-", "-"]] stop_times: [[624a, 629a, 632a, 636a, 646a, 648a, 652a, "-", "-", "-"], [653a, 658a, 701a, 705a, 715a, 717a, 721a, 742a, 759a, 816a], [723a, 728a, 731a, 735a, 745a, 747a, 751a, 813a, 830a, 847a], [734a, 739a, 743a, 747a, 757a, 759a, 803a, 825a, 842a, 859a], [749a, 754a, 758a, 802a, 812a, 814a, 818a, 840a, 857a, 914a], [807a, 812a, 816a, 820a, 830a, 832a, 836a, 858a, 915a, 932a], [827a, 832a, 836a, 840a, 850a, 852a, 856a, 918a, 935a, 950a], [852a, 857a, 901a, 905a, 915a, 917a, 921a, 942a, 959a, 1014a], [922a, 927a, 931a, 935a, 945a, 947a, 951a, 1011a, 1028a, 1043a], [953a, 958a, 1001a, 1005a, 1015a, 1017a, 1021a, 1041a, 1058a, 1113a], [1023a, 1028a, 1031a, 1035a, 1045a, 1047a, 1051a, 1111a, 1128a, 1143a], [1053a, 1058a, 1101a, 1105a, 1115a, 1117a, 1121a, 1141a, 1158a, 1213p], [1123a, 1128a, 1131a, 1135a, 1145a, 1147a, 1151a, 1211p, 1228p, 1243p], [1153a, 1158a, 1201p, 1205p, 1215p, 1217p, 1221p, 1241p, 1258p, 113p], [1223p, 1228p, 1231p, 1235p, 1245p, 1247p, 1251p, 111p, 128p, 143p], [1253p, 1258p, 101p, 105p, 115p, 117p, 121p, 141p, 158p, 213p], [123p, 128p, 131p, 135p, 145p, 147p, 151p, 211p, 228p, 243p], [153p, 158p, 201p, 205p, 215p, 217p, 221p, 241p, 258p, 316p], [223p, 228p, 231p, 235p, 245p, 247p, 251p, 312p, 329p, 348p], [253p, 258p, 301p, 305p, 315p, 317p, 321p, 343p, 400p, 419p], [322p, 327p, 331p, 335p, 345p, 347p, 351p, 413p, 430p, 449p], [342p, 347p, 351p, 355p, 405p, 407p, 411p, 433p, 450p, 509p], [412p, 417p, 421p, 425p, 435p, 437p, 441p, 503p, 520p, 539p], [432p, 437p, 441p, 445p, 455p, 457p, 501p, 523p, 540p, 559p], [457p, 502p, 506p, 510p, 520p, 522p, 526p, 548p, 605p, 624p], [522p, 527p, 531p, 535p, 545p, 547p, 551p, 613p, 630p, 645p], [552p, 557p, 601p, 605p, 615p, 617p, 621p, 641p, 655p, 710p], [622p, 627p, 631p, 635p, 645p, 647p, 651p, 710p, 724p, 739p], [711p, 716p, 719p, 723p, 733p, 735p, 739p, "-", "-", "-"], [811p, 816p, 819p, 823p, 833p, 835p, 839p, "-", "-", "-"], [911p, 916p, 919p, 923p, 933p, 935p, 939p, "-", "-", "-"], [1011p, 1016p, 1019p, 1023p, 1033p, 1035p, 1039p, "-", "-", "-"]]
   
--- ---
time_points: [Tuggeranong Bus Station (Platform 8), Woden Bus Station (Platform 9), City Bus Station (Platform 3), Belconnen Community Bus Station (Platform 4), Westfield Bus Station (Platform 2), Cohen Street Bus Station (Platform 6), Page Shops, Scullin Shops, Charnwood Shops, Fraser West Terminus] time_points: [Tuggeranong Bus Station (Platform 8), Woden Bus Station (Platform 9), City Bus Station (Platform 3), Belconnen Community Bus Station (Platform 4), Westfield Bus Station (Platform 2), Cohen Street Bus Station (Platform 6), Page, Scullin, Charnwood, Fraser West Terminus]
long_name: To Fraser West Terminus long_name: To Fraser West Terminus
between_stops: between_stops:
Belconnen Community Bus Station (Platform 4)-Westfield Bus Station (Platform 2): [] Belconnen Community Bus Station (Platform 4)-Westfield Bus Station (Platform 2): []
Tuggeranong Bus Station (Platform 8)-Woden Bus Station (Platform 9): [Wjz213q, Wjz238T, Wjz239F, Wjz2lDC, Wjz2mGO, Wjz2mTK, Wjz2nLE, Wjz3gMq, Wjz3gQn, Wjz3gK-, Wjz3hL_, Wjz3iFK, Wjz3kwU, Wjz3kyX, Wjz3kAx] Tuggeranong Bus Station (Platform 8)-Woden Bus Station (Platform 9): [Wjz213q, Wjz238T, Wjz239F, Wjz2lDC, Wjz2mGO, Wjz2mTK, Wjz2nLE, Wjz3gMq, Wjz3gQn, Wjz3gK-, Wjz3hL_, Wjz3iFK, Wjz3kwU, Wjz3kyX, Wjz3kAx]
Westfield Bus Station (Platform 2)-Cohen Street Bus Station (Platform 6): [] Westfield Bus Station (Platform 2)-Cohen Street Bus Station (Platform 6): []
City Bus Station (Platform 3)-Belconnen Community Bus Station (Platform 4): [Wjz5F-1, Wjz5FSY, Wjz5GMT, Wjz5GNG, Wjz5G6U, Wjz5G6B, Wjz5maK, Wjz5mbS, Wjz5nwb, Wjz5nw6, Wjz6giR, Wjz6gia, Wjz68W3, Wjz68W5, Wjz689c, Wjz681S] City Bus Station (Platform 3)-Belconnen Community Bus Station (Platform 4): [Wjz5F-1, Wjz5FSY, Wjz5GMT, Wjz5GNG, Wjz5G6U, Wjz5G6B, Wjz5maK, Wjz5mbS, Wjz5nwb, Wjz5nw6, Wjz6giR, Wjz6gia, Wjz68W3, Wjz68W5, Wjz689c, Wjz681S]
Woden Bus Station (Platform 9)-City Bus Station (Platform 3): [Wjz3m3b, Wjz3m3b, Wjz3eRR, Wjz3eZ4, Wjz4KNu, Wjz4KO9, Wjz5Nht] Woden Bus Station (Platform 9)-City Bus Station (Platform 3): [Wjz3m3b, Wjz3m3b, Wjz3eRR, Wjz3eZ4, Wjz4KNu, Wjz4KO9, Wjz5Nht]
short_name: 13 313 short_name: 13 313
stop_times: [["-", "-", "-", 733a, 735a, 739a, 742a, 746a, 755a, 803a], [710a, 728a, 746a, 807a, 809a, 813a, 816a, 820a, 829a, 837a], [751a, 810a, 828a, 849a, 851a, 855a, 858a, 902a, 911a, 919a], [811a, 830a, 848a, 909a, 911a, 915a, 918a, 922a, 931a, 938a], [850a, 909a, 927a, 947a, 949a, 953a, 955a, 959a, 1007a, 1014a], [921a, 940a, 956a, 1016a, 1018a, 1022a, 1024a, 1028a, 1036a, 1043a], [951a, 1009a, 1025a, 1045a, 1047a, 1051a, 1053a, 1057a, 1105a, 1112a], [1021a, 1039a, 1055a, 1115a, 1117a, 1121a, 1123a, 1127a, 1135a, 1142a], [1051a, 1109a, 1125a, 1145a, 1147a, 1151a, 1153a, 1157a, 1205p, 1212p], [1121a, 1139a, 1155a, 1215p, 1217p, 1221p, 1223p, 1227p, 1235p, 1242p], [1151a, 1209p, 1225p, 1245p, 1247p, 1251p, 1253p, 1257p, 105p, 112p], [1221p, 1239p, 1255p, 115p, 117p, 121p, 123p, 127p, 135p, 142p], [1251p, 109p, 125p, 145p, 147p, 151p, 153p, 157p, 205p, 212p], [121p, 139p, 155p, 215p, 217p, 221p, 223p, 227p, 235p, 242p], [151p, 209p, 225p, 245p, 247p, 251p, 253p, 257p, 306p, 313p], [221p, 239p, 255p, 316p, 318p, 322p, 325p, 330p, 340p, 347p], [250p, 308p, 326p, 347p, 349p, 353p, 356p, 401p, 411p, 418p], [316p, 335p, 353p, 414p, 416p, 420p, 423p, 428p, 438p, 445p], [346p, 405p, 423p, 444p, 446p, 450p, 453p, 458p, 508p, 515p], [406p, 425p, 443p, 504p, 506p, 510p, 513p, 518p, 528p, 535p], [426p, 445p, 503p, 524p, 526p, 530p, 533p, 538p, 548p, 555p], [446p, 505p, 523p, 544p, 546p, 550p, 553p, 558p, 608p, 615p], [526p, 545p, 603p, 624p, 626p, 630p, 632p, 636p, 644p, 651p], [556p, 615p, 632p, 652p, 654p, 658p, 700p, 704p, 712p, 719p], [656p, 713p, 728p, 748p, 750p, 754p, 756p, 800p, 808p, 815p], ["-", "-", "-", 835p, 837p, 841p, 843p, 847p, 855p, 902p], ["-", "-", "-", 935p, 937p, 941p, 943p, 947p, 955p, 1002p], ["-", "-", "-", 1034p, 1036p, 1040p, 1042p, 1046p, 1054p, 1101p]] stop_times: [["-", "-", "-", 733a, 735a, 739a, 742a, 746a, 755a, 803a], [710a, 728a, 746a, 807a, 809a, 813a, 816a, 820a, 829a, 837a], [751a, 810a, 828a, 849a, 851a, 855a, 858a, 902a, 911a, 919a], [811a, 830a, 848a, 909a, 911a, 915a, 918a, 922a, 931a, 938a], [850a, 909a, 927a, 947a, 949a, 953a, 955a, 959a, 1007a, 1014a], [921a, 940a, 956a, 1016a, 1018a, 1022a, 1024a, 1028a, 1036a, 1043a], [951a, 1009a, 1025a, 1045a, 1047a, 1051a, 1053a, 1057a, 1105a, 1112a], [1021a, 1039a, 1055a, 1115a, 1117a, 1121a, 1123a, 1127a, 1135a, 1142a], [1051a, 1109a, 1125a, 1145a, 1147a, 1151a, 1153a, 1157a, 1205p, 1212p], [1121a, 1139a, 1155a, 1215p, 1217p, 1221p, 1223p, 1227p, 1235p, 1242p], [1151a, 1209p, 1225p, 1245p, 1247p, 1251p, 1253p, 1257p, 105p, 112p], [1221p, 1239p, 1255p, 115p, 117p, 121p, 123p, 127p, 135p, 142p], [1251p, 109p, 125p, 145p, 147p, 151p, 153p, 157p, 205p, 212p], [121p, 139p, 155p, 215p, 217p, 221p, 223p, 227p, 235p, 242p], [151p, 209p, 225p, 245p, 247p, 251p, 253p, 257p, 306p, 313p], [221p, 239p, 255p, 316p, 318p, 322p, 325p, 330p, 340p, 347p], [250p, 308p, 326p, 347p, 349p, 353p, 356p, 401p, 411p, 418p], [316p, 335p, 353p, 414p, 416p, 420p, 423p, 428p, 438p, 445p], [346p, 405p, 423p, 444p, 446p, 450p, 453p, 458p, 508p, 515p], [406p, 425p, 443p, 504p, 506p, 510p, 513p, 518p, 528p, 535p], [426p, 445p, 503p, 524p, 526p, 530p, 533p, 538p, 548p, 555p], [446p, 505p, 523p, 544p, 546p, 550p, 553p, 558p, 608p, 615p], [526p, 545p, 603p, 624p, 626p, 630p, 632p, 636p, 644p, 651p], [556p, 615p, 632p, 652p, 654p, 658p, 700p, 704p, 712p, 719p], [656p, 713p, 728p, 748p, 750p, 754p, 756p, 800p, 808p, 815p], ["-", "-", "-", 835p, 837p, 841p, 843p, 847p, 855p, 902p], ["-", "-", "-", 935p, 937p, 941p, 943p, 947p, 955p, 1002p], ["-", "-", "-", 1034p, 1036p, 1040p, 1042p, 1046p, 1054p, 1101p]]
   
--- ---
time_points: [Fraser West Terminus, Charnwood Shops, Scullin Shops, Page Shops, Cohen Street Bus Station (Platform 3), Westfield Bus Station (Platform 1), Belconnen Community Bus Station (Platform 1), City Bus Station (Platform 1), Woden Bus Station (Platform 6), Tuggeranong Bus Station] time_points: [Fraser West Terminus, Charnwood, Scullin, Page, Cohen Street Bus Station (Platform 3), Westfield Bus Station (Platform 1), Belconnen Community Bus Station (Platform 1), City Bus Station (Platform 1), Woden Bus Station (Platform 6), Tuggeranong Bus Station]
long_name: To Tuggeranong Bus Station long_name: To Tuggeranong Bus Station
between_stops: between_stops:
Cohen Street Bus Station (Platform 3)-Westfield Bus Station (Platform 1): [] Cohen Street Bus Station (Platform 3)-Westfield Bus Station (Platform 1): []
Westfield Bus Station (Platform 1)-Belconnen Community Bus Station (Platform 1): [] Westfield Bus Station (Platform 1)-Belconnen Community Bus Station (Platform 1): []
Woden Bus Station (Platform 6)-Tuggeranong Bus Station: [Wjz3kAx, Wjz3kyX, Wjz3kwU, Wjz3iFK, Wjz3hL_, Wjz3gK-, Wjz3gQn, Wjz3gMq, Wjz2nLE, Wjz2mTK, Wjz2mGO, Wjz2lDC, Wjz239F, Wjz238T, Wjz213q] Woden Bus Station (Platform 6)-Tuggeranong Bus Station: [Wjz3kAx, Wjz3kyX, Wjz3kwU, Wjz3iFK, Wjz3hL_, Wjz3gK-, Wjz3gQn, Wjz3gMq, Wjz2nLE, Wjz2mTK, Wjz2mGO, Wjz2lDC, Wjz239F, Wjz238T, Wjz213q]
City Bus Station (Platform 1)-Woden Bus Station (Platform 6): [Wjz5Nht, Wjz4KO9, Wjz4KNu, Wjz3eZ4, Wjz3eRR, Wjz3m3b, Wjz3m3b] City Bus Station (Platform 1)-Woden Bus Station (Platform 6): [Wjz5Nht, Wjz4KO9, Wjz4KNu, Wjz3eZ4, Wjz3eRR, Wjz3m3b, Wjz3m3b]
Belconnen Community Bus Station (Platform 1)-City Bus Station (Platform 1): [Wjz681S, Wjz689c, Wjz68W3, Wjz68W5, Wjz6gia, Wjz6giR, Wjz5nw6, Wjz5nwb, Wjz5mbS, Wjz5maK, Wjz5G6B, Wjz5G6U, Wjz5GNG, Wjz5GMT, Wjz5FSY, Wjz5F-1] Belconnen Community Bus Station (Platform 1)-City Bus Station (Platform 1): [Wjz681S, Wjz689c, Wjz68W3, Wjz68W5, Wjz6gia, Wjz6giR, Wjz5nw6, Wjz5nwb, Wjz5mbS, Wjz5maK, Wjz5G6B, Wjz5G6U, Wjz5GNG, Wjz5GMT, Wjz5FSY, Wjz5F-1]
short_name: 13 313 short_name: 13 313
stop_times: [[546a, 550a, 559a, 603a, 610a, 612a, 616a, 636a, 653a, 706a], [616a, 620a, 629a, 633a, 640a, 642a, 646a, 706a, 723a, 738a], [646a, 650a, 659a, 703a, 710a, 712a, 716a, 737a, 754a, 811a], [712a, 716a, 725a, 729a, 737a, 739a, 743a, 806a, 823a, 840a], [737a, 742a, 752a, 757a, 805a, 807a, 811a, 833a, 850a, 907a], [757a, 802a, 812a, 817a, 825a, 827a, 831a, 853a, 910a, 927a], [817a, 822a, 832a, 837a, 845a, 847a, 851a, 913a, 930a, 945a], [842a, 847a, 857a, 902a, 910a, 912a, 916a, 937a, 954a, 1009a], [914a, 919a, 929a, 933a, 940a, 942a, 946a, 1006a, 1023a, 1038a], [946a, 950a, 959a, 1003a, 1010a, 1012a, 1016a, 1036a, 1053a, 1108a], [1016a, 1020a, 1029a, 1033a, 1040a, 1042a, 1046a, 1106a, 1123a, 1138a], [1046a, 1050a, 1059a, 1103a, 1110a, 1112a, 1116a, 1136a, 1153a, 1208p], [1116a, 1120a, 1129a, 1133a, 1140a, 1142a, 1146a, 1206p, 1223p, 1238p], [1146a, 1150a, 1159a, 1203p, 1210p, 1212p, 1216p, 1236p, 1253p, 108p], [1216p, 1220p, 1229p, 1233p, 1240p, 1242p, 1246p, 106p, 123p, 138p], [1246p, 1250p, 1259p, 103p, 110p, 112p, 116p, 136p, 153p, 208p], [116p, 120p, 129p, 133p, 140p, 142p, 146p, 206p, 223p, 238p], [146p, 150p, 159p, 203p, 210p, 212p, 216p, 236p, 253p, 310p], [216p, 220p, 229p, 233p, 240p, 242p, 246p, 307p, 324p, 343p], [245p, 249p, 258p, 302p, 310p, 312p, 316p, 338p, 355p, 414p], [313p, 318p, 328p, 332p, 340p, 342p, 346p, 408p, 425p, 444p], [343p, 348p, 358p, 402p, 410p, 412p, 416p, 438p, 455p, 514p], [418p, 423p, 433p, 437p, 445p, 447p, 451p, "-", "-", "-"], [449p, 454p, 504p, 508p, 516p, 518p, 522p, "-", "-", "-"], [513p, 518p, 528p, 532p, 540p, 542p, 546p, 608p, 625p, 641p], [543p, 548p, 558p, 602p, 610p, 612p, 616p, 636p, 650p, 705p], [615p, 620p, 630p, 634p, 640p, 642p, 646p, 705p, 719p, 734p], [710p, 714p, 723p, 727p, 733p, 735p, 739p, "-", "-", "-"], [810p, 814p, 823p, 827p, 833p, 835p, 839p, "-", "-", "-"], [910p, 914p, 923p, 927p, 933p, 935p, 939p, "-", "-", "-"], [1010p, 1014p, 1023p, 1027p, 1033p, 1035p, 1039p, "-", "-", "-"]] stop_times: [[546a, 550a, 559a, 603a, 610a, 612a, 616a, 636a, 653a, 706a], [616a, 620a, 629a, 633a, 640a, 642a, 646a, 706a, 723a, 738a], [646a, 650a, 659a, 703a, 710a, 712a, 716a, 737a, 754a, 811a], [712a, 716a, 725a, 729a, 737a, 739a, 743a, 806a, 823a, 840a], [737a, 742a, 752a, 757a, 805a, 807a, 811a, 833a, 850a, 907a], [757a, 802a, 812a, 817a, 825a, 827a, 831a, 853a, 910a, 927a], [817a, 822a, 832a, 837a, 845a, 847a, 851a, 913a, 930a, 945a], [842a, 847a, 857a, 902a, 910a, 912a, 916a, 937a, 954a, 1009a], [914a, 919a, 929a, 933a, 940a, 942a, 946a, 1006a, 1023a, 1038a], [946a, 950a, 959a, 1003a, 1010a, 1012a, 1016a, 1036a, 1053a, 1108a], [1016a, 1020a, 1029a, 1033a, 1040a, 1042a, 1046a, 1106a, 1123a, 1138a], [1046a, 1050a, 1059a, 1103a, 1110a, 1112a, 1116a, 1136a, 1153a, 1208p], [1116a, 1120a, 1129a, 1133a, 1140a, 1142a, 1146a, 1206p, 1223p, 1238p], [1146a, 1150a, 1159a, 1203p, 1210p, 1212p, 1216p, 1236p, 1253p, 108p], [1216p, 1220p, 1229p, 1233p, 1240p, 1242p, 1246p, 106p, 123p, 138p], [1246p, 1250p, 1259p, 103p, 110p, 112p, 116p, 136p, 153p, 208p], [116p, 120p, 129p, 133p, 140p, 142p, 146p, 206p, 223p, 238p], [146p, 150p, 159p, 203p, 210p, 212p, 216p, 236p, 253p, 310p], [216p, 220p, 229p, 233p, 240p, 242p, 246p, 307p, 324p, 343p], [245p, 249p, 258p, 302p, 310p, 312p, 316p, 338p, 355p, 414p], [313p, 318p, 328p, 332p, 340p, 342p, 346p, 408p, 425p, 444p], [343p, 348p, 358p, 402p, 410p, 412p, 416p, 438p, 455p, 514p], [418p, 423p, 433p, 437p, 445p, 447p, 451p, "-", "-", "-"], [449p, 454p, 504p, 508p, 516p, 518p, 522p, "-", "-", "-"], [513p, 518p, 528p, 532p, 540p, 542p, 546p, 608p, 625p, 641p], [543p, 548p, 558p, 602p, 610p, 612p, 616p, 636p, 650p, 705p], [615p, 620p, 630p, 634p, 640p, 642p, 646p, 705p, 719p, 734p], [710p, 714p, 723p, 727p, 733p, 735p, 739p, "-", "-", "-"], [810p, 814p, 823p, 827p, 833p, 835p, 839p, "-", "-", "-"], [910p, 914p, 923p, 927p, 933p, 935p, 939p, "-", "-", "-"], [1010p, 1014p, 1023p, 1027p, 1033p, 1035p, 1039p, "-", "-", "-"]]
   
--- ---
time_points: [Tuggeranong Bus Station (Platform 8), Woden Bus Station (Platform 9), City Bus Station (Platform 3), Belconnen Community Bus Station (Platform 4), Westfield Bus Station (Platform 2), Cohen Street Bus Station (Platform 6), St Francis Xavier Florey, Charnwood Tillyard Dr, Fraser Shops, Fraser West Terminus] time_points: [Tuggeranong Bus Station (Platform 8), Woden Bus Station (Platform 9), City Bus Station (Platform 3), Belconnen Community Bus Station (Platform 4), Westfield Bus Station (Platform 2), Cohen Street Bus Station (Platform 6), St Francis Xavier Florey, Charnwood Tillyard Dr, Fraser, Fraser West Terminus]
long_name: To Fraser West Terminus long_name: To Fraser West Terminus
between_stops: between_stops:
Belconnen Community Bus Station (Platform 4)-Westfield Bus Station (Platform 2): [] Belconnen Community Bus Station (Platform 4)-Westfield Bus Station (Platform 2): []
Tuggeranong Bus Station (Platform 8)-Woden Bus Station (Platform 9): [Wjz213q, Wjz238T, Wjz239F, Wjz2lDC, Wjz2mGO, Wjz2mTK, Wjz2nLE, Wjz3gMq, Wjz3gQn, Wjz3gK-, Wjz3hL_, Wjz3iFK, Wjz3kwU, Wjz3kyX, Wjz3kAx] Tuggeranong Bus Station (Platform 8)-Woden Bus Station (Platform 9): [Wjz213q, Wjz238T, Wjz239F, Wjz2lDC, Wjz2mGO, Wjz2mTK, Wjz2nLE, Wjz3gMq, Wjz3gQn, Wjz3gK-, Wjz3hL_, Wjz3iFK, Wjz3kwU, Wjz3kyX, Wjz3kAx]
Westfield Bus Station (Platform 2)-Cohen Street Bus Station (Platform 6): [] Westfield Bus Station (Platform 2)-Cohen Street Bus Station (Platform 6): []
City Bus Station (Platform 3)-Belconnen Community Bus Station (Platform 4): [Wjz5F-1, Wjz5FSY, Wjz5GMT, Wjz5GNG, Wjz5G6U, Wjz5G6B, Wjz5maK, Wjz5mbS, Wjz5nwb, Wjz5nw6, Wjz6giR, Wjz6gia, Wjz68W3, Wjz68W5, Wjz689c, Wjz681S] City Bus Station (Platform 3)-Belconnen Community Bus Station (Platform 4): [Wjz5F-1, Wjz5FSY, Wjz5GMT, Wjz5GNG, Wjz5G6U, Wjz5G6B, Wjz5maK, Wjz5mbS, Wjz5nwb, Wjz5nw6, Wjz6giR, Wjz6gia, Wjz68W3, Wjz68W5, Wjz689c, Wjz681S]
Woden Bus Station (Platform 9)-City Bus Station (Platform 3): [Wjz3m3b, Wjz3m3b, Wjz3eRR, Wjz3eZ4, Wjz4KNu, Wjz4KO9, Wjz5Nht] Woden Bus Station (Platform 9)-City Bus Station (Platform 3): [Wjz3m3b, Wjz3m3b, Wjz3eRR, Wjz3eZ4, Wjz4KNu, Wjz4KO9, Wjz5Nht]
short_name: 14 314 short_name: 14 314
stop_times: [["-", "-", "-", 706a, 708a, 712a, 717a, 722a, 726a, 735a], ["-", "-", "-", 722a, 724a, 728a, 734a, 739a, 744a, 753a], [706a, 724a, 741a, 802a, 804a, 808a, 814a, 819a, 824a, 833a], [746a, 805a, 823a, 844a, 846a, 850a, 856a, 901a, 906a, 915a], [805a, 824a, 842a, 903a, 905a, 909a, 915a, 920a, 925a, 934a], [843a, 902a, 920a, 940a, 942a, 946a, 951a, 956a, 1000a, 1009a], [916a, 935a, 951a, 1011a, 1013a, 1017a, 1022a, 1027a, 1031a, 1040a], [946a, 1004a, 1020a, 1040a, 1042a, 1046a, 1051a, 1056a, 1100a, 1109a], [1016a, 1034a, 1050a, 1110a, 1112a, 1116a, 1121a, 1126a, 1130a, 1139a], [1046a, 1104a, 1120a, 1140a, 1142a, 1146a, 1151a, 1156a, 1200p, 1209p], [1116a, 1134a, 1150a, 1210p, 1212p, 1216p, 1221p, 1226p, 1230p, 1239p], [1146a, 1204p, 1220p, 1240p, 1242p, 1246p, 1251p, 1256p, 100p, 109p], [1216p, 1234p, 1250p, 110p, 112p, 116p, 121p, 126p, 130p, 139p], [1246p, 104p, 120p, 140p, 142p, 146p, 151p, 156p, 200p, 209p], [116p, 134p, 150p, 210p, 212p, 216p, 221p, 226p, 230p, 239p], [146p, 204p, 220p, 240p, 242p, 246p, 251p, 256p, 300p, 310p], [216p, 234p, 250p, 311p, 313p, 317p, 323p, 328p, 333p, 343p], [245p, 303p, 321p, 342p, 344p, 348p, 354p, 359p, 404p, 414p], ["-", "-", 340p, 345p, 347p, 351p, 357p, 402p, 407p, 417p], [321p, 340p, 358p, 419p, 421p, 425p, 431p, 436p, 441p, 451p], [351p, 410p, 428p, 449p, 451p, 455p, 501p, 506p, 511p, 521p], [421p, 440p, 458p, 519p, 521p, 525p, 531p, 536p, 541p, 551p], [451p, 510p, 528p, 549p, 551p, 555p, 601p, 606p, 611p, 621p], [511p, 530p, 548p, 609p, 611p, 615p, 621p, 626p, 631p, 640p], [531p, 550p, 608p, 629p, 631p, 635p, 640p, 645p, 649p, 658p], [551p, 610p, 628p, 648p, 650p, 654p, 659p, 704p, 708p, 717p], [621p, 639p, 654p, 714p, 716p, 720p, 725p, 730p, 734p, 743p], ["-", "-", "-", 804p, 806p, 810p, 815p, 820p, 824p, 833p], ["-", "-", "-", 904p, 906p, 910p, 915p, 920p, 924p, 933p], ["-", "-", "-", 1004p, 1006p, 1010p, 1015p, 1020p, 1024p, 1033p], ["-", "-", "-", 1104p, 1106p, 1110p, 1115p, 1120p, 1124p, 1133p], []] stop_times: [["-", "-", "-", 706a, 708a, 712a, 717a, 722a, 726a, 735a], ["-", "-", "-", 722a, 724a, 728a, 734a, 739a, 744a, 753a], [706a, 724a, 741a, 802a, 804a, 808a, 814a, 819a, 824a, 833a], [746a, 805a, 823a, 844a, 846a, 850a, 856a, 901a, 906a, 915a], [805a, 824a, 842a, 903a, 905a, 909a, 915a, 920a, 925a, 934a], [843a, 902a, 920a, 940a, 942a, 946a, 951a, 956a, 1000a, 1009a], [916a, 935a, 951a, 1011a, 1013a, 1017a, 1022a, 1027a, 1031a, 1040a], [946a, 1004a, 1020a, 1040a, 1042a, 1046a, 1051a, 1056a, 1100a, 1109a], [1016a, 1034a, 1050a, 1110a, 1112a, 1116a, 1121a, 1126a, 1130a, 1139a], [1046a, 1104a, 1120a, 1140a, 1142a, 1146a, 1151a, 1156a, 1200p, 1209p], [1116a, 1134a, 1150a, 1210p, 1212p, 1216p, 1221p, 1226p, 1230p, 1239p], [1146a, 1204p, 1220p, 1240p, 1242p, 1246p, 1251p, 1256p, 100p, 109p], [1216p, 1234p, 1250p, 110p, 112p, 116p, 121p, 126p, 130p, 139p], [1246p, 104p, 120p, 140p, 142p, 146p, 151p, 156p, 200p, 209p], [116p, 134p, 150p, 210p, 212p, 216p, 221p, 226p, 230p, 239p], [146p, 204p, 220p, 240p, 242p, 246p, 251p, 256p, 300p, 310p], [216p, 234p, 250p, 311p, 313p, 317p, 323p, 328p, 333p, 343p], [245p, 303p, 321p, 342p, 344p, 348p, 354p, 359p, 404p, 414p], ["-", "-", 340p, 345p, 347p, 351p, 357p, 402p, 407p, 417p], [321p, 340p, 358p, 419p, 421p, 425p, 431p, 436p, 441p, 451p], [351p, 410p, 428p, 449p, 451p, 455p, 501p, 506p, 511p, 521p], [421p, 440p, 458p, 519p, 521p, 525p, 531p, 536p, 541p, 551p], [451p, 510p, 528p, 549p, 551p, 555p, 601p, 606p, 611p, 621p], [511p, 530p, 548p, 609p, 611p, 615p, 621p, 626p, 631p, 640p], [531p, 550p, 608p, 629p, 631p, 635p, 640p, 645p, 649p, 658p], [551p, 610p, 628p, 648p, 650p, 654p, 659p, 704p, 708p, 717p], [621p, 639p, 654p, 714p, 716p, 720p, 725p, 730p, 734p, 743p], ["-", "-", "-", 804p, 806p, 810p, 815p, 820p, 824p, 833p], ["-", "-", "-", 904p, 906p, 910p, 915p, 920p, 924p, 933p], ["-", "-", "-", 1004p, 1006p, 1010p, 1015p, 1020p, 1024p, 1033p], ["-", "-", "-", 1104p, 1106p, 1110p, 1115p, 1120p, 1124p, 1133p], []]
   
--- ---
time_points: [Fraser West Terminus, Fraser Shops, Charnwood Tillyard Dr, St Francis Xavier Florey, Cohen Street Bus Station (Platform 3), Westfield Bus Station (Platform 1), Belconnen Community Bus Station (Platform 1), City Bus Station (Platform 1), Woden Bus Station (Platform 6), Tuggeranong Bus Station] time_points: [Fraser West Terminus, Fraser, Charnwood Tillyard Dr, St Francis Xavier Florey, Cohen Street Bus Station (Platform 3), Westfield Bus Station (Platform 1), Belconnen Community Bus Station (Platform 1), City Bus Station (Platform 1), Woden Bus Station (Platform 6), Tuggeranong Bus Station]
long_name: To Tuggeranong Bus Station long_name: To Tuggeranong Bus Station
between_stops: between_stops:
Cohen Street Bus Station (Platform 3)-Westfield Bus Station (Platform 1): [] Cohen Street Bus Station (Platform 3)-Westfield Bus Station (Platform 1): []
Westfield Bus Station (Platform 1)-Belconnen Community Bus Station (Platform 1): [] Westfield Bus Station (Platform 1)-Belconnen Community Bus Station (Platform 1): []
Woden Bus Station (Platform 6)-Tuggeranong Bus Station: [Wjz3kAx, Wjz3kyX, Wjz3kwU, Wjz3iFK, Wjz3hL_, Wjz3gK-, Wjz3gQn, Wjz3gMq, Wjz2nLE, Wjz2mTK, Wjz2mGO, Wjz2lDC, Wjz239F, Wjz238T, Wjz213q] Woden Bus Station (Platform 6)-Tuggeranong Bus Station: [Wjz3kAx, Wjz3kyX, Wjz3kwU, Wjz3iFK, Wjz3hL_, Wjz3gK-, Wjz3gQn, Wjz3gMq, Wjz2nLE, Wjz2mTK, Wjz2mGO, Wjz2lDC, Wjz239F, Wjz238T, Wjz213q]
City Bus Station (Platform 1)-Woden Bus Station (Platform 6): [Wjz5Nht, Wjz4KO9, Wjz4KNu, Wjz3eZ4, Wjz3eRR, Wjz3m3b, Wjz3m3b] City Bus Station (Platform 1)-Woden Bus Station (Platform 6): [Wjz5Nht, Wjz4KO9, Wjz4KNu, Wjz3eZ4, Wjz3eRR, Wjz3m3b, Wjz3m3b]
Belconnen Community Bus Station (Platform 1)-City Bus Station (Platform 1): [Wjz681S, Wjz689c, Wjz68W3, Wjz68W5, Wjz6gia, Wjz6giR, Wjz5nw6, Wjz5nwb, Wjz5mbS, Wjz5maK, Wjz5G6B, Wjz5G6U, Wjz5GNG, Wjz5GMT, Wjz5FSY, Wjz5F-1] Belconnen Community Bus Station (Platform 1)-City Bus Station (Platform 1): [Wjz681S, Wjz689c, Wjz68W3, Wjz68W5, Wjz6gia, Wjz6giR, Wjz5nw6, Wjz5nwb, Wjz5mbS, Wjz5maK, Wjz5G6B, Wjz5G6U, Wjz5GNG, Wjz5GMT, Wjz5FSY, Wjz5F-1]
short_name: 14 314 short_name: 14 314
stop_times: [[611a, 618a, 622a, 627a, 636a, 638a, 642a, "-", "-", "-"], [640a, 647a, 651a, 656a, 705a, 707a, 711a, 731a, 748a, 805a], [709a, 716a, 720a, 725a, 734a, 736a, 740a, 803a, 820a, 837a], [732a, 740a, 745a, 750a, 800a, 802a, 806a, 828a, 845a, 902a], [752a, 800a, 805a, 810a, 820a, 822a, 826a, 848a, 905a, 922a], [812a, 820a, 825a, 830a, 840a, 842a, 846a, 908a, 925a, 941a], [837a, 845a, 850a, 855a, 905a, 907a, 911a, 933a, 950a, 1005a], [908a, 916a, 921a, 926a, 935a, 937a, 941a, 1001a, 1018a, 1033a], [940a, 947a, 951a, 956a, 1005a, 1007a, 1011a, 1031a, 1048a, 1103a], [1010a, 1017a, 1021a, 1026a, 1035a, 1037a, 1041a, 1101a, 1118a, 1133a], [1040a, 1047a, 1051a, 1056a, 1105a, 1107a, 1111a, 1131a, 1148a, 1203p], [1110a, 1117a, 1121a, 1126a, 1135a, 1137a, 1141a, 1201p, 1218p, 1233p], [1140a, 1147a, 1151a, 1156a, 1205p, 1207p, 1211p, 1231p, 1248p, 103p], [1210p, 1217p, 1221p, 1226p, 1235p, 1237p, 1241p, 101p, 118p, 133p], [1240p, 1247p, 1251p, 1256p, 105p, 107p, 111p, 131p, 148p, 203p], [110p, 117p, 121p, 126p, 135p, 137p, 141p, 201p, 218p, 233p], [140p, 147p, 151p, 156p, 205p, 207p, 211p, 231p, 248p, 304p], [210p, 217p, 221p, 226p, 235p, 237p, 241p, 301p, 318p, 337p], [239p, 246p, 250p, 255p, 305p, 307p, 311p, 333p, 350p, 409p], [308p, 315p, 320p, 325p, 335p, 337p, 341p, 403p, 420p, 439p], [348p, 355p, 400p, 405p, 415p, 417p, 421p, 443p, 500p, 519p], [418p, 425p, 430p, 435p, 445p, 447p, 451p, 513p, 530p, 549p], [450p, 457p, 502p, 507p, 517p, 519p, 523p, "-", "-", "-"], [538p, 545p, 550p, 555p, 605p, 607p, 611p, 632p, 646p, 701p], [609p, 616p, 621p, 626p, 635p, 637p, 641p, 700p, 714p, 729p], [637p, 644p, 648p, 653p, 701p, 703p, 707p, "-", "-", "-"], [707p, 714p, 718p, 723p, 731p, 733p, 737p, "-", "-", "-"], [745p, 752p, 756p, 801p, 809p, 811p, 815p, "-", "-", "-"], [839p, 846p, 850p, 855p, 903p, 905p, 909p, "-", "-", "-"], [939p, 946p, 950p, 955p, 1003p, 1005p, 1009p, "-", "-", "-"], [1039p, 1046p, 1050p, 1055p, 1103p, 1105p, 1109p, "-", "-", "-"]] stop_times: [[611a, 618a, 622a, 627a, 636a, 638a, 642a, "-", "-", "-"], [640a, 647a, 651a, 656a, 705a, 707a, 711a, 731a, 748a, 805a], [709a, 716a, 720a, 725a, 734a, 736a, 740a, 803a, 820a, 837a], [732a, 740a, 745a, 750a, 800a, 802a, 806a, 828a, 845a, 902a], [752a, 800a, 805a, 810a, 820a, 822a, 826a, 848a, 905a, 922a], [812a, 820a, 825a, 830a, 840a, 842a, 846a, 908a, 925a, 941a], [837a, 845a, 850a, 855a, 905a, 907a, 911a, 933a, 950a, 1005a], [908a, 916a, 921a, 926a, 935a, 937a, 941a, 1001a, 1018a, 1033a], [940a, 947a, 951a, 956a, 1005a, 1007a, 1011a, 1031a, 1048a, 1103a], [1010a, 1017a, 1021a, 1026a, 1035a, 1037a, 1041a, 1101a, 1118a, 1133a], [1040a, 1047a, 1051a, 1056a, 1105a, 1107a, 1111a, 1131a, 1148a, 1203p], [1110a, 1117a, 1121a, 1126a, 1135a, 1137a, 1141a, 1201p, 1218p, 1233p], [1140a, 1147a, 1151a, 1156a, 1205p, 1207p, 1211p, 1231p, 1248p, 103p], [1210p, 1217p, 1221p, 1226p, 1235p, 1237p, 1241p, 101p, 118p, 133p], [1240p, 1247p, 1251p, 1256p, 105p, 107p, 111p, 131p, 148p, 203p], [110p, 117p, 121p, 126p, 135p, 137p, 141p, 201p, 218p, 233p], [140p, 147p, 151p, 156p, 205p, 207p, 211p, 231p, 248p, 304p], [210p, 217p, 221p, 226p, 235p, 237p, 241p, 301p, 318p, 337p], [239p, 246p, 250p, 255p, 305p, 307p, 311p, 333p, 350p, 409p], [308p, 315p, 320p, 325p, 335p, 337p, 341p, 403p, 420p, 439p], [348p, 355p, 400p, 405p, 415p, 417p, 421p, 443p, 500p, 519p], [418p, 425p, 430p, 435p, 445p, 447p, 451p, 513p, 530p, 549p], [450p, 457p, 502p, 507p, 517p, 519p, 523p, "-", "-", "-"], [538p, 545p, 550p, 555p, 605p, 607p, 611p, 632p, 646p, 701p], [609p, 616p, 621p, 626p, 635p, 637p, 641p, 700p, 714p, 729p], [637p, 644p, 648p, 653p, 701p, 703p, 707p, "-", "-", "-"], [707p, 714p, 718p, 723p, 731p, 733p, 737p, "-", "-", "-"], [745p, 752p, 756p, 801p, 809p, 811p, 815p, "-", "-", "-"], [839p, 846p, 850p, 855p, 903p, 905p, 909p, "-", "-", "-"], [939p, 946p, 950p, 955p, 1003p, 1005p, 1009p, "-", "-", "-"], [1039p, 1046p, 1050p, 1055p, 1103p, 1105p, 1109p, "-", "-", "-"]]
   
--- ---
time_points: [Tuggeranong Bus Station (Platform 8), Woden Bus Station (Platform 9), City Bus Station (Platform 3), Belconnen Community Bus Station (Platform 4), Westfield Bus Station (Platform 2), Cohen Street Bus Station (Platform 6), Copland College, Melba Shops, Spence Shops, Spence Terminus] time_points: [Tuggeranong Bus Station (Platform 8), Woden Bus Station (Platform 9), City Bus Station (Platform 3), Belconnen Community Bus Station (Platform 4), Westfield Bus Station (Platform 2), Cohen Street Bus Station (Platform 6), Copland College, Melba, Spence, Spence Terminus]
long_name: To Spence Terminus long_name: To Spence Terminus
between_stops: between_stops:
Belconnen Community Bus Station (Platform 4)-Westfield Bus Station (Platform 2): [] Belconnen Community Bus Station (Platform 4)-Westfield Bus Station (Platform 2): []
Tuggeranong Bus Station (Platform 8)-Woden Bus Station (Platform 9): [Wjz213q, Wjz238T, Wjz239F, Wjz2lDC, Wjz2mGO, Wjz2mTK, Wjz2nLE, Wjz3gMq, Wjz3gQn, Wjz3gK-, Wjz3hL_, Wjz3iFK, Wjz3kwU, Wjz3kyX, Wjz3kAx] Tuggeranong Bus Station (Platform 8)-Woden Bus Station (Platform 9): [Wjz213q, Wjz238T, Wjz239F, Wjz2lDC, Wjz2mGO, Wjz2mTK, Wjz2nLE, Wjz3gMq, Wjz3gQn, Wjz3gK-, Wjz3hL_, Wjz3iFK, Wjz3kwU, Wjz3kyX, Wjz3kAx]
Westfield Bus Station (Platform 2)-Cohen Street Bus Station (Platform 6): [] Westfield Bus Station (Platform 2)-Cohen Street Bus Station (Platform 6): []
City Bus Station (Platform 3)-Belconnen Community Bus Station (Platform 4): [Wjz5F-1, Wjz5FSY, Wjz5GMT, Wjz5GNG, Wjz5G6U, Wjz5G6B, Wjz5maK, Wjz5mbS, Wjz5nwb, Wjz5nw6, Wjz6giR, Wjz6gia, Wjz68W3, Wjz68W5, Wjz689c, Wjz681S] City Bus Station (Platform 3)-Belconnen Community Bus Station (Platform 4): [Wjz5F-1, Wjz5FSY, Wjz5GMT, Wjz5GNG, Wjz5G6U, Wjz5G6B, Wjz5maK, Wjz5mbS, Wjz5nwb, Wjz5nw6, Wjz6giR, Wjz6gia, Wjz68W3, Wjz68W5, Wjz689c, Wjz681S]
Woden Bus Station (Platform 9)-City Bus Station (Platform 3): [Wjz3m3b, Wjz3m3b, Wjz3eRR, Wjz3eZ4, Wjz4KNu, Wjz4KO9, Wjz5Nht] Woden Bus Station (Platform 9)-City Bus Station (Platform 3): [Wjz3m3b, Wjz3m3b, Wjz3eRR, Wjz3eZ4, Wjz4KNu, Wjz4KO9, Wjz5Nht]
short_name: 15 315 short_name: 15 315
stop_times: [["-", "-", "-", 723a, 725a, 729a, 737a, 741a, 749a, 755a], ["-", "-", "-", 803a, 805a, 809a, 817a, 821a, 829a, 835a], [731a, 750a, 808a, 829a, 831a, 835a, 843a, 847a, 855a, 901a], [831a, 850a, 908a, 929a, 931a, 935a, 942a, 945a, 951a, 957a], [911a, 930a, 946a, 1006a, 1008a, 1012a, 1019a, 1022a, 1028a, 1034a], [941a, 959a, 1015a, 1035a, 1037a, 1041a, 1048a, 1051a, 1057a, 1103a], [1011a, 1029a, 1045a, 1105a, 1107a, 1111a, 1118a, 1121a, 1127a, 1133a], [1041a, 1059a, 1115a, 1135a, 1137a, 1141a, 1148a, 1151a, 1157a, 1203p], [1111a, 1129a, 1145a, 1205p, 1207p, 1211p, 1218p, 1221p, 1227p, 1233p], [1141a, 1159a, 1215p, 1235p, 1237p, 1241p, 1248p, 1251p, 1257p, 103p], [1211p, 1229p, 1245p, 105p, 107p, 111p, 118p, 121p, 127p, 133p], [1241p, 1259p, 115p, 135p, 137p, 141p, 148p, 151p, 157p, 203p], [111p, 129p, 145p, 205p, 207p, 211p, 218p, 221p, 227p, 233p], [141p, 159p, 215p, 235p, 237p, 241p, 248p, 251p, 257p, 303p], [211p, 229p, 245p, 305p, 307p, 311p, 319p, 323p, 331p, 337p], [240p, 258p, 316p, 337p, 339p, 343p, 351p, 355p, 403p, 409p], ["-", "-", "-", 357p, 359p, 403p, 411p, 415p, 423p, 429p], [311p, 330p, 348p, 409p, 411p, 415p, 423p, 427p, 435p, 441p], [341p, 400p, 418p, 439p, 441p, 445p, 453p, 457p, 505p, 511p], [411p, 430p, 448p, 509p, 511p, 515p, 523p, 527p, 535p, 541p], [441p, 500p, 518p, 539p, 541p, 545p, 553p, 557p, 605p, 611p], [501p, 520p, 538p, 559p, 601p, 605p, 613p, 617p, 625p, 631p], [521p, 540p, 558p, 619p, 621p, 625p, 633p, 636p, 642p, 648p], [601p, 620p, 636p, 656p, 658p, 702p, 709p, 712p, 718p, 724p], ["-", "-", "-", 728p, 730p, 734p, 741p, 744p, 750p, 756p], ["-", "-", "-", 804p, 806p, 810p, 817p, 820p, 826p, 832p], ["-", "-", "-", 904p, 906p, 910p, 917p, 920p, 926p, 932p], ["-", "-", "-", 1004p, 1006p, 1010p, 1017p, 1020p, 1026p, 1032p], ["-", "-", "-", 1104p, 1106p, 1110p, 1117p, 1120p, 1126p, 1132p], []] stop_times: [["-", "-", "-", 723a, 725a, 729a, 737a, 741a, 749a, 755a], ["-", "-", "-", 803a, 805a, 809a, 817a, 821a, 829a, 835a], [731a, 750a, 808a, 829a, 831a, 835a, 843a, 847a, 855a, 901a], [831a, 850a, 908a, 929a, 931a, 935a, 942a, 945a, 951a, 957a], [911a, 930a, 946a, 1006a, 1008a, 1012a, 1019a, 1022a, 1028a, 1034a], [941a, 959a, 1015a, 1035a, 1037a, 1041a, 1048a, 1051a, 1057a, 1103a], [1011a, 1029a, 1045a, 1105a, 1107a, 1111a, 1118a, 1121a, 1127a, 1133a], [1041a, 1059a, 1115a, 1135a, 1137a, 1141a, 1148a, 1151a, 1157a, 1203p], [1111a, 1129a, 1145a, 1205p, 1207p, 1211p, 1218p, 1221p, 1227p, 1233p], [1141a, 1159a, 1215p, 1235p, 1237p, 1241p, 1248p, 1251p, 1257p, 103p], [1211p, 1229p, 1245p, 105p, 107p, 111p, 118p, 121p, 127p, 133p], [1241p, 1259p, 115p, 135p, 137p, 141p, 148p, 151p, 157p, 203p], [111p, 129p, 145p, 205p, 207p, 211p, 218p, 221p, 227p, 233p], [141p, 159p, 215p, 235p, 237p, 241p, 248p, 251p, 257p, 303p], [211p, 229p, 245p, 305p, 307p, 311p, 319p, 323p, 331p, 337p], [240p, 258p, 316p, 337p, 339p, 343p, 351p, 355p, 403p, 409p], ["-", "-", "-", 357p, 359p, 403p, 411p, 415p, 423p, 429p], [311p, 330p, 348p, 409p, 411p, 415p, 423p, 427p, 435p, 441p], [341p, 400p, 418p, 439p, 441p, 445p, 453p, 457p, 505p, 511p], [411p, 430p, 448p, 509p, 511p, 515p, 523p, 527p, 535p, 541p], [441p, 500p, 518p, 539p, 541p, 545p, 553p, 557p, 605p, 611p], [501p, 520p, 538p, 559p, 601p, 605p, 613p, 617p, 625p, 631p], [521p, 540p, 558p, 619p, 621p, 625p, 633p, 636p, 642p, 648p], [601p, 620p, 636p, 656p, 658p, 702p, 709p, 712p, 718p, 724p], ["-", "-", "-", 728p, 730p, 734p, 741p, 744p, 750p, 756p], ["-", "-", "-", 804p, 806p, 810p, 817p, 820p, 826p, 832p], ["-", "-", "-", 904p, 906p, 910p, 917p, 920p, 926p, 932p], ["-", "-", "-", 1004p, 1006p, 1010p, 1017p, 1020p, 1026p, 1032p], ["-", "-", "-", 1104p, 1106p, 1110p, 1117p, 1120p, 1126p, 1132p], []]
   
--- ---
time_points: [Spence Terminus, Spence Shops, Melba Shops, Copland College, Cohen Street Bus Station (Platform 3), Westfield Bus Station (Platform 1), Belconnen Community Bus Station (Platform 1), City Bus Station (Platform 1), Woden Bus Station (Platform 6), Tuggeranong Bus Station] time_points: [Spence Terminus, Spence, Melba, Copland College, Cohen Street Bus Station (Platform 3), Westfield Bus Station (Platform 1), Belconnen Community Bus Station (Platform 1), City Bus Station (Platform 1), Woden Bus Station (Platform 6), Tuggeranong Bus Station]
long_name: To Tuggeranong Bus Station long_name: To Tuggeranong Bus Station
between_stops: between_stops:
Cohen Street Bus Station (Platform 3)-Westfield Bus Station (Platform 1): [] Cohen Street Bus Station (Platform 3)-Westfield Bus Station (Platform 1): []
Westfield Bus Station (Platform 1)-Belconnen Community Bus Station (Platform 1): [] Westfield Bus Station (Platform 1)-Belconnen Community Bus Station (Platform 1): []
Woden Bus Station (Platform 6)-Tuggeranong Bus Station: [Wjz3kAx, Wjz3kyX, Wjz3kwU, Wjz3iFK, Wjz3hL_, Wjz3gK-, Wjz3gQn, Wjz3gMq, Wjz2nLE, Wjz2mTK, Wjz2mGO, Wjz2lDC, Wjz239F, Wjz238T, Wjz213q] Woden Bus Station (Platform 6)-Tuggeranong Bus Station: [Wjz3kAx, Wjz3kyX, Wjz3kwU, Wjz3iFK, Wjz3hL_, Wjz3gK-, Wjz3gQn, Wjz3gMq, Wjz2nLE, Wjz2mTK, Wjz2mGO, Wjz2lDC, Wjz239F, Wjz238T, Wjz213q]
City Bus Station (Platform 1)-Woden Bus Station (Platform 6): [Wjz5Nht, Wjz4KO9, Wjz4KNu, Wjz3eZ4, Wjz3eRR, Wjz3m3b, Wjz3m3b] City Bus Station (Platform 1)-Woden Bus Station (Platform 6): [Wjz5Nht, Wjz4KO9, Wjz4KNu, Wjz3eZ4, Wjz3eRR, Wjz3m3b, Wjz3m3b]
Belconnen Community Bus Station (Platform 1)-City Bus Station (Platform 1): [Wjz681S, Wjz689c, Wjz68W3, Wjz68W5, Wjz6gia, Wjz6giR, Wjz5nw6, Wjz5nwb, Wjz5mbS, Wjz5maK, Wjz5G6B, Wjz5G6U, Wjz5GNG, Wjz5GMT, Wjz5FSY, Wjz5F-1] Belconnen Community Bus Station (Platform 1)-City Bus Station (Platform 1): [Wjz681S, Wjz689c, Wjz68W3, Wjz68W5, Wjz6gia, Wjz6giR, Wjz5nw6, Wjz5nwb, Wjz5mbS, Wjz5maK, Wjz5G6B, Wjz5G6U, Wjz5GNG, Wjz5GMT, Wjz5FSY, Wjz5F-1]
short_name: 15 315 short_name: 15 315
stop_times: [[533a, 538a, 543a, 546a, 556a, 558a, 602a, "-", "-", "-"], [603a, 608a, 613a, 616a, 626a, 628a, 632a, "-", "-", "-"], [632a, 637a, 642a, 645a, 655a, 657a, 701a, 721a, 738a, 755a], [702a, 707a, 712a, 715a, 725a, 727a, 731a, 753a, 810a, 827a], [728a, 733a, 739a, 743a, 753a, 755a, 759a, 821a, 838a, 855a], [750a, 755a, 801a, 805a, 815a, 817a, 821a, 843a, 900a, 917a], ["-", "-", 818a, 822a, 832a, 834a, 838a, 858a, "-", "-"], [810a, 815a, 821a, 825a, 835a, 837a, 841a, 903a, 920a, 936a], [830a, 835a, 841a, 845a, 855a, 857a, 901a, 923a, 940a, 955a], [900a, 905a, 911a, 915a, 925a, 927a, 931a, 951a, 1008a, 1023a], [932a, 937a, 942a, 945a, 955a, 957a, 1001a, 1021a, 1038a, 1053a], [1002a, 1007a, 1012a, 1015a, 1025a, 1027a, 1031a, 1051a, 1108a, 1123a], [1032a, 1037a, 1042a, 1045a, 1055a, 1057a, 1101a, 1121a, 1138a, 1153a], [1102a, 1107a, 1112a, 1115a, 1125a, 1127a, 1131a, 1151a, 1208p, 1223p], [1132a, 1137a, 1142a, 1145a, 1155a, 1157a, 1201p, 1221p, 1238p, 1253p], [1202p, 1207p, 1212p, 1215p, 1225p, 1227p, 1231p, 1251p, 108p, 123p], [1232p, 1237p, 1242p, 1245p, 1255p, 1257p, 101p, 121p, 138p, 153p], [102p, 107p, 112p, 115p, 125p, 127p, 131p, 151p, 208p, 223p], [132p, 137p, 142p, 145p, 155p, 157p, 201p, 221p, 238p, 253p], [202p, 207p, 212p, 215p, 225p, 227p, 231p, 251p, 308p, 327p], [233p, 238p, 243p, 246p, 256p, 258p, 302p, 324p, 341p, 400p], [300p, 305p, 311p, 315p, 325p, 327p, 331p, 353p, 410p, 429p], [330p, 335p, 341p, 345p, 355p, 357p, 401p, 423p, 440p, 459p], [400p, 405p, 411p, 415p, 425p, 427p, 431p, 453p, 510p, 529p], [440p, 445p, 451p, 455p, 505p, 507p, 511p, 533p, 550p, 609p], [530p, 535p, 541p, 545p, 555p, 557p, 601p, 623p, 638p, 653p], [600p, 605p, 611p, 615p, 625p, 627p, 631p, 650p, 704p, 719p], [623p, 628p, 633p, 636p, 645p, 647p, 651p, "-", "-", "-"], [656p, 701p, 706p, 709p, 718p, 720p, 724p, "-", "-", "-"], [740p, 745p, 750p, 753p, 802p, 804p, 808p, "-", "-", "-"], [840p, 845p, 850p, 853p, 902p, 904p, 908p, "-", "-", "-"], [940p, 945p, 950p, 953p, 1002p, 1004p, 1008p, "-", "-", "-"], [1040p, 1045p, 1050p, 1053p, 1102p, 1104p, 1108p, "-", "-", "-"], []] stop_times: [[533a, 538a, 543a, 546a, 556a, 558a, 602a, "-", "-", "-"], [603a, 608a, 613a, 616a, 626a, 628a, 632a, "-", "-", "-"], [632a, 637a, 642a, 645a, 655a, 657a, 701a, 721a, 738a, 755a], [702a, 707a, 712a, 715a, 725a, 727a, 731a, 753a, 810a, 827a], [728a, 733a, 739a, 743a, 753a, 755a, 759a, 821a, 838a, 855a], [750a, 755a, 801a, 805a, 815a, 817a, 821a, 843a, 900a, 917a], ["-", "-", 818a, 822a, 832a, 834a, 838a, 858a, "-", "-"], [810a, 815a, 821a, 825a, 835a, 837a, 841a, 903a, 920a, 936a], [830a, 835a, 841a, 845a, 855a, 857a, 901a, 923a, 940a, 955a], [900a, 905a, 911a, 915a, 925a, 927a, 931a, 951a, 1008a, 1023a], [932a, 937a, 942a, 945a, 955a, 957a, 1001a, 1021a, 1038a, 1053a], [1002a, 1007a, 1012a, 1015a, 1025a, 1027a, 1031a, 1051a, 1108a, 1123a], [1032a, 1037a, 1042a, 1045a, 1055a, 1057a, 1101a, 1121a, 1138a, 1153a], [1102a, 1107a, 1112a, 1115a, 1125a, 1127a, 1131a, 1151a, 1208p, 1223p], [1132a, 1137a, 1142a, 1145a, 1155a, 1157a, 1201p, 1221p, 1238p, 1253p], [1202p, 1207p, 1212p, 1215p, 1225p, 1227p, 1231p, 1251p, 108p, 123p], [1232p, 1237p, 1242p, 1245p, 1255p, 1257p, 101p, 121p, 138p, 153p], [102p, 107p, 112p, 115p, 125p, 127p, 131p, 151p, 208p, 223p], [132p, 137p, 142p, 145p, 155p, 157p, 201p, 221p, 238p, 253p], [202p, 207p, 212p, 215p, 225p, 227p, 231p, 251p, 308p, 327p], [233p, 238p, 243p, 246p, 256p, 258p, 302p, 324p, 341p, 400p], [300p, 305p, 311p, 315p, 325p, 327p, 331p, 353p, 410p, 429p], [330p, 335p, 341p, 345p, 355p, 357p, 401p, 423p, 440p, 459p], [400p, 405p, 411p, 415p, 425p, 427p, 431p, 453p, 510p, 529p], [440p, 445p, 451p, 455p, 505p, 507p, 511p, 533p, 550p, 609p], [530p, 535p, 541p, 545p, 555p, 557p, 601p, 623p, 638p, 653p], [600p, 605p, 611p, 615p, 625p, 627p, 631p, 650p, 704p, 719p], [623p, 628p, 633p, 636p, 645p, 647p, 651p, "-", "-", "-"], [656p, 701p, 706p, 709p, 718p, 720p, 724p, "-", "-", "-"], [740p, 745p, 750p, 753p, 802p, 804p, 808p, "-", "-", "-"], [840p, 845p, 850p, 853p, 902p, 904p, 908p, "-", "-", "-"], [940p, 945p, 950p, 953p, 1002p, 1004p, 1008p, "-", "-", "-"], [1040p, 1045p, 1050p, 1053p, 1102p, 1104p, 1108p, "-", "-", "-"], []]
   
--- ---
time_points: [Kippax, Latham Post Office, Florey Shops, Cohen Street Bus Station, Westfield Bus Station, Belconnen Community Bus Station] time_points: [Kippax, Latham Post Office, Florey, Cohen Street Bus Station, Westfield Bus Station, Belconnen Community Bus Station]
long_name: To Belconnen Community Bus Station long_name: To Belconnen Community Bus Station
between_stops: between_stops:
Westfield Bus Station-Belconnen Community Bus Station: [] Westfield Bus Station-Belconnen Community Bus Station: []
Cohen Street Bus Station-Westfield Bus Station: [] Cohen Street Bus Station-Westfield Bus Station: []
short_name: "16" short_name: "16"
stop_times: [[610a, 619a, 625a, 630a, 632a, 636a], [640a, 649a, 655a, 700a, 702a, 706a], [711a, 720a, 726a, 731a, 733a, 737a], [730a, 741a, 747a, 753a, 755a, 759a], [750a, 801a, 807a, 813a, 815a, 819a], [810a, 821a, 827a, 833a, 835a, 839a], [830a, 841a, 847a, 853a, 855a, 859a], [851a, 902a, 908a, 914a, 916a, 920a], [916a, 927a, 933a, 938a, 940a, 944a], [946a, 955a, 1001a, 1006a, 1008a, 1012a], [1011a, 1020a, 1026a, 1031a, 1033a, 1037a], [1046a, 1055a, 1101a, 1106a, 1108a, 1112a], [1111a, 1120a, 1126a, 1131a, 1133a, 1137a], [1146a, 1155a, 1201p, 1206p, 1208p, 1212p], [1211p, 1220p, 1226p, 1231p, 1233p, 1237p], [1246p, 1255p, 101p, 106p, 108p, 112p], [111p, 120p, 126p, 131p, 133p, 137p], [146p, 155p, 201p, 206p, 208p, 212p], [211p, 220p, 226p, 231p, 233p, 237p], [246p, 255p, 301p, 307p, 309p, 313p], [311p, 322p, 328p, 334p, 336p, 340p], [341p, 352p, 358p, 404p, 406p, 410p], [407p, 418p, 424p, 430p, 432p, 436p], [431p, 442p, 448p, 454p, 456p, 500p], [456p, 507p, 513p, 519p, 521p, 525p], [526p, 537p, 543p, 549p, 551p, 555p], [555p, 606p, 612p, 618p, 620p, 624p], [655p, 704p, 710p, 714p, 716p, 720p], [755p, 804p, 810p, 814p, 816p, 820p], [855p, 904p, 910p, 914p, 916p, 920p], [955p, 1004p, 1010p, 1014p, 1016p, 1020p], [1055p, 1104p, 1110p, 1114p, 1116p, 1120p]] stop_times: [[610a, 619a, 625a, 630a, 632a, 636a], [640a, 649a, 655a, 700a, 702a, 706a], [711a, 720a, 726a, 731a, 733a, 737a], [730a, 741a, 747a, 753a, 755a, 759a], [750a, 801a, 807a, 813a, 815a, 819a], [810a, 821a, 827a, 833a, 835a, 839a], [830a, 841a, 847a, 853a, 855a, 859a], [851a, 902a, 908a, 914a, 916a, 920a], [916a, 927a, 933a, 938a, 940a, 944a], [946a, 955a, 1001a, 1006a, 1008a, 1012a], [1011a, 1020a, 1026a, 1031a, 1033a, 1037a], [1046a, 1055a, 1101a, 1106a, 1108a, 1112a], [1111a, 1120a, 1126a, 1131a, 1133a, 1137a], [1146a, 1155a, 1201p, 1206p, 1208p, 1212p], [1211p, 1220p, 1226p, 1231p, 1233p, 1237p], [1246p, 1255p, 101p, 106p, 108p, 112p], [111p, 120p, 126p, 131p, 133p, 137p], [146p, 155p, 201p, 206p, 208p, 212p], [211p, 220p, 226p, 231p, 233p, 237p], [246p, 255p, 301p, 307p, 309p, 313p], [311p, 322p, 328p, 334p, 336p, 340p], [341p, 352p, 358p, 404p, 406p, 410p], [407p, 418p, 424p, 430p, 432p, 436p], [431p, 442p, 448p, 454p, 456p, 500p], [456p, 507p, 513p, 519p, 521p, 525p], [526p, 537p, 543p, 549p, 551p, 555p], [555p, 606p, 612p, 618p, 620p, 624p], [655p, 704p, 710p, 714p, 716p, 720p], [755p, 804p, 810p, 814p, 816p, 820p], [855p, 904p, 910p, 914p, 916p, 920p], [955p, 1004p, 1010p, 1014p, 1016p, 1020p], [1055p, 1104p, 1110p, 1114p, 1116p, 1120p]]
   
--- ---
time_points: [Belconnen Community Bus Station (Platform 6), Westfield Bus Station (Platform 2), Cohen Street Bus Station (Platform 5), Florey Shops, Latham Post Office, Kippax] time_points: [Belconnen Community Bus Station (Platform 6), Westfield Bus Station (Platform 2), Cohen Street Bus Station (Platform 5), Florey, Latham Post Office, Kippax]
long_name: To Kippax long_name: To Kippax
between_stops: between_stops:
Belconnen Community Bus Station (Platform 6)-Westfield Bus Station (Platform 2): [] Belconnen Community Bus Station (Platform 6)-Westfield Bus Station (Platform 2): []
Westfield Bus Station (Platform 2)-Cohen Street Bus Station (Platform 5): [] Westfield Bus Station (Platform 2)-Cohen Street Bus Station (Platform 5): []
short_name: "16" short_name: "16"
stop_times: [[700a, 702a, 706a, 711a, 717a, 727a], [800a, 802a, 806a, 812a, 818a, 830a], [826a, 828a, 832a, 838a, 844a, 856a], [913a, 915a, 919a, 925a, 931a, 941a], [939a, 941a, 945a, 950a, 956a, 1006a], [1014a, 1016a, 1020a, 1025a, 1031a, 1041a], [1039a, 1041a, 1045a, 1050a, 1056a, 1106a], [1114a, 1116a, 1120a, 1125a, 1131a, 1141a], [1139a, 1141a, 1145a, 1150a, 1156a, 1206p], [1214p, 1216p, 1220p, 1225p, 1231p, 1241p], [1239p, 1241p, 1245p, 1250p, 1256p, 106p], [114p, 116p, 120p, 125p, 131p, 141p], [139p, 141p, 145p, 150p, 156p, 206p], [214p, 216p, 220p, 225p, 231p, 241p], [238p, 240p, 244p, 249p, 255p, 306p], [307p, 309p, 313p, 319p, 325p, 337p], [326p, 328p, 332p, 338p, 344p, 356p], [356p, 358p, 402p, 408p, 414p, 426p], [426p, 428p, 432p, 438p, 444p, 456p], [446p, 448p, 452p, 458p, 504p, 516p], [506p, 508p, 512p, 518p, 524p, 536p], [526p, 528p, 532p, 538p, 544p, 556p], [546p, 548p, 552p, 558p, 604p, 616p], [601p, 603p, 607p, 613p, 619p, 631p], [622p, 624p, 628p, 633p, 639p, 649p], [721p, 723p, 727p, 731p, 737p, 747p], [821p, 823p, 827p, 831p, 837p, 847p], [921p, 923p, 927p, 931p, 937p, 947p], [1021p, 1023p, 1027p, 1031p, 1037p, 1047p], [1121p, 1123p, 1127p, 1131p, 1137p, 1147p]] stop_times: [[700a, 702a, 706a, 711a, 717a, 727a], [800a, 802a, 806a, 812a, 818a, 830a], [826a, 828a, 832a, 838a, 844a, 856a], [913a, 915a, 919a, 925a, 931a, 941a], [939a, 941a, 945a, 950a, 956a, 1006a], [1014a, 1016a, 1020a, 1025a, 1031a, 1041a], [1039a, 1041a, 1045a, 1050a, 1056a, 1106a], [1114a, 1116a, 1120a, 1125a, 1131a, 1141a], [1139a, 1141a, 1145a, 1150a, 1156a, 1206p], [1214p, 1216p, 1220p, 1225p, 1231p, 1241p], [1239p, 1241p, 1245p, 1250p, 1256p, 106p], [114p, 116p, 120p, 125p, 131p, 141p], [139p, 141p, 145p, 150p, 156p, 206p], [214p, 216p, 220p, 225p, 231p, 241p], [238p, 240p, 244p, 249p, 255p, 306p], [307p, 309p, 313p, 319p, 325p, 337p], [326p, 328p, 332p, 338p, 344p, 356p], [356p, 358p, 402p, 408p, 414p, 426p], [426p, 428p, 432p, 438p, 444p, 456p], [446p, 448p, 452p, 458p, 504p, 516p], [506p, 508p, 512p, 518p, 524p, 536p], [526p, 528p, 532p, 538p, 544p, 556p], [546p, 548p, 552p, 558p, 604p, 616p], [601p, 603p, 607p, 613p, 619p, 631p], [622p, 624p, 628p, 633p, 639p, 649p], [721p, 723p, 727p, 731p, 737p, 747p], [821p, 823p, 827p, 831p, 837p, 847p], [921p, 923p, 927p, 931p, 937p, 947p], [1021p, 1023p, 1027p, 1031p, 1037p, 1047p], [1121p, 1123p, 1127p, 1131p, 1137p, 1147p]]
   
--- ---
time_points: [Kippax, Higgins, Hawker College, Hawker Shops, Weetangera Shops, Cohen Street Bus Station, Westfield Bus Station, Belconnen Community Bus Station] time_points: [Kippax, Higgins, Hawker College, Hawker, Weetangera, Cohen Street Bus Station, Westfield Bus Station, Belconnen Community Bus Station]
long_name: To Belconnen Community Bus Station long_name: To Belconnen Community Bus Station
between_stops: between_stops:
Westfield Bus Station-Belconnen Community Bus Station: [] Westfield Bus Station-Belconnen Community Bus Station: []
Cohen Street Bus Station-Westfield Bus Station: [] Cohen Street Bus Station-Westfield Bus Station: []
short_name: "17" short_name: "17"
stop_times: [[601a, 606a, 612a, 617a, 620a, 625a, 627a, 631a], [631a, 636a, 642a, 647a, 650a, 655a, 657a, 701a], [701a, 706a, 712a, 717a, 720a, 725a, 727a, 731a], [721a, 726a, 732a, 737a, 740a, 746a, 748a, 752a], [741a, 747a, 753a, 758a, 801a, 807a, 809a, 813a], [801a, 807a, 813a, 818a, 821a, 827a, 829a, 833a], [821a, 827a, 833a, 838a, 841a, 847a, 849a, 853a], [841a, 847a, 853a, 858a, 901a, 907a, 909a, 913a], [925a, 931a, 937a, 942a, 945a, 950a, 952a, 956a], [956a, 1001a, 1007a, 1012a, 1015a, 1020a, 1022a, 1026a], [1026a, 1031a, 1037a, 1042a, 1045a, 1050a, 1052a, 1056a], [1056a, 1101a, 1107a, 1112a, 1115a, 1120a, 1122a, 1126a], [1126a, 1131a, 1137a, 1142a, 1145a, 1150a, 1152a, 1156a], [1156a, 1201p, 1207p, 1212p, 1215p, 1220p, 1222p, 1226p], [1226p, 1231p, 1237p, 1242p, 1245p, 1250p, 1252p, 1256p], [1256p, 101p, 107p, 112p, 115p, 120p, 122p, 126p], ["-", "-", 122p, 127p, 130p, 135p, 137p, 141p], [126p, 131p, 137p, 142p, 145p, 150p, 152p, 156p], [156p, 201p, 207p, 212p, 215p, 220p, 222p, 226p], [226p, 231p, 237p, 242p, 245p, 250p, 252p, 256p], ["-", "-", 252p, 257p, 300p, 306p, 308p, 312p], [256p, 301p, 307p, 312p, 315p, 321p, 323p, 327p], ["-", "-", 325p, 330p, 333p, 339p, 341p, 345p], [326p, 332p, 338p, 343p, 346p, 352p, 354p, 358p], [347p, 353p, 359p, 404p, 407p, 413p, 415p, 419p], ["-", "-", 403p, 408p, 411p, 417p, 419p, 423p], [417p, 423p, 429p, 434p, 437p, 443p, 445p, 449p], [447p, 453p, 459p, 504p, 507p, 513p, 515p, 519p], [517p, 523p, 529p, 534p, 537p, 543p, 545p, 549p], [547p, 553p, 559p, 604p, 607p, 613p, 615p, 619p], [617p, 623p, 629p, 634p, 637p, 641p, 643p, 647p], [719p, 724p, 730p, 735p, 738p, 742p, 744p, 748p], [819p, 824p, 830p, 835p, 838p, 842p, 844p, 848p], [919p, 924p, 930p, 935p, 938p, 942p, 944p, 948p], [1019p, 1024p, 1030p, 1035p, 1038p, 1042p, 1044p, 1048p], [1119p, 1124p, 1130p, 1135p, 1138p, 1142p, 1144p, 1148p], []] stop_times: [[601a, 606a, 612a, 617a, 620a, 625a, 627a, 631a], [631a, 636a, 642a, 647a, 650a, 655a, 657a, 701a], [701a, 706a, 712a, 717a, 720a, 725a, 727a, 731a], [721a, 726a, 732a, 737a, 740a, 746a, 748a, 752a], [741a, 747a, 753a, 758a, 801a, 807a, 809a, 813a], [801a, 807a, 813a, 818a, 821a, 827a, 829a, 833a], [821a, 827a, 833a, 838a, 841a, 847a, 849a, 853a], [841a, 847a, 853a, 858a, 901a, 907a, 909a, 913a], [925a, 931a, 937a, 942a, 945a, 950a, 952a, 956a], [956a, 1001a, 1007a, 1012a, 1015a, 1020a, 1022a, 1026a], [1026a, 1031a, 1037a, 1042a, 1045a, 1050a, 1052a, 1056a], [1056a, 1101a, 1107a, 1112a, 1115a, 1120a, 1122a, 1126a], [1126a, 1131a, 1137a, 1142a, 1145a, 1150a, 1152a, 1156a], [1156a, 1201p, 1207p, 1212p, 1215p, 1220p, 1222p, 1226p], [1226p, 1231p, 1237p, 1242p, 1245p, 1250p, 1252p, 1256p], [1256p, 101p, 107p, 112p, 115p, 120p, 122p, 126p], ["-", "-", 122p, 127p, 130p, 135p, 137p, 141p], [126p, 131p, 137p, 142p, 145p, 150p, 152p, 156p], [156p, 201p, 207p, 212p, 215p, 220p, 222p, 226p], [226p, 231p, 237p, 242p, 245p, 250p, 252p, 256p], ["-", "-", 252p, 257p, 300p, 306p, 308p, 312p], [256p, 301p, 307p, 312p, 315p, 321p, 323p, 327p], ["-", "-", 325p, 330p, 333p, 339p, 341p, 345p], [326p, 332p, 338p, 343p, 346p, 352p, 354p, 358p], [347p, 353p, 359p, 404p, 407p, 413p, 415p, 419p], ["-", "-", 403p, 408p, 411p, 417p, 419p, 423p], [417p, 423p, 429p, 434p, 437p, 443p, 445p, 449p], [447p, 453p, 459p, 504p, 507p, 513p, 515p, 519p], [517p, 523p, 529p, 534p, 537p, 543p, 545p, 549p], [547p, 553p, 559p, 604p, 607p, 613p, 615p, 619p], [617p, 623p, 629p, 634p, 637p, 641p, 643p, 647p], [719p, 724p, 730p, 735p, 738p, 742p, 744p, 748p], [819p, 824p, 830p, 835p, 838p, 842p, 844p, 848p], [919p, 924p, 930p, 935p, 938p, 942p, 944p, 948p], [1019p, 1024p, 1030p, 1035p, 1038p, 1042p, 1044p, 1048p], [1119p, 1124p, 1130p, 1135p, 1138p, 1142p, 1144p, 1148p], []]
   
--- ---
time_points: [Belconnen Community Bus Station (Platform 6), Westfield Bus Station (Platform 2), Cohen Street Bus Station (Platform 5), Weetangera Shops, Hawker Shops, Hawker College, Higgins, Kippax] time_points: [Belconnen Community Bus Station (Platform 6), Westfield Bus Station (Platform 2), Cohen Street Bus Station (Platform 5), Weetangera, Hawker, Hawker College, Higgins, Kippax]
long_name: To Kippax long_name: To Kippax
between_stops: between_stops:
Belconnen Community Bus Station (Platform 6)-Westfield Bus Station (Platform 2): [] Belconnen Community Bus Station (Platform 6)-Westfield Bus Station (Platform 2): []
Westfield Bus Station (Platform 2)-Cohen Street Bus Station (Platform 5): [] Westfield Bus Station (Platform 2)-Cohen Street Bus Station (Platform 5): []
short_name: "17" short_name: "17"
stop_times: [[706a, 708a, 712a, 716a, 719a, 724a, 729a, 737a], [806a, 808a, 812a, 817a, 820a, 825a, 830a, 838a], [840a, 842a, 846a, 851a, 854a, 859a, 904a, 912a], [854a, 856a, 900a, 905a, 908a, 913a, 918a, 926a], [922a, 924a, 928a, 932a, 935a, 940a, 945a, 951a], [952a, 954a, 958a, 1002a, 1005a, 1010a, 1015a, 1021a], [1022a, 1024a, 1028a, 1032a, 1035a, 1040a, 1045a, 1051a], [1052a, 1054a, 1058a, 1102a, 1105a, 1110a, 1115a, 1121a], [1122a, 1124a, 1128a, 1132a, 1135a, 1140a, 1145a, 1151a], [1152a, 1154a, 1158a, 1202p, 1205p, 1210p, 1215p, 1221p], [1222p, 1224p, 1228p, 1232p, 1235p, 1240p, 1245p, 1251p], [1252p, 1254p, 1258p, 102p, 105p, 110p, 115p, 121p], [122p, 124p, 128p, 132p, 135p, 140p, 145p, 151p], [152p, 154p, 158p, 202p, 205p, 210p, 215p, 221p], [222p, 224p, 228p, 232p, 235p, 240p, 245p, 251p], [249p, 251p, 255p, 259p, 302p, 307p, 313p, 321p], [324p, 326p, 330p, 335p, 338p, 343p, 349p, 357p], [353p, 355p, 359p, 404p, 407p, 412p, 418p, 426p], [412p, 414p, 418p, 423p, 426p, 431p, 437p, 445p], [432p, 434p, 438p, 443p, 446p, 451p, 457p, 505p], [452p, 454p, 458p, 503p, 506p, 511p, 517p, 525p], [512p, 514p, 518p, 523p, 526p, 531p, 537p, 545p], [532p, 534p, 538p, 543p, 546p, 551p, 557p, 605p], [552p, 554p, 558p, 603p, 606p, 611p, 617p, 625p], [612p, 614p, 618p, 623p, 626p, 631p, 636p, 642p], [644p, 646p, 650p, 654p, 657p, 702p, 707p, 713p], [737p, 739p, 743p, 747p, 750p, 755p, 800p, 806p], [837p, 839p, 843p, 847p, 850p, 855p, 900p, 906p], [937p, 939p, 943p, 947p, 950p, 955p, 1000p, 1006p], [1037p, 1039p, 1043p, 1047p, 1050p, 1055p, 1100p, 1106p], [1138p, 1140p, 1144p, 1148p, 1151p, 1156p, 1201a, 1207a]] stop_times: [[706a, 708a, 712a, 716a, 719a, 724a, 729a, 737a], [806a, 808a, 812a, 817a, 820a, 825a, 830a, 838a], [840a, 842a, 846a, 851a, 854a, 859a, 904a, 912a], [854a, 856a, 900a, 905a, 908a, 913a, 918a, 926a], [922a, 924a, 928a, 932a, 935a, 940a, 945a, 951a], [952a, 954a, 958a, 1002a, 1005a, 1010a, 1015a, 1021a], [1022a, 1024a, 1028a, 1032a, 1035a, 1040a, 1045a, 1051a], [1052a, 1054a, 1058a, 1102a, 1105a, 1110a, 1115a, 1121a], [1122a, 1124a, 1128a, 1132a, 1135a, 1140a, 1145a, 1151a], [1152a, 1154a, 1158a, 1202p, 1205p, 1210p, 1215p, 1221p], [1222p, 1224p, 1228p, 1232p, 1235p, 1240p, 1245p, 1251p], [1252p, 1254p, 1258p, 102p, 105p, 110p, 115p, 121p], [122p, 124p, 128p, 132p, 135p, 140p, 145p, 151p], [152p, 154p, 158p, 202p, 205p, 210p, 215p, 221p], [222p, 224p, 228p, 232p, 235p, 240p, 245p, 251p], [249p, 251p, 255p, 259p, 302p, 307p, 313p, 321p], [324p, 326p, 330p, 335p, 338p, 343p, 349p, 357p], [353p, 355p, 359p, 404p, 407p, 412p, 418p, 426p], [412p, 414p, 418p, 423p, 426p, 431p, 437p, 445p], [432p, 434p, 438p, 443p, 446p, 451p, 457p, 505p], [452p, 454p, 458p, 503p, 506p, 511p, 517p, 525p], [512p, 514p, 518p, 523p, 526p, 531p, 537p, 545p], [532p, 534p, 538p, 543p, 546p, 551p, 557p, 605p], [552p, 554p, 558p, 603p, 606p, 611p, 617p, 625p], [612p, 614p, 618p, 623p, 626p, 631p, 636p, 642p], [644p, 646p, 650p, 654p, 657p, 702p, 707p, 713p], [737p, 739p, 743p, 747p, 750p, 755p, 800p, 806p], [837p, 839p, 843p, 847p, 850p, 855p, 900p, 906p], [937p, 939p, 943p, 947p, 950p, 955p, 1000p, 1006p], [1037p, 1039p, 1043p, 1047p, 1050p, 1055p, 1100p, 1106p], [1138p, 1140p, 1144p, 1148p, 1151p, 1156p, 1201a, 1207a]]
   
--- ---
time_points: [Lanyon Market Place, Conder Primary, St Clare of Assisi, Bonython Primary School, Tuggeranong Bus Station (Platform 8), Woden Bus Station (Platform 9), City Bus Station (Platform 3), Belconnen Community Bus Station, Westfield Bus Station, Cohen Street Bus Station] time_points: [Lanyon Market Place, Conder Primary, St Clare of Assisi Primary, Bonython Primary School, Tuggeranong Bus Station (Platform 8), Woden Bus Station (Platform 9), City Bus Station (Platform 3), Belconnen Community Bus Station, Westfield Bus Station, Cohen Street Bus Station]
long_name: To Cohen Street Bus Station long_name: To Cohen Street Bus Station
between_stops: between_stops:
Westfield Bus Station-Cohen Street Bus Station: [] Westfield Bus Station-Cohen Street Bus Station: []
City Bus Station (Platform 3)-Belconnen Community Bus Station: [Wjz5F-1, Wjz5FSY, Wjz5GMT, Wjz5GNG, Wjz5G6U, Wjz5G6B, Wjz5maK, Wjz5mbS, Wjz5nwb, Wjz5nw6, Wjz6giR, Wjz6gia, Wjz68W3, Wjz68W5, Wjz689c, Wjz681S] City Bus Station (Platform 3)-Belconnen Community Bus Station: [Wjz5F-1, Wjz5FSY, Wjz5GMT, Wjz5GNG, Wjz5G6U, Wjz5G6B, Wjz5maK, Wjz5mbS, Wjz5nwb, Wjz5nw6, Wjz6giR, Wjz6gia, Wjz68W3, Wjz68W5, Wjz689c, Wjz681S]
Belconnen Community Bus Station-Westfield Bus Station: [] Belconnen Community Bus Station-Westfield Bus Station: []
Tuggeranong Bus Station (Platform 8)-Woden Bus Station (Platform 9): [Wjz213q, Wjz238T, Wjz239F, Wjz2lDC, Wjz2mGO, Wjz2mTK, Wjz2nLE, Wjz3gMq, Wjz3gQn, Wjz3gK-, Wjz3hL_, Wjz3iFK, Wjz3kwU, Wjz3kyX, Wjz3kAx] Tuggeranong Bus Station (Platform 8)-Woden Bus Station (Platform 9): [Wjz213q, Wjz238T, Wjz239F, Wjz2lDC, Wjz2mGO, Wjz2mTK, Wjz2nLE, Wjz3gMq, Wjz3gQn, Wjz3gK-, Wjz3hL_, Wjz3iFK, Wjz3kwU, Wjz3kyX, Wjz3kAx]
Woden Bus Station (Platform 9)-City Bus Station (Platform 3): [Wjz3m3b, Wjz3m3b, Wjz3eRR, Wjz3eZ4, Wjz4KNu, Wjz4KO9, Wjz5Nht] Woden Bus Station (Platform 9)-City Bus Station (Platform 3): [Wjz3m3b, Wjz3m3b, Wjz3eRR, Wjz3eZ4, Wjz4KNu, Wjz4KO9, Wjz5Nht]
short_name: 19 319 short_name: 19 319
stop_times: [[556a, 602a, 608a, 614a, 625a, 643a, 659a, 719a, 721a, 726a], [622a, 628a, 634a, 640a, 651a, 709a, 725a, 746a, 748a, 753a], [646a, 652a, 658a, 704a, 715a, 733a, 751a, 812a, 814a, 819a], [706a, 712a, 718a, 724a, 735a, 754a, 812a, 833a, 835a, 840a], [723a, 729a, 735a, 743a, 755a, 814a, 832a, 853a, 855a, 900a], [735a, 742a, 752a, 800a, 810a, "-", "-", "-", "-", "-"], [742a, 749a, 755a, 803a, 815a, 834a, 852a, 913a, 915a, 920a], [802a, 809a, 815a, 823a, 835a, 854a, 912a, 933a, 935a, 940a], [822a, 829a, 835a, 843a, 855a, 914a, 932a, 952a, 954a, 959a], [853a, 900a, 906a, 914a, 926a, 944a, 1000a, 1020a, 1022a, 1027a], [926a, 933a, 939a, 945a, 956a, 1014a, 1030a, 1050a, 1052a, 1057a], [957a, 1003a, 1009a, 1015a, 1026a, 1044a, 1100a, 1120a, 1122a, 1127a], [1027a, 1033a, 1039a, 1045a, 1056a, 1114a, 1130a, 1150a, 1152a, 1157a], [1057a, 1103a, 1109a, 1115a, 1126a, 1144a, 1200p, 1220p, 1222p, 1227p], [1127a, 1133a, 1139a, 1145a, 1156a, 1214p, 1230p, 1250p, 1252p, 1257p], [1157a, 1203p, 1209p, 1215p, 1226p, 1244p, 100p, 120p, 122p, 127p], [1227p, 1233p, 1239p, 1245p, 1256p, 114p, 130p, 150p, 152p, 157p], [1257p, 103p, 109p, 115p, 126p, 144p, 200p, 220p, 222p, 227p], [127p, 133p, 139p, 145p, 156p, 214p, 230p, 250p, 252p, 257p], [157p, 203p, 209p, 215p, 226p, 244p, 300p, 321p, 323p, 328p], [226p, 232p, 238p, 244p, 255p, 314p, 332p, 353p, 355p, 400p], [253p, 259p, 305p, 313p, 325p, 344p, 402p, 423p, 425p, 430p], [320p, 327p, 337p, 345p, 355p, "-", "-", "-", "-", "-"], [352p, 359p, 409p, 417p, 427p, "-", "-", "-", "-", "-"], [424p, 431p, 441p, 449p, 459p, "-", "-", "-", "-", "-"], [454p, 501p, 511p, 519p, 529p, "-", "-", "-", "-", "-"], [524p, 531p, 541p, 549p, 559p, "-", "-", "-", "-", "-"], [556p, 603p, 613p, 621p, 631p, "-", "-", "-", "-", "-"], [654p, 700p, 710p, 716p, 725p, "-", "-", "-", "-", "-"], [754p, 800p, 810p, 816p, 825p, "-", "-", "-", "-", "-"], [849p, 855p, 905p, 911p, 920p, "-", "-", "-", "-", "-"], [949p, 955p, 1005p, 1011p, 1020p, "-", "-", "-", "-", "-"], [1049p, 1055p, 1105p, 1111p, 1120p, "-", "-", "-", "-", "-"], []] stop_times: [[556a, 602a, 608a, 614a, 625a, 643a, 659a, 719a, 721a, 726a], [622a, 628a, 634a, 640a, 651a, 709a, 725a, 746a, 748a, 753a], [646a, 652a, 658a, 704a, 715a, 733a, 751a, 812a, 814a, 819a], [706a, 712a, 718a, 724a, 735a, 754a, 812a, 833a, 835a, 840a], [723a, 729a, 735a, 743a, 755a, 814a, 832a, 853a, 855a, 900a], [735a, 742a, 752a, 800a, 810a, "-", "-", "-", "-", "-"], [742a, 749a, 755a, 803a, 815a, 834a, 852a, 913a, 915a, 920a], [802a, 809a, 815a, 823a, 835a, 854a, 912a, 933a, 935a, 940a], [822a, 829a, 835a, 843a, 855a, 914a, 932a, 952a, 954a, 959a], [853a, 900a, 906a, 914a, 926a, 944a, 1000a, 1020a, 1022a, 1027a], [926a, 933a, 939a, 945a, 956a, 1014a, 1030a, 1050a, 1052a, 1057a], [957a, 1003a, 1009a, 1015a, 1026a, 1044a, 1100a, 1120a, 1122a, 1127a], [1027a, 1033a, 1039a, 1045a, 1056a, 1114a, 1130a, 1150a, 1152a, 1157a], [1057a, 1103a, 1109a, 1115a, 1126a, 1144a, 1200p, 1220p, 1222p, 1227p], [1127a, 1133a, 1139a, 1145a, 1156a, 1214p, 1230p, 1250p, 1252p, 1257p], [1157a, 1203p, 1209p, 1215p, 1226p, 1244p, 100p, 120p, 122p, 127p], [1227p, 1233p, 1239p, 1245p, 1256p, 114p, 130p, 150p, 152p, 157p], [1257p, 103p, 109p, 115p, 126p, 144p, 200p, 220p, 222p, 227p], [127p, 133p, 139p, 145p, 156p, 214p, 230p, 250p, 252p, 257p], [157p, 203p, 209p, 215p, 226p, 244p, 300p, 321p, 323p, 328p], [226p, 232p, 238p, 244p, 255p, 314p, 332p, 353p, 355p, 400p], [253p, 259p, 305p, 313p, 325p, 344p, 402p, 423p, 425p, 430p], [320p, 327p, 337p, 345p, 355p, "-", "-", "-", "-", "-"], [352p, 359p, 409p, 417p, 427p, "-", "-", "-", "-", "-"], [424p, 431p, 441p, 449p, 459p, "-", "-", "-", "-", "-"], [454p, 501p, 511p, 519p, 529p, "-", "-", "-", "-", "-"], [524p, 531p, 541p, 549p, 559p, "-", "-", "-", "-", "-"], [556p, 603p, 613p, 621p, 631p, "-", "-", "-", "-", "-"], [654p, 700p, 710p, 716p, 725p, "-", "-", "-", "-", "-"], [754p, 800p, 810p, 816p, 825p, "-", "-", "-", "-", "-"], [849p, 855p, 905p, 911p, 920p, "-", "-", "-", "-", "-"], [949p, 955p, 1005p, 1011p, 1020p, "-", "-", "-", "-", "-"], [1049p, 1055p, 1105p, 1111p, 1120p, "-", "-", "-", "-", "-"], []]
   
--- ---
time_points: [Cohen Street Bus Station (Platform 2), Westfield Bus Station (Platform 1), Belconnen Community Bus Station (Platform 1), City Bus Station (Platform 1), Woden Bus Station (Platform 6), Tuggeranong Bus Station (Platform 4), Bonython Primary School, St Clare of Assisi, Conder Primary, Lanyon Market Place] time_points: [Cohen Street Bus Station (Platform 2), Westfield Bus Station (Platform 1), Belconnen Community Bus Station (Platform 1), City Bus Station (Platform 1), Woden Bus Station (Platform 6), Tuggeranong Bus Station (Platform 4), Bonython Primary School, St Clare of Assisi Primary, Conder Primary, Lanyon Market Place]
long_name: To Lanyon Market Place long_name: To Lanyon Market Place
between_stops: between_stops:
Woden Bus Station (Platform 6)-Tuggeranong Bus Station (Platform 4): [Wjz3kAx, Wjz3kyX, Wjz3kwU, Wjz3iFK, Wjz3hL_, Wjz3gK-, Wjz3gQn, Wjz3gMq, Wjz2nLE, Wjz2mTK, Wjz2mGO, Wjz2lDC, Wjz239F, Wjz238T, Wjz213q] Woden Bus Station (Platform 6)-Tuggeranong Bus Station (Platform 4): [Wjz3kAx, Wjz3kyX, Wjz3kwU, Wjz3iFK, Wjz3hL_, Wjz3gK-, Wjz3gQn, Wjz3gMq, Wjz2nLE, Wjz2mTK, Wjz2mGO, Wjz2lDC, Wjz239F, Wjz238T, Wjz213q]
Cohen Street Bus Station (Platform 2)-Westfield Bus Station (Platform 1): [] Cohen Street Bus Station (Platform 2)-Westfield Bus Station (Platform 1): []
Westfield Bus Station (Platform 1)-Belconnen Community Bus Station (Platform 1): [] Westfield Bus Station (Platform 1)-Belconnen Community Bus Station (Platform 1): []
City Bus Station (Platform 1)-Woden Bus Station (Platform 6): [Wjz5Nht, Wjz4KO9, Wjz4KNu, Wjz3eZ4, Wjz3eRR, Wjz3m3b, Wjz3m3b] City Bus Station (Platform 1)-Woden Bus Station (Platform 6): [Wjz5Nht, Wjz4KO9, Wjz4KNu, Wjz3eZ4, Wjz3eRR, Wjz3m3b, Wjz3m3b]
Belconnen Community Bus Station (Platform 1)-City Bus Station (Platform 1): [Wjz681S, Wjz689c, Wjz68W3, Wjz68W5, Wjz6gia, Wjz6giR, Wjz5nw6, Wjz5nwb, Wjz5mbS, Wjz5maK, Wjz5G6B, Wjz5G6U, Wjz5GNG, Wjz5GMT, Wjz5FSY, Wjz5F-1] Belconnen Community Bus Station (Platform 1)-City Bus Station (Platform 1): [Wjz681S, Wjz689c, Wjz68W3, Wjz68W5, Wjz6gia, Wjz6giR, Wjz5nw6, Wjz5nwb, Wjz5mbS, Wjz5maK, Wjz5G6B, Wjz5G6U, Wjz5GNG, Wjz5GMT, Wjz5FSY, Wjz5F-1]
short_name: 19 319 short_name: 19 319
stop_times: [["-", "-", "-", "-", "-", 705a, 711a, 716a, 725a, 731a], ["-", "-", "-", "-", "-", 740a, 747a, 754a, 803a, 810a], [700a, 702a, 706a, 726a, 743a, 801a, 808a, 815a, 824a, 831a], [730a, 732a, 736a, 758a, 815a, 833a, 840a, 847a, 856a, 903a], ["-", "-", "-", "-", "-", 901a, 908a, 915a, 924a, 930a], ["-", "-", "-", "-", "-", 930a, 936a, 941a, 950a, 956a], [900a, 902a, 906a, 928a, 945a, 1001a, 1007a, 1012a, 1021a, 1027a], [930a, 932a, 936a, 956a, 1013a, 1029a, 1035a, 1040a, 1049a, 1055a], [1000a, 1002a, 1006a, 1026a, 1043a, 1059a, 1105a, 1110a, 1119a, 1125a], [1030a, 1032a, 1036a, 1056a, 1113a, 1129a, 1135a, 1140a, 1149a, 1155a], [1100a, 1102a, 1106a, 1126a, 1143a, 1159a, 1205p, 1210p, 1219p, 1225p], [1130a, 1132a, 1136a, 1156a, 1213p, 1229p, 1235p, 1240p, 1249p, 1255p], [1200p, 1202p, 1206p, 1226p, 1243p, 1259p, 105p, 110p, 119p, 125p], [1230p, 1232p, 1236p, 1256p, 113p, 129p, 135p, 140p, 149p, 155p], [100p, 102p, 106p, 126p, 143p, 159p, 205p, 210p, 219p, 225p], [130p, 132p, 136p, 156p, 213p, 229p, 235p, 240p, 249p, 255p], [200p, 202p, 206p, 226p, 243p, 259p, 306p, 313p, 322p, 329p], [230p, 232p, 236p, 256p, 313p, 333p, 340p, 347p, 356p, 403p], ["-", "-", "-", "-", 332p, 352p, 359p, 406p, 415p, 422p], [300p, 302p, 306p, 328p, 345p, 405p, 412p, 419p, 428p, 435p], [330p, 332p, 336p, 358p, 415p, 435p, 442p, 449p, 458p, 505p], [400p, 402p, 406p, 428p, 445p, 505p, 512p, 519p, 528p, 535p], [430p, 432p, 436p, 458p, 515p, 535p, 542p, 549p, 558p, 605p], [450p, 452p, 456p, 518p, 535p, 555p, 602p, 609p, 618p, 625p], [510p, 512p, 516p, 538p, 555p, 615p, 622p, 629p, 638p, 644p], [530p, 532p, 536p, 558p, 615p, 634p, 640p, 645p, 654p, 700p], [600p, 602p, 606p, 628p, 642p, 658p, 704p, 709p, 718p, 724p], [630p, 632p, 636p, 655p, 709p, 725p, 731p, 736p, 745p, 751p], ["-", "-", "-", "-", "-", 818p, 824p, 829p, 838p, 844p], ["-", "-", "-", "-", "-", 918p, 924p, 929p, 938p, 944p], ["-", "-", "-", "-", "-", 1018p, 1024p, 1029p, 1038p, 1044p], ["-", "-", "-", "-", "-", 1118p, 1124p, 1129p, 1138p, 1144p]] stop_times: [["-", "-", "-", "-", "-", 705a, 711a, 716a, 725a, 731a], ["-", "-", "-", "-", "-", 740a, 747a, 754a, 803a, 810a], [700a, 702a, 706a, 726a, 743a, 801a, 808a, 815a, 824a, 831a], [730a, 732a, 736a, 758a, 815a, 833a, 840a, 847a, 856a, 903a], ["-", "-", "-", "-", "-", 901a, 908a, 915a, 924a, 930a], ["-", "-", "-", "-", "-", 930a, 936a, 941a, 950a, 956a], [900a, 902a, 906a, 928a, 945a, 1001a, 1007a, 1012a, 1021a, 1027a], [930a, 932a, 936a, 956a, 1013a, 1029a, 1035a, 1040a, 1049a, 1055a], [1000a, 1002a, 1006a, 1026a, 1043a, 1059a, 1105a, 1110a, 1119a, 1125a], [1030a, 1032a, 1036a, 1056a, 1113a, 1129a, 1135a, 1140a, 1149a, 1155a], [1100a, 1102a, 1106a, 1126a, 1143a, 1159a, 1205p, 1210p, 1219p, 1225p], [1130a, 1132a, 1136a, 1156a, 1213p, 1229p, 1235p, 1240p, 1249p, 1255p], [1200p, 1202p, 1206p, 1226p, 1243p, 1259p, 105p, 110p, 119p, 125p], [1230p, 1232p, 1236p, 1256p, 113p, 129p, 135p, 140p, 149p, 155p], [100p, 102p, 106p, 126p, 143p, 159p, 205p, 210p, 219p, 225p], [130p, 132p, 136p, 156p, 213p, 229p, 235p, 240p, 249p, 255p], [200p, 202p, 206p, 226p, 243p, 259p, 306p, 313p, 322p, 329p], [230p, 232p, 236p, 256p, 313p, 333p, 340p, 347p, 356p, 403p], ["-", "-", "-", "-", 332p, 352p, 359p, 406p, 415p, 422p], [300p, 302p, 306p, 328p, 345p, 405p, 412p, 419p, 428p, 435p], [330p, 332p, 336p, 358p, 415p, 435p, 442p, 449p, 458p, 505p], [400p, 402p, 406p, 428p, 445p, 505p, 512p, 519p, 528p, 535p], [430p, 432p, 436p, 458p, 515p, 535p, 542p, 549p, 558p, 605p], [450p, 452p, 456p, 518p, 535p, 555p, 602p, 609p, 618p, 625p], [510p, 512p, 516p, 538p, 555p, 615p, 622p, 629p, 638p, 644p], [530p, 532p, 536p, 558p, 615p, 634p, 640p, 645p, 654p, 700p], [600p, 602p, 606p, 628p, 642p, 658p, 704p, 709p, 718p, 724p], [630p, 632p, 636p, 655p, 709p, 725p, 731p, 736p, 745p, 751p], ["-", "-", "-", "-", "-", 818p, 824p, 829p, 838p, 844p], ["-", "-", "-", "-", "-", 918p, 924p, 929p, 938p, 944p], ["-", "-", "-", "-", "-", 1018p, 1024p, 1029p, 1038p, 1044p], ["-", "-", "-", "-", "-", 1118p, 1124p, 1129p, 1138p, 1144p]]
   
---  
time_points: [Woden Bus Station (Platform 4), Curtin Shops, John James Hospital, Yarralumla Shops, Deakin, Parliament House, Kings Ave / National Circuit, City Bus Station (Platform 5), Olims Hotel, Ainslie Shops, Hackett Shops, Dickson Shops]  
long_name: To Dickson Shops  
between_stops:  
City Bus Station (Platform 5)-Olims Hotel: [Wjz5NAQ, Wjz5NHD, Wjz5NRJ, Wjz5V64]  
Kings Ave / National Circuit-City Bus Station (Platform 5): [Wjz4Quk, Wjz4RwH, Wjz4RFJ, Wjz4Rs-, Wjz4S1U, Wjz5FOn]  
Olims Hotel-Ainslie Shops: [Wjz5W8l, Wjz5W3H, Wjz5XwW, Wjz5XrS, Wjz5XnQ, Wjz5Yq4, Wjz5YAK]  
short_name: "2"  
stop_times: [["-", "-", "-", "-", "-", "-", "-", 703a, 712a, 717a, 725a, 733a], [653a, 704a, 708a, 711a, 715a, 719a, 723a, 733a, 742a, 748a, 756a, 805a], [708a, 719a, 723a, 726a, 730a, 734a, 738a, 749a, 758a, 804a, 812a, 821a], [719a, 730a, 734a, 737a, 741a, 745a, 749a, 800a, 809a, 815a, 823a, 833a], [738a, 749a, 754a, 758a, 803a, 808a, 814a, 830a, 838a, 845a, 853a, 859a], [753a, 804a, 808a, 812a, 817a, 823a, 826a, 843a, 849a, 854a, 902a, 910a], [808a, 819a, 823a, 826a, 830a, 834a, 838a, 849a, 858a, 904a, 912a, 921a], [823a, 834a, 838a, 841a, 845a, 849a, 853a, 904a, 913a, 919a, 927a, 935a], [838a, 849a, 853a, 856a, 900a, 904a, 908a, 918a, "-", "-", "-", "-"], [851a, 902a, 906a, 909a, 913a, 917a, 921a, 932a, 941a, 946a, 954a, 1001a], [921a, 932a, 936a, 939a, 943a, 947a, 951a, 1002a, 1011a, 1016a, 1024a, 1031a], [951a, 1002a, 1006a, 1009a, 1013a, 1017a, 1021a, 1032a, 1041a, 1046a, 1054a, 1101a], [1021a, 1032a, 1036a, 1039a, 1043a, 1047a, 1051a, 1102a, 1111a, 1116a, 1124a, 1131a], [1051a, 1102a, 1106a, 1109a, 1113a, 1117a, 1121a, 1132a, 1141a, 1146a, 1154a, 1201p], [1121a, 1132a, 1136a, 1139a, 1143a, 1147a, 1151a, 1202p, 1211p, 1216p, 1224p, 1231p], [1151a, 1202p, 1206p, 1209p, 1213p, 1217p, 1221p, 1232p, 1241p, 1246p, 1254p, 101p], [1221p, 1232p, 1236p, 1239p, 1243p, 1247p, 1251p, 102p, 111p, 116p, 124p, 131p], [1251p, 102p, 106p, 109p, 113p, 117p, 121p, 132p, 141p, 146p, 154p, 201p], [121p, 132p, 136p, 139p, 143p, 147p, 151p, 202p, 211p, 216p, 224p, 231p], [151p, 202p, 206p, 209p, 213p, 217p, 221p, 232p, 241p, 246p, 254p, 301p], [216p, 227p, 231p, 234p, 238p, 242p, 246p, 257p, 306p, 312p, 320p, 328p], [238p, 249p, 253p, 256p, 300p, 304p, 308p, 319p, 328p, 334p, 342p, 351p], [253p, 304p, 308p, 311p, 315p, 319p, 323p, 334p, 343p, 349p, 357p, 406p], [308p, 318p, 322p, 325p, 329p, 333p, 337p, 348p, 357p, 403p, 411p, 420p], [323p, 333p, 337p, 340p, 344p, 348p, 352p, 403p, 412p, 418p, 426p, 435p], [338p, 348p, 352p, 355p, 359p, 403p, 407p, 418p, 427p, 433p, 441p, 450p], [353p, 403p, 407p, 410p, 414p, 418p, 422p, 433p, 442p, 448p, 456p, 505p], [408p, 418p, 422p, 425p, 429p, 433p, 437p, 448p, 457p, 503p, 511p, 520p], [423p, 433p, 437p, 440p, 444p, 448p, 452p, 503p, 512p, 518p, 526p, 535p], [438p, 448p, 452p, 455p, 459p, 503p, 507p, 518p, 527p, 533p, 541p, 550p], [453p, 503p, 507p, 510p, 514p, 518p, 522p, 533p, 542p, 548p, 556p, 605p], [508p, 518p, 522p, 525p, 529p, 533p, 537p, 548p, 557p, 603p, 611p, 620p], [523p, 533p, 537p, 540p, 544p, 548p, 552p, 603p, 612p, 618p, 626p, 633p], [538p, 548p, 552p, 555p, 559p, 603p, 607p, 618p, 627p, 633p, 639p, 645p], [553p, 603p, 607p, 610p, 614p, 618p, 622p, 633p, 640p, 645p, 651p, 657p], [640p, 650p, 653p, 656p, 700p, 703p, 707p, 717p, 724p, 729p, 735p, 741p], [740p, 750p, 753p, 756p, 800p, 803p, 807p, 817p, 824p, 829p, 835p, 841p], [840p, 850p, 853p, 856p, 900p, 903p, 907p, 917p, 924p, 929p, 935p, 941p], [940p, 950p, 953p, 956p, 1000p, 1003p, 1007p, 1017p, 1024p, 1029p, 1035p, 1041p], [1040p, 1050p, 1053p, 1056p, 1100p, 1103p, 1107p, 1117p, 1124p, 1129p, 1135p, 1141p]]  
 
  ---
  time_points: [Woden Bus Station (Platform 4), Curtin, John James Hospital, Yarralumla, Deakin, Parliament House, Kings Ave / National Circuit, City Bus Station (Platform 5), Olims Hotel, Ainslie, Hackett, Dickson / Antill St]
  long_name: To Dickson
  between_stops:
  Ainslie-Hackett: [Wjz5YKO, Wjz5ZO1, Wjz5ZZQ, Wjzd68O, Wjzd6iW, Wjzd6lW, Wjzd6Cq, Wjzd6Pn, Wjzd6XP, WjzdeeQ]
  Olims Hotel-Ainslie: [Wjz5W8l, Wjz5W3H, Wjz5XwW, Wjz5XrS, Wjz5XnQ, Wjz5Yq4, Wjz5YAK]
  City Bus Station (Platform 5)-Olims Hotel: [Wjz5NAQ, Wjz5NHD, Wjz5NRJ, Wjz5V64]
  Parliament House-Kings Ave / National Circuit: [Wjz4INj, Wjz4P6x]
  Kings Ave / National Circuit-City Bus Station (Platform 5): [Wjz4Quk, Wjz4RwH, Wjz4RFJ, Wjz4Rs-, Wjz4S1U, Wjz5FOn]
  short_name: "2"
  stop_times: [["-", "-", "-", "-", "-", "-", "-", 703a, 712a, 717a, 725a, 733a], [653a, 704a, 708a, 711a, 715a, 719a, 723a, 733a, 742a, 748a, 756a, 805a], [708a, 719a, 723a, 726a, 730a, 734a, 738a, 749a, 758a, 804a, 812a, 821a], [719a, 730a, 734a, 737a, 741a, 745a, 749a, 800a, 809a, 815a, 823a, 833a], [738a, 749a, 754a, 758a, 803a, 808a, 814a, 830a, 838a, 845a, 853a, 859a], [753a, 804a, 808a, 812a, 817a, 823a, 826a, 843a, 849a, 854a, 902a, 910a], [808a, 819a, 823a, 826a, 830a, 834a, 838a, 849a, 858a, 904a, 912a, 921a], [823a, 834a, 838a, 841a, 845a, 849a, 853a, 904a, 913a, 919a, 927a, 935a], [838a, 849a, 853a, 856a, 900a, 904a, 908a, 918a, "-", "-", "-", "-"], [851a, 902a, 906a, 909a, 913a, 917a, 921a, 932a, 941a, 946a, 954a, 1001a], [921a, 932a, 936a, 939a, 943a, 947a, 951a, 1002a, 1011a, 1016a, 1024a, 1031a], [951a, 1002a, 1006a, 1009a, 1013a, 1017a, 1021a, 1032a, 1041a, 1046a, 1054a, 1101a], [1021a, 1032a, 1036a, 1039a, 1043a, 1047a, 1051a, 1102a, 1111a, 1116a, 1124a, 1131a], [1051a, 1102a, 1106a, 1109a, 1113a, 1117a, 1121a, 1132a, 1141a, 1146a, 1154a, 1201p], [1121a, 1132a, 1136a, 1139a, 1143a, 1147a, 1151a, 1202p, 1211p, 1216p, 1224p, 1231p], [1151a, 1202p, 1206p, 1209p, 1213p, 1217p, 1221p, 1232p, 1241p, 1246p, 1254p, 101p], [1221p, 1232p, 1236p, 1239p, 1243p, 1247p, 1251p, 102p, 111p, 116p, 124p, 131p], [1251p, 102p, 106p, 109p, 113p, 117p, 121p, 132p, 141p, 146p, 154p, 201p], [121p, 132p, 136p, 139p, 143p, 147p, 151p, 202p, 211p, 216p, 224p, 231p], [151p, 202p, 206p, 209p, 213p, 217p, 221p, 232p, 241p, 246p, 254p, 301p], [216p, 227p, 231p, 234p, 238p, 242p, 246p, 257p, 306p, 312p, 320p, 328p], [238p, 249p, 253p, 256p, 300p, 304p, 308p, 319p, 328p, 334p, 342p, 351p], [253p, 304p, 308p, 311p, 315p, 319p, 323p, 334p, 343p, 349p, 357p, 406p], [308p, 318p, 322p, 325p, 329p, 333p, 337p, 348p, 357p, 403p, 411p, 420p], [323p, 333p, 337p, 340p, 344p, 348p, 352p, 403p, 412p, 418p, 426p, 435p], [338p, 348p, 352p, 355p, 359p, 403p, 407p, 418p, 427p, 433p, 441p, 450p], [353p, 403p, 407p, 410p, 414p, 418p, 422p, 433p, 442p, 448p, 456p, 505p], [408p, 418p, 422p, 425p, 429p, 433p, 437p, 448p, 457p, 503p, 511p, 520p], [423p, 433p, 437p, 440p, 444p, 448p, 452p, 503p, 512p, 518p, 526p, 535p], [438p, 448p, 452p, 455p, 459p, 503p, 507p, 518p, 527p, 533p, 541p, 550p], [453p, 503p, 507p, 510p, 514p, 518p, 522p, 533p, 542p, 548p, 556p, 605p], [508p, 518p, 522p, 525p, 529p, 533p, 537p, 548p, 557p, 603p, 611p, 620p], [523p, 533p, 537p, 540p, 544p, 548p, 552p, 603p, 612p, 618p, 626p, 633p], [538p, 548p, 552p, 555p, 559p, 603p, 607p, 618p, 627p, 633p, 639p, 645p], [553p, 603p, 607p, 610p, 614p, 618p, 622p, 633p, 640p, 645p, 651p, 657p], [640p, 650p, 653p, 656p, 700p, 703p, 707p, 717p, 724p, 729p, 735p, 741p], [740p, 750p, 753p, 756p, 800p, 803p, 807p, 817p, 824p, 829p, 835p, 841p], [840p, 850p, 853p, 856p, 900p, 903p, 907p, 917p, 924p, 929p, 935p, 941p], [940p, 950p, 953p, 956p, 1000p, 1003p, 1007p, 1017p, 1024p, 1029p, 1035p, 1041p], [1040p, 1050p, 1053p, 1056p, 1100p, 1103p, 1107p, 1117p, 1124p, 1129p, 1135p, 1141p]]
 
--- ---
time_points: [Dickson Shops, Hackett Shops, Ainslie Shops, Olims Hotel, City Bus Station (Platform 2), Kings Ave / National Circuit, Parliament House, Deakin, Yarralumla Shops, John James Hospital, Curtin Shops, Woden Bus Station] time_points: [Dickson / Antill St, Hackett, Ainslie, Olims Hotel, City Bus Station (Platform 2), Kings Ave / National Circuit, Parliament House, Deakin, Yarralumla, John James Hospital, Curtin, Woden Bus Station]
long_name: To Woden Bus Station long_name: To Woden Bus Station
between_stops: between_stops:
  Ainslie-Olims Hotel: [Wjz5YAK, Wjz5Yq4, Wjz5XnQ, Wjz5XrS, Wjz5XwW, Wjz5W3H, Wjz5W8l]
Olims Hotel-City Bus Station (Platform 2): [Wjz5V64, Wjz5NRJ, Wjz5NHD, Wjz5NAQ] Olims Hotel-City Bus Station (Platform 2): [Wjz5V64, Wjz5NRJ, Wjz5NHD, Wjz5NAQ]
  Kings Ave / National Circuit-Parliament House: [Wjz4P6x, Wjz4IrL]
City Bus Station (Platform 2)-Kings Ave / National Circuit: [Wjz5FOn, Wjz4S1U, Wjz4Rs-, Wjz4RFJ, Wjz4RwH, Wjz4Quk] City Bus Station (Platform 2)-Kings Ave / National Circuit: [Wjz5FOn, Wjz4S1U, Wjz4Rs-, Wjz4RFJ, Wjz4RwH, Wjz4Quk]
Ainslie Shops-Olims Hotel: [Wjz5YAK, Wjz5Yq4, Wjz5XnQ, Wjz5XrS, Wjz5XwW, Wjz5W3H, Wjz5W8l] Hackett-Ainslie: [WjzdeeQ, Wjzd6XP, Wjzd6Pn, Wjzd6Cq, Wjzd6lW, Wjzd6iW, Wjzd68O, Wjz5ZZQ, Wjz5ZO1, Wjz5YKO]
short_name: "2" short_name: "2"
stop_times: [[634a, 639a, 647a, 653a, 700a, 709a, 713a, 718a, 722a, 725a, 729a, 741a], [701a, 706a, 714a, 720a, 727a, 737a, 742a, 747a, 751a, 754a, 758a, 810a], [710a, 715a, 723a, 729a, 736a, 746a, 751a, 756a, 800a, 803a, 807a, 819a], [724a, 729a, 743a, 749a, 756a, 806a, 811a, 816a, 820a, 823a, 827a, 839a], [739a, 748a, 803a, 809a, 816a, 826a, 831a, 836a, 840a, 843a, 847a, 859a], [758a, 807a, 822a, 828a, 835a, 845a, 850a, 855a, 859a, 902a, 906a, 918a], [809a, 818a, 833a, 839a, 846a, 856a, 901a, 906a, 910a, 913a, 917a, 929a], [822a, 831a, 846a, 852a, 859a, 909a, 914a, 919a, 923a, 926a, 930a, 942a], [839a, 848a, 903a, 909a, 916a, 926a, 931a, 936a, 940a, 943a, 947a, 959a], [856a, 905a, 920a, 926a, 933a, 943a, 948a, 953a, 957a, 1000a, 1004a, 1016a], [936a, 941a, 949a, 955a, 1002a, 1012a, 1017a, 1022a, 1026a, 1029a, 1033a, 1045a], [1006a, 1011a, 1019a, 1025a, 1032a, 1042a, 1047a, 1052a, 1056a, 1059a, 1103a, 1115a], [1036a, 1041a, 1049a, 1055a, 1102a, 1112a, 1117a, 1122a, 1126a, 1129a, 1133a, 1145a], [1106a, 1111a, 1119a, 1125a, 1132a, 1142a, 1147a, 1152a, 1156a, 1159a, 1203p, 1215p], [1136a, 1141a, 1149a, 1155a, 1202p, 1212p, 1217p, 1222p, 1226p, 1229p, 1233p, 1245p], [1206p, 1211p, 1219p, 1225p, 1232p, 1242p, 1247p, 1252p, 1256p, 1259p, 103p, 115p], [1236p, 1241p, 1249p, 1255p, 102p, 112p, 117p, 122p, 126p, 129p, 133p, 145p], [106p, 111p, 119p, 125p, 132p, 142p, 147p, 152p, 156p, 159p, 203p, 215p], [136p, 141p, 149p, 155p, 202p, 212p, 217p, 222p, 226p, 229p, 233p, 245p], [206p, 211p, 219p, 225p, 232p, 242p, 247p, 252p, 256p, 259p, 303p, 315p], [236p, 241p, 249p, 255p, 302p, 313p, 318p, 323p, 327p, 330p, 334p, 346p], [249p, 254p, 302p, 308p, 315p, 326p, 331p, 336p, 340p, 343p, 347p, 359p], [306p, 311p, 319p, 325p, 334p, 345p, 350p, 355p, 359p, 402p, 406p, 418p], [312p, 317p, 325p, 331p, 339p, "-", "-", "-", "-", "-", "-", "-"], [319p, 326p, 334p, 340p, 347p, 358p, 403p, 408p, 412p, 415p, 419p, 431p], [332p, 339p, 347p, 353p, 400p, 411p, 416p, 421p, 425p, 428p, 432p, 444p], [349p, 356p, 404p, 410p, 417p, 428p, 433p, 438p, 442p, 445p, 449p, 501p], [402p, 409p, 417p, 423p, 430p, 441p, 446p, 451p, 455p, 458p, 502p, 514p], [419p, 426p, 434p, 440p, 447p, 458p, 503p, 508p, 512p, 515p, 519p, 531p], [432p, 439p, 447p, 453p, 500p, 511p, 516p, 521p, 525p, 528p, 532p, 544p], [449p, 456p, 504p, 510p, 517p, 528p, 533p, 538p, 542p, 545p, 549p, 601p], [502p, 509p, 517p, 523p, 530p, 541p, 546p, 551p, 555p, 558p, 602p, 614p], [519p, 526p, 534p, 540p, 547p, 558p, 603p, 608p, 612p, 615p, 619p, 631p], [532p, 539p, 547p, 553p, 600p, 611p, 616p, 621p, 625p, 628p, 632p, 643p], [549p, 556p, 604p, 610p, 617p, 628p, 633p, 637p, 641p, 644p, 648p, 659p], [603p, 610p, 618p, 624p, 631p, 640p, 645p, 649p, 653p, 656p, 700p, 711p], [626p, 632p, 638p, 643p, 649p, 658p, 703p, 707p, 711p, 714p, 718p, 729p], [726p, 731p, 737p, 742p, 748p, 757p, 802p, 806p, 810p, 813p, 817p, 828p], [826p, 831p, 837p, 842p, 848p, 857p, 902p, 906p, 910p, 913p, 917p, 928p], [926p, 931p, 937p, 942p, 948p, 957p, 1002p, 1006p, 1010p, 1013p, 1017p, 1028p], [1026p, 1031p, 1037p, 1042p, 1048p, 1057p, 1102p, 1106p, 1110p, 1113p, 1117p, 1128p], [1126p, 1131p, 1137p, 1142p, 1147p, "-", "-", "-", "-", "-", "-", "-"], [], [], []] stop_times: [[634a, 639a, 647a, 653a, 700a, 709a, 713a, 718a, 722a, 725a, 729a, 741a], [701a, 706a, 714a, 720a, 727a, 737a, 742a, 747a, 751a, 754a, 758a, 810a], [710a, 715a, 723a, 729a, 736a, 746a, 751a, 756a, 800a, 803a, 807a, 819a], [724a, 729a, 743a, 749a, 756a, 806a, 811a, 816a, 820a, 823a, 827a, 839a], [739a, 748a, 803a, 809a, 816a, 826a, 831a, 836a, 840a, 843a, 847a, 859a], [758a, 807a, 822a, 828a, 835a, 845a, 850a, 855a, 859a, 902a, 906a, 918a], [809a, 818a, 833a, 839a, 846a, 856a, 901a, 906a, 910a, 913a, 917a, 929a], [822a, 831a, 846a, 852a, 859a, 909a, 914a, 919a, 923a, 926a, 930a, 942a], [839a, 848a, 903a, 909a, 916a, 926a, 931a, 936a, 940a, 943a, 947a, 959a], [856a, 905a, 920a, 926a, 933a, 943a, 948a, 953a, 957a, 1000a, 1004a, 1016a], [936a, 941a, 949a, 955a, 1002a, 1012a, 1017a, 1022a, 1026a, 1029a, 1033a, 1045a], [1006a, 1011a, 1019a, 1025a, 1032a, 1042a, 1047a, 1052a, 1056a, 1059a, 1103a, 1115a], [1036a, 1041a, 1049a, 1055a, 1102a, 1112a, 1117a, 1122a, 1126a, 1129a, 1133a, 1145a], [1106a, 1111a, 1119a, 1125a, 1132a, 1142a, 1147a, 1152a, 1156a, 1159a, 1203p, 1215p], [1136a, 1141a, 1149a, 1155a, 1202p, 1212p, 1217p, 1222p, 1226p, 1229p, 1233p, 1245p], [1206p, 1211p, 1219p, 1225p, 1232p, 1242p, 1247p, 1252p, 1256p, 1259p, 103p, 115p], [1236p, 1241p, 1249p, 1255p, 102p, 112p, 117p, 122p, 126p, 129p, 133p, 145p], [106p, 111p, 119p, 125p, 132p, 142p, 147p, 152p, 156p, 159p, 203p, 215p], [136p, 141p, 149p, 155p, 202p, 212p, 217p, 222p, 226p, 229p, 233p, 245p], [206p, 211p, 219p, 225p, 232p, 242p, 247p, 252p, 256p, 259p, 303p, 315p], [236p, 241p, 249p, 255p, 302p, 313p, 318p, 323p, 327p, 330p, 334p, 346p], [249p, 254p, 302p, 308p, 315p, 326p, 331p, 336p, 340p, 343p, 347p, 359p], [306p, 311p, 319p, 325p, 334p, 345p, 350p, 355p, 359p, 402p, 406p, 418p], [312p, 317p, 325p, 331p, 339p, "-", "-", "-", "-", "-", "-", "-"], [319p, 326p, 334p, 340p, 347p, 358p, 403p, 408p, 412p, 415p, 419p, 431p], [332p, 339p, 347p, 353p, 400p, 411p, 416p, 421p, 425p, 428p, 432p, 444p], [349p, 356p, 404p, 410p, 417p, 428p, 433p, 438p, 442p, 445p, 449p, 501p], [402p, 409p, 417p, 423p, 430p, 441p, 446p, 451p, 455p, 458p, 502p, 514p], [419p, 426p, 434p, 440p, 447p, 458p, 503p, 508p, 512p, 515p, 519p, 531p], [432p, 439p, 447p, 453p, 500p, 511p, 516p, 521p, 525p, 528p, 532p, 544p], [449p, 456p, 504p, 510p, 517p, 528p, 533p, 538p, 542p, 545p, 549p, 601p], [502p, 509p, 517p, 523p, 530p, 541p, 546p, 551p, 555p, 558p, 602p, 614p], [519p, 526p, 534p, 540p, 547p, 558p, 603p, 608p, 612p, 615p, 619p, 631p], [532p, 539p, 547p, 553p, 600p, 611p, 616p, 621p, 625p, 628p, 632p, 643p], [549p, 556p, 604p, 610p, 617p, 628p, 633p, 637p, 641p, 644p, 648p, 659p], [603p, 610p, 618p, 624p, 631p, 640p, 645p, 649p, 653p, 656p, 700p, 711p], [626p, 632p, 638p, 643p, 649p, 658p, 703p, 707p, 711p, 714p, 718p, 729p], [726p, 731p, 737p, 742p, 748p, 757p, 802p, 806p, 810p, 813p, 817p, 828p], [826p, 831p, 837p, 842p, 848p, 857p, 902p, 906p, 910p, 913p, 917p, 928p], [926p, 931p, 937p, 942p, 948p, 957p, 1002p, 1006p, 1010p, 1013p, 1017p, 1028p], [1026p, 1031p, 1037p, 1042p, 1048p, 1057p, 1102p, 1106p, 1110p, 1113p, 1117p, 1128p], [1126p, 1131p, 1137p, 1142p, 1147p, "-", "-", "-", "-", "-", "-", "-"], [], [], []]
   
--- ---
time_points: [Woden Bus Station (Platform 15), Pearce, Torrens Shops, Southlands Mawson, Woden Bus Station] time_points: [Woden Bus Station (Platform 15), Pearce, Torrens, Southlands Mawson, Woden Bus Station]
long_name: To Woden Bus Station long_name: To Woden Bus Station
between_stops: {} between_stops: {}
   
short_name: "21" short_name: "21"
stop_times: [[657a, 703a, 706a, 712a, 724a], [727a, 734a, 737a, 744a, 757a], [757a, 804a, 807a, 814a, 827a], [827a, 834a, 837a, 844a, 857a], [904a, 911a, 914a, 921a, 934a], [1004a, 1010a, 1013a, 1019a, 1031a], [1104a, 1110a, 1113a, 1119a, 1131a], [1204p, 1210p, 1213p, 1219p, 1231p], [104p, 110p, 113p, 119p, 131p], [204p, 210p, 213p, 219p, 231p], [304p, 311p, 314p, 321p, 334p], [327p, 334p, 337p, 344p, 357p], [357p, 404p, 407p, 414p, 427p], [427p, 434p, 437p, 444p, 457p], [457p, 504p, 507p, 514p, 527p], [527p, 534p, 537p, 544p, 557p], [557p, 604p, 607p, 614p, 627p], [627p, 633p, 636p, 642p, 654p], [720p, 726p, 729p, 735p, 747p], [820p, 826p, 829p, 835p, 847p], [920p, 926p, 929p, 935p, 947p], [1020p, 1026p, 1029p, 1035p, 1047p], [1120p, 1126p, 1129p, 1135p, "-"]] stop_times: [[657a, 703a, 706a, 712a, 724a], [727a, 734a, 737a, 744a, 757a], [757a, 804a, 807a, 814a, 827a], [827a, 834a, 837a, 844a, 857a], [904a, 911a, 914a, 921a, 934a], [1004a, 1010a, 1013a, 1019a, 1031a], [1104a, 1110a, 1113a, 1119a, 1131a], [1204p, 1210p, 1213p, 1219p, 1231p], [104p, 110p, 113p, 119p, 131p], [204p, 210p, 213p, 219p, 231p], [304p, 311p, 314p, 321p, 334p], [327p, 334p, 337p, 344p, 357p], [357p, 404p, 407p, 414p, 427p], [427p, 434p, 437p, 444p, 457p], [457p, 504p, 507p, 514p, 527p], [527p, 534p, 537p, 544p, 557p], [557p, 604p, 607p, 614p, 627p], [627p, 633p, 636p, 642p, 654p], [720p, 726p, 729p, 735p, 747p], [820p, 826p, 829p, 835p, 847p], [920p, 926p, 929p, 935p, 947p], [1020p, 1026p, 1029p, 1035p, 1047p], [1120p, 1126p, 1129p, 1135p, "-"]]
   
--- ---
time_points: [Woden Bus Station (Platform 15), Southlands Mawson, Torrens Shops, Pearce, Woden Bus Station] time_points: [Woden Bus Station (Platform 15), Southlands Mawson, Torrens, Pearce, Woden Bus Station]
long_name: To Woden Bus Station long_name: To Woden Bus Station
between_stops: {} between_stops: {}
   
short_name: "22" short_name: "22"
stop_times: [[635a, 648a, 656a, 659a, 707a], [705a, 718a, 726a, 729a, 738a], [735a, 749a, 758a, 801a, 810a], [805a, 819a, 828a, 831a, 840a], [843a, 857a, 906a, 909a, 918a], [943a, 956a, 1004a, 1007a, 1015a], [1043a, 1056a, 1104a, 1107a, 1115a], [1143a, 1156a, 1204p, 1207p, 1215p], [1243p, 1256p, 104p, 107p, 115p], [143p, 156p, 204p, 207p, 215p], [243p, 256p, 305p, 308p, 317p], [313p, 327p, 336p, 339p, 348p], [335p, 349p, 358p, 401p, 410p], [405p, 419p, 428p, 431p, 440p], [435p, 449p, 458p, 501p, 510p], [505p, 519p, 528p, 531p, 540p], [535p, 549p, 558p, 601p, 610p], [605p, 619p, 628p, 631p, 639p], [638p, 651p, 659p, 702p, 710p], [738p, 751p, 759p, 802p, 810p], [838p, 851p, 859p, 902p, 910p], [938p, 951p, 959p, 1002p, 1010p], [1038p, 1051p, 1059p, 1102p, 1110p]] stop_times: [[635a, 648a, 656a, 659a, 707a], [705a, 718a, 726a, 729a, 738a], [735a, 749a, 758a, 801a, 810a], [805a, 819a, 828a, 831a, 840a], [843a, 857a, 906a, 909a, 918a], [943a, 956a, 1004a, 1007a, 1015a], [1043a, 1056a, 1104a, 1107a, 1115a], [1143a, 1156a, 1204p, 1207p, 1215p], [1243p, 1256p, 104p, 107p, 115p], [143p, 156p, 204p, 207p, 215p], [243p, 256p, 305p, 308p, 317p], [313p, 327p, 336p, 339p, 348p], [335p, 349p, 358p, 401p, 410p], [405p, 419p, 428p, 431p, 440p], [435p, 449p, 458p, 501p, 510p], [505p, 519p, 528p, 531p, 540p], [535p, 549p, 558p, 601p, 610p], [605p, 619p, 628p, 631p, 639p], [638p, 651p, 659p, 702p, 710p], [738p, 751p, 759p, 802p, 810p], [838p, 851p, 859p, 902p, 910p], [938p, 951p, 959p, 1002p, 1010p], [1038p, 1051p, 1059p, 1102p, 1110p]]
   
--- ---
time_points: [Woden Bus Station (Platform 15), Lyons Shops, Chifley Shops, Southlands Mawson, Farrer Terminus, Isaacs Shops, Canberra Hospital, Woden Bus Station] time_points: [Woden Bus Station (Platform 15), Lyons, Chifley, Southlands Mawson, Farrer Terminus, Isaacs, Canberra Hospital, Woden Bus Station]
long_name: To Woden Bus Station long_name: To Woden Bus Station
between_stops: between_stops:
Canberra Hospital-Woden Bus Station: [Wjz3mWn, Wjz3mPO, Wjz3mAg] Canberra Hospital-Woden Bus Station: [Wjz3mWn, Wjz3mPO, Wjz3mAg]
short_name: "23" short_name: "23"
stop_times: [[607a, 609a, 613a, 622a, 628a, 634a, 642a, 647a], [644a, 646a, 650a, 659a, 705a, 711a, 719a, 724a], [714a, 716a, 720a, 729a, 736a, 742a, 752a, 757a], [744a, 748a, 753a, 801a, 808a, 814a, 824a, 829a], [814a, 818a, 823a, 831a, 838a, 844a, 854a, 859a], [844a, 848a, 853a, 901a, 908a, 914a, 924a, 929a], [926a, 930a, 934a, 943a, 949a, 955a, 1003a, 1008a], [1026a, 1028a, 1032a, 1041a, 1047a, 1053a, 1101a, 1106a], [1126a, 1128a, 1132a, 1141a, 1147a, 1153a, 1201p, 1206p], [1226p, 1228p, 1232p, 1241p, 1247p, 1253p, 101p, 106p], [126p, 128p, 132p, 141p, 147p, 153p, 201p, 206p], [226p, 228p, 232p, 241p, 247p, 253p, 301p, 306p], [314p, 318p, 323p, 331p, 338p, 344p, 354p, 359p], [344p, 348p, 353p, 401p, 408p, 414p, 424p, 429p], [414p, 418p, 423p, 431p, 438p, 444p, 454p, 459p], [444p, 448p, 453p, 501p, 508p, 514p, 524p, 529p], [514p, 518p, 523p, 531p, 538p, 544p, 554p, 559p], [544p, 548p, 553p, 601p, 608p, 614p, 624p, 629p], [626p, 630p, 634p, 643p, 649p, 655p, 703p, 708p], [726p, 728p, 732p, 741p, 747p, 753p, 801p, 806p], [826p, 828p, 832p, 841p, 847p, 853p, 901p, 906p], [926p, 928p, 932p, 941p, 947p, 953p, 1001p, 1006p], [1026p, 1028p, 1032p, 1041p, 1047p, 1053p, 1101p, 1106p], [1126p, 1128p, 1132p, 1141p, "-", "-", "-", "-"]] stop_times: [[607a, 609a, 613a, 622a, 628a, 634a, 642a, 647a], [644a, 646a, 650a, 659a, 705a, 711a, 719a, 724a], [714a, 716a, 720a, 729a, 736a, 742a, 752a, 757a], [744a, 748a, 753a, 801a, 808a, 814a, 824a, 829a], [814a, 818a, 823a, 831a, 838a, 844a, 854a, 859a], [844a, 848a, 853a, 901a, 908a, 914a, 924a, 929a], [926a, 930a, 934a, 943a, 949a, 955a, 1003a, 1008a], [1026a, 1028a, 1032a, 1041a, 1047a, 1053a, 1101a, 1106a], [1126a, 1128a, 1132a, 1141a, 1147a, 1153a, 1201p, 1206p], [1226p, 1228p, 1232p, 1241p, 1247p, 1253p, 101p, 106p], [126p, 128p, 132p, 141p, 147p, 153p, 201p, 206p], [226p, 228p, 232p, 241p, 247p, 253p, 301p, 306p], [314p, 318p, 323p, 331p, 338p, 344p, 354p, 359p], [344p, 348p, 353p, 401p, 408p, 414p, 424p, 429p], [414p, 418p, 423p, 431p, 438p, 444p, 454p, 459p], [444p, 448p, 453p, 501p, 508p, 514p, 524p, 529p], [514p, 518p, 523p, 531p, 538p, 544p, 554p, 559p], [544p, 548p, 553p, 601p, 608p, 614p, 624p, 629p], [626p, 630p, 634p, 643p, 649p, 655p, 703p, 708p], [726p, 728p, 732p, 741p, 747p, 753p, 801p, 806p], [826p, 828p, 832p, 841p, 847p, 853p, 901p, 906p], [926p, 928p, 932p, 941p, 947p, 953p, 1001p, 1006p], [1026p, 1028p, 1032p, 1041p, 1047p, 1053p, 1101p, 1106p], [1126p, 1128p, 1132p, 1141p, "-", "-", "-", "-"]]
   
--- ---
time_points: [Woden Bus Station (Platform 15), Canberra Hospital, Isaacs Shops, Farrer Terminus, Southlands Mawson, Chifley Shops, Lyons Shops, Woden Bus Station] time_points: [Woden Bus Station (Platform 15), Canberra Hospital, Isaacs, Farrer Terminus, Southlands Mawson, Chifley, Lyons, Woden Bus Station]
long_name: To Woden Bus Station long_name: To Woden Bus Station
between_stops: between_stops:
Woden Bus Station (Platform 15)-Canberra Hospital: [Wjz3mAg, Wjz3mPO, Wjz3mWn] Woden Bus Station (Platform 15)-Canberra Hospital: [Wjz3mAg, Wjz3mPO, Wjz3mWn]
short_name: "24" short_name: "24"
stop_times: [["-", "-", "-", 703a, 709a, 715a, 720a, 724a], [702a, 708a, 715a, 720a, 726a, 732a, 737a, 742a], [739a, 746a, 754a, 800a, 806a, 813a, 818a, 823a], [809a, 816a, 824a, 830a, 836a, 843a, 848a, 853a], [839a, 846a, 854a, 900a, 906a, 913a, 918a, 923a], [956a, 1002a, 1009a, 1014a, 1020a, 1026a, 1031a, 1035a], [1056a, 1102a, 1109a, 1114a, 1120a, 1126a, 1131a, 1135a], [1156a, 1202p, 1209p, 1214p, 1220p, 1226p, 1231p, 1235p], [1256p, 102p, 109p, 114p, 120p, 126p, 131p, 135p], [156p, 202p, 209p, 214p, 220p, 226p, 231p, 235p], [256p, 302p, 310p, 316p, 322p, 329p, 334p, 339p], [339p, 346p, 354p, 400p, 406p, 413p, 418p, 423p], [409p, 416p, 424p, 430p, 436p, 443p, 448p, 453p], [439p, 446p, 454p, 500p, 506p, 513p, 518p, 523p], [509p, 516p, 524p, 530p, 536p, 543p, 548p, 553p], [538p, 545p, 553p, 559p, 605p, 612p, 617p, 622p], [608p, 615p, 623p, 629p, 635p, 641p, 646p, 650p], [659p, 705p, 712p, 717p, 723p, 729p, 734p, 738p], [759p, 805p, 812p, 817p, 823p, 829p, 834p, 838p], [859p, 905p, 912p, 917p, 923p, 929p, 934p, 938p], [959p, 1005p, 1012p, 1017p, 1023p, 1029p, 1034p, 1038p], [1059p, 1105p, 1112p, 1117p, 1123p, 1129p, 1134p, 1138p]] stop_times: [["-", "-", "-", 703a, 709a, 715a, 720a, 724a], [702a, 708a, 715a, 720a, 726a, 732a, 737a, 742a], [739a, 746a, 754a, 800a, 806a, 813a, 818a, 823a], [809a, 816a, 824a, 830a, 836a, 843a, 848a, 853a], [839a, 846a, 854a, 900a, 906a, 913a, 918a, 923a], [956a, 1002a, 1009a, 1014a, 1020a, 1026a, 1031a, 1035a], [1056a, 1102a, 1109a, 1114a, 1120a, 1126a, 1131a, 1135a], [1156a, 1202p, 1209p, 1214p, 1220p, 1226p, 1231p, 1235p], [1256p, 102p, 109p, 114p, 120p, 126p, 131p, 135p], [156p, 202p, 209p, 214p, 220p, 226p, 231p, 235p], [256p, 302p, 310p, 316p, 322p, 329p, 334p, 339p], [339p, 346p, 354p, 400p, 406p, 413p, 418p, 423p], [409p, 416p, 424p, 430p, 436p, 443p, 448p, 453p], [439p, 446p, 454p, 500p, 506p, 513p, 518p, 523p], [509p, 516p, 524p, 530p, 536p, 543p, 548p, 553p], [538p, 545p, 553p, 559p, 605p, 612p, 617p, 622p], [608p, 615p, 623p, 629p, 635p, 641p, 646p, 650p], [659p, 705p, 712p, 717p, 723p, 729p, 734p, 738p], [759p, 805p, 812p, 817p, 823p, 829p, 834p, 838p], [859p, 905p, 912p, 917p, 923p, 929p, 934p, 938p], [959p, 1005p, 1012p, 1017p, 1023p, 1029p, 1034p, 1038p], [1059p, 1105p, 1112p, 1117p, 1123p, 1129p, 1134p, 1138p]]
   
--- ---
time_points: [Cooleman Court, Holder Shops, Weston Primary, Woden Bus Station (Platform 10), Kings Ave / National Circuit, Russell Offices, ADFA, Campbell Park Offices] time_points: [Cooleman Court, Holder, Weston Primary, Woden Bus Station (Platform 10), Kings Ave / National Circuit, Russell Offices, ADFA, Campbell Park Offices]
long_name: To Campbell Park Offices long_name: To Campbell Park Offices
between_stops: between_stops:
Kings Ave / National Circuit-Russell Offices: [Wjz4Quk, Wjz4RwH, Wjz4RFJ] Kings Ave / National Circuit-Russell Offices: [Wjz4Quk, Wjz4RwH, Wjz4RFJ]
ADFA-Campbell Park Offices: [Wjzcend, Wjzce4H, Wjzce7O] ADFA-Campbell Park Offices: [Wjzcend, Wjzce4H, Wjzce7O]
Russell Offices-ADFA: [Wjzc60A, Wjzc60i, Wjzc55s, Wjzc54R, Wjzce7O, Wjzce4H, Wjzcend] Russell Offices-ADFA: [Wjzc60A, Wjzc60i, Wjzc55s, Wjzc54R, Wjzce7O, Wjzce4H, Wjzcend]
short_name: 25 225 short_name: 25 225
stop_times: [[612a, 622a, 625a, 634a, "-", "-", "-", "-"], [642a, 652a, 655a, 705a, 719a, 722a, 726a, 730a], [702a, 712a, 715a, 725a, 739a, 743a, 747a, 751a], [734a, 749a, 752a, 805a, 819a, 823a, 827a, 831a], [808a, 823a, 826a, 838a, "-", "-", "-", "-"], [838a, 853a, 856a, 908a, "-", "-", "-", "-"], [910a, 925a, 928a, 938a, "-", "-", "-", "-"], [1012a, 1022a, 1025a, 1035a, "-", "-", "-", "-"], [1112a, 1122a, 1125a, 1135a, "-", "-", "-", "-"], [1212p, 1222p, 1225p, 1235p, "-", "-", "-", "-"], [112p, 122p, 125p, 135p, "-", "-", "-", "-"], [212p, 222p, 225p, 235p, "-", "-", "-", "-"], [312p, 324p, 327p, 336p, "-", "-", "-", "-"], [342p, 354p, 357p, 406p, "-", "-", "-", "-"], [412p, 424p, 427p, 436p, "-", "-", "-", "-"], [512p, 524p, 527p, 536p, "-", "-", "-", "-"], [622p, 633p, 636p, 645p, "-", "-", "-", "-"], [722p, 732p, 735p, 744p, "-", "-", "-", "-"], [822p, 832p, 835p, 844p, "-", "-", "-", "-"], [922p, 932p, 935p, 944p, "-", "-", "-", "-"], [1022p, 1032p, 1035p, 1044p, "-", "-", "-", "-"]] stop_times: [[612a, 622a, 625a, 634a, "-", "-", "-", "-"], [642a, 652a, 655a, 705a, 719a, 722a, 726a, 730a], [702a, 712a, 715a, 725a, 739a, 743a, 747a, 751a], [734a, 749a, 752a, 805a, 819a, 823a, 827a, 831a], [808a, 823a, 826a, 838a, "-", "-", "-", "-"], [838a, 853a, 856a, 908a, "-", "-", "-", "-"], [910a, 925a, 928a, 938a, "-", "-", "-", "-"], [1012a, 1022a, 1025a, 1035a, "-", "-", "-", "-"], [1112a, 1122a, 1125a, 1135a, "-", "-", "-", "-"], [1212p, 1222p, 1225p, 1235p, "-", "-", "-", "-"], [112p, 122p, 125p, 135p, "-", "-", "-", "-"], [212p, 222p, 225p, 235p, "-", "-", "-", "-"], [312p, 324p, 327p, 336p, "-", "-", "-", "-"], [342p, 354p, 357p, 406p, "-", "-", "-", "-"], [412p, 424p, 427p, 436p, "-", "-", "-", "-"], [512p, 524p, 527p, 536p, "-", "-", "-", "-"], [622p, 633p, 636p, 645p, "-", "-", "-", "-"], [722p, 732p, 735p, 744p, "-", "-", "-", "-"], [822p, 832p, 835p, 844p, "-", "-", "-", "-"], [922p, 932p, 935p, 944p, "-", "-", "-", "-"], [1022p, 1032p, 1035p, 1044p, "-", "-", "-", "-"]]
   
--- ---
time_points: [Campbell Park Offices, ADFA, Russell Offices, Kings Ave / National Circuit, Woden Bus Station (Platform 16), Weston Primary, Holder Shops, Cooleman Court] time_points: [Campbell Park Offices, ADFA, Russell Offices, Kings Ave / National Circuit, Woden Bus Station (Platform 16), Weston Primary, Holder, Cooleman Court]
long_name: To Cooleman Court long_name: To Cooleman Court
between_stops: between_stops:
Russell Offices-Kings Ave / National Circuit: [Wjz4RFJ, Wjz4RwH, Wjz4Quk] Russell Offices-Kings Ave / National Circuit: [Wjz4RFJ, Wjz4RwH, Wjz4Quk]
ADFA-Russell Offices: [Wjzcend, Wjzce4H, Wjzce7O, Wjzc54R, Wjzc55s, Wjzc60i, Wjzc60A] ADFA-Russell Offices: [Wjzcend, Wjzce4H, Wjzce7O, Wjzc54R, Wjzc55s, Wjzc60i, Wjzc60A]
Campbell Park Offices-ADFA: [Wjzce7O, Wjzce4H, Wjzcend] Campbell Park Offices-ADFA: [Wjzce7O, Wjzce4H, Wjzcend]
short_name: 25 225 short_name: 25 225
stop_times: [["-", "-", "-", "-", 712a, 720a, 723a, 734a], ["-", "-", "-", "-", 807a, 819a, 823a, 835a], ["-", "-", "-", "-", 842a, 854a, 858a, 910a], ["-", "-", "-", "-", 940a, 949a, 952a, 1002a], ["-", "-", "-", "-", 1040a, 1049a, 1052a, 1102a], ["-", "-", "-", "-", 1140a, 1149a, 1152a, 1202p], ["-", "-", "-", "-", 1240p, 1249p, 1252p, 102p], ["-", "-", "-", "-", 140p, 149p, 152p, 202p], ["-", "-", "-", "-", 240p, 249p, 252p, 306p], ["-", "-", "-", "-", 342p, 352p, 356p, 408p], ["-", "-", "-", "-", 412p, 422p, 426p, 438p], [417p, 421p, 425p, 428p, 443p, 453p, 457p, 509p], [447p, 451p, 455p, 458p, 513p, 523p, 527p, 539p], [517p, 521p, 525p, 528p, 543p, 553p, 557p, 609p], ["-", "-", "-", "-", 612p, 622p, 626p, 637p], ["-", "-", "-", "-", 656p, 704p, 707p, 717p], ["-", "-", "-", "-", 756p, 804p, 807p, 817p], ["-", "-", "-", "-", 856p, 904p, 907p, 917p], ["-", "-", "-", "-", 956p, 1004p, 1007p, 1017p], ["-", "-", "-", "-", 1056p, 1104p, 1107p, 1117p]] stop_times: [["-", "-", "-", "-", 712a, 720a, 723a, 734a], ["-", "-", "-", "-", 807a, 819a, 823a, 835a], ["-", "-", "-", "-", 842a, 854a, 858a, 910a], ["-", "-", "-", "-", 940a, 949a, 952a, 1002a], ["-", "-", "-", "-", 1040a, 1049a, 1052a, 1102a], ["-", "-", "-", "-", 1140a, 1149a, 1152a, 1202p], ["-", "-", "-", "-", 1240p, 1249p, 1252p, 102p], ["-", "-", "-", "-", 140p, 149p, 152p, 202p], ["-", "-", "-", "-", 240p, 249p, 252p, 306p], ["-", "-", "-", "-", 342p, 352p, 356p, 408p], ["-", "-", "-", "-", 412p, 422p, 426p, 438p], [417p, 421p, 425p, 428p, 443p, 453p, 457p, 509p], [447p, 451p, 455p, 458p, 513p, 523p, 527p, 539p], [517p, 521p, 525p, 528p, 543p, 553p, 557p, 609p], ["-", "-", "-", "-", 612p, 622p, 626p, 637p], ["-", "-", "-", "-", 656p, 704p, 707p, 717p], ["-", "-", "-", "-", 756p, 804p, 807p, 817p], ["-", "-", "-", "-", 856p, 904p, 907p, 917p], ["-", "-", "-", "-", 956p, 1004p, 1007p, 1017p], ["-", "-", "-", "-", 1056p, 1104p, 1107p, 1117p]]
   
--- ---
time_points: [Weston Creek Terminus, Chapman Shops, Canberra College Weston Campus, Cooleman Court, Woden Bus Station (Platform 10), Kings Ave / National Circuit, Russell Offices, ADFA, Campbell Park Offices] time_points: [Weston Creek Terminus, Chapman, Canberra College Weston Campus, Cooleman Court, Woden Bus Station (Platform 10), Kings Ave / National Circuit, Russell Offices, ADFA, Campbell Park Offices]
long_name: To Campbell Park Offices long_name: To Campbell Park Offices
between_stops: between_stops:
Kings Ave / National Circuit-Russell Offices: [Wjz4Quk, Wjz4RwH, Wjz4RFJ] Kings Ave / National Circuit-Russell Offices: [Wjz4Quk, Wjz4RwH, Wjz4RFJ]
ADFA-Campbell Park Offices: [Wjzcend, Wjzce4H, Wjzce7O] ADFA-Campbell Park Offices: [Wjzcend, Wjzce4H, Wjzce7O]
Russell Offices-ADFA: [Wjzc60A, Wjzc60i, Wjzc55s, Wjzc54R, Wjzce7O, Wjzce4H, Wjzcend] Russell Offices-ADFA: [Wjzc60A, Wjzc60i, Wjzc55s, Wjzc54R, Wjzce7O, Wjzce4H, Wjzcend]
short_name: 26 226 short_name: 26 226
stop_times: [[615a, 619a, 623a, 625a, 632a, "-", "-", "-", "-"], [657a, 701a, 705a, 707a, 715a, 729a, 733a, 737a, 741a], [716a, 720a, 724a, 726a, 736a, 750a, 754a, 758a, 802a], [747a, 752a, 758a, 802a, 815a, 829a, 833a, 837a, 841a], [800a, 805a, 811a, 815a, 827a, "-", "-", "-", "-"], [820a, 825a, 831a, 835a, 847a, "-", "-", "-", "-"], [850a, 855a, 901a, 905a, 917a, "-", "-", "-", "-"], [925a, 930a, 935a, 938a, 948a, "-", "-", "-", "-"], [1025a, 1029a, 1034a, 1037a, 1047a, "-", "-", "-", "-"], [1125a, 1129a, 1134a, 1137a, 1147a, "-", "-", "-", "-"], [1225p, 1229p, 1234p, 1237p, 1247p, "-", "-", "-", "-"], [125p, 129p, 134p, 137p, 147p, "-", "-", "-", "-"], [225p, 229p, 234p, 237p, 247p, "-", "-", "-", "-"], [255p, 259p, 305p, 308p, 317p, "-", "-", "-", "-"], [320p, 324p, 330p, 333p, 342p, "-", "-", "-", "-"], [420p, 424p, 430p, 433p, 442p, "-", "-", "-", "-"], [520p, 524p, 530p, 533p, 542p, "-", "-", "-", "-"], [620p, 624p, 630p, 632p, 639p, "-", "-", "-", "-"], [714p, 718p, 722p, 724p, 731p, "-", "-", "-", "-"], [814p, 818p, 822p, 824p, 831p, "-", "-", "-", "-"], [914p, 918p, 922p, 924p, 931p, "-", "-", "-", "-"], [1014p, 1018p, 1022p, 1024p, 1031p, "-", "-", "-", "-"]] stop_times: [[615a, 619a, 623a, 625a, 632a, "-", "-", "-", "-"], [657a, 701a, 705a, 707a, 715a, 729a, 733a, 737a, 741a], [716a, 720a, 724a, 726a, 736a, 750a, 754a, 758a, 802a], [747a, 752a, 758a, 802a, 815a, 829a, 833a, 837a, 841a], [800a, 805a, 811a, 815a, 827a, "-", "-", "-", "-"], [820a, 825a, 831a, 835a, 847a, "-", "-", "-", "-"], [850a, 855a, 901a, 905a, 917a, "-", "-", "-", "-"], [925a, 930a, 935a, 938a, 948a, "-", "-", "-", "-"], [1025a, 1029a, 1034a, 1037a, 1047a, "-", "-", "-", "-"], [1125a, 1129a, 1134a, 1137a, 1147a, "-", "-", "-", "-"], [1225p, 1229p, 1234p, 1237p, 1247p, "-", "-", "-", "-"], [125p, 129p, 134p, 137p, 147p, "-", "-", "-", "-"], [225p, 229p, 234p, 237p, 247p, "-", "-", "-", "-"], [255p, 259p, 305p, 308p, 317p, "-", "-", "-", "-"], [320p, 324p, 330p, 333p, 342p, "-", "-", "-", "-"], [420p, 424p, 430p, 433p, 442p, "-", "-", "-", "-"], [520p, 524p, 530p, 533p, 542p, "-", "-", "-", "-"], [620p, 624p, 630p, 632p, 639p, "-", "-", "-", "-"], [714p, 718p, 722p, 724p, 731p, "-", "-", "-", "-"], [814p, 818p, 822p, 824p, 831p, "-", "-", "-", "-"], [914p, 918p, 922p, 924p, 931p, "-", "-", "-", "-"], [1014p, 1018p, 1022p, 1024p, 1031p, "-", "-", "-", "-"]]
   
--- ---
time_points: [Campbell Park Offices, ADFA, Russell Offices, Kings Ave / National Circuit, Woden Bus Station (Platform 3), Cooleman Court, Canberra College Weston Campus, Chapman Shops, Weston Creek Terminus] time_points: [Campbell Park Offices, ADFA, Russell Offices, Kings Ave / National Circuit, Woden Bus Station (Platform 3), Cooleman Court, Canberra College Weston Campus, Chapman, Weston Creek Terminus]
long_name: To Weston Creek Terminus long_name: To Weston Creek Terminus
between_stops: between_stops:
Russell Offices-Kings Ave / National Circuit: [Wjz4RFJ, Wjz4RwH, Wjz4Quk] Russell Offices-Kings Ave / National Circuit: [Wjz4RFJ, Wjz4RwH, Wjz4Quk]
ADFA-Russell Offices: [Wjzcend, Wjzce4H, Wjzce7O, Wjzc54R, Wjzc55s, Wjzc60i, Wjzc60A] ADFA-Russell Offices: [Wjzcend, Wjzce4H, Wjzce7O, Wjzc54R, Wjzc55s, Wjzc60i, Wjzc60A]
Campbell Park Offices-ADFA: [Wjzce7O, Wjzce4H, Wjzcend] Campbell Park Offices-ADFA: [Wjzce7O, Wjzce4H, Wjzcend]
short_name: 26 226 short_name: 26 226
stop_times: [["-", "-", "-", "-", 718a, 725a, 727a, 731a, 735a], ["-", "-", "-", "-", 818a, 828a, 832a, 837a, 841a], ["-", "-", "-", "-", 858a, 908a, 912a, 917a, 921a], ["-", "-", "-", "-", 958a, 1007a, 1010a, 1015a, 1019a], ["-", "-", "-", "-", 1058a, 1107a, 1110a, 1115a, 1119a], ["-", "-", "-", "-", 1158a, 1207p, 1210p, 1215p, 1219p], ["-", "-", "-", "-", 1258p, 107p, 110p, 115p, 119p], ["-", "-", "-", "-", 158p, 207p, 210p, 215p, 219p], ["-", "-", "-", "-", 258p, 309p, 313p, 319p, 324p], ["-", "-", "-", "-", 328p, 340p, 344p, 350p, 355p], ["-", "-", "-", "-", 354p, 406p, 410p, 416p, 421p], ["-", "-", "-", "-", 418p, 430p, 434p, 440p, 445p], ["-", "-", "-", "-", 448p, 500p, 504p, 510p, 515p], [452p, 456p, 500p, 503p, 518p, 530p, 534p, 540p, 545p], [522p, 526p, 530p, 533p, 548p, 600p, 604p, 610p, 615p], ["-", "-", "-", "-", 618p, 630p, 632p, 636p, 640p], ["-", "-", "-", "-", 650p, 657p, 659p, 703p, 707p], ["-", "-", "-", "-", 750p, 757p, 759p, 803p, 807p], ["-", "-", "-", "-", 850p, 857p, 859p, 903p, 907p], ["-", "-", "-", "-", 950p, 957p, 959p, 1003p, 1007p], ["-", "-", "-", "-", 1050p, 1057p, 1059p, 1103p, 1107p]] stop_times: [["-", "-", "-", "-", 718a, 725a, 727a, 731a, 735a], ["-", "-", "-", "-", 818a, 828a, 832a, 837a, 841a], ["-", "-", "-", "-", 858a, 908a, 912a, 917a, 921a], ["-", "-", "-", "-", 958a, 1007a, 1010a, 1015a, 1019a], ["-", "-", "-", "-", 1058a, 1107a, 1110a, 1115a, 1119a], ["-", "-", "-", "-", 1158a, 1207p, 1210p, 1215p, 1219p], ["-", "-", "-", "-", 1258p, 107p, 110p, 115p, 119p], ["-", "-", "-", "-", 158p, 207p, 210p, 215p, 219p], ["-", "-", "-", "-", 258p, 309p, 313p, 319p, 324p], ["-", "-", "-", "-", 328p, 340p, 344p, 350p, 355p], ["-", "-", "-", "-", 354p, 406p, 410p, 416p, 421p], ["-", "-", "-", "-", 418p, 430p, 434p, 440p, 445p], ["-", "-", "-", "-", 448p, 500p, 504p, 510p, 515p], [452p, 456p, 500p, 503p, 518p, 530p, 534p, 540p, 545p], [522p, 526p, 530p, 533p, 548p, 600p, 604p, 610p, 615p], ["-", "-", "-", "-", 618p, 630p, 632p, 636p, 640p], ["-", "-", "-", "-", 650p, 657p, 659p, 703p, 707p], ["-", "-", "-", "-", 750p, 757p, 759p, 803p, 807p], ["-", "-", "-", "-", 850p, 857p, 859p, 903p, 907p], ["-", "-", "-", "-", 950p, 957p, 959p, 1003p, 1007p], ["-", "-", "-", "-", 1050p, 1057p, 1059p, 1103p, 1107p]]
   
--- ---
time_points: [Cooleman Court, Rivett Shops, Fisher Shops, Waramanga Shops, Woden Bus Station (Platform 10), Kings Ave / National Circuit, Russell Offices, ADFA, Campbell Park Offices] time_points: [Cooleman Court, Rivett, Fisher, Waramanga, Woden Bus Station (Platform 10), Kings Ave / National Circuit, Russell Offices, ADFA, Campbell Park Offices]
long_name: To Campbell Park Offices long_name: To Campbell Park Offices
between_stops: between_stops:
Kings Ave / National Circuit-Russell Offices: [Wjz4Quk, Wjz4RwH, Wjz4RFJ] Kings Ave / National Circuit-Russell Offices: [Wjz4Quk, Wjz4RwH, Wjz4RFJ]
ADFA-Campbell Park Offices: [Wjzcend, Wjzce4H, Wjzce7O] ADFA-Campbell Park Offices: [Wjzcend, Wjzce4H, Wjzce7O]
Russell Offices-ADFA: [Wjzc60A, Wjzc60i, Wjzc55s, Wjzc54R, Wjzce7O, Wjzce4H, Wjzcend] Russell Offices-ADFA: [Wjzc60A, Wjzc60i, Wjzc55s, Wjzc54R, Wjzce7O, Wjzce4H, Wjzcend]
short_name: 27 227 short_name: 27 227
stop_times: [[629a, 635a, 643a, 647a, 655a, 709a, 712a, 716a, 720a], [654a, 700a, 708a, 712a, 720a, 734a, 738a, 742a, 746a], ["-", "-", 728a, 735a, 746a, "-", "-", "-", "-"], [722a, 728a, 736a, 743a, 755a, 809a, 813a, 817a, 821a], [740a, 746a, 754a, 801a, 812a, "-", "-", "-", "-"], [748a, 754a, 802a, 809a, 820a, "-", "-", "-", "-"], [823a, 829a, 837a, 844a, 855a, "-", "-", "-", "-"], [853a, 859a, 907a, 914a, 925a, "-", "-", "-", "-"], [925a, 931a, 938a, 942a, 949a, "-", "-", "-", "-"], [1025a, 1031a, 1038a, 1042a, 1049a, "-", "-", "-", "-"], [1125a, 1131a, 1138a, 1142a, 1149a, "-", "-", "-", "-"], [1225p, 1231p, 1238p, 1242p, 1249p, "-", "-", "-", "-"], [125p, 131p, 138p, 142p, 149p, "-", "-", "-", "-"], [225p, 231p, 238p, 242p, 249p, "-", "-", "-", "-"], [325p, 330p, 337p, 341p, 349p, "-", "-", "-", "-"], [355p, 400p, 407p, 411p, 419p, "-", "-", "-", "-"], [425p, 430p, 437p, 441p, 449p, "-", "-", "-", "-"], [525p, 530p, 537p, 541p, 549p, "-", "-", "-", "-"], [625p, 630p, 637p, 640p, 647p, "-", "-", "-", "-"], [700p, 705p, 712p, 715p, 722p, "-", "-", "-", "-"], [800p, 805p, 812p, 815p, 822p, "-", "-", "-", "-"], [900p, 905p, 912p, 915p, 922p, "-", "-", "-", "-"], [1000p, 1005p, 1012p, 1015p, 1022p, "-", "-", "-", "-"]] stop_times: [[629a, 635a, 643a, 647a, 655a, 709a, 712a, 716a, 720a], [654a, 700a, 708a, 712a, 720a, 734a, 738a, 742a, 746a], ["-", "-", 728a, 735a, 746a, "-", "-", "-", "-"], [722a, 728a, 736a, 743a, 755a, 809a, 813a, 817a, 821a], [740a, 746a, 754a, 801a, 812a, "-", "-", "-", "-"], [748a, 754a, 802a, 809a, 820a, "-", "-", "-", "-"], [823a, 829a, 837a, 844a, 855a, "-", "-", "-", "-"], [853a, 859a, 907a, 914a, 925a, "-", "-", "-", "-"], [925a, 931a, 938a, 942a, 949a, "-", "-", "-", "-"], [1025a, 1031a, 1038a, 1042a, 1049a, "-", "-", "-", "-"], [1125a, 1131a, 1138a, 1142a, 1149a, "-", "-", "-", "-"], [1225p, 1231p, 1238p, 1242p, 1249p, "-", "-", "-", "-"], [125p, 131p, 138p, 142p, 149p, "-", "-", "-", "-"], [225p, 231p, 238p, 242p, 249p, "-", "-", "-", "-"], [325p, 330p, 337p, 341p, 349p, "-", "-", "-", "-"], [355p, 400p, 407p, 411p, 419p, "-", "-", "-", "-"], [425p, 430p, 437p, 441p, 449p, "-", "-", "-", "-"], [525p, 530p, 537p, 541p, 549p, "-", "-", "-", "-"], [625p, 630p, 637p, 640p, 647p, "-", "-", "-", "-"], [700p, 705p, 712p, 715p, 722p, "-", "-", "-", "-"], [800p, 805p, 812p, 815p, 822p, "-", "-", "-", "-"], [900p, 905p, 912p, 915p, 922p, "-", "-", "-", "-"], [1000p, 1005p, 1012p, 1015p, 1022p, "-", "-", "-", "-"]]
   
--- ---
time_points: [Campbell Park Offices, ADFA, Russell Offices, Kings Ave / National Circuit, Woden Bus Station (Platform 3), Waramanga Shops, Fisher Shops, Rivett Shops, Cooleman Court] time_points: [Campbell Park Offices, ADFA, Russell Offices, Kings Ave / National Circuit, Woden Bus Station (Platform 3), Waramanga, Fisher, Rivett, Cooleman Court]
long_name: To Cooleman Court long_name: To Cooleman Court
between_stops: between_stops:
Russell Offices-Kings Ave / National Circuit: [Wjz4RFJ, Wjz4RwH, Wjz4Quk] Russell Offices-Kings Ave / National Circuit: [Wjz4RFJ, Wjz4RwH, Wjz4Quk]
ADFA-Russell Offices: [Wjzcend, Wjzce4H, Wjzce7O, Wjzc54R, Wjzc55s, Wjzc60i, Wjzc60A] ADFA-Russell Offices: [Wjzcend, Wjzce4H, Wjzce7O, Wjzc54R, Wjzc55s, Wjzc60i, Wjzc60A]
Campbell Park Offices-ADFA: [Wjzce7O, Wjzce4H, Wjzcend] Campbell Park Offices-ADFA: [Wjzce7O, Wjzce4H, Wjzcend]
short_name: 27 227 short_name: 27 227
stop_times: [["-", "-", "-", "-", 821a, 829a, 833a, 840a, 845a], ["-", "-", "-", "-", 854a, 902a, 906a, 913a, 918a], ["-", "-", "-", "-", 954a, 1001a, 1005a, 1013a, 1019a], ["-", "-", "-", "-", 1054a, 1101a, 1105a, 1113a, 1119a], ["-", "-", "-", "-", 1154a, 1201p, 1205p, 1213p, 1219p], ["-", "-", "-", "-", 1254p, 101p, 105p, 113p, 119p], ["-", "-", "-", "-", 154p, 201p, 205p, 213p, 219p], ["-", "-", "-", "-", 254p, 302p, 307p, 314p, 322p], ["-", "-", "-", "-", 321p, 333p, 338p, 345p, 353p], ["-", "-", "-", "-", 351p, 403p, 408p, 415p, 423p], ["-", "-", "-", "-", 421p, 433p, 438p, 445p, 453p], [427p, 431p, 435p, 438p, 453p, 505p, 510p, 517p, 525p], ["-", "-", "-", "-", 521p, 533p, 538p, 545p, 553p], [527p, 531p, 535p, 538p, 553p, 605p, 610p, 617p, 625p], ["-", "-", "-", "-", 635p, 641p, 644p, 650p, 655p], ["-", "-", "-", "-", 735p, 741p, 744p, 750p, 755p], ["-", "-", "-", "-", 835p, 841p, 844p, 850p, 855p], ["-", "-", "-", "-", 935p, 941p, 944p, 950p, 955p], ["-", "-", "-", "-", 1035p, 1041p, 1044p, 1050p, 1055p]] stop_times: [["-", "-", "-", "-", 821a, 829a, 833a, 840a, 845a], ["-", "-", "-", "-", 854a, 902a, 906a, 913a, 918a], ["-", "-", "-", "-", 954a, 1001a, 1005a, 1013a, 1019a], ["-", "-", "-", "-", 1054a, 1101a, 1105a, 1113a, 1119a], ["-", "-", "-", "-", 1154a, 1201p, 1205p, 1213p, 1219p], ["-", "-", "-", "-", 1254p, 101p, 105p, 113p, 119p], ["-", "-", "-", "-", 154p, 201p, 205p, 213p, 219p], ["-", "-", "-", "-", 254p, 302p, 307p, 314p, 322p], ["-", "-", "-", "-", 321p, 333p, 338p, 345p, 353p], ["-", "-", "-", "-", 351p, 403p, 408p, 415p, 423p], ["-", "-", "-", "-", 421p, 433p, 438p, 445p, 453p], [427p, 431p, 435p, 438p, 453p, 505p, 510p, 517p, 525p], ["-", "-", "-", "-", 521p, 533p, 538p, 545p, 553p], [527p, 531p, 535p, 538p, 553p, 605p, 610p, 617p, 625p], ["-", "-", "-", "-", 635p, 641p, 644p, 650p, 655p], ["-", "-", "-", "-", 735p, 741p, 744p, 750p, 755p], ["-", "-", "-", "-", 835p, 841p, 844p, 850p, 855p], ["-", "-", "-", "-", 935p, 941p, 944p, 950p, 955p], ["-", "-", "-", "-", 1035p, 1041p, 1044p, 1050p, 1055p]]
   
--- ---
time_points: [Fairbairn Park, Brindabella Business Park, Russell Offices, Kings Ave / National Circuit, Woden Bus Station (Platform 16), Lyons Shops, CIT Weston, Duffy Primary, Cooleman Court] time_points: [Fairbairn Park, Brindabella Business Park, Russell Offices, Kings Ave / National Circuit, Woden Bus Station (Platform 16), Lyons, CIT Weston, Duffy Primary, Cooleman Court]
long_name: To Cooleman Court long_name: To Cooleman Court
between_stops: between_stops:
Russell Offices-Kings Ave / National Circuit: [Wjz4RFJ, Wjz4RwH, Wjz4Quk] Russell Offices-Kings Ave / National Circuit: [Wjz4RFJ, Wjz4RwH, Wjz4Quk]
Fairbairn Park-Brindabella Business Park: [WjzcJ38, WjzcBHZ, WjzcJ0K, WjzcrEu, WjzcrrQ, WjzcrK3] Fairbairn Park-Brindabella Business Park: [WjzcJ38, WjzcBHZ, WjzcJ0K, WjzcrEu, WjzcrrQ, WjzcrK3]
short_name: "28" short_name: "28"
stop_times: [["-", "-", "-", "-", 742a, 746a, 751a, 759a, 811a], ["-", "-", "-", "-", 845a, 849a, 854a, 902a, 914a], ["-", "-", "-", "-", 952a, 956a, 1000a, 1007a, 1019a], ["-", "-", "-", "-", 1052a, 1056a, 1100a, 1107a, 1119a], ["-", "-", "-", "-", 1152a, 1156a, 1200p, 1207p, 1219p], ["-", "-", "-", "-", 1252p, 1256p, 100p, 107p, 119p], ["-", "-", "-", "-", 152p, 156p, 200p, 207p, 219p], ["-", "-", "-", "-", 252p, 256p, 300p, 308p, 320p], ["-", "-", "-", "-", 312p, 316p, 321p, 329p, 341p], ["-", "-", "-", "-", 342p, 346p, 351p, 359p, 411p], ["-", "-", "-", "-", 412p, 416p, 421p, 429p, 441p], ["-", "-", "-", "-", 442p, 446p, 451p, 459p, 511p], [429p, 439p, 453p, 456p, 511p, 515p, 520p, 528p, 540p], [449p, 459p, 513p, 516p, 531p, 535p, 540p, 548p, 600p], [519p, 529p, 543p, 546p, 601p, 605p, 610p, 618p, 630p], [549p, 559p, 613p, 616p, 631p, 634p, 638p, 645p, 654p], ["-", "-", "-", "-", 732p, 735p, 739p, 746p, 755p], ["-", "-", "-", "-", 832p, 835p, 839p, 846p, 855p], ["-", "-", "-", "-", 932p, 935p, 939p, 946p, 955p], ["-", "-", "-", "-", 1032p, 1035p, 1039p, 1046p, 1055p]] stop_times: [["-", "-", "-", "-", 742a, 746a, 751a, 759a, 811a], ["-", "-", "-", "-", 845a, 849a, 854a, 902a, 914a], ["-", "-", "-", "-", 952a, 956a, 1000a, 1007a, 1019a], ["-", "-", "-", "-", 1052a, 1056a, 1100a, 1107a, 1119a], ["-", "-", "-", "-", 1152a, 1156a, 1200p, 1207p, 1219p], ["-", "-", "-", "-", 1252p, 1256p, 100p, 107p, 119p], ["-", "-", "-", "-", 152p, 156p, 200p, 207p, 219p], ["-", "-", "-", "-", 252p, 256p, 300p, 308p, 320p], ["-", "-", "-", "-", 312p, 316p, 321p, 329p, 341p], ["-", "-", "-", "-", 342p, 346p, 351p, 359p, 411p], ["-", "-", "-", "-", 412p, 416p, 421p, 429p, 441p], ["-", "-", "-", "-", 442p, 446p, 451p, 459p, 511p], [429p, 439p, 453p, 456p, 511p, 515p, 520p, 528p, 540p], [449p, 459p, 513p, 516p, 531p, 535p, 540p, 548p, 600p], [519p, 529p, 543p, 546p, 601p, 605p, 610p, 618p, 630p], [549p, 559p, 613p, 616p, 631p, 634p, 638p, 645p, 654p], ["-", "-", "-", "-", 732p, 735p, 739p, 746p, 755p], ["-", "-", "-", "-", 832p, 835p, 839p, 846p, 855p], ["-", "-", "-", "-", 932p, 935p, 939p, 946p, 955p], ["-", "-", "-", "-", 1032p, 1035p, 1039p, 1046p, 1055p]]
   
--- ---
time_points: [Cooleman Court, Duffy Primary, CIT Weston, Lyons Shops, Woden Bus Station (Platform 10), Kings Ave / National Circuit, Russell Offices, Brindabella Business Park, Fairbairn Park] time_points: [Cooleman Court, Duffy Primary, CIT Weston, Lyons, Woden Bus Station (Platform 10), Kings Ave / National Circuit, Russell Offices, Brindabella Business Park, Fairbairn Park]
long_name: To Fairbairn Park long_name: To Fairbairn Park
between_stops: between_stops:
Brindabella Business Park-Fairbairn Park: [WjzcrK3, WjzcrrQ, WjzcrEu, WjzcJ0K, WjzcBHZ, WjzcJ38] Brindabella Business Park-Fairbairn Park: [WjzcrK3, WjzcrrQ, WjzcrEu, WjzcJ0K, WjzcBHZ, WjzcJ38]
Kings Ave / National Circuit-Russell Offices: [Wjz4Quk, Wjz4RwH, Wjz4RFJ] Kings Ave / National Circuit-Russell Offices: [Wjz4Quk, Wjz4RwH, Wjz4RFJ]
short_name: "28" short_name: "28"
stop_times: [[615a, 624a, 630a, 634a, 638a, 652a, 655a, 709a, 719a], [637a, 646a, 652a, 656a, 700a, 714a, 717a, 731a, 741a], [705a, 714a, 720a, 724a, 728a, 742a, 746a, 800a, 810a], [745a, 757a, 805a, 810a, 815a, 829a, 833a, 847a, 857a], [815a, 827a, 835a, 840a, 844a, "-", "-", "-", "-"], [844a, 856a, 904a, 909a, 913a, "-", "-", "-", "-"], [926a, 938a, 945a, 949a, 953a, "-", "-", "-", "-"], [1026a, 1038a, 1045a, 1049a, 1053a, "-", "-", "-", "-"], [1126a, 1138a, 1145a, 1149a, 1153a, "-", "-", "-", "-"], [1226p, 1238p, 1245p, 1249p, 1253p, "-", "-", "-", "-"], [126p, 138p, 145p, 149p, 153p, "-", "-", "-", "-"], [226p, 238p, 245p, 249p, 253p, "-", "-", "-", "-"], [326p, 338p, 346p, 351p, 354p, "-", "-", "-", "-"], [356p, 408p, 416p, 421p, 425p, "-", "-", "-", "-"], [415p, 427p, 435p, 440p, 444p, "-", "-", "-", "-"], [515p, 527p, 535p, 540p, 544p, "-", "-", "-", "-"], [615p, 627p, 634p, 638p, 641p, "-", "-", "-", "-"], [700p, 709p, 715p, 719p, 722p, "-", "-", "-", "-"], [800p, 809p, 815p, 819p, 822p, "-", "-", "-", "-"], [900p, 909p, 915p, 919p, 922p, "-", "-", "-", "-"], [1000p, 1009p, 1015p, 1019p, 1022p, "-", "-", "-", "-"]] stop_times: [[615a, 624a, 630a, 634a, 638a, 652a, 655a, 709a, 719a], [637a, 646a, 652a, 656a, 700a, 714a, 717a, 731a, 741a], [705a, 714a, 720a, 724a, 728a, 742a, 746a, 800a, 810a], [745a, 757a, 805a, 810a, 815a, 829a, 833a, 847a, 857a], [815a, 827a, 835a, 840a, 844a, "-", "-", "-", "-"], [844a, 856a, 904a, 909a, 913a, "-", "-", "-", "-"], [926a, 938a, 945a, 949a, 953a, "-", "-", "-", "-"], [1026a, 1038a, 1045a, 1049a, 1053a, "-", "-", "-", "-"], [1126a, 1138a, 1145a, 1149a, 1153a, "-", "-", "-", "-"], [1226p, 1238p, 1245p, 1249p, 1253p, "-", "-", "-", "-"], [126p, 138p, 145p, 149p, 153p, "-", "-", "-", "-"], [226p, 238p, 245p, 249p, 253p, "-", "-", "-", "-"], [326p, 338p, 346p, 351p, 354p, "-", "-", "-", "-"], [356p, 408p, 416p, 421p, 425p, "-", "-", "-", "-"], [415p, 427p, 435p, 440p, 444p, "-", "-", "-", "-"], [515p, 527p, 535p, 540p, 544p, "-", "-", "-", "-"], [615p, 627p, 634p, 638p, 641p, "-", "-", "-", "-"], [700p, 709p, 715p, 719p, 722p, "-", "-", "-", "-"], [800p, 809p, 815p, 819p, 822p, "-", "-", "-", "-"], [900p, 909p, 915p, 919p, 922p, "-", "-", "-", "-"], [1000p, 1009p, 1015p, 1019p, 1022p, "-", "-", "-", "-"]]
   
--- ---
time_points: [Woden Bus Station (Platform 14), Canberra Hospital, Garran Shops, Hughes Shops, Deakin Shops, Parliament House, Kings Ave / National Circuit, City Bus Station (Platform 4), National Museum of Australia, Burton and Garran Hall Daley Road, O'Connor Shops, Calvary Hospital, Belconnen Community Bus Station, Westfield Bus Station, Cohen Street Bus Station] time_points: [Woden Bus Station (Platform 14), Canberra Hospital, Garran, Hughes, Deakin, Parliament House, Kings Ave / National Circuit, City Bus Station (Platform 4), National Museum of Australia, Burton and Garran Hall Daley Road, O'Connor, Calvary Hospital, Belconnen Community Bus Station, Westfield Bus Station, Cohen Street Bus Station]
long_name: To Cohen Street Bus Station long_name: To Cohen Street Bus Station
between_stops: between_stops:
Westfield Bus Station-Cohen Street Bus Station: [] Westfield Bus Station-Cohen Street Bus Station: []
  Parliament House-Kings Ave / National Circuit: [Wjz4INj, Wjz4P6x]
Belconnen Community Bus Station-Westfield Bus Station: [] Belconnen Community Bus Station-Westfield Bus Station: []
Woden Bus Station (Platform 14)-Canberra Hospital: [Wjz3mAg, Wjz3mPO, Wjz3mWn] Woden Bus Station (Platform 14)-Canberra Hospital: [Wjz3mAg, Wjz3mPO, Wjz3mWn]
Kings Ave / National Circuit-City Bus Station (Platform 4): [Wjz4Quk, Wjz4RwH, Wjz4RFJ, Wjz4Rs-, Wjz4S1U, Wjz5FOn] Kings Ave / National Circuit-City Bus Station (Platform 4): [Wjz4Quk, Wjz4RwH, Wjz4RFJ, Wjz4Rs-, Wjz4S1U, Wjz5FOn]
short_name: "3" short_name: "3"
stop_times: [[612a, 619a, 621a, 625a, 630a, 634a, 638a, 650a, 701a, 706a, 711a, 718a, 730a, 732a, 737a], [642a, 649a, 651a, 655a, 700a, 704a, 708a, 720a, 731a, 736a, 741a, 750a, 803a, 805a, 810a], [712a, 719a, 721a, 725a, 730a, 734a, 740a, 752a, 803a, 808a, 813a, 822a, 835a, 837a, 842a], [738a, 746a, 749a, 754a, 802a, 806a, 812a, 824a, 835a, 840a, 845a, 854a, 907a, 909a, 914a], [808a, 816a, 819a, 824a, 832a, 836a, 842a, 854a, 905a, 910a, 915a, 924a, 936a, 938a, 943a], [838a, 846a, 849a, 854a, 902a, 906a, 912a, 924a, 935a, 940a, 945a, 952a, 1004a, 1006a, 1011a], [912a, 920a, 923a, 928a, 934a, 938a, 942a, 954a, 1005a, 1010a, 1015a, 1022a, 1034a, 1036a, 1041a], [942a, 949a, 951a, 955a, 1000a, 1004a, 1008a, 1020a, 1031a, 1036a, 1041a, 1048a, 1100a, 1102a, 1107a], [1012a, 1019a, 1021a, 1025a, 1030a, 1034a, 1038a, 1050a, 1101a, 1106a, 1111a, 1118a, 1130a, 1132a, 1137a], [1042a, 1049a, 1051a, 1055a, 1100a, 1104a, 1108a, 1120a, 1131a, 1136a, 1141a, 1148a, 1200p, 1202p, 1207p], [1112a, 1119a, 1121a, 1125a, 1130a, 1134a, 1138a, 1150a, 1201p, 1206p, 1211p, 1218p, 1230p, 1232p, 1237p], [1142a, 1149a, 1151a, 1155a, 1200p, 1204p, 1208p, 1220p, 1231p, 1236p, 1241p, 1248p, 100p, 102p, 107p], [1212p, 1219p, 1221p, 1225p, 1230p, 1234p, 1238p, 1250p, 101p, 106p, 111p, 118p, 130p, 132p, 137p], [1242p, 1249p, 1251p, 1255p, 100p, 104p, 108p, 120p, 131p, 136p, 141p, 148p, 200p, 202p, 207p], [112p, 119p, 121p, 125p, 130p, 134p, 138p, 150p, 201p, 206p, 211p, 218p, 230p, 232p, 237p], [142p, 149p, 151p, 155p, 200p, 204p, 208p, 220p, 231p, 236p, 241p, 248p, 300p, 302p, 307p], [212p, 219p, 221p, 225p, 230p, 234p, 238p, 250p, 301p, 307p, 313p, 321p, 334p, 336p, 341p], [242p, 249p, 251p, 255p, 300p, 304p, 308p, 320p, 331p, 337p, 343p, 351p, 404p, 406p, 411p], [309p, 317p, 319p, 324p, 330p, 334p, 338p, 350p, 401p, 407p, 413p, 421p, 434p, 436p, 441p], [339p, 347p, 349p, 354p, 400p, 404p, 408p, 420p, 431p, 437p, 443p, 451p, 504p, 506p, 511p], [409p, 417p, 419p, 424p, 430p, 434p, 438p, 450p, 501p, 507p, 513p, 521p, 534p, 536p, 541p], [439p, 447p, 449p, 454p, 500p, 504p, 508p, 520p, 531p, 537p, 543p, 551p, 604p, 606p, 611p], [511p, 519p, 521p, 526p, 532p, 536p, 540p, 552p, 603p, 609p, 615p, 623p, 636p, 638p, 643p], [539p, 547p, 549p, 554p, 600p, 604p, 608p, 620p, 631p, 636p, 641p, 648p, 700p, 702p, 707p], [608p, 616p, 618p, 623p, 629p, 632p, 636p, 648p, 659p, 704p, 709p, 716p, 728p, 730p, 735p], [643p, 649p, 651p, 655p, 700p, 703p, 707p, 719p, 730p, 735p, 740p, 747p, 759p, 801p, 806p], [713p, 719p, 721p, 725p, 730p, 733p, 737p, 749p, 800p, 805p, 810p, 817p, 829p, 831p, 836p], [813p, 819p, 821p, 825p, 830p, 833p, 837p, 849p, 900p, 905p, 910p, 917p, 929p, 931p, 936p], [913p, 919p, 921p, 925p, 930p, 933p, 937p, 949p, 1000p, 1005p, 1010p, 1017p, 1029p, 1031p, 1036p], [1013p, 1019p, 1021p, 1025p, 1030p, 1033p, 1037p, 1049p, 1100p, 1105p, 1110p, 1117p, 1129p, 1131p, 1136p], [1113p, 1119p, 1121p, 1125p, 1130p, 1133p, 1137p, 1147p, "-", "-", "-", "-", "-", "-", "-"]] stop_times: [[612a, 619a, 621a, 625a, 630a, 634a, 638a, 650a, 701a, 706a, 711a, 718a, 730a, 732a, 737a], [642a, 649a, 651a, 655a, 700a, 704a, 708a, 720a, 731a, 736a, 741a, 750a, 803a, 805a, 810a], [712a, 719a, 721a, 725a, 730a, 734a, 740a, 752a, 803a, 808a, 813a, 822a, 835a, 837a, 842a], [738a, 746a, 749a, 754a, 802a, 806a, 812a, 824a, 835a, 840a, 845a, 854a, 907a, 909a, 914a], [808a, 816a, 819a, 824a, 832a, 836a, 842a, 854a, 905a, 910a, 915a, 924a, 936a, 938a, 943a], [838a, 846a, 849a, 854a, 902a, 906a, 912a, 924a, 935a, 940a, 945a, 952a, 1004a, 1006a, 1011a], [912a, 920a, 923a, 928a, 934a, 938a, 942a, 954a, 1005a, 1010a, 1015a, 1022a, 1034a, 1036a, 1041a], [942a, 949a, 951a, 955a, 1000a, 1004a, 1008a, 1020a, 1031a, 1036a, 1041a, 1048a, 1100a, 1102a, 1107a], [1012a, 1019a, 1021a, 1025a, 1030a, 1034a, 1038a, 1050a, 1101a, 1106a, 1111a, 1118a, 1130a, 1132a, 1137a], [1042a, 1049a, 1051a, 1055a, 1100a, 1104a, 1108a, 1120a, 1131a, 1136a, 1141a, 1148a, 1200p, 1202p, 1207p], [1112a, 1119a, 1121a, 1125a, 1130a, 1134a, 1138a, 1150a, 1201p, 1206p, 1211p, 1218p, 1230p, 1232p, 1237p], [1142a, 1149a, 1151a, 1155a, 1200p, 1204p, 1208p, 1220p, 1231p, 1236p, 1241p, 1248p, 100p, 102p, 107p], [1212p, 1219p, 1221p, 1225p, 1230p, 1234p, 1238p, 1250p, 101p, 106p, 111p, 118p, 130p, 132p, 137p], [1242p, 1249p, 1251p, 1255p, 100p, 104p, 108p, 120p, 131p, 136p, 141p, 148p, 200p, 202p, 207p], [112p, 119p, 121p, 125p, 130p, 134p, 138p, 150p, 201p, 206p, 211p, 218p, 230p, 232p, 237p], [142p, 149p, 151p, 155p, 200p, 204p, 208p, 220p, 231p, 236p, 241p, 248p, 300p, 302p, 307p], [212p, 219p, 221p, 225p, 230p, 234p, 238p, 250p, 301p, 307p, 313p, 321p, 334p, 336p, 341p], [242p, 249p, 251p, 255p, 300p, 304p, 308p, 320p, 331p, 337p, 343p, 351p, 404p, 406p, 411p], [309p, 317p, 319p, 324p, 330p, 334p, 338p, 350p, 401p, 407p, 413p, 421p, 434p, 436p, 441p], [339p, 347p, 349p, 354p, 400p, 404p, 408p, 420p, 431p, 437p, 443p, 451p, 504p, 506p, 511p], [409p, 417p, 419p, 424p, 430p, 434p, 438p, 450p, 501p, 507p, 513p, 521p, 534p, 536p, 541p], [439p, 447p, 449p, 454p, 500p, 504p, 508p, 520p, 531p, 537p, 543p, 551p, 604p, 606p, 611p], [511p, 519p, 521p, 526p, 532p, 536p, 540p, 552p, 603p, 609p, 615p, 623p, 636p, 638p, 643p], [539p, 547p, 549p, 554p, 600p, 604p, 608p, 620p, 631p, 636p, 641p, 648p, 700p, 702p, 707p], [608p, 616p, 618p, 623p, 629p, 632p, 636p, 648p, 659p, 704p, 709p, 716p, 728p, 730p, 735p], [643p, 649p, 651p, 655p, 700p, 703p, 707p, 719p, 730p, 735p, 740p, 747p, 759p, 801p, 806p], [713p, 719p, 721p, 725p, 730p, 733p, 737p, 749p, 800p, 805p, 810p, 817p, 829p, 831p, 836p], [813p, 819p, 821p, 825p, 830p, 833p, 837p, 849p, 900p, 905p, 910p, 917p, 929p, 931p, 936p], [913p, 919p, 921p, 925p, 930p, 933p, 937p, 949p, 1000p, 1005p, 1010p, 1017p, 1029p, 1031p, 1036p], [1013p, 1019p, 1021p, 1025p, 1030p, 1033p, 1037p, 1049p, 1100p, 1105p, 1110p, 1117p, 1129p, 1131p, 1136p], [1113p, 1119p, 1121p, 1125p, 1130p, 1133p, 1137p, 1147p, "-", "-", "-", "-", "-", "-", "-"]]
   
--- ---
time_points: [Cohen Street Bus Station (Platform 2), Westfield Bus Station (Platform 1), Belconnen Community Bus Station (Platform 3), Calvary Hospital, O'Connor Shops, Burton and Garran Hall Daley Road, National Museum of Australia, City Bus Station (Platform 2), Kings Ave / National Circuit, Parliament House, Deakin Shops, Hughes Shops, Garran Shops, Canberra Hospital, Woden Bus Station] time_points: [Cohen Street Bus Station (Platform 2), Westfield Bus Station (Platform 1), Belconnen Community Bus Station (Platform 3), Calvary Hospital, O'Connor, Burton and Garran Hall Daley Road, National Museum of Australia, City Bus Station (Platform 2), Kings Ave / National Circuit, Parliament House, Deakin, Hughes, Garran, Canberra Hospital, Woden Bus Station]
long_name: To Woden Bus Station long_name: To Woden Bus Station
between_stops: between_stops:
Westfield Bus Station (Platform 1)-Belconnen Community Bus Station (Platform 3): [] Westfield Bus Station (Platform 1)-Belconnen Community Bus Station (Platform 3): []
  Kings Ave / National Circuit-Parliament House: [Wjz4P6x, Wjz4IrL]
City Bus Station (Platform 2)-Kings Ave / National Circuit: [Wjz5FOn, Wjz4S1U, Wjz4Rs-, Wjz4RFJ, Wjz4RwH, Wjz4Quk] City Bus Station (Platform 2)-Kings Ave / National Circuit: [Wjz5FOn, Wjz4S1U, Wjz4Rs-, Wjz4RFJ, Wjz4RwH, Wjz4Quk]
Cohen Street Bus Station (Platform 2)-Westfield Bus Station (Platform 1): [] Cohen Street Bus Station (Platform 2)-Westfield Bus Station (Platform 1): []
Canberra Hospital-Woden Bus Station: [Wjz3mWn, Wjz3mPO, Wjz3mAg] Canberra Hospital-Woden Bus Station: [Wjz3mWn, Wjz3mPO, Wjz3mAg]
short_name: "3" short_name: "3"
stop_times: [["-", "-", "-", "-", "-", "-", "-", 618a, 627a, 631a, 636a, 640a, 644a, 646a, 653a], ["-", "-", "-", "-", "-", "-", "-", 648a, 657a, 701a, 706a, 710a, 714a, 716a, 723a], [628a, 630a, 634a, 651a, 657a, 701a, 705a, 718a, 727a, 731a, 736a, 742a, 746a, 748a, 758a], [656a, 658a, 702a, 719a, 725a, 729a, 734a, 748a, 758a, 803a, 808a, 814a, 818a, 820a, 830a], [721a, 723a, 727a, 746a, 754a, 759a, 804a, 818a, 828a, 833a, 838a, 844a, 848a, 850a, 900a], [745a, 747a, 751a, 810a, 819a, 827a, 832a, 848a, 853a, 901a, 906a, 908a, 912a, 914a, 924a], [821a, 823a, 827a, 846a, 854a, 859a, 904a, 918a, 928a, 932a, 937a, 942a, 946a, 948a, 955a], [851a, 853a, 857a, 916a, 924a, 929a, 934a, 948a, 958a, 1002a, 1007a, 1012a, 1016a, 1018a, 1025a], [924a, 926a, 930a, 947a, 954a, 959a, 1004a, 1018a, 1028a, 1032a, 1037a, 1042a, 1046a, 1048a, 1055a], [954a, 956a, 1000a, 1017a, 1024a, 1029a, 1034a, 1048a, 1058a, 1102a, 1107a, 1112a, 1116a, 1118a, 1125a], [1024a, 1026a, 1030a, 1047a, 1054a, 1059a, 1104a, 1118a, 1128a, 1132a, 1137a, 1142a, 1146a, 1148a, 1155a], [1054a, 1056a, 1100a, 1117a, 1124a, 1129a, 1134a, 1148a, 1158a, 1202p, 1207p, 1212p, 1216p, 1218p, 1225p], [1124a, 1126a, 1130a, 1147a, 1154a, 1159a, 1204p, 1218p, 1228p, 1232p, 1237p, 1242p, 1246p, 1248p, 1255p], [1154a, 1156a, 1200p, 1217p, 1224p, 1229p, 1234p, 1248p, 1258p, 102p, 107p, 112p, 116p, 118p, 125p], [1224p, 1226p, 1230p, 1247p, 1254p, 1259p, 104p, 118p, 128p, 132p, 137p, 142p, 146p, 148p, 155p], [1254p, 1256p, 100p, 117p, 124p, 129p, 134p, 148p, 158p, 202p, 207p, 212p, 216p, 218p, 225p], [124p, 126p, 130p, 147p, 154p, 159p, 204p, 218p, 228p, 232p, 237p, 242p, 246p, 248p, 255p], [154p, 156p, 200p, 217p, 224p, 229p, 234p, 248p, 258p, 303p, 308p, 314p, 318p, 320p, 329p], [229p, 231p, 235p, 248p, 258p, 303p, 310p, 324p, 334p, 339p, 344p, 350p, 354p, 356p, 405p], [250p, 252p, 256p, 315p, 323p, 328p, 334p, 348p, 358p, 403p, 408p, 414p, 418p, 420p, 429p], [317p, 319p, 323p, 342p, 350p, 355p, 401p, 415p, 425p, 430p, 435p, 441p, 445p, 447p, 456p], [346p, 348p, 352p, 411p, 419p, 424p, 430p, 444p, 454p, 459p, 504p, 510p, 514p, 516p, 525p], [418p, 420p, 424p, 443p, 451p, 456p, 502p, 516p, 526p, 531p, 536p, 542p, 546p, 548p, 557p], [445p, 447p, 451p, 510p, 518p, 523p, 529p, 543p, 553p, 558p, 603p, 609p, 613p, 615p, 624p], [515p, 517p, 521p, 540p, 548p, 553p, 559p, 613p, 623p, 628p, 632p, 637p, 641p, 643p, 650p], [547p, 549p, 553p, 612p, 620p, 625p, 631p, 644p, 653p, 658p, 702p, 707p, 711p, 713p, 720p], [620p, 622p, 626p, 643p, 650p, 655p, 700p, 713p, 722p, 727p, 731p, 736p, 740p, 742p, 749p], [723p, 725p, 729p, 746p, 753p, 758p, 803p, 816p, 825p, 830p, 834p, 839p, 843p, 845p, 852p], [825p, 827p, 831p, 848p, 855p, 900p, 905p, 918p, 927p, 932p, 936p, 941p, 945p, 947p, 954p], [925p, 927p, 931p, 948p, 955p, 1000p, 1005p, 1018p, 1027p, 1032p, 1036p, 1041p, 1045p, 1047p, 1054p], [1025p, 1027p, 1031p, 1048p, 1055p, 1100p, 1105p, 1116p, "-", "-", "-", "-", "-", "-", "-"]] stop_times: [["-", "-", "-", "-", "-", "-", "-", 618a, 627a, 631a, 636a, 640a, 644a, 646a, 653a], ["-", "-", "-", "-", "-", "-", "-", 648a, 657a, 701a, 706a, 710a, 714a, 716a, 723a], [628a, 630a, 634a, 651a, 657a, 701a, 705a, 718a, 727a, 731a, 736a, 742a, 746a, 748a, 758a], [656a, 658a, 702a, 719a, 725a, 729a, 734a, 748a, 758a, 803a, 808a, 814a, 818a, 820a, 830a], [721a, 723a, 727a, 746a, 754a, 759a, 804a, 818a, 828a, 833a, 838a, 844a, 848a, 850a, 900a], [745a, 747a, 751a, 810a, 819a, 827a, 832a, 848a, 853a, 901a, 906a, 908a, 912a, 914a, 924a], [821a, 823a, 827a, 846a, 854a, 859a, 904a, 918a, 928a, 932a, 937a, 942a, 946a, 948a, 955a], [851a, 853a, 857a, 916a, 924a, 929a, 934a, 948a, 958a, 1002a, 1007a, 1012a, 1016a, 1018a, 1025a], [924a, 926a, 930a, 947a, 954a, 959a, 1004a, 1018a, 1028a, 1032a, 1037a, 1042a, 1046a, 1048a, 1055a], [954a, 956a, 1000a, 1017a, 1024a, 1029a, 1034a, 1048a, 1058a, 1102a, 1107a, 1112a, 1116a, 1118a, 1125a], [1024a, 1026a, 1030a, 1047a, 1054a, 1059a, 1104a, 1118a, 1128a, 1132a, 1137a, 1142a, 1146a, 1148a, 1155a], [1054a, 1056a, 1100a, 1117a, 1124a, 1129a, 1134a, 1148a, 1158a, 1202p, 1207p, 1212p, 1216p, 1218p, 1225p], [1124a, 1126a, 1130a, 1147a, 1154a, 1159a, 1204p, 1218p, 1228p, 1232p, 1237p, 1242p, 1246p, 1248p, 1255p], [1154a, 1156a, 1200p, 1217p, 1224p, 1229p, 1234p, 1248p, 1258p, 102p, 107p, 112p, 116p, 118p, 125p], [1224p, 1226p, 1230p, 1247p, 1254p, 1259p, 104p, 118p, 128p, 132p, 137p, 142p, 146p, 148p, 155p], [1254p, 1256p, 100p, 117p, 124p, 129p, 134p, 148p, 158p, 202p, 207p, 212p, 216p, 218p, 225p], [124p, 126p, 130p, 147p, 154p, 159p, 204p, 218p, 228p, 232p, 237p, 242p, 246p, 248p, 255p], [154p, 156p, 200p, 217p, 224p, 229p, 234p, 248p, 258p, 303p, 308p, 314p, 318p, 320p, 329p], [229p, 231p, 235p, 248p, 258p, 303p, 310p, 324p, 334p, 339p, 344p, 350p, 354p, 356p, 405p], [250p, 252p, 256p, 315p, 323p, 328p, 334p, 348p, 358p, 403p, 408p, 414p, 418p, 420p, 429p], [317p, 319p, 323p, 342p, 350p, 355p, 401p, 415p, 425p, 430p, 435p, 441p, 445p, 447p, 456p], [346p, 348p, 352p, 411p, 419p, 424p, 430p, 444p, 454p, 459p, 504p, 510p, 514p, 516p, 525p], [418p, 420p, 424p, 443p, 451p, 456p, 502p, 516p, 526p, 531p, 536p, 542p, 546p, 548p, 557p], [445p, 447p, 451p, 510p, 518p, 523p, 529p, 543p, 553p, 558p, 603p, 609p, 613p, 615p, 624p], [515p, 517p, 521p, 540p, 548p, 553p, 559p, 613p, 623p, 628p, 632p, 637p, 641p, 643p, 650p], [547p, 549p, 553p, 612p, 620p, 625p, 631p, 644p, 653p, 658p, 702p, 707p, 711p, 713p, 720p], [620p, 622p, 626p, 643p, 650p, 655p, 700p, 713p, 722p, 727p, 731p, 736p, 740p, 742p, 749p], [723p, 725p, 729p, 746p, 753p, 758p, 803p, 816p, 825p, 830p, 834p, 839p, 843p, 845p, 852p], [825p, 827p, 831p, 848p, 855p, 900p, 905p, 918p, 927p, 932p, 936p, 941p, 945p, 947p, 954p], [925p, 927p, 931p, 948p, 955p, 1000p, 1005p, 1018p, 1027p, 1032p, 1036p, 1041p, 1045p, 1047p, 1054p], [1025p, 1027p, 1031p, 1048p, 1055p, 1100p, 1105p, 1116p, "-", "-", "-", "-", "-", "-", "-"]]
   
--- ---
time_points: [City Bus Station (Platform 8), Macarthur / Northbourne Ave, Dickson Shops, Watson, Watson Terminus, Watson, Dickson Shops, Northbourne Avenue / Antill St, Macarthur / Northbourne Ave, City Bus Station] time_points: [City Bus Station (Platform 8), Macarthur / Northbourne Ave, Dickson / Antill St, Watson, Watson Terminus, Watson, Dickson / Antill St, Northbourne Avenue / Antill St, Macarthur / Northbourne Ave, City Bus Station]
long_name: To City Bus Station long_name: To City Bus Station
between_stops: between_stops:
City Bus Station (Platform 8)-Macarthur / Northbourne Ave: [Wjz5O3Q, Wjz5Oci, Wjz5P8n, Wjz5P8K, Wjz5PdJ, Wjz5Pl0, Wjz5Qi2, Wjz5Qgn, Wjz5Qmu, Wjz5QmR] City Bus Station (Platform 8)-Macarthur / Northbourne Ave: [Wjz5O3Q, Wjz5Oci, Wjz5P8n, Wjz5P8K, Wjz5PdJ, Wjz5Pl0, Wjz5Qi2, Wjz5Qgn, Wjz5Qmu, Wjz5QmR]
Macarthur / Northbourne Ave-City Bus Station: [Wjz5QmR, Wjz5Qmu, Wjz5Qgn, Wjz5Qi2, Wjz5Pl0, Wjz5PdJ, Wjz5P8K, Wjz5P8n, Wjz5Oci, Wjz5O3Q] Macarthur / Northbourne Ave-City Bus Station: [Wjz5QmR, Wjz5Qmu, Wjz5Qgn, Wjz5Qi2, Wjz5Pl0, Wjz5PdJ, Wjz5P8K, Wjz5P8n, Wjz5Oci, Wjz5O3Q]
short_name: "39" short_name: "39"
stop_times: [["-", "-", "-", 549a, 555a, 601a, 606a, 607a, 610a, 617a], [609a, 615a, 618a, 624a, 630a, 636a, 641a, 642a, 645a, 652a], [639a, 645a, 648a, 654a, 700a, 706a, 711a, 712a, 715a, 722a], ["-", "-", "-", 707a, 713a, 719a, 724a, 725a, 728a, 741a], [703a, 709a, 712a, 718a, 724a, 730a, 736a, 737a, 742a, 757a], ["-", "-", "-", 726a, 732a, 738a, 744a, 745a, 750a, 805a], [718a, 724a, 727a, 734a, 740a, 746a, 752a, 753a, 758a, 813a], ["-", "-", "-", 742a, 748a, 754a, 800a, 801a, 806a, 821a], [733a, 739a, 742a, 749a, 755a, 801a, 807a, 808a, 813a, 828a], ["-", "-", "-", 756a, 802a, 808a, 814a, 815a, 820a, 835a], [748a, 754a, 757a, 804a, 810a, 816a, 822a, 823a, 828a, 843a], [758a, 804a, 807a, 814a, 820a, 826a, 832a, 833a, 838a, 853a], ["-", "-", "-", 824a, 830a, 836a, 842a, 843a, 848a, 903a], [818a, 824a, 827a, 834a, 840a, 846a, 852a, 853a, 858a, 913a], [833a, 839a, 842a, 849a, 855a, 901a, 907a, 908a, 913a, 928a], [910a, 918a, 924a, 929a, 935a, 942a, 949a, 952a, 954a, 1001a], [940a, 946a, 949a, 954a, 1000a, 1005a, 1010a, 1011a, 1013a, 1019a], [1010a, 1016a, 1019a, 1024a, 1030a, 1035a, 1040a, 1041a, 1043a, 1049a], [1040a, 1046a, 1049a, 1054a, 1100a, 1105a, 1110a, 1111a, 1113a, 1119a], [1110a, 1116a, 1119a, 1124a, 1130a, 1135a, 1140a, 1141a, 1143a, 1149a], [1140a, 1146a, 1149a, 1154a, 1200p, 1205p, 1210p, 1211p, 1213p, 1219p], [1210p, 1216p, 1219p, 1224p, 1230p, 1235p, 1240p, 1241p, 1243p, 1249p], [1240p, 1246p, 1249p, 1254p, 100p, 105p, 110p, 111p, 113p, 119p], [110p, 116p, 119p, 124p, 130p, 135p, 140p, 141p, 143p, 149p], [140p, 146p, 149p, 154p, 200p, 205p, 210p, 211p, 213p, 219p], [210p, 216p, 219p, 224p, 230p, 235p, 240p, 241p, 243p, 249p], [240p, 246p, 249p, 254p, 300p, 307p, 313p, 314p, 317p, 324p], [309p, 315p, 318p, 324p, 330p, 337p, 343p, 344p, 347p, 354p], [328p, 334p, 337p, 343p, 349p, 356p, 402p, 403p, 406p, 413p], [358p, 404p, 407p, 413p, 419p, 426p, 432p, 433p, 436p, 443p], [417p, 423p, 426p, 432p, 438p, 445p, 451p, 452p, 455p, 502p], [432p, 438p, 441p, 447p, 453p, 500p, 506p, 507p, 510p, 517p], [447p, 453p, 456p, 502p, 508p, 515p, 521p, 522p, 525p, 532p], [506p, 512p, 515p, 521p, 527p, 534p, 540p, 541p, 544p, 551p], [512p, 518p, 521p, 527p, 533p, 540p, "-", "-", "-", "-"], [521p, 527p, 530p, 536p, 542p, 549p, 555p, 556p, 559p, 606p], [536p, 542p, 545p, 551p, 557p, 604p, 610p, 611p, 614p, 621p], [546p, 552p, 555p, 601p, 607p, 614p, "-", "-", "-", "-"], [555p, 601p, 604p, 610p, 616p, 623p, 629p, 630p, 632p, 638p], [610p, 616p, 619p, 625p, 631p, 636p, 641p, 642p, 644p, 650p], [710p, 716p, 719p, 724p, 730p, 735p, 740p, 741p, 743p, 749p], [810p, 816p, 819p, 824p, 830p, 835p, 840p, 841p, 843p, 849p], [910p, 916p, 919p, 924p, 930p, 935p, 940p, 941p, 943p, 949p], [1010p, 1016p, 1019p, 1024p, 1030p, 1035p, 1040p, 1041p, 1043p, 1049p], [1110p, 1116p, 1119p, 1124p, 1130p, 1135p, "-", "-", "-", "-"]] stop_times: [["-", "-", "-", 549a, 555a, 601a, 606a, 607a, 610a, 617a], [609a, 615a, 618a, 624a, 630a, 636a, 641a, 642a, 645a, 652a], [639a, 645a, 648a, 654a, 700a, 706a, 711a, 712a, 715a, 722a], ["-", "-", "-", 707a, 713a, 719a, 724a, 725a, 728a, 741a], [703a, 709a, 712a, 718a, 724a, 730a, 736a, 737a, 742a, 757a], ["-", "-", "-", 726a, 732a, 738a, 744a, 745a, 750a, 805a], [718a, 724a, 727a, 734a, 740a, 746a, 752a, 753a, 758a, 813a], ["-", "-", "-", 742a, 748a, 754a, 800a, 801a, 806a, 821a], [733a, 739a, 742a, 749a, 755a, 801a, 807a, 808a, 813a, 828a], ["-", "-", "-", 756a, 802a, 808a, 814a, 815a, 820a, 835a], [748a, 754a, 757a, 804a, 810a, 816a, 822a, 823a, 828a, 843a], [758a, 804a, 807a, 814a, 820a, 826a, 832a, 833a, 838a, 853a], ["-", "-", "-", 824a, 830a, 836a, 842a, 843a, 848a, 903a], [818a, 824a, 827a, 834a, 840a, 846a, 852a, 853a, 858a, 913a], [833a, 839a, 842a, 849a, 855a, 901a, 907a, 908a, 913a, 928a], [910a, 918a, 924a, 929a, 935a, 942a, 949a, 952a, 954a, 1001a], [940a, 946a, 949a, 954a, 1000a, 1005a, 1010a, 1011a, 1013a, 1019a], [1010a, 1016a, 1019a, 1024a, 1030a, 1035a, 1040a, 1041a, 1043a, 1049a], [1040a, 1046a, 1049a, 1054a, 1100a, 1105a, 1110a, 1111a, 1113a, 1119a], [1110a, 1116a, 1119a, 1124a, 1130a, 1135a, 1140a, 1141a, 1143a, 1149a], [1140a, 1146a, 1149a, 1154a, 1200p, 1205p, 1210p, 1211p, 1213p, 1219p], [1210p, 1216p, 1219p, 1224p, 1230p, 1235p, 1240p, 1241p, 1243p, 1249p], [1240p, 1246p, 1249p, 1254p, 100p, 105p, 110p, 111p, 113p, 119p], [110p, 116p, 119p, 124p, 130p, 135p, 140p, 141p, 143p, 149p], [140p, 146p, 149p, 154p, 200p, 205p, 210p, 211p, 213p, 219p], [210p, 216p, 219p, 224p, 230p, 235p, 240p, 241p, 243p, 249p], [240p, 246p, 249p, 254p, 300p, 307p, 313p, 314p, 317p, 324p], [309p, 315p, 318p, 324p, 330p, 337p, 343p, 344p, 347p, 354p], [328p, 334p, 337p, 343p, 349p, 356p, 402p, 403p, 406p, 413p], [358p, 404p, 407p, 413p, 419p, 426p, 432p, 433p, 436p, 443p], [417p, 423p, 426p, 432p, 438p, 445p, 451p, 452p, 455p, 502p], [432p, 438p, 441p, 447p, 453p, 500p, 506p, 507p, 510p, 517p], [447p, 453p, 456p, 502p, 508p, 515p, 521p, 522p, 525p, 532p], [506p, 512p, 515p, 521p, 527p, 534p, 540p, 541p, 544p, 551p], [512p, 518p, 521p, 527p, 533p, 540p, "-", "-", "-", "-"], [521p, 527p, 530p, 536p, 542p, 549p, 555p, 556p, 559p, 606p], [536p, 542p, 545p, 551p, 557p, 604p, 610p, 611p, 614p, 621p], [546p, 552p, 555p, 601p, 607p, 614p, "-", "-", "-", "-"], [555p, 601p, 604p, 610p, 616p, 623p, 629p, 630p, 632p, 638p], [610p, 616p, 619p, 625p, 631p, 636p, 641p, 642p, 644p, 650p], [710p, 716p, 719p, 724p, 730p, 735p, 740p, 741p, 743p, 749p], [810p, 816p, 819p, 824p, 830p, 835p, 840p, 841p, 843p, 849p], [910p, 916p, 919p, 924p, 930p, 935p, 940p, 941p, 943p, 949p], [1010p, 1016p, 1019p, 1024p, 1030p, 1035p, 1040p, 1041p, 1043p, 1049p], [1110p, 1116p, 1119p, 1124p, 1130p, 1135p, "-", "-", "-", "-"]]
   
--- ---
time_points: [Belconnen Community Bus Station (Platform 5), Westfield Bus Station (Platform 2), Cohen Street Bus Station (Platform 5), Kippax, Macgregor Shops, Charnwood Shops, Macgregor Shops, Kippax, Cohen Street Bus Station, Westfield Bus Station, Belconnen Community Bus Station] time_points: [Belconnen Community Bus Station (Platform 5), Westfield Bus Station (Platform 2), Cohen Street Bus Station (Platform 5), Kippax, Macgregor, Charnwood, Macgregor, Kippax, Cohen Street Bus Station, Westfield Bus Station, Belconnen Community Bus Station]
long_name: To Belconnen Community Bus Station long_name: To Belconnen Community Bus Station
between_stops: between_stops:
Belconnen Community Bus Station (Platform 5)-Westfield Bus Station (Platform 2): [] Belconnen Community Bus Station (Platform 5)-Westfield Bus Station (Platform 2): []
Westfield Bus Station-Belconnen Community Bus Station: [] Westfield Bus Station-Belconnen Community Bus Station: []
Cohen Street Bus Station-Westfield Bus Station: [] Cohen Street Bus Station-Westfield Bus Station: []
Westfield Bus Station (Platform 2)-Cohen Street Bus Station (Platform 5): [] Westfield Bus Station (Platform 2)-Cohen Street Bus Station (Platform 5): []
short_name: "43" short_name: "43"
stop_times: [["-", "-", "-", "-", 621a, 629a, 638a, 643a, 648a, 650a, 654a], ["-", "-", "-", "-", 640a, 648a, 657a, 702a, 707a, 709a, 713a], [644a, 646a, 650a, 655a, 700a, 708a, 717a, 722a, 727a, 729a, 733a], ["-", "-", "-", "-", 720a, 728a, 739a, 744a, 752a, 754a, 758a], ["-", "-", "-", "-", 741a, 749a, 800a, 805a, 813a, 815a, 819a], ["-", "-", "-", "-", 802a, 810a, 821a, 826a, 834a, 836a, 840a], ["-", "-", "-", "-", 824a, 832a, 843a, 848a, 856a, 858a, 902a], [823a, 825a, 829a, 837a, 842a, 850a, 901a, 906a, 914a, 916a, 920a], [843a, 845a, 849a, 857a, 902a, 910a, 921a, 926a, 933a, 935a, 939a], [903a, 905a, 909a, 917a, 922a, 930a, 939a, 944a, 952a, 954a, 958a], [1003a, 1005a, 1009a, 1015a, 1020a, 1028a, 1037a, 1042a, 1048a, 1050a, 1054a], [1103a, 1105a, 1109a, 1115a, 1120a, 1128a, 1137a, 1142a, 1148a, 1150a, 1154a], [1203p, 1205p, 1209p, 1215p, 1220p, 1228p, 1237p, 1242p, 1248p, 1250p, 1254p], [103p, 105p, 109p, 115p, 120p, 128p, 137p, 142p, 148p, 150p, 154p], [203p, 205p, 209p, 215p, 220p, 228p, 237p, 242p, 248p, 250p, 254p], [254p, 256p, 300p, 308p, 313p, 321p, 332p, 337p, 345p, 347p, 351p], [323p, 325p, 329p, 337p, 342p, 350p, 401p, 406p, 414p, 416p, 420p], [343p, 345p, 349p, 357p, 402p, 410p, 421p, 426p, 434p, 436p, 440p], [403p, 405p, 409p, 417p, 422p, 430p, 441p, 446p, 454p, 456p, 500p], [423p, 425p, 429p, 437p, 442p, 450p, 501p, 506p, 514p, 516p, 520p], [443p, 445p, 449p, 457p, 502p, 510p, 521p, 526p, 534p, 536p, 540p], [503p, 505p, 509p, 517p, 522p, 530p, 541p, 546p, 554p, 556p, 600p], [523p, 525p, 529p, 537p, 542p, 550p, 601p, 606p, 614p, 616p, 620p], [602p, 604p, 608p, 616p, 621p, 629p, 638p, 643p, 648p, 650p, 654p], [702p, 704p, 708p, 713p, 718p, 726p, 735p, 740p, 745p, 747p, 751p], [802p, 804p, 808p, 813p, 818p, 826p, 835p, 840p, 845p, 847p, 851p], [902p, 904p, 908p, 913p, 918p, 926p, 935p, 940p, 945p, 947p, 951p], [1002p, 1004p, 1008p, 1013p, 1018p, 1026p, 1035p, 1040p, 1045p, 1047p, 1051p], [1102p, 1104p, 1108p, 1113p, 1118p, 1126p, 1135p, "-", "-", "-", "-"], []] stop_times: [["-", "-", "-", "-", 621a, 629a, 638a, 643a, 648a, 650a, 654a], ["-", "-", "-", "-", 640a, 648a, 657a, 702a, 707a, 709a, 713a], [644a, 646a, 650a, 655a, 700a, 708a, 717a, 722a, 727a, 729a, 733a], ["-", "-", "-", "-", 720a, 728a, 739a, 744a, 752a, 754a, 758a], ["-", "-", "-", "-", 741a, 749a, 800a, 805a, 813a, 815a, 819a], ["-", "-", "-", "-", 802a, 810a, 821a, 826a, 834a, 836a, 840a], ["-", "-", "-", "-", 824a, 832a, 843a, 848a, 856a, 858a, 902a], [823a, 825a, 829a, 837a, 842a, 850a, 901a, 906a, 914a, 916a, 920a], [843a, 845a, 849a, 857a, 902a, 910a, 921a, 926a, 933a, 935a, 939a], [903a, 905a, 909a, 917a, 922a, 930a, 939a, 944a, 952a, 954a, 958a], [1003a, 1005a, 1009a, 1015a, 1020a, 1028a, 1037a, 1042a, 1048a, 1050a, 1054a], [1103a, 1105a, 1109a, 1115a, 1120a, 1128a, 1137a, 1142a, 1148a, 1150a, 1154a], [1203p, 1205p, 1209p, 1215p, 1220p, 1228p, 1237p, 1242p, 1248p, 1250p, 1254p], [103p, 105p, 109p, 115p, 120p, 128p, 137p, 142p, 148p, 150p, 154p], [203p, 205p, 209p, 215p, 220p, 228p, 237p, 242p, 248p, 250p, 254p], [254p, 256p, 300p, 308p, 313p, 321p, 332p, 337p, 345p, 347p, 351p], [323p, 325p, 329p, 337p, 342p, 350p, 401p, 406p, 414p, 416p, 420p], [343p, 345p, 349p, 357p, 402p, 410p, 421p, 426p, 434p, 436p, 440p], [403p, 405p, 409p, 417p, 422p, 430p, 441p, 446p, 454p, 456p, 500p], [423p, 425p, 429p, 437p, 442p, 450p, 501p, 506p, 514p, 516p, 520p], [443p, 445p, 449p, 457p, 502p, 510p, 521p, 526p, 534p, 536p, 540p], [503p, 505p, 509p, 517p, 522p, 530p, 541p, 546p, 554p, 556p, 600p], [523p, 525p, 529p, 537p, 542p, 550p, 601p, 606p, 614p, 616p, 620p], [602p, 604p, 608p, 616p, 621p, 629p, 638p, 643p, 648p, 650p, 654p], [702p, 704p, 708p, 713p, 718p, 726p, 735p, 740p, 745p, 747p, 751p], [802p, 804p, 808p, 813p, 818p, 826p, 835p, 840p, 845p, 847p, 851p], [902p, 904p, 908p, 913p, 918p, 926p, 935p, 940p, 945p, 947p, 951p], [1002p, 1004p, 1008p, 1013p, 1018p, 1026p, 1035p, 1040p, 1045p, 1047p, 1051p], [1102p, 1104p, 1108p, 1113p, 1118p, 1126p, 1135p, "-", "-", "-", "-"], []]
   
--- ---
time_points: [Kippax, Holt Shops, West Macgregor, Higgins Shops, Belconnen Way, Cohen Street Bus Station, Westfield Bus Station, Belconnen Community Bus Station] time_points: [Kippax, Holt, West Macgregor, Higgins, Belconnen Way, Cohen Street Bus Station, Westfield Bus Station, Belconnen Community Bus Station]
long_name: To Belconnen Community Bus Station long_name: To Belconnen Community Bus Station
between_stops: between_stops:
Westfield Bus Station-Belconnen Community Bus Station: [] Westfield Bus Station-Belconnen Community Bus Station: []
Cohen Street Bus Station-Westfield Bus Station: [] Cohen Street Bus Station-Westfield Bus Station: []
short_name: "44" short_name: "44"
stop_times: [[605a, 607a, 616a, 625a, 630a, 635a, 637a, 641a], [638a, 640a, 649a, 658a, 703a, 708a, 710a, 714a], [705a, 707a, 716a, 725a, 730a, 736a, 738a, 742a], ["-", "-", "-", 732a, 739a, 745a, 747a, 751a], [738a, 741a, 750a, 759a, 806a, 812a, 814a, 818a], [808a, 811a, 820a, 829a, 836a, 842a, 844a, 848a], [842a, 845a, 854a, 903a, 910a, 916a, 918a, 922a], [912a, 915a, 924a, 933a, 939a, 945a, 947a, 951a], [938a, 940a, 949a, 958a, 1004a, 1010a, 1012a, 1016a], [1037a, 1039a, 1048a, 1057a, 1103a, 1109a, 1111a, 1115a], [1137a, 1139a, 1148a, 1157a, 1203p, 1209p, 1211p, 1215p], [1237p, 1239p, 1248p, 1257p, 103p, 109p, 111p, 115p], [137p, 139p, 148p, 157p, 203p, 209p, 211p, 215p], [237p, 239p, 248p, 257p, 304p, 310p, 312p, 316p], [313p, 315p, 324p, 333p, 340p, 346p, 348p, 352p], [348p, 350p, 359p, 408p, 415p, 421p, 423p, 427p], [420p, 422p, 431p, 440p, 447p, 453p, 455p, 459p], [452p, 454p, 503p, 512p, 519p, 525p, 527p, 531p], [523p, 525p, 534p, 543p, 550p, 556p, 558p, 602p], [600p, 602p, 611p, 620p, 627p, 633p, 635p, 639p], [628p, 630p, 639p, 648p, 654p, 659p, 701p, 705p], [642p, 644p, 653p, 702p, 708p, 713p, 715p, 719p], [737p, 739p, 748p, 757p, 803p, 808p, 810p, 814p], [837p, 839p, 848p, 857p, 903p, 908p, 910p, 914p], [937p, 939p, 948p, 957p, 1003p, 1008p, 1010p, 1014p], [1037p, 1039p, 1048p, 1057p, 1103p, 1108p, 1110p, 1114p]] stop_times: [[605a, 607a, 616a, 625a, 630a, 635a, 637a, 641a], [638a, 640a, 649a, 658a, 703a, 708a, 710a, 714a], [705a, 707a, 716a, 725a, 730a, 736a, 738a, 742a], ["-", "-", "-", 732a, 739a, 745a, 747a, 751a], [738a, 741a, 750a, 759a, 806a, 812a, 814a, 818a], [808a, 811a, 820a, 829a, 836a, 842a, 844a, 848a], [842a, 845a, 854a, 903a, 910a, 916a, 918a, 922a], [912a, 915a, 924a, 933a, 939a, 945a, 947a, 951a], [938a, 940a, 949a, 958a, 1004a, 1010a, 1012a, 1016a], [1037a, 1039a, 1048a, 1057a, 1103a, 1109a, 1111a, 1115a], [1137a, 1139a, 1148a, 1157a, 1203p, 1209p, 1211p, 1215p], [1237p, 1239p, 1248p, 1257p, 103p, 109p, 111p, 115p], [137p, 139p, 148p, 157p, 203p, 209p, 211p, 215p], [237p, 239p, 248p, 257p, 304p, 310p, 312p, 316p], [313p, 315p, 324p, 333p, 340p, 346p, 348p, 352p], [348p, 350p, 359p, 408p, 415p, 421p, 423p, 427p], [420p, 422p, 431p, 440p, 447p, 453p, 455p, 459p], [452p, 454p, 503p, 512p, 519p, 525p, 527p, 531p], [523p, 525p, 534p, 543p, 550p, 556p, 558p, 602p], [600p, 602p, 611p, 620p, 627p, 633p, 635p, 639p], [628p, 630p, 639p, 648p, 654p, 659p, 701p, 705p], [642p, 644p, 653p, 702p, 708p, 713p, 715p, 719p], [737p, 739p, 748p, 757p, 803p, 808p, 810p, 814p], [837p, 839p, 848p, 857p, 903p, 908p, 910p, 914p], [937p, 939p, 948p, 957p, 1003p, 1008p, 1010p, 1014p], [1037p, 1039p, 1048p, 1057p, 1103p, 1108p, 1110p, 1114p]]
   
--- ---
time_points: [Belconnen Community Bus Station (Platform 5), Westfield Bus Station (Platform 2), Cohen Street Bus Station (Platform 5), Belconnen Way, Higgins Shops, West Macgregor, Holt Shops, Kippax] time_points: [Belconnen Community Bus Station (Platform 5), Westfield Bus Station (Platform 2), Cohen Street Bus Station (Platform 5), Belconnen Way, Higgins, West Macgregor, Holt, Kippax]
long_name: To Kippax long_name: To Kippax
between_stops: between_stops:
Belconnen Community Bus Station (Platform 5)-Westfield Bus Station (Platform 2): [] Belconnen Community Bus Station (Platform 5)-Westfield Bus Station (Platform 2): []
Westfield Bus Station (Platform 2)-Cohen Street Bus Station (Platform 5): [] Westfield Bus Station (Platform 2)-Cohen Street Bus Station (Platform 5): []
short_name: "44" short_name: "44"
stop_times: [[725a, 727a, 731a, 737a, 744a, 756a, 801a, 804a], [754a, 756a, 800a, 806a, 813a, 825a, 830a, 833a], [854a, 856a, 900a, 906a, 913a, 925a, 930a, 933a], [955a, 957a, 1001a, 1006a, 1012a, 1024a, 1029a, 1032a], [1055a, 1057a, 1101a, 1106a, 1112a, 1124a, 1129a, 1132a], [1155a, 1157a, 1201p, 1206p, 1212p, 1224p, 1229p, 1232p], [1255p, 1257p, 101p, 106p, 112p, 124p, 129p, 132p], [155p, 157p, 201p, 206p, 212p, 224p, 229p, 232p], [305p, 307p, 311p, 317p, 324p, 336p, 341p, 344p], [337p, 339p, 343p, 349p, 356p, 408p, 413p, 416p], [411p, 413p, 417p, 423p, 430p, 442p, 447p, 450p], [442p, 444p, 448p, 454p, 501p, 513p, 518p, 521p], [516p, 518p, 522p, 528p, 535p, 547p, 552p, 555p], [547p, 549p, 553p, 559p, 606p, 618p, 623p, 626p], [619p, 621p, 625p, 631p, 637p, 649p, 654p, 657p], [654p, 656p, 700p, 705p, 711p, 723p, 728p, 731p], [754p, 756p, 800p, 805p, 811p, 823p, 828p, 831p], [854p, 856p, 900p, 905p, 911p, 923p, 928p, 931p], [954p, 956p, 1000p, 1005p, 1011p, 1023p, 1028p, 1031p], [1054p, 1056p, 1100p, 1105p, 1111p, 1123p, 1128p, 1131p]] stop_times: [[725a, 727a, 731a, 737a, 744a, 756a, 801a, 804a], [754a, 756a, 800a, 806a, 813a, 825a, 830a, 833a], [854a, 856a, 900a, 906a, 913a, 925a, 930a, 933a], [955a, 957a, 1001a, 1006a, 1012a, 1024a, 1029a, 1032a], [1055a, 1057a, 1101a, 1106a, 1112a, 1124a, 1129a, 1132a], [1155a, 1157a, 1201p, 1206p, 1212p, 1224p, 1229p, 1232p], [1255p, 1257p, 101p, 106p, 112p, 124p, 129p, 132p], [155p, 157p, 201p, 206p, 212p, 224p, 229p, 232p], [305p, 307p, 311p, 317p, 324p, 336p, 341p, 344p], [337p, 339p, 343p, 349p, 356p, 408p, 413p, 416p], [411p, 413p, 417p, 423p, 430p, 442p, 447p, 450p], [442p, 444p, 448p, 454p, 501p, 513p, 518p, 521p], [516p, 518p, 522p, 528p, 535p, 547p, 552p, 555p], [547p, 549p, 553p, 559p, 606p, 618p, 623p, 626p], [619p, 621p, 625p, 631p, 637p, 649p, 654p, 657p], [654p, 656p, 700p, 705p, 711p, 723p, 728p, 731p], [754p, 756p, 800p, 805p, 811p, 823p, 828p, 831p], [854p, 856p, 900p, 905p, 911p, 923p, 928p, 931p], [954p, 956p, 1000p, 1005p, 1011p, 1023p, 1028p, 1031p], [1054p, 1056p, 1100p, 1105p, 1111p, 1123p, 1128p, 1131p]]
   
--- ---
time_points: [Cohen Street Bus Station (Platform 1), Westfield Bus Station (Platform 1), Belconnen Community Bus Station (Platform 3), Copland College, Tillyard / Spalding, Charnwood Shops, Kerrigan / Lhotsky, Charnwood Shops, Tillyard / Spalding, Copland College, Belconnen Community Bus Station, Westfield Bus Station, Cohen Street Bus Station] time_points: [Cohen Street Bus Station (Platform 1), Westfield Bus Station (Platform 1), Belconnen Community Bus Station (Platform 3), Copland College, Tillyard / Spalding, Charnwood, Kerrigan / Lhotsky, Charnwood, Tillyard / Spalding, Copland College, Belconnen Community Bus Station, Westfield Bus Station, Cohen Street Bus Station]
long_name: To Cohen Street Bus Station long_name: To Cohen Street Bus Station
between_stops: between_stops:
Westfield Bus Station-Cohen Street Bus Station: [] Westfield Bus Station-Cohen Street Bus Station: []
Belconnen Community Bus Station-Westfield Bus Station: [] Belconnen Community Bus Station-Westfield Bus Station: []
Westfield Bus Station (Platform 1)-Belconnen Community Bus Station (Platform 3): [] Westfield Bus Station (Platform 1)-Belconnen Community Bus Station (Platform 3): []
Cohen Street Bus Station (Platform 1)-Westfield Bus Station (Platform 1): [] Cohen Street Bus Station (Platform 1)-Westfield Bus Station (Platform 1): []
short_name: "45" short_name: "45"
stop_times: [["-", "-", "-", "-", "-", 627a, 632a, 638a, 640a, 648a, 658a, 700a, 705a], ["-", "-", "-", "-", "-", 657a, 702a, 708a, 710a, 718a, 728a, 730a, 735a], ["-", "-", "-", "-", "-", 729a, 734a, 740a, 742a, 750a, 800a, 802a, 807a], ["-", "-", "-", "-", "-", 759a, 804a, 810a, 812a, 820a, 830a, 832a, 837a], ["-", "-", "-", "-", "-", 822a, 827a, 833a, 835a, 843a, 853a, 855a, 900a], ["-", "-", "-", "-", "-", 844a, 849a, 855a, 857a, 905a, 915a, 917a, 922a], [828a, 830a, 834a, 842a, 850a, 852a, 857a, 903a, 905a, 913a, 923a, 925a, 930a], [858a, 900a, 904a, 912a, 920a, 922a, 927a, 933a, 935a, 943a, 953a, 955a, 1000a], [921a, 923a, 927a, 935a, 943a, 945a, 950a, 956a, 958a, 1006a, 1016a, 1018a, 1023a], [1021a, 1023a, 1027a, 1035a, 1043a, 1045a, 1050a, 1056a, 1058a, 1106a, 1116a, 1118a, 1123a], [1121a, 1123a, 1127a, 1135a, 1143a, 1145a, 1150a, 1156a, 1158a, 1206p, 1216p, 1218p, 1223p], [1221p, 1223p, 1227p, 1235p, 1243p, 1245p, 1250p, 1256p, 1258p, 106p, 116p, 118p, 123p], [121p, 123p, 127p, 135p, 143p, 145p, 150p, 156p, 158p, 206p, 216p, 218p, 223p], [221p, 223p, 227p, 235p, 243p, 245p, 250p, 256p, 258p, 306p, 316p, 318p, 323p], [258p, 300p, 304p, 312p, 320p, 322p, 327p, 333p, 335p, 343p, 353p, 355p, 400p], [328p, 330p, 334p, 342p, 350p, 352p, 357p, 403p, 405p, 413p, 423p, 425p, 430p], [358p, 400p, 404p, 412p, 420p, 422p, 427p, 433p, 435p, 443p, 453p, 455p, 500p], [428p, 430p, 434p, 442p, 450p, 452p, 457p, 503p, 505p, 513p, 523p, 525p, 530p], [458p, 500p, 504p, 512p, 520p, 522p, 527p, 533p, 535p, 543p, 553p, 555p, 600p], [528p, 530p, 534p, 542p, 550p, 552p, 557p, 603p, 605p, 613p, 623p, 625p, 630p], [558p, 600p, 604p, 612p, 620p, 622p, 627p, 633p, 635p, 643p, 652p, 654p, 659p], [621p, 623p, 627p, 634p, 642p, 644p, 649p, 655p, 657p, 705p, 714p, 716p, 721p], [720p, 722p, 726p, 733p, 741p, 743p, 748p, 754p, 756p, 804p, 813p, 815p, 820p], [820p, 822p, 826p, 833p, 841p, 843p, 848p, 854p, 856p, 904p, 913p, 915p, 920p], [920p, 922p, 926p, 933p, 941p, 943p, 948p, 954p, 956p, 1004p, 1013p, 1015p, 1020p], [1020p, 1022p, 1026p, 1033p, 1041p, 1043p, 1048p, 1054p, 1056p, 1104p, 1113p, 1115p, 1120p], [1120p, 1122p, 1126p, 1133p, 1141p, 1143p, 1148p, 1154p, "-", "-", "-", "-", "-"], []] stop_times: [["-", "-", "-", "-", "-", 627a, 632a, 638a, 640a, 648a, 658a, 700a, 705a], ["-", "-", "-", "-", "-", 657a, 702a, 708a, 710a, 718a, 728a, 730a, 735a], ["-", "-", "-", "-", "-", 729a, 734a, 740a, 742a, 750a, 800a, 802a, 807a], ["-", "-", "-", "-", "-", 759a, 804a, 810a, 812a, 820a, 830a, 832a, 837a], ["-", "-", "-", "-", "-", 822a, 827a, 833a, 835a, 843a, 853a, 855a, 900a], ["-", "-", "-", "-", "-", 844a, 849a, 855a, 857a, 905a, 915a, 917a, 922a], [828a, 830a, 834a, 842a, 850a, 852a, 857a, 903a, 905a, 913a, 923a, 925a, 930a], [858a, 900a, 904a, 912a, 920a, 922a, 927a, 933a, 935a, 943a, 953a, 955a, 1000a], [921a, 923a, 927a, 935a, 943a, 945a, 950a, 956a, 958a, 1006a, 1016a, 1018a, 1023a], [1021a, 1023a, 1027a, 1035a, 1043a, 1045a, 1050a, 1056a, 1058a, 1106a, 1116a, 1118a, 1123a], [1121a, 1123a, 1127a, 1135a, 1143a, 1145a, 1150a, 1156a, 1158a, 1206p, 1216p, 1218p, 1223p], [1221p, 1223p, 1227p, 1235p, 1243p, 1245p, 1250p, 1256p, 1258p, 106p, 116p, 118p, 123p], [121p, 123p, 127p, 135p, 143p, 145p, 150p, 156p, 158p, 206p, 216p, 218p, 223p], [221p, 223p, 227p, 235p, 243p, 245p, 250p, 256p, 258p, 306p, 316p, 318p, 323p], [258p, 300p, 304p, 312p, 320p, 322p, 327p, 333p, 335p, 343p, 353p, 355p, 400p], [328p, 330p, 334p, 342p, 350p, 352p, 357p, 403p, 405p, 413p, 423p, 425p, 430p], [358p, 400p, 404p, 412p, 420p, 422p, 427p, 433p, 435p, 443p, 453p, 455p, 500p], [428p, 430p, 434p, 442p, 450p, 452p, 457p, 503p, 505p, 513p, 523p, 525p, 530p], [458p, 500p, 504p, 512p, 520p, 522p, 527p, 533p, 535p, 543p, 553p, 555p, 600p], [528p, 530p, 534p, 542p, 550p, 552p, 557p, 603p, 605p, 613p, 623p, 625p, 630p], [558p, 600p, 604p, 612p, 620p, 622p, 627p, 633p, 635p, 643p, 652p, 654p, 659p], [621p, 623p, 627p, 634p, 642p, 644p, 649p, 655p, 657p, 705p, 714p, 716p, 721p], [720p, 722p, 726p, 733p, 741p, 743p, 748p, 754p, 756p, 804p, 813p, 815p, 820p], [820p, 822p, 826p, 833p, 841p, 843p, 848p, 854p, 856p, 904p, 913p, 915p, 920p], [920p, 922p, 926p, 933p, 941p, 943p, 948p, 954p, 956p, 1004p, 1013p, 1015p, 1020p], [1020p, 1022p, 1026p, 1033p, 1041p, 1043p, 1048p, 1054p, 1056p, 1104p, 1113p, 1115p, 1120p], [1120p, 1122p, 1126p, 1133p, 1141p, 1143p, 1148p, 1154p, "-", "-", "-", "-", "-"], []]
   
--- ---
time_points: [Woden Bus Station (Platform 14), Canberra Hospital, Red Hill, Manuka, Kings Ave / National Circuit, City Bus Station (Platform 4), Lyneham Shops Wattle Street, North Lyneham, Dickson] time_points: [Woden Bus Station (Platform 14), Canberra Hospital, Red Hill, Manuka, Kings Ave / National Circuit, City Bus Station (Platform 4), Lyneham / Wattle St, North Lyneham, Dickson / Antill St]
long_name: To Dickson long_name: To Dickson
between_stops: between_stops:
Woden Bus Station (Platform 14)-Canberra Hospital: [Wjz3mAg, Wjz3mPO, Wjz3mWn] Woden Bus Station (Platform 14)-Canberra Hospital: [Wjz3mAg, Wjz3mPO, Wjz3mWn]
short_name: "6" short_name: "6"
stop_times: [[618a, 626a, 638a, 645a, 650a, 701a, 713a, 719a, 725a], [653a, 701a, 713a, 720a, 725a, 737a, 751a, 759a, 806a], [723a, 731a, 745a, 753a, 758a, 812a, 826a, 834a, 841a], [753a, 803a, 817a, 825a, 830a, 844a, 858a, 906a, 913a], [823a, 833a, 847a, 855a, 900a, 914a, 928a, 936a, 943a], [853a, 903a, 917a, 925a, 930a, 944a, 956a, 1004a, 1011a], [923a, 933a, 945a, 952a, 957a, 1011a, 1023a, 1031a, 1038a], [1023a, 1033a, 1045a, 1052a, 1057a, 1111a, 1123a, 1131a, 1138a], [1123a, 1133a, 1145a, 1152a, 1157a, 1211p, 1223p, 1231p, 1238p], [1223p, 1233p, 1245p, 1252p, 1257p, 111p, 123p, 131p, 138p], [123p, 133p, 145p, 152p, 157p, 211p, 223p, 231p, 238p], [223p, 233p, 245p, 252p, 257p, 311p, 325p, 333p, 340p], ["-", "-", "-", "-", "-", 344p, 358p, 406p, 413p], [323p, 333p, 347p, 355p, 400p, 414p, 428p, 436p, 443p], [353p, 403p, 417p, 425p, 430p, 444p, 458p, 506p, 513p], [423p, 433p, 447p, 455p, 500p, 514p, 528p, 536p, 543p], [453p, 503p, 517p, 525p, 530p, 544p, 558p, 606p, 613p], [516p, 526p, 540p, 548p, 553p, 607p, 621p, 629p, 635p], [553p, 603p, 617p, 625p, 630p, 640p, 650p, 656p, 702p], [630p, 638p, 648p, 655p, 700p, 710p, 720p, 726p, 732p], [730p, 738p, 748p, 755p, 800p, 810p, 820p, 826p, 832p], [830p, 838p, 848p, 855p, 900p, 910p, 920p, 926p, 932p], [930p, 938p, 948p, 955p, 1000p, 1010p, 1020p, 1026p, 1032p], [1030p, 1038p, 1048p, 1055p, 1100p, 1110p, 1120p, 1126p, 1132p]] stop_times: [[618a, 626a, 638a, 645a, 650a, 701a, 713a, 719a, 725a], [653a, 701a, 713a, 720a, 725a, 737a, 751a, 759a, 806a], [723a, 731a, 745a, 753a, 758a, 812a, 826a, 834a, 841a], [753a, 803a, 817a, 825a, 830a, 844a, 858a, 906a, 913a], [823a, 833a, 847a, 855a, 900a, 914a, 928a, 936a, 943a], [853a, 903a, 917a, 925a, 930a, 944a, 956a, 1004a, 1011a], [923a, 933a, 945a, 952a, 957a, 1011a, 1023a, 1031a, 1038a], [1023a, 1033a, 1045a, 1052a, 1057a, 1111a, 1123a, 1131a, 1138a], [1123a, 1133a, 1145a, 1152a, 1157a, 1211p, 1223p, 1231p, 1238p], [1223p, 1233p, 1245p, 1252p, 1257p, 111p, 123p, 131p, 138p], [123p, 133p, 145p, 152p, 157p, 211p, 223p, 231p, 238p], [223p, 233p, 245p, 252p, 257p, 311p, 325p, 333p, 340p], ["-", "-", "-", "-", "-", 344p, 358p, 406p, 413p], [323p, 333p, 347p, 355p, 400p, 414p, 428p, 436p, 443p], [353p, 403p, 417p, 425p, 430p, 444p, 458p, 506p, 513p], [423p, 433p, 447p, 455p, 500p, 514p, 528p, 536p, 543p], [453p, 503p, 517p, 525p, 530p, 544p, 558p, 606p, 613p], [516p, 526p, 540p, 548p, 553p, 607p, 621p, 629p, 635p], [553p, 603p, 617p, 625p, 630p, 640p, 650p, 656p, 702p], [630p, 638p, 648p, 655p, 700p, 710p, 720p, 726p, 732p], [730p, 738p, 748p, 755p, 800p, 810p, 820p, 826p, 832p], [830p, 838p, 848p, 855p, 900p, 910p, 920p, 926p, 932p], [930p, 938p, 948p, 955p, 1000p, 1010p, 1020p, 1026p, 1032p], [1030p, 1038p, 1048p, 1055p, 1100p, 1110p, 1120p, 1126p, 1132p]]
   
--- ---
time_points: [Dickson, North Lyneham, Lyneham Shops Wattle Street, City Bus Station (Platform 4), Kings Ave / National Circuit, Manuka, Red Hill, Canberra Hospital, Woden Bus Station] time_points: [Dickson / Antill St, North Lyneham, Lyneham / Wattle St, City Bus Station (Platform 4), Kings Ave / National Circuit, Manuka, Red Hill, Canberra Hospital, Woden Bus Station]
long_name: To Woden Bus Station long_name: To Woden Bus Station
between_stops: between_stops:
Canberra Hospital-Woden Bus Station: [Wjz3mWn, Wjz3mPO, Wjz3mAg] Canberra Hospital-Woden Bus Station: [Wjz3mWn, Wjz3mPO, Wjz3mAg]
short_name: "6" short_name: "6"
stop_times: [["-", "-", "-", 650a, 658a, 703a, 710a, 720a, 728a], [648a, 655a, 701a, 715a, 723a, 728a, 736a, 750a, 758a], [718a, 725a, 731a, 747a, 759a, 804a, 812a, 826a, 834a], [748a, 756a, 804a, 820a, 830a, 838a, 846a, 903a, 911a], [818a, 827a, 836a, 852a, 904a, 909a, 917a, 931a, 939a], [848a, 856a, 903a, 919a, 931a, 936a, 943a, 955a, 1003a], [918a, 926a, 933a, 947a, 957a, 1002a, 1009a, 1021a, 1029a], [948a, 956a, 1003a, 1017a, 1027a, 1032a, 1039a, 1051a, 1059a], [1048a, 1056a, 1103a, 1117a, 1127a, 1132a, 1139a, 1151a, 1159a], [1148a, 1156a, 1203p, 1217p, 1227p, 1232p, 1239p, 1251p, 1259p], [1248p, 1256p, 103p, 117p, 127p, 132p, 139p, 151p, 159p], [148p, 156p, 203p, 217p, 227p, 232p, 239p, 251p, 259p], [248p, 256p, 303p, 319p, 331p, 336p, 344p, 358p, 406p], [318p, 326p, 333p, 349p, 401p, 406p, 414p, 428p, 436p], [348p, 356p, 403p, 419p, 431p, 436p, 444p, 458p, 506p], [418p, 426p, 433p, 449p, 501p, 506p, 514p, 528p, 536p], [448p, 456p, 503p, 519p, 531p, 536p, 544p, 558p, 606p], [518p, 526p, 533p, 549p, 601p, 606p, 614p, 628p, 636p], [548p, 556p, 603p, 619p, 631p, 636p, 643p, 653p, 701p], [640p, 647p, 653p, 705p, 713p, 718p, 725p, 735p, 743p], [740p, 747p, 753p, 805p, 813p, 818p, 825p, 835p, 843p], [840p, 847p, 853p, 905p, 913p, 918p, 925p, 935p, 943p], [940p, 947p, 953p, 1005p, 1013p, 1018p, 1025p, 1035p, 1043p], [1040p, 1047p, 1053p, 1105p, 1113p, 1118p, 1125p, 1135p, 1143p]] stop_times: [["-", "-", "-", 650a, 658a, 703a, 710a, 720a, 728a], [648a, 655a, 701a, 715a, 723a, 728a, 736a, 750a, 758a], [718a, 725a, 731a, 747a, 759a, 804a, 812a, 826a, 834a], [748a, 756a, 804a, 820a, 830a, 838a, 846a, 903a, 911a], [818a, 827a, 836a, 852a, 904a, 909a, 917a, 931a, 939a], [848a, 856a, 903a, 919a, 931a, 936a, 943a, 955a, 1003a], [918a, 926a, 933a, 947a, 957a, 1002a, 1009a, 1021a, 1029a], [948a, 956a, 1003a, 1017a, 1027a, 1032a, 1039a, 1051a, 1059a], [1048a, 1056a, 1103a, 1117a, 1127a, 1132a, 1139a, 1151a, 1159a], [1148a, 1156a, 1203p, 1217p, 1227p, 1232p, 1239p, 1251p, 1259p], [1248p, 1256p, 103p, 117p, 127p, 132p, 139p, 151p, 159p], [148p, 156p, 203p, 217p, 227p, 232p, 239p, 251p, 259p], [248p, 256p, 303p, 319p, 331p, 336p, 344p, 358p, 406p], [318p, 326p, 333p, 349p, 401p, 406p, 414p, 428p, 436p], [348p, 356p, 403p, 419p, 431p, 436p, 444p, 458p, 506p], [418p, 426p, 433p, 449p, 501p, 506p, 514p, 528p, 536p], [448p, 456p, 503p, 519p, 531p, 536p, 544p, 558p, 606p], [518p, 526p, 533p, 549p, 601p, 606p, 614p, 628p, 636p], [548p, 556p, 603p, 619p, 631p, 636p, 643p, 653p, 701p], [640p, 647p, 653p, 705p, 713p, 718p, 725p, 735p, 743p], [740p, 747p, 753p, 805p, 813p, 818p, 825p, 835p, 843p], [840p, 847p, 853p, 905p, 913p, 918p, 925p, 935p, 943p], [940p, 947p, 953p, 1005p, 1013p, 1018p, 1025p, 1035p, 1043p], [1040p, 1047p, 1053p, 1105p, 1113p, 1118p, 1125p, 1135p, 1143p]]
   
--- ---
time_points: [Tuggeranong Bus Station (Platform 3), Taverner St / Erindale Dr, Livingston Shops / Kambah, Athllon / Sulwood Kambah, Woden Bus Station, City Bus Station, City West] time_points: [Tuggeranong Bus Station (Platform 3), Taverner St / Erindale Dr, Kambah / Livingston St, Athllon / Sulwood Kambah, Woden Bus Station, City Bus Station, City West]
long_name: To City West long_name: To City West
between_stops: between_stops:
City Bus Station-City West: [] City Bus Station-City West: []
Athllon / Sulwood Kambah-Woden Bus Station: [Wjz2mTK, Wjz3gMq, Wjz3gQn, Wjz3gK-, Wjz3hL_, Wjz3iFK, Wjz3kwU, Wjz3kyX, Wjz3kAx] Athllon / Sulwood Kambah-Woden Bus Station: [Wjz2mTK, Wjz3gMq, Wjz3gQn, Wjz3gK-, Wjz3hL_, Wjz3iFK, Wjz3kwU, Wjz3kyX, Wjz3kAx]
Woden Bus Station-City Bus Station: [Wjz3m3b, Wjz3m3b, Wjz3eRR, Wjz3eZ4, Wjz4KNu, Wjz4KO9, Wjz5Nht] Woden Bus Station-City Bus Station: [Wjz3m3b, Wjz3m3b, Wjz3eRR, Wjz3eZ4, Wjz4KNu, Wjz4KO9, Wjz5Nht]
short_name: 61 161 short_name: 61 161
stop_times: [[630a, 641a, 646a, 651a, 658a, "-", "-"], [700a, 712a, 717a, 722a, 733a, "-", "-"], [726a, 739a, 746a, 751a, 805a, 819a, 822a], [740a, 754a, 759a, 804a, 813a, "-", "-"], [800a, 814a, 819a, 825a, 839a, "-", "-"], [837a, 851a, 856a, 901a, 910a, "-", "-"], [900a, 914a, 919a, 924a, 933a, "-", "-"], [930a, 943a, 948a, 953a, 1001a, "-", "-"], [1030a, 1043a, 1048a, 1053a, 1101a, "-", "-"], [1130a, 1143a, 1148a, 1153a, 1201p, "-", "-"], [1230p, 1243p, 1248p, 1253p, 101p, "-", "-"], [130p, 143p, 148p, 153p, 201p, "-", "-"], [230p, 243p, 248p, 253p, 301p, "-", "-"], [330p, 344p, 349p, 354p, 403p, "-", "-"], [400p, 414p, 419p, 424p, 433p, "-", "-"], [430p, 444p, 449p, 454p, 503p, "-", "-"], [500p, 514p, 519p, 524p, 533p, "-", "-"], [530p, 544p, 549p, 554p, 603p, "-", "-"], [600p, 614p, 619p, 624p, 633p, "-", "-"], [630p, 643p, 648p, 653p, 701p, "-", "-"], [730p, 743p, 748p, 753p, 801p, "-", "-"], [830p, 843p, 848p, 853p, 901p, "-", "-"], [930p, 943p, 948p, 953p, 1001p, "-", "-"], [1030p, 1043p, 1048p, 1053p, 1101p, "-", "-"], [1130p, 1143p, 1148p, 1153p, "-", "-", "-"], []] stop_times: [[630a, 641a, 646a, 651a, 658a, "-", "-"], [700a, 712a, 717a, 722a, 733a, "-", "-"], [726a, 739a, 746a, 751a, 805a, 819a, 822a], [740a, 754a, 759a, 804a, 813a, "-", "-"], [800a, 814a, 819a, 825a, 839a, "-", "-"], [837a, 851a, 856a, 901a, 910a, "-", "-"], [900a, 914a, 919a, 924a, 933a, "-", "-"], [930a, 943a, 948a, 953a, 1001a, "-", "-"], [1030a, 1043a, 1048a, 1053a, 1101a, "-", "-"], [1130a, 1143a, 1148a, 1153a, 1201p, "-", "-"], [1230p, 1243p, 1248p, 1253p, 101p, "-", "-"], [130p, 143p, 148p, 153p, 201p, "-", "-"], [230p, 243p, 248p, 253p, 301p, "-", "-"], [330p, 344p, 349p, 354p, 403p, "-", "-"], [400p, 414p, 419p, 424p, 433p, "-", "-"], [430p, 444p, 449p, 454p, 503p, "-", "-"], [500p, 514p, 519p, 524p, 533p, "-", "-"], [530p, 544p, 549p, 554p, 603p, "-", "-"], [600p, 614p, 619p, 624p, 633p, "-", "-"], [630p, 643p, 648p, 653p, 701p, "-", "-"], [730p, 743p, 748p, 753p, 801p, "-", "-"], [830p, 843p, 848p, 853p, 901p, "-", "-"], [930p, 943p, 948p, 953p, 1001p, "-", "-"], [1030p, 1043p, 1048p, 1053p, 1101p, "-", "-"], [1130p, 1143p, 1148p, 1153p, "-", "-", "-"], []]
   
--- ---
time_points: [City West, City Bus Station (Platform 1), Woden Bus Station (Platform 11), Athllon / Sulwood Kambah, Livingston Shops / Kambah, Taverner St / Erindale Dr, Tuggeranong Bus Station] time_points: [City West, City Bus Station (Platform 1), Woden Bus Station (Platform 11), Athllon / Sulwood Kambah, Kambah / Livingston St, Taverner St / Erindale Dr, Tuggeranong Bus Station]
long_name: To Tuggeranong Bus Station long_name: To Tuggeranong Bus Station
between_stops: between_stops:
Woden Bus Station (Platform 11)-Athllon / Sulwood Kambah: [Wjz3kAx, Wjz3kyX, Wjz3kwU, Wjz3iFK, Wjz3hL_, Wjz3gK-, Wjz3gQn, Wjz3gMq, Wjz2mTK] Woden Bus Station (Platform 11)-Athllon / Sulwood Kambah: [Wjz3kAx, Wjz3kyX, Wjz3kwU, Wjz3iFK, Wjz3hL_, Wjz3gK-, Wjz3gQn, Wjz3gMq, Wjz2mTK]
City Bus Station (Platform 1)-Woden Bus Station (Platform 11): [Wjz5Nht, Wjz4KO9, Wjz4KNu, Wjz3eZ4, Wjz3eRR, Wjz3m3b, Wjz3m3b] City Bus Station (Platform 1)-Woden Bus Station (Platform 11): [Wjz5Nht, Wjz4KO9, Wjz4KNu, Wjz3eZ4, Wjz3eRR, Wjz3m3b, Wjz3m3b]
City West-City Bus Station (Platform 1): [] City West-City Bus Station (Platform 1): []
short_name: 61 161 short_name: 61 161
stop_times: [["-", "-", 642a, 649a, 654a, 659a, 710a], ["-", "-", 712a, 719a, 724a, 729a, 743a], ["-", "-", 742a, 751a, 756a, 801a, 815a], ["-", "-", 812a, 821a, 826a, 831a, 845a], ["-", "-", 842a, 859a, 905a, 909a, 920a], ["-", "-", 912a, 921a, 926a, 931a, 944a], ["-", "-", 1012a, 1020a, 1025a, 1030a, 1043a], ["-", "-", 1112a, 1120a, 1125a, 1130a, 1143a], ["-", "-", 1212p, 1220p, 1225p, 1230p, 1243p], ["-", "-", 112p, 120p, 125p, 130p, 143p], ["-", "-", 212p, 220p, 225p, 230p, 243p], ["-", "-", 320p, 329p, 334p, 339p, 353p], ["-", "-", 342p, 351p, 356p, 401p, 415p], ["-", "-", 412p, 421p, 426p, 431p, 445p], ["-", "-", 442p, 451p, 456p, 501p, 515p], ["-", "-", 512p, 521p, 526p, 531p, 545p], [520p, 526p, 542p, 551p, 556p, 601p, 615p], ["-", "-", 612p, 621p, 626p, 631p, 644p], ["-", "-", 712p, 720p, 725p, 730p, 743p], ["-", "-", 810p, 818p, 823p, 828p, 841p], ["-", "-", 910p, 918p, 923p, 928p, 941p], ["-", "-", 1010p, 1018p, 1023p, 1028p, 1041p], ["-", "-", 1112p, 1120p, 1125p, 1130p, 1143p], []] stop_times: [["-", "-", 642a, 649a, 654a, 659a, 710a], ["-", "-", 712a, 719a, 724a, 729a, 743a], ["-", "-", 742a, 751a, 756a, 801a, 815a], ["-", "-", 812a, 821a, 826a, 831a, 845a], ["-", "-", 842a, 859a, 905a, 909a, 920a], ["-", "-", 912a, 921a, 926a, 931a, 944a], ["-", "-", 1012a, 1020a, 1025a, 1030a, 1043a], ["-", "-", 1112a, 1120a, 1125a, 1130a, 1143a], ["-", "-", 1212p, 1220p, 1225p, 1230p, 1243p], ["-", "-", 112p, 120p, 125p, 130p, 143p], ["-", "-", 212p, 220p, 225p, 230p, 243p], ["-", "-", 320p, 329p, 334p, 339p, 353p], ["-", "-", 342p, 351p, 356p, 401p, 415p], ["-", "-", 412p, 421p, 426p, 431p, 445p], ["-", "-", 442p, 451p, 456p, 501p, 515p], ["-", "-", 512p, 521p, 526p, 531p, 545p], [520p, 526p, 542p, 551p, 556p, 601p, 615p], ["-", "-", 612p, 621p, 626p, 631p, 644p], ["-", "-", 712p, 720p, 725p, 730p, 743p], ["-", "-", 810p, 818p, 823p, 828p, 841p], ["-", "-", 910p, 918p, 923p, 928p, 941p], ["-", "-", 1010p, 1018p, 1023p, 1028p, 1041p], ["-", "-", 1112p, 1120p, 1125p, 1130p, 1143p], []]
   
--- ---
time_points: [Tuggeranong Bus Station (Platform 7), Calwell Shops, Chisholm Shops, Erindale / Sternberg Cres, Woden Bus Station (Platform 10), Kings Ave / National Circuit, Russell Offices, City Bus Station (Platform 11), City West] time_points: [Tuggeranong Bus Station (Platform 7), Calwell, Chisholm, Erindale / Sternberg Cres, Woden Bus Station (Platform 10), Kings Ave / National Circuit, Russell Offices, City Bus Station (Platform 11), City West]
long_name: To City West long_name: To City West
between_stops: between_stops:
Russell Offices-City Bus Station (Platform 11): [Wjz4-WZ, Wjz4-WL, Wjz4-Rc, Wjz4-KO, Wjz4_wS, Wjz4_jm, Wjz4T-X, Wjz5MEL, Wjz5MsT, Wjz5MsD, Wjz5Nht] Russell Offices-City Bus Station (Platform 11): [Wjz4-WZ, Wjz4-WL, Wjz4-Rc, Wjz4-KO, Wjz4_wS, Wjz4_jm, Wjz4T-X, Wjz5MEL, Wjz5MsT, Wjz5MsD, Wjz5Nht]
Kings Ave / National Circuit-Russell Offices: [Wjz4Quk, Wjz4RwH, Wjz4RFJ] Kings Ave / National Circuit-Russell Offices: [Wjz4Quk, Wjz4RwH, Wjz4RFJ]
City Bus Station (Platform 11)-City West: [] City Bus Station (Platform 11)-City West: []
short_name: 67 267 short_name: 67 267
stop_times: [[603a, 615a, 627a, 635a, 644a, "-", "-", "-", "-"], [633a, 645a, 657a, 705a, 716a, "-", "-", "-", "-"], [702a, 715a, 726a, 735a, 750a, 804a, 808a, 818a, 821a], [718a, 730a, 745a, 755a, 809a, "-", "-", "-", "-"], [731a, 746a, 800a, 810a, 825a, 839a, 843a, 853a, 856a], [803a, 817a, 832a, 842a, 856a, "-", "-", "-", "-"], [833a, 847a, 902a, 912a, 926a, "-", "-", "-", "-"], [903a, 917a, 932a, 940a, 953a, "-", "-", "-", "-"], [1003a, 1016a, 1028a, 1036a, 1049a, "-", "-", "-", "-"], [1103a, 1116a, 1128a, 1136a, 1149a, "-", "-", "-", "-"], [1203p, 1216p, 1228p, 1236p, 1249p, "-", "-", "-", "-"], [103p, 116p, 128p, 136p, 149p, "-", "-", "-", "-"], [203p, 216p, 228p, 236p, 249p, "-", "-", "-", "-"], [303p, 317p, 332p, 342p, 356p, "-", "-", "-", "-"], [333p, 347p, 402p, 412p, 426p, "-", "-", "-", "-"], [403p, 417p, 432p, 442p, 456p, "-", "-", "-", "-"], [433p, 447p, 502p, 512p, 526p, "-", "-", "-", "-"], [503p, 517p, 532p, 542p, 556p, "-", "-", "-", "-"], [533p, 547p, 602p, 612p, 626p, "-", "-", "-", "-"], [603p, 617p, 632p, 640p, 653p, "-", "-", "-", "-"], [703p, 716p, 728p, 736p, 749p, "-", "-", "-", "-"], [803p, 816p, 828p, 836p, 849p, "-", "-", "-", "-"], [903p, 916p, 928p, 936p, 949p, "-", "-", "-", "-"], [1003p, 1016p, 1028p, 1036p, 1049p, "-", "-", "-", "-"], [1103p, 1116p, 1128p, 1136p, "-", "-", "-", "-", "-"]] stop_times: [[603a, 615a, 627a, 635a, 644a, "-", "-", "-", "-"], [633a, 645a, 657a, 705a, 716a, "-", "-", "-", "-"], [702a, 715a, 726a, 735a, 750a, 804a, 808a, 818a, 821a], [718a, 730a, 745a, 755a, 809a, "-", "-", "-", "-"], [731a, 746a, 800a, 810a, 825a, 839a, 843a, 853a, 856a], [803a, 817a, 832a, 842a, 856a, "-", "-", "-", "-"], [833a, 847a, 902a, 912a, 926a, "-", "-", "-", "-"], [903a, 917a, 932a, 940a, 953a, "-", "-", "-", "-"], [1003a, 1016a, 1028a, 1036a, 1049a, "-", "-", "-", "-"], [1103a, 1116a, 1128a, 1136a, 1149a, "-", "-", "-", "-"], [1203p, 1216p, 1228p, 1236p, 1249p, "-", "-", "-", "-"], [103p, 116p, 128p, 136p, 149p, "-", "-", "-", "-"], [203p, 216p, 228p, 236p, 249p, "-", "-", "-", "-"], [303p, 317p, 332p, 342p, 356p, "-", "-", "-", "-"], [333p, 347p, 402p, 412p, 426p, "-", "-", "-", "-"], [403p, 417p, 432p, 442p, 456p, "-", "-", "-", "-"], [433p, 447p, 502p, 512p, 526p, "-", "-", "-", "-"], [503p, 517p, 532p, 542p, 556p, "-", "-", "-", "-"], [533p, 547p, 602p, 612p, 626p, "-", "-", "-", "-"], [603p, 617p, 632p, 640p, 653p, "-", "-", "-", "-"], [703p, 716p, 728p, 736p, 749p, "-", "-", "-", "-"], [803p, 816p, 828p, 836p, 849p, "-", "-", "-", "-"], [903p, 916p, 928p, 936p, 949p, "-", "-", "-", "-"], [1003p, 1016p, 1028p, 1036p, 1049p, "-", "-", "-", "-"], [1103p, 1116p, 1128p, 1136p, "-", "-", "-", "-", "-"]]
   
--- ---
time_points: [City West, City Bus Station (Platform 10), Russell Offices, Kings Ave / National Circuit, Woden Bus Station (Platform 5), Erindale / Sternberg Cres, Bugden Sternberg, Chisholm Shops, Calwell Shops, Tuggeranong Bus Station] time_points: [City West, City Bus Station (Platform 10), Russell Offices, Kings Ave / National Circuit, Woden Bus Station (Platform 5), Erindale / Sternberg Cres, Bugden Sternberg, Chisholm, Calwell, Tuggeranong Bus Station]
long_name: To Tuggeranong Bus Station long_name: To Tuggeranong Bus Station
between_stops: between_stops:
Russell Offices-Kings Ave / National Circuit: [Wjz4RFJ, Wjz4RwH, Wjz4Quk] Russell Offices-Kings Ave / National Circuit: [Wjz4RFJ, Wjz4RwH, Wjz4Quk]
City West-City Bus Station (Platform 10): [] City West-City Bus Station (Platform 10): []
City Bus Station (Platform 10)-Russell Offices: [Wjz5Nht, Wjz5MsD, Wjz5MsT, Wjz5MEL, Wjz4T-X, Wjz4_jm, Wjz4_wS, Wjz4-KO, Wjz4-Rc, Wjz4-WL, Wjz4-WZ] City Bus Station (Platform 10)-Russell Offices: [Wjz5Nht, Wjz5MsD, Wjz5MsT, Wjz5MEL, Wjz4T-X, Wjz4_jm, Wjz4_wS, Wjz4-KO, Wjz4-Rc, Wjz4-WL, Wjz4-WZ]
short_name: 67 267 short_name: 67 267
stop_times: [["-", "-", "-", "-", "-", "-", 601a, 608a, 618a, 632a], ["-", "-", "-", "-", 617a, 626a, 626a, 633a, 643a, 657a], ["-", "-", "-", "-", 647a, 656a, 656a, 703a, 713a, 727a], ["-", "-", "-", "-", 717a, 726a, 726a, 734a, 746a, 803a], ["-", "-", "-", "-", 747a, 804a, 804a, 813a, 825a, 842a], ["-", "-", "-", "-", 817a, 834a, 834a, 843a, 855a, 912a], ["-", "-", "-", "-", 847a, 904a, 904a, 913a, 925a, 941a], ["-", "-", "-", "-", 917a, 933a, 933a, 940a, 949a, 1004a], ["-", "-", "-", "-", 1017a, 1030a, 1030a, 1037a, 1046a, 1101a], ["-", "-", "-", "-", 1117a, 1130a, 1130a, 1137a, 1146a, 1201p], ["-", "-", "-", "-", 1217p, 1230p, 1230p, 1237p, 1246p, 101p], ["-", "-", "-", "-", 117p, 130p, 130p, 137p, 146p, 201p], ["-", "-", "-", "-", 217p, 230p, 230p, 237p, 246p, 301p], ["-", "-", "-", "-", 247p, 300p, 300p, 310p, 325p, 341p], ["-", "-", "-", "-", 317p, 334p, 334p, 344p, 359p, 415p], ["-", "-", "-", "-", 347p, 404p, 404p, 414p, 429p, 445p], ["-", "-", "-", "-", 417p, 434p, 434p, 444p, 459p, 515p], ["-", "-", "-", "-", 447p, 504p, 504p, 514p, 529p, 545p], [430p, 436p, 445p, 448p, 503p, 520p, 520p, 530p, 545p, 601p], [500p, 506p, 515p, 518p, 533p, 550p, 550p, 600p, 615p, 631p], [544p, 550p, 559p, 602p, 617p, 633p, 633p, 640p, 649p, 704p], ["-", "-", "-", "-", 717p, 730p, 730p, 737p, 746p, 801p], ["-", "-", "-", "-", 817p, 830p, 830p, 837p, 846p, 901p], ["-", "-", "-", "-", 917p, 930p, 930p, 937p, 946p, 1001p], ["-", "-", "-", "-", 1017p, 1030p, 1030p, 1037p, 1046p, 1101p], ["-", "-", "-", "-", 1117p, 1130p, 1130p, 1137p, 1146p, 1201a]] stop_times: [["-", "-", "-", "-", "-", "-", 601a, 608a, 618a, 632a], ["-", "-", "-", "-", 617a, 626a, 626a, 633a, 643a, 657a], ["-", "-", "-", "-", 647a, 656a, 656a, 703a, 713a, 727a], ["-", "-", "-", "-", 717a, 726a, 726a, 734a, 746a, 803a], ["-", "-", "-", "-", 747a, 804a, 804a, 813a, 825a, 842a], ["-", "-", "-", "-", 817a, 834a, 834a, 843a, 855a, 912a], ["-", "-", "-", "-", 847a, 904a, 904a, 913a, 925a, 941a], ["-", "-", "-", "-", 917a, 933a, 933a, 940a, 949a, 1004a], ["-", "-", "-", "-", 1017a, 1030a, 1030a, 1037a, 1046a, 1101a], ["-", "-", "-", "-", 1117a, 1130a, 1130a, 1137a, 1146a, 1201p], ["-", "-", "-", "-", 1217p, 1230p, 1230p, 1237p, 1246p, 101p], ["-", "-", "-", "-", 117p, 130p, 130p, 137p, 146p, 201p], ["-", "-", "-", "-", 217p, 230p, 230p, 237p, 246p, 301p], ["-", "-", "-", "-", 247p, 300p, 300p, 310p, 325p, 341p], ["-", "-", "-", "-", 317p, 334p, 334p, 344p, 359p, 415p], ["-", "-", "-", "-", 347p, 404p, 404p, 414p, 429p, 445p], ["-", "-", "-", "-", 417p, 434p, 434p, 444p, 459p, 515p], ["-", "-", "-", "-", 447p, 504p, 504p, 514p, 529p, 545p], [430p, 436p, 445p, 448p, 503p, 520p, 520p, 530p, 545p, 601p], [500p, 506p, 515p, 518p, 533p, 550p, 550p, 600p, 615p, 631p], [544p, 550p, 559p, 602p, 617p, 633p, 633p, 640p, 649p, 704p], ["-", "-", "-", "-", 717p, 730p, 730p, 737p, 746p, 801p], ["-", "-", "-", "-", 817p, 830p, 830p, 837p, 846p, 901p], ["-", "-", "-", "-", 917p, 930p, 930p, 937p, 946p, 1001p], ["-", "-", "-", "-", 1017p, 1030p, 1030p, 1037p, 1046p, 1101p], ["-", "-", "-", "-", 1117p, 1130p, 1130p, 1137p, 1146p, 1201a]]
   
--- ---
time_points: [Cohen Street Bus Station (Platform 2), Westfield Bus Station (Platform 1), Belconnen Community Bus Station (Platform 3), Australian Institute of Sport, Dickson, Merici College, City Bus Station] time_points: [Cohen Street Bus Station (Platform 2), Westfield Bus Station (Platform 1), Belconnen Community Bus Station (Platform 3), Australian Institute of Sport, Dickson / Antill St, Merici College, City Bus Station]
long_name: To City Bus Station long_name: To City Bus Station
between_stops: between_stops:
Westfield Bus Station (Platform 1)-Belconnen Community Bus Station (Platform 3): [] Westfield Bus Station (Platform 1)-Belconnen Community Bus Station (Platform 3): []
Cohen Street Bus Station (Platform 2)-Westfield Bus Station (Platform 1): [] Cohen Street Bus Station (Platform 2)-Westfield Bus Station (Platform 1): []
short_name: "7" short_name: "7"
stop_times: [[541a, 543a, 547a, 600a, 608a, 615a, 623a], [611a, 613a, 617a, 630a, 638a, 645a, 653a], [641a, 643a, 647a, 700a, 708a, 715a, 723a], [711a, 713a, 717a, 730a, 739a, 746a, 755a], [741a, 743a, 747a, 804a, 817a, 824a, 837a], [811a, 813a, 817a, 832a, 841a, 848a, 903a], [841a, 843a, 847a, 902a, 911a, 918a, 927a], [915a, 917a, 921a, 935a, 943a, 950a, 958a], [946a, 948a, 952a, 1005a, 1013a, 1020a, 1028a], [1016a, 1018a, 1022a, 1035a, 1043a, 1050a, 1058a], [1046a, 1048a, 1052a, 1105a, 1113a, 1120a, 1128a], [1116a, 1118a, 1122a, 1135a, 1143a, 1150a, 1158a], [1146a, 1148a, 1152a, 1205p, 1213p, 1220p, 1228p], [1216p, 1218p, 1222p, 1235p, 1243p, 1250p, 1258p], [1246p, 1248p, 1252p, 105p, 113p, 120p, 128p], [116p, 118p, 122p, 135p, 143p, 150p, 158p], [146p, 148p, 152p, 205p, 213p, 220p, 228p], [216p, 218p, 222p, 235p, 243p, 250p, 258p], [246p, 248p, 252p, 306p, 314p, 321p, 330p], [311p, 313p, 317p, 332p, 340p, 347p, 356p], [341p, 343p, 347p, 402p, 410p, 417p, 426p], [411p, 413p, 417p, 432p, 440p, 447p, 456p], [441p, 443p, 447p, 502p, 510p, 517p, 526p], [511p, 513p, 517p, 532p, 540p, 547p, 601p], [541p, 543p, 547p, 602p, 610p, 617p, 626p], [646p, 648p, 652p, 705p, 713p, 719p, 727p], [746p, 748p, 752p, 805p, 813p, 819p, 827p], [846p, 848p, 852p, 905p, 913p, 919p, 927p], [946p, 948p, 952p, 1005p, 1013p, 1019p, 1027p], [1046p, 1048p, 1052p, 1105p, 1113p, 1119p, 1127p]] stop_times: [[541a, 543a, 547a, 600a, 608a, 615a, 623a], [611a, 613a, 617a, 630a, 638a, 645a, 653a], [641a, 643a, 647a, 700a, 708a, 715a, 723a], [711a, 713a, 717a, 730a, 739a, 746a, 755a], [741a, 743a, 747a, 804a, 817a, 824a, 837a], [811a, 813a, 817a, 832a, 841a, 848a, 903a], [841a, 843a, 847a, 902a, 911a, 918a, 927a], [915a, 917a, 921a, 935a, 943a, 950a, 958a], [946a, 948a, 952a, 1005a, 1013a, 1020a, 1028a], [1016a, 1018a, 1022a, 1035a, 1043a, 1050a, 1058a], [1046a, 1048a, 1052a, 1105a, 1113a, 1120a, 1128a], [1116a, 1118a, 1122a, 1135a, 1143a, 1150a, 1158a], [1146a, 1148a, 1152a, 1205p, 1213p, 1220p, 1228p], [1216p, 1218p, 1222p, 1235p, 1243p, 1250p, 1258p], [1246p, 1248p, 1252p, 105p, 113p, 120p, 128p], [116p, 118p, 122p, 135p, 143p, 150p, 158p], [146p, 148p, 152p, 205p, 213p, 220p, 228p], [216p, 218p, 222p, 235p, 243p, 250p, 258p], [246p, 248p, 252p, 306p, 314p, 321p, 330p], [311p, 313p, 317p, 332p, 340p, 347p, 356p], [341p, 343p, 347p, 402p, 410p, 417p, 426p], [411p, 413p, 417p, 432p, 440p, 447p, 456p], [441p, 443p, 447p, 502p, 510p, 517p, 526p], [511p, 513p, 517p, 532p, 540p, 547p, 601p], [541p, 543p, 547p, 602p, 610p, 617p, 626p], [646p, 648p, 652p, 705p, 713p, 719p, 727p], [746p, 748p, 752p, 805p, 813p, 819p, 827p], [846p, 848p, 852p, 905p, 913p, 919p, 927p], [946p, 948p, 952p, 1005p, 1013p, 1019p, 1027p], [1046p, 1048p, 1052p, 1105p, 1113p, 1119p, 1127p]]
   
--- ---
time_points: [City Bus Station (Platform 5), Merici College, Dickson, Australian Institute of Sport, Belconnen Community Bus Station, Westfield Bus Station, Cohen Street Bus Station] time_points: [City Bus Station (Platform 5), Merici College, Dickson / Antill St, Australian Institute of Sport, Belconnen Community Bus Station, Westfield Bus Station, Cohen Street Bus Station]
long_name: To Cohen Street Bus Station long_name: To Cohen Street Bus Station
between_stops: between_stops:
Westfield Bus Station-Cohen Street Bus Station: [] Westfield Bus Station-Cohen Street Bus Station: []
Belconnen Community Bus Station-Westfield Bus Station: [] Belconnen Community Bus Station-Westfield Bus Station: []
short_name: "7" short_name: "7"
stop_times: [[632a, 639a, 646a, 654a, 708a, 710a, 715a], [701a, 708a, 715a, 723a, 738a, 740a, 745a], [731a, 739a, 746a, 754a, 810a, 812a, 817a], [801a, 809a, 816a, 824a, 840a, 842a, 847a], [829a, 837a, 844a, 852a, 908a, 910a, 915a], [858a, 906a, 913a, 921a, 936a, 938a, 943a], [930a, 937a, 944a, 952a, 1006a, 1008a, 1013a], [1000a, 1007a, 1014a, 1022a, 1036a, 1038a, 1043a], [1030a, 1037a, 1044a, 1052a, 1106a, 1108a, 1113a], [1100a, 1107a, 1114a, 1122a, 1136a, 1138a, 1143a], [1130a, 1137a, 1144a, 1152a, 1206p, 1208p, 1213p], [1200p, 1207p, 1214p, 1222p, 1236p, 1238p, 1243p], [1230p, 1237p, 1244p, 1252p, 106p, 108p, 113p], [100p, 107p, 114p, 122p, 136p, 138p, 143p], [130p, 137p, 144p, 152p, 206p, 208p, 213p], [200p, 207p, 214p, 222p, 236p, 238p, 243p], [230p, 237p, 244p, 252p, 307p, 309p, 314p], [259p, 307p, 314p, 323p, 339p, 341p, 346p], [331p, 339p, 346p, 355p, 411p, 413p, 418p], [401p, 409p, 416p, 425p, 441p, 443p, 448p], [431p, 439p, 446p, 455p, 511p, 513p, 518p], [501p, 509p, 516p, 525p, 541p, 543p, 548p], [531p, 539p, 546p, 555p, 611p, 613p, 618p], [631p, 637p, 644p, 652p, 706p, 708p, 713p], [731p, 737p, 744p, 752p, 806p, 808p, 813p], [831p, 837p, 844p, 852p, 906p, 908p, 913p], [931p, 937p, 944p, 952p, 1006p, 1008p, 1013p], [1031p, 1037p, 1044p, 1052p, 1106p, 1108p, 1113p]] stop_times: [[632a, 639a, 646a, 654a, 708a, 710a, 715a], [701a, 708a, 715a, 723a, 738a, 740a, 745a], [731a, 739a, 746a, 754a, 810a, 812a, 817a], [801a, 809a, 816a, 824a, 840a, 842a, 847a], [829a, 837a, 844a, 852a, 908a, 910a, 915a], [858a, 906a, 913a, 921a, 936a, 938a, 943a], [930a, 937a, 944a, 952a, 1006a, 1008a, 1013a], [1000a, 1007a, 1014a, 1022a, 1036a, 1038a, 1043a], [1030a, 1037a, 1044a, 1052a, 1106a, 1108a, 1113a], [1100a, 1107a, 1114a, 1122a, 1136a, 1138a, 1143a], [1130a, 1137a, 1144a, 1152a, 1206p, 1208p, 1213p], [1200p, 1207p, 1214p, 1222p, 1236p, 1238p, 1243p], [1230p, 1237p, 1244p, 1252p, 106p, 108p, 113p], [100p, 107p, 114p, 122p, 136p, 138p, 143p], [130p, 137p, 144p, 152p, 206p, 208p, 213p], [200p, 207p, 214p, 222p, 236p, 238p, 243p], [230p, 237p, 244p, 252p, 307p, 309p, 314p], [259p, 307p, 314p, 323p, 339p, 341p, 346p], [331p, 339p, 346p, 355p, 411p, 413p, 418p], [401p, 409p, 416p, 425p, 441p, 443p, 448p], [431p, 439p, 446p, 455p, 511p, 513p, 518p], [501p, 509p, 516p, 525p, 541p, 543p, 548p], [531p, 539p, 546p, 555p, 611p, 613p, 618p], [631p, 637p, 644p, 652p, 706p, 708p, 713p], [731p, 737p, 744p, 752p, 806p, 808p, 813p], [831p, 837p, 844p, 852p, 906p, 908p, 913p], [931p, 937p, 944p, 952p, 1006p, 1008p, 1013p], [1031p, 1037p, 1044p, 1052p, 1106p, 1108p, 1113p]]
   
--- ---
time_points: [Spence Terminus, Spence Shops, Copland College, William Webb / Ginninderra Drive, Northbourne Avenue / Antill St, Macarthur / Northbourne Ave, City Bus Station (Platform 10), Russell Offices, National Circ / Canberra Ave] time_points: [Spence Terminus, Spence, Copland College, William Webb / Ginninderra Drive, Northbourne Avenue / Antill St, Macarthur / Northbourne Ave, City Bus Station (Platform 10), Russell Offices, National Circ / Canberra Ave]
long_name: To National Circ / Canberra Ave long_name: To National Circ / Canberra Ave
between_stops: between_stops:
Northbourne Avenue / Antill St-Macarthur / Northbourne Ave: [Wjz5SDc, Wjz5Sux, Wjz5SrO, Wjz5Sqk, Wjz5RvC, Wjz5Rsi, Wjz5RkN] Northbourne Avenue / Antill St-Macarthur / Northbourne Ave: [Wjz5SDc, Wjz5Sux, Wjz5SrO, Wjz5Sqk, Wjz5RvC, Wjz5Rsi, Wjz5RkN]
Macarthur / Northbourne Ave-City Bus Station (Platform 10): [Wjz5QmR, Wjz5Qmu, Wjz5Qgn, Wjz5Qi2, Wjz5Pl0, Wjz5PdJ, Wjz5P8K, Wjz5P8n, Wjz5Oci, Wjz5O3Q] Macarthur / Northbourne Ave-City Bus Station (Platform 10): [Wjz5QmR, Wjz5Qmu, Wjz5Qgn, Wjz5Qi2, Wjz5Pl0, Wjz5PdJ, Wjz5P8K, Wjz5P8n, Wjz5Oci, Wjz5O3Q]
City Bus Station (Platform 10)-Russell Offices: [Wjz5Nht, Wjz5MsD, Wjz5MsT, Wjz5MEL, Wjz4T-X, Wjz4_jm, Wjz4_wS, Wjz4-KO, Wjz4-Rc, Wjz4-WL, Wjz4-WZ] City Bus Station (Platform 10)-Russell Offices: [Wjz5Nht, Wjz5MsD, Wjz5MsT, Wjz5MEL, Wjz4T-X, Wjz4_jm, Wjz4_wS, Wjz4-KO, Wjz4-Rc, Wjz4-WL, Wjz4-WZ]
short_name: "701" short_name: "701"
stop_times: [[658a, 703a, 710a, 714a, 724a, 726a, 737a, 746a, 754a], [731a, 736a, 743a, 747a, 805a, 810a, 826a, 835a, 843a], [745a, 750a, 757a, 801a, 819a, 824a, 840a, 849a, 857a]] stop_times: [[658a, 703a, 710a, 714a, 724a, 726a, 737a, 746a, 754a], [731a, 736a, 743a, 747a, 805a, 810a, 826a, 835a, 843a], [745a, 750a, 757a, 801a, 819a, 824a, 840a, 849a, 857a]]
   
--- ---
time_points: [Sydney Ave, Russell Offices, City Bus Station (Platform 11), Macarthur / Northbourne Ave, Northbourne Avenue / Antill St, William Webb / Ginninderra Drive, Copland College, Spence Shops, Spence Terminus] time_points: [Sydney Ave, Russell Offices, City Bus Station (Platform 11), Macarthur / Northbourne Ave, Northbourne Avenue / Antill St, William Webb / Ginninderra Drive, Copland College, Spence, Spence Terminus]
long_name: To Spence Terminus long_name: To Spence Terminus
between_stops: between_stops:
Macarthur / Northbourne Ave-Northbourne Avenue / Antill St: [Wjz5RkN, Wjz5Rsi, Wjz5RvC, Wjz5Sqk, Wjz5SrO, Wjz5Sux, Wjz5SDc] Macarthur / Northbourne Ave-Northbourne Avenue / Antill St: [Wjz5RkN, Wjz5Rsi, Wjz5RvC, Wjz5Sqk, Wjz5SrO, Wjz5Sux, Wjz5SDc]
Russell Offices-City Bus Station (Platform 11): [Wjz4-WZ, Wjz4-WL, Wjz4-Rc, Wjz4-KO, Wjz4_wS, Wjz4_jm, Wjz4T-X, Wjz5MEL, Wjz5MsT, Wjz5MsD, Wjz5Nht] Russell Offices-City Bus Station (Platform 11): [Wjz4-WZ, Wjz4-WL, Wjz4-Rc, Wjz4-KO, Wjz4_wS, Wjz4_jm, Wjz4T-X, Wjz5MEL, Wjz5MsT, Wjz5MsD, Wjz5Nht]
City Bus Station (Platform 11)-Macarthur / Northbourne Ave: [Wjz5O3Q, Wjz5Oci, Wjz5P8n, Wjz5P8K, Wjz5PdJ, Wjz5Pl0, Wjz5Qi2, Wjz5Qgn, Wjz5Qmu, Wjz5QmR] City Bus Station (Platform 11)-Macarthur / Northbourne Ave: [Wjz5O3Q, Wjz5Oci, Wjz5P8n, Wjz5P8K, Wjz5PdJ, Wjz5Pl0, Wjz5Qi2, Wjz5Qgn, Wjz5Qmu, Wjz5QmR]
short_name: "701" short_name: "701"
stop_times: [[442p, 450p, 502p, 509p, 512p, 522p, 527p, 534p, 540p], ["-", "-", 520p, 527p, 529p, 539p, 543p, 550p, 554p], [525p, 533p, 543p, 550p, 552p, 602p, 606p, 613p, 617p], [542p, 550p, 600p, 607p, 609p, 619p, 623p, 630p, 634p]] stop_times: [[442p, 450p, 502p, 509p, 512p, 522p, 527p, 534p, 540p], ["-", "-", 520p, 527p, 529p, 539p, 543p, 550p, 554p], [525p, 533p, 543p, 550p, 552p, 602p, 606p, 613p, 617p], [542p, 550p, 600p, 607p, 609p, 619p, 623p, 630p, 634p]]
   
--- ---
time_points: [Sydney Ave, Russell Offices, City Bus Station (Platform 11), Macarthur / Northbourne Ave, Northbourne Avenue / Antill St, Flynn, Charnwood Shops, Fraser Shops, Fraser East Terminus] time_points: [Sydney Ave, Russell Offices, City Bus Station (Platform 11), Macarthur / Northbourne Ave, Northbourne Avenue / Antill St, Flynn, Charnwood, Fraser, Fraser East Terminus]
long_name: To Fraser East Terminus long_name: To Fraser East Terminus
between_stops: between_stops:
Macarthur / Northbourne Ave-Northbourne Avenue / Antill St: [Wjz5RkN, Wjz5Rsi, Wjz5RvC, Wjz5Sqk, Wjz5SrO, Wjz5Sux, Wjz5SDc] Macarthur / Northbourne Ave-Northbourne Avenue / Antill St: [Wjz5RkN, Wjz5Rsi, Wjz5RvC, Wjz5Sqk, Wjz5SrO, Wjz5Sux, Wjz5SDc]
Russell Offices-City Bus Station (Platform 11): [Wjz4-WZ, Wjz4-WL, Wjz4-Rc, Wjz4-KO, Wjz4_wS, Wjz4_jm, Wjz4T-X, Wjz5MEL, Wjz5MsT, Wjz5MsD, Wjz5Nht] Russell Offices-City Bus Station (Platform 11): [Wjz4-WZ, Wjz4-WL, Wjz4-Rc, Wjz4-KO, Wjz4_wS, Wjz4_jm, Wjz4T-X, Wjz5MEL, Wjz5MsT, Wjz5MsD, Wjz5Nht]
City Bus Station (Platform 11)-Macarthur / Northbourne Ave: [Wjz5O3Q, Wjz5Oci, Wjz5P8n, Wjz5P8K, Wjz5PdJ, Wjz5Pl0, Wjz5Qi2, Wjz5Qgn, Wjz5Qmu, Wjz5QmR] City Bus Station (Platform 11)-Macarthur / Northbourne Ave: [Wjz5O3Q, Wjz5Oci, Wjz5P8n, Wjz5P8K, Wjz5PdJ, Wjz5Pl0, Wjz5Qi2, Wjz5Qgn, Wjz5Qmu, Wjz5QmR]
short_name: "702" short_name: "702"
stop_times: [[450p, 458p, 508p, 513p, 515p, 527p, 532p, 538p, 542p], ["-", "-", 530p, 535p, 537p, 549p, 554p, 600p, 604p], [535p, 543p, 553p, 558p, 600p, 612p, 617p, 623p, 627p]] stop_times: [[450p, 458p, 508p, 513p, 515p, 527p, 532p, 538p, 542p], ["-", "-", 530p, 535p, 537p, 549p, 554p, 600p, 604p], [535p, 543p, 553p, 558p, 600p, 612p, 617p, 623p, 627p]]
   
--- ---
time_points: [Fraser East Terminus, Fraser Shops, Charnwood Shops, Flynn, Northbourne Avenue / Antill St, Macarthur / Northbourne Ave, City Bus Station (Platform 10), Russell Offices, National Circ / Canberra Ave] time_points: [Fraser East Terminus, Fraser, Charnwood, Flynn, Northbourne Avenue / Antill St, Macarthur / Northbourne Ave, City Bus Station (Platform 10), Russell Offices, National Circ / Canberra Ave]
long_name: To National Circ / Canberra Ave long_name: To National Circ / Canberra Ave
between_stops: between_stops:
Northbourne Avenue / Antill St-Macarthur / Northbourne Ave: [Wjz5SDc, Wjz5Sux, Wjz5SrO, Wjz5Sqk, Wjz5RvC, Wjz5Rsi, Wjz5RkN] Northbourne Avenue / Antill St-Macarthur / Northbourne Ave: [Wjz5SDc, Wjz5Sux, Wjz5SrO, Wjz5Sqk, Wjz5RvC, Wjz5Rsi, Wjz5RkN]
Macarthur / Northbourne Ave-City Bus Station (Platform 10): [Wjz5QmR, Wjz5Qmu, Wjz5Qgn, Wjz5Qi2, Wjz5Pl0, Wjz5PdJ, Wjz5P8K, Wjz5P8n, Wjz5Oci, Wjz5O3Q] Macarthur / Northbourne Ave-City Bus Station (Platform 10): [Wjz5QmR, Wjz5Qmu, Wjz5Qgn, Wjz5Qi2, Wjz5Pl0, Wjz5PdJ, Wjz5P8K, Wjz5P8n, Wjz5Oci, Wjz5O3Q]
City Bus Station (Platform 10)-Russell Offices: [Wjz5Nht, Wjz5MsD, Wjz5MsT, Wjz5MEL, Wjz4T-X, Wjz4_jm, Wjz4_wS, Wjz4-KO, Wjz4-Rc, Wjz4-WL, Wjz4-WZ] City Bus Station (Platform 10)-Russell Offices: [Wjz5Nht, Wjz5MsD, Wjz5MsT, Wjz5MEL, Wjz4T-X, Wjz4_jm, Wjz4_wS, Wjz4-KO, Wjz4-Rc, Wjz4-WL, Wjz4-WZ]
short_name: "702" short_name: "702"
stop_times: [[658a, 703a, 709a, 714a, 727a, 730a, 745a, 754a, 802a], [735a, 740a, 746a, 751a, 805a, 810a, 826a, 835a, 843a], [754a, 759a, 806a, 811a, 828a, 833a, 849a, 858a, 906a]] stop_times: [[658a, 703a, 709a, 714a, 727a, 730a, 745a, 754a, 802a], [735a, 740a, 746a, 751a, 805a, 810a, 826a, 835a, 843a], [754a, 759a, 806a, 811a, 828a, 833a, 849a, 858a, 906a]]
   
--- ---
time_points: [Sydney Ave, Russell Offices, City Bus Station (Platform 11), Belconnen Way, Macgregor Shops, Dunlop, Fraser West Terminus] time_points: [Sydney Ave, Russell Offices, City Bus Station (Platform 11), Belconnen Way, Macgregor, Dunlop, Fraser West Terminus]
long_name: To Fraser West Terminus long_name: To Fraser West Terminus
between_stops: between_stops:
Russell Offices-City Bus Station (Platform 11): [Wjz4-WZ, Wjz4-WL, Wjz4-Rc, Wjz4-KO, Wjz4_wS, Wjz4_jm, Wjz4T-X, Wjz5MEL, Wjz5MsT, Wjz5MsD, Wjz5Nht] Russell Offices-City Bus Station (Platform 11): [Wjz4-WZ, Wjz4-WL, Wjz4-Rc, Wjz4-KO, Wjz4_wS, Wjz4_jm, Wjz4T-X, Wjz5MEL, Wjz5MsT, Wjz5MsD, Wjz5Nht]
short_name: "703" short_name: "703"
stop_times: [[440p, 448p, 458p, 516p, 527p, 534p, 541p], ["-", "-", 515p, 533p, 544p, 551p, 558p], ["-", "-", 526p, 544p, 555p, 602p, 609p], [520p, 528p, 538p, 556p, 607p, 614p, 621p], [545p, 553p, 603p, 621p, 632p, 639p, 646p]] stop_times: [[440p, 448p, 458p, 516p, 527p, 534p, 541p], ["-", "-", 515p, 533p, 544p, 551p, 558p], ["-", "-", 526p, 544p, 555p, 602p, 609p], [520p, 528p, 538p, 556p, 607p, 614p, 621p], [545p, 553p, 603p, 621p, 632p, 639p, 646p]]
   
--- ---
time_points: [Fraser West Terminus, Dunlop, Macgregor Shops, Belconnen Way, City Bus Station (Platform 10), Russell Offices, National Circ / Canberra Ave] time_points: [Fraser West Terminus, Dunlop, Macgregor, Belconnen Way, City Bus Station (Platform 10), Russell Offices, National Circ / Canberra Ave]
long_name: To National Circ / Canberra Ave long_name: To National Circ / Canberra Ave
between_stops: between_stops:
City Bus Station (Platform 10)-Russell Offices: [Wjz5Nht, Wjz5MsD, Wjz5MsT, Wjz5MEL, Wjz4T-X, Wjz4_jm, Wjz4_wS, Wjz4-KO, Wjz4-Rc, Wjz4-WL, Wjz4-WZ] City Bus Station (Platform 10)-Russell Offices: [Wjz5Nht, Wjz5MsD, Wjz5MsT, Wjz5MEL, Wjz4T-X, Wjz4_jm, Wjz4_wS, Wjz4-KO, Wjz4-Rc, Wjz4-WL, Wjz4-WZ]
short_name: "703" short_name: "703"
stop_times: [[653a, 700a, 706a, 718a, 737a, 746a, 754a], [710a, 717a, 723a, 735a, 753a, "-", "-"], [723a, 730a, 736a, 748a, 806a, "-", "-"], [738a, 745a, 751a, 803a, 834a, 843a, 851a], [758a, 806a, 813a, 827a, 849a, 858a, 906a]] stop_times: [[653a, 700a, 706a, 718a, 737a, 746a, 754a], [710a, 717a, 723a, 735a, 753a, "-", "-"], [723a, 730a, 736a, 748a, 806a, "-", "-"], [738a, 745a, 751a, 803a, 834a, 843a, 851a], [758a, 806a, 813a, 827a, 849a, 858a, 906a]]
   
--- ---
time_points: [Sydney Ave, Russell Offices, City Bus Station (Platform 11), Aranda Shops, Macquarie, Hawker Shops, Hawker College, Higgins, Kippax] time_points: [Sydney Ave, Russell Offices, City Bus Station (Platform 11), Aranda, Macquarie, Hawker, Hawker College, Higgins, Kippax]
long_name: To Kippax long_name: To Kippax
between_stops: between_stops:
Russell Offices-City Bus Station (Platform 11): [Wjz4-WZ, Wjz4-WL, Wjz4-Rc, Wjz4-KO, Wjz4_wS, Wjz4_jm, Wjz4T-X, Wjz5MEL, Wjz5MsT, Wjz5MsD, Wjz5Nht] Russell Offices-City Bus Station (Platform 11): [Wjz4-WZ, Wjz4-WL, Wjz4-Rc, Wjz4-KO, Wjz4_wS, Wjz4_jm, Wjz4T-X, Wjz5MEL, Wjz5MsT, Wjz5MsD, Wjz5Nht]
short_name: "704" short_name: "704"
stop_times: [[506p, 514p, 524p, 533p, 542p, 550p, 555p, 600p, 606p]] stop_times: [[506p, 514p, 524p, 533p, 542p, 550p, 555p, 600p, 606p]]
   
--- ---
time_points: [Kippax, Higgins, Hawker College, Hawker Shops, Macquarie, Aranda Shops, City Bus Station (Platform 10), Russell Offices, National Circ / Canberra Ave] time_points: [Kippax, Higgins, Hawker College, Hawker, Macquarie, Aranda, City Bus Station (Platform 10), Russell Offices, National Circ / Canberra Ave]
long_name: To National Circ / Canberra Ave long_name: To National Circ / Canberra Ave
between_stops: between_stops:
City Bus Station (Platform 10)-Russell Offices: [Wjz5Nht, Wjz5MsD, Wjz5MsT, Wjz5MEL, Wjz4T-X, Wjz4_jm, Wjz4_wS, Wjz4-KO, Wjz4-Rc, Wjz4-WL, Wjz4-WZ] City Bus Station (Platform 10)-Russell Offices: [Wjz5Nht, Wjz5MsD, Wjz5MsT, Wjz5MEL, Wjz4T-X, Wjz4_jm, Wjz4_wS, Wjz4-KO, Wjz4-Rc, Wjz4-WL, Wjz4-WZ]
short_name: "704" short_name: "704"
stop_times: [[738a, 744a, 749a, 754a, 803a, 812a, 825a, 833a, 840a], [753a, 759a, 804a, 809a, 818a, 827a, 840a, 848a, 855a]] stop_times: [[738a, 744a, 749a, 754a, 803a, 812a, 825a, 833a, 840a], [753a, 759a, 804a, 809a, 818a, 827a, 840a, 848a, 855a]]
   
--- ---
time_points: [Farrer Terminus, Southlands Mawson, Garran Shops, Hughes Shops, City West, City Bus Station, ACTEW AGL House] time_points: [Farrer Terminus, Southlands Mawson, Garran, Hughes, City West, City Bus Station, ACTEW AGL House]
long_name: To ACTEW AGL House long_name: To ACTEW AGL House
between_stops: between_stops:
City Bus Station-ACTEW AGL House: [Wjz5Nht] City Bus Station-ACTEW AGL House: [Wjz5Nht]
City West-City Bus Station: [] City West-City Bus Station: []
short_name: "720" short_name: "720"
stop_times: [[710a, 716a, 728a, 734a, 752a, 756a, 757a], [740a, 746a, 758a, 804a, 822a, 826a, 827a], [816a, 822a, 834a, 840a, 858a, 902a, 903a], [840a, 846a, 858a, 904a, 922a, 926a, 927a]] stop_times: [[710a, 716a, 728a, 734a, 752a, 756a, 757a], [740a, 746a, 758a, 804a, 822a, 826a, 827a], [816a, 822a, 834a, 840a, 858a, 902a, 903a], [840a, 846a, 858a, 904a, 922a, 926a, 927a]]
   
--- ---
time_points: [City West, City Bus Station (Platform 10), Hughes Shops, Garran Shops, Southlands Mawson, Farrer Terminus] time_points: [City West, City Bus Station (Platform 10), Hughes, Garran, Southlands Mawson, Farrer Terminus]
long_name: To Farrer Terminus long_name: To Farrer Terminus
between_stops: between_stops:
City West-City Bus Station (Platform 10): [] City West-City Bus Station (Platform 10): []
short_name: "720" short_name: "720"
stop_times: [[440p, 446p, 504p, 510p, 523p, 529p], [510p, 516p, 534p, 540p, 553p, 559p], [540p, 546p, 604p, 610p, 623p, 629p]] stop_times: [[440p, 446p, 504p, 510p, 523p, 529p], [510p, 516p, 534p, 540p, 553p, 559p], [540p, 546p, 604p, 610p, 623p, 629p]]
   
--- ---
time_points: [Cooleman Court, Rivett Shops, Duffy Primary, Holder Shops, City West, City Bus Station, ACTEW AGL House] time_points: [Cooleman Court, Rivett, Duffy Primary, Holder, City West, City Bus Station, ACTEW AGL House]
long_name: To ACTEW AGL House long_name: To ACTEW AGL House
between_stops: between_stops:
City Bus Station-ACTEW AGL House: [Wjz5Nht] City Bus Station-ACTEW AGL House: [Wjz5Nht]
City West-City Bus Station: [] City West-City Bus Station: []
short_name: "729" short_name: "729"
stop_times: [[709a, 715a, 724a, 728a, 749a, 753a, 755a], [739a, 745a, 754a, 758a, 819a, 823a, 825a]] stop_times: [[709a, 715a, 724a, 728a, 749a, 753a, 755a], [739a, 745a, 754a, 758a, 819a, 823a, 825a]]
   
--- ---
time_points: [City West, City Bus Station (Platform 10), Holder Shops, Duffy Primary, Rivett Shops, Cooleman Court] time_points: [City West, City Bus Station (Platform 10), Holder, Duffy Primary, Rivett, Cooleman Court]
long_name: To Cooleman Court long_name: To Cooleman Court
between_stops: between_stops:
City West-City Bus Station (Platform 10): [] City West-City Bus Station (Platform 10): []
short_name: "729" short_name: "729"
stop_times: [[445p, 451p, 513p, 518p, 526p, 532p], [515p, 521p, 543p, 548p, 556p, 602p]] stop_times: [[445p, 451p, 513p, 518p, 526p, 532p], [515p, 521p, 543p, 548p, 556p, 602p]]
   
--- ---
time_points: [Belconnen Community Bus Station (Platform 5), Westfield Bus Station (Platform 2), Cohen Street Bus Station (Platform 5), Florey Shops, Page Shops, Hawker Shops, Cook Shops, Jamison Centre, Calvary Hospital, Belconnen Community Bus Station, Westfield Bus Station, Cohen Street Bus Station] time_points: [Belconnen Community Bus Station (Platform 5), Westfield Bus Station (Platform 2), Cohen Street Bus Station (Platform 5), Florey, Page, Hawker, Cook, Jamison Centre, Calvary Hospital, Belconnen Community Bus Station, Westfield Bus Station, Cohen Street Bus Station]
long_name: To Cohen Street Bus Station long_name: To Cohen Street Bus Station
between_stops: between_stops:
Westfield Bus Station-Cohen Street Bus Station: [] Westfield Bus Station-Cohen Street Bus Station: []
Belconnen Community Bus Station (Platform 5)-Westfield Bus Station (Platform 2): [] Belconnen Community Bus Station (Platform 5)-Westfield Bus Station (Platform 2): []
Belconnen Community Bus Station-Westfield Bus Station: [] Belconnen Community Bus Station-Westfield Bus Station: []
Westfield Bus Station (Platform 2)-Cohen Street Bus Station (Platform 5): [] Westfield Bus Station (Platform 2)-Cohen Street Bus Station (Platform 5): []
short_name: "73" short_name: "73"
stop_times: [[916a, 918a, 922a, 927a, 933a, 937a, 944a, 947a, 954a, 1005a, 1007a, 1012a], [1046a, 1048a, 1052a, 1057a, 1103a, 1107a, 1114a, 1117a, 1124a, 1135a, 1137a, 1142a], [1216p, 1218p, 1222p, 1227p, 1233p, 1237p, 1244p, 1247p, 1254p, 105p, 107p, 112p], [146p, 148p, 152p, 157p, 203p, 207p, 214p, 217p, 224p, 235p, 237p, 242p]] stop_times: [[916a, 918a, 922a, 927a, 933a, 937a, 944a, 947a, 954a, 1005a, 1007a, 1012a], [1046a, 1048a, 1052a, 1057a, 1103a, 1107a, 1114a, 1117a, 1124a, 1135a, 1137a, 1142a], [1216p, 1218p, 1222p, 1227p, 1233p, 1237p, 1244p, 1247p, 1254p, 105p, 107p, 112p], [146p, 148p, 152p, 157p, 203p, 207p, 214p, 217p, 224p, 235p, 237p, 242p]]
   
--- ---
time_points: [Cohen Street Bus Station (Platform 1), Westfield Bus Station (Platform 1), Belconnen Community Bus Station (Platform 3), Calvary Hospital, Jamison Centre, Cook Shops, Hawker Shops, Page Shops, Florey Shops, Cohen Street Bus Station, Westfield Bus Station, Belconnen Community Bus Station] time_points: [Cohen Street Bus Station (Platform 1), Westfield Bus Station (Platform 1), Belconnen Community Bus Station (Platform 3), Calvary Hospital, Jamison Centre, Cook, Hawker, Page, Florey, Cohen Street Bus Station, Westfield Bus Station, Belconnen Community Bus Station]
long_name: To Belconnen Community Bus Station long_name: To Belconnen Community Bus Station
between_stops: between_stops:
Westfield Bus Station (Platform 1)-Belconnen Community Bus Station (Platform 3): [] Westfield Bus Station (Platform 1)-Belconnen Community Bus Station (Platform 3): []
Cohen Street Bus Station (Platform 1)-Westfield Bus Station (Platform 1): [] Cohen Street Bus Station (Platform 1)-Westfield Bus Station (Platform 1): []
Westfield Bus Station-Belconnen Community Bus Station: [] Westfield Bus Station-Belconnen Community Bus Station: []
Cohen Street Bus Station-Westfield Bus Station: [] Cohen Street Bus Station-Westfield Bus Station: []
short_name: "74" short_name: "74"
stop_times: [[950a, 952a, 956a, 1005a, 1012a, 1015a, 1023a, 1027a, 1033a, 1039a, 1041a, 1045a], [1120a, 1122a, 1126a, 1135a, 1142a, 1145a, 1153a, 1157a, 1203p, 1209p, 1211p, 1215p], [1250p, 1252p, 1256p, 105p, 112p, 115p, 123p, 127p, 133p, 139p, 141p, 145p], [220p, 222p, 226p, 235p, 242p, 245p, 253p, 257p, 303p, 309p, 311p, 315p]] stop_times: [[950a, 952a, 956a, 1005a, 1012a, 1015a, 1023a, 1027a, 1033a, 1039a, 1041a, 1045a], [1120a, 1122a, 1126a, 1135a, 1142a, 1145a, 1153a, 1157a, 1203p, 1209p, 1211p, 1215p], [1250p, 1252p, 1256p, 105p, 112p, 115p, 123p, 127p, 133p, 139p, 141p, 145p], [220p, 222p, 226p, 235p, 242p, 245p, 253p, 257p, 303p, 309p, 311p, 315p]]
   
---  
time_points: [City West, City Bus Station (Platform 10), Russell Offices, Chisholm Shops, Isabella Shops, Calwell Shops]  
long_name: To Calwell Shops  
between_stops:  
City West-City Bus Station (Platform 10): []  
City Bus Station (Platform 10)-Russell Offices: [Wjz5Nht, Wjz5MsD, Wjz5MsT, Wjz5MEL, Wjz4T-X, Wjz4_jm, Wjz4_wS, Wjz4-KO, Wjz4-Rc, Wjz4-WL, Wjz4-WZ]  
short_name: "768"  
stop_times: [[447p, 453p, 502p, 526p, 537p, 545p], [519p, 525p, 534p, 558p, 609p, 617p]]  
 
  ---
  time_points: [City West, City Bus Station (Platform 10), Russell Offices, Chisholm, Isabella, Calwell]
  long_name: To Calwell
  between_stops:
  City West-City Bus Station (Platform 10): []
  City Bus Station (Platform 10)-Russell Offices: [Wjz5Nht, Wjz5MsD, Wjz5MsT, Wjz5MEL, Wjz4T-X, Wjz4_jm, Wjz4_wS, Wjz4-KO, Wjz4-Rc, Wjz4-WL, Wjz4-WZ]
  short_name: "768"
  stop_times: [[447p, 453p, 502p, 526p, 537p, 545p], [519p, 525p, 534p, 558p, 609p, 617p]]
 
--- ---
time_points: [Calwell Shops, Isabella Shops, Chisholm Shops, Russell Offices, City Bus Station (Platform 11), City West] time_points: [Calwell, Isabella, Chisholm, Russell Offices, City Bus Station (Platform 11), City West]
long_name: To City West long_name: To City West
between_stops: between_stops:
Russell Offices-City Bus Station (Platform 11): [Wjz4-WZ, Wjz4-WL, Wjz4-Rc, Wjz4-KO, Wjz4_wS, Wjz4_jm, Wjz4T-X, Wjz5MEL, Wjz5MsT, Wjz5MsD, Wjz5Nht] Russell Offices-City Bus Station (Platform 11): [Wjz4-WZ, Wjz4-WL, Wjz4-Rc, Wjz4-KO, Wjz4_wS, Wjz4_jm, Wjz4T-X, Wjz5MEL, Wjz5MsT, Wjz5MsD, Wjz5Nht]
City Bus Station (Platform 11)-City West: [] City Bus Station (Platform 11)-City West: []
short_name: "768" short_name: "768"
stop_times: [[707a, 715a, 726a, 751a, 800a, 804a], [737a, 748a, 801a, 833a, 845a, 848a]] stop_times: [[707a, 715a, 726a, 751a, 800a, 804a], [737a, 748a, 801a, 833a, 845a, 848a]]
   
--- ---
time_points: [Tharwa Drive, Theodore, Calwell Shops, Chisholm Shops, Russell Offices, City Bus Station (Platform 11), City West] time_points: [Tharwa Drive, Theodore, Calwell, Chisholm, Russell Offices, City Bus Station (Platform 11), City West]
long_name: To City West long_name: To City West
between_stops: between_stops:
Russell Offices-City Bus Station (Platform 11): [Wjz4-WZ, Wjz4-WL, Wjz4-Rc, Wjz4-KO, Wjz4_wS, Wjz4_jm, Wjz4T-X, Wjz5MEL, Wjz5MsT, Wjz5MsD, Wjz5Nht] Russell Offices-City Bus Station (Platform 11): [Wjz4-WZ, Wjz4-WL, Wjz4-Rc, Wjz4-KO, Wjz4_wS, Wjz4_jm, Wjz4T-X, Wjz5MEL, Wjz5MsT, Wjz5MsD, Wjz5Nht]
City Bus Station (Platform 11)-City West: [] City Bus Station (Platform 11)-City West: []
short_name: "769" short_name: "769"
stop_times: [[641a, 646a, 656a, 706a, 733a, 743a, 747a], [721a, 726a, 736a, 746a, 813a, 823a, 827a], [741a, 746a, 756a, 806a, 833a, 843a, 847a]] stop_times: [[641a, 646a, 656a, 706a, 733a, 743a, 747a], [721a, 726a, 736a, 746a, 813a, 823a, 827a], [741a, 746a, 756a, 806a, 833a, 843a, 847a]]
   
--- ---
time_points: [City West, City Bus Station (Platform 10), Russell Offices, Chisholm Shops, Calwell Shops, Theodore, Tharwa Drive] time_points: [City West, City Bus Station (Platform 10), Russell Offices, Chisholm, Calwell, Theodore, Tharwa Drive]
long_name: To Tharwa Drive long_name: To Tharwa Drive
between_stops: between_stops:
City West-City Bus Station (Platform 10): [] City West-City Bus Station (Platform 10): []
City Bus Station (Platform 10)-Russell Offices: [Wjz5Nht, Wjz5MsD, Wjz5MsT, Wjz5MEL, Wjz4T-X, Wjz4_jm, Wjz4_wS, Wjz4-KO, Wjz4-Rc, Wjz4-WL, Wjz4-WZ] City Bus Station (Platform 10)-Russell Offices: [Wjz5Nht, Wjz5MsD, Wjz5MsT, Wjz5MEL, Wjz4T-X, Wjz4_jm, Wjz4_wS, Wjz4-KO, Wjz4-Rc, Wjz4-WL, Wjz4-WZ]
short_name: "769" short_name: "769"
stop_times: [[427p, 433p, 442p, 507p, 517p, 527p, 532p], [500p, 506p, 515p, 540p, 550p, 600p, 605p], [537p, 543p, 552p, 617p, 627p, 637p, 642p]] stop_times: [[427p, 433p, 442p, 507p, 517p, 527p, 532p], [500p, 506p, 515p, 540p, 550p, 600p, 605p], [537p, 543p, 552p, 617p, 627p, 637p, 642p]]
   
--- ---
time_points: [Lanyon Market Place, Tharwa Dr / Pockett Ave, Mentone View / Tharwa Drive, City West, City Bus Station (Platform 10), ACTEW AGL House] time_points: [Lanyon Market Place, Tharwa Drive / Pockett Ave, Mentone View / Tharwa Drive, City West, City Bus Station (Platform 10), ACTEW AGL House]
long_name: To ACTEW AGL House long_name: To ACTEW AGL House
between_stops: between_stops:
City West-City Bus Station (Platform 10): [] City West-City Bus Station (Platform 10): []
City Bus Station (Platform 10)-ACTEW AGL House: [Wjz5Nht] City Bus Station (Platform 10)-ACTEW AGL House: [Wjz5Nht]
short_name: "785" short_name: "785"
stop_times: [[652a, 655a, 713a, 743a, 747a, 749a], [725a, 728a, 746a, 816a, 820a, 822a], [745a, 748a, 806a, 836a, 840a, 842a]] stop_times: [[652a, 655a, 713a, 743a, 747a, 749a], [725a, 728a, 746a, 816a, 820a, 822a], [745a, 748a, 806a, 836a, 840a, 842a]]
   
--- ---
time_points: [City West, City Bus Station (Platform 10), ACTEW AGL House, Mentone View / Tharwa Drive, Tharwa Dr / Pockett Ave, Lanyon Market Place] time_points: [City West, City Bus Station (Platform 10), ACTEW AGL House, Mentone View / Tharwa Drive, Tharwa Drive / Pockett Ave, Lanyon Market Place]
long_name: To Lanyon Market Place long_name: To Lanyon Market Place
between_stops: between_stops:
ACTEW AGL House-Mentone View / Tharwa Drive: [Wjz33LB, Wjz34Gq, WjrXUAm, WjrXUsW, WjrXUoV, WjrW_uo, Wjz2a26, Wjz1kvl] ACTEW AGL House-Mentone View / Tharwa Drive: [Wjz33LB, Wjz34Gq, WjrXUAm, WjrXUsW, WjrXUoV, WjrW_uo, Wjz2a26, Wjz1kvl]
City West-City Bus Station (Platform 10): [] City West-City Bus Station (Platform 10): []
City Bus Station (Platform 10)-ACTEW AGL House: [Wjz5Nht] City Bus Station (Platform 10)-ACTEW AGL House: [Wjz5Nht]
short_name: "785" short_name: "785"
stop_times: [[505p, 511p, 513p, 549p, 605p, 607p], [530p, 536p, 538p, 614p, 630p, 632p], [545p, 551p, 553p, 629p, 645p, 647p]] stop_times: [[505p, 511p, 513p, 549p, 605p, 607p], [530p, 536p, 538p, 614p, 630p, 632p], [545p, 551p, 553p, 629p, 645p, 647p]]
   
--- ---
time_points: [Tuggeranong Bus Station (Platform 7), Chisholm Shops, Brindabella Business Park, Fairbairn Park] time_points: [Tuggeranong Bus Station (Platform 7), Chisholm, Brindabella Business Park, Fairbairn Park]
long_name: To Fairbairn Park long_name: To Fairbairn Park
between_stops: between_stops:
Brindabella Business Park-Fairbairn Park: [WjzcrK3, WjzcrrQ, WjzcrEu, WjzcJ0K, WjzcBHZ, WjzcJ38] Brindabella Business Park-Fairbairn Park: [WjzcrK3, WjzcrrQ, WjzcrEu, WjzcJ0K, WjzcBHZ, WjzcJ38]
short_name: "786" short_name: "786"
stop_times: [[646a, 656a, 716a, 726a], [706a, 716a, 736a, 746a], [727a, 737a, 804a, 814a]] stop_times: [[646a, 656a, 716a, 726a], [706a, 716a, 736a, 746a], [727a, 737a, 804a, 814a]]
   
--- ---
time_points: [Fairbairn Park, Brindabella Business Park, Chisholm Shops, Tuggeranong Bus Station] time_points: [Fairbairn Park, Brindabella Business Park, Chisholm, Tuggeranong Bus Station]
long_name: To Tuggeranong Bus Station long_name: To Tuggeranong Bus Station
between_stops: between_stops:
Fairbairn Park-Brindabella Business Park: [WjzcJ38, WjzcBHZ, WjzcJ0K, WjzcrEu, WjzcrrQ, WjzcrK3] Fairbairn Park-Brindabella Business Park: [WjzcJ38, WjzcBHZ, WjzcJ0K, WjzcrEu, WjzcrrQ, WjzcrK3]
short_name: "786" short_name: "786"
stop_times: [[445p, 455p, 520p, 533p], [515p, 525p, 550p, 603p], [545p, 555p, 620p, 633p]] stop_times: [[445p, 455p, 520p, 533p], [515p, 525p, 550p, 603p], [545p, 555p, 620p, 633p]]
   
--- ---
time_points: [Woodcock / Clare Dennis, Tharwa Dr / Pockett Ave, Mentone View / Tharwa Drive, Russell Offices, City Bus Station (Platform 11), City West] time_points: [Woodcock / Clare Dennis, Tharwa Drive / Pockett Ave, Mentone View / Tharwa Drive, Russell Offices, City Bus Station (Platform 11), City West]
long_name: To City West long_name: To City West
between_stops: between_stops:
Russell Offices-City Bus Station (Platform 11): [Wjz4-WZ, Wjz4-WL, Wjz4-Rc, Wjz4-KO, Wjz4_wS, Wjz4_jm, Wjz4T-X, Wjz5MEL, Wjz5MsT, Wjz5MsD, Wjz5Nht] Russell Offices-City Bus Station (Platform 11): [Wjz4-WZ, Wjz4-WL, Wjz4-Rc, Wjz4-KO, Wjz4_wS, Wjz4_jm, Wjz4T-X, Wjz5MEL, Wjz5MsT, Wjz5MsD, Wjz5Nht]
City Bus Station (Platform 11)-City West: [] City Bus Station (Platform 11)-City West: []
short_name: "788" short_name: "788"
stop_times: [[710a, 719a, 734a, 811a, 820a, 824a], [740a, 749a, 804a, 841a, 850a, 854a]] stop_times: [[710a, 719a, 734a, 811a, 820a, 824a], [740a, 749a, 804a, 841a, 850a, 854a]]
   
--- ---
time_points: [City West, City Bus Station (Platform 10), Russell Offices, Mentone View / Tharwa Drive, Tharwa Dr / Pockett Ave, Woodcock / Clare Dennis] time_points: [City West, City Bus Station (Platform 10), Russell Offices, Mentone View / Tharwa Drive, Tharwa Drive / Pockett Ave, Woodcock / Clare Dennis]
long_name: To Woodcock / Clare Dennis long_name: To Woodcock / Clare Dennis
between_stops: between_stops:
City West-City Bus Station (Platform 10): [] City West-City Bus Station (Platform 10): []
City Bus Station (Platform 10)-Russell Offices: [Wjz5Nht, Wjz5MsD, Wjz5MsT, Wjz5MEL, Wjz4T-X, Wjz4_jm, Wjz4_wS, Wjz4-KO, Wjz4-Rc, Wjz4-WL, Wjz4-WZ] City Bus Station (Platform 10)-Russell Offices: [Wjz5Nht, Wjz5MsD, Wjz5MsT, Wjz5MEL, Wjz4T-X, Wjz4_jm, Wjz4_wS, Wjz4-KO, Wjz4-Rc, Wjz4-WL, Wjz4-WZ]
short_name: "788" short_name: "788"
stop_times: [[426p, 432p, 441p, 512p, 526p, 536p], [502p, 507p, 518p, 552p, 606p, 615p], [532p, 538p, 547p, 618p, 632p, 642p]] stop_times: [[426p, 432p, 441p, 512p, 526p, 536p], [502p, 507p, 518p, 552p, 606p, 615p], [532p, 538p, 547p, 618p, 632p, 642p]]
   
--- ---
time_points: [Dickson, Lyneham Shops Wattle Street, Macarthur / Miller O'Connor, City Bus Station] time_points: [Dickson / Antill St, Lyneham / Wattle St, Macarthur / Miller O'Connor, City Bus Station]
long_name: To City Bus Station long_name: To City Bus Station
between_stops: {} between_stops: {}
   
short_name: "8" short_name: "8"
stop_times: [[626a, 632a, 637a, 644a], [657a, 703a, 708a, 715a], [724a, 730a, 737a, 746a], [757a, 804a, 811a, 820a], [831a, 838a, 845a, 854a], [904a, 911a, 918a, 927a], [1009a, 1015a, 1020a, 1027a], [1109a, 1115a, 1120a, 1127a], [1209p, 1215p, 1220p, 1227p], [109p, 115p, 120p, 127p], [209p, 215p, 220p, 227p], [302p, 309p, 316p, 325p], [332p, 339p, 346p, 355p], [408p, 415p, 422p, 431p], [437p, 444p, 451p, 500p], [507p, 514p, 521p, 530p], [537p, 544p, 551p, 600p], [646p, 652p, 657p, 702p], [746p, 752p, 757p, 802p], [846p, 852p, 857p, 902p], [946p, 952p, 957p, 1002p], [1046p, 1052p, 1057p, 1102p]] stop_times: [[626a, 632a, 637a, 644a], [657a, 703a, 708a, 715a], [724a, 730a, 737a, 746a], [757a, 804a, 811a, 820a], [831a, 838a, 845a, 854a], [904a, 911a, 918a, 927a], [1009a, 1015a, 1020a, 1027a], [1109a, 1115a, 1120a, 1127a], [1209p, 1215p, 1220p, 1227p], [109p, 115p, 120p, 127p], [209p, 215p, 220p, 227p], [302p, 309p, 316p, 325p], [332p, 339p, 346p, 355p], [408p, 415p, 422p, 431p], [437p, 444p, 451p, 500p], [507p, 514p, 521p, 530p], [537p, 544p, 551p, 600p], [646p, 652p, 657p, 702p], [746p, 752p, 757p, 802p], [846p, 852p, 857p, 902p], [946p, 952p, 957p, 1002p], [1046p, 1052p, 1057p, 1102p]]
   
--- ---
time_points: [City Bus Station (Platform 4), Macarthur / Miller O'Connor, Lyneham Shops Wattle Street, Dickson] time_points: [City Bus Station (Platform 4), Macarthur / Miller O'Connor, Lyneham / Wattle St, Dickson / Antill St]
long_name: To Dickson long_name: To Dickson
between_stops: {} between_stops: {}
   
short_name: "8" short_name: "8"
stop_times: [[655a, 702a, 707a, 713a], [714a, 721a, 726a, 732a], [741a, 750a, 757a, 804a], [811a, 820a, 827a, 834a], [841a, 850a, 857a, 904a], [915a, 924a, 931a, 937a], [946a, 953a, 958a, 1004a], [1018a, 1025a, 1030a, 1036a], [1046a, 1053a, 1058a, 1104a], [1146a, 1153a, 1158a, 1204p], [1246p, 1253p, 1258p, 104p], [146p, 153p, 158p, 204p], [246p, 253p, 258p, 305p], [311p, 320p, 327p, 334p], [346p, 355p, 402p, 409p], [411p, 420p, 427p, 434p], [444p, 453p, 500p, 507p], [523p, 532p, 539p, 546p], [553p, 602p, 609p, 616p], [623p, 631p, 636p, 642p], [650p, 655p, 700p, 706p], [705p, 710p, 715p, 721p], [805p, 810p, 815p, 821p], [905p, 910p, 915p, 921p], [1005p, 1010p, 1015p, 1021p], [1105p, 1110p, 1115p, 1121p]] stop_times: [[655a, 702a, 707a, 713a], [714a, 721a, 726a, 732a], [741a, 750a, 757a, 804a], [811a, 820a, 827a, 834a], [841a, 850a, 857a, 904a], [915a, 924a, 931a, 937a], [946a, 953a, 958a, 1004a], [1018a, 1025a, 1030a, 1036a], [1046a, 1053a, 1058a, 1104a], [1146a, 1153a, 1158a, 1204p], [1246p, 1253p, 1258p, 104p], [146p, 153p, 158p, 204p], [246p, 253p, 258p, 305p], [311p, 320p, 327p, 334p], [346p, 355p, 402p, 409p], [411p, 420p, 427p, 434p], [444p, 453p, 500p, 507p], [523p, 532p, 539p, 546p], [553p, 602p, 609p, 616p], [623p, 631p, 636p, 642p], [650p, 655p, 700p, 706p], [705p, 710p, 715p, 721p], [805p, 810p, 815p, 821p], [905p, 910p, 915p, 921p], [1005p, 1010p, 1015p, 1021p], [1105p, 1110p, 1115p, 1121p]]
   
--- ---
time_points: [Woden Bus Station (Platform 4), Alexander Maconochie Centre] time_points: [Woden Bus Station (Platform 4), Alexander Maconochie Centre]
long_name: To Alexander Maconochie Centre long_name: To Alexander Maconochie Centre
between_stops: {} between_stops:
  Woden Bus Station (Platform 4)-Alexander Maconochie Centre: [Wjz3dXS, Wjz3kAx]
short_name: "88" short_name: "88"
stop_times: [[920a, 940a], [1255p, 115p], [455p, 515p]] stop_times: [[920a, 940a], [1255p, 115p], [455p, 515p]]
   
--- ---
time_points: [Alexander Maconochie Centre, Woden Bus Station] time_points: [Alexander Maconochie Centre, Woden Bus Station]
long_name: To Woden Bus Station long_name: To Woden Bus Station
between_stops: {} between_stops:
  Alexander Maconochie Centre-Woden Bus Station: [Wjz3kAx, Wjz3dXS]
short_name: "88" short_name: "88"
stop_times: [[1150a, 1210p], [320p, 340p], [730p, 750p]] stop_times: [[1150a, 1210p], [320p, 340p], [730p, 750p]]
   
--- ---
time_points: [City Bus Station (Platform 7), St Thomas More's Campbell, Russell Offices, Hospice / Menindee Dr, ADFA, Campbell Park Offices] time_points: [City Bus Station (Platform 7), St Thomas More's Campbell, Russell Offices, Hospice / Menindee Dr, ADFA, Campbell Park Offices]
long_name: To Campbell Park Offices long_name: To Campbell Park Offices
between_stops: between_stops:
  Hospice / Menindee Dr-ADFA: [Wjzcd8D, Wjzcd2U, Wjzcdi7, Wjzcdsn, WjzcdDs, WjzceFT, WjzceHt]
ADFA-Campbell Park Offices: [Wjzcend, Wjzce4H, Wjzce7O] ADFA-Campbell Park Offices: [Wjzcend, Wjzce4H, Wjzce7O]
short_name: "9" short_name: "9"
stop_times: [[714a, 726a, 731a, 733a, 741a, 745a], [814a, 829a, 834a, 836a, 844a, 848a], [857a, 911a, 916a, 918a, 926a, 931a], [957a, 1011a, 1016a, 1018a, 1026a, 1029a], [1057a, 1111a, 1116a, 1118a, 1126a, 1129a], [1157a, 1211p, 1216p, 1218p, 1226p, 1229p], [1257p, 111p, 116p, 118p, 126p, 129p], [157p, 211p, 216p, 218p, 226p, 229p], [257p, 312p, 317p, 319p, 327p, 331p], [344p, 359p, 404p, 406p, 414p, 418p], [414p, 429p, 434p, 436p, 444p, 448p], [444p, 459p, 504p, 506p, 514p, 518p], [514p, 529p, 534p, 536p, 544p, 548p], [557p, 612p, 617p, 619p, 627p, 631p], [657p, 708p, 712p, 714p, 720p, 723p], [757p, 808p, 812p, 814p, 820p, 823p], [857p, 908p, 912p, 914p, 920p, 923p], [957p, 1008p, 1012p, 1014p, 1020p, 1023p], [1057p, 1108p, 1112p, 1114p, 1120p, 1123p]] stop_times: [[714a, 726a, 731a, 733a, 741a, 745a], [814a, 829a, 834a, 836a, 844a, 848a], [857a, 911a, 916a, 918a, 926a, 931a], [957a, 1011a, 1016a, 1018a, 1026a, 1029a], [1057a, 1111a, 1116a, 1118a, 1126a, 1129a], [1157a, 1211p, 1216p, 1218p, 1226p, 1229p], [1257p, 111p, 116p, 118p, 126p, 129p], [157p, 211p, 216p, 218p, 226p, 229p], [257p, 312p, 317p, 319p, 327p, 331p], [344p, 359p, 404p, 406p, 414p, 418p], [414p, 429p, 434p, 436p, 444p, 448p], [444p, 459p, 504p, 506p, 514p, 518p], [514p, 529p, 534p, 536p, 544p, 548p], [557p, 612p, 617p, 619p, 627p, 631p], [657p, 708p, 712p, 714p, 720p, 723p], [757p, 808p, 812p, 814p, 820p, 823p], [857p, 908p, 912p, 914p, 920p, 923p], [957p, 1008p, 1012p, 1014p, 1020p, 1023p], [1057p, 1108p, 1112p, 1114p, 1120p, 1123p]]
   
--- ---
time_points: [Campbell Park Offices, ADFA, Hospice / Menindee Dr, Russell Offices, St Thomas More's Campbell, City Bus Station] time_points: [Campbell Park Offices, ADFA, Hospice / Menindee Dr, Russell Offices, St Thomas More's Campbell, City Bus Station]
long_name: To City Bus Station long_name: To City Bus Station
between_stops: between_stops:
  ADFA-Hospice / Menindee Dr: [WjzceHt, WjzceFT, WjzcdDs, Wjzcdsn, Wjzcdi7, Wjzcd2U, Wjzcd8D]
Campbell Park Offices-ADFA: [Wjzce7O, Wjzce4H, Wjzcend] Campbell Park Offices-ADFA: [Wjzce7O, Wjzce4H, Wjzcend]
short_name: "9" short_name: "9"
stop_times: [["-", 655a, 701a, 703a, 708a, 720a], [720a, 723a, 729a, 731a, 736a, 751a], [752a, 756a, 804a, 806a, 811a, 826a], [822a, 826a, 834a, 836a, 841a, 856a], [852a, 856a, 904a, 906a, 911a, 926a], [934a, 937a, 945a, 947a, 952a, 1006a], [1034a, 1037a, 1045a, 1047a, 1052a, 1106a], [1134a, 1137a, 1145a, 1147a, 1152a, 1206p], [1234p, 1237p, 1245p, 1247p, 1252p, 106p], [134p, 137p, 145p, 147p, 152p, 206p], [234p, 237p, 245p, 247p, 252p, 306p], [335p, 339p, 347p, 349p, 354p, 409p], [352p, 356p, 404p, 406p, 411p, 426p], [422p, 426p, 434p, 436p, 441p, 456p], [452p, 456p, 504p, 506p, 511p, 526p], [522p, 526p, 534p, 536p, 541p, 556p], [552p, 556p, 604p, 606p, 611p, 626p], [628p, 632p, 638p, 640p, 645p, 656p], [728p, 731p, 737p, 739p, 744p, 755p], [828p, 831p, 837p, 839p, 844p, 855p], [928p, 931p, 937p, 939p, 944p, 955p], [1028p, 1031p, 1037p, 1039p, 1044p, 1055p]] stop_times: [["-", 655a, 701a, 703a, 708a, 720a], [720a, 723a, 729a, 731a, 736a, 751a], [752a, 756a, 804a, 806a, 811a, 826a], [822a, 826a, 834a, 836a, 841a, 856a], [852a, 856a, 904a, 906a, 911a, 926a], [934a, 937a, 945a, 947a, 952a, 1006a], [1034a, 1037a, 1045a, 1047a, 1052a, 1106a], [1134a, 1137a, 1145a, 1147a, 1152a, 1206p], [1234p, 1237p, 1245p, 1247p, 1252p, 106p], [134p, 137p, 145p, 147p, 152p, 206p], [234p, 237p, 245p, 247p, 252p, 306p], [335p, 339p, 347p, 349p, 354p, 409p], [352p, 356p, 404p, 406p, 411p, 426p], [422p, 426p, 434p, 436p, 441p, 456p], [452p, 456p, 504p, 506p, 511p, 526p], [522p, 526p, 534p, 536p, 541p, 556p], [552p, 556p, 604p, 606p, 611p, 626p], [628p, 632p, 638p, 640p, 645p, 656p], [728p, 731p, 737p, 739p, 744p, 755p], [828p, 831p, 837p, 839p, 844p, 855p], [928p, 931p, 937p, 939p, 944p, 955p], [1028p, 1031p, 1037p, 1039p, 1044p, 1055p]]
   
---  
time_points: [Tuggeranong Bus Station (Platform 8), Erindale Centre, Woden Bus Station (Platform 9), City Bus Station (Platform 3), Belconnen Community Bus Station, Westfield Bus Station, Cohen Street Bus Station]  
long_name: To Cohen Street Bus Station  
between_stops:  
Westfield Bus Station-Cohen Street Bus Station: []  
Belconnen Community Bus Station-Westfield Bus Station: []  
short_name: "900"  
stop_times: [[730a, 741a, 757a, 813a, 830a, 832a, 837a], [745a, 756a, 812a, 828a, 845a, 847a, 852a], [800a, 811a, 827a, 843a, 900a, 902a, 907a], [815a, 826a, 842a, 858a, 915a, 917a, 922a], [830a, 841a, 857a, 913a, 930a, 932a, 937a], [845a, 856a, 912a, 928a, 945a, 947a, 952a], [900a, 911a, 927a, 943a, 1000a, 1002a, 1007a], [915a, 926a, 942a, 958a, 1015a, 1017a, 1022a], [930a, 941a, 957a, 1013a, 1030a, 1032a, 1037a], [945a, 956a, 1012a, 1028a, 1045a, 1047a, 1052a], [1000a, 1011a, 1027a, 1043a, 1100a, 1102a, 1107a], [1015a, 1026a, 1042a, 1058a, 1115a, 1117a, 1122a], [1030a, 1041a, 1057a, 1113a, 1130a, 1132a, 1137a], [1045a, 1056a, 1112a, 1128a, 1145a, 1147a, 1152a], [1100a, 1111a, 1127a, 1143a, 1200p, 1202p, 1207p], [1115a, 1126a, 1142a, 1158a, 1215p, 1217p, 1222p], [1130a, 1141a, 1157a, 1213p, 1230p, 1232p, 1237p], [1145a, 1156a, 1212p, 1228p, 1245p, 1247p, 1252p], [1200p, 1211p, 1227p, 1243p, 100p, 102p, 107p], [1215p, 1226p, 1242p, 1258p, 115p, 117p, 122p], [1230p, 1241p, 1257p, 113p, 130p, 132p, 137p], [1245p, 1256p, 112p, 128p, 145p, 147p, 152p], [100p, 111p, 127p, 143p, 200p, 202p, 207p], [115p, 126p, 142p, 158p, 215p, 217p, 222p], [130p, 141p, 157p, 213p, 230p, 232p, 237p], [145p, 156p, 212p, 228p, 245p, 247p, 252p], [200p, 211p, 227p, 243p, 300p, 302p, 307p], [215p, 226p, 242p, 258p, 315p, 317p, 322p], [230p, 241p, 257p, 313p, 330p, 332p, 337p], [245p, 256p, 312p, 328p, 345p, 347p, 352p], [300p, 311p, 327p, 343p, 400p, 402p, 407p], [315p, 326p, 342p, 358p, 415p, 417p, 422p], [330p, 341p, 357p, 413p, 430p, 432p, 437p], [345p, 356p, 412p, 428p, 445p, 447p, 452p], [400p, 411p, 427p, 443p, 500p, 502p, 507p], [415p, 426p, 442p, 458p, 515p, 517p, 522p], [430p, 441p, 457p, 513p, 530p, 532p, 537p], [445p, 456p, 512p, 528p, 545p, 547p, 552p], [500p, 511p, 527p, 543p, 600p, 602p, 607p], [515p, 526p, 542p, 558p, 615p, 617p, 622p], [530p, 541p, 557p, 613p, 630p, 632p, 637p], [545p, 556p, 612p, 628p, 645p, 647p, 652p], [600p, 611p, 627p, 642p, 659p, 701p, 706p], [615p, 626p, 641p, 656p, 713p, 715p, 720p], [630p, 640p, 655p, 710p, 727p, 729p, 734p], [645p, 655p, 710p, 725p, 742p, 744p, 749p], [700p, 710p, 725p, 740p, 757p, 759p, 804p], [715p, 725p, 740p, 755p, 812p, 814p, 819p]]  
 
---  
time_points: [Cohen Street Bus Station (Platform 2), Westfield Bus Station (Platform 1), Belconnen Community Bus Station (Platform 1), City Bus Station (Platform 1), Woden Bus Station (Platform 6), Erindale Centre, Tuggeranong Bus Station]  
long_name: To Tuggeranong Bus Station  
between_stops:  
Cohen Street Bus Station (Platform 2)-Westfield Bus Station (Platform 1): []  
Westfield Bus Station (Platform 1)-Belconnen Community Bus Station (Platform 1): []  
short_name: "900"  
stop_times: [[731a, 733a, 737a, 757a, 814a, 829a, 835a], [746a, 748a, 752a, 812a, 829a, 844a, 850a], [801a, 803a, 807a, 827a, 844a, 859a, 905a], [816a, 818a, 822a, 842a, 859a, 914a, 920a], [831a, 833a, 837a, 857a, 914a, 929a, 935a], [846a, 848a, 852a, 912a, 929a, 944a, 950a], [901a, 903a, 907a, 927a, 944a, 959a, 1005a], [916a, 918a, 922a, 942a, 959a, 1014a, 1020a], [931a, 933a, 937a, 957a, 1014a, 1029a, 1035a], [946a, 948a, 952a, 1012a, 1029a, 1044a, 1050a], [1001a, 1003a, 1007a, 1027a, 1044a, 1059a, 1105a], [1016a, 1018a, 1022a, 1042a, 1059a, 1114a, 1120a], [1031a, 1033a, 1037a, 1057a, 1114a, 1129a, 1135a], [1046a, 1048a, 1052a, 1112a, 1129a, 1144a, 1150a], [1101a, 1103a, 1107a, 1127a, 1144a, 1159a, 1205p], [1116a, 1118a, 1122a, 1142a, 1159a, 1214p, 1220p], [1131a, 1133a, 1137a, 1157a, 1214p, 1229p, 1235p], [1146a, 1148a, 1152a, 1212p, 1229p, 1244p, 1250p], [1201p, 1203p, 1207p, 1227p, 1244p, 1259p, 105p], [1216p, 1218p, 1222p, 1242p, 1259p, 114p, 120p], [1231p, 1233p, 1237p, 1257p, 114p, 129p, 135p], [1246p, 1248p, 1252p, 112p, 129p, 144p, 150p], [101p, 103p, 107p, 127p, 144p, 159p, 205p], [116p, 118p, 122p, 142p, 159p, 214p, 220p], [131p, 133p, 137p, 157p, 214p, 229p, 235p], [146p, 148p, 152p, 212p, 229p, 244p, 250p], [201p, 203p, 207p, 227p, 244p, 259p, 305p], [216p, 218p, 222p, 242p, 259p, 314p, 320p], [231p, 233p, 237p, 257p, 314p, 329p, 335p], [246p, 248p, 252p, 312p, 329p, 344p, 350p], [301p, 303p, 307p, 327p, 344p, 359p, 405p], [316p, 318p, 322p, 342p, 359p, 414p, 420p], [331p, 333p, 337p, 357p, 414p, 429p, 435p], [346p, 348p, 352p, 412p, 429p, 444p, 450p], [401p, 403p, 407p, 427p, 444p, 459p, 505p], [416p, 418p, 422p, 442p, 459p, 514p, 520p], [431p, 433p, 437p, 457p, 514p, 529p, 535p], [446p, 448p, 452p, 512p, 529p, 544p, 550p], [501p, 503p, 507p, 527p, 544p, 559p, 605p], [516p, 518p, 522p, 542p, 559p, 614p, 620p], [531p, 533p, 537p, 557p, 614p, 629p, 635p], [546p, 548p, 552p, 612p, 629p, 643p, 649p], [601p, 603p, 607p, 627p, 642p, 656p, 702p], [616p, 618p, 622p, 641p, 655p, 709p, 715p], [631p, 633p, 637p, 656p, 710p, 724p, 730p], [646p, 648p, 652p, 711p, 725p, 739p, 745p], [701p, 703p, 707p, 726p, 740p, 754p, 800p]]  
 
---  
time_points: [Belconnen Community Bus Station (Platform 4), Westfield Bus Station (Platform 2), Cohen Street Bus Station (Platform 6), McKellar Shops, Evatt Shops, Spence Terminus, Evatt Shops, McKellar Shops, Cohen Street Bus Station, Westfield Bus Station, Belconnen Community Bus Station]  
long_name: To Belconnen Community Bus Station  
between_stops:  
Belconnen Community Bus Station (Platform 4)-Westfield Bus Station (Platform 2): []  
Westfield Bus Station-Belconnen Community Bus Station: []  
Cohen Street Bus Station-Westfield Bus Station: []  
Westfield Bus Station (Platform 2)-Cohen Street Bus Station (Platform 6): []  
short_name: "902"  
stop_times: [[851a, 853a, 857a, 904a, 912a, 918a, 923a, 931a, 939a, 941a, 945a], [951a, 953a, 957a, 1004a, 1012a, 1018a, 1023a, 1031a, 1039a, 1041a, 1045a], [1051a, 1053a, 1057a, 1104a, 1112a, 1118a, 1123a, 1131a, 1139a, 1141a, 1145a], [1151a, 1153a, 1157a, 1204p, 1212p, 1218p, 1223p, 1231p, 1239p, 1241p, 1245p], [1251p, 1253p, 1257p, 104p, 112p, 118p, 123p, 131p, 139p, 141p, 145p], [151p, 153p, 157p, 204p, 212p, 218p, 223p, 231p, 239p, 241p, 245p], [251p, 253p, 257p, 304p, 312p, 318p, 323p, 331p, 339p, 341p, 345p], [351p, 353p, 357p, 404p, 412p, 418p, 423p, 431p, 439p, 441p, 445p], [451p, 453p, 457p, 504p, 512p, 518p, 523p, 531p, 539p, 541p, 545p], [551p, 553p, 557p, 604p, 612p, 618p, 623p, 631p, 638p, 640p, 644p], [651p, 653p, 657p, 703p, 710p, 716p, 721p, 729p, 736p, 738p, 742p]]  
 
--- ---
time_points: [Belconnen Community Bus Station (Platform 4), Westfield Bus Station (Platform 2), Cohen Street Bus Station (Platform 6), McKellar Shops, Evatt Shops, Spence Terminus, Evatt Shops, McKellar Shops, Cohen Street Bus Station, Westfield Bus Station, Belconnen Community Bus Station] time_points: [Belconnen Community Bus Station (Platform 4), Westfield Bus Station (Platform 2), Cohen Street Bus Station (Platform 6), McKellar, Evatt, Spence Terminus, Evatt, McKellar, Cohen Street Bus Station, Westfield Bus Station, Belconnen Community Bus Station]
long_name: To Belconnen Community Bus Station long_name: To Belconnen Community Bus Station
between_stops: between_stops:
Belconnen Community Bus Station (Platform 4)-Westfield Bus Station (Platform 2): [] Belconnen Community Bus Station (Platform 4)-Westfield Bus Station (Platform 2): []
Westfield Bus Station-Belconnen Community Bus Station: [] Westfield Bus Station-Belconnen Community Bus Station: []
Cohen Street Bus Station-Westfield Bus Station: [] Cohen Street Bus Station-Westfield Bus Station: []
Westfield Bus Station (Platform 2)-Cohen Street Bus Station (Platform 6): [] Westfield Bus Station (Platform 2)-Cohen Street Bus Station (Platform 6): []
stop_times_saturday: [["-", "-", "-", "-", "-", 718a, 723a, 731a, 739a, 741a, 745a], ["-", "-", "-", "-", "-", 818a, 823a, 831a, 839a, 841a, 845a], [851a, 853a, 857a, 904a, 912a, 918a, 923a, 931a, 939a, 941a, 945a], [951a, 953a, 957a, 1004a, 1012a, 1018a, 1023a, 1031a, 1039a, 1041a, 1045a], [1051a, 1053a, 1057a, 1104a, 1112a, 1118a, 1123a, 1131a, 1139a, 1141a, 1145a], [1151a, 1153a, 1157a, 1204p, 1212p, 1218p, 1223p, 1231p, 1239p, 1241p, 1245p], [1251p, 1253p, 1257p, 104p, 112p, 118p, 123p, 131p, 139p, 141p, 145p], [151p, 153p, 157p, 204p, 212p, 218p, 223p, 231p, 239p, 241p, 245p], [251p, 253p, 257p, 304p, 312p, 318p, 323p, 331p, 339p, 341p, 345p], [351p, 353p, 357p, 404p, 412p, 418p, 423p, 431p, 439p, 441p, 445p], [451p, 453p, 457p, 504p, 512p, 518p, 523p, 531p, 539p, 541p, 545p], [551p, 553p, 557p, 604p, 612p, 618p, 623p, 631p, 638p, 640p, 644p], [650p, 652p, 656p, 702p, 709p, 715p, 720p, 728p, 735p, 737p, 741p], [750p, 752p, 756p, 802p, 809p, 815p, 820p, 828p, 835p, 837p, 841p], [850p, 852p, 856p, 902p, 909p, 915p, 920p, 928p, 935p, 937p, 941p], [950p, 952p, 956p, 1002p, 1009p, 1015p, 1020p, 1028p, 1035p, 1037p, 1041p], [1050p, 1052p, 1056p, 1102p, 1109p, 1115p, 1120p, 1128p, 1135p, 1137p, 1141p]] stop_times_saturday: [["-", "-", "-", "-", "-", 718a, 723a, 731a, 739a, 741a, 745a], ["-", "-", "-", "-", "-", 818a, 823a, 831a, 839a, 841a, 845a], [851a, 853a, 857a, 904a, 912a, 918a, 923a, 931a, 939a, 941a, 945a], [951a, 953a, 957a, 1004a, 1012a, 1018a, 1023a, 1031a, 1039a, 1041a, 1045a], [1051a, 1053a, 1057a, 1104a, 1112a, 1118a, 1123a, 1131a, 1139a, 1141a, 1145a], [1151a, 1153a, 1157a, 1204p, 1212p, 1218p, 1223p, 1231p, 1239p, 1241p, 1245p], [1251p, 1253p, 1257p, 104p, 112p, 118p, 123p, 131p, 139p, 141p, 145p], [151p, 153p, 157p, 204p, 212p, 218p, 223p, 231p, 239p, 241p, 245p], [251p, 253p, 257p, 304p, 312p, 318p, 323p, 331p, 339p, 341p, 345p], [351p, 353p, 357p, 404p, 412p, 418p, 423p, 431p, 439p, 441p, 445p], [451p, 453p, 457p, 504p, 512p, 518p, 523p, 531p, 539p, 541p, 545p], [551p, 553p, 557p, 604p, 612p, 618p, 623p, 631p, 638p, 640p, 644p], [650p, 652p, 656p, 702p, 709p, 715p, 720p, 728p, 735p, 737p, 741p], [750p, 752p, 756p, 802p, 809p, 815p, 820p, 828p, 835p, 837p, 841p], [850p, 852p, 856p, 902p, 909p, 915p, 920p, 928p, 935p, 937p, 941p], [950p, 952p, 956p, 1002p, 1009p, 1015p, 1020p, 1028p, 1035p, 1037p, 1041p], [1050p, 1052p, 1056p, 1102p, 1109p, 1115p, 1120p, 1128p, 1135p, 1137p, 1141p]]
short_name: "902" short_name: "902"
   
--- ---
time_points: [Belconnen Community Bus Station (Platform 4), Westfield Bus Station (Platform 2), Cohen Street Bus Station (Platform 6), McKellar Shops, Evatt Shops, Spence Terminus, Evatt Shops, McKellar Shops, Cohen Street Bus Station, Westfield Bus Station, Belconnen Community Bus Station] time_points: [Belconnen Community Bus Station (Platform 4), Westfield Bus Station (Platform 2), Cohen Street Bus Station (Platform 6), McKellar, Evatt, Spence Terminus, Evatt, McKellar, Cohen Street Bus Station, Westfield Bus Station, Belconnen Community Bus Station]
long_name: To Belconnen Community Bus Station long_name: To Belconnen Community Bus Station
between_stops: between_stops:
Belconnen Community Bus Station (Platform 4)-Westfield Bus Station (Platform 2): [] Belconnen Community Bus Station (Platform 4)-Westfield Bus Station (Platform 2): []
Westfield Bus Station-Belconnen Community Bus Station: [] Westfield Bus Station-Belconnen Community Bus Station: []
Cohen Street Bus Station-Westfield Bus Station: [] Cohen Street Bus Station-Westfield Bus Station: []
Westfield Bus Station (Platform 2)-Cohen Street Bus Station (Platform 6): [] Westfield Bus Station (Platform 2)-Cohen Street Bus Station (Platform 6): []
short_name: "902" short_name: "902"
stop_times_sunday: [[851a, 853a, 857a, 904a, 912a, 918a, 923a, 931a, 939a, 941a, 945a], [951a, 953a, 957a, 1004a, 1012a, 1018a, 1023a, 1031a, 1039a, 1041a, 1045a], [1051a, 1053a, 1057a, 1104a, 1112a, 1118a, 1123a, 1131a, 1139a, 1141a, 1145a], [1151a, 1153a, 1157a, 1204p, 1212p, 1218p, 1223p, 1231p, 1239p, 1241p, 1245p], [1251p, 1253p, 1257p, 104p, 112p, 118p, 123p, 131p, 139p, 141p, 145p], [151p, 153p, 157p, 204p, 212p, 218p, 223p, 231p, 239p, 241p, 245p], [251p, 253p, 257p, 304p, 312p, 318p, 323p, 331p, 339p, 341p, 345p], [351p, 353p, 357p, 404p, 412p, 418p, 423p, 431p, 439p, 441p, 445p], [451p, 453p, 457p, 504p, 512p, 518p, 523p, 531p, 539p, 541p, 545p], [551p, 553p, 557p, 604p, 612p, 618p, 623p, 631p, 638p, 640p, 644p], [651p, 653p, 657p, 703p, 710p, 716p, 721p, 729p, 736p, 738p, 742p]] stop_times_sunday: [[851a, 853a, 857a, 904a, 912a, 918a, 923a, 931a, 939a, 941a, 945a], [951a, 953a, 957a, 1004a, 1012a, 1018a, 1023a, 1031a, 1039a, 1041a, 1045a], [1051a, 1053a, 1057a, 1104a, 1112a, 1118a, 1123a, 1131a, 1139a, 1141a, 1145a], [1151a, 1153a, 1157a, 1204p, 1212p, 1218p, 1223p, 1231p, 1239p, 1241p, 1245p], [1251p, 1253p, 1257p, 104p, 112p, 118p, 123p, 131p, 139p, 141p, 145p], [151p, 153p, 157p, 204p, 212p, 218p, 223p, 231p, 239p, 241p, 245p], [251p, 253p, 257p, 304p, 312p, 318p, 323p, 331p, 339p, 341p, 345p], [351p, 353p, 357p, 404p, 412p, 418p, 423p, 431p, 439p, 441p, 445p], [451p, 453p, 457p, 504p, 512p, 518p, 523p, 531p, 539p, 541p, 545p], [551p, 553p, 557p, 604p, 612p, 618p, 623p, 631p, 638p, 640p, 644p], [651p, 653p, 657p, 703p, 710p, 716p, 721p, 729p, 736p, 738p, 742p]]
   
---  
time_points: [Belconnen Community Bus Station (Platform 4), Westfield Bus Station (Platform 2), Cohen Street Bus Station (Platform 6), Kippax, Fraser West Terminus, Kippax, Cohen Street Bus Station, Westfield Bus Station, Belconnen Community Bus Station]  
long_name: To Belconnen Community Bus Station  
between_stops:  
Belconnen Community Bus Station (Platform 4)-Westfield Bus Station (Platform 2): []  
Westfield Bus Station-Belconnen Community Bus Station: []  
Cohen Street Bus Station-Westfield Bus Station: []  
Westfield Bus Station (Platform 2)-Cohen Street Bus Station (Platform 6): []  
short_name: "903"  
stop_times: [[859a, 901a, 905a, 919a, 934a, 948a, 1002a, 1004a, 1008a], [959a, 1001a, 1005a, 1019a, 1034a, 1048a, 1102a, 1104a, 1108a], [1059a, 1101a, 1105a, 1119a, 1134a, 1148a, 1202p, 1204p, 1208p], [1159a, 1201p, 1205p, 1219p, 1234p, 1248p, 102p, 104p, 108p], [1259p, 101p, 105p, 119p, 134p, 148p, 202p, 204p, 208p], [159p, 201p, 205p, 219p, 234p, 248p, 302p, 304p, 308p], [259p, 301p, 305p, 319p, 334p, 348p, 402p, 404p, 408p], [359p, 401p, 405p, 419p, 434p, 448p, 502p, 504p, 508p], [459p, 501p, 505p, 519p, 534p, 548p, 602p, 604p, 608p], [559p, 601p, 605p, 619p, 634p, 648p, 701p, 703p, 707p]]  
 
---  
time_points: [Belconnen Community Bus Station (Platform 6), Westfield Bus Station (Platform 2), Cohen Street Bus Station (Platform 5), Higgins Shops, Kippax, Higgins Shops, Cohen Street Bus Station, Westfield Bus Station, Belconnen Community Bus Station]  
long_name: To Belconnen Community Bus Station  
between_stops:  
Belconnen Community Bus Station (Platform 6)-Westfield Bus Station (Platform 2): []  
Westfield Bus Station-Belconnen Community Bus Station: []  
Cohen Street Bus Station-Westfield Bus Station: []  
Westfield Bus Station (Platform 2)-Cohen Street Bus Station (Platform 5): []  
short_name: "904"  
stop_times: [[819a, 821a, 825a, 846a, 857a, 907a, 928a, 930a, 934a], [919a, 921a, 925a, 946a, 957a, 1007a, 1028a, 1030a, 1034a], [1019a, 1021a, 1025a, 1046a, 1057a, 1107a, 1128a, 1130a, 1134a], [1119a, 1121a, 1125a, 1146a, 1157a, 1207p, 1228p, 1230p, 1234p], [1219p, 1221p, 1225p, 1246p, 1257p, 107p, 128p, 130p, 134p], [119p, 121p, 125p, 146p, 157p, 207p, 228p, 230p, 234p], [219p, 221p, 225p, 246p, 257p, 307p, 328p, 330p, 334p], [319p, 321p, 325p, 346p, 357p, 407p, 428p, 430p, 434p], [419p, 421p, 425p, 446p, 457p, 507p, 528p, 530p, 534p], [519p, 521p, 525p, 546p, 557p, 607p, 628p, 630p, 634p], [619p, 621p, 625p, 645p, 656p, 706p, 726p, 728p, 732p]]  
 
--- ---
time_points: [Belconnen Community Bus Station (Platform 6), Westfield Bus Station (Platform 2), Cohen Street Bus Station (Platform 5), Higgins Shops, Kippax, Higgins Shops, Cohen Street Bus Station, Westfield Bus Station, Belconnen Community Bus Station] time_points: [Belconnen Community Bus Station (Platform 6), Westfield Bus Station (Platform 2), Cohen Street Bus Station (Platform 5), Higgins, Kippax, Higgins, Cohen Street Bus Station, Westfield Bus Station, Belconnen Community Bus Station]
long_name: To Belconnen Community Bus Station long_name: To Belconnen Community Bus Station
between_stops: between_stops:
Belconnen Community Bus Station (Platform 6)-Westfield Bus Station (Platform 2): [] Belconnen Community Bus Station (Platform 6)-Westfield Bus Station (Platform 2): []
Westfield Bus Station-Belconnen Community Bus Station: [] Westfield Bus Station-Belconnen Community Bus Station: []
Cohen Street Bus Station-Westfield Bus Station: [] Cohen Street Bus Station-Westfield Bus Station: []
Westfield Bus Station (Platform 2)-Cohen Street Bus Station (Platform 5): [] Westfield Bus Station (Platform 2)-Cohen Street Bus Station (Platform 5): []
stop_times_saturday: [["-", "-", "-", "-", 757a, 807a, 828a, 830a, 834a], [819a, 821a, 825a, 846a, 857a, 907a, 928a, 930a, 934a], [919a, 921a, 925a, 946a, 957a, 1007a, 1028a, 1030a, 1034a], [1019a, 1021a, 1025a, 1046a, 1057a, 1107a, 1128a, 1130a, 1134a], [1119a, 1121a, 1125a, 1146a, 1157a, 1207p, 1228p, 1230p, 1234p], [1219p, 1221p, 1225p, 1246p, 1257p, 107p, 128p, 130p, 134p], [119p, 121p, 125p, 146p, 157p, 207p, 228p, 230p, 234p], [219p, 221p, 225p, 246p, 257p, 307p, 328p, 330p, 334p], [319p, 321p, 325p, 346p, 357p, 407p, 428p, 430p, 434p], [419p, 421p, 425p, 446p, 457p, 507p, 528p, 530p, 534p], [519p, 521p, 525p, 546p, 557p, 607p, 628p, 630p, 634p], [619p, 621p, 625p, 645p, 656p, 706p, 726p, 728p, 732p], [718p, 720p, 724p, 744p, 755p, 805p, 825p, 827p, 831p], [818p, 820p, 824p, 844p, 855p, 905p, 925p, 927p, 931p], [918p, 920p, 924p, 944p, 955p, 1005p, 1025p, 1027p, 1031p], [1018p, 1020p, 1024p, 1044p, 1055p, 1105p, 1125p, 1127p, 1131p], [1118p, 1120p, 1124p, 1144p, 1155p, "-", "-", "-", "-"]] stop_times_saturday: [["-", "-", "-", "-", 757a, 807a, 828a, 830a, 834a], [819a, 821a, 825a, 846a, 857a, 907a, 928a, 930a, 934a], [919a, 921a, 925a, 946a, 957a, 1007a, 1028a, 1030a, 1034a], [1019a, 1021a, 1025a, 1046a, 1057a, 1107a, 1128a, 1130a, 1134a], [1119a, 1121a, 1125a, 1146a, 1157a, 1207p, 1228p, 1230p, 1234p], [1219p, 1221p, 1225p, 1246p, 1257p, 107p, 128p, 130p, 134p], [119p, 121p, 125p, 146p, 157p, 207p, 228p, 230p, 234p], [219p, 221p, 225p, 246p, 257p, 307p, 328p, 330p, 334p], [319p, 321p, 325p, 346p, 357p, 407p, 428p, 430p, 434p], [419p, 421p, 425p, 446p, 457p, 507p, 528p, 530p, 534p], [519p, 521p, 525p, 546p, 557p, 607p, 628p, 630p, 634p], [619p, 621p, 625p, 645p, 656p, 706p, 726p, 728p, 732p], [718p, 720p, 724p, 744p, 755p, 805p, 825p, 827p, 831p], [818p, 820p, 824p, 844p, 855p, 905p, 925p, 927p, 931p], [918p, 920p, 924p, 944p, 955p, 1005p, 1025p, 1027p, 1031p], [1018p, 1020p, 1024p, 1044p, 1055p, 1105p, 1125p, 1127p, 1131p], [1118p, 1120p, 1124p, 1144p, 1155p, "-", "-", "-", "-"]]
short_name: "904" short_name: "904"
   
--- ---
time_points: [Belconnen Community Bus Station (Platform 6), Westfield Bus Station (Platform 2), Cohen Street Bus Station (Platform 5), Higgins Shops, Kippax, Higgins Shops, Cohen Street Bus Station, Westfield Bus Station, Belconnen Community Bus Station] time_points: [Belconnen Community Bus Station (Platform 6), Westfield Bus Station (Platform 2), Cohen Street Bus Station (Platform 5), Higgins, Kippax, Higgins, Cohen Street Bus Station, Westfield Bus Station, Belconnen Community Bus Station]
long_name: To Belconnen Community Bus Station long_name: To Belconnen Community Bus Station
between_stops: between_stops:
Belconnen Community Bus Station (Platform 6)-Westfield Bus Station (Platform 2): [] Belconnen Community Bus Station (Platform 6)-Westfield Bus Station (Platform 2): []
Westfield Bus Station-Belconnen Community Bus Station: [] Westfield Bus Station-Belconnen Community Bus Station: []
Cohen Street Bus Station-Westfield Bus Station: [] Cohen Street Bus Station-Westfield Bus Station: []
Westfield Bus Station (Platform 2)-Cohen Street Bus Station (Platform 5): [] Westfield Bus Station (Platform 2)-Cohen Street Bus Station (Platform 5): []
short_name: "904" short_name: "904"
stop_times_sunday: [[819a, 821a, 825a, 846a, 857a, 907a, 928a, 930a, 934a], [919a, 921a, 925a, 946a, 957a, 1007a, 1028a, 1030a, 1034a], [1019a, 1021a, 1025a, 1046a, 1057a, 1107a, 1128a, 1130a, 1134a], [1119a, 1121a, 1125a, 1146a, 1157a, 1207p, 1228p, 1230p, 1234p], [1219p, 1221p, 1225p, 1246p, 1257p, 107p, 128p, 130p, 134p], [119p, 121p, 125p, 146p, 157p, 207p, 228p, 230p, 234p], [219p, 221p, 225p, 246p, 257p, 307p, 328p, 330p, 334p], [319p, 321p, 325p, 346p, 357p, 407p, 428p, 430p, 434p], [419p, 421p, 425p, 446p, 457p, 507p, 528p, 530p, 534p], [519p, 521p, 525p, 546p, 557p, 607p, 628p, 630p, 634p], [619p, 621p, 625p, 645p, 656p, 706p, 726p, 728p, 732p]] stop_times_sunday: [[819a, 821a, 825a, 846a, 857a, 907a, 928a, 930a, 934a], [919a, 921a, 925a, 946a, 957a, 1007a, 1028a, 1030a, 1034a], [1019a, 1021a, 1025a, 1046a, 1057a, 1107a, 1128a, 1130a, 1134a], [1119a, 1121a, 1125a, 1146a, 1157a, 1207p, 1228p, 1230p, 1234p], [1219p, 1221p, 1225p, 1246p, 1257p, 107p, 128p, 130p, 134p], [119p, 121p, 125p, 146p, 157p, 207p, 228p, 230p, 234p], [219p, 221p, 225p, 246p, 257p, 307p, 328p, 330p, 334p], [319p, 321p, 325p, 346p, 357p, 407p, 428p, 430p, 434p], [419p, 421p, 425p, 446p, 457p, 507p, 528p, 530p, 534p], [519p, 521p, 525p, 546p, 557p, 607p, 628p, 630p, 634p], [619p, 621p, 625p, 645p, 656p, 706p, 726p, 728p, 732p]]
   
---  
time_points: [Belconnen Community Bus Station (Platform 4), Westfield Bus Station (Platform 2), Cohen Street Bus Station (Platform 6), Kippax, Macgregor, Charnwood Shops, Fraser West Terminus, Charnwood Shops, Macgregor, Kippax, Cohen Street Bus Station, Westfield Bus Station, Belconnen Community Bus Station]  
long_name: To Belconnen Community Bus Station  
between_stops:  
Belconnen Community Bus Station (Platform 4)-Westfield Bus Station (Platform 2): []  
Westfield Bus Station-Belconnen Community Bus Station: []  
Cohen Street Bus Station-Westfield Bus Station: []  
Westfield Bus Station (Platform 2)-Cohen Street Bus Station (Platform 6): []  
short_name: "905"  
stop_times: [["-", "-", "-", "-", "-", "-", 857a, 909a, 916a, 923a, 936a, 938a, 942a], [914a, 916a, 920a, 933a, 939a, 946a, 957a, 1009a, 1016a, 1023a, 1036a, 1038a, 1042a], [1014a, 1016a, 1020a, 1033a, 1039a, 1046a, 1057a, 1109a, 1116a, 1123a, 1136a, 1138a, 1142a], [1114a, 1116a, 1120a, 1133a, 1139a, 1146a, 1157a, 1209p, 1216p, 1223p, 1236p, 1238p, 1242p], [1214p, 1216p, 1220p, 1233p, 1239p, 1246p, 1257p, 109p, 116p, 123p, 136p, 138p, 142p], [114p, 116p, 120p, 133p, 139p, 146p, 157p, 209p, 216p, 223p, 236p, 238p, 242p], [214p, 216p, 220p, 233p, 239p, 246p, 257p, 309p, 316p, 323p, 336p, 338p, 342p], [314p, 316p, 320p, 333p, 339p, 346p, 357p, 409p, 416p, 423p, 436p, 438p, 442p], [414p, 416p, 420p, 433p, 439p, 446p, 457p, 509p, 516p, 523p, 536p, 538p, 542p], [514p, 516p, 520p, 533p, 539p, 546p, 557p, 609p, 616p, 623p, 636p, 638p, 642p], [614p, 616p, 620p, 633p, 639p, 646p, 656p, "-", "-", "-", "-", "-", "-"]]  
 
--- ---
time_points: [Belconnen Community Bus Station (Platform 4), Westfield Bus Station (Platform 2), Cohen Street Bus Station (Platform 6), Kippax, Macgregor, Charnwood Shops, Fraser West Terminus, Charnwood Shops, Macgregor, Kippax, Cohen Street Bus Station, Westfield Bus Station, Belconnen Community Bus Station] time_points: [Belconnen Community Bus Station (Platform 4), Westfield Bus Station (Platform 2), Cohen Street Bus Station (Platform 6), Kippax, Macgregor, Charnwood, Fraser West Terminus, Charnwood, Macgregor, Kippax, Cohen Street Bus Station, Westfield Bus Station, Belconnen Community Bus Station]
long_name: To Belconnen Community Bus Station long_name: To Belconnen Community Bus Station
between_stops: between_stops:
Belconnen Community Bus Station (Platform 4)-Westfield Bus Station (Platform 2): [] Belconnen Community Bus Station (Platform 4)-Westfield Bus Station (Platform 2): []
Westfield Bus Station-Belconnen Community Bus Station: [] Westfield Bus Station-Belconnen Community Bus Station: []
Cohen Street Bus Station-Westfield Bus Station: [] Cohen Street Bus Station-Westfield Bus Station: []
Westfield Bus Station (Platform 2)-Cohen Street Bus Station (Platform 6): [] Westfield Bus Station (Platform 2)-Cohen Street Bus Station (Platform 6): []
stop_times_saturday: [["-", "-", "-", "-", "-", "-", 757a, 809a, 816a, 823a, 836a, 838a, 842a], [814a, 816a, 820a, 833a, 839a, 846a, 857a, 909a, 916a, 923a, 936a, 938a, 942a], [914a, 916a, 920a, 933a, 939a, 946a, 957a, 1009a, 1016a, 1023a, 1036a, 1038a, 1042a], [1014a, 1016a, 1020a, 1033a, 1039a, 1046a, 1057a, 1109a, 1116a, 1123a, 1136a, 1138a, 1142a], [1114a, 1116a, 1120a, 1133a, 1139a, 1146a, 1157a, 1209p, 1216p, 1223p, 1236p, 1238p, 1242p], [1214p, 1216p, 1220p, 1233p, 1239p, 1246p, 1257p, 109p, 116p, 123p, 136p, 138p, 142p], [114p, 116p, 120p, 133p, 139p, 146p, 157p, 209p, 216p, 223p, 236p, 238p, 242p], [214p, 216p, 220p, 233p, 239p, 246p, 257p, 309p, 316p, 323p, 336p, 338p, 342p], [314p, 316p, 320p, 333p, 339p, 346p, 357p, 409p, 416p, 423p, 436p, 438p, 442p], [414p, 416p, 420p, 433p, 439p, 446p, 457p, 509p, 516p, 523p, 536p, 538p, 542p], [514p, 516p, 520p, 533p, 539p, 546p, 557p, 609p, 616p, 623p, 636p, 638p, 642p], [614p, 616p, 620p, 633p, 639p, 646p, 656p, 707p, 714p, 721p, 733p, 735p, 739p], [713p, 715p, 719p, 731p, 737p, 744p, 754p, 805p, 812p, 819p, 831p, 833p, 837p], [813p, 815p, 819p, 831p, 837p, 844p, 854p, 905p, 912p, 919p, 931p, 933p, 937p], [913p, 915p, 919p, 931p, 937p, 944p, 954p, 1005p, 1012p, 1019p, 1031p, 1033p, 1037p], [1013p, 1015p, 1019p, 1031p, 1037p, 1044p, 1054p, "-", "-", "-", "-", "-", "-"], [1113p, 1115p, 1119p, 1131p, 1137p, 1144p, 1154p, "-", "-", "-", "-", "-", "-"]] stop_times_saturday: [["-", "-", "-", "-", "-", "-", 757a, 809a, 816a, 823a, 836a, 838a, 842a], [814a, 816a, 820a, 833a, 839a, 846a, 857a, 909a, 916a, 923a, 936a, 938a, 942a], [914a, 916a, 920a, 933a, 939a, 946a, 957a, 1009a, 1016a, 1023a, 1036a, 1038a, 1042a], [1014a, 1016a, 1020a, 1033a, 1039a, 1046a, 1057a, 1109a, 1116a, 1123a, 1136a, 1138a, 1142a], [1114a, 1116a, 1120a, 1133a, 1139a, 1146a, 1157a, 1209p, 1216p, 1223p, 1236p, 1238p, 1242p], [1214p, 1216p, 1220p, 1233p, 1239p, 1246p, 1257p, 109p, 116p, 123p, 136p, 138p, 142p], [114p, 116p, 120p, 133p, 139p, 146p, 157p, 209p, 216p, 223p, 236p, 238p, 242p], [214p, 216p, 220p, 233p, 239p, 246p, 257p, 309p, 316p, 323p, 336p, 338p, 342p], [314p, 316p, 320p, 333p, 339p, 346p, 357p, 409p, 416p, 423p, 436p, 438p, 442p], [414p, 416p, 420p, 433p, 439p, 446p, 457p, 509p, 516p, 523p, 536p, 538p, 542p], [514p, 516p, 520p, 533p, 539p, 546p, 557p, 609p, 616p, 623p, 636p, 638p, 642p], [614p, 616p, 620p, 633p, 639p, 646p, 656p, 707p, 714p, 721p, 733p, 735p, 739p], [713p, 715p, 719p, 731p, 737p, 744p, 754p, 805p, 812p, 819p, 831p, 833p, 837p], [813p, 815p, 819p, 831p, 837p, 844p, 854p, 905p, 912p, 919p, 931p, 933p, 937p], [913p, 915p, 919p, 931p, 937p, 944p, 954p, 1005p, 1012p, 1019p, 1031p, 1033p, 1037p], [1013p, 1015p, 1019p, 1031p, 1037p, 1044p, 1054p, "-", "-", "-", "-", "-", "-"], [1113p, 1115p, 1119p, 1131p, 1137p, 1144p, 1154p, "-", "-", "-", "-", "-", "-"]]
short_name: "905" short_name: "905"
   
--- ---
time_points: [Belconnen Community Bus Station (Platform 4), Westfield Bus Station (Platform 2), Cohen Street Bus Station (Platform 6), Kippax, Macgregor, Charnwood Shops, Fraser West Terminus, Charnwood Shops, Macgregor, Kippax, Cohen Street Bus Station, Westfield Bus Station, Belconnen Community Bus Station] time_points: [Belconnen Community Bus Station (Platform 4), Westfield Bus Station (Platform 2), Cohen Street Bus Station (Platform 6), Kippax, Macgregor, Charnwood, Fraser West Terminus, Charnwood, Macgregor, Kippax, Cohen Street Bus Station, Westfield Bus Station, Belconnen Community Bus Station]
long_name: To Belconnen Community Bus Station long_name: To Belconnen Community Bus Station
between_stops: between_stops:
Belconnen Community Bus Station (Platform 4)-Westfield Bus Station (Platform 2): [] Belconnen Community Bus Station (Platform 4)-Westfield Bus Station (Platform 2): []
Westfield Bus Station-Belconnen Community Bus Station: [] Westfield Bus Station-Belconnen Community Bus Station: []
Cohen Street Bus Station-Westfield Bus Station: [] Cohen Street Bus Station-Westfield Bus Station: []
Westfield Bus Station (Platform 2)-Cohen Street Bus Station (Platform 6): [] Westfield Bus Station (Platform 2)-Cohen Street Bus Station (Platform 6): []
short_name: "905" short_name: "905"
stop_times_sunday: [["-", "-", "-", "-", "-", "-", 857a, 909a, 916a, 923a, 936a, 938a, 942a], [914a, 916a, 920a, 933a, 939a, 946a, 957a, 1009a, 1016a, 1023a, 1036a, 1038a, 1042a], [1014a, 1016a, 1020a, 1033a, 1039a, 1046a, 1057a, 1109a, 1116a, 1123a, 1136a, 1138a, 1142a], [1114a, 1116a, 1120a, 1133a, 1139a, 1146a, 1157a, 1209p, 1216p, 1223p, 1236p, 1238p, 1242p], [1214p, 1216p, 1220p, 1233p, 1239p, 1246p, 1257p, 109p, 116p, 123p, 136p, 138p, 142p], [114p, 116p, 120p, 133p, 139p, 146p, 157p, 209p, 216p, 223p, 236p, 238p, 242p], [214p, 216p, 220p, 233p, 239p, 246p, 257p, 309p, 316p, 323p, 336p, 338p, 342p], [314p, 316p, 320p, 333p, 339p, 346p, 357p, 409p, 416p, 423p, 436p, 438p, 442p], [414p, 416p, 420p, 433p, 439p, 446p, 457p, 509p, 516p, 523p, 536p, 538p, 542p], [514p, 516p, 520p, 533p, 539p, 546p, 557p, 609p, 616p, 623p, 636p, 638p, 642p], [614p, 616p, 620p, 633p, 639p, 646p, 656p, "-", "-", "-", "-", "-", "-"]] stop_times_sunday: [["-", "-", "-", "-", "-", "-", 857a, 909a, 916a, 923a, 936a, 938a, 942a], [914a, 916a, 920a, 933a, 939a, 946a, 957a, 1009a, 1016a, 1023a, 1036a, 1038a, 1042a], [1014a, 1016a, 1020a, 1033a, 1039a, 1046a, 1057a, 1109a, 1116a, 1123a, 1136a, 1138a, 1142a], [1114a, 1116a, 1120a, 1133a, 1139a, 1146a, 1157a, 1209p, 1216p, 1223p, 1236p, 1238p, 1242p], [1214p, 1216p, 1220p, 1233p, 1239p, 1246p, 1257p, 109p, 116p, 123p, 136p, 138p, 142p], [114p, 116p, 120p, 133p, 139p, 146p, 157p, 209p, 216p, 223p, 236p, 238p, 242p], [214p, 216p, 220p, 233p, 239p, 246p, 257p, 309p, 316p, 323p, 336p, 338p, 342p], [314p, 316p, 320p, 333p, 339p, 346p, 357p, 409p, 416p, 423p, 436p, 438p, 442p], [414p, 416p, 420p, 433p, 439p, 446p, 457p, 509p, 516p, 523p, 536p, 538p, 542p], [514p, 516p, 520p, 533p, 539p, 546p, 557p, 609p, 616p, 623p, 636p, 638p, 642p], [614p, 616p, 620p, 633p, 639p, 646p, 656p, "-", "-", "-", "-", "-", "-"]]
   
---  
time_points: [Belconnen Community Bus Station (Platform 4), Westfield Bus Station (Platform 2), Cohen Street Bus Station (Platform 6), Melba Shops, Spence Terminus, Melba Shops, Cohen Street Bus Station, Westfield Bus Station, Belconnen Community Bus Station]  
long_name: To Belconnen Community Bus Station  
between_stops:  
Belconnen Community Bus Station (Platform 4)-Westfield Bus Station (Platform 2): []  
Westfield Bus Station-Belconnen Community Bus Station: []  
Cohen Street Bus Station-Westfield Bus Station: []  
Westfield Bus Station (Platform 2)-Cohen Street Bus Station (Platform 6): []  
short_name: "906"  
stop_times: [[852a, 854a, 858a, 911a, 925a, 938a, 953a, 955a, 959a], [952a, 954a, 958a, 1011a, 1025a, 1038a, 1053a, 1055a, 1059a], [1052a, 1054a, 1058a, 1111a, 1125a, 1138a, 1153a, 1155a, 1159a], [1152a, 1154a, 1158a, 1211p, 1225p, 1238p, 1253p, 1255p, 1259p], [1252p, 1254p, 1258p, 111p, 125p, 138p, 153p, 155p, 159p], [152p, 154p, 158p, 211p, 225p, 238p, 253p, 255p, 259p], [252p, 254p, 258p, 311p, 325p, 338p, 353p, 355p, 359p], [352p, 354p, 358p, 411p, 425p, 438p, 453p, 455p, 459p], [452p, 454p, 458p, 511p, 525p, 538p, 553p, 555p, 559p], [552p, 554p, 558p, 611p, 625p, 638p, 652p, 654p, 658p]]  
 
--- ---
time_points: [Belconnen Community Bus Station (Platform 4), Westfield Bus Station (Platform 2), Cohen Street Bus Station (Platform 6), Melba Shops, Spence Terminus, Melba Shops, Cohen Street Bus Station, Westfield Bus Station, Belconnen Community Bus Station] time_points: [Belconnen Community Bus Station (Platform 4), Westfield Bus Station (Platform 2), Cohen Street Bus Station (Platform 6), Melba, Spence Terminus, Melba, Cohen Street Bus Station, Westfield Bus Station, Belconnen Community Bus Station]
long_name: To Belconnen Community Bus Station long_name: To Belconnen Community Bus Station
between_stops: between_stops:
Belconnen Community Bus Station (Platform 4)-Westfield Bus Station (Platform 2): [] Belconnen Community Bus Station (Platform 4)-Westfield Bus Station (Platform 2): []
Westfield Bus Station-Belconnen Community Bus Station: [] Westfield Bus Station-Belconnen Community Bus Station: []
Cohen Street Bus Station-Westfield Bus Station: [] Cohen Street Bus Station-Westfield Bus Station: []
Westfield Bus Station (Platform 2)-Cohen Street Bus Station (Platform 6): [] Westfield Bus Station (Platform 2)-Cohen Street Bus Station (Platform 6): []
stop_times_saturday: [["-", "-", "-", "-", 725a, 738a, 753a, 755a, 759a], [752a, 754a, 758a, 811a, 825a, 838a, 853a, 855a, 859a], [852a, 854a, 858a, 911a, 925a, 938a, 953a, 955a, 959a], [952a, 954a, 958a, 1011a, 1025a, 1038a, 1053a, 1055a, 1059a], [1052a, 1054a, 1058a, 1111a, 1125a, 1138a, 1153a, 1155a, 1159a], [1152a, 1154a, 1158a, 1211p, 1225p, 1238p, 1253p, 1255p, 1259p], [1252p, 1254p, 1258p, 111p, 125p, 138p, 153p, 155p, 159p], [152p, 154p, 158p, 211p, 225p, 238p, 253p, 255p, 259p], [252p, 254p, 258p, 311p, 325p, 338p, 353p, 355p, 359p], [352p, 354p, 358p, 411p, 425p, 438p, 453p, 455p, 459p], [452p, 454p, 458p, 511p, 525p, 538p, 553p, 555p, 559p], [552p, 554p, 558p, 611p, 625p, 638p, 652p, 654p, 658p], [651p, 653p, 657p, 709p, 723p, 736p, 750p, 752p, 756p], [751p, 753p, 757p, 809p, 823p, 836p, 850p, 852p, 856p], [855p, 857p, 901p, 913p, 927p, 940p, 954p, 956p, 1000p], [955p, 957p, 1001p, 1013p, 1027p, 1040p, 1054p, 1056p, 1100p], [1055p, 1057p, 1101p, 1113p, 1127p, 1140p, 1154p, 1156p, 1200a]] stop_times_saturday: [["-", "-", "-", "-", 725a, 738a, 753a, 755a, 759a], [752a, 754a, 758a, 811a, 825a, 838a, 853a, 855a, 859a], [852a, 854a, 858a, 911a, 925a, 938a, 953a, 955a, 959a], [952a, 954a, 958a, 1011a, 1025a, 1038a, 1053a, 1055a, 1059a], [1052a, 1054a, 1058a, 1111a, 1125a, 1138a, 1153a, 1155a, 1159a], [1152a, 1154a, 1158a, 1211p, 1225p, 1238p, 1253p, 1255p, 1259p], [1252p, 1254p, 1258p, 111p, 125p, 138p, 153p, 155p, 159p], [152p, 154p, 158p, 211p, 225p, 238p, 253p, 255p, 259p], [252p, 254p, 258p, 311p, 325p, 338p, 353p, 355p, 359p], [352p, 354p, 358p, 411p, 425p, 438p, 453p, 455p, 459p], [452p, 454p, 458p, 511p, 525p, 538p, 553p, 555p, 559p], [552p, 554p, 558p, 611p, 625p, 638p, 652p, 654p, 658p], [651p, 653p, 657p, 709p, 723p, 736p, 750p, 752p, 756p], [751p, 753p, 757p, 809p, 823p, 836p, 850p, 852p, 856p], [855p, 857p, 901p, 913p, 927p, 940p, 954p, 956p, 1000p], [955p, 957p, 1001p, 1013p, 1027p, 1040p, 1054p, 1056p, 1100p], [1055p, 1057p, 1101p, 1113p, 1127p, 1140p, 1154p, 1156p, 1200a]]
short_name: "906" short_name: "906"
   
--- ---
time_points: [Belconnen Community Bus Station (Platform 4), Westfield Bus Station (Platform 2), Cohen Street Bus Station (Platform 6), Melba Shops, Spence Terminus, Melba Shops, Cohen Street Bus Station, Westfield Bus Station, Belconnen Community Bus Station] time_points: [Belconnen Community Bus Station (Platform 4), Westfield Bus Station (Platform 2), Cohen Street Bus Station (Platform 6), Melba, Spence Terminus, Melba, Cohen Street Bus Station, Westfield Bus Station, Belconnen Community Bus Station]
long_name: To Belconnen Community Bus Station long_name: To Belconnen Community Bus Station
between_stops: between_stops:
Belconnen Community Bus Station (Platform 4)-Westfield Bus Station (Platform 2): [] Belconnen Community Bus Station (Platform 4)-Westfield Bus Station (Platform 2): []
Westfield Bus Station-Belconnen Community Bus Station: [] Westfield Bus Station-Belconnen Community Bus Station: []
Cohen Street Bus Station-Westfield Bus Station: [] Cohen Street Bus Station-Westfield Bus Station: []
Westfield Bus Station (Platform 2)-Cohen Street Bus Station (Platform 6): [] Westfield Bus Station (Platform 2)-Cohen Street Bus Station (Platform 6): []
short_name: "906" short_name: "906"
stop_times_sunday: [[852a, 854a, 858a, 911a, 925a, 938a, 953a, 955a, 959a], [952a, 954a, 958a, 1011a, 1025a, 1038a, 1053a, 1055a, 1059a], [1052a, 1054a, 1058a, 1111a, 1125a, 1138a, 1153a, 1155a, 1159a], [1152a, 1154a, 1158a, 1211p, 1225p, 1238p, 1253p, 1255p, 1259p], [1252p, 1254p, 1258p, 111p, 125p, 138p, 153p, 155p, 159p], [152p, 154p, 158p, 211p, 225p, 238p, 253p, 255p, 259p], [252p, 254p, 258p, 311p, 325p, 338p, 353p, 355p, 359p], [352p, 354p, 358p, 411p, 425p, 438p, 453p, 455p, 459p], [452p, 454p, 458p, 511p, 525p, 538p, 553p, 555p, 559p], [552p, 554p, 558p, 611p, 625p, 638p, 652p, 654p, 658p]] stop_times_sunday: [[852a, 854a, 858a, 911a, 925a, 938a, 953a, 955a, 959a], [952a, 954a, 958a, 1011a, 1025a, 1038a, 1053a, 1055a, 1059a], [1052a, 1054a, 1058a, 1111a, 1125a, 1138a, 1153a, 1155a, 1159a], [1152a, 1154a, 1158a, 1211p, 1225p, 1238p, 1253p, 1255p, 1259p], [1252p, 1254p, 1258p, 111p, 125p, 138p, 153p, 155p, 159p], [152p, 154p, 158p, 211p, 225p, 238p, 253p, 255p, 259p], [252p, 254p, 258p, 311p, 325p, 338p, 353p, 355p, 359p], [352p, 354p, 358p, 411p, 425p, 438p, 453p, 455p, 459p], [452p, 454p, 458p, 511p, 525p, 538p, 553p, 555p, 559p], [552p, 554p, 558p, 611p, 625p, 638p, 652p, 654p, 658p]]
   
---  
time_points: [Belconnen Community Bus Station (Platform 6), Westfield Bus Station (Platform 2), Cohen Street Bus Station (Platform 5), Charnwood Shops, Fraser East Terminus, Charnwood Shops, Cohen Street Bus Station, Westfield Bus Station, Belconnen Community Bus Station]  
long_name: To Belconnen Community Bus Station  
between_stops:  
Belconnen Community Bus Station (Platform 6)-Westfield Bus Station (Platform 2): []  
Westfield Bus Station-Belconnen Community Bus Station: []  
Cohen Street Bus Station-Westfield Bus Station: []  
Westfield Bus Station (Platform 2)-Cohen Street Bus Station (Platform 5): []  
short_name: "907"  
stop_times: [[848a, 850a, 854a, 908a, 916a, 923a, 937a, 939a, 943a], [948a, 950a, 954a, 1008a, 1016a, 1023a, 1037a, 1039a, 1043a], [1048a, 1050a, 1054a, 1108a, 1116a, 1123a, 1137a, 1139a, 1143a], [1148a, 1150a, 1154a, 1208p, 1216p, 1223p, 1237p, 1239p, 1243p], [1248p, 1250p, 1254p, 108p, 116p, 123p, 137p, 139p, 143p], [148p, 150p, 154p, 208p, 216p, 223p, 237p, 239p, 243p], [248p, 250p, 254p, 308p, 316p, 323p, 337p, 339p, 343p], [348p, 350p, 354p, 408p, 416p, 423p, 437p, 439p, 443p], [448p, 450p, 454p, 508p, 516p, 523p, 537p, 539p, 543p], [548p, 550p, 554p, 608p, 616p, 623p, 637p, 639p, 643p], [647p, 649p, 653p, 706p, 714p, 721p, 734p, 736p, 740p]]  
 
--- ---
time_points: [Belconnen Community Bus Station (Platform 6), Westfield Bus Station (Platform 2), Cohen Street Bus Station (Platform 5), Charnwood Shops, Fraser East Terminus, Charnwood Shops, Cohen Street Bus Station, Westfield Bus Station, Belconnen Community Bus Station] time_points: [Belconnen Community Bus Station (Platform 6), Westfield Bus Station (Platform 2), Cohen Street Bus Station (Platform 5), Charnwood, Fraser East Terminus, Charnwood, Cohen Street Bus Station, Westfield Bus Station, Belconnen Community Bus Station]
long_name: To Belconnen Community Bus Station long_name: To Belconnen Community Bus Station
between_stops: between_stops:
Belconnen Community Bus Station (Platform 6)-Westfield Bus Station (Platform 2): [] Belconnen Community Bus Station (Platform 6)-Westfield Bus Station (Platform 2): []
Westfield Bus Station-Belconnen Community Bus Station: [] Westfield Bus Station-Belconnen Community Bus Station: []
Cohen Street Bus Station-Westfield Bus Station: [] Cohen Street Bus Station-Westfield Bus Station: []
Westfield Bus Station (Platform 2)-Cohen Street Bus Station (Platform 5): [] Westfield Bus Station (Platform 2)-Cohen Street Bus Station (Platform 5): []
stop_times_saturday: [["-", "-", "-", 708a, 716a, 723a, 737a, 739a, 743a], ["-", "-", "-", 808a, 816a, 823a, 837a, 839a, 843a], [848a, 850a, 854a, 908a, 916a, 923a, 937a, 939a, 943a], [948a, 950a, 954a, 1008a, 1016a, 1023a, 1037a, 1039a, 1043a], [1048a, 1050a, 1054a, 1108a, 1116a, 1123a, 1137a, 1139a, 1143a], [1148a, 1150a, 1154a, 1208p, 1216p, 1223p, 1237p, 1239p, 1243p], [1248p, 1250p, 1254p, 108p, 116p, 123p, 137p, 139p, 143p], [148p, 150p, 154p, 208p, 216p, 223p, 237p, 239p, 243p], [248p, 250p, 254p, 308p, 316p, 323p, 337p, 339p, 343p], [348p, 350p, 354p, 408p, 416p, 423p, 437p, 439p, 443p], [448p, 450p, 454p, 508p, 516p, 523p, 537p, 539p, 543p], [548p, 550p, 554p, 608p, 616p, 623p, 637p, 639p, 643p], [647p, 649p, 653p, 706p, 714p, 721p, 734p, 736p, 740p], [747p, 749p, 753p, 806p, 814p, 821p, 834p, 836p, 840p], [847p, 849p, 853p, 906p, 914p, 921p, 934p, 936p, 940p], [947p, 949p, 953p, 1006p, 1014p, 1021p, 1034p, 1036p, 1040p], [1047p, 1049p, 1053p, 1106p, 1114p, 1121p, 1134p, 1136p, 1140p]] stop_times_saturday: [["-", "-", "-", 708a, 716a, 723a, 737a, 739a, 743a], ["-", "-", "-", 808a, 816a, 823a, 837a, 839a, 843a], [848a, 850a, 854a, 908a, 916a, 923a, 937a, 939a, 943a], [948a, 950a, 954a, 1008a, 1016a, 1023a, 1037a, 1039a, 1043a], [1048a, 1050a, 1054a, 1108a, 1116a, 1123a, 1137a, 1139a, 1143a], [1148a, 1150a, 1154a, 1208p, 1216p, 1223p, 1237p, 1239p, 1243p], [1248p, 1250p, 1254p, 108p, 116p, 123p, 137p, 139p, 143p], [148p, 150p, 154p, 208p, 216p, 223p, 237p, 239p, 243p], [248p, 250p, 254p, 308p, 316p, 323p, 337p, 339p, 343p], [348p, 350p, 354p, 408p, 416p, 423p, 437p, 439p, 443p], [448p, 450p, 454p, 508p, 516p, 523p, 537p, 539p, 543p], [548p, 550p, 554p, 608p, 616p, 623p, 637p, 639p, 643p], [647p, 649p, 653p, 706p, 714p, 721p, 734p, 736p, 740p], [747p, 749p, 753p, 806p, 814p, 821p, 834p, 836p, 840p], [847p, 849p, 853p, 906p, 914p, 921p, 934p, 936p, 940p], [947p, 949p, 953p, 1006p, 1014p, 1021p, 1034p, 1036p, 1040p], [1047p, 1049p, 1053p, 1106p, 1114p, 1121p, 1134p, 1136p, 1140p]]
short_name: "907" short_name: "907"
   
--- ---
time_points: [Belconnen Community Bus Station (Platform 6), Westfield Bus Station (Platform 2), Cohen Street Bus Station (Platform 5), Charnwood Shops, Fraser East Terminus, Charnwood Shops, Cohen Street Bus Station, Westfield Bus Station, Belconnen Community Bus Station] time_points: [Belconnen Community Bus Station (Platform 6), Westfield Bus Station (Platform 2), Cohen Street Bus Station (Platform 5), Charnwood, Fraser East Terminus, Charnwood, Cohen Street Bus Station, Westfield Bus Station, Belconnen Community Bus Station]
long_name: To Belconnen Community Bus Station long_name: To Belconnen Community Bus Station
between_stops: between_stops:
Belconnen Community Bus Station (Platform 6)-Westfield Bus Station (Platform 2): [] Belconnen Community Bus Station (Platform 6)-Westfield Bus Station (Platform 2): []
Westfield Bus Station-Belconnen Community Bus Station: [] Westfield Bus Station-Belconnen Community Bus Station: []
Cohen Street Bus Station-Westfield Bus Station: [] Cohen Street Bus Station-Westfield Bus Station: []
Westfield Bus Station (Platform 2)-Cohen Street Bus Station (Platform 5): [] Westfield Bus Station (Platform 2)-Cohen Street Bus Station (Platform 5): []
short_name: "907" short_name: "907"
stop_times_sunday: [[848a, 850a, 854a, 908a, 916a, 923a, 937a, 939a, 943a], [948a, 950a, 954a, 1008a, 1016a, 1023a, 1037a, 1039a, 1043a], [1048a, 1050a, 1054a, 1108a, 1116a, 1123a, 1137a, 1139a, 1143a], [1148a, 1150a, 1154a, 1208p, 1216p, 1223p, 1237p, 1239p, 1243p], [1248p, 1250p, 1254p, 108p, 116p, 123p, 137p, 139p, 143p], [148p, 150p, 154p, 208p, 216p, 223p, 237p, 239p, 243p], [248p, 250p, 254p, 308p, 316p, 323p, 337p, 339p, 343p], [348p, 350p, 354p, 408p, 416p, 423p, 437p, 439p, 443p], [448p, 450p, 454p, 508p, 516p, 523p, 537p, 539p, 543p], [548p, 550p, 554p, 608p, 616p, 623p, 637p, 639p, 643p], [647p, 649p, 653p, 706p, 714p, 721p, 734p, 736p, 740p]] stop_times_sunday: [[848a, 850a, 854a, 908a, 916a, 923a, 937a, 939a, 943a], [948a, 950a, 954a, 1008a, 1016a, 1023a, 1037a, 1039a, 1043a], [1048a, 1050a, 1054a, 1108a, 1116a, 1123a, 1137a, 1139a, 1143a], [1148a, 1150a, 1154a, 1208p, 1216p, 1223p, 1237p, 1239p, 1243p], [1248p, 1250p, 1254p, 108p, 116p, 123p, 137p, 139p, 143p], [148p, 150p, 154p, 208p, 216p, 223p, 237p, 239p, 243p], [248p, 250p, 254p, 308p, 316p, 323p, 337p, 339p, 343p], [348p, 350p, 354p, 408p, 416p, 423p, 437p, 439p, 443p], [448p, 450p, 454p, 508p, 516p, 523p, 537p, 539p, 543p], [548p, 550p, 554p, 608p, 616p, 623p, 637p, 639p, 643p], [647p, 649p, 653p, 706p, 714p, 721p, 734p, 736p, 740p]]
   
---  
time_points: [Tuggeranong Bus Station (Platform 4), Isabella Shops, Calwell Shops, Theodore, Outtrim / Duggan, Tuggeranong Bus Station]  
long_name: To Tuggeranong Bus Station  
between_stops: {}  
 
short_name: "912"  
stop_times: [[1015a, 1025a, 1030a, 1039a, 1046a, 1055a], [1215p, 1225p, 1230p, 1239p, 1246p, 1255p], [215p, 225p, 230p, 239p, 246p, 255p], [415p, 425p, 430p, 439p, 446p, 455p], [615p, 625p, 630p, 639p, 646p, 655p]]  
 
--- ---
time_points: [Tuggeranong Bus Station (Platform 4), Isabella Shops, Calwell Shops, Theodore, Outtrim / Duggan, Tuggeranong Bus Station] time_points: [Tuggeranong Bus Station (Platform 4), Isabella, Calwell, Theodore, Outtrim / Duggan, Tuggeranong Bus Station]
long_name: To Tuggeranong Bus Station long_name: To Tuggeranong Bus Station
between_stops: {} between_stops: {}
   
stop_times_saturday: [[815a, 825a, 830a, 839a, 846a, 855a], [1015a, 1025a, 1030a, 1039a, 1046a, 1055a], [1215p, 1225p, 1230p, 1239p, 1246p, 1255p], [215p, 225p, 230p, 239p, 246p, 255p], [415p, 425p, 430p, 439p, 446p, 455p], [615p, 625p, 630p, 639p, 646p, 655p], [818p, 828p, 833p, 842p, 849p, 858p], [1018p, 1028p, 1033p, 1042p, 1049p, 1058p]] stop_times_saturday: [[815a, 825a, 830a, 839a, 846a, 855a], [1015a, 1025a, 1030a, 1039a, 1046a, 1055a], [1215p, 1225p, 1230p, 1239p, 1246p, 1255p], [215p, 225p, 230p, 239p, 246p, 255p], [415p, 425p, 430p, 439p, 446p, 455p], [615p, 625p, 630p, 639p, 646p, 655p], [818p, 828p, 833p, 842p, 849p, 858p], [1018p, 1028p, 1033p, 1042p, 1049p, 1058p]]
short_name: "912" short_name: "912"
   
--- ---
time_points: [Tuggeranong Bus Station (Platform 4), Isabella Shops, Calwell Shops, Theodore, Outtrim / Duggan, Tuggeranong Bus Station] time_points: [Tuggeranong Bus Station (Platform 4), Isabella, Calwell, Theodore, Outtrim / Duggan, Tuggeranong Bus Station]
long_name: To Tuggeranong Bus Station long_name: To Tuggeranong Bus Station
between_stops: {} between_stops: {}
   
short_name: "912" short_name: "912"
stop_times_sunday: [[1015a, 1025a, 1030a, 1039a, 1046a, 1055a], [1215p, 1225p, 1230p, 1239p, 1246p, 1255p], [215p, 225p, 230p, 239p, 246p, 255p], [415p, 425p, 430p, 439p, 446p, 455p], [615p, 625p, 630p, 639p, 646p, 655p]] stop_times_sunday: [[1015a, 1025a, 1030a, 1039a, 1046a, 1055a], [1215p, 1225p, 1230p, 1239p, 1246p, 1255p], [215p, 225p, 230p, 239p, 246p, 255p], [415p, 425p, 430p, 439p, 446p, 455p], [615p, 625p, 630p, 639p, 646p, 655p]]
   
---  
time_points: [Tuggeranong Bus Station (Platform 7), Bonython Primary School, Woodcock / Clare Dennis, Gordon Primary, Tharwa Drive / Knoke Ave, Conder Primary, Lanyon Market Place, Bonython Primary School, Tuggeranong Bus Station]  
long_name: To Tuggeranong Bus Station  
between_stops: {}  
 
short_name: "913"  
stop_times: [[925a, 933a, 937a, 941a, 944a, 949a, 1000a, 1005a, 1013a], [1125a, 1133a, 1137a, 1141a, 1144a, 1149a, 1200p, 1205p, 1213p], [125p, 133p, 137p, 141p, 144p, 149p, 200p, 205p, 213p], [325p, 333p, 337p, 341p, 344p, 349p, 400p, 405p, 413p], [525p, 533p, 537p, 541p, 544p, 549p, 600p, 605p, 613p], [725p, 733p, 737p, 741p, 744p, 749p, 800p, 805p, 813p]]  
 
---  
time_points: [Tuggeranong Bus Station (Platform 7), Bonython Primary School, Lanyon Market Place, Conder Primary, Tharwa Drive / Knoke Ave, Gordon Primary, Woodcock / Clare Dennis, Bonython Primary School, Tuggeranong Bus Station]  
long_name: To Tuggeranong Bus Station  
between_stops: {}  
 
short_name: "914"  
stop_times: [[1025a, 1034a, 1040a, 1047a, 1050a, 1054a, 1059a, 1102a, 1112a], [1225p, 1234p, 1240p, 1247p, 1250p, 1254p, 1259p, 102p, 112p], [225p, 234p, 240p, 247p, 250p, 254p, 259p, 302p, 312p], [425p, 434p, 440p, 447p, 450p, 454p, 459p, 502p, 512p], [625p, 634p, 640p, 647p, 650p, 654p, 659p, 702p, 712p]]  
 
--- ---
time_points: [Tuggeranong Bus Station (Platform 7), Bonython Primary School, Lanyon Market Place, Conder Primary, Tharwa Dr / Pockett Ave, Gordon Primary, Woodcock / Clare Dennis, Bonython Primary School, Tuggeranong Bus Station] time_points: [Tuggeranong Bus Station (Platform 7), Bonython Primary School, Lanyon Market Place, Conder Primary, Tharwa Drive / Pockett Ave, Gordon Primary, Woodcock / Clare Dennis, Bonython Primary School, Tuggeranong Bus Station]
long_name: To Tuggeranong Bus Station long_name: To Tuggeranong Bus Station
between_stops: {} between_stops: {}
   
stop_times_saturday: [[625a, 634a, 640a, 647a, 650a, 654a, 659a, 702a, 712a], [825a, 834a, 840a, 847a, 850a, 854a, 859a, 902a, 912a], [1025a, 1034a, 1040a, 1047a, 1050a, 1054a, 1059a, 1102a, 1112a], [1225p, 1234p, 1240p, 1247p, 1250p, 1254p, 1259p, 102p, 112p], [225p, 234p, 240p, 247p, 250p, 254p, 259p, 302p, 312p], [425p, 434p, 440p, 447p, 450p, 454p, 459p, 502p, 512p], [625p, 634p, 640p, 647p, 650p, 654p, 659p, 702p, 712p], [828p, 837p, 843p, 850p, 853p, 857p, 902p, 905p, 915p], [1028p, 1037p, 1043p, 1050p, 1053p, 1057p, 1102p, 1105p, 1115p]] stop_times_saturday: [[625a, 634a, 640a, 647a, 650a, 654a, 659a, 702a, 712a], [825a, 834a, 840a, 847a, 850a, 854a, 859a, 902a, 912a], [1025a, 1034a, 1040a, 1047a, 1050a, 1054a, 1059a, 1102a, 1112a], [1225p, 1234p, 1240p, 1247p, 1250p, 1254p, 1259p, 102p, 112p], [225p, 234p, 240p, 247p, 250p, 254p, 259p, 302p, 312p], [425p, 434p, 440p, 447p, 450p, 454p, 459p, 502p, 512p], [625p, 634p, 640p, 647p, 650p, 654p, 659p, 702p, 712p], [828p, 837p, 843p, 850p, 853p, 857p, 902p, 905p, 915p], [1028p, 1037p, 1043p, 1050p, 1053p, 1057p, 1102p, 1105p, 1115p]]
short_name: "914" short_name: "914"
   
---  
time_points: [Tuggeranong Bus Station (Platform 4), Isabella Shops, Theodore, Calwell Shops, Outtrim / Duggan, Tuggeranong Bus Station]  
long_name: To Tuggeranong Bus Station  
between_stops: {}  
 
short_name: "915"  
stop_times: [[915a, 925a, 934a, 943a, 946a, 955a], [1115a, 1125a, 1134a, 1143a, 1146a, 1155a], [115p, 125p, 134p, 143p, 146p, 155p], [315p, 325p, 334p, 343p, 346p, 355p], [515p, 525p, 534p, 543p, 546p, 555p], [715p, 725p, 734p, 743p, 746p, 755p]]  
 
--- ---
time_points: [Tuggeranong Bus Station (Platform 4), Isabella Shops, Theodore, Calwell Shops, Outtrim / Duggan, Tuggeranong Bus Station] time_points: [Tuggeranong Bus Station (Platform 4), Isabella, Theodore, Calwell, Outtrim / Duggan, Tuggeranong Bus Station]
long_name: To Tuggeranong Bus Station long_name: To Tuggeranong Bus Station
between_stops: {} between_stops: {}
   
stop_times_saturday: [[715a, 725a, 734a, 743a, 746a, 755a], [915a, 925a, 934a, 943a, 946a, 955a], [1115a, 1125a, 1134a, 1143a, 1146a, 1155a], [115p, 125p, 134p, 143p, 146p, 155p], [315p, 325p, 334p, 343p, 346p, 355p], [515p, 525p, 534p, 543p, 546p, 555p], [715p, 725p, 734p, 743p, 746p, 755p], [918p, 928p, 937p, 946p, 949p, 958p], [1118p, 1128p, 1137p, 1146p, 1149p, "-"]] stop_times_saturday: [[715a, 725a, 734a, 743a, 746a, 755a], [915a, 925a, 934a, 943a, 946a, 955a], [1115a, 1125a, 1134a, 1143a, 1146a, 1155a], [115p, 125p, 134p, 143p, 146p, 155p], [315p, 325p, 334p, 343p, 346p, 355p], [515p, 525p, 534p, 543p, 546p, 555p], [715p, 725p, 734p, 743p, 746p, 755p], [918p, 928p, 937p, 946p, 949p, 958p], [1118p, 1128p, 1137p, 1146p, 1149p, "-"]]
short_name: "915" short_name: "915"
   
--- ---
time_points: [Tuggeranong Bus Station (Platform 4), Isabella Shops, Theodore, Calwell Shops, Outtrim / Duggan, Tuggeranong Bus Station] time_points: [Tuggeranong Bus Station (Platform 4), Isabella, Theodore, Calwell, Outtrim / Duggan, Tuggeranong Bus Station]
long_name: To Tuggeranong Bus Station long_name: To Tuggeranong Bus Station
between_stops: {} between_stops: {}
   
short_name: "915" short_name: "915"
stop_times_sunday: [[915a, 925a, 934a, 943a, 946a, 955a], [1115a, 1125a, 1134a, 1143a, 1146a, 1155a], [115p, 125p, 134p, 143p, 146p, 155p], [315p, 325p, 334p, 343p, 346p, 355p], [515p, 525p, 534p, 543p, 546p, 555p], [715p, 725p, 734p, 743p, 746p, 755p]] stop_times_sunday: [[915a, 925a, 934a, 943a, 946a, 955a], [1115a, 1125a, 1134a, 1143a, 1146a, 1155a], [115p, 125p, 134p, 143p, 146p, 155p], [315p, 325p, 334p, 343p, 346p, 355p], [515p, 525p, 534p, 543p, 546p, 555p], [715p, 725p, 734p, 743p, 746p, 755p]]
   
---  
time_points: [Woden Bus Station (Platform 15), Lyons Shops, Chifley Shops, Torrens Shops, Southlands Mawson, Pearce Shops, Woden Bus Station]  
long_name: To Woden Bus Station  
between_stops: {}  
 
short_name: "921"  
stop_times: [[933a, 936a, 940a, 945a, 951a, 955a, 1001a], [1133a, 1136a, 1140a, 1145a, 1151a, 1155a, 1201p], [133p, 136p, 140p, 145p, 151p, 155p, 201p], [333p, 336p, 340p, 345p, 351p, 355p, 401p], [533p, 536p, 540p, 545p, 551p, 555p, 601p]]  
 
--- ---
time_points: [Woden Bus Station (Platform 15), Lyons Shops, Chifley Shops, Torrens Shops, Southlands Mawson, Pearce Shops, Woden Bus Station] time_points: [Woden Bus Station (Platform 15), Lyons, Chifley, Torrens, Southlands Mawson, Pearce, Woden Bus Station]
long_name: To Woden Bus Station long_name: To Woden Bus Station
between_stops: {} between_stops: {}
   
stop_times_saturday: [[933a, 936a, 940a, 945a, 951a, 955a, 1001a], [1133a, 1136a, 1140a, 1145a, 1151a, 1155a, 1201p], [133p, 136p, 140p, 145p, 151p, 155p, 201p], [333p, 336p, 340p, 345p, 351p, 355p, 401p], [533p, 536p, 540p, 545p, 551p, 555p, 601p], [733p, 736p, 740p, 745p, 751p, 755p, 801p], [933p, 936p, 940p, 945p, 951p, 955p, 1001p], [1133p, 1136p, 1140p, 1145p, 1151p, 1155p, "-"]] stop_times_saturday: [[933a, 936a, 940a, 945a, 951a, 955a, 1001a], [1133a, 1136a, 1140a, 1145a, 1151a, 1155a, 1201p], [133p, 136p, 140p, 145p, 151p, 155p, 201p], [333p, 336p, 340p, 345p, 351p, 355p, 401p], [533p, 536p, 540p, 545p, 551p, 555p, 601p], [733p, 736p, 740p, 745p, 751p, 755p, 801p], [933p, 936p, 940p, 945p, 951p, 955p, 1001p], [1133p, 1136p, 1140p, 1145p, 1151p, 1155p, "-"]]
short_name: "921" short_name: "921"
   
--- ---
time_points: [Woden Bus Station (Platform 15), Lyons Shops, Chifley Shops, Torrens Shops, Southlands Mawson, Pearce Shops, Woden Bus Station] time_points: [Woden Bus Station (Platform 15), Lyons, Chifley, Torrens, Southlands Mawson, Pearce, Woden Bus Station]
long_name: To Woden Bus Station long_name: To Woden Bus Station
between_stops: {} between_stops: {}
   
short_name: "921" short_name: "921"
stop_times_sunday: [[933a, 936a, 940a, 945a, 951a, 955a, 1001a], [1133a, 1136a, 1140a, 1145a, 1151a, 1155a, 1201p], [133p, 136p, 140p, 145p, 151p, 155p, 201p], [333p, 336p, 340p, 345p, 351p, 355p, 401p], [533p, 536p, 540p, 545p, 551p, 555p, 601p]] stop_times_sunday: [[933a, 936a, 940a, 945a, 951a, 955a, 1001a], [1133a, 1136a, 1140a, 1145a, 1151a, 1155a, 1201p], [133p, 136p, 140p, 145p, 151p, 155p, 201p], [333p, 336p, 340p, 345p, 351p, 355p, 401p], [533p, 536p, 540p, 545p, 551p, 555p, 601p]]
   
---  
time_points: [Woden Bus Station (Platform 15), Pearce Shops, Southlands Mawson, Torrens Shops, Chifley Shops, Lyons Shops, Woden Bus Station]  
long_name: To Woden Bus Station  
between_stops: {}  
 
short_name: "922"  
stop_times: [[1033a, 1039a, 1043a, 1049a, 1054a, 1058a, 1101a], [1233p, 1239p, 1243p, 1249p, 1254p, 1258p, 101p], [233p, 239p, 243p, 249p, 254p, 258p, 301p], [433p, 439p, 443p, 449p, 454p, 458p, 501p], [633p, 639p, 643p, 649p, 654p, 658p, 701p]]  
 
--- ---
time_points: [Woden Bus Station (Platform 15), Pearce Shops, Southlands Mawson, Torrens Shops, Chifley Shops, Lyons Shops, Woden Bus Station] time_points: [Woden Bus Station (Platform 15), Pearce, Southlands Mawson, Torrens, Chifley, Lyons, Woden Bus Station]
long_name: To Woden Bus Station long_name: To Woden Bus Station
between_stops: {} between_stops: {}
   
stop_times_saturday: [[833a, 839a, 843a, 849a, 854a, 858a, 901a], [1033a, 1039a, 1043a, 1049a, 1054a, 1058a, 1101a], [1233p, 1239p, 1243p, 1249p, 1254p, 1258p, 101p], [233p, 239p, 243p, 249p, 254p, 258p, 301p], [433p, 439p, 443p, 449p, 454p, 458p, 501p], [633p, 639p, 643p, 649p, 654p, 658p, 701p], [833p, 839p, 843p, 849p, 854p, 858p, 901p], [1033p, 1039p, 1043p, 1049p, 1054p, 1058p, 1101p]] stop_times_saturday: [[833a, 839a, 843a, 849a, 854a, 858a, 901a], [1033a, 1039a, 1043a, 1049a, 1054a, 1058a, 1101a], [1233p, 1239p, 1243p, 1249p, 1254p, 1258p, 101p], [233p, 239p, 243p, 249p, 254p, 258p, 301p], [433p, 439p, 443p, 449p, 454p, 458p, 501p], [633p, 639p, 643p, 649p, 654p, 658p, 701p], [833p, 839p, 843p, 849p, 854p, 858p, 901p], [1033p, 1039p, 1043p, 1049p, 1054p, 1058p, 1101p]]
short_name: "922" short_name: "922"
   
--- ---
time_points: [Woden Bus Station (Platform 15), Pearce Shops, Southlands Mawson, Torrens Shops, Chifley Shops, Lyons Shops, Woden Bus Station] time_points: [Woden Bus Station (Platform 15), Pearce, Southlands Mawson, Torrens, Chifley, Lyons, Woden Bus Station]
long_name: To Woden Bus Station long_name: To Woden Bus Station
between_stops: {} between_stops: {}
   
short_name: "922" short_name: "922"
stop_times_sunday: [[1033a, 1039a, 1043a, 1049a, 1054a, 1058a, 1101a], [1233p, 1239p, 1243p, 1249p, 1254p, 1258p, 101p], [233p, 239p, 243p, 249p, 254p, 258p, 301p], [433p, 439p, 443p, 449p, 454p, 458p, 501p], [633p, 639p, 643p, 649p, 654p, 658p, 701p]] stop_times_sunday: [[1033a, 1039a, 1043a, 1049a, 1054a, 1058a, 1101a], [1233p, 1239p, 1243p, 1249p, 1254p, 1258p, 101p], [233p, 239p, 243p, 249p, 254p, 258p, 301p], [433p, 439p, 443p, 449p, 454p, 458p, 501p], [633p, 639p, 643p, 649p, 654p, 658p, 701p]]
   
---  
time_points: [Woden Bus Station (Platform 15), Pearce Shops, Isaacs Shops, Farrer Primary School, Southlands Mawson, Woden Bus Station]  
long_name: To Woden Bus Station  
between_stops: {}  
 
short_name: "923"  
stop_times: [[910a, 916a, 921a, 927a, 933a, 943a], [1110a, 1116a, 1121a, 1127a, 1133a, 1143a], [110p, 116p, 121p, 127p, 133p, 143p], [310p, 316p, 321p, 327p, 333p, 343p], [510p, 516p, 521p, 527p, 533p, 543p]]  
 
--- ---
time_points: [Woden Bus Station (Platform 15), Canberra Hospital, Isaacs Shops, Farrer Primary School, Southlands Mawson, Woden Bus Station] time_points: [Woden Bus Station (Platform 15), Canberra Hospital, Isaacs, Farrer Primary School, Southlands Mawson, Woden Bus Station]
long_name: To Woden Bus Station long_name: To Woden Bus Station
between_stops: {} between_stops: {}
   
stop_times_saturday: [[910a, 916a, 921a, 927a, 933a, 943a], [1110a, 1116a, 1121a, 1127a, 1133a, 1143a], [110p, 116p, 121p, 127p, 133p, 143p], [310p, 316p, 321p, 327p, 333p, 343p], [510p, 516p, 521p, 527p, 533p, 543p], [713p, 718p, 723p, 728p, 734p, 743p], [913p, 918p, 923p, 928p, 934p, 943p], [1113p, 1118p, 1123p, 1128p, 1134p, 1143p]] stop_times_saturday: [[910a, 916a, 921a, 927a, 933a, 943a], [1110a, 1116a, 1121a, 1127a, 1133a, 1143a], [110p, 116p, 121p, 127p, 133p, 143p], [310p, 316p, 321p, 327p, 333p, 343p], [510p, 516p, 521p, 527p, 533p, 543p], [713p, 718p, 723p, 728p, 734p, 743p], [913p, 918p, 923p, 928p, 934p, 943p], [1113p, 1118p, 1123p, 1128p, 1134p, 1143p]]
short_name: "923" short_name: "923"
   
--- ---
time_points: [Woden Bus Station (Platform 15), Pearce Shops, Isaacs Shops, Farrer Primary School, Southlands Mawson, Woden Bus Station] time_points: [Woden Bus Station (Platform 15), Pearce, Isaacs, Farrer Primary School, Southlands Mawson, Woden Bus Station]
long_name: To Woden Bus Station long_name: To Woden Bus Station
between_stops: {} between_stops: {}
   
short_name: "923" short_name: "923"
stop_times_sunday: [[910a, 916a, 921a, 927a, 933a, 943a], [1110a, 1116a, 1121a, 1127a, 1133a, 1143a], [110p, 116p, 121p, 127p, 133p, 143p], [310p, 316p, 321p, 327p, 333p, 343p], [510p, 516p, 521p, 527p, 533p, 543p]] stop_times_sunday: [[910a, 916a, 921a, 927a, 933a, 943a], [1110a, 1116a, 1121a, 1127a, 1133a, 1143a], [110p, 116p, 121p, 127p, 133p, 143p], [310p, 316p, 321p, 327p, 333p, 343p], [510p, 516p, 521p, 527p, 533p, 543p]]
   
---  
time_points: [Woden Bus Station (Platform 15), Southlands Mawson, Farrer Primary School, Isaacs Shops, Pearce Shops, Woden Bus Station]  
long_name: To Woden Bus Station  
between_stops: {}  
 
short_name: "924"  
stop_times: [[1010a, 1019a, 1024a, 1029a, 1033a, 1041a], [1210p, 1219p, 1224p, 1229p, 1233p, 1241p], [210p, 219p, 224p, 229p, 233p, 241p], [410p, 419p, 424p, 429p, 433p, 441p], [610p, 619p, 624p, 629p, 633p, 641p]]  
 
--- ---
time_points: [Woden Bus Station (Platform 15), Southlands Mawson, Farrer Primary School, Isaacs Shops, Canberra Hospital, Woden Bus Station] time_points: [Woden Bus Station (Platform 15), Southlands Mawson, Farrer Primary School, Isaacs, Canberra Hospital, Woden Bus Station]
long_name: To Woden Bus Station long_name: To Woden Bus Station
between_stops: between_stops:
Canberra Hospital-Woden Bus Station: [Wjz3mWn, Wjz3mPO, Wjz3mAg] Canberra Hospital-Woden Bus Station: [Wjz3mWn, Wjz3mPO, Wjz3mAg]
stop_times_saturday: [[810a, 819a, 824a, 829a, 833a, 841a], [1010a, 1019a, 1024a, 1029a, 1033a, 1041a], [1210p, 1219p, 1224p, 1229p, 1233p, 1241p], [210p, 219p, 224p, 229p, 233p, 241p], [410p, 419p, 424p, 429p, 433p, 441p], [610p, 619p, 624p, 629p, 633p, 641p], [813p, 821p, 826p, 830p, 834p, 841p], [1013p, 1021p, 1026p, 1030p, 1034p, 1041p]] stop_times_saturday: [[810a, 819a, 824a, 829a, 833a, 841a], [1010a, 1019a, 1024a, 1029a, 1033a, 1041a], [1210p, 1219p, 1224p, 1229p, 1233p, 1241p], [210p, 219p, 224p, 229p, 233p, 241p], [410p, 419p, 424p, 429p, 433p, 441p], [610p, 619p, 624p, 629p, 633p, 641p], [813p, 821p, 826p, 830p, 834p, 841p], [1013p, 1021p, 1026p, 1030p, 1034p, 1041p]]
short_name: "924" short_name: "924"
   
--- ---
time_points: [Woden Bus Station (Platform 15), Southlands Mawson, Farrer Primary School, Isaacs Shops, Pearce Shops, Woden Bus Station] time_points: [Woden Bus Station (Platform 15), Southlands Mawson, Farrer Primary School, Isaacs, Pearce, Woden Bus Station]
long_name: To Woden Bus Station long_name: To Woden Bus Station
between_stops: {} between_stops: {}
   
short_name: "924" short_name: "924"
stop_times_sunday: [[1010a, 1019a, 1024a, 1029a, 1033a, 1041a], [1210p, 1219p, 1224p, 1229p, 1233p, 1241p], [210p, 219p, 224p, 229p, 233p, 241p], [410p, 419p, 424p, 429p, 433p, 441p], [610p, 619p, 624p, 629p, 633p, 641p]] stop_times_sunday: [[1010a, 1019a, 1024a, 1029a, 1033a, 1041a], [1210p, 1219p, 1224p, 1229p, 1233p, 1241p], [210p, 219p, 224p, 229p, 233p, 241p], [410p, 419p, 424p, 429p, 433p, 441p], [610p, 619p, 624p, 629p, 633p, 641p]]
   
---  
time_points: [Woden Bus Station (Platform 16), Weston Primary, Holder, Duffy, Cooleman Court]  
long_name: To Cooleman Court  
between_stops: {}  
 
short_name: "925"  
stop_times: [[957a, 1007a, 1009a, 1011a, 1019a], [1057a, 1107a, 1109a, 1111a, 1119a], [1157a, 1207p, 1209p, 1211p, 1219p], [1257p, 107p, 109p, 111p, 119p], [157p, 207p, 209p, 211p, 219p], [257p, 307p, 309p, 311p, 319p], [357p, 407p, 409p, 411p, 419p], [457p, 507p, 509p, 511p, 519p], [557p, 607p, 609p, 611p, 619p], [657p, 707p, 709p, 711p, 719p]]  
 
---  
time_points: [Cooleman Court, Duffy, Holder, Weston Primary, Woden Bus Station]  
long_name: To Woden Bus Station  
between_stops: {}  
 
short_name: "925"  
stop_times: [[924a, 931a, 934a, 937a, 946a], [1024a, 1031a, 1034a, 1037a, 1046a], [1124a, 1131a, 1134a, 1137a, 1146a], [1224p, 1231p, 1234p, 1237p, 1246p], [124p, 131p, 134p, 137p, 146p], [224p, 231p, 234p, 237p, 246p], [324p, 331p, 334p, 337p, 346p], [424p, 431p, 434p, 437p, 446p], [524p, 531p, 534p, 537p, 546p], [624p, 631p, 634p, 637p, 646p]]  
 
---  
time_points: [Woden Bus Station (Platform 3), Waramanga Shops, Fisher Shops, Chapman Shops, Rivett Shops, Cooleman Court]  
long_name: To Cooleman Court  
between_stops: {}  
 
short_name: "927"  
stop_times: [[920a, 929a, 932a, 942a, 945a, 950a], [1020a, 1029a, 1032a, 1042a, 1045a, 1050a], [1120a, 1129a, 1132a, 1142a, 1145a, 1150a], [1220p, 1229p, 1232p, 1242p, 1245p, 1250p], [120p, 129p, 132p, 142p, 145p, 150p], [220p, 229p, 232p, 242p, 245p, 250p], [320p, 329p, 332p, 342p, 345p, 350p], [420p, 429p, 432p, 442p, 445p, 450p], [520p, 529p, 532p, 542p, 545p, 550p], [620p, 629p, 632p, 642p, 645p, 650p]]  
 
--- ---
time_points: [Woden Bus Station (Platform 3), Waramanga Shops, Fisher Shops, Chapman Shops, Rivett Shops, Cooleman Court] time_points: [Woden Bus Station (Platform 3), Waramanga, Fisher, Chapman, Rivett, Cooleman Court]
long_name: To Cooleman Court long_name: To Cooleman Court
between_stops: {} between_stops: {}
   
stop_times_saturday: [[920a, 929a, 932a, 942a, 945a, 950a], [1020a, 1029a, 1032a, 1042a, 1045a, 1050a], [1120a, 1129a, 1132a, 1142a, 1145a, 1150a], [1220p, 1229p, 1232p, 1242p, 1245p, 1250p], [120p, 129p, 132p, 142p, 145p, 150p], [220p, 229p, 232p, 242p, 245p, 250p], [320p, 329p, 332p, 342p, 345p, 350p], [420p, 429p, 432p, 442p, 445p, 450p], [520p, 529p, 532p, 542p, 545p, 550p], [620p, 629p, 632p, 642p, 645p, 650p], [720p, 729p, 732p, 742p, 745p, 750p], [820p, 829p, 832p, 842p, 845p, 850p], [920p, 929p, 932p, 942p, 945p, 950p], [1020p, 1029p, 1032p, 1042p, 1045p, 1050p], [1120p, 1129p, 1132p, 1142p, 1145p, 1150p]] stop_times_saturday: [[920a, 929a, 932a, 942a, 945a, 950a], [1020a, 1029a, 1032a, 1042a, 1045a, 1050a], [1120a, 1129a, 1132a, 1142a, 1145a, 1150a], [1220p, 1229p, 1232p, 1242p, 1245p, 1250p], [120p, 129p, 132p, 142p, 145p, 150p], [220p, 229p, 232p, 242p, 245p, 250p], [320p, 329p, 332p, 342p, 345p, 350p], [420p, 429p, 432p, 442p, 445p, 450p], [520p, 529p, 532p, 542p, 545p, 550p], [620p, 629p, 632p, 642p, 645p, 650p], [720p, 729p, 732p, 742p, 745p, 750p], [820p, 829p, 832p, 842p, 845p, 850p], [920p, 929p, 932p, 942p, 945p, 950p], [1020p, 1029p, 1032p, 1042p, 1045p, 1050p], [1120p, 1129p, 1132p, 1142p, 1145p, 1150p]]
short_name: "927" short_name: "927"
   
--- ---
time_points: [Woden Bus Station (Platform 3), Waramanga Shops, Fisher Shops, Chapman Shops, Rivett Shops, Cooleman Court] time_points: [Woden Bus Station (Platform 3), Waramanga, Fisher, Chapman, Rivett, Cooleman Court]
long_name: To Cooleman Court long_name: To Cooleman Court
between_stops: {} between_stops: {}
   
short_name: "927" short_name: "927"
stop_times_sunday: [[920a, 929a, 932a, 942a, 945a, 950a], [1020a, 1029a, 1032a, 1042a, 1045a, 1050a], [1120a, 1129a, 1132a, 1142a, 1145a, 1150a], [1220p, 1229p, 1232p, 1242p, 1245p, 1250p], [120p, 129p, 132p, 142p, 145p, 150p], [220p, 229p, 232p, 242p, 245p, 250p], [320p, 329p, 332p, 342p, 345p, 350p], [420p, 429p, 432p, 442p, 445p, 450p], [520p, 529p, 532p, 542p, 545p, 550p], [620p, 629p, 632p, 642p, 645p, 650p]] stop_times_sunday: [[920a, 929a, 932a, 942a, 945a, 950a], [1020a, 1029a, 1032a, 1042a, 1045a, 1050a], [1120a, 1129a, 1132a, 1142a, 1145a, 1150a], [1220p, 1229p, 1232p, 1242p, 1245p, 1250p], [120p, 129p, 132p, 142p, 145p, 150p], [220p, 229p, 232p, 242p, 245p, 250p], [320p, 329p, 332p, 342p, 345p, 350p], [420p, 429p, 432p, 442p, 445p, 450p], [520p, 529p, 532p, 542p, 545p, 550p], [620p, 629p, 632p, 642p, 645p, 650p]]
   
---  
time_points: [Cooleman Court, Rivett Shops, Chapman Shops, Fisher Shops, Waramanga Shops, Woden Bus Station]  
long_name: To Woden Bus Station  
between_stops: {}  
 
short_name: "927"  
stop_times: [[855a, 903a, 906a, 916a, 919a, 926a], [955a, 1003a, 1006a, 1016a, 1019a, 1026a], [1055a, 1103a, 1106a, 1116a, 1119a, 1126a], [1155a, 1203p, 1206p, 1216p, 1219p, 1226p], [1255p, 103p, 106p, 116p, 119p, 126p], [155p, 203p, 206p, 216p, 219p, 226p], [255p, 303p, 306p, 316p, 319p, 326p], [355p, 403p, 406p, 416p, 419p, 426p], [455p, 503p, 506p, 516p, 519p, 526p], [555p, 603p, 606p, 616p, 619p, 626p], [655p, 703p, 706p, 716p, 719p, 726p]]  
 
--- ---
time_points: [Cooleman Court, Rivett Shops, Chapman Shops, Fisher Shops, Waramanga Shops, Woden Bus Station] time_points: [Cooleman Court, Rivett, Chapman, Fisher, Waramanga, Woden Bus Station]
long_name: To Woden Bus Station long_name: To Woden Bus Station
between_stops: {} between_stops: {}
   
stop_times_saturday: [[755a, 803a, 806a, 816a, 819a, 826a], [855a, 903a, 906a, 916a, 919a, 926a], [955a, 1003a, 1006a, 1016a, 1019a, 1026a], [1055a, 1103a, 1106a, 1116a, 1119a, 1126a], [1155a, 1203p, 1206p, 1216p, 1219p, 1226p], [1255p, 103p, 106p, 116p, 119p, 126p], [155p, 203p, 206p, 216p, 219p, 226p], [255p, 303p, 306p, 316p, 319p, 326p], [355p, 403p, 406p, 416p, 419p, 426p], [455p, 503p, 506p, 516p, 519p, 526p], [555p, 603p, 606p, 616p, 619p, 626p], [655p, 703p, 706p, 716p, 719p, 726p], [755p, 803p, 806p, 816p, 819p, 826p], [855p, 903p, 906p, 916p, 919p, 926p], [955p, 1003p, 1006p, 1016p, 1019p, 1026p], [1055p, 1103p, 1106p, 1116p, 1119p, 1126p]] stop_times_saturday: [[755a, 803a, 806a, 816a, 819a, 826a], [855a, 903a, 906a, 916a, 919a, 926a], [955a, 1003a, 1006a, 1016a, 1019a, 1026a], [1055a, 1103a, 1106a, 1116a, 1119a, 1126a], [1155a, 1203p, 1206p, 1216p, 1219p, 1226p], [1255p, 103p, 106p, 116p, 119p, 126p], [155p, 203p, 206p, 216p, 219p, 226p], [255p, 303p, 306p, 316p, 319p, 326p], [355p, 403p, 406p, 416p, 419p, 426p], [455p, 503p, 506p, 516p, 519p, 526p], [555p, 603p, 606p, 616p, 619p, 626p], [655p, 703p, 706p, 716p, 719p, 726p], [755p, 803p, 806p, 816p, 819p, 826p], [855p, 903p, 906p, 916p, 919p, 926p], [955p, 1003p, 1006p, 1016p, 1019p, 1026p], [1055p, 1103p, 1106p, 1116p, 1119p, 1126p]]
short_name: "927" short_name: "927"
   
--- ---
time_points: [Cooleman Court, Rivett Shops, Chapman Shops, Fisher Shops, Waramanga Shops, Woden Bus Station] time_points: [Cooleman Court, Rivett, Chapman, Fisher, Waramanga, Woden Bus Station]
long_name: To Woden Bus Station long_name: To Woden Bus Station
between_stops: {} between_stops: {}
   
short_name: "927" short_name: "927"
stop_times_sunday: [[855a, 903a, 906a, 916a, 919a, 926a], [955a, 1003a, 1006a, 1016a, 1019a, 1026a], [1055a, 1103a, 1106a, 1116a, 1119a, 1126a], [1155a, 1203p, 1206p, 1216p, 1219p, 1226p], [1255p, 103p, 106p, 116p, 119p, 126p], [155p, 203p, 206p, 216p, 219p, 226p], [255p, 303p, 306p, 316p, 319p, 326p], [355p, 403p, 406p, 416p, 419p, 426p], [455p, 503p, 506p, 516p, 519p, 526p], [555p, 603p, 606p, 616p, 619p, 626p], [655p, 703p, 706p, 716p, 719p, 726p]] stop_times_sunday: [[855a, 903a, 906a, 916a, 919a, 926a], [955a, 1003a, 1006a, 1016a, 1019a, 1026a], [1055a, 1103a, 1106a, 1116a, 1119a, 1126a], [1155a, 1203p, 1206p, 1216p, 1219p, 1226p], [1255p, 103p, 106p, 116p, 119p, 126p], [155p, 203p, 206p, 216p, 219p, 226p], [255p, 303p, 306p, 316p, 319p, 326p], [355p, 403p, 406p, 416p, 419p, 426p], [455p, 503p, 506p, 516p, 519p, 526p], [555p, 603p, 606p, 616p, 619p, 626p], [655p, 703p, 706p, 716p, 719p, 726p]]
   
---  
time_points: [City Bus Station (Platform 8), St Thomas More's Campbell, Hospice / Menindee Dr, ADFA, City Bus Station]  
long_name: To City Bus Station  
between_stops:  
ADFA-City Bus Station: [Wjzcend, Wjzd8br, Wjzd0CK, Wjz5VUU, Wjz5VFA, Wjz5VAq, Wjz5V64, Wjz5NRJ, Wjz5NAQ]  
short_name: "930"  
stop_times: [[1001a, 1013a, 1020a, 1027a, 1041a], [1201p, 1213p, 1220p, 1227p, 1241p], [201p, 213p, 220p, 227p, 241p], [401p, 413p, 420p, 427p, 441p], [601p, 613p, 620p, 627p, 641p]]  
 
---  
time_points: [City Bus Station (Platform 8), ADFA, Hospice / Menindee Dr, St Thomas More's Campbell, City Bus Station]  
long_name: To City Bus Station  
between_stops: {}  
 
short_name: "931"  
stop_times: [[901a, 915a, 922a, 929a, 941a], [1101a, 1115a, 1122a, 1129a, 1141a], [101p, 115p, 122p, 129p, 141p], [301p, 315p, 322p, 329p, 341p], [501p, 515p, 522p, 529p, 541p], [701p, 715p, 722p, 729p, 741p]]  
 
--- ---
time_points: [City Bus Station (Platform 8), ADFA, Hospice / Menindee Dr, St Thomas More's Campbell, City Bus Station] time_points: [City Bus Station (Platform 8), ADFA, Hospice / Menindee Dr, St Thomas More's Campbell, City Bus Station]
long_name: To City Bus Station long_name: To City Bus Station
between_stops: {} between_stops:
  ADFA-Hospice / Menindee Dr: [WjzceHt, WjzceFT, WjzcdDs, Wjzcdsn, Wjzcdi7, Wjzcd2U, Wjzcd8D]
stop_times_saturday: [[801a, 815a, 822a, 829a, 841a], [901a, 915a, 922a, 929a, 941a], [1101a, 1115a, 1122a, 1129a, 1141a], [101p, 115p, 122p, 129p, 141p], [301p, 315p, 322p, 329p, 341p], [501p, 515p, 522p, 529p, 541p], [701p, 715p, 722p, 729p, 741p]] stop_times_saturday: [[801a, 815a, 822a, 829a, 841a], [901a, 915a, 922a, 929a, 941a], [1101a, 1115a, 1122a, 1129a, 1141a], [101p, 115p, 122p, 129p, 141p], [301p, 315p, 322p, 329p, 341p], [501p, 515p, 522p, 529p, 541p], [701p, 715p, 722p, 729p, 741p]]
short_name: "931" short_name: "931"
   
--- ---
time_points: [City Bus Station (Platform 8), ADFA, Hospice / Menindee Dr, St Thomas More's Campbell, City Bus Station] time_points: [City Bus Station (Platform 8), ADFA, Hospice / Menindee Dr, St Thomas More's Campbell, City Bus Station]
long_name: To City Bus Station long_name: To City Bus Station
between_stops: {} between_stops:
  ADFA-Hospice / Menindee Dr: [WjzceHt, WjzceFT, WjzcdDs, Wjzcdsn, Wjzcdi7, Wjzcd2U, Wjzcd8D]
short_name: "931" short_name: "931"
stop_times_sunday: [[901a, 915a, 922a, 929a, 941a], [1101a, 1115a, 1122a, 1129a, 1141a], [101p, 115p, 122p, 129p, 141p], [301p, 315p, 322p, 329p, 341p], [501p, 515p, 522p, 529p, 541p], [701p, 715p, 722p, 729p, 741p]] stop_times_sunday: [[901a, 915a, 922a, 929a, 941a], [1101a, 1115a, 1122a, 1129a, 1141a], [101p, 115p, 122p, 129p, 141p], [301p, 315p, 322p, 329p, 341p], [501p, 515p, 522p, 529p, 541p], [701p, 715p, 722p, 729p, 741p]]
   
---  
time_points: [Woden Bus Station (Platform 4), Curtin Shops, John James Hospital, Yarralumla Shops, City Bus Station (Platform 8), Macarthur / Northbourne Ave, Southwell Park, Giralang Shops, Kaleen Village / Marybrynong, Gwydir Square Kaleen, University of Canberra, Belconnen Community Bus Station, Westfield Bus Station, Cohen Street Bus Station]  
long_name: To Cohen Street Bus Station  
between_stops:  
Westfield Bus Station-Cohen Street Bus Station: []  
Belconnen Community Bus Station-Westfield Bus Station: []  
University of Canberra-Belconnen Community Bus Station: [Wjz68Yy, Wjz68Y0, Wjz68IH, Wjz68Ip, Wjz689c, Wjz681S]  
short_name: "932"  
stop_times: [[839a, 850a, 853a, 856a, 909a, 915a, 919a, 928a, 936a, 941a, 947a, 950a, 952a, 957a], [939a, 950a, 953a, 956a, 1009a, 1015a, 1019a, 1028a, 1036a, 1041a, 1047a, 1050a, 1052a, 1057a], [1039a, 1050a, 1053a, 1056a, 1109a, 1115a, 1119a, 1128a, 1136a, 1141a, 1147a, 1150a, 1152a, 1157a], [1139a, 1150a, 1153a, 1156a, 1209p, 1215p, 1219p, 1228p, 1236p, 1241p, 1247p, 1250p, 1252p, 1257p], [1239p, 1250p, 1253p, 1256p, 109p, 115p, 119p, 128p, 136p, 141p, 147p, 150p, 152p, 157p], [139p, 150p, 153p, 156p, 209p, 215p, 219p, 228p, 236p, 241p, 247p, 250p, 252p, 257p], [239p, 250p, 253p, 256p, 309p, 315p, 319p, 328p, 336p, 341p, 347p, 350p, 352p, 357p], [339p, 350p, 353p, 356p, 409p, 415p, 419p, 428p, 436p, 441p, 447p, 450p, 452p, 457p], [439p, 450p, 453p, 456p, 509p, 515p, 519p, 528p, 536p, 541p, 547p, 550p, 552p, 557p], [539p, 550p, 553p, 556p, 609p, 615p, 619p, 628p, 635p, 640p, 645p, 648p, 650p, 655p], [639p, 648p, 651p, 654p, 707p, 712p, 716p, 725p, 732p, 737p, 742p, 745p, 747p, 752p]]  
 
--- ---
time_points: [Woden Bus Station (Platform 4), Curtin Shops, John James Hospital, Yarralumla Shops, City Bus Station (Platform 8), Macarthur / Northbourne Ave, Southwell Park, Giralang Shops, Kaleen Village / Marybrynong, Gwydir Square Kaleen, University of Canberra, Belconnen Community Bus Station, Westfield Bus Station, Cohen Street Bus Station] time_points: [Woden Bus Station (Platform 4), Curtin, John James Hospital, Yarralumla, City Bus Station (Platform 8), Macarthur / Northbourne Ave, Southwell Park, Giralang, Kaleen Village / Marybrynong, Gwydir Square Kaleen, University of Canberra, Belconnen Community Bus Station, Westfield Bus Station, Cohen Street Bus Station]
long_name: To Cohen Street Bus Station long_name: To Cohen Street Bus Station
between_stops: between_stops:
Westfield Bus Station-Cohen Street Bus Station: [] Westfield Bus Station-Cohen Street Bus Station: []
Belconnen Community Bus Station-Westfield Bus Station: [] Belconnen Community Bus Station-Westfield Bus Station: []
University of Canberra-Belconnen Community Bus Station: [Wjz68Yy, Wjz68Y0, Wjz68IH, Wjz68Ip, Wjz689c, Wjz681S] University of Canberra-Belconnen Community Bus Station: [Wjz68Yy, Wjz68Y0, Wjz68IH, Wjz68Ip, Wjz689c, Wjz681S]
stop_times_saturday: [[739a, 750a, 753a, 756a, 809a, 815a, 819a, 828a, 836a, 841a, 847a, 850a, 852a, 857a], [839a, 850a, 853a, 856a, 909a, 915a, 919a, 928a, 936a, 941a, 947a, 950a, 952a, 957a], [939a, 950a, 953a, 956a, 1009a, 1015a, 1019a, 1028a, 1036a, 1041a, 1047a, 1050a, 1052a, 1057a], [1039a, 1050a, 1053a, 1056a, 1109a, 1115a, 1119a, 1128a, 1136a, 1141a, 1147a, 1150a, 1152a, 1157a], [1139a, 1150a, 1153a, 1156a, 1209p, 1215p, 1219p, 1228p, 1236p, 1241p, 1247p, 1250p, 1252p, 1257p], [1239p, 1250p, 1253p, 1256p, 109p, 115p, 119p, 128p, 136p, 141p, 147p, 150p, 152p, 157p], [139p, 150p, 153p, 156p, 209p, 215p, 219p, 228p, 236p, 241p, 247p, 250p, 252p, 257p], [239p, 250p, 253p, 256p, 309p, 315p, 319p, 328p, 336p, 341p, 347p, 350p, 352p, 357p], [339p, 350p, 353p, 356p, 409p, 415p, 419p, 428p, 436p, 441p, 447p, 450p, 452p, 457p], [439p, 450p, 453p, 456p, 509p, 515p, 519p, 528p, 536p, 541p, 547p, 550p, 552p, 557p], [539p, 550p, 553p, 556p, 609p, 615p, 619p, 628p, 635p, 640p, 645p, 648p, 650p, 655p], [639p, 648p, 651p, 654p, 707p, 712p, 716p, 725p, 732p, 737p, 742p, 745p, 747p, 752p], [739p, 748p, 751p, 754p, 807p, 812p, 816p, 825p, 832p, 837p, 842p, 845p, 847p, 852p], [839p, 848p, 851p, 854p, 907p, 912p, 916p, 925p, 932p, 937p, 942p, 945p, 947p, 952p], [939p, 948p, 951p, 954p, 1007p, 1012p, 1016p, 1025p, 1032p, 1037p, 1042p, 1045p, 1047p, 1052p], [1039p, 1048p, 1051p, 1054p, 1107p, 1112p, 1116p, 1125p, 1132p, 1137p, 1142p, 1145p, 1147p, 1152p], [1139p, 1150p, 1153p, 1156p, 1208a, "-", "-", "-", "-", "-", "-", "-", "-"]] stop_times_saturday: [[739a, 750a, 753a, 756a, 809a, 815a, 819a, 828a, 836a, 841a, 847a, 850a, 852a, 857a], [839a, 850a, 853a, 856a, 909a, 915a, 919a, 928a, 936a, 941a, 947a, 950a, 952a, 957a], [939a, 950a, 953a, 956a, 1009a, 1015a, 1019a, 1028a, 1036a, 1041a, 1047a, 1050a, 1052a, 1057a], [1039a, 1050a, 1053a, 1056a, 1109a, 1115a, 1119a, 1128a, 1136a, 1141a, 1147a, 1150a, 1152a, 1157a], [1139a, 1150a, 1153a, 1156a, 1209p, 1215p, 1219p, 1228p, 1236p, 1241p, 1247p, 1250p, 1252p, 1257p], [1239p, 1250p, 1253p, 1256p, 109p, 115p, 119p, 128p, 136p, 141p, 147p, 150p, 152p, 157p], [139p, 150p, 153p, 156p, 209p, 215p, 219p, 228p, 236p, 241p, 247p, 250p, 252p, 257p], [239p, 250p, 253p, 256p, 309p, 315p, 319p, 328p, 336p, 341p, 347p, 350p, 352p, 357p], [339p, 350p, 353p, 356p, 409p, 415p, 419p, 428p, 436p, 441p, 447p, 450p, 452p, 457p], [439p, 450p, 453p, 456p, 509p, 515p, 519p, 528p, 536p, 541p, 547p, 550p, 552p, 557p], [539p, 550p, 553p, 556p, 609p, 615p, 619p, 628p, 635p, 640p, 645p, 648p, 650p, 655p], [639p, 648p, 651p, 654p, 707p, 712p, 716p, 725p, 732p, 737p, 742p, 745p, 747p, 752p], [739p, 748p, 751p, 754p, 807p, 812p, 816p, 825p, 832p, 837p, 842p, 845p, 847p, 852p], [839p, 848p, 851p, 854p, 907p, 912p, 916p, 925p, 932p, 937p, 942p, 945p, 947p, 952p], [939p, 948p, 951p, 954p, 1007p, 1012p, 1016p, 1025p, 1032p, 1037p, 1042p, 1045p, 1047p, 1052p], [1039p, 1048p, 1051p, 1054p, 1107p, 1112p, 1116p, 1125p, 1132p, 1137p, 1142p, 1145p, 1147p, 1152p], [1139p, 1150p, 1153p, 1156p, 1208a, "-", "-", "-", "-", "-", "-", "-", "-"]]
short_name: "932" short_name: "932"
   
--- ---
time_points: [Woden Bus Station (Platform 4), Curtin Shops, John James Hospital, Yarralumla Shops, City Bus Station (Platform 8), Macarthur / Northbourne Ave, Southwell Park, Giralang Shops, Kaleen Village / Marybrynong, Gwydir Square Kaleen, University of Canberra, Belconnen Community Bus Station, Westfield Bus Station, Cohen Street Bus Station] time_points: [Woden Bus Station (Platform 4), Curtin, John James Hospital, Yarralumla, City Bus Station (Platform 8), Macarthur / Northbourne Ave, Southwell Park, Giralang, Kaleen Village / Marybrynong, Gwydir Square Kaleen, University of Canberra, Belconnen Community Bus Station, Westfield Bus Station, Cohen Street Bus Station]
long_name: To Cohen Street Bus Station long_name: To Cohen Street Bus Station
between_stops: between_stops:
Westfield Bus Station-Cohen Street Bus Station: [] Westfield Bus Station-Cohen Street Bus Station: []
Belconnen Community Bus Station-Westfield Bus Station: [] Belconnen Community Bus Station-Westfield Bus Station: []
University of Canberra-Belconnen Community Bus Station: [Wjz68Yy, Wjz68Y0, Wjz68IH, Wjz68Ip, Wjz689c, Wjz681S] University of Canberra-Belconnen Community Bus Station: [Wjz68Yy, Wjz68Y0, Wjz68IH, Wjz68Ip, Wjz689c, Wjz681S]
short_name: "932" short_name: "932"
stop_times_sunday: [[839a, 850a, 853a, 856a, 909a, 915a, 919a, 928a, 936a, 941a, 947a, 950a, 952a, 957a], [939a, 950a, 953a, 956a, 1009a, 1015a, 1019a, 1028a, 1036a, 1041a, 1047a, 1050a, 1052a, 1057a], [1039a, 1050a, 1053a, 1056a, 1109a, 1115a, 1119a, 1128a, 1136a, 1141a, 1147a, 1150a, 1152a, 1157a], [1139a, 1150a, 1153a, 1156a, 1209p, 1215p, 1219p, 1228p, 1236p, 1241p, 1247p, 1250p, 1252p, 1257p], [1239p, 1250p, 1253p, 1256p, 109p, 115p, 119p, 128p, 136p, 141p, 147p, 150p, 152p, 157p], [139p, 150p, 153p, 156p, 209p, 215p, 219p, 228p, 236p, 241p, 247p, 250p, 252p, 257p], [239p, 250p, 253p, 256p, 309p, 315p, 319p, 328p, 336p, 341p, 347p, 350p, 352p, 357p], [339p, 350p, 353p, 356p, 409p, 415p, 419p, 428p, 436p, 441p, 447p, 450p, 452p, 457p], [439p, 450p, 453p, 456p, 509p, 515p, 519p, 528p, 536p, 541p, 547p, 550p, 552p, 557p], [539p, 550p, 553p, 556p, 609p, 615p, 619p, 628p, 635p, 640p, 645p, 648p, 650p, 655p], [639p, 648p, 651p, 654p, 707p, 712p, 716p, 725p, 732p, 737p, 742p, 745p, 747p, 752p]] stop_times_sunday: [[839a, 850a, 853a, 856a, 909a, 915a, 919a, 928a, 936a, 941a, 947a, 950a, 952a, 957a], [939a, 950a, 953a, 956a, 1009a, 1015a, 1019a, 1028a, 1036a, 1041a, 1047a, 1050a, 1052a, 1057a], [1039a, 1050a, 1053a, 1056a, 1109a, 1115a, 1119a, 1128a, 1136a, 1141a, 1147a, 1150a, 1152a, 1157a], [1139a, 1150a, 1153a, 1156a, 1209p, 1215p, 1219p, 1228p, 1236p, 1241p, 1247p, 1250p, 1252p, 1257p], [1239p, 1250p, 1253p, 1256p, 109p, 115p, 119p, 128p, 136p, 141p, 147p, 150p, 152p, 157p], [139p, 150p, 153p, 156p, 209p, 215p, 219p, 228p, 236p, 241p, 247p, 250p, 252p, 257p], [239p, 250p, 253p, 256p, 309p, 315p, 319p, 328p, 336p, 341p, 347p, 350p, 352p, 357p], [339p, 350p, 353p, 356p, 409p, 415p, 419p, 428p, 436p, 441p, 447p, 450p, 452p, 457p], [439p, 450p, 453p, 456p, 509p, 515p, 519p, 528p, 536p, 541p, 547p, 550p, 552p, 557p], [539p, 550p, 553p, 556p, 609p, 615p, 619p, 628p, 635p, 640p, 645p, 648p, 650p, 655p], [639p, 648p, 651p, 654p, 707p, 712p, 716p, 725p, 732p, 737p, 742p, 745p, 747p, 752p]]
   
---  
time_points: [Cohen Street Bus Station (Platform 2), Westfield Bus Station (Platform 1), Belconnen Community Bus Station (Platform 3), University of Canberra, Gwydir Square Kaleen, Kaleen Village / Marybrynong, Giralang Shops, Southwell Park, Macarthur / Northbourne Ave, City Bus Station (Platform 9), Yarralumla Shops, John James Hospital, Curtin Shops, Woden Bus Station]  
long_name: To Woden Bus Station  
between_stops:  
Westfield Bus Station (Platform 1)-Belconnen Community Bus Station (Platform 3): []  
Cohen Street Bus Station (Platform 2)-Westfield Bus Station (Platform 1): []  
Belconnen Community Bus Station (Platform 3)-University of Canberra: [Wjz681S, Wjz689c, Wjz68Ip, Wjz68IH, Wjz68Y0, Wjz68Yy]  
short_name: "932"  
stop_times: [[746a, 748a, 752a, 757a, 803a, 808a, 810a, 825a, 830a, 838a, 850a, 853a, 857a, 908a], [846a, 848a, 852a, 857a, 903a, 908a, 910a, 925a, 930a, 938a, 950a, 953a, 957a, 1008a], [946a, 948a, 952a, 957a, 1003a, 1008a, 1010a, 1025a, 1030a, 1038a, 1050a, 1053a, 1057a, 1108a], [1046a, 1048a, 1052a, 1057a, 1103a, 1108a, 1110a, 1125a, 1130a, 1138a, 1150a, 1153a, 1157a, 1208p], [1146a, 1148a, 1152a, 1157a, 1203p, 1208p, 1210p, 1225p, 1230p, 1238p, 1250p, 1253p, 1257p, 108p], [1246p, 1248p, 1252p, 1257p, 103p, 108p, 110p, 125p, 130p, 138p, 150p, 153p, 157p, 208p], [146p, 148p, 152p, 157p, 203p, 208p, 210p, 225p, 230p, 238p, 250p, 253p, 257p, 308p], [246p, 248p, 252p, 257p, 303p, 308p, 310p, 325p, 330p, 338p, 350p, 353p, 357p, 408p], [346p, 348p, 352p, 357p, 403p, 408p, 410p, 425p, 430p, 438p, 450p, 453p, 457p, 508p], [446p, 448p, 452p, 457p, 503p, 508p, 510p, 525p, 530p, 538p, 550p, 553p, 557p, 608p], [546p, 548p, 552p, 557p, 603p, 608p, 610p, 625p, 630p, 637p, 649p, 652p, 655p, 705p]]  
 
--- ---
time_points: [Cohen Street Bus Station (Platform 2), Westfield Bus Station (Platform 1), Belconnen Community Bus Station (Platform 3), University of Canberra, Gwydir Square Kaleen, Kaleen Village / Marybrynong, Giralang Shops, Southwell Park, Macarthur / Northbourne Ave, City Bus Station (Platform 9), Yarralumla Shops, John James Hospital, Curtin Shops, Woden Bus Station] time_points: [Cohen Street Bus Station (Platform 2), Westfield Bus Station (Platform 1), Belconnen Community Bus Station (Platform 3), University of Canberra, Gwydir Square Kaleen, Kaleen Village / Marybrynong, Giralang, Southwell Park, Macarthur / Northbourne Ave, City Bus Station (Platform 9), Yarralumla, John James Hospital, Curtin, Woden Bus Station]
long_name: To Woden Bus Station long_name: To Woden Bus Station
between_stops: between_stops:
Westfield Bus Station (Platform 1)-Belconnen Community Bus Station (Platform 3): [] Westfield Bus Station (Platform 1)-Belconnen Community Bus Station (Platform 3): []
Cohen Street Bus Station (Platform 2)-Westfield Bus Station (Platform 1): [] Cohen Street Bus Station (Platform 2)-Westfield Bus Station (Platform 1): []
Belconnen Community Bus Station (Platform 3)-University of Canberra: [Wjz681S, Wjz689c, Wjz68Ip, Wjz68IH, Wjz68Y0, Wjz68Yy] Belconnen Community Bus Station (Platform 3)-University of Canberra: [Wjz681S, Wjz689c, Wjz68Ip, Wjz68IH, Wjz68Y0, Wjz68Yy]
stop_times_saturday: [[746a, 748a, 752a, 757a, 803a, 808a, 810a, 825a, 830a, 838a, 850a, 853a, 857a, 908a], [846a, 848a, 852a, 857a, 903a, 908a, 910a, 925a, 930a, 938a, 950a, 953a, 957a, 1008a], [946a, 948a, 952a, 957a, 1003a, 1008a, 1010a, 1025a, 1030a, 1038a, 1050a, 1053a, 1057a, 1108a], [1046a, 1048a, 1052a, 1057a, 1103a, 1108a, 1110a, 1125a, 1130a, 1138a, 1150a, 1153a, 1157a, 1208p], [1146a, 1148a, 1152a, 1157a, 1203p, 1208p, 1210p, 1225p, 1230p, 1238p, 1250p, 1253p, 1257p, 108p], [1246p, 1248p, 1252p, 1257p, 103p, 108p, 110p, 125p, 130p, 138p, 150p, 153p, 157p, 208p], [146p, 148p, 152p, 157p, 203p, 208p, 210p, 225p, 230p, 238p, 250p, 253p, 257p, 308p], [246p, 248p, 252p, 257p, 303p, 308p, 310p, 325p, 330p, 338p, 350p, 353p, 357p, 408p], [346p, 348p, 352p, 357p, 403p, 408p, 410p, 425p, 430p, 438p, 450p, 453p, 457p, 508p], [446p, 448p, 452p, 457p, 503p, 508p, 510p, 525p, 530p, 538p, 550p, 553p, 557p, 608p], [546p, 548p, 552p, 557p, 603p, 608p, 610p, 625p, 630p, 637p, 649p, 652p, 655p, 705p], [645p, 647p, 651p, 656p, 701p, 706p, 708p, 723p, 728p, 735p, 747p, 750p, 753p, 803p], [745p, 747p, 751p, 756p, 801p, 806p, 808p, 823p, 828p, 835p, 847p, 850p, 853p, 903p], [845p, 847p, 851p, 856p, 901p, 906p, 908p, 923p, 928p, 935p, 947p, 950p, 953p, 1003p], [945p, 947p, 951p, 956p, 1001p, 1006p, 1008p, 1023p, 1028p, 1035p, 1047p, 1050p, 1053p, 1103p], [1045p, 1047p, 1051p, 1056p, 1101p, 1106p, 1108p, 1123p, 1128p, 1134p, "-", "-", "-", "-"]] stop_times_saturday: [[746a, 748a, 752a, 757a, 803a, 808a, 810a, 825a, 830a, 838a, 850a, 853a, 857a, 908a], [846a, 848a, 852a, 857a, 903a, 908a, 910a, 925a, 930a, 938a, 950a, 953a, 957a, 1008a], [946a, 948a, 952a, 957a, 1003a, 1008a, 1010a, 1025a, 1030a, 1038a, 1050a, 1053a, 1057a, 1108a], [1046a, 1048a, 1052a, 1057a, 1103a, 1108a, 1110a, 1125a, 1130a, 1138a, 1150a, 1153a, 1157a, 1208p], [1146a, 1148a, 1152a, 1157a, 1203p, 1208p, 1210p, 1225p, 1230p, 1238p, 1250p, 1253p, 1257p, 108p], [1246p, 1248p, 1252p, 1257p, 103p, 108p, 110p, 125p, 130p, 138p, 150p, 153p, 157p, 208p], [146p, 148p, 152p, 157p, 203p, 208p, 210p, 225p, 230p, 238p, 250p, 253p, 257p, 308p], [246p, 248p, 252p, 257p, 303p, 308p, 310p, 325p, 330p, 338p, 350p, 353p, 357p, 408p], [346p, 348p, 352p, 357p, 403p, 408p, 410p, 425p, 430p, 438p, 450p, 453p, 457p, 508p], [446p, 448p, 452p, 457p, 503p, 508p, 510p, 525p, 530p, 538p, 550p, 553p, 557p, 608p], [546p, 548p, 552p, 557p, 603p, 608p, 610p, 625p, 630p, 637p, 649p, 652p, 655p, 705p], [645p, 647p, 651p, 656p, 701p, 706p, 708p, 723p, 728p, 735p, 747p, 750p, 753p, 803p], [745p, 747p, 751p, 756p, 801p, 806p, 808p, 823p, 828p, 835p, 847p, 850p, 853p, 903p], [845p, 847p, 851p, 856p, 901p, 906p, 908p, 923p, 928p, 935p, 947p, 950p, 953p, 1003p], [945p, 947p, 951p, 956p, 1001p, 1006p, 1008p, 1023p, 1028p, 1035p, 1047p, 1050p, 1053p, 1103p], [1045p, 1047p, 1051p, 1056p, 1101p, 1106p, 1108p, 1123p, 1128p, 1134p, "-", "-", "-", "-"]]
short_name: "932" short_name: "932"
   
--- ---
time_points: [Cohen Street Bus Station (Platform 2), Westfield Bus Station (Platform 1), Belconnen Community Bus Station (Platform 3), University of Canberra, Gwydir Square Kaleen, Kaleen Village / Marybrynong, Giralang Shops, Southwell Park, Macarthur / Northbourne Ave, City Bus Station (Platform 9), Yarralumla Shops, John James Hospital, Curtin Shops, Woden Bus Station] time_points: [Cohen Street Bus Station (Platform 2), Westfield Bus Station (Platform 1), Belconnen Community Bus Station (Platform 3), University of Canberra, Gwydir Square Kaleen, Kaleen Village / Marybrynong, Giralang, Southwell Park, Macarthur / Northbourne Ave, City Bus Station (Platform 9), Yarralumla, John James Hospital, Curtin, Woden Bus Station]
long_name: To Woden Bus Station long_name: To Woden Bus Station
between_stops: between_stops:
Westfield Bus Station (Platform 1)-Belconnen Community Bus Station (Platform 3): [] Westfield Bus Station (Platform 1)-Belconnen Community Bus Station (Platform 3): []
Cohen Street Bus Station (Platform 2)-Westfield Bus Station (Platform 1): [] Cohen Street Bus Station (Platform 2)-Westfield Bus Station (Platform 1): []
Belconnen Community Bus Station (Platform 3)-University of Canberra: [Wjz681S, Wjz689c, Wjz68Ip, Wjz68IH, Wjz68Y0, Wjz68Yy] Belconnen Community Bus Station (Platform 3)-University of Canberra: [Wjz681S, Wjz689c, Wjz68Ip, Wjz68IH, Wjz68Y0, Wjz68Yy]
short_name: "932" short_name: "932"
stop_times_sunday: [[746a, 748a, 752a, 757a, 803a, 808a, 810a, 825a, 830a, 838a, 850a, 853a, 857a, 908a], [846a, 848a, 852a, 857a, 903a, 908a, 910a, 925a, 930a, 938a, 950a, 953a, 957a, 1008a], [946a, 948a, 952a, 957a, 1003a, 1008a, 1010a, 1025a, 1030a, 1038a, 1050a, 1053a, 1057a, 1108a], [1046a, 1048a, 1052a, 1057a, 1103a, 1108a, 1110a, 1125a, 1130a, 1138a, 1150a, 1153a, 1157a, 1208p], [1146a, 1148a, 1152a, 1157a, 1203p, 1208p, 1210p, 1225p, 1230p, 1238p, 1250p, 1253p, 1257p, 108p], [1246p, 1248p, 1252p, 1257p, 103p, 108p, 110p, 125p, 130p, 138p, 150p, 153p, 157p, 208p], [146p, 148p, 152p, 157p, 203p, 208p, 210p, 225p, 230p, 238p, 250p, 253p, 257p, 308p], [246p, 248p, 252p, 257p, 303p, 308p, 310p, 325p, 330p, 338p, 350p, 353p, 357p, 408p], [346p, 348p, 352p, 357p, 403p, 408p, 410p, 425p, 430p, 438p, 450p, 453p, 457p, 508p], [446p, 448p, 452p, 457p, 503p, 508p, 510p, 525p, 530p, 538p, 550p, 553p, 557p, 608p], [546p, 548p, 552p, 557p, 603p, 608p, 610p, 625p, 630p, 637p, 649p, 652p, 655p, 705p]] stop_times_sunday: [[746a, 748a, 752a, 757a, 803a, 808a, 810a, 825a, 830a, 838a, 850a, 853a, 857a, 908a], [846a, 848a, 852a, 857a, 903a, 908a, 910a, 925a, 930a, 938a, 950a, 953a, 957a, 1008a], [946a, 948a, 952a, 957a, 1003a, 1008a, 1010a, 1025a, 1030a, 1038a, 1050a, 1053a, 1057a, 1108a], [1046a, 1048a, 1052a, 1057a, 1103a, 1108a, 1110a, 1125a, 1130a, 1138a, 1150a, 1153a, 1157a, 1208p], [1146a, 1148a, 1152a, 1157a, 1203p, 1208p, 1210p, 1225p, 1230p, 1238p, 1250p, 1253p, 1257p, 108p], [1246p, 1248p, 1252p, 1257p, 103p, 108p, 110p, 125p, 130p, 138p, 150p, 153p, 157p, 208p], [146p, 148p, 152p, 157p, 203p, 208p, 210p, 225p, 230p, 238p, 250p, 253p, 257p, 308p], [246p, 248p, 252p, 257p, 303p, 308p, 310p, 325p, 330p, 338p, 350p, 353p, 357p, 408p], [346p, 348p, 352p, 357p, 403p, 408p, 410p, 425p, 430p, 438p, 450p, 453p, 457p, 508p], [446p, 448p, 452p, 457p, 503p, 508p, 510p, 525p, 530p, 538p, 550p, 553p, 557p, 608p], [546p, 548p, 552p, 557p, 603p, 608p, 610p, 625p, 630p, 637p, 649p, 652p, 655p, 705p]]
   
---  
time_points: [Woden Bus Station (Platform 14), Canberra Hospital, Garran Shops, Hughes Shops, Deakin Shops, Kings Ave / National Circuit, City Bus Station (Platform 4), National Museum of Australia, Burton and Garran Hall Daley Road, O'Connor Shops, Calvary Hospital, Belconnen Community Bus Station, Westfield Bus Station, Cohen Street Bus Station]  
long_name: To Cohen Street Bus Station  
between_stops:  
Westfield Bus Station-Cohen Street Bus Station: []  
Belconnen Community Bus Station-Westfield Bus Station: []  
Woden Bus Station (Platform 14)-Canberra Hospital: [Wjz3mAg, Wjz3mPO, Wjz3mWn]  
Kings Ave / National Circuit-City Bus Station (Platform 4): [Wjz4Quk, Wjz4RwH, Wjz4RFJ, Wjz4Rs-, Wjz4S1U, Wjz5FOn]  
short_name: "934"  
stop_times: [[813a, 820a, 822a, 826a, 831a, 840a, 852a, 859a, 904a, 909a, 916a, 933a, 935a, 940a], [913a, 920a, 922a, 926a, 931a, 940a, 952a, 959a, 1004a, 1009a, 1016a, 1033a, 1035a, 1040a], [1013a, 1020a, 1022a, 1026a, 1031a, 1040a, 1052a, 1059a, 1104a, 1109a, 1116a, 1133a, 1135a, 1140a], [1113a, 1120a, 1122a, 1126a, 1131a, 1140a, 1152a, 1159a, 1204p, 1209p, 1216p, 1233p, 1235p, 1240p], [1213p, 1220p, 1222p, 1226p, 1231p, 1240p, 1252p, 1259p, 104p, 109p, 116p, 133p, 135p, 140p], [113p, 120p, 122p, 126p, 131p, 140p, 152p, 159p, 204p, 209p, 216p, 233p, 235p, 240p], [213p, 220p, 222p, 226p, 231p, 240p, 252p, 259p, 304p, 309p, 316p, 333p, 335p, 340p], [313p, 320p, 322p, 326p, 331p, 340p, 352p, 359p, 404p, 409p, 416p, 433p, 435p, 440p], [413p, 420p, 422p, 426p, 431p, 440p, 452p, 459p, 504p, 509p, 516p, 533p, 535p, 540p], [513p, 520p, 522p, 526p, 531p, 540p, 552p, 559p, 604p, 609p, 616p, 633p, 635p, 640p], [613p, 620p, 622p, 626p, 631p, 640p, 652p, 659p, 704p, 709p, 716p, 733p, 735p, 740p]]  
 
--- ---
time_points: [Woden Bus Station (Platform 14), Canberra Hospital, Garran Shops, Hughes Shops, Deakin Shops, Kings Ave / National Circuit, City Bus Station (Platform 4), National Museum of Australia, Burton and Garran Hall Daley Road, O'Connor Shops, Calvary Hospital, Belconnen Community Bus Station, Westfield Bus Station, Cohen Street Bus Station] time_points: [Woden Bus Station (Platform 14), Canberra Hospital, Garran, Hughes, Deakin, Kings Ave / National Circuit, City Bus Station (Platform 4), National Museum of Australia, Burton and Garran Hall Daley Road, O'Connor, Calvary Hospital, Belconnen Community Bus Station, Westfield Bus Station, Cohen Street Bus Station]
long_name: To Cohen Street Bus Station long_name: To Cohen Street Bus Station
between_stops: between_stops:
Westfield Bus Station-Cohen Street Bus Station: [] Westfield Bus Station-Cohen Street Bus Station: []
Belconnen Community Bus Station-Westfield Bus Station: [] Belconnen Community Bus Station-Westfield Bus Station: []
Woden Bus Station (Platform 14)-Canberra Hospital: [Wjz3mAg, Wjz3mPO, Wjz3mWn] Woden Bus Station (Platform 14)-Canberra Hospital: [Wjz3mAg, Wjz3mPO, Wjz3mWn]
Kings Ave / National Circuit-City Bus Station (Platform 4): [Wjz4Quk, Wjz4RwH, Wjz4RFJ, Wjz4Rs-, Wjz4S1U, Wjz5FOn] Kings Ave / National Circuit-City Bus Station (Platform 4): [Wjz4Quk, Wjz4RwH, Wjz4RFJ, Wjz4Rs-, Wjz4S1U, Wjz5FOn]
stop_times_saturday: [["-", "-", "-", "-", "-", "-", 752a, 759a, 804a, 809a, 816a, 833a, 835a, 840a], [813a, 820a, 822a, 826a, 831a, 840a, 852a, 859a, 904a, 909a, 916a, 933a, 935a, 940a], [913a, 920a, 922a, 926a, 931a, 940a, 952a, 959a, 1004a, 1009a, 1016a, 1033a, 1035a, 1040a], [1013a, 1020a, 1022a, 1026a, 1031a, 1040a, 1052a, 1059a, 1104a, 1109a, 1116a, 1133a, 1135a, 1140a], [1113a, 1120a, 1122a, 1126a, 1131a, 1140a, 1152a, 1159a, 1204p, 1209p, 1216p, 1233p, 1235p, 1240p], [1213p, 1220p, 1222p, 1226p, 1231p, 1240p, 1252p, 1259p, 104p, 109p, 116p, 133p, 135p, 140p], [113p, 120p, 122p, 126p, 131p, 140p, 152p, 159p, 204p, 209p, 216p, 233p, 235p, 240p], [213p, 220p, 222p, 226p, 231p, 240p, 252p, 259p, 304p, 309p, 316p, 333p, 335p, 340p], [313p, 320p, 322p, 326p, 331p, 340p, 352p, 359p, 404p, 409p, 416p, 433p, 435p, 440p], [413p, 420p, 422p, 426p, 431p, 440p, 452p, 459p, 504p, 509p, 516p, 533p, 535p, 540p], [513p, 520p, 522p, 526p, 531p, 540p, 552p, 559p, 604p, 609p, 616p, 633p, 635p, 640p], [613p, 620p, 622p, 626p, 631p, 640p, 652p, 659p, 704p, 709p, 716p, 733p, 735p, 740p], [713p, 720p, 722p, 726p, 731p, 740p, 752p, 759p, 804p, 809p, 816p, 833p, 835p, 840p], [813p, 820p, 822p, 826p, 831p, 840p, 852p, 859p, 904p, 909p, 916p, 933p, 935p, 940p], [913p, 920p, 922p, 926p, 931p, 940p, 952p, 959p, 1004p, 1009p, 1016p, 1033p, 1035p, 1040p], [1013p, 1020p, 1022p, 1026p, 1031p, 1040p, 1052p, 1059p, 1104p, 1109p, 1116p, 1133p, 1135p, 1140p], [1113p, 1120p, 1122p, 1126p, 1131p, 1140p, 1150p, "-", "-", "-", "-", "-", "-", "-"]] stop_times_saturday: [["-", "-", "-", "-", "-", "-", 752a, 759a, 804a, 809a, 816a, 833a, 835a, 840a], [813a, 820a, 822a, 826a, 831a, 840a, 852a, 859a, 904a, 909a, 916a, 933a, 935a, 940a], [913a, 920a, 922a, 926a, 931a, 940a, 952a, 959a, 1004a, 1009a, 1016a, 1033a, 1035a, 1040a], [1013a, 1020a, 1022a, 1026a, 1031a, 1040a, 1052a, 1059a, 1104a, 1109a, 1116a, 1133a, 1135a, 1140a], [1113a, 1120a, 1122a, 1126a, 1131a, 1140a, 1152a, 1159a, 1204p, 1209p, 1216p, 1233p, 1235p, 1240p], [1213p, 1220p, 1222p, 1226p, 1231p, 1240p, 1252p, 1259p, 104p, 109p, 116p, 133p, 135p, 140p], [113p, 120p, 122p, 126p, 131p, 140p, 152p, 159p, 204p, 209p, 216p, 233p, 235p, 240p], [213p, 220p, 222p, 226p, 231p, 240p, 252p, 259p, 304p, 309p, 316p, 333p, 335p, 340p], [313p, 320p, 322p, 326p, 331p, 340p, 352p, 359p, 404p, 409p, 416p, 433p, 435p, 440p], [413p, 420p, 422p, 426p, 431p, 440p, 452p, 459p, 504p, 509p, 516p, 533p, 535p, 540p], [513p, 520p, 522p, 526p, 531p, 540p, 552p, 559p, 604p, 609p, 616p, 633p, 635p, 640p], [613p, 620p, 622p, 626p, 631p, 640p, 652p, 659p, 704p, 709p, 716p, 733p, 735p, 740p], [713p, 720p, 722p, 726p, 731p, 740p, 752p, 759p, 804p, 809p, 816p, 833p, 835p, 840p], [813p, 820p, 822p, 826p, 831p, 840p, 852p, 859p, 904p, 909p, 916p, 933p, 935p, 940p], [913p, 920p, 922p, 926p, 931p, 940p, 952p, 959p, 1004p, 1009p, 1016p, 1033p, 1035p, 1040p], [1013p, 1020p, 1022p, 1026p, 1031p, 1040p, 1052p, 1059p, 1104p, 1109p, 1116p, 1133p, 1135p, 1140p], [1113p, 1120p, 1122p, 1126p, 1131p, 1140p, 1150p, "-", "-", "-", "-", "-", "-", "-"]]
short_name: "934" short_name: "934"
   
--- ---
time_points: [Woden Bus Station (Platform 14), Canberra Hospital, Garran Shops, Hughes Shops, Deakin Shops, Kings Ave / National Circuit, City Bus Station (Platform 4), National Museum of Australia, Burton and Garran Hall Daley Road, O'Connor Shops, Calvary Hospital, Belconnen Community Bus Station, Westfield Bus Station, Cohen Street Bus Station] time_points: [Woden Bus Station (Platform 14), Canberra Hospital, Garran, Hughes, Deakin, Kings Ave / National Circuit, City Bus Station (Platform 4), National Museum of Australia, Burton and Garran Hall Daley Road, O'Connor, Calvary Hospital, Belconnen Community Bus Station, Westfield Bus Station, Cohen Street Bus Station]
long_name: To Cohen Street Bus Station long_name: To Cohen Street Bus Station
between_stops: between_stops:
Westfield Bus Station-Cohen Street Bus Station: [] Westfield Bus Station-Cohen Street Bus Station: []
Belconnen Community Bus Station-Westfield Bus Station: [] Belconnen Community Bus Station-Westfield Bus Station: []
Woden Bus Station (Platform 14)-Canberra Hospital: [Wjz3mAg, Wjz3mPO, Wjz3mWn] Woden Bus Station (Platform 14)-Canberra Hospital: [Wjz3mAg, Wjz3mPO, Wjz3mWn]
Kings Ave / National Circuit-City Bus Station (Platform 4): [Wjz4Quk, Wjz4RwH, Wjz4RFJ, Wjz4Rs-, Wjz4S1U, Wjz5FOn] Kings Ave / National Circuit-City Bus Station (Platform 4): [Wjz4Quk, Wjz4RwH, Wjz4RFJ, Wjz4Rs-, Wjz4S1U, Wjz5FOn]
short_name: "934" short_name: "934"
stop_times_sunday: [[813a, 820a, 822a, 826a, 831a, 840a, 852a, 859a, 904a, 909a, 916a, 933a, 935a, 940a], [913a, 920a, 922a, 926a, 931a, 940a, 952a, 959a, 1004a, 1009a, 1016a, 1033a, 1035a, 1040a], [1013a, 1020a, 1022a, 1026a, 1031a, 1040a, 1052a, 1059a, 1104a, 1109a, 1116a, 1133a, 1135a, 1140a], [1113a, 1120a, 1122a, 1126a, 1131a, 1140a, 1152a, 1159a, 1204p, 1209p, 1216p, 1233p, 1235p, 1240p], [1213p, 1220p, 1222p, 1226p, 1231p, 1240p, 1252p, 1259p, 104p, 109p, 116p, 133p, 135p, 140p], [113p, 120p, 122p, 126p, 131p, 140p, 152p, 159p, 204p, 209p, 216p, 233p, 235p, 240p], [213p, 220p, 222p, 226p, 231p, 240p, 252p, 259p, 304p, 309p, 316p, 333p, 335p, 340p], [313p, 320p, 322p, 326p, 331p, 340p, 352p, 359p, 404p, 409p, 416p, 433p, 435p, 440p], [413p, 420p, 422p, 426p, 431p, 440p, 452p, 459p, 504p, 509p, 516p, 533p, 535p, 540p], [513p, 520p, 522p, 526p, 531p, 540p, 552p, 559p, 604p, 609p, 616p, 633p, 635p, 640p], [613p, 620p, 622p, 626p, 631p, 640p, 652p, 659p, 704p, 709p, 716p, 733p, 735p, 740p]] stop_times_sunday: [[813a, 820a, 822a, 826a, 831a, 840a, 852a, 859a, 904a, 909a, 916a, 933a, 935a, 940a], [913a, 920a, 922a, 926a, 931a, 940a, 952a, 959a, 1004a, 1009a, 1016a, 1033a, 1035a, 1040a], [1013a, 1020a, 1022a, 1026a, 1031a, 1040a, 1052a, 1059a, 1104a, 1109a, 1116a, 1133a, 1135a, 1140a], [1113a, 1120a, 1122a, 1126a, 1131a, 1140a, 1152a, 1159a, 1204p, 1209p, 1216p, 1233p, 1235p, 1240p], [1213p, 1220p, 1222p, 1226p, 1231p, 1240p, 1252p, 1259p, 104p, 109p, 116p, 133p, 135p, 140p], [113p, 120p, 122p, 126p, 131p, 140p, 152p, 159p, 204p, 209p, 216p, 233p, 235p, 240p], [213p, 220p, 222p, 226p, 231p, 240p, 252p, 259p, 304p, 309p, 316p, 333p, 335p, 340p], [313p, 320p, 322p, 326p, 331p, 340p, 352p, 359p, 404p, 409p, 416p, 433p, 435p, 440p], [413p, 420p, 422p, 426p, 431p, 440p, 452p, 459p, 504p, 509p, 516p, 533p, 535p, 540p], [513p, 520p, 522p, 526p, 531p, 540p, 552p, 559p, 604p, 609p, 616p, 633p, 635p, 640p], [613p, 620p, 622p, 626p, 631p, 640p, 652p, 659p, 704p, 709p, 716p, 733p, 735p, 740p]]
   
---  
time_points: [Cohen Street Bus Station (Platform 2), Westfield Bus Station (Platform 1), Belconnen Community Bus Station (Platform 3), Calvary Hospital, O'Connor Shops, Burton and Garran Hall Daley Road, National Museum of Australia, City Bus Station (Platform 2), Kings Ave / National Circuit, Deakin Shops, Hughes Shops, Garran Shops, Canberra Hospital, Woden Bus Station]  
long_name: To Woden Bus Station  
between_stops:  
Westfield Bus Station (Platform 1)-Belconnen Community Bus Station (Platform 3): []  
City Bus Station (Platform 2)-Kings Ave / National Circuit: [Wjz5FOn, Wjz4S1U, Wjz4Rs-, Wjz4RFJ, Wjz4RwH, Wjz4Quk]  
Cohen Street Bus Station (Platform 2)-Westfield Bus Station (Platform 1): []  
Canberra Hospital-Woden Bus Station: [Wjz3mWn, Wjz3mPO, Wjz3mAg]  
short_name: "934"  
stop_times: [[829a, 831a, 835a, 852a, 859a, 904a, 909a, 919a, 928a, 937a, 942a, 946a, 948a, 955a], [929a, 931a, 935a, 952a, 959a, 1004a, 1009a, 1019a, 1028a, 1037a, 1042a, 1046a, 1048a, 1055a], [1029a, 1031a, 1035a, 1052a, 1059a, 1104a, 1109a, 1119a, 1128a, 1137a, 1142a, 1146a, 1148a, 1155a], [1129a, 1131a, 1135a, 1152a, 1159a, 1204p, 1209p, 1219p, 1228p, 1237p, 1242p, 1246p, 1248p, 1255p], [1229p, 1231p, 1235p, 1252p, 1259p, 104p, 109p, 119p, 128p, 137p, 142p, 146p, 148p, 155p], [129p, 131p, 135p, 152p, 159p, 204p, 209p, 219p, 228p, 237p, 242p, 246p, 248p, 255p], [229p, 231p, 235p, 252p, 259p, 304p, 309p, 319p, 328p, 337p, 342p, 346p, 348p, 355p], [329p, 331p, 335p, 352p, 359p, 404p, 409p, 419p, 428p, 437p, 442p, 446p, 448p, 455p], [429p, 431p, 435p, 452p, 459p, 504p, 509p, 519p, 528p, 537p, 542p, 546p, 548p, 555p], [529p, 531p, 535p, 552p, 559p, 604p, 609p, 619p, 628p, 637p, 642p, 646p, 648p, 655p], [629p, 631p, 635p, 652p, 659p, 704p, 709p, 719p, 728p, 737p, 742p, 746p, 748p, 755p]]  
 
--- ---
time_points: [Cohen Street Bus Station (Platform 2), Westfield Bus Station (Platform 1), Belconnen Community Bus Station (Platform 3), Calvary Hospital, O'Connor Shops, Burton and Garran Hall Daley Road, National Museum of Australia, City Bus Station (Platform 2), Kings Ave / National Circuit, Deakin Shops, Hughes Shops, Garran Shops, Canberra Hospital, Woden Bus Station] time_points: [Cohen Street Bus Station (Platform 2), Westfield Bus Station (Platform 1), Belconnen Community Bus Station (Platform 3), Calvary Hospital, O'Connor, Burton and Garran Hall Daley Road, National Museum of Australia, City Bus Station (Platform 2), Kings Ave / National Circuit, Deakin, Hughes, Garran, Canberra Hospital, Woden Bus Station]
long_name: To Woden Bus Station long_name: To Woden Bus Station
between_stops: between_stops:
Westfield Bus Station (Platform 1)-Belconnen Community Bus Station (Platform 3): [] Westfield Bus Station (Platform 1)-Belconnen Community Bus Station (Platform 3): []
City Bus Station (Platform 2)-Kings Ave / National Circuit: [Wjz5FOn, Wjz4S1U, Wjz4Rs-, Wjz4RFJ, Wjz4RwH, Wjz4Quk] City Bus Station (Platform 2)-Kings Ave / National Circuit: [Wjz5FOn, Wjz4S1U, Wjz4Rs-, Wjz4RFJ, Wjz4RwH, Wjz4Quk]
Cohen Street Bus Station (Platform 2)-Westfield Bus Station (Platform 1): [] Cohen Street Bus Station (Platform 2)-Westfield Bus Station (Platform 1): []
Canberra Hospital-Woden Bus Station: [Wjz3mWn, Wjz3mPO, Wjz3mAg] Canberra Hospital-Woden Bus Station: [Wjz3mWn, Wjz3mPO, Wjz3mAg]
stop_times_saturday: [[729a, 731a, 735a, 752a, 759a, 804a, 809a, 819a, 828a, 837a, 842a, 846a, 848a, 855a], [829a, 831a, 835a, 852a, 859a, 904a, 909a, 919a, 928a, 937a, 942a, 946a, 948a, 955a], [929a, 931a, 935a, 952a, 959a, 1004a, 1009a, 1019a, 1028a, 1037a, 1042a, 1046a, 1048a, 1055a], [1029a, 1031a, 1035a, 1052a, 1059a, 1104a, 1109a, 1119a, 1128a, 1137a, 1142a, 1146a, 1148a, 1155a], [1129a, 1131a, 1135a, 1152a, 1159a, 1204p, 1209p, 1219p, 1228p, 1237p, 1242p, 1246p, 1248p, 1255p], [1229p, 1231p, 1235p, 1252p, 1259p, 104p, 109p, 119p, 128p, 137p, 142p, 146p, 148p, 155p], [129p, 131p, 135p, 152p, 159p, 204p, 209p, 219p, 228p, 237p, 242p, 246p, 248p, 255p], [229p, 231p, 235p, 252p, 259p, 304p, 309p, 319p, 328p, 337p, 342p, 346p, 348p, 355p], [329p, 331p, 335p, 352p, 359p, 404p, 409p, 419p, 428p, 437p, 442p, 446p, 448p, 455p], [429p, 431p, 435p, 452p, 459p, 504p, 509p, 519p, 528p, 537p, 542p, 546p, 548p, 555p], [529p, 531p, 535p, 552p, 559p, 604p, 609p, 619p, 628p, 637p, 642p, 646p, 648p, 655p], [629p, 631p, 635p, 652p, 659p, 704p, 709p, 719p, 728p, 737p, 742p, 746p, 748p, 755p], [729p, 731p, 735p, 752p, 759p, 804p, 809p, 819p, 828p, 837p, 842p, 846p, 848p, 855p], [829p, 831p, 835p, 852p, 859p, 904p, 909p, 919p, 928p, 937p, 942p, 946p, 948p, 955p], [929p, 931p, 935p, 952p, 959p, 1004p, 1009p, 1019p, 1028p, 1037p, 1042p, 1046p, 1048p, 1055p], [1029p, 1031p, 1035p, 1052p, 1059p, 1104p, 1109p, 1117p, "-", "-", "-", "-", "-", "-"]] stop_times_saturday: [[729a, 731a, 735a, 752a, 759a, 804a, 809a, 819a, 828a, 837a, 842a, 846a, 848a, 855a], [829a, 831a, 835a, 852a, 859a, 904a, 909a, 919a, 928a, 937a, 942a, 946a, 948a, 955a], [929a, 931a, 935a, 952a, 959a, 1004a, 1009a, 1019a, 1028a, 1037a, 1042a, 1046a, 1048a, 1055a], [1029a, 1031a, 1035a, 1052a, 1059a, 1104a, 1109a, 1119a, 1128a, 1137a, 1142a, 1146a, 1148a, 1155a], [1129a, 1131a, 1135a, 1152a, 1159a, 1204p, 1209p, 1219p, 1228p, 1237p, 1242p, 1246p, 1248p, 1255p], [1229p, 1231p, 1235p, 1252p, 1259p, 104p, 109p, 119p, 128p, 137p, 142p, 146p, 148p, 155p], [129p, 131p, 135p, 152p, 159p, 204p, 209p, 219p, 228p, 237p, 242p, 246p, 248p, 255p], [229p, 231p, 235p, 252p, 259p, 304p, 309p, 319p, 328p, 337p, 342p, 346p, 348p, 355p], [329p, 331p, 335p, 352p, 359p, 404p, 409p, 419p, 428p, 437p, 442p, 446p, 448p, 455p], [429p, 431p, 435p, 452p, 459p, 504p, 509p, 519p, 528p, 537p, 542p, 546p, 548p, 555p], [529p, 531p, 535p, 552p, 559p, 604p, 609p, 619p, 628p, 637p, 642p, 646p, 648p, 655p], [629p, 631p, 635p, 652p, 659p, 704p, 709p, 719p, 728p, 737p, 742p, 746p, 748p, 755p], [729p, 731p, 735p, 752p, 759p, 804p, 809p, 819p, 828p, 837p, 842p, 846p, 848p, 855p], [829p, 831p, 835p, 852p, 859p, 904p, 909p, 919p, 928p, 937p, 942p, 946p, 948p, 955p], [929p, 931p, 935p, 952p, 959p, 1004p, 1009p, 1019p, 1028p, 1037p, 1042p, 1046p, 1048p, 1055p], [1029p, 1031p, 1035p, 1052p, 1059p, 1104p, 1109p, 1117p, "-", "-", "-", "-", "-", "-"]]
short_name: "934" short_name: "934"
   
--- ---
time_points: [Cohen Street Bus Station (Platform 2), Westfield Bus Station (Platform 1), Belconnen Community Bus Station (Platform 3), Calvary Hospital, O'Connor Shops, Burton and Garran Hall Daley Road, National Museum of Australia, City Bus Station (Platform 2), Kings Ave / National Circuit, Deakin Shops, Hughes Shops, Garran Shops, Canberra Hospital, Woden Bus Station] time_points: [Cohen Street Bus Station (Platform 2), Westfield Bus Station (Platform 1), Belconnen Community Bus Station (Platform 3), Calvary Hospital, O'Connor, Burton and Garran Hall Daley Road, National Museum of Australia, City Bus Station (Platform 2), Kings Ave / National Circuit, Deakin, Hughes, Garran, Canberra Hospital, Woden Bus Station]
long_name: To Woden Bus Station long_name: To Woden Bus Station
between_stops: between_stops:
Westfield Bus Station (Platform 1)-Belconnen Community Bus Station (Platform 3): [] Westfield Bus Station (Platform 1)-Belconnen Community Bus Station (Platform 3): []
City Bus Station (Platform 2)-Kings Ave / National Circuit: [Wjz5FOn, Wjz4S1U, Wjz4Rs-, Wjz4RFJ, Wjz4RwH, Wjz4Quk] City Bus Station (Platform 2)-Kings Ave / National Circuit: [Wjz5FOn, Wjz4S1U, Wjz4Rs-, Wjz4RFJ, Wjz4RwH, Wjz4Quk]
Cohen Street Bus Station (Platform 2)-Westfield Bus Station (Platform 1): [] Cohen Street Bus Station (Platform 2)-Westfield Bus Station (Platform 1): []
Canberra Hospital-Woden Bus Station: [Wjz3mWn, Wjz3mPO, Wjz3mAg] Canberra Hospital-Woden Bus Station: [Wjz3mWn, Wjz3mPO, Wjz3mAg]
short_name: "934" short_name: "934"
stop_times_sunday: [[829a, 831a, 835a, 852a, 859a, 904a, 909a, 919a, 928a, 937a, 942a, 946a, 948a, 955a], [929a, 931a, 935a, 952a, 959a, 1004a, 1009a, 1019a, 1028a, 1037a, 1042a, 1046a, 1048a, 1055a], [1029a, 1031a, 1035a, 1052a, 1059a, 1104a, 1109a, 1119a, 1128a, 1137a, 1142a, 1146a, 1148a, 1155a], [1129a, 1131a, 1135a, 1152a, 1159a, 1204p, 1209p, 1219p, 1228p, 1237p, 1242p, 1246p, 1248p, 1255p], [1229p, 1231p, 1235p, 1252p, 1259p, 104p, 109p, 119p, 128p, 137p, 142p, 146p, 148p, 155p], [129p, 131p, 135p, 152p, 159p, 204p, 209p, 219p, 228p, 237p, 242p, 246p, 248p, 255p], [229p, 231p, 235p, 252p, 259p, 304p, 309p, 319p, 328p, 337p, 342p, 346p, 348p, 355p], [329p, 331p, 335p, 352p, 359p, 404p, 409p, 419p, 428p, 437p, 442p, 446p, 448p, 455p], [429p, 431p, 435p, 452p, 459p, 504p, 509p, 519p, 528p, 537p, 542p, 546p, 548p, 555p], [529p, 531p, 535p, 552p, 559p, 604p, 609p, 619p, 628p, 637p, 642p, 646p, 648p, 655p], [629p, 631p, 635p, 652p, 659p, 704p, 709p, 719p, 728p, 737p, 742p, 746p, 748p, 755p]] stop_times_sunday: [[829a, 831a, 835a, 852a, 859a, 904a, 909a, 919a, 928a, 937a, 942a, 946a, 948a, 955a], [929a, 931a, 935a, 952a, 959a, 1004a, 1009a, 1019a, 1028a, 1037a, 1042a, 1046a, 1048a, 1055a], [1029a, 1031a, 1035a, 1052a, 1059a, 1104a, 1109a, 1119a, 1128a, 1137a, 1142a, 1146a, 1148a, 1155a], [1129a, 1131a, 1135a, 1152a, 1159a, 1204p, 1209p, 1219p, 1228p, 1237p, 1242p, 1246p, 1248p, 1255p], [1229p, 1231p, 1235p, 1252p, 1259p, 104p, 109p, 119p, 128p, 137p, 142p, 146p, 148p, 155p], [129p, 131p, 135p, 152p, 159p, 204p, 209p, 219p, 228p, 237p, 242p, 246p, 248p, 255p], [229p, 231p, 235p, 252p, 259p, 304p, 309p, 319p, 328p, 337p, 342p, 346p, 348p, 355p], [329p, 331p, 335p, 352p, 359p, 404p, 409p, 419p, 428p, 437p, 442p, 446p, 448p, 455p], [429p, 431p, 435p, 452p, 459p, 504p, 509p, 519p, 528p, 537p, 542p, 546p, 548p, 555p], [529p, 531p, 535p, 552p, 559p, 604p, 609p, 619p, 628p, 637p, 642p, 646p, 648p, 655p], [629p, 631p, 635p, 652p, 659p, 704p, 709p, 719p, 728p, 737p, 742p, 746p, 748p, 755p]]
   
---  
time_points: [City Bus Station (Platform 7), Kings Ave / National Circuit, Manuka, Red Hill Shops, Narrabundah Terminus, Red Hill Shops, Manuka, Kings Ave / National Circuit, City Bus Station]  
long_name: To City Bus Station  
between_stops:  
City Bus Station (Platform 7)-Kings Ave / National Circuit: [Wjz5FOn, Wjz4S1U, Wjz4Rs-, Wjz4RFJ, Wjz4RwH, Wjz4Quk]  
Kings Ave / National Circuit-City Bus Station: [Wjz4Quk, Wjz4RwH, Wjz4RFJ, Wjz4Rs-, Wjz4S1U, Wjz5FOn]  
short_name: "935"  
stop_times: [["-", "-", "-", "-", 824a, 833a, 839a, 843a, 852a], [856a, 903a, 907a, 914a, 924a, 933a, 939a, 943a, 952a], [956a, 1003a, 1007a, 1014a, 1024a, 1033a, 1039a, 1043a, 1052a], [1056a, 1103a, 1107a, 1114a, 1124a, 1133a, 1139a, 1143a, 1152a], [1156a, 1203p, 1207p, 1214p, 1224p, 1233p, 1239p, 1243p, 1252p], [1256p, 103p, 107p, 114p, 124p, 133p, 139p, 143p, 152p], [156p, 203p, 207p, 214p, 224p, 233p, 239p, 243p, 252p], [256p, 303p, 307p, 314p, 324p, 333p, 339p, 343p, 352p], [356p, 403p, 407p, 414p, 424p, 433p, 439p, 443p, 452p], [456p, 503p, 507p, 514p, 524p, 533p, 539p, 543p, 552p], [556p, 603p, 607p, 614p, 624p, 633p, 639p, 643p, 652p], [656p, 703p, 707p, 714p, 724p, 733p, 739p, 743p, 752p]]  
 
--- ---
time_points: [City Bus Station (Platform 7), Kings Ave / National Circuit, Manuka, Red Hill Shops, Narrabundah Terminus, Red Hill Shops, Manuka, Kings Ave / National Circuit, City Bus Station] time_points: [City Bus Station (Platform 7), Kings Ave / National Circuit, Manuka, Red Hill, Narrabundah Terminus, Red Hill, Manuka, Kings Ave / National Circuit, City Bus Station]
long_name: To City Bus Station long_name: To City Bus Station
between_stops: between_stops:
City Bus Station (Platform 7)-Kings Ave / National Circuit: [Wjz5FOn, Wjz4S1U, Wjz4Rs-, Wjz4RFJ, Wjz4RwH, Wjz4Quk] City Bus Station (Platform 7)-Kings Ave / National Circuit: [Wjz5FOn, Wjz4S1U, Wjz4Rs-, Wjz4RFJ, Wjz4RwH, Wjz4Quk]
Kings Ave / National Circuit-City Bus Station: [Wjz4Quk, Wjz4RwH, Wjz4RFJ, Wjz4Rs-, Wjz4S1U, Wjz5FOn] Kings Ave / National Circuit-City Bus Station: [Wjz4Quk, Wjz4RwH, Wjz4RFJ, Wjz4Rs-, Wjz4S1U, Wjz5FOn]
short_name: "935" short_name: "935"
stop_times_sunday: [["-", "-", "-", "-", 824a, 833a, 839a, 843a, 852a], [856a, 903a, 907a, 914a, 924a, 933a, 939a, 943a, 952a], [956a, 1003a, 1007a, 1014a, 1024a, 1033a, 1039a, 1043a, 1052a], [1056a, 1103a, 1107a, 1114a, 1124a, 1133a, 1139a, 1143a, 1152a], [1156a, 1203p, 1207p, 1214p, 1224p, 1233p, 1239p, 1243p, 1252p], [1256p, 103p, 107p, 114p, 124p, 133p, 139p, 143p, 152p], [156p, 203p, 207p, 214p, 224p, 233p, 239p, 243p, 252p], [256p, 303p, 307p, 314p, 324p, 333p, 339p, 343p, 352p], [356p, 403p, 407p, 414p, 424p, 433p, 439p, 443p, 452p], [456p, 503p, 507p, 514p, 524p, 533p, 539p, 543p, 552p], [556p, 603p, 607p, 614p, 624p, 633p, 639p, 643p, 652p], [656p, 703p, 707p, 714p, 724p, 733p, 739p, 743p, 752p]] stop_times_sunday: [["-", "-", "-", "-", 824a, 833a, 839a, 843a, 852a], [856a, 903a, 907a, 914a, 924a, 933a, 939a, 943a, 952a], [956a, 1003a, 1007a, 1014a, 1024a, 1033a, 1039a, 1043a, 1052a], [1056a, 1103a, 1107a, 1114a, 1124a, 1133a, 1139a, 1143a, 1152a], [1156a, 1203p, 1207p, 1214p, 1224p, 1233p, 1239p, 1243p, 1252p], [1256p, 103p, 107p, 114p, 124p, 133p, 139p, 143p, 152p], [156p, 203p, 207p, 214p, 224p, 233p, 239p, 243p, 252p], [256p, 303p, 307p, 314p, 324p, 333p, 339p, 343p, 352p], [356p, 403p, 407p, 414p, 424p, 433p, 439p, 443p, 452p], [456p, 503p, 507p, 514p, 524p, 533p, 539p, 543p, 552p], [556p, 603p, 607p, 614p, 624p, 633p, 639p, 643p, 652p], [656p, 703p, 707p, 714p, 724p, 733p, 739p, 743p, 752p]]
   
---  
time_points: [City Bus Station (Platform 4), Macarthur / Miller O'Connor, Lyneham Shops Wattle Street, North Lyneham, Dickson Shops, Hackett Shops, Ainslie Shops, City Bus Station]  
long_name: To City Bus Station  
between_stops: {}  
 
short_name: "936"  
stop_times: [[818a, 827a, 830a, 835a, 844a, 849a, 857a, 909a], [918a, 927a, 930a, 935a, 944a, 949a, 957a, 1009a], [1018a, 1027a, 1030a, 1035a, 1044a, 1049a, 1057a, 1109a], [1118a, 1127a, 1130a, 1135a, 1144a, 1149a, 1157a, 1209p], [1218p, 1227p, 1230p, 1235p, 1244p, 1249p, 1257p, 109p], [118p, 127p, 130p, 135p, 144p, 149p, 157p, 209p], [218p, 227p, 230p, 235p, 244p, 249p, 257p, 309p], [318p, 327p, 330p, 335p, 344p, 349p, 357p, 409p], [418p, 427p, 430p, 435p, 444p, 449p, 457p, 509p], [518p, 527p, 530p, 535p, 544p, 549p, 557p, 609p], [618p, 627p, 630p, 635p, 644p, 649p, 657p, 709p], [718p, 727p, 730p, 735p, 744p, 749p, 757p, 809p]]  
 
--- ---
time_points: [City Bus Station (Platform 4), Macarthur / Miller O'Connor, Lyneham Shops Wattle Street, North Lyneham, Dickson Shops, Hackett Shops, Ainslie Shops, City Bus Station] time_points: [City Bus Station (Platform 4), Macarthur / Miller O'Connor, Lyneham, North Lyneham, Dickson / Antill St, Hackett, Ainslie, City Bus Station]
long_name: To City Bus Station long_name: To City Bus Station
between_stops: {} between_stops:
  Ainslie-City Bus Station: [Wjz5YAK, Wjz5Yq4, Wjz5XnQ, Wjz5XrS, Wjz5XwW, Wjz5W3H, Wjz5W8l, Wjz5V64, Wjz5NRJ, Wjz5NHD]
stop_times_saturday: [[718a, 727a, 730a, 735a, 744a, 749a, 757a, 809a], [818a, 827a, 830a, 835a, 844a, 849a, 857a, 909a], [918a, 927a, 930a, 935a, 944a, 949a, 957a, 1009a], [1018a, 1027a, 1030a, 1035a, 1044a, 1049a, 1057a, 1109a], [1118a, 1127a, 1130a, 1135a, 1144a, 1149a, 1157a, 1209p], [1218p, 1227p, 1230p, 1235p, 1244p, 1249p, 1257p, 109p], [118p, 127p, 130p, 135p, 144p, 149p, 157p, 209p], [218p, 227p, 230p, 235p, 244p, 249p, 257p, 309p], [318p, 327p, 330p, 335p, 344p, 349p, 357p, 409p], [418p, 427p, 430p, 435p, 444p, 449p, 457p, 509p], [518p, 527p, 530p, 535p, 544p, 549p, 557p, 609p], [618p, 627p, 630p, 635p, 644p, 649p, 657p, 709p], [718p, 727p, 730p, 735p, 744p, 749p, 757p, 809p], [818p, 827p, 830p, 835p, 844p, 849p, 857p, 909p], [918p, 927p, 930p, 935p, 944p, 949p, 957p, 1009p], [1018p, 1027p, 1030p, 1035p, 1044p, 1049p, 1057p, 1109p], [1118p, 1127p, 1130p, 1135p, 1144p, "-", "-", "-"]] stop_times_saturday: [[718a, 727a, 730a, 735a, 744a, 749a, 757a, 809a], [818a, 827a, 830a, 835a, 844a, 849a, 857a, 909a], [918a, 927a, 930a, 935a, 944a, 949a, 957a, 1009a], [1018a, 1027a, 1030a, 1035a, 1044a, 1049a, 1057a, 1109a], [1118a, 1127a, 1130a, 1135a, 1144a, 1149a, 1157a, 1209p], [1218p, 1227p, 1230p, 1235p, 1244p, 1249p, 1257p, 109p], [118p, 127p, 130p, 135p, 144p, 149p, 157p, 209p], [218p, 227p, 230p, 235p, 244p, 249p, 257p, 309p], [318p, 327p, 330p, 335p, 344p, 349p, 357p, 409p], [418p, 427p, 430p, 435p, 444p, 449p, 457p, 509p], [518p, 527p, 530p, 535p, 544p, 549p, 557p, 609p], [618p, 627p, 630p, 635p, 644p, 649p, 657p, 709p], [718p, 727p, 730p, 735p, 744p, 749p, 757p, 809p], [818p, 827p, 830p, 835p, 844p, 849p, 857p, 909p], [918p, 927p, 930p, 935p, 944p, 949p, 957p, 1009p], [1018p, 1027p, 1030p, 1035p, 1044p, 1049p, 1057p, 1109p], [1118p, 1127p, 1130p, 1135p, 1144p, "-", "-", "-"]]
short_name: "936" short_name: "936"
   
--- ---
time_points: [City Bus Station (Platform 4), Macarthur / Miller O'Connor, Lyneham Shops Wattle Street, North Lyneham, Dickson Shops, Hackett Shops, Ainslie Shops, City Bus Station] time_points: [City Bus Station (Platform 4), Macarthur / Miller O'Connor, Lyneham, North Lyneham, Dickson / Antill St, Hackett, Ainslie, City Bus Station]
long_name: To City Bus Station long_name: To City Bus Station
between_stops: {} between_stops:
  Ainslie-City Bus Station: [Wjz5YAK, Wjz5Yq4, Wjz5XnQ, Wjz5XrS, Wjz5XwW, Wjz5W3H, Wjz5W8l, Wjz5V64, Wjz5NRJ, Wjz5NHD]
short_name: "936" short_name: "936"
stop_times_sunday: [[818a, 827a, 830a, 835a, 844a, 849a, 857a, 909a], [918a, 927a, 930a, 935a, 944a, 949a, 957a, 1009a], [1018a, 1027a, 1030a, 1035a, 1044a, 1049a, 1057a, 1109a], [1118a, 1127a, 1130a, 1135a, 1144a, 1149a, 1157a, 1209p], [1218p, 1227p, 1230p, 1235p, 1244p, 1249p, 1257p, 109p], [118p, 127p, 130p, 135p, 144p, 149p, 157p, 209p], [218p, 227p, 230p, 235p, 244p, 249p, 257p, 309p], [318p, 327p, 330p, 335p, 344p, 349p, 357p, 409p], [418p, 427p, 430p, 435p, 444p, 449p, 457p, 509p], [518p, 527p, 530p, 535p, 544p, 549p, 557p, 609p], [618p, 627p, 630p, 635p, 644p, 649p, 657p, 709p], [718p, 727p, 730p, 735p, 744p, 749p, 757p, 809p]] stop_times_sunday: [[818a, 827a, 830a, 835a, 844a, 849a, 857a, 909a], [918a, 927a, 930a, 935a, 944a, 949a, 957a, 1009a], [1018a, 1027a, 1030a, 1035a, 1044a, 1049a, 1057a, 1109a], [1118a, 1127a, 1130a, 1135a, 1144a, 1149a, 1157a, 1209p], [1218p, 1227p, 1230p, 1235p, 1244p, 1249p, 1257p, 109p], [118p, 127p, 130p, 135p, 144p, 149p, 157p, 209p], [218p, 227p, 230p, 235p, 244p, 249p, 257p, 309p], [318p, 327p, 330p, 335p, 344p, 349p, 357p, 409p], [418p, 427p, 430p, 435p, 444p, 449p, 457p, 509p], [518p, 527p, 530p, 535p, 544p, 549p, 557p, 609p], [618p, 627p, 630p, 635p, 644p, 649p, 657p, 709p], [718p, 727p, 730p, 735p, 744p, 749p, 757p, 809p]]
   
---  
time_points: [City Bus Station (Platform 8), Ainslie Shops, Hackett Shops, Dickson Shops, North Lyneham, Lyneham Shops Wattle Street, Macarthur / Miller O'Connor, City Bus Station]  
long_name: To City Bus Station  
between_stops: {}  
 
short_name: "937"  
stop_times: [[859a, 911a, 919a, 925a, 934a, 939a, 942a, 951a], [959a, 1011a, 1019a, 1025a, 1034a, 1039a, 1042a, 1051a], [1059a, 1111a, 1119a, 1125a, 1134a, 1139a, 1142a, 1151a], [1159a, 1211p, 1219p, 1225p, 1234p, 1239p, 1242p, 1251p], [1259p, 111p, 119p, 125p, 134p, 139p, 142p, 151p], [159p, 211p, 219p, 225p, 234p, 239p, 242p, 251p], [259p, 311p, 319p, 325p, 334p, 339p, 342p, 351p], [359p, 411p, 419p, 425p, 434p, 439p, 442p, 451p], [459p, 511p, 519p, 525p, 534p, 539p, 542p, 551p], [559p, 611p, 619p, 625p, 634p, 639p, 642p, 651p], [659p, 711p, 719p, 725p, 734p, 739p, 742p, 751p]]  
 
--- ---
time_points: [City Bus Station (Platform 8), Ainslie Shops, Hackett Shops, Dickson Shops, North Lyneham, Lyneham Shops Wattle Street, Macarthur / Miller O'Connor, City Bus Station] time_points: [City Bus Station (Platform 8), Ainslie, Hackett, Dickson / Antill St, North Lyneham, Lyneham, Macarthur / Miller O'Connor, City Bus Station]
long_name: To City Bus Station long_name: To City Bus Station
between_stops: {} between_stops:
  Ainslie-Hackett: [Wjz5YKO, Wjz5ZO1, Wjz5ZZQ, Wjzd68O, Wjzd6iW, Wjzd6lW, Wjzd6Cq, Wjzd6Pn, Wjzd6XP, WjzdeeQ]
stop_times_saturday: [[759a, 811a, 819a, 825a, 834a, 839a, 842a, 851a], [859a, 911a, 919a, 925a, 934a, 939a, 942a, 951a], [959a, 1011a, 1019a, 1025a, 1034a, 1039a, 1042a, 1051a], [1059a, 1111a, 1119a, 1125a, 1134a, 1139a, 1142a, 1151a], [1159a, 1211p, 1219p, 1225p, 1234p, 1239p, 1242p, 1251p], [1259p, 111p, 119p, 125p, 134p, 139p, 142p, 151p], [159p, 211p, 219p, 225p, 234p, 239p, 242p, 251p], [259p, 311p, 319p, 325p, 334p, 339p, 342p, 351p], [359p, 411p, 419p, 425p, 434p, 439p, 442p, 451p], [459p, 511p, 519p, 525p, 534p, 539p, 542p, 551p], [559p, 611p, 619p, 625p, 634p, 639p, 642p, 651p], [659p, 711p, 719p, 725p, 734p, 739p, 742p, 751p], [749p, 801p, 809p, 815p, 824p, 829p, 832p, 841p], [849p, 901p, 909p, 915p, 924p, 929p, 932p, 941p], [949p, 1001p, 1009p, 1015p, 1024p, 1029p, 1032p, 1041p], [1049p, 1101p, 1109p, 1115p, 1124p, 1129p, 1132p, 1141p]] stop_times_saturday: [[759a, 811a, 819a, 825a, 834a, 839a, 842a, 851a], [859a, 911a, 919a, 925a, 934a, 939a, 942a, 951a], [959a, 1011a, 1019a, 1025a, 1034a, 1039a, 1042a, 1051a], [1059a, 1111a, 1119a, 1125a, 1134a, 1139a, 1142a, 1151a], [1159a, 1211p, 1219p, 1225p, 1234p, 1239p, 1242p, 1251p], [1259p, 111p, 119p, 125p, 134p, 139p, 142p, 151p], [159p, 211p, 219p, 225p, 234p, 239p, 242p, 251p], [259p, 311p, 319p, 325p, 334p, 339p, 342p, 351p], [359p, 411p, 419p, 425p, 434p, 439p, 442p, 451p], [459p, 511p, 519p, 525p, 534p, 539p, 542p, 551p], [559p, 611p, 619p, 625p, 634p, 639p, 642p, 651p], [659p, 711p, 719p, 725p, 734p, 739p, 742p, 751p], [749p, 801p, 809p, 815p, 824p, 829p, 832p, 841p], [849p, 901p, 909p, 915p, 924p, 929p, 932p, 941p], [949p, 1001p, 1009p, 1015p, 1024p, 1029p, 1032p, 1041p], [1049p, 1101p, 1109p, 1115p, 1124p, 1129p, 1132p, 1141p]]
short_name: "937" short_name: "937"
   
--- ---
time_points: [City Bus Station (Platform 8), Ainslie Shops, Hackett Shops, Dickson Shops, North Lyneham, Lyneham Shops Wattle Street, Macarthur / Miller O'Connor, City Bus Station] time_points: [City Bus Station (Platform 8), Ainslie, Hackett, Dickson / Antill St, North Lyneham, Lyneham, Macarthur / Miller O'Connor, City Bus Station]
long_name: To City Bus Station long_name: To City Bus Station
between_stops: {} between_stops:
  Ainslie-Hackett: [Wjz5YKO, Wjz5ZO1, Wjz5ZZQ, Wjzd68O, Wjzd6iW, Wjzd6lW, Wjzd6Cq, Wjzd6Pn, Wjzd6XP, WjzdeeQ]
short_name: "937" short_name: "937"
stop_times_sunday: [[859a, 911a, 919a, 925a, 934a, 939a, 942a, 951a], [959a, 1011a, 1019a, 1025a, 1034a, 1039a, 1042a, 1051a], [1059a, 1111a, 1119a, 1125a, 1134a, 1139a, 1142a, 1151a], [1159a, 1211p, 1219p, 1225p, 1234p, 1239p, 1242p, 1251p], [1259p, 111p, 119p, 125p, 134p, 139p, 142p, 151p], [159p, 211p, 219p, 225p, 234p, 239p, 242p, 251p], [259p, 311p, 319p, 325p, 334p, 339p, 342p, 351p], [359p, 411p, 419p, 425p, 434p, 439p, 442p, 451p], [459p, 511p, 519p, 525p, 534p, 539p, 542p, 551p], [559p, 611p, 619p, 625p, 634p, 639p, 642p, 651p], [659p, 711p, 719p, 725p, 734p, 739p, 742p, 751p]] stop_times_sunday: [[859a, 911a, 919a, 925a, 934a, 939a, 942a, 951a], [959a, 1011a, 1019a, 1025a, 1034a, 1039a, 1042a, 1051a], [1059a, 1111a, 1119a, 1125a, 1134a, 1139a, 1142a, 1151a], [1159a, 1211p, 1219p, 1225p, 1234p, 1239p, 1242p, 1251p], [1259p, 111p, 119p, 125p, 134p, 139p, 142p, 151p], [159p, 211p, 219p, 225p, 234p, 239p, 242p, 251p], [259p, 311p, 319p, 325p, 334p, 339p, 342p, 351p], [359p, 411p, 419p, 425p, 434p, 439p, 442p, 451p], [459p, 511p, 519p, 525p, 534p, 539p, 542p, 551p], [559p, 611p, 619p, 625p, 634p, 639p, 642p, 651p], [659p, 711p, 719p, 725p, 734p, 739p, 742p, 751p]]
   
---  
time_points: [Woden Bus Station (Platform 14), Pearce Shops, Narrabundah College, Kingston, Kings Ave / National Circuit, Russell Offices, City Bus Station]  
long_name: To City Bus Station  
between_stops: {}  
 
short_name: "938"  
stop_times: [[800a, 808a, 818a, 833a, 837a, 841a, 849a], [900a, 908a, 918a, 933a, 937a, 941a, 949a], [1000a, 1008a, 1018a, 1033a, 1037a, 1041a, 1049a], [1100a, 1108a, 1118a, 1133a, 1137a, 1141a, 1149a], [1200p, 1208p, 1218p, 1233p, 1237p, 1241p, 1249p], [100p, 108p, 118p, 133p, 137p, 141p, 149p], [200p, 208p, 218p, 233p, 237p, 241p, 249p], [300p, 308p, 318p, 333p, 337p, 341p, 349p], [400p, 408p, 418p, 433p, 437p, 441p, 449p], [500p, 508p, 518p, 533p, 537p, 541p, 549p], [600p, 608p, 618p, 633p, 637p, 641p, 649p], [700p, 707p, 716p, 729p, 733p, 737p, 744p]]  
 
--- ---
time_points: [Woden Bus Station (Platform 14), Pearce Shops, Narrabundah College, Kingston, Kings Ave / National Circuit, Russell Offices, City Bus Station] time_points: [Woden Bus Station (Platform 14), Pearce, Narrabundah College, Kingston, Kings Ave / National Circuit, Russell Offices, City Bus Station]
long_name: To City Bus Station long_name: To City Bus Station
between_stops: {} between_stops: {}
   
short_name: "938" short_name: "938"
stop_times_sunday: [[800a, 808a, 818a, 833a, 837a, 841a, 849a], [900a, 908a, 918a, 933a, 937a, 941a, 949a], [1000a, 1008a, 1018a, 1033a, 1037a, 1041a, 1049a], [1100a, 1108a, 1118a, 1133a, 1137a, 1141a, 1149a], [1200p, 1208p, 1218p, 1233p, 1237p, 1241p, 1249p], [100p, 108p, 118p, 133p, 137p, 141p, 149p], [200p, 208p, 218p, 233p, 237p, 241p, 249p], [300p, 308p, 318p, 333p, 337p, 341p, 349p], [400p, 408p, 418p, 433p, 437p, 441p, 449p], [500p, 508p, 518p, 533p, 537p, 541p, 549p], [600p, 608p, 618p, 633p, 637p, 641p, 649p], [700p, 707p, 716p, 729p, 733p, 737p, 744p]] stop_times_sunday: [[800a, 808a, 818a, 833a, 837a, 841a, 849a], [900a, 908a, 918a, 933a, 937a, 941a, 949a], [1000a, 1008a, 1018a, 1033a, 1037a, 1041a, 1049a], [1100a, 1108a, 1118a, 1133a, 1137a, 1141a, 1149a], [1200p, 1208p, 1218p, 1233p, 1237p, 1241p, 1249p], [100p, 108p, 118p, 133p, 137p, 141p, 149p], [200p, 208p, 218p, 233p, 237p, 241p, 249p], [300p, 308p, 318p, 333p, 337p, 341p, 349p], [400p, 408p, 418p, 433p, 437p, 441p, 449p], [500p, 508p, 518p, 533p, 537p, 541p, 549p], [600p, 608p, 618p, 633p, 637p, 641p, 649p], [700p, 707p, 716p, 729p, 733p, 737p, 744p]]
   
---  
time_points: [City Bus Station (Platform 9), Russell Offices, Kings Ave / National Circuit, Kingston, Narrabundah College, Pearce Shops, Woden Bus Station]  
long_name: To Woden Bus Station  
between_stops: {}  
 
short_name: "938"  
stop_times: [[846a, 854a, 858a, 902a, 917a, 927a, 934a], [946a, 954a, 958a, 1002a, 1017a, 1027a, 1034a], [1046a, 1054a, 1058a, 1102a, 1117a, 1127a, 1134a], [1146a, 1154a, 1158a, 1202p, 1217p, 1227p, 1234p], [1246p, 1254p, 1258p, 102p, 117p, 127p, 134p], [146p, 154p, 158p, 202p, 217p, 227p, 234p], [246p, 254p, 258p, 302p, 317p, 327p, 334p], [346p, 354p, 358p, 402p, 417p, 427p, 434p], [446p, 454p, 458p, 502p, 517p, 527p, 534p], [546p, 554p, 558p, 602p, 617p, 627p, 634p], [646p, 654p, 658p, 702p, 715p, 724p, 731p]]  
 
--- ---
time_points: [City Bus Station (Platform 9), Russell Offices, Kings Ave / National Circuit, Kingston, Narrabundah College, Pearce Shops, Woden Bus Station] time_points: [City Bus Station (Platform 9), Russell Offices, Kings Ave / National Circuit, Kingston, Narrabundah College, Pearce, Woden Bus Station]
long_name: To Woden Bus Station long_name: To Woden Bus Station
between_stops: {} between_stops: {}
   
short_name: "938" short_name: "938"
stop_times_sunday: [[846a, 854a, 858a, 902a, 917a, 927a, 934a], [946a, 954a, 958a, 1002a, 1017a, 1027a, 1034a], [1046a, 1054a, 1058a, 1102a, 1117a, 1127a, 1134a], [1146a, 1154a, 1158a, 1202p, 1217p, 1227p, 1234p], [1246p, 1254p, 1258p, 102p, 117p, 127p, 134p], [146p, 154p, 158p, 202p, 217p, 227p, 234p], [246p, 254p, 258p, 302p, 317p, 327p, 334p], [346p, 354p, 358p, 402p, 417p, 427p, 434p], [446p, 454p, 458p, 502p, 517p, 527p, 534p], [546p, 554p, 558p, 602p, 617p, 627p, 634p], [646p, 654p, 658p, 702p, 715p, 724p, 731p]] stop_times_sunday: [[846a, 854a, 858a, 902a, 917a, 927a, 934a], [946a, 954a, 958a, 1002a, 1017a, 1027a, 1034a], [1046a, 1054a, 1058a, 1102a, 1117a, 1127a, 1134a], [1146a, 1154a, 1158a, 1202p, 1217p, 1227p, 1234p], [1246p, 1254p, 1258p, 102p, 117p, 127p, 134p], [146p, 154p, 158p, 202p, 217p, 227p, 234p], [246p, 254p, 258p, 302p, 317p, 327p, 334p], [346p, 354p, 358p, 402p, 417p, 427p, 434p], [446p, 454p, 458p, 502p, 517p, 527p, 534p], [546p, 554p, 558p, 602p, 617p, 627p, 634p], [646p, 654p, 658p, 702p, 715p, 724p, 731p]]
   
---  
time_points: [City Bus Station (Platform 8), Dickson Shops, Watson Shops, Watson Terminus, Watson Shops, Dickson Shops, City Bus Station]  
long_name: To City Bus Station  
between_stops: {}  
 
short_name: "939"  
stop_times: [[846a, 903a, 908a, 915a, 920a, 926a, 941a], [946a, 1003a, 1008a, 1015a, 1020a, 1026a, 1041a], [1046a, 1103a, 1108a, 1115a, 1120a, 1126a, 1141a], [1146a, 1203p, 1208p, 1215p, 1220p, 1226p, 1241p], [1246p, 103p, 108p, 115p, 120p, 126p, 141p], [146p, 203p, 208p, 215p, 220p, 226p, 241p], [246p, 303p, 308p, 315p, 320p, 326p, 341p], [346p, 403p, 408p, 415p, 420p, 426p, 441p], [446p, 503p, 508p, 515p, 520p, 526p, 541p], [546p, 603p, 608p, 615p, 620p, 626p, 641p], [646p, 703p, 708p, 715p, 720p, 726p, 741p]]  
 
--- ---
time_points: [City Bus Station (Platform 8), Dickson Shops, Watson Shops, Watson Terminus, Watson Shops, Dickson Shops, City Bus Station] time_points: [City Bus Station (Platform 8), Dickson / Antill St, Watson, Watson Terminus, Watson, Dickson / Antill St, City Bus Station]
long_name: To City Bus Station long_name: To City Bus Station
between_stops: {} between_stops: {}
   
stop_times_saturday: [["-", "-", "-", 708a, 713a, 719a, 734a], ["-", "-", "-", 808a, 813a, 819a, 834a], [846a, 903a, 908a, 915a, 920a, 926a, 941a], [946a, 1003a, 1008a, 1015a, 1020a, 1026a, 1041a], [1046a, 1103a, 1108a, 1115a, 1120a, 1126a, 1141a], [1146a, 1203p, 1208p, 1215p, 1220p, 1226p, 1241p], [1246p, 103p, 108p, 115p, 120p, 126p, 141p], [146p, 203p, 208p, 215p, 220p, 226p, 241p], [246p, 303p, 308p, 315p, 320p, 326p, 341p], [346p, 403p, 408p, 415p, 420p, 426p, 441p], [446p, 503p, 508p, 515p, 520p, 526p, 541p], [546p, 603p, 608p, 615p, 620p, 626p, 641p], [646p, 703p, 708p, 715p, 720p, 726p, 741p], [746p, 803p, 808p, 815p, 820p, 826p, 841p], [846p, 903p, 908p, 915p, 920p, 926p, 941p], [946p, 1003p, 1008p, 1015p, 1020p, 1026p, 1041p], [1046p, 1103p, 1108p, 1115p, 1120p, 1126p, 1141p]] stop_times_saturday: [["-", "-", "-", 708a, 713a, 719a, 734a], ["-", "-", "-", 808a, 813a, 819a, 834a], [846a, 903a, 908a, 915a, 920a, 926a, 941a], [946a, 1003a, 1008a, 1015a, 1020a, 1026a, 1041a], [1046a, 1103a, 1108a, 1115a, 1120a, 1126a, 1141a], [1146a, 1203p, 1208p, 1215p, 1220p, 1226p, 1241p], [1246p, 103p, 108p, 115p, 120p, 126p, 141p], [146p, 203p, 208p, 215p, 220p, 226p, 241p], [246p, 303p, 308p, 315p, 320p, 326p, 341p], [346p, 403p, 408p, 415p, 420p, 426p, 441p], [446p, 503p, 508p, 515p, 520p, 526p, 541p], [546p, 603p, 608p, 615p, 620p, 626p, 641p], [646p, 703p, 708p, 715p, 720p, 726p, 741p], [746p, 803p, 808p, 815p, 820p, 826p, 841p], [846p, 903p, 908p, 915p, 920p, 926p, 941p], [946p, 1003p, 1008p, 1015p, 1020p, 1026p, 1041p], [1046p, 1103p, 1108p, 1115p, 1120p, 1126p, 1141p]]
short_name: "939" short_name: "939"
   
--- ---
time_points: [City Bus Station (Platform 8), Dickson Shops, Watson Shops, Watson Terminus, Watson Shops, Dickson Shops, City Bus Station] time_points: [City Bus Station (Platform 8), Dickson / Antill St, Watson, Watson Terminus, Watson, Dickson / Antill St, City Bus Station]
long_name: To City Bus Station long_name: To City Bus Station
between_stops: {} between_stops: {}
   
short_name: "939" short_name: "939"
stop_times_sunday: [[846a, 903a, 908a, 915a, 920a, 926a, 941a], [946a, 1003a, 1008a, 1015a, 1020a, 1026a, 1041a], [1046a, 1103a, 1108a, 1115a, 1120a, 1126a, 1141a], [1146a, 1203p, 1208p, 1215p, 1220p, 1226p, 1241p], [1246p, 103p, 108p, 115p, 120p, 126p, 141p], [146p, 203p, 208p, 215p, 220p, 226p, 241p], [246p, 303p, 308p, 315p, 320p, 326p, 341p], [346p, 403p, 408p, 415p, 420p, 426p, 441p], [446p, 503p, 508p, 515p, 520p, 526p, 541p], [546p, 603p, 608p, 615p, 620p, 626p, 641p], [646p, 703p, 708p, 715p, 720p, 726p, 741p]] stop_times_sunday: [[846a, 903a, 908a, 915a, 920a, 926a, 941a], [946a, 1003a, 1008a, 1015a, 1020a, 1026a, 1041a], [1046a, 1103a, 1108a, 1115a, 1120a, 1126a, 1141a], [1146a, 1203p, 1208p, 1215p, 1220p, 1226p, 1241p], [1246p, 103p, 108p, 115p, 120p, 126p, 141p], [146p, 203p, 208p, 215p, 220p, 226p, 241p], [246p, 303p, 308p, 315p, 320p, 326p, 341p], [346p, 403p, 408p, 415p, 420p, 426p, 441p], [446p, 503p, 508p, 515p, 520p, 526p, 541p], [546p, 603p, 608p, 615p, 620p, 626p, 641p], [646p, 703p, 708p, 715p, 720p, 726p, 741p]]
   
---  
time_points: [Cohen Street Bus Station (Platform 2), Westfield Bus Station (Platform 1), Belconnen Community Bus Station (Platform 3), Jamison Centre, Cook Shops, Aranda, Caswell Drive, City Bus Station]  
long_name: To City Bus Station  
between_stops:  
Westfield Bus Station (Platform 1)-Belconnen Community Bus Station (Platform 3): []  
Cohen Street Bus Station (Platform 2)-Westfield Bus Station (Platform 1): []  
short_name: "942"  
stop_times: [[815a, 817a, 821a, 830a, 839a, 843a, 844a, 855a], [915a, 917a, 921a, 930a, 939a, 943a, 944a, 955a], [1015a, 1017a, 1021a, 1030a, 1039a, 1043a, 1044a, 1055a], [1115a, 1117a, 1121a, 1130a, 1139a, 1143a, 1144a, 1155a], [1215p, 1217p, 1221p, 1230p, 1239p, 1243p, 1244p, 1255p], [115p, 117p, 121p, 130p, 139p, 143p, 144p, 155p], [215p, 217p, 221p, 230p, 239p, 243p, 244p, 255p], [315p, 317p, 321p, 330p, 339p, 343p, 344p, 355p], [415p, 417p, 421p, 430p, 439p, 443p, 444p, 455p], [515p, 517p, 521p, 530p, 539p, 543p, 544p, 555p], [615p, 617p, 621p, 630p, 639p, 643p, 644p, 655p]]  
 
--- ---
time_points: [Cohen Street Bus Station (Platform 2), Westfield Bus Station (Platform 1), Belconnen Community Bus Station (Platform 3), Jamison Centre, Cook Shops, Aranda, Caswell Drive, City Bus Station] time_points: [Cohen Street Bus Station (Platform 2), Westfield Bus Station (Platform 1), Belconnen Community Bus Station (Platform 3), Jamison Centre, Cook, Aranda, Caswell Drive, City Bus Station]
long_name: To City Bus Station long_name: To City Bus Station
between_stops: between_stops:
Westfield Bus Station (Platform 1)-Belconnen Community Bus Station (Platform 3): [] Westfield Bus Station (Platform 1)-Belconnen Community Bus Station (Platform 3): []
Cohen Street Bus Station (Platform 2)-Westfield Bus Station (Platform 1): [] Cohen Street Bus Station (Platform 2)-Westfield Bus Station (Platform 1): []
stop_times_saturday: [[815a, 817a, 821a, 830a, 839a, 843a, 844a, 855a], [915a, 917a, 921a, 930a, 939a, 943a, 944a, 955a], [1015a, 1017a, 1021a, 1030a, 1039a, 1043a, 1044a, 1055a], [1115a, 1117a, 1121a, 1130a, 1139a, 1143a, 1144a, 1155a], [1215p, 1217p, 1221p, 1230p, 1239p, 1243p, 1244p, 1255p], [115p, 117p, 121p, 130p, 139p, 143p, 144p, 155p], [215p, 217p, 221p, 230p, 239p, 243p, 244p, 255p], [315p, 317p, 321p, 330p, 339p, 343p, 344p, 355p], [415p, 417p, 421p, 430p, 439p, 443p, 444p, 455p], [515p, 517p, 521p, 530p, 539p, 543p, 544p, 555p], [615p, 617p, 621p, 630p, 639p, 643p, 644p, 655p], [715p, 717p, 721p, 730p, 739p, 743p, 744p, 755p], [815p, 817p, 821p, 830p, 839p, 843p, 844p, 855p], [915p, 917p, 921p, 930p, 939p, 943p, 944p, 955p], [1015p, 1017p, 1021p, 1030p, 1039p, 1043p, 1044p, 1055p]] stop_times_saturday: [[815a, 817a, 821a, 830a, 839a, 843a, 844a, 855a], [915a, 917a, 921a, 930a, 939a, 943a, 944a, 955a], [1015a, 1017a, 1021a, 1030a, 1039a, 1043a, 1044a, 1055a], [1115a, 1117a, 1121a, 1130a, 1139a, 1143a, 1144a, 1155a], [1215p, 1217p, 1221p, 1230p, 1239p, 1243p, 1244p, 1255p], [115p, 117p, 121p, 130p, 139p, 143p, 144p, 155p], [215p, 217p, 221p, 230p, 239p, 243p, 244p, 255p], [315p, 317p, 321p, 330p, 339p, 343p, 344p, 355p], [415p, 417p, 421p, 430p, 439p, 443p, 444p, 455p], [515p, 517p, 521p, 530p, 539p, 543p, 544p, 555p], [615p, 617p, 621p, 630p, 639p, 643p, 644p, 655p], [715p, 717p, 721p, 730p, 739p, 743p, 744p, 755p], [815p, 817p, 821p, 830p, 839p, 843p, 844p, 855p], [915p, 917p, 921p, 930p, 939p, 943p, 944p, 955p], [1015p, 1017p, 1021p, 1030p, 1039p, 1043p, 1044p, 1055p]]
short_name: "942" short_name: "942"
   
--- ---
time_points: [Cohen Street Bus Station (Platform 2), Westfield Bus Station (Platform 1), Belconnen Community Bus Station (Platform 3), Jamison Centre, Cook Shops, Aranda, Caswell Drive, City Bus Station] time_points: [Cohen Street Bus Station (Platform 2), Westfield Bus Station (Platform 1), Belconnen Community Bus Station (Platform 3), Jamison Centre, Cook, Aranda, Caswell Drive, City Bus Station]
long_name: To City Bus Station long_name: To City Bus Station
between_stops: between_stops:
Westfield Bus Station (Platform 1)-Belconnen Community Bus Station (Platform 3): [] Westfield Bus Station (Platform 1)-Belconnen Community Bus Station (Platform 3): []
Cohen Street Bus Station (Platform 2)-Westfield Bus Station (Platform 1): [] Cohen Street Bus Station (Platform 2)-Westfield Bus Station (Platform 1): []
short_name: "942" short_name: "942"
stop_times_sunday: [[815a, 817a, 821a, 830a, 839a, 843a, 844a, 855a], [915a, 917a, 921a, 930a, 939a, 943a, 944a, 955a], [1015a, 1017a, 1021a, 1030a, 1039a, 1043a, 1044a, 1055a], [1115a, 1117a, 1121a, 1130a, 1139a, 1143a, 1144a, 1155a], [1215p, 1217p, 1221p, 1230p, 1239p, 1243p, 1244p, 1255p], [115p, 117p, 121p, 130p, 139p, 143p, 144p, 155p], [215p, 217p, 221p, 230p, 239p, 243p, 244p, 255p], [315p, 317p, 321p, 330p, 339p, 343p, 344p, 355p], [415p, 417p, 421p, 430p, 439p, 443p, 444p, 455p], [515p, 517p, 521p, 530p, 539p, 543p, 544p, 555p], [615p, 617p, 621p, 630p, 639p, 643p, 644p, 655p]] stop_times_sunday: [[815a, 817a, 821a, 830a, 839a, 843a, 844a, 855a], [915a, 917a, 921a, 930a, 939a, 943a, 944a, 955a], [1015a, 1017a, 1021a, 1030a, 1039a, 1043a, 1044a, 1055a], [1115a, 1117a, 1121a, 1130a, 1139a, 1143a, 1144a, 1155a], [1215p, 1217p, 1221p, 1230p, 1239p, 1243p, 1244p, 1255p], [115p, 117p, 121p, 130p, 139p, 143p, 144p, 155p], [215p, 217p, 221p, 230p, 239p, 243p, 244p, 255p], [315p, 317p, 321p, 330p, 339p, 343p, 344p, 355p], [415p, 417p, 421p, 430p, 439p, 443p, 444p, 455p], [515p, 517p, 521p, 530p, 539p, 543p, 544p, 555p], [615p, 617p, 621p, 630p, 639p, 643p, 644p, 655p]]
   
---  
time_points: [City Bus Station (Platform 4), Caswell Drive, Aranda, Cook Shops, Jamison Centre, Belconnen Community Bus Station, Westfield Bus Station, Cohen Street Bus Station]  
long_name: To Cohen Street Bus Station  
between_stops:  
Westfield Bus Station-Cohen Street Bus Station: []  
Belconnen Community Bus Station-Westfield Bus Station: []  
short_name: "942"  
stop_times: [[914a, 923a, 924a, 927a, 936a, 945a, 947a, 952a], [1014a, 1023a, 1024a, 1027a, 1036a, 1045a, 1047a, 1052a], [1114a, 1123a, 1124a, 1127a, 1136a, 1145a, 1147a, 1152a], [1214p, 1223p, 1224p, 1227p, 1236p, 1245p, 1247p, 1252p], [114p, 123p, 124p, 127p, 136p, 145p, 147p, 152p], [214p, 223p, 224p, 227p, 236p, 245p, 247p, 252p], [314p, 323p, 324p, 327p, 336p, 345p, 347p, 352p], [414p, 423p, 424p, 427p, 436p, 445p, 447p, 452p], [514p, 523p, 524p, 527p, 536p, 545p, 547p, 552p], [614p, 623p, 624p, 627p, 636p, 645p, 647p, 652p]]  
 
--- ---
time_points: [City Bus Station (Platform 4), Caswell Drive, Aranda, Cook Shops, Jamison Centre, Belconnen Community Bus Station, Westfield Bus Station, Cohen Street Bus Station] time_points: [City Bus Station (Platform 4), Caswell Drive, Aranda, Cook, Jamison Centre, Belconnen Community Bus Station, Westfield Bus Station, Cohen Street Bus Station]
long_name: To Cohen Street Bus Station long_name: To Cohen Street Bus Station
between_stops: between_stops:
Westfield Bus Station-Cohen Street Bus Station: [] Westfield Bus Station-Cohen Street Bus Station: []
Belconnen Community Bus Station-Westfield Bus Station: [] Belconnen Community Bus Station-Westfield Bus Station: []
stop_times_saturday: [[814a, 823a, 824a, 827a, 836a, 845a, 847a, 852a], [914a, 923a, 924a, 927a, 936a, 945a, 947a, 952a], [1014a, 1023a, 1024a, 1027a, 1036a, 1045a, 1047a, 1052a], [1114a, 1123a, 1124a, 1127a, 1136a, 1145a, 1147a, 1152a], [1214p, 1223p, 1224p, 1227p, 1236p, 1245p, 1247p, 1252p], [114p, 123p, 124p, 127p, 136p, 145p, 147p, 152p], [214p, 223p, 224p, 227p, 236p, 245p, 247p, 252p], [314p, 323p, 324p, 327p, 336p, 345p, 347p, 352p], [414p, 423p, 424p, 427p, 436p, 445p, 447p, 452p], [514p, 523p, 524p, 527p, 536p, 545p, 547p, 552p], [614p, 623p, 624p, 627p, 636p, 645p, 647p, 652p], [714p, 723p, 724p, 727p, 736p, 745p, 747p, 752p], [814p, 823p, 824p, 827p, 836p, 845p, 847p, 852p], [914p, 923p, 924p, 927p, 936p, 945p, 947p, 952p], [1014p, 1023p, 1024p, 1027p, 1036p, 1045p, 1047p, 1052p], [1114p, 1123p, 1124p, 1127p, 1136p, 1145p, 1147p, 1152p]] stop_times_saturday: [[814a, 823a, 824a, 827a, 836a, 845a, 847a, 852a], [914a, 923a, 924a, 927a, 936a, 945a, 947a, 952a], [1014a, 1023a, 1024a, 1027a, 1036a, 1045a, 1047a, 1052a], [1114a, 1123a, 1124a, 1127a, 1136a, 1145a, 1147a, 1152a], [1214p, 1223p, 1224p, 1227p, 1236p, 1245p, 1247p, 1252p], [114p, 123p, 124p, 127p, 136p, 145p, 147p, 152p], [214p, 223p, 224p, 227p, 236p, 245p, 247p, 252p], [314p, 323p, 324p, 327p, 336p, 345p, 347p, 352p], [414p, 423p, 424p, 427p, 436p, 445p, 447p, 452p], [514p, 523p, 524p, 527p, 536p, 545p, 547p, 552p], [614p, 623p, 624p, 627p, 636p, 645p, 647p, 652p], [714p, 723p, 724p, 727p, 736p, 745p, 747p, 752p], [814p, 823p, 824p, 827p, 836p, 845p, 847p, 852p], [914p, 923p, 924p, 927p, 936p, 945p, 947p, 952p], [1014p, 1023p, 1024p, 1027p, 1036p, 1045p, 1047p, 1052p], [1114p, 1123p, 1124p, 1127p, 1136p, 1145p, 1147p, 1152p]]
short_name: "942" short_name: "942"
   
--- ---
time_points: [City Bus Station (Platform 4), Caswell Drive, Aranda, Cook Shops, Jamison Centre, Belconnen Community Bus Station, Westfield Bus Station, Cohen Street Bus Station] time_points: [City Bus Station (Platform 4), Caswell Drive, Aranda, Cook, Jamison Centre, Belconnen Community Bus Station, Westfield Bus Station, Cohen Street Bus Station]
long_name: To Cohen Street Bus Station long_name: To Cohen Street Bus Station
between_stops: between_stops:
Westfield Bus Station-Cohen Street Bus Station: [] Westfield Bus Station-Cohen Street Bus Station: []
Belconnen Community Bus Station-Westfield Bus Station: [] Belconnen Community Bus Station-Westfield Bus Station: []
short_name: "942" short_name: "942"
stop_times_sunday: [[914a, 923a, 924a, 927a, 936a, 945a, 947a, 952a], [1014a, 1023a, 1024a, 1027a, 1036a, 1045a, 1047a, 1052a], [1114a, 1123a, 1124a, 1127a, 1136a, 1145a, 1147a, 1152a], [1214p, 1223p, 1224p, 1227p, 1236p, 1245p, 1247p, 1252p], [114p, 123p, 124p, 127p, 136p, 145p, 147p, 152p], [214p, 223p, 224p, 227p, 236p, 245p, 247p, 252p], [314p, 323p, 324p, 327p, 336p, 345p, 347p, 352p], [414p, 423p, 424p, 427p, 436p, 445p, 447p, 452p], [514p, 523p, 524p, 527p, 536p, 545p, 547p, 552p], [614p, 623p, 624p, 627p, 636p, 645p, 647p, 652p]] stop_times_sunday: [[914a, 923a, 924a, 927a, 936a, 945a, 947a, 952a], [1014a, 1023a, 1024a, 1027a, 1036a, 1045a, 1047a, 1052a], [1114a, 1123a, 1124a, 1127a, 1136a, 1145a, 1147a, 1152a], [1214p, 1223p, 1224p, 1227p, 1236p, 1245p, 1247p, 1252p], [114p, 123p, 124p, 127p, 136p, 145p, 147p, 152p], [214p, 223p, 224p, 227p, 236p, 245p, 247p, 252p], [314p, 323p, 324p, 327p, 336p, 345p, 347p, 352p], [414p, 423p, 424p, 427p, 436p, 445p, 447p, 452p], [514p, 523p, 524p, 527p, 536p, 545p, 547p, 552p], [614p, 623p, 624p, 627p, 636p, 645p, 647p, 652p]]
   
---  
time_points: [Gungahlin Marketplace, Ngunnawal Primary, Nicholls Primary, Federation Square, Chuculba / William Slim Dr, Belconnen Community Bus Station, Westfield Bus Station, Cohen Street Bus Station]  
long_name: To Cohen Street Bus Station  
between_stops:  
Westfield Bus Station-Cohen Street Bus Station: []  
Belconnen Community Bus Station-Westfield Bus Station: []  
Chuculba / William Slim Dr-Belconnen Community Bus Station: [Wjz6mip, Wjz69vO, Wjz69uI, Wjz69ht, Wjz69gA]  
short_name: "951"  
stop_times: [[912a, 921a, 931a, 937a, 942a, 950a, 952a, 957a], [1012a, 1021a, 1031a, 1037a, 1042a, 1050a, 1052a, 1057a], [1112a, 1121a, 1131a, 1137a, 1142a, 1150a, 1152a, 1157a], [1212p, 1221p, 1231p, 1237p, 1242p, 1250p, 1252p, 1257p], [112p, 121p, 131p, 137p, 142p, 150p, 152p, 157p], [212p, 221p, 231p, 237p, 242p, 250p, 252p, 257p], [312p, 321p, 331p, 337p, 342p, 350p, 352p, 357p], [412p, 421p, 431p, 437p, 442p, 450p, 452p, 457p], [512p, 521p, 531p, 537p, 542p, 550p, 552p, 557p], [612p, 621p, 631p, 637p, 642p, 650p, 652p, 657p]]  
 
---  
time_points: [Cohen Street Bus Station (Platform 1), Westfield Bus Station (Platform 1), Belconnen Community Bus Station (Platform 2), Chuculba / William Slim Dr, Federation Square, Nicholls Primary, Ngunnawal Primary, Gungahlin Marketplace]  
long_name: To Gungahlin Market Place  
between_stops:  
Belconnen Community Bus Station (Platform 2)-Chuculba / William Slim Dr: [Wjz69gA, Wjz69ht, Wjz69uI, Wjz69vO, Wjz6mip]  
Cohen Street Bus Station (Platform 1)-Westfield Bus Station (Platform 1): []  
Westfield Bus Station (Platform 1)-Belconnen Community Bus Station (Platform 2): []  
short_name: "951"  
stop_times: [[920a, 922a, 926a, 934a, 939a, 944a, 954a, 1004a], [1020a, 1022a, 1026a, 1034a, 1039a, 1044a, 1054a, 1104a], [1120a, 1122a, 1126a, 1134a, 1139a, 1144a, 1154a, 1204p], [1220p, 1222p, 1226p, 1234p, 1239p, 1244p, 1254p, 104p], [120p, 122p, 126p, 134p, 139p, 144p, 154p, 204p], [220p, 222p, 226p, 234p, 239p, 244p, 254p, 304p], [320p, 322p, 326p, 334p, 339p, 344p, 354p, 404p], [420p, 422p, 426p, 434p, 439p, 444p, 454p, 504p], [520p, 522p, 526p, 534p, 539p, 544p, 554p, 604p], [620p, 622p, 626p, 634p, 639p, 644p, 654p, 704p]]  
 
---  
time_points: [Gungahlin Marketplace, Nicholls Primary, Federation Square, Chuculba / William Slim Dr, Belconnen Community Bus Station, Westfield Bus Station, Cohen Street Bus Station]  
long_name: To Cohen Street Bus Station  
between_stops:  
Westfield Bus Station-Cohen Street Bus Station: []  
Belconnen Community Bus Station-Westfield Bus Station: []  
Chuculba / William Slim Dr-Belconnen Community Bus Station: [Wjz6mip, Wjz69vO, Wjz69uI, Wjz69ht, Wjz69gA]  
short_name: "952"  
stop_times: [[839a, 847a, 900a, 905a, 918a, 920a, 925a], [939a, 947a, 1000a, 1005a, 1018a, 1020a, 1025a], [1039a, 1047a, 1100a, 1105a, 1118a, 1120a, 1125a], [1139a, 1147a, 1200p, 1205p, 1218p, 1220p, 1225p], [1239p, 1247p, 100p, 105p, 118p, 120p, 125p], [139p, 147p, 200p, 205p, 218p, 220p, 225p], [239p, 247p, 300p, 305p, 318p, 320p, 325p], [339p, 347p, 400p, 405p, 418p, 420p, 425p], [439p, 447p, 500p, 505p, 518p, 520p, 525p], [539p, 547p, 600p, 605p, 618p, 620p, 625p], [639p, 647p, 700p, 705p, 718p, 720p, 725p]]  
 
---  
time_points: [Cohen Street Bus Station (Platform 1), Westfield Bus Station (Platform 1), Belconnen Community Bus Station (Platform 2), Chuculba / William Slim Dr, Federation Square, Nicholls Primary, Gungahlin Marketplace]  
long_name: To Gungahlin Market Place  
between_stops:  
Belconnen Community Bus Station (Platform 2)-Chuculba / William Slim Dr: [Wjz69gA, Wjz69ht, Wjz69uI, Wjz69vO, Wjz6mip]  
Cohen Street Bus Station (Platform 1)-Westfield Bus Station (Platform 1): []  
Westfield Bus Station (Platform 1)-Belconnen Community Bus Station (Platform 2): []  
short_name: "952"  
stop_times: [[945a, 947a, 951a, 1004a, 1009a, 1022a, 1031a], [1045a, 1047a, 1051a, 1104a, 1109a, 1122a, 1131a], [1145a, 1147a, 1151a, 1204p, 1209p, 1222p, 1231p], [1245p, 1247p, 1251p, 104p, 109p, 122p, 131p], [145p, 147p, 151p, 204p, 209p, 222p, 231p], [245p, 247p, 251p, 304p, 309p, 322p, 331p], [345p, 347p, 351p, 404p, 409p, 422p, 431p], [445p, 447p, 451p, 504p, 509p, 522p, 531p], [545p, 547p, 551p, 604p, 609p, 622p, 631p], [645p, 647p, 651p, 704p, 709p, 722p, 731p]]  
 
---  
time_points: [Cohen Street Bus Station (Platform 2), Westfield Bus Station (Platform 1), Belconnen Community Bus Station (Platform 2), William Webb / Ginninderra Drive, Chuculba / William Slim Dr, Gungahlin Marketplace, Kosciuszko / Everard, Flemington Rd / Sandford St, Macarthur / Northbourne Ave, City Bus Station]  
long_name: To City Bus Station  
between_stops:  
Macarthur / Northbourne Ave-City Bus Station: [Wjz5QmR, Wjz5Qmu, Wjz5Qgn, Wjz5Qi2, Wjz5Pl0, Wjz5PdJ, Wjz5P8K, Wjz5P8n, Wjz5Oci, Wjz5O3Q]  
Westfield Bus Station (Platform 1)-Belconnen Community Bus Station (Platform 2): []  
Cohen Street Bus Station (Platform 2)-Westfield Bus Station (Platform 1): []  
short_name: "956"  
stop_times: [[841a, 843a, 847a, 853a, 858a, 908a, 918a, 925a, 933a, 940a], [941a, 943a, 947a, 953a, 958a, 1008a, 1018a, 1025a, 1033a, 1040a], [1041a, 1043a, 1047a, 1053a, 1058a, 1108a, 1118a, 1125a, 1133a, 1140a], [1141a, 1143a, 1147a, 1153a, 1158a, 1208p, 1218p, 1225p, 1233p, 1240p], [1241p, 1243p, 1247p, 1253p, 1258p, 108p, 118p, 125p, 133p, 140p], [141p, 143p, 147p, 153p, 158p, 208p, 218p, 225p, 233p, 240p], [241p, 243p, 247p, 253p, 258p, 308p, 318p, 325p, 333p, 340p], [341p, 343p, 347p, 353p, 358p, 408p, 418p, 425p, 433p, 440p], [441p, 443p, 447p, 453p, 458p, 508p, 518p, 525p, 533p, 540p], [541p, 543p, 547p, 553p, 558p, 608p, 618p, 625p, 633p, 640p], [641p, 643p, 647p, 653p, 658p, 708p, 718p, 725p, 733p, 740p]]  
 
---  
time_points: [City Bus Station (Platform 8), Macarthur / Northbourne Ave, Flemington Rd / Sandford St, Kosciuszko / Everard, Gungahlin Marketplace, Chuculba / William Slim Dr, William Webb / Ginninderra Drive, Belconnen Community Bus Station, Westfield Bus Station, Cohen Street Bus Station]  
long_name: To Cohen Street Bus Station  
between_stops:  
Westfield Bus Station-Cohen Street Bus Station: []  
City Bus Station (Platform 8)-Macarthur / Northbourne Ave: [Wjz5O3Q, Wjz5Oci, Wjz5P8n, Wjz5P8K, Wjz5PdJ, Wjz5Pl0, Wjz5Qi2, Wjz5Qgn, Wjz5Qmu, Wjz5QmR]  
Belconnen Community Bus Station-Westfield Bus Station: []  
short_name: "956"  
stop_times: [[838a, 844a, 852a, 859a, 909a, 919a, 924a, 930a, 932a, 937a], [938a, 944a, 952a, 959a, 1009a, 1019a, 1024a, 1030a, 1032a, 1037a], [1038a, 1044a, 1052a, 1059a, 1109a, 1119a, 1124a, 1130a, 1132a, 1137a], [1138a, 1144a, 1152a, 1159a, 1209p, 1219p, 1224p, 1230p, 1232p, 1237p], [1238p, 1244p, 1252p, 1259p, 109p, 119p, 124p, 130p, 132p, 137p], [138p, 144p, 152p, 159p, 209p, 219p, 224p, 230p, 232p, 237p], [238p, 244p, 252p, 259p, 309p, 319p, 324p, 330p, 332p, 337p], [338p, 344p, 352p, 359p, 409p, 419p, 424p, 430p, 432p, 437p], [438p, 444p, 452p, 459p, 509p, 519p, 524p, 530p, 532p, 537p], [538p, 544p, 552p, 559p, 609p, 619p, 624p, 630p, 632p, 637p], [638p, 644p, 652p, 659p, 709p, 719p, 724p, 730p, 732p, 737p]]  
 
---  
time_points: [Cohen Street Bus Station (Platform 1), Westfield Bus Station (Platform 1), Belconnen Community Bus Station (Platform 2), Chuculba / William Slim Dr, Ngunnawal Primary, Shoalhaven / Katherine Ave, Gungahlin Marketplace, Anthony Rolfe Av / Moonlight Av, Flemington Rd / Nullabor Ave, Flemington Rd / Sandford St, Macarthur / Northbourne Ave, City Bus Station]  
long_name: To City Bus Station  
between_stops:  
Belconnen Community Bus Station (Platform 2)-Chuculba / William Slim Dr: [Wjz69gA, Wjz69ht, Wjz69uI, Wjz69vO, Wjz6mip]  
Macarthur / Northbourne Ave-City Bus Station: [Wjz5QmR, Wjz5Qmu, Wjz5Qgn, Wjz5Qi2, Wjz5Pl0, Wjz5PdJ, Wjz5P8K, Wjz5P8n, Wjz5Oci, Wjz5O3Q]  
Cohen Street Bus Station (Platform 1)-Westfield Bus Station (Platform 1): []  
Westfield Bus Station (Platform 1)-Belconnen Community Bus Station (Platform 2): []  
short_name: "958"  
stop_times: [[852a, 854a, 858a, 908a, 919a, 927a, 935a, 944a, 951a, 958a, 1006a, 1013a], [952a, 954a, 958a, 1008a, 1019a, 1027a, 1035a, 1044a, 1051a, 1058a, 1106a, 1113a], [1052a, 1054a, 1058a, 1108a, 1119a, 1127a, 1135a, 1144a, 1151a, 1158a, 1206p, 1213p], [1152a, 1154a, 1158a, 1208p, 1219p, 1227p, 1235p, 1244p, 1251p, 1258p, 106p, 113p], [1252p, 1254p, 1258p, 108p, 119p, 127p, 135p, 144p, 151p, 158p, 206p, 213p], [152p, 154p, 158p, 208p, 219p, 227p, 235p, 244p, 251p, 258p, 306p, 313p], [252p, 254p, 258p, 308p, 319p, 327p, 335p, 344p, 351p, 358p, 406p, 413p], [352p, 354p, 358p, 408p, 419p, 427p, 435p, 444p, 451p, 458p, 506p, 513p], [452p, 454p, 458p, 508p, 519p, 527p, 535p, 544p, 551p, 558p, 606p, 613p], [552p, 554p, 558p, 608p, 619p, 627p, 635p, 644p, 651p, 658p, 706p, 713p], [652p, 654p, 658p, 708p, 719p, 727p, 735p, 744p, 751p, 758p, 806p, 813p]]  
 
---  
time_points: [City Bus Station (Platform 8), Macarthur / Northbourne Ave, Flemington Rd / Sandford St, Flemington Rd / Nullabor Ave, Anthony Rolfe Av / Moonlight Av, Gungahlin Marketplace, Shoalhaven / Katherine Ave, Ngunnawal Primary, Chuculba / William Slim Dr, Belconnen Community Bus Station, Westfield Bus Station, Cohen Street Bus Station]  
long_name: To Cohen Street Bus Station  
between_stops:  
Westfield Bus Station-Cohen Street Bus Station: []  
City Bus Station (Platform 8)-Macarthur / Northbourne Ave: [Wjz5O3Q, Wjz5Oci, Wjz5P8n, Wjz5P8K, Wjz5PdJ, Wjz5Pl0, Wjz5Qi2, Wjz5Qgn, Wjz5Qmu, Wjz5QmR]  
Belconnen Community Bus Station-Westfield Bus Station: []  
Chuculba / William Slim Dr-Belconnen Community Bus Station: [Wjz6mip, Wjz69vO, Wjz69uI, Wjz69ht, Wjz69gA]  
short_name: "958"  
stop_times: [[900a, 906a, 914a, 921a, 928a, 937a, 945a, 953a, 1004a, 1014a, 1016a, 1021a], [1000a, 1006a, 1014a, 1021a, 1028a, 1037a, 1045a, 1053a, 1104a, 1114a, 1116a, 1121a], [1100a, 1106a, 1114a, 1121a, 1128a, 1137a, 1145a, 1153a, 1204p, 1214p, 1216p, 1221p], [1200p, 1206p, 1214p, 1221p, 1228p, 1237p, 1245p, 1253p, 104p, 114p, 116p, 121p], [100p, 106p, 114p, 121p, 128p, 137p, 145p, 153p, 204p, 214p, 216p, 221p], [200p, 206p, 214p, 221p, 228p, 237p, 245p, 253p, 304p, 314p, 316p, 321p], [300p, 306p, 314p, 321p, 328p, 337p, 345p, 353p, 404p, 414p, 416p, 421p], [400p, 406p, 414p, 421p, 428p, 437p, 445p, 453p, 504p, 514p, 516p, 521p], [500p, 506p, 514p, 521p, 528p, 537p, 545p, 553p, 604p, 614p, 616p, 621p], [600p, 606p, 614p, 621p, 628p, 637p, 645p, 653p, 704p, 714p, 716p, 721p], [700p, 706p, 714p, 721p, 728p, 737p, 745p, 753p, 804p, 814p, 816p, 821p]]  
 
---  
time_points: [Woden Bus Station (Platform 5), Mount Neighbour School, Kambah High, Tuggeranong Bus Station]  
long_name: To Tuggeranong Bus Station  
between_stops: {}  
 
short_name: "960"  
stop_times: [[850a, 902a, 908a, 918a], [950a, 1002a, 1008a, 1018a], [1050a, 1102a, 1108a, 1118a], [1150a, 1202p, 1208p, 1218p], [1250p, 102p, 108p, 118p], [150p, 202p, 208p, 218p], [250p, 302p, 308p, 318p], [350p, 402p, 408p, 418p], [450p, 502p, 508p, 518p], [550p, 602p, 608p, 618p], [650p, 702p, 708p, 717p]]  
 
---  
time_points: [Tuggeranong Bus Station (Platform 3), Kambah High, Mount Neighbour School, Woden Bus Station]  
long_name: To Woden Bus Station  
between_stops: {}  
 
short_name: "960"  
stop_times: [[755a, 805a, 811a, 823a], [855a, 905a, 911a, 923a], [955a, 1005a, 1011a, 1023a], [1055a, 1105a, 1111a, 1123a], [1155a, 1205p, 1211p, 1223p], [1255p, 105p, 111p, 123p], [155p, 205p, 211p, 223p], [255p, 305p, 311p, 323p], [355p, 405p, 411p, 423p], [455p, 505p, 511p, 523p], [555p, 605p, 611p, 623p], [655p, 705p, 711p, 721p]]  
 
---  
time_points: [Woden Bus Station (Platform 11), Athllon / Sulwood Kambah, Erindale Centre, Tuggeranong Bus Station]  
long_name: To Tuggeranong Bus Station  
between_stops:  
Woden Bus Station (Platform 11)-Athllon / Sulwood Kambah: [Wjz3kAx, Wjz3kyX, Wjz3kwU, Wjz3iFK, Wjz3hL_, Wjz3gK-, Wjz3gQn, Wjz3gMq, Wjz2mTK]  
short_name: "961"  
stop_times: [[931a, 940a, 950a, 1003a], [1031a, 1040a, 1050a, 1103a], [1131a, 1140a, 1150a, 1203p], [1231p, 1240p, 1250p, 103p], [131p, 140p, 150p, 203p], [231p, 240p, 250p, 303p], [331p, 340p, 350p, 403p], [431p, 440p, 450p, 503p], [531p, 540p, 550p, 603p], [628p, 637p, 647p, 700p]]  
 
---  
time_points: [Tuggeranong Bus Station (Platform 3), Erindale Centre, Athllon / Sulwood Kambah, Woden Bus Station]  
long_name: To Woden Bus Station  
between_stops:  
Athllon / Sulwood Kambah-Woden Bus Station: [Wjz2mTK, Wjz3gMq, Wjz3gQn, Wjz3gK-, Wjz3hL_, Wjz3iFK, Wjz3kwU, Wjz3kyX, Wjz3kAx]  
short_name: "961"  
stop_times: [[942a, 956a, 1006a, 1015a], [1042a, 1056a, 1106a, 1115a], [1142a, 1156a, 1206p, 1215p], [1242p, 1256p, 106p, 115p], [142p, 156p, 206p, 215p], [242p, 256p, 306p, 315p], [342p, 356p, 406p, 415p], [442p, 456p, 506p, 515p], [542p, 556p, 606p, 615p]]  
 
---  
time_points: [Woden Bus Station (Platform 5), Kambah Village, Kambah High, Tuggeranong Bus Station]  
long_name: To Tuggeranong Bus Station  
between_stops: {}  
 
short_name: "962"  
stop_times: [[951a, 1002a, 1010a, 1017a], [1051a, 1102a, 1110a, 1117a], [1151a, 1202p, 1210p, 1217p], [1251p, 102p, 110p, 117p], [151p, 202p, 210p, 217p], [251p, 302p, 310p, 317p], [351p, 402p, 410p, 417p], [451p, 502p, 510p, 517p], [551p, 602p, 610p, 617p], [651p, 702p, 710p, 717p]]  
 
---  
time_points: [Tuggeranong Bus Station (Platform 4), Kambah High, Kambah Village, Woden Bus Station]  
long_name: To Woden Bus Station  
between_stops: {}  
 
short_name: "962"  
stop_times: [[924a, 931a, 939a, 952a], [1024a, 1031a, 1039a, 1052a], [1124a, 1131a, 1139a, 1152a], [1224p, 1231p, 1239p, 1252p], [124p, 131p, 139p, 152p], [224p, 231p, 239p, 252p], [324p, 331p, 339p, 352p], [424p, 431p, 439p, 452p], [524p, 531p, 539p, 552p], [624p, 631p, 638p, 649p]]  
 
---  
time_points: [Woden Bus Station (Platform 11), Athllon / Sulwood Kambah, Erindale Centre, Tuggeranong Bus Station]  
long_name: To Tuggeranong Bus Station  
between_stops:  
Woden Bus Station (Platform 11)-Athllon / Sulwood Kambah: [Wjz3kAx, Wjz3kyX, Wjz3kwU, Wjz3iFK, Wjz3hL_, Wjz3gK-, Wjz3gQn, Wjz3gMq, Wjz2mTK]  
short_name: "964"  
stop_times: [[905a, 914a, 926a, 937a], [1005a, 1014a, 1026a, 1037a], [1105a, 1114a, 1126a, 1137a], [1205p, 1214p, 1226p, 1237p], [105p, 114p, 126p, 137p], [205p, 214p, 226p, 237p], [305p, 314p, 326p, 337p], [405p, 414p, 426p, 437p], [505p, 514p, 526p, 537p], [605p, 614p, 626p, 637p], [705p, 714p, 726p, 737p]]  
 
---  
time_points: [Tuggeranong Bus Station (Platform 5), Erindale Centre, Athllon / Sulwood Kambah, Woden Bus Station]  
long_name: To Woden Bus Station  
between_stops:  
Athllon / Sulwood Kambah-Woden Bus Station: [Wjz2mTK, Wjz3gMq, Wjz3gQn, Wjz3gK-, Wjz3hL_, Wjz3iFK, Wjz3kwU, Wjz3kyX, Wjz3kAx]  
short_name: "964"  
stop_times: [[925a, 937a, 949a, 958a], [1025a, 1037a, 1049a, 1058a], [1125a, 1137a, 1149a, 1158a], [1225p, 1237p, 1249p, 1258p], [125p, 137p, 149p, 158p], [225p, 237p, 249p, 258p], [325p, 337p, 349p, 358p], [425p, 437p, 449p, 458p], [525p, 537p, 549p, 558p], [625p, 637p, 649p, 658p]]  
 
---  
time_points: [Tuggeranong Bus Station (Platform 7), Erindale Centre, Gowrie Shops, Chisholm Shops, Gowrie Shops, Erindale Centre, Tuggeranong Bus Station]  
long_name: To Tuggeranong Bus Station  
between_stops: {}  
 
short_name: "966"  
stop_times: [[908a, 920a, 927a, 936a, 948a, 957a, 1010a], [1008a, 1020a, 1027a, 1036a, 1048a, 1057a, 1110a], [1108a, 1120a, 1127a, 1136a, 1148a, 1157a, 1210p], [1208p, 1220p, 1227p, 1236p, 1248p, 1257p, 110p], [108p, 120p, 127p, 136p, 148p, 157p, 210p], [208p, 220p, 227p, 236p, 248p, 257p, 310p], [308p, 320p, 327p, 336p, 348p, 357p, 410p], [408p, 420p, 427p, 436p, 448p, 457p, 510p], [508p, 520p, 527p, 536p, 548p, 557p, 610p], [608p, 620p, 627p, 636p, 648p, 657p, 710p], [705p, 717p, 724p, 733p, 745p, 754p, 807p]]  
 
--- ---
time_points: [Tuggeranong Bus Station (Platform 7), Erindale Centre, Gowrie Shops, Chisholm Shops, Gowrie Shops, Erindale Centre, Tuggeranong Bus Station] time_points: [Tuggeranong Bus Station (Platform 7), Erindale Centre, Gowrie, Chisholm, Gowrie, Erindale Centre, Tuggeranong Bus Station]
long_name: To Tuggeranong Bus Station long_name: To Tuggeranong Bus Station
between_stops: {} between_stops: {}
   
stop_times_saturday: [["-", "-", "-", 736a, 748a, 757a, 810a], [808a, 820a, 827a, 836a, 848a, 857a, 910a], [908a, 920a, 927a, 936a, 948a, 957a, 1010a], [1008a, 1020a, 1027a, 1036a, 1048a, 1057a, 1110a], [1108a, 1120a, 1127a, 1136a, 1148a, 1157a, 1210p], [1208p, 1220p, 1227p, 1236p, 1248p, 1257p, 110p], [108p, 120p, 127p, 136p, 148p, 157p, 210p], [208p, 220p, 227p, 236p, 248p, 257p, 310p], [308p, 320p, 327p, 336p, 348p, 357p, 410p], [408p, 420p, 427p, 436p, 448p, 457p, 510p], [508p, 520p, 527p, 536p, 548p, 557p, 610p], [608p, 620p, 627p, 636p, 648p, 657p, 710p], [705p, 717p, 724p, 733p, 745p, 754p, 807p], [803p, 815p, 822p, 831p, 843p, 852p, 905p], [903p, 915p, 922p, 931p, 943p, 952p, 1005p], [1003p, 1015p, 1022p, 1031p, 1043p, 1052p, 1105p], [1103p, 1115p, 1122p, 1131p, "-", "-", "-"]] stop_times_saturday: [["-", "-", "-", 736a, 748a, 757a, 810a], [808a, 820a, 827a, 836a, 848a, 857a, 910a], [908a, 920a, 927a, 936a, 948a, 957a, 1010a], [1008a, 1020a, 1027a, 1036a, 1048a, 1057a, 1110a], [1108a, 1120a, 1127a, 1136a, 1148a, 1157a, 1210p], [1208p, 1220p, 1227p, 1236p, 1248p, 1257p, 110p], [108p, 120p, 127p, 136p, 148p, 157p, 210p], [208p, 220p, 227p, 236p, 248p, 257p, 310p], [308p, 320p, 327p, 336p, 348p, 357p, 410p], [408p, 420p, 427p, 436p, 448p, 457p, 510p], [508p, 520p, 527p, 536p, 548p, 557p, 610p], [608p, 620p, 627p, 636p, 648p, 657p, 710p], [705p, 717p, 724p, 733p, 745p, 754p, 807p], [803p, 815p, 822p, 831p, 843p, 852p, 905p], [903p, 915p, 922p, 931p, 943p, 952p, 1005p], [1003p, 1015p, 1022p, 1031p, 1043p, 1052p, 1105p], [1103p, 1115p, 1122p, 1131p, "-", "-", "-"]]
short_name: "966" short_name: "966"
   
--- ---
time_points: [Tuggeranong Bus Station (Platform 7), Erindale Centre, Gowrie Shops, Chisholm Shops, Gowrie Shops, Erindale Centre, Tuggeranong Bus Station] time_points: [Tuggeranong Bus Station (Platform 7), Erindale Centre, Gowrie, Chisholm, Gowrie, Erindale Centre, Tuggeranong Bus Station]
long_name: To Tuggeranong Bus Station long_name: To Tuggeranong Bus Station
between_stops: {} between_stops: {}
   
short_name: "966" short_name: "966"
stop_times_sunday: [[908a, 920a, 927a, 936a, 948a, 957a, 1010a], [1008a, 1020a, 1027a, 1036a, 1048a, 1057a, 1110a], [1108a, 1120a, 1127a, 1136a, 1148a, 1157a, 1210p], [1208p, 1220p, 1227p, 1236p, 1248p, 1257p, 110p], [108p, 120p, 127p, 136p, 148p, 157p, 210p], [208p, 220p, 227p, 236p, 248p, 257p, 310p], [308p, 320p, 327p, 336p, 348p, 357p, 410p], [408p, 420p, 427p, 436p, 448p, 457p, 510p], [508p, 520p, 527p, 536p, 548p, 557p, 610p], [608p, 620p, 627p, 636p, 648p, 657p, 710p], [705p, 717p, 724p, 733p, 745p, 754p, 807p]] stop_times_sunday: [[908a, 920a, 927a, 936a, 948a, 957a, 1010a], [1008a, 1020a, 1027a, 1036a, 1048a, 1057a, 1110a], [1108a, 1120a, 1127a, 1136a, 1148a, 1157a, 1210p], [1208p, 1220p, 1227p, 1236p, 1248p, 1257p, 110p], [108p, 120p, 127p, 136p, 148p, 157p, 210p], [208p, 220p, 227p, 236p, 248p, 257p, 310p], [308p, 320p, 327p, 336p, 348p, 357p, 410p], [408p, 420p, 427p, 436p, 448p, 457p, 510p], [508p, 520p, 527p, 536p, 548p, 557p, 610p], [608p, 620p, 627p, 636p, 648p, 657p, 710p], [705p, 717p, 724p, 733p, 745p, 754p, 807p]]
   
---  
time_points: [Tuggeranong Bus Station (Platform 7), Erindale Centre, Chisholm Shops, Heagney / Clift Richardson, Tuggeranong Bus Station]  
long_name: To Tuggeranong Bus Station  
between_stops: {}  
 
short_name: "967"  
stop_times: [[903a, 914a, 928a, 937a, 950a], [1103a, 1114a, 1128a, 1137a, 1150a], [103p, 114p, 128p, 137p, 150p], [303p, 314p, 328p, 337p, 350p], [503p, 514p, 528p, 537p, 550p], [703p, 714p, 728p, 737p, 750p]]  
 
--- ---
time_points: [Tuggeranong Bus Station (Platform 7), Erindale Centre, Chisholm Shops, Heagney / Clift Richardson, Tuggeranong Bus Station] time_points: [Tuggeranong Bus Station (Platform 7), Erindale Centre, Chisholm, Heagney / Clift Richardson, Tuggeranong Bus Station]
long_name: To Tuggeranong Bus Station long_name: To Tuggeranong Bus Station
between_stops: {} between_stops: {}
   
stop_times_saturday: [[903a, 914a, 928a, 937a, 950a], [1103a, 1114a, 1128a, 1137a, 1150a], [103p, 114p, 128p, 137p, 150p], [303p, 314p, 328p, 337p, 350p], [503p, 514p, 528p, 537p, 550p], [703p, 714p, 728p, 737p, 750p], [903p, 914p, 928p, 937p, 950p], [1103p, 1114p, 1128p, 1137p, 1150p]] stop_times_saturday: [[903a, 914a, 928a, 937a, 950a], [1103a, 1114a, 1128a, 1137a, 1150a], [103p, 114p, 128p, 137p, 150p], [303p, 314p, 328p, 337p, 350p], [503p, 514p, 528p, 537p, 550p], [703p, 714p, 728p, 737p, 750p], [903p, 914p, 928p, 937p, 950p], [1103p, 1114p, 1128p, 1137p, 1150p]]
short_name: "967" short_name: "967"
   
--- ---
time_points: [Tuggeranong Bus Station (Platform 7), Erindale Centre, Chisholm Shops, Heagney / Clift Richardson, Tuggeranong Bus Station] time_points: [Tuggeranong Bus Station (Platform 7), Erindale Centre, Chisholm, Heagney / Clift Richardson, Tuggeranong Bus Station]
long_name: To Tuggeranong Bus Station long_name: To Tuggeranong Bus Station
between_stops: {} between_stops: {}
   
short_name: "967" short_name: "967"
stop_times_sunday: [[903a, 914a, 928a, 937a, 950a], [1103a, 1114a, 1128a, 1137a, 1150a], [103p, 114p, 128p, 137p, 150p], [303p, 314p, 328p, 337p, 350p], [503p, 514p, 528p, 537p, 550p], [703p, 714p, 728p, 737p, 750p]] stop_times_sunday: [[903a, 914a, 928a, 937a, 950a], [1103a, 1114a, 1128a, 1137a, 1150a], [103p, 114p, 128p, 137p, 150p], [303p, 314p, 328p, 337p, 350p], [503p, 514p, 528p, 537p, 550p], [703p, 714p, 728p, 737p, 750p]]
   
---  
time_points: [Tuggeranong Bus Station (Platform 7), Heagney / Clift Richardson, Chisholm Shops, Erindale Centre, Tuggeranong Bus Station]  
long_name: To Tuggeranong Bus Station  
between_stops: {}  
 
short_name: "968"  
stop_times: [[1003a, 1016a, 1024a, 1038a, 1048a], [1203p, 1216p, 1224p, 1238p, 1248p], [203p, 216p, 224p, 238p, 248p], [403p, 416p, 424p, 438p, 448p], [603p, 616p, 624p, 638p, 648p]]  
 
--- ---
time_points: [Tuggeranong Bus Station (Platform 7), Heagney / Clift Richardson, Chisholm Shops, Erindale Centre, Tuggeranong Bus Station] time_points: [Tuggeranong Bus Station (Platform 7), Heagney / Clift Richardson, Chisholm, Erindale Centre, Tuggeranong Bus Station]
long_name: To Tuggeranong Bus Station long_name: To Tuggeranong Bus Station
between_stops: {} between_stops: {}
   
stop_times_saturday: [[803a, 816a, 824a, 838a, 848a], [1003a, 1016a, 1024a, 1038a, 1048a], [1203p, 1216p, 1224p, 1238p, 1248p], [203p, 216p, 224p, 238p, 248p], [403p, 416p, 424p, 438p, 448p], [603p, 616p, 624p, 638p, 648p], [803p, 816p, 824p, 838p, 848p], [1003p, 1016p, 1024p, 1038p, 1048p]] stop_times_saturday: [[803a, 816a, 824a, 838a, 848a], [1003a, 1016a, 1024a, 1038a, 1048a], [1203p, 1216p, 1224p, 1238p, 1248p], [203p, 216p, 224p, 238p, 248p], [403p, 416p, 424p, 438p, 448p], [603p, 616p, 624p, 638p, 648p], [803p, 816p, 824p, 838p, 848p], [1003p, 1016p, 1024p, 1038p, 1048p]]
short_name: "968" short_name: "968"
   
--- ---
time_points: [Tuggeranong Bus Station (Platform 7), Heagney / Clift Richardson, Chisholm Shops, Erindale Centre, Tuggeranong Bus Station] time_points: [Tuggeranong Bus Station (Platform 7), Heagney / Clift Richardson, Chisholm, Erindale Centre, Tuggeranong Bus Station]
long_name: To Tuggeranong Bus Station long_name: To Tuggeranong Bus Station
between_stops: {} between_stops: {}
   
short_name: "968" short_name: "968"
stop_times_sunday: [[1003a, 1016a, 1024a, 1038a, 1048a], [1203p, 1216p, 1224p, 1238p, 1248p], [203p, 216p, 224p, 238p, 248p], [403p, 416p, 424p, 438p, 448p], [603p, 616p, 624p, 638p, 648p]] stop_times_sunday: [[1003a, 1016a, 1024a, 1038a, 1048a], [1203p, 1216p, 1224p, 1238p, 1248p], [203p, 216p, 224p, 238p, 248p], [403p, 416p, 424p, 438p, 448p], [603p, 616p, 624p, 638p, 648p]]
   
---  
time_points: [Lithgow St Terminus Fyshwick, Fyshwick Direct Factory Outlet, Canberra Times, Railway Station Kingston, Russell Offices, City Bus Station (Platform 8), Macarthur / Northbourne Ave, National Hockey Centre Lyneham, Australian Institute of Sport, University of Canberra, Belconnen Community Bus Station, Westfield Bus Station, Cohen Street Bus Station]  
long_name: To Cohen Street Bus Station  
between_stops:  
Westfield Bus Station-Cohen Street Bus Station: []  
Belconnen Community Bus Station-Westfield Bus Station: []  
University of Canberra-Belconnen Community Bus Station: [Wjz68Yy, Wjz68Y0, Wjz68IH, Wjz68Ip, Wjz689c, Wjz681S]  
short_name: "980"  
stop_times: [[845a, 853a, 904a, 911a, 917a, 928a, 934a, 939a, 943a, 949a, 956a, 958a, 1003a], [945a, 953a, 1004a, 1011a, 1017a, 1028a, 1034a, 1039a, 1043a, 1049a, 1056a, 1058a, 1103a], [1045a, 1053a, 1104a, 1111a, 1117a, 1128a, 1134a, 1139a, 1143a, 1149a, 1156a, 1158a, 1203p], ["-", "-", "-", 1130a, 1136a, 1146a, "-", "-", "-", "-", "-", "-", "-"], [1145a, 1153a, 1204p, 1211p, 1217p, 1228p, 1234p, 1239p, 1243p, 1249p, 1256p, 1258p, 103p], [1245p, 1253p, 104p, 111p, 117p, 128p, 134p, 139p, 143p, 149p, 156p, 158p, 203p], [145p, 153p, 204p, 211p, 217p, 228p, 234p, 239p, 243p, 249p, 256p, 258p, 303p], [245p, 253p, 304p, 311p, 317p, 328p, 334p, 339p, 343p, 349p, 356p, 358p, 403p], [345p, 353p, 404p, 411p, 417p, 428p, 434p, 439p, 443p, 449p, 456p, 458p, 503p], ["-", "-", "-", 440p, 446p, 456p, "-", "-", "-", "-", "-", "-", "-"], [445p, 453p, 504p, 511p, 517p, 528p, 534p, 539p, 543p, 549p, 556p, 558p, 603p], [545p, 553p, 604p, 611p, 617p, 628p, 634p, 639p, 643p, 649p, 656p, 658p, 703p]]  
 
---  
time_points: [Cohen Street Bus Station (Platform 1), Westfield Bus Station (Platform 1), Belconnen Community Bus Station (Platform 3), University of Canberra, Australian Institute of Sport, National Hockey Centre Lyneham, Macarthur / Northbourne Ave, City Bus Station (Platform 9), Russell Offices, Railway Station Kingston, Newcastle Street after Isa Street, Fyshwick Direct Factory Outlet, Lithgow St Terminus Fyshwick]  
long_name: To Lithgow St Terminus Fyshwick  
between_stops:  
Westfield Bus Station (Platform 1)-Belconnen Community Bus Station (Platform 3): []  
Cohen Street Bus Station (Platform 1)-Westfield Bus Station (Platform 1): []  
Belconnen Community Bus Station (Platform 3)-University of Canberra: [Wjz681S, Wjz689c, Wjz68Ip, Wjz68IH, Wjz68Y0, Wjz68Yy]  
short_name: "980"  
stop_times: [[820a, 822a, 826a, 834a, 840a, 845a, 851a, 859a, 908a, 914a, 922a, 931a, 940a], [920a, 922a, 926a, 934a, 940a, 945a, 951a, 959a, 1008a, 1014a, 1022a, 1031a, 1040a], [1020a, 1022a, 1026a, 1034a, 1040a, 1045a, 1051a, 1059a, 1108a, 1114a, 1122a, 1131a, 1140a], [1120a, 1122a, 1126a, 1134a, 1140a, 1145a, 1151a, 1159a, 1208p, 1214p, 1222p, 1231p, 1240p], [1220p, 1222p, 1226p, 1234p, 1240p, 1245p, 1251p, 1259p, 108p, 114p, 122p, 131p, 140p], [120p, 122p, 126p, 134p, 140p, 145p, 151p, 159p, 208p, 214p, 222p, 231p, 240p], [220p, 222p, 226p, 234p, 240p, 245p, 251p, 259p, 308p, 314p, 322p, 331p, 340p], [320p, 322p, 326p, 334p, 340p, 345p, 351p, 359p, 408p, 414p, 422p, 431p, 440p], ["-", "-", "-", "-", "-", "-", "-", 415p, 424p, 430p, "-", "-", "-"], [420p, 422p, 426p, 434p, 440p, 445p, 451p, 459p, 508p, 514p, 522p, 531p, 540p], [520p, 522p, 526p, 534p, 540p, 545p, 551p, 558p, "-", "-", "-", "-", "-"], [615p, 617p, 621p, 629p, 635p, 640p, 645p, 652p, "-", "-", "-", "-", "-"]]  
 
---  
time_points: [City Bus Station (Platform 9), National Zoo and Aquarium, Black Mountain Telstra Tower, Botanic Gardens, City Bus Station]  
long_name: To City Bus Station  
between_stops: {}  
 
short_name: "981"  
stop_times: [[1020a, 1034a, 1042a, 1048a, 1055a], [1150a, 1204p, 1212p, 1218p, 1225p], [120p, 134p, 142p, 148p, 155p], [250p, 304p, 312p, 318p, 325p], [420p, 434p, 442p, 448p, 455p]]  
 
---  
time_points: [City Bus Station (Platform 8), Macarthur / Northbourne Ave, Northbourne Avenue / Antill St, Bimberi Centre]  
long_name: To Bimberi Centre  
between_stops:  
City Bus Station (Platform 8)-Macarthur / Northbourne Ave: [Wjz5O3Q, Wjz5Oci, Wjz5P8n, Wjz5P8K, Wjz5PdJ, Wjz5Pl0, Wjz5Qi2, Wjz5Qgn, Wjz5Qmu, Wjz5QmR]  
short_name: "982"  
stop_times: [[342p, 348p, 350p, 400p]]  
 
---  
time_points: [Bimberi Centre, Northbourne Avenue / Antill St, Macarthur / Northbourne Ave, City Bus Station]  
long_name: To City Bus Station  
between_stops:  
Macarthur / Northbourne Ave-City Bus Station: [Wjz5QmR, Wjz5Qmu, Wjz5Qgn, Wjz5Qi2, Wjz5Pl0, Wjz5PdJ, Wjz5P8K, Wjz5P8n, Wjz5Oci, Wjz5O3Q]  
short_name: "982"  
stop_times: [[715p, 724p, 726p, 733p]]  
 
---  
time_points: [Woden Bus Station (Platform 4), Alexander Maconochie Centre]  
long_name: To Alexander Machonochie Centre Hume  
between_stops: {}  
 
short_name: "988"  
stop_times: [[920a, 940a], [1255p, 115p], [455p, 515p]]  
 
--- ---
time_points: [Woden Bus Station (Platform 4), Alexander Maconochie Centre] time_points: [Woden Bus Station (Platform 4), Alexander Maconochie Centre]
long_name: To Alexander Machonochie Centre Hume long_name: To Alexander Machonochie Centre Hume
between_stops: {} between_stops:
  Woden Bus Station (Platform 4)-Alexander Maconochie Centre: [Wjz3dXS, Wjz3kAx]
short_name: "988" short_name: "988"
stop_times_sunday: [[920a, 940a], [1255p, 115p], [455p, 515p]] stop_times_sunday: [[920a, 940a], [1255p, 115p], [455p, 515p]]
   
---  
time_points: [Woden Bus Station (Platform 4), Alexander Maconochie Centre]  
long_name: To Alexander Maconochie Centre Hume  
between_stops: {}  
 
short_name: "988"  
stop_times: [[920a, 940a], [1255p, 115p], [455p, 515p]]  
 
--- ---
time_points: [Woden Bus Station (Platform 4), Alexander Maconochie Centre] time_points: [Woden Bus Station (Platform 4), Alexander Maconochie Centre]
long_name: To Alexander Maconochie Centre Hume long_name: To Alexander Maconochie Centre Hume
between_stops: {} between_stops:
  Woden Bus Station (Platform 4)-Alexander Maconochie Centre: [Wjz3dXS, Wjz3kAx]
stop_times_saturday: [[920a, 940a], [1255p, 115p], [455p, 515p]] stop_times_saturday: [[920a, 940a], [1255p, 115p], [455p, 515p]]
short_name: "988" short_name: "988"
   
---  
time_points: [Alexander Maconochie Centre, Woden Bus Station]  
long_name: To Woden Bus Station  
between_stops: {}  
 
short_name: "988"  
stop_times: [[1130a, 1150a], [320p, 340p], [730p, 750p]]  
 
--- ---
time_points: [Alexander Maconochie Centre, Woden Bus Station] time_points: [Alexander Maconochie Centre, Woden Bus Station]
long_name: To Woden Bus Station long_name: To Woden Bus Station
between_stops: {} between_stops:
  Alexander Maconochie Centre-Woden Bus Station: [Wjz3kAx, Wjz3dXS]
stop_times_saturday: [[1130a, 1150a], [320p, 340p], [730p, 750p]] stop_times_saturday: [[1130a, 1150a], [320p, 340p], [730p, 750p]]
short_name: "988" short_name: "988"
   
--- ---
time_points: [Alexander Maconochie Centre, Woden Bus Station] time_points: [Alexander Maconochie Centre, Woden Bus Station]
long_name: To Woden Bus Station long_name: To Woden Bus Station
between_stops: {} between_stops:
  Alexander Maconochie Centre-Woden Bus Station: [Wjz3kAx, Wjz3dXS]
short_name: "988" short_name: "988"
stop_times_sunday: [[1130a, 1150a], [320p, 340p], [730p, 750p]] stop_times_sunday: [[1130a, 1150a], [320p, 340p], [730p, 750p]]
   
 Binary files a/origin-src/transitfeed-1.2.5.tar.gz and /dev/null differ
 
Apache License  
Version 2.0, January 2004  
http://www.apache.org/licenses/  
 
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION  
 
1. Definitions.  
 
"License" shall mean the terms and conditions for use, reproduction,  
and distribution as defined by Sections 1 through 9 of this document.  
 
"Licensor" shall mean the copyright owner or entity authorized by  
the copyright owner that is granting the License.  
 
"Legal Entity" shall mean the union of the acting entity and all  
other entities that control, are controlled by, or are under common  
control with that entity. For the purposes of this definition,  
"control" means (i) the power, direct or indirect, to cause the  
direction or management of such entity, whether by contract or  
otherwise, or (ii) ownership of fifty percent (50%) or more of the  
outstanding shares, or (iii) beneficial ownership of such entity.  
 
"You" (or "Your") shall mean an individual or Legal Entity  
exercising permissions granted by this License.  
 
"Source" form shall mean the preferred form for making modifications,  
including but not limited to software source code, documentation  
source, and configuration files.  
 
"Object" form shall mean any form resulting from mechanical  
transformation or translation of a Source form, including but  
not limited to compiled object code, generated documentation,  
and conversions to other media types.  
 
"Work" shall mean the work of authorship, whether in Source or  
Object form, made available under the License, as indicated by a  
copyright notice that is included in or attached to the work  
(an example is provided in the Appendix below).  
 
"Derivative Works" shall mean any work, whether in Source or Object  
form, that is based on (or derived from) the Work and for which the  
editorial revisions, annotations, elaborations, or other modifications  
represent, as a whole, an original work of authorship. For the purposes  
of this License, Derivative Works shall not include works that remain  
separable from, or merely link (or bind by name) to the interfaces of,  
the Work and Derivative Works thereof.  
 
"Contribution" shall mean any work of authorship, including  
the original version of the Work and any modifications or additions  
to that Work or Derivative Works thereof, that is intentionally  
submitted to Licensor for inclusion in the Work by the copyright owner  
or by an individual or Legal Entity authorized to submit on behalf of  
the copyright owner. For the purposes of this definition, "submitted"  
means any form of electronic, verbal, or written communication sent  
to the Licensor or its representatives, including but not limited to  
communication on electronic mailing lists, source code control systems,  
and issue tracking systems that are managed by, or on behalf of, the  
Licensor for the purpose of discussing and improving the Work, but  
excluding communication that is conspicuously marked or otherwise  
designated in writing by the copyright owner as "Not a Contribution."  
 
"Contributor" shall mean Licensor and any individual or Legal Entity  
on behalf of whom a Contribution has been received by Licensor and  
subsequently incorporated within the Work.  
 
2. Grant of Copyright License. Subject to the terms and conditions of  
this License, each Contributor hereby grants to You a perpetual,  
worldwide, non-exclusive, no-charge, royalty-free, irrevocable  
copyright license to reproduce, prepare Derivative Works of,  
publicly display, publicly perform, sublicense, and distribute the  
Work and such Derivative Works in Source or Object form.  
 
3. Grant of Patent License. Subject to the terms and conditions of  
this License, each Contributor hereby grants to You a perpetual,  
worldwide, non-exclusive, no-charge, royalty-free, irrevocable  
(except as stated in this section) patent license to make, have made,  
use, offer to sell, sell, import, and otherwise transfer the Work,  
where such license applies only to those patent claims licensable  
by such Contributor that are necessarily infringed by their  
Contribution(s) alone or by combination of their Contribution(s)  
with the Work to which such Contribution(s) was submitted. If You  
institute patent litigation against any entity (including a  
cross-claim or counterclaim in a lawsuit) alleging that the Work  
or a Contribution incorporated within the Work constitutes direct  
or contributory patent infringement, then any patent licenses  
granted to You under this License for that Work shall terminate  
as of the date such litigation is filed.  
 
4. Redistribution. You may reproduce and distribute copies of the  
Work or Derivative Works thereof in any medium, with or without  
modifications, and in Source or Object form, provided that You  
meet the following conditions:  
 
(a) You must give any other recipients of the Work or  
Derivative Works a copy of this License; and  
 
(b) You must cause any modified files to carry prominent notices  
stating that You changed the files; and  
 
(c) You must retain, in the Source form of any Derivative Works  
that You distribute, all copyright, patent, trademark, and  
attribution notices from the Source form of the Work,  
excluding those notices that do not pertain to any part of  
the Derivative Works; and  
 
(d) If the Work includes a "NOTICE" text file as part of its  
distribution, then any Derivative Works that You distribute must  
include a readable copy of the attribution notices contained  
within such NOTICE file, excluding those notices that do not  
pertain to any part of the Derivative Works, in at least one  
of the following places: within a NOTICE text file distributed  
as part of the Derivative Works; within the Source form or  
documentation, if provided along with the Derivative Works; or,  
within a display generated by the Derivative Works, if and  
wherever such third-party notices normally appear. The contents  
of the NOTICE file are for informational purposes only and  
do not modify the License. You may add Your own attribution  
notices within Derivative Works that You distribute, alongside  
or as an addendum to the NOTICE text from the Work, provided  
that such additional attribution notices cannot be construed  
as modifying the License.  
 
You may add Your own copyright statement to Your modifications and  
may provide additional or different license terms and conditions  
for use, reproduction, or distribution of Your modifications, or  
for any such Derivative Works as a whole, provided Your use,  
reproduction, and distribution of the Work otherwise complies with  
the conditions stated in this License.  
 
5. Submission of Contributions. Unless You explicitly state otherwise,  
any Contribution intentionally submitted for inclusion in the Work  
by You to the Licensor shall be under the terms and conditions of  
this License, without any additional terms or conditions.  
Notwithstanding the above, nothing herein shall supersede or modify  
the terms of any separate license agreement you may have executed  
with Licensor regarding such Contributions.  
 
6. Trademarks. This License does not grant permission to use the trade  
names, trademarks, service marks, or product names of the Licensor,  
except as required for reasonable and customary use in describing the  
origin of the Work and reproducing the content of the NOTICE file.  
 
7. Disclaimer of Warranty. Unless required by applicable law or  
agreed to in writing, Licensor provides the Work (and each  
Contributor provides its Contributions) on an "AS IS" BASIS,  
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or  
implied, including, without limitation, any warranties or conditions  
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A  
PARTICULAR PURPOSE. You are solely responsible for determining the  
appropriateness of using or redistributing the Work and assume any  
risks associated with Your exercise of permissions under this License.  
 
8. Limitation of Liability. In no event and under no legal theory,  
whether in tort (including negligence), contract, or otherwise,  
unless required by applicable law (such as deliberate and grossly  
negligent acts) or agreed to in writing, shall any Contributor be  
liable to You for damages, including any direct, indirect, special,  
incidental, or consequential damages of any character arising as a  
result of this License or out of the use or inability to use the  
Work (including but not limited to damages for loss of goodwill,  
work stoppage, computer failure or malfunction, or any and all  
other commercial damages or losses), even if such Contributor  
has been advised of the possibility of such damages.  
 
9. Accepting Warranty or Additional Liability. While redistributing  
the Work or Derivative Works thereof, You may choose to offer,  
and charge a fee for, acceptance of support, warranty, indemnity,  
or other liability obligations and/or rights consistent with this  
License. However, in accepting such obligations, You may act only  
on Your own behalf and on Your sole responsibility, not on behalf  
of any other Contributor, and only if You agree to indemnify,  
defend, and hold each Contributor harmless for any liability  
incurred by, or claims asserted against, such Contributor by reason  
of your accepting any such warranty or additional liability.  
 
END OF TERMS AND CONDITIONS  
 
APPENDIX: How to apply the Apache License to your work.  
 
To apply the Apache License to your work, attach the following  
boilerplate notice, with the fields enclosed by brackets "[]"  
replaced with your own identifying information. (Don't include  
the brackets!) The text should be enclosed in the appropriate  
comment syntax for the file format. We also recommend that a  
file or class name and description of purpose be included on the  
same "printed page" as the copyright notice for easier  
identification within third-party archives.  
 
Copyright [yyyy] [name of copyright owner]  
 
Licensed under the Apache License, Version 2.0 (the "License");  
you may not use this file except in compliance with the License.  
You may obtain a copy of the License at  
 
http://www.apache.org/licenses/LICENSE-2.0  
 
Unless required by applicable law or agreed to in writing, software  
distributed under the License is distributed on an "AS IS" BASIS,  
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.  
See the License for the specific language governing permissions and  
limitations under the License.  
 
INSTALL file for transitfeed distribution  
 
 
 
To download and install in one step make sure you have easy-install installed and run  
easy_install transitfeed  
 
 
 
Since you got this far chances are you have downloaded a copy of the source  
code. Install with the command  
 
python setup.py install  
 
 
 
If you don't want to install you may be able to run the scripts from this  
directory. For example, try running  
 
./feedvalidator.py -n test/data/good_feed.zip  
 
 
Metadata-Version: 1.0  
Name: transitfeed  
Version: 1.2.5  
Summary: Google Transit Feed Specification library and tools  
Home-page: http://code.google.com/p/googletransitdatafeed/  
Author: Tom Brown  
Author-email: tom.brown.code@gmail.com  
License: Apache License, Version 2.0  
Download-URL: http://googletransitdatafeed.googlecode.com/files/transitfeed-1.2.5.tar.gz  
Description: This module provides a library for reading, writing and validating Google Transit Feed Specification files. It includes some scripts that validate a feed, display it using the Google Maps API and the start of a KML importer and exporter.  
Platform: OS Independent  
Classifier: Development Status :: 4 - Beta  
Classifier: Intended Audience :: Developers  
Classifier: Intended Audience :: Information Technology  
Classifier: Intended Audience :: Other Audience  
Classifier: License :: OSI Approved :: Apache Software License  
Classifier: Operating System :: OS Independent  
Classifier: Programming Language :: Python  
Classifier: Topic :: Scientific/Engineering :: GIS  
Classifier: Topic :: Software Development :: Libraries :: Python Modules  
 
README file for transitfeed distribution  
 
 
 
This distribution contains a library to help you parse and generate Google  
Transit Feed files. It also contains some sample tools that demonstrate the  
library and are useful in their own right when maintaining Google  
Transit Feed files. You may fetch the specification from  
http://code.google.com/transit/spec/transit_feed_specification.htm  
 
 
See INSTALL for installation instructions  
 
The most recent source can be downloaded from our subversion repository at  
http://googletransitdatafeed.googlecode.com/svn/trunk/python/  
 
See http://code.google.com/p/googletransitdatafeed/wiki/TransitFeedDistribution  
for more information.  
 
__doc__ = """  
Package holding files for Google Transit Feed Specification Schedule Viewer.  
"""  
# This package contains the data files for schedule_viewer.py, a script that  
# comes with the transitfeed distribution. According to the thread  
# "[Distutils] distutils data_files and setuptools.pkg_resources are driving  
# me crazy" this is the easiest way to include data files. My experience  
# agrees. - Tom 2007-05-29  
 
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"  
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">  
<html xmlns="http://www.w3.org/1999/xhtml" xmlns:v="urn:schemas-microsoft-com:vml">  
<head>  
<meta http-equiv="content-type" content="text/html; charset=utf-8"/>  
<title>[agency]</title>  
<link href="file/style.css" rel="stylesheet" type="text/css" />  
<style type="text/css">  
v\:* {  
behavior:url(#default#VML);  
}  
</style>  
<script src="http://[host]/maps?file=api&amp;v=2&amp;key=[key]" type="text/javascript"></script>  
<script src="/file/labeled_marker.js" type="text/javascript"></script>  
<script language="VBScript" src="/file/svgcheck.vbs"></script>  
<script type="text/javascript">  
//<![CDATA[  
var map;  
// Set to true when debugging for log statements about HTTP requests.  
var log = false;  
var twelveHourTime = false; // set to true to see AM/PM  
var selectedRoute = null;  
var forbid_editing = [forbid_editing];  
 
function load() {  
if (GBrowserIsCompatible()) {  
sizeRouteList();  
var map_dom = document.getElementById("map");  
map = new GMap2(map_dom);  
map.addControl(new GLargeMapControl());  
map.addControl(new GMapTypeControl());  
map.addControl(new GOverviewMapControl());  
map.enableScrollWheelZoom();  
var bb = new GLatLngBounds(new GLatLng([min_lat], [min_lon]),new GLatLng([max_lat], [max_lon]));  
map.setCenter(bb.getCenter(), map.getBoundsZoomLevel(bb));  
map.enableDoubleClickZoom();  
initIcons();  
GEvent.addListener(map, "moveend", callbackMoveEnd);  
GEvent.addListener(map, "zoomend", callbackZoomEnd);  
callbackMoveEnd(); // Pretend we just moved to current center  
fetchRoutes();  
}  
}  
 
function callbackZoomEnd() {  
}  
 
function callbackMoveEnd() {  
// Map moved, search for stops near the center  
fetchStopsInBounds(map.getBounds());  
}  
 
/**  
* Fetch a sample of stops in the bounding box.  
*/  
function fetchStopsInBounds(bounds) {  
url = "/json/boundboxstops?n=" + bounds.getNorthEast().lat()  
+ "&e=" + bounds.getNorthEast().lng()  
+ "&s=" + bounds.getSouthWest().lat()  
+ "&w=" + bounds.getSouthWest().lng()  
+ "&limit=50";  
if (log)  
GLog.writeUrl(url);  
GDownloadUrl(url, callbackDisplayStopsBackground);  
}  
 
/**  
* Displays stops returned by the server on the map. Expected to be called  
* when GDownloadUrl finishes.  
*  
* @param {String} data JSON encoded list of list, each  
* containing a row of stops.txt  
* @param {Number} responseCode Response code from server  
*/  
function callbackDisplayStops(data, responseCode) {  
if (responseCode != 200) {  
return;  
}  
clearMap();  
var stops = eval(data);  
if (stops.length == 1) {  
var marker = addStopMarkerFromList(stops[0], true);  
fetchStopInfoWindow(marker);  
} else {  
for (var i=0; i<stops.length; ++i) {  
addStopMarkerFromList(stops[i], true);  
}  
}  
}  
 
function stopTextSearchSubmit() {  
var text = document.getElementById("stopTextSearchInput").value;  
var url = "/json/stopsearch?q=" + text; // TODO URI escape  
if (log)  
GLog.writeUrl(url);  
GDownloadUrl(url, callbackDisplayStops);  
}  
 
function tripTextSearchSubmit() {  
var text = document.getElementById("tripTextSearchInput").value;  
selectTrip(text);  
}  
 
/**  
* Add stops markers to the map and remove stops no longer in the  
* background.  
*/  
function callbackDisplayStopsBackground(data, responseCode) {  
if (responseCode != 200) {  
return;  
}  
var stops = eval(data);  
// Make a list of all background markers  
var oldStopMarkers = {};  
for (var stopId in stopMarkersBackground) {  
oldStopMarkers[stopId] = 1;  
}  
// Add new markers to the map and remove from oldStopMarkers  
for (var i=0; i<stops.length; ++i) {  
var marker = addStopMarkerFromList(stops[i], false);  
if (oldStopMarkers[marker.stopId]) {  
delete oldStopMarkers[marker.stopId];  
}  
}  
// Delete all markers that remain in oldStopMarkers  
for (var stopId in oldStopMarkers) {  
GEvent.removeListener(stopMarkersBackground[stopId].clickListener);  
map.removeOverlay(stopMarkersBackground[stopId]);  
delete stopMarkersBackground[stopId]  
}  
}  
 
/**  
* Remove all overlays from the map  
*/  
function clearMap() {  
boundsOfPolyLine = null;  
for (var stopId in stopMarkersSelected) {  
GEvent.removeListener(stopMarkersSelected[stopId].clickListener);  
}  
for (var stopId in stopMarkersBackground) {  
GEvent.removeListener(stopMarkersBackground[stopId].clickListener);  
}  
stopMarkersSelected = {};  
stopMarkersBackground = {};  
map.clearOverlays();  
}  
 
/**  
* Return a new GIcon used for stops  
*/  
function makeStopIcon() {  
var icon = new GIcon();  
icon.iconSize = new GSize(12, 20);  
icon.shadowSize = new GSize(22, 20);  
icon.iconAnchor = new GPoint(6, 20);  
icon.infoWindowAnchor = new GPoint(5, 1);  
return icon;  
}  
 
/**  
* Initialize icons. Call once during load.  
*/  
function initIcons() {  
iconSelected = makeStopIcon();  
iconSelected.image = "/file/mm_20_yellow.png";  
iconSelected.shadow = "/file/mm_20_shadow.png";  
iconBackground = makeStopIcon();  
iconBackground.image = "/file/mm_20_blue_trans.png";  
iconBackground.shadow = "/file/mm_20_shadow_trans.png";  
iconBackgroundStation = makeStopIcon();  
iconBackgroundStation.image = "/file/mm_20_red_trans.png";  
iconBackgroundStation.shadow = "/file/mm_20_shadow_trans.png";  
}  
 
var iconSelected;  
var iconBackground;  
var iconBackgroundStation;  
// Map from stopId to GMarker object for stops selected because they are  
// part of a trip, etc  
var stopMarkersSelected = {};  
// Map from stopId to GMarker object for stops found by the background  
// passive search  
var stopMarkersBackground = {};  
/**  
* Add a stop to the map, given a row from stops.txt.  
*/  
function addStopMarkerFromList(list, selected, text) {  
return addStopMarker(list[0], list[1], list[2], list[3], list[4], selected, text);  
}  
 
/**  
* Add a stop to the map, returning the new marker  
*/  
function addStopMarker(stopId, stopName, stopLat, stopLon, locationType, selected, text) {  
if (stopMarkersSelected[stopId]) {  
// stop was selected  
var marker = stopMarkersSelected[stopId];  
if (text) {  
oldText = marker.getText();  
if (oldText) {  
oldText = oldText + "<br>";  
}  
marker.setText(oldText + text);  
}  
return marker;  
}  
if (stopMarkersBackground[stopId]) {  
// Stop was in the background. Either delete it from the background or  
// leave it where it is.  
if (selected) {  
map.removeOverlay(stopMarkersBackground[stopId]);  
delete stopMarkersBackground[stopId];  
} else {  
return stopMarkersBackground[stopId];  
}  
}  
 
var icon;  
if (selected) {  
icon = iconSelected;  
} else if (locationType == 1) {  
icon = iconBackgroundStation  
} else {  
icon = iconBackground;  
}  
var ll = new GLatLng(stopLat,stopLon);  
var marker;  
if (selected || text) {  
if (!text) {  
text = ""; // Make sure every selected icon has a text box, even if empty  
}  
var markerOpts = new Object();  
markerOpts.icon = icon;  
markerOpts.labelText = text;  
markerOpts.labelClass = "tooltip";  
markerOpts.labelOffset = new GSize(6, -20);  
marker = new LabeledMarker(ll, markerOpts);  
} else {  
marker = new GMarker(ll, {icon: icon, draggable: !forbid_editing});  
}  
marker.stopName = stopName;  
marker.stopId = stopId;  
if (selected) {  
stopMarkersSelected[stopId] = marker;  
} else {  
stopMarkersBackground[stopId] = marker;  
}  
map.addOverlay(marker);  
marker.clickListener = GEvent.addListener(marker, "click", function() {fetchStopInfoWindow(marker);});  
GEvent.addListener(marker, "dragend", function() {  
 
document.getElementById("edit").style.visibility = "visible";  
document.getElementById("edit_status").innerHTML = "updating..."  
changeStopLocation(marker);  
});  
return marker;  
}  
 
/**  
* Sends new location of a stop to server.  
*/  
function changeStopLocation(marker) {  
var url = "/json/setstoplocation?id=" +  
encodeURIComponent(marker.stopId) +  
"&lat=" + encodeURIComponent(marker.getLatLng().lat()) +  
"&lng=" + encodeURIComponent(marker.getLatLng().lng());  
GDownloadUrl(url, function(data, responseCode) {  
document.getElementById("edit_status").innerHTML = unescape(data);  
} );  
if (log)  
GLog.writeUrl(url);  
}  
 
/**  
* Saves the current state of the data file opened at server side to file.  
*/  
function saveData() {  
var url = "/json/savedata";  
GDownloadUrl(url, function(data, responseCode) {  
document.getElementById("edit_status").innerHTML = data;} );  
if (log)  
GLog.writeUrl(url);  
}  
 
/**  
* Fetch the next departing trips from the stop for display in an info  
* window.  
*/  
function fetchStopInfoWindow(marker) {  
var url = "/json/stoptrips?stop=" + encodeURIComponent(marker.stopId) + "&time=" + parseTimeInput();  
GDownloadUrl(url, function(data, responseCode) {  
callbackDisplayStopInfoWindow(marker, data, responseCode); } );  
if (log)  
GLog.writeUrl(url);  
}  
 
function callbackDisplayStopInfoWindow(marker, data, responseCode) {  
if (responseCode != 200) {  
return;  
}  
var timeTrips = eval(data);  
var html = "<b>" + marker.stopName + "</b> (" + marker.stopId + ")<br>";  
var latLng = marker.getLatLng();  
html = html + "(" + latLng.lat() + ", " + latLng.lng() + ")<br>";  
html = html + "<table><tr><th>service_id<th>time<th>name</tr>";  
for (var i=0; i < timeTrips.length; ++i) {  
var time = timeTrips[i][0];  
var tripid = timeTrips[i][1][0];  
var tripname = timeTrips[i][1][1];  
var service_id = timeTrips[i][1][2];  
var timepoint = timeTrips[i][2];  
html = html + "<tr onClick='map.closeInfoWindow();selectTrip(\"" +  
tripid + "\")'>" +  
"<td>" + service_id +  
"<td align='right'>" + (timepoint ? "" : "~") +  
formatTime(time) + "<td>" + tripname + "</tr>";  
}  
html = html + "</table>";  
marker.openInfoWindowHtml(html);  
}  
 
function leadingZero(digit) {  
if (digit < 10)  
return "0" + digit;  
else  
return "" + digit;  
}  
 
function formatTime(secSinceMidnight) {  
var hours = Math.floor(secSinceMidnight / 3600);  
var suffix = "";  
 
if (twelveHourTime) {  
suffix = (hours >= 12) ? "p" : "a";  
suffix += (hours >= 24) ? " next day" : "";  
hours = hours % 12;  
if (hours == 0)  
hours = 12;  
}  
var minutes = Math.floor(secSinceMidnight / 60) % 60;  
var seconds = secSinceMidnight % 60;  
if (seconds == 0) {  
return hours + ":" + leadingZero(minutes) + suffix;  
} else {  
return hours + ":" + leadingZero(minutes) + ":" + leadingZero(seconds) + suffix;  
}  
}  
 
function parseTimeInput() {  
var text = document.getElementById("timeInput").value;  
var m = text.match(/([012]?\d):([012345]?\d)(:([012345]?\d))?/);  
if (m) {  
var seconds = parseInt(m[1], 10) * 3600;  
seconds += parseInt(m[2], 10) * 60;  
if (m[4]) {  
second += parseInt(m[4], 10);  
}  
return seconds;  
} else {  
if (log)  
GLog.write("Couldn't match " + text);  
}  
}  
 
/**  
* Create a string of dots that gets longer with the log of count.  
*/  
function countToRepeatedDots(count) {  
// Find ln_2(count) + 1  
var logCount = Math.ceil(Math.log(count) / 0.693148) + 1;  
return new Array(logCount + 1).join(".");  
}  
 
function fetchRoutes() {  
url = "/json/routes";  
if (log)  
GLog.writeUrl(url);  
GDownloadUrl(url, callbackDisplayRoutes);  
}  
 
function callbackDisplayRoutes(data, responseCode) {  
if (responseCode != 200) {  
patternDiv.appendChild(div);  
}  
var routes = eval(data);  
var routesList = document.getElementById("routeList");  
while (routesList.hasChildNodes()) {  
routesList.removeChild(routesList.firstChild);  
}  
for (i = 0; i < routes.length; ++i) {  
var routeId = routes[i][0];  
var shortName = document.createElement("span");  
shortName.className = "shortName";  
shortName.appendChild(document.createTextNode(routes[i][1] + " "));  
var routeName = routes[i][2];  
var elem = document.createElement("div");  
elem.appendChild(shortName);  
elem.appendChild(document.createTextNode(routeName));  
elem.id = "route_" + routeId;  
elem.className = "routeChoice";  
elem.title = routeName;  
GEvent.addDomListener(elem, "click", makeClosure(selectRoute, routeId));  
 
var routeContainer = document.createElement("div");  
routeContainer.id = "route_container_" + routeId;  
routeContainer.className = "routeContainer";  
routeContainer.appendChild(elem);  
routesList.appendChild(routeContainer);  
}  
}  
 
function selectRoute(routeId) {  
var routesList = document.getElementById("routeList");  
routeSpans = routesList.getElementsByTagName("div");  
for (var i = 0; i < routeSpans.length; ++i) {  
if (routeSpans[i].className == "routeChoiceSelected") {  
routeSpans[i].className = "routeChoice";  
}  
}  
 
// remove any previously-expanded route  
var tripInfo = document.getElementById("tripInfo");  
if (tripInfo)  
tripInfo.parentNode.removeChild(tripInfo);  
 
selectedRoute = routeId;  
var span = document.getElementById("route_" + routeId);  
span.className = "routeChoiceSelected";  
fetchPatterns(routeId);  
}  
 
function fetchPatterns(routeId) {  
url = "/json/routepatterns?route=" + encodeURIComponent(routeId) + "&time=" + parseTimeInput();  
if (log)  
GLog.writeUrl(url);  
GDownloadUrl(url, callbackDisplayPatterns);  
}  
 
function callbackDisplayPatterns(data, responseCode) {  
if (responseCode != 200) {  
return;  
}  
var div = document.createElement("div");  
div.className = "tripSection";  
div.id = "tripInfo";  
var firstTrip = null;  
var patterns = eval(data);  
clearMap();  
for (i = 0; i < patterns.length; ++i) {  
patternDiv = document.createElement("div")  
patternDiv.className = 'patternSection';  
div.appendChild(patternDiv)  
var pat = patterns[i]; // [patName, patId, len(early trips), trips, len(later trips), has_non_zero_trip_type]  
if (pat[5] == '1') {  
patternDiv.className += " unusualPattern"  
}  
patternDiv.appendChild(document.createTextNode(pat[0]));  
patternDiv.appendChild(document.createTextNode(", " + (pat[2] + pat[3].length + pat[4]) + " trips: "));  
if (pat[2] > 0) {  
patternDiv.appendChild(document.createTextNode(countToRepeatedDots(pat[2]) + " "));  
}  
for (j = 0; j < pat[3].length; ++j) {  
var trip = pat[3][j];  
var tripId = trip[1];  
if ((i == 0) && (j == 0))  
firstTrip = tripId;  
patternDiv.appendChild(document.createTextNode(" "));  
var span = document.createElement("span");  
span.appendChild(document.createTextNode(formatTime(trip[0])));  
span.id = "trip_" + tripId;  
GEvent.addDomListener(span, "click", makeClosure(selectTrip, tripId));  
patternDiv.appendChild(span)  
span.className = "tripChoice";  
}  
if (pat[4] > 0) {  
patternDiv.appendChild(document.createTextNode(" " + countToRepeatedDots(pat[4])));  
}  
patternDiv.appendChild(document.createElement("br"));  
}  
route = document.getElementById("route_container_" + selectedRoute);  
route.appendChild(div);  
if (tripId != null)  
selectTrip(firstTrip);  
}  
 
// Needed to get around limitation in javascript scope rules.  
// See http://calculist.blogspot.com/2005/12/gotcha-gotcha.html  
function makeClosure(f, a, b, c) {  
return function() { f(a, b, c); };  
}  
function make1ArgClosure(f, a, b, c) {  
return function(x) { f(x, a, b, c); };  
}  
function make2ArgClosure(f, a, b, c) {  
return function(x, y) { f(x, y, a, b, c); };  
}  
 
function selectTrip(tripId) {  
var tripInfo = document.getElementById("tripInfo");  
if (tripInfo) {  
tripSpans = tripInfo.getElementsByTagName('span');  
for (var i = 0; i < tripSpans.length; ++i) {  
tripSpans[i].className = 'tripChoice';  
}  
}  
var span = document.getElementById("trip_" + tripId);  
// Won't find the span if a different route is selected  
if (span) {  
span.className = 'tripChoiceSelected';  
}  
clearMap();  
url = "/json/tripstoptimes?trip=" + encodeURIComponent(tripId);  
if (log)  
GLog.writeUrl(url);  
GDownloadUrl(url, callbackDisplayTripStopTimes);  
fetchTripPolyLine(tripId);  
fetchTripRows(tripId);  
}  
 
function callbackDisplayTripStopTimes(data, responseCode) {  
if (responseCode != 200) {  
return;  
}  
var stopsTimes = eval(data);  
if (!stopsTimes) return;  
displayTripStopTimes(stopsTimes[0], stopsTimes[1]);  
}  
 
function fetchTripPolyLine(tripId) {  
url = "/json/tripshape?trip=" + encodeURIComponent(tripId);  
if (log)  
GLog.writeUrl(url);  
GDownloadUrl(url, callbackDisplayTripPolyLine);  
}  
 
function callbackDisplayTripPolyLine(data, responseCode) {  
if (responseCode != 200) {  
return;  
}  
var points = eval(data);  
if (!points) return;  
displayPolyLine(points);  
}  
 
var boundsOfPolyLine = null;  
function expandBoundingBox(latLng) {  
if (boundsOfPolyLine == null) {  
boundsOfPolyLine = new GLatLngBounds(latLng, latLng);  
} else {  
boundsOfPolyLine.extend(latLng);  
}  
}  
 
/**  
* Display a line given a list of points  
*  
* @param {Array} List of lat,lng pairs  
*/  
function displayPolyLine(points) {  
var linePoints = Array();  
for (i = 0; i < points.length; ++i) {  
var ll = new GLatLng(points[i][0], points[i][1]);  
expandBoundingBox(ll);  
linePoints[linePoints.length] = ll;  
}  
var polyline = new GPolyline(linePoints, "#FF0000", 4);  
map.addOverlay(polyline);  
map.setCenter(boundsOfPolyLine.getCenter(), map.getBoundsZoomLevel(boundsOfPolyLine));  
}  
 
function displayTripStopTimes(stops, times) {  
for (i = 0; i < stops.length; ++i) {  
var marker;  
if (times && times[i] != null) {  
marker = addStopMarkerFromList(stops[i], true, formatTime(times[i]));  
} else {  
marker = addStopMarkerFromList(stops[i], true);  
}  
expandBoundingBox(marker.getPoint());  
}  
map.setCenter(boundsOfPolyLine.getCenter(), map.getBoundsZoomLevel(boundsOfPolyLine));  
}  
 
function fetchTripRows(tripId) {  
url = "/json/triprows?trip=" + encodeURIComponent(tripId);  
if (log)  
GLog.writeUrl(url);  
GDownloadUrl(url, make2ArgClosure(callbackDisplayTripRows, tripId));  
}  
 
function callbackDisplayTripRows(data, responseCode, tripId) {  
if (responseCode != 200) {  
return;  
}  
var rows = eval(data);  
if (!rows) return;  
var html = "";  
for (var i = 0; i < rows.length; ++i) {  
var filename = rows[i][0];  
var row = rows[i][1];  
html += "<b>" + filename + "</b>: " + formatDictionary(row) + "<br>";  
}  
html += svgTag("/ttablegraph?height=100&trip=" + tripId, "height='115' width='100%'");  
var bottombarDiv = document.getElementById("bottombar");  
bottombarDiv.style.display = "block";  
bottombarDiv.style.height = "175px";  
bottombarDiv.innerHTML = html;  
sizeRouteList();  
}  
 
/**  
* Return HTML to embed a SVG object in this page. src is the location of  
* the SVG and attributes is inserted directly into the object or embed  
* tag.  
*/  
function svgTag(src, attributes) {  
if (navigator.userAgent.toLowerCase().indexOf("msie") != -1) {  
if (isSVGControlInstalled()) {  
return "<embed pluginspage='http://www.adobe.com/svg/viewer/install/' src='" + src + "' " + attributes +"></embed>";  
} else {  
return "<p>Please install the <a href='http://www.adobe.com/svg/viewer/install/'>Adobe SVG Viewer</a> to get SVG support in IE</p>";  
}  
} else {  
return "<object data='" + src + "' type='image/svg+xml' " + attributes + "><p>No SVG support in your browser. Try Firefox 1.5 or newer or install the <a href='http://www.adobe.com/svg/viewer/install/'>Adobe SVG Viewer</a></p></object>";  
}  
}  
 
/**  
* Format an Array object containing key-value pairs into a human readable  
* string.  
*/  
function formatDictionary(d) {  
var output = "";  
var first = 1;  
for (var k in d) {  
if (first) {  
first = 0;  
} else {  
output += "&nbsp;&nbsp; ";  
}  
output += "<b>" + k + "</b>=" + d[k];  
}  
return output;  
}  
 
 
function windowHeight() {  
// Standard browsers (Mozilla, Safari, etc.)  
if (self.innerHeight)  
return self.innerHeight;  
// IE 6  
if (document.documentElement && document.documentElement.clientHeight)  
return document.documentElement.clientHeight;  
// IE 5  
if (document.body)  
return document.body.clientHeight;  
// Just in case.  
return 0;  
}  
 
function sizeRouteList() {  
var bottombarHeight = 0;  
var bottombarDiv = document.getElementById('bottombar');  
if (bottombarDiv.style.display != 'none') {  
bottombarHeight = document.getElementById('bottombar').offsetHeight  
+ document.getElementById('bottombar').style.marginTop;  
}  
var height = windowHeight() - document.getElementById('topbar').offsetHeight - 15 - bottombarHeight;  
document.getElementById('content').style.height = height + 'px';  
if (map) {  
// Without this displayPolyLine does not use the correct map size  
map.checkResize();  
}  
}  
 
//]]>  
</script>  
</head>  
 
<body class='sidebar-left' onload="load();" onunload="GUnload()" onresize="sizeRouteList()">  
<div id='topbar'>  
<div id="edit">  
<span id="edit_status">...</span>  
<form onSubmit="saveData(); return false;"><input value="Save" type="submit">  
</div>  
<div id="agencyHeader">[agency]</div>  
</div>  
<div id='content'>  
<div id='sidebar-wrapper'><div id='sidebar'>  
Time:&nbsp;<input type="text" value="8:00" width="9" id="timeInput"><br>  
<form onSubmit="stopTextSearchSubmit(); return false;">  
Find Station: <input type="text" id="stopTextSearchInput"><input value="Search" type="submit"></form><br>  
<form onSubmit="tripTextSearchSubmit(); return false;">  
Find Trip ID: <input type="text" id="tripTextSearchInput"><input value="Search" type="submit"></form><br>  
<div id="routeList">routelist</div>  
</div></div>  
 
<div id='map-wrapper'> <div id='map'></div> </div>  
</div>  
 
<div id='bottombar'>bottom bar</div>  
 
</body>  
</html>  
 
/*  
* LabeledMarker Class  
*  
* Copyright 2007 Mike Purvis (http://uwmike.com)  
*  
* Licensed under the Apache License, Version 2.0 (the "License");  
* you may not use this file except in compliance with the License.  
* You may obtain a copy of the License at  
*  
* http://www.apache.org/licenses/LICENSE-2.0  
*  
* Unless required by applicable law or agreed to in writing, software  
* distributed under the License is distributed on an "AS IS" BASIS,  
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.  
* See the License for the specific language governing permissions and  
* limitations under the License.  
*  
* This class extends the Maps API's standard GMarker class with the ability  
* to support markers with textual labels. Please see articles here:  
*  
* http://googlemapsbook.com/2007/01/22/extending-gmarker/  
* http://googlemapsbook.com/2007/03/06/clickable-labeledmarker/  
*/  
 
/**  
* Constructor for LabeledMarker, which picks up on strings from the GMarker  
* options array, and then calls the GMarker constructor.  
*  
* @param {GLatLng} latlng  
* @param {GMarkerOptions} Named optional arguments:  
* opt_opts.labelText {String} text to place in the overlay div.  
* opt_opts.labelClass {String} class to use for the overlay div.  
* (default "markerLabel")  
* opt_opts.labelOffset {GSize} label offset, the x- and y-distance between  
* the marker's latlng and the upper-left corner of the text div.  
*/  
function LabeledMarker(latlng, opt_opts){  
this.latlng_ = latlng;  
this.opts_ = opt_opts;  
 
this.initText_ = opt_opts.labelText || "";  
this.labelClass_ = opt_opts.labelClass || "markerLabel";  
this.labelOffset_ = opt_opts.labelOffset || new GSize(0, 0);  
 
this.clickable_ = opt_opts.clickable || true;  
 
if (opt_opts.draggable) {  
// This version of LabeledMarker doesn't support dragging.  
opt_opts.draggable = false;  
}  
 
GMarker.apply(this, arguments);  
}  
 
 
// It's a limitation of JavaScript inheritance that we can't conveniently  
// inherit from GMarker without having to run its constructor. In order for  
// the constructor to run, it requires some dummy GLatLng.  
LabeledMarker.prototype = new GMarker(new GLatLng(0, 0));  
 
/**  
* Is called by GMap2's addOverlay method. Creates the text div and adds it  
* to the relevant parent div.  
*  
* @param {GMap2} map the map that has had this labeledmarker added to it.  
*/  
LabeledMarker.prototype.initialize = function(map) {  
// Do the GMarker constructor first.  
GMarker.prototype.initialize.apply(this, arguments);  
 
this.map_ = map;  
this.setText(this.initText_);  
}  
 
/**  
* Create a new div for this label.  
*/  
LabeledMarker.prototype.makeDiv_ = function(map) {  
if (this.div_) {  
return;  
}  
this.div_ = document.createElement("div");  
this.div_.className = this.labelClass_;  
this.div_.style.position = "absolute";  
this.div_.style.cursor = "pointer";  
this.map_.getPane(G_MAP_MARKER_PANE).appendChild(this.div_);  
 
if (this.clickable_) {  
/**  
* Creates a closure for passing events through to the source marker  
* This is located in here to avoid cluttering the global namespace.  
* The downside is that the local variables from initialize() continue  
* to occupy space on the stack.  
*  
* @param {Object} object to receive event trigger.  
* @param {GEventListener} event to be triggered.  
*/  
function newEventPassthru(obj, event) {  
return function() {  
GEvent.trigger(obj, event);  
};  
}  
 
// Pass through events fired on the text div to the marker.  
var eventPassthrus = ['click', 'dblclick', 'mousedown', 'mouseup', 'mouseover', 'mouseout'];  
for(var i = 0; i < eventPassthrus.length; i++) {  
var name = eventPassthrus[i];  
GEvent.addDomListener(this.div_, name, newEventPassthru(this, name));  
}  
}  
}  
 
/**  
* Return the html in the div of this label, or "" if none is set  
*/  
LabeledMarker.prototype.getText = function(text) {  
if (this.div_) {  
return this.div_.innerHTML;  
} else {  
return "";  
}  
}  
 
/**  
* Set the html in the div of this label to text. If text is "" or null remove  
* the div.  
*/  
LabeledMarker.prototype.setText = function(text) {  
if (this.div_) {  
if (text) {  
this.div_.innerHTML = text;  
} else {  
// remove div  
GEvent.clearInstanceListeners(this.div_);  
this.div_.parentNode.removeChild(this.div_);  
this.div_ = null;  
}  
} else {  
if (text) {  
this.makeDiv_();  
this.div_.innerHTML = text;  
this.redraw();  
}  
}  
}  
 
/**  
* Move the text div based on current projection and zoom level, call the redraw()  
* handler in GMarker.  
*  
* @param {Boolean} force will be true when pixel coordinates need to be recomputed.  
*/  
LabeledMarker.prototype.redraw = function(force) {  
GMarker.prototype.redraw.apply(this, arguments);  
 
if (this.div_) {  
// Calculate the DIV coordinates of two opposite corners of our bounds to  
// get the size and position of our rectangle  
var p = this.map_.fromLatLngToDivPixel(this.latlng_);  
var z = GOverlay.getZIndex(this.latlng_.lat());  
 
// Now position our div based on the div coordinates of our bounds  
this.div_.style.left = (p.x + this.labelOffset_.width) + "px";  
this.div_.style.top = (p.y + this.labelOffset_.height) + "px";  
this.div_.style.zIndex = z; // in front of the marker  
}  
}  
 
/**  
* Remove the text div from the map pane, destroy event passthrus, and calls the  
* default remove() handler in GMarker.  
*/  
LabeledMarker.prototype.remove = function() {  
this.setText(null);  
GMarker.prototype.remove.apply(this, arguments);  
}  
 
/**  
* Return a copy of this overlay, for the parent Map to duplicate itself in full. This  
* is part of the Overlay interface and is used, for example, to copy everything in the  
* main view into the mini-map.  
*/  
LabeledMarker.prototype.copy = function() {  
return new LabeledMarker(this.latlng_, this.opt_opts_);  
}  
 
 Binary files a/origin-src/transitfeed-1.2.5/build/lib/gtfsscheduleviewer/files/mm_20_blue.png and /dev/null differ
 Binary files a/origin-src/transitfeed-1.2.5/build/lib/gtfsscheduleviewer/files/mm_20_blue_trans.png and /dev/null differ
 Binary files a/origin-src/transitfeed-1.2.5/build/lib/gtfsscheduleviewer/files/mm_20_red_trans.png and /dev/null differ
 Binary files a/origin-src/transitfeed-1.2.5/build/lib/gtfsscheduleviewer/files/mm_20_shadow.png and /dev/null differ
 Binary files a/origin-src/transitfeed-1.2.5/build/lib/gtfsscheduleviewer/files/mm_20_shadow_trans.png and /dev/null differ
 Binary files a/origin-src/transitfeed-1.2.5/build/lib/gtfsscheduleviewer/files/mm_20_yellow.png and /dev/null differ
html { overflow: hidden; }  
 
html, body {  
margin: 0;  
padding: 0;  
height: 100%;  
}  
 
body { margin: 5px; }  
 
#content {  
position: relative;  
margin-top: 5px;  
}  
 
#map-wrapper {  
position: relative;  
height: 100%;  
width: auto;  
left: 0;  
top: 0;  
z-index: 100;  
}  
 
#map {  
position: relative;  
height: 100%;  
width: auto;  
border: 1px solid #aaa;  
}  
 
#sidebar-wrapper {  
position: absolute;  
height: 100%;  
width: 220px;  
top: 0;  
border: 1px solid #aaa;  
overflow: auto;  
z-index: 300;  
}  
 
#sidebar {  
position: relative;  
width: auto;  
padding: 4px;  
overflow: hidden;  
}  
 
#topbar {  
position: relative;  
padding: 2px;  
border: 1px solid #aaa;  
margin: 0;  
}  
 
#topbar h1 {  
white-space: nowrap;  
overflow: hidden;  
font-size: 14pt;  
font-weight: bold;  
font-face:  
margin: 0;  
}  
 
 
body.sidebar-right #map-wrapper { margin-right: 229px; }  
body.sidebar-right #sidebar-wrapper { right: 0; }  
 
body.sidebar-left #map { margin-left: 229px; }  
body.sidebar-left #sidebar { left: 0; }  
 
body.nosidebar #map { margin: 0; }  
body.nosidebar #sidebar { display: none; }  
 
#bottombar {  
position: relative;  
padding: 2px;  
border: 1px solid #aaa;  
margin-top: 5px;  
display: none;  
}  
 
/* holly hack for IE to get position:bottom right  
see: http://www.positioniseverything.net/abs_relbugs.html  
\*/  
* html #topbar { height: 1px; }  
/* */  
 
body {  
font-family:helvetica,arial,sans, sans-serif;  
}  
h1 {  
margin-top: 0.5em;  
margin-bottom: 0.5em;  
}  
h2 {  
margin-top: 0.2em;  
margin-bottom: 0.2em;  
}  
h3 {  
margin-top: 0.2em;  
margin-bottom: 0.2em;  
}  
.tooltip {  
white-space: nowrap;  
padding: 2px;  
color: black;  
font-size: 12px;  
background-color: white;  
border: 1px solid black;  
cursor: pointer;  
filter:alpha(opacity=60);  
-moz-opacity: 0.6;  
opacity: 0.6;  
}  
#routeList {  
border: 1px solid black;  
overflow: auto;  
}  
.shortName {  
font-size: bigger;  
font-weight: bold;  
}  
.routeChoice,.tripChoice,.routeChoiceSelected,.tripChoiceSelected {  
white-space: nowrap;  
cursor: pointer;  
padding: 0px 2px;  
color: black;  
line-height: 1.4em;  
font-size: smaller;  
overflow: hidden;  
}  
.tripChoice {  
color: blue;  
}  
.routeChoiceSelected,.tripChoiceSelected {  
background-color: blue;  
color: white;  
}  
.tripSection {  
padding-left: 0px;  
font-size: 10pt;  
background-color: lightblue;  
}  
.patternSection {  
margin-left: 8px;  
padding-left: 2px;  
border-bottom: 1px solid grey;  
}  
.unusualPattern {  
background-color: #aaa;  
color: #444;  
}  
/* Following styles are used by location_editor.py */  
#edit {  
visibility: hidden;  
float: right;  
font-size: 80%;  
}  
#edit form {  
display: inline;  
}  
' Copyright 1999-2000 Adobe Systems Inc. All rights reserved. Permission to redistribute  
' granted provided that this file is not modified in any way. This file is provided with  
' absolutely no warranties of any kind.  
Function isSVGControlInstalled()  
on error resume next  
isSVGControlInstalled = IsObject(CreateObject("Adobe.SVGCtl"))  
end Function  
 
#!/usr/bin/python2.5  
#  
# Copyright (C) 2007 Google Inc.  
#  
# Licensed under the Apache License, Version 2.0 (the "License");  
# you may not use this file except in compliance with the License.  
# You may obtain a copy of the License at  
#  
# http://www.apache.org/licenses/LICENSE-2.0  
#  
# Unless required by applicable law or agreed to in writing, software  
# distributed under the License is distributed on an "AS IS" BASIS,  
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.  
# See the License for the specific language governing permissions and  
# limitations under the License.  
 
"""Output svg/xml data for a marey graph  
 
Marey graphs are a visualization form typically used for timetables. Time  
is on the x-axis and position on the y-axis. This module reads data from a  
transitfeed.Schedule and creates a marey graph in svg/xml format. The graph  
shows the speed between stops for each trip of a route.  
 
TODO: This module was taken from an internal Google tool. It works but is not  
well intergrated into transitfeed and schedule_viewer. Also, it has lots of  
ugly hacks to compensate set canvas size and so on which could be cleaned up.  
 
For a little more information see (I didn't make this URL ;-)  
http://transliteracies.english.ucsb.edu/post/research-project/research-clearinghouse-individual/research-reports/the-indexical-imagination-marey%e2%80%99s-graphic-method-and-the-technological-transformation-of-writing-in-the-nineteenth-century  
 
MareyGraph: Class, keeps cache of graph data and graph properties  
and draws marey graphs in svg/xml format on request.  
 
"""  
 
import itertools  
import transitfeed  
 
 
class MareyGraph:  
"""Produces and caches marey graph from transit feed data."""  
 
_MAX_ZOOM = 5.0 # change docstring of ChangeScaleFactor if this changes  
_DUMMY_SEPARATOR = 10 #pixel  
 
def __init__(self):  
# Timetablerelated state  
self._cache = str()  
self._stoplist = []  
self._tlist = []  
self._stations = []  
self._decorators = []  
 
# TODO: Initialize default values via constructor parameters  
# or via a class constants  
 
# Graph properties  
self._tspan = 30 # number of hours to display  
self._offset = 0 # starting hour  
self._hour_grid = 60 # number of pixels for an hour  
self._min_grid = 5 # number of pixels between subhour lines  
 
# Canvas properties  
self._zoomfactor = 0.9 # svg Scaling factor  
self._xoffset = 0 # move graph horizontally  
self._yoffset = 0 # move graph veritcally  
self._bgcolor = "lightgrey"  
 
# height/width of graph canvas before transform  
self._gwidth = self._tspan * self._hour_grid  
 
def Draw(self, stoplist=None, triplist=None, height=520):  
"""Main interface for drawing the marey graph.  
 
If called without arguments, the data generated in the previous call  
will be used. New decorators can be added between calls.  
 
Args:  
# Class Stop is defined in transitfeed.py  
stoplist: [Stop, Stop, ...]  
# Class Trip is defined in transitfeed.py  
triplist: [Trip, Trip, ...]  
 
Returns:  
# A string that contain a svg/xml web-page with a marey graph.  
" <svg width="1440" height="520" version="1.1" ... "  
"""  
output = str()  
if not triplist:  
triplist = []  
if not stoplist:  
stoplist = []  
 
if not self._cache or triplist or stoplist:  
self._gheight = height  
self._tlist=triplist  
self._slist=stoplist  
self._decorators = []  
self._stations = self._BuildStations(stoplist)  
self._cache = "%s %s %s %s" % (self._DrawBox(),  
self._DrawHours(),  
self._DrawStations(),  
self._DrawTrips(triplist))  
 
 
 
output = "%s %s %s %s" % (self._DrawHeader(),  
self._cache,  
self._DrawDecorators(),  
self._DrawFooter())  
return output  
 
def _DrawHeader(self):  
svg_header = """  
<svg width="%s" height="%s" version="1.1"  
xmlns="http://www.w3.org/2000/svg">  
<script type="text/ecmascript"><![CDATA[  
function init(evt) {  
if ( window.svgDocument == null )  
svgDocument = evt.target.ownerDocument;  
}  
var oldLine = 0;  
var oldStroke = 0;  
var hoffset= %s; // Data from python  
 
function parseLinePoints(pointnode){  
var wordlist = pointnode.split(" ");  
var xlist = new Array();  
var h;  
var m;  
// TODO: add linebreaks as appropriate  
var xstr = " Stop Times :";  
for (i=0;i<wordlist.length;i=i+2){  
var coord = wordlist[i].split(",");  
h = Math.floor(parseInt((coord[0])-20)/60);  
m = parseInt((coord[0]-20))%%60;  
xstr = xstr +" "+ (hoffset+h) +":"+m;  
}  
 
return xstr;  
}  
 
function LineClick(tripid, x) {  
var line = document.getElementById(tripid);  
if (oldLine)  
oldLine.setAttribute("stroke",oldStroke);  
oldLine = line;  
oldStroke = line.getAttribute("stroke");  
 
line.setAttribute("stroke","#fff");  
 
var dynTxt = document.getElementById("dynamicText");  
var tripIdTxt = document.createTextNode(x);  
while (dynTxt.hasChildNodes()){  
dynTxt.removeChild(dynTxt.firstChild);  
}  
dynTxt.appendChild(tripIdTxt);  
}  
]]> </script>  
<style type="text/css"><![CDATA[  
.T { fill:none; stroke-width:1.5 }  
.TB { fill:none; stroke:#e20; stroke-width:2 }  
.Station { fill:none; stroke-width:1 }  
.Dec { fill:none; stroke-width:1.5 }  
.FullHour { fill:none; stroke:#eee; stroke-width:1 }  
.SubHour { fill:none; stroke:#ddd; stroke-width:1 }  
.Label { fill:#aaa; font-family:Helvetica,Arial,sans;  
text-anchor:middle }  
.Info { fill:#111; font-family:Helvetica,Arial,sans;  
text-anchor:start; }  
]]></style>  
<text class="Info" id="dynamicText" x="0" y="%d"></text>  
<g id="mcanvas" transform="translate(%s,%s)">  
<g id="zcanvas" transform="scale(%s)">  
 
""" % (self._gwidth + self._xoffset + 20, self._gheight + 15,  
self._offset, self._gheight + 10,  
self._xoffset, self._yoffset, self._zoomfactor)  
 
return svg_header  
 
def _DrawFooter(self):  
return "</g></g></svg>"  
 
def _DrawDecorators(self):  
"""Used to draw fancy overlays on trip graphs."""  
return " ".join(self._decorators)  
 
def _DrawBox(self):  
tmpstr = """<rect x="%s" y="%s" width="%s" height="%s"  
fill="lightgrey" stroke="%s" stroke-width="2" />  
""" % (0, 0, self._gwidth + 20, self._gheight, self._bgcolor)  
return tmpstr  
 
def _BuildStations(self, stoplist):  
"""Dispatches the best algorithm for calculating station line position.  
 
Args:  
# Class Stop is defined in transitfeed.py  
stoplist: [Stop, Stop, ...]  
# Class Trip is defined in transitfeed.py  
triplist: [Trip, Trip, ...]  
 
Returns:  
# One integer y-coordinate for each station normalized between  
# 0 and X, where X is the height of the graph in pixels  
[0, 33, 140, ... , X]  
"""  
stations = []  
dists = self._EuclidianDistances(stoplist)  
stations = self._CalculateYLines(dists)  
return stations  
 
def _EuclidianDistances(self,slist):  
"""Calculate euclidian distances between stops.  
 
Uses the stoplists long/lats to approximate distances  
between stations and build a list with y-coordinates for the  
horizontal lines in the graph.  
 
Args:  
# Class Stop is defined in transitfeed.py  
stoplist: [Stop, Stop, ...]  
 
Returns:  
# One integer for each pair of stations  
# indicating the approximate distance  
[0,33,140, ... ,X]  
"""  
e_dists2 = [transitfeed.ApproximateDistanceBetweenStops(stop, tail) for  
(stop,tail) in itertools.izip(slist, slist[1:])]  
 
return e_dists2  
 
def _CalculateYLines(self, dists):  
"""Builds a list with y-coordinates for the horizontal lines in the graph.  
 
Args:  
# One integer for each pair of stations  
# indicating the approximate distance  
dists: [0,33,140, ... ,X]  
 
Returns:  
# One integer y-coordinate for each station normalized between  
# 0 and X, where X is the height of the graph in pixels  
[0, 33, 140, ... , X]  
"""  
tot_dist = sum(dists)  
if tot_dist > 0:  
pixel_dist = [float(d * (self._gheight-20))/tot_dist for d in dists]  
pixel_grid = [0]+[int(pd + sum(pixel_dist[0:i])) for i,pd in  
enumerate(pixel_dist)]  
else:  
pixel_grid = []  
 
return pixel_grid  
 
def _TravelTimes(self,triplist,index=0):  
""" Calculate distances and plot stops.  
 
Uses a timetable to approximate distances  
between stations  
 
Args:  
# Class Trip is defined in transitfeed.py  
triplist: [Trip, Trip, ...]  
# (Optional) Index of Triplist prefered for timetable Calculation  
index: 3  
 
Returns:  
# One integer for each pair of stations  
# indicating the approximate distance  
[0,33,140, ... ,X]  
"""  
 
def DistanceInTravelTime(dep_secs, arr_secs):  
t_dist = arr_secs-dep_secs  
if t_dist<0:  
t_dist = self._DUMMY_SEPARATOR # min separation  
return t_dist  
 
if not triplist:  
return []  
 
if 0 < index < len(triplist):  
trip = triplist[index]  
else:  
trip = triplist[0]  
 
t_dists2 = [DistanceInTravelTime(stop[3],tail[2]) for (stop,tail)  
in itertools.izip(trip.GetTimeStops(),trip.GetTimeStops()[1:])]  
return t_dists2  
 
def _AddWarning(self, str):  
print str  
 
def _DrawTrips(self,triplist,colpar=""):  
"""Generates svg polylines for each transit trip.  
 
Args:  
# Class Trip is defined in transitfeed.py  
[Trip, Trip, ...]  
 
Returns:  
# A string containing a polyline tag for each trip  
' <polyline class="T" stroke="#336633" points="433,0 ...'  
"""  
 
stations = []  
if not self._stations and triplist:  
self._stations = self._CalculateYLines(self._TravelTimes(triplist))  
if not self._stations:  
self._AddWarning("Failed to use traveltimes for graph")  
self._stations = self._CalculateYLines(self._Uniform(triplist))  
if not self._stations:  
self._AddWarning("Failed to calculate station distances")  
return  
 
stations = self._stations  
tmpstrs = []  
servlist = []  
for t in triplist:  
if not colpar:  
if t.service_id not in servlist:  
servlist.append(t.service_id)  
shade = int(servlist.index(t.service_id) * (200/len(servlist))+55)  
color = "#00%s00" % hex(shade)[2:4]  
else:  
color=colpar  
 
start_offsets = [0]  
first_stop = t.GetTimeStops()[0]  
 
for j,freq_offset in enumerate(start_offsets):  
if j>0 and not colpar:  
color="purple"  
scriptcall = 'onmouseover="LineClick(\'%s\',\'Trip %s starting %s\')"' % (t.trip_id,  
t.trip_id, transitfeed.FormatSecondsSinceMidnight(t.GetStartTime()))  
tmpstrhead = '<polyline class="T" id="%s" stroke="%s" %s points="' % \  
(str(t.trip_id),color, scriptcall)  
tmpstrs.append(tmpstrhead)  
 
for i, s in enumerate(t.GetTimeStops()):  
arr_t = s[0]  
dep_t = s[1]  
if arr_t is None or dep_t is None:  
continue  
arr_x = int(arr_t/3600.0 * self._hour_grid) - self._hour_grid * self._offset  
dep_x = int(dep_t/3600.0 * self._hour_grid) - self._hour_grid * self._offset  
tmpstrs.append("%s,%s " % (int(arr_x+20), int(stations[i]+20)))  
tmpstrs.append("%s,%s " % (int(dep_x+20), int(stations[i]+20)))  
tmpstrs.append('" />')  
return "".join(tmpstrs)  
 
def _Uniform(self, triplist):  
"""Fallback to assuming uniform distance between stations"""  
# This should not be neseccary, but we are in fallback mode  
longest = max([len(t.GetTimeStops()) for t in triplist])  
return [100] * longest  
 
def _DrawStations(self, color="#aaa"):  
"""Generates svg with a horizontal line for each station/stop.  
 
Args:  
# Class Stop is defined in transitfeed.py  
stations: [Stop, Stop, ...]  
 
Returns:  
# A string containing a polyline tag for each stop  
" <polyline class="Station" stroke="#336633" points="20,0 ..."  
"""  
stations=self._stations  
tmpstrs = []  
for y in stations:  
tmpstrs.append(' <polyline class="Station" stroke="%s" \  
points="%s,%s, %s,%s" />' %(color,20,20+y+.5,self._gwidth+20,20+y+.5))  
return "".join(tmpstrs)  
 
def _DrawHours(self):  
"""Generates svg to show a vertical hour and sub-hour grid  
 
Returns:  
# A string containing a polyline tag for each grid line  
" <polyline class="FullHour" points="20,0 ..."  
"""  
tmpstrs = []  
for i in range(0, self._gwidth, self._min_grid):  
if i % self._hour_grid == 0:  
tmpstrs.append('<polyline class="FullHour" points="%d,%d, %d,%d" />' \  
% (i + .5 + 20, 20, i + .5 + 20, self._gheight))  
tmpstrs.append('<text class="Label" x="%d" y="%d">%d</text>'  
% (i + 20, 20,  
(i / self._hour_grid + self._offset) % 24))  
else:  
tmpstrs.append('<polyline class="SubHour" points="%d,%d,%d,%d" />' \  
% (i + .5 + 20, 20, i + .5 + 20, self._gheight))  
return "".join(tmpstrs)  
 
def AddStationDecoration(self, index, color="#f00"):  
"""Flushes existing decorations and highlights the given station-line.  
 
Args:  
# Integer, index of stop to be highlighted.  
index: 4  
# An optional string with a html color code  
color: "#fff"  
"""  
tmpstr = str()  
num_stations = len(self._stations)  
ind = int(index)  
if self._stations:  
if 0<ind<num_stations:  
y = self._stations[ind]  
tmpstr = '<polyline class="Dec" stroke="%s" points="%s,%s,%s,%s" />' \  
% (color, 20, 20+y+.5, self._gwidth+20, 20+y+.5)  
self._decorators.append(tmpstr)  
 
def AddTripDecoration(self, triplist, color="#f00"):  
"""Flushes existing decorations and highlights the given trips.  
 
Args:  
# Class Trip is defined in transitfeed.py  
triplist: [Trip, Trip, ...]  
# An optional string with a html color code  
color: "#fff"  
"""  
tmpstr = self._DrawTrips(triplist,color)  
self._decorators.append(tmpstr)  
 
def ChangeScaleFactor(self, newfactor):  
"""Changes the zoom of the graph manually.  
 
1.0 is the original canvas size.  
 
Args:  
# float value between 0.0 and 5.0  
newfactor: 0.7  
"""  
if float(newfactor) > 0 and float(newfactor) < self._MAX_ZOOM:  
self._zoomfactor = newfactor  
 
def ScaleLarger(self):  
"""Increases the zoom of the graph one step (0.1 units)."""  
newfactor = self._zoomfactor + 0.1  
if float(newfactor) > 0 and float(newfactor) < self._MAX_ZOOM:  
self._zoomfactor = newfactor  
 
def ScaleSmaller(self):  
"""Decreases the zoom of the graph one step(0.1 units)."""  
newfactor = self._zoomfactor - 0.1  
if float(newfactor) > 0 and float(newfactor) < self._MAX_ZOOM:  
self._zoomfactor = newfactor  
 
def ClearDecorators(self):  
"""Removes all the current decorators.  
"""  
self._decorators = []  
 
def AddTextStripDecoration(self,txtstr):  
tmpstr = '<text class="Info" x="%d" y="%d">%s</text>' % (0,  
20 + self._gheight, txtstr)  
self._decorators.append(tmpstr)  
 
def SetSpan(self, first_arr, last_arr, mint=5 ,maxt=30):  
s_hour = (first_arr / 3600) - 1  
e_hour = (last_arr / 3600) + 1  
self._offset = max(min(s_hour, 23), 0)  
self._tspan = max(min(e_hour - s_hour, maxt), mint)  
self._gwidth = self._tspan * self._hour_grid  
 
#!/usr/bin/python2.5  
 
# Copyright (C) 2007 Google Inc.  
#  
# Licensed under the Apache License, Version 2.0 (the "License");  
# you may not use this file except in compliance with the License.  
# You may obtain a copy of the License at  
#  
# http://www.apache.org/licenses/LICENSE-2.0  
#  
# Unless required by applicable law or agreed to in writing, software  
# distributed under the License is distributed on an "AS IS" BASIS,  
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.  
# See the License for the specific language governing permissions and  
# limitations under the License.  
 
"""Expose some modules in this package.  
 
Before transitfeed version 1.2.4 all our library code was distributed in a  
one file module, transitfeed.py, and could be used as  
 
import transitfeed  
schedule = transitfeed.Schedule()  
 
At that time the module (one file, transitfeed.py) was converted into a  
package (a directory named transitfeed containing __init__.py and multiple .py  
files). Classes and attributes exposed by the old module may still be imported  
in the same way. Indeed, code that depends on the library <em>should</em>  
continue to use import commands such as the above and ignore _transitfeed.  
"""  
 
from _transitfeed import *  
 
__version__ = _transitfeed.__version__  
 
#!/usr/bin/python2.5  
 
# Copyright (C) 2007 Google Inc.  
#  
# Licensed under the Apache License, Version 2.0 (the "License");  
# you may not use this file except in compliance with the License.  
# You may obtain a copy of the License at  
#  
# http://www.apache.org/licenses/LICENSE-2.0  
#  
# Unless required by applicable law or agreed to in writing, software  
# distributed under the License is distributed on an "AS IS" BASIS,  
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.  
# See the License for the specific language governing permissions and  
# limitations under the License.  
 
"""Easy interface for handling a Google Transit Feed file.  
 
Do not import this module directly. Thanks to __init__.py you should do  
something like:  
 
import transitfeed  
schedule = transitfeed.Schedule()  
...  
 
This module is a library to help you create, read and write Google  
Transit Feed files. Refer to the feed specification, available at  
http://code.google.com/transit/spec/transit_feed_specification.htm, for a  
complete description how the transit feed represents a transit schedule. This  
library supports all required parts of the specification but does not yet  
support all optional parts. Patches welcome!  
 
The specification describes several tables such as stops, routes and trips.  
In a feed file these are stored as comma separeted value files. This library  
represents each row of these tables with a single Python object. This object has  
attributes for each value on the row. For example, schedule.AddStop returns a  
Stop object which has attributes such as stop_lat and stop_name.  
 
Schedule: Central object of the parser  
GenericGTFSObject: A base class for each of the objects below  
Route: Represents a single route  
Trip: Represents a single trip  
Stop: Represents a single stop  
ServicePeriod: Represents a single service, a set of dates  
Agency: Represents the agency in this feed  
Transfer: Represents a single transfer rule  
TimeToSecondsSinceMidnight(): Convert HH:MM:SS into seconds since midnight.  
FormatSecondsSinceMidnight(s): Formats number of seconds past midnight into a string  
"""  
 
# TODO: Preserve arbitrary columns?  
 
import bisect  
import cStringIO as StringIO  
import codecs  
from transitfeed.util import defaultdict  
import csv  
import datetime  
import logging  
import math  
import os  
import random  
try:  
import sqlite3 as sqlite  
except ImportError:  
from pysqlite2 import dbapi2 as sqlite  
import re  
import tempfile  
import time  
import warnings  
# Objects in a schedule (Route, Trip, etc) should not keep a strong reference  
# to the Schedule object to avoid a reference cycle. Schedule needs to use  
# __del__ to cleanup its temporary file. The garbage collector can't handle  
# reference cycles containing objects with custom cleanup code.  
import weakref  
import zipfile  
 
OUTPUT_ENCODING = 'utf-8'  
MAX_DISTANCE_FROM_STOP_TO_SHAPE = 1000  
MAX_DISTANCE_BETWEEN_STOP_AND_PARENT_STATION_WARNING = 100.0  
MAX_DISTANCE_BETWEEN_STOP_AND_PARENT_STATION_ERROR = 1000.0  
 
__version__ = '1.2.5'  
 
 
def EncodeUnicode(text):  
"""  
Optionally encode text and return it. The result should be safe to print.  
"""  
if type(text) == type(u''):  
return text.encode(OUTPUT_ENCODING)  
else:  
return text  
 
 
# These are used to distinguish between errors (not allowed by the spec)  
# and warnings (not recommended) when reporting issues.  
TYPE_ERROR = 0  
TYPE_WARNING = 1  
 
 
class ProblemReporterBase:  
"""Base class for problem reporters. Tracks the current context and creates  
an exception object for each problem. Subclasses must implement  
_Report(self, e)"""  
 
def __init__(self):  
self.ClearContext()  
 
def ClearContext(self):  
"""Clear any previous context."""  
self._context = None  
 
def SetFileContext(self, file_name, row_num, row, headers):  
"""Save the current context to be output with any errors.  
 
Args:  
file_name: string  
row_num: int  
row: list of strings  
headers: list of column headers, its order corresponding to row's  
"""  
self._context = (file_name, row_num, row, headers)  
 
def FeedNotFound(self, feed_name, context=None):  
e = FeedNotFound(feed_name=feed_name, context=context,  
context2=self._context)  
self._Report(e)  
 
def UnknownFormat(self, feed_name, context=None):  
e = UnknownFormat(feed_name=feed_name, context=context,  
context2=self._context)  
self._Report(e)  
 
def FileFormat(self, problem, context=None):  
e = FileFormat(problem=problem, context=context,  
context2=self._context)  
self._Report(e)  
 
def MissingFile(self, file_name, context=None):  
e = MissingFile(file_name=file_name, context=context,  
context2=self._context)  
self._Report(e)  
 
def UnknownFile(self, file_name, context=None):  
e = UnknownFile(file_name=file_name, context=context,  
context2=self._context, type=TYPE_WARNING)  
self._Report(e)  
 
def EmptyFile(self, file_name, context=None):  
e = EmptyFile(file_name=file_name, context=context,  
context2=self._context)  
self._Report(e)  
 
def MissingColumn(self, file_name, column_name, context=None):  
e = MissingColumn(file_name=file_name, column_name=column_name,  
context=context, context2=self._context)  
self._Report(e)  
 
def UnrecognizedColumn(self, file_name, column_name, context=None):  
e = UnrecognizedColumn(file_name=file_name, column_name=column_name,  
context=context, context2=self._context,  
type=TYPE_WARNING)  
self._Report(e)  
 
def CsvSyntax(self, description=None, context=None, type=TYPE_ERROR):  
e = CsvSyntax(description=description, context=context,  
context2=self._context, type=type)  
self._Report(e)  
 
def DuplicateColumn(self, file_name, header, count, type=TYPE_ERROR,  
context=None):  
e = DuplicateColumn(file_name=file_name,  
header=header,  
count=count,  
type=type,  
context=context,  
context2=self._context)  
self._Report(e)  
 
def MissingValue(self, column_name, reason=None, context=None):  
e = MissingValue(column_name=column_name, reason=reason, context=context,  
context2=self._context)  
self._Report(e)  
 
def InvalidValue(self, column_name, value, reason=None, context=None,  
type=TYPE_ERROR):  
e = InvalidValue(column_name=column_name, value=value, reason=reason,  
context=context, context2=self._context, type=type)  
self._Report(e)  
 
def DuplicateID(self, column_names, values, context=None, type=TYPE_ERROR):  
if isinstance(column_names, tuple):  
column_names = '(' + ', '.join(column_names) + ')'  
if isinstance(values, tuple):  
values = '(' + ', '.join(values) + ')'  
e = DuplicateID(column_name=column_names, value=values,  
context=context, context2=self._context, type=type)  
self._Report(e)  
 
def UnusedStop(self, stop_id, stop_name, context=None):  
e = UnusedStop(stop_id=stop_id, stop_name=stop_name,  
context=context, context2=self._context, type=TYPE_WARNING)  
self._Report(e)  
 
def UsedStation(self, stop_id, stop_name, context=None):  
e = UsedStation(stop_id=stop_id, stop_name=stop_name,  
context=context, context2=self._context, type=TYPE_ERROR)  
self._Report(e)  
 
def StopTooFarFromParentStation(self, stop_id, stop_name, parent_stop_id,  
parent_stop_name, distance,  
type=TYPE_WARNING, context=None):  
e = StopTooFarFromParentStation(  
stop_id=stop_id, stop_name=stop_name,  
parent_stop_id=parent_stop_id,  
parent_stop_name=parent_stop_name, distance=distance,  
context=context, context2=self._context, type=type)  
self._Report(e)  
 
def StopsTooClose(self, stop_name_a, stop_id_a, stop_name_b, stop_id_b,  
distance, type=TYPE_WARNING, context=None):  
e = StopsTooClose(  
stop_name_a=stop_name_a, stop_id_a=stop_id_a, stop_name_b=stop_name_b,  
stop_id_b=stop_id_b, distance=distance, context=context,  
context2=self._context, type=type)  
self._Report(e)  
 
def StationsTooClose(self, stop_name_a, stop_id_a, stop_name_b, stop_id_b,  
distance, type=TYPE_WARNING, context=None):  
e = StationsTooClose(  
stop_name_a=stop_name_a, stop_id_a=stop_id_a, stop_name_b=stop_name_b,  
stop_id_b=stop_id_b, distance=distance, context=context,  
context2=self._context, type=type)  
self._Report(e)  
 
def DifferentStationTooClose(self, stop_name, stop_id,  
station_stop_name, station_stop_id,  
distance, type=TYPE_WARNING, context=None):  
e = DifferentStationTooClose(  
stop_name=stop_name, stop_id=stop_id,  
station_stop_name=station_stop_name, station_stop_id=station_stop_id,  
distance=distance, context=context, context2=self._context, type=type)  
self._Report(e)  
 
def StopTooFarFromShapeWithDistTraveled(self, trip_id, stop_name, stop_id,  
shape_dist_traveled, shape_id,  
distance, max_distance,  
type=TYPE_WARNING):  
e = StopTooFarFromShapeWithDistTraveled(  
trip_id=trip_id, stop_name=stop_name, stop_id=stop_id,  
shape_dist_traveled=shape_dist_traveled, shape_id=shape_id,  
distance=distance, max_distance=max_distance, type=type)  
self._Report(e)  
 
def ExpirationDate(self, expiration, context=None):  
e = ExpirationDate(expiration=expiration, context=context,  
context2=self._context, type=TYPE_WARNING)  
self._Report(e)  
 
def FutureService(self, start_date, context=None):  
e = FutureService(start_date=start_date, context=context,  
context2=self._context, type=TYPE_WARNING)  
self._Report(e)  
 
def InvalidLineEnd(self, bad_line_end, context=None):  
"""bad_line_end is a human readable string."""  
e = InvalidLineEnd(bad_line_end=bad_line_end, context=context,  
context2=self._context, type=TYPE_WARNING)  
self._Report(e)  
 
def TooFastTravel(self, trip_id, prev_stop, next_stop, dist, time, speed,  
type=TYPE_ERROR):  
e = TooFastTravel(trip_id=trip_id, prev_stop=prev_stop,  
next_stop=next_stop, time=time, dist=dist, speed=speed,  
context=None, context2=self._context, type=type)  
self._Report(e)  
 
def StopWithMultipleRouteTypes(self, stop_name, stop_id, route_id1, route_id2,  
context=None):  
e = StopWithMultipleRouteTypes(stop_name=stop_name, stop_id=stop_id,  
route_id1=route_id1, route_id2=route_id2,  
context=context, context2=self._context,  
type=TYPE_WARNING)  
self._Report(e)  
 
def DuplicateTrip(self, trip_id1, route_id1, trip_id2, route_id2,  
context=None):  
e = DuplicateTrip(trip_id1=trip_id1, route_id1=route_id1, trip_id2=trip_id2,  
route_id2=route_id2, context=context,  
context2=self._context, type=TYPE_WARNING)  
self._Report(e)  
 
def OtherProblem(self, description, context=None, type=TYPE_ERROR):  
e = OtherProblem(description=description,  
context=context, context2=self._context, type=type)  
self._Report(e)  
 
def TooManyDaysWithoutService(self,  
first_day_without_service,  
last_day_without_service,  
consecutive_days_without_service,  
context=None,  
type=TYPE_WARNING):  
e = TooManyDaysWithoutService(  
first_day_without_service=first_day_without_service,  
last_day_without_service=last_day_without_service,  
consecutive_days_without_service=consecutive_days_without_service,  
context=context,  
context2=self._context,  
type=type)  
self._Report(e)  
 
class ProblemReporter(ProblemReporterBase):  
"""This is a basic problem reporter that just prints to console."""  
def _Report(self, e):  
context = e.FormatContext()  
if context:  
print context  
print EncodeUnicode(self._LineWrap(e.FormatProblem(), 78))  
 
@staticmethod  
def _LineWrap(text, width):  
"""  
A word-wrap function that preserves existing line breaks  
and most spaces in the text. Expects that existing line  
breaks are posix newlines (\n).  
 
Taken from:  
http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/148061  
"""  
return reduce(lambda line, word, width=width: '%s%s%s' %  
(line,  
' \n'[(len(line) - line.rfind('\n') - 1 +  
len(word.split('\n', 1)[0]) >= width)],  
word),  
text.split(' ')  
)  
 
 
class ExceptionWithContext(Exception):  
def __init__(self, context=None, context2=None, **kwargs):  
"""Initialize an exception object, saving all keyword arguments in self.  
context and context2, if present, must be a tuple of (file_name, row_num,  
row, headers). context2 comes from ProblemReporter.SetFileContext. context  
was passed in with the keyword arguments. context2 is ignored if context  
is present."""  
Exception.__init__(self)  
 
if context:  
self.__dict__.update(self.ContextTupleToDict(context))  
elif context2:  
self.__dict__.update(self.ContextTupleToDict(context2))  
self.__dict__.update(kwargs)  
 
if ('type' in kwargs) and (kwargs['type'] == TYPE_WARNING):  
self._type = TYPE_WARNING  
else:  
self._type = TYPE_ERROR  
 
def GetType(self):  
return self._type  
 
def IsError(self):  
return self._type == TYPE_ERROR  
 
def IsWarning(self):  
return self._type == TYPE_WARNING  
 
CONTEXT_PARTS = ['file_name', 'row_num', 'row', 'headers']  
@staticmethod  
def ContextTupleToDict(context):  
"""Convert a tuple representing a context into a dict of (key, value) pairs"""  
d = {}  
if not context:  
return d  
for k, v in zip(ExceptionWithContext.CONTEXT_PARTS, context):  
if v != '' and v != None: # Don't ignore int(0), a valid row_num  
d[k] = v  
return d  
 
def __str__(self):  
return self.FormatProblem()  
 
def GetDictToFormat(self):  
"""Return a copy of self as a dict, suitable for passing to FormatProblem"""  
d = {}  
for k, v in self.__dict__.items():  
# TODO: Better handling of unicode/utf-8 within Schedule objects.  
# Concatinating a unicode and utf-8 str object causes an exception such  
# as "UnicodeDecodeError: 'ascii' codec can't decode byte ..." as python  
# tries to convert the str to a unicode. To avoid that happening within  
# the problem reporter convert all unicode attributes to utf-8.  
# Currently valid utf-8 fields are converted to unicode in _ReadCsvDict.  
# Perhaps all fields should be left as utf-8.  
d[k] = EncodeUnicode(v)  
return d  
 
def FormatProblem(self, d=None):  
"""Return a text string describing the problem.  
 
Args:  
d: map returned by GetDictToFormat with with formatting added  
"""  
if not d:  
d = self.GetDictToFormat()  
 
output_error_text = self.__class__.ERROR_TEXT % d  
if ('reason' in d) and d['reason']:  
return '%s\n%s' % (output_error_text, d['reason'])  
else:  
return output_error_text  
 
def FormatContext(self):  
"""Return a text string describing the context"""  
text = ''  
if hasattr(self, 'feed_name'):  
text += "In feed '%s': " % self.feed_name  
if hasattr(self, 'file_name'):  
text += self.file_name  
if hasattr(self, 'row_num'):  
text += ":%i" % self.row_num  
if hasattr(self, 'column_name'):  
text += " column %s" % self.column_name  
return text  
 
def __cmp__(self, y):  
"""Return an int <0/0/>0 when self is more/same/less significant than y.  
 
Subclasses should define this if exceptions should be listed in something  
other than the order they are reported.  
 
Args:  
y: object to compare to self  
 
Returns:  
An int which is negative if self is more significant than y, 0 if they  
are similar significance and positive if self is less significant than  
y. Returning a float won't work.  
 
Raises:  
TypeError by default, meaning objects of the type can not be compared.  
"""  
raise TypeError("__cmp__ not defined")  
 
 
class MissingFile(ExceptionWithContext):  
ERROR_TEXT = "File %(file_name)s is not found"  
 
class EmptyFile(ExceptionWithContext):  
ERROR_TEXT = "File %(file_name)s is empty"  
 
class UnknownFile(ExceptionWithContext):  
ERROR_TEXT = 'The file named %(file_name)s was not expected.\n' \  
'This may be a misspelled file name or the file may be ' \  
'included in a subdirectory. Please check spellings and ' \  
'make sure that there are no subdirectories within the feed'  
 
class FeedNotFound(ExceptionWithContext):  
ERROR_TEXT = 'Couldn\'t find a feed named %(feed_name)s'  
 
class UnknownFormat(ExceptionWithContext):  
ERROR_TEXT = 'The feed named %(feed_name)s had an unknown format:\n' \  
'feeds should be either .zip files or directories.'  
 
class FileFormat(ExceptionWithContext):  
ERROR_TEXT = 'Files must be encoded in utf-8 and may not contain ' \  
'any null bytes (0x00). %(file_name)s %(problem)s.'  
 
class MissingColumn(ExceptionWithContext):  
ERROR_TEXT = 'Missing column %(column_name)s in file %(file_name)s'  
 
class UnrecognizedColumn(ExceptionWithContext):  
ERROR_TEXT = 'Unrecognized column %(column_name)s in file %(file_name)s. ' \  
'This might be a misspelled column name (capitalization ' \  
'matters!). Or it could be extra information (such as a ' \  
'proposed feed extension) that the validator doesn\'t know ' \  
'about yet. Extra information is fine; this warning is here ' \  
'to catch misspelled optional column names.'  
 
class CsvSyntax(ExceptionWithContext):  
ERROR_TEXT = '%(description)s'  
 
class DuplicateColumn(ExceptionWithContext):  
ERROR_TEXT = 'Column %(header)s appears %(count)i times in file %(file_name)s'  
 
class MissingValue(ExceptionWithContext):  
ERROR_TEXT = 'Missing value for column %(column_name)s'  
 
class InvalidValue(ExceptionWithContext):  
ERROR_TEXT = 'Invalid value %(value)s in field %(column_name)s'  
 
class DuplicateID(ExceptionWithContext):  
ERROR_TEXT = 'Duplicate ID %(value)s in column %(column_name)s'  
 
class UnusedStop(ExceptionWithContext):  
ERROR_TEXT = "%(stop_name)s (ID %(stop_id)s) isn't used in any trips"  
 
class UsedStation(ExceptionWithContext):  
ERROR_TEXT = "%(stop_name)s (ID %(stop_id)s) has location_type=1 " \  
"(station) so it should not appear in stop_times"  
 
class StopTooFarFromParentStation(ExceptionWithContext):  
ERROR_TEXT = (  
"%(stop_name)s (ID %(stop_id)s) is too far from its parent station "  
"%(parent_stop_name)s (ID %(parent_stop_id)s) : %(distance).2f meters.")  
def __cmp__(self, y):  
# Sort in decreasing order because more distance is more significant.  
return cmp(y.distance, self.distance)  
 
 
class StopsTooClose(ExceptionWithContext):  
ERROR_TEXT = (  
"The stops \"%(stop_name_a)s\" (ID %(stop_id_a)s) and \"%(stop_name_b)s\""  
" (ID %(stop_id_b)s) are %(distance)0.2fm apart and probably represent "  
"the same location.")  
def __cmp__(self, y):  
# Sort in increasing order because less distance is more significant.  
return cmp(self.distance, y.distance)  
 
class StationsTooClose(ExceptionWithContext):  
ERROR_TEXT = (  
"The stations \"%(stop_name_a)s\" (ID %(stop_id_a)s) and "  
"\"%(stop_name_b)s\" (ID %(stop_id_b)s) are %(distance)0.2fm apart and "  
"probably represent the same location.")  
def __cmp__(self, y):  
# Sort in increasing order because less distance is more significant.  
return cmp(self.distance, y.distance)  
 
class DifferentStationTooClose(ExceptionWithContext):  
ERROR_TEXT = (  
"The parent_station of stop \"%(stop_name)s\" (ID %(stop_id)s) is not "  
"station \"%(station_stop_name)s\" (ID %(station_stop_id)s) but they are "  
"only %(distance)0.2fm apart.")  
def __cmp__(self, y):  
# Sort in increasing order because less distance is more significant.  
return cmp(self.distance, y.distance)  
 
class StopTooFarFromShapeWithDistTraveled(ExceptionWithContext):  
ERROR_TEXT = (  
"For trip %(trip_id)s the stop \"%(stop_name)s\" (ID %(stop_id)s) is "  
"%(distance).0f meters away from the corresponding point "  
"(shape_dist_traveled: %(shape_dist_traveled)f) on shape %(shape_id)s. "  
"It should be closer than %(max_distance).0f meters.")  
def __cmp__(self, y):  
# Sort in decreasing order because more distance is more significant.  
return cmp(y.distance, self.distance)  
 
 
class TooManyDaysWithoutService(ExceptionWithContext):  
ERROR_TEXT = "There are %(consecutive_days_without_service)i consecutive"\  
" days, from %(first_day_without_service)s to" \  
" %(last_day_without_service)s, without any scheduled service." \  
" Please ensure this is intentional."  
 
 
class ExpirationDate(ExceptionWithContext):  
def FormatProblem(self, d=None):  
if not d:  
d = self.GetDictToFormat()  
expiration = d['expiration']  
formatted_date = time.strftime("%B %d, %Y",  
time.localtime(expiration))  
if (expiration < time.mktime(time.localtime())):  
return "This feed expired on %s" % formatted_date  
else:  
return "This feed will soon expire, on %s" % formatted_date  
 
class FutureService(ExceptionWithContext):  
def FormatProblem(self, d=None):  
if not d:  
d = self.GetDictToFormat()  
formatted_date = time.strftime("%B %d, %Y", time.localtime(d['start_date']))  
return ("The earliest service date in this feed is in the future, on %s. "  
"Published feeds must always include the current date." %  
formatted_date)  
 
 
class InvalidLineEnd(ExceptionWithContext):  
ERROR_TEXT = "Each line must end with CR LF or LF except for the last line " \  
"of the file. This line ends with \"%(bad_line_end)s\"."  
 
class StopWithMultipleRouteTypes(ExceptionWithContext):  
ERROR_TEXT = "Stop %(stop_name)s (ID=%(stop_id)s) belongs to both " \  
"subway (ID=%(route_id1)s) and bus line (ID=%(route_id2)s)."  
 
class TooFastTravel(ExceptionWithContext):  
def FormatProblem(self, d=None):  
if not d:  
d = self.GetDictToFormat()  
if not d['speed']:  
return "High speed travel detected in trip %(trip_id)s: %(prev_stop)s" \  
" to %(next_stop)s. %(dist).0f meters in %(time)d seconds." % d  
else:  
return "High speed travel detected in trip %(trip_id)s: %(prev_stop)s" \  
" to %(next_stop)s. %(dist).0f meters in %(time)d seconds." \  
" (%(speed).0f km/h)." % d  
def __cmp__(self, y):  
# Sort in decreasing order because more distance is more significant. We  
# can't sort by speed because not all TooFastTravel objects have a speed.  
return cmp(y.dist, self.dist)  
 
class DuplicateTrip(ExceptionWithContext):  
ERROR_TEXT = "Trip %(trip_id1)s of route %(route_id1)s might be duplicated " \  
"with trip %(trip_id2)s of route %(route_id2)s. They go " \  
"through the same stops with same service."  
 
class OtherProblem(ExceptionWithContext):  
ERROR_TEXT = '%(description)s'  
 
 
class ExceptionProblemReporter(ProblemReporter):  
def __init__(self, raise_warnings=False):  
ProblemReporterBase.__init__(self)  
self.raise_warnings = raise_warnings  
 
def _Report(self, e):  
if self.raise_warnings or e.IsError():  
raise e  
else:  
ProblemReporter._Report(self, e)  
 
 
default_problem_reporter = ExceptionProblemReporter()  
 
# Add a default handler to send log messages to console  
console = logging.StreamHandler()  
console.setLevel(logging.WARNING)  
log = logging.getLogger("schedule_builder")  
log.addHandler(console)  
 
 
class Error(Exception):  
pass  
 
 
def IsValidURL(url):  
"""Checks the validity of a URL value."""  
# TODO: Add more thorough checking of URL  
return url.startswith(u'http://') or url.startswith(u'https://')  
 
 
def IsValidColor(color):  
"""Checks the validity of a hex color value."""  
return not re.match('^[0-9a-fA-F]{6}$', color) == None  
 
 
def ColorLuminance(color):  
"""Compute the brightness of an sRGB color using the formula from  
http://www.w3.org/TR/2000/WD-AERT-20000426#color-contrast.  
 
Args:  
color: a string of six hex digits in the format verified by IsValidColor().  
 
Returns:  
A floating-point number between 0.0 (black) and 255.0 (white). """  
r = int(color[0:2], 16)  
g = int(color[2:4], 16)  
b = int(color[4:6], 16)  
return (299*r + 587*g + 114*b) / 1000.0  
 
 
def IsEmpty(value):  
return value is None or (isinstance(value, basestring) and not value.strip())  
 
 
def FindUniqueId(dic):  
"""Return a string not used as a key in the dictionary dic"""  
name = str(len(dic))  
while name in dic:  
name = str(random.randint(1, 999999999))  
return name  
 
 
def TimeToSecondsSinceMidnight(time_string):  
"""Convert HHH:MM:SS into seconds since midnight.  
 
For example "01:02:03" returns 3723. The leading zero of the hours may be  
omitted. HH may be more than 23 if the time is on the following day."""  
m = re.match(r'(\d{1,3}):([0-5]\d):([0-5]\d)$', time_string)  
# ignored: matching for leap seconds  
if not m:  
raise Error, 'Bad HH:MM:SS "%s"' % time_string  
return int(m.group(1)) * 3600 + int(m.group(2)) * 60 + int(m.group(3))  
 
 
def FormatSecondsSinceMidnight(s):  
"""Formats an int number of seconds past midnight into a string  
as "HH:MM:SS"."""  
return "%02d:%02d:%02d" % (s / 3600, (s / 60) % 60, s % 60)  
 
 
def DateStringToDateObject(date_string):  
"""Return a date object for a string "YYYYMMDD"."""  
# If this becomes a bottleneck date objects could be cached  
return datetime.date(int(date_string[0:4]), int(date_string[4:6]),  
int(date_string[6:8]))  
 
 
def FloatStringToFloat(float_string):  
"""Convert a float as a string to a float or raise an exception"""  
# Will raise TypeError unless a string  
if not re.match(r"^[+-]?\d+(\.\d+)?$", float_string):  
raise ValueError()  
return float(float_string)  
 
 
def NonNegIntStringToInt(int_string):  
"""Convert an non-negative integer string to an int or raise an exception"""  
# Will raise TypeError unless a string  
if not re.match(r"^(?:0|[1-9]\d*)$", int_string):  
raise ValueError()  
return int(int_string)  
 
 
EARTH_RADIUS = 6378135 # in meters  
def ApproximateDistance(degree_lat1, degree_lng1, degree_lat2, degree_lng2):  
"""Compute approximate distance between two points in meters. Assumes the  
Earth is a sphere."""  
# TODO: change to ellipsoid approximation, such as  
# http://www.codeguru.com/Cpp/Cpp/algorithms/article.php/c5115/  
lat1 = math.radians(degree_lat1)  
lng1 = math.radians(degree_lng1)  
lat2 = math.radians(degree_lat2)  
lng2 = math.radians(degree_lng2)  
dlat = math.sin(0.5 * (lat2 - lat1))  
dlng = math.sin(0.5 * (lng2 - lng1))  
x = dlat * dlat + dlng * dlng * math.cos(lat1) * math.cos(lat2)  
return EARTH_RADIUS * (2 * math.atan2(math.sqrt(x),  
math.sqrt(max(0.0, 1.0 - x))))  
 
 
def ApproximateDistanceBetweenStops(stop1, stop2):  
"""Compute approximate distance between two stops in meters. Assumes the  
Earth is a sphere."""  
return ApproximateDistance(stop1.stop_lat, stop1.stop_lon,  
stop2.stop_lat, stop2.stop_lon)  
 
 
class GenericGTFSObject(object):  
"""Object with arbitrary attributes which may be added to a schedule.  
 
This class should be used as the base class for GTFS objects which may  
be stored in a Schedule. It defines some methods for reading and writing  
attributes. If self._schedule is None than the object is not in a Schedule.  
 
Subclasses must:  
* define an __init__ method which sets the _schedule member to None or a  
weakref to a Schedule  
* Set the _TABLE_NAME class variable to a name such as 'stops', 'agency', ...  
* define methods to validate objects of that type  
"""  
def __getitem__(self, name):  
"""Return a unicode or str representation of name or "" if not set."""  
if name in self.__dict__ and self.__dict__[name] is not None:  
return "%s" % self.__dict__[name]  
else:  
return ""  
 
def __getattr__(self, name):  
"""Return None or the default value if name is a known attribute.  
 
This method is only called when name is not found in __dict__.  
"""  
if name in self.__class__._FIELD_NAMES:  
return None  
else:  
raise AttributeError(name)  
 
def iteritems(self):  
"""Return a iterable for (name, value) pairs of public attributes."""  
for name, value in self.__dict__.iteritems():  
if (not name) or name[0] == "_":  
continue  
yield name, value  
 
def __setattr__(self, name, value):  
"""Set an attribute, adding name to the list of columns as needed."""  
object.__setattr__(self, name, value)  
if name[0] != '_' and self._schedule:  
self._schedule.AddTableColumn(self.__class__._TABLE_NAME, name)  
 
def __eq__(self, other):  
"""Return true iff self and other are equivalent"""  
if not other:  
return False  
 
if id(self) == id(other):  
return True  
 
for k in self.keys().union(other.keys()):  
# use __getitem__ which returns "" for missing columns values  
if self[k] != other[k]:  
return False  
return True  
 
def __ne__(self, other):  
return not self.__eq__(other)  
 
def __repr__(self):  
return "<%s %s>" % (self.__class__.__name__, sorted(self.iteritems()))  
 
def keys(self):  
"""Return iterable of columns used by this object."""  
columns = set()  
for name in vars(self):  
if (not name) or name[0] == "_":  
continue  
columns.add(name)  
return columns  
 
def _ColumnNames(self):  
return self.keys()  
 
 
class Stop(GenericGTFSObject):  
"""Represents a single stop. A stop must have a latitude, longitude and name.  
 
Callers may assign arbitrary values to instance attributes.  
Stop.ParseAttributes validates attributes according to GTFS and converts some  
into native types. ParseAttributes may delete invalid attributes.  
Accessing an attribute that is a column in GTFS will return None if this  
object does not have a value or it is ''.  
A Stop object acts like a dict with string values.  
 
Attributes:  
stop_lat: a float representing the latitude of the stop  
stop_lon: a float representing the longitude of the stop  
All other attributes are strings.  
"""  
_REQUIRED_FIELD_NAMES = ['stop_id', 'stop_name', 'stop_lat', 'stop_lon']  
_FIELD_NAMES = _REQUIRED_FIELD_NAMES + \  
['stop_desc', 'zone_id', 'stop_url', 'stop_code',  
'location_type', 'parent_station']  
_TABLE_NAME = 'stops'  
 
def __init__(self, lat=None, lng=None, name=None, stop_id=None,  
field_dict=None, stop_code=None):  
"""Initialize a new Stop object.  
 
Args:  
field_dict: A dictionary mapping attribute name to unicode string  
lat: a float, ignored when field_dict is present  
lng: a float, ignored when field_dict is present  
name: a string, ignored when field_dict is present  
stop_id: a string, ignored when field_dict is present  
stop_code: a string, ignored when field_dict is present  
"""  
self._schedule = None  
if field_dict:  
if isinstance(field_dict, Stop):  
# Special case so that we don't need to re-parse the attributes to  
# native types iteritems returns all attributes that don't start with _  
for k, v in field_dict.iteritems():  
self.__dict__[k] = v  
else:  
self.__dict__.update(field_dict)  
else:  
if lat is not None:  
self.stop_lat = lat  
if lng is not None:  
self.stop_lon = lng  
if name is not None:  
self.stop_name = name  
if stop_id is not None:  
self.stop_id = stop_id  
if stop_code is not None:  
self.stop_code = stop_code  
 
def GetTrips(self, schedule=None):  
"""Return iterable containing trips that visit this stop."""  
return [trip for trip, ss in self._GetTripSequence(schedule)]  
 
def _GetTripSequence(self, schedule=None):  
"""Return a list of (trip, stop_sequence) for all trips visiting this stop.  
 
A trip may be in the list multiple times with different index.  
stop_sequence is an integer.  
 
Args:  
schedule: Deprecated, do not use.  
"""  
if schedule is None:  
schedule = getattr(self, "_schedule", None)  
if schedule is None:  
warnings.warn("No longer supported. _schedule attribute is used to get "  
"stop_times table", DeprecationWarning)  
cursor = schedule._connection.cursor()  
cursor.execute("SELECT trip_id,stop_sequence FROM stop_times "  
"WHERE stop_id=?",  
(self.stop_id, ))  
return [(schedule.GetTrip(row[0]), row[1]) for row in cursor]  
 
def _GetTripIndex(self, schedule=None):  
"""Return a list of (trip, index).  
 
trip: a Trip object  
index: an offset in trip.GetStopTimes()  
"""  
trip_index = []  
for trip, sequence in self._GetTripSequence(schedule):  
for index, st in enumerate(trip.GetStopTimes()):  
if st.stop_sequence == sequence:  
trip_index.append((trip, index))  
break  
else:  
raise RuntimeError("stop_sequence %d not found in trip_id %s" %  
sequence, trip.trip_id)  
return trip_index  
 
def GetStopTimeTrips(self, schedule=None):  
"""Return a list of (time, (trip, index), is_timepoint).  
 
time: an integer. It might be interpolated.  
trip: a Trip object.  
index: the offset of this stop in trip.GetStopTimes(), which may be  
different from the stop_sequence.  
is_timepoint: a bool  
"""  
time_trips = []  
for trip, index in self._GetTripIndex(schedule):  
secs, stoptime, is_timepoint = trip.GetTimeInterpolatedStops()[index]  
time_trips.append((secs, (trip, index), is_timepoint))  
return time_trips  
 
def ParseAttributes(self, problems):  
"""Parse all attributes, calling problems as needed."""  
# Need to use items() instead of iteritems() because _CheckAndSetAttr may  
# modify self.__dict__  
for name, value in vars(self).items():  
if name[0] == "_":  
continue  
self._CheckAndSetAttr(name, value, problems)  
 
def _CheckAndSetAttr(self, name, value, problems):  
"""If value is valid for attribute name store it.  
 
If value is not valid call problems. Return a new value of the correct type  
or None if value couldn't be converted.  
"""  
if name == 'stop_lat':  
try:  
if isinstance(value, (float, int)):  
self.stop_lat = value  
else:  
self.stop_lat = FloatStringToFloat(value)  
except (ValueError, TypeError):  
problems.InvalidValue('stop_lat', value)  
del self.stop_lat  
else:  
if self.stop_lat > 90 or self.stop_lat < -90:  
problems.InvalidValue('stop_lat', value)  
elif name == 'stop_lon':  
try:  
if isinstance(value, (float, int)):  
self.stop_lon = value  
else:  
self.stop_lon = FloatStringToFloat(value)  
except (ValueError, TypeError):  
problems.InvalidValue('stop_lon', value)  
del self.stop_lon  
else:  
if self.stop_lon > 180 or self.stop_lon < -180:  
problems.InvalidValue('stop_lon', value)  
elif name == 'stop_url':  
if value and not IsValidURL(value):  
problems.InvalidValue('stop_url', value)  
del self.stop_url  
elif name == 'location_type':  
if value == '':  
self.location_type = 0  
else:  
try:  
self.location_type = int(value)  
except (ValueError, TypeError):  
problems.InvalidValue('location_type', value)  
del self.location_type  
else:  
if self.location_type not in (0, 1):  
problems.InvalidValue('location_type', value, type=TYPE_WARNING)  
 
def __getattr__(self, name):  
"""Return None or the default value if name is a known attribute.  
 
This method is only called when name is not found in __dict__.  
"""  
if name == "location_type":  
return 0  
elif name == "trip_index":  
return self._GetTripIndex()  
elif name in Stop._FIELD_NAMES:  
return None  
else:  
raise AttributeError(name)  
 
def Validate(self, problems=default_problem_reporter):  
# First check that all required fields are present because ParseAttributes  
# may remove invalid attributes.  
for required in Stop._REQUIRED_FIELD_NAMES:  
if IsEmpty(getattr(self, required, None)):  
# TODO: For now I'm keeping the API stable but it would be cleaner to  
# treat whitespace stop_id as invalid, instead of missing  
problems.MissingValue(required)  
 
# Check individual values and convert to native types  
self.ParseAttributes(problems)  
 
# Check that this object is consistent with itself  
if (self.stop_lat is not None and self.stop_lon is not None and  
abs(self.stop_lat) < 1.0) and (abs(self.stop_lon) < 1.0):  
problems.InvalidValue('stop_lat', self.stop_lat,  
'Stop location too close to 0, 0',  
type=TYPE_WARNING)  
if (self.stop_desc is not None and self.stop_name is not None and  
self.stop_desc and self.stop_name and  
not IsEmpty(self.stop_desc) and  
self.stop_name.strip().lower() == self.stop_desc.strip().lower()):  
problems.InvalidValue('stop_desc', self.stop_desc,  
'stop_desc should not be the same as stop_name')  
 
if self.parent_station and self.location_type == 1:  
problems.InvalidValue('parent_station', self.parent_station,  
'Stop row with location_type=1 (a station) must '  
'not have a parent_station')  
 
 
class Route(GenericGTFSObject):  
"""Represents a single route."""  
 
_REQUIRED_FIELD_NAMES = [  
'route_id', 'route_short_name', 'route_long_name', 'route_type'  
]  
_FIELD_NAMES = _REQUIRED_FIELD_NAMES + [  
'agency_id', 'route_desc', 'route_url', 'route_color', 'route_text_color'  
]  
_ROUTE_TYPES = {  
0: {'name':'Tram', 'max_speed':100},  
1: {'name':'Subway', 'max_speed':150},  
2: {'name':'Rail', 'max_speed':300},  
3: {'name':'Bus', 'max_speed':100},  
4: {'name':'Ferry', 'max_speed':80},  
5: {'name':'Cable Car', 'max_speed':50},  
6: {'name':'Gondola', 'max_speed':50},  
7: {'name':'Funicular', 'max_speed':50},  
}  
# Create a reverse lookup dict of route type names to route types.  
_ROUTE_TYPE_IDS = set(_ROUTE_TYPES.keys())  
_ROUTE_TYPE_NAMES = dict((v['name'], k) for k, v in _ROUTE_TYPES.items())  
_TABLE_NAME = 'routes'  
 
def __init__(self, short_name=None, long_name=None, route_type=None,  
route_id=None, agency_id=None, field_dict=None):  
self._schedule = None  
self._trips = []  
 
if not field_dict:  
field_dict = {}  
if short_name is not None:  
field_dict['route_short_name'] = short_name  
if long_name is not None:  
field_dict['route_long_name'] = long_name  
if route_type is not None:  
if route_type in Route._ROUTE_TYPE_NAMES:  
self.route_type = Route._ROUTE_TYPE_NAMES[route_type]  
else:  
field_dict['route_type'] = route_type  
if route_id is not None:  
field_dict['route_id'] = route_id  
if agency_id is not None:  
field_dict['agency_id'] = agency_id  
self.__dict__.update(field_dict)  
 
def AddTrip(self, schedule, headsign, service_period=None, trip_id=None):  
""" Adds a trip to this route.  
 
Args:  
headsign: headsign of the trip as a string  
 
Returns:  
a new Trip object  
"""  
if trip_id is None:  
trip_id = unicode(len(schedule.trips))  
if service_period is None:  
service_period = schedule.GetDefaultServicePeriod()  
trip = Trip(route=self, headsign=headsign, service_period=service_period,  
trip_id=trip_id)  
schedule.AddTripObject(trip)  
return trip  
 
def _AddTripObject(self, trip):  
# Only class Schedule may call this. Users of the API should call  
# Route.AddTrip or schedule.AddTripObject.  
self._trips.append(trip)  
 
def __getattr__(self, name):  
"""Return None or the default value if name is a known attribute.  
 
This method overrides GenericGTFSObject.__getattr__ to provide backwards  
compatible access to trips.  
"""  
if name == 'trips':  
return self._trips  
else:  
return GenericGTFSObject.__getattr__(self, name)  
 
def GetPatternIdTripDict(self):  
"""Return a dictionary that maps pattern_id to a list of Trip objects."""  
d = {}  
for t in self._trips:  
d.setdefault(t.pattern_id, []).append(t)  
return d  
 
def Validate(self, problems=default_problem_reporter):  
if IsEmpty(self.route_id):  
problems.MissingValue('route_id')  
if IsEmpty(self.route_type):  
problems.MissingValue('route_type')  
 
if IsEmpty(self.route_short_name) and IsEmpty(self.route_long_name):  
problems.InvalidValue('route_short_name',  
self.route_short_name,  
'Both route_short_name and '  
'route_long name are blank.')  
 
if self.route_short_name and len(self.route_short_name) > 6:  
problems.InvalidValue('route_short_name',  
self.route_short_name,  
'This route_short_name is relatively long, which '  
'probably means that it contains a place name. '  
'You should only use this field to hold a short '  
'code that riders use to identify a route. '  
'If this route doesn\'t have such a code, it\'s '  
'OK to leave this field empty.', type=TYPE_WARNING)  
 
if self.route_short_name and self.route_long_name:  
short_name = self.route_short_name.strip().lower()  
long_name = self.route_long_name.strip().lower()  
if (long_name.startswith(short_name + ' ') or  
long_name.startswith(short_name + '(') or  
long_name.startswith(short_name + '-')):  
problems.InvalidValue('route_long_name',  
self.route_long_name,  
'route_long_name shouldn\'t contain '  
'the route_short_name value, as both '  
'fields are often displayed '  
'side-by-side.', type=TYPE_WARNING)  
if long_name == short_name:  
problems.InvalidValue('route_long_name',  
self.route_long_name,  
'route_long_name shouldn\'t be the same '  
'the route_short_name value, as both '  
'fields are often displayed '  
'side-by-side. It\'s OK to omit either the '  
'short or long name (but not both).',  
type=TYPE_WARNING)  
if (self.route_desc and  
((self.route_desc == self.route_short_name) or  
(self.route_desc == self.route_long_name))):  
problems.InvalidValue('route_desc',  
self.route_desc,  
'route_desc shouldn\'t be the same as '  
'route_short_name or route_long_name')  
 
if self.route_type is not None:  
try:  
if not isinstance(self.route_type, int):  
self.route_type = NonNegIntStringToInt(self.route_type)  
except (TypeError, ValueError):  
problems.InvalidValue('route_type', self.route_type)  
else:  
if self.route_type not in Route._ROUTE_TYPE_IDS:  
problems.InvalidValue('route_type',  
self.route_type,  
type=TYPE_WARNING)  
 
if self.route_url and not IsValidURL(self.route_url):  
problems.InvalidValue('route_url', self.route_url)  
 
txt_lum = ColorLuminance('000000') # black (default)  
bg_lum = ColorLuminance('ffffff') # white (default)  
if self.route_color:  
if IsValidColor(self.route_color):  
bg_lum = ColorLuminance(self.route_color)  
else:  
problems.InvalidValue('route_color', self.route_color,  
'route_color should be a valid color description '  
'which consists of 6 hexadecimal characters '  
'representing the RGB values. Example: 44AA06')  
if self.route_text_color:  
if IsValidColor(self.route_text_color):  
txt_lum = ColorLuminance(self.route_text_color)  
else:  
problems.InvalidValue('route_text_color', self.route_text_color,  
'route_text_color should be a valid color '  
'description, which consists of 6 hexadecimal '  
'characters representing the RGB values. '  
'Example: 44AA06')  
if abs(txt_lum - bg_lum) < 510/7.:  
# http://www.w3.org/TR/2000/WD-AERT-20000426#color-contrast recommends  
# a threshold of 125, but that is for normal text and too harsh for  
# big colored logos like line names, so we keep the original threshold  
# from r541 (but note that weight has shifted between RGB components).  
problems.InvalidValue('route_color', self.route_color,  
'The route_text_color and route_color should '  
'be set to contrasting colors, as they are used '  
'as the text and background color (respectively) '  
'for displaying route names. When left blank, '  
'route_text_color defaults to 000000 (black) and '  
'route_color defaults to FFFFFF (white). A common '  
'source of issues here is setting route_color to '  
'a dark color, while leaving route_text_color set '  
'to black. In this case, route_text_color should '  
'be set to a lighter color like FFFFFF to ensure '  
'a legible contrast between the two.',  
type=TYPE_WARNING)  
 
 
def SortListOfTripByTime(trips):  
trips.sort(key=Trip.GetStartTime)  
 
 
class StopTime(object):  
"""  
Represents a single stop of a trip. StopTime contains most of the columns  
from the stop_times.txt file. It does not contain trip_id, which is implied  
by the Trip used to access it.  
 
See the Google Transit Feed Specification for the semantic details.  
 
stop: A Stop object  
arrival_time: str in the form HH:MM:SS; readonly after __init__  
departure_time: str in the form HH:MM:SS; readonly after __init__  
arrival_secs: int number of seconds since midnight  
departure_secs: int number of seconds since midnight  
stop_headsign: str  
pickup_type: int  
drop_off_type: int  
shape_dist_traveled: float  
stop_id: str; readonly  
stop_time: The only time given for this stop. If present, it is used  
for both arrival and departure time.  
stop_sequence: int  
"""  
_REQUIRED_FIELD_NAMES = ['trip_id', 'arrival_time', 'departure_time',  
'stop_id', 'stop_sequence']  
_OPTIONAL_FIELD_NAMES = ['stop_headsign', 'pickup_type',  
'drop_off_type', 'shape_dist_traveled']  
_FIELD_NAMES = _REQUIRED_FIELD_NAMES + _OPTIONAL_FIELD_NAMES  
_SQL_FIELD_NAMES = ['trip_id', 'arrival_secs', 'departure_secs',  
'stop_id', 'stop_sequence', 'stop_headsign',  
'pickup_type', 'drop_off_type', 'shape_dist_traveled']  
 
__slots__ = ('arrival_secs', 'departure_secs', 'stop_headsign', 'stop',  
'stop_headsign', 'pickup_type', 'drop_off_type',  
'shape_dist_traveled', 'stop_sequence')  
def __init__(self, problems, stop,  
arrival_time=None, departure_time=None,  
stop_headsign=None, pickup_type=None, drop_off_type=None,  
shape_dist_traveled=None, arrival_secs=None,  
departure_secs=None, stop_time=None, stop_sequence=None):  
if stop_time != None:  
arrival_time = departure_time = stop_time  
 
if arrival_secs != None:  
self.arrival_secs = arrival_secs  
elif arrival_time in (None, ""):  
self.arrival_secs = None # Untimed  
arrival_time = None  
else:  
try:  
self.arrival_secs = TimeToSecondsSinceMidnight(arrival_time)  
except Error:  
problems.InvalidValue('arrival_time', arrival_time)  
self.arrival_secs = None  
 
if departure_secs != None:  
self.departure_secs = departure_secs  
elif departure_time in (None, ""):  
self.departure_secs = None  
departure_time = None  
else:  
try:  
self.departure_secs = TimeToSecondsSinceMidnight(departure_time)  
except Error:  
problems.InvalidValue('departure_time', departure_time)  
self.departure_secs = None  
 
if not isinstance(stop, Stop):  
# Not quite correct, but better than letting the problem propagate  
problems.InvalidValue('stop', stop)  
self.stop = stop  
self.stop_headsign = stop_headsign  
 
if pickup_type in (None, ""):  
self.pickup_type = None  
else:  
try:  
pickup_type = int(pickup_type)  
except ValueError:  
problems.InvalidValue('pickup_type', pickup_type)  
else:  
if pickup_type < 0 or pickup_type > 3:  
problems.InvalidValue('pickup_type', pickup_type)  
self.pickup_type = pickup_type  
 
if drop_off_type in (None, ""):  
self.drop_off_type = None  
else:  
try:  
drop_off_type = int(drop_off_type)  
except ValueError:  
problems.InvalidValue('drop_off_type', drop_off_type)  
else:  
if drop_off_type < 0 or drop_off_type > 3:  
problems.InvalidValue('drop_off_type', drop_off_type)  
self.drop_off_type = drop_off_type  
 
if (self.pickup_type == 1 and self.drop_off_type == 1 and  
self.arrival_secs == None and self.departure_secs == None):  
problems.OtherProblem('This stop time has a pickup_type and '  
'drop_off_type of 1, indicating that riders '  
'can\'t get on or off here. Since it doesn\'t '  
'define a timepoint either, this entry serves no '  
'purpose and should be excluded from the trip.',  
type=TYPE_WARNING)  
 
if ((self.arrival_secs != None) and (self.departure_secs != None) and  
(self.departure_secs < self.arrival_secs)):  
problems.InvalidValue('departure_time', departure_time,  
'The departure time at this stop (%s) is before '  
'the arrival time (%s). This is often caused by '  
'problems in the feed exporter\'s time conversion')  
 
# If the caller passed a valid arrival time but didn't attempt to pass a  
# departure time complain  
if (self.arrival_secs != None and  
self.departure_secs == None and departure_time == None):  
# self.departure_secs might be None because departure_time was invalid,  
# so we need to check both  
problems.MissingValue('departure_time',  
'arrival_time and departure_time should either '  
'both be provided or both be left blank. '  
'It\'s OK to set them both to the same value.')  
# If the caller passed a valid departure time but didn't attempt to pass a  
# arrival time complain  
if (self.departure_secs != None and  
self.arrival_secs == None and arrival_time == None):  
problems.MissingValue('arrival_time',  
'arrival_time and departure_time should either '  
'both be provided or both be left blank. '  
'It\'s OK to set them both to the same value.')  
 
if shape_dist_traveled in (None, ""):  
self.shape_dist_traveled = None  
else:  
try:  
self.shape_dist_traveled = float(shape_dist_traveled)  
except ValueError:  
problems.InvalidValue('shape_dist_traveled', shape_dist_traveled)  
 
if stop_sequence is not None:  
self.stop_sequence = stop_sequence  
 
def GetFieldValuesTuple(self, trip_id):  
"""Return a tuple that outputs a row of _FIELD_NAMES.  
 
trip must be provided because it is not stored in StopTime.  
"""  
result = []  
for fn in StopTime._FIELD_NAMES:  
if fn == 'trip_id':  
result.append(trip_id)  
else:  
result.append(getattr(self, fn) or '' )  
return tuple(result)  
 
def GetSqlValuesTuple(self, trip_id):  
result = []  
for fn in StopTime._SQL_FIELD_NAMES:  
if fn == 'trip_id':  
result.append(trip_id)  
else:  
# This might append None, which will be inserted into SQLite as NULL  
result.append(getattr(self, fn))  
return tuple(result)  
 
def GetTimeSecs(self):  
"""Return the first of arrival_secs and departure_secs that is not None.  
If both are None return None."""  
if self.arrival_secs != None:  
return self.arrival_secs  
elif self.departure_secs != None:  
return self.departure_secs  
else:  
return None  
 
def __getattr__(self, name):  
if name == 'stop_id':  
return self.stop.stop_id  
elif name == 'arrival_time':  
return (self.arrival_secs != None and  
FormatSecondsSinceMidnight(self.arrival_secs) or '')  
elif name == 'departure_time':  
return (self.departure_secs != None and  
FormatSecondsSinceMidnight(self.departure_secs) or '')  
elif name == 'shape_dist_traveled':  
return ''  
raise AttributeError(name)  
 
 
class Trip(GenericGTFSObject):  
_REQUIRED_FIELD_NAMES = ['route_id', 'service_id', 'trip_id']  
_FIELD_NAMES = _REQUIRED_FIELD_NAMES + [  
'trip_headsign', 'direction_id', 'block_id', 'shape_id'  
]  
_FIELD_NAMES_HEADWAY = ['trip_id', 'start_time', 'end_time', 'headway_secs']  
_TABLE_NAME= "trips"  
 
def __init__(self, headsign=None, service_period=None,  
route=None, trip_id=None, field_dict=None):  
self._schedule = None  
self._headways = [] # [(start_time, end_time, headway_secs)]  
if not field_dict:  
field_dict = {}  
if headsign is not None:  
field_dict['trip_headsign'] = headsign  
if route:  
field_dict['route_id'] = route.route_id  
if trip_id is not None:  
field_dict['trip_id'] = trip_id  
if service_period is not None:  
field_dict['service_id'] = service_period.service_id  
# Earlier versions of transitfeed.py assigned self.service_period here  
# and allowed the caller to set self.service_id. Schedule.Validate  
# checked the service_id attribute if it was assigned and changed it to a  
# service_period attribute. Now only the service_id attribute is used and  
# it is validated by Trip.Validate.  
if service_period is not None:  
# For backwards compatibility  
self.service_id = service_period.service_id  
self.__dict__.update(field_dict)  
 
def GetFieldValuesTuple(self):  
return [getattr(self, fn) or '' for fn in Trip._FIELD_NAMES]  
 
def AddStopTime(self, stop, problems=None, schedule=None, **kwargs):  
"""Add a stop to this trip. Stops must be added in the order visited.  
 
Args:  
stop: A Stop object  
kwargs: remaining keyword args passed to StopTime.__init__  
 
Returns:  
None  
"""  
if problems is None:  
# TODO: delete this branch when StopTime.__init__ doesn't need a  
# ProblemReporter  
problems = default_problem_reporter  
stoptime = StopTime(problems=problems, stop=stop, **kwargs)  
self.AddStopTimeObject(stoptime, schedule)  
 
def _AddStopTimeObjectUnordered(self, stoptime, schedule):  
"""Add StopTime object to this trip.  
 
The trip isn't checked for duplicate sequence numbers so it must be  
validated later."""  
cursor = schedule._connection.cursor()  
insert_query = "INSERT INTO stop_times (%s) VALUES (%s);" % (  
','.join(StopTime._SQL_FIELD_NAMES),  
','.join(['?'] * len(StopTime._SQL_FIELD_NAMES)))  
cursor = schedule._connection.cursor()  
cursor.execute(  
insert_query, stoptime.GetSqlValuesTuple(self.trip_id))  
 
def ReplaceStopTimeObject(self, stoptime, schedule=None):  
"""Replace a StopTime object from this trip with the given one.  
 
Keys the StopTime object to be replaced by trip_id, stop_sequence  
and stop_id as 'stoptime', with the object 'stoptime'.  
"""  
 
if schedule is None:  
schedule = self._schedule  
 
new_secs = stoptime.GetTimeSecs()  
cursor = schedule._connection.cursor()  
cursor.execute("DELETE FROM stop_times WHERE trip_id=? and "  
"stop_sequence=? and stop_id=?",  
(self.trip_id, stoptime.stop_sequence, stoptime.stop_id))  
if cursor.rowcount == 0:  
raise Error, 'Attempted replacement of StopTime object which does not exist'  
self._AddStopTimeObjectUnordered(stoptime, schedule)  
 
def AddStopTimeObject(self, stoptime, schedule=None, problems=None):  
"""Add a StopTime object to the end of this trip.  
 
Args:  
stoptime: A StopTime object. Should not be reused in multiple trips.  
schedule: Schedule object containing this trip which must be  
passed to Trip.__init__ or here  
problems: ProblemReporter object for validating the StopTime in its new  
home  
 
Returns:  
None  
"""  
if schedule is None:  
schedule = self._schedule  
if schedule is None:  
warnings.warn("No longer supported. _schedule attribute is used to get "  
"stop_times table", DeprecationWarning)  
if problems is None:  
problems = schedule.problem_reporter  
 
new_secs = stoptime.GetTimeSecs()  
cursor = schedule._connection.cursor()  
cursor.execute("SELECT max(stop_sequence), max(arrival_secs), "  
"max(departure_secs) FROM stop_times WHERE trip_id=?",  
(self.trip_id,))  
row = cursor.fetchone()  
if row[0] is None:  
# This is the first stop_time of the trip  
stoptime.stop_sequence = 1  
if new_secs == None:  
problems.OtherProblem(  
'No time for first StopTime of trip_id "%s"' % (self.trip_id,))  
else:  
stoptime.stop_sequence = row[0] + 1  
prev_secs = max(row[1], row[2])  
if new_secs != None and new_secs < prev_secs:  
problems.OtherProblem(  
'out of order stop time for stop_id=%s trip_id=%s %s < %s' %  
(EncodeUnicode(stoptime.stop_id), EncodeUnicode(self.trip_id),  
FormatSecondsSinceMidnight(new_secs),  
FormatSecondsSinceMidnight(prev_secs)))  
self._AddStopTimeObjectUnordered(stoptime, schedule)  
 
def GetTimeStops(self):  
"""Return a list of (arrival_secs, departure_secs, stop) tuples.  
 
Caution: arrival_secs and departure_secs may be 0, a false value meaning a  
stop at midnight or None, a false value meaning the stop is untimed."""  
return [(st.arrival_secs, st.departure_secs, st.stop) for st in  
self.GetStopTimes()]  
 
def GetCountStopTimes(self):  
"""Return the number of stops made by this trip."""  
cursor = self._schedule._connection.cursor()  
cursor.execute(  
'SELECT count(*) FROM stop_times WHERE trip_id=?', (self.trip_id,))  
return cursor.fetchone()[0]  
 
def GetTimeInterpolatedStops(self):  
"""Return a list of (secs, stoptime, is_timepoint) tuples.  
 
secs will always be an int. If the StopTime object does not have explict  
times this method guesses using distance. stoptime is a StopTime object and  
is_timepoint is a bool.  
 
Raises:  
ValueError if this trip does not have the times needed to interpolate  
"""  
rv = []  
 
stoptimes = self.GetStopTimes()  
# If there are no stoptimes [] is the correct return value but if the start  
# or end are missing times there is no correct return value.  
if not stoptimes:  
return []  
if (stoptimes[0].GetTimeSecs() is None or  
stoptimes[-1].GetTimeSecs() is None):  
raise ValueError("%s must have time at first and last stop" % (self))  
 
cur_timepoint = None  
next_timepoint = None  
distance_between_timepoints = 0  
distance_traveled_between_timepoints = 0  
 
for i, st in enumerate(stoptimes):  
if st.GetTimeSecs() != None:  
cur_timepoint = st  
distance_between_timepoints = 0  
distance_traveled_between_timepoints = 0  
if i + 1 < len(stoptimes):  
k = i + 1  
distance_between_timepoints += ApproximateDistanceBetweenStops(stoptimes[k-1].stop, stoptimes[k].stop)  
while stoptimes[k].GetTimeSecs() == None:  
k += 1  
distance_between_timepoints += ApproximateDistanceBetweenStops(stoptimes[k-1].stop, stoptimes[k].stop)  
next_timepoint = stoptimes[k]  
rv.append( (st.GetTimeSecs(), st, True) )  
else:  
distance_traveled_between_timepoints += ApproximateDistanceBetweenStops(stoptimes[i-1].stop, st.stop)  
distance_percent = distance_traveled_between_timepoints / distance_between_timepoints  
total_time = next_timepoint.GetTimeSecs() - cur_timepoint.GetTimeSecs()  
time_estimate = distance_percent * total_time + cur_timepoint.GetTimeSecs()  
rv.append( (int(round(time_estimate)), st, False) )  
 
return rv  
 
def ClearStopTimes(self):  
"""Remove all stop times from this trip.  
 
StopTime objects previously returned by GetStopTimes are unchanged but are  
no longer associated with this trip.  
"""  
cursor = self._schedule._connection.cursor()  
cursor.execute('DELETE FROM stop_times WHERE trip_id=?', (self.trip_id,))  
 
def GetStopTimes(self, problems=None):  
"""Return a sorted list of StopTime objects for this trip."""  
# In theory problems=None should be safe because data from database has been  
# validated. See comment in _LoadStopTimes for why this isn't always true.  
cursor = self._schedule._connection.cursor()  
cursor.execute(  
'SELECT arrival_secs,departure_secs,stop_headsign,pickup_type,'  
'drop_off_type,shape_dist_traveled,stop_id,stop_sequence FROM '  
'stop_times WHERE '  
'trip_id=? ORDER BY stop_sequence', (self.trip_id,))  
stop_times = []  
for row in cursor.fetchall():  
stop = self._schedule.GetStop(row[6])  
stop_times.append(StopTime(problems=problems, stop=stop, arrival_secs=row[0],  
departure_secs=row[1],  
stop_headsign=row[2],  
pickup_type=row[3],  
drop_off_type=row[4],  
shape_dist_traveled=row[5],  
stop_sequence=row[7]))  
return stop_times  
 
def GetHeadwayStopTimes(self, problems=None):  
"""Return a list of StopTime objects for each headway-based run.  
 
Returns:  
a list of list of StopTime objects. Each list of StopTime objects  
represents one run. If this trip doesn't have headways returns an empty  
list.  
"""  
stoptimes_list = [] # list of stoptime lists to be returned  
stoptime_pattern = self.GetStopTimes()  
first_secs = stoptime_pattern[0].arrival_secs # first time of the trip  
# for each start time of a headway run  
for run_secs in self.GetHeadwayStartTimes():  
# stop time list for a headway run  
stoptimes = []  
# go through the pattern and generate stoptimes  
for st in stoptime_pattern:  
arrival_secs, departure_secs = None, None # default value if the stoptime is not timepoint  
if st.arrival_secs != None:  
arrival_secs = st.arrival_secs - first_secs + run_secs  
if st.departure_secs != None:  
departure_secs = st.departure_secs - first_secs + run_secs  
# append stoptime  
stoptimes.append(StopTime(problems=problems, stop=st.stop,  
arrival_secs=arrival_secs,  
departure_secs=departure_secs,  
stop_headsign=st.stop_headsign,  
pickup_type=st.pickup_type,  
drop_off_type=st.drop_off_type,  
shape_dist_traveled=st.shape_dist_traveled,  
stop_sequence=st.stop_sequence))  
# add stoptimes to the stoptimes_list  
stoptimes_list.append ( stoptimes )  
return stoptimes_list  
 
def GetStartTime(self, problems=default_problem_reporter):  
"""Return the first time of the trip. TODO: For trips defined by frequency  
return the first time of the first trip."""  
cursor = self._schedule._connection.cursor()  
cursor.execute(  
'SELECT arrival_secs,departure_secs FROM stop_times WHERE '  
'trip_id=? ORDER BY stop_sequence LIMIT 1', (self.trip_id,))  
(arrival_secs, departure_secs) = cursor.fetchone()  
if arrival_secs != None:  
return arrival_secs  
elif departure_secs != None:  
return departure_secs  
else:  
problems.InvalidValue('departure_time', '',  
'The first stop_time in trip %s is missing '  
'times.' % self.trip_id)  
 
def GetHeadwayStartTimes(self):  
"""Return a list of start time for each headway-based run.  
 
Returns:  
a sorted list of seconds since midnight, the start time of each run. If  
this trip doesn't have headways returns an empty list."""  
start_times = []  
# for each headway period of the trip  
for start_secs, end_secs, headway_secs in self.GetHeadwayPeriodTuples():  
# reset run secs to the start of the timeframe  
run_secs = start_secs  
while run_secs < end_secs:  
start_times.append(run_secs)  
# increment current run secs by headway secs  
run_secs += headway_secs  
return start_times  
 
def GetEndTime(self, problems=default_problem_reporter):  
"""Return the last time of the trip. TODO: For trips defined by frequency  
return the last time of the last trip."""  
cursor = self._schedule._connection.cursor()  
cursor.execute(  
'SELECT arrival_secs,departure_secs FROM stop_times WHERE '  
'trip_id=? ORDER BY stop_sequence DESC LIMIT 1', (self.trip_id,))  
(arrival_secs, departure_secs) = cursor.fetchone()  
if departure_secs != None:  
return departure_secs  
elif arrival_secs != None:  
return arrival_secs  
else:  
problems.InvalidValue('arrival_time', '',  
'The last stop_time in trip %s is missing '  
'times.' % self.trip_id)  
 
def _GenerateStopTimesTuples(self):  
"""Generator for rows of the stop_times file"""  
stoptimes = self.GetStopTimes()  
for i, st in enumerate(stoptimes):  
yield st.GetFieldValuesTuple(self.trip_id)  
 
def GetStopTimesTuples(self):  
results = []  
for time_tuple in self._GenerateStopTimesTuples():  
results.append(time_tuple)  
return results  
 
def GetPattern(self):  
"""Return a tuple of Stop objects, in the order visited"""  
stoptimes = self.GetStopTimes()  
return tuple(st.stop for st in stoptimes)  
 
def AddHeadwayPeriod(self, start_time, end_time, headway_secs,  
problem_reporter=default_problem_reporter):  
"""Adds a period to this trip during which the vehicle travels  
at regular intervals (rather than specifying exact times for each stop).  
 
Args:  
start_time: The time at which this headway period starts, either in  
numerical seconds since midnight or as "HH:MM:SS" since midnight.  
end_time: The time at which this headway period ends, either in  
numerical seconds since midnight or as "HH:MM:SS" since midnight.  
This value should be larger than start_time.  
headway_secs: The amount of time, in seconds, between occurences of  
this trip.  
problem_reporter: Optional parameter that can be used to select  
how any errors in the other input parameters will be reported.  
Returns:  
None  
"""  
if start_time == None or start_time == '': # 0 is OK  
problem_reporter.MissingValue('start_time')  
return  
if isinstance(start_time, basestring):  
try:  
start_time = TimeToSecondsSinceMidnight(start_time)  
except Error:  
problem_reporter.InvalidValue('start_time', start_time)  
return  
elif start_time < 0:  
problem_reporter.InvalidValue('start_time', start_time)  
 
if end_time == None or end_time == '':  
problem_reporter.MissingValue('end_time')  
return  
if isinstance(end_time, basestring):  
try:  
end_time = TimeToSecondsSinceMidnight(end_time)  
except Error:  
problem_reporter.InvalidValue('end_time', end_time)  
return  
elif end_time < 0:  
problem_reporter.InvalidValue('end_time', end_time)  
return  
 
if not headway_secs:  
problem_reporter.MissingValue('headway_secs')  
return  
try:  
headway_secs = int(headway_secs)  
except ValueError:  
problem_reporter.InvalidValue('headway_secs', headway_secs)  
return  
 
if headway_secs <= 0:  
problem_reporter.InvalidValue('headway_secs', headway_secs)  
return  
 
if end_time <= start_time:  
problem_reporter.InvalidValue('end_time', end_time,  
'should be greater than start_time')  
 
self._headways.append((start_time, end_time, headway_secs))  
 
def ClearHeadwayPeriods(self):  
self._headways = []  
 
def _HeadwayOutputTuple(self, headway):  
return (self.trip_id,  
FormatSecondsSinceMidnight(headway[0]),  
FormatSecondsSinceMidnight(headway[1]),  
unicode(headway[2]))  
 
def GetHeadwayPeriodOutputTuples(self):  
tuples = []  
for headway in self._headways:  
tuples.append(self._HeadwayOutputTuple(headway))  
return tuples  
 
def GetHeadwayPeriodTuples(self):  
return self._headways  
 
def __getattr__(self, name):  
if name == 'service_period':  
assert self._schedule, "Must be in a schedule to get service_period"  
return self._schedule.GetServicePeriod(self.service_id)  
elif name == 'pattern_id':  
if '_pattern_id' not in self.__dict__:  
self.__dict__['_pattern_id'] = hash(self.GetPattern())  
return self.__dict__['_pattern_id']  
else:  
return GenericGTFSObject.__getattr__(self, name)  
 
def Validate(self, problems, validate_children=True):  
"""Validate attributes of this object.  
 
Check that this object has all required values set to a valid value without  
reference to the rest of the schedule. If the _schedule attribute is set  
then check that references such as route_id and service_id are correct.  
 
Args:  
problems: A ProblemReporter object  
validate_children: if True and the _schedule attribute is set than call  
ValidateChildren  
"""  
if IsEmpty(self.route_id):  
problems.MissingValue('route_id')  
if 'service_period' in self.__dict__:  
# Some tests assign to the service_period attribute. Patch up self before  
# proceeding with validation. See also comment in Trip.__init__.  
self.service_id = self.__dict__['service_period'].service_id  
del self.service_period  
if IsEmpty(self.service_id):  
problems.MissingValue('service_id')  
if IsEmpty(self.trip_id):  
problems.MissingValue('trip_id')  
if hasattr(self, 'direction_id') and (not IsEmpty(self.direction_id)) and \  
(self.direction_id != '0') and (self.direction_id != '1'):  
problems.InvalidValue('direction_id', self.direction_id,  
'direction_id must be "0" or "1"')  
if self._schedule:  
if self.shape_id and self.shape_id not in self._schedule._shapes:  
problems.InvalidValue('shape_id', self.shape_id)  
if self.route_id and self.route_id not in self._schedule.routes:  
problems.InvalidValue('route_id', self.route_id)  
if (self.service_id and  
self.service_id not in self._schedule.service_periods):  
problems.InvalidValue('service_id', self.service_id)  
 
if validate_children:  
self.ValidateChildren(problems)  
 
def ValidateChildren(self, problems):  
"""Validate StopTimes and headways of this trip."""  
assert self._schedule, "Trip must be in a schedule to ValidateChildren"  
# TODO: validate distance values in stop times (if applicable)  
cursor = self._schedule._connection.cursor()  
cursor.execute("SELECT COUNT(stop_sequence) AS a FROM stop_times "  
"WHERE trip_id=? GROUP BY stop_sequence HAVING a > 1",  
(self.trip_id,))  
for row in cursor:  
problems.InvalidValue('stop_sequence', row[0],  
'Duplicate stop_sequence in trip_id %s' %  
self.trip_id)  
 
stoptimes = self.GetStopTimes(problems)  
if stoptimes:  
if stoptimes[0].arrival_time is None and stoptimes[0].departure_time is None:  
problems.OtherProblem(  
'No time for start of trip_id "%s""' % (self.trip_id))  
if stoptimes[-1].arrival_time is None and stoptimes[-1].departure_time is None:  
problems.OtherProblem(  
'No time for end of trip_id "%s""' % (self.trip_id))  
 
# Sorts the stoptimes by sequence and then checks that the arrival time  
# for each time point is after the departure time of the previous.  
stoptimes.sort(key=lambda x: x.stop_sequence)  
prev_departure = 0  
prev_stop = None  
prev_distance = None  
try:  
route_type = self._schedule.GetRoute(self.route_id).route_type  
max_speed = Route._ROUTE_TYPES[route_type]['max_speed']  
except KeyError, e:  
# If route_type cannot be found, assume it is 0 (Tram) for checking  
# speeds between stops.  
max_speed = Route._ROUTE_TYPES[0]['max_speed']  
for timepoint in stoptimes:  
# Distance should be a nonnegative float number, so it should be  
# always larger than None.  
distance = timepoint.shape_dist_traveled  
if distance is not None:  
if distance > prev_distance and distance >= 0:  
prev_distance = distance  
else:  
if distance == prev_distance:  
type = TYPE_WARNING  
else:  
type = TYPE_ERROR  
problems.InvalidValue('stoptimes.shape_dist_traveled', distance,  
'For the trip %s the stop %s has shape_dist_traveled=%s, '  
'which should be larger than the previous ones. In this '  
'case, the previous distance was %s.' %  
(self.trip_id, timepoint.stop_id, distance, prev_distance),  
type=type)  
 
if timepoint.arrival_secs is not None:  
self._CheckSpeed(prev_stop, timepoint.stop, prev_departure,  
timepoint.arrival_secs, max_speed, problems)  
 
if timepoint.arrival_secs >= prev_departure:  
prev_departure = timepoint.departure_secs  
prev_stop = timepoint.stop  
else:  
problems.OtherProblem('Timetravel detected! Arrival time '  
'is before previous departure '  
'at sequence number %s in trip %s' %  
(timepoint.stop_sequence, self.trip_id))  
 
if self.shape_id and self.shape_id in self._schedule._shapes:  
shape = self._schedule.GetShape(self.shape_id)  
max_shape_dist = shape.max_distance  
st = stoptimes[-1]  
if (st.shape_dist_traveled and  
st.shape_dist_traveled > max_shape_dist):  
problems.OtherProblem(  
'In stop_times.txt, the stop with trip_id=%s and '  
'stop_sequence=%d has shape_dist_traveled=%f, which is larger '  
'than the max shape_dist_traveled=%f of the corresponding '  
'shape (shape_id=%s)' %  
(self.trip_id, st.stop_sequence, st.shape_dist_traveled,  
max_shape_dist, self.shape_id), type=TYPE_WARNING)  
 
# shape_dist_traveled is valid in shape if max_shape_dist larger than  
# 0.  
if max_shape_dist > 0:  
for st in stoptimes:  
if st.shape_dist_traveled is None:  
continue  
pt = shape.GetPointWithDistanceTraveled(st.shape_dist_traveled)  
if pt:  
stop = self._schedule.GetStop(st.stop_id)  
distance = ApproximateDistance(stop.stop_lat, stop.stop_lon,  
pt[0], pt[1])  
if distance > MAX_DISTANCE_FROM_STOP_TO_SHAPE:  
problems.StopTooFarFromShapeWithDistTraveled(  
self.trip_id, stop.stop_name, stop.stop_id, pt[2],  
self.shape_id, distance, MAX_DISTANCE_FROM_STOP_TO_SHAPE)  
 
# O(n^2), but we don't anticipate many headway periods per trip  
for headway_index, headway in enumerate(self._headways[0:-1]):  
for other in self._headways[headway_index + 1:]:  
if (other[0] < headway[1]) and (other[1] > headway[0]):  
problems.OtherProblem('Trip contains overlapping headway periods '  
'%s and %s' %  
(self._HeadwayOutputTuple(headway),  
self._HeadwayOutputTuple(other)))  
 
def _CheckSpeed(self, prev_stop, next_stop, depart_time,  
arrive_time, max_speed, problems):  
# Checks that the speed between two stops is not faster than max_speed  
if prev_stop != None:  
try:  
time_between_stops = arrive_time - depart_time  
except TypeError:  
return  
 
try:  
dist_between_stops = \  
ApproximateDistanceBetweenStops(next_stop, prev_stop)  
except TypeError, e:  
return  
 
if time_between_stops == 0:  
# HASTUS makes it hard to output GTFS with times to the nearest second;  
# it rounds times to the nearest minute. Therefore stop_times at the  
# same time ending in :00 are fairly common. These times off by no more  
# than 30 have not caused a problem. See  
# http://code.google.com/p/googletransitdatafeed/issues/detail?id=193  
# Show a warning if times are not rounded to the nearest minute or  
# distance is more than max_speed for one minute.  
if depart_time % 60 != 0 or dist_between_stops / 1000 * 60 > max_speed:  
problems.TooFastTravel(self.trip_id,  
prev_stop.stop_name,  
next_stop.stop_name,  
dist_between_stops,  
time_between_stops,  
speed=None,  
type=TYPE_WARNING)  
return  
# This needs floating point division for precision.  
speed_between_stops = ((float(dist_between_stops) / 1000) /  
(float(time_between_stops) / 3600))  
if speed_between_stops > max_speed:  
problems.TooFastTravel(self.trip_id,  
prev_stop.stop_name,  
next_stop.stop_name,  
dist_between_stops,  
time_between_stops,  
speed_between_stops,  
type=TYPE_WARNING)  
 
# TODO: move these into a separate file  
class ISO4217(object):  
"""Represents the set of currencies recognized by the ISO-4217 spec."""  
codes = { # map of alpha code to numerical code  
'AED': 784, 'AFN': 971, 'ALL': 8, 'AMD': 51, 'ANG': 532, 'AOA': 973,  
'ARS': 32, 'AUD': 36, 'AWG': 533, 'AZN': 944, 'BAM': 977, 'BBD': 52,  
'BDT': 50, 'BGN': 975, 'BHD': 48, 'BIF': 108, 'BMD': 60, 'BND': 96,  
'BOB': 68, 'BOV': 984, 'BRL': 986, 'BSD': 44, 'BTN': 64, 'BWP': 72,  
'BYR': 974, 'BZD': 84, 'CAD': 124, 'CDF': 976, 'CHE': 947, 'CHF': 756,  
'CHW': 948, 'CLF': 990, 'CLP': 152, 'CNY': 156, 'COP': 170, 'COU': 970,  
'CRC': 188, 'CUP': 192, 'CVE': 132, 'CYP': 196, 'CZK': 203, 'DJF': 262,  
'DKK': 208, 'DOP': 214, 'DZD': 12, 'EEK': 233, 'EGP': 818, 'ERN': 232,  
'ETB': 230, 'EUR': 978, 'FJD': 242, 'FKP': 238, 'GBP': 826, 'GEL': 981,  
'GHC': 288, 'GIP': 292, 'GMD': 270, 'GNF': 324, 'GTQ': 320, 'GYD': 328,  
'HKD': 344, 'HNL': 340, 'HRK': 191, 'HTG': 332, 'HUF': 348, 'IDR': 360,  
'ILS': 376, 'INR': 356, 'IQD': 368, 'IRR': 364, 'ISK': 352, 'JMD': 388,  
'JOD': 400, 'JPY': 392, 'KES': 404, 'KGS': 417, 'KHR': 116, 'KMF': 174,  
'KPW': 408, 'KRW': 410, 'KWD': 414, 'KYD': 136, 'KZT': 398, 'LAK': 418,  
'LBP': 422, 'LKR': 144, 'LRD': 430, 'LSL': 426, 'LTL': 440, 'LVL': 428,  
'LYD': 434, 'MAD': 504, 'MDL': 498, 'MGA': 969, 'MKD': 807, 'MMK': 104,  
'MNT': 496, 'MOP': 446, 'MRO': 478, 'MTL': 470, 'MUR': 480, 'MVR': 462,  
'MWK': 454, 'MXN': 484, 'MXV': 979, 'MYR': 458, 'MZN': 943, 'NAD': 516,  
'NGN': 566, 'NIO': 558, 'NOK': 578, 'NPR': 524, 'NZD': 554, 'OMR': 512,  
'PAB': 590, 'PEN': 604, 'PGK': 598, 'PHP': 608, 'PKR': 586, 'PLN': 985,  
'PYG': 600, 'QAR': 634, 'ROL': 642, 'RON': 946, 'RSD': 941, 'RUB': 643,  
'RWF': 646, 'SAR': 682, 'SBD': 90, 'SCR': 690, 'SDD': 736, 'SDG': 938,  
'SEK': 752, 'SGD': 702, 'SHP': 654, 'SKK': 703, 'SLL': 694, 'SOS': 706,  
'SRD': 968, 'STD': 678, 'SYP': 760, 'SZL': 748, 'THB': 764, 'TJS': 972,  
'TMM': 795, 'TND': 788, 'TOP': 776, 'TRY': 949, 'TTD': 780, 'TWD': 901,  
'TZS': 834, 'UAH': 980, 'UGX': 800, 'USD': 840, 'USN': 997, 'USS': 998,  
'UYU': 858, 'UZS': 860, 'VEB': 862, 'VND': 704, 'VUV': 548, 'WST': 882,  
'XAF': 950, 'XAG': 961, 'XAU': 959, 'XBA': 955, 'XBB': 956, 'XBC': 957,  
'XBD': 958, 'XCD': 951, 'XDR': 960, 'XFO': None, 'XFU': None, 'XOF': 952,  
'XPD': 964, 'XPF': 953, 'XPT': 962, 'XTS': 963, 'XXX': 999, 'YER': 886,  
'ZAR': 710, 'ZMK': 894, 'ZWD': 716,  
}  
 
 
class Fare(object):  
"""Represents a fare type."""  
_REQUIRED_FIELD_NAMES = ['fare_id', 'price', 'currency_type',  
'payment_method', 'transfers']  
_FIELD_NAMES = _REQUIRED_FIELD_NAMES + ['transfer_duration']  
 
def __init__(self,  
fare_id=None, price=None, currency_type=None,  
payment_method=None, transfers=None, transfer_duration=None,  
field_list=None):  
self.rules = []  
(self.fare_id, self.price, self.currency_type, self.payment_method,  
self.transfers, self.transfer_duration) = \  
(fare_id, price, currency_type, payment_method,  
transfers, transfer_duration)  
if field_list:  
(self.fare_id, self.price, self.currency_type, self.payment_method,  
self.transfers, self.transfer_duration) = field_list  
 
try:  
self.price = float(self.price)  
except (TypeError, ValueError):  
pass  
try:  
self.payment_method = int(self.payment_method)  
except (TypeError, ValueError):  
pass  
if self.transfers == None or self.transfers == "":  
self.transfers = None  
else:  
try:  
self.transfers = int(self.transfers)  
except (TypeError, ValueError):  
pass  
if self.transfer_duration == None or self.transfer_duration == "":  
self.transfer_duration = None  
else:  
try:  
self.transfer_duration = int(self.transfer_duration)  
except (TypeError, ValueError):  
pass  
 
def GetFareRuleList(self):  
return self.rules  
 
def ClearFareRules(self):  
self.rules = []  
 
def GetFieldValuesTuple(self):  
return [getattr(self, fn) for fn in Fare._FIELD_NAMES]  
 
def __getitem__(self, name):  
return getattr(self, name)  
 
def __eq__(self, other):  
if not other:  
return False  
 
if id(self) == id(other):  
return True  
 
if self.GetFieldValuesTuple() != other.GetFieldValuesTuple():  
return False  
 
self_rules = [r.GetFieldValuesTuple() for r in self.GetFareRuleList()]  
self_rules.sort()  
other_rules = [r.GetFieldValuesTuple() for r in other.GetFareRuleList()]  
other_rules.sort()  
return self_rules == other_rules  
 
def __ne__(self, other):  
return not self.__eq__(other)  
 
def Validate(self, problems=default_problem_reporter):  
if IsEmpty(self.fare_id):  
problems.MissingValue("fare_id")  
 
if self.price == None:  
problems.MissingValue("price")  
elif not isinstance(self.price, float) and not isinstance(self.price, int):  
problems.InvalidValue("price", self.price)  
elif self.price < 0:  
problems.InvalidValue("price", self.price)  
 
if IsEmpty(self.currency_type):  
problems.MissingValue("currency_type")  
elif self.currency_type not in ISO4217.codes:  
problems.InvalidValue("currency_type", self.currency_type)  
 
if self.payment_method == "" or self.payment_method == None:  
problems.MissingValue("payment_method")  
elif (not isinstance(self.payment_method, int) or  
self.payment_method not in range(0, 2)):  
problems.InvalidValue("payment_method", self.payment_method)  
 
if not ((self.transfers == None) or  
(isinstance(self.transfers, int) and  
self.transfers in range(0, 3))):  
problems.InvalidValue("transfers", self.transfers)  
 
if ((self.transfer_duration != None) and  
not isinstance(self.transfer_duration, int)):  
problems.InvalidValue("transfer_duration", self.transfer_duration)  
if self.transfer_duration and (self.transfer_duration < 0):  
problems.InvalidValue("transfer_duration", self.transfer_duration)  
if (self.transfer_duration and (self.transfer_duration > 0) and  
self.transfers == 0):  
problems.InvalidValue("transfer_duration", self.transfer_duration,  
"can't have a nonzero transfer_duration for "  
"a fare that doesn't allow transfers!")  
 
 
class FareRule(object):  
"""This class represents a rule that determines which itineraries a  
fare rule applies to."""  
_REQUIRED_FIELD_NAMES = ['fare_id']  
_FIELD_NAMES = _REQUIRED_FIELD_NAMES + ['route_id',  
'origin_id', 'destination_id',  
'contains_id']  
 
def __init__(self, fare_id=None, route_id=None,  
origin_id=None, destination_id=None, contains_id=None,  
field_list=None):  
(self.fare_id, self.route_id, self.origin_id, self.destination_id,  
self.contains_id) = \  
(fare_id, route_id, origin_id, destination_id, contains_id)  
if field_list:  
(self.fare_id, self.route_id, self.origin_id, self.destination_id,  
self.contains_id) = field_list  
 
# canonicalize non-content values as None  
if not self.route_id:  
self.route_id = None  
if not self.origin_id:  
self.origin_id = None  
if not self.destination_id:  
self.destination_id = None  
if not self.contains_id:  
self.contains_id = None  
 
def GetFieldValuesTuple(self):  
return [getattr(self, fn) for fn in FareRule._FIELD_NAMES]  
 
def __getitem__(self, name):  
return getattr(self, name)  
 
def __eq__(self, other):  
if not other:  
return False  
 
if id(self) == id(other):  
return True  
 
return self.GetFieldValuesTuple() == other.GetFieldValuesTuple()  
 
def __ne__(self, other):  
return not self.__eq__(other)  
 
 
class Shape(object):  
"""This class represents a geographic shape that corresponds to the route  
taken by one or more Trips."""  
_REQUIRED_FIELD_NAMES = ['shape_id', 'shape_pt_lat', 'shape_pt_lon',  
'shape_pt_sequence']  
_FIELD_NAMES = _REQUIRED_FIELD_NAMES + ['shape_dist_traveled']  
def __init__(self, shape_id):  
# List of shape point tuple (lat, lng, shape_dist_traveled), where lat and  
# lon is the location of the shape point, and shape_dist_traveled is an  
# increasing metric representing the distance traveled along the shape.  
self.points = []  
# An ID that uniquely identifies a shape in the dataset.  
self.shape_id = shape_id  
# The max shape_dist_traveled of shape points in this shape.  
self.max_distance = 0  
# List of shape_dist_traveled of each shape point.  
self.distance = []  
 
def AddPoint(self, lat, lon, distance=None,  
problems=default_problem_reporter):  
 
try:  
lat = float(lat)  
if abs(lat) > 90.0:  
problems.InvalidValue('shape_pt_lat', lat)  
return  
except (TypeError, ValueError):  
problems.InvalidValue('shape_pt_lat', lat)  
return  
 
try:  
lon = float(lon)  
if abs(lon) > 180.0:  
problems.InvalidValue('shape_pt_lon', lon)  
return  
except (TypeError, ValueError):  
problems.InvalidValue('shape_pt_lon', lon)  
return  
 
if (abs(lat) < 1.0) and (abs(lon) < 1.0):  
problems.InvalidValue('shape_pt_lat', lat,  
'Point location too close to 0, 0, which means '  
'that it\'s probably an incorrect location.',  
type=TYPE_WARNING)  
return  
 
if distance == '': # canonicalizing empty string to None for comparison  
distance = None  
 
if distance != None:  
try:  
distance = float(distance)  
if (distance < self.max_distance and not  
(len(self.points) == 0 and distance == 0)): # first one can be 0  
problems.InvalidValue('shape_dist_traveled', distance,  
'Each subsequent point in a shape should '  
'have a distance value that\'s at least as '  
'large as the previous ones. In this case, '  
'the previous distance was %f.' %  
self.max_distance)  
return  
else:  
self.max_distance = distance  
self.distance.append(distance)  
except (TypeError, ValueError):  
problems.InvalidValue('shape_dist_traveled', distance,  
'This value should be a positive number.')  
return  
 
self.points.append((lat, lon, distance))  
 
def ClearPoints(self):  
self.points = []  
 
def __eq__(self, other):  
if not other:  
return False  
 
if id(self) == id(other):  
return True  
 
return self.points == other.points  
 
def __ne__(self, other):  
return not self.__eq__(other)  
 
def __repr__(self):  
return "<Shape %s>" % self.__dict__  
 
def Validate(self, problems=default_problem_reporter):  
if IsEmpty(self.shape_id):  
problems.MissingValue('shape_id')  
 
if not self.points:  
problems.OtherProblem('The shape with shape_id "%s" contains no points.' %  
self.shape_id, type=TYPE_WARNING)  
 
def GetPointWithDistanceTraveled(self, shape_dist_traveled):  
"""Returns a point on the shape polyline with the input shape_dist_traveled.  
 
Args:  
shape_dist_traveled: The input shape_dist_traveled.  
 
Returns:  
The shape point as a tuple (lat, lng, shape_dist_traveled), where lat and  
lng is the location of the shape point, and shape_dist_traveled is an  
increasing metric representing the distance traveled along the shape.  
Returns None if there is data error in shape.  
"""  
if not self.distance:  
return None  
if shape_dist_traveled <= self.distance[0]:  
return self.points[0]  
if shape_dist_traveled >= self.distance[-1]:  
return self.points[-1]  
 
index = bisect.bisect(self.distance, shape_dist_traveled)  
(lat0, lng0, dist0) = self.points[index - 1]  
(lat1, lng1, dist1) = self.points[index]  
 
# Interpolate if shape_dist_traveled does not equal to any of the point  
# in shape segment.  
# (lat0, lng0) (lat, lng) (lat1, lng1)  
# -----|--------------------|---------------------|------  
# dist0 shape_dist_traveled dist1  
# \------- ca --------/ \-------- bc -------/  
# \----------------- ba ------------------/  
ca = shape_dist_traveled - dist0  
bc = dist1 - shape_dist_traveled  
ba = bc + ca  
if ba == 0:  
# This only happens when there's data error in shapes and should have been  
# catched before. Check to avoid crash.  
return None  
# This won't work crossing longitude 180 and is only an approximation which  
# works well for short distance.  
lat = (lat1 * ca + lat0 * bc) / ba  
lng = (lng1 * ca + lng0 * bc) / ba  
return (lat, lng, shape_dist_traveled)  
 
 
class ISO639(object):  
# Set of all the 2-letter ISO 639-1 language codes.  
codes_2letter = set([  
'aa', 'ab', 'ae', 'af', 'ak', 'am', 'an', 'ar', 'as', 'av', 'ay', 'az',  
'ba', 'be', 'bg', 'bh', 'bi', 'bm', 'bn', 'bo', 'br', 'bs', 'ca', 'ce',  
'ch', 'co', 'cr', 'cs', 'cu', 'cv', 'cy', 'da', 'de', 'dv', 'dz', 'ee',  
'el', 'en', 'eo', 'es', 'et', 'eu', 'fa', 'ff', 'fi', 'fj', 'fo', 'fr',  
'fy', 'ga', 'gd', 'gl', 'gn', 'gu', 'gv', 'ha', 'he', 'hi', 'ho', 'hr',  
'ht', 'hu', 'hy', 'hz', 'ia', 'id', 'ie', 'ig', 'ii', 'ik', 'io', 'is',  
'it', 'iu', 'ja', 'jv', 'ka', 'kg', 'ki', 'kj', 'kk', 'kl', 'km', 'kn',  
'ko', 'kr', 'ks', 'ku', 'kv', 'kw', 'ky', 'la', 'lb', 'lg', 'li', 'ln',  
'lo', 'lt', 'lu', 'lv', 'mg', 'mh', 'mi', 'mk', 'ml', 'mn', 'mo', 'mr',  
'ms', 'mt', 'my', 'na', 'nb', 'nd', 'ne', 'ng', 'nl', 'nn', 'no', 'nr',  
'nv', 'ny', 'oc', 'oj', 'om', 'or', 'os', 'pa', 'pi', 'pl', 'ps', 'pt',  
'qu', 'rm', 'rn', 'ro', 'ru', 'rw', 'sa', 'sc', 'sd', 'se', 'sg', 'si',  
'sk', 'sl', 'sm', 'sn', 'so', 'sq', 'sr', 'ss', 'st', 'su', 'sv', 'sw',  
'ta', 'te', 'tg', 'th', 'ti', 'tk', 'tl', 'tn', 'to', 'tr', 'ts', 'tt',  
'tw', 'ty', 'ug', 'uk', 'ur', 'uz', 've', 'vi', 'vo', 'wa', 'wo', 'xh',  
'yi', 'yo', 'za', 'zh', 'zu',  
])  
 
 
class Agency(GenericGTFSObject):  
"""Represents an agency in a schedule.  
 
Callers may assign arbitrary values to instance attributes. __init__ makes no  
attempt at validating the attributes. Call Validate() to check that  
attributes are valid and the agency object is consistent with itself.  
 
Attributes:  
All attributes are strings.  
"""  
_REQUIRED_FIELD_NAMES = ['agency_name', 'agency_url', 'agency_timezone']  
_FIELD_NAMES = _REQUIRED_FIELD_NAMES + ['agency_id', 'agency_lang',  
'agency_phone']  
_TABLE_NAME = 'agency'  
 
def __init__(self, name=None, url=None, timezone=None, id=None,  
field_dict=None, lang=None, **kwargs):  
"""Initialize a new Agency object.  
 
Args:  
field_dict: A dictionary mapping attribute name to unicode string  
name: a string, ignored when field_dict is present  
url: a string, ignored when field_dict is present  
timezone: a string, ignored when field_dict is present  
id: a string, ignored when field_dict is present  
kwargs: arbitrary keyword arguments may be used to add attributes to the  
new object, ignored when field_dict is present  
"""  
self._schedule = None  
 
if not field_dict:  
if name:  
kwargs['agency_name'] = name  
if url:  
kwargs['agency_url'] = url  
if timezone:  
kwargs['agency_timezone'] = timezone  
if id:  
kwargs['agency_id'] = id  
if lang:  
kwargs['agency_lang'] = lang  
field_dict = kwargs  
 
self.__dict__.update(field_dict)  
 
def Validate(self, problems=default_problem_reporter):  
"""Validate attribute values and this object's internal consistency.  
 
Returns:  
True iff all validation checks passed.  
"""  
found_problem = False  
for required in Agency._REQUIRED_FIELD_NAMES:  
if IsEmpty(getattr(self, required, None)):  
problems.MissingValue(required)  
found_problem = True  
 
if self.agency_url and not IsValidURL(self.agency_url):  
problems.InvalidValue('agency_url', self.agency_url)  
found_problem = True  
 
if (not IsEmpty(self.agency_lang) and  
self.agency_lang.lower() not in ISO639.codes_2letter):  
problems.InvalidValue('agency_lang', self.agency_lang)  
found_problem = True  
 
try:  
import pytz  
if self.agency_timezone not in pytz.common_timezones:  
problems.InvalidValue(  
'agency_timezone',  
self.agency_timezone,  
'"%s" is not a common timezone name according to pytz version %s' %  
(self.agency_timezone, pytz.VERSION))  
found_problem = True  
except ImportError: # no pytz  
print ("Timezone not checked "  
"(install pytz package for timezone validation)")  
return not found_problem  
 
 
class Transfer(object):  
"""Represents a transfer in a schedule"""  
_REQUIRED_FIELD_NAMES = ['from_stop_id', 'to_stop_id', 'transfer_type']  
_FIELD_NAMES = _REQUIRED_FIELD_NAMES + ['min_transfer_time']  
 
def __init__(self, schedule=None, from_stop_id=None, to_stop_id=None, transfer_type=None,  
min_transfer_time=None, field_dict=None):  
if schedule is not None:  
self._schedule = weakref.proxy(schedule) # See weakref comment at top  
else:  
self._schedule = None  
if field_dict:  
self.__dict__.update(field_dict)  
else:  
self.from_stop_id = from_stop_id  
self.to_stop_id = to_stop_id  
self.transfer_type = transfer_type  
self.min_transfer_time = min_transfer_time  
 
if getattr(self, 'transfer_type', None) in ("", None):  
# Use the default, recommended transfer, if attribute is not set or blank  
self.transfer_type = 0  
else:  
try:  
self.transfer_type = NonNegIntStringToInt(self.transfer_type)  
except (TypeError, ValueError):  
pass  
 
if hasattr(self, 'min_transfer_time'):  
try:  
self.min_transfer_time = NonNegIntStringToInt(self.min_transfer_time)  
except (TypeError, ValueError):  
pass  
else:  
self.min_transfer_time = None  
 
def GetFieldValuesTuple(self):  
return [getattr(self, fn) for fn in Transfer._FIELD_NAMES]  
 
def __getitem__(self, name):  
return getattr(self, name)  
 
def __eq__(self, other):  
if not other:  
return False  
 
if id(self) == id(other):  
return True  
 
return self.GetFieldValuesTuple() == other.GetFieldValuesTuple()  
 
def __ne__(self, other):  
return not self.__eq__(other)  
 
def __repr__(self):  
return "<Transfer %s>" % self.__dict__  
 
def Validate(self, problems=default_problem_reporter):  
if IsEmpty(self.from_stop_id):  
problems.MissingValue('from_stop_id')  
elif self._schedule:  
if self.from_stop_id not in self._schedule.stops.keys():  
problems.InvalidValue('from_stop_id', self.from_stop_id)  
 
if IsEmpty(self.to_stop_id):  
problems.MissingValue('to_stop_id')  
elif self._schedule:  
if self.to_stop_id not in self._schedule.stops.keys():  
problems.InvalidValue('to_stop_id', self.to_stop_id)  
 
if not IsEmpty(self.transfer_type):  
if (not isinstance(self.transfer_type, int)) or \  
(self.transfer_type not in range(0, 4)):  
problems.InvalidValue('transfer_type', self.transfer_type)  
 
if not IsEmpty(self.min_transfer_time):  
if (not isinstance(self.min_transfer_time, int)) or \  
self.min_transfer_time < 0:  
problems.InvalidValue('min_transfer_time', self.min_transfer_time)  
 
 
class ServicePeriod(object):  
"""Represents a service, which identifies a set of dates when one or more  
trips operate."""  
_DAYS_OF_WEEK = [  
'monday', 'tuesday', 'wednesday', 'thursday', 'friday',  
'saturday', 'sunday'  
]  
_FIELD_NAMES_REQUIRED = [  
'service_id', 'start_date', 'end_date'  
] + _DAYS_OF_WEEK  
_FIELD_NAMES = _FIELD_NAMES_REQUIRED # no optional fields in this one  
_FIELD_NAMES_CALENDAR_DATES = ['service_id', 'date', 'exception_type']  
 
def __init__(self, id=None, field_list=None):  
self.original_day_values = []  
if field_list:  
self.service_id = field_list[self._FIELD_NAMES.index('service_id')]  
self.day_of_week = [False] * len(self._DAYS_OF_WEEK)  
 
for day in self._DAYS_OF_WEEK:  
value = field_list[self._FIELD_NAMES.index(day)] or '' # can be None  
self.original_day_values += [value.strip()]  
self.day_of_week[self._DAYS_OF_WEEK.index(day)] = (value == u'1')  
 
self.start_date = field_list[self._FIELD_NAMES.index('start_date')]  
self.end_date = field_list[self._FIELD_NAMES.index('end_date')]  
else:  
self.service_id = id  
self.day_of_week = [False] * 7  
self.start_date = None  
self.end_date = None  
self.date_exceptions = {} # Map from 'YYYYMMDD' to 1 (add) or 2 (remove)  
 
def _IsValidDate(self, date):  
if re.match('^\d{8}$', date) == None:  
return False  
 
try:  
time.strptime(date, "%Y%m%d")  
return True  
except ValueError:  
return False  
 
def GetDateRange(self):  
"""Return the range over which this ServicePeriod is valid.  
 
The range includes exception dates that add service outside of  
(start_date, end_date), but doesn't shrink the range if exception  
dates take away service at the edges of the range.  
 
Returns:  
A tuple of "YYYYMMDD" strings, (start date, end date) or (None, None) if  
no dates have been given.  
"""  
start = self.start_date  
end = self.end_date  
 
for date in self.date_exceptions:  
if self.date_exceptions[date] == 2:  
continue  
if not start or (date < start):  
start = date  
if not end or (date > end):  
end = date  
if start is None:  
start = end  
elif end is None:  
end = start  
# If start and end are None we did a little harmless shuffling  
return (start, end)  
 
def GetCalendarFieldValuesTuple(self):  
"""Return the tuple of calendar.txt values or None if this ServicePeriod  
should not be in calendar.txt ."""  
if self.start_date and self.end_date:  
return [getattr(self, fn) for fn in ServicePeriod._FIELD_NAMES]  
 
def GenerateCalendarDatesFieldValuesTuples(self):  
"""Generates tuples of calendar_dates.txt values. Yield zero tuples if  
this ServicePeriod should not be in calendar_dates.txt ."""  
for date, exception_type in self.date_exceptions.items():  
yield (self.service_id, date, unicode(exception_type))  
 
def GetCalendarDatesFieldValuesTuples(self):  
"""Return a list of date execeptions"""  
result = []  
for date_tuple in self.GenerateCalendarDatesFieldValuesTuples():  
result.append(date_tuple)  
result.sort() # helps with __eq__  
return result  
 
def SetDateHasService(self, date, has_service=True, problems=None):  
if date in self.date_exceptions and problems:  
problems.DuplicateID(('service_id', 'date'),  
(self.service_id, date),  
type=TYPE_WARNING)  
self.date_exceptions[date] = has_service and 1 or 2  
 
def ResetDateToNormalService(self, date):  
if date in self.date_exceptions:  
del self.date_exceptions[date]  
 
def SetStartDate(self, start_date):  
"""Set the first day of service as a string in YYYYMMDD format"""  
self.start_date = start_date  
 
def SetEndDate(self, end_date):  
"""Set the last day of service as a string in YYYYMMDD format"""  
self.end_date = end_date  
 
def SetDayOfWeekHasService(self, dow, has_service=True):  
"""Set service as running (or not) on a day of the week. By default the  
service does not run on any days.  
 
Args:  
dow: 0 for Monday through 6 for Sunday  
has_service: True if this service operates on dow, False if it does not.  
 
Returns:  
None  
"""  
assert(dow >= 0 and dow < 7)  
self.day_of_week[dow] = has_service  
 
def SetWeekdayService(self, has_service=True):  
"""Set service as running (or not) on all of Monday through Friday."""  
for i in range(0, 5):  
self.SetDayOfWeekHasService(i, has_service)  
 
def SetWeekendService(self, has_service=True):  
"""Set service as running (or not) on Saturday and Sunday."""  
self.SetDayOfWeekHasService(5, has_service)  
self.SetDayOfWeekHasService(6, has_service)  
 
def SetServiceId(self, service_id):  
"""Set the service_id for this schedule. Generally the default will  
suffice so you won't need to call this method."""  
self.service_id = service_id  
 
def IsActiveOn(self, date, date_object=None):  
"""Test if this service period is active on a date.  
 
Args:  
date: a string of form "YYYYMMDD"  
date_object: a date object representing the same date as date.  
This parameter is optional, and present only for performance  
reasons.  
If the caller constructs the date string from a date object  
that date object can be passed directly, thus avoiding the  
costly conversion from string to date object.  
 
Returns:  
True iff this service is active on date.  
"""  
if date in self.date_exceptions:  
if self.date_exceptions[date] == 1:  
return True  
else:  
return False  
if (self.start_date and self.end_date and self.start_date <= date and  
date <= self.end_date):  
if date_object is None:  
date_object = DateStringToDateObject(date)  
return self.day_of_week[date_object.weekday()]  
return False  
 
def ActiveDates(self):  
"""Return dates this service period is active as a list of "YYYYMMDD"."""  
(earliest, latest) = self.GetDateRange()  
if earliest is None:  
return []  
dates = []  
date_it = DateStringToDateObject(earliest)  
date_end = DateStringToDateObject(latest)  
delta = datetime.timedelta(days=1)  
while date_it <= date_end:  
date_it_string = date_it.strftime("%Y%m%d")  
if self.IsActiveOn(date_it_string, date_it):  
dates.append(date_it_string)  
date_it = date_it + delta  
return dates  
 
def __getattr__(self, name):  
try:  
# Return 1 if value in day_of_week is True, 0 otherwise  
return (self.day_of_week[ServicePeriod._DAYS_OF_WEEK.index(name)]  
and 1 or 0)  
except KeyError:  
pass  
except ValueError: # not a day of the week  
pass  
raise AttributeError(name)  
 
def __getitem__(self, name):  
return getattr(self, name)  
 
def __eq__(self, other):  
if not other:  
return False  
 
if id(self) == id(other):  
return True  
 
if (self.GetCalendarFieldValuesTuple() !=  
other.GetCalendarFieldValuesTuple()):  
return False  
 
if (self.GetCalendarDatesFieldValuesTuples() !=  
other.GetCalendarDatesFieldValuesTuples()):  
return False  
 
return True  
 
def __ne__(self, other):  
return not self.__eq__(other)  
 
def Validate(self, problems=default_problem_reporter):  
if IsEmpty(self.service_id):  
problems.MissingValue('service_id')  
# self.start_date/self.end_date is None in 3 cases:  
# ServicePeriod created by loader and  
# 1a) self.service_id wasn't in calendar.txt  
# 1b) calendar.txt didn't have a start_date/end_date column  
# ServicePeriod created directly and  
# 2) start_date/end_date wasn't set  
# In case 1a no problem is reported. In case 1b the missing required column  
# generates an error in _ReadCSV so this method should not report another  
# problem. There is no way to tell the difference between cases 1b and 2  
# so case 2 is ignored because making the feedvalidator pretty is more  
# important than perfect validation when an API users makes a mistake.  
start_date = None  
if self.start_date is not None:  
if IsEmpty(self.start_date):  
problems.MissingValue('start_date')  
elif self._IsValidDate(self.start_date):  
start_date = self.start_date  
else:  
problems.InvalidValue('start_date', self.start_date)  
end_date = None  
if self.end_date is not None:  
if IsEmpty(self.end_date):  
problems.MissingValue('end_date')  
elif self._IsValidDate(self.end_date):  
end_date = self.end_date  
else:  
problems.InvalidValue('end_date', self.end_date)  
if start_date and end_date and end_date < start_date:  
problems.InvalidValue('end_date', end_date,  
'end_date of %s is earlier than '  
'start_date of "%s"' %  
(end_date, start_date))  
if self.original_day_values:  
index = 0  
for value in self.original_day_values:  
column_name = self._DAYS_OF_WEEK[index]  
if IsEmpty(value):  
problems.MissingValue(column_name)  
elif (value != u'0') and (value != '1'):  
problems.InvalidValue(column_name, value)  
index += 1  
if (True not in self.day_of_week and  
1 not in self.date_exceptions.values()):  
problems.OtherProblem('Service period with service_id "%s" '  
'doesn\'t have service on any days '  
'of the week.' % self.service_id,  
type=TYPE_WARNING)  
for date in self.date_exceptions:  
if not self._IsValidDate(date):  
problems.InvalidValue('date', date)  
 
 
class CsvUnicodeWriter:  
"""  
Create a wrapper around a csv writer object which can safely write unicode  
values. Passes all arguments to csv.writer.  
"""  
def __init__(self, *args, **kwargs):  
self.writer = csv.writer(*args, **kwargs)  
 
def writerow(self, row):  
"""Write row to the csv file. Any unicode strings in row are encoded as  
utf-8."""  
encoded_row = []  
for s in row:  
if isinstance(s, unicode):  
encoded_row.append(s.encode("utf-8"))  
else:  
encoded_row.append(s)  
try:  
self.writer.writerow(encoded_row)  
except Exception, e:  
print 'error writing %s as %s' % (row, encoded_row)  
raise e  
 
def writerows(self, rows):  
"""Write rows to the csv file. Any unicode strings in rows are encoded as  
utf-8."""  
for row in rows:  
self.writerow(row)  
 
def __getattr__(self, name):  
return getattr(self.writer, name)  
 
 
class Schedule:  
"""Represents a Schedule, a collection of stops, routes, trips and  
an agency. This is the main class for this module."""  
 
def __init__(self, problem_reporter=default_problem_reporter,  
memory_db=True, check_duplicate_trips=False):  
# Map from table name to list of columns present in this schedule  
self._table_columns = {}  
 
self._agencies = {}  
self.stops = {}  
self.routes = {}  
self.trips = {}  
self.service_periods = {}  
self.fares = {}  
self.fare_zones = {} # represents the set of all known fare zones  
self._shapes = {} # shape_id to Shape  
self._transfers = [] # list of transfers  
self._default_service_period = None  
self._default_agency = None  
self.problem_reporter = problem_reporter  
self._check_duplicate_trips = check_duplicate_trips  
self.ConnectDb(memory_db)  
 
def AddTableColumn(self, table, column):  
"""Add column to table if it is not already there."""  
if column not in self._table_columns[table]:  
self._table_columns[table].append(column)  
 
def AddTableColumns(self, table, columns):  
"""Add columns to table if they are not already there.  
 
Args:  
table: table name as a string  
columns: an iterable of column names"""  
table_columns = self._table_columns.setdefault(table, [])  
for attr in columns:  
if attr not in table_columns:  
table_columns.append(attr)  
 
def GetTableColumns(self, table):  
"""Return list of columns in a table."""  
return self._table_columns[table]  
 
def __del__(self):  
if hasattr(self, '_temp_db_filename'):  
os.remove(self._temp_db_filename)  
 
def ConnectDb(self, memory_db):  
if memory_db:  
self._connection = sqlite.connect(":memory:")  
else:  
try:  
self._temp_db_file = tempfile.NamedTemporaryFile()  
self._connection = sqlite.connect(self._temp_db_file.name)  
except sqlite.OperationalError:  
# Windows won't let a file be opened twice. mkstemp does not remove the  
# file when all handles to it are closed.  
self._temp_db_file = None  
(fd, self._temp_db_filename) = tempfile.mkstemp(".db")  
os.close(fd)  
self._connection = sqlite.connect(self._temp_db_filename)  
 
cursor = self._connection.cursor()  
cursor.execute("""CREATE TABLE stop_times (  
trip_id CHAR(50),  
arrival_secs INTEGER,  
departure_secs INTEGER,  
stop_id CHAR(50),  
stop_sequence INTEGER,  
stop_headsign VAR CHAR(100),  
pickup_type INTEGER,  
drop_off_type INTEGER,  
shape_dist_traveled FLOAT);""")  
cursor.execute("""CREATE INDEX trip_index ON stop_times (trip_id);""")  
cursor.execute("""CREATE INDEX stop_index ON stop_times (stop_id);""")  
 
def GetStopBoundingBox(self):  
return (min(s.stop_lat for s in self.stops.values()),  
min(s.stop_lon for s in self.stops.values()),  
max(s.stop_lat for s in self.stops.values()),  
max(s.stop_lon for s in self.stops.values()),  
)  
 
def AddAgency(self, name, url, timezone, agency_id=None):  
"""Adds an agency to this schedule."""  
agency = Agency(name, url, timezone, agency_id)  
self.AddAgencyObject(agency)  
return agency  
 
def AddAgencyObject(self, agency, problem_reporter=None, validate=True):  
assert agency._schedule is None  
 
if not problem_reporter:  
problem_reporter = self.problem_reporter  
 
if agency.agency_id in self._agencies:  
problem_reporter.DuplicateID('agency_id', agency.agency_id)  
return  
 
self.AddTableColumns('agency', agency._ColumnNames())  
agency._schedule = weakref.proxy(self)  
 
if validate:  
agency.Validate(problem_reporter)  
self._agencies[agency.agency_id] = agency  
 
def GetAgency(self, agency_id):  
"""Return Agency with agency_id or throw a KeyError"""  
return self._agencies[agency_id]  
 
def GetDefaultAgency(self):  
"""Return the default Agency. If no default Agency has been set select the  
default depending on how many Agency objects are in the Schedule. If there  
are 0 make a new Agency the default, if there is 1 it becomes the default,  
if there is more than 1 then return None.  
"""  
if not self._default_agency:  
if len(self._agencies) == 0:  
self.NewDefaultAgency()  
elif len(self._agencies) == 1:  
self._default_agency = self._agencies.values()[0]  
return self._default_agency  
 
def NewDefaultAgency(self, **kwargs):  
"""Create a new Agency object and make it the default agency for this Schedule"""  
agency = Agency(**kwargs)  
if not agency.agency_id:  
agency.agency_id = FindUniqueId(self._agencies)  
self._default_agency = agency  
self.SetDefaultAgency(agency, validate=False) # Blank agency won't validate  
return agency  
 
def SetDefaultAgency(self, agency, validate=True):  
"""Make agency the default and add it to the schedule if not already added"""  
assert isinstance(agency, Agency)  
self._default_agency = agency  
if agency.agency_id not in self._agencies:  
self.AddAgencyObject(agency, validate=validate)  
 
def GetAgencyList(self):  
"""Returns the list of Agency objects known to this Schedule."""  
return self._agencies.values()  
 
def GetServicePeriod(self, service_id):  
"""Returns the ServicePeriod object with the given ID."""  
return self.service_periods[service_id]  
 
def GetDefaultServicePeriod(self):  
"""Return the default ServicePeriod. If no default ServicePeriod has been  
set select the default depending on how many ServicePeriod objects are in  
the Schedule. If there are 0 make a new ServicePeriod the default, if there  
is 1 it becomes the default, if there is more than 1 then return None.  
"""  
if not self._default_service_period:  
if len(self.service_periods) == 0:  
self.NewDefaultServicePeriod()  
elif len(self.service_periods) == 1:  
self._default_service_period = self.service_periods.values()[0]  
return self._default_service_period  
 
def NewDefaultServicePeriod(self):  
"""Create a new ServicePeriod object, make it the default service period and  
return it. The default service period is used when you create a trip without  
providing an explict service period. """  
service_period = ServicePeriod()  
service_period.service_id = FindUniqueId(self.service_periods)  
# blank service won't validate in AddServicePeriodObject  
self.SetDefaultServicePeriod(service_period, validate=False)  
return service_period  
 
def SetDefaultServicePeriod(self, service_period, validate=True):  
assert isinstance(service_period, ServicePeriod)  
self._default_service_period = service_period  
if service_period.service_id not in self.service_periods:  
self.AddServicePeriodObject(service_period, validate=validate)  
 
def AddServicePeriodObject(self, service_period, problem_reporter=None,  
validate=True):  
if not problem_reporter:  
problem_reporter = self.problem_reporter  
 
if service_period.service_id in self.service_periods:  
problem_reporter.DuplicateID('service_id', service_period.service_id)  
return  
 
if validate:  
service_period.Validate(problem_reporter)  
self.service_periods[service_period.service_id] = service_period  
 
def GetServicePeriodList(self):  
return self.service_periods.values()  
 
def GetDateRange(self):  
"""Returns a tuple of (earliest, latest) dates on which the service  
periods in the schedule define service, in YYYYMMDD form."""  
 
ranges = [period.GetDateRange() for period in self.GetServicePeriodList()]  
starts = filter(lambda x: x, [item[0] for item in ranges])  
ends = filter(lambda x: x, [item[1] for item in ranges])  
 
if not starts or not ends:  
return (None, None)  
 
return (min(starts), max(ends))  
 
def GetServicePeriodsActiveEachDate(self, date_start, date_end):  
"""Return a list of tuples (date, [period1, period2, ...]).  
 
For each date in the range [date_start, date_end) make list of each  
ServicePeriod object which is active.  
 
Args:  
date_start: The first date in the list, a date object  
date_end: The first date after the list, a date object  
 
Returns:  
A list of tuples. Each tuple contains a date object and a list of zero or  
more ServicePeriod objects.  
"""  
date_it = date_start  
one_day = datetime.timedelta(days=1)  
date_service_period_list = []  
while date_it < date_end:  
periods_today = []  
date_it_string = date_it.strftime("%Y%m%d")  
for service in self.GetServicePeriodList():  
if service.IsActiveOn(date_it_string, date_it):  
periods_today.append(service)  
date_service_period_list.append((date_it, periods_today))  
date_it += one_day  
return date_service_period_list  
 
 
def AddStop(self, lat, lng, name):  
"""Add a stop to this schedule.  
 
A new stop_id is created for this stop. Do not use this method unless all  
stops in this Schedule are created with it. See source for details.  
 
Args:  
lat: Latitude of the stop as a float or string  
lng: Longitude of the stop as a float or string  
name: Name of the stop, which will appear in the feed  
 
Returns:  
A new Stop object  
"""  
# TODO: stop_id isn't guarenteed to be unique and conflicts are not  
# handled. Please fix.  
stop_id = unicode(len(self.stops))  
stop = Stop(stop_id=stop_id, lat=lat, lng=lng, name=name)  
self.AddStopObject(stop)  
return stop  
 
def AddStopObject(self, stop, problem_reporter=None):  
"""Add Stop object to this schedule if stop_id is non-blank."""  
assert stop._schedule is None  
if not problem_reporter:  
problem_reporter = self.problem_reporter  
 
if not stop.stop_id:  
return  
 
if stop.stop_id in self.stops:  
problem_reporter.DuplicateID('stop_id', stop.stop_id)  
return  
 
stop._schedule = weakref.proxy(self)  
self.AddTableColumns('stops', stop._ColumnNames())  
self.stops[stop.stop_id] = stop  
if hasattr(stop, 'zone_id') and stop.zone_id:  
self.fare_zones[stop.zone_id] = True  
 
def GetStopList(self):  
return self.stops.values()  
 
def AddRoute(self, short_name, long_name, route_type):  
"""Add a route to this schedule.  
 
Args:  
short_name: Short name of the route, such as "71L"  
long_name: Full name of the route, such as "NW 21st Ave/St Helens Rd"  
route_type: A type such as "Tram", "Subway" or "Bus"  
Returns:  
A new Route object  
"""  
route_id = unicode(len(self.routes))  
route = Route(short_name=short_name, long_name=long_name,  
route_type=route_type, route_id=route_id)  
route.agency_id = self.GetDefaultAgency().agency_id  
self.AddRouteObject(route)  
return route  
 
def AddRouteObject(self, route, problem_reporter=None):  
if not problem_reporter:  
problem_reporter = self.problem_reporter  
 
route.Validate(problem_reporter)  
 
if route.route_id in self.routes:  
problem_reporter.DuplicateID('route_id', route.route_id)  
return  
 
if route.agency_id not in self._agencies:  
if not route.agency_id and len(self._agencies) == 1:  
# we'll just assume that the route applies to the only agency  
pass  
else:  
problem_reporter.InvalidValue('agency_id', route.agency_id,  
'Route uses an unknown agency_id.')  
return  
 
self.AddTableColumns('routes', route._ColumnNames())  
route._schedule = weakref.proxy(self)  
self.routes[route.route_id] = route  
 
def GetRouteList(self):  
return self.routes.values()  
 
def GetRoute(self, route_id):  
return self.routes[route_id]  
 
def AddShapeObject(self, shape, problem_reporter=None):  
if not problem_reporter:  
problem_reporter = self.problem_reporter  
 
shape.Validate(problem_reporter)  
 
if shape.shape_id in self._shapes:  
problem_reporter.DuplicateID('shape_id', shape.shape_id)  
return  
 
self._shapes[shape.shape_id] = shape  
 
def GetShapeList(self):  
return self._shapes.values()  
 
def GetShape(self, shape_id):  
return self._shapes[shape_id]  
 
def AddTripObject(self, trip, problem_reporter=None, validate=True):  
if not problem_reporter:  
problem_reporter = self.problem_reporter  
 
if trip.trip_id in self.trips:  
problem_reporter.DuplicateID('trip_id', trip.trip_id)  
return  
 
self.AddTableColumns('trips', trip._ColumnNames())  
trip._schedule = weakref.proxy(self)  
self.trips[trip.trip_id] = trip  
 
# Call Trip.Validate after setting trip._schedule so that references  
# are checked. trip.ValidateChildren will be called directly by  
# schedule.Validate, after stop_times has been loaded.  
if validate:  
if not problem_reporter:  
problem_reporter = self.problem_reporter  
trip.Validate(problem_reporter, validate_children=False)  
try:  
self.routes[trip.route_id]._AddTripObject(trip)  
except KeyError:  
# Invalid route_id was reported in the Trip.Validate call above  
pass  
 
def GetTripList(self):  
return self.trips.values()  
 
def GetTrip(self, trip_id):  
return self.trips[trip_id]  
 
def AddFareObject(self, fare, problem_reporter=None):  
if not problem_reporter:  
problem_reporter = self.problem_reporter  
fare.Validate(problem_reporter)  
 
if fare.fare_id in self.fares:  
problem_reporter.DuplicateID('fare_id', fare.fare_id)  
return  
 
self.fares[fare.fare_id] = fare  
 
def GetFareList(self):  
return self.fares.values()  
 
def GetFare(self, fare_id):  
return self.fares[fare_id]  
 
def AddFareRuleObject(self, rule, problem_reporter=None):  
if not problem_reporter:  
problem_reporter = self.problem_reporter  
 
if IsEmpty(rule.fare_id):  
problem_reporter.MissingValue('fare_id')  
return  
 
if rule.route_id and rule.route_id not in self.routes:  
problem_reporter.InvalidValue('route_id', rule.route_id)  
if rule.origin_id and rule.origin_id not in self.fare_zones:  
problem_reporter.InvalidValue('origin_id', rule.origin_id)  
if rule.destination_id and rule.destination_id not in self.fare_zones:  
problem_reporter.InvalidValue('destination_id', rule.destination_id)  
if rule.contains_id and rule.contains_id not in self.fare_zones:  
problem_reporter.InvalidValue('contains_id', rule.contains_id)  
 
if rule.fare_id in self.fares:  
self.GetFare(rule.fare_id).rules.append(rule)  
else:  
problem_reporter.InvalidValue('fare_id', rule.fare_id,  
'(This fare_id doesn\'t correspond to any '  
'of the IDs defined in the '  
'fare attributes.)')  
 
def AddTransferObject(self, transfer, problem_reporter=None):  
assert transfer._schedule is None, "only add Transfer to a schedule once"  
transfer._schedule = weakref.proxy(self) # See weakref comment at top  
if not problem_reporter:  
problem_reporter = self.problem_reporter  
 
transfer.Validate(problem_reporter)  
self._transfers.append(transfer)  
 
def GetTransferList(self):  
return self._transfers  
 
def GetStop(self, id):  
return self.stops[id]  
 
def GetFareZones(self):  
"""Returns the list of all fare zones that have been identified by  
the stops that have been added."""  
return self.fare_zones.keys()  
 
def GetNearestStops(self, lat, lon, n=1):  
"""Return the n nearest stops to lat,lon"""  
dist_stop_list = []  
for s in self.stops.values():  
# TODO: Use ApproximateDistanceBetweenStops?  
dist = (s.stop_lat - lat)**2 + (s.stop_lon - lon)**2  
if len(dist_stop_list) < n:  
bisect.insort(dist_stop_list, (dist, s))  
elif dist < dist_stop_list[-1][0]:  
bisect.insort(dist_stop_list, (dist, s))  
dist_stop_list.pop() # Remove stop with greatest distance  
return [stop for dist, stop in dist_stop_list]  
 
def GetStopsInBoundingBox(self, north, east, south, west, n):  
"""Return a sample of up to n stops in a bounding box"""  
stop_list = []  
for s in self.stops.values():  
if (s.stop_lat <= north and s.stop_lat >= south and  
s.stop_lon <= east and s.stop_lon >= west):  
stop_list.append(s)  
if len(stop_list) == n:  
break  
return stop_list  
 
def Load(self, feed_path, extra_validation=False):  
loader = Loader(feed_path, self, problems=self.problem_reporter,  
extra_validation=extra_validation)  
loader.Load()  
 
def _WriteArchiveString(self, archive, filename, stringio):  
zi = zipfile.ZipInfo(filename)  
# See  
# http://stackoverflow.com/questions/434641/how-do-i-set-permissions-attributes-on-a-file-in-a-zip-file-using-pythons-zipf  
zi.external_attr = 0666 << 16L # Set unix permissions to -rw-rw-rw  
# ZIP_DEFLATED requires zlib. zlib comes with Python 2.4 and 2.5  
zi.compress_type = zipfile.ZIP_DEFLATED  
archive.writestr(zi, stringio.getvalue())  
 
def WriteGoogleTransitFeed(self, file):  
"""Output this schedule as a Google Transit Feed in file_name.  
 
Args:  
file: path of new feed file (a string) or a file-like object  
 
Returns:  
None  
"""  
# Compression type given when adding each file  
archive = zipfile.ZipFile(file, 'w')  
 
if 'agency' in self._table_columns:  
agency_string = StringIO.StringIO()  
writer = CsvUnicodeWriter(agency_string)  
columns = self.GetTableColumns('agency')  
writer.writerow(columns)  
for a in self._agencies.values():  
writer.writerow([EncodeUnicode(a[c]) for c in columns])  
self._WriteArchiveString(archive, 'agency.txt', agency_string)  
 
calendar_dates_string = StringIO.StringIO()  
writer = CsvUnicodeWriter(calendar_dates_string)  
writer.writerow(ServicePeriod._FIELD_NAMES_CALENDAR_DATES)  
has_data = False  
for period in self.service_periods.values():  
for row in period.GenerateCalendarDatesFieldValuesTuples():  
has_data = True  
writer.writerow(row)  
wrote_calendar_dates = False  
if has_data:  
wrote_calendar_dates = True  
self._WriteArchiveString(archive, 'calendar_dates.txt',  
calendar_dates_string)  
 
calendar_string = StringIO.StringIO()  
writer = CsvUnicodeWriter(calendar_string)  
writer.writerow(ServicePeriod._FIELD_NAMES)  
has_data = False  
for s in self.service_periods.values():  
row = s.GetCalendarFieldValuesTuple()  
if row:  
has_data = True  
writer.writerow(row)  
if has_data or not wrote_calendar_dates:  
self._WriteArchiveString(archive, 'calendar.txt', calendar_string)  
 
if 'stops' in self._table_columns:  
stop_string = StringIO.StringIO()  
writer = CsvUnicodeWriter(stop_string)  
columns = self.GetTableColumns('stops')  
writer.writerow(columns)  
for s in self.stops.values():  
writer.writerow([EncodeUnicode(s[c]) for c in columns])  
self._WriteArchiveString(archive, 'stops.txt', stop_string)  
 
if 'routes' in self._table_columns:  
route_string = StringIO.StringIO()  
writer = CsvUnicodeWriter(route_string)  
columns = self.GetTableColumns('routes')  
writer.writerow(columns)  
for r in self.routes.values():  
writer.writerow([EncodeUnicode(r[c]) for c in columns])  
self._WriteArchiveString(archive, 'routes.txt', route_string)  
 
if 'trips' in self._table_columns:  
trips_string = StringIO.StringIO()  
writer = CsvUnicodeWriter(trips_string)  
columns = self.GetTableColumns('trips')  
writer.writerow(columns)  
for t in self.trips.values():  
writer.writerow([EncodeUnicode(t[c]) for c in columns])  
self._WriteArchiveString(archive, 'trips.txt', trips_string)  
 
# write frequencies.txt (if applicable)  
headway_rows = []  
for trip in self.GetTripList():  
headway_rows += trip.GetHeadwayPeriodOutputTuples()  
if headway_rows:  
headway_string = StringIO.StringIO()  
writer = CsvUnicodeWriter(headway_string)  
writer.writerow(Trip._FIELD_NAMES_HEADWAY)  
writer.writerows(headway_rows)  
self._WriteArchiveString(archive, 'frequencies.txt', headway_string)  
 
# write fares (if applicable)  
if self.GetFareList():  
fare_string = StringIO.StringIO()  
writer = CsvUnicodeWriter(fare_string)  
writer.writerow(Fare._FIELD_NAMES)  
writer.writerows(f.GetFieldValuesTuple() for f in self.GetFareList())  
self._WriteArchiveString(archive, 'fare_attributes.txt', fare_string)  
 
# write fare rules (if applicable)  
rule_rows = []  
for fare in self.GetFareList():  
for rule in fare.GetFareRuleList():  
rule_rows.append(rule.GetFieldValuesTuple())  
if rule_rows:  
rule_string = StringIO.StringIO()  
writer = CsvUnicodeWriter(rule_string)  
writer.writerow(FareRule._FIELD_NAMES)  
writer.writerows(rule_rows)  
self._WriteArchiveString(archive, 'fare_rules.txt', rule_string)  
stop_times_string = StringIO.StringIO()  
writer = CsvUnicodeWriter(stop_times_string)  
writer.writerow(StopTime._FIELD_NAMES)  
for t in self.trips.values():  
writer.writerows(t._GenerateStopTimesTuples())  
self._WriteArchiveString(archive, 'stop_times.txt', stop_times_string)  
 
# write shapes (if applicable)  
shape_rows = []  
for shape in self.GetShapeList():  
seq = 1  
for (lat, lon, dist) in shape.points:  
shape_rows.append((shape.shape_id, lat, lon, seq, dist))  
seq += 1  
if shape_rows:  
shape_string = StringIO.StringIO()  
writer = CsvUnicodeWriter(shape_string)  
writer.writerow(Shape._FIELD_NAMES)  
writer.writerows(shape_rows)  
self._WriteArchiveString(archive, 'shapes.txt', shape_string)  
 
# write transfers (if applicable)  
if self.GetTransferList():  
transfer_string = StringIO.StringIO()  
writer = CsvUnicodeWriter(transfer_string)  
writer.writerow(Transfer._FIELD_NAMES)  
writer.writerows(f.GetFieldValuesTuple() for f in self.GetTransferList())  
self._WriteArchiveString(archive, 'transfers.txt', transfer_string)  
 
archive.close()  
 
def GenerateDateTripsDeparturesList(self, date_start, date_end):  
"""Return a list of (date object, number of trips, number of departures).  
 
The list is generated for dates in the range [date_start, date_end).  
 
Args:  
date_start: The first date in the list, a date object  
date_end: The first date after the list, a date object  
 
Returns:  
a list of (date object, number of trips, number of departures) tuples  
"""  
 
service_id_to_trips = defaultdict(lambda: 0)  
service_id_to_departures = defaultdict(lambda: 0)  
for trip in self.GetTripList():  
headway_start_times = trip.GetHeadwayStartTimes()  
if headway_start_times:  
trip_runs = len(headway_start_times)  
else:  
trip_runs = 1  
 
service_id_to_trips[trip.service_id] += trip_runs  
service_id_to_departures[trip.service_id] += (  
(trip.GetCountStopTimes() - 1) * trip_runs)  
 
date_services = self.GetServicePeriodsActiveEachDate(date_start, date_end)  
date_trips = []  
 
for date, services in date_services:  
day_trips = sum(service_id_to_trips[s.service_id] for s in services)  
day_departures = sum(  
service_id_to_departures[s.service_id] for s in services)  
date_trips.append((date, day_trips, day_departures))  
return date_trips  
 
def ValidateFeedStartAndExpirationDates(self,  
problems,  
first_date,  
last_date,  
today):  
"""Validate the start and expiration dates of the feed.  
Issue a warning if it only starts in the future, or if  
it expires within 60 days.  
 
Args:  
problems: The problem reporter object  
first_date: A date object representing the first day the feed is active  
last_date: A date object representing the last day the feed is active  
today: A date object representing the date the validation is being run on  
 
Returns:  
None  
"""  
warning_cutoff = today + datetime.timedelta(days=60)  
if last_date < warning_cutoff:  
problems.ExpirationDate(time.mktime(last_date.timetuple()))  
 
if first_date > today:  
problems.FutureService(time.mktime(first_date.timetuple()))  
 
def ValidateServiceGaps(self,  
problems,  
validation_start_date,  
validation_end_date,  
service_gap_interval):  
"""Validate consecutive dates without service in the feed.  
Issue a warning if it finds service gaps of at least  
"service_gap_interval" consecutive days in the date range  
[validation_start_date, last_service_date)  
 
Args:  
problems: The problem reporter object  
validation_start_date: A date object representing the date from which the  
validation should take place  
validation_end_date: A date object representing the first day the feed is  
active  
service_gap_interval: An integer indicating how many consecutive days the  
service gaps need to have for a warning to be issued  
 
Returns:  
None  
"""  
if service_gap_interval is None:  
return  
 
departures = self.GenerateDateTripsDeparturesList(validation_start_date,  
validation_end_date)  
 
# The first day without service of the _current_ gap  
first_day_without_service = validation_start_date  
# The last day without service of the _current_ gap  
last_day_without_service = validation_start_date  
 
consecutive_days_without_service = 0  
 
for day_date, day_trips, _ in departures:  
if day_trips == 0:  
if consecutive_days_without_service == 0:  
first_day_without_service = day_date  
consecutive_days_without_service += 1  
last_day_without_service = day_date  
else:  
if consecutive_days_without_service >= service_gap_interval:  
problems.TooManyDaysWithoutService(first_day_without_service,  
last_day_without_service,  
consecutive_days_without_service)  
 
consecutive_days_without_service = 0  
 
# We have to check if there is a gap at the end of the specified date range  
if consecutive_days_without_service >= service_gap_interval:  
problems.TooManyDaysWithoutService(first_day_without_service,  
last_day_without_service,  
consecutive_days_without_service)  
 
def Validate(self,  
problems=None,  
validate_children=True,  
today=None,  
service_gap_interval=None):  
"""Validates various holistic aspects of the schedule  
(mostly interrelationships between the various data sets)."""  
 
if today is None:  
today = datetime.date.today()  
 
if not problems:  
problems = self.problem_reporter  
 
(start_date, end_date) = self.GetDateRange()  
if not end_date or not start_date:  
problems.OtherProblem('This feed has no effective service dates!',  
type=TYPE_WARNING)  
else:  
try:  
last_service_day = datetime.datetime(  
*(time.strptime(end_date, "%Y%m%d")[0:6])).date()  
first_service_day = datetime.datetime(  
*(time.strptime(start_date, "%Y%m%d")[0:6])).date()  
 
except ValueError:  
# Format of start_date and end_date checked in class ServicePeriod  
pass  
 
else:  
 
self.ValidateFeedStartAndExpirationDates(problems,  
first_service_day,  
last_service_day,  
today)  
 
# We start checking for service gaps a bit in the past if the  
# feed was active then. See  
# http://code.google.com/p/googletransitdatafeed/issues/detail?id=188  
#  
# We subtract 1 from service_gap_interval so that if today has  
# service no warning is issued.  
#  
# Service gaps are searched for only up to one year from today  
if service_gap_interval is not None:  
service_gap_timedelta = datetime.timedelta(  
days=service_gap_interval - 1)  
one_year = datetime.timedelta(days=365)  
self.ValidateServiceGaps(  
problems,  
max(first_service_day,  
today - service_gap_timedelta),  
min(last_service_day,  
today + one_year),  
service_gap_interval)  
 
# TODO: Check Trip fields against valid values  
 
# Check for stops that aren't referenced by any trips and broken  
# parent_station references. Also check that the parent station isn't too  
# far from its child stops.  
for stop in self.stops.values():  
if validate_children:  
stop.Validate(problems)  
cursor = self._connection.cursor()  
cursor.execute("SELECT count(*) FROM stop_times WHERE stop_id=? LIMIT 1",  
(stop.stop_id,))  
count = cursor.fetchone()[0]  
if stop.location_type == 0 and count == 0:  
problems.UnusedStop(stop.stop_id, stop.stop_name)  
elif stop.location_type == 1 and count != 0:  
problems.UsedStation(stop.stop_id, stop.stop_name)  
 
if stop.location_type != 1 and stop.parent_station:  
if stop.parent_station not in self.stops:  
problems.InvalidValue("parent_station",  
EncodeUnicode(stop.parent_station),  
"parent_station '%s' not found for stop_id "  
"'%s' in stops.txt" %  
(EncodeUnicode(stop.parent_station),  
EncodeUnicode(stop.stop_id)))  
elif self.stops[stop.parent_station].location_type != 1:  
problems.InvalidValue("parent_station",  
EncodeUnicode(stop.parent_station),  
"parent_station '%s' of stop_id '%s' must "  
"have location_type=1 in stops.txt" %  
(EncodeUnicode(stop.parent_station),  
EncodeUnicode(stop.stop_id)))  
else:  
parent_station = self.stops[stop.parent_station]  
distance = ApproximateDistanceBetweenStops(stop, parent_station)  
if distance > MAX_DISTANCE_BETWEEN_STOP_AND_PARENT_STATION_ERROR:  
problems.StopTooFarFromParentStation(  
stop.stop_id, stop.stop_name, parent_station.stop_id,  
parent_station.stop_name, distance, TYPE_ERROR)  
elif distance > MAX_DISTANCE_BETWEEN_STOP_AND_PARENT_STATION_WARNING:  
problems.StopTooFarFromParentStation(  
stop.stop_id, stop.stop_name, parent_station.stop_id,  
parent_station.stop_name, distance, TYPE_WARNING)  
 
#TODO: check that every station is used.  
# Then uncomment testStationWithoutReference.  
 
# Check for stops that might represent the same location (specifically,  
# stops that are less that 2 meters apart) First filter out stops without a  
# valid lat and lon. Then sort by latitude, then find the distance between  
# each pair of stations within 2 meters latitude of each other. This avoids  
# doing n^2 comparisons in the average case and doesn't need a spatial  
# index.  
sorted_stops = filter(lambda s: s.stop_lat and s.stop_lon,  
self.GetStopList())  
sorted_stops.sort(key=(lambda x: x.stop_lat))  
TWO_METERS_LAT = 0.000018  
for index, stop in enumerate(sorted_stops[:-1]):  
index += 1  
while ((index < len(sorted_stops)) and  
((sorted_stops[index].stop_lat - stop.stop_lat) < TWO_METERS_LAT)):  
distance = ApproximateDistanceBetweenStops(stop, sorted_stops[index])  
if distance < 2:  
other_stop = sorted_stops[index]  
if stop.location_type == 0 and other_stop.location_type == 0:  
problems.StopsTooClose(  
EncodeUnicode(stop.stop_name),  
EncodeUnicode(stop.stop_id),  
EncodeUnicode(other_stop.stop_name),  
EncodeUnicode(other_stop.stop_id), distance)  
elif stop.location_type == 1 and other_stop.location_type == 1:  
problems.StationsTooClose(  
EncodeUnicode(stop.stop_name), EncodeUnicode(stop.stop_id),  
EncodeUnicode(other_stop.stop_name),  
EncodeUnicode(other_stop.stop_id), distance)  
elif (stop.location_type in (0, 1) and  
other_stop.location_type in (0, 1)):  
if stop.location_type == 0 and other_stop.location_type == 1:  
this_stop = stop  
this_station = other_stop  
elif stop.location_type == 1 and other_stop.location_type == 0:  
this_stop = other_stop  
this_station = stop  
if this_stop.parent_station != this_station.stop_id:  
problems.DifferentStationTooClose(  
EncodeUnicode(this_stop.stop_name),  
EncodeUnicode(this_stop.stop_id),  
EncodeUnicode(this_station.stop_name),  
EncodeUnicode(this_station.stop_id), distance)  
index += 1  
 
# Check for multiple routes using same short + long name  
route_names = {}  
for route in self.routes.values():  
if validate_children:  
route.Validate(problems)  
short_name = ''  
if not IsEmpty(route.route_short_name):  
short_name = route.route_short_name.lower().strip()  
long_name = ''  
if not IsEmpty(route.route_long_name):  
long_name = route.route_long_name.lower().strip()  
name = (short_name, long_name)  
if name in route_names:  
problems.InvalidValue('route_long_name',  
long_name,  
'The same combination of '  
'route_short_name and route_long_name '  
'shouldn\'t be used for more than one '  
'route, as it is for the for the two routes '  
'with IDs "%s" and "%s".' %  
(route.route_id, route_names[name].route_id),  
type=TYPE_WARNING)  
else:  
route_names[name] = route  
 
stop_types = {} # a dict mapping stop_id to [route_id, route_type, is_match]  
trips = {} # a dict mapping tuple to (route_id, trip_id)  
for trip in sorted(self.trips.values()):  
if trip.route_id not in self.routes:  
continue  
route_type = self.GetRoute(trip.route_id).route_type  
arrival_times = []  
stop_ids = []  
for index, st in enumerate(trip.GetStopTimes(problems)):  
stop_id = st.stop.stop_id  
arrival_times.append(st.arrival_time)  
stop_ids.append(stop_id)  
# Check a stop if which belongs to both subway and bus.  
if (route_type == Route._ROUTE_TYPE_NAMES['Subway'] or  
route_type == Route._ROUTE_TYPE_NAMES['Bus']):  
if stop_id not in stop_types:  
stop_types[stop_id] = [trip.route_id, route_type, 0]  
elif (stop_types[stop_id][1] != route_type and  
stop_types[stop_id][2] == 0):  
stop_types[stop_id][2] = 1  
if stop_types[stop_id][1] == Route._ROUTE_TYPE_NAMES['Subway']:  
subway_route_id = stop_types[stop_id][0]  
bus_route_id = trip.route_id  
else:  
subway_route_id = trip.route_id  
bus_route_id = stop_types[stop_id][0]  
problems.StopWithMultipleRouteTypes(st.stop.stop_name, stop_id,  
subway_route_id, bus_route_id)  
 
# Check duplicate trips which go through the same stops with same  
# service and start times.  
if self._check_duplicate_trips:  
if not stop_ids or not arrival_times:  
continue  
key = (trip.service_id, min(arrival_times), str(stop_ids))  
if key not in trips:  
trips[key] = (trip.route_id, trip.trip_id)  
else:  
problems.DuplicateTrip(trips[key][1], trips[key][0], trip.trip_id,  
trip.route_id)  
 
# Check that routes' agency IDs are valid, if set  
for route in self.routes.values():  
if (not IsEmpty(route.agency_id) and  
not route.agency_id in self._agencies):  
problems.InvalidValue('agency_id',  
route.agency_id,  
'The route with ID "%s" specifies agency_id '  
'"%s", which doesn\'t exist.' %  
(route.route_id, route.agency_id))  
 
# Make sure all trips have stop_times  
# We're doing this here instead of in Trip.Validate() so that  
# Trips can be validated without error during the reading of trips.txt  
for trip in self.trips.values():  
trip.ValidateChildren(problems)  
count_stop_times = trip.GetCountStopTimes()  
if not count_stop_times:  
problems.OtherProblem('The trip with the trip_id "%s" doesn\'t have '  
'any stop times defined.' % trip.trip_id,  
type=TYPE_WARNING)  
if len(trip._headways) > 0: # no stoptimes, but there are headways  
problems.OtherProblem('Frequencies defined, but no stop times given '  
'in trip %s' % trip.trip_id, type=TYPE_ERROR)  
elif count_stop_times == 1:  
problems.OtherProblem('The trip with the trip_id "%s" only has one '  
'stop on it; it should have at least one more '  
'stop so that the riders can leave!' %  
trip.trip_id, type=TYPE_WARNING)  
else:  
# These methods report InvalidValue if there's no first or last time  
trip.GetStartTime(problems=problems)  
trip.GetEndTime(problems=problems)  
 
# Check for unused shapes  
known_shape_ids = set(self._shapes.keys())  
used_shape_ids = set()  
for trip in self.GetTripList():  
used_shape_ids.add(trip.shape_id)  
unused_shape_ids = known_shape_ids - used_shape_ids  
if unused_shape_ids:  
problems.OtherProblem('The shapes with the following shape_ids aren\'t '  
'used by any trips: %s' %  
', '.join(unused_shape_ids),  
type=TYPE_WARNING)  
 
 
# Map from literal string that should never be found in the csv data to a human  
# readable description  
INVALID_LINE_SEPARATOR_UTF8 = {  
"\x0c": "ASCII Form Feed 0x0C",  
# May be part of end of line, but not found elsewhere  
"\x0d": "ASCII Carriage Return 0x0D, \\r",  
"\xe2\x80\xa8": "Unicode LINE SEPARATOR U+2028",  
"\xe2\x80\xa9": "Unicode PARAGRAPH SEPARATOR U+2029",  
"\xc2\x85": "Unicode NEXT LINE SEPARATOR U+0085",  
}  
 
class EndOfLineChecker:  
"""Wrapper for a file-like object that checks for consistent line ends.  
 
The check for consistent end of lines (all CR LF or all LF) only happens if  
next() is called until it raises StopIteration.  
"""  
def __init__(self, f, name, problems):  
"""Create new object.  
 
Args:  
f: file-like object to wrap  
name: name to use for f. StringIO objects don't have a name attribute.  
problems: a ProblemReporterBase object  
"""  
self._f = f  
self._name = name  
self._crlf = 0  
self._crlf_examples = []  
self._lf = 0  
self._lf_examples = []  
self._line_number = 0 # first line will be number 1  
self._problems = problems  
 
def __iter__(self):  
return self  
 
def next(self):  
"""Return next line without end of line marker or raise StopIteration."""  
try:  
next_line = self._f.next()  
except StopIteration:  
self._FinalCheck()  
raise  
 
self._line_number += 1  
m_eol = re.search(r"[\x0a\x0d]*$", next_line)  
if m_eol.group() == "\x0d\x0a":  
self._crlf += 1  
if self._crlf <= 5:  
self._crlf_examples.append(self._line_number)  
elif m_eol.group() == "\x0a":  
self._lf += 1  
if self._lf <= 5:  
self._lf_examples.append(self._line_number)  
elif m_eol.group() == "":  
# Should only happen at the end of the file  
try:  
self._f.next()  
raise RuntimeError("Unexpected row without new line sequence")  
except StopIteration:  
# Will be raised again when EndOfLineChecker.next() is next called  
pass  
else:  
self._problems.InvalidLineEnd(  
codecs.getencoder('string_escape')(m_eol.group())[0],  
(self._name, self._line_number))  
next_line_contents = next_line[0:m_eol.start()]  
for seq, name in INVALID_LINE_SEPARATOR_UTF8.items():  
if next_line_contents.find(seq) != -1:  
self._problems.OtherProblem(  
"Line contains %s" % name,  
context=(self._name, self._line_number))  
return next_line_contents  
 
def _FinalCheck(self):  
if self._crlf > 0 and self._lf > 0:  
crlf_plural = self._crlf > 1 and "s" or ""  
crlf_lines = ", ".join(["%s" % e for e in self._crlf_examples])  
if self._crlf > len(self._crlf_examples):  
crlf_lines += ", ..."  
lf_plural = self._lf > 1 and "s" or ""  
lf_lines = ", ".join(["%s" % e for e in self._lf_examples])  
if self._lf > len(self._lf_examples):  
lf_lines += ", ..."  
 
self._problems.OtherProblem(  
"Found %d CR LF \"\\r\\n\" line end%s (line%s %s) and "  
"%d LF \"\\n\" line end%s (line%s %s). A file must use a "  
"consistent line end." % (self._crlf, crlf_plural, crlf_plural,  
crlf_lines, self._lf, lf_plural,  
lf_plural, lf_lines),  
(self._name,))  
# Prevent _FinalCheck() from reporting the problem twice, in the unlikely  
# case that it is run twice  
self._crlf = 0  
self._lf = 0  
 
 
# Filenames specified in GTFS spec  
KNOWN_FILENAMES = [  
'agency.txt',  
'stops.txt',  
'routes.txt',  
'trips.txt',  
'stop_times.txt',  
'calendar.txt',  
'calendar_dates.txt',  
'fare_attributes.txt',  
'fare_rules.txt',  
'shapes.txt',  
'frequencies.txt',  
'transfers.txt',  
]  
 
class Loader:  
def __init__(self,  
feed_path=None,  
schedule=None,  
problems=default_problem_reporter,  
extra_validation=False,  
load_stop_times=True,  
memory_db=True,  
zip=None,  
check_duplicate_trips=False):  
"""Initialize a new Loader object.  
 
Args:  
feed_path: string path to a zip file or directory  
schedule: a Schedule object or None to have one created  
problems: a ProblemReporter object, the default reporter raises an  
exception for each problem  
extra_validation: True if you would like extra validation  
load_stop_times: load the stop_times table, used to speed load time when  
times are not needed. The default is True.  
memory_db: if creating a new Schedule object use an in-memory sqlite  
database instead of creating one in a temporary file  
zip: a zipfile.ZipFile object, optionally used instead of path  
"""  
if not schedule:  
schedule = Schedule(problem_reporter=problems, memory_db=memory_db,  
check_duplicate_trips=check_duplicate_trips)  
self._extra_validation = extra_validation  
self._schedule = schedule  
self._problems = problems  
self._path = feed_path  
self._zip = zip  
self._load_stop_times = load_stop_times  
 
def _DetermineFormat(self):  
"""Determines whether the feed is in a form that we understand, and  
if so, returns True."""  
if self._zip:  
# If zip was passed to __init__ then path isn't used  
assert not self._path  
return True  
 
if not isinstance(self._path, basestring) and hasattr(self._path, 'read'):  
# A file-like object, used for testing with a StringIO file  
self._zip = zipfile.ZipFile(self._path, mode='r')  
return True  
 
if not os.path.exists(self._path):  
self._problems.FeedNotFound(self._path)  
return False  
 
if self._path.endswith('.zip'):  
try:  
self._zip = zipfile.ZipFile(self._path, mode='r')  
except IOError: # self._path is a directory  
pass  
except zipfile.BadZipfile:  
self._problems.UnknownFormat(self._path)  
return False  
 
if not self._zip and not os.path.isdir(self._path):  
self._problems.UnknownFormat(self._path)  
return False  
 
return True  
 
def _GetFileNames(self):  
"""Returns a list of file names in the feed."""  
if self._zip:  
return self._zip.namelist()  
else:  
return os.listdir(self._path)  
 
def _CheckFileNames(self):  
filenames = self._GetFileNames()  
for feed_file in filenames:  
if feed_file not in KNOWN_FILENAMES:  
if not feed_file.startswith('.'):  
# Don't worry about .svn files and other hidden files  
# as this will break the tests.  
self._problems.UnknownFile(feed_file)  
 
def _GetUtf8Contents(self, file_name):  
"""Check for errors in file_name and return a string for csv reader."""  
contents = self._FileContents(file_name)  
if not contents: # Missing file  
return  
 
# Check for errors that will prevent csv.reader from working  
if len(contents) >= 2 and contents[0:2] in (codecs.BOM_UTF16_BE,  
codecs.BOM_UTF16_LE):  
self._problems.FileFormat("appears to be encoded in utf-16", (file_name, ))  
# Convert and continue, so we can find more errors  
contents = codecs.getdecoder('utf-16')(contents)[0].encode('utf-8')  
 
null_index = contents.find('\0')  
if null_index != -1:  
# It is easier to get some surrounding text than calculate the exact  
# row_num  
m = re.search(r'.{,20}\0.{,20}', contents, re.DOTALL)  
self._problems.FileFormat(  
"contains a null in text \"%s\" at byte %d" %  
(codecs.getencoder('string_escape')(m.group()), null_index + 1),  
(file_name, ))  
return  
 
# strip out any UTF-8 Byte Order Marker (otherwise it'll be  
# treated as part of the first column name, causing a mis-parse)  
contents = contents.lstrip(codecs.BOM_UTF8)  
return contents  
 
def _ReadCsvDict(self, file_name, all_cols, required):  
"""Reads lines from file_name, yielding a dict of unicode values."""  
assert file_name.endswith(".txt")  
table_name = file_name[0:-4]  
contents = self._GetUtf8Contents(file_name)  
if not contents:  
return  
 
eol_checker = EndOfLineChecker(StringIO.StringIO(contents),  
file_name, self._problems)  
# The csv module doesn't provide a way to skip trailing space, but when I  
# checked 15/675 feeds had trailing space in a header row and 120 had spaces  
# after fields. Space after header fields can cause a serious parsing  
# problem, so warn. Space after body fields can cause a problem time,  
# integer and id fields; they will be validated at higher levels.  
reader = csv.reader(eol_checker, skipinitialspace=True)  
 
raw_header = reader.next()  
header_occurrences = defaultdict(lambda: 0)  
header = []  
valid_columns = [] # Index into raw_header and raw_row  
for i, h in enumerate(raw_header):  
h_stripped = h.strip()  
if not h_stripped:  
self._problems.CsvSyntax(  
description="The header row should not contain any blank values. "  
"The corresponding column will be skipped for the "  
"entire file.",  
context=(file_name, 1, [''] * len(raw_header), raw_header),  
type=TYPE_ERROR)  
continue  
elif h != h_stripped:  
self._problems.CsvSyntax(  
description="The header row should not contain any "  
"space characters.",  
context=(file_name, 1, [''] * len(raw_header), raw_header),  
type=TYPE_WARNING)  
header.append(h_stripped)  
valid_columns.append(i)  
header_occurrences[h_stripped] += 1  
 
for name, count in header_occurrences.items():  
if count > 1:  
self._problems.DuplicateColumn(  
header=name,  
file_name=file_name,  
count=count)  
 
self._schedule._table_columns[table_name] = header  
 
# check for unrecognized columns, which are often misspellings  
unknown_cols = set(header) - set(all_cols)  
if len(unknown_cols) == len(header):  
self._problems.CsvSyntax(  
description="The header row did not contain any known column "  
"names. The file is most likely missing the header row "  
"or not in the expected CSV format.",  
context=(file_name, 1, [''] * len(raw_header), raw_header),  
type=TYPE_ERROR)  
else:  
for col in unknown_cols:  
# this is provided in order to create a nice colored list of  
# columns in the validator output  
context = (file_name, 1, [''] * len(header), header)  
self._problems.UnrecognizedColumn(file_name, col, context)  
 
missing_cols = set(required) - set(header)  
for col in missing_cols:  
# this is provided in order to create a nice colored list of  
# columns in the validator output  
context = (file_name, 1, [''] * len(header), header)  
self._problems.MissingColumn(file_name, col, context)  
 
line_num = 1 # First line read by reader.next() above  
for raw_row in reader:  
line_num += 1  
if len(raw_row) == 0: # skip extra empty lines in file  
continue  
 
if len(raw_row) > len(raw_header):  
self._problems.OtherProblem('Found too many cells (commas) in line '  
'%d of file "%s". Every row in the file '  
'should have the same number of cells as '  
'the header (first line) does.' %  
(line_num, file_name),  
(file_name, line_num),  
type=TYPE_WARNING)  
 
if len(raw_row) < len(raw_header):  
self._problems.OtherProblem('Found missing cells (commas) in line '  
'%d of file "%s". Every row in the file '  
'should have the same number of cells as '  
'the header (first line) does.' %  
(line_num, file_name),  
(file_name, line_num),  
type=TYPE_WARNING)  
 
# raw_row is a list of raw bytes which should be valid utf-8. Convert each  
# valid_columns of raw_row into Unicode.  
valid_values = []  
unicode_error_columns = [] # index of valid_values elements with an error  
for i in valid_columns:  
try:  
valid_values.append(raw_row[i].decode('utf-8'))  
except UnicodeDecodeError:  
# Replace all invalid characters with REPLACEMENT CHARACTER (U+FFFD)  
valid_values.append(codecs.getdecoder("utf8")  
(raw_row[i], errors="replace")[0])  
unicode_error_columns.append(len(valid_values) - 1)  
except IndexError:  
break  
 
# The error report may contain a dump of all values in valid_values so  
# problems can not be reported until after converting all of raw_row to  
# Unicode.  
for i in unicode_error_columns:  
self._problems.InvalidValue(header[i], valid_values[i],  
'Unicode error',  
(file_name, line_num,  
valid_values, header))  
 
 
d = dict(zip(header, valid_values))  
yield (d, line_num, header, valid_values)  
 
# TODO: Add testing for this specific function  
def _ReadCSV(self, file_name, cols, required):  
"""Reads lines from file_name, yielding a list of unicode values  
corresponding to the column names in cols."""  
contents = self._GetUtf8Contents(file_name)  
if not contents:  
return  
 
eol_checker = EndOfLineChecker(StringIO.StringIO(contents),  
file_name, self._problems)  
reader = csv.reader(eol_checker) # Use excel dialect  
 
header = reader.next()  
header = map(lambda x: x.strip(), header) # trim any whitespace  
header_occurrences = defaultdict(lambda: 0)  
for column_header in header:  
header_occurrences[column_header] += 1  
 
for name, count in header_occurrences.items():  
if count > 1:  
self._problems.DuplicateColumn(  
header=name,  
file_name=file_name,  
count=count)  
 
# check for unrecognized columns, which are often misspellings  
unknown_cols = set(header).difference(set(cols))  
for col in unknown_cols:  
# this is provided in order to create a nice colored list of  
# columns in the validator output  
context = (file_name, 1, [''] * len(header), header)  
self._problems.UnrecognizedColumn(file_name, col, context)  
 
col_index = [-1] * len(cols)  
for i in range(len(cols)):  
if cols[i] in header:  
col_index[i] = header.index(cols[i])  
elif cols[i] in required:  
self._problems.MissingColumn(file_name, cols[i])  
 
row_num = 1  
for row in reader:  
row_num += 1  
if len(row) == 0: # skip extra empty lines in file  
continue  
 
if len(row) > len(header):  
self._problems.OtherProblem('Found too many cells (commas) in line '  
'%d of file "%s". Every row in the file '  
'should have the same number of cells as '  
'the header (first line) does.' %  
(row_num, file_name), (file_name, row_num),  
type=TYPE_WARNING)  
 
if len(row) < len(header):  
self._problems.OtherProblem('Found missing cells (commas) in line '  
'%d of file "%s". Every row in the file '  
'should have the same number of cells as '  
'the header (first line) does.' %  
(row_num, file_name), (file_name, row_num),  
type=TYPE_WARNING)  
 
result = [None] * len(cols)  
unicode_error_columns = [] # A list of column numbers with an error  
for i in range(len(cols)):  
ci = col_index[i]  
if ci >= 0:  
if len(row) <= ci: # handle short CSV rows  
result[i] = u''  
else:  
try:  
result[i] = row[ci].decode('utf-8').strip()  
except UnicodeDecodeError:  
# Replace all invalid characters with  
# REPLACEMENT CHARACTER (U+FFFD)  
result[i] = codecs.getdecoder("utf8")(row[ci],  
errors="replace")[0].strip()  
unicode_error_columns.append(i)  
 
for i in unicode_error_columns:  
self._problems.InvalidValue(cols[i], result[i],  
'Unicode error',  
(file_name, row_num, result, cols))  
yield (result, row_num, cols)  
 
def _HasFile(self, file_name):  
"""Returns True if there's a file in the current feed with the  
given file_name in the current feed."""  
if self._zip:  
return file_name in self._zip.namelist()  
else:  
file_path = os.path.join(self._path, file_name)  
return os.path.exists(file_path) and os.path.isfile(file_path)  
 
def _FileContents(self, file_name):  
results = None  
if self._zip:  
try:  
results = self._zip.read(file_name)  
except KeyError: # file not found in archve  
self._problems.MissingFile(file_name)  
return None  
else:  
try:  
data_file = open(os.path.join(self._path, file_name), 'rb')  
results = data_file.read()  
except IOError: # file not found  
self._problems.MissingFile(file_name)  
return None  
 
if not results:  
self._problems.EmptyFile(file_name)  
return results  
 
def _LoadAgencies(self):  
for (d, row_num, header, row) in self._ReadCsvDict('agency.txt',  
Agency._FIELD_NAMES,  
Agency._REQUIRED_FIELD_NAMES):  
self._problems.SetFileContext('agency.txt', row_num, row, header)  
agency = Agency(field_dict=d)  
self._schedule.AddAgencyObject(agency, self._problems)  
self._problems.ClearContext()  
 
def _LoadStops(self):  
for (d, row_num, header, row) in self._ReadCsvDict(  
'stops.txt',  
Stop._FIELD_NAMES,  
Stop._REQUIRED_FIELD_NAMES):  
self._problems.SetFileContext('stops.txt', row_num, row, header)  
 
stop = Stop(field_dict=d)  
stop.Validate(self._problems)  
self._schedule.AddStopObject(stop, self._problems)  
 
self._problems.ClearContext()  
 
def _LoadRoutes(self):  
for (d, row_num, header, row) in self._ReadCsvDict(  
'routes.txt',  
Route._FIELD_NAMES,  
Route._REQUIRED_FIELD_NAMES):  
self._problems.SetFileContext('routes.txt', row_num, row, header)  
 
route = Route(field_dict=d)  
self._schedule.AddRouteObject(route, self._problems)  
 
self._problems.ClearContext()  
 
def _LoadCalendar(self):  
file_name = 'calendar.txt'  
file_name_dates = 'calendar_dates.txt'  
if not self._HasFile(file_name) and not self._HasFile(file_name_dates):  
self._problems.MissingFile(file_name)  
return  
 
# map period IDs to (period object, (file_name, row_num, row, cols))  
periods = {}  
 
# process calendar.txt  
if self._HasFile(file_name):  
has_useful_contents = False  
for (row, row_num, cols) in \  
self._ReadCSV(file_name,  
ServicePeriod._FIELD_NAMES,  
ServicePeriod._FIELD_NAMES_REQUIRED):  
context = (file_name, row_num, row, cols)  
self._problems.SetFileContext(*context)  
 
period = ServicePeriod(field_list=row)  
 
if period.service_id in periods:  
self._problems.DuplicateID('service_id', period.service_id)  
else:  
periods[period.service_id] = (period, context)  
self._problems.ClearContext()  
 
# process calendar_dates.txt  
if self._HasFile(file_name_dates):  
# ['service_id', 'date', 'exception_type']  
fields = ServicePeriod._FIELD_NAMES_CALENDAR_DATES  
for (row, row_num, cols) in self._ReadCSV(file_name_dates,  
fields, fields):  
context = (file_name_dates, row_num, row, cols)  
self._problems.SetFileContext(*context)  
 
service_id = row[0]  
 
period = None  
if service_id in periods:  
period = periods[service_id][0]  
else:  
period = ServicePeriod(service_id)  
periods[period.service_id] = (period, context)  
 
exception_type = row[2]  
if exception_type == u'1':  
period.SetDateHasService(row[1], True, self._problems)  
elif exception_type == u'2':  
period.SetDateHasService(row[1], False, self._problems)  
else:  
self._problems.InvalidValue('exception_type', exception_type)  
self._problems.ClearContext()  
 
# Now insert the periods into the schedule object, so that they're  
# validated with both calendar and calendar_dates info present  
for period, context in periods.values():  
self._problems.SetFileContext(*context)  
self._schedule.AddServicePeriodObject(period, self._problems)  
self._problems.ClearContext()  
 
def _LoadShapes(self):  
if not self._HasFile('shapes.txt'):  
return  
 
shapes = {} # shape_id to tuple  
for (row, row_num, cols) in self._ReadCSV('shapes.txt',  
Shape._FIELD_NAMES,  
Shape._REQUIRED_FIELD_NAMES):  
file_context = ('shapes.txt', row_num, row, cols)  
self._problems.SetFileContext(*file_context)  
 
(shape_id, lat, lon, seq, dist) = row  
if IsEmpty(shape_id):  
self._problems.MissingValue('shape_id')  
continue  
try:  
seq = int(seq)  
except (TypeError, ValueError):  
self._problems.InvalidValue('shape_pt_sequence', seq,  
'Value should be a number (0 or higher)')  
continue  
 
shapes.setdefault(shape_id, []).append((seq, lat, lon, dist, file_context))  
self._problems.ClearContext()  
 
for shape_id, points in shapes.items():  
shape = Shape(shape_id)  
points.sort()  
if points and points[0][0] < 0:  
self._problems.InvalidValue('shape_pt_sequence', points[0][0],  
'In shape %s, a negative sequence number '  
'%d was found; sequence numbers should be '  
'0 or higher.' % (shape_id, points[0][0]))  
 
last_seq = None  
for (seq, lat, lon, dist, file_context) in points:  
if (seq == last_seq):  
self._problems.SetFileContext(*file_context)  
self._problems.InvalidValue('shape_pt_sequence', seq,  
'The sequence number %d occurs more '  
'than once in shape %s.' %  
(seq, shape_id))  
last_seq = seq  
shape.AddPoint(lat, lon, dist, self._problems)  
self._problems.ClearContext()  
 
self._schedule.AddShapeObject(shape, self._problems)  
 
def _LoadTrips(self):  
for (d, row_num, header, row) in self._ReadCsvDict(  
'trips.txt',  
Trip._FIELD_NAMES,  
Trip._REQUIRED_FIELD_NAMES):  
self._problems.SetFileContext('trips.txt', row_num, row, header)  
 
trip = Trip(field_dict=d)  
self._schedule.AddTripObject(trip, self._problems)  
 
self._problems.ClearContext()  
 
def _LoadFares(self):  
if not self._HasFile('fare_attributes.txt'):  
return  
for (row, row_num, cols) in self._ReadCSV('fare_attributes.txt',  
Fare._FIELD_NAMES,  
Fare._REQUIRED_FIELD_NAMES):  
self._problems.SetFileContext('fare_attributes.txt', row_num, row, cols)  
 
fare = Fare(field_list=row)  
self._schedule.AddFareObject(fare, self._problems)  
 
self._problems.ClearContext()  
 
def _LoadFareRules(self):  
if not self._HasFile('fare_rules.txt'):  
return  
for (row, row_num, cols) in self._ReadCSV('fare_rules.txt',  
FareRule._FIELD_NAMES,  
FareRule._REQUIRED_FIELD_NAMES):  
self._problems.SetFileContext('fare_rules.txt', row_num, row, cols)  
 
rule = FareRule(field_list=row)  
self._schedule.AddFareRuleObject(rule, self._problems)  
 
self._problems.ClearContext()  
 
def _LoadHeadways(self):  
file_name = 'frequencies.txt'  
if not self._HasFile(file_name): # headways are an optional feature  
return  
 
# ['trip_id', 'start_time', 'end_time', 'headway_secs']  
fields = Trip._FIELD_NAMES_HEADWAY  
modified_trips = {}  
for (row, row_num, cols) in self._ReadCSV(file_name, fields, fields):  
self._problems.SetFileContext(file_name, row_num, row, cols)  
(trip_id, start_time, end_time, headway_secs) = row  
try:  
trip = self._schedule.GetTrip(trip_id)  
trip.AddHeadwayPeriod(start_time, end_time, headway_secs,  
self._problems)  
modified_trips[trip_id] = trip  
except KeyError:  
self._problems.InvalidValue('trip_id', trip_id)  
self._problems.ClearContext()  
 
for trip in modified_trips.values():  
trip.Validate(self._problems)  
 
def _LoadStopTimes(self):  
for (row, row_num, cols) in self._ReadCSV('stop_times.txt',  
StopTime._FIELD_NAMES,  
StopTime._REQUIRED_FIELD_NAMES):  
file_context = ('stop_times.txt', row_num, row, cols)  
self._problems.SetFileContext(*file_context)  
 
(trip_id, arrival_time, departure_time, stop_id, stop_sequence,  
stop_headsign, pickup_type, drop_off_type, shape_dist_traveled) = row  
 
try:  
sequence = int(stop_sequence)  
except (TypeError, ValueError):  
self._problems.InvalidValue('stop_sequence', stop_sequence,  
'This should be a number.')  
continue  
if sequence < 0:  
self._problems.InvalidValue('stop_sequence', sequence,  
'Sequence numbers should be 0 or higher.')  
 
if stop_id not in self._schedule.stops:  
self._problems.InvalidValue('stop_id', stop_id,  
'This value wasn\'t defined in stops.txt')  
continue  
stop = self._schedule.stops[stop_id]  
if trip_id not in self._schedule.trips:  
self._problems.InvalidValue('trip_id', trip_id,  
'This value wasn\'t defined in trips.txt')  
continue  
trip = self._schedule.trips[trip_id]  
 
# If self._problems.Report returns then StopTime.__init__ will return  
# even if the StopTime object has an error. Thus this code may add a  
# StopTime that didn't validate to the database.  
# Trip.GetStopTimes then tries to make a StopTime from the invalid data  
# and calls the problem reporter for errors. An ugly solution is to  
# wrap problems and a better solution is to move all validation out of  
# __init__. For now make sure Trip.GetStopTimes gets a problem reporter  
# when called from Trip.Validate.  
stop_time = StopTime(self._problems, stop, arrival_time,  
departure_time, stop_headsign,  
pickup_type, drop_off_type,  
shape_dist_traveled, stop_sequence=sequence)  
trip._AddStopTimeObjectUnordered(stop_time, self._schedule)  
self._problems.ClearContext()  
 
# stop_times are validated in Trip.ValidateChildren, called by  
# Schedule.Validate  
 
def _LoadTransfers(self):  
file_name = 'transfers.txt'  
if not self._HasFile(file_name): # transfers are an optional feature  
return  
for (d, row_num, header, row) in self._ReadCsvDict(file_name,  
Transfer._FIELD_NAMES,  
Transfer._REQUIRED_FIELD_NAMES):  
self._problems.SetFileContext(file_name, row_num, row, header)  
transfer = Transfer(field_dict=d)  
self._schedule.AddTransferObject(transfer, self._problems)  
self._problems.ClearContext()  
 
def Load(self):  
self._problems.ClearContext()  
if not self._DetermineFormat():  
return self._schedule  
 
self._CheckFileNames()  
 
self._LoadAgencies()  
self._LoadStops()  
self._LoadRoutes()  
self._LoadCalendar()  
self._LoadShapes()  
self._LoadTrips()  
self._LoadHeadways()  
if self._load_stop_times:  
self._LoadStopTimes()  
self._LoadFares()  
self._LoadFareRules()  
self._LoadTransfers()  
 
if self._zip:  
self._zip.close()  
self._zip = None  
 
if self._extra_validation:  
self._schedule.Validate(self._problems, validate_children=False)  
 
return self._schedule  
 
 
class ShapeLoader(Loader):  
"""A subclass of Loader that only loads the shapes from a GTFS file."""  
 
def __init__(self, *args, **kwargs):  
"""Initialize a new ShapeLoader object.  
 
See Loader.__init__ for argument documentation.  
"""  
Loader.__init__(self, *args, **kwargs)  
 
def Load(self):  
self._LoadShapes()  
return self._schedule  
 
#!/usr/bin/python2.4  
#  
# Copyright 2007 Google Inc. All Rights Reserved.  
#  
# Licensed under the Apache License, Version 2.0 (the "License");  
# you may not use this file except in compliance with the License.  
# You may obtain a copy of the License at  
#  
# http://www.apache.org/licenses/LICENSE-2.0  
#  
# Unless required by applicable law or agreed to in writing, software  
# distributed under the License is distributed on an "AS IS" BASIS,  
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.  
# See the License for the specific language governing permissions and  
# limitations under the License.  
 
"""A library for manipulating points and polylines.  
 
This is a library for creating and manipulating points on the unit  
sphere, as an approximate model of Earth. The primary use of this  
library is to make manipulation and matching of polylines easy in the  
transitfeed library.  
 
NOTE: in this library, Earth is modelled as a sphere, whereas  
GTFS specifies that latitudes and longitudes are in WGS84. For the  
purpose of comparing and matching latitudes and longitudes that  
are relatively close together on the surface of the earth, this  
is adequate; for other purposes, this library may not be accurate  
enough.  
"""  
 
__author__ = 'chris.harrelson.code@gmail.com (Chris Harrelson)'  
 
import copy  
import decimal  
import heapq  
import math  
 
class ShapeError(Exception):  
"""Thrown whenever there is a shape parsing error."""  
pass  
 
 
EARTH_RADIUS_METERS = 6371010.0  
 
 
class Point(object):  
"""  
A class representing a point on the unit sphere in three dimensions.  
"""  
def __init__(self, x, y, z):  
self.x = x  
self.y = y  
self.z = z  
 
def __hash__(self):  
return hash((self.x, self.y, self.z))  
 
def __cmp__(self, other):  
if not isinstance(other, Point):  
raise TypeError('Point.__cmp__(x,y) requires y to be a "Point", '  
'not a "%s"' % type(other).__name__)  
return cmp((self.x, self.y, self.z), (other.x, other.y, other.z))  
 
def __str__(self):  
return "(%.15f, %.15f, %.15f) " % (self.x, self.y, self.z)  
 
def Norm2(self):  
"""  
Returns the L_2 (Euclidean) norm of self.  
"""  
sum = self.x * self.x + self.y * self.y + self.z * self.z  
return math.sqrt(float(sum))  
 
def IsUnitLength(self):  
return abs(self.Norm2() - 1.0) < 1e-14  
 
def Plus(self, other):  
"""  
Returns a new point which is the pointwise sum of self and other.  
"""  
return Point(self.x + other.x,  
self.y + other.y,  
self.z + other.z)  
 
def Minus(self, other):  
"""  
Returns a new point which is the pointwise subtraction of other from  
self.  
"""  
return Point(self.x - other.x,  
self.y - other.y,  
self.z - other.z)  
 
def DotProd(self, other):  
"""  
Returns the (scalar) dot product of self with other.  
"""  
return self.x * other.x + self.y * other.y + self.z * other.z  
 
def Times(self, val):  
"""  
Returns a new point which is pointwise multiplied by val.  
"""  
return Point(self.x * val, self.y * val, self.z * val)  
 
def Normalize(self):  
"""  
Returns a unit point in the same direction as self.  
"""  
return self.Times(1 / self.Norm2())  
 
def RobustCrossProd(self, other):  
"""  
A robust version of cross product. If self and other  
are not nearly the same point, returns the same value  
as CrossProd() modulo normalization. Otherwise returns  
an arbitrary unit point orthogonal to self.  
"""  
assert(self.IsUnitLength() and other.IsUnitLength())  
x = self.Plus(other).CrossProd(other.Minus(self))  
if abs(x.x) > 1e-15 or abs(x.y) > 1e-15 or abs(x.z) > 1e-15:  
return x.Normalize()  
else:  
return self.Ortho()  
 
def LargestComponent(self):  
"""  
Returns (i, val) where i is the component index (0 - 2)  
which has largest absolute value and val is the value  
of the component.  
"""  
if abs(self.x) > abs(self.y):  
if abs(self.x) > abs(self.z):  
return (0, self.x)  
else:  
return (2, self.z)  
else:  
if abs(self.y) > abs(self.z):  
return (1, self.y)  
else:  
return (2, self.z)  
 
def Ortho(self):  
"""Returns a unit-length point orthogonal to this point"""  
(index, val) = self.LargestComponent()  
index = index - 1  
if index < 0:  
index = 2  
temp = Point(0.012, 0.053, 0.00457)  
if index == 0:  
temp.x = 1  
elif index == 1:  
temp.y = 1  
elif index == 2:  
temp.z = 1  
return self.CrossProd(temp).Normalize()  
 
def CrossProd(self, other):  
"""  
Returns the cross product of self and other.  
"""  
return Point(  
self.y * other.z - self.z * other.y,  
self.z * other.x - self.x * other.z,  
self.x * other.y - self.y * other.x)  
 
@staticmethod  
def _approxEq(a, b):  
return abs(a - b) < 1e-11  
 
def Equals(self, other):  
"""  
Returns true of self and other are approximately equal.  
"""  
return (self._approxEq(self.x, other.x)  
and self._approxEq(self.y, other.y)  
and self._approxEq(self.z, other.z))  
 
def Angle(self, other):  
"""  
Returns the angle in radians between self and other.  
"""  
return math.atan2(self.CrossProd(other).Norm2(),  
self.DotProd(other))  
 
def ToLatLng(self):  
"""  
Returns that latitude and longitude that this point represents  
under a spherical Earth model.  
"""  
rad_lat = math.atan2(self.z, math.sqrt(self.x * self.x + self.y * self.y))  
rad_lng = math.atan2(self.y, self.x)  
return (rad_lat * 180.0 / math.pi, rad_lng * 180.0 / math.pi)  
 
@staticmethod  
def FromLatLng(lat, lng):  
"""  
Returns a new point representing this latitude and longitude under  
a spherical Earth model.  
"""  
phi = lat * (math.pi / 180.0)  
theta = lng * (math.pi / 180.0)  
cosphi = math.cos(phi)  
return Point(math.cos(theta) * cosphi,  
math.sin(theta) * cosphi,  
math.sin(phi))  
 
def GetDistanceMeters(self, other):  
assert(self.IsUnitLength() and other.IsUnitLength())  
return self.Angle(other) * EARTH_RADIUS_METERS  
 
 
def SimpleCCW(a, b, c):  
"""  
Returns true if the triangle abc is oriented counterclockwise.  
"""  
return c.CrossProd(a).DotProd(b) > 0  
 
def GetClosestPoint(x, a, b):  
"""  
Returns the point on the great circle segment ab closest to x.  
"""  
assert(x.IsUnitLength())  
assert(a.IsUnitLength())  
assert(b.IsUnitLength())  
 
a_cross_b = a.RobustCrossProd(b)  
# project to the great circle going through a and b  
p = x.Minus(  
a_cross_b.Times(  
x.DotProd(a_cross_b) / a_cross_b.Norm2()))  
 
# if p lies between a and b, return it  
if SimpleCCW(a_cross_b, a, p) and SimpleCCW(p, b, a_cross_b):  
return p.Normalize()  
 
# otherwise return the closer of a or b  
if x.Minus(a).Norm2() <= x.Minus(b).Norm2():  
return a  
else:  
return b  
 
 
class Poly(object):  
"""  
A class representing a polyline.  
"""  
def __init__(self, points = [], name=None):  
self._points = list(points)  
self._name = name  
 
def AddPoint(self, p):  
"""  
Adds a new point to the end of the polyline.  
"""  
assert(p.IsUnitLength())  
self._points.append(p)  
 
def GetName(self):  
return self._name  
 
def GetPoint(self, i):  
return self._points[i]  
 
def GetPoints(self):  
return self._points  
 
def GetNumPoints(self):  
return len(self._points)  
 
def _GetPointSafe(self, i):  
try:  
return self.GetPoint(i)  
except IndexError:  
return None  
 
def GetClosestPoint(self, p):  
"""  
Returns (closest_p, closest_i), where closest_p is the closest point  
to p on the piecewise linear curve represented by the polyline,  
and closest_i is the index of the point on the polyline just before  
the polyline segment that contains closest_p.  
"""  
assert(len(self._points) > 0)  
closest_point = self._points[0]  
closest_i = 0  
 
for i in range(0, len(self._points) - 1):  
(a, b) = (self._points[i], self._points[i+1])  
cur_closest_point = GetClosestPoint(p, a, b)  
if p.Angle(cur_closest_point) < p.Angle(closest_point):  
closest_point = cur_closest_point.Normalize()  
closest_i = i  
 
return (closest_point, closest_i)  
 
def LengthMeters(self):  
"""Return length of this polyline in meters."""  
assert(len(self._points) > 0)  
length = 0  
for i in range(0, len(self._points) - 1):  
length += self._points[i].GetDistanceMeters(self._points[i+1])  
return length  
 
def Reversed(self):  
"""Return a polyline that is the reverse of this polyline."""  
return Poly(reversed(self.GetPoints()), self.GetName())  
 
def CutAtClosestPoint(self, p):  
"""  
Let x be the point on the polyline closest to p. Then  
CutAtClosestPoint returns two new polylines, one representing  
the polyline from the beginning up to x, and one representing  
x onwards to the end of the polyline. x is the first point  
returned in the second polyline.  
"""  
(closest, i) = self.GetClosestPoint(p)  
 
tmp = [closest]  
tmp.extend(self._points[i+1:])  
return (Poly(self._points[0:i+1]),  
Poly(tmp))  
 
def GreedyPolyMatchDist(self, shape):  
"""  
Tries a greedy matching algorithm to match self to the  
given shape. Returns the maximum distance in meters of  
any point in self to its matched point in shape under the  
algorithm.  
 
Args: shape, a Poly object.  
"""  
tmp_shape = Poly(shape.GetPoints())  
max_radius = 0  
for (i, point) in enumerate(self._points):  
tmp_shape = tmp_shape.CutAtClosestPoint(point)[1]  
dist = tmp_shape.GetPoint(0).GetDistanceMeters(point)  
max_radius = max(max_radius, dist)  
return max_radius  
 
@staticmethod  
def MergePolys(polys, merge_point_threshold=10):  
"""  
Merge multiple polylines, in the order that they were passed in.  
Merged polyline will have the names of their component parts joined by ';'.  
Example: merging [a,b], [c,d] and [e,f] will result in [a,b,c,d,e,f].  
However if the endpoints of two adjacent polylines are less than  
merge_point_threshold meters apart, we will only use the first endpoint in  
the merged polyline.  
"""  
name = ";".join((p.GetName(), '')[p.GetName() is None] for p in polys)  
merged = Poly([], name)  
if polys:  
first_poly = polys[0]  
for p in first_poly.GetPoints():  
merged.AddPoint(p)  
last_point = merged._GetPointSafe(-1)  
for poly in polys[1:]:  
first_point = poly._GetPointSafe(0)  
if (last_point and first_point and  
last_point.GetDistanceMeters(first_point) <= merge_point_threshold):  
points = poly.GetPoints()[1:]  
else:  
points = poly.GetPoints()  
for p in points:  
merged.AddPoint(p)  
last_point = merged._GetPointSafe(-1)  
return merged  
 
 
def __str__(self):  
return self._ToString(str)  
 
def ToLatLngString(self):  
return self._ToString(lambda p: str(p.ToLatLng()))  
 
def _ToString(self, pointToStringFn):  
return "%s: %s" % (self.GetName() or "",  
", ".join([pointToStringFn(p) for p in self._points]))  
 
 
class PolyCollection(object):  
"""  
A class representing a collection of polylines.  
"""  
def __init__(self):  
self._name_to_shape = {}  
pass  
 
def AddPoly(self, poly, smart_duplicate_handling=True):  
"""  
Adds a new polyline to the collection.  
"""  
inserted_name = poly.GetName()  
if poly.GetName() in self._name_to_shape:  
if not smart_duplicate_handling:  
raise ShapeError("Duplicate shape found: " + poly.GetName())  
 
print ("Warning: duplicate shape id being added to collection: " +  
poly.GetName())  
if poly.GreedyPolyMatchDist(self._name_to_shape[poly.GetName()]) < 10:  
print " (Skipping as it apears to be an exact duplicate)"  
else:  
print " (Adding new shape variant with uniquified name)"  
inserted_name = "%s-%d" % (inserted_name, len(self._name_to_shape))  
self._name_to_shape[inserted_name] = poly  
 
def NumPolys(self):  
return len(self._name_to_shape)  
 
def FindMatchingPolys(self, start_point, end_point, max_radius=150):  
"""  
Returns a list of polylines in the collection that have endpoints  
within max_radius of the given start and end points.  
"""  
matches = []  
for shape in self._name_to_shape.itervalues():  
if start_point.GetDistanceMeters(shape.GetPoint(0)) < max_radius and \  
end_point.GetDistanceMeters(shape.GetPoint(-1)) < max_radius:  
matches.append(shape)  
return matches  
 
class PolyGraph(PolyCollection):  
"""  
A class representing a graph where the edges are polylines.  
"""  
def __init__(self):  
PolyCollection.__init__(self)  
self._nodes = {}  
 
def AddPoly(self, poly, smart_duplicate_handling=True):  
PolyCollection.AddPoly(self, poly, smart_duplicate_handling)  
start_point = poly.GetPoint(0)  
end_point = poly.GetPoint(-1)  
self._AddNodeWithEdge(start_point, poly)  
self._AddNodeWithEdge(end_point, poly)  
 
def _AddNodeWithEdge(self, point, edge):  
if point in self._nodes:  
self._nodes[point].add(edge)  
else:  
self._nodes[point] = set([edge])  
 
def ShortestPath(self, start, goal):  
"""Uses the A* algorithm to find a shortest path between start and goal.  
 
For more background see http://en.wikipedia.org/wiki/A-star_algorithm  
 
Some definitions:  
g(x): The actual shortest distance traveled from initial node to current  
node.  
h(x): The estimated (or "heuristic") distance from current node to goal.  
We use the distance on Earth from node to goal as the heuristic.  
This heuristic is both admissible and monotonic (see wikipedia for  
more details).  
f(x): The sum of g(x) and h(x), used to prioritize elements to look at.  
 
Arguments:  
start: Point that is in the graph, start point of the search.  
goal: Point that is in the graph, end point for the search.  
 
Returns:  
A Poly object representing the shortest polyline through the graph from  
start to goal, or None if no path found.  
"""  
 
assert start in self._nodes  
assert goal in self._nodes  
closed_set = set() # Set of nodes already evaluated.  
open_heap = [(0, start)] # Nodes to visit, heapified by f(x).  
open_set = set([start]) # Same as open_heap, but a set instead of a heap.  
g_scores = { start: 0 } # Distance from start along optimal path  
came_from = {} # Map to reconstruct optimal path once we're done.  
while open_set:  
(f_x, x) = heapq.heappop(open_heap)  
open_set.remove(x)  
if x == goal:  
return self._ReconstructPath(came_from, goal)  
closed_set.add(x)  
edges = self._nodes[x]  
for edge in edges:  
if edge.GetPoint(0) == x:  
y = edge.GetPoint(-1)  
else:  
y = edge.GetPoint(0)  
if y in closed_set:  
continue  
tentative_g_score = g_scores[x] + edge.LengthMeters()  
tentative_is_better = False  
if y not in open_set:  
h_y = y.GetDistanceMeters(goal)  
f_y = tentative_g_score + h_y  
open_set.add(y)  
heapq.heappush(open_heap, (f_y, y))  
tentative_is_better = True  
elif tentative_g_score < g_scores[y]:  
tentative_is_better = True  
if tentative_is_better:  
came_from[y] = (x, edge)  
g_scores[y] = tentative_g_score  
return None  
 
def _ReconstructPath(self, came_from, current_node):  
"""  
Helper method for ShortestPath, to reconstruct path.  
 
Arguments:  
came_from: a dictionary mapping Point to (Point, Poly) tuples.  
This dictionary keeps track of the previous neighbor to a node, and  
the edge used to get from the previous neighbor to the node.  
current_node: the current Point in the path.  
 
Returns:  
A Poly that represents the path through the graph from the start of the  
search to current_node.  
"""  
if current_node in came_from:  
(previous_node, previous_edge) = came_from[current_node]  
if previous_edge.GetPoint(0) == current_node:  
previous_edge = previous_edge.Reversed()  
p = self._ReconstructPath(came_from, previous_node)  
return Poly.MergePolys([p, previous_edge], merge_point_threshold=0)  
else:  
return Poly([], '')  
 
def FindShortestMultiPointPath(self, points, max_radius=150, keep_best_n=10,  
verbosity=0):  
"""  
Return a polyline, representing the shortest path through this graph that  
has edge endpoints on each of a given list of points in sequence. We allow  
fuzziness in matching of input points to points in this graph.  
 
We limit ourselves to a view of the best keep_best_n paths at any time, as a  
greedy optimization.  
"""  
assert len(points) > 1  
nearby_points = []  
paths_found = [] # A heap sorted by inverse path length.  
 
for i, point in enumerate(points):  
nearby = [p for p in self._nodes.iterkeys()  
if p.GetDistanceMeters(point) < max_radius]  
if verbosity >= 2:  
print ("Nearby points for point %d %s: %s"  
% (i + 1,  
str(point.ToLatLng()),  
", ".join([str(n.ToLatLng()) for n in nearby])))  
if nearby:  
nearby_points.append(nearby)  
else:  
print "No nearby points found for point %s" % str(point.ToLatLng())  
return None  
 
pathToStr = lambda start, end, path: (" Best path %s -> %s: %s"  
% (str(start.ToLatLng()),  
str(end.ToLatLng()),  
path and path.GetName() or  
"None"))  
if verbosity >= 3:  
print "Step 1"  
step = 2  
 
start_points = nearby_points[0]  
end_points = nearby_points[1]  
 
for start in start_points:  
for end in end_points:  
path = self.ShortestPath(start, end)  
if verbosity >= 3:  
print pathToStr(start, end, path)  
PolyGraph._AddPathToHeap(paths_found, path, keep_best_n)  
 
for possible_points in nearby_points[2:]:  
if verbosity >= 3:  
print "\nStep %d" % step  
step += 1  
new_paths_found = []  
 
start_end_paths = {} # cache of shortest paths between (start, end) pairs  
for score, path in paths_found:  
start = path.GetPoint(-1)  
for end in possible_points:  
if (start, end) in start_end_paths:  
new_segment = start_end_paths[(start, end)]  
else:  
new_segment = self.ShortestPath(start, end)  
if verbosity >= 3:  
print pathToStr(start, end, new_segment)  
start_end_paths[(start, end)] = new_segment  
 
if new_segment:  
new_path = Poly.MergePolys([path, new_segment],  
merge_point_threshold=0)  
PolyGraph._AddPathToHeap(new_paths_found, new_path, keep_best_n)  
paths_found = new_paths_found  
 
if paths_found:  
best_score, best_path = max(paths_found)  
return best_path  
else:  
return None  
 
@staticmethod  
def _AddPathToHeap(heap, path, keep_best_n):  
if path and path.GetNumPoints():  
new_item = (-path.LengthMeters(), path)  
if new_item not in heap:  
if len(heap) < keep_best_n:  
heapq.heappush(heap, new_item)  
else:  
heapq.heapreplace(heap, new_item)  
 
#!/usr/bin/python2.5  
 
# Copyright (C) 2009 Google Inc.  
#  
# Licensed under the Apache License, Version 2.0 (the "License");  
# you may not use this file except in compliance with the License.  
# You may obtain a copy of the License at  
#  
# http://www.apache.org/licenses/LICENSE-2.0  
#  
# Unless required by applicable law or agreed to in writing, software  
# distributed under the License is distributed on an "AS IS" BASIS,  
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.  
# See the License for the specific language governing permissions and  
# limitations under the License.  
 
 
import optparse  
import sys  
 
 
class OptionParserLongError(optparse.OptionParser):  
"""OptionParser subclass that includes list of options above error message."""  
def error(self, msg):  
print >>sys.stderr, self.format_help()  
print >>sys.stderr, '\n\n%s: error: %s\n\n' % (self.get_prog_name(), msg)  
sys.exit(2)  
 
 
def RunWithCrashHandler(f):  
try:  
exit_code = f()  
sys.exit(exit_code)  
except (SystemExit, KeyboardInterrupt):  
raise  
except:  
import inspect  
import traceback  
 
# Save trace and exception now. These calls look at the most recently  
# raised exception. The code that makes the report might trigger other  
# exceptions.  
original_trace = inspect.trace(3)[1:]  
formatted_exception = traceback.format_exception_only(*(sys.exc_info()[:2]))  
 
apology = """Yikes, the program threw an unexpected exception!  
 
Hopefully a complete report has been saved to transitfeedcrash.txt,  
though if you are seeing this message we've already disappointed you once  
today. Please include the report in a new issue at  
http://code.google.com/p/googletransitdatafeed/issues/entry  
or an email to the public group googletransitdatafeed@googlegroups.com. Sorry!  
 
"""  
dashes = '%s\n' % ('-' * 60)  
dump = []  
dump.append(apology)  
dump.append(dashes)  
try:  
import transitfeed  
dump.append("transitfeed version %s\n\n" % transitfeed.__version__)  
except NameError:  
# Oh well, guess we won't put the version in the report  
pass  
 
for (frame_obj, filename, line_num, fun_name, context_lines,  
context_index) in original_trace:  
dump.append('File "%s", line %d, in %s\n' % (filename, line_num,  
fun_name))  
if context_lines:  
for (i, line) in enumerate(context_lines):  
if i == context_index:  
dump.append(' --> %s' % line)  
else:  
dump.append(' %s' % line)  
for local_name, local_val in frame_obj.f_locals.items():  
try:  
truncated_val = str(local_val)[0:500]  
except Exception, e:  
dump.append(' Exception in str(%s): %s' % (local_name, e))  
else:  
if len(truncated_val) >= 500:  
truncated_val = '%s...' % truncated_val[0:499]  
dump.append(' %s = %s\n' % (local_name, truncated_val))  
dump.append('\n')  
 
dump.append(''.join(formatted_exception))  
 
open('transitfeedcrash.txt', 'w').write(''.join(dump))  
 
print ''.join(dump)  
print  
print dashes  
print apology  
 
try:  
raw_input('Press enter to continue...')  
except EOFError:  
# Ignore stdin being closed. This happens during some tests.  
pass  
sys.exit(127)  
 
 
# Pick one of two defaultdict implementations. A native version was added to  
# the collections library in python 2.5. If that is not available use Jason's  
# pure python recipe. He gave us permission to distribute it.  
 
# On Mon, Nov 30, 2009 at 07:27, jason kirtland <jek at discorporate.us> wrote:  
# >  
# > Hi Tom, sure thing! It's not easy to find on the cookbook site, but the  
# > recipe is under the Python license.  
# >  
# > Cheers,  
# > Jason  
# >  
# > On Thu, Nov 26, 2009 at 3:03 PM, Tom Brown <tom.brown.code@gmail.com> wrote:  
# >  
# >> I would like to include http://code.activestate.com/recipes/523034/ in  
# >> http://code.google.com/p/googletransitdatafeed/wiki/TransitFeedDistribution  
# >> which is distributed under the Apache License, Version 2.0 with Copyright  
# >> Google. May we include your code with a comment in the source pointing at  
# >> the original URL? Thanks, Tom Brown  
 
try:  
# Try the native implementation first  
from collections import defaultdict  
except:  
# Fallback for python2.4, which didn't include collections.defaultdict  
class defaultdict(dict):  
def __init__(self, default_factory=None, *a, **kw):  
if (default_factory is not None and  
not hasattr(default_factory, '__call__')):  
raise TypeError('first argument must be callable')  
dict.__init__(self, *a, **kw)  
self.default_factory = default_factory  
def __getitem__(self, key):  
try:  
return dict.__getitem__(self, key)  
except KeyError:  
return self.__missing__(key)  
def __missing__(self, key):  
if self.default_factory is None:  
raise KeyError(key)  
self[key] = value = self.default_factory()  
return value  
def __reduce__(self):  
if self.default_factory is None:  
args = tuple()  
else:  
args = self.default_factory,  
return type(self), args, None, None, self.items()  
def copy(self):  
return self.__copy__()  
def __copy__(self):  
return type(self)(self.default_factory, self)  
def __deepcopy__(self, memo):  
import copy  
return type(self)(self.default_factory,  
copy.deepcopy(self.items()))  
def __repr__(self):  
return 'defaultdict(%s, %s)' % (self.default_factory,  
dict.__repr__(self))  
 
#!/usr/bin/python  
 
# Copyright (C) 2007 Google Inc.  
#  
# Licensed under the Apache License, Version 2.0 (the "License");  
# you may not use this file except in compliance with the License.  
# You may obtain a copy of the License at  
#  
# http://www.apache.org/licenses/LICENSE-2.0  
#  
# Unless required by applicable law or agreed to in writing, software  
# distributed under the License is distributed on an "AS IS" BASIS,  
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.  
# See the License for the specific language governing permissions and  
# limitations under the License.  
 
 
"""Validates a GTFS file.  
 
For usage information run feedvalidator.py --help  
"""  
 
import bisect  
import codecs  
import datetime  
from transitfeed.util import defaultdict  
import optparse  
import os  
import os.path  
import re  
import socket  
import sys  
import time  
import transitfeed  
from transitfeed import TYPE_ERROR, TYPE_WARNING  
from urllib2 import Request, urlopen, HTTPError, URLError  
from transitfeed import util  
import webbrowser  
 
SVN_TAG_URL = 'http://googletransitdatafeed.googlecode.com/svn/tags/'  
 
 
def MaybePluralizeWord(count, word):  
if count == 1:  
return word  
else:  
return word + 's'  
 
 
def PrettyNumberWord(count, word):  
return '%d %s' % (count, MaybePluralizeWord(count, word))  
 
 
def UnCamelCase(camel):  
return re.sub(r'([a-z])([A-Z])', r'\1 \2', camel)  
 
 
def ProblemCountText(error_count, warning_count):  
results = []  
if error_count:  
results.append(PrettyNumberWord(error_count, 'error'))  
if warning_count:  
results.append(PrettyNumberWord(warning_count, 'warning'))  
 
return ' and '.join(results)  
 
 
def CalendarSummary(schedule):  
today = datetime.date.today()  
summary_end_date = today + datetime.timedelta(days=60)  
start_date, end_date = schedule.GetDateRange()  
 
if not start_date or not end_date:  
return {}  
 
try:  
start_date_object = transitfeed.DateStringToDateObject(start_date)  
end_date_object = transitfeed.DateStringToDateObject(end_date)  
except ValueError:  
return {}  
 
# Get the list of trips only during the period the feed is active.  
# As such we have to check if it starts in the future and/or if  
# if it ends in less than 60 days.  
date_trips_departures = schedule.GenerateDateTripsDeparturesList(  
max(today, start_date_object),  
min(summary_end_date, end_date_object))  
 
if not date_trips_departures:  
return {}  
 
# Check that the dates which will be shown in summary agree with these  
# calculations. Failure implies a bug which should be fixed. It isn't good  
# for users to discover assertion failures but means it will likely be fixed.  
assert start_date <= date_trips_departures[0][0].strftime("%Y%m%d")  
assert end_date >= date_trips_departures[-1][0].strftime("%Y%m%d")  
 
# Generate a map from int number of trips in a day to a list of date objects  
# with that many trips. The list of dates is sorted.  
trips_dates = defaultdict(lambda: [])  
trips = 0  
for date, day_trips, day_departures in date_trips_departures:  
trips += day_trips  
trips_dates[day_trips].append(date)  
mean_trips = trips / len(date_trips_departures)  
max_trips = max(trips_dates.keys())  
min_trips = min(trips_dates.keys())  
 
calendar_summary = {}  
calendar_summary['mean_trips'] = mean_trips  
calendar_summary['max_trips'] = max_trips  
calendar_summary['max_trips_dates'] = FormatDateList(trips_dates[max_trips])  
calendar_summary['min_trips'] = min_trips  
calendar_summary['min_trips_dates'] = FormatDateList(trips_dates[min_trips])  
calendar_summary['date_trips_departures'] = date_trips_departures  
calendar_summary['date_summary_range'] = "%s to %s" % (  
date_trips_departures[0][0].strftime("%a %b %d"),  
date_trips_departures[-1][0].strftime("%a %b %d"))  
 
return calendar_summary  
 
 
def FormatDateList(dates):  
if not dates:  
return "0 service dates"  
 
formatted = [d.strftime("%a %b %d") for d in dates[0:3]]  
if len(dates) > 3:  
formatted.append("...")  
return "%s (%s)" % (PrettyNumberWord(len(dates), "service date"),  
", ".join(formatted))  
 
 
def MaxVersion(versions):  
versions = filter(None, versions)  
versions.sort(lambda x,y: -cmp([int(item) for item in x.split('.')],  
[int(item) for item in y.split('.')]))  
if len(versions) > 0:  
return versions[0]  
 
 
class CountingConsoleProblemReporter(transitfeed.ProblemReporter):  
def __init__(self):  
transitfeed.ProblemReporter.__init__(self)  
self._error_count = 0  
self._warning_count = 0  
 
def _Report(self, e):  
transitfeed.ProblemReporter._Report(self, e)  
if e.IsError():  
self._error_count += 1  
else:  
self._warning_count += 1  
 
def ErrorCount(self):  
return self._error_count  
 
def WarningCount(self):  
return self._warning_count  
 
def FormatCount(self):  
return ProblemCountText(self.ErrorCount(), self.WarningCount())  
 
def HasIssues(self):  
return self.ErrorCount() or self.WarningCount()  
 
 
class BoundedProblemList(object):  
"""A list of one type of ExceptionWithContext objects with bounded size."""  
def __init__(self, size_bound):  
self._count = 0  
self._exceptions = []  
self._size_bound = size_bound  
 
def Add(self, e):  
self._count += 1  
try:  
bisect.insort(self._exceptions, e)  
except TypeError:  
# The base class ExceptionWithContext raises this exception in __cmp__  
# to signal that an object is not comparable. Instead of keeping the most  
# significant issue keep the first reported.  
if self._count <= self._size_bound:  
self._exceptions.append(e)  
else:  
# self._exceptions is in order. Drop the least significant if the list is  
# now too long.  
if self._count > self._size_bound:  
del self._exceptions[-1]  
 
def _GetDroppedCount(self):  
return self._count - len(self._exceptions)  
 
def __repr__(self):  
return "<BoundedProblemList %s>" % repr(self._exceptions)  
 
count = property(lambda s: s._count)  
dropped_count = property(_GetDroppedCount)  
problems = property(lambda s: s._exceptions)  
 
 
class LimitPerTypeProblemReporter(transitfeed.ProblemReporter):  
def __init__(self, limit_per_type):  
transitfeed.ProblemReporter.__init__(self)  
 
# {TYPE_WARNING: {"ClassName": BoundedProblemList()}}  
self._type_to_name_to_problist = {  
TYPE_WARNING: defaultdict(lambda: BoundedProblemList(limit_per_type)),  
TYPE_ERROR: defaultdict(lambda: BoundedProblemList(limit_per_type))  
}  
 
def HasIssues(self):  
return (self._type_to_name_to_problist[TYPE_ERROR] or  
self._type_to_name_to_problist[TYPE_WARNING])  
 
def _Report(self, e):  
self._type_to_name_to_problist[e.GetType()][e.__class__.__name__].Add(e)  
 
def ErrorCount(self):  
error_sets = self._type_to_name_to_problist[TYPE_ERROR].values()  
return sum(map(lambda v: v.count, error_sets))  
 
def WarningCount(self):  
warning_sets = self._type_to_name_to_problist[TYPE_WARNING].values()  
return sum(map(lambda v: v.count, warning_sets))  
 
def ProblemList(self, problem_type, class_name):  
"""Return the BoundedProblemList object for given type and class."""  
return self._type_to_name_to_problist[problem_type][class_name]  
 
def ProblemListMap(self, problem_type):  
"""Return the map from class name to BoundedProblemList object."""  
return self._type_to_name_to_problist[problem_type]  
 
 
class HTMLCountingProblemReporter(LimitPerTypeProblemReporter):  
def FormatType(self, f, level_name, class_problist):  
"""Write the HTML dumping all problems of one type.  
 
Args:  
f: file object open for writing  
level_name: string such as "Error" or "Warning"  
class_problist: sequence of tuples (class name,  
BoundedProblemList object)  
"""  
class_problist.sort()  
output = []  
for classname, problist in class_problist:  
output.append('<h4 class="issueHeader"><a name="%s%s">%s</a></h4><ul>\n' %  
(level_name, classname, UnCamelCase(classname)))  
for e in problist.problems:  
self.FormatException(e, output)  
if problist.dropped_count:  
output.append('<li>and %d more of this type.' %  
(problist.dropped_count))  
output.append('</ul>\n')  
f.write(''.join(output))  
 
def FormatTypeSummaryTable(self, level_name, name_to_problist):  
"""Return an HTML table listing the number of problems by class name.  
 
Args:  
level_name: string such as "Error" or "Warning"  
name_to_problist: dict mapping class name to an BoundedProblemList object  
 
Returns:  
HTML in a string  
"""  
output = []  
output.append('<table>')  
for classname in sorted(name_to_problist.keys()):  
problist = name_to_problist[classname]  
human_name = MaybePluralizeWord(problist.count, UnCamelCase(classname))  
output.append('<tr><td>%d</td><td><a href="#%s%s">%s</a></td></tr>\n' %  
(problist.count, level_name, classname, human_name))  
output.append('</table>\n')  
return ''.join(output)  
 
def FormatException(self, e, output):  
"""Append HTML version of e to list output."""  
d = e.GetDictToFormat()  
for k in ('file_name', 'feedname', 'column_name'):  
if k in d.keys():  
d[k] = '<code>%s</code>' % d[k]  
problem_text = e.FormatProblem(d).replace('\n', '<br>')  
output.append('<li>')  
output.append('<div class="problem">%s</div>' %  
transitfeed.EncodeUnicode(problem_text))  
try:  
if hasattr(e, 'row_num'):  
line_str = 'line %d of ' % e.row_num  
else:  
line_str = ''  
output.append('in %s<code>%s</code><br>\n' %  
(line_str, e.file_name))  
row = e.row  
headers = e.headers  
column_name = e.column_name  
table_header = '' # HTML  
table_data = '' # HTML  
for header, value in zip(headers, row):  
attributes = ''  
if header == column_name:  
attributes = ' class="problem"'  
table_header += '<th%s>%s</th>' % (attributes, header)  
table_data += '<td%s>%s</td>' % (attributes, value)  
# Make sure output is encoded into UTF-8  
output.append('<table class="dump"><tr>%s</tr>\n' %  
transitfeed.EncodeUnicode(table_header))  
output.append('<tr>%s</tr></table>\n' %  
transitfeed.EncodeUnicode(table_data))  
except AttributeError, e:  
pass # Hope this was getting an attribute from e ;-)  
output.append('<br></li>\n')  
 
def FormatCount(self):  
return ProblemCountText(self.ErrorCount(), self.WarningCount())  
 
def CountTable(self):  
output = []  
output.append('<table class="count_outside">\n')  
output.append('<tr>')  
if self.ProblemListMap(TYPE_ERROR):  
output.append('<td><span class="fail">%s</span></td>' %  
PrettyNumberWord(self.ErrorCount(), "error"))  
if self.ProblemListMap(TYPE_WARNING):  
output.append('<td><span class="fail">%s</span></td>' %  
PrettyNumberWord(self.WarningCount(), "warning"))  
output.append('</tr>\n<tr>')  
if self.ProblemListMap(TYPE_ERROR):  
output.append('<td>\n')  
output.append(self.FormatTypeSummaryTable("Error",  
self.ProblemListMap(TYPE_ERROR)))  
output.append('</td>\n')  
if self.ProblemListMap(TYPE_WARNING):  
output.append('<td>\n')  
output.append(self.FormatTypeSummaryTable("Warning",  
self.ProblemListMap(TYPE_WARNING)))  
output.append('</td>\n')  
output.append('</table>')  
return ''.join(output)  
 
def WriteOutput(self, feed_location, f, schedule, other_problems):  
"""Write the html output to f."""  
if self.HasIssues():  
if self.ErrorCount() + self.WarningCount() == 1:  
summary = ('<span class="fail">Found this problem:</span>\n%s' %  
self.CountTable())  
else:  
summary = ('<span class="fail">Found these problems:</span>\n%s' %  
self.CountTable())  
else:  
summary = '<span class="pass">feed validated successfully</span>'  
if other_problems is not None:  
summary = ('<span class="fail">\n%s</span><br><br>' %  
other_problems) + summary  
 
basename = os.path.basename(feed_location)  
feed_path = (feed_location[:feed_location.rfind(basename)], basename)  
 
agencies = ', '.join(['<a href="%s">%s</a>' % (a.agency_url, a.agency_name)  
for a in schedule.GetAgencyList()])  
if not agencies:  
agencies = '?'  
 
dates = "No valid service dates found"  
(start, end) = schedule.GetDateRange()  
if start and end:  
def FormatDate(yyyymmdd):  
src_format = "%Y%m%d"  
dst_format = "%B %d, %Y"  
try:  
return time.strftime(dst_format,  
time.strptime(yyyymmdd, src_format))  
except ValueError:  
return yyyymmdd  
 
formatted_start = FormatDate(start)  
formatted_end = FormatDate(end)  
dates = "%s to %s" % (formatted_start, formatted_end)  
 
calendar_summary = CalendarSummary(schedule)  
if calendar_summary:  
calendar_summary_html = """<br>  
During the upcoming service dates %(date_summary_range)s:  
<table>  
<tr><th class="header">Average trips per date:</th><td class="header">%(mean_trips)s</td></tr>  
<tr><th class="header">Most trips on a date:</th><td class="header">%(max_trips)s, on %(max_trips_dates)s</td></tr>  
<tr><th class="header">Least trips on a date:</th><td class="header">%(min_trips)s, on %(min_trips_dates)s</td></tr>  
</table>""" % calendar_summary  
else:  
calendar_summary_html = ""  
 
output_prefix = """  
<html>  
<head>  
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">  
<title>FeedValidator: %(feed_file)s</title>  
<style>  
body {font-family: Georgia, serif; background-color: white}  
.path {color: gray}  
div.problem {max-width: 500px}  
table.dump td,th {background-color: khaki; padding: 2px; font-family:monospace}  
table.dump td.problem,th.problem {background-color: dc143c; color: white; padding: 2px; font-family:monospace}  
table.count_outside td {vertical-align: top}  
table.count_outside {border-spacing: 0px; }  
table {border-spacing: 5px 0px; margin-top: 3px}  
h3.issueHeader {padding-left: 0.5em}  
h4.issueHeader {padding-left: 1em}  
.pass {background-color: lightgreen}  
.fail {background-color: yellow}  
.pass, .fail {font-size: 16pt}  
.header {background-color: white; font-family: Georgia, serif; padding: 0px}  
th.header {text-align: right; font-weight: normal; color: gray}  
.footer {font-size: 10pt}  
</style>  
</head>  
<body>  
GTFS validation results for feed:<br>  
<code><span class="path">%(feed_dir)s</span><b>%(feed_file)s</b></code>  
<br><br>  
<table>  
<tr><th class="header">Agencies:</th><td class="header">%(agencies)s</td></tr>  
<tr><th class="header">Routes:</th><td class="header">%(routes)s</td></tr>  
<tr><th class="header">Stops:</th><td class="header">%(stops)s</td></tr>  
<tr><th class="header">Trips:</th><td class="header">%(trips)s</td></tr>  
<tr><th class="header">Shapes:</th><td class="header">%(shapes)s</td></tr>  
<tr><th class="header">Effective:</th><td class="header">%(dates)s</td></tr>  
</table>  
%(calendar_summary)s  
<br>  
%(problem_summary)s  
<br><br>  
""" % { "feed_file": feed_path[1],  
"feed_dir": feed_path[0],  
"agencies": agencies,  
"routes": len(schedule.GetRouteList()),  
"stops": len(schedule.GetStopList()),  
"trips": len(schedule.GetTripList()),  
"shapes": len(schedule.GetShapeList()),  
"dates": dates,  
"problem_summary": summary,  
"calendar_summary": calendar_summary_html}  
 
# In output_suffix string  
# time.strftime() returns a regular local time string (not a Unicode one) with  
# default system encoding. And decode() will then convert this time string back  
# into a Unicode string. We use decode() here because we don't want the operating  
# system to do any system encoding (which may cause some problem if the string  
# contains some non-English characters) for the string. Therefore we decode it  
# back to its original Unicode code print.  
 
time_unicode = (time.strftime('%B %d, %Y at %I:%M %p %Z').  
decode(sys.getfilesystemencoding()))  
output_suffix = """  
<div class="footer">  
Generated by <a href="http://code.google.com/p/googletransitdatafeed/wiki/FeedValidator">  
FeedValidator</a> version %s on %s.  
</div>  
</body>  
</html>""" % (transitfeed.__version__, time_unicode)  
 
f.write(transitfeed.EncodeUnicode(output_prefix))  
if self.ProblemListMap(TYPE_ERROR):  
f.write('<h3 class="issueHeader">Errors:</h3>')  
self.FormatType(f, "Error",  
self.ProblemListMap(TYPE_ERROR).items())  
if self.ProblemListMap(TYPE_WARNING):  
f.write('<h3 class="issueHeader">Warnings:</h3>')  
self.FormatType(f, "Warning",  
self.ProblemListMap(TYPE_WARNING).items())  
f.write(transitfeed.EncodeUnicode(output_suffix))  
 
 
def RunValidationOutputFromOptions(feed, options):  
"""Validate feed, output results per options and return an exit code."""  
if options.output.upper() == "CONSOLE":  
return RunValidationOutputToConsole(feed, options)  
else:  
return RunValidationOutputToFilename(feed, options, options.output)  
 
 
def RunValidationOutputToFilename(feed, options, output_filename):  
"""Validate feed, save HTML at output_filename and return an exit code."""  
try:  
output_file = open(output_filename, 'w')  
exit_code = RunValidationOutputToFile(feed, options, output_file)  
output_file.close()  
except IOError, e:  
print 'Error while writing %s: %s' % (output_filename, e)  
output_filename = None  
exit_code = 2  
 
if options.manual_entry and output_filename:  
webbrowser.open('file://%s' % os.path.abspath(output_filename))  
 
return exit_code  
 
 
def RunValidationOutputToFile(feed, options, output_file):  
"""Validate feed, write HTML to output_file and return an exit code."""  
problems = HTMLCountingProblemReporter(options.limit_per_type)  
schedule, exit_code, other_problems_string = RunValidation(feed, options,  
problems)  
if isinstance(feed, basestring):  
feed_location = feed  
else:  
feed_location = getattr(feed, 'name', repr(feed))  
problems.WriteOutput(feed_location, output_file, schedule,  
other_problems_string)  
return exit_code  
 
 
def RunValidationOutputToConsole(feed, options):  
"""Validate feed, print reports and return an exit code."""  
problems = CountingConsoleProblemReporter()  
_, exit_code, _ = RunValidation(feed, options, problems)  
return exit_code  
 
 
def RunValidation(feed, options, problems):  
"""Validate feed, returning the loaded Schedule and exit code.  
 
Args:  
feed: GTFS file, either path of the file as a string or a file object  
options: options object returned by optparse  
problems: transitfeed.ProblemReporter instance  
 
Returns:  
a transitfeed.Schedule object, exit code and plain text string of other  
problems  
Exit code is 1 if problems are found and 0 if the Schedule is problem free.  
plain text string is '' if no other problems are found.  
"""  
other_problems_string = CheckVersion(latest_version=options.latest_version)  
print 'validating %s' % feed  
loader = transitfeed.Loader(feed, problems=problems, extra_validation=False,  
memory_db=options.memory_db,  
check_duplicate_trips=\  
options.check_duplicate_trips)  
schedule = loader.Load()  
schedule.Validate(service_gap_interval=options.service_gap_interval)  
 
if feed == 'IWantMyvalidation-crash.txt':  
# See test/testfeedvalidator.py  
raise Exception('For testing the feed validator crash handler.')  
 
if other_problems_string:  
print other_problems_string  
 
if problems.HasIssues():  
print 'ERROR: %s found' % problems.FormatCount()  
return schedule, 1, other_problems_string  
else:  
print 'feed validated successfully'  
return schedule, 0, other_problems_string  
 
 
def CheckVersion(latest_version=''):  
"""  
Check there is newer version of this project.  
 
Codes are based on http://www.voidspace.org.uk/python/articles/urllib2.shtml  
Already got permission from the copyright holder.  
"""  
current_version = transitfeed.__version__  
if not latest_version:  
timeout = 20  
socket.setdefaulttimeout(timeout)  
request = Request(SVN_TAG_URL)  
 
try:  
response = urlopen(request)  
content = response.read()  
versions = re.findall(r'>transitfeed-([\d\.]+)\/<\/a>', content)  
latest_version = MaxVersion(versions)  
 
except HTTPError, e:  
return('The server couldn\'t fulfill the request. Error code: %s.'  
% e.code)  
except URLError, e:  
return('We failed to reach transitfeed server. Reason: %s.' % e.reason)  
 
if not latest_version:  
return('We had trouble parsing the contents of %s.' % SVN_TAG_URL)  
 
newest_version = MaxVersion([latest_version, current_version])  
if current_version != newest_version:  
return('A new version %s of transitfeed is available. Please visit '  
'http://code.google.com/p/googletransitdatafeed and download.'  
% newest_version)  
 
 
def main():  
usage = \  
'''%prog [options] [<input GTFS.zip>]  
 
Validates GTFS file (or directory) <input GTFS.zip> and writes a HTML  
report of the results to validation-results.html.  
 
If <input GTFS.zip> is ommited the filename is read from the console. Dragging  
a file into the console may enter the filename.  
 
For more information see  
http://code.google.com/p/googletransitdatafeed/wiki/FeedValidator  
'''  
 
parser = util.OptionParserLongError(  
usage=usage, version='%prog '+transitfeed.__version__)  
parser.add_option('-n', '--noprompt', action='store_false',  
dest='manual_entry',  
help='do not prompt for feed location or load output in '  
'browser')  
parser.add_option('-o', '--output', dest='output', metavar='FILE',  
help='write html output to FILE or --output=CONSOLE to '  
'print all errors and warnings to the command console')  
parser.add_option('-p', '--performance', action='store_true',  
dest='performance',  
help='output memory and time performance (Availability: '  
'Unix')  
parser.add_option('-m', '--memory_db', dest='memory_db', action='store_true',  
help='Use in-memory sqlite db instead of a temporary file. '  
'It is faster but uses more RAM.')  
parser.add_option('-d', '--duplicate_trip_check',  
dest='check_duplicate_trips', action='store_true',  
help='Check for duplicate trips which go through the same '  
'stops with same service and start times')  
parser.add_option('-l', '--limit_per_type',  
dest='limit_per_type', action='store', type='int',  
help='Maximum number of errors and warnings to keep of '  
'each type')  
parser.add_option('--latest_version', dest='latest_version',  
action='store',  
help='a version number such as 1.2.1 or None to get the '  
'latest version from code.google.com. Output a warning if '  
'transitfeed.py is older than this version.')  
parser.add_option('--service_gap_interval',  
dest='service_gap_interval',  
action='store',  
type='int',  
help='the number of consecutive days to search for with no '  
'scheduled service. For each interval with no service '  
'having this number of days or more a warning will be '  
'issued')  
 
parser.set_defaults(manual_entry=True, output='validation-results.html',  
memory_db=False, check_duplicate_trips=False,  
limit_per_type=5, latest_version='',  
service_gap_interval=13)  
(options, args) = parser.parse_args()  
 
if not len(args) == 1:  
if options.manual_entry:  
feed = raw_input('Enter Feed Location: ')  
else:  
parser.error('You must provide the path of a single feed')  
else:  
feed = args[0]  
 
feed = feed.strip('"')  
 
if options.performance:  
return ProfileRunValidationOutputFromOptions(feed, options)  
else:  
return RunValidationOutputFromOptions(feed, options)  
 
 
def ProfileRunValidationOutputFromOptions(feed, options):  
"""Run RunValidationOutputFromOptions, print profile and return exit code."""  
import cProfile  
import pstats  
# runctx will modify a dict, but not locals(). We need a way to get rv back.  
locals_for_exec = locals()  
cProfile.runctx('rv = RunValidationOutputFromOptions(feed, options)',  
globals(), locals_for_exec, 'validate-stats')  
 
# Only available on Unix, http://docs.python.org/lib/module-resource.html  
import resource  
print "Time: %d seconds" % (  
resource.getrusage(resource.RUSAGE_SELF).ru_utime +  
resource.getrusage(resource.RUSAGE_SELF).ru_stime)  
 
# http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/286222  
# http://aspn.activestate.com/ASPN/Cookbook/ "The recipes are freely  
# available for review and use."  
def _VmB(VmKey):  
"""Return size from proc status in bytes."""  
_proc_status = '/proc/%d/status' % os.getpid()  
_scale = {'kB': 1024.0, 'mB': 1024.0*1024.0,  
'KB': 1024.0, 'MB': 1024.0*1024.0}  
 
# get pseudo file /proc/<pid>/status  
try:  
t = open(_proc_status)  
v = t.read()  
t.close()  
except:  
raise Exception("no proc file %s" % _proc_status)  
return 0 # non-Linux?  
# get VmKey line e.g. 'VmRSS: 9999 kB\n ...'  
i = v.index(VmKey)  
v = v[i:].split(None, 3) # whitespace  
if len(v) < 3:  
raise Exception("%s" % v)  
return 0 # invalid format?  
# convert Vm value to bytes  
return int(float(v[1]) * _scale[v[2]])  
 
# I ran this on over a hundred GTFS files, comparing VmSize to VmRSS  
# (resident set size). The difference was always under 2% or 3MB.  
print "Virtual Memory Size: %d bytes" % _VmB('VmSize:')  
 
# Output report of where CPU time was spent.  
p = pstats.Stats('validate-stats')  
p.strip_dirs()  
p.sort_stats('cumulative').print_stats(30)  
p.sort_stats('cumulative').print_callers(30)  
return locals_for_exec['rv']  
 
 
if __name__ == '__main__':  
util.RunWithCrashHandler(main)  
 
#!/usr/bin/python  
 
# Copyright (C) 2007 Google Inc.  
#  
# Licensed under the Apache License, Version 2.0 (the "License");  
# you may not use this file except in compliance with the License.  
# You may obtain a copy of the License at  
#  
# http://www.apache.org/licenses/LICENSE-2.0  
#  
# Unless required by applicable law or agreed to in writing, software  
# distributed under the License is distributed on an "AS IS" BASIS,  
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.  
# See the License for the specific language governing permissions and  
# limitations under the License.  
 
"""  
This package provides implementation of a converter from a kml  
file format into Google transit feed format.  
 
The KmlParser class is the main class implementing the parser.  
 
Currently only information about stops is extracted from a kml file.  
The extractor expects the stops to be represented as placemarks with  
a single point.  
"""  
 
import re  
import string  
import sys  
import transitfeed  
from transitfeed import util  
import xml.dom.minidom as minidom  
import zipfile  
 
 
class Placemark(object):  
def __init__(self):  
self.name = ""  
self.coordinates = []  
 
def IsPoint(self):  
return len(self.coordinates) == 1  
 
def IsLine(self):  
return len(self.coordinates) > 1  
 
class KmlParser(object):  
def __init__(self, stopNameRe = '(.*)'):  
"""  
Args:  
stopNameRe - a regular expression to extract a stop name from a  
placemaker name  
"""  
self.stopNameRe = re.compile(stopNameRe)  
 
def Parse(self, filename, feed):  
"""  
Reads the kml file, parses it and updated the Google transit feed  
object with the extracted information.  
 
Args:  
filename - kml file name  
feed - an instance of Schedule class to be updated  
"""  
dom = minidom.parse(filename)  
self.ParseDom(dom, feed)  
 
def ParseDom(self, dom, feed):  
"""  
Parses the given kml dom tree and updates the Google transit feed object.  
 
Args:  
dom - kml dom tree  
feed - an instance of Schedule class to be updated  
"""  
shape_num = 0  
for node in dom.getElementsByTagName('Placemark'):  
p = self.ParsePlacemark(node)  
if p.IsPoint():  
(lon, lat) = p.coordinates[0]  
m = self.stopNameRe.search(p.name)  
feed.AddStop(lat, lon, m.group(1))  
elif p.IsLine():  
shape_num = shape_num + 1  
shape = transitfeed.Shape("kml_shape_" + str(shape_num))  
for (lon, lat) in p.coordinates:  
shape.AddPoint(lat, lon)  
feed.AddShapeObject(shape)  
 
def ParsePlacemark(self, node):  
ret = Placemark()  
for child in node.childNodes:  
if child.nodeName == 'name':  
ret.name = self.ExtractText(child)  
if child.nodeName == 'Point' or child.nodeName == 'LineString':  
ret.coordinates = self.ExtractCoordinates(child)  
return ret  
 
def ExtractText(self, node):  
for child in node.childNodes:  
if child.nodeType == child.TEXT_NODE:  
return child.wholeText # is a unicode string  
return ""  
 
def ExtractCoordinates(self, node):  
coordinatesText = ""  
for child in node.childNodes:  
if child.nodeName == 'coordinates':  
coordinatesText = self.ExtractText(child)  
break  
ret = []  
for point in coordinatesText.split():  
coords = point.split(',')  
ret.append((float(coords[0]), float(coords[1])))  
return ret  
 
 
def main():  
usage = \  
"""%prog <input.kml> <output GTFS.zip>  
 
Reads KML file <input.kml> and creates GTFS file <output GTFS.zip> with  
placemarks in the KML represented as stops.  
"""  
 
parser = util.OptionParserLongError(  
usage=usage, version='%prog '+transitfeed.__version__)  
(options, args) = parser.parse_args()  
if len(args) != 2:  
parser.error('You did not provide all required command line arguments.')  
 
if args[0] == 'IWantMyCrash':  
raise Exception('For testCrashHandler')  
 
parser = KmlParser()  
feed = transitfeed.Schedule()  
feed.save_all_stops = True  
parser.Parse(args[0], feed)  
feed.WriteGoogleTransitFeed(args[1])  
 
print "Done."  
 
 
if __name__ == '__main__':  
util.RunWithCrashHandler(main)  
 
#!/usr/bin/python  
#  
# Copyright 2008 Google Inc. All Rights Reserved.  
#  
# Licensed under the Apache License, Version 2.0 (the "License");  
# you may not use this file except in compliance with the License.  
# You may obtain a copy of the License at  
#  
# http://www.apache.org/licenses/LICENSE-2.0  
#  
# Unless required by applicable law or agreed to in writing, software  
# distributed under the License is distributed on an "AS IS" BASIS,  
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.  
# See the License for the specific language governing permissions and  
# limitations under the License.  
 
"""A module for writing GTFS feeds out into Google Earth KML format.  
 
For usage information run kmlwriter.py --help  
 
If no output filename is specified, the output file will be given the same  
name as the feed file (with ".kml" appended) and will be placed in the same  
directory as the input feed.  
 
The resulting KML file has a folder hierarchy which looks like this:  
 
- Stops  
* stop1  
* stop2  
- Routes  
- route1  
- Shapes  
* shape1  
* shape2  
- Patterns  
- pattern1  
- pattern2  
- Trips  
* trip1  
* trip2  
- Shapes  
* shape1  
- Shape Points  
* shape_point1  
* shape_point2  
* shape2  
- Shape Points  
* shape_point1  
* shape_point2  
 
where the hyphens represent folders and the asteriks represent placemarks.  
 
In a trip, a vehicle visits stops in a certain sequence. Such a sequence of  
stops is called a pattern. A pattern is represented by a linestring connecting  
the stops. The "Shapes" subfolder of a route folder contains placemarks for  
each shape used by a trip in the route. The "Patterns" subfolder contains a  
placemark for each unique pattern used by a trip in the route. The "Trips"  
subfolder contains a placemark for each trip in the route.  
 
Since there can be many trips and trips for the same route are usually similar,  
they are not exported unless the --showtrips option is used. There is also  
another option --splitroutes that groups the routes by vehicle type resulting  
in a folder hierarchy which looks like this at the top level:  
 
- Stops  
- Routes - Bus  
- Routes - Tram  
- Routes - Rail  
- Shapes  
"""  
 
try:  
import xml.etree.ElementTree as ET # python 2.5  
except ImportError, e:  
import elementtree.ElementTree as ET # older pythons  
import optparse  
import os.path  
import sys  
import transitfeed  
from transitfeed import util  
 
 
class KMLWriter(object):  
"""This class knows how to write out a transit feed as KML.  
 
Sample usage:  
KMLWriter().Write(<transitfeed.Schedule object>, <output filename>)  
 
Attributes:  
show_trips: True if the individual trips should be included in the routes.  
show_trips: True if the individual trips should be placed on ground.  
split_routes: True if the routes should be split by type.  
shape_points: True if individual shape points should be plotted.  
"""  
 
def __init__(self):  
"""Initialise."""  
self.show_trips = False  
self.split_routes = False  
self.shape_points = False  
self.altitude_per_sec = 0.0  
self.date_filter = None  
 
def _SetIndentation(self, elem, level=0):  
"""Indented the ElementTree DOM.  
 
This is the recommended way to cause an ElementTree DOM to be  
prettyprinted on output, as per: http://effbot.org/zone/element-lib.htm  
 
Run this on the root element before outputting the tree.  
 
Args:  
elem: The element to start indenting from, usually the document root.  
level: Current indentation level for recursion.  
"""  
i = "\n" + level*" "  
if len(elem):  
if not elem.text or not elem.text.strip():  
elem.text = i + " "  
for elem in elem:  
self._SetIndentation(elem, level+1)  
if not elem.tail or not elem.tail.strip():  
elem.tail = i  
else:  
if level and (not elem.tail or not elem.tail.strip()):  
elem.tail = i  
 
def _CreateFolder(self, parent, name, visible=True, description=None):  
"""Create a KML Folder element.  
 
Args:  
parent: The parent ElementTree.Element instance.  
name: The folder name as a string.  
visible: Whether the folder is initially visible or not.  
description: A description string or None.  
 
Returns:  
The folder ElementTree.Element instance.  
"""  
folder = ET.SubElement(parent, 'Folder')  
name_tag = ET.SubElement(folder, 'name')  
name_tag.text = name  
if description is not None:  
desc_tag = ET.SubElement(folder, 'description')  
desc_tag.text = description  
if not visible:  
visibility = ET.SubElement(folder, 'visibility')  
visibility.text = '0'  
return folder  
 
def _CreateStyleForRoute(self, doc, route):  
"""Create a KML Style element for the route.  
 
The style sets the line colour if the route colour is specified. The  
line thickness is set depending on the vehicle type.  
 
Args:  
doc: The KML Document ElementTree.Element instance.  
route: The transitfeed.Route to create the style for.  
 
Returns:  
The id of the style as a string.  
"""  
style_id = 'route_%s' % route.route_id  
style = ET.SubElement(doc, 'Style', {'id': style_id})  
linestyle = ET.SubElement(style, 'LineStyle')  
width = ET.SubElement(linestyle, 'width')  
type_to_width = {0: '3', # Tram  
1: '3', # Subway  
2: '5', # Rail  
3: '1'} # Bus  
width.text = type_to_width.get(route.route_type, '1')  
if route.route_color:  
color = ET.SubElement(linestyle, 'color')  
red = route.route_color[0:2].lower()  
green = route.route_color[2:4].lower()  
blue = route.route_color[4:6].lower()  
color.text = 'ff%s%s%s' % (blue, green, red)  
return style_id  
 
def _CreatePlacemark(self, parent, name, style_id=None, visible=True,  
description=None):  
"""Create a KML Placemark element.  
 
Args:  
parent: The parent ElementTree.Element instance.  
name: The placemark name as a string.  
style_id: If not None, the id of a style to use for the placemark.  
visible: Whether the placemark is initially visible or not.  
description: A description string or None.  
 
Returns:  
The placemark ElementTree.Element instance.  
"""  
placemark = ET.SubElement(parent, 'Placemark')  
placemark_name = ET.SubElement(placemark, 'name')  
placemark_name.text = name  
if description is not None:  
desc_tag = ET.SubElement(placemark, 'description')  
desc_tag.text = description  
if style_id is not None:  
styleurl = ET.SubElement(placemark, 'styleUrl')  
styleurl.text = '#%s' % style_id  
if not visible:  
visibility = ET.SubElement(placemark, 'visibility')  
visibility.text = '0'  
return placemark  
 
def _CreateLineString(self, parent, coordinate_list):  
"""Create a KML LineString element.  
 
The points of the string are given in coordinate_list. Every element of  
coordinate_list should be one of a tuple (longitude, latitude) or a tuple  
(longitude, latitude, altitude).  
 
Args:  
parent: The parent ElementTree.Element instance.  
coordinate_list: The list of coordinates.  
 
Returns:  
The LineString ElementTree.Element instance or None if coordinate_list is  
empty.  
"""  
if not coordinate_list:  
return None  
linestring = ET.SubElement(parent, 'LineString')  
tessellate = ET.SubElement(linestring, 'tessellate')  
tessellate.text = '1'  
if len(coordinate_list[0]) == 3:  
altitude_mode = ET.SubElement(linestring, 'altitudeMode')  
altitude_mode.text = 'absolute'  
coordinates = ET.SubElement(linestring, 'coordinates')  
if len(coordinate_list[0]) == 3:  
coordinate_str_list = ['%f,%f,%f' % t for t in coordinate_list]  
else:  
coordinate_str_list = ['%f,%f' % t for t in coordinate_list]  
coordinates.text = ' '.join(coordinate_str_list)  
return linestring  
 
def _CreateLineStringForShape(self, parent, shape):  
"""Create a KML LineString using coordinates from a shape.  
 
Args:  
parent: The parent ElementTree.Element instance.  
shape: The transitfeed.Shape instance.  
 
Returns:  
The LineString ElementTree.Element instance or None if coordinate_list is  
empty.  
"""  
coordinate_list = [(longitude, latitude) for  
(latitude, longitude, distance) in shape.points]  
return self._CreateLineString(parent, coordinate_list)  
 
def _CreateStopsFolder(self, schedule, doc):  
"""Create a KML Folder containing placemarks for each stop in the schedule.  
 
If there are no stops in the schedule then no folder is created.  
 
Args:  
schedule: The transitfeed.Schedule instance.  
doc: The KML Document ElementTree.Element instance.  
 
Returns:  
The Folder ElementTree.Element instance or None if there are no stops.  
"""  
if not schedule.GetStopList():  
return None  
stop_folder = self._CreateFolder(doc, 'Stops')  
stops = list(schedule.GetStopList())  
stops.sort(key=lambda x: x.stop_name)  
for stop in stops:  
desc_items = []  
if stop.stop_desc:  
desc_items.append(stop.stop_desc)  
if stop.stop_url:  
desc_items.append('Stop info page: <a href="%s">%s</a>' % (  
stop.stop_url, stop.stop_url))  
description = '<br/>'.join(desc_items) or None  
placemark = self._CreatePlacemark(stop_folder, stop.stop_name,  
description=description)  
point = ET.SubElement(placemark, 'Point')  
coordinates = ET.SubElement(point, 'coordinates')  
coordinates.text = '%.6f,%.6f' % (stop.stop_lon, stop.stop_lat)  
return stop_folder  
 
def _CreateRoutePatternsFolder(self, parent, route,  
style_id=None, visible=True):  
"""Create a KML Folder containing placemarks for each pattern in the route.  
 
A pattern is a sequence of stops used by one of the trips in the route.  
 
If there are not patterns for the route then no folder is created and None  
is returned.  
 
Args:  
parent: The parent ElementTree.Element instance.  
route: The transitfeed.Route instance.  
style_id: The id of a style to use if not None.  
visible: Whether the folder is initially visible or not.  
 
Returns:  
The Folder ElementTree.Element instance or None if there are no patterns.  
"""  
pattern_id_to_trips = route.GetPatternIdTripDict()  
if not pattern_id_to_trips:  
return None  
 
# sort by number of trips using the pattern  
pattern_trips = pattern_id_to_trips.values()  
pattern_trips.sort(lambda a, b: cmp(len(b), len(a)))  
 
folder = self._CreateFolder(parent, 'Patterns', visible)  
for n, trips in enumerate(pattern_trips):  
trip_ids = [trip.trip_id for trip in trips]  
name = 'Pattern %d (trips: %d)' % (n+1, len(trips))  
description = 'Trips using this pattern (%d in total): %s' % (  
len(trips), ', '.join(trip_ids))  
placemark = self._CreatePlacemark(folder, name, style_id, visible,  
description)  
coordinates = [(stop.stop_lon, stop.stop_lat)  
for stop in trips[0].GetPattern()]  
self._CreateLineString(placemark, coordinates)  
return folder  
 
def _CreateRouteShapesFolder(self, schedule, parent, route,  
style_id=None, visible=True):  
"""Create a KML Folder for the shapes of a route.  
 
The folder contains a placemark for each shape referenced by a trip in the  
route. If there are no such shapes, no folder is created and None is  
returned.  
 
Args:  
schedule: The transitfeed.Schedule instance.  
parent: The parent ElementTree.Element instance.  
route: The transitfeed.Route instance.  
style_id: The id of a style to use if not None.  
visible: Whether the placemark is initially visible or not.  
 
Returns:  
The Folder ElementTree.Element instance or None.  
"""  
shape_id_to_trips = {}  
for trip in route.trips:  
if trip.shape_id:  
shape_id_to_trips.setdefault(trip.shape_id, []).append(trip)  
if not shape_id_to_trips:  
return None  
 
# sort by the number of trips using the shape  
shape_id_to_trips_items = shape_id_to_trips.items()  
shape_id_to_trips_items.sort(lambda a, b: cmp(len(b[1]), len(a[1])))  
 
folder = self._CreateFolder(parent, 'Shapes', visible)  
for shape_id, trips in shape_id_to_trips_items:  
trip_ids = [trip.trip_id for trip in trips]  
name = '%s (trips: %d)' % (shape_id, len(trips))  
description = 'Trips using this shape (%d in total): %s' % (  
len(trips), ', '.join(trip_ids))  
placemark = self._CreatePlacemark(folder, name, style_id, visible,  
description)  
self._CreateLineStringForShape(placemark, schedule.GetShape(shape_id))  
return folder  
 
def _CreateRouteTripsFolder(self, parent, route, style_id=None, schedule=None):  
"""Create a KML Folder containing all the trips in the route.  
 
The folder contains a placemark for each of these trips. If there are no  
trips in the route, no folder is created and None is returned.  
 
Args:  
parent: The parent ElementTree.Element instance.  
route: The transitfeed.Route instance.  
style_id: A style id string for the placemarks or None.  
 
Returns:  
The Folder ElementTree.Element instance or None.  
"""  
if not route.trips:  
return None  
trips = list(route.trips)  
trips.sort(key=lambda x: x.trip_id)  
trips_folder = self._CreateFolder(parent, 'Trips', visible=False)  
for trip in trips:  
if (self.date_filter and  
not trip.service_period.IsActiveOn(self.date_filter)):  
continue  
 
if trip.trip_headsign:  
description = 'Headsign: %s' % trip.trip_headsign  
else:  
description = None  
 
coordinate_list = []  
for secs, stoptime, tp in trip.GetTimeInterpolatedStops():  
if self.altitude_per_sec > 0:  
coordinate_list.append((stoptime.stop.stop_lon, stoptime.stop.stop_lat,  
(secs - 3600 * 4) * self.altitude_per_sec))  
else:  
coordinate_list.append((stoptime.stop.stop_lon,  
stoptime.stop.stop_lat))  
placemark = self._CreatePlacemark(trips_folder,  
trip.trip_id,  
style_id=style_id,  
visible=False,  
description=description)  
self._CreateLineString(placemark, coordinate_list)  
return trips_folder  
 
def _CreateRoutesFolder(self, schedule, doc, route_type=None):  
"""Create a KML Folder containing routes in a schedule.  
 
The folder contains a subfolder for each route in the schedule of type  
route_type. If route_type is None, then all routes are selected. Each  
subfolder contains a flattened graph placemark, a route shapes placemark  
and, if show_trips is True, a subfolder containing placemarks for each of  
the trips in the route.  
 
If there are no routes in the schedule then no folder is created and None  
is returned.  
 
Args:  
schedule: The transitfeed.Schedule instance.  
doc: The KML Document ElementTree.Element instance.  
route_type: The route type integer or None.  
 
Returns:  
The Folder ElementTree.Element instance or None.  
"""  
 
def GetRouteName(route):  
"""Return a placemark name for the route.  
 
Args:  
route: The transitfeed.Route instance.  
 
Returns:  
The name as a string.  
"""  
name_parts = []  
if route.route_short_name:  
name_parts.append('<b>%s</b>' % route.route_short_name)  
if route.route_long_name:  
name_parts.append(route.route_long_name)  
return ' - '.join(name_parts) or route.route_id  
 
def GetRouteDescription(route):  
"""Return a placemark description for the route.  
 
Args:  
route: The transitfeed.Route instance.  
 
Returns:  
The description as a string.  
"""  
desc_items = []  
if route.route_desc:  
desc_items.append(route.route_desc)  
if route.route_url:  
desc_items.append('Route info page: <a href="%s">%s</a>' % (  
route.route_url, route.route_url))  
description = '<br/>'.join(desc_items)  
return description or None  
 
routes = [route for route in schedule.GetRouteList()  
if route_type is None or route.route_type == route_type]  
if not routes:  
return None  
routes.sort(key=lambda x: GetRouteName(x))  
 
if route_type is not None:  
route_type_names = {0: 'Tram, Streetcar or Light rail',  
1: 'Subway or Metro',  
2: 'Rail',  
3: 'Bus',  
4: 'Ferry',  
5: 'Cable car',  
6: 'Gondola or suspended cable car',  
7: 'Funicular'}  
type_name = route_type_names.get(route_type, str(route_type))  
folder_name = 'Routes - %s' % type_name  
else:  
folder_name = 'Routes'  
routes_folder = self._CreateFolder(doc, folder_name, visible=False)  
 
for route in routes:  
style_id = self._CreateStyleForRoute(doc, route)  
route_folder = self._CreateFolder(routes_folder,  
GetRouteName(route),  
description=GetRouteDescription(route))  
self._CreateRouteShapesFolder(schedule, route_folder, route,  
style_id, False)  
self._CreateRoutePatternsFolder(route_folder, route, style_id, False)  
if self.show_trips:  
self._CreateRouteTripsFolder(route_folder, route, style_id, schedule)  
return routes_folder  
 
def _CreateShapesFolder(self, schedule, doc):  
"""Create a KML Folder containing all the shapes in a schedule.  
 
The folder contains a placemark for each shape. If there are no shapes in  
the schedule then the folder is not created and None is returned.  
 
Args:  
schedule: The transitfeed.Schedule instance.  
doc: The KML Document ElementTree.Element instance.  
 
Returns:  
The Folder ElementTree.Element instance or None.  
"""  
if not schedule.GetShapeList():  
return None  
shapes_folder = self._CreateFolder(doc, 'Shapes')  
shapes = list(schedule.GetShapeList())  
shapes.sort(key=lambda x: x.shape_id)  
for shape in shapes:  
placemark = self._CreatePlacemark(shapes_folder, shape.shape_id)  
self._CreateLineStringForShape(placemark, shape)  
if self.shape_points:  
self._CreateShapePointFolder(shapes_folder, shape)  
return shapes_folder  
 
def _CreateShapePointFolder(self, shapes_folder, shape):  
"""Create a KML Folder containing all the shape points in a shape.  
 
The folder contains placemarks for each shapepoint.  
 
Args:  
shapes_folder: A KML Shape Folder ElementTree.Element instance  
shape: The shape to plot.  
 
Returns:  
The Folder ElementTree.Element instance or None.  
"""  
 
folder_name = shape.shape_id + ' Shape Points'  
folder = self._CreateFolder(shapes_folder, folder_name, visible=False)  
for (index, (lat, lon, dist)) in enumerate(shape.points):  
placemark = self._CreatePlacemark(folder, str(index+1))  
point = ET.SubElement(placemark, 'Point')  
coordinates = ET.SubElement(point, 'coordinates')  
coordinates.text = '%.6f,%.6f' % (lon, lat)  
return folder  
 
def Write(self, schedule, output_file):  
"""Writes out a feed as KML.  
 
Args:  
schedule: A transitfeed.Schedule object containing the feed to write.  
output_file: The name of the output KML file, or file object to use.  
"""  
# Generate the DOM to write  
root = ET.Element('kml')  
root.attrib['xmlns'] = 'http://earth.google.com/kml/2.1'  
doc = ET.SubElement(root, 'Document')  
open_tag = ET.SubElement(doc, 'open')  
open_tag.text = '1'  
self._CreateStopsFolder(schedule, doc)  
if self.split_routes:  
route_types = set()  
for route in schedule.GetRouteList():  
route_types.add(route.route_type)  
route_types = list(route_types)  
route_types.sort()  
for route_type in route_types:  
self._CreateRoutesFolder(schedule, doc, route_type)  
else:  
self._CreateRoutesFolder(schedule, doc)  
self._CreateShapesFolder(schedule, doc)  
 
# Make sure we pretty-print  
self._SetIndentation(root)  
 
# Now write the output  
if isinstance(output_file, file):  
output = output_file  
else:  
output = open(output_file, 'w')  
output.write("""<?xml version="1.0" encoding="UTF-8"?>\n""")  
ET.ElementTree(root).write(output, 'utf-8')  
 
 
def main():  
usage = \  
'''%prog [options] <input GTFS.zip> [<output.kml>]  
 
Reads GTFS file or directory <input GTFS.zip> and creates a KML file  
<output.kml> that contains the geographical features of the input. If  
<output.kml> is omitted a default filename is picked based on  
<input GTFS.zip>. By default the KML contains all stops and shapes.  
'''  
 
parser = util.OptionParserLongError(  
usage=usage, version='%prog '+transitfeed.__version__)  
parser.add_option('-t', '--showtrips', action='store_true',  
dest='show_trips',  
help='include the individual trips for each route')  
parser.add_option('-a', '--altitude_per_sec', action='store', type='float',  
dest='altitude_per_sec',  
help='if greater than 0 trips are drawn with time axis '  
'set to this many meters high for each second of time')  
parser.add_option('-s', '--splitroutes', action='store_true',  
dest='split_routes',  
help='split the routes by type')  
parser.add_option('-d', '--date_filter', action='store', type='string',  
dest='date_filter',  
help='Restrict to trips active on date YYYYMMDD')  
parser.add_option('-p', '--display_shape_points', action='store_true',  
dest='shape_points',  
help='shows the actual points along shapes')  
 
parser.set_defaults(altitude_per_sec=1.0)  
options, args = parser.parse_args()  
 
if len(args) < 1:  
parser.error('You must provide the path of an input GTFS file.')  
 
if args[0] == 'IWantMyCrash':  
raise Exception('For testCrashHandler')  
 
input_path = args[0]  
if len(args) >= 2:  
output_path = args[1]  
else:  
path = os.path.normpath(input_path)  
(feed_dir, feed) = os.path.split(path)  
if '.' in feed:  
feed = feed.rsplit('.', 1)[0] # strip extension  
output_filename = '%s.kml' % feed  
output_path = os.path.join(feed_dir, output_filename)  
 
loader = transitfeed.Loader(input_path,  
problems=transitfeed.ProblemReporter())  
feed = loader.Load()  
print "Writing %s" % output_path  
writer = KMLWriter()  
writer.show_trips = options.show_trips  
writer.altitude_per_sec = options.altitude_per_sec  
writer.split_routes = options.split_routes  
writer.date_filter = options.date_filter  
writer.shape_points = options.shape_points  
writer.Write(feed, output_path)  
 
 
if __name__ == '__main__':  
util.RunWithCrashHandler(main)  
 
#!/usr/bin/python  
#  
# Copyright 2007 Google Inc. All Rights Reserved.  
#  
# Licensed under the Apache License, Version 2.0 (the "License");  
# you may not use this file except in compliance with the License.  
# You may obtain a copy of the License at  
#  
# http://www.apache.org/licenses/LICENSE-2.0  
#  
# Unless required by applicable law or agreed to in writing, software  
# distributed under the License is distributed on an "AS IS" BASIS,  
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.  
# See the License for the specific language governing permissions and  
# limitations under the License.  
 
"""A tool for merging two Google Transit feeds.  
 
Given two Google Transit feeds intending to cover two disjoint calendar  
intervals, this tool will attempt to produce a single feed by merging as much  
of the two feeds together as possible.  
 
For example, most stops remain the same throughout the year. Therefore, many  
of the stops given in stops.txt for the first feed represent the same stops  
given in the second feed. This tool will try to merge these stops so they  
only appear once in the resultant feed.  
 
A note on terminology: The first schedule is referred to as the "old" schedule;  
the second as the "new" schedule. The resultant schedule is referred to as  
the "merged" schedule. Names of things in the old schedule are variations of  
the letter "a" while names of things from the new schedule are variations of  
"b". The objects that represents routes, agencies and so on are called  
"entities".  
 
usage: merge.py [options] old_feed_path new_feed_path merged_feed_path  
 
Run merge.py --help for a list of the possible options.  
"""  
 
 
__author__ = 'timothy.stranex@gmail.com (Timothy Stranex)'  
 
 
import datetime  
import optparse  
import os  
import re  
import sys  
import time  
import transitfeed  
from transitfeed import util  
import webbrowser  
 
 
# TODO:  
# 1. write unit tests that use actual data  
# 2. write a proper trip and stop_times merger  
# 3. add a serialised access method for stop_times and shapes to transitfeed  
# 4. add support for merging schedules which have some service period overlap  
 
 
def ApproximateDistanceBetweenPoints(pa, pb):  
"""Finds the distance between two points on the Earth's surface.  
 
This is an approximate distance based on assuming that the Earth is a sphere.  
The points are specified by their lattitude and longitude.  
 
Args:  
pa: the first (lat, lon) point tuple  
pb: the second (lat, lon) point tuple  
 
Returns:  
The distance as a float in metres.  
"""  
alat, alon = pa  
blat, blon = pb  
sa = transitfeed.Stop(lat=alat, lng=alon)  
sb = transitfeed.Stop(lat=blat, lng=blon)  
return transitfeed.ApproximateDistanceBetweenStops(sa, sb)  
 
 
class Error(Exception):  
"""The base exception class for this module."""  
 
 
class MergeError(Error):  
"""An error produced when two entities could not be merged."""  
 
 
class MergeProblemWithContext(transitfeed.ExceptionWithContext):  
"""The base exception class for problem reporting in the merge module.  
 
Attributes:  
dataset_merger: The DataSetMerger that generated this problem.  
entity_type_name: The entity type of the dataset_merger. This is just  
dataset_merger.ENTITY_TYPE_NAME.  
ERROR_TEXT: The text used for generating the problem message.  
"""  
 
def __init__(self, dataset_merger, problem_type=transitfeed.TYPE_WARNING,  
**kwargs):  
"""Initialise the exception object.  
 
Args:  
dataset_merger: The DataSetMerger instance that generated this problem.  
problem_type: The problem severity. This should be set to one of the  
corresponding constants in transitfeed.  
kwargs: Keyword arguments to be saved as instance attributes.  
"""  
kwargs['type'] = problem_type  
kwargs['entity_type_name'] = dataset_merger.ENTITY_TYPE_NAME  
transitfeed.ExceptionWithContext.__init__(self, None, None, **kwargs)  
self.dataset_merger = dataset_merger  
 
def FormatContext(self):  
return "In files '%s'" % self.dataset_merger.FILE_NAME  
 
 
class SameIdButNotMerged(MergeProblemWithContext):  
ERROR_TEXT = ("There is a %(entity_type_name)s in the old feed with id "  
"'%(id)s' and one from the new feed with the same id but "  
"they could not be merged:")  
 
 
class CalendarsNotDisjoint(MergeProblemWithContext):  
ERROR_TEXT = ("The service periods could not be merged since they are not "  
"disjoint.")  
 
 
class MergeNotImplemented(MergeProblemWithContext):  
ERROR_TEXT = ("The feed merger does not currently support merging in this "  
"file. The entries have been duplicated instead.")  
 
 
class FareRulesBroken(MergeProblemWithContext):  
ERROR_TEXT = ("The feed merger is currently unable to handle fare rules "  
"properly.")  
 
 
class MergeProblemReporterBase(transitfeed.ProblemReporterBase):  
"""The base problem reporter class for the merge module."""  
 
def SameIdButNotMerged(self, dataset, entity_id, reason):  
self._Report(SameIdButNotMerged(dataset, id=entity_id, reason=reason))  
 
def CalendarsNotDisjoint(self, dataset):  
self._Report(CalendarsNotDisjoint(dataset,  
problem_type=transitfeed.TYPE_ERROR))  
 
def MergeNotImplemented(self, dataset):  
self._Report(MergeNotImplemented(dataset))  
 
def FareRulesBroken(self, dataset):  
self._Report(FareRulesBroken(dataset))  
 
 
class ExceptionProblemReporter(MergeProblemReporterBase):  
"""A problem reporter that reports errors by raising exceptions."""  
 
def __init__(self, raise_warnings=False):  
"""Initialise.  
 
Args:  
raise_warnings: If this is True then warnings are also raised as  
exceptions.  
"""  
MergeProblemReporterBase.__init__(self)  
self._raise_warnings = raise_warnings  
 
def _Report(self, merge_problem):  
if self._raise_warnings or merge_problem.IsError():  
raise merge_problem  
 
 
class HTMLProblemReporter(MergeProblemReporterBase):  
"""A problem reporter which generates HTML output."""  
 
def __init__(self):  
"""Initialise."""  
MergeProblemReporterBase.__init__(self)  
self._dataset_warnings = {} # a map from DataSetMergers to their warnings  
self._dataset_errors = {}  
self._warning_count = 0  
self._error_count = 0  
 
def _Report(self, merge_problem):  
if merge_problem.IsWarning():  
dataset_problems = self._dataset_warnings  
self._warning_count += 1  
else:  
dataset_problems = self._dataset_errors  
self._error_count += 1  
 
problem_html = '<li>%s</li>' % (  
merge_problem.FormatProblem().replace('\n', '<br>'))  
dataset_problems.setdefault(merge_problem.dataset_merger, []).append(  
problem_html)  
 
def _GenerateStatsTable(self, feed_merger):  
"""Generate an HTML table of merge statistics.  
 
Args:  
feed_merger: The FeedMerger instance.  
 
Returns:  
The generated HTML as a string.  
"""  
rows = []  
rows.append('<tr><th class="header"/><th class="header">Merged</th>'  
'<th class="header">Copied from old feed</th>'  
'<th class="header">Copied from new feed</th></tr>')  
for merger in feed_merger.GetMergerList():  
stats = merger.GetMergeStats()  
if stats is None:  
continue  
merged, not_merged_a, not_merged_b = stats  
rows.append('<tr><th class="header">%s</th>'  
'<td class="header">%d</td>'  
'<td class="header">%d</td>'  
'<td class="header">%d</td></tr>' %  
(merger.DATASET_NAME, merged, not_merged_a, not_merged_b))  
return '<table>%s</table>' % '\n'.join(rows)  
 
def _GenerateSection(self, problem_type):  
"""Generate a listing of the given type of problems.  
 
Args:  
problem_type: The type of problem. This is one of the problem type  
constants from transitfeed.  
 
Returns:  
The generated HTML as a string.  
"""  
if problem_type == transitfeed.TYPE_WARNING:  
dataset_problems = self._dataset_warnings  
heading = 'Warnings'  
else:  
dataset_problems = self._dataset_errors  
heading = 'Errors'  
 
if not dataset_problems:  
return ''  
 
prefix = '<h2 class="issueHeader">%s:</h2>' % heading  
dataset_sections = []  
for dataset_merger, problems in dataset_problems.items():  
dataset_sections.append('<h3>%s</h3><ol>%s</ol>' % (  
dataset_merger.FILE_NAME, '\n'.join(problems)))  
body = '\n'.join(dataset_sections)  
return prefix + body  
 
def _GenerateSummary(self):  
"""Generate a summary of the warnings and errors.  
 
Returns:  
The generated HTML as a string.  
"""  
items = []  
if self._dataset_errors:  
items.append('errors: %d' % self._error_count)  
if self._dataset_warnings:  
items.append('warnings: %d' % self._warning_count)  
 
if items:  
return '<p><span class="fail">%s</span></p>' % '<br>'.join(items)  
else:  
return '<p><span class="pass">feeds merged successfully</span></p>'  
 
def WriteOutput(self, output_file, feed_merger,  
old_feed_path, new_feed_path, merged_feed_path):  
"""Write the HTML output to a file.  
 
Args:  
output_file: The file object that the HTML output will be written to.  
feed_merger: The FeedMerger instance.  
old_feed_path: The path to the old feed file as a string.  
new_feed_path: The path to the new feed file as a string  
merged_feed_path: The path to the merged feed file as a string. This  
may be None if no merged feed was written.  
"""  
if merged_feed_path is None:  
html_merged_feed_path = ''  
else:  
html_merged_feed_path = '<p>Merged feed created: <code>%s</code></p>' % (  
merged_feed_path)  
 
html_header = """<html>  
<head>  
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/>  
<title>Feed Merger Results</title>  
<style>  
body {font-family: Georgia, serif; background-color: white}  
.path {color: gray}  
div.problem {max-width: 500px}  
td,th {background-color: khaki; padding: 2px; font-family:monospace}  
td.problem,th.problem {background-color: dc143c; color: white; padding: 2px;  
font-family:monospace}  
table {border-spacing: 5px 0px; margin-top: 3px}  
h3.issueHeader {padding-left: 1em}  
span.pass {background-color: lightgreen}  
span.fail {background-color: yellow}  
.pass, .fail {font-size: 16pt; padding: 3px}  
ol,.unused {padding-left: 40pt}  
.header {background-color: white; font-family: Georgia, serif; padding: 0px}  
th.header {text-align: right; font-weight: normal; color: gray}  
.footer {font-size: 10pt}  
</style>  
</head>  
<body>  
<h1>Feed merger results</h1>  
<p>Old feed: <code>%(old_feed_path)s</code></p>  
<p>New feed: <code>%(new_feed_path)s</code></p>  
%(html_merged_feed_path)s""" % locals()  
 
html_stats = self._GenerateStatsTable(feed_merger)  
html_summary = self._GenerateSummary()  
html_errors = self._GenerateSection(transitfeed.TYPE_ERROR)  
html_warnings = self._GenerateSection(transitfeed.TYPE_WARNING)  
 
html_footer = """  
<div class="footer">  
Generated using transitfeed version %s on %s.  
</div>  
</body>  
</html>""" % (transitfeed.__version__,  
time.strftime('%B %d, %Y at %I:%M %p %Z'))  
 
output_file.write(transitfeed.EncodeUnicode(html_header))  
output_file.write(transitfeed.EncodeUnicode(html_stats))  
output_file.write(transitfeed.EncodeUnicode(html_summary))  
output_file.write(transitfeed.EncodeUnicode(html_errors))  
output_file.write(transitfeed.EncodeUnicode(html_warnings))  
output_file.write(transitfeed.EncodeUnicode(html_footer))  
 
 
class ConsoleWarningRaiseErrorProblemReporter(transitfeed.ProblemReporterBase):  
"""Problem reporter to use when loading feeds for merge."""  
 
def _Report(self, e):  
if e.IsError():  
raise e  
else:  
print transitfeed.EncodeUnicode(e.FormatProblem())  
context = e.FormatContext()  
if context:  
print context  
 
 
def LoadWithoutErrors(path, memory_db):  
""""Return a Schedule object loaded from path; sys.exit for any error."""  
loading_problem_handler = ConsoleWarningRaiseErrorProblemReporter()  
try:  
schedule = transitfeed.Loader(path,  
memory_db=memory_db,  
problems=loading_problem_handler).Load()  
except transitfeed.ExceptionWithContext, e:  
print >>sys.stderr, (  
"\n\nFeeds to merge must load without any errors.\n"  
"While loading %s the following error was found:\n%s\n%s\n" %  
(path, e.FormatContext(), transitfeed.EncodeUnicode(e.FormatProblem())))  
sys.exit(1)  
return schedule  
 
 
class DataSetMerger(object):  
"""A DataSetMerger is in charge of merging a set of entities.  
 
This is an abstract class and should be subclassed for each different entity  
type.  
 
Attributes:  
ENTITY_TYPE_NAME: The name of the entity type like 'agency' or 'stop'.  
FILE_NAME: The name of the file containing this data set like 'agency.txt'.  
DATASET_NAME: A name for the dataset like 'Agencies' or 'Stops'.  
"""  
 
def __init__(self, feed_merger):  
"""Initialise.  
 
Args:  
feed_merger: The FeedMerger.  
"""  
self.feed_merger = feed_merger  
self._num_merged = 0  
self._num_not_merged_a = 0  
self._num_not_merged_b = 0  
 
def _MergeIdentical(self, a, b):  
"""Tries to merge two values. The values are required to be identical.  
 
Args:  
a: The first value.  
b: The second value.  
 
Returns:  
The trivially merged value.  
 
Raises:  
MergeError: The values were not identical.  
"""  
if a != b:  
raise MergeError("values must be identical ('%s' vs '%s')" %  
(transitfeed.EncodeUnicode(a),  
transitfeed.EncodeUnicode(b)))  
return b  
 
def _MergeIdenticalCaseInsensitive(self, a, b):  
"""Tries to merge two strings.  
 
The string are required to be the same ignoring case. The second string is  
always used as the merged value.  
 
Args:  
a: The first string.  
b: The second string.  
 
Returns:  
The merged string. This is equal to the second string.  
 
Raises:  
MergeError: The strings were not the same ignoring case.  
"""  
if a.lower() != b.lower():  
raise MergeError("values must be the same (case insensitive) "  
"('%s' vs '%s')" % (transitfeed.EncodeUnicode(a),  
transitfeed.EncodeUnicode(b)))  
return b  
 
def _MergeOptional(self, a, b):  
"""Tries to merge two values which may be None.  
 
If both values are not None, they are required to be the same and the  
merge is trivial. If one of the values is None and the other is not None,  
the merge results in the one which is not None. If both are None, the merge  
results in None.  
 
Args:  
a: The first value.  
b: The second value.  
 
Returns:  
The merged value.  
 
Raises:  
MergeError: If both values are not None and are not the same.  
"""  
if a and b:  
if a != b:  
raise MergeError("values must be identical if both specified "  
"('%s' vs '%s')" % (transitfeed.EncodeUnicode(a),  
transitfeed.EncodeUnicode(b)))  
return a or b  
 
def _MergeSameAgency(self, a_agency_id, b_agency_id):  
"""Merge agency ids to the corresponding agency id in the merged schedule.  
 
Args:  
a_agency_id: an agency id from the old schedule  
b_agency_id: an agency id from the new schedule  
 
Returns:  
The agency id of the corresponding merged agency.  
 
Raises:  
MergeError: If a_agency_id and b_agency_id do not correspond to the same  
merged agency.  
KeyError: Either aaid or baid is not a valid agency id.  
"""  
a_agency_id = (a_agency_id or  
self.feed_merger.a_schedule.GetDefaultAgency().agency_id)  
b_agency_id = (b_agency_id or  
self.feed_merger.b_schedule.GetDefaultAgency().agency_id)  
a_agency = self.feed_merger.a_merge_map[  
self.feed_merger.a_schedule.GetAgency(a_agency_id)]  
b_agency = self.feed_merger.b_merge_map[  
self.feed_merger.b_schedule.GetAgency(b_agency_id)]  
if a_agency != b_agency:  
raise MergeError('agency must be the same')  
return a_agency.agency_id  
 
def _SchemedMerge(self, scheme, a, b):  
"""Tries to merge two entities according to a merge scheme.  
 
A scheme is specified by a map where the keys are entity attributes and the  
values are merge functions like Merger._MergeIdentical or  
Merger._MergeOptional. The entity is first migrated to the merged schedule.  
Then the attributes are individually merged as specified by the scheme.  
 
Args:  
scheme: The merge scheme, a map from entity attributes to merge  
functions.  
a: The entity from the old schedule.  
b: The entity from the new schedule.  
 
Returns:  
The migrated and merged entity.  
 
Raises:  
MergeError: One of the attributes was not able to be merged.  
"""  
migrated = self._Migrate(b, self.feed_merger.b_schedule, False)  
for attr, merger in scheme.items():  
a_attr = getattr(a, attr, None)  
b_attr = getattr(b, attr, None)  
try:  
merged_attr = merger(a_attr, b_attr)  
except MergeError, merge_error:  
raise MergeError("Attribute '%s' could not be merged: %s." % (  
attr, merge_error))  
if migrated is not None:  
setattr(migrated, attr, merged_attr)  
return migrated  
 
def _MergeSameId(self):  
"""Tries to merge entities based on their ids.  
 
This tries to merge only the entities from the old and new schedules which  
have the same id. These are added into the merged schedule. Entities which  
do not merge or do not have the same id as another entity in the other  
schedule are simply migrated into the merged schedule.  
 
This method is less flexible than _MergeDifferentId since it only tries  
to merge entities which have the same id while _MergeDifferentId tries to  
merge everything. However, it is faster and so should be used whenever  
possible.  
 
This method makes use of various methods like _Merge and _Migrate which  
are not implemented in the abstract DataSetMerger class. These method  
should be overwritten in a subclass to allow _MergeSameId to work with  
different entity types.  
 
Returns:  
The number of merged entities.  
"""  
a_not_merged = []  
b_not_merged = []  
 
for a in self._GetIter(self.feed_merger.a_schedule):  
try:  
b = self._GetById(self.feed_merger.b_schedule, self._GetId(a))  
except KeyError:  
# there was no entity in B with the same id as a  
a_not_merged.append(a)  
continue  
try:  
self._Add(a, b, self._MergeEntities(a, b))  
self._num_merged += 1  
except MergeError, merge_error:  
a_not_merged.append(a)  
b_not_merged.append(b)  
self._ReportSameIdButNotMerged(self._GetId(a), merge_error)  
 
for b in self._GetIter(self.feed_merger.b_schedule):  
try:  
a = self._GetById(self.feed_merger.a_schedule, self._GetId(b))  
except KeyError:  
# there was no entity in A with the same id as b  
b_not_merged.append(b)  
 
# migrate the remaining entities  
for a in a_not_merged:  
newid = self._HasId(self.feed_merger.b_schedule, self._GetId(a))  
self._Add(a, None, self._Migrate(a, self.feed_merger.a_schedule, newid))  
for b in b_not_merged:  
newid = self._HasId(self.feed_merger.a_schedule, self._GetId(b))  
self._Add(None, b, self._Migrate(b, self.feed_merger.b_schedule, newid))  
 
self._num_not_merged_a = len(a_not_merged)  
self._num_not_merged_b = len(b_not_merged)  
return self._num_merged  
 
def _MergeDifferentId(self):  
"""Tries to merge all possible combinations of entities.  
 
This tries to merge every entity in the old schedule with every entity in  
the new schedule. Unlike _MergeSameId, the ids do not need to match.  
However, _MergeDifferentId is much slower than _MergeSameId.  
 
This method makes use of various methods like _Merge and _Migrate which  
are not implemented in the abstract DataSetMerger class. These method  
should be overwritten in a subclass to allow _MergeSameId to work with  
different entity types.  
 
Returns:  
The number of merged entities.  
"""  
# TODO: The same entity from A could merge with multiple from B.  
# This should either generate an error or should be prevented from  
# happening.  
for a in self._GetIter(self.feed_merger.a_schedule):  
for b in self._GetIter(self.feed_merger.b_schedule):  
try:  
self._Add(a, b, self._MergeEntities(a, b))  
self._num_merged += 1  
except MergeError:  
continue  
 
for a in self._GetIter(self.feed_merger.a_schedule):  
if a not in self.feed_merger.a_merge_map:  
self._num_not_merged_a += 1  
newid = self._HasId(self.feed_merger.b_schedule, self._GetId(a))  
self._Add(a, None,  
self._Migrate(a, self.feed_merger.a_schedule, newid))  
for b in self._GetIter(self.feed_merger.b_schedule):  
if b not in self.feed_merger.b_merge_map:  
self._num_not_merged_b += 1  
newid = self._HasId(self.feed_merger.a_schedule, self._GetId(b))  
self._Add(None, b,  
self._Migrate(b, self.feed_merger.b_schedule, newid))  
 
return self._num_merged  
 
def _ReportSameIdButNotMerged(self, entity_id, reason):  
"""Report that two entities have the same id but could not be merged.  
 
Args:  
entity_id: The id of the entities.  
reason: A string giving a reason why they could not be merged.  
"""  
self.feed_merger.problem_reporter.SameIdButNotMerged(self,  
entity_id,  
reason)  
 
def _GetIter(self, schedule):  
"""Returns an iterator of entities for this data set in the given schedule.  
 
This method usually corresponds to one of the methods from  
transitfeed.Schedule like GetAgencyList() or GetRouteList().  
 
Note: This method must be overwritten in a subclass if _MergeSameId or  
_MergeDifferentId are to be used.  
 
Args:  
schedule: Either the old or new schedule from the FeedMerger.  
 
Returns:  
An iterator of entities.  
"""  
raise NotImplementedError()  
 
def _GetById(self, schedule, entity_id):  
"""Returns an entity given its id.  
 
This method usually corresponds to one of the methods from  
transitfeed.Schedule like GetAgency() or GetRoute().  
 
Note: This method must be overwritten in a subclass if _MergeSameId or  
_MergeDifferentId are to be used.  
 
Args:  
schedule: Either the old or new schedule from the FeedMerger.  
entity_id: The id string of the entity.  
 
Returns:  
The entity with the given id.  
 
Raises:  
KeyError: There is not entity with the given id.  
"""  
raise NotImplementedError()  
 
def _HasId(self, schedule, entity_id):  
"""Check if the schedule has an entity with the given id.  
 
Args:  
schedule: The transitfeed.Schedule instance to look in.  
entity_id: The id of the entity.  
 
Returns:  
True if the schedule has an entity with the id or False if not.  
"""  
try:  
self._GetById(schedule, entity_id)  
has = True  
except KeyError:  
has = False  
return has  
 
def _MergeEntities(self, a, b):  
"""Tries to merge the two entities.  
 
Note: This method must be overwritten in a subclass if _MergeSameId or  
_MergeDifferentId are to be used.  
 
Args:  
a: The entity from the old schedule.  
b: The entity from the new schedule.  
 
Returns:  
The merged migrated entity.  
 
Raises:  
MergeError: The entities were not able to be merged.  
"""  
raise NotImplementedError()  
 
def _Migrate(self, entity, schedule, newid):  
"""Migrates the entity to the merge schedule.  
 
This involves copying the entity and updating any ids to point to the  
corresponding entities in the merged schedule. If newid is True then  
a unique id is generated for the migrated entity using the original id  
as a prefix.  
 
Note: This method must be overwritten in a subclass if _MergeSameId or  
_MergeDifferentId are to be used.  
 
Args:  
entity: The entity to migrate.  
schedule: The schedule from the FeedMerger that contains ent.  
newid: Whether to generate a new id (True) or keep the original (False).  
 
Returns:  
The migrated entity.  
"""  
raise NotImplementedError()  
 
def _Add(self, a, b, migrated):  
"""Adds the migrated entity to the merged schedule.  
 
If a and b are both not None, it means that a and b were merged to create  
migrated. If one of a or b is None, it means that the other was not merged  
but has been migrated. This mapping is registered with the FeedMerger.  
 
Note: This method must be overwritten in a subclass if _MergeSameId or  
_MergeDifferentId are to be used.  
 
Args:  
a: The original entity from the old schedule.  
b: The original entity from the new schedule.  
migrated: The migrated entity for the merged schedule.  
"""  
raise NotImplementedError()  
 
def _GetId(self, entity):  
"""Returns the id of the given entity.  
 
Note: This method must be overwritten in a subclass if _MergeSameId or  
_MergeDifferentId are to be used.  
 
Args:  
entity: The entity.  
 
Returns:  
The id of the entity as a string or None.  
"""  
raise NotImplementedError()  
 
def MergeDataSets(self):  
"""Merge the data sets.  
 
This method is called in FeedMerger.MergeSchedule().  
 
Note: This method must be overwritten in a subclass.  
 
Returns:  
A boolean which is False if the dataset was unable to be merged and  
as a result the entire merge should be aborted. In this case, the problem  
will have been reported using the FeedMerger's problem reporter.  
"""  
raise NotImplementedError()  
 
def GetMergeStats(self):  
"""Returns some merge statistics.  
 
These are given as a tuple (merged, not_merged_a, not_merged_b) where  
"merged" is the number of merged entities, "not_merged_a" is the number of  
entities from the old schedule that were not merged and "not_merged_b" is  
the number of entities from the new schedule that were not merged.  
 
The return value can also be None. This means that there are no statistics  
for this entity type.  
 
The statistics are only available after MergeDataSets() has been called.  
 
Returns:  
Either the statistics tuple or None.  
"""  
return (self._num_merged, self._num_not_merged_a, self._num_not_merged_b)  
 
 
class AgencyMerger(DataSetMerger):  
"""A DataSetMerger for agencies."""  
 
ENTITY_TYPE_NAME = 'agency'  
FILE_NAME = 'agency.txt'  
DATASET_NAME = 'Agencies'  
 
def _GetIter(self, schedule):  
return schedule.GetAgencyList()  
 
def _GetById(self, schedule, agency_id):  
return schedule.GetAgency(agency_id)  
 
def _MergeEntities(self, a, b):  
"""Merges two agencies.  
 
To be merged, they are required to have the same id, name, url and  
timezone. The remaining language attribute is taken from the new agency.  
 
Args:  
a: The first agency.  
b: The second agency.  
 
Returns:  
The merged agency.  
 
Raises:  
MergeError: The agencies could not be merged.  
"""  
 
def _MergeAgencyId(a_agency_id, b_agency_id):  
"""Merge two agency ids.  
 
The only difference between this and _MergeIdentical() is that the values  
None and '' are regarded as being the same.  
 
Args:  
a_agency_id: The first agency id.  
b_agency_id: The second agency id.  
 
Returns:  
The merged agency id.  
 
Raises:  
MergeError: The agency ids could not be merged.  
"""  
a_agency_id = a_agency_id or None  
b_agency_id = b_agency_id or None  
return self._MergeIdentical(a_agency_id, b_agency_id)  
 
scheme = {'agency_id': _MergeAgencyId,  
'agency_name': self._MergeIdentical,  
'agency_url': self._MergeIdentical,  
'agency_timezone': self._MergeIdentical}  
return self._SchemedMerge(scheme, a, b)  
 
def _Migrate(self, entity, schedule, newid):  
a = transitfeed.Agency(field_dict=entity)  
if newid:  
a.agency_id = self.feed_merger.GenerateId(entity.agency_id)  
return a  
 
def _Add(self, a, b, migrated):  
self.feed_merger.Register(a, b, migrated)  
self.feed_merger.merged_schedule.AddAgencyObject(migrated)  
 
def _GetId(self, entity):  
return entity.agency_id  
 
def MergeDataSets(self):  
self._MergeSameId()  
return True  
 
 
class StopMerger(DataSetMerger):  
"""A DataSetMerger for stops.  
 
Attributes:  
largest_stop_distance: The largest distance allowed between stops that  
will be merged in metres.  
"""  
 
ENTITY_TYPE_NAME = 'stop'  
FILE_NAME = 'stops.txt'  
DATASET_NAME = 'Stops'  
 
largest_stop_distance = 10.0  
 
def __init__(self, feed_merger):  
DataSetMerger.__init__(self, feed_merger)  
self._merged = []  
self._a_not_merged = []  
self._b_not_merged = []  
 
def SetLargestStopDistance(self, distance):  
"""Sets largest_stop_distance."""  
self.largest_stop_distance = distance  
 
def _GetIter(self, schedule):  
return schedule.GetStopList()  
 
def _GetById(self, schedule, stop_id):  
return schedule.GetStop(stop_id)  
 
def _MergeEntities(self, a, b):  
"""Merges two stops.  
 
For the stops to be merged, they must have:  
- the same stop_id  
- the same stop_name (case insensitive)  
- the same zone_id  
- locations less than largest_stop_distance apart  
The other attributes can have arbitary changes. The merged attributes are  
taken from the new stop.  
 
Args:  
a: The first stop.  
b: The second stop.  
 
Returns:  
The merged stop.  
 
Raises:  
MergeError: The stops could not be merged.  
"""  
distance = transitfeed.ApproximateDistanceBetweenStops(a, b)  
if distance > self.largest_stop_distance:  
raise MergeError("Stops are too far apart: %.1fm "  
"(largest_stop_distance is %.1fm)." %  
(distance, self.largest_stop_distance))  
scheme = {'stop_id': self._MergeIdentical,  
'stop_name': self._MergeIdenticalCaseInsensitive,  
'zone_id': self._MergeIdentical,  
'location_type': self._MergeIdentical}  
return self._SchemedMerge(scheme, a, b)  
 
def _Migrate(self, entity, schedule, newid):  
migrated_stop = transitfeed.Stop(field_dict=entity)  
if newid:  
migrated_stop.stop_id = self.feed_merger.GenerateId(entity.stop_id)  
return migrated_stop  
 
def _Add(self, a, b, migrated_stop):  
self.feed_merger.Register(a, b, migrated_stop)  
 
# The migrated_stop will be added to feed_merger.merged_schedule later  
# since adding must be done after the zone_ids have been finalized.  
if a and b:  
self._merged.append((a, b, migrated_stop))  
elif a:  
self._a_not_merged.append((a, migrated_stop))  
elif b:  
self._b_not_merged.append((b, migrated_stop))  
 
def _GetId(self, entity):  
return entity.stop_id  
 
def MergeDataSets(self):  
num_merged = self._MergeSameId()  
fm = self.feed_merger  
 
# now we do all the zone_id and parent_station mapping  
 
# the zone_ids for merged stops can be preserved  
for (a, b, merged_stop) in self._merged:  
assert a.zone_id == b.zone_id  
fm.a_zone_map[a.zone_id] = a.zone_id  
fm.b_zone_map[b.zone_id] = b.zone_id  
merged_stop.zone_id = a.zone_id  
if merged_stop.parent_station:  
# Merged stop has a parent. Update it to be the parent it had in b.  
parent_in_b = fm.b_schedule.GetStop(b.parent_station)  
merged_stop.parent_station = fm.b_merge_map[parent_in_b].stop_id  
fm.merged_schedule.AddStopObject(merged_stop)  
 
self._UpdateAndMigrateUnmerged(self._a_not_merged, fm.a_zone_map,  
fm.a_merge_map, fm.a_schedule)  
self._UpdateAndMigrateUnmerged(self._b_not_merged, fm.b_zone_map,  
fm.b_merge_map, fm.b_schedule)  
 
print 'Stops merged: %d of %d, %d' % (  
num_merged,  
len(fm.a_schedule.GetStopList()),  
len(fm.b_schedule.GetStopList()))  
return True  
 
def _UpdateAndMigrateUnmerged(self, not_merged_stops, zone_map, merge_map,  
schedule):  
"""Correct references in migrated unmerged stops and add to merged_schedule.  
 
For stops migrated from one of the input feeds to the output feed update the  
parent_station and zone_id references to point to objects in the output  
feed. Then add the migrated stop to the new schedule.  
 
Args:  
not_merged_stops: list of stops from one input feed that have not been  
merged  
zone_map: map from zone_id in the input feed to zone_id in the output feed  
merge_map: map from Stop objects in the input feed to Stop objects in  
the output feed  
schedule: the input Schedule object  
"""  
# for the unmerged stops, we use an already mapped zone_id if possible  
# if not, we generate a new one and add it to the map  
for stop, migrated_stop in not_merged_stops:  
if stop.zone_id in zone_map:  
migrated_stop.zone_id = zone_map[stop.zone_id]  
else:  
migrated_stop.zone_id = self.feed_merger.GenerateId(stop.zone_id)  
zone_map[stop.zone_id] = migrated_stop.zone_id  
if stop.parent_station:  
parent_original = schedule.GetStop(stop.parent_station)  
migrated_stop.parent_station = merge_map[parent_original].stop_id  
self.feed_merger.merged_schedule.AddStopObject(migrated_stop)  
 
 
class RouteMerger(DataSetMerger):  
"""A DataSetMerger for routes."""  
 
ENTITY_TYPE_NAME = 'route'  
FILE_NAME = 'routes.txt'  
DATASET_NAME = 'Routes'  
 
def _GetIter(self, schedule):  
return schedule.GetRouteList()  
 
def _GetById(self, schedule, route_id):  
return schedule.GetRoute(route_id)  
 
def _MergeEntities(self, a, b):  
scheme = {'route_short_name': self._MergeIdentical,  
'route_long_name': self._MergeIdentical,  
'agency_id': self._MergeSameAgency,  
'route_type': self._MergeIdentical,  
'route_id': self._MergeIdentical,  
'route_url': self._MergeOptional,  
'route_color': self._MergeOptional,  
'route_text_color': self._MergeOptional}  
return self._SchemedMerge(scheme, a, b)  
 
def _Migrate(self, entity, schedule, newid):  
migrated_route = transitfeed.Route(field_dict=entity)  
if newid:  
migrated_route.route_id = self.feed_merger.GenerateId(entity.route_id)  
if entity.agency_id:  
original_agency = schedule.GetAgency(entity.agency_id)  
else:  
original_agency = schedule.GetDefaultAgency()  
 
migrated_agency = self.feed_merger.GetMergedObject(original_agency)  
migrated_route.agency_id = migrated_agency.agency_id  
return migrated_route  
 
def _Add(self, a, b, migrated_route):  
self.feed_merger.Register(a, b, migrated_route)  
self.feed_merger.merged_schedule.AddRouteObject(migrated_route)  
 
def _GetId(self, entity):  
return entity.route_id  
 
def MergeDataSets(self):  
self._MergeSameId()  
return True  
 
 
class ServicePeriodMerger(DataSetMerger):  
"""A DataSetMerger for service periods.  
 
Attributes:  
require_disjoint_calendars: A boolean specifying whether to require  
disjoint calendars when merging (True) or not (False).  
"""  
 
ENTITY_TYPE_NAME = 'service period'  
FILE_NAME = 'calendar.txt/calendar_dates.txt'  
DATASET_NAME = 'Service Periods'  
 
def __init__(self, feed_merger):  
DataSetMerger.__init__(self, feed_merger)  
self.require_disjoint_calendars = True  
 
def _ReportSameIdButNotMerged(self, entity_id, reason):  
pass  
 
def _GetIter(self, schedule):  
return schedule.GetServicePeriodList()  
 
def _GetById(self, schedule, service_id):  
return schedule.GetServicePeriod(service_id)  
 
def _MergeEntities(self, a, b):  
"""Tries to merge two service periods.  
 
Note: Currently this just raises a MergeError since service periods cannot  
be merged.  
 
Args:  
a: The first service period.  
b: The second service period.  
 
Returns:  
The merged service period.  
 
Raises:  
MergeError: When the service periods could not be merged.  
"""  
raise MergeError('Cannot merge service periods')  
 
def _Migrate(self, original_service_period, schedule, newid):  
migrated_service_period = transitfeed.ServicePeriod()  
migrated_service_period.day_of_week = list(  
original_service_period.day_of_week)  
migrated_service_period.start_date = original_service_period.start_date  
migrated_service_period.end_date = original_service_period.end_date  
migrated_service_period.date_exceptions = dict(  
original_service_period.date_exceptions)  
if newid:  
migrated_service_period.service_id = self.feed_merger.GenerateId(  
original_service_period.service_id)  
else:  
migrated_service_period.service_id = original_service_period.service_id  
return migrated_service_period  
 
def _Add(self, a, b, migrated_service_period):  
self.feed_merger.Register(a, b, migrated_service_period)  
self.feed_merger.merged_schedule.AddServicePeriodObject(  
migrated_service_period)  
 
def _GetId(self, entity):  
return entity.service_id  
 
def MergeDataSets(self):  
if self.require_disjoint_calendars and not self.CheckDisjointCalendars():  
self.feed_merger.problem_reporter.CalendarsNotDisjoint(self)  
return False  
self._MergeSameId()  
self.feed_merger.problem_reporter.MergeNotImplemented(self)  
return True  
 
def DisjoinCalendars(self, cutoff):  
"""Forces the old and new calendars to be disjoint about a cutoff date.  
 
This truncates the service periods of the old schedule so that service  
stops one day before the given cutoff date and truncates the new schedule  
so that service only begins on the cutoff date.  
 
Args:  
cutoff: The cutoff date as a string in YYYYMMDD format. The timezone  
is the same as used in the calendar.txt file.  
"""  
 
def TruncatePeriod(service_period, start, end):  
"""Truncate the service period to into the range [start, end].  
 
Args:  
service_period: The service period to truncate.  
start: The start date as a string in YYYYMMDD format.  
end: The end date as a string in YYYYMMDD format.  
"""  
service_period.start_date = max(service_period.start_date, start)  
service_period.end_date = min(service_period.end_date, end)  
dates_to_delete = []  
for k in service_period.date_exceptions:  
if (k < start) or (k > end):  
dates_to_delete.append(k)  
for k in dates_to_delete:  
del service_period.date_exceptions[k]  
 
# find the date one day before cutoff  
year = int(cutoff[:4])  
month = int(cutoff[4:6])  
day = int(cutoff[6:8])  
cutoff_date = datetime.date(year, month, day)  
one_day_delta = datetime.timedelta(days=1)  
before = (cutoff_date - one_day_delta).strftime('%Y%m%d')  
 
for a in self.feed_merger.a_schedule.GetServicePeriodList():  
TruncatePeriod(a, 0, before)  
for b in self.feed_merger.b_schedule.GetServicePeriodList():  
TruncatePeriod(b, cutoff, '9'*8)  
 
def CheckDisjointCalendars(self):  
"""Check whether any old service periods intersect with any new ones.  
 
This is a rather coarse check based on  
transitfeed.SevicePeriod.GetDateRange.  
 
Returns:  
True if the calendars are disjoint or False if not.  
"""  
# TODO: Do an exact check here.  
 
a_service_periods = self.feed_merger.a_schedule.GetServicePeriodList()  
b_service_periods = self.feed_merger.b_schedule.GetServicePeriodList()  
 
for a_service_period in a_service_periods:  
a_start, a_end = a_service_period.GetDateRange()  
for b_service_period in b_service_periods:  
b_start, b_end = b_service_period.GetDateRange()  
overlap_start = max(a_start, b_start)  
overlap_end = min(a_end, b_end)  
if overlap_end >= overlap_start:  
return False  
return True  
 
def GetMergeStats(self):  
return None  
 
 
class FareMerger(DataSetMerger):  
"""A DataSetMerger for fares."""  
 
ENTITY_TYPE_NAME = 'fare'  
FILE_NAME = 'fare_attributes.txt'  
DATASET_NAME = 'Fares'  
 
def _GetIter(self, schedule):  
return schedule.GetFareList()  
 
def _GetById(self, schedule, fare_id):  
return schedule.GetFare(fare_id)  
 
def _MergeEntities(self, a, b):  
"""Merges the fares if all the attributes are the same."""  
scheme = {'price': self._MergeIdentical,  
'currency_type': self._MergeIdentical,  
'payment_method': self._MergeIdentical,  
'transfers': self._MergeIdentical,  
'transfer_duration': self._MergeIdentical}  
return self._SchemedMerge(scheme, a, b)  
 
def _Migrate(self, original_fare, schedule, newid):  
migrated_fare = transitfeed.Fare(  
field_list=original_fare.GetFieldValuesTuple())  
if newid:  
migrated_fare.fare_id = self.feed_merger.GenerateId(  
original_fare.fare_id)  
return migrated_fare  
 
def _Add(self, a, b, migrated_fare):  
self.feed_merger.Register(a, b, migrated_fare)  
self.feed_merger.merged_schedule.AddFareObject(migrated_fare)  
 
def _GetId(self, fare):  
return fare.fare_id  
 
def MergeDataSets(self):  
num_merged = self._MergeSameId()  
print 'Fares merged: %d of %d, %d' % (  
num_merged,  
len(self.feed_merger.a_schedule.GetFareList()),  
len(self.feed_merger.b_schedule.GetFareList()))  
return True  
 
 
class ShapeMerger(DataSetMerger):  
"""A DataSetMerger for shapes.  
 
In this implementation, merging shapes means just taking the new shape.  
The only conditions for a merge are that the shape_ids are the same and  
the endpoints of the old and new shapes are no further than  
largest_shape_distance apart.  
 
Attributes:  
largest_shape_distance: The largest distance between the endpoints of two  
shapes allowed for them to be merged in metres.  
"""  
 
ENTITY_TYPE_NAME = 'shape'  
FILE_NAME = 'shapes.txt'  
DATASET_NAME = 'Shapes'  
 
largest_shape_distance = 10.0  
 
def SetLargestShapeDistance(self, distance):  
"""Sets largest_shape_distance."""  
self.largest_shape_distance = distance  
 
def _GetIter(self, schedule):  
return schedule.GetShapeList()  
 
def _GetById(self, schedule, shape_id):  
return schedule.GetShape(shape_id)  
 
def _MergeEntities(self, a, b):  
"""Merges the shapes by taking the new shape.  
 
Args:  
a: The first transitfeed.Shape instance.  
b: The second transitfeed.Shape instance.  
 
Returns:  
The merged shape.  
 
Raises:  
MergeError: If the ids are different or if the endpoints are further  
than largest_shape_distance apart.  
"""  
if a.shape_id != b.shape_id:  
raise MergeError('shape_id must be the same')  
 
distance = max(ApproximateDistanceBetweenPoints(a.points[0][:2],  
b.points[0][:2]),  
ApproximateDistanceBetweenPoints(a.points[-1][:2],  
b.points[-1][:2]))  
if distance > self.largest_shape_distance:  
raise MergeError('The shape endpoints are too far away: %.1fm '  
'(largest_shape_distance is %.1fm)' %  
(distance, self.largest_shape_distance))  
 
return self._Migrate(b, self.feed_merger.b_schedule, False)  
 
def _Migrate(self, original_shape, schedule, newid):  
migrated_shape = transitfeed.Shape(original_shape.shape_id)  
if newid:  
migrated_shape.shape_id = self.feed_merger.GenerateId(  
original_shape.shape_id)  
for (lat, lon, dist) in original_shape.points:  
migrated_shape.AddPoint(lat=lat, lon=lon, distance=dist)  
return migrated_shape  
 
def _Add(self, a, b, migrated_shape):  
self.feed_merger.Register(a, b, migrated_shape)  
self.feed_merger.merged_schedule.AddShapeObject(migrated_shape)  
 
def _GetId(self, shape):  
return shape.shape_id  
 
def MergeDataSets(self):  
self._MergeSameId()  
return True  
 
 
class TripMerger(DataSetMerger):  
"""A DataSetMerger for trips.  
 
This implementation makes no attempt to merge trips, it simply migrates  
them all to the merged feed.  
"""  
 
ENTITY_TYPE_NAME = 'trip'  
FILE_NAME = 'trips.txt'  
DATASET_NAME = 'Trips'  
 
def _ReportSameIdButNotMerged(self, trip_id, reason):  
pass  
 
def _GetIter(self, schedule):  
return schedule.GetTripList()  
 
def _GetById(self, schedule, trip_id):  
return schedule.GetTrip(trip_id)  
 
def _MergeEntities(self, a, b):  
"""Raises a MergeError because currently trips cannot be merged."""  
raise MergeError('Cannot merge trips')  
 
def _Migrate(self, original_trip, schedule, newid):  
migrated_trip = transitfeed.Trip(field_dict=original_trip)  
# Make new trip_id first. AddTripObject reports a problem if it conflicts  
# with an existing id.  
if newid:  
migrated_trip.trip_id = self.feed_merger.GenerateId(  
original_trip.trip_id)  
# Need to add trip to schedule before copying stoptimes  
self.feed_merger.merged_schedule.AddTripObject(migrated_trip,  
validate=False)  
 
if schedule == self.feed_merger.a_schedule:  
merge_map = self.feed_merger.a_merge_map  
else:  
merge_map = self.feed_merger.b_merge_map  
 
original_route = schedule.GetRoute(original_trip.route_id)  
migrated_trip.route_id = merge_map[original_route].route_id  
 
original_service_period = schedule.GetServicePeriod(  
original_trip.service_id)  
migrated_trip.service_id = merge_map[original_service_period].service_id  
 
if original_trip.block_id:  
migrated_trip.block_id = '%s_%s' % (  
self.feed_merger.GetScheduleName(schedule),  
original_trip.block_id)  
 
if original_trip.shape_id:  
original_shape = schedule.GetShape(original_trip.shape_id)  
migrated_trip.shape_id = merge_map[original_shape].shape_id  
 
for original_stop_time in original_trip.GetStopTimes():  
migrated_stop_time = transitfeed.StopTime(  
None,  
merge_map[original_stop_time.stop],  
original_stop_time.arrival_time,  
original_stop_time.departure_time,  
original_stop_time.stop_headsign,  
original_stop_time.pickup_type,  
original_stop_time.drop_off_type,  
original_stop_time.shape_dist_traveled,  
original_stop_time.arrival_secs,  
original_stop_time.departure_secs)  
migrated_trip.AddStopTimeObject(migrated_stop_time)  
 
for headway_period in original_trip.GetHeadwayPeriodTuples():  
migrated_trip.AddHeadwayPeriod(*headway_period)  
 
return migrated_trip  
 
def _Add(self, a, b, migrated_trip):  
# Validate now, since it wasn't done in _Migrate  
migrated_trip.Validate(self.feed_merger.merged_schedule.problem_reporter)  
self.feed_merger.Register(a, b, migrated_trip)  
 
def _GetId(self, trip):  
return trip.trip_id  
 
def MergeDataSets(self):  
self._MergeSameId()  
self.feed_merger.problem_reporter.MergeNotImplemented(self)  
return True  
 
def GetMergeStats(self):  
return None  
 
 
class FareRuleMerger(DataSetMerger):  
"""A DataSetMerger for fare rules."""  
 
ENTITY_TYPE_NAME = 'fare rule'  
FILE_NAME = 'fare_rules.txt'  
DATASET_NAME = 'Fare Rules'  
 
def MergeDataSets(self):  
"""Merge the fare rule datasets.  
 
The fare rules are first migrated. Merging is done by removing any  
duplicate rules.  
 
Returns:  
True since fare rules can always be merged.  
"""  
rules = set()  
for (schedule, merge_map, zone_map) in ([self.feed_merger.a_schedule,  
self.feed_merger.a_merge_map,  
self.feed_merger.a_zone_map],  
[self.feed_merger.b_schedule,  
self.feed_merger.b_merge_map,  
self.feed_merger.b_zone_map]):  
for fare in schedule.GetFareList():  
for fare_rule in fare.GetFareRuleList():  
fare_id = merge_map[schedule.GetFare(fare_rule.fare_id)].fare_id  
route_id = (fare_rule.route_id and  
merge_map[schedule.GetRoute(fare_rule.route_id)].route_id)  
origin_id = (fare_rule.origin_id and  
zone_map[fare_rule.origin_id])  
destination_id = (fare_rule.destination_id and  
zone_map[fare_rule.destination_id])  
contains_id = (fare_rule.contains_id and  
zone_map[fare_rule.contains_id])  
rules.add((fare_id, route_id, origin_id, destination_id,  
contains_id))  
for fare_rule_tuple in rules:  
migrated_fare_rule = transitfeed.FareRule(*fare_rule_tuple)  
self.feed_merger.merged_schedule.AddFareRuleObject(migrated_fare_rule)  
 
if rules:  
self.feed_merger.problem_reporter.FareRulesBroken(self)  
print 'Fare Rules: union has %d fare rules' % len(rules)  
return True  
 
def GetMergeStats(self):  
return None  
 
 
class FeedMerger(object):  
"""A class for merging two whole feeds.  
 
This class takes two instances of transitfeed.Schedule and uses  
DataSetMerger instances to merge the feeds and produce the resultant  
merged feed.  
 
Attributes:  
a_schedule: The old transitfeed.Schedule instance.  
b_schedule: The new transitfeed.Schedule instance.  
problem_reporter: The merge problem reporter.  
merged_schedule: The merged transitfeed.Schedule instance.  
a_merge_map: A map from old entities to merged entities.  
b_merge_map: A map from new entities to merged entities.  
a_zone_map: A map from old zone ids to merged zone ids.  
b_zone_map: A map from new zone ids to merged zone ids.  
"""  
 
def __init__(self, a_schedule, b_schedule, merged_schedule,  
problem_reporter=None):  
"""Initialise the merger.  
 
Once this initialiser has been called, a_schedule and b_schedule should  
not be modified.  
 
Args:  
a_schedule: The old schedule, an instance of transitfeed.Schedule.  
b_schedule: The new schedule, an instance of transitfeed.Schedule.  
problem_reporter: The problem reporter, an instance of  
transitfeed.ProblemReporterBase. This can be None in  
which case the ExceptionProblemReporter is used.  
"""  
self.a_schedule = a_schedule  
self.b_schedule = b_schedule  
self.merged_schedule = merged_schedule  
self.a_merge_map = {}  
self.b_merge_map = {}  
self.a_zone_map = {}  
self.b_zone_map = {}  
self._mergers = []  
self._idnum = max(self._FindLargestIdPostfixNumber(self.a_schedule),  
self._FindLargestIdPostfixNumber(self.b_schedule))  
 
if problem_reporter is not None:  
self.problem_reporter = problem_reporter  
else:  
self.problem_reporter = ExceptionProblemReporter()  
 
def _FindLargestIdPostfixNumber(self, schedule):  
"""Finds the largest integer used as the ending of an id in the schedule.  
 
Args:  
schedule: The schedule to check.  
 
Returns:  
The maximum integer used as an ending for an id.  
"""  
postfix_number_re = re.compile('(\d+)$')  
 
def ExtractPostfixNumber(entity_id):  
"""Try to extract an integer from the end of entity_id.  
 
If entity_id is None or if there is no integer ending the id, zero is  
returned.  
 
Args:  
entity_id: An id string or None.  
 
Returns:  
An integer ending the entity_id or zero.  
"""  
if entity_id is None:  
return 0  
match = postfix_number_re.search(entity_id)  
if match is not None:  
return int(match.group(1))  
else:  
return 0  
 
id_data_sets = {'agency_id': schedule.GetAgencyList(),  
'stop_id': schedule.GetStopList(),  
'route_id': schedule.GetRouteList(),  
'trip_id': schedule.GetTripList(),  
'service_id': schedule.GetServicePeriodList(),  
'fare_id': schedule.GetFareList(),  
'shape_id': schedule.GetShapeList()}  
 
max_postfix_number = 0  
for id_name, entity_list in id_data_sets.items():  
for entity in entity_list:  
entity_id = getattr(entity, id_name)  
postfix_number = ExtractPostfixNumber(entity_id)  
max_postfix_number = max(max_postfix_number, postfix_number)  
return max_postfix_number  
 
def GetScheduleName(self, schedule):  
"""Returns a single letter identifier for the schedule.  
 
This only works for the old and new schedules which return 'a' and 'b'  
respectively. The purpose of such identifiers is for generating ids.  
 
Args:  
schedule: The transitfeed.Schedule instance.  
 
Returns:  
The schedule identifier.  
 
Raises:  
KeyError: schedule is not the old or new schedule.  
"""  
return {self.a_schedule: 'a', self.b_schedule: 'b'}[schedule]  
 
def GenerateId(self, entity_id=None):  
"""Generate a unique id based on the given id.  
 
This is done by appending a counter which is then incremented. The  
counter is initialised at the maximum number used as an ending for  
any id in the old and new schedules.  
 
Args:  
entity_id: The base id string. This is allowed to be None.  
 
Returns:  
The generated id.  
"""  
self._idnum += 1  
if entity_id:  
return '%s_merged_%d' % (entity_id, self._idnum)  
else:  
return 'merged_%d' % self._idnum  
 
def Register(self, a, b, migrated_entity):  
"""Registers a merge mapping.  
 
If a and b are both not None, this means that entities a and b were merged  
to produce migrated_entity. If one of a or b are not None, then it means  
it was not merged but simply migrated.  
 
The effect of a call to register is to update a_merge_map and b_merge_map  
according to the merge.  
 
Args:  
a: The entity from the old feed or None.  
b: The entity from the new feed or None.  
migrated_entity: The migrated entity.  
"""  
if a is not None: self.a_merge_map[a] = migrated_entity  
if b is not None: self.b_merge_map[b] = migrated_entity  
 
def AddMerger(self, merger):  
"""Add a DataSetMerger to be run by Merge().  
 
Args:  
merger: The DataSetMerger instance.  
"""  
self._mergers.append(merger)  
 
def AddDefaultMergers(self):  
"""Adds the default DataSetMergers defined in this module."""  
self.AddMerger(AgencyMerger(self))  
self.AddMerger(StopMerger(self))  
self.AddMerger(RouteMerger(self))  
self.AddMerger(ServicePeriodMerger(self))  
self.AddMerger(FareMerger(self))  
self.AddMerger(ShapeMerger(self))  
self.AddMerger(TripMerger(self))  
self.AddMerger(FareRuleMerger(self))  
 
def GetMerger(self, cls):  
"""Looks for an added DataSetMerger derived from the given class.  
 
Args:  
cls: A class derived from DataSetMerger.  
 
Returns:  
The matching DataSetMerger instance.  
 
Raises:  
LookupError: No matching DataSetMerger has been added.  
"""  
for merger in self._mergers:  
if isinstance(merger, cls):  
return merger  
raise LookupError('No matching DataSetMerger found')  
 
def GetMergerList(self):  
"""Returns the list of DataSetMerger instances that have been added."""  
return self._mergers  
 
def MergeSchedules(self):  
"""Merge the schedules.  
 
This is done by running the DataSetMergers that have been added with  
AddMerger() in the order that they were added.  
 
Returns:  
True if the merge was successful.  
"""  
for merger in self._mergers:  
if not merger.MergeDataSets():  
return False  
return True  
 
def GetMergedSchedule(self):  
"""Returns the merged schedule.  
 
This will be empty before MergeSchedules() is called.  
 
Returns:  
The merged schedule.  
"""  
return self.merged_schedule  
 
def GetMergedObject(self, original):  
"""Returns an object that represents original in the merged schedule."""  
# TODO: I think this would be better implemented by adding a private  
# attribute to the objects in the original feeds  
merged = (self.a_merge_map.get(original) or  
self.b_merge_map.get(original))  
if merged:  
return merged  
else:  
raise KeyError()  
 
 
def main():  
"""Run the merge driver program."""  
usage = \  
"""%prog [options] <input GTFS a.zip> <input GTFS b.zip> <output GTFS.zip>  
 
Merges <input GTFS a.zip> and <input GTFS b.zip> into a new GTFS file  
<output GTFS.zip>.  
"""  
 
parser = util.OptionParserLongError(  
usage=usage, version='%prog '+transitfeed.__version__)  
parser.add_option('--cutoff_date',  
dest='cutoff_date',  
default=None,  
help='a transition date from the old feed to the new '  
'feed in the format YYYYMMDD')  
parser.add_option('--largest_stop_distance',  
dest='largest_stop_distance',  
default=StopMerger.largest_stop_distance,  
help='the furthest distance two stops can be apart and '  
'still be merged, in metres')  
parser.add_option('--largest_shape_distance',  
dest='largest_shape_distance',  
default=ShapeMerger.largest_shape_distance,  
help='the furthest distance the endpoints of two shapes '  
'can be apart and the shape still be merged, in metres')  
parser.add_option('--html_output_path',  
dest='html_output_path',  
default='merge-results.html',  
help='write the html output to this file')  
parser.add_option('--no_browser',  
dest='no_browser',  
action='store_true',  
help='prevents the merge results from being opened in a '  
'browser')  
parser.add_option('-m', '--memory_db', dest='memory_db', action='store_true',  
help='Use in-memory sqlite db instead of a temporary file. '  
'It is faster but uses more RAM.')  
parser.set_defaults(memory_db=False)  
(options, args) = parser.parse_args()  
 
if len(args) != 3:  
parser.error('You did not provide all required command line arguments.')  
 
old_feed_path = os.path.abspath(args[0])  
new_feed_path = os.path.abspath(args[1])  
merged_feed_path = os.path.abspath(args[2])  
 
if old_feed_path.find("IWantMyCrash") != -1:  
# See test/testmerge.py  
raise Exception('For testing the merge crash handler.')  
 
a_schedule = LoadWithoutErrors(old_feed_path, options.memory_db)  
b_schedule = LoadWithoutErrors(new_feed_path, options.memory_db)  
merged_schedule = transitfeed.Schedule(memory_db=options.memory_db)  
problem_reporter = HTMLProblemReporter()  
feed_merger = FeedMerger(a_schedule, b_schedule, merged_schedule,  
problem_reporter)  
feed_merger.AddDefaultMergers()  
 
feed_merger.GetMerger(StopMerger).SetLargestStopDistance(float(  
options.largest_stop_distance))  
feed_merger.GetMerger(ShapeMerger).SetLargestShapeDistance(float(  
options.largest_shape_distance))  
 
if options.cutoff_date is not None:  
service_period_merger = feed_merger.GetMerger(ServicePeriodMerger)  
service_period_merger.DisjoinCalendars(options.cutoff_date)  
 
if feed_merger.MergeSchedules():  
feed_merger.GetMergedSchedule().WriteGoogleTransitFeed(merged_feed_path)  
else:  
merged_feed_path = None  
 
output_file = file(options.html_output_path, 'w')  
problem_reporter.WriteOutput(output_file, feed_merger,  
old_feed_path, new_feed_path, merged_feed_path)  
output_file.close()  
 
if not options.no_browser:  
webbrowser.open('file://%s' % os.path.abspath(options.html_output_path))  
 
 
if __name__ == '__main__':  
util.RunWithCrashHandler(main)  
 
#!/usr/bin/python  
 
# Copyright (C) 2007 Google Inc.  
#  
# Licensed under the Apache License, Version 2.0 (the "License");  
# you may not use this file except in compliance with the License.  
# You may obtain a copy of the License at  
#  
# http://www.apache.org/licenses/LICENSE-2.0  
#  
# Unless required by applicable law or agreed to in writing, software  
# distributed under the License is distributed on an "AS IS" BASIS,  
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.  
# See the License for the specific language governing permissions and  
# limitations under the License.  
 
"""  
An example application that uses the transitfeed module.  
 
You must provide a Google Maps API key.  
"""  
 
 
import BaseHTTPServer, sys, urlparse  
import bisect  
from gtfsscheduleviewer.marey_graph import MareyGraph  
import gtfsscheduleviewer  
import mimetypes  
import os.path  
import re  
import signal  
import simplejson  
import socket  
import time  
import transitfeed  
from transitfeed import util  
import urllib  
 
 
# By default Windows kills Python with Ctrl+Break. Instead make Ctrl+Break  
# raise a KeyboardInterrupt.  
if hasattr(signal, 'SIGBREAK'):  
signal.signal(signal.SIGBREAK, signal.default_int_handler)  
 
 
mimetypes.add_type('text/plain', '.vbs')  
 
 
class ResultEncoder(simplejson.JSONEncoder):  
def default(self, obj):  
try:  
iterable = iter(obj)  
except TypeError:  
pass  
else:  
return list(iterable)  
return simplejson.JSONEncoder.default(self, obj)  
 
# Code taken from  
# http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/425210/index_txt  
# An alternate approach is shown at  
# http://mail.python.org/pipermail/python-list/2003-July/212751.html  
# but it requires multiple threads. A sqlite object can only be used from one  
# thread.  
class StoppableHTTPServer(BaseHTTPServer.HTTPServer):  
def server_bind(self):  
BaseHTTPServer.HTTPServer.server_bind(self)  
self.socket.settimeout(1)  
self._run = True  
 
def get_request(self):  
while self._run:  
try:  
sock, addr = self.socket.accept()  
sock.settimeout(None)  
return (sock, addr)  
except socket.timeout:  
pass  
 
def stop(self):  
self._run = False  
 
def serve(self):  
while self._run:  
self.handle_request()  
 
 
def StopToTuple(stop):  
"""Return tuple as expected by javascript function addStopMarkerFromList"""  
return (stop.stop_id, stop.stop_name, float(stop.stop_lat),  
float(stop.stop_lon), stop.location_type)  
 
 
class ScheduleRequestHandler(BaseHTTPServer.BaseHTTPRequestHandler):  
def do_GET(self):  
scheme, host, path, x, params, fragment = urlparse.urlparse(self.path)  
parsed_params = {}  
for k in params.split('&'):  
k = urllib.unquote(k)  
if '=' in k:  
k, v = k.split('=', 1)  
parsed_params[k] = unicode(v, 'utf8')  
else:  
parsed_params[k] = ''  
 
if path == '/':  
return self.handle_GET_home()  
 
m = re.match(r'/json/([a-z]{1,64})', path)  
if m:  
handler_name = 'handle_json_GET_%s' % m.group(1)  
handler = getattr(self, handler_name, None)  
if callable(handler):  
return self.handle_json_wrapper_GET(handler, parsed_params)  
 
# Restrict allowable file names to prevent relative path attacks etc  
m = re.match(r'/file/([a-z0-9_-]{1,64}\.?[a-z0-9_-]{1,64})$', path)  
if m and m.group(1):  
try:  
f, mime_type = self.OpenFile(m.group(1))  
return self.handle_static_file_GET(f, mime_type)  
except IOError, e:  
print "Error: unable to open %s" % m.group(1)  
# Ignore and treat as 404  
 
m = re.match(r'/([a-z]{1,64})', path)  
if m:  
handler_name = 'handle_GET_%s' % m.group(1)  
handler = getattr(self, handler_name, None)  
if callable(handler):  
return handler(parsed_params)  
 
return self.handle_GET_default(parsed_params, path)  
 
def OpenFile(self, filename):  
"""Try to open filename in the static files directory of this server.  
Return a tuple (file object, string mime_type) or raise an exception."""  
(mime_type, encoding) = mimetypes.guess_type(filename)  
assert mime_type  
# A crude guess of when we should use binary mode. Without it non-unix  
# platforms may corrupt binary files.  
if mime_type.startswith('text/'):  
mode = 'r'  
else:  
mode = 'rb'  
return open(os.path.join(self.server.file_dir, filename), mode), mime_type  
 
def handle_GET_default(self, parsed_params, path):  
self.send_error(404)  
 
def handle_static_file_GET(self, fh, mime_type):  
content = fh.read()  
self.send_response(200)  
self.send_header('Content-Type', mime_type)  
self.send_header('Content-Length', str(len(content)))  
self.end_headers()  
self.wfile.write(content)  
 
def AllowEditMode(self):  
return False  
 
def handle_GET_home(self):  
schedule = self.server.schedule  
(min_lat, min_lon, max_lat, max_lon) = schedule.GetStopBoundingBox()  
forbid_editing = ('true', 'false')[self.AllowEditMode()]  
 
agency = ', '.join(a.agency_name for a in schedule.GetAgencyList()).encode('utf-8')  
 
key = self.server.key  
host = self.server.host  
 
# A very simple template system. For a fixed set of values replace [xxx]  
# with the value of local variable xxx  
f, _ = self.OpenFile('index.html')  
content = f.read()  
for v in ('agency', 'min_lat', 'min_lon', 'max_lat', 'max_lon', 'key',  
'host', 'forbid_editing'):  
content = content.replace('[%s]' % v, str(locals()[v]))  
 
self.send_response(200)  
self.send_header('Content-Type', 'text/html')  
self.send_header('Content-Length', str(len(content)))  
self.end_headers()  
self.wfile.write(content)  
 
def handle_json_GET_routepatterns(self, params):  
"""Given a route_id generate a list of patterns of the route. For each  
pattern include some basic information and a few sample trips."""  
schedule = self.server.schedule  
route = schedule.GetRoute(params.get('route', None))  
if not route:  
self.send_error(404)  
return  
time = int(params.get('time', 0))  
sample_size = 3 # For each pattern return the start time for this many trips  
 
pattern_id_trip_dict = route.GetPatternIdTripDict()  
patterns = []  
 
for pattern_id, trips in pattern_id_trip_dict.items():  
time_stops = trips[0].GetTimeStops()  
if not time_stops:  
continue  
has_non_zero_trip_type = False;  
for trip in trips:  
if trip['trip_type'] and trip['trip_type'] != '0':  
has_non_zero_trip_type = True  
name = u'%s to %s, %d stops' % (time_stops[0][2].stop_name, time_stops[-1][2].stop_name, len(time_stops))  
transitfeed.SortListOfTripByTime(trips)  
 
num_trips = len(trips)  
if num_trips <= sample_size:  
start_sample_index = 0  
num_after_sample = 0  
else:  
# Will return sample_size trips that start after the 'time' param.  
 
# Linear search because I couldn't find a built-in way to do a binary  
# search with a custom key.  
start_sample_index = len(trips)  
for i, trip in enumerate(trips):  
if trip.GetStartTime() >= time:  
start_sample_index = i  
break  
 
num_after_sample = num_trips - (start_sample_index + sample_size)  
if num_after_sample < 0:  
# Less than sample_size trips start after 'time' so return all the  
# last sample_size trips.  
num_after_sample = 0  
start_sample_index = num_trips - sample_size  
 
sample = []  
for t in trips[start_sample_index:start_sample_index + sample_size]:  
sample.append( (t.GetStartTime(), t.trip_id) )  
 
patterns.append((name, pattern_id, start_sample_index, sample,  
num_after_sample, (0,1)[has_non_zero_trip_type]))  
 
patterns.sort()  
return patterns  
 
def handle_json_wrapper_GET(self, handler, parsed_params):  
"""Call handler and output the return value in JSON."""  
schedule = self.server.schedule  
result = handler(parsed_params)  
content = ResultEncoder().encode(result)  
self.send_response(200)  
self.send_header('Content-Type', 'text/plain')  
self.send_header('Content-Length', str(len(content)))  
self.end_headers()  
self.wfile.write(content)  
 
def handle_json_GET_routes(self, params):  
"""Return a list of all routes."""  
schedule = self.server.schedule  
result = []  
for r in schedule.GetRouteList():  
result.append( (r.route_id, r.route_short_name, r.route_long_name) )  
result.sort(key = lambda x: x[1:3])  
return result  
 
def handle_json_GET_routerow(self, params):  
schedule = self.server.schedule  
route = schedule.GetRoute(params.get('route', None))  
return [transitfeed.Route._FIELD_NAMES, route.GetFieldValuesTuple()]  
 
def handle_json_GET_triprows(self, params):  
"""Return a list of rows from the feed file that are related to this  
trip."""  
schedule = self.server.schedule  
try:  
trip = schedule.GetTrip(params.get('trip', None))  
except KeyError:  
# if a non-existent trip is searched for, the return nothing  
return  
route = schedule.GetRoute(trip.route_id)  
trip_row = dict(trip.iteritems())  
route_row = dict(route.iteritems())  
return [['trips.txt', trip_row], ['routes.txt', route_row]]  
 
def handle_json_GET_tripstoptimes(self, params):  
schedule = self.server.schedule  
try:  
trip = schedule.GetTrip(params.get('trip'))  
except KeyError:  
# if a non-existent trip is searched for, the return nothing  
return  
time_stops = trip.GetTimeStops()  
stops = []  
times = []  
for arr,dep,stop in time_stops:  
stops.append(StopToTuple(stop))  
times.append(arr)  
return [stops, times]  
 
def handle_json_GET_tripshape(self, params):  
schedule = self.server.schedule  
try:  
trip = schedule.GetTrip(params.get('trip'))  
except KeyError:  
# if a non-existent trip is searched for, the return nothing  
return  
points = []  
if trip.shape_id:  
shape = schedule.GetShape(trip.shape_id)  
for (lat, lon, dist) in shape.points:  
points.append((lat, lon))  
else:  
time_stops = trip.GetTimeStops()  
for arr,dep,stop in time_stops:  
points.append((stop.stop_lat, stop.stop_lon))  
return points  
 
def handle_json_GET_neareststops(self, params):  
"""Return a list of the nearest 'limit' stops to 'lat', 'lon'"""  
schedule = self.server.schedule  
lat = float(params.get('lat'))  
lon = float(params.get('lon'))  
limit = int(params.get('limit'))  
stops = schedule.GetNearestStops(lat=lat, lon=lon, n=limit)  
return [StopToTuple(s) for s in stops]  
 
def handle_json_GET_boundboxstops(self, params):  
"""Return a list of up to 'limit' stops within bounding box with 'n','e'  
and 's','w' in the NE and SW corners. Does not handle boxes crossing  
longitude line 180."""  
schedule = self.server.schedule  
n = float(params.get('n'))  
e = float(params.get('e'))  
s = float(params.get('s'))  
w = float(params.get('w'))  
limit = int(params.get('limit'))  
stops = schedule.GetStopsInBoundingBox(north=n, east=e, south=s, west=w, n=limit)  
return [StopToTuple(s) for s in stops]  
 
def handle_json_GET_stopsearch(self, params):  
schedule = self.server.schedule  
query = params.get('q', None).lower()  
matches = []  
for s in schedule.GetStopList():  
if s.stop_id.lower().find(query) != -1 or s.stop_name.lower().find(query) != -1:  
matches.append(StopToTuple(s))  
return matches  
 
def handle_json_GET_stoptrips(self, params):  
"""Given a stop_id and time in seconds since midnight return the next  
trips to visit the stop."""  
schedule = self.server.schedule  
stop = schedule.GetStop(params.get('stop', None))  
time = int(params.get('time', 0))  
time_trips = stop.GetStopTimeTrips(schedule)  
time_trips.sort() # OPT: use bisect.insort to make this O(N*ln(N)) -> O(N)  
# Keep the first 5 after param 'time'.  
# Need make a tuple to find correct bisect point  
time_trips = time_trips[bisect.bisect_left(time_trips, (time, 0)):]  
time_trips = time_trips[:5]  
# TODO: combine times for a route to show next 2 departure times  
result = []  
for time, (trip, index), tp in time_trips:  
headsign = None  
# Find the most recent headsign from the StopTime objects  
for stoptime in trip.GetStopTimes()[index::-1]:  
if stoptime.stop_headsign:  
headsign = stoptime.stop_headsign  
break  
# If stop_headsign isn't found, look for a trip_headsign  
if not headsign:  
headsign = trip.trip_headsign  
route = schedule.GetRoute(trip.route_id)  
trip_name = ''  
if route.route_short_name:  
trip_name += route.route_short_name  
if route.route_long_name:  
if len(trip_name):  
trip_name += " - "  
trip_name += route.route_long_name  
if headsign:  
trip_name += " (Direction: %s)" % headsign  
 
result.append((time, (trip.trip_id, trip_name, trip.service_id), tp))  
return result  
 
def handle_GET_ttablegraph(self,params):  
"""Draw a Marey graph in SVG for a pattern (collection of trips in a route  
that visit the same sequence of stops)."""  
schedule = self.server.schedule  
marey = MareyGraph()  
trip = schedule.GetTrip(params.get('trip', None))  
route = schedule.GetRoute(trip.route_id)  
height = int(params.get('height', 300))  
 
if not route:  
print 'no such route'  
self.send_error(404)  
return  
 
pattern_id_trip_dict = route.GetPatternIdTripDict()  
pattern_id = trip.pattern_id  
if pattern_id not in pattern_id_trip_dict:  
print 'no pattern %s found in %s' % (pattern_id, pattern_id_trip_dict.keys())  
self.send_error(404)  
return  
triplist = pattern_id_trip_dict[pattern_id]  
 
pattern_start_time = min((t.GetStartTime() for t in triplist))  
pattern_end_time = max((t.GetEndTime() for t in triplist))  
 
marey.SetSpan(pattern_start_time,pattern_end_time)  
marey.Draw(triplist[0].GetPattern(), triplist, height)  
 
content = marey.Draw()  
 
self.send_response(200)  
self.send_header('Content-Type', 'image/svg+xml')  
self.send_header('Content-Length', str(len(content)))  
self.end_headers()  
self.wfile.write(content)  
 
 
def FindPy2ExeBase():  
"""If this is running in py2exe return the install directory else return  
None"""  
# py2exe puts gtfsscheduleviewer in library.zip. For py2exe setup.py is  
# configured to put the data next to library.zip.  
windows_ending = gtfsscheduleviewer.__file__.find('\\library.zip\\')  
if windows_ending != -1:  
return transitfeed.__file__[:windows_ending]  
else:  
return None  
 
 
def FindDefaultFileDir():  
"""Return the path of the directory containing the static files. By default  
the directory is called 'files'. The location depends on where setup.py put  
it."""  
base = FindPy2ExeBase()  
if base:  
return os.path.join(base, 'schedule_viewer_files')  
else:  
# For all other distributions 'files' is in the gtfsscheduleviewer  
# directory.  
base = os.path.dirname(gtfsscheduleviewer.__file__) # Strip __init__.py  
return os.path.join(base, 'files')  
 
 
def GetDefaultKeyFilePath():  
"""In py2exe return absolute path of file in the base directory and in all  
other distributions return relative path 'key.txt'"""  
windows_base = FindPy2ExeBase()  
if windows_base:  
return os.path.join(windows_base, 'key.txt')  
else:  
return 'key.txt'  
 
 
def main(RequestHandlerClass = ScheduleRequestHandler):  
usage = \  
'''%prog [options] [<input GTFS.zip>]  
 
Runs a webserver that lets you explore a <input GTFS.zip> in your browser.  
 
If <input GTFS.zip> is omited the filename is read from the console. Dragging  
a file into the console may enter the filename.  
'''  
parser = util.OptionParserLongError(  
usage=usage, version='%prog '+transitfeed.__version__)  
parser.add_option('--feed_filename', '--feed', dest='feed_filename',  
help='file name of feed to load')  
parser.add_option('--key', dest='key',  
help='Google Maps API key or the name '  
'of a text file that contains an API key')  
parser.add_option('--host', dest='host', help='Host name of Google Maps')  
parser.add_option('--port', dest='port', type='int',  
help='port on which to listen')  
parser.add_option('--file_dir', dest='file_dir',  
help='directory containing static files')  
parser.add_option('-n', '--noprompt', action='store_false',  
dest='manual_entry',  
help='disable interactive prompts')  
parser.set_defaults(port=8765,  
host='maps.google.com',  
file_dir=FindDefaultFileDir(),  
manual_entry=True)  
(options, args) = parser.parse_args()  
 
if not os.path.isfile(os.path.join(options.file_dir, 'index.html')):  
print "Can't find index.html with --file_dir=%s" % options.file_dir  
exit(1)  
 
if not options.feed_filename and len(args) == 1:  
options.feed_filename = args[0]  
 
if not options.feed_filename and options.manual_entry:  
options.feed_filename = raw_input('Enter Feed Location: ').strip('"')  
 
default_key_file = GetDefaultKeyFilePath()  
if not options.key and os.path.isfile(default_key_file):  
options.key = open(default_key_file).read().strip()  
 
if options.key and os.path.isfile(options.key):  
options.key = open(options.key).read().strip()  
 
schedule = transitfeed.Schedule(problem_reporter=transitfeed.ProblemReporter())  
print 'Loading data from feed "%s"...' % options.feed_filename  
print '(this may take a few minutes for larger cities)'  
schedule.Load(options.feed_filename)  
 
server = StoppableHTTPServer(server_address=('', options.port),  
RequestHandlerClass=RequestHandlerClass)  
server.key = options.key  
server.schedule = schedule  
server.file_dir = options.file_dir  
server.host = options.host  
server.feed_path = options.feed_filename  
 
print ("To view, point your browser at http://localhost:%d/" %  
(server.server_port))  
server.serve_forever()  
 
 
if __name__ == '__main__':  
main()  
 
#!/usr/bin/python  
#  
# Copyright 2007 Google Inc. All Rights Reserved.  
#  
# Licensed under the Apache License, Version 2.0 (the "License");  
# you may not use this file except in compliance with the License.  
# You may obtain a copy of the License at  
#  
# http://www.apache.org/licenses/LICENSE-2.0  
#  
# Unless required by applicable law or agreed to in writing, software  
# distributed under the License is distributed on an "AS IS" BASIS,  
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.  
# See the License for the specific language governing permissions and  
# limitations under the License.  
 
"""A utility program to help add shapes to an existing GTFS feed.  
 
Requires the ogr python package.  
"""  
 
__author__ = 'chris.harrelson.code@gmail.com (Chris Harrelson)'  
 
import csv  
import glob  
import ogr  
import os  
import shutil  
import sys  
import tempfile  
import transitfeed  
from transitfeed import shapelib  
from transitfeed import util  
import zipfile  
 
 
class ShapeImporterError(Exception):  
pass  
 
 
def PrintColumns(shapefile):  
"""  
Print the columns of layer 0 of the shapefile to the screen.  
"""  
ds = ogr.Open(shapefile)  
layer = ds.GetLayer(0)  
if len(layer) == 0:  
raise ShapeImporterError("Layer 0 has no elements!")  
 
feature = layer.GetFeature(0)  
print "%d features" % feature.GetFieldCount()  
for j in range(0, feature.GetFieldCount()):  
print '--' + feature.GetFieldDefnRef(j).GetName() + \  
': ' + feature.GetFieldAsString(j)  
 
 
def AddShapefile(shapefile, graph, key_cols):  
"""  
Adds shapes found in the given shape filename to the given polyline  
graph object.  
"""  
ds = ogr.Open(shapefile)  
layer = ds.GetLayer(0)  
 
for i in range(0, len(layer)):  
feature = layer.GetFeature(i)  
 
geometry = feature.GetGeometryRef()  
 
if key_cols:  
key_list = []  
for col in key_cols:  
key_list.append(str(feature.GetField(col)))  
shape_id = '-'.join(key_list)  
else:  
shape_id = '%s-%d' % (shapefile, i)  
 
poly = shapelib.Poly(name=shape_id)  
for j in range(0, geometry.GetPointCount()):  
(lat, lng) = (round(geometry.GetY(j), 15), round(geometry.GetX(j), 15))  
poly.AddPoint(shapelib.Point.FromLatLng(lat, lng))  
graph.AddPoly(poly)  
 
return graph  
 
 
def GetMatchingShape(pattern_poly, trip, matches, max_distance, verbosity=0):  
"""  
Tries to find a matching shape for the given pattern Poly object,  
trip, and set of possibly matching Polys from which to choose a match.  
"""  
if len(matches) == 0:  
print ('No matching shape found within max-distance %d for trip %s '  
% (max_distance, trip.trip_id))  
return None  
 
if verbosity >= 1:  
for match in matches:  
print "match: size %d" % match.GetNumPoints()  
scores = [(pattern_poly.GreedyPolyMatchDist(match), match)  
for match in matches]  
 
scores.sort()  
 
if scores[0][0] > max_distance:  
print ('No matching shape found within max-distance %d for trip %s '  
'(min score was %f)'  
% (max_distance, trip.trip_id, scores[0][0]))  
return None  
 
return scores[0][1]  
 
def AddExtraShapes(extra_shapes_txt, graph):  
"""  
Add extra shapes into our input set by parsing them out of a GTFS-formatted  
shapes.txt file. Useful for manually adding lines to a shape file, since it's  
a pain to edit .shp files.  
"""  
 
print "Adding extra shapes from %s" % extra_shapes_txt  
try:  
tmpdir = tempfile.mkdtemp()  
shutil.copy(extra_shapes_txt, os.path.join(tmpdir, 'shapes.txt'))  
loader = transitfeed.ShapeLoader(tmpdir)  
schedule = loader.Load()  
for shape in schedule.GetShapeList():  
print "Adding extra shape: %s" % shape.shape_id  
graph.AddPoly(ShapeToPoly(shape))  
finally:  
if tmpdir:  
shutil.rmtree(tmpdir)  
 
 
# Note: this method lives here to avoid cross-dependencies between  
# shapelib and transitfeed.  
def ShapeToPoly(shape):  
poly = shapelib.Poly(name=shape.shape_id)  
for lat, lng, distance in shape.points:  
point = shapelib.Point.FromLatLng(round(lat, 15), round(lng, 15))  
poly.AddPoint(point)  
return poly  
 
 
def ValidateArgs(options_parser, options, args):  
if not (args and options.source_gtfs and options.dest_gtfs):  
options_parser.error("You must specify a source and dest GTFS file, "  
"and at least one source shapefile")  
 
 
def DefineOptions():  
usage = \  
"""%prog [options] --source_gtfs=<input GTFS.zip> --dest_gtfs=<output GTFS.zip>\  
<input.shp> [<input.shp>...]  
 
Try to match shapes in one or more SHP files to trips in a GTFS file."""  
options_parser = util.OptionParserLongError(  
usage=usage, version='%prog '+transitfeed.__version__)  
options_parser.add_option("--print_columns",  
action="store_true",  
default=False,  
dest="print_columns",  
help="Print column names in shapefile DBF and exit")  
options_parser.add_option("--keycols",  
default="",  
dest="keycols",  
help="Comma-separated list of the column names used"  
"to index shape ids")  
options_parser.add_option("--max_distance",  
type="int",  
default=150,  
dest="max_distance",  
help="Max distance from a shape to which to match")  
options_parser.add_option("--source_gtfs",  
default="",  
dest="source_gtfs",  
metavar="FILE",  
help="Read input GTFS from FILE")  
options_parser.add_option("--dest_gtfs",  
default="",  
dest="dest_gtfs",  
metavar="FILE",  
help="Write output GTFS with shapes to FILE")  
options_parser.add_option("--extra_shapes",  
default="",  
dest="extra_shapes",  
metavar="FILE",  
help="Extra shapes.txt (CSV) formatted file")  
options_parser.add_option("--verbosity",  
type="int",  
default=0,  
dest="verbosity",  
help="Verbosity level. Higher is more verbose")  
return options_parser  
 
 
def main(key_cols):  
print 'Parsing shapefile(s)...'  
graph = shapelib.PolyGraph()  
for arg in args:  
print ' ' + arg  
AddShapefile(arg, graph, key_cols)  
 
if options.extra_shapes:  
AddExtraShapes(options.extra_shapes, graph)  
 
print 'Loading GTFS from %s...' % options.source_gtfs  
schedule = transitfeed.Loader(options.source_gtfs).Load()  
shape_count = 0  
pattern_count = 0  
 
verbosity = options.verbosity  
 
print 'Matching shapes to trips...'  
for route in schedule.GetRouteList():  
print 'Processing route', route.route_short_name  
patterns = route.GetPatternIdTripDict()  
for pattern_id, trips in patterns.iteritems():  
pattern_count += 1  
pattern = trips[0].GetPattern()  
 
poly_points = [shapelib.Point.FromLatLng(p.stop_lat, p.stop_lon)  
for p in pattern]  
if verbosity >= 2:  
print "\npattern %d, %d points:" % (pattern_id, len(poly_points))  
for i, (stop, point) in enumerate(zip(pattern, poly_points)):  
print "Stop %d '%s': %s" % (i + 1, stop.stop_name, point.ToLatLng())  
 
# First, try to find polys that run all the way from  
# the start of the trip to the end.  
matches = graph.FindMatchingPolys(poly_points[0], poly_points[-1],  
options.max_distance)  
if not matches:  
# Try to find a path through the graph, joining  
# multiple edges to find a path that covers all the  
# points in the trip. Some shape files are structured  
# this way, with a polyline for each segment between  
# stations instead of a polyline covering an entire line.  
shortest_path = graph.FindShortestMultiPointPath(poly_points,  
options.max_distance,  
verbosity=verbosity)  
if shortest_path:  
matches = [shortest_path]  
else:  
matches = []  
 
pattern_poly = shapelib.Poly(poly_points)  
shape_match = GetMatchingShape(pattern_poly, trips[0],  
matches, options.max_distance,  
verbosity=verbosity)  
if shape_match:  
shape_count += 1  
# Rename shape for readability.  
shape_match = shapelib.Poly(points=shape_match.GetPoints(),  
name="shape_%d" % shape_count)  
for trip in trips:  
try:  
shape = schedule.GetShape(shape_match.GetName())  
except KeyError:  
shape = transitfeed.Shape(shape_match.GetName())  
for point in shape_match.GetPoints():  
(lat, lng) = point.ToLatLng()  
shape.AddPoint(lat, lng)  
schedule.AddShapeObject(shape)  
trip.shape_id = shape.shape_id  
 
print "Matched %d shapes out of %d patterns" % (shape_count, pattern_count)  
schedule.WriteGoogleTransitFeed(options.dest_gtfs)  
 
 
if __name__ == '__main__':  
# Import psyco if available for better performance.  
try:  
import psyco  
psyco.full()  
except ImportError:  
pass  
 
options_parser = DefineOptions()  
(options, args) = options_parser.parse_args()  
 
ValidateArgs(options_parser, options, args)  
 
if options.print_columns:  
for arg in args:  
PrintColumns(arg)  
sys.exit(0)  
 
key_cols = options.keycols.split(',')  
 
main(key_cols)  
 
#!/usr/bin/python  
 
# Copyright (C) 2007 Google Inc.  
#  
# Licensed under the Apache License, Version 2.0 (the "License");  
# you may not use this file except in compliance with the License.  
# You may obtain a copy of the License at  
#  
# http://www.apache.org/licenses/LICENSE-2.0  
#  
# Unless required by applicable law or agreed to in writing, software  
# distributed under the License is distributed on an "AS IS" BASIS,  
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.  
# See the License for the specific language governing permissions and  
# limitations under the License.  
 
"""  
Filters out trips which are not on the defualt routes and  
set their trip_typeattribute accordingly.  
 
For usage information run unusual_trip_filter.py --help  
"""  
 
__author__ = 'Jiri Semecky <jiri.semecky@gmail.com>'  
 
import codecs  
import os  
import os.path  
import sys  
import time  
import transitfeed  
from transitfeed import util  
 
 
class UnusualTripFilter(object):  
"""Class filtering trips going on unusual paths.  
 
Those are usually trips going to/from depot or changing to another route  
in the middle. Sets the 'trip_type' attribute of the trips.txt dataset  
so that non-standard trips are marked as special (value 1)  
instead of regular (default value 0).  
"""  
 
def __init__ (self, threshold=0.1, force=False, quiet=False, route_type=None):  
self._threshold = threshold  
self._quiet = quiet  
self._force = force  
if route_type in transitfeed.Route._ROUTE_TYPE_NAMES:  
self._route_type = transitfeed.Route._ROUTE_TYPE_NAMES[route_type]  
elif route_type is None:  
self._route_type = None  
else:  
self._route_type = int(route_type)  
 
def filter_line(self, route):  
"""Mark unusual trips for the given route."""  
if self._route_type is not None and self._route_type != route.route_type:  
self.info('Skipping route %s due to different route_type value (%s)' %  
(route['route_id'], route['route_type']))  
return  
self.info('Filtering infrequent trips for route %s.' % route.route_id)  
trip_count = len(route.trips)  
for pattern_id, pattern in route.GetPatternIdTripDict().items():  
ratio = float(1.0 * len(pattern) / trip_count)  
if not self._force:  
if (ratio < self._threshold):  
self.info("\t%d trips on route %s with headsign '%s' recognized "  
"as unusual (ratio %f)" %  
(len(pattern),  
route['route_short_name'],  
pattern[0]['trip_headsign'],  
ratio))  
for trip in pattern:  
trip.trip_type = 1 # special  
self.info("\t\tsetting trip_type of trip %s as special" %  
trip.trip_id)  
else:  
self.info("\t%d trips on route %s with headsign '%s' recognized "  
"as %s (ratio %f)" %  
(len(pattern),  
route['route_short_name'],  
pattern[0]['trip_headsign'],  
('regular', 'unusual')[ratio < self._threshold],  
ratio))  
for trip in pattern:  
trip.trip_type = ('0','1')[ratio < self._threshold]  
self.info("\t\tsetting trip_type of trip %s as %s" %  
(trip.trip_id,  
('regular', 'unusual')[ratio < self._threshold]))  
 
def filter(self, dataset):  
"""Mark unusual trips for all the routes in the dataset."""  
self.info('Going to filter infrequent routes in the dataset')  
for route in dataset.routes.values():  
self.filter_line(route)  
 
def info(self, text):  
if not self._quiet:  
print text.encode("utf-8")  
 
 
def main():  
usage = \  
'''%prog [options] <GTFS.zip>  
 
Filters out trips which do not follow the most common stop sequences and  
sets their trip_type attribute accordingly. <GTFS.zip> is overwritten with  
the modifed GTFS file unless the --output option is used.  
'''  
parser = util.OptionParserLongError(  
usage=usage, version='%prog '+transitfeed.__version__)  
parser.add_option('-o', '--output', dest='output', metavar='FILE',  
help='Name of the output GTFS file (writing to input feed if omitted).')  
parser.add_option('-m', '--memory_db', dest='memory_db', action='store_true',  
help='Force use of in-memory sqlite db.')  
parser.add_option('-t', '--threshold', default=0.1,  
dest='threshold', type='float',  
help='Frequency threshold for considering pattern as non-regular.')  
parser.add_option('-r', '--route_type', default=None,  
dest='route_type', type='string',  
help='Filter only selected route type (specified by number'  
'or one of the following names: ' + \  
', '.join(transitfeed.Route._ROUTE_TYPE_NAMES) + ').')  
parser.add_option('-f', '--override_trip_type', default=False,  
dest='override_trip_type', action='store_true',  
help='Forces overwrite of current trip_type values.')  
parser.add_option('-q', '--quiet', dest='quiet',  
default=False, action='store_true',  
help='Suppress information output.')  
 
(options, args) = parser.parse_args()  
if len(args) != 1:  
parser.error('You must provide the path of a single feed.')  
 
filter = UnusualTripFilter(float(options.threshold),  
force=options.override_trip_type,  
quiet=options.quiet,  
route_type=options.route_type)  
feed_name = args[0]  
feed_name = feed_name.strip()  
filter.info('Loading %s' % feed_name)  
loader = transitfeed.Loader(feed_name, extra_validation=True,  
memory_db=options.memory_db)  
data = loader.Load()  
filter.filter(data)  
print 'Saving data'  
 
# Write the result  
if options.output is None:  
data.WriteGoogleTransitFeed(feed_name)  
else:  
data.WriteGoogleTransitFeed(options.output)  
 
 
if __name__ == '__main__':  
util.RunWithCrashHandler(main)  
 
#!/usr/bin/python2.5  
 
# Copyright (C) 2007 Google Inc.  
#  
# Licensed under the Apache License, Version 2.0 (the "License");  
# you may not use this file except in compliance with the License.  
# You may obtain a copy of the License at  
#  
# http://www.apache.org/licenses/LICENSE-2.0  
#  
# Unless required by applicable law or agreed to in writing, software  
# distributed under the License is distributed on an "AS IS" BASIS,  
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.  
# See the License for the specific language governing permissions and  
# limitations under the License.  
 
 
"""Filter the unused stops out of a transit feed file."""  
 
import optparse  
import sys  
import transitfeed  
 
 
def main():  
parser = optparse.OptionParser(  
usage="usage: %prog [options] input_feed output_feed",  
version="%prog "+transitfeed.__version__)  
parser.add_option("-l", "--list_removed", dest="list_removed",  
default=False,  
action="store_true",  
help="Print removed stops to stdout")  
(options, args) = parser.parse_args()  
if len(args) != 2:  
print >>sys.stderr, parser.format_help()  
print >>sys.stderr, "\n\nYou must provide input_feed and output_feed\n\n"  
sys.exit(2)  
input_path = args[0]  
output_path = args[1]  
 
loader = transitfeed.Loader(input_path)  
schedule = loader.Load()  
 
print "Removing unused stops..."  
removed = 0  
for stop_id, stop in schedule.stops.items():  
if not stop.GetTrips(schedule):  
removed += 1  
del schedule.stops[stop_id]  
if options.list_removed:  
print "Removing %s (%s)" % (stop_id, stop.stop_name)  
if removed == 0:  
print "No unused stops."  
elif removed == 1:  
print "Removed 1 stop"  
else:  
print "Removed %d stops" % removed  
 
schedule.WriteGoogleTransitFeed(output_path)  
 
if __name__ == "__main__":  
main()  
 
#!/usr/bin/python2.5  
 
# Copyright (C) 2007 Google Inc.  
#  
# Licensed under the Apache License, Version 2.0 (the "License");  
# you may not use this file except in compliance with the License.  
# You may obtain a copy of the License at  
#  
# http://www.apache.org/licenses/LICENSE-2.0  
#  
# Unless required by applicable law or agreed to in writing, software  
# distributed under the License is distributed on an "AS IS" BASIS,  
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.  
# See the License for the specific language governing permissions and  
# limitations under the License.  
 
 
"""Output Google Transit URLs for queries near stops.  
 
The output can be used to speed up manual testing. Load the output from this  
file and then open many of the links in new tabs. In each result check that the  
polyline looks okay (no unnecassary loops, no jumps to a far away location) and  
look at the time of each leg. Also check the route names and headsigns are  
formatted correctly and not redundant.  
"""  
 
from datetime import datetime  
from datetime import timedelta  
import math  
import optparse  
import os.path  
import random  
import sys  
import transitfeed  
import urllib  
import urlparse  
 
 
def Distance(lat0, lng0, lat1, lng1):  
"""  
Compute the geodesic distance in meters between two points on the  
surface of the Earth. The latitude and longitude angles are in  
degrees.  
 
Approximate geodesic distance function (Haversine Formula) assuming  
a perfect sphere of radius 6367 km (see "What are some algorithms  
for calculating the distance between 2 points?" in the GIS Faq at  
http://www.census.gov/geo/www/faq-index.html). The approximate  
radius is adequate for our needs here, but a more sophisticated  
geodesic function should be used if greater accuracy is required  
(see "When is it NOT okay to assume the Earth is a sphere?" in the  
same faq).  
"""  
deg2rad = math.pi / 180.0  
lat0 = lat0 * deg2rad  
lng0 = lng0 * deg2rad  
lat1 = lat1 * deg2rad  
lng1 = lng1 * deg2rad  
dlng = lng1 - lng0  
dlat = lat1 - lat0  
a = math.sin(dlat*0.5)  
b = math.sin(dlng*0.5)  
a = a * a + math.cos(lat0) * math.cos(lat1) * b * b  
c = 2.0 * math.atan2(math.sqrt(a), math.sqrt(1.0 - a))  
return 6367000.0 * c  
 
 
def AddNoiseToLatLng(lat, lng):  
"""Add up to 500m of error to each coordinate of lat, lng."""  
m_per_tenth_lat = Distance(lat, lng, lat + 0.1, lng)  
m_per_tenth_lng = Distance(lat, lng, lat, lng + 0.1)  
lat_per_100m = 1 / m_per_tenth_lat * 10  
lng_per_100m = 1 / m_per_tenth_lng * 10  
return (lat + (lat_per_100m * 5 * (random.random() * 2 - 1)),  
lng + (lng_per_100m * 5 * (random.random() * 2 - 1)))  
 
 
def GetRandomLocationsNearStops(schedule):  
"""Return a list of (lat, lng) tuples."""  
locations = []  
for s in schedule.GetStopList():  
locations.append(AddNoiseToLatLng(s.stop_lat, s.stop_lon))  
return locations  
 
 
def GetRandomDatetime():  
"""Return a datetime in the next week."""  
seconds_offset = random.randint(0, 60 * 60 * 24 * 7)  
dt = datetime.today() + timedelta(seconds=seconds_offset)  
return dt.replace(second=0, microsecond=0)  
 
 
def FormatLatLng(lat_lng):  
"""Format a (lat, lng) tuple into a string for maps.google.com."""  
return "%0.6f,%0.6f" % lat_lng  
 
 
def LatLngsToGoogleUrl(source, destination, dt):  
"""Return a URL for routing between two (lat, lng) at a datetime."""  
params = {"saddr": FormatLatLng(source),  
"daddr": FormatLatLng(destination),  
"time": dt.strftime("%I:%M%p"),  
"date": dt.strftime("%Y-%m-%d"),  
"dirflg": "r",  
"ie": "UTF8",  
"oe": "UTF8"}  
url = urlparse.urlunsplit(("http", "maps.google.com", "/maps",  
urllib.urlencode(params), ""))  
return url  
 
 
def LatLngsToGoogleLink(source, destination):  
"""Return a string "<a ..." for a trip at a random time."""  
dt = GetRandomDatetime()  
return "<a href='%s'>from:%s to:%s on %s</a>" % (  
LatLngsToGoogleUrl(source, destination, dt),  
FormatLatLng(source), FormatLatLng(destination),  
dt.ctime())  
 
 
def WriteOutput(title, locations, limit, f):  
"""Write html to f for up to limit trips between locations.  
 
Args:  
title: String used in html title  
locations: list of (lat, lng) tuples  
limit: maximum number of queries in the html  
f: a file object  
"""  
output_prefix = """  
<html>  
<head>  
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">  
<title>%(title)s</title>  
</head>  
<body>  
Random queries for %(title)s<p>  
This list of random queries should speed up important manual testing. Here are  
some things to check when looking at the results of a query.  
<ul>  
<li> Check the agency attribution under the trip results:  
<ul>  
<li> has correct name and spelling of the agency  
<li> opens a page with general information about the service  
</ul>  
<li> For each alternate trip check that each of these is reasonable:  
<ul>  
<li> the total time of the trip  
<li> the time for each leg. Bad data frequently results in a leg going a long  
way in a few minutes.  
<li> the icons and mode names (Tram, Bus, etc) are correct for each leg  
<li> the route names and headsigns are correctly formatted and not  
redundant.  
For a good example see <a  
href="http://code.google.com/transit/spec/transit_feed_specification.html#transitScreenshots">the  
screenshots in the Google Transit Feed Specification</a>.  
<li> the shape line on the map looks correct. Make sure the polyline does  
not zig-zag, loop, skip stops or jump far away unless the trip does the  
same thing.  
<li> the route is active on the day the trip planner returns  
</ul>  
</ul>  
If you find a problem be sure to save the URL. This file is generated randomly.  
<ol>  
""" % locals()  
 
output_suffix = """  
</ol>  
</body>  
</html>  
""" % locals()  
 
f.write(transitfeed.EncodeUnicode(output_prefix))  
for source, destination in zip(locations[0:limit], locations[1:limit + 1]):  
f.write(transitfeed.EncodeUnicode("<li>%s\n" %  
LatLngsToGoogleLink(source, destination)))  
f.write(transitfeed.EncodeUnicode(output_suffix))  
 
 
def ParentAndBaseName(path):  
"""Given a path return only the parent name and file name as a string."""  
dirname, basename = os.path.split(path)  
dirname = dirname.rstrip(os.path.sep)  
if os.path.altsep:  
dirname = dirname.rstrip(os.path.altsep)  
_, parentname = os.path.split(dirname)  
return os.path.join(parentname, basename)  
 
 
def main():  
parser = optparse.OptionParser(  
usage="usage: %prog [options] feed_filename output_filename",  
version="%prog "+transitfeed.__version__)  
parser.add_option("-l", "--limit", dest="limit", type="int",  
help="Maximum number of URLs to generate")  
parser.add_option('-o', '--output', dest='output', metavar='FILE',  
help='write html output to FILE')  
parser.set_defaults(output="google_random_queries.html", limit=50)  
(options, args) = parser.parse_args()  
if len(args) != 1:  
print >>sys.stderr, parser.format_help()  
print >>sys.stderr, "\n\nYou must provide the path of a single feed\n\n"  
sys.exit(2)  
feed_path = args[0]  
 
# ProblemReporter prints problems on console.  
loader = transitfeed.Loader(feed_path, problems=transitfeed.ProblemReporter(),  
load_stop_times=False)  
schedule = loader.Load()  
locations = GetRandomLocationsNearStops(schedule)  
random.shuffle(locations)  
agencies = ", ".join([a.agency_name for a in schedule.GetAgencyList()])  
title = "%s (%s)" % (agencies, ParentAndBaseName(feed_path))  
 
WriteOutput(title,  
locations,  
options.limit,  
open(options.output, "w"))  
print ("Load %s in your web browser. It contains more instructions." %  
options.output)  
 
 
if __name__ == "__main__":  
main()  
 
#!/usr/bin/python2.5  
 
# Copyright (C) 2007 Google Inc.  
#  
# Licensed under the Apache License, Version 2.0 (the "License");  
# you may not use this file except in compliance with the License.  
# You may obtain a copy of the License at  
#  
# http://www.apache.org/licenses/LICENSE-2.0  
#  
# Unless required by applicable law or agreed to in writing, software  
# distributed under the License is distributed on an "AS IS" BASIS,  
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.  
# See the License for the specific language governing permissions and  
# limitations under the License.  
 
"""Google has a homegrown database for managing the company shuttle. The  
database dumps its contents in XML. This scripts converts the proprietary XML  
format into a Google Transit Feed Specification file.  
"""  
 
import datetime  
from optparse import OptionParser  
import os.path  
import re  
import transitfeed  
import urllib  
 
try:  
import xml.etree.ElementTree as ET # python 2.5  
except ImportError, e:  
import elementtree.ElementTree as ET # older pythons  
 
 
class NoUnusedStopExceptionProblemReporter(  
transitfeed.ExceptionProblemReporter):  
"""The company shuttle database has a few unused stops for reasons unrelated  
to this script. Ignore them.  
"""  
def UnusedStop(self, stop_id, stop_name):  
pass  
 
def SaveFeed(input, output):  
tree = ET.parse(urllib.urlopen(input))  
 
schedule = transitfeed.Schedule()  
service_period = schedule.GetDefaultServicePeriod()  
service_period.SetWeekdayService()  
service_period.SetStartDate('20070314')  
service_period.SetEndDate('20071231')  
# Holidays for 2007  
service_period.SetDateHasService('20070528', has_service=False)  
service_period.SetDateHasService('20070704', has_service=False)  
service_period.SetDateHasService('20070903', has_service=False)  
service_period.SetDateHasService('20071122', has_service=False)  
service_period.SetDateHasService('20071123', has_service=False)  
service_period.SetDateHasService('20071224', has_service=False)  
service_period.SetDateHasService('20071225', has_service=False)  
service_period.SetDateHasService('20071226', has_service=False)  
service_period.SetDateHasService('20071231', has_service=False)  
 
stops = {} # Map from xml stop id to python Stop object  
agency = schedule.NewDefaultAgency(name='GBus', url='http://shuttle/',  
timezone='America/Los_Angeles')  
 
for xml_stop in tree.getiterator('stop'):  
stop = schedule.AddStop(lat=float(xml_stop.attrib['lat']),  
lng=float(xml_stop.attrib['lng']),  
name=xml_stop.attrib['name'])  
stops[xml_stop.attrib['id']] = stop  
 
for xml_shuttleGroup in tree.getiterator('shuttleGroup'):  
if xml_shuttleGroup.attrib['name'] == 'Test':  
continue  
r = schedule.AddRoute(short_name="",  
long_name=xml_shuttleGroup.attrib['name'], route_type='Bus')  
for xml_route in xml_shuttleGroup.getiterator('route'):  
t = r.AddTrip(schedule=schedule, headsign=xml_route.attrib['name'],  
trip_id=xml_route.attrib['id'])  
trip_stops = [] # Build a list of (time, Stop) tuples  
for xml_schedule in xml_route.getiterator('schedule'):  
trip_stops.append( (int(xml_schedule.attrib['time']) / 1000,  
stops[xml_schedule.attrib['stopId']]) )  
trip_stops.sort() # Sort by time  
for (time, stop) in trip_stops:  
t.AddStopTime(stop=stop, arrival_secs=time, departure_secs=time)  
 
schedule.Validate(problems=NoUnusedStopExceptionProblemReporter())  
schedule.WriteGoogleTransitFeed(output)  
 
 
def main():  
parser = OptionParser()  
parser.add_option('--input', dest='input',  
help='Path or URL of input')  
parser.add_option('--output', dest='output',  
help='Path of output file. Should end in .zip and if it '  
'contains the substring YYYYMMDD it will be replaced with '  
'today\'s date. It is impossible to include the literal '  
'string YYYYYMMDD in the path of the output file.')  
parser.add_option('--execute', dest='execute',  
help='Commands to run to copy the output. %(path)s is '  
'replaced with full path of the output and %(name)s is '  
'replaced with name part of the path. Try '  
'scp %(path)s myhost:www/%(name)s',  
action='append')  
parser.set_defaults(input=None, output=None, execute=[])  
(options, args) = parser.parse_args()  
 
today = datetime.date.today().strftime('%Y%m%d')  
options.output = re.sub(r'YYYYMMDD', today, options.output)  
(_, name) = os.path.split(options.output)  
path = options.output  
 
SaveFeed(options.input, options.output)  
 
for command in options.execute:  
import subprocess  
def check_call(cmd):  
"""Convenience function that is in the docs for subprocess but not  
installed on my system."""  
retcode = subprocess.call(cmd, shell=True)  
if retcode < 0:  
raise Exception("Child '%s' was terminated by signal %d" % (cmd,  
-retcode))  
elif retcode != 0:  
raise Exception("Child '%s' returned %d" % (cmd, retcode))  
 
# path_output and filename_current can be used to run arbitrary commands  
check_call(command % locals())  
 
if __name__ == '__main__':  
main()  
 
<shuttle><office id="us-nye" name="US Nye County">  
<stops>  
<stop id="1" name="Stagecoach Hotel and Casino" shortName="Stagecoach" lat="36.915682" lng="-116.751677" />  
<stop id="2" name="North Ave / N A Ave" shortName="N Ave / A Ave N" lat="36.914944" lng="-116.761472" />  
<stop id="3" name="North Ave / D Ave N" shortName="N Ave / D Ave N" lat="36.914893" lng="-116.76821" />  
<stop id="4" name="Doing Ave / D Ave N" shortName="Doing / D Ave N" lat="36.909489" lng="-116.768242" />  
<stop id="5" name="E Main St / S Irving St" shortName="E Main / S Irving" lat="36.905697" lng="-116.76218" />  
</stops>  
<shuttleGroups>  
<shuttleGroup id="4" name="Bar Circle Loop" >  
<routes>  
<route id="1" name="Outbound">  
<schedules>  
<schedule id="164" stopId="1" time="60300000"/>  
<schedule id="165" stopId="2" time="60600000"/>  
<schedule id="166" stopId="3" time="60720000"/>  
<schedule id="167" stopId="4" time="60780000"/>  
<schedule id="168" stopId="5" time="60900000"/>  
</schedules><meta></meta></route>  
<route id="2" name="Inbound">  
<schedules>  
<schedule id="260" stopId="5" time="30000000"/>  
<schedule id="261" stopId="4" time="30120000"/>  
<schedule id="262" stopId="3" time="30180000"/>  
<schedule id="263" stopId="2" time="30300000"/>  
<schedule id="264" stopId="1" time="30600000"/>  
</schedules><meta></meta></route></routes>  
</shuttleGroup>  
</shuttleGroups></office></shuttle>  
 
#!/usr/bin/python2.5  
 
# A really simple example of using transitfeed to build a Google Transit  
# Feed Specification file.  
 
import transitfeed  
from optparse import OptionParser  
 
 
parser = OptionParser()  
parser.add_option('--output', dest='output',  
help='Path of output file. Should end in .zip')  
parser.set_defaults(output='google_transit.zip')  
(options, args) = parser.parse_args()  
 
schedule = transitfeed.Schedule()  
schedule.AddAgency("Fly Agency", "http://iflyagency.com",  
"America/Los_Angeles")  
 
service_period = schedule.GetDefaultServicePeriod()  
service_period.SetWeekdayService(True)  
service_period.SetDateHasService('20070704')  
 
stop1 = schedule.AddStop(lng=-122, lat=37.2, name="Suburbia")  
stop2 = schedule.AddStop(lng=-122.001, lat=37.201, name="Civic Center")  
 
route = schedule.AddRoute(short_name="22", long_name="Civic Center Express",  
route_type="Bus")  
 
trip = route.AddTrip(schedule, headsign="To Downtown")  
trip.AddStopTime(stop1, stop_time='09:00:00')  
trip.AddStopTime(stop2, stop_time='09:15:00')  
 
trip = route.AddTrip(schedule, headsign="To Suburbia")  
trip.AddStopTime(stop1, stop_time='17:30:00')  
trip.AddStopTime(stop2, stop_time='17:45:00')  
 
schedule.Validate()  
schedule.WriteGoogleTransitFeed(options.output)  
 
#!/usr/bin/python2.5  
 
# Copyright (C) 2007 Google Inc.  
#  
# Licensed under the Apache License, Version 2.0 (the "License");  
# you may not use this file except in compliance with the License.  
# You may obtain a copy of the License at  
#  
# http://www.apache.org/licenses/LICENSE-2.0  
#  
# Unless required by applicable law or agreed to in writing, software  
# distributed under the License is distributed on an "AS IS" BASIS,  
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.  
# See the License for the specific language governing permissions and  
# limitations under the License.  
 
# An example script that demonstrates converting a proprietary format to a  
# Google Transit Feed Specification file.  
#  
# You can load table.txt, the example input, in Excel. It contains three  
# sections:  
# 1) A list of global options, starting with a line containing the word  
# 'options'. Each option has an name in the first column and most options  
# have a value in the second column.  
# 2) A table of stops, starting with a line containing the word 'stops'. Each  
# row of the table has 3 columns: name, latitude, longitude  
# 3) A list of routes. There is an empty row between each route. The first row  
# for a route lists the short_name and long_name. After the first row the  
# left-most column lists the stop names visited by the route. Each column  
# contains the times a single trip visits the stops.  
#  
# This is very simple example which you could use as a base for your own  
# transit feed builder.  
 
import transitfeed  
from optparse import OptionParser  
import re  
 
stops = {}  
 
# table is a list of lists in this form  
# [ ['Short Name', 'Long Name'],  
# ['Stop 1', 'Stop 2', ...]  
# [time_at_1, time_at_2, ...] # times for trip 1  
# [time_at_1, time_at_2, ...] # times for trip 2  
# ... ]  
def AddRouteToSchedule(schedule, table):  
if len(table) >= 2:  
r = schedule.AddRoute(short_name=table[0][0], long_name=table[0][1], route_type='Bus')  
for trip in table[2:]:  
if len(trip) > len(table[1]):  
print "ignoring %s" % trip[len(table[1]):]  
trip = trip[0:len(table[1])]  
t = r.AddTrip(schedule, headsign='My headsign')  
trip_stops = [] # Build a list of (time, stopname) tuples  
for i in range(0, len(trip)):  
if re.search(r'\S', trip[i]):  
trip_stops.append( (transitfeed.TimeToSecondsSinceMidnight(trip[i]), table[1][i]) )  
trip_stops.sort() # Sort by time  
for (time, stopname) in trip_stops:  
t.AddStopTime(stop=stops[stopname.lower()], arrival_secs=time,  
departure_secs=time)  
 
def TransposeTable(table):  
"""Transpose a list of lists, using None to extend all input lists to the  
same length.  
 
For example:  
>>> TransposeTable(  
[ [11, 12, 13],  
[21, 22],  
[31, 32, 33, 34]])  
 
[ [11, 21, 31],  
[12, 22, 32],  
[13, None, 33],  
[None, None, 34]]  
"""  
transposed = []  
rows = len(table)  
cols = max(len(row) for row in table)  
for x in range(cols):  
transposed.append([])  
for y in range(rows):  
if x < len(table[y]):  
transposed[x].append(table[y][x])  
else:  
transposed[x].append(None)  
return transposed  
 
def ProcessOptions(schedule, table):  
service_period = schedule.GetDefaultServicePeriod()  
agency_name, agency_url, agency_timezone = (None, None, None)  
 
for row in table[1:]:  
command = row[0].lower()  
if command == 'weekday':  
service_period.SetWeekdayService()  
elif command == 'start_date':  
service_period.SetStartDate(row[1])  
elif command == 'end_date':  
service_period.SetEndDate(row[1])  
elif command == 'add_date':  
service_period.SetDateHasService(date=row[1])  
elif command == 'remove_date':  
service_period.SetDateHasService(date=row[1], has_service=False)  
elif command == 'agency_name':  
agency_name = row[1]  
elif command == 'agency_url':  
agency_url = row[1]  
elif command == 'agency_timezone':  
agency_timezone = row[1]  
 
if not (agency_name and agency_url and agency_timezone):  
print "You must provide agency information"  
 
schedule.NewDefaultAgency(agency_name=agency_name, agency_url=agency_url,  
agency_timezone=agency_timezone)  
 
 
def AddStops(schedule, table):  
for name, lat_str, lng_str in table[1:]:  
stop = schedule.AddStop(lat=float(lat_str), lng=float(lng_str), name=name)  
stops[name.lower()] = stop  
 
 
def ProcessTable(schedule, table):  
if table[0][0].lower() == 'options':  
ProcessOptions(schedule, table)  
elif table[0][0].lower() == 'stops':  
AddStops(schedule, table)  
else:  
transposed = [table[0]] # Keep route_short_name and route_long_name on first row  
 
# Transpose rest of table. Input contains the stop names in table[x][0], x  
# >= 1 with trips found in columns, so we need to transpose table[1:].  
# As a diagram Transpose from  
# [['stop 1', '10:00', '11:00', '12:00'],  
# ['stop 2', '10:10', '11:10', '12:10'],  
# ['stop 3', '10:20', '11:20', '12:20']]  
# to  
# [['stop 1', 'stop 2', 'stop 3'],  
# ['10:00', '10:10', '10:20'],  
# ['11:00', '11:11', '11:20'],  
# ['12:00', '12:12', '12:20']]  
transposed.extend(TransposeTable(table[1:]))  
AddRouteToSchedule(schedule, transposed)  
 
 
def main():  
parser = OptionParser()  
parser.add_option('--input', dest='input',  
help='Path of input file')  
parser.add_option('--output', dest='output',  
help='Path of output file, should end in .zip')  
parser.set_defaults(output='feed.zip')  
(options, args) = parser.parse_args()  
 
schedule = transitfeed.Schedule()  
 
table = []  
for line in open(options.input):  
line = line.rstrip()  
if not line:  
ProcessTable(schedule, table)  
table = []  
else:  
table.append(line.split('\t'))  
 
ProcessTable(schedule, table)  
 
schedule.WriteGoogleTransitFeed(options.output)  
 
 
if __name__ == '__main__':  
main()  
 
options  
weekday  
start_date 20070315  
end_date 20071215  
remove_date 20070704  
agency_name Gbus  
agency_url http://shuttle/  
agency_timezone America/Los_Angeles  
 
stops  
Stagecoach 36.915682 -116.751677  
N Ave / A Ave N 36.914944 -116.761472  
N Ave / D Ave N 36.914893 -116.76821  
Doing / D Ave N 36.909489 -116.768242  
E Main / S Irving 36.905697 -116.76218  
 
O in Bar Circle Inbound  
Stagecoach 9:00:00 9:30:00 10:00:00 12:00:00  
N Ave / A Ave N 9:05:00 9:35:00 10:05:00 12:05:00  
N Ave / D Ave N 9:07:00 9:37:00 10:07:00 12:07:00  
Doing / D Ave N 9:09:00 9:39:00 10:09:00 12:09:00  
E Main / S Irving 9:11:00 9:41:00 10:11:00 12:11:00  
 
O out Bar Circle Outbound  
E Main / S Irving 15:00:00 15:30:00 16:00:00 18:00:00  
Doing / D Ave N 15:05:00 15:35:00 16:05:00 18:05:00  
N Ave / D Ave N 15:07:00 15:37:00 16:07:00 18:07:00  
N Ave / A Ave N 15:09:00 15:39:00 16:09:00 18:09:00  
Stagecoach 15:11:00 15:41:00 16:11:00 18:11:00  
 
#!/usr/bin/python2.5  
 
# Copyright (C) 2007 Google Inc.  
#  
# Licensed under the Apache License, Version 2.0 (the "License");  
# you may not use this file except in compliance with the License.  
# You may obtain a copy of the License at  
#  
# http://www.apache.org/licenses/LICENSE-2.0  
#  
# Unless required by applicable law or agreed to in writing, software  
# distributed under the License is distributed on an "AS IS" BASIS,  
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.  
# See the License for the specific language governing permissions and  
# limitations under the License.  
 
 
"""Validates a GTFS file.  
 
For usage information run feedvalidator.py --help  
"""  
 
import bisect  
import codecs  
import datetime  
from transitfeed.util import defaultdict  
import optparse  
import os  
import os.path  
import re  
import socket  
import sys  
import time  
import transitfeed  
from transitfeed import TYPE_ERROR, TYPE_WARNING  
from urllib2 import Request, urlopen, HTTPError, URLError  
from transitfeed import util  
import webbrowser  
 
SVN_TAG_URL = 'http://googletransitdatafeed.googlecode.com/svn/tags/'  
 
 
def MaybePluralizeWord(count, word):  
if count == 1:  
return word  
else:  
return word + 's'  
 
 
def PrettyNumberWord(count, word):  
return '%d %s' % (count, MaybePluralizeWord(count, word))  
 
 
def UnCamelCase(camel):  
return re.sub(r'([a-z])([A-Z])', r'\1 \2', camel)  
 
 
def ProblemCountText(error_count, warning_count):  
results = []  
if error_count:  
results.append(PrettyNumberWord(error_count, 'error'))  
if warning_count:  
results.append(PrettyNumberWord(warning_count, 'warning'))  
 
return ' and '.join(results)  
 
 
def CalendarSummary(schedule):  
today = datetime.date.today()  
summary_end_date = today + datetime.timedelta(days=60)  
start_date, end_date = schedule.GetDateRange()  
 
if not start_date or not end_date:  
return {}  
 
try:  
start_date_object = transitfeed.DateStringToDateObject(start_date)  
end_date_object = transitfeed.DateStringToDateObject(end_date)  
except ValueError:  
return {}  
 
# Get the list of trips only during the period the feed is active.  
# As such we have to check if it starts in the future and/or if  
# if it ends in less than 60 days.  
date_trips_departures = schedule.GenerateDateTripsDeparturesList(  
max(today, start_date_object),  
min(summary_end_date, end_date_object))  
 
if not date_trips_departures:  
return {}  
 
# Check that the dates which will be shown in summary agree with these  
# calculations. Failure implies a bug which should be fixed. It isn't good  
# for users to discover assertion failures but means it will likely be fixed.  
assert start_date <= date_trips_departures[0][0].strftime("%Y%m%d")  
assert end_date >= date_trips_departures[-1][0].strftime("%Y%m%d")  
 
# Generate a map from int number of trips in a day to a list of date objects  
# with that many trips. The list of dates is sorted.  
trips_dates = defaultdict(lambda: [])  
trips = 0  
for date, day_trips, day_departures in date_trips_departures:  
trips += day_trips  
trips_dates[day_trips].append(date)  
mean_trips = trips / len(date_trips_departures)  
max_trips = max(trips_dates.keys())  
min_trips = min(trips_dates.keys())  
 
calendar_summary = {}  
calendar_summary['mean_trips'] = mean_trips  
calendar_summary['max_trips'] = max_trips  
calendar_summary['max_trips_dates'] = FormatDateList(trips_dates[max_trips])  
calendar_summary['min_trips'] = min_trips  
calendar_summary['min_trips_dates'] = FormatDateList(trips_dates[min_trips])  
calendar_summary['date_trips_departures'] = date_trips_departures  
calendar_summary['date_summary_range'] = "%s to %s" % (  
date_trips_departures[0][0].strftime("%a %b %d"),  
date_trips_departures[-1][0].strftime("%a %b %d"))  
 
return calendar_summary  
 
 
def FormatDateList(dates):  
if not dates:  
return "0 service dates"  
 
formatted = [d.strftime("%a %b %d") for d in dates[0:3]]  
if len(dates) > 3:  
formatted.append("...")  
return "%s (%s)" % (PrettyNumberWord(len(dates), "service date"),  
", ".join(formatted))  
 
 
def MaxVersion(versions):  
versions = filter(None, versions)  
versions.sort(lambda x,y: -cmp([int(item) for item in x.split('.')],  
[int(item) for item in y.split('.')]))  
if len(versions) > 0:  
return versions[0]  
 
 
class CountingConsoleProblemReporter(transitfeed.ProblemReporter):  
def __init__(self):  
transitfeed.ProblemReporter.__init__(self)  
self._error_count = 0  
self._warning_count = 0  
 
def _Report(self, e):  
transitfeed.ProblemReporter._Report(self, e)  
if e.IsError():  
self._error_count += 1  
else:  
self._warning_count += 1  
 
def ErrorCount(self):  
return self._error_count  
 
def WarningCount(self):  
return self._warning_count  
 
def FormatCount(self):  
return ProblemCountText(self.ErrorCount(), self.WarningCount())  
 
def HasIssues(self):  
return self.ErrorCount() or self.WarningCount()  
 
 
class BoundedProblemList(object):  
"""A list of one type of ExceptionWithContext objects with bounded size."""  
def __init__(self, size_bound):  
self._count = 0  
self._exceptions = []  
self._size_bound = size_bound  
 
def Add(self, e):  
self._count += 1  
try:  
bisect.insort(self._exceptions, e)  
except TypeError:  
# The base class ExceptionWithContext raises this exception in __cmp__  
# to signal that an object is not comparable. Instead of keeping the most  
# significant issue keep the first reported.  
if self._count <= self._size_bound:  
self._exceptions.append(e)  
else:  
# self._exceptions is in order. Drop the least significant if the list is  
# now too long.  
if self._count > self._size_bound:  
del self._exceptions[-1]  
 
def _GetDroppedCount(self):  
return self._count - len(self._exceptions)  
 
def __repr__(self):  
return "<BoundedProblemList %s>" % repr(self._exceptions)  
 
count = property(lambda s: s._count)  
dropped_count = property(_GetDroppedCount)  
problems = property(lambda s: s._exceptions)  
 
 
class LimitPerTypeProblemReporter(transitfeed.ProblemReporter):  
def __init__(self, limit_per_type):  
transitfeed.ProblemReporter.__init__(self)  
 
# {TYPE_WARNING: {"ClassName": BoundedProblemList()}}  
self._type_to_name_to_problist = {  
TYPE_WARNING: defaultdict(lambda: BoundedProblemList(limit_per_type)),  
TYPE_ERROR: defaultdict(lambda: BoundedProblemList(limit_per_type))  
}  
 
def HasIssues(self):  
return (self._type_to_name_to_problist[TYPE_ERROR] or  
self._type_to_name_to_problist[TYPE_WARNING])  
 
def _Report(self, e):  
self._type_to_name_to_problist[e.GetType()][e.__class__.__name__].Add(e)  
 
def ErrorCount(self):  
error_sets = self._type_to_name_to_problist[TYPE_ERROR].values()  
return sum(map(lambda v: v.count, error_sets))  
 
def WarningCount(self):  
warning_sets = self._type_to_name_to_problist[TYPE_WARNING].values()  
return sum(map(lambda v: v.count, warning_sets))  
 
def ProblemList(self, problem_type, class_name):  
"""Return the BoundedProblemList object for given type and class."""  
return self._type_to_name_to_problist[problem_type][class_name]  
 
def ProblemListMap(self, problem_type):  
"""Return the map from class name to BoundedProblemList object."""  
return self._type_to_name_to_problist[problem_type]  
 
 
class HTMLCountingProblemReporter(LimitPerTypeProblemReporter):  
def FormatType(self, f, level_name, class_problist):  
"""Write the HTML dumping all problems of one type.  
 
Args:  
f: file object open for writing  
level_name: string such as "Error" or "Warning"  
class_problist: sequence of tuples (class name,  
BoundedProblemList object)  
"""  
class_problist.sort()  
output = []  
for classname, problist in class_problist:  
output.append('<h4 class="issueHeader"><a name="%s%s">%s</a></h4><ul>\n' %  
(level_name, classname, UnCamelCase(classname)))  
for e in problist.problems:  
self.FormatException(e, output)  
if problist.dropped_count:  
output.append('<li>and %d more of this type.' %  
(problist.dropped_count))  
output.append('</ul>\n')  
f.write(''.join(output))  
 
def FormatTypeSummaryTable(self, level_name, name_to_problist):  
"""Return an HTML table listing the number of problems by class name.  
 
Args:  
level_name: string such as "Error" or "Warning"  
name_to_problist: dict mapping class name to an BoundedProblemList object  
 
Returns:  
HTML in a string  
"""  
output = []  
output.append('<table>')  
for classname in sorted(name_to_problist.keys()):  
problist = name_to_problist[classname]  
human_name = MaybePluralizeWord(problist.count, UnCamelCase(classname))  
output.append('<tr><td>%d</td><td><a href="#%s%s">%s</a></td></tr>\n' %  
(problist.count, level_name, classname, human_name))  
output.append('</table>\n')  
return ''.join(output)  
 
def FormatException(self, e, output):  
"""Append HTML version of e to list output."""  
d = e.GetDictToFormat()  
for k in ('file_name', 'feedname', 'column_name'):  
if k in d.keys():  
d[k] = '<code>%s</code>' % d[k]  
problem_text = e.FormatProblem(d).replace('\n', '<br>')  
output.append('<li>')  
output.append('<div class="problem">%s</div>' %  
transitfeed.EncodeUnicode(problem_text))  
try:  
if hasattr(e, 'row_num'):  
line_str = 'line %d of ' % e.row_num  
else:  
line_str = ''  
output.append('in %s<code>%s</code><br>\n' %  
(line_str, e.file_name))  
row = e.row  
headers = e.headers  
column_name = e.column_name  
table_header = '' # HTML  
table_data = '' # HTML  
for header, value in zip(headers, row):  
attributes = ''  
if header == column_name:  
attributes = ' class="problem"'  
table_header += '<th%s>%s</th>' % (attributes, header)  
table_data += '<td%s>%s</td>' % (attributes, value)  
# Make sure output is encoded into UTF-8  
output.append('<table class="dump"><tr>%s</tr>\n' %  
transitfeed.EncodeUnicode(table_header))  
output.append('<tr>%s</tr></table>\n' %  
transitfeed.EncodeUnicode(table_data))  
except AttributeError, e:  
pass # Hope this was getting an attribute from e ;-)  
output.append('<br></li>\n')  
 
def FormatCount(self):  
return ProblemCountText(self.ErrorCount(), self.WarningCount())  
 
def CountTable(self):  
output = []  
output.append('<table class="count_outside">\n')  
output.append('<tr>')  
if self.ProblemListMap(TYPE_ERROR):  
output.append('<td><span class="fail">%s</span></td>' %  
PrettyNumberWord(self.ErrorCount(), "error"))  
if self.ProblemListMap(TYPE_WARNING):  
output.append('<td><span class="fail">%s</span></td>' %  
PrettyNumberWord(self.WarningCount(), "warning"))  
output.append('</tr>\n<tr>')  
if self.ProblemListMap(TYPE_ERROR):  
output.append('<td>\n')  
output.append(self.FormatTypeSummaryTable("Error",  
self.ProblemListMap(TYPE_ERROR)))  
output.append('</td>\n')  
if self.ProblemListMap(TYPE_WARNING):  
output.append('<td>\n')  
output.append(self.FormatTypeSummaryTable("Warning",  
self.ProblemListMap(TYPE_WARNING)))  
output.append('</td>\n')  
output.append('</table>')  
return ''.join(output)  
 
def WriteOutput(self, feed_location, f, schedule, other_problems):  
"""Write the html output to f."""  
if self.HasIssues():  
if self.ErrorCount() + self.WarningCount() == 1:  
summary = ('<span class="fail">Found this problem:</span>\n%s' %  
self.CountTable())  
else:  
summary = ('<span class="fail">Found these problems:</span>\n%s' %  
self.CountTable())  
else:  
summary = '<span class="pass">feed validated successfully</span>'  
if other_problems is not None:  
summary = ('<span class="fail">\n%s</span><br><br>' %  
other_problems) + summary  
 
basename = os.path.basename(feed_location)  
feed_path = (feed_location[:feed_location.rfind(basename)], basename)  
 
agencies = ', '.join(['<a href="%s">%s</a>' % (a.agency_url, a.agency_name)  
for a in schedule.GetAgencyList()])  
if not agencies:  
agencies = '?'  
 
dates = "No valid service dates found"  
(start, end) = schedule.GetDateRange()  
if start and end:  
def FormatDate(yyyymmdd):  
src_format = "%Y%m%d"  
dst_format = "%B %d, %Y"  
try:  
return time.strftime(dst_format,  
time.strptime(yyyymmdd, src_format))  
except ValueError:  
return yyyymmdd  
 
formatted_start = FormatDate(start)  
formatted_end = FormatDate(end)  
dates = "%s to %s" % (formatted_start, formatted_end)  
 
calendar_summary = CalendarSummary(schedule)  
if calendar_summary:  
calendar_summary_html = """<br>  
During the upcoming service dates %(date_summary_range)s:  
<table>  
<tr><th class="header">Average trips per date:</th><td class="header">%(mean_trips)s</td></tr>  
<tr><th class="header">Most trips on a date:</th><td class="header">%(max_trips)s, on %(max_trips_dates)s</td></tr>  
<tr><th class="header">Least trips on a date:</th><td class="header">%(min_trips)s, on %(min_trips_dates)s</td></tr>  
</table>""" % calendar_summary  
else:  
calendar_summary_html = ""  
 
output_prefix = """  
<html>  
<head>  
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">  
<title>FeedValidator: %(feed_file)s</title>  
<style>  
body {font-family: Georgia, serif; background-color: white}  
.path {color: gray}  
div.problem {max-width: 500px}  
table.dump td,th {background-color: khaki; padding: 2px; font-family:monospace}  
table.dump td.problem,th.problem {background-color: dc143c; color: white; padding: 2px; font-family:monospace}  
table.count_outside td {vertical-align: top}  
table.count_outside {border-spacing: 0px; }  
table {border-spacing: 5px 0px; margin-top: 3px}  
h3.issueHeader {padding-left: 0.5em}  
h4.issueHeader {padding-left: 1em}  
.pass {background-color: lightgreen}  
.fail {background-color: yellow}  
.pass, .fail {font-size: 16pt}  
.header {background-color: white; font-family: Georgia, serif; padding: 0px}  
th.header {text-align: right; font-weight: normal; color: gray}  
.footer {font-size: 10pt}  
</style>  
</head>  
<body>  
GTFS validation results for feed:<br>  
<code><span class="path">%(feed_dir)s</span><b>%(feed_file)s</b></code>  
<br><br>  
<table>  
<tr><th class="header">Agencies:</th><td class="header">%(agencies)s</td></tr>  
<tr><th class="header">Routes:</th><td class="header">%(routes)s</td></tr>  
<tr><th class="header">Stops:</th><td class="header">%(stops)s</td></tr>  
<tr><th class="header">Trips:</th><td class="header">%(trips)s</td></tr>  
<tr><th class="header">Shapes:</th><td class="header">%(shapes)s</td></tr>  
<tr><th class="header">Effective:</th><td class="header">%(dates)s</td></tr>  
</table>  
%(calendar_summary)s  
<br>  
%(problem_summary)s  
<br><br>  
""" % { "feed_file": feed_path[1],  
"feed_dir": feed_path[0],  
"agencies": agencies,  
"routes": len(schedule.GetRouteList()),  
"stops": len(schedule.GetStopList()),  
"trips": len(schedule.GetTripList()),  
"shapes": len(schedule.GetShapeList()),  
"dates": dates,  
"problem_summary": summary,  
"calendar_summary": calendar_summary_html}  
 
# In output_suffix string  
# time.strftime() returns a regular local time string (not a Unicode one) with  
# default system encoding. And decode() will then convert this time string back  
# into a Unicode string. We use decode() here because we don't want the operating  
# system to do any system encoding (which may cause some problem if the string  
# contains some non-English characters) for the string. Therefore we decode it  
# back to its original Unicode code print.  
 
time_unicode = (time.strftime('%B %d, %Y at %I:%M %p %Z').  
decode(sys.getfilesystemencoding()))  
output_suffix = """  
<div class="footer">  
Generated by <a href="http://code.google.com/p/googletransitdatafeed/wiki/FeedValidator">  
FeedValidator</a> version %s on %s.  
</div>  
</body>  
</html>""" % (transitfeed.__version__, time_unicode)  
 
f.write(transitfeed.EncodeUnicode(output_prefix))  
if self.ProblemListMap(TYPE_ERROR):  
f.write('<h3 class="issueHeader">Errors:</h3>')  
self.FormatType(f, "Error",  
self.ProblemListMap(TYPE_ERROR).items())  
if self.ProblemListMap(TYPE_WARNING):  
f.write('<h3 class="issueHeader">Warnings:</h3>')  
self.FormatType(f, "Warning",  
self.ProblemListMap(TYPE_WARNING).items())  
f.write(transitfeed.EncodeUnicode(output_suffix))  
 
 
def RunValidationOutputFromOptions(feed, options):  
"""Validate feed, output results per options and return an exit code."""  
if options.output.upper() == "CONSOLE":  
return RunValidationOutputToConsole(feed, options)  
else:  
return RunValidationOutputToFilename(feed, options, options.output)  
 
 
def RunValidationOutputToFilename(feed, options, output_filename):  
"""Validate feed, save HTML at output_filename and return an exit code."""  
try:  
output_file = open(output_filename, 'w')  
exit_code = RunValidationOutputToFile(feed, options, output_file)  
output_file.close()  
except IOError, e:  
print 'Error while writing %s: %s' % (output_filename, e)  
output_filename = None  
exit_code = 2  
 
if options.manual_entry and output_filename:  
webbrowser.open('file://%s' % os.path.abspath(output_filename))  
 
return exit_code  
 
 
def RunValidationOutputToFile(feed, options, output_file):  
"""Validate feed, write HTML to output_file and return an exit code."""  
problems = HTMLCountingProblemReporter(options.limit_per_type)  
schedule, exit_code, other_problems_string = RunValidation(feed, options,  
problems)  
if isinstance(feed, basestring):  
feed_location = feed  
else:  
feed_location = getattr(feed, 'name', repr(feed))  
problems.WriteOutput(feed_location, output_file, schedule,  
other_problems_string)  
return exit_code  
 
 
def RunValidationOutputToConsole(feed, options):  
"""Validate feed, print reports and return an exit code."""  
problems = CountingConsoleProblemReporter()  
_, exit_code, _ = RunValidation(feed, options, problems)  
return exit_code  
 
 
def RunValidation(feed, options, problems):  
"""Validate feed, returning the loaded Schedule and exit code.  
 
Args:  
feed: GTFS file, either path of the file as a string or a file object  
options: options object returned by optparse  
problems: transitfeed.ProblemReporter instance  
 
Returns:  
a transitfeed.Schedule object, exit code and plain text string of other  
problems  
Exit code is 1 if problems are found and 0 if the Schedule is problem free.  
plain text string is '' if no other problems are found.  
"""  
other_problems_string = CheckVersion(latest_version=options.latest_version)  
print 'validating %s' % feed  
loader = transitfeed.Loader(feed, problems=problems, extra_validation=False,  
memory_db=options.memory_db,  
check_duplicate_trips=\  
options.check_duplicate_trips)  
schedule = loader.Load()  
schedule.Validate(service_gap_interval=options.service_gap_interval)  
 
if feed == 'IWantMyvalidation-crash.txt':  
# See test/testfeedvalidator.py  
raise Exception('For testing the feed validator crash handler.')  
 
if other_problems_string:  
print other_problems_string  
 
if problems.HasIssues():  
print 'ERROR: %s found' % problems.FormatCount()  
return schedule, 1, other_problems_string  
else:  
print 'feed validated successfully'  
return schedule, 0, other_problems_string  
 
 
def CheckVersion(latest_version=''):  
"""  
Check there is newer version of this project.  
 
Codes are based on http://www.voidspace.org.uk/python/articles/urllib2.shtml  
Already got permission from the copyright holder.  
"""  
current_version = transitfeed.__version__  
if not latest_version:  
timeout = 20  
socket.setdefaulttimeout(timeout)  
request = Request(SVN_TAG_URL)  
 
try:  
response = urlopen(request)  
content = response.read()  
versions = re.findall(r'>transitfeed-([\d\.]+)\/<\/a>', content)  
latest_version = MaxVersion(versions)  
 
except HTTPError, e:  
return('The server couldn\'t fulfill the request. Error code: %s.'  
% e.code)  
except URLError, e:  
return('We failed to reach transitfeed server. Reason: %s.' % e.reason)  
 
if not latest_version:  
return('We had trouble parsing the contents of %s.' % SVN_TAG_URL)  
 
newest_version = MaxVersion([latest_version, current_version])  
if current_version != newest_version:  
return('A new version %s of transitfeed is available. Please visit '  
'http://code.google.com/p/googletransitdatafeed and download.'  
% newest_version)  
 
 
def main():  
usage = \  
'''%prog [options] [<input GTFS.zip>]  
 
Validates GTFS file (or directory) <input GTFS.zip> and writes a HTML  
report of the results to validation-results.html.  
 
If <input GTFS.zip> is ommited the filename is read from the console. Dragging  
a file into the console may enter the filename.  
 
For more information see  
http://code.google.com/p/googletransitdatafeed/wiki/FeedValidator  
'''  
 
parser = util.OptionParserLongError(  
usage=usage, version='%prog '+transitfeed.__version__)  
parser.add_option('-n', '--noprompt', action='store_false',  
dest='manual_entry',  
help='do not prompt for feed location or load output in '  
'browser')  
parser.add_option('-o', '--output', dest='output', metavar='FILE',  
help='write html output to FILE or --output=CONSOLE to '  
'print all errors and warnings to the command console')  
parser.add_option('-p', '--performance', action='store_true',  
dest='performance',  
help='output memory and time performance (Availability: '  
'Unix')  
parser.add_option('-m', '--memory_db', dest='memory_db', action='store_true',  
help='Use in-memory sqlite db instead of a temporary file. '  
'It is faster but uses more RAM.')  
parser.add_option('-d', '--duplicate_trip_check',  
dest='check_duplicate_trips', action='store_true',  
help='Check for duplicate trips which go through the same '  
'stops with same service and start times')  
parser.add_option('-l', '--limit_per_type',  
dest='limit_per_type', action='store', type='int',  
help='Maximum number of errors and warnings to keep of '  
'each type')  
parser.add_option('--latest_version', dest='latest_version',  
action='store',  
help='a version number such as 1.2.1 or None to get the '  
'latest version from code.google.com. Output a warning if '  
'transitfeed.py is older than this version.')  
parser.add_option('--service_gap_interval',  
dest='service_gap_interval',  
action='store',  
type='int',  
help='the number of consecutive days to search for with no '  
'scheduled service. For each interval with no service '  
'having this number of days or more a warning will be '  
'issued')  
 
parser.set_defaults(manual_entry=True, output='validation-results.html',  
memory_db=False, check_duplicate_trips=False,  
limit_per_type=5, latest_version='',  
service_gap_interval=13)  
(options, args) = parser.parse_args()  
 
if not len(args) == 1:  
if options.manual_entry:  
feed = raw_input('Enter Feed Location: ')  
else:  
parser.error('You must provide the path of a single feed')  
else:  
feed = args[0]  
 
feed = feed.strip('"')  
 
if options.performance:  
return ProfileRunValidationOutputFromOptions(feed, options)  
else:  
return RunValidationOutputFromOptions(feed, options)  
 
 
def ProfileRunValidationOutputFromOptions(feed, options):  
"""Run RunValidationOutputFromOptions, print profile and return exit code."""  
import cProfile  
import pstats  
# runctx will modify a dict, but not locals(). We need a way to get rv back.  
locals_for_exec = locals()  
cProfile.runctx('rv = RunValidationOutputFromOptions(feed, options)',  
globals(), locals_for_exec, 'validate-stats')  
 
# Only available on Unix, http://docs.python.org/lib/module-resource.html  
import resource  
print "Time: %d seconds" % (  
resource.getrusage(resource.RUSAGE_SELF).ru_utime +  
resource.getrusage(resource.RUSAGE_SELF).ru_stime)  
 
# http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/286222  
# http://aspn.activestate.com/ASPN/Cookbook/ "The recipes are freely  
# available for review and use."  
def _VmB(VmKey):  
"""Return size from proc status in bytes."""  
_proc_status = '/proc/%d/status' % os.getpid()  
_scale = {'kB': 1024.0, 'mB': 1024.0*1024.0,  
'KB': 1024.0, 'MB': 1024.0*1024.0}  
 
# get pseudo file /proc/<pid>/status  
try:  
t = open(_proc_status)  
v = t.read()  
t.close()  
except:  
raise Exception("no proc file %s" % _proc_status)  
return 0 # non-Linux?  
# get VmKey line e.g. 'VmRSS: 9999 kB\n ...'  
i = v.index(VmKey)  
v = v[i:].split(None, 3) # whitespace  
if len(v) < 3:  
raise Exception("%s" % v)  
return 0 # invalid format?  
# convert Vm value to bytes  
return int(float(v[1]) * _scale[v[2]])  
 
# I ran this on over a hundred GTFS files, comparing VmSize to VmRSS  
# (resident set size). The difference was always under 2% or 3MB.  
print "Virtual Memory Size: %d bytes" % _VmB('VmSize:')  
 
# Output report of where CPU time was spent.  
p = pstats.Stats('validate-stats')  
p.strip_dirs()  
p.sort_stats('cumulative').print_stats(30)  
p.sort_stats('cumulative').print_callers(30)  
return locals_for_exec['rv']  
 
 
if __name__ == '__main__':  
util.RunWithCrashHandler(main)  
 
__doc__ = """  
Package holding files for Google Transit Feed Specification Schedule Viewer.  
"""  
# This package contains the data files for schedule_viewer.py, a script that  
# comes with the transitfeed distribution. According to the thread  
# "[Distutils] distutils data_files and setuptools.pkg_resources are driving  
# me crazy" this is the easiest way to include data files. My experience  
# agrees. - Tom 2007-05-29  
 
 Binary files a/origin-src/transitfeed-1.2.5/gtfsscheduleviewer/__init__.pyc and /dev/null differ
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"  
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">  
<html xmlns="http://www.w3.org/1999/xhtml" xmlns:v="urn:schemas-microsoft-com:vml">  
<head>  
<meta http-equiv="content-type" content="text/html; charset=utf-8"/>  
<title>[agency]</title>  
<link href="file/style.css" rel="stylesheet" type="text/css" />  
<style type="text/css">  
v\:* {  
behavior:url(#default#VML);  
}  
</style>  
<script src="http://[host]/maps?file=api&amp;v=2&amp;key=[key]" type="text/javascript"></script>  
<script src="/file/labeled_marker.js" type="text/javascript"></script>  
<script language="VBScript" src="/file/svgcheck.vbs"></script>  
<script type="text/javascript">  
//<![CDATA[  
var map;  
// Set to true when debugging for log statements about HTTP requests.  
var log = false;  
var twelveHourTime = false; // set to true to see AM/PM  
var selectedRoute = null;  
var forbid_editing = [forbid_editing];  
 
function load() {  
if (GBrowserIsCompatible()) {  
sizeRouteList();  
var map_dom = document.getElementById("map");  
map = new GMap2(map_dom);  
map.addControl(new GLargeMapControl());  
map.addControl(new GMapTypeControl());  
map.addControl(new GOverviewMapControl());  
map.enableScrollWheelZoom();  
var bb = new GLatLngBounds(new GLatLng([min_lat], [min_lon]),new GLatLng([max_lat], [max_lon]));  
map.setCenter(bb.getCenter(), map.getBoundsZoomLevel(bb));  
map.enableDoubleClickZoom();  
initIcons();  
GEvent.addListener(map, "moveend", callbackMoveEnd);  
GEvent.addListener(map, "zoomend", callbackZoomEnd);  
callbackMoveEnd(); // Pretend we just moved to current center  
fetchRoutes();  
}  
}  
 
function callbackZoomEnd() {  
}  
 
function callbackMoveEnd() {  
// Map moved, search for stops near the center  
fetchStopsInBounds(map.getBounds());  
}  
 
/**  
* Fetch a sample of stops in the bounding box.  
*/  
function fetchStopsInBounds(bounds) {  
url = "/json/boundboxstops?n=" + bounds.getNorthEast().lat()  
+ "&e=" + bounds.getNorthEast().lng()  
+ "&s=" + bounds.getSouthWest().lat()  
+ "&w=" + bounds.getSouthWest().lng()  
+ "&limit=50";  
if (log)  
GLog.writeUrl(url);  
GDownloadUrl(url, callbackDisplayStopsBackground);  
}  
 
/**  
* Displays stops returned by the server on the map. Expected to be called  
* when GDownloadUrl finishes.  
*  
* @param {String} data JSON encoded list of list, each  
* containing a row of stops.txt  
* @param {Number} responseCode Response code from server  
*/  
function callbackDisplayStops(data, responseCode) {  
if (responseCode != 200) {  
return;  
}  
clearMap();  
var stops = eval(data);  
if (stops.length == 1) {  
var marker = addStopMarkerFromList(stops[0], true);  
fetchStopInfoWindow(marker);  
} else {  
for (var i=0; i<stops.length; ++i) {  
addStopMarkerFromList(stops[i], true);  
}  
}  
}  
 
function stopTextSearchSubmit() {  
var text = document.getElementById("stopTextSearchInput").value;  
var url = "/json/stopsearch?q=" + text; // TODO URI escape  
if (log)  
GLog.writeUrl(url);  
GDownloadUrl(url, callbackDisplayStops);  
}  
 
function tripTextSearchSubmit() {  
var text = document.getElementById("tripTextSearchInput").value;  
selectTrip(text);  
}  
 
/**  
* Add stops markers to the map and remove stops no longer in the  
* background.  
*/  
function callbackDisplayStopsBackground(data, responseCode) {  
if (responseCode != 200) {  
return;  
}  
var stops = eval(data);  
// Make a list of all background markers  
var oldStopMarkers = {};  
for (var stopId in stopMarkersBackground) {  
oldStopMarkers[stopId] = 1;  
}  
// Add new markers to the map and remove from oldStopMarkers  
for (var i=0; i<stops.length; ++i) {  
var marker = addStopMarkerFromList(stops[i], false);  
if (oldStopMarkers[marker.stopId]) {  
delete oldStopMarkers[marker.stopId];  
}  
}  
// Delete all markers that remain in oldStopMarkers  
for (var stopId in oldStopMarkers) {  
GEvent.removeListener(stopMarkersBackground[stopId].clickListener);  
map.removeOverlay(stopMarkersBackground[stopId]);  
delete stopMarkersBackground[stopId]  
}  
}  
 
/**  
* Remove all overlays from the map  
*/  
function clearMap() {  
boundsOfPolyLine = null;  
for (var stopId in stopMarkersSelected) {  
GEvent.removeListener(stopMarkersSelected[stopId].clickListener);  
}  
for (var stopId in stopMarkersBackground) {  
GEvent.removeListener(stopMarkersBackground[stopId].clickListener);  
}  
stopMarkersSelected = {};  
stopMarkersBackground = {};  
map.clearOverlays();  
}  
 
/**  
* Return a new GIcon used for stops  
*/  
function makeStopIcon() {  
var icon = new GIcon();  
icon.iconSize = new GSize(12, 20);  
icon.shadowSize = new GSize(22, 20);  
icon.iconAnchor = new GPoint(6, 20);  
icon.infoWindowAnchor = new GPoint(5, 1);  
return icon;  
}  
 
/**  
* Initialize icons. Call once during load.  
*/  
function initIcons() {  
iconSelected = makeStopIcon();  
iconSelected.image = "/file/mm_20_yellow.png";  
iconSelected.shadow = "/file/mm_20_shadow.png";  
iconBackground = makeStopIcon();  
iconBackground.image = "/file/mm_20_blue_trans.png";  
iconBackground.shadow = "/file/mm_20_shadow_trans.png";  
iconBackgroundStation = makeStopIcon();  
iconBackgroundStation.image = "/file/mm_20_red_trans.png";  
iconBackgroundStation.shadow = "/file/mm_20_shadow_trans.png";  
}  
 
var iconSelected;  
var iconBackground;  
var iconBackgroundStation;  
// Map from stopId to GMarker object for stops selected because they are  
// part of a trip, etc  
var stopMarkersSelected = {};  
// Map from stopId to GMarker object for stops found by the background  
// passive search  
var stopMarkersBackground = {};  
/**  
* Add a stop to the map, given a row from stops.txt.  
*/  
function addStopMarkerFromList(list, selected, text) {  
return addStopMarker(list[0], list[1], list[2], list[3], list[4], selected, text);  
}  
 
/**  
* Add a stop to the map, returning the new marker  
*/  
function addStopMarker(stopId, stopName, stopLat, stopLon, locationType, selected, text) {  
if (stopMarkersSelected[stopId]) {  
// stop was selected  
var marker = stopMarkersSelected[stopId];  
if (text) {  
oldText = marker.getText();  
if (oldText) {  
oldText = oldText + "<br>";  
}  
marker.setText(oldText + text);  
}  
return marker;  
}  
if (stopMarkersBackground[stopId]) {  
// Stop was in the background. Either delete it from the background or  
// leave it where it is.  
if (selected) {  
map.removeOverlay(stopMarkersBackground[stopId]);  
delete stopMarkersBackground[stopId];  
} else {  
return stopMarkersBackground[stopId];  
}  
}  
 
var icon;  
if (selected) {  
icon = iconSelected;  
} else if (locationType == 1) {  
icon = iconBackgroundStation  
} else {  
icon = iconBackground;  
}  
var ll = new GLatLng(stopLat,stopLon);  
var marker;  
if (selected || text) {  
if (!text) {  
text = ""; // Make sure every selected icon has a text box, even if empty  
}  
var markerOpts = new Object();  
markerOpts.icon = icon;  
markerOpts.labelText = text;  
markerOpts.labelClass = "tooltip";  
markerOpts.labelOffset = new GSize(6, -20);  
marker = new LabeledMarker(ll, markerOpts);  
} else {  
marker = new GMarker(ll, {icon: icon, draggable: !forbid_editing});  
}  
marker.stopName = stopName;  
marker.stopId = stopId;  
if (selected) {  
stopMarkersSelected[stopId] = marker;  
} else {  
stopMarkersBackground[stopId] = marker;  
}  
map.addOverlay(marker);  
marker.clickListener = GEvent.addListener(marker, "click", function() {fetchStopInfoWindow(marker);});  
GEvent.addListener(marker, "dragend", function() {  
 
document.getElementById("edit").style.visibility = "visible";  
document.getElementById("edit_status").innerHTML = "updating..."  
changeStopLocation(marker);  
});  
return marker;  
}  
 
/**  
* Sends new location of a stop to server.  
*/  
function changeStopLocation(marker) {  
var url = "/json/setstoplocation?id=" +  
encodeURIComponent(marker.stopId) +  
"&lat=" + encodeURIComponent(marker.getLatLng().lat()) +  
"&lng=" + encodeURIComponent(marker.getLatLng().lng());  
GDownloadUrl(url, function(data, responseCode) {  
document.getElementById("edit_status").innerHTML = unescape(data);  
} );  
if (log)  
GLog.writeUrl(url);  
}  
 
/**  
* Saves the current state of the data file opened at server side to file.  
*/  
function saveData() {  
var url = "/json/savedata";  
GDownloadUrl(url, function(data, responseCode) {  
document.getElementById("edit_status").innerHTML = data;} );  
if (log)  
GLog.writeUrl(url);  
}  
 
/**  
* Fetch the next departing trips from the stop for display in an info  
* window.  
*/  
function fetchStopInfoWindow(marker) {  
var url = "/json/stoptrips?stop=" + encodeURIComponent(marker.stopId) + "&time=" + parseTimeInput();  
GDownloadUrl(url, function(data, responseCode) {  
callbackDisplayStopInfoWindow(marker, data, responseCode); } );  
if (log)  
GLog.writeUrl(url);  
}  
 
function callbackDisplayStopInfoWindow(marker, data, responseCode) {  
if (responseCode != 200) {  
return;  
}  
var timeTrips = eval(data);  
var html = "<b>" + marker.stopName + "</b> (" + marker.stopId + ")<br>";  
var latLng = marker.getLatLng();  
html = html + "(" + latLng.lat() + ", " + latLng.lng() + ")<br>";  
html = html + "<table><tr><th>service_id<th>time<th>name</tr>";  
for (var i=0; i < timeTrips.length; ++i) {  
var time = timeTrips[i][0];  
var tripid = timeTrips[i][1][0];  
var tripname = timeTrips[i][1][1];  
var service_id = timeTrips[i][1][2];  
var timepoint = timeTrips[i][2];  
html = html + "<tr onClick='map.closeInfoWindow();selectTrip(\"" +  
tripid + "\")'>" +  
"<td>" + service_id +  
"<td align='right'>" + (timepoint ? "" : "~") +  
formatTime(time) + "<td>" + tripname + "</tr>";  
}  
html = html + "</table>";  
marker.openInfoWindowHtml(html);  
}  
 
function leadingZero(digit) {  
if (digit < 10)  
return "0" + digit;  
else  
return "" + digit;  
}  
 
function formatTime(secSinceMidnight) {  
var hours = Math.floor(secSinceMidnight / 3600);  
var suffix = "";  
 
if (twelveHourTime) {  
suffix = (hours >= 12) ? "p" : "a";  
suffix += (hours >= 24) ? " next day" : "";  
hours = hours % 12;  
if (hours == 0)  
hours = 12;  
}  
var minutes = Math.floor(secSinceMidnight / 60) % 60;  
var seconds = secSinceMidnight % 60;  
if (seconds == 0) {  
return hours + ":" + leadingZero(minutes) + suffix;  
} else {  
return hours + ":" + leadingZero(minutes) + ":" + leadingZero(seconds) + suffix;  
}  
}  
 
function parseTimeInput() {  
var text = document.getElementById("timeInput").value;  
var m = text.match(/([012]?\d):([012345]?\d)(:([012345]?\d))?/);  
if (m) {  
var seconds = parseInt(m[1], 10) * 3600;  
seconds += parseInt(m[2], 10) * 60;  
if (m[4]) {  
second += parseInt(m[4], 10);  
}  
return seconds;  
} else {  
if (log)  
GLog.write("Couldn't match " + text);  
}  
}  
 
/**  
* Create a string of dots that gets longer with the log of count.  
*/  
function countToRepeatedDots(count) {  
// Find ln_2(count) + 1  
var logCount = Math.ceil(Math.log(count) / 0.693148) + 1;  
return new Array(logCount + 1).join(".");  
}  
 
function fetchRoutes() {  
url = "/json/routes";  
if (log)  
GLog.writeUrl(url);  
GDownloadUrl(url, callbackDisplayRoutes);  
}  
 
function callbackDisplayRoutes(data, responseCode) {  
if (responseCode != 200) {  
patternDiv.appendChild(div);  
}  
var routes = eval(data);  
var routesList = document.getElementById("routeList");  
while (routesList.hasChildNodes()) {  
routesList.removeChild(routesList.firstChild);  
}  
for (i = 0; i < routes.length; ++i) {  
var routeId = routes[i][0];  
var shortName = document.createElement("span");  
shortName.className = "shortName";  
shortName.appendChild(document.createTextNode(routes[i][1] + " "));  
var routeName = routes[i][2];  
var elem = document.createElement("div");  
elem.appendChild(shortName);  
elem.appendChild(document.createTextNode(routeName));  
elem.id = "route_" + routeId;  
elem.className = "routeChoice";  
elem.title = routeName;  
GEvent.addDomListener(elem, "click", makeClosure(selectRoute, routeId));  
 
var routeContainer = document.createElement("div");  
routeContainer.id = "route_container_" + routeId;  
routeContainer.className = "routeContainer";  
routeContainer.appendChild(elem);  
routesList.appendChild(routeContainer);  
}  
}  
 
function selectRoute(routeId) {  
var routesList = document.getElementById("routeList");  
routeSpans = routesList.getElementsByTagName("div");  
for (var i = 0; i < routeSpans.length; ++i) {  
if (routeSpans[i].className == "routeChoiceSelected") {  
routeSpans[i].className = "routeChoice";  
}  
}  
 
// remove any previously-expanded route  
var tripInfo = document.getElementById("tripInfo");  
if (tripInfo)  
tripInfo.parentNode.removeChild(tripInfo);  
 
selectedRoute = routeId;  
var span = document.getElementById("route_" + routeId);  
span.className = "routeChoiceSelected";  
fetchPatterns(routeId);  
}  
 
function fetchPatterns(routeId) {  
url = "/json/routepatterns?route=" + encodeURIComponent(routeId) + "&time=" + parseTimeInput();  
if (log)  
GLog.writeUrl(url);  
GDownloadUrl(url, callbackDisplayPatterns);  
}  
 
function callbackDisplayPatterns(data, responseCode) {  
if (responseCode != 200) {  
return;  
}  
var div = document.createElement("div");  
div.className = "tripSection";  
div.id = "tripInfo";  
var firstTrip = null;  
var patterns = eval(data);  
clearMap();  
for (i = 0; i < patterns.length; ++i) {  
patternDiv = document.createElement("div")  
patternDiv.className = 'patternSection';  
div.appendChild(patternDiv)  
var pat = patterns[i]; // [patName, patId, len(early trips), trips, len(later trips), has_non_zero_trip_type]  
if (pat[5] == '1') {  
patternDiv.className += " unusualPattern"  
}  
patternDiv.appendChild(document.createTextNode(pat[0]));  
patternDiv.appendChild(document.createTextNode(", " + (pat[2] + pat[3].length + pat[4]) + " trips: "));  
if (pat[2] > 0) {  
patternDiv.appendChild(document.createTextNode(countToRepeatedDots(pat[2]) + " "));  
}  
for (j = 0; j < pat[3].length; ++j) {  
var trip = pat[3][j];  
var tripId = trip[1];  
if ((i == 0) && (j == 0))  
firstTrip = tripId;  
patternDiv.appendChild(document.createTextNode(" "));  
var span = document.createElement("span");  
span.appendChild(document.createTextNode(formatTime(trip[0])));  
span.id = "trip_" + tripId;  
GEvent.addDomListener(span, "click", makeClosure(selectTrip, tripId));  
patternDiv.appendChild(span)  
span.className = "tripChoice";  
}  
if (pat[4] > 0) {  
patternDiv.appendChild(document.createTextNode(" " + countToRepeatedDots(pat[4])));  
}  
patternDiv.appendChild(document.createElement("br"));  
}  
route = document.getElementById("route_container_" + selectedRoute);  
route.appendChild(div);  
if (tripId != null)  
selectTrip(firstTrip);  
}  
 
// Needed to get around limitation in javascript scope rules.  
// See http://calculist.blogspot.com/2005/12/gotcha-gotcha.html  
function makeClosure(f, a, b, c) {  
return function() { f(a, b, c); };  
}  
function make1ArgClosure(f, a, b, c) {  
return function(x) { f(x, a, b, c); };  
}  
function make2ArgClosure(f, a, b, c) {  
return function(x, y) { f(x, y, a, b, c); };  
}  
 
function selectTrip(tripId) {  
var tripInfo = document.getElementById("tripInfo");  
if (tripInfo) {  
tripSpans = tripInfo.getElementsByTagName('span');  
for (var i = 0; i < tripSpans.length; ++i) {  
tripSpans[i].className = 'tripChoice';  
}  
}  
var span = document.getElementById("trip_" + tripId);  
// Won't find the span if a different route is selected  
if (span) {  
span.className = 'tripChoiceSelected';  
}  
clearMap();  
url = "/json/tripstoptimes?trip=" + encodeURIComponent(tripId);  
if (log)  
GLog.writeUrl(url);  
GDownloadUrl(url, callbackDisplayTripStopTimes);  
fetchTripPolyLine(tripId);  
fetchTripRows(tripId);  
}  
 
function callbackDisplayTripStopTimes(data, responseCode) {  
if (responseCode != 200) {  
return;  
}  
var stopsTimes = eval(data);  
if (!stopsTimes) return;  
displayTripStopTimes(stopsTimes[0], stopsTimes[1]);  
}  
 
function fetchTripPolyLine(tripId) {  
url = "/json/tripshape?trip=" + encodeURIComponent(tripId);  
if (log)  
GLog.writeUrl(url);  
GDownloadUrl(url, callbackDisplayTripPolyLine);  
}  
 
function callbackDisplayTripPolyLine(data, responseCode) {  
if (responseCode != 200) {  
return;  
}  
var points = eval(data);  
if (!points) return;  
displayPolyLine(points);  
}  
 
var boundsOfPolyLine = null;  
function expandBoundingBox(latLng) {  
if (boundsOfPolyLine == null) {  
boundsOfPolyLine = new GLatLngBounds(latLng, latLng);  
} else {  
boundsOfPolyLine.extend(latLng);  
}  
}  
 
/**  
* Display a line given a list of points  
*  
* @param {Array} List of lat,lng pairs  
*/  
function displayPolyLine(points) {  
var linePoints = Array();  
for (i = 0; i < points.length; ++i) {  
var ll = new GLatLng(points[i][0], points[i][1]);  
expandBoundingBox(ll);  
linePoints[linePoints.length] = ll;  
}  
var polyline = new GPolyline(linePoints, "#FF0000", 4);  
map.addOverlay(polyline);  
map.setCenter(boundsOfPolyLine.getCenter(), map.getBoundsZoomLevel(boundsOfPolyLine));  
}  
 
function displayTripStopTimes(stops, times) {  
for (i = 0; i < stops.length; ++i) {  
var marker;  
if (times && times[i] != null) {  
marker = addStopMarkerFromList(stops[i], true, formatTime(times[i]));  
} else {  
marker = addStopMarkerFromList(stops[i], true);  
}  
expandBoundingBox(marker.getPoint());  
}  
map.setCenter(boundsOfPolyLine.getCenter(), map.getBoundsZoomLevel(boundsOfPolyLine));  
}  
 
function fetchTripRows(tripId) {  
url = "/json/triprows?trip=" + encodeURIComponent(tripId);  
if (log)  
GLog.writeUrl(url);  
GDownloadUrl(url, make2ArgClosure(callbackDisplayTripRows, tripId));  
}  
 
function callbackDisplayTripRows(data, responseCode, tripId) {  
if (responseCode != 200) {  
return;  
}  
var rows = eval(data);  
if (!rows) return;  
var html = "";  
for (var i = 0; i < rows.length; ++i) {  
var filename = rows[i][0];  
var row = rows[i][1];  
html += "<b>" + filename + "</b>: " + formatDictionary(row) + "<br>";  
}  
html += svgTag("/ttablegraph?height=100&trip=" + tripId, "height='115' width='100%'");  
var bottombarDiv = document.getElementById("bottombar");  
bottombarDiv.style.display = "block";  
bottombarDiv.style.height = "175px";  
bottombarDiv.innerHTML = html;  
sizeRouteList();  
}  
 
/**  
* Return HTML to embed a SVG object in this page. src is the location of  
* the SVG and attributes is inserted directly into the object or embed  
* tag.  
*/  
function svgTag(src, attributes) {  
if (navigator.userAgent.toLowerCase().indexOf("msie") != -1) {  
if (isSVGControlInstalled()) {  
return "<embed pluginspage='http://www.adobe.com/svg/viewer/install/' src='" + src + "' " + attributes +"></embed>";  
} else {  
return "<p>Please install the <a href='http://www.adobe.com/svg/viewer/install/'>Adobe SVG Viewer</a> to get SVG support in IE</p>";  
}  
} else {  
return "<object data='" + src + "' type='image/svg+xml' " + attributes + "><p>No SVG support in your browser. Try Firefox 1.5 or newer or install the <a href='http://www.adobe.com/svg/viewer/install/'>Adobe SVG Viewer</a></p></object>";  
}  
}  
 
/**  
* Format an Array object containing key-value pairs into a human readable  
* string.  
*/  
function formatDictionary(d) {  
var output = "";  
var first = 1;  
for (var k in d) {  
if (first) {  
first = 0;  
} else {  
output += "&nbsp;&nbsp; ";  
}  
output += "<b>" + k + "</b>=" + d[k];  
}  
return output;  
}  
 
 
function windowHeight() {  
// Standard browsers (Mozilla, Safari, etc.)  
if (self.innerHeight)  
return self.innerHeight;  
// IE 6  
if (document.documentElement && document.documentElement.clientHeight)  
return document.documentElement.clientHeight;  
// IE 5  
if (document.body)  
return document.body.clientHeight;  
// Just in case.  
return 0;  
}  
 
function sizeRouteList() {  
var bottombarHeight = 0;  
var bottombarDiv = document.getElementById('bottombar');  
if (bottombarDiv.style.display != 'none') {  
bottombarHeight = document.getElementById('bottombar').offsetHeight  
+ document.getElementById('bottombar').style.marginTop;  
}  
var height = windowHeight() - document.getElementById('topbar').offsetHeight - 15 - bottombarHeight;  
document.getElementById('content').style.height = height + 'px';  
if (map) {  
// Without this displayPolyLine does not use the correct map size  
map.checkResize();  
}  
}  
 
//]]>  
</script>  
</head>  
 
<body class='sidebar-left' onload="load();" onunload="GUnload()" onresize="sizeRouteList()">  
<div id='topbar'>  
<div id="edit">  
<span id="edit_status">...</span>  
<form onSubmit="saveData(); return false;"><input value="Save" type="submit">  
</div>  
<div id="agencyHeader">[agency]</div>  
</div>  
<div id='content'>  
<div id='sidebar-wrapper'><div id='sidebar'>  
Time:&nbsp;<input type="text" value="8:00" width="9" id="timeInput"><br>  
<form onSubmit="stopTextSearchSubmit(); return false;">  
Find Station: <input type="text" id="stopTextSearchInput"><input value="Search" type="submit"></form><br>  
<form onSubmit="tripTextSearchSubmit(); return false;">  
Find Trip ID: <input type="text" id="tripTextSearchInput"><input value="Search" type="submit"></form><br>  
<div id="routeList">routelist</div>  
</div></div>  
 
<div id='map-wrapper'> <div id='map'></div> </div>  
</div>  
 
<div id='bottombar'>bottom bar</div>  
 
</body>  
</html>  
 
/*  
* LabeledMarker Class  
*  
* Copyright 2007 Mike Purvis (http://uwmike.com)  
*  
* Licensed under the Apache License, Version 2.0 (the "License");  
* you may not use this file except in compliance with the License.  
* You may obtain a copy of the License at  
*  
* http://www.apache.org/licenses/LICENSE-2.0  
*  
* Unless required by applicable law or agreed to in writing, software  
* distributed under the License is distributed on an "AS IS" BASIS,  
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.  
* See the License for the specific language governing permissions and  
* limitations under the License.  
*  
* This class extends the Maps API's standard GMarker class with the ability  
* to support markers with textual labels. Please see articles here:  
*  
* http://googlemapsbook.com/2007/01/22/extending-gmarker/  
* http://googlemapsbook.com/2007/03/06/clickable-labeledmarker/  
*/  
 
/**  
* Constructor for LabeledMarker, which picks up on strings from the GMarker  
* options array, and then calls the GMarker constructor.  
*  
* @param {GLatLng} latlng  
* @param {GMarkerOptions} Named optional arguments:  
* opt_opts.labelText {String} text to place in the overlay div.  
* opt_opts.labelClass {String} class to use for the overlay div.  
* (default "markerLabel")  
* opt_opts.labelOffset {GSize} label offset, the x- and y-distance between  
* the marker's latlng and the upper-left corner of the text div.  
*/  
function LabeledMarker(latlng, opt_opts){  
this.latlng_ = latlng;  
this.opts_ = opt_opts;  
 
this.initText_ = opt_opts.labelText || "";  
this.labelClass_ = opt_opts.labelClass || "markerLabel";  
this.labelOffset_ = opt_opts.labelOffset || new GSize(0, 0);  
 
this.clickable_ = opt_opts.clickable || true;  
 
if (opt_opts.draggable) {  
// This version of LabeledMarker doesn't support dragging.  
opt_opts.draggable = false;  
}  
 
GMarker.apply(this, arguments);  
}  
 
 
// It's a limitation of JavaScript inheritance that we can't conveniently  
// inherit from GMarker without having to run its constructor. In order for  
// the constructor to run, it requires some dummy GLatLng.  
LabeledMarker.prototype = new GMarker(new GLatLng(0, 0));  
 
/**  
* Is called by GMap2's addOverlay method. Creates the text div and adds it  
* to the relevant parent div.  
*  
* @param {GMap2} map the map that has had this labeledmarker added to it.  
*/  
LabeledMarker.prototype.initialize = function(map) {  
// Do the GMarker constructor first.  
GMarker.prototype.initialize.apply(this, arguments);  
 
this.map_ = map;  
this.setText(this.initText_);  
}  
 
/**  
* Create a new div for this label.  
*/  
LabeledMarker.prototype.makeDiv_ = function(map) {  
if (this.div_) {  
return;  
}  
this.div_ = document.createElement("div");  
this.div_.className = this.labelClass_;  
this.div_.style.position = "absolute";  
this.div_.style.cursor = "pointer";  
this.map_.getPane(G_MAP_MARKER_PANE).appendChild(this.div_);  
 
if (this.clickable_) {  
/**  
* Creates a closure for passing events through to the source marker  
* This is located in here to avoid cluttering the global namespace.  
* The downside is that the local variables from initialize() continue  
* to occupy space on the stack.  
*  
* @param {Object} object to receive event trigger.  
* @param {GEventListener} event to be triggered.  
*/  
function newEventPassthru(obj, event) {  
return function() {  
GEvent.trigger(obj, event);  
};  
}  
 
// Pass through events fired on the text div to the marker.  
var eventPassthrus = ['click', 'dblclick', 'mousedown', 'mouseup', 'mouseover', 'mouseout'];  
for(var i = 0; i < eventPassthrus.length; i++) {  
var name = eventPassthrus[i];  
GEvent.addDomListener(this.div_, name, newEventPassthru(this, name));  
}  
}  
}  
 
/**  
* Return the html in the div of this label, or "" if none is set  
*/  
LabeledMarker.prototype.getText = function(text) {  
if (this.div_) {  
return this.div_.innerHTML;  
} else {  
return "";  
}  
}  
 
/**  
* Set the html in the div of this label to text. If text is "" or null remove  
* the div.  
*/  
LabeledMarker.prototype.setText = function(text) {  
if (this.div_) {  
if (text) {  
this.div_.innerHTML = text;  
} else {  
// remove div  
GEvent.clearInstanceListeners(this.div_);  
this.div_.parentNode.removeChild(this.div_);  
this.div_ = null;  
}  
} else {  
if (text) {  
this.makeDiv_();  
this.div_.innerHTML = text;  
this.redraw();  
}  
}  
}  
 
/**  
* Move the text div based on current projection and zoom level, call the redraw()  
* handler in GMarker.  
*  
* @param {Boolean} force will be true when pixel coordinates need to be recomputed.  
*/  
LabeledMarker.prototype.redraw = function(force) {  
GMarker.prototype.redraw.apply(this, arguments);  
 
if (this.div_) {  
// Calculate the DIV coordinates of two opposite corners of our bounds to  
// get the size and position of our rectangle  
var p = this.map_.fromLatLngToDivPixel(this.latlng_);  
var z = GOverlay.getZIndex(this.latlng_.lat());  
 
// Now position our div based on the div coordinates of our bounds  
this.div_.style.left = (p.x + this.labelOffset_.width) + "px";  
this.div_.style.top = (p.y + this.labelOffset_.height) + "px";  
this.div_.style.zIndex = z; // in front of the marker  
}  
}  
 
/**  
* Remove the text div from the map pane, destroy event passthrus, and calls the  
* default remove() handler in GMarker.  
*/  
LabeledMarker.prototype.remove = function() {  
this.setText(null);  
GMarker.prototype.remove.apply(this, arguments);  
}  
 
/**  
* Return a copy of this overlay, for the parent Map to duplicate itself in full. This  
* is part of the Overlay interface and is used, for example, to copy everything in the  
* main view into the mini-map.  
*/  
LabeledMarker.prototype.copy = function() {  
return new LabeledMarker(this.latlng_, this.opt_opts_);  
}  
 
 Binary files a/origin-src/transitfeed-1.2.5/gtfsscheduleviewer/files/mm_20_blue.png and /dev/null differ
 Binary files a/origin-src/transitfeed-1.2.5/gtfsscheduleviewer/files/mm_20_blue_trans.png and /dev/null differ
 Binary files a/origin-src/transitfeed-1.2.5/gtfsscheduleviewer/files/mm_20_red_trans.png and /dev/null differ
 Binary files a/origin-src/transitfeed-1.2.5/gtfsscheduleviewer/files/mm_20_shadow.png and /dev/null differ
 Binary files a/origin-src/transitfeed-1.2.5/gtfsscheduleviewer/files/mm_20_shadow_trans.png and /dev/null differ
 Binary files a/origin-src/transitfeed-1.2.5/gtfsscheduleviewer/files/mm_20_yellow.png and /dev/null differ
html { overflow: hidden; }  
 
html, body {  
margin: 0;  
padding: 0;  
height: 100%;  
}  
 
body { margin: 5px; }  
 
#content {  
position: relative;  
margin-top: 5px;  
}  
 
#map-wrapper {  
position: relative;  
height: 100%;  
width: auto;  
left: 0;  
top: 0;  
z-index: 100;  
}  
 
#map {  
position: relative;  
height: 100%;  
width: auto;  
border: 1px solid #aaa;  
}  
 
#sidebar-wrapper {  
position: absolute;  
height: 100%;  
width: 220px;  
top: 0;  
border: 1px solid #aaa;  
overflow: auto;  
z-index: 300;  
}  
 
#sidebar {  
position: relative;  
width: auto;  
padding: 4px;  
overflow: hidden;  
}  
 
#topbar {  
position: relative;  
padding: 2px;  
border: 1px solid #aaa;  
margin: 0;  
}  
 
#topbar h1 {  
white-space: nowrap;  
overflow: hidden;  
font-size: 14pt;  
font-weight: bold;  
font-face:  
margin: 0;  
}  
 
 
body.sidebar-right #map-wrapper { margin-right: 229px; }  
body.sidebar-right #sidebar-wrapper { right: 0; }  
 
body.sidebar-left #map { margin-left: 229px; }  
body.sidebar-left #sidebar { left: 0; }  
 
body.nosidebar #map { margin: 0; }  
body.nosidebar #sidebar { display: none; }  
 
#bottombar {  
position: relative;  
padding: 2px;  
border: 1px solid #aaa;  
margin-top: 5px;  
display: none;  
}  
 
/* holly hack for IE to get position:bottom right  
see: http://www.positioniseverything.net/abs_relbugs.html  
\*/  
* html #topbar { height: 1px; }  
/* */  
 
body {  
font-family:helvetica,arial,sans, sans-serif;  
}  
h1 {  
margin-top: 0.5em;  
margin-bottom: 0.5em;  
}  
h2 {  
margin-top: 0.2em;  
margin-bottom: 0.2em;  
}  
h3 {  
margin-top: 0.2em;  
margin-bottom: 0.2em;  
}  
.tooltip {  
white-space: nowrap;  
padding: 2px;  
color: black;  
font-size: 12px;  
background-color: white;  
border: 1px solid black;  
cursor: pointer;  
filter:alpha(opacity=60);  
-moz-opacity: 0.6;  
opacity: 0.6;  
}  
#routeList {  
border: 1px solid black;  
overflow: auto;  
}  
.shortName {  
font-size: bigger;  
font-weight: bold;  
}  
.routeChoice,.tripChoice,.routeChoiceSelected,.tripChoiceSelected {  
white-space: nowrap;  
cursor: pointer;  
padding: 0px 2px;  
color: black;  
line-height: 1.4em;  
font-size: smaller;  
overflow: hidden;  
}  
.tripChoice {  
color: blue;  
}  
.routeChoiceSelected,.tripChoiceSelected {  
background-color: blue;  
color: white;  
}  
.tripSection {  
padding-left: 0px;  
font-size: 10pt;  
background-color: lightblue;  
}  
.patternSection {  
margin-left: 8px;  
padding-left: 2px;  
border-bottom: 1px solid grey;  
}  
.unusualPattern {  
background-color: #aaa;  
color: #444;  
}  
/* Following styles are used by location_editor.py */  
#edit {  
visibility: hidden;  
float: right;  
font-size: 80%;  
}  
#edit form {  
display: inline;  
}  
' Copyright 1999-2000 Adobe Systems Inc. All rights reserved. Permission to redistribute  
' granted provided that this file is not modified in any way. This file is provided with  
' absolutely no warranties of any kind.  
Function isSVGControlInstalled()  
on error resume next  
isSVGControlInstalled = IsObject(CreateObject("Adobe.SVGCtl"))  
end Function  
 
#!/usr/bin/python2.5  
#  
# Copyright (C) 2007 Google Inc.  
#  
# Licensed under the Apache License, Version 2.0 (the "License");  
# you may not use this file except in compliance with the License.  
# You may obtain a copy of the License at  
#  
# http://www.apache.org/licenses/LICENSE-2.0  
#  
# Unless required by applicable law or agreed to in writing, software  
# distributed under the License is distributed on an "AS IS" BASIS,  
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.  
# See the License for the specific language governing permissions and  
# limitations under the License.  
 
"""Output svg/xml data for a marey graph  
 
Marey graphs are a visualization form typically used for timetables. Time  
is on the x-axis and position on the y-axis. This module reads data from a  
transitfeed.Schedule and creates a marey graph in svg/xml format. The graph  
shows the speed between stops for each trip of a route.  
 
TODO: This module was taken from an internal Google tool. It works but is not  
well intergrated into transitfeed and schedule_viewer. Also, it has lots of  
ugly hacks to compensate set canvas size and so on which could be cleaned up.  
 
For a little more information see (I didn't make this URL ;-)  
http://transliteracies.english.ucsb.edu/post/research-project/research-clearinghouse-individual/research-reports/the-indexical-imagination-marey%e2%80%99s-graphic-method-and-the-technological-transformation-of-writing-in-the-nineteenth-century  
 
MareyGraph: Class, keeps cache of graph data and graph properties  
and draws marey graphs in svg/xml format on request.  
 
"""  
 
import itertools  
import transitfeed  
 
 
class MareyGraph:  
"""Produces and caches marey graph from transit feed data."""  
 
_MAX_ZOOM = 5.0 # change docstring of ChangeScaleFactor if this changes  
_DUMMY_SEPARATOR = 10 #pixel  
 
def __init__(self):  
# Timetablerelated state  
self._cache = str()  
self._stoplist = []  
self._tlist = []  
self._stations = []  
self._decorators = []  
 
# TODO: Initialize default values via constructor parameters  
# or via a class constants  
 
# Graph properties  
self._tspan = 30 # number of hours to display  
self._offset = 0 # starting hour  
self._hour_grid = 60 # number of pixels for an hour  
self._min_grid = 5 # number of pixels between subhour lines  
 
# Canvas properties  
self._zoomfactor = 0.9 # svg Scaling factor  
self._xoffset = 0 # move graph horizontally  
self._yoffset = 0 # move graph veritcally  
self._bgcolor = "lightgrey"  
 
# height/width of graph canvas before transform  
self._gwidth = self._tspan * self._hour_grid  
 
def Draw(self, stoplist=None, triplist=None, height=520):  
"""Main interface for drawing the marey graph.  
 
If called without arguments, the data generated in the previous call  
will be used. New decorators can be added between calls.  
 
Args:  
# Class Stop is defined in transitfeed.py  
stoplist: [Stop, Stop, ...]  
# Class Trip is defined in transitfeed.py  
triplist: [Trip, Trip, ...]  
 
Returns:  
# A string that contain a svg/xml web-page with a marey graph.  
" <svg width="1440" height="520" version="1.1" ... "  
"""  
output = str()  
if not triplist:  
triplist = []  
if not stoplist:  
stoplist = []  
 
if not self._cache or triplist or stoplist:  
self._gheight = height  
self._tlist=triplist  
self._slist=stoplist  
self._decorators = []  
self._stations = self._BuildStations(stoplist)  
self._cache = "%s %s %s %s" % (self._DrawBox(),  
self._DrawHours(),  
self._DrawStations(),  
self._DrawTrips(triplist))  
 
 
 
output = "%s %s %s %s" % (self._DrawHeader(),  
self._cache,  
self._DrawDecorators(),  
self._DrawFooter())  
return output  
 
def _DrawHeader(self):  
svg_header = """  
<svg width="%s" height="%s" version="1.1"  
xmlns="http://www.w3.org/2000/svg">  
<script type="text/ecmascript"><![CDATA[  
function init(evt) {  
if ( window.svgDocument == null )  
svgDocument = evt.target.ownerDocument;  
}  
var oldLine = 0;  
var oldStroke = 0;  
var hoffset= %s; // Data from python  
 
function parseLinePoints(pointnode){  
var wordlist = pointnode.split(" ");  
var xlist = new Array();  
var h;  
var m;  
// TODO: add linebreaks as appropriate  
var xstr = " Stop Times :";  
for (i=0;i<wordlist.length;i=i+2){  
var coord = wordlist[i].split(",");  
h = Math.floor(parseInt((coord[0])-20)/60);  
m = parseInt((coord[0]-20))%%60;  
xstr = xstr +" "+ (hoffset+h) +":"+m;  
}  
 
return xstr;  
}  
 
function LineClick(tripid, x) {  
var line = document.getElementById(tripid);  
if (oldLine)  
oldLine.setAttribute("stroke",oldStroke);  
oldLine = line;  
oldStroke = line.getAttribute("stroke");  
 
line.setAttribute("stroke","#fff");  
 
var dynTxt = document.getElementById("dynamicText");  
var tripIdTxt = document.createTextNode(x);  
while (dynTxt.hasChildNodes()){  
dynTxt.removeChild(dynTxt.firstChild);  
}  
dynTxt.appendChild(tripIdTxt);  
}  
]]> </script>  
<style type="text/css"><![CDATA[  
.T { fill:none; stroke-width:1.5 }  
.TB { fill:none; stroke:#e20; stroke-width:2 }  
.Station { fill:none; stroke-width:1 }  
.Dec { fill:none; stroke-width:1.5 }  
.FullHour { fill:none; stroke:#eee; stroke-width:1 }  
.SubHour { fill:none; stroke:#ddd; stroke-width:1 }  
.Label { fill:#aaa; font-family:Helvetica,Arial,sans;  
text-anchor:middle }  
.Info { fill:#111; font-family:Helvetica,Arial,sans;  
text-anchor:start; }  
]]></style>  
<text class="Info" id="dynamicText" x="0" y="%d"></text>  
<g id="mcanvas" transform="translate(%s,%s)">  
<g id="zcanvas" transform="scale(%s)">  
 
""" % (self._gwidth + self._xoffset + 20, self._gheight + 15,  
self._offset, self._gheight + 10,  
self._xoffset, self._yoffset, self._zoomfactor)  
 
return svg_header  
 
def _DrawFooter(self):  
return "</g></g></svg>"  
 
def _DrawDecorators(self):  
"""Used to draw fancy overlays on trip graphs."""  
return " ".join(self._decorators)  
 
def _DrawBox(self):  
tmpstr = """<rect x="%s" y="%s" width="%s" height="%s"  
fill="lightgrey" stroke="%s" stroke-width="2" />  
""" % (0, 0, self._gwidth + 20, self._gheight, self._bgcolor)  
return tmpstr  
 
def _BuildStations(self, stoplist):  
"""Dispatches the best algorithm for calculating station line position.  
 
Args:  
# Class Stop is defined in transitfeed.py  
stoplist: [Stop, Stop, ...]  
# Class Trip is defined in transitfeed.py  
triplist: [Trip, Trip, ...]  
 
Returns:  
# One integer y-coordinate for each station normalized between  
# 0 and X, where X is the height of the graph in pixels  
[0, 33, 140, ... , X]  
"""  
stations = []  
dists = self._EuclidianDistances(stoplist)  
stations = self._CalculateYLines(dists)  
return stations  
 
def _EuclidianDistances(self,slist):  
"""Calculate euclidian distances between stops.  
 
Uses the stoplists long/lats to approximate distances  
between stations and build a list with y-coordinates for the  
horizontal lines in the graph.  
 
Args:  
# Class Stop is defined in transitfeed.py  
stoplist: [Stop, Stop, ...]  
 
Returns:  
# One integer for each pair of stations  
# indicating the approximate distance  
[0,33,140, ... ,X]  
"""  
e_dists2 = [transitfeed.ApproximateDistanceBetweenStops(stop, tail) for  
(stop,tail) in itertools.izip(slist, slist[1:])]  
 
return e_dists2  
 
def _CalculateYLines(self, dists):  
"""Builds a list with y-coordinates for the horizontal lines in the graph.  
 
Args:  
# One integer for each pair of stations  
# indicating the approximate distance  
dists: [0,33,140, ... ,X]  
 
Returns:  
# One integer y-coordinate for each station normalized between  
# 0 and X, where X is the height of the graph in pixels  
[0, 33, 140, ... , X]  
"""  
tot_dist = sum(dists)  
if tot_dist > 0:  
pixel_dist = [float(d * (self._gheight-20))/tot_dist for d in dists]  
pixel_grid = [0]+[int(pd + sum(pixel_dist[0:i])) for i,pd in  
enumerate(pixel_dist)]  
else:  
pixel_grid = []  
 
return pixel_grid  
 
def _TravelTimes(self,triplist,index=0):  
""" Calculate distances and plot stops.  
 
Uses a timetable to approximate distances  
between stations  
 
Args:  
# Class Trip is defined in transitfeed.py  
triplist: [Trip, Trip, ...]  
# (Optional) Index of Triplist prefered for timetable Calculation  
index: 3  
 
Returns:  
# One integer for each pair of stations  
# indicating the approximate distance  
[0,33,140, ... ,X]  
"""  
 
def DistanceInTravelTime(dep_secs, arr_secs):  
t_dist = arr_secs-dep_secs  
if t_dist<0:  
t_dist = self._DUMMY_SEPARATOR # min separation  
return t_dist  
 
if not triplist:  
return []  
 
if 0 < index < len(triplist):  
trip = triplist[index]  
else:  
trip = triplist[0]  
 
t_dists2 = [DistanceInTravelTime(stop[3],tail[2]) for (stop,tail)  
in itertools.izip(trip.GetTimeStops(),trip.GetTimeStops()[1:])]  
return t_dists2  
 
def _AddWarning(self, str):  
print str  
 
def _DrawTrips(self,triplist,colpar=""):  
"""Generates svg polylines for each transit trip.  
 
Args:  
# Class Trip is defined in transitfeed.py  
[Trip, Trip, ...]  
 
Returns:  
# A string containing a polyline tag for each trip  
' <polyline class="T" stroke="#336633" points="433,0 ...'  
"""  
 
stations = []  
if not self._stations and triplist:  
self._stations = self._CalculateYLines(self._TravelTimes(triplist))  
if not self._stations:  
self._AddWarning("Failed to use traveltimes for graph")  
self._stations = self._CalculateYLines(self._Uniform(triplist))  
if not self._stations:  
self._AddWarning("Failed to calculate station distances")  
return  
 
stations = self._stations  
tmpstrs = []  
servlist = []  
for t in triplist:  
if not colpar:  
if t.service_id not in servlist:  
servlist.append(t.service_id)  
shade = int(servlist.index(t.service_id) * (200/len(servlist))+55)  
color = "#00%s00" % hex(shade)[2:4]  
else:  
color=colpar  
 
start_offsets = [0]  
first_stop = t.GetTimeStops()[0]  
 
for j,freq_offset in enumerate(start_offsets):  
if j>0 and not colpar:  
color="purple"  
scriptcall = 'onmouseover="LineClick(\'%s\',\'Trip %s starting %s\')"' % (t.trip_id,  
t.trip_id, transitfeed.FormatSecondsSinceMidnight(t.GetStartTime()))  
tmpstrhead = '<polyline class="T" id="%s" stroke="%s" %s points="' % \  
(str(t.trip_id),color, scriptcall)  
tmpstrs.append(tmpstrhead)  
 
for i, s in enumerate(t.GetTimeStops()):  
arr_t = s[0]  
dep_t = s[1]  
if arr_t is None or dep_t is None:  
continue  
arr_x = int(arr_t/3600.0 * self._hour_grid) - self._hour_grid * self._offset  
dep_x = int(dep_t/3600.0 * self._hour_grid) - self._hour_grid * self._offset  
tmpstrs.append("%s,%s " % (int(arr_x+20), int(stations[i]+20)))  
tmpstrs.append("%s,%s " % (int(dep_x+20), int(stations[i]+20)))  
tmpstrs.append('" />')  
return "".join(tmpstrs)  
 
def _Uniform(self, triplist):  
"""Fallback to assuming uniform distance between stations"""  
# This should not be neseccary, but we are in fallback mode  
longest = max([len(t.GetTimeStops()) for t in triplist])  
return [100] * longest  
 
def _DrawStations(self, color="#aaa"):  
"""Generates svg with a horizontal line for each station/stop.  
 
Args:  
# Class Stop is defined in transitfeed.py  
stations: [Stop, Stop, ...]  
 
Returns:  
# A string containing a polyline tag for each stop  
" <polyline class="Station" stroke="#336633" points="20,0 ..."  
"""  
stations=self._stations  
tmpstrs = []  
for y in stations:  
tmpstrs.append(' <polyline class="Station" stroke="%s" \  
points="%s,%s, %s,%s" />' %(color,20,20+y+.5,self._gwidth+20,20+y+.5))  
return "".join(tmpstrs)  
 
def _DrawHours(self):  
"""Generates svg to show a vertical hour and sub-hour grid  
 
Returns:  
# A string containing a polyline tag for each grid line  
" <polyline class="FullHour" points="20,0 ..."  
"""  
tmpstrs = []  
for i in range(0, self._gwidth, self._min_grid):  
if i % self._hour_grid == 0:  
tmpstrs.append('<polyline class="FullHour" points="%d,%d, %d,%d" />' \  
% (i + .5 + 20, 20, i + .5 + 20, self._gheight))  
tmpstrs.append('<text class="Label" x="%d" y="%d">%d</text>'  
% (i + 20, 20,  
(i / self._hour_grid + self._offset) % 24))  
else:  
tmpstrs.append('<polyline class="SubHour" points="%d,%d,%d,%d" />' \  
% (i + .5 + 20, 20, i + .5 + 20, self._gheight))  
return "".join(tmpstrs)  
 
def AddStationDecoration(self, index, color="#f00"):  
"""Flushes existing decorations and highlights the given station-line.  
 
Args:  
# Integer, index of stop to be highlighted.  
index: 4  
# An optional string with a html color code  
color: "#fff"  
"""  
tmpstr = str()  
num_stations = len(self._stations)  
ind = int(index)  
if self._stations:  
if 0<ind<num_stations:  
y = self._stations[ind]  
tmpstr = '<polyline class="Dec" stroke="%s" points="%s,%s,%s,%s" />' \  
% (color, 20, 20+y+.5, self._gwidth+20, 20+y+.5)  
self._decorators.append(tmpstr)  
 
def AddTripDecoration(self, triplist, color="#f00"):  
"""Flushes existing decorations and highlights the given trips.  
 
Args:  
# Class Trip is defined in transitfeed.py  
triplist: [Trip, Trip, ...]  
# An optional string with a html color code  
color: "#fff"  
"""  
tmpstr = self._DrawTrips(triplist,color)  
self._decorators.append(tmpstr)  
 
def ChangeScaleFactor(self, newfactor):  
"""Changes the zoom of the graph manually.  
 
1.0 is the original canvas size.  
 
Args:  
# float value between 0.0 and 5.0  
newfactor: 0.7  
"""  
if float(newfactor) > 0 and float(newfactor) < self._MAX_ZOOM:  
self._zoomfactor = newfactor  
 
def ScaleLarger(self):  
"""Increases the zoom of the graph one step (0.1 units)."""  
newfactor = self._zoomfactor + 0.1  
if float(newfactor) > 0 and float(newfactor) < self._MAX_ZOOM:  
self._zoomfactor = newfactor  
 
def ScaleSmaller(self):  
"""Decreases the zoom of the graph one step(0.1 units)."""  
newfactor = self._zoomfactor - 0.1  
if float(newfactor) > 0 and float(newfactor) < self._MAX_ZOOM:  
self._zoomfactor = newfactor  
 
def ClearDecorators(self):  
"""Removes all the current decorators.  
"""  
self._decorators = []  
 
def AddTextStripDecoration(self,txtstr):  
tmpstr = '<text class="Info" x="%d" y="%d">%s</text>' % (0,  
20 + self._gheight, txtstr)  
self._decorators.append(tmpstr)  
 
def SetSpan(self, first_arr, last_arr, mint=5 ,maxt=30):  
s_hour = (first_arr / 3600) - 1  
e_hour = (last_arr / 3600) + 1  
self._offset = max(min(s_hour, 23), 0)  
self._tspan = max(min(e_hour - s_hour, maxt), mint)  
self._gwidth = self._tspan * self._hour_grid  
 
 Binary files a/origin-src/transitfeed-1.2.5/gtfsscheduleviewer/marey_graph.pyc and /dev/null differ
#!/usr/bin/python2.5  
 
# Copyright (C) 2007 Google Inc.  
#  
# Licensed under the Apache License, Version 2.0 (the "License");  
# you may not use this file except in compliance with the License.  
# You may obtain a copy of the License at  
#  
# http://www.apache.org/licenses/LICENSE-2.0  
#  
# Unless required by applicable law or agreed to in writing, software  
# distributed under the License is distributed on an "AS IS" BASIS,  
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.  
# See the License for the specific language governing permissions and  
# limitations under the License.  
 
"""  
This package provides implementation of a converter from a kml  
file format into Google transit feed format.  
 
The KmlParser class is the main class implementing the parser.  
 
Currently only information about stops is extracted from a kml file.  
The extractor expects the stops to be represented as placemarks with  
a single point.  
"""  
 
import re  
import string  
import sys  
import transitfeed  
from transitfeed import util  
import xml.dom.minidom as minidom  
import zipfile  
 
 
class Placemark(object):  
def __init__(self):  
self.name = ""  
self.coordinates = []  
 
def IsPoint(self):  
return len(self.coordinates) == 1  
 
def IsLine(self):  
return len(self.coordinates) > 1  
 
class KmlParser(object):  
def __init__(self, stopNameRe = '(.*)'):  
"""  
Args:  
stopNameRe - a regular expression to extract a stop name from a  
placemaker name  
"""  
self.stopNameRe = re.compile(stopNameRe)  
 
def Parse(self, filename, feed):  
"""  
Reads the kml file, parses it and updated the Google transit feed  
object with the extracted information.  
 
Args:  
filename - kml file name  
feed - an instance of Schedule class to be updated  
"""  
dom = minidom.parse(filename)  
self.ParseDom(dom, feed)  
 
def ParseDom(self, dom, feed):  
"""  
Parses the given kml dom tree and updates the Google transit feed object.  
 
Args:  
dom - kml dom tree  
feed - an instance of Schedule class to be updated  
"""  
shape_num = 0  
for node in dom.getElementsByTagName('Placemark'):  
p = self.ParsePlacemark(node)  
if p.IsPoint():  
(lon, lat) = p.coordinates[0]  
m = self.stopNameRe.search(p.name)  
feed.AddStop(lat, lon, m.group(1))  
elif p.IsLine():  
shape_num = shape_num + 1  
shape = transitfeed.Shape("kml_shape_" + str(shape_num))  
for (lon, lat) in p.coordinates:  
shape.AddPoint(lat, lon)  
feed.AddShapeObject(shape)  
 
def ParsePlacemark(self, node):  
ret = Placemark()  
for child in node.childNodes:  
if child.nodeName == 'name':  
ret.name = self.ExtractText(child)  
if child.nodeName == 'Point' or child.nodeName == 'LineString':  
ret.coordinates = self.ExtractCoordinates(child)  
return ret  
 
def ExtractText(self, node):  
for child in node.childNodes:  
if child.nodeType == child.TEXT_NODE:  
return child.wholeText # is a unicode string  
return ""  
 
def ExtractCoordinates(self, node):  
coordinatesText = ""  
for child in node.childNodes:  
if child.nodeName == 'coordinates':  
coordinatesText = self.ExtractText(child)  
break  
ret = []  
for point in coordinatesText.split():  
coords = point.split(',')  
ret.append((float(coords[0]), float(coords[1])))  
return ret  
 
 
def main():  
usage = \  
"""%prog <input.kml> <output GTFS.zip>  
 
Reads KML file <input.kml> and creates GTFS file <output GTFS.zip> with  
placemarks in the KML represented as stops.  
"""  
 
parser = util.OptionParserLongError(  
usage=usage, version='%prog '+transitfeed.__version__)  
(options, args) = parser.parse_args()  
if len(args) != 2:  
parser.error('You did not provide all required command line arguments.')  
 
if args[0] == 'IWantMyCrash':  
raise Exception('For testCrashHandler')  
 
parser = KmlParser()  
feed = transitfeed.Schedule()  
feed.save_all_stops = True  
parser.Parse(args[0], feed)  
feed.WriteGoogleTransitFeed(args[1])  
 
print "Done."  
 
 
if __name__ == '__main__':  
util.RunWithCrashHandler(main)  
 
#!/usr/bin/python2.5  
#  
# Copyright 2008 Google Inc. All Rights Reserved.  
#  
# Licensed under the Apache License, Version 2.0 (the "License");  
# you may not use this file except in compliance with the License.  
# You may obtain a copy of the License at  
#  
# http://www.apache.org/licenses/LICENSE-2.0  
#  
# Unless required by applicable law or agreed to in writing, software  
# distributed under the License is distributed on an "AS IS" BASIS,  
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.  
# See the License for the specific language governing permissions and  
# limitations under the License.  
 
"""A module for writing GTFS feeds out into Google Earth KML format.  
 
For usage information run kmlwriter.py --help  
 
If no output filename is specified, the output file will be given the same  
name as the feed file (with ".kml" appended) and will be placed in the same  
directory as the input feed.  
 
The resulting KML file has a folder hierarchy which looks like this:  
 
- Stops  
* stop1  
* stop2  
- Routes  
- route1  
- Shapes  
* shape1  
* shape2  
- Patterns  
- pattern1  
- pattern2  
- Trips  
* trip1  
* trip2  
- Shapes  
* shape1  
- Shape Points  
* shape_point1  
* shape_point2  
* shape2  
- Shape Points  
* shape_point1  
* shape_point2  
 
where the hyphens represent folders and the asteriks represent placemarks.  
 
In a trip, a vehicle visits stops in a certain sequence. Such a sequence of  
stops is called a pattern. A pattern is represented by a linestring connecting  
the stops. The "Shapes" subfolder of a route folder contains placemarks for  
each shape used by a trip in the route. The "Patterns" subfolder contains a  
placemark for each unique pattern used by a trip in the route. The "Trips"  
subfolder contains a placemark for each trip in the route.  
 
Since there can be many trips and trips for the same route are usually similar,  
they are not exported unless the --showtrips option is used. There is also  
another option --splitroutes that groups the routes by vehicle type resulting  
in a folder hierarchy which looks like this at the top level:  
 
- Stops  
- Routes - Bus  
- Routes - Tram  
- Routes - Rail  
- Shapes  
"""  
 
try:  
import xml.etree.ElementTree as ET # python 2.5  
except ImportError, e:  
import elementtree.ElementTree as ET # older pythons  
import optparse  
import os.path  
import sys  
import transitfeed  
from transitfeed import util  
 
 
class KMLWriter(object):  
"""This class knows how to write out a transit feed as KML.  
 
Sample usage:  
KMLWriter().Write(<transitfeed.Schedule object>, <output filename>)  
 
Attributes:  
show_trips: True if the individual trips should be included in the routes.  
show_trips: True if the individual trips should be placed on ground.  
split_routes: True if the routes should be split by type.  
shape_points: True if individual shape points should be plotted.  
"""  
 
def __init__(self):  
"""Initialise."""  
self.show_trips = False  
self.split_routes = False  
self.shape_points = False  
self.altitude_per_sec = 0.0  
self.date_filter = None  
 
def _SetIndentation(self, elem, level=0):  
"""Indented the ElementTree DOM.  
 
This is the recommended way to cause an ElementTree DOM to be  
prettyprinted on output, as per: http://effbot.org/zone/element-lib.htm  
 
Run this on the root element before outputting the tree.  
 
Args:  
elem: The element to start indenting from, usually the document root.  
level: Current indentation level for recursion.  
"""  
i = "\n" + level*" "  
if len(elem):  
if not elem.text or not elem.text.strip():  
elem.text = i + " "  
for elem in elem:  
self._SetIndentation(elem, level+1)  
if not elem.tail or not elem.tail.strip():  
elem.tail = i  
else:  
if level and (not elem.tail or not elem.tail.strip()):  
elem.tail = i  
 
def _CreateFolder(self, parent, name, visible=True, description=None):  
"""Create a KML Folder element.  
 
Args:  
parent: The parent ElementTree.Element instance.  
name: The folder name as a string.  
visible: Whether the folder is initially visible or not.  
description: A description string or None.  
 
Returns:  
The folder ElementTree.Element instance.  
"""  
folder = ET.SubElement(parent, 'Folder')  
name_tag = ET.SubElement(folder, 'name')  
name_tag.text = name  
if description is not None:  
desc_tag = ET.SubElement(folder, 'description')  
desc_tag.text = description  
if not visible:  
visibility = ET.SubElement(folder, 'visibility')  
visibility.text = '0'  
return folder  
 
def _CreateStyleForRoute(self, doc, route):  
"""Create a KML Style element for the route.  
 
The style sets the line colour if the route colour is specified. The  
line thickness is set depending on the vehicle type.  
 
Args:  
doc: The KML Document ElementTree.Element instance.  
route: The transitfeed.Route to create the style for.  
 
Returns:  
The id of the style as a string.  
"""  
style_id = 'route_%s' % route.route_id  
style = ET.SubElement(doc, 'Style', {'id': style_id})  
linestyle = ET.SubElement(style, 'LineStyle')  
width = ET.SubElement(linestyle, 'width')  
type_to_width = {0: '3', # Tram  
1: '3', # Subway  
2: '5', # Rail  
3: '1'} # Bus  
width.text = type_to_width.get(route.route_type, '1')  
if route.route_color:  
color = ET.SubElement(linestyle, 'color')  
red = route.route_color[0:2].lower()  
green = route.route_color[2:4].lower()  
blue = route.route_color[4:6].lower()  
color.text = 'ff%s%s%s' % (blue, green, red)  
return style_id  
 
def _CreatePlacemark(self, parent, name, style_id=None, visible=True,  
description=None):  
"""Create a KML Placemark element.  
 
Args:  
parent: The parent ElementTree.Element instance.  
name: The placemark name as a string.  
style_id: If not None, the id of a style to use for the placemark.  
visible: Whether the placemark is initially visible or not.  
description: A description string or None.  
 
Returns:  
The placemark ElementTree.Element instance.  
"""  
placemark = ET.SubElement(parent, 'Placemark')  
placemark_name = ET.SubElement(placemark, 'name')  
placemark_name.text = name  
if description is not None:  
desc_tag = ET.SubElement(placemark, 'description')  
desc_tag.text = description  
if style_id is not None:  
styleurl = ET.SubElement(placemark, 'styleUrl')  
styleurl.text = '#%s' % style_id  
if not visible:  
visibility = ET.SubElement(placemark, 'visibility')  
visibility.text = '0'  
return placemark  
 
def _CreateLineString(self, parent, coordinate_list):  
"""Create a KML LineString element.  
 
The points of the string are given in coordinate_list. Every element of  
coordinate_list should be one of a tuple (longitude, latitude) or a tuple  
(longitude, latitude, altitude).  
 
Args:  
parent: The parent ElementTree.Element instance.  
coordinate_list: The list of coordinates.  
 
Returns:  
The LineString ElementTree.Element instance or None if coordinate_list is  
empty.  
"""  
if not coordinate_list:  
return None  
linestring = ET.SubElement(parent, 'LineString')  
tessellate = ET.SubElement(linestring, 'tessellate')  
tessellate.text = '1'  
if len(coordinate_list[0]) == 3:  
altitude_mode = ET.SubElement(linestring, 'altitudeMode')  
altitude_mode.text = 'absolute'  
coordinates = ET.SubElement(linestring, 'coordinates')  
if len(coordinate_list[0]) == 3:  
coordinate_str_list = ['%f,%f,%f' % t for t in coordinate_list]  
else:  
coordinate_str_list = ['%f,%f' % t for t in coordinate_list]  
coordinates.text = ' '.join(coordinate_str_list)  
return linestring  
 
def _CreateLineStringForShape(self, parent, shape):  
"""Create a KML LineString using coordinates from a shape.  
 
Args:  
parent: The parent ElementTree.Element instance.  
shape: The transitfeed.Shape instance.  
 
Returns:  
The LineString ElementTree.Element instance or None if coordinate_list is  
empty.  
"""  
coordinate_list = [(longitude, latitude) for  
(latitude, longitude, distance) in shape.points]  
return self._CreateLineString(parent, coordinate_list)  
 
def _CreateStopsFolder(self, schedule, doc):  
"""Create a KML Folder containing placemarks for each stop in the schedule.  
 
If there are no stops in the schedule then no folder is created.  
 
Args:  
schedule: The transitfeed.Schedule instance.  
doc: The KML Document ElementTree.Element instance.  
 
Returns:  
The Folder ElementTree.Element instance or None if there are no stops.  
"""  
if not schedule.GetStopList():  
return None  
stop_folder = self._CreateFolder(doc, 'Stops')  
stops = list(schedule.GetStopList())  
stops.sort(key=lambda x: x.stop_name)  
for stop in stops:  
desc_items = []  
if stop.stop_desc:  
desc_items.append(stop.stop_desc)  
if stop.stop_url:  
desc_items.append('Stop info page: <a href="%s">%s</a>' % (  
stop.stop_url, stop.stop_url))  
description = '<br/>'.join(desc_items) or None  
placemark = self._CreatePlacemark(stop_folder, stop.stop_name,  
description=description)  
point = ET.SubElement(placemark, 'Point')  
coordinates = ET.SubElement(point, 'coordinates')  
coordinates.text = '%.6f,%.6f' % (stop.stop_lon, stop.stop_lat)  
return stop_folder  
 
def _CreateRoutePatternsFolder(self, parent, route,  
style_id=None, visible=True):  
"""Create a KML Folder containing placemarks for each pattern in the route.  
 
A pattern is a sequence of stops used by one of the trips in the route.  
 
If there are not patterns for the route then no folder is created and None  
is returned.  
 
Args:  
parent: The parent ElementTree.Element instance.  
route: The transitfeed.Route instance.  
style_id: The id of a style to use if not None.  
visible: Whether the folder is initially visible or not.  
 
Returns:  
The Folder ElementTree.Element instance or None if there are no patterns.  
"""  
pattern_id_to_trips = route.GetPatternIdTripDict()  
if not pattern_id_to_trips:  
return None  
 
# sort by number of trips using the pattern  
pattern_trips = pattern_id_to_trips.values()  
pattern_trips.sort(lambda a, b: cmp(len(b), len(a)))  
 
folder = self._CreateFolder(parent, 'Patterns', visible)  
for n, trips in enumerate(pattern_trips):  
trip_ids = [trip.trip_id for trip in trips]  
name = 'Pattern %d (trips: %d)' % (n+1, len(trips))  
description = 'Trips using this pattern (%d in total): %s' % (  
len(trips), ', '.join(trip_ids))  
placemark = self._CreatePlacemark(folder, name, style_id, visible,  
description)  
coordinates = [(stop.stop_lon, stop.stop_lat)  
for stop in trips[0].GetPattern()]  
self._CreateLineString(placemark, coordinates)  
return folder  
 
def _CreateRouteShapesFolder(self, schedule, parent, route,  
style_id=None, visible=True):  
"""Create a KML Folder for the shapes of a route.  
 
The folder contains a placemark for each shape referenced by a trip in the  
route. If there are no such shapes, no folder is created and None is  
returned.  
 
Args:  
schedule: The transitfeed.Schedule instance.  
parent: The parent ElementTree.Element instance.  
route: The transitfeed.Route instance.  
style_id: The id of a style to use if not None.  
visible: Whether the placemark is initially visible or not.  
 
Returns:  
The Folder ElementTree.Element instance or None.  
"""  
shape_id_to_trips = {}  
for trip in route.trips:  
if trip.shape_id:  
shape_id_to_trips.setdefault(trip.shape_id, []).append(trip)  
if not shape_id_to_trips:  
return None  
 
# sort by the number of trips using the shape  
shape_id_to_trips_items = shape_id_to_trips.items()  
shape_id_to_trips_items.sort(lambda a, b: cmp(len(b[1]), len(a[1])))  
 
folder = self._CreateFolder(parent, 'Shapes', visible)  
for shape_id, trips in shape_id_to_trips_items:  
trip_ids = [trip.trip_id for trip in trips]  
name = '%s (trips: %d)' % (shape_id, len(trips))  
description = 'Trips using this shape (%d in total): %s' % (  
len(trips), ', '.join(trip_ids))  
placemark = self._CreatePlacemark(folder, name, style_id, visible,  
description)  
self._CreateLineStringForShape(placemark, schedule.GetShape(shape_id))  
return folder  
 
def _CreateRouteTripsFolder(self, parent, route, style_id=None, schedule=None):  
"""Create a KML Folder containing all the trips in the route.  
 
The folder contains a placemark for each of these trips. If there are no  
trips in the route, no folder is created and None is returned.  
 
Args:  
parent: The parent ElementTree.Element instance.  
route: The transitfeed.Route instance.  
style_id: A style id string for the placemarks or None.  
 
Returns:  
The Folder ElementTree.Element instance or None.  
"""  
if not route.trips:  
return None  
trips = list(route.trips)  
trips.sort(key=lambda x: x.trip_id)  
trips_folder = self._CreateFolder(parent, 'Trips', visible=False)  
for trip in trips:  
if (self.date_filter and  
not trip.service_period.IsActiveOn(self.date_filter)):  
continue  
 
if trip.trip_headsign:  
description = 'Headsign: %s' % trip.trip_headsign  
else:  
description = None  
 
coordinate_list = []  
for secs, stoptime, tp in trip.GetTimeInterpolatedStops():  
if self.altitude_per_sec > 0:  
coordinate_list.append((stoptime.stop.stop_lon, stoptime.stop.stop_lat,  
(secs - 3600 * 4) * self.altitude_per_sec))  
else:  
coordinate_list.append((stoptime.stop.stop_lon,  
stoptime.stop.stop_lat))  
placemark = self._CreatePlacemark(trips_folder,  
trip.trip_id,  
style_id=style_id,  
visible=False,  
description=description)  
self._CreateLineString(placemark, coordinate_list)  
return trips_folder  
 
def _CreateRoutesFolder(self, schedule, doc, route_type=None):  
"""Create a KML Folder containing routes in a schedule.  
 
The folder contains a subfolder for each route in the schedule of type  
route_type. If route_type is None, then all routes are selected. Each  
subfolder contains a flattened graph placemark, a route shapes placemark  
and, if show_trips is True, a subfolder containing placemarks for each of  
the trips in the route.  
 
If there are no routes in the schedule then no folder is created and None  
is returned.  
 
Args:  
schedule: The transitfeed.Schedule instance.  
doc: The KML Document ElementTree.Element instance.  
route_type: The route type integer or None.  
 
Returns:  
The Folder ElementTree.Element instance or None.  
"""  
 
def GetRouteName(route):  
"""Return a placemark name for the route.  
 
Args:  
route: The transitfeed.Route instance.  
 
Returns:  
The name as a string.  
"""  
name_parts = []  
if route.route_short_name:  
name_parts.append('<b>%s</b>' % route.route_short_name)  
if route.route_long_name:  
name_parts.append(route.route_long_name)  
return ' - '.join(name_parts) or route.route_id  
 
def GetRouteDescription(route):  
"""Return a placemark description for the route.  
 
Args:  
route: The transitfeed.Route instance.  
 
Returns:  
The description as a string.  
"""  
desc_items = []  
if route.route_desc:  
desc_items.append(route.route_desc)  
if route.route_url:  
desc_items.append('Route info page: <a href="%s">%s</a>' % (  
route.route_url, route.route_url))  
description = '<br/>'.join(desc_items)  
return description or None  
 
routes = [route for route in schedule.GetRouteList()  
if route_type is None or route.route_type == route_type]  
if not routes:  
return None  
routes.sort(key=lambda x: GetRouteName(x))  
 
if route_type is not None:  
route_type_names = {0: 'Tram, Streetcar or Light rail',  
1: 'Subway or Metro',  
2: 'Rail',  
3: 'Bus',  
4: 'Ferry',  
5: 'Cable car',  
6: 'Gondola or suspended cable car',  
7: 'Funicular'}  
type_name = route_type_names.get(route_type, str(route_type))  
folder_name = 'Routes - %s' % type_name  
else:  
folder_name = 'Routes'  
routes_folder = self._CreateFolder(doc, folder_name, visible=False)  
 
for route in routes:  
style_id = self._CreateStyleForRoute(doc, route)  
route_folder = self._CreateFolder(routes_folder,  
GetRouteName(route),  
description=GetRouteDescription(route))  
self._CreateRouteShapesFolder(schedule, route_folder, route,  
style_id, False)  
self._CreateRoutePatternsFolder(route_folder, route, style_id, False)  
if self.show_trips:  
self._CreateRouteTripsFolder(route_folder, route, style_id, schedule)  
return routes_folder  
 
def _CreateShapesFolder(self, schedule, doc):  
"""Create a KML Folder containing all the shapes in a schedule.  
 
The folder contains a placemark for each shape. If there are no shapes in  
the schedule then the folder is not created and None is returned.  
 
Args:  
schedule: The transitfeed.Schedule instance.  
doc: The KML Document ElementTree.Element instance.  
 
Returns:  
The Folder ElementTree.Element instance or None.  
"""  
if not schedule.GetShapeList():  
return None  
shapes_folder = self._CreateFolder(doc, 'Shapes')  
shapes = list(schedule.GetShapeList())  
shapes.sort(key=lambda x: x.shape_id)  
for shape in shapes:  
placemark = self._CreatePlacemark(shapes_folder, shape.shape_id)  
self._CreateLineStringForShape(placemark, shape)  
if self.shape_points:  
self._CreateShapePointFolder(shapes_folder, shape)  
return shapes_folder  
 
def _CreateShapePointFolder(self, shapes_folder, shape):  
"""Create a KML Folder containing all the shape points in a shape.  
 
The folder contains placemarks for each shapepoint.  
 
Args:  
shapes_folder: A KML Shape Folder ElementTree.Element instance  
shape: The shape to plot.  
 
Returns:  
The Folder ElementTree.Element instance or None.  
"""  
 
folder_name = shape.shape_id + ' Shape Points'  
folder = self._CreateFolder(shapes_folder, folder_name, visible=False)  
for (index, (lat, lon, dist)) in enumerate(shape.points):  
placemark = self._CreatePlacemark(folder, str(index+1))  
point = ET.SubElement(placemark, 'Point')  
coordinates = ET.SubElement(point, 'coordinates')  
coordinates.text = '%.6f,%.6f' % (lon, lat)  
return folder  
 
def Write(self, schedule, output_file):  
"""Writes out a feed as KML.  
 
Args:  
schedule: A transitfeed.Schedule object containing the feed to write.  
output_file: The name of the output KML file, or file object to use.  
"""  
# Generate the DOM to write  
root = ET.Element('kml')  
root.attrib['xmlns'] = 'http://earth.google.com/kml/2.1'  
doc = ET.SubElement(root, 'Document')  
open_tag = ET.SubElement(doc, 'open')  
open_tag.text = '1'  
self._CreateStopsFolder(schedule, doc)  
if self.split_routes:  
route_types = set()  
for route in schedule.GetRouteList():  
route_types.add(route.route_type)  
route_types = list(route_types)  
route_types.sort()  
for route_type in route_types:  
self._CreateRoutesFolder(schedule, doc, route_type)  
else:  
self._CreateRoutesFolder(schedule, doc)  
self._CreateShapesFolder(schedule, doc)  
 
# Make sure we pretty-print  
self._SetIndentation(root)  
 
# Now write the output  
if isinstance(output_file, file):  
output = output_file  
else:  
output = open(output_file, 'w')  
output.write("""<?xml version="1.0" encoding="UTF-8"?>\n""")  
ET.ElementTree(root).write(output, 'utf-8')  
 
 
def main():  
usage = \  
'''%prog [options] <input GTFS.zip> [<output.kml>]  
 
Reads GTFS file or directory <input GTFS.zip> and creates a KML file  
<output.kml> that contains the geographical features of the input. If  
<output.kml> is omitted a default filename is picked based on  
<input GTFS.zip>. By default the KML contains all stops and shapes.  
'''  
 
parser = util.OptionParserLongError(  
usage=usage, version='%prog '+transitfeed.__version__)  
parser.add_option('-t', '--showtrips', action='store_true',  
dest='show_trips',  
help='include the individual trips for each route')  
parser.add_option('-a', '--altitude_per_sec', action='store', type='float',  
dest='altitude_per_sec',  
help='if greater than 0 trips are drawn with time axis '  
'set to this many meters high for each second of time')  
parser.add_option('-s', '--splitroutes', action='store_true',  
dest='split_routes',  
help='split the routes by type')  
parser.add_option('-d', '--date_filter', action='store', type='string',  
dest='date_filter',  
help='Restrict to trips active on date YYYYMMDD')  
parser.add_option('-p', '--display_shape_points', action='store_true',  
dest='shape_points',  
help='shows the actual points along shapes')  
 
parser.set_defaults(altitude_per_sec=1.0)  
options, args = parser.parse_args()  
 
if len(args) < 1:  
parser.error('You must provide the path of an input GTFS file.')  
 
if args[0] == 'IWantMyCrash':  
raise Exception('For testCrashHandler')  
 
input_path = args[0]  
if len(args) >= 2:  
output_path = args[1]  
else:  
path = os.path.normpath(input_path)  
(feed_dir, feed) = os.path.split(path)  
if '.' in feed:  
feed = feed.rsplit('.', 1)[0] # strip extension  
output_filename = '%s.kml' % feed  
output_path = os.path.join(feed_dir, output_filename)  
 
loader = transitfeed.Loader(input_path,  
problems=transitfeed.ProblemReporter())  
feed = loader.Load()  
print "Writing %s" % output_path  
writer = KMLWriter()  
writer.show_trips = options.show_trips  
writer.altitude_per_sec = options.altitude_per_sec  
writer.split_routes = options.split_routes  
writer.date_filter = options.date_filter  
writer.shape_points = options.shape_points  
writer.Write(feed, output_path)  
 
 
if __name__ == '__main__':  
util.RunWithCrashHandler(main)  
 
#!/usr/bin/python2.5  
#  
# Copyright 2007 Google Inc. All Rights Reserved.  
#  
# Licensed under the Apache License, Version 2.0 (the "License");  
# you may not use this file except in compliance with the License.  
# You may obtain a copy of the License at  
#  
# http://www.apache.org/licenses/LICENSE-2.0  
#  
# Unless required by applicable law or agreed to in writing, software  
# distributed under the License is distributed on an "AS IS" BASIS,  
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.  
# See the License for the specific language governing permissions and  
# limitations under the License.  
 
"""A tool for merging two Google Transit feeds.  
 
Given two Google Transit feeds intending to cover two disjoint calendar  
intervals, this tool will attempt to produce a single feed by merging as much  
of the two feeds together as possible.  
 
For example, most stops remain the same throughout the year. Therefore, many  
of the stops given in stops.txt for the first feed represent the same stops  
given in the second feed. This tool will try to merge these stops so they  
only appear once in the resultant feed.  
 
A note on terminology: The first schedule is referred to as the "old" schedule;  
the second as the "new" schedule. The resultant schedule is referred to as  
the "merged" schedule. Names of things in the old schedule are variations of  
the letter "a" while names of things from the new schedule are variations of  
"b". The objects that represents routes, agencies and so on are called  
"entities".  
 
usage: merge.py [options] old_feed_path new_feed_path merged_feed_path  
 
Run merge.py --help for a list of the possible options.  
"""  
 
 
__author__ = 'timothy.stranex@gmail.com (Timothy Stranex)'  
 
 
import datetime  
import optparse  
import os  
import re  
import sys  
import time  
import transitfeed  
from transitfeed import util  
import webbrowser  
 
 
# TODO:  
# 1. write unit tests that use actual data  
# 2. write a proper trip and stop_times merger  
# 3. add a serialised access method for stop_times and shapes to transitfeed  
# 4. add support for merging schedules which have some service period overlap  
 
 
def ApproximateDistanceBetweenPoints(pa, pb):  
"""Finds the distance between two points on the Earth's surface.  
 
This is an approximate distance based on assuming that the Earth is a sphere.  
The points are specified by their lattitude and longitude.  
 
Args:  
pa: the first (lat, lon) point tuple  
pb: the second (lat, lon) point tuple  
 
Returns:  
The distance as a float in metres.  
"""  
alat, alon = pa  
blat, blon = pb  
sa = transitfeed.Stop(lat=alat, lng=alon)  
sb = transitfeed.Stop(lat=blat, lng=blon)  
return transitfeed.ApproximateDistanceBetweenStops(sa, sb)  
 
 
class Error(Exception):  
"""The base exception class for this module."""  
 
 
class MergeError(Error):  
"""An error produced when two entities could not be merged."""  
 
 
class MergeProblemWithContext(transitfeed.ExceptionWithContext):  
"""The base exception class for problem reporting in the merge module.  
 
Attributes:  
dataset_merger: The DataSetMerger that generated this problem.  
entity_type_name: The entity type of the dataset_merger. This is just  
dataset_merger.ENTITY_TYPE_NAME.  
ERROR_TEXT: The text used for generating the problem message.  
"""  
 
def __init__(self, dataset_merger, problem_type=transitfeed.TYPE_WARNING,  
**kwargs):  
"""Initialise the exception object.  
 
Args:  
dataset_merger: The DataSetMerger instance that generated this problem.  
problem_type: The problem severity. This should be set to one of the  
corresponding constants in transitfeed.  
kwargs: Keyword arguments to be saved as instance attributes.  
"""  
kwargs['type'] = problem_type  
kwargs['entity_type_name'] = dataset_merger.ENTITY_TYPE_NAME  
transitfeed.ExceptionWithContext.__init__(self, None, None, **kwargs)  
self.dataset_merger = dataset_merger  
 
def FormatContext(self):  
return "In files '%s'" % self.dataset_merger.FILE_NAME  
 
 
class SameIdButNotMerged(MergeProblemWithContext):  
ERROR_TEXT = ("There is a %(entity_type_name)s in the old feed with id "  
"'%(id)s' and one from the new feed with the same id but "  
"they could not be merged:")  
 
 
class CalendarsNotDisjoint(MergeProblemWithContext):  
ERROR_TEXT = ("The service periods could not be merged since they are not "  
"disjoint.")  
 
 
class MergeNotImplemented(MergeProblemWithContext):  
ERROR_TEXT = ("The feed merger does not currently support merging in this "  
"file. The entries have been duplicated instead.")  
 
 
class FareRulesBroken(MergeProblemWithContext):  
ERROR_TEXT = ("The feed merger is currently unable to handle fare rules "  
"properly.")  
 
 
class MergeProblemReporterBase(transitfeed.ProblemReporterBase):  
"""The base problem reporter class for the merge module."""  
 
def SameIdButNotMerged(self, dataset, entity_id, reason):  
self._Report(SameIdButNotMerged(dataset, id=entity_id, reason=reason))  
 
def CalendarsNotDisjoint(self, dataset):  
self._Report(CalendarsNotDisjoint(dataset,  
problem_type=transitfeed.TYPE_ERROR))  
 
def MergeNotImplemented(self, dataset):  
self._Report(MergeNotImplemented(dataset))  
 
def FareRulesBroken(self, dataset):  
self._Report(FareRulesBroken(dataset))  
 
 
class ExceptionProblemReporter(MergeProblemReporterBase):  
"""A problem reporter that reports errors by raising exceptions."""  
 
def __init__(self, raise_warnings=False):  
"""Initialise.  
 
Args:  
raise_warnings: If this is True then warnings are also raised as  
exceptions.  
"""  
MergeProblemReporterBase.__init__(self)  
self._raise_warnings = raise_warnings  
 
def _Report(self, merge_problem):  
if self._raise_warnings or merge_problem.IsError():  
raise merge_problem  
 
 
class HTMLProblemReporter(MergeProblemReporterBase):  
"""A problem reporter which generates HTML output."""  
 
def __init__(self):  
"""Initialise."""  
MergeProblemReporterBase.__init__(self)  
self._dataset_warnings = {} # a map from DataSetMergers to their warnings  
self._dataset_errors = {}  
self._warning_count = 0  
self._error_count = 0  
 
def _Report(self, merge_problem):  
if merge_problem.IsWarning():  
dataset_problems = self._dataset_warnings  
self._warning_count += 1  
else:  
dataset_problems = self._dataset_errors  
self._error_count += 1  
 
problem_html = '<li>%s</li>' % (  
merge_problem.FormatProblem().replace('\n', '<br>'))  
dataset_problems.setdefault(merge_problem.dataset_merger, []).append(  
problem_html)  
 
def _GenerateStatsTable(self, feed_merger):  
"""Generate an HTML table of merge statistics.  
 
Args:  
feed_merger: The FeedMerger instance.  
 
Returns:  
The generated HTML as a string.  
"""  
rows = []  
rows.append('<tr><th class="header"/><th class="header">Merged</th>'  
'<th class="header">Copied from old feed</th>'  
'<th class="header">Copied from new feed</th></tr>')  
for merger in feed_merger.GetMergerList():  
stats = merger.GetMergeStats()  
if stats is None:  
continue  
merged, not_merged_a, not_merged_b = stats  
rows.append('<tr><th class="header">%s</th>'  
'<td class="header">%d</td>'  
'<td class="header">%d</td>'  
'<td class="header">%d</td></tr>' %  
(merger.DATASET_NAME, merged, not_merged_a, not_merged_b))  
return '<table>%s</table>' % '\n'.join(rows)  
 
def _GenerateSection(self, problem_type):  
"""Generate a listing of the given type of problems.  
 
Args:  
problem_type: The type of problem. This is one of the problem type  
constants from transitfeed.  
 
Returns:  
The generated HTML as a string.  
"""  
if problem_type == transitfeed.TYPE_WARNING:  
dataset_problems = self._dataset_warnings  
heading = 'Warnings'  
else:  
dataset_problems = self._dataset_errors  
heading = 'Errors'  
 
if not dataset_problems:  
return ''  
 
prefix = '<h2 class="issueHeader">%s:</h2>' % heading  
dataset_sections = []  
for dataset_merger, problems in dataset_problems.items():  
dataset_sections.append('<h3>%s</h3><ol>%s</ol>' % (  
dataset_merger.FILE_NAME, '\n'.join(problems)))  
body = '\n'.join(dataset_sections)  
return prefix + body  
 
def _GenerateSummary(self):  
"""Generate a summary of the warnings and errors.  
 
Returns:  
The generated HTML as a string.  
"""  
items = []  
if self._dataset_errors:  
items.append('errors: %d' % self._error_count)  
if self._dataset_warnings:  
items.append('warnings: %d' % self._warning_count)  
 
if items:  
return '<p><span class="fail">%s</span></p>' % '<br>'.join(items)  
else:  
return '<p><span class="pass">feeds merged successfully</span></p>'  
 
def WriteOutput(self, output_file, feed_merger,  
old_feed_path, new_feed_path, merged_feed_path):  
"""Write the HTML output to a file.  
 
Args:  
output_file: The file object that the HTML output will be written to.  
feed_merger: The FeedMerger instance.  
old_feed_path: The path to the old feed file as a string.  
new_feed_path: The path to the new feed file as a string  
merged_feed_path: The path to the merged feed file as a string. This  
may be None if no merged feed was written.  
"""  
if merged_feed_path is None:  
html_merged_feed_path = ''  
else:  
html_merged_feed_path = '<p>Merged feed created: <code>%s</code></p>' % (  
merged_feed_path)  
 
html_header = """<html>  
<head>  
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/>  
<title>Feed Merger Results</title>  
<style>  
body {font-family: Georgia, serif; background-color: white}  
.path {color: gray}  
div.problem {max-width: 500px}  
td,th {background-color: khaki; padding: 2px; font-family:monospace}  
td.problem,th.problem {background-color: dc143c; color: white; padding: 2px;  
font-family:monospace}  
table {border-spacing: 5px 0px; margin-top: 3px}  
h3.issueHeader {padding-left: 1em}  
span.pass {background-color: lightgreen}  
span.fail {background-color: yellow}  
.pass, .fail {font-size: 16pt; padding: 3px}  
ol,.unused {padding-left: 40pt}  
.header {background-color: white; font-family: Georgia, serif; padding: 0px}  
th.header {text-align: right; font-weight: normal; color: gray}  
.footer {font-size: 10pt}  
</style>  
</head>  
<body>  
<h1>Feed merger results</h1>  
<p>Old feed: <code>%(old_feed_path)s</code></p>  
<p>New feed: <code>%(new_feed_path)s</code></p>  
%(html_merged_feed_path)s""" % locals()  
 
html_stats = self._GenerateStatsTable(feed_merger)  
html_summary = self._GenerateSummary()  
html_errors = self._GenerateSection(transitfeed.TYPE_ERROR)  
html_warnings = self._GenerateSection(transitfeed.TYPE_WARNING)  
 
html_footer = """  
<div class="footer">  
Generated using transitfeed version %s on %s.  
</div>  
</body>  
</html>""" % (transitfeed.__version__,  
time.strftime('%B %d, %Y at %I:%M %p %Z'))  
 
output_file.write(transitfeed.EncodeUnicode(html_header))  
output_file.write(transitfeed.EncodeUnicode(html_stats))  
output_file.write(transitfeed.EncodeUnicode(html_summary))  
output_file.write(transitfeed.EncodeUnicode(html_errors))  
output_file.write(transitfeed.EncodeUnicode(html_warnings))  
output_file.write(transitfeed.EncodeUnicode(html_footer))  
 
 
class ConsoleWarningRaiseErrorProblemReporter(transitfeed.ProblemReporterBase):  
"""Problem reporter to use when loading feeds for merge."""  
 
def _Report(self, e):  
if e.IsError():  
raise e  
else:  
print transitfeed.EncodeUnicode(e.FormatProblem())  
context = e.FormatContext()  
if context:  
print context  
 
 
def LoadWithoutErrors(path, memory_db):  
""""Return a Schedule object loaded from path; sys.exit for any error."""  
loading_problem_handler = ConsoleWarningRaiseErrorProblemReporter()  
try:  
schedule = transitfeed.Loader(path,  
memory_db=memory_db,  
problems=loading_problem_handler).Load()  
except transitfeed.ExceptionWithContext, e:  
print >>sys.stderr, (  
"\n\nFeeds to merge must load without any errors.\n"  
"While loading %s the following error was found:\n%s\n%s\n" %  
(path, e.FormatContext(), transitfeed.EncodeUnicode(e.FormatProblem())))  
sys.exit(1)  
return schedule  
 
 
class DataSetMerger(object):  
"""A DataSetMerger is in charge of merging a set of entities.  
 
This is an abstract class and should be subclassed for each different entity  
type.  
 
Attributes:  
ENTITY_TYPE_NAME: The name of the entity type like 'agency' or 'stop'.  
FILE_NAME: The name of the file containing this data set like 'agency.txt'.  
DATASET_NAME: A name for the dataset like 'Agencies' or 'Stops'.  
"""  
 
def __init__(self, feed_merger):  
"""Initialise.  
 
Args:  
feed_merger: The FeedMerger.  
"""  
self.feed_merger = feed_merger  
self._num_merged = 0  
self._num_not_merged_a = 0  
self._num_not_merged_b = 0  
 
def _MergeIdentical(self, a, b):  
"""Tries to merge two values. The values are required to be identical.  
 
Args:  
a: The first value.  
b: The second value.  
 
Returns:  
The trivially merged value.  
 
Raises:  
MergeError: The values were not identical.  
"""  
if a != b:  
raise MergeError("values must be identical ('%s' vs '%s')" %  
(transitfeed.EncodeUnicode(a),  
transitfeed.EncodeUnicode(b)))  
return b  
 
def _MergeIdenticalCaseInsensitive(self, a, b):  
"""Tries to merge two strings.  
 
The string are required to be the same ignoring case. The second string is  
always used as the merged value.  
 
Args:  
a: The first string.  
b: The second string.  
 
Returns:  
The merged string. This is equal to the second string.  
 
Raises:  
MergeError: The strings were not the same ignoring case.  
"""  
if a.lower() != b.lower():  
raise MergeError("values must be the same (case insensitive) "  
"('%s' vs '%s')" % (transitfeed.EncodeUnicode(a),  
transitfeed.EncodeUnicode(b)))  
return b  
 
def _MergeOptional(self, a, b):  
"""Tries to merge two values which may be None.  
 
If both values are not None, they are required to be the same and the  
merge is trivial. If one of the values is None and the other is not None,  
the merge results in the one which is not None. If both are None, the merge  
results in None.  
 
Args:  
a: The first value.  
b: The second value.  
 
Returns:  
The merged value.  
 
Raises:  
MergeError: If both values are not None and are not the same.  
"""  
if a and b:  
if a != b:  
raise MergeError("values must be identical if both specified "  
"('%s' vs '%s')" % (transitfeed.EncodeUnicode(a),  
transitfeed.EncodeUnicode(b)))  
return a or b  
 
def _MergeSameAgency(self, a_agency_id, b_agency_id):  
"""Merge agency ids to the corresponding agency id in the merged schedule.  
 
Args:  
a_agency_id: an agency id from the old schedule  
b_agency_id: an agency id from the new schedule  
 
Returns:  
The agency id of the corresponding merged agency.  
 
Raises:  
MergeError: If a_agency_id and b_agency_id do not correspond to the same  
merged agency.  
KeyError: Either aaid or baid is not a valid agency id.  
"""  
a_agency_id = (a_agency_id or  
self.feed_merger.a_schedule.GetDefaultAgency().agency_id)  
b_agency_id = (b_agency_id or  
self.feed_merger.b_schedule.GetDefaultAgency().agency_id)  
a_agency = self.feed_merger.a_merge_map[  
self.feed_merger.a_schedule.GetAgency(a_agency_id)]  
b_agency = self.feed_merger.b_merge_map[  
self.feed_merger.b_schedule.GetAgency(b_agency_id)]  
if a_agency != b_agency:  
raise MergeError('agency must be the same')  
return a_agency.agency_id  
 
def _SchemedMerge(self, scheme, a, b):  
"""Tries to merge two entities according to a merge scheme.  
 
A scheme is specified by a map where the keys are entity attributes and the  
values are merge functions like Merger._MergeIdentical or  
Merger._MergeOptional. The entity is first migrated to the merged schedule.  
Then the attributes are individually merged as specified by the scheme.  
 
Args:  
scheme: The merge scheme, a map from entity attributes to merge  
functions.  
a: The entity from the old schedule.  
b: The entity from the new schedule.  
 
Returns:  
The migrated and merged entity.  
 
Raises:  
MergeError: One of the attributes was not able to be merged.  
"""  
migrated = self._Migrate(b, self.feed_merger.b_schedule, False)  
for attr, merger in scheme.items():  
a_attr = getattr(a, attr, None)  
b_attr = getattr(b, attr, None)  
try:  
merged_attr = merger(a_attr, b_attr)  
except MergeError, merge_error:  
raise MergeError("Attribute '%s' could not be merged: %s." % (  
attr, merge_error))  
if migrated is not None:  
setattr(migrated, attr, merged_attr)  
return migrated  
 
def _MergeSameId(self):  
"""Tries to merge entities based on their ids.  
 
This tries to merge only the entities from the old and new schedules which  
have the same id. These are added into the merged schedule. Entities which  
do not merge or do not have the same id as another entity in the other  
schedule are simply migrated into the merged schedule.  
 
This method is less flexible than _MergeDifferentId since it only tries  
to merge entities which have the same id while _MergeDifferentId tries to  
merge everything. However, it is faster and so should be used whenever  
possible.  
 
This method makes use of various methods like _Merge and _Migrate which  
are not implemented in the abstract DataSetMerger class. These method  
should be overwritten in a subclass to allow _MergeSameId to work with  
different entity types.  
 
Returns:  
The number of merged entities.  
"""  
a_not_merged = []  
b_not_merged = []  
 
for a in self._GetIter(self.feed_merger.a_schedule):  
try:  
b = self._GetById(self.feed_merger.b_schedule, self._GetId(a))  
except KeyError:  
# there was no entity in B with the same id as a  
a_not_merged.append(a)  
continue  
try:  
self._Add(a, b, self._MergeEntities(a, b))  
self._num_merged += 1  
except MergeError, merge_error:  
a_not_merged.append(a)  
b_not_merged.append(b)  
self._ReportSameIdButNotMerged(self._GetId(a), merge_error)  
 
for b in self._GetIter(self.feed_merger.b_schedule):  
try:  
a = self._GetById(self.feed_merger.a_schedule, self._GetId(b))  
except KeyError:  
# there was no entity in A with the same id as b  
b_not_merged.append(b)  
 
# migrate the remaining entities  
for a in a_not_merged:  
newid = self._HasId(self.feed_merger.b_schedule, self._GetId(a))  
self._Add(a, None, self._Migrate(a, self.feed_merger.a_schedule, newid))  
for b in b_not_merged:  
newid = self._HasId(self.feed_merger.a_schedule, self._GetId(b))  
self._Add(None, b, self._Migrate(b, self.feed_merger.b_schedule, newid))  
 
self._num_not_merged_a = len(a_not_merged)  
self._num_not_merged_b = len(b_not_merged)  
return self._num_merged  
 
def _MergeDifferentId(self):  
"""Tries to merge all possible combinations of entities.  
 
This tries to merge every entity in the old schedule with every entity in  
the new schedule. Unlike _MergeSameId, the ids do not need to match.  
However, _MergeDifferentId is much slower than _MergeSameId.  
 
This method makes use of various methods like _Merge and _Migrate which  
are not implemented in the abstract DataSetMerger class. These method  
should be overwritten in a subclass to allow _MergeSameId to work with  
different entity types.  
 
Returns:  
The number of merged entities.  
"""  
# TODO: The same entity from A could merge with multiple from B.  
# This should either generate an error or should be prevented from  
# happening.  
for a in self._GetIter(self.feed_merger.a_schedule):  
for b in self._GetIter(self.feed_merger.b_schedule):  
try:  
self._Add(a, b, self._MergeEntities(a, b))  
self._num_merged += 1  
except MergeError:  
continue  
 
for a in self._GetIter(self.feed_merger.a_schedule):  
if a not in self.feed_merger.a_merge_map:  
self._num_not_merged_a += 1  
newid = self._HasId(self.feed_merger.b_schedule, self._GetId(a))  
self._Add(a, None,  
self._Migrate(a, self.feed_merger.a_schedule, newid))  
for b in self._GetIter(self.feed_merger.b_schedule):  
if b not in self.feed_merger.b_merge_map:  
self._num_not_merged_b += 1  
newid = self._HasId(self.feed_merger.a_schedule, self._GetId(b))  
self._Add(None, b,  
self._Migrate(b, self.feed_merger.b_schedule, newid))  
 
return self._num_merged  
 
def _ReportSameIdButNotMerged(self, entity_id, reason):  
"""Report that two entities have the same id but could not be merged.  
 
Args:  
entity_id: The id of the entities.  
reason: A string giving a reason why they could not be merged.  
"""  
self.feed_merger.problem_reporter.SameIdButNotMerged(self,  
entity_id,  
reason)  
 
def _GetIter(self, schedule):  
"""Returns an iterator of entities for this data set in the given schedule.  
 
This method usually corresponds to one of the methods from  
transitfeed.Schedule like GetAgencyList() or GetRouteList().  
 
Note: This method must be overwritten in a subclass if _MergeSameId or  
_MergeDifferentId are to be used.  
 
Args:  
schedule: Either the old or new schedule from the FeedMerger.  
 
Returns:  
An iterator of entities.  
"""  
raise NotImplementedError()  
 
def _GetById(self, schedule, entity_id):  
"""Returns an entity given its id.  
 
This method usually corresponds to one of the methods from  
transitfeed.Schedule like GetAgency() or GetRoute().  
 
Note: This method must be overwritten in a subclass if _MergeSameId or  
_MergeDifferentId are to be used.  
 
Args:  
schedule: Either the old or new schedule from the FeedMerger.  
entity_id: The id string of the entity.  
 
Returns:  
The entity with the given id.  
 
Raises:  
KeyError: There is not entity with the given id.  
"""  
raise NotImplementedError()  
 
def _HasId(self, schedule, entity_id):  
"""Check if the schedule has an entity with the given id.  
 
Args:  
schedule: The transitfeed.Schedule instance to look in.  
entity_id: The id of the entity.  
 
Returns:  
True if the schedule has an entity with the id or False if not.  
"""  
try:  
self._GetById(schedule, entity_id)  
has = True  
except KeyError:  
has = False  
return has  
 
def _MergeEntities(self, a, b):  
"""Tries to merge the two entities.  
 
Note: This method must be overwritten in a subclass if _MergeSameId or  
_MergeDifferentId are to be used.  
 
Args:  
a: The entity from the old schedule.  
b: The entity from the new schedule.  
 
Returns:  
The merged migrated entity.  
 
Raises:  
MergeError: The entities were not able to be merged.  
"""  
raise NotImplementedError()  
 
def _Migrate(self, entity, schedule, newid):  
"""Migrates the entity to the merge schedule.  
 
This involves copying the entity and updating any ids to point to the  
corresponding entities in the merged schedule. If newid is True then  
a unique id is generated for the migrated entity using the original id  
as a prefix.  
 
Note: This method must be overwritten in a subclass if _MergeSameId or  
_MergeDifferentId are to be used.  
 
Args:  
entity: The entity to migrate.  
schedule: The schedule from the FeedMerger that contains ent.  
newid: Whether to generate a new id (True) or keep the original (False).  
 
Returns:  
The migrated entity.  
"""  
raise NotImplementedError()  
 
def _Add(self, a, b, migrated):  
"""Adds the migrated entity to the merged schedule.  
 
If a and b are both not None, it means that a and b were merged to create  
migrated. If one of a or b is None, it means that the other was not merged  
but has been migrated. This mapping is registered with the FeedMerger.  
 
Note: This method must be overwritten in a subclass if _MergeSameId or  
_MergeDifferentId are to be used.  
 
Args:  
a: The original entity from the old schedule.  
b: The original entity from the new schedule.  
migrated: The migrated entity for the merged schedule.  
"""  
raise NotImplementedError()  
 
def _GetId(self, entity):  
"""Returns the id of the given entity.  
 
Note: This method must be overwritten in a subclass if _MergeSameId or  
_MergeDifferentId are to be used.  
 
Args:  
entity: The entity.  
 
Returns:  
The id of the entity as a string or None.  
"""  
raise NotImplementedError()  
 
def MergeDataSets(self):  
"""Merge the data sets.  
 
This method is called in FeedMerger.MergeSchedule().  
 
Note: This method must be overwritten in a subclass.  
 
Returns:  
A boolean which is False if the dataset was unable to be merged and  
as a result the entire merge should be aborted. In this case, the problem  
will have been reported using the FeedMerger's problem reporter.  
"""  
raise NotImplementedError()  
 
def GetMergeStats(self):  
"""Returns some merge statistics.  
 
These are given as a tuple (merged, not_merged_a, not_merged_b) where  
"merged" is the number of merged entities, "not_merged_a" is the number of  
entities from the old schedule that were not merged and "not_merged_b" is  
the number of entities from the new schedule that were not merged.  
 
The return value can also be None. This means that there are no statistics  
for this entity type.  
 
The statistics are only available after MergeDataSets() has been called.  
 
Returns:  
Either the statistics tuple or None.  
"""  
return (self._num_merged, self._num_not_merged_a, self._num_not_merged_b)  
 
 
class AgencyMerger(DataSetMerger):  
"""A DataSetMerger for agencies."""  
 
ENTITY_TYPE_NAME = 'agency'  
FILE_NAME = 'agency.txt'  
DATASET_NAME = 'Agencies'  
 
def _GetIter(self, schedule):  
return schedule.GetAgencyList()  
 
def _GetById(self, schedule, agency_id):  
return schedule.GetAgency(agency_id)  
 
def _MergeEntities(self, a, b):  
"""Merges two agencies.  
 
To be merged, they are required to have the same id, name, url and  
timezone. The remaining language attribute is taken from the new agency.  
 
Args:  
a: The first agency.  
b: The second agency.  
 
Returns:  
The merged agency.  
 
Raises:  
MergeError: The agencies could not be merged.  
"""  
 
def _MergeAgencyId(a_agency_id, b_agency_id):  
"""Merge two agency ids.  
 
The only difference between this and _MergeIdentical() is that the values  
None and '' are regarded as being the same.  
 
Args:  
a_agency_id: The first agency id.  
b_agency_id: The second agency id.  
 
Returns:  
The merged agency id.  
 
Raises:  
MergeError: The agency ids could not be merged.  
"""  
a_agency_id = a_agency_id or None  
b_agency_id = b_agency_id or None  
return self._MergeIdentical(a_agency_id, b_agency_id)  
 
scheme = {'agency_id': _MergeAgencyId,  
'agency_name': self._MergeIdentical,  
'agency_url': self._MergeIdentical,  
'agency_timezone': self._MergeIdentical}  
return self._SchemedMerge(scheme, a, b)  
 
def _Migrate(self, entity, schedule, newid):  
a = transitfeed.Agency(field_dict=entity)  
if newid:  
a.agency_id = self.feed_merger.GenerateId(entity.agency_id)  
return a  
 
def _Add(self, a, b, migrated):  
self.feed_merger.Register(a, b, migrated)  
self.feed_merger.merged_schedule.AddAgencyObject(migrated)  
 
def _GetId(self, entity):  
return entity.agency_id  
 
def MergeDataSets(self):  
self._MergeSameId()  
return True  
 
 
class StopMerger(DataSetMerger):  
"""A DataSetMerger for stops.  
 
Attributes:  
largest_stop_distance: The largest distance allowed between stops that  
will be merged in metres.  
"""  
 
ENTITY_TYPE_NAME = 'stop'  
FILE_NAME = 'stops.txt'  
DATASET_NAME = 'Stops'  
 
largest_stop_distance = 10.0  
 
def __init__(self, feed_merger):  
DataSetMerger.__init__(self, feed_merger)  
self._merged = []  
self._a_not_merged = []  
self._b_not_merged = []  
 
def SetLargestStopDistance(self, distance):  
"""Sets largest_stop_distance."""  
self.largest_stop_distance = distance  
 
def _GetIter(self, schedule):  
return schedule.GetStopList()  
 
def _GetById(self, schedule, stop_id):  
return schedule.GetStop(stop_id)  
 
def _MergeEntities(self, a, b):  
"""Merges two stops.  
 
For the stops to be merged, they must have:  
- the same stop_id  
- the same stop_name (case insensitive)  
- the same zone_id  
- locations less than largest_stop_distance apart  
The other attributes can have arbitary changes. The merged attributes are  
taken from the new stop.  
 
Args:  
a: The first stop.  
b: The second stop.  
 
Returns:  
The merged stop.  
 
Raises:  
MergeError: The stops could not be merged.  
"""  
distance = transitfeed.ApproximateDistanceBetweenStops(a, b)  
if distance > self.largest_stop_distance:  
raise MergeError("Stops are too far apart: %.1fm "  
"(largest_stop_distance is %.1fm)." %  
(distance, self.largest_stop_distance))  
scheme = {'stop_id': self._MergeIdentical,  
'stop_name': self._MergeIdenticalCaseInsensitive,  
'zone_id': self._MergeIdentical,  
'location_type': self._MergeIdentical}  
return self._SchemedMerge(scheme, a, b)  
 
def _Migrate(self, entity, schedule, newid):  
migrated_stop = transitfeed.Stop(field_dict=entity)  
if newid:  
migrated_stop.stop_id = self.feed_merger.GenerateId(entity.stop_id)  
return migrated_stop  
 
def _Add(self, a, b, migrated_stop):  
self.feed_merger.Register(a, b, migrated_stop)  
 
# The migrated_stop will be added to feed_merger.merged_schedule later  
# since adding must be done after the zone_ids have been finalized.  
if a and b:  
self._merged.append((a, b, migrated_stop))  
elif a:  
self._a_not_merged.append((a, migrated_stop))  
elif b:  
self._b_not_merged.append((b, migrated_stop))  
 
def _GetId(self, entity):  
return entity.stop_id  
 
def MergeDataSets(self):  
num_merged = self._MergeSameId()  
fm = self.feed_merger  
 
# now we do all the zone_id and parent_station mapping  
 
# the zone_ids for merged stops can be preserved  
for (a, b, merged_stop) in self._merged:  
assert a.zone_id == b.zone_id  
fm.a_zone_map[a.zone_id] = a.zone_id  
fm.b_zone_map[b.zone_id] = b.zone_id  
merged_stop.zone_id = a.zone_id  
if merged_stop.parent_station:  
# Merged stop has a parent. Update it to be the parent it had in b.  
parent_in_b = fm.b_schedule.GetStop(b.parent_station)  
merged_stop.parent_station = fm.b_merge_map[parent_in_b].stop_id  
fm.merged_schedule.AddStopObject(merged_stop)  
 
self._UpdateAndMigrateUnmerged(self._a_not_merged, fm.a_zone_map,  
fm.a_merge_map, fm.a_schedule)  
self._UpdateAndMigrateUnmerged(self._b_not_merged, fm.b_zone_map,  
fm.b_merge_map, fm.b_schedule)  
 
print 'Stops merged: %d of %d, %d' % (  
num_merged,  
len(fm.a_schedule.GetStopList()),  
len(fm.b_schedule.GetStopList()))  
return True  
 
def _UpdateAndMigrateUnmerged(self, not_merged_stops, zone_map, merge_map,  
schedule):  
"""Correct references in migrated unmerged stops and add to merged_schedule.  
 
For stops migrated from one of the input feeds to the output feed update the  
parent_station and zone_id references to point to objects in the output  
feed. Then add the migrated stop to the new schedule.  
 
Args:  
not_merged_stops: list of stops from one input feed that have not been  
merged  
zone_map: map from zone_id in the input feed to zone_id in the output feed  
merge_map: map from Stop objects in the input feed to Stop objects in  
the output feed  
schedule: the input Schedule object  
"""  
# for the unmerged stops, we use an already mapped zone_id if possible  
# if not, we generate a new one and add it to the map  
for stop, migrated_stop in not_merged_stops:  
if stop.zone_id in zone_map:  
migrated_stop.zone_id = zone_map[stop.zone_id]  
else:  
migrated_stop.zone_id = self.feed_merger.GenerateId(stop.zone_id)  
zone_map[stop.zone_id] = migrated_stop.zone_id  
if stop.parent_station:  
parent_original = schedule.GetStop(stop.parent_station)  
migrated_stop.parent_station = merge_map[parent_original].stop_id  
self.feed_merger.merged_schedule.AddStopObject(migrated_stop)  
 
 
class RouteMerger(DataSetMerger):  
"""A DataSetMerger for routes."""  
 
ENTITY_TYPE_NAME = 'route'  
FILE_NAME = 'routes.txt'  
DATASET_NAME = 'Routes'  
 
def _GetIter(self, schedule):  
return schedule.GetRouteList()  
 
def _GetById(self, schedule, route_id):  
return schedule.GetRoute(route_id)  
 
def _MergeEntities(self, a, b):  
scheme = {'route_short_name': self._MergeIdentical,  
'route_long_name': self._MergeIdentical,  
'agency_id': self._MergeSameAgency,  
'route_type': self._MergeIdentical,  
'route_id': self._MergeIdentical,  
'route_url': self._MergeOptional,  
'route_color': self._MergeOptional,  
'route_text_color': self._MergeOptional}  
return self._SchemedMerge(scheme, a, b)  
 
def _Migrate(self, entity, schedule, newid):  
migrated_route = transitfeed.Route(field_dict=entity)  
if newid:  
migrated_route.route_id = self.feed_merger.GenerateId(entity.route_id)  
if entity.agency_id:  
original_agency = schedule.GetAgency(entity.agency_id)  
else:  
original_agency = schedule.GetDefaultAgency()  
 
migrated_agency = self.feed_merger.GetMergedObject(original_agency)  
migrated_route.agency_id = migrated_agency.agency_id  
return migrated_route  
 
def _Add(self, a, b, migrated_route):  
self.feed_merger.Register(a, b, migrated_route)  
self.feed_merger.merged_schedule.AddRouteObject(migrated_route)  
 
def _GetId(self, entity):  
return entity.route_id  
 
def MergeDataSets(self):  
self._MergeSameId()  
return True  
 
 
class ServicePeriodMerger(DataSetMerger):  
"""A DataSetMerger for service periods.  
 
Attributes:  
require_disjoint_calendars: A boolean specifying whether to require  
disjoint calendars when merging (True) or not (False).  
"""  
 
ENTITY_TYPE_NAME = 'service period'  
FILE_NAME = 'calendar.txt/calendar_dates.txt'  
DATASET_NAME = 'Service Periods'  
 
def __init__(self, feed_merger):  
DataSetMerger.__init__(self, feed_merger)  
self.require_disjoint_calendars = True  
 
def _ReportSameIdButNotMerged(self, entity_id, reason):  
pass  
 
def _GetIter(self, schedule):  
return schedule.GetServicePeriodList()  
 
def _GetById(self, schedule, service_id):  
return schedule.GetServicePeriod(service_id)  
 
def _MergeEntities(self, a, b):  
"""Tries to merge two service periods.  
 
Note: Currently this just raises a MergeError since service periods cannot  
be merged.  
 
Args:  
a: The first service period.  
b: The second service period.  
 
Returns:  
The merged service period.  
 
Raises:  
MergeError: When the service periods could not be merged.  
"""  
raise MergeError('Cannot merge service periods')  
 
def _Migrate(self, original_service_period, schedule, newid):  
migrated_service_period = transitfeed.ServicePeriod()  
migrated_service_period.day_of_week = list(  
original_service_period.day_of_week)  
migrated_service_period.start_date = original_service_period.start_date  
migrated_service_period.end_date = original_service_period.end_date  
migrated_service_period.date_exceptions = dict(  
original_service_period.date_exceptions)  
if newid:  
migrated_service_period.service_id = self.feed_merger.GenerateId(  
original_service_period.service_id)  
else:  
migrated_service_period.service_id = original_service_period.service_id  
return migrated_service_period  
 
def _Add(self, a, b, migrated_service_period):  
self.feed_merger.Register(a, b, migrated_service_period)  
self.feed_merger.merged_schedule.AddServicePeriodObject(  
migrated_service_period)  
 
def _GetId(self, entity):  
return entity.service_id  
 
def MergeDataSets(self):  
if self.require_disjoint_calendars and not self.CheckDisjointCalendars():  
self.feed_merger.problem_reporter.CalendarsNotDisjoint(self)  
return False  
self._MergeSameId()  
self.feed_merger.problem_reporter.MergeNotImplemented(self)  
return True  
 
def DisjoinCalendars(self, cutoff):  
"""Forces the old and new calendars to be disjoint about a cutoff date.  
 
This truncates the service periods of the old schedule so that service  
stops one day before the given cutoff date and truncates the new schedule  
so that service only begins on the cutoff date.  
 
Args:  
cutoff: The cutoff date as a string in YYYYMMDD format. The timezone  
is the same as used in the calendar.txt file.  
"""  
 
def TruncatePeriod(service_period, start, end):  
"""Truncate the service period to into the range [start, end].  
 
Args:  
service_period: The service period to truncate.  
start: The start date as a string in YYYYMMDD format.  
end: The end date as a string in YYYYMMDD format.  
"""  
service_period.start_date = max(service_period.start_date, start)  
service_period.end_date = min(service_period.end_date, end)  
dates_to_delete = []  
for k in service_period.date_exceptions:  
if (k < start) or (k > end):  
dates_to_delete.append(k)  
for k in dates_to_delete:  
del service_period.date_exceptions[k]  
 
# find the date one day before cutoff  
year = int(cutoff[:4])  
month = int(cutoff[4:6])  
day = int(cutoff[6:8])  
cutoff_date = datetime.date(year, month, day)  
one_day_delta = datetime.timedelta(days=1)  
before = (cutoff_date - one_day_delta).strftime('%Y%m%d')  
 
for a in self.feed_merger.a_schedule.GetServicePeriodList():  
TruncatePeriod(a, 0, before)  
for b in self.feed_merger.b_schedule.GetServicePeriodList():  
TruncatePeriod(b, cutoff, '9'*8)  
 
def CheckDisjointCalendars(self):  
"""Check whether any old service periods intersect with any new ones.  
 
This is a rather coarse check based on  
transitfeed.SevicePeriod.GetDateRange.  
 
Returns:  
True if the calendars are disjoint or False if not.  
"""  
# TODO: Do an exact check here.  
 
a_service_periods = self.feed_merger.a_schedule.GetServicePeriodList()  
b_service_periods = self.feed_merger.b_schedule.GetServicePeriodList()  
 
for a_service_period in a_service_periods:  
a_start, a_end = a_service_period.GetDateRange()  
for b_service_period in b_service_periods:  
b_start, b_end = b_service_period.GetDateRange()  
overlap_start = max(a_start, b_start)  
overlap_end = min(a_end, b_end)  
if overlap_end >= overlap_start:  
return False  
return True  
 
def GetMergeStats(self):  
return None  
 
 
class FareMerger(DataSetMerger):  
"""A DataSetMerger for fares."""  
 
ENTITY_TYPE_NAME = 'fare'  
FILE_NAME = 'fare_attributes.txt'  
DATASET_NAME = 'Fares'  
 
def _GetIter(self, schedule):  
return schedule.GetFareList()  
 
def _GetById(self, schedule, fare_id):  
return schedule.GetFare(fare_id)  
 
def _MergeEntities(self, a, b):  
"""Merges the fares if all the attributes are the same."""  
scheme = {'price': self._MergeIdentical,  
'currency_type': self._MergeIdentical,  
'payment_method': self._MergeIdentical,  
'transfers': self._MergeIdentical,  
'transfer_duration': self._MergeIdentical}  
return self._SchemedMerge(scheme, a, b)  
 
def _Migrate(self, original_fare, schedule, newid):  
migrated_fare = transitfeed.Fare(  
field_list=original_fare.GetFieldValuesTuple())  
if newid:  
migrated_fare.fare_id = self.feed_merger.GenerateId(  
original_fare.fare_id)  
return migrated_fare  
 
def _Add(self, a, b, migrated_fare):  
self.feed_merger.Register(a, b, migrated_fare)  
self.feed_merger.merged_schedule.AddFareObject(migrated_fare)  
 
def _GetId(self, fare):  
return fare.fare_id  
 
def MergeDataSets(self):  
num_merged = self._MergeSameId()  
print 'Fares merged: %d of %d, %d' % (  
num_merged,  
len(self.feed_merger.a_schedule.GetFareList()),  
len(self.feed_merger.b_schedule.GetFareList()))  
return True  
 
 
class ShapeMerger(DataSetMerger):  
"""A DataSetMerger for shapes.  
 
In this implementation, merging shapes means just taking the new shape.  
The only conditions for a merge are that the shape_ids are the same and  
the endpoints of the old and new shapes are no further than  
largest_shape_distance apart.  
 
Attributes:  
largest_shape_distance: The largest distance between the endpoints of two  
shapes allowed for them to be merged in metres.  
"""  
 
ENTITY_TYPE_NAME = 'shape'  
FILE_NAME = 'shapes.txt'  
DATASET_NAME = 'Shapes'  
 
largest_shape_distance = 10.0  
 
def SetLargestShapeDistance(self, distance):  
"""Sets largest_shape_distance."""  
self.largest_shape_distance = distance  
 
def _GetIter(self, schedule):  
return schedule.GetShapeList()  
 
def _GetById(self, schedule, shape_id):  
return schedule.GetShape(shape_id)  
 
def _MergeEntities(self, a, b):  
"""Merges the shapes by taking the new shape.  
 
Args:  
a: The first transitfeed.Shape instance.  
b: The second transitfeed.Shape instance.  
 
Returns:  
The merged shape.  
 
Raises:  
MergeError: If the ids are different or if the endpoints are further  
than largest_shape_distance apart.  
"""  
if a.shape_id != b.shape_id:  
raise MergeError('shape_id must be the same')  
 
distance = max(ApproximateDistanceBetweenPoints(a.points[0][:2],  
b.points[0][:2]),  
ApproximateDistanceBetweenPoints(a.points[-1][:2],  
b.points[-1][:2]))  
if distance > self.largest_shape_distance:  
raise MergeError('The shape endpoints are too far away: %.1fm '  
'(largest_shape_distance is %.1fm)' %  
(distance, self.largest_shape_distance))  
 
return self._Migrate(b, self.feed_merger.b_schedule, False)  
 
def _Migrate(self, original_shape, schedule, newid):  
migrated_shape = transitfeed.Shape(original_shape.shape_id)  
if newid:  
migrated_shape.shape_id = self.feed_merger.GenerateId(  
original_shape.shape_id)  
for (lat, lon, dist) in original_shape.points:  
migrated_shape.AddPoint(lat=lat, lon=lon, distance=dist)  
return migrated_shape  
 
def _Add(self, a, b, migrated_shape):  
self.feed_merger.Register(a, b, migrated_shape)  
self.feed_merger.merged_schedule.AddShapeObject(migrated_shape)  
 
def _GetId(self, shape):  
return shape.shape_id  
 
def MergeDataSets(self):  
self._MergeSameId()  
return True  
 
 
class TripMerger(DataSetMerger):  
"""A DataSetMerger for trips.  
 
This implementation makes no attempt to merge trips, it simply migrates  
them all to the merged feed.  
"""  
 
ENTITY_TYPE_NAME = 'trip'  
FILE_NAME = 'trips.txt'  
DATASET_NAME = 'Trips'  
 
def _ReportSameIdButNotMerged(self, trip_id, reason):  
pass  
 
def _GetIter(self, schedule):  
return schedule.GetTripList()  
 
def _GetById(self, schedule, trip_id):  
return schedule.GetTrip(trip_id)  
 
def _MergeEntities(self, a, b):  
"""Raises a MergeError because currently trips cannot be merged."""  
raise MergeError('Cannot merge trips')  
 
def _Migrate(self, original_trip, schedule, newid):  
migrated_trip = transitfeed.Trip(field_dict=original_trip)  
# Make new trip_id first. AddTripObject reports a problem if it conflicts  
# with an existing id.  
if newid:  
migrated_trip.trip_id = self.feed_merger.GenerateId(  
original_trip.trip_id)  
# Need to add trip to schedule before copying stoptimes  
self.feed_merger.merged_schedule.AddTripObject(migrated_trip,  
validate=False)  
 
if schedule == self.feed_merger.a_schedule:  
merge_map = self.feed_merger.a_merge_map  
else:  
merge_map = self.feed_merger.b_merge_map  
 
original_route = schedule.GetRoute(original_trip.route_id)  
migrated_trip.route_id = merge_map[original_route].route_id  
 
original_service_period = schedule.GetServicePeriod(  
original_trip.service_id)  
migrated_trip.service_id = merge_map[original_service_period].service_id  
 
if original_trip.block_id:  
migrated_trip.block_id = '%s_%s' % (  
self.feed_merger.GetScheduleName(schedule),  
original_trip.block_id)  
 
if original_trip.shape_id:  
original_shape = schedule.GetShape(original_trip.shape_id)  
migrated_trip.shape_id = merge_map[original_shape].shape_id  
 
for original_stop_time in original_trip.GetStopTimes():  
migrated_stop_time = transitfeed.StopTime(  
None,  
merge_map[original_stop_time.stop],  
original_stop_time.arrival_time,  
original_stop_time.departure_time,  
original_stop_time.stop_headsign,  
original_stop_time.pickup_type,  
original_stop_time.drop_off_type,  
original_stop_time.shape_dist_traveled,  
original_stop_time.arrival_secs,  
original_stop_time.departure_secs)  
migrated_trip.AddStopTimeObject(migrated_stop_time)  
 
for headway_period in original_trip.GetHeadwayPeriodTuples():  
migrated_trip.AddHeadwayPeriod(*headway_period)  
 
return migrated_trip  
 
def _Add(self, a, b, migrated_trip):  
# Validate now, since it wasn't done in _Migrate  
migrated_trip.Validate(self.feed_merger.merged_schedule.problem_reporter)  
self.feed_merger.Register(a, b, migrated_trip)  
 
def _GetId(self, trip):  
return trip.trip_id  
 
def MergeDataSets(self):  
self._MergeSameId()  
self.feed_merger.problem_reporter.MergeNotImplemented(self)  
return True  
 
def GetMergeStats(self):  
return None  
 
 
class FareRuleMerger(DataSetMerger):  
"""A DataSetMerger for fare rules."""  
 
ENTITY_TYPE_NAME = 'fare rule'  
FILE_NAME = 'fare_rules.txt'  
DATASET_NAME = 'Fare Rules'  
 
def MergeDataSets(self):  
"""Merge the fare rule datasets.  
 
The fare rules are first migrated. Merging is done by removing any  
duplicate rules.  
 
Returns:  
True since fare rules can always be merged.  
"""  
rules = set()  
for (schedule, merge_map, zone_map) in ([self.feed_merger.a_schedule,  
self.feed_merger.a_merge_map,  
self.feed_merger.a_zone_map],  
[self.feed_merger.b_schedule,  
self.feed_merger.b_merge_map,  
self.feed_merger.b_zone_map]):  
for fare in schedule.GetFareList():  
for fare_rule in fare.GetFareRuleList():  
fare_id = merge_map[schedule.GetFare(fare_rule.fare_id)].fare_id  
route_id = (fare_rule.route_id and  
merge_map[schedule.GetRoute(fare_rule.route_id)].route_id)  
origin_id = (fare_rule.origin_id and  
zone_map[fare_rule.origin_id])  
destination_id = (fare_rule.destination_id and  
zone_map[fare_rule.destination_id])  
contains_id = (fare_rule.contains_id and  
zone_map[fare_rule.contains_id])  
rules.add((fare_id, route_id, origin_id, destination_id,  
contains_id))  
for fare_rule_tuple in rules:  
migrated_fare_rule = transitfeed.FareRule(*fare_rule_tuple)  
self.feed_merger.merged_schedule.AddFareRuleObject(migrated_fare_rule)  
 
if rules:  
self.feed_merger.problem_reporter.FareRulesBroken(self)  
print 'Fare Rules: union has %d fare rules' % len(rules)  
return True  
 
def GetMergeStats(self):  
return None  
 
 
class FeedMerger(object):  
"""A class for merging two whole feeds.  
 
This class takes two instances of transitfeed.Schedule and uses  
DataSetMerger instances to merge the feeds and produce the resultant  
merged feed.  
 
Attributes:  
a_schedule: The old transitfeed.Schedule instance.  
b_schedule: The new transitfeed.Schedule instance.  
problem_reporter: The merge problem reporter.  
merged_schedule: The merged transitfeed.Schedule instance.  
a_merge_map: A map from old entities to merged entities.  
b_merge_map: A map from new entities to merged entities.  
a_zone_map: A map from old zone ids to merged zone ids.  
b_zone_map: A map from new zone ids to merged zone ids.  
"""  
 
def __init__(self, a_schedule, b_schedule, merged_schedule,  
problem_reporter=None):  
"""Initialise the merger.  
 
Once this initialiser has been called, a_schedule and b_schedule should  
not be modified.  
 
Args:  
a_schedule: The old schedule, an instance of transitfeed.Schedule.  
b_schedule: The new schedule, an instance of transitfeed.Schedule.  
problem_reporter: The problem reporter, an instance of  
transitfeed.ProblemReporterBase. This can be None in  
which case the ExceptionProblemReporter is used.  
"""  
self.a_schedule = a_schedule  
self.b_schedule = b_schedule  
self.merged_schedule = merged_schedule  
self.a_merge_map = {}  
self.b_merge_map = {}  
self.a_zone_map = {}  
self.b_zone_map = {}  
self._mergers = []  
self._idnum = max(self._FindLargestIdPostfixNumber(self.a_schedule),  
self._FindLargestIdPostfixNumber(self.b_schedule))  
 
if problem_reporter is not None:  
self.problem_reporter = problem_reporter  
else:  
self.problem_reporter = ExceptionProblemReporter()  
 
def _FindLargestIdPostfixNumber(self, schedule):  
"""Finds the largest integer used as the ending of an id in the schedule.  
 
Args:  
schedule: The schedule to check.  
 
Returns:  
The maximum integer used as an ending for an id.  
"""  
postfix_number_re = re.compile('(\d+)$')  
 
def ExtractPostfixNumber(entity_id):  
"""Try to extract an integer from the end of entity_id.  
 
If entity_id is None or if there is no integer ending the id, zero is  
returned.  
 
Args:  
entity_id: An id string or None.  
 
Returns:  
An integer ending the entity_id or zero.  
"""  
if entity_id is None:  
return 0  
match = postfix_number_re.search(entity_id)  
if match is not None:  
return int(match.group(1))  
else:  
return 0  
 
id_data_sets = {'agency_id': schedule.GetAgencyList(),  
'stop_id': schedule.GetStopList(),  
'route_id': schedule.GetRouteList(),  
'trip_id': schedule.GetTripList(),  
'service_id': schedule.GetServicePeriodList(),  
'fare_id': schedule.GetFareList(),  
'shape_id': schedule.GetShapeList()}  
 
max_postfix_number = 0  
for id_name, entity_list in id_data_sets.items():  
for entity in entity_list:  
entity_id = getattr(entity, id_name)  
postfix_number = ExtractPostfixNumber(entity_id)  
max_postfix_number = max(max_postfix_number, postfix_number)  
return max_postfix_number  
 
def GetScheduleName(self, schedule):  
"""Returns a single letter identifier for the schedule.  
 
This only works for the old and new schedules which return 'a' and 'b'  
respectively. The purpose of such identifiers is for generating ids.  
 
Args:  
schedule: The transitfeed.Schedule instance.  
 
Returns:  
The schedule identifier.  
 
Raises:  
KeyError: schedule is not the old or new schedule.  
"""  
return {self.a_schedule: 'a', self.b_schedule: 'b'}[schedule]  
 
def GenerateId(self, entity_id=None):  
"""Generate a unique id based on the given id.  
 
This is done by appending a counter which is then incremented. The  
counter is initialised at the maximum number used as an ending for  
any id in the old and new schedules.  
 
Args:  
entity_id: The base id string. This is allowed to be None.  
 
Returns:  
The generated id.  
"""  
self._idnum += 1  
if entity_id:  
return '%s_merged_%d' % (entity_id, self._idnum)  
else:  
return 'merged_%d' % self._idnum  
 
def Register(self, a, b, migrated_entity):  
"""Registers a merge mapping.  
 
If a and b are both not None, this means that entities a and b were merged  
to produce migrated_entity. If one of a or b are not None, then it means  
it was not merged but simply migrated.  
 
The effect of a call to register is to update a_merge_map and b_merge_map  
according to the merge.  
 
Args:  
a: The entity from the old feed or None.  
b: The entity from the new feed or None.  
migrated_entity: The migrated entity.  
"""  
if a is not None: self.a_merge_map[a] = migrated_entity  
if b is not None: self.b_merge_map[b] = migrated_entity  
 
def AddMerger(self, merger):  
"""Add a DataSetMerger to be run by Merge().  
 
Args:  
merger: The DataSetMerger instance.  
"""  
self._mergers.append(merger)  
 
def AddDefaultMergers(self):  
"""Adds the default DataSetMergers defined in this module."""  
self.AddMerger(AgencyMerger(self))  
self.AddMerger(StopMerger(self))  
self.AddMerger(RouteMerger(self))  
self.AddMerger(ServicePeriodMerger(self))  
self.AddMerger(FareMerger(self))  
self.AddMerger(ShapeMerger(self))  
self.AddMerger(TripMerger(self))  
self.AddMerger(FareRuleMerger(self))  
 
def GetMerger(self, cls):  
"""Looks for an added DataSetMerger derived from the given class.  
 
Args:  
cls: A class derived from DataSetMerger.  
 
Returns:  
The matching DataSetMerger instance.  
 
Raises:  
LookupError: No matching DataSetMerger has been added.  
"""  
for merger in self._mergers:  
if isinstance(merger, cls):  
return merger  
raise LookupError('No matching DataSetMerger found')  
 
def GetMergerList(self):  
"""Returns the list of DataSetMerger instances that have been added."""  
return self._mergers  
 
def MergeSchedules(self):  
"""Merge the schedules.  
 
This is done by running the DataSetMergers that have been added with  
AddMerger() in the order that they were added.  
 
Returns:  
True if the merge was successful.  
"""  
for merger in self._mergers:  
if not merger.MergeDataSets():  
return False  
return True  
 
def GetMergedSchedule(self):  
"""Returns the merged schedule.  
 
This will be empty before MergeSchedules() is called.  
 
Returns:  
The merged schedule.  
"""  
return self.merged_schedule  
 
def GetMergedObject(self, original):  
"""Returns an object that represents original in the merged schedule."""  
# TODO: I think this would be better implemented by adding a private  
# attribute to the objects in the original feeds  
merged = (self.a_merge_map.get(original) or  
self.b_merge_map.get(original))  
if merged:  
return merged  
else:  
raise KeyError()  
 
 
def main():  
"""Run the merge driver program."""  
usage = \  
"""%prog [options] <input GTFS a.zip> <input GTFS b.zip> <output GTFS.zip>  
 
Merges <input GTFS a.zip> and <input GTFS b.zip> into a new GTFS file  
<output GTFS.zip>.  
"""  
 
parser = util.OptionParserLongError(  
usage=usage, version='%prog '+transitfeed.__version__)  
parser.add_option('--cutoff_date',  
dest='cutoff_date',  
default=None,  
help='a transition date from the old feed to the new '  
'feed in the format YYYYMMDD')  
parser.add_option('--largest_stop_distance',  
dest='largest_stop_distance',  
default=StopMerger.largest_stop_distance,  
help='the furthest distance two stops can be apart and '  
'still be merged, in metres')  
parser.add_option('--largest_shape_distance',  
dest='largest_shape_distance',  
default=ShapeMerger.largest_shape_distance,  
help='the furthest distance the endpoints of two shapes '  
'can be apart and the shape still be merged, in metres')  
parser.add_option('--html_output_path',  
dest='html_output_path',  
default='merge-results.html',  
help='write the html output to this file')  
parser.add_option('--no_browser',  
dest='no_browser',  
action='store_true',  
help='prevents the merge results from being opened in a '  
'browser')  
parser.add_option('-m', '--memory_db', dest='memory_db', action='store_true',  
help='Use in-memory sqlite db instead of a temporary file. '  
'It is faster but uses more RAM.')  
parser.set_defaults(memory_db=False)  
(options, args) = parser.parse_args()  
 
if len(args) != 3:  
parser.error('You did not provide all required command line arguments.')  
 
old_feed_path = os.path.abspath(args[0])  
new_feed_path = os.path.abspath(args[1])  
merged_feed_path = os.path.abspath(args[2])  
 
if old_feed_path.find("IWantMyCrash") != -1:  
# See test/testmerge.py  
raise Exception('For testing the merge crash handler.')  
 
a_schedule = LoadWithoutErrors(old_feed_path, options.memory_db)  
b_schedule = LoadWithoutErrors(new_feed_path, options.memory_db)  
merged_schedule = transitfeed.Schedule(memory_db=options.memory_db)  
problem_reporter = HTMLProblemReporter()  
feed_merger = FeedMerger(a_schedule, b_schedule, merged_schedule,  
problem_reporter)  
feed_merger.AddDefaultMergers()  
 
feed_merger.GetMerger(StopMerger).SetLargestStopDistance(float(  
options.largest_stop_distance))  
feed_merger.GetMerger(ShapeMerger).SetLargestShapeDistance(float(  
options.largest_shape_distance))  
 
if options.cutoff_date is not None:  
service_period_merger = feed_merger.GetMerger(ServicePeriodMerger)  
service_period_merger.DisjoinCalendars(options.cutoff_date)  
 
if feed_merger.MergeSchedules():  
feed_merger.GetMergedSchedule().WriteGoogleTransitFeed(merged_feed_path)  
else:  
merged_feed_path = None  
 
output_file = file(options.html_output_path, 'w')  
problem_reporter.WriteOutput(output_file, feed_merger,  
old_feed_path, new_feed_path, merged_feed_path)  
output_file.close()  
 
if not options.no_browser:  
webbrowser.open('file://%s' % os.path.abspath(options.html_output_path))  
 
 
if __name__ == '__main__':  
util.RunWithCrashHandler(main)  
 
#!/usr/bin/python2.5  
 
# Copyright (C) 2007 Google Inc.  
#  
# Licensed under the Apache License, Version 2.0 (the "License");  
# you may not use this file except in compliance with the License.  
# You may obtain a copy of the License at  
#  
# http://www.apache.org/licenses/LICENSE-2.0  
#  
# Unless required by applicable law or agreed to in writing, software  
# distributed under the License is distributed on an "AS IS" BASIS,  
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.  
# See the License for the specific language governing permissions and  
# limitations under the License.  
 
"""  
An example application that uses the transitfeed module.  
 
You must provide a Google Maps API key.  
"""  
 
 
import BaseHTTPServer, sys, urlparse  
import bisect  
from gtfsscheduleviewer.marey_graph import MareyGraph  
import gtfsscheduleviewer  
import mimetypes  
import os.path  
import re  
import signal  
import simplejson  
import socket  
import time  
import transitfeed  
from transitfeed import util  
import urllib  
 
 
# By default Windows kills Python with Ctrl+Break. Instead make Ctrl+Break  
# raise a KeyboardInterrupt.  
if hasattr(signal, 'SIGBREAK'):  
signal.signal(signal.SIGBREAK, signal.default_int_handler)  
 
 
mimetypes.add_type('text/plain', '.vbs')  
 
 
class ResultEncoder(simplejson.JSONEncoder):  
def default(self, obj):  
try:  
iterable = iter(obj)  
except TypeError:  
pass  
else:  
return list(iterable)  
return simplejson.JSONEncoder.default(self, obj)  
 
# Code taken from  
# http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/425210/index_txt  
# An alternate approach is shown at  
# http://mail.python.org/pipermail/python-list/2003-July/212751.html  
# but it requires multiple threads. A sqlite object can only be used from one  
# thread.  
class StoppableHTTPServer(BaseHTTPServer.HTTPServer):  
def server_bind(self):  
BaseHTTPServer.HTTPServer.server_bind(self)  
self.socket.settimeout(1)  
self._run = True  
 
def get_request(self):  
while self._run:  
try:  
sock, addr = self.socket.accept()  
sock.settimeout(None)  
return (sock, addr)  
except socket.timeout:  
pass  
 
def stop(self):  
self._run = False  
 
def serve(self):  
while self._run:  
self.handle_request()  
 
 
def StopToTuple(stop):  
"""Return tuple as expected by javascript function addStopMarkerFromList"""  
return (stop.stop_id, stop.stop_name, float(stop.stop_lat),  
float(stop.stop_lon), stop.location_type)  
 
 
class ScheduleRequestHandler(BaseHTTPServer.BaseHTTPRequestHandler):  
def do_GET(self):  
scheme, host, path, x, params, fragment = urlparse.urlparse(self.path)  
parsed_params = {}  
for k in params.split('&'):  
k = urllib.unquote(k)  
if '=' in k:  
k, v = k.split('=', 1)  
parsed_params[k] = unicode(v, 'utf8')  
else:  
parsed_params[k] = ''  
 
if path == '/':  
return self.handle_GET_home()  
 
m = re.match(r'/json/([a-z]{1,64})', path)  
if m:  
handler_name = 'handle_json_GET_%s' % m.group(1)  
handler = getattr(self, handler_name, None)  
if callable(handler):  
return self.handle_json_wrapper_GET(handler, parsed_params)  
 
# Restrict allowable file names to prevent relative path attacks etc  
m = re.match(r'/file/([a-z0-9_-]{1,64}\.?[a-z0-9_-]{1,64})$', path)  
if m and m.group(1):  
try:  
f, mime_type = self.OpenFile(m.group(1))  
return self.handle_static_file_GET(f, mime_type)  
except IOError, e:  
print "Error: unable to open %s" % m.group(1)  
# Ignore and treat as 404  
 
m = re.match(r'/([a-z]{1,64})', path)  
if m:  
handler_name = 'handle_GET_%s' % m.group(1)  
handler = getattr(self, handler_name, None)  
if callable(handler):  
return handler(parsed_params)  
 
return self.handle_GET_default(parsed_params, path)  
 
def OpenFile(self, filename):  
"""Try to open filename in the static files directory of this server.  
Return a tuple (file object, string mime_type) or raise an exception."""  
(mime_type, encoding) = mimetypes.guess_type(filename)  
assert mime_type  
# A crude guess of when we should use binary mode. Without it non-unix  
# platforms may corrupt binary files.  
if mime_type.startswith('text/'):  
mode = 'r'  
else:  
mode = 'rb'  
return open(os.path.join(self.server.file_dir, filename), mode), mime_type  
 
def handle_GET_default(self, parsed_params, path):  
self.send_error(404)  
 
def handle_static_file_GET(self, fh, mime_type):  
content = fh.read()  
self.send_response(200)  
self.send_header('Content-Type', mime_type)  
self.send_header('Content-Length', str(len(content)))  
self.end_headers()  
self.wfile.write(content)  
 
def AllowEditMode(self):  
return False  
 
def handle_GET_home(self):  
schedule = self.server.schedule  
(min_lat, min_lon, max_lat, max_lon) = schedule.GetStopBoundingBox()  
forbid_editing = ('true', 'false')[self.AllowEditMode()]  
 
agency = ', '.join(a.agency_name for a in schedule.GetAgencyList()).encode('utf-8')  
 
key = self.server.key  
host = self.server.host  
 
# A very simple template system. For a fixed set of values replace [xxx]  
# with the value of local variable xxx  
f, _ = self.OpenFile('index.html')  
content = f.read()  
for v in ('agency', 'min_lat', 'min_lon', 'max_lat', 'max_lon', 'key',  
'host', 'forbid_editing'):  
content = content.replace('[%s]' % v, str(locals()[v]))  
 
self.send_response(200)  
self.send_header('Content-Type', 'text/html')  
self.send_header('Content-Length', str(len(content)))  
self.end_headers()  
self.wfile.write(content)  
 
def handle_json_GET_routepatterns(self, params):  
"""Given a route_id generate a list of patterns of the route. For each  
pattern include some basic information and a few sample trips."""  
schedule = self.server.schedule  
route = schedule.GetRoute(params.get('route', None))  
if not route:  
self.send_error(404)  
return  
time = int(params.get('time', 0))  
sample_size = 3 # For each pattern return the start time for this many trips  
 
pattern_id_trip_dict = route.GetPatternIdTripDict()  
patterns = []  
 
for pattern_id, trips in pattern_id_trip_dict.items():  
time_stops = trips[0].GetTimeStops()  
if not time_stops:  
continue  
has_non_zero_trip_type = False;  
for trip in trips:  
if trip['trip_type'] and trip['trip_type'] != '0':  
has_non_zero_trip_type = True  
name = u'%s to %s, %d stops' % (time_stops[0][2].stop_name, time_stops[-1][2].stop_name, len(time_stops))  
transitfeed.SortListOfTripByTime(trips)  
 
num_trips = len(trips)  
if num_trips <= sample_size:  
start_sample_index = 0  
num_after_sample = 0  
else:  
# Will return sample_size trips that start after the 'time' param.  
 
# Linear search because I couldn't find a built-in way to do a binary  
# search with a custom key.  
start_sample_index = len(trips)  
for i, trip in enumerate(trips):  
if trip.GetStartTime() >= time:  
start_sample_index = i  
break  
 
num_after_sample = num_trips - (start_sample_index + sample_size)  
if num_after_sample < 0:  
# Less than sample_size trips start after 'time' so return all the  
# last sample_size trips.  
num_after_sample = 0  
start_sample_index = num_trips - sample_size  
 
sample = []  
for t in trips[start_sample_index:start_sample_index + sample_size]:  
sample.append( (t.GetStartTime(), t.trip_id) )  
 
patterns.append((name, pattern_id, start_sample_index, sample,  
num_after_sample, (0,1)[has_non_zero_trip_type]))  
 
patterns.sort()  
return patterns  
 
def handle_json_wrapper_GET(self, handler, parsed_params):  
"""Call handler and output the return value in JSON."""  
schedule = self.server.schedule  
result = handler(parsed_params)  
content = ResultEncoder().encode(result)  
self.send_response(200)  
self.send_header('Content-Type', 'text/plain')  
self.send_header('Content-Length', str(len(content)))  
self.end_headers()  
self.wfile.write(content)  
 
def handle_json_GET_routes(self, params):  
"""Return a list of all routes."""  
schedule = self.server.schedule  
result = []  
for r in schedule.GetRouteList():  
result.append( (r.route_id, r.route_short_name, r.route_long_name) )  
result.sort(key = lambda x: x[1:3])  
return result  
 
def handle_json_GET_routerow(self, params):  
schedule = self.server.schedule  
route = schedule.GetRoute(params.get('route', None))  
return [transitfeed.Route._FIELD_NAMES, route.GetFieldValuesTuple()]  
 
def handle_json_GET_triprows(self, params):  
"""Return a list of rows from the feed file that are related to this  
trip."""  
schedule = self.server.schedule  
try:  
trip = schedule.GetTrip(params.get('trip', None))  
except KeyError:  
# if a non-existent trip is searched for, the return nothing  
return  
route = schedule.GetRoute(trip.route_id)  
trip_row = dict(trip.iteritems())  
route_row = dict(route.iteritems())  
return [['trips.txt', trip_row], ['routes.txt', route_row]]  
 
def handle_json_GET_tripstoptimes(self, params):  
schedule = self.server.schedule  
try:  
trip = schedule.GetTrip(params.get('trip'))  
except KeyError:  
# if a non-existent trip is searched for, the return nothing  
return  
time_stops = trip.GetTimeStops()  
stops = []  
times = []  
for arr,dep,stop in time_stops:  
stops.append(StopToTuple(stop))  
times.append(arr)  
return [stops, times]  
 
def handle_json_GET_tripshape(self, params):  
schedule = self.server.schedule  
try:  
trip = schedule.GetTrip(params.get('trip'))  
except KeyError:  
# if a non-existent trip is searched for, the return nothing  
return  
points = []  
if trip.shape_id:  
shape = schedule.GetShape(trip.shape_id)  
for (lat, lon, dist) in shape.points:  
points.append((lat, lon))  
else:  
time_stops = trip.GetTimeStops()  
for arr,dep,stop in time_stops:  
points.append((stop.stop_lat, stop.stop_lon))  
return points  
 
def handle_json_GET_neareststops(self, params):  
"""Return a list of the nearest 'limit' stops to 'lat', 'lon'"""  
schedule = self.server.schedule  
lat = float(params.get('lat'))  
lon = float(params.get('lon'))  
limit = int(params.get('limit'))  
stops = schedule.GetNearestStops(lat=lat, lon=lon, n=limit)  
return [StopToTuple(s) for s in stops]  
 
def handle_json_GET_boundboxstops(self, params):  
"""Return a list of up to 'limit' stops within bounding box with 'n','e'  
and 's','w' in the NE and SW corners. Does not handle boxes crossing  
longitude line 180."""  
schedule = self.server.schedule  
n = float(params.get('n'))  
e = float(params.get('e'))  
s = float(params.get('s'))  
w = float(params.get('w'))  
limit = int(params.get('limit'))  
stops = schedule.GetStopsInBoundingBox(north=n, east=e, south=s, west=w, n=limit)  
return [StopToTuple(s) for s in stops]  
 
def handle_json_GET_stopsearch(self, params):  
schedule = self.server.schedule  
query = params.get('q', None).lower()  
matches = []  
for s in schedule.GetStopList():  
if s.stop_id.lower().find(query) != -1 or s.stop_name.lower().find(query) != -1:  
matches.append(StopToTuple(s))  
return matches  
 
def handle_json_GET_stoptrips(self, params):  
"""Given a stop_id and time in seconds since midnight return the next  
trips to visit the stop."""  
schedule = self.server.schedule  
stop = schedule.GetStop(params.get('stop', None))  
time = int(params.get('time', 0))  
time_trips = stop.GetStopTimeTrips(schedule)  
time_trips.sort() # OPT: use bisect.insort to make this O(N*ln(N)) -> O(N)  
# Keep the first 5 after param 'time'.  
# Need make a tuple to find correct bisect point  
time_trips = time_trips[bisect.bisect_left(time_trips, (time, 0)):]  
time_trips = time_trips[:5]  
# TODO: combine times for a route to show next 2 departure times  
result = []  
for time, (trip, index), tp in time_trips:  
headsign = None  
# Find the most recent headsign from the StopTime objects  
for stoptime in trip.GetStopTimes()[index::-1]:  
if stoptime.stop_headsign:  
headsign = stoptime.stop_headsign  
break  
# If stop_headsign isn't found, look for a trip_headsign  
if not headsign:  
headsign = trip.trip_headsign  
route = schedule.GetRoute(trip.route_id)  
trip_name = ''  
if route.route_short_name:  
trip_name += route.route_short_name  
if route.route_long_name:  
if len(trip_name):  
trip_name += " - "  
trip_name += route.route_long_name  
if headsign:  
trip_name += " (Direction: %s)" % headsign  
 
result.append((time, (trip.trip_id, trip_name, trip.service_id), tp))  
return result  
 
def handle_GET_ttablegraph(self,params):  
"""Draw a Marey graph in SVG for a pattern (collection of trips in a route  
that visit the same sequence of stops)."""  
schedule = self.server.schedule  
marey = MareyGraph()  
trip = schedule.GetTrip(params.get('trip', None))  
route = schedule.GetRoute(trip.route_id)  
height = int(params.get('height', 300))  
 
if not route:  
print 'no such route'  
self.send_error(404)  
return  
 
pattern_id_trip_dict = route.GetPatternIdTripDict()  
pattern_id = trip.pattern_id  
if pattern_id not in pattern_id_trip_dict:  
print 'no pattern %s found in %s' % (pattern_id, pattern_id_trip_dict.keys())  
self.send_error(404)  
return  
triplist = pattern_id_trip_dict[pattern_id]  
 
pattern_start_time = min((t.GetStartTime() for t in triplist))  
pattern_end_time = max((t.GetEndTime() for t in triplist))  
 
marey.SetSpan(pattern_start_time,pattern_end_time)  
marey.Draw(triplist[0].GetPattern(), triplist, height)  
 
content = marey.Draw()  
 
self.send_response(200)  
self.send_header('Content-Type', 'image/svg+xml')  
self.send_header('Content-Length', str(len(content)))  
self.end_headers()  
self.wfile.write(content)  
 
 
def FindPy2ExeBase():  
"""If this is running in py2exe return the install directory else return  
None"""  
# py2exe puts gtfsscheduleviewer in library.zip. For py2exe setup.py is  
# configured to put the data next to library.zip.  
windows_ending = gtfsscheduleviewer.__file__.find('\\library.zip\\')  
if windows_ending != -1:  
return transitfeed.__file__[:windows_ending]  
else:  
return None  
 
 
def FindDefaultFileDir():  
"""Return the path of the directory containing the static files. By default  
the directory is called 'files'. The location depends on where setup.py put  
it."""  
base = FindPy2ExeBase()  
if base:  
return os.path.join(base, 'schedule_viewer_files')  
else:  
# For all other distributions 'files' is in the gtfsscheduleviewer  
# directory.  
base = os.path.dirname(gtfsscheduleviewer.__file__) # Strip __init__.py  
return os.path.join(base, 'files')  
 
 
def GetDefaultKeyFilePath():  
"""In py2exe return absolute path of file in the base directory and in all  
other distributions return relative path 'key.txt'"""  
windows_base = FindPy2ExeBase()  
if windows_base:  
return os.path.join(windows_base, 'key.txt')  
else:  
return 'key.txt'  
 
 
def main(RequestHandlerClass = ScheduleRequestHandler):  
usage = \  
'''%prog [options] [<input GTFS.zip>]  
 
Runs a webserver that lets you explore a <input GTFS.zip> in your browser.  
 
If <input GTFS.zip> is omited the filename is read from the console. Dragging  
a file into the console may enter the filename.  
'''  
parser = util.OptionParserLongError(  
usage=usage, version='%prog '+transitfeed.__version__)  
parser.add_option('--feed_filename', '--feed', dest='feed_filename',  
help='file name of feed to load')  
parser.add_option('--key', dest='key',  
help='Google Maps API key or the name '  
'of a text file that contains an API key')  
parser.add_option('--host', dest='host', help='Host name of Google Maps')  
parser.add_option('--port', dest='port', type='int',  
help='port on which to listen')  
parser.add_option('--file_dir', dest='file_dir',  
help='directory containing static files')  
parser.add_option('-n', '--noprompt', action='store_false',  
dest='manual_entry',  
help='disable interactive prompts')  
parser.set_defaults(port=8765,  
host='maps.google.com',  
file_dir=FindDefaultFileDir(),  
manual_entry=True)  
(options, args) = parser.parse_args()  
 
if not os.path.isfile(os.path.join(options.file_dir, 'index.html')):  
print "Can't find index.html with --file_dir=%s" % options.file_dir  
exit(1)  
 
if not options.feed_filename and len(args) == 1:  
options.feed_filename = args[0]  
 
if not options.feed_filename and options.manual_entry:  
options.feed_filename = raw_input('Enter Feed Location: ').strip('"')  
 
default_key_file = GetDefaultKeyFilePath()  
if not options.key and os.path.isfile(default_key_file):  
options.key = open(default_key_file).read().strip()  
 
if options.key and os.path.isfile(options.key):  
options.key = open(options.key).read().strip()  
 
schedule = transitfeed.Schedule(problem_reporter=transitfeed.ProblemReporter())  
print 'Loading data from feed "%s"...' % options.feed_filename  
print '(this may take a few minutes for larger cities)'  
schedule.Load(options.feed_filename)  
 
server = StoppableHTTPServer(server_address=('', options.port),  
RequestHandlerClass=RequestHandlerClass)  
server.key = options.key  
server.schedule = schedule  
server.file_dir = options.file_dir  
server.host = options.host  
server.feed_path = options.feed_filename  
 
print ("To view, point your browser at http://localhost:%d/" %  
(server.server_port))  
server.serve_forever()  
 
 
if __name__ == '__main__':  
main()  
 
#!/usr/bin/python2.5  
 
# Copyright (C) 2007 Google Inc.  
#  
# Licensed under the Apache License, Version 2.0 (the "License");  
# you may not use this file except in compliance with the License.  
# You may obtain a copy of the License at  
#  
# http://www.apache.org/licenses/LICENSE-2.0  
#  
# Unless required by applicable law or agreed to in writing, software  
# distributed under the License is distributed on an "AS IS" BASIS,  
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.  
# See the License for the specific language governing permissions and  
# limitations under the License.  
 
"""  
This script can be used to create a source distribution, binary distribution  
or Windows executable files. The output is put in dist/  
 
See  
http://code.google.com/p/googletransitdatafeed/wiki/BuildingPythonWindowsExecutables  
for help on creating Windows executables.  
"""  
 
from distutils.core import setup  
import glob  
import os.path  
from transitfeed import __version__ as VERSION  
 
try:  
import py2exe  
has_py2exe = True  
except ImportError, e:  
# Won't be able to generate win32 exe  
has_py2exe = False  
 
 
# py2exe doesn't automatically include pytz dependency because it is optional  
options = {'py2exe': {'packages': ['pytz']}}  
scripts_for_py2exe = ['feedvalidator.py', 'schedule_viewer.py', 'kmlparser.py',  
'kmlwriter.py', 'merge.py', 'unusual_trip_filter.py']  
# On Nov 23, 2009 Tom Brown said: I'm not confident that we can include a  
# working copy of this script in the py2exe distribution because it depends on  
# ogr. I do want it included in the source tar.gz.  
scripts_for_source_only = ['shape_importer.py']  
kwargs = {}  
 
if has_py2exe:  
kwargs['console'] = scripts_for_py2exe  
# py2exe seems to ignore package_data and not add marey_graph. This makes it  
# work.  
kwargs['data_files'] = \  
[('schedule_viewer_files',  
glob.glob(os.path.join('gtfsscheduleviewer', 'files', '*')))]  
options['py2exe'] = {'dist_dir': 'transitfeed-windows-binary-%s' % VERSION}  
 
setup(  
version=VERSION,  
name='transitfeed',  
url='http://code.google.com/p/googletransitdatafeed/',  
download_url='http://googletransitdatafeed.googlecode.com/'  
'files/transitfeed-%s.tar.gz' % VERSION,  
maintainer='Tom Brown',  
maintainer_email='tom.brown.code@gmail.com',  
description='Google Transit Feed Specification library and tools',  
long_description='This module provides a library for reading, writing and '  
'validating Google Transit Feed Specification files. It includes some '  
'scripts that validate a feed, display it using the Google Maps API and '  
'the start of a KML importer and exporter.',  
platforms='OS Independent',  
license='Apache License, Version 2.0',  
packages=['gtfsscheduleviewer', 'transitfeed'],  
# Also need to list package_data contents in MANIFEST.in for it to be  
# included in sdist. See "[Distutils] package_data not used by sdist  
# command" Feb 2, 2007  
package_data={'gtfsscheduleviewer': ['files/*']},  
scripts=scripts_for_py2exe + scripts_for_source_only,  
zip_safe=False,  
classifiers=[  
'Development Status :: 4 - Beta',  
'Intended Audience :: Developers',  
'Intended Audience :: Information Technology',  
'Intended Audience :: Other Audience',  
'License :: OSI Approved :: Apache Software License',  
'Operating System :: OS Independent',  
'Programming Language :: Python',  
'Topic :: Scientific/Engineering :: GIS',  
'Topic :: Software Development :: Libraries :: Python Modules'  
],  
options=options,  
**kwargs  
)  
 
if has_py2exe:  
# Sometime between pytz-2008a and pytz-2008i common_timezones started to  
# include only names of zones with a corresponding data file in zoneinfo.  
# pytz installs the zoneinfo directory tree in the same directory  
# as the pytz/__init__.py file. These data files are loaded using  
# pkg_resources.resource_stream. py2exe does not copy this to library.zip so  
# resource_stream can't find the files and common_timezones is empty when  
# read in the py2exe executable.  
# This manually copies zoneinfo into the zip. See also  
# http://code.google.com/p/googletransitdatafeed/issues/detail?id=121  
import pytz  
import zipfile  
# Make sure the layout of pytz hasn't changed  
assert (pytz.__file__.endswith('__init__.pyc') or  
pytz.__file__.endswith('__init__.py')), pytz.__file__  
zoneinfo_dir = os.path.join(os.path.dirname(pytz.__file__), 'zoneinfo')  
# '..\\Lib\\pytz\\__init__.py' -> '..\\Lib'  
disk_basedir = os.path.dirname(os.path.dirname(pytz.__file__))  
zipfile_path = os.path.join(options['py2exe']['dist_dir'], 'library.zip')  
z = zipfile.ZipFile(zipfile_path, 'a')  
for absdir, directories, filenames in os.walk(zoneinfo_dir):  
assert absdir.startswith(disk_basedir), (absdir, disk_basedir)  
zip_dir = absdir[len(disk_basedir):]  
for f in filenames:  
z.write(os.path.join(absdir, f), os.path.join(zip_dir, f))  
z.close()  
 
#!/usr/bin/python2.4  
#  
# Copyright 2007 Google Inc. All Rights Reserved.  
#  
# Licensed under the Apache License, Version 2.0 (the "License");  
# you may not use this file except in compliance with the License.  
# You may obtain a copy of the License at  
#  
# http://www.apache.org/licenses/LICENSE-2.0  
#  
# Unless required by applicable law or agreed to in writing, software  
# distributed under the License is distributed on an "AS IS" BASIS,  
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.  
# See the License for the specific language governing permissions and  
# limitations under the License.  
 
"""A utility program to help add shapes to an existing GTFS feed.  
 
Requires the ogr python package.  
"""  
 
__author__ = 'chris.harrelson.code@gmail.com (Chris Harrelson)'  
 
import csv  
import glob  
import ogr  
import os  
import shutil  
import sys  
import tempfile  
import transitfeed  
from transitfeed import shapelib  
from transitfeed import util  
import zipfile  
 
 
class ShapeImporterError(Exception):  
pass  
 
 
def PrintColumns(shapefile):  
"""  
Print the columns of layer 0 of the shapefile to the screen.  
"""  
ds = ogr.Open(shapefile)  
layer = ds.GetLayer(0)  
if len(layer) == 0:  
raise ShapeImporterError("Layer 0 has no elements!")  
 
feature = layer.GetFeature(0)  
print "%d features" % feature.GetFieldCount()  
for j in range(0, feature.GetFieldCount()):  
print '--' + feature.GetFieldDefnRef(j).GetName() + \  
': ' + feature.GetFieldAsString(j)  
 
 
def AddShapefile(shapefile, graph, key_cols):  
"""  
Adds shapes found in the given shape filename to the given polyline  
graph object.  
"""  
ds = ogr.Open(shapefile)  
layer = ds.GetLayer(0)  
 
for i in range(0, len(layer)):  
feature = layer.GetFeature(i)  
 
geometry = feature.GetGeometryRef()  
 
if key_cols:  
key_list = []  
for col in key_cols:  
key_list.append(str(feature.GetField(col)))  
shape_id = '-'.join(key_list)  
else:  
shape_id = '%s-%d' % (shapefile, i)  
 
poly = shapelib.Poly(name=shape_id)  
for j in range(0, geometry.GetPointCount()):  
(lat, lng) = (round(geometry.GetY(j), 15), round(geometry.GetX(j), 15))  
poly.AddPoint(shapelib.Point.FromLatLng(lat, lng))  
graph.AddPoly(poly)  
 
return graph  
 
 
def GetMatchingShape(pattern_poly, trip, matches, max_distance, verbosity=0):  
"""  
Tries to find a matching shape for the given pattern Poly object,  
trip, and set of possibly matching Polys from which to choose a match.  
"""  
if len(matches) == 0:  
print ('No matching shape found within max-distance %d for trip %s '  
% (max_distance, trip.trip_id))  
return None  
 
if verbosity >= 1:  
for match in matches:  
print "match: size %d" % match.GetNumPoints()  
scores = [(pattern_poly.GreedyPolyMatchDist(match), match)  
for match in matches]  
 
scores.sort()  
 
if scores[0][0] > max_distance:  
print ('No matching shape found within max-distance %d for trip %s '  
'(min score was %f)'  
% (max_distance, trip.trip_id, scores[0][0]))  
return None  
 
return scores[0][1]  
 
def AddExtraShapes(extra_shapes_txt, graph):  
"""  
Add extra shapes into our input set by parsing them out of a GTFS-formatted  
shapes.txt file. Useful for manually adding lines to a shape file, since it's  
a pain to edit .shp files.  
"""  
 
print "Adding extra shapes from %s" % extra_shapes_txt  
try:  
tmpdir = tempfile.mkdtemp()  
shutil.copy(extra_shapes_txt, os.path.join(tmpdir, 'shapes.txt'))  
loader = transitfeed.ShapeLoader(tmpdir)  
schedule = loader.Load()  
for shape in schedule.GetShapeList():  
print "Adding extra shape: %s" % shape.shape_id  
graph.AddPoly(ShapeToPoly(shape))  
finally:  
if tmpdir:  
shutil.rmtree(tmpdir)  
 
 
# Note: this method lives here to avoid cross-dependencies between  
# shapelib and transitfeed.  
def ShapeToPoly(shape):  
poly = shapelib.Poly(name=shape.shape_id)  
for lat, lng, distance in shape.points:  
point = shapelib.Point.FromLatLng(round(lat, 15), round(lng, 15))  
poly.AddPoint(point)  
return poly  
 
 
def ValidateArgs(options_parser, options, args):  
if not (args and options.source_gtfs and options.dest_gtfs):  
options_parser.error("You must specify a source and dest GTFS file, "  
"and at least one source shapefile")  
 
 
def DefineOptions():  
usage = \  
"""%prog [options] --source_gtfs=<input GTFS.zip> --dest_gtfs=<output GTFS.zip>\  
<input.shp> [<input.shp>...]  
 
Try to match shapes in one or more SHP files to trips in a GTFS file."""  
options_parser = util.OptionParserLongError(  
usage=usage, version='%prog '+transitfeed.__version__)  
options_parser.add_option("--print_columns",  
action="store_true",  
default=False,  
dest="print_columns",  
help="Print column names in shapefile DBF and exit")  
options_parser.add_option("--keycols",  
default="",  
dest="keycols",  
help="Comma-separated list of the column names used"  
"to index shape ids")  
options_parser.add_option("--max_distance",  
type="int",  
default=150,  
dest="max_distance",  
help="Max distance from a shape to which to match")  
options_parser.add_option("--source_gtfs",  
default="",  
dest="source_gtfs",  
metavar="FILE",  
help="Read input GTFS from FILE")  
options_parser.add_option("--dest_gtfs",  
default="",  
dest="dest_gtfs",  
metavar="FILE",  
help="Write output GTFS with shapes to FILE")  
options_parser.add_option("--extra_shapes",  
default="",  
dest="extra_shapes",  
metavar="FILE",  
help="Extra shapes.txt (CSV) formatted file")  
options_parser.add_option("--verbosity",  
type="int",  
default=0,  
dest="verbosity",  
help="Verbosity level. Higher is more verbose")  
return options_parser  
 
 
def main(key_cols):  
print 'Parsing shapefile(s)...'  
graph = shapelib.PolyGraph()  
for arg in args:  
print ' ' + arg  
AddShapefile(arg, graph, key_cols)  
 
if options.extra_shapes:  
AddExtraShapes(options.extra_shapes, graph)  
 
print 'Loading GTFS from %s...' % options.source_gtfs  
schedule = transitfeed.Loader(options.source_gtfs).Load()  
shape_count = 0  
pattern_count = 0  
 
verbosity = options.verbosity  
 
print 'Matching shapes to trips...'  
for route in schedule.GetRouteList():  
print 'Processing route', route.route_short_name  
patterns = route.GetPatternIdTripDict()  
for pattern_id, trips in patterns.iteritems():  
pattern_count += 1  
pattern = trips[0].GetPattern()  
 
poly_points = [shapelib.Point.FromLatLng(p.stop_lat, p.stop_lon)  
for p in pattern]  
if verbosity >= 2:  
print "\npattern %d, %d points:" % (pattern_id, len(poly_points))  
for i, (stop, point) in enumerate(zip(pattern, poly_points)):  
print "Stop %d '%s': %s" % (i + 1, stop.stop_name, point.ToLatLng())  
 
# First, try to find polys that run all the way from  
# the start of the trip to the end.  
matches = graph.FindMatchingPolys(poly_points[0], poly_points[-1],  
options.max_distance)  
if not matches:  
# Try to find a path through the graph, joining  
# multiple edges to find a path that covers all the  
# points in the trip. Some shape files are structured  
# this way, with a polyline for each segment between  
# stations instead of a polyline covering an entire line.  
shortest_path = graph.FindShortestMultiPointPath(poly_points,  
options.max_distance,  
verbosity=verbosity)  
if shortest_path:  
matches = [shortest_path]  
else:  
matches = []  
 
pattern_poly = shapelib.Poly(poly_points)  
shape_match = GetMatchingShape(pattern_poly, trips[0],  
matches, options.max_distance,  
verbosity=verbosity)  
if shape_match:  
shape_count += 1  
# Rename shape for readability.  
shape_match = shapelib.Poly(points=shape_match.GetPoints(),  
name="shape_%d" % shape_count)  
for trip in trips:  
try:  
shape = schedule.GetShape(shape_match.GetName())  
except KeyError:  
shape = transitfeed.Shape(shape_match.GetName())  
for point in shape_match.GetPoints():  
(lat, lng) = point.ToLatLng()  
shape.AddPoint(lat, lng)  
schedule.AddShapeObject(shape)  
trip.shape_id = shape.shape_id  
 
print "Matched %d shapes out of %d patterns" % (shape_count, pattern_count)  
schedule.WriteGoogleTransitFeed(options.dest_gtfs)  
 
 
if __name__ == '__main__':  
# Import psyco if available for better performance.  
try:  
import psyco  
psyco.full()  
except ImportError:  
pass  
 
options_parser = DefineOptions()  
(options, args) = options_parser.parse_args()  
 
ValidateArgs(options_parser, options, args)  
 
if options.print_columns:  
for arg in args:  
PrintColumns(arg)  
sys.exit(0)  
 
key_cols = options.keycols.split(',')  
 
main(key_cols)  
 
agency_id,agency_name,agency_url,agency_timezone,agency_phone  
DTA,Autorité de passage de démonstration,http://google.com,America/Los_Angeles,123 12314  
 
service_id,monday,tuesday,wednesday,thursday,friday,saturday,sunday,start_date,end_date  
FULLW,1,1,1,1,1,1,1,2007.01.01,20101231  
WE,0,0,0,0,0,1,1,20070101,20101231  
 
service_id,date,exception_type  
FULLW,2007-06-04,2  
 
fare_id,price,currency_type,payment_method,transfers,transfer_duration  
p,1.25,USD,0,0,  
a,5.25,USD,0,0,  
 
fare_id,route_id,origin_id,destination_id,contains_id  
p,AB,,,  
p,STBA,,,  
p,BFC,,,  
a,AAMV,,,  
 
route_id,agency_id,route_short_name,route_long_name,route_desc,route_type,route_url,route_color,route_text_color  
AB,DTA,,Airport ⇒ Bullfrog,,3,,,  
BFC,DTA,,Bullfrog ⇒ Furnace Creek Resort,,3,,,  
STBA,DTA,,Stagecoach ⇒ Airport Shuttle,,3,,,  
CITY,DTA,Ō,Bar Circle,Route with ĸool unicode shortname,3,,,  
AAMV,DTA,,Airport ⇒ Amargosa Valley,,3,,,  
 
trip_id,arrival_time,departure_time,stop_id,stop_sequence,stop_headsign,pickup_type,drop_off_type,shape_dist_traveled  
STBA,6:00:00,6:00:00,STAGECOACH,0,to airport,1,0,0.212  
STBA,6:20:00,6:20:00,BEATTY_AIRPORT,2,,0,0,1.043  
CITY1,6:00:00,6:00:00,STAGECOACH,0,,,,  
CITY1,6:05:00,6:07:00,NANAA,5,going to nadav,2,3,  
CITY1,6:12:00,6:14:00,NADAV,10,,,,  
CITY1,6:19:00,6:21:00,DADAN,15,,,,  
CITY1,6:26:00,6:28:00,EMSI,20,,,,  
CITY2,6:28:00,6:30:00,EMSI,100,,,,  
CITY2,6:35:00,6:37:00,DADAN,200,,,,  
CITY2,6:42:00,6:44:00,NADAV,300,,,,  
CITY2,6:49:00,6:51:00,NANAA,400,,,,  
CITY2,6:56:00,6:58:00,STAGECOACH,500,,,,  
AB1,8:00:00,8:00:00,BEATTY_AIRPORT,1,,,,  
AB1,8:10:00,8:15:00,BULLFROG,2,,,,  
AB2,12:05:00,12:05:00,BULLFROG,1,,,,  
AB2,12:15:00,12:15:00,BEATTY_AIRPORT,2,,,,  
BFC1,8:20:00,8:20:00,BULLFROG,1,,,,  
BFC1,9:20:00,9:20:00,FUR_CREEK_RES,2,,,,  
BFC2,11:00:00,11:00:00,FUR_CREEK_RES,1,,,,  
BFC2,12:00:00,12:00:00,BULLFROG,2,,,,  
AAMV1,8:00:00,8:00:00,BEATTY_AIRPORT,1,,,,  
AAMV1,9:00:00,9:00:00,AMV,2,,,,  
AAMV2,10:00:00,10:00:00,AMV,1,,,,  
AAMV2,11:00:00,11:00:00,BEATTY_AIRPORT,2,,,,  
AAMV3,13:00:00,13:00:00,BEATTY_AIRPORT,1,,,,  
AAMV3,14:00:00,14:00:00,AMV,2,,,,  
AAMV4,15:00:00,15:00:00,AMV,1,,,,  
AAMV4,16:00:00,16:00:00,BEATTY_AIRPORT,2,,,,  
 
stop_id,stop_name,stop_desc,stop_lat,stop_lon,zone_id,stop_url,stop_code,location_type,parent_station  
FUR_CREEK_RES,Furnace Creek Resort (Demo),,36.425288,-117.133162,,,1234,,  
BEATTY_AIRPORT,Nye County Airport (Demo),,36.868446,-116.784582,,,1235,0,BEATTY_AIRPORT_STATION  
BEATTY_AIRPORT_STATION,Nye County Airport (Demo),,36.868446,-116.784582,,,1235,1,  
BULLFROG,Bullfrog (Demo),,36.88108,-116.81797,,,,,  
STAGECOACH,Stagecoach Hotel & Casino (Demo),,36.915682,-116.751677,,,1236,,  
NADAV,North Ave / D Ave N (Demo),,36.914893,-116.76821,,,1237,,  
NANAA,North Ave / N A Ave (Demo),,36.914944,-116.761472,,,1238,,  
DADAN,Doing Ave / D Ave N (Demo),,36.909489,-116.768242,,,,,  
EMSI,E Main St / S Irving St (Demo),,36.905697,-116.76218,,,,,  
AMV,Amargosa Valley (Demo),,36.641496,-116.40094,,,,,  
 
from_stop_id,to_stop_id,transfer_type,min_transfer_time  
NADAV,NANAA,3,  
EMSI,NANAA,2,1200  
 
route_id,service_id,trip_id,trip_headsign,direction_id,block_id,shape_id  
AB,FULLW,AB1,to Bullfrog,0,1,  
AB,FULLW,AB2,to Airport,1,2,  
STBA,FULLW,STBA,Shuttle,,,  
CITY,FULLW,CITY1,,0,,  
CITY,FULLW,CITY2,,1,,  
BFC,FULLW,BFC1,to Furnace Creek Resort,0,1,  
BFC,FULLW,BFC2,to Bullfrog,1,2,  
AAMV,WE,AAMV1,to Amargosa Valley,0,,  
AAMV,WE,AAMV2,to Airport,1,,  
AAMV,WE,AAMV3,to Amargosa Valley,0,,  
AAMV,WE,AAMV4,to Airport,1,,  
 Binary files a/origin-src/transitfeed-1.2.5/test/data/bad_eol.zip and /dev/null differ
service_id,monday,tuesday,wednesday,thursday,friday,saturday,sunday,start_date,end_date  
FULLW,1,1,1,1,1,1,1,20070101,20101231  
WE,0,0,0,0,0,1,1,20070101,20101231  
service_id,date,exception_type  
FULLW,20070604,2  
fare_id,price,currency_type,payment_method,transfers,transfer_duration  
p,1.25,USD,0,0,  
a,5.25,USD,0,0,  
 
fare_id,route_id,origin_id,destination_id,contains_id  
p,AB,,,  
p,STBA,,,  
p,BFC,,,  
a,AAMV,,,  
 
trip_id,start_time,end_time,headway_secs  
STBA,6:00:00,22:00:00,1800  
CITY1,6:00:00,7:59:59,1800  
CITY2,6:00:00,7:59:59,1800  
CITY1,8:00:00,9:59:59,600  
CITY2,8:00:00,9:59:59,600  
CITY1,10:00:00,15:59:59,1800  
CITY2,10:00:00,15:59:59,1800  
CITY1,16:00:00,18:59:59,600  
CITY2,16:00:00,18:59:59,600  
CITY1,19:00:00,22:00:00,1800  
CITY2,19:00:00,22:00:00,1800  
route_id,agency_id,route_short_name,route_long_name,route_desc,route_type,route_url,route_color,route_text_color  
AB,DTA,,Airport - Bullfrog,,3,,,  
BFC,DTA,,Bullfrog - Furnace Creek Resort,,3,,,  
STBA,DTA,,Stagecoach - Airport Shuttle,,3,,,  
 
AAMV,DTA,,Airport - Amargosa Valley,,3,,,  
 
trip_id,arrival_time,departure_time,stop_id,stop_sequence,stop_headsign,pickup_type,drop_off_type,shape_dist_traveled  
 
STBA,6:20:00,6:20:00,BEATTY_AIRPORT,2,,0,0,1.043  
CITY1,6:00:00,6:00:00,STAGECOACH,0,,,,  
CITY1,6:05:00,6:07:00,NANAA,5,going to nadav,2,3,  
CITY1,6:12:00,6:14:00,NADAV,10,,,,  
CITY1,6:19:00,6:21:00,DADAN,15,,,,  
CITY1,6:26:00,6:28:00,EMSI,20,,,,  
CITY2,6:28:00,6:30:00,EMSI,100,,,,  
CITY2,6:35:00,6:37:00,DADAN,200,,,,  
CITY2,6:42:00,6:44:00,NADAV,300,,,,  
CITY2,6:49:00,6:51:00,NANAA,400,,,,  
CITY2,6:56:00,6:58:00,STAGECOACH,500,,,,  
AB1,8:00:00,8:00:00,BEATTY_AIRPORT,1,,,,  
AB1,8:10:00,8:15:00,BULLFROG,2,,,,  
AB2,12:05:00,12:05:00,BULLFROG,1,,,,  
AB2,12:15:00,12:15:00,BEATTY_AIRPORT,2,,,,  
BFC1,8:20:00,8:20:00,BULLFROG,1,,,,  
BFC1,9:20:00,9:20:00,FUR_CREEK_RES,2,,,,  
BFC2,11:00:00,11:00:00,FUR_CREEK_RES,1,,,,  
BFC2,12:00:00,12:00:00,BULLFROG,2,,,,  
AAMV1,8:00:00,8:00:00,BEATTY_AIRPORT,1,,,,  
AAMV1,9:00:00,9:00:00,AMV,2,,,,  
AAMV2,10:00:00,10:00:00,AMV,1,,,,  
AAMV2,11:00:00,11:00:00,BEATTY_AIRPORT,2,,,,  
AAMV3,13:00:00,13:00:00,BEATTY_AIRPORT,1,,,,  
AAMV3,14:00:00,14:00:00,AMV,2,,,,  
AAMV4,15:00:00,15:00:00,AMV,1,,,,  
AAMV4,16:00:00,16:00:00,BEATTY_AIRPORT,2,,,,  
 
stop_id,stop_name,stop_desc,stop_lat,stop_lon,zone_id,stop_url  
 
BEATTY_AIRPORT,Nye County Airport (Demo),,36.868446,-116.784582,,  
BULLFROG,Bullfrog (Demo),,36.88108,-116.81797,,  
STAGECOACH,Stagecoach Hotel & Casino (Demo),,36.915682,-116.751677,,  
NADAV,North Ave / D Ave N (Demo),,36.914893,-116.76821,,  
NANAA,North Ave / N A Ave (Demo),,36.914944,-116.761472,,  
DADAN,Doing Ave / D Ave N (Demo),,36.909489,-116.768242,,  
EMSI,E Main St / S Irving St (Demo),,36.905697,-116.76218,,  
AMV,Amargosa Valley (Demo),,36.641496,-116.40094,,  
 
route_id,service_id,trip_id,trip_headsign,direction_id,block_id,shape_id  
 
AB,FULLW,AB2,to Airport,1,2,  
STBA,FULLW,STBA,Shuttle,,,  
CITY,FULLW,CITY1,,0,,  
CITY,FULLW,CITY2,,1,,  
BFC,FULLW,BFC1,to Furnace Creek Resort,0,1,  
BFC,FULLW,BFC2,to Bullfrog,1,2,  
AAMV,WE,AAMV1,to Amargosa Valley,0,,  
AAMV,WE,AAMV2,to Airport,1,,  
AAMV,WE,AAMV3,to Amargosa Valley,0,,  
AAMV,WE,AAMV4,to Airport,1,,  
 
agency_id,agency_name,agency_url,agency_timezone  
DTA,Autorité de passage de démonstration,http://google.com,America/Los_Angeles  
 
service_id,monday,tuesday,wednesday,thursday,friday,saturday,sunday,start_date,end_date  
FULLW,1,1,1,1,1,1,1,20070101,20101231  
WE,0,0,0,0,0,1,1,20070101,20101231  
service_id,date,exception_type  
FULLW,20070604,2  
fare_id,price,currency_type,payment_method,transfers,transfer_duration  
p,1.25,USD,0,0,  
a,5.25,USD,0,0,  
 
fare_id,route_id,origin_id,destination_id,contains_id  
p,AB,,,  
p,STBA,,,  
p,BFC,,,  
a,AAMV,,,  
 
trip_id,start_time,end_time,headway_secs  
STBA,6:00:00,22:00:00,1800  
CITY1,6:00:00,7:59:59,1800  
CITY2,6:00:00,7:59:59,1800  
CITY1,8:00:00,9:59:59,600  
CITY2,8:00:00,9:59:59,600  
CITY1,10:00:00,15:59:59,1800  
CITY2,10:00:00,15:59:59,1800  
CITY1,16:00:00,18:59:59,600  
CITY2,16:00:00,18:59:59,600  
CITY1,19:00:00,22:00:00,1800  
CITY2,19:00:00,22:00:00,1800  
route_id,agency_id,route_short_name,route_long_name,route_desc,route_type,route_url,route_color,route_text_color  
AB,DTA,,Airport ⇒ Bullfrog,,3  
BFC,DTA,,Bullfrog ⇒ Furnace Creek Resort,,3  
STBA,DTA,,Stagecoach ⇒ Airport Shuttle,,3  
CITY,DTA,Ō,Bar Circle,Route with ĸool unicode short name,3  
AAMV,DTA,,Airport ⇒ Amargosa Valley,,3  
 
trip_id,arrival_time,departure_time,stop_id,stop_sequence,stop_headsign,pickup_type,drop_off_type,shape_dist_traveled  
STBA,6:00:00,6:00:00,STAGECOACH,1,to airport,1,0,0.212  
STBA,6:20:00,6:20:00,BEATTY_AIRPORT,2,,0,0,1.043  
CITY1,6:00:00,6:00:00,STAGECOACH,1  
CITY1,6:05:00,6:07:00,NANAA,2,going to nadav,2,3,  
CITY1,6:12:00,6:14:00,NADAV,3  
CITY1,6:19:00,6:21:00,DADAN,4  
CITY1,6:26:00,6:28:00,EMSI,5  
CITY2,6:28:00,6:30:00,EMSI,1  
CITY2,6:35:00,6:37:00,DADAN,2  
CITY2,6:42:00,6:44:00,NADAV,3  
CITY2,6:49:00,6:51:00,NANAA,4  
CITY2,6:56:00,6:58:00,STAGECOACH,5  
AB1,8:00:00,8:00:00,BEATTY_AIRPORT,1  
AB1,8:10:00,8:15:00,BULLFROG,2  
AB2,12:05:00,12:05:00,BULLFROG,1  
AB2,12:15:00,12:15:00,BEATTY_AIRPORT,2  
BFC1,8:20:00,8:20:00,BULLFROG,1  
BFC1,9:20:00,9:20:00,FUR_CREEK_RES,2  
BFC2,11:00:00,11:00:00,FUR_CREEK_RES,1  
BFC2,12:00:00,12:00:00,BULLFROG,2  
AAMV1,8:00:00,8:00:00,BEATTY_AIRPORT,1  
AAMV1,9:00:00,9:00:00,AMV,2  
AAMV2,10:00:00,10:00:00,AMV,1  
AAMV2,11:00:00,11:00:00,BEATTY_AIRPORT,2  
AAMV3,13:00:00,13:00:00,BEATTY_AIRPORT,1  
AAMV3,14:00:00,14:00:00,AMV,2  
AAMV4,15:00:00,15:00:00,AMV,1  
AAMV4,16:00:00,16:00:00,BEATTY_AIRPORT,2  
 
 Binary files a/origin-src/transitfeed-1.2.5/test/data/contains_null/stops.txt and /dev/null differ
route_id,service_id,trip_id,trip_headsign,direction_id,block_id,shape_id  
AB,FULLW,AB1,to Bullfrog,0,1  
AB,FULLW,AB2,to Airport,1,2  
STBA,FULLW,STBA,Shuttle  
CITY,FULLW,CITY1,Ō,0  
CITY,FULLW,CITY2,Ō,1  
BFC,FULLW,BFC1,to Furnace Creek Resort,0,1  
BFC,FULLW,BFC2,to Bullfrog,1,2  
AAMV,WE,AAMV1,to Amargosa Valley,0  
AAMV,WE,AAMV2,to Airport,1  
AAMV,WE,AAMV3,to Amargosa Valley,0  
AAMV,WE,AAMV4,to Airport,1  
 
agency_id,agency_name,agency_url,agency_timezone  
DTA,Autorité de passage de démonstration,http://google.com,America/Los_Angeles  
 
service_id,monday,tuesday,wednesday,thursday,friday,saturday,sunday,start_date,end_date  
FULLW,1,1,1,1,1,1,1,20070101,20101231  
WE,0,0,0,0,0,1,1,20070101,20101231  
WE,0,0,0,0,0,1,1,20070101,20101231  
 
service_id,date,exception_type  
FULLW,20070604,2  
fare_id,price,currency_type,payment_method,transfers,transfer_duration  
p,1.25,USD,0,0,  
a,5.25,USD,0,0,  
 
fare_id,route_id,origin_id,destination_id,contains_id  
p,AB,,,  
p,STBA,,,  
p,BFC,,,  
a,AAMV,,,  
 
trip_id,start_time,end_time,headway_secs  
STBA,6:00:00,22:00:00,1800  
CITY1,6:00:00,7:59:59,1800  
CITY2,6:00:00,7:59:59,1800  
CITY1,8:00:00,9:59:59,600  
CITY2,8:00:00,9:59:59,600  
CITY1,10:00:00,15:59:59,1800  
CITY2,10:00:00,15:59:59,1800  
CITY1,16:00:00,18:59:59,600  
CITY2,16:00:00,18:59:59,600  
CITY1,19:00:00,22:00:00,1800  
CITY2,19:00:00,22:00:00,1800  
route_id,agency_id,route_short_name,route_long_name,route_desc,route_type,route_url,route_color,route_text_color  
AB,DTA,,Airport ⇒ Bullfrog,,3,,,  
BFC,DTA,,Bullfrog ⇒ Furnace Creek Resort,,3,,,  
STBA,DTA,,Stagecoach ⇒ Airport Shuttle,,3,,,  
CITY,DTA,Ō,Bar Circle,Route with ĸool unicode short name,3,,,  
AAMV,DTA,,Airport ⇒ Amargosa Valley,,3,,,  
 
trip_id,arrival_time,departure_time,stop_id,stop_sequence,stop_headsign,pickup_type,drop_off_type,shape_dist_traveled  
STBA,6:00:00,6:00:00,STAGECOACH,1,to airport,1,0,0.212  
STBA,6:20:00,6:20:00,BEATTY_AIRPORT,2,,0,0,1.043  
CITY1,6:00:00,6:00:00,STAGECOACH,1,,,,  
CITY1,6:05:00,6:07:00,NANAA,2,going to nadav,2,3,  
CITY1,6:12:00,6:14:00,NADAV,3,,,,  
CITY1,6:19:00,6:21:00,DADAN,4,,,,  
CITY1,6:26:00,6:28:00,EMSI,5,,,,  
CITY2,6:28:00,6:30:00,EMSI,1,,,,  
CITY2,6:35:00,6:37:00,DADAN,2,,,,  
CITY2,6:42:00,6:44:00,NADAV,3,,,,  
CITY2,6:49:00,6:51:00,NANAA,4,,,,  
CITY2,6:56:00,6:58:00,STAGECOACH,5,,,,  
AB1,8:00:00,8:00:00,BEATTY_AIRPORT,1,,,,  
AB1,8:10:00,8:15:00,BULLFROG,2,,,,  
AB2,12:05:00,12:05:00,BULLFROG,1,,,,  
AB2,12:15:00,12:15:00,BEATTY_AIRPORT,2,,,,  
BFC1,8:20:00,8:20:00,BULLFROG,1,,,,  
BFC1,9:20:00,9:20:00,FUR_CREEK_RES,2,,,,  
BFC2,11:00:00,11:00:00,FUR_CREEK_RES,1,,,,  
BFC2,12:00:00,12:00:00,BULLFROG,2,,,,  
AAMV1,8:00:00,8:00:00,BEATTY_AIRPORT,1,,,,  
AAMV1,9:00:00,9:00:00,AMV,2,,,,  
AAMV2,10:00:00,10:00:00,AMV,1,,,,  
AAMV2,11:00:00,11:00:00,BEATTY_AIRPORT,2,,,,  
AAMV3,13:00:00,13:00:00,BEATTY_AIRPORT,1,,,,  
AAMV3,14:00:00,14:00:00,AMV,2,,,,  
AAMV4,15:00:00,15:00:00,AMV,1,,,,  
AAMV4,16:00:00,16:00:00,BEATTY_AIRPORT,2,,,,  
 
stop_id,stop_name,stop_desc,stop_lat,stop_lon,zone_id,stop_url  
FUR_CREEK_RES,Furnace Creek Resort (Démonstration),,36.425288,-117.133162,,  
BEATTY_AIRPORT,Nye County Airport (Demo),,36.868446,-116.784582,,  
BULLFROG,Bullfrog (Demo),,36.88108,-116.81797,,  
STAGECOACH,Stagecoach Hotel & Casino (Demo),,36.915682,-116.751677,,  
NADAV,North Ave / D Ave N (Demo),,36.914893,-116.76821,,  
NANAA,North Ave / N A Ave (Demo),,36.914944,-116.761472,,  
DADAN,Doing Ave / D Ave N (Demo),,36.909489,-116.768242,,  
EMSI,E Main St / S Irving St (Demo),,36.905697,-116.76218,,  
AMV,Amargosa Valley (Demo),,36.641496,-116.40094,,  
 
route_id,service_id,trip_id,trip_headsign,direction_id,block_id,shape_id  
AB,FULLW,AB1,to Bullfrog,0,1,  
AB,FULLW,AB2,to Airport,1,2,  
STBA,FULLW,STBA,Shuttle,,,  
CITY,FULLW,CITY1,Ō,0,,  
CITY,FULLW,CITY2,Ō,1,,  
BFC,FULLW,BFC1,to Furnace Creek Resort,0,1,  
BFC,FULLW,BFC2,to Bullfrog,1,2,  
AAMV,WE,AAMV1,to Amargosa Valley,0,,  
AAMV,WE,AAMV2,to Airport,1,,  
AAMV,WE,AAMV3,to Amargosa Valley,0,,  
AAMV,WE,AAMV4,to Airport,1,,  
 
agency_id,agency_name,agency_url,agency_timezone  
DTA,Demo Transit Authority,http://google.com,America/Los_Angeles  
service_id,monday,tuesday,wednesday,thursday,friday,saturday,sunday,start_date,end_date  
FULLW,1,1,1,1,1,1,1,20070101,20101231  
WE,0,0,0,0,0,1,1,20070101,20101231  
service_id,date,exception_type  
FULLW,20070604,2  
fare_id,price,currency_type,payment_method,transfers,transfer_duration  
p,1.25,USD,0,0,  
a,5.25,USD,0,0,  
 
fare_id,route_id,origin_id,destination_id,contains_id  
p,AB,,,  
p,STBA,,,  
p,BFC,,,  
a,AAMV,,,  
 
trip_id,start_time,end_time,headway_secs  
STBA,6:00:00,22:00:00,1800  
CITY1,6:00:00,7:59:59,1800  
CITY2,6:00:00,7:59:59,1800  
CITY1,8:00:00,9:59:59,600  
CITY2,8:00:00,9:59:59,600  
CITY1,10:00:00,15:59:59,1800  
CITY2,10:00:00,15:59:59,1800  
CITY1,16:00:00,18:59:59,600  
CITY2,16:00:00,18:59:59,600  
CITY1,19:00:00,22:00:00,1800  
CITY2,19:00:00,22:00:00,1800  
route_id,agency_id,route_short_name,route_long_name,route_desc,route_type,route_url,route_color,route_text_color  
AB,DTA,,Airport - Bullfrog,,3  
BFC,DTA,,Bullfrog - Furnace Creek Resort,,3  
STBA,DTA,,Stagecoach - Airport Shuttle,,3  
CITY,DTA,,City,,3  
AAMV,DTA,,Airport - Amargosa Valley,,3  
trip_id,arrival_time,departure_time,stop_id,stop_sequence,stop_headsign,pickup_type,drop_off_type,shape_dist_traveled  
STBA,6:00:00,6:00:00,STAGECOACH,1  
STBA,6:20:00,6:20:00,BEATTY_AIRPORT,2  
CITY1,6:00:00,6:00:00,STAGECOACH,1  
CITY1,6:05:00,6:07:00,NANAA,2  
CITY1,6:12:00,6:14:00,NADAV,3  
CITY1,6:19:00,6:21:00,DADAN,4  
CITY1,6:26:00,6:28:00,EMSI,5  
CITY2,6:28:00,6:30:00,EMSI,1  
CITY2,6:35:00,6:37:00,DADAN,2  
CITY2,6:42:00,6:44:00,NADAV,3  
CITY2,6:49:00,6:51:00,NANAA,4  
CITY2,6:56:00,6:58:00,STAGECOACH,5  
AB1,8:00:00,8:00:00,BEATTY_AIRPORT,1  
AB1,8:10:00,8:15:00,BULLFROG,2  
AB2,12:05:00,12:05:00,BULLFROG,1  
AB2,12:15:00,12:15:00,BEATTY_AIRPORT,2  
BFC1,8:20:00,8:20:00,FROG,1  
BFC1,9:20:00,9:20:00,FUR_CREEK_RES,2  
BFC2,11:00:00,11:00:00,FUR_CREEK_RES,1  
BFC2,12:00:00,12:00:00,BULLFROG,2  
AAMV1,8:00:00,8:00:00,BEATTY_AIRPORT,1  
AAMV1,9:00:00,9:00:00,AMV,2  
AAMV2,10:00:00,10:00:00,AMV,1  
AAMV2,11:00:00,11:00:00,BEATTY_AIRPORT,2  
AAMV3,13:00:00,13:00:00,BEATTY_AIRPORT,1  
AAMV3,14:00:00,14:00:00,AMV,2  
AAMV4,15:00:00,15:00:00,AMV,1  
AAMV4,16:00:00,16:00:00,BEATTY_AIRPORT,2  
stop_id,stop_name,stop_desc,stop_lat,stop_lon,zone_id,stop_url  
FUR_CREEK_RES,Furnace Creek Resort (Demo),,36.425288,-117.133162  
BEATTY_AIRPORT,Nye County Airport (Demo),,36.868446,-116.784582  
BULLFROG,Bullfrog (Demo),,36.88108,-116.81797  
FROG,Bull Frog,,36.881083,-116.817968  
STAGECOACH,Stagecoach Hotel & Casino (Demo),,36.915682,-116.751677  
NADAV,North Ave / D Ave N (Demo),,36.914893,-116.76821  
NANAA,North Ave / N A Ave (Demo),,36.914944,-116.761472  
DADAN,Doing Ave / D Ave N (Demo),,36.909489,-116.768242  
EMSI,E Main St / S Irving St (Demo),,36.905697,-116.76218  
AMV,Amargosa Valley (Demo),,36.641496,-116.40094  
route_id,service_id,trip_id,trip_headsign,direction_id,block_id,shape_id  
AB,FULLW,AB1,to Bullfrog,0,1  
AB,FULLW,AB2,to Airport,1,2  
STBA,FULLW,STBA,Shuttle  
CITY,FULLW,CITY1,,0  
CITY,FULLW,CITY2,,1  
BFC,FULLW,BFC1,to Furnace Creek Resort,0,1  
BFC,FULLW,BFC2,to Bullfrog,1,2  
AAMV,WE,AAMV1,to Amargosa Valley,0  
AAMV,WE,AAMV2,to Airport,1  
AAMV,WE,AAMV3,to Amargosa Valley,0  
AAMV,WE,AAMV4,to Airport,1  
agency_id,agency_name,agency_url,agency_timezone  
DTA,Autorité de passage de démonstration,http://google.com,America/Los_Angeles  
 
service_id,monday,tuesday,wednesday,thursday,friday,saturday,sunday,start_date,end_date  
FULLW,1,1,1,1,1,1,1,20070101,20101231  
 
route_id,agency_id,route_short_name,route_long_name,route_desc,route_type,route_url,route_color,route_text_color  
CITY,DTA,Ō,Bar Circle,Route with ĸool unicode shortname,3,,,  
 
trip_id,arrival_time,departure_time,stop_id,stop_sequence,stop_headsign,pickup_type,drop_off_type,shape_dist_traveled  
CITY1,6:00:00,6:00:00,STAGECOACH,0,,,,  
CITY1,6:05:00,6:07:00,NANAA,10,going to nadav,2,3,  
CITY1,6:12:00,6:14:00,NADAV,10,,,,  
CITY1,6:19:00,6:21:00,DADAN,15,,,,  
CITY1,6:26:00,6:28:00,EMSI,20,,,,  
 
stop_id,stop_name,stop_desc,stop_lat,stop_lon,zone_id,stop_url,stop_code  
STAGECOACH,Stagecoach Hotel & Casino (Demo),,36.915682,-116.751677,,,1236  
NADAV,North Ave / D Ave N (Demo),,36.914893,-116.76821,,,1237  
NANAA,North Ave / N A Ave (Demo),,36.914944,-116.761472,,,1238  
DADAN,Doing Ave / D Ave N (Demo),,36.909489,-116.768242,,,  
EMSI,E Main St / S Irving St (Demo),,36.905697,-116.76218,,,  
 
route_id,service_id,trip_id,trip_headsign,direction_id,block_id,shape_id  
CITY,FULLW,CITY1,,0,,  
 
service_id,monday,tuesday,wednesday,thursday,friday,saturday,sunday,start_date,end_date  
FULLW,1,1,1,1,1,1,1,20070101,20101231  
WE,0,0,0,0,0,1,1,20070101,20101231  
service_id,date,exception_type  
FULLW,20070604,2  
fare_id,price,currency_type,payment_method,transfers,transfer_duration  
p,1.25,USD,0,0,  
a,5.25,USD,0,0,  
 
fare_id,route_id,origin_id,destination_id,contains_id  
p,AB,,,  
p,STBA,,,  
p,BFC,,,  
a,AAMV,,,  
 
trip_id,start_time,end_time,headway_secs  
STBA,6:00:00,22:00:00,1800  
CITY1,6:00:00,7:59:59,1800  
CITY2,6:00:00,7:59:59,1800  
CITY1,8:00:00,9:59:59,600  
CITY2,8:00:00,9:59:59,600  
CITY1,10:00:00,15:59:59,1800  
CITY2,10:00:00,15:59:59,1800  
CITY1,16:00:00,18:59:59,600  
CITY2,16:00:00,18:59:59,600  
CITY1,19:00:00,22:00:00,1800  
CITY2,19:00:00,22:00:00,1800  
route_id,agency_id,route_short_name,route_long_name,route_desc,route_type,route_url,route_color,route_text_color  
AB,DTA,,Airport - Bullfrog,,3  
BFC,DTA,,Bullfrog - Furnace Creek Resort,,3  
STBA,DTA,,Stagecoach - Airport Shuttle,,3  
CITY,DTA,,City,,3  
AAMV,DTA,,Airport - Amargosa Valley,,3  
trip_id,arrival_time,departure_time,stop_id,stop_sequence  
STBA,6:00:00,6:00:00,STAGECOACH,1  
STBA,6:20:00,6:20:00,BEATTY_AIRPORT,2  
CITY1,6:00:00,6:00:00,STAGECOACH,1  
CITY1,6:05:00,6:07:00,NANAA,2  
CITY1,6:12:00,6:14:00,NADAV,3  
CITY1,6:19:00,6:21:00,DADAN,4  
CITY1,6:26:00,6:28:00,EMSI,5  
CITY2,6:28:00,6:30:00,EMSI,1  
CITY2,6:35:00,6:37:00,DADAN,2  
CITY2,6:42:00,6:44:00,NADAV,3  
CITY2,6:49:00,6:51:00,NANAA,4  
CITY2,6:56:00,6:58:00,STAGECOACH,5  
AB1,8:00:00,8:00:00,BEATTY_AIRPORT,1  
AB1,8:10:00,8:15:00,BULLFROG,2  
AB2,12:05:00,12:05:00,BULLFROG,1  
AB2,12:15:00,12:15:00,BEATTY_AIRPORT,2  
BFC1,8:20:00,8:20:00,BULLFROG,1  
BFC1,9:20:00,9:20:00,FUR_CREEK_RES,2  
BFC2,11:00:00,11:00:00,FUR_CREEK_RES,1  
BFC2,12:00:00,12:00:00,BULLFROG,2  
AAMV1,8:00:00,8:00:00,BEATTY_AIRPORT,1  
AAMV1,9:00:00,9:00:00,AMV,2  
AAMV2,10:00:00,10:00:00,AMV,1  
AAMV2,11:00:00,11:00:00,BEATTY_AIRPORT,2  
AAMV3,13:00:00,13:00:00,BEATTY_AIRPORT,1  
AAMV3,14:00:00,14:00:00,AMV,2  
AAMV4,15:00:00,15:00:00,AMV,1  
AAMV4,16:00:00,16:00:00,BEATTY_AIRPORT,2  
 
stop_id,stop_name,stop_desc,stop_lat,stop_lon,zone_id,stop_url  
FUR_CREEK_RES,Furnace Creek Resort (Demo),,36.425288,-117.133162  
BEATTY_AIRPORT,Nye County Airport (Demo),,36.868446,-116.784582  
BULLFROG,Bullfrog (Demo),,36.88108,-116.81797  
STAGECOACH,Stagecoach Hotel & Casino (Demo),,36.915682,-116.751677  
NADAV,North Ave / D Ave N (Demo),,36.914893,-116.76821  
NANAA,North Ave / N A Ave (Demo),,36.914944,-116.761472  
DADAN,Doing Ave / D Ave N (Demo),,36.909489,-116.768242  
EMSI,E Main St / S Irving St (Demo),,36.905697,-116.76218  
AMV,Amargosa Valley (Demo),,36.641496,-116.40094  
route_id,service_id,trip_id,trip_headsign,direction_id,block_id,shape_id  
AB,FULLW,AB1,to Bullfrog,0,1  
AB,FULLW,AB2,to Airport,1,2  
STBA,FULLW,STBA,Shuttle  
CITY,FULLW,CITY1,,0  
CITY,FULLW,CITY2,,1  
BFC,FULLW,BFC1,to Furnace Creek Resort,0,1  
BFC,FULLW,BFC2,to Bullfrog,1,2  
AAMV,WE,AAMV1,to Amargosa Valley,0  
AAMV,WE,AAMV2,to Airport,1  
AAMV,WE,AAMV3,to Amargosa Valley,0  
AAMV,WE,AAMV4,to Airport,1  
agency_id,agency_name,agency_url,agency_timezone  
DTA,Demo Transit Authority,http://google.com,America/Los_Angeles  
service_id,monday,tuesday,wednesday,thursday,friday,saturday,sunday,start_date,end_date  
FULLW,1,1,1,1,1,1,1,20070101,20101231  
WE,0,0,0,0,0,1,1,20070101,20101231  
service_id,date,exception_type  
FULLW,20070604,2  
fare_id,price,currency_type,payment_method,transfers,transfer_duration  
p,1.25,USD,0,0,  
a,5.25,USD,0,0,  
 
fare_id,route_id,origin_id,destination_id,contains_id  
p,AB,,,  
p,STBA,,,  
p,BFC,,,  
a,AAMV,,,  
 
trip_id,start_time,end_time,headway_secs  
STBA,6:00:00,22:00:00,1800  
CITY1,6:00:00,7:59:59,1800  
CITY2,6:00:00,7:59:59,1800  
CITY1,8:00:00,9:59:59,600  
CITY2,8:00:00,9:59:59,600  
CITY1,10:00:00,15:59:59,1800  
CITY2,10:00:00,15:59:59,1800  
CITY1,16:00:00,18:59:59,600  
CITY2,16:00:00,18:59:59,600  
CITY1,19:00:00,22:00:00,1800  
CITY2,19:00:00,22:00:00,1800  
route_id,agency_id,route_short_name,route_long_name,route_desc,route_type  
AB,DTA,,Airport - Bullfrog,,3  
BFC,DTA,,Bullfrog - Furnace Creek Resort,,3  
STBA,DTA,,Stagecoach - Airport Shuttle,,3,  
CITY,DTA,,City,,3  
AAMV,DTA,,Airport - Amargosa Valley,,3  
 
trip_id,arrival_time,departure_time,stop_id,stop_sequence  
STBA,6:00:00,6:00:00,STAGECOACH,1  
STBA,6:20:00,6:20:00,BEATTY_AIRPORT,2  
CITY1,6:00:00,6:00:00,STAGECOACH,1  
CITY1,6:05:00,6:07:00,NANAA,2  
CITY1,6:12:00,6:14:00,NADAV,3  
CITY1,6:19:00,6:21:00,DADAN,4  
CITY1,6:26:00,6:28:00,EMSI,5  
CITY2,6:28:00,6:30:00,EMSI,1  
CITY2,6:35:00,6:37:00,DADAN,2  
CITY2,6:42:00,6:44:00,NADAV,3  
CITY2,6:49:00,6:51:00,NANAA,4  
CITY2,6:56:00,6:58:00,STAGECOACH,5  
AB1,8:00:00,8:00:00,BEATTY_AIRPORT,1  
AB1,8:10:00,8:15:00,BULLFROG,2  
AB2,12:05:00,12:05:00,BULLFROG,1  
AB2,12:15:00,12:15:00,BEATTY_AIRPORT,2  
BFC1,8:20:00,8:20:00,BULLFROG,1  
BFC1,9:20:00,9:20:00,FUR_CREEK_RES,2  
BFC2,11:00:00,11:00:00,FUR_CREEK_RES,1  
BFC2,12:00:00,12:00:00,BULLFROG,2  
AAMV1,8:00:00,8:00:00,BEATTY_AIRPORT,1  
AAMV1,9:00:00,9:00:00,AMV,2  
AAMV2,10:00:00,10:00:00,AMV,1  
AAMV2,11:00:00,11:00:00,BEATTY_AIRPORT,2  
AAMV3,13:00:00,13:00:00,BEATTY_AIRPORT,1  
AAMV3,14:00:00,14:00:00,AMV,2  
AAMV4,15:00:00,15:00:00,AMV,1  
AAMV4,16:00:00,16:00:00,BEATTY_AIRPORT,2  
 
stop_id,stop_name,stop_desc,stop_lat,stop_lon  
FUR_CREEK_RES,Furnace Creek Resort (Demo),,36.425288,-117.133162  
BEATTY_AIRPORT,Nye County Airport (Demo),,36.868446,-116.784582  
BULLFROG,Bullfrog (Demo),,36.88108,-116.81797  
STAGECOACH,Stagecoach Hotel & Casino (Demo),,36.915682,-116.751677  
NADAV,North Ave / D Ave N (Demo),,36.914893,-116.76821  
NANAA,North Ave / N A Ave (Demo),,36.914944,-116.761472  
DADAN,Doing Ave / D Ave N (Demo),,36.909489,-116.768242  
EMSI,E Main St / S Irving St (Demo),,36.905697,-116.76218  
AMV,Amargosa Valley (Demo),,36.641496,-116.40094  
 
route_id,service_id,trip_id,trip_headsign,direction_id,block_id  
AB,FULLW,AB1,to Bullfrog,0,1  
AB,FULLW,AB2,to Airport,1,2  
STBA,FULLW,STBA,Shuttle,1,  
CITY,FULLW,CITY1,,0,  
CITY,FULLW,CITY2,,1,  
BFC,FULLW,BFC1,to Furnace Creek Resort,0,1  
BFC,FULLW,BFC2,to Bullfrog,1,2  
AAMV,WE,AAMV1,to Amargosa Valley,0,  
AAMV,WE,AAMV2,to Airport,1,  
AAMV,WE,AAMV3,to Amargosa Valley,0,  
AAMV,WE,AAMV4,to Airport,1,  
 
agency_id,agency_name,agency_url,agency_timezone  
DTA,Demo Transit Authority,http://google.com,America/Los_Angeles  
service_id,monday,tuesday,wednesday,thursday,friday,saturday,sunday,start_date,end_date  
FULLW,1,1,1,1,1,1,1,20070101,20101231  
WE,0,0,0,0,0,1,1,20070101,20101231  
service_id,date,exception_type  
FULLW,20070604,2  
fare_id,price,currency_type,payment_method,transfers,transfer_duration  
p,1.25,USD,0,0,  
a,5.25,USD,0,0,  
fare_id,route_id,origin_id,destination_id,contains_id  
p,AB,,,  
p,STBA,,,  
p,BFC,,,  
a,AAMV,,,  
trip_id,start_time,end_time,headway_secs  
STBA,6:00:00,22:00:00,1800  
CITY1,6:00:00,7:59:59,1800  
CITY2,6:00:00,7:59:59,1800  
CITY1,8:00:00,9:59:59,600  
CITY2,8:00:00,9:59:59,600  
CITY1,10:00:00,15:59:59,1800  
CITY2,10:00:00,15:59:59,1800  
CITY1,16:00:00,18:59:59,600  
CITY2,16:00:00,18:59:59,600  
CITY1,19:00:00,22:00:00,1800  
CITY2,19:00:00,22:00:00,1800  
route_id,agency_id,route_short_name,route_long_name,route_desc,route_type,route_url,route_color,route_text_color  
AB,DTA,10,Airport - Bullfrog,,3,,,  
BFC,DTA,20,Bullfrog - Furnace Creek Resort,,3,,,  
STBA,DTA,30,Stagecoach - Airport Shuttle,,3,,,  
CITY,DTA,40,City,,3,,,  
AAMV,DTA,50,Airport - Amargosa Valley,,3,,,  
shape_id,shape_pt_lat,shape_pt_lon,shape_pt_sequence,shape_dist_traveled  
trip_id,arrival_time,departure_time,stop_id,stop_sequence,stop_headsign,pickup_type,shape_dist_traveled  
STBA,6:00:00,6:00:00,STAGECOACH,1,,,  
STBA,6:20:00,6:20:00,BEATTY_AIRPORT,2,,,  
CITY1,6:00:00,6:00:00,STAGECOACH,1,,,  
CITY1,6:05:00,6:07:00,NANAA,2,,,  
CITY1,6:12:00,6:14:00,NADAV,3,,,  
CITY1,6:19:00,6:21:00,DADAN,4,,,  
CITY1,6:26:00,6:28:00,EMSI,5,,,  
CITY2,6:28:00,6:30:00,EMSI,1,,,  
CITY2,6:35:00,6:37:00,DADAN,2,,,  
CITY2,6:42:00,6:44:00,NADAV,3,,,  
CITY2,6:49:00,6:51:00,NANAA,4,,,  
CITY2,6:56:00,6:58:00,STAGECOACH,5,,,  
CITY3,6:00:00,6:00:00,STAGECOACH,1,,,  
CITY3,6:05:00,6:07:00,NANAA,2,,,  
CITY3,6:12:00,6:14:00,NADAV,3,,,  
CITY3,6:19:00,6:21:00,DADAN,4,,,  
CITY3,6:26:00,6:28:00,EMSI,5,,,  
CITY4,6:28:00,6:30:00,EMSI,1,,,  
CITY4,6:35:00,6:37:00,DADAN,2,,,  
CITY4,6:42:00,6:44:00,NADAV,3,,,  
CITY4,6:49:00,6:51:00,NANAA,4,,,  
CITY4,6:56:00,6:58:00,STAGECOACH,5,,,  
CITY5,6:00:00,6:00:00,STAGECOACH,1,,,  
CITY5,6:05:00,6:07:00,NANAA,2,,,  
CITY5,6:12:00,6:14:00,NADAV,3,,,  
CITY5,6:19:00,6:21:00,DADAN,4,,,  
CITY5,6:26:00,6:28:00,EMSI,5,,,  
CITY6,6:28:00,6:30:00,EMSI,1,,,  
CITY6,6:35:00,6:37:00,DADAN,2,,,  
CITY6,6:42:00,6:44:00,NADAV,3,,,  
CITY6,6:49:00,6:51:00,NANAA,4,,,  
CITY6,6:56:00,6:58:00,STAGECOACH,5,,,  
CITY7,6:00:00,6:00:00,STAGECOACH,1,,,  
CITY7,6:05:00,6:07:00,NANAA,2,,,  
CITY7,6:12:00,6:14:00,NADAV,3,,,  
CITY7,6:19:00,6:21:00,DADAN,4,,,  
CITY7,6:26:00,6:28:00,EMSI,5,,,  
CITY8,6:28:00,6:30:00,EMSI,1,,,  
CITY8,6:35:00,6:37:00,DADAN,2,,,  
CITY8,6:42:00,6:44:00,NADAV,3,,,  
CITY8,6:49:00,6:51:00,NANAA,4,,,  
CITY8,6:56:00,6:58:00,STAGECOACH,5,,,  
CITY9,6:00:00,6:00:00,STAGECOACH,1,,,  
CITY9,6:05:00,6:07:00,NANAA,2,,,  
CITY9,6:12:00,6:14:00,NADAV,3,,,  
CITY9,6:19:00,6:21:00,DADAN,4,,,  
CITY9,6:26:00,6:28:00,EMSI,5,,,  
CITY10,6:28:00,6:30:00,EMSI,1,,,  
CITY10,6:35:00,6:37:00,DADAN,2,,,  
CITY10,6:42:00,6:44:00,NADAV,3,,,  
CITY10,6:49:00,6:51:00,NANAA,4,,,  
CITY10,6:56:00,6:58:00,STAGECOACH,5,,,  
CITY11,6:00:00,6:00:00,NANAA,1,,,  
CITY11,6:05:00,6:07:00,BEATTY_AIRPORT,2,,,  
CITY11,6:12:00,6:14:00,BULLFROG,3,,,  
CITY11,6:19:00,6:21:00,DADAN,4,,,  
CITY11,6:26:00,6:28:00,EMSI,5,,,  
CITY12,6:28:00,6:30:00,EMSI,1,,,  
CITY12,6:35:00,6:37:00,DADAN,2,,,  
CITY12,7:07:00,7:09:00,AMV,3,,,  
CITY12,7:39:00,7:41:00,BEATTY_AIRPORT,4,,,  
CITY12,7:46:00,7:48:00,STAGECOACH,5,,,  
AB1,8:00:00,8:00:00,BEATTY_AIRPORT,1,,,  
AB1,8:10:00,8:15:00,BULLFROG,2,,,  
AB2,12:05:00,12:05:00,BULLFROG,1,,,  
AB2,12:15:00,12:15:00,BEATTY_AIRPORT,2,,,  
BFC1,8:20:00,8:20:00,BULLFROG,1,,,  
BFC1,9:20:00,9:20:00,FUR_CREEK_RES,2,,,  
BFC2,11:00:00,11:00:00,FUR_CREEK_RES,1,,,  
BFC2,12:00:00,12:00:00,BULLFROG,2,,,  
AAMV1,8:00:00,8:00:00,BEATTY_AIRPORT,1,,,  
AAMV1,9:00:00,9:00:00,AMV,2,,,  
AAMV2,10:00:00,10:00:00,AMV,1,,,  
AAMV2,11:00:00,11:00:00,BEATTY_AIRPORT,2,,,  
AAMV3,13:00:00,13:00:00,BEATTY_AIRPORT,1,,,  
AAMV3,14:00:00,14:00:00,AMV,2,,,  
AAMV4,15:00:00,15:00:00,AMV,1,,,  
AAMV4,16:00:00,16:00:00,BEATTY_AIRPORT,2,,,  
 
stop_id,stop_name,stop_desc,stop_lat,stop_lon,zone_id,stop_url  
FUR_CREEK_RES,Furnace Creek Resort (Demo),,36.425288,-117.133162,,  
BEATTY_AIRPORT,Nye County Airport (Demo),,36.868446,-116.784582,,  
BULLFROG,Bullfrog (Demo),,36.88108,-116.81797,,  
STAGECOACH,Stagecoach Hotel & Casino (Demo),,36.915682,-116.751677,,  
NADAV,North Ave / D Ave N (Demo),,36.914893,-116.76821,,  
NANAA,North Ave / N A Ave (Demo),,36.914944,-116.761472,,  
DADAN,Doing Ave / D Ave N (Demo),,36.909489,-116.768242,,  
EMSI,E Main St / S Irving St (Demo),,36.905697,-116.76218,,  
AMV,Amargosa Valley (Demo),,36.641496,-116.40094,,  
route_id,service_id,trip_id,trip_headsign,direction_id,block_id,shape_id  
AB,FULLW,AB1,to Bullfrog,0,1,  
AB,FULLW,AB2,to Airport,1,2,  
STBA,FULLW,STBA,Shuttle,,,  
CITY,FULLW,CITY1,,0,,  
CITY,FULLW,CITY2,,1,,  
CITY,FULLW,CITY3,,0,,  
CITY,FULLW,CITY4,,1,,  
CITY,FULLW,CITY5,,0,,  
CITY,FULLW,CITY6,,1,,  
CITY,FULLW,CITY7,,0,,  
CITY,FULLW,CITY8,,1,,  
CITY,FULLW,CITY9,,0,,  
CITY,FULLW,CITY10,,1,,  
CITY,FULLW,CITY11,,0,,  
CITY,FULLW,CITY12,,1,,  
BFC,FULLW,BFC1,to Furnace Creek Resort,0,1,  
BFC,FULLW,BFC2,to Bullfrog,1,2,  
AAMV,WE,AAMV1,to Amargosa Valley,0,,  
AAMV,WE,AAMV2,to Airport,1,,  
AAMV,WE,AAMV3,to Amargosa Valley,0,,  
AAMV,WE,AAMV4,to Airport,1,,  
agency_id,agency_name,agency_url,agency_timezone  
DTA,Demo Transit Authority,http://google.com,America/Los_Angeles  
service_id,monday,tuesday,wednesday,thursday,friday,saturday,sunday,start_date,end_date  
FULLW,1,1,1,1,1,1,1,20070101,20101231  
WE,0,0,0,0,0,1,1,20070101,20101231  
service_id,date,exception_type  
FULLW,20070604,2  
WE,20070604,1  
 
fare_id,price,currency_type,payment_method,transfers,transfer_duration  
p,1.25,USD,0,0,  
a,5.25,USD,0,0,  
 
route_id,agency_id,route_short_name,route_long_name,route_desc,route_type,route_url,route_color,route_text_color  
route_1,DTA,1,route with a single trip,,0,http://routes.com/route_1,FF0000,  
route_2,DTA,2,route with two trips and one component,test route desc 2,1,,00FF00,  
route_3,DTA,3,route with two trips and two components,test route desc 3,2,http://routes.com/route_3,,  
route_4,DTA,4,route with two equal trips,test route desc 4,3,http://routes.com/route_4,FFFF00,  
route_5,DTA,5,route with two trip but no graph,test route desc 5,4,http://routes.com/route_5,FF00FF,  
route_6,DTA,6,route with one trip and no stops,test route desc 6,5,http://routes.com/route_6,00FFFF,  
route_7,DTA,7,route with no trips,test route desc 7,6,http://routes.com/route_7,,  
route_8,DTA,8,route with a cyclic pattern,test route desc 8,7,http://routes.com/route_8,,  
 
shape_id,shape_pt_sequence,shape_pt_lat,shape_pt_lon  
shape_1,1,1,1  
shape_1,2,2,4  
shape_1,3,3,9  
shape_1,4,4,16  
shape_2,1,11,11  
shape_2,2,12,14  
shape_2,3,13,19  
shape_2,4,14,26  
shape_3,1,21,21  
shape_3,2,22,24  
shape_3,3,23,29  
shape_3,4,24,36  
 
trip_id,arrival_time,departure_time,stop_id,stop_sequence  
route_1_1,6:00:00,6:00:00,stop1,1  
route_1_1,7:00:00,7:00:00,stop2,2  
route_1_1,8:00:00,8:00:00,stop3,3  
route_2_1,6:00:00,6:00:00,stop1,1  
route_2_1,7:00:00,7:00:00,stop2,2  
route_2_1,8:00:00,8:00:00,stop3,3  
route_2_2,6:00:00,6:00:00,stop2,1  
route_2_2,7:00:00,7:00:00,stop4,2  
route_2_2,8:00:00,8:00:00,stop5,3  
route_3_1,6:00:00,6:00:00,stop1,1  
route_3_1,7:00:00,7:00:00,stop2,2  
route_3_1,8:00:00,8:00:00,stop3,3  
route_3_2,6:00:00,6:00:00,stop4,1  
route_3_2,7:00:00,7:00:00,stop5,2  
route_3_2,8:00:00,8:00:00,stop6,3  
route_4_1,6:00:00,6:00:00,stop1,1  
route_4_1,7:00:00,7:00:00,stop2,2  
route_4_1,8:00:00,8:00:00,stop3,3  
route_4_2,6:00:00,6:00:00,stop1,1  
route_4_2,7:00:00,7:00:00,stop2,2  
route_4_2,8:00:00,8:00:00,stop3,3  
route_5_1,6:00:00,6:00:00,stop1,1  
route_5_2,6:00:00,6:00:00,stop2,1  
route_8_1,6:00:00,6:00:00,stop1,1  
route_8_1,7:00:00,7:00:00,stop2,2  
route_8_1,8:00:00,8:00:00,stop3,3  
route_8_1,9:00:00,9:00:00,stop1,4  
 
stop_id,stop_name,stop_desc,stop_lat,stop_lon,zone_id,stop_url  
stop1,Furnace Creek Resort (Demo),,36.425288,-117.133162,,http://stops.com/stop1  
stop2,Nye County Airport (Demo),the stop at Nye County Airport,36.868446,-116.784582,,  
stop3,Bullfrog (Demo),the stop at Bullfrog,36.88108,-116.81797,,http://stops.com/stop3  
stop4,Stagecoach Hotel & Casino (Demo),the stop at Stagecoach Hotel & Casino,36.915682,-116.751677,,http://stops.com/stop4  
stop5,North Ave / D Ave N (Demo),the stop at North Ave / D Ave N,36.914893,-116.76821,,http://stops.com/stop5  
stop6,North Ave / N A Ave (Demo),the stop at North Ave / N A Ave,36.914944,-116.761472,,http://stops.com/stop6  
stop7,Doing Ave / D Ave N (Demo),the stop at Doing Ave / D Ave N,36.909489,-116.768242,,http://stops.com/stop7  
stop8,E Main St / S Irving St (Demo),the stop at E Main St / S Irving St,36.905697,-116.76218,,http://stops.com/stop8  
stop9,Amargosa Valley (Demo),the stop at Amargosa Valley,36.641496,-116.40094,,http://stops.com/stop9  
 
route_id,service_id,trip_id,shape_id  
route_1,FULLW,route_1_1,shape_1  
route_2,FULLW,route_2_1,shape_2  
route_2,FULLW,route_2_2,shape_3  
route_3,FULLW,route_3_1,shape_1  
route_3,FULLW,route_3_2,shape_1  
route_4,FULLW,route_4_1,  
route_4,FULLW,route_4_2,  
route_5,FULLW,route_5_1,  
route_5,FULLW,route_5_2,  
route_8,FULLW,route_8_1,  
route_8,WE,route_8_2,  
 
 Binary files a/origin-src/transitfeed-1.2.5/test/data/good_feed.zip and /dev/null differ
agency_id,agency_name,agency_url,agency_timezone,agency_phone  
DTA,Autorité de passage de démonstration,http://google.com,America/Los_Angeles,123 12314  
 
service_id,monday,tuesday,wednesday,thursday,friday,saturday,sunday,start_date,end_date  
FULLW,1,1,1,1,1,1,1,20070101,20111231  
WE,0,0,0,0,0,1,1,20070101,20111231  
 
service_id,date,exception_type  
FULLW,20070604,2  
fare_id,price,currency_type,payment_method,transfers,transfer_duration  
p,1.25,USD,0,0,  
a,5.25,USD,0,0,  
 
fare_id,route_id,origin_id,destination_id,contains_id  
p,AB,,,  
p,STBA,,,  
p,BFC,,,  
a,AAMV,,,  
 
trip_id,start_time,end_time,headway_secs  
STBA,6:00:00,22:00:00,1800  
CITY1,6:00:00,7:59:59,1800  
CITY2,6:00:00,7:59:59,1800  
CITY1,8:00:00,9:59:59,600  
CITY2,8:00:00,9:59:59,600  
CITY1,10:00:00,15:59:59,1800  
CITY2,10:00:00,15:59:59,1800  
CITY1,16:00:00,18:59:59,600  
CITY2,16:00:00,18:59:59,600  
CITY1,19:00:00,22:00:00,1800  
CITY2,19:00:00,22:00:00,1800  
route_id,agency_id,route_short_name,route_long_name,route_desc,route_type,route_url,route_color,route_text_color  
AB,DTA,,Airport ⇒ Bullfrog,,3,,,  
BFC,DTA,,Bullfrog ⇒ Furnace Creek Resort,,3,,,  
STBA,DTA,,Stagecoach ⇒ Airport Shuttle,,3,,,  
CITY,DTA,Ō,Bar Circle,Route with ĸool unicode shortname,3,,,  
AAMV,DTA,,Airport ⇒ Amargosa Valley,,3,,,  
 
trip_id,arrival_time,departure_time,stop_id,stop_sequence,stop_headsign,pickup_type,drop_off_type,shape_dist_traveled  
STBA,6:00:00,6:00:00,STAGECOACH,0,to airport,1,0,0.212  
STBA,6:20:00,6:20:00,BEATTY_AIRPORT,2,,0,0,1.043  
CITY1,6:00:00,6:00:00,STAGECOACH,0,,,,  
CITY1,6:05:00,6:07:00,NANAA,5,going to nadav,2,3,  
CITY1,6:12:00,6:14:00,NADAV,10,,,,  
CITY1,6:19:00,6:21:00,DADAN,15,,,,  
CITY1,6:26:00,6:28:00,EMSI,20,,,,  
CITY2,6:28:00,6:30:00,EMSI,100,,,,  
CITY2,6:35:00,6:37:00,DADAN,200,,,,  
CITY2,6:42:00,6:44:00,NADAV,300,,,,  
CITY2,6:49:00,6:51:00,NANAA,400,,,,  
CITY2,6:56:00,6:58:00,STAGECOACH,500,,,,  
AB1,8:00:00,8:00:00,BEATTY_AIRPORT,1,,,,  
AB1,8:10:00,8:15:00,BULLFROG,2,,,,  
AB2,12:05:00,12:05:00,BULLFROG,1,,,,  
AB2,12:15:00,12:15:00,BEATTY_AIRPORT,2,,,,  
BFC1,8:20:00,8:20:00,BULLFROG,1,,,,  
BFC1,9:20:00,9:20:00,FUR_CREEK_RES,2,,,,  
BFC2,11:00:00,11:00:00,FUR_CREEK_RES,1,,,,  
BFC2,12:00:00,12:00:00,BULLFROG,2,,,,  
AAMV1,8:00:00,8:00:00,BEATTY_AIRPORT,1,,,,  
AAMV1,9:00:00,9:00:00,AMV,2,,,,  
AAMV2,10:00:00,10:00:00,AMV,1,,,,  
AAMV2,11:00:00,11:00:00,BEATTY_AIRPORT,2,,,,  
AAMV3,13:00:00,13:00:00,BEATTY_AIRPORT,1,,,,  
AAMV3,14:00:00,14:00:00,AMV,2,,,,  
AAMV4,15:00:00,15:00:00,AMV,1,,,,  
AAMV4,16:00:00,16:00:00,BEATTY_AIRPORT,2,,,,  
 
stop_id,stop_name,stop_desc,stop_lat,stop_lon,zone_id,stop_url,stop_code,location_type,parent_station  
FUR_CREEK_RES,Furnace Creek Resort (Demo),,36.425288,-117.133162,,,1234,,  
BEATTY_AIRPORT,Nye County Airport (Demo),,36.868446,-116.784582,,,1235,0,BEATTY_AIRPORT_STATION  
BEATTY_AIRPORT_STATION,Nye County Airport (Demo),,36.868446,-116.784582,,,1235,1,  
BULLFROG,Bullfrog (Demo),,36.88108,-116.81797,,,,,  
STAGECOACH,Stagecoach Hotel & Casino (Demo),,36.915682,-116.751677,,,1236,,  
NADAV,North Ave / D Ave N (Demo),,36.914893,-116.76821,,,1237,,  
NANAA,North Ave / N A Ave (Demo),,36.914944,-116.761472,,,1238,,  
DADAN,Doing Ave / D Ave N (Demo),,36.909489,-116.768242,,,,,  
EMSI,E Main St / S Irving St (Demo),,36.905697,-116.76218,,,,,  
AMV,Amargosa Valley (Demo),,36.641496,-116.40094,,,,,  
 
from_stop_id,to_stop_id,transfer_type,min_transfer_time  
NADAV,NANAA,3,  
EMSI,NANAA,2,1200  
 
route_id,service_id,trip_id,trip_headsign,direction_id,block_id,shape_id  
AB,FULLW,AB1,to Bullfrog,0,1,  
AB,FULLW,AB2,to Airport,1,2,  
STBA,FULLW,STBA,Shuttle,,,  
CITY,FULLW,CITY1,,0,,  
CITY,FULLW,CITY2,,1,,  
BFC,FULLW,BFC1,to Furnace Creek Resort,0,1,  
BFC,FULLW,BFC2,to Bullfrog,1,2,  
AAMV,WE,AAMV1,to Amargosa Valley,0,,  
AAMV,WE,AAMV2,to Airport,1,,  
AAMV,WE,AAMV3,to Amargosa Valley,0,,  
AAMV,WE,AAMV4,to Airport,1,,  
agency_id,agency_name,agency_url,agency_timezone  
DTA,Demo Transit Authority,http://google.com,America/Los_Angeles  
service_id,monday,tuesday,wednesday,thursday,friday,saturday,sunday,start_date,end_date  
FULLW,1,1,1,1,1,1,1,20070101,20101231  
WE,0,0,0,0,0,1,1,20070101,20101231  
service_id,date,exception_type  
FULLW,20070604,2  
route_id,agency_id,route_short_name,route_long_name,route_desc,route_type,route_url,route_color,route_text_color  
AB,DTA,,Airport - Bullfrog,,3,,,  
BFC,DTA,,Bullfrog - Furnace Creek Resort,,3,,,  
STBA,DVT,,Stagecoach - Airport Shuttle,,3,,,  
CITY,DTA,,City,,3,,,  
AAMV,DTA,,Airport - Amargosa Valley,,3,,,  
 
trip_id,arrival_time,departure_time,stop_id,stop_sequence,stop_headsign,pickup_type,drop_off_type,shape_dist_traveled  
STBA,6:00:00,6:00:00,STAGECOACH,1,,,,  
STBA,6:20:00,6:20:00,BEATTY_AIRPORT,2,,,,  
CITY1,6:00:00,6:00:00,STAGECOACH,1,,,,  
CITY1,6:05:00,6:07:00,NANAA,2,,,,  
CITY1,6:12:00,6:14:00,NADAV,3,,,,  
CITY1,6:19:00,6:21:00,DADAN,4,,,,  
CITY1,6:26:00,6:28:00,EMSI,5,,,,  
CITY2,6:28:00,6:30:00,EMSI,1,,,,  
CITY2,6:35:00,6:37:00,DADAN,2,,,,  
CITY2,6:42:00,6:44:00,NADAV,3,,,,  
CITY2,6:49:00,6:51:00,NANAA,4,,,,  
CITY2,6:56:00,6:58:00,STAGECOACH,5,,,,  
AB1,8:00:00,8:00:00,BEATTY_AIRPORT,1,,,,  
AB1,8:10:00,8:15:00,BULLFROG,2,,,,  
AB2,12:05:00,12:05:00,BULLFROG,1,,,,  
AB2,12:15:00,12:15:00,BEATTY_AIRPORT,2,,,,  
BFC1,8:20:00,8:20:00,BULLFROG,1,,,,  
BFC1,9:20:00,9:20:00,FUR_CREEK_RES,2,,,,  
BFC2,11:00:00,11:00:00,FUR_CREEK_RES,1,,,,  
BFC2,12:00:00,12:00:00,BULLFROG,2,,,,  
AAMV1,8:00:00,8:00:00,BEATTY_AIRPORT,1,,,,  
AAMV1,9:00:00,9:00:00,AMV,2,,,,  
AAMV2,10:00:00,10:00:00,AMV,1,,,,  
AAMV2,11:00:00,11:00:00,BEATTY_AIRPORT,2,,,,  
AAMV3,13:00:00,13:00:00,BEATTY_AIRPORT,1,,,,  
AAMV3,14:00:00,14:00:00,AMV,2,,,,  
AAMV4,15:00:00,15:00:00,AMV,1,,,,  
AAMV4,16:00:00,16:00:00,BEATTY_AIRPORT,2,,,,  
stop_id,stop_name,stop_desc,stop_lat,stop_lon,zone_id,stop_url  
FUR_CREEK_RES,Furnace Creek Resort (Demo),,36.425288,-117.133162,,  
BEATTY_AIRPORT,Nye County Airport (Demo),,36.868446,-116.784582,,  
BULLFROG,Bullfrog (Demo),,36.88108,-116.81797,,  
STAGECOACH,Stagecoach Hotel & Casino (Demo),,36.915682,-116.751677,,  
NADAV,North Ave / D Ave N (Demo),,36.914893,-116.76821,,  
NANAA,North Ave / N A Ave (Demo),,36.914944,-116.761472,,  
DADAN,Doing Ave / D Ave N (Demo),,36.909489,-116.768242,,  
EMSI,E Main St / S Irving St (Demo),,36.905697,-116.76218,,  
AMV,Amargosa Valley (Demo),,36.641496,-116.40094,,  
route_id,service_id,trip_id,trip_headsign,direction_id,block_id,shape_id  
AB,FULLW,AB1,to Bullfrog,0,1,  
AB,FULLW,AB2,to Airport,1,2,  
STBA,FULLW,STBA,Shuttle,,,  
CITY,FULLW,CITY1,,0,,  
CITY,FULLW,CITY2,,1,,  
BFC,FULLW,BFC1,to Furnace Creek Resort,0,1,  
BFC,FULLW,BFC2,to Bullfrog,1,2,  
AAMV,WE,AAMV1,to Amargosa Valley,0,,  
AAMV,WE,AAMV2,to Airport,1,,  
AAMV,WE,AAMV3,to Amargosa Valley,0,,  
AAMV,WE,AAMV4,to Airport,1,,  
service_id,monday,tuesday,wednesday,thursday,friday,saturday,sunday,start_date,end_date  
FULLW,1,1,1,1,1,1,1,20070101,20101231  
WE,0,0,0,0,0,1,1,20070101,20101231  
service_id,date,exception_type  
FULLW,20070604,2  
fare_id,price,currency_type,payment_method,transfers,transfer_duration  
p,1.25,USD,0,0,  
a,5.25,USD,0,0,  
 
fare_id,route_id,origin_id,destination_id,contains_id  
p,AB,,,  
p,STBA,,,  
p,BFC,,,  
a,AAMV,,,  
 
trip_id,start_time,end_time,headway_secs  
STBA,6:00:00,22:00:00,1800  
CITY1,6:00:00,7:59:59,1800  
CITY2,6:00:00,7:59:59,1800  
CITY1,8:00:00,9:59:59,600  
CITY2,8:00:00,9:59:59,600  
CITY1,10:00:00,15:59:59,1800  
CITY2,10:00:00,15:59:59,1800  
CITY1,16:00:00,18:59:59,600  
CITY2,16:00:00,18:59:59,600  
CITY1,19:00:00,22:00:00,1800  
CITY2,19:00:00,22:00:00,1800  
route_id,agency_id,route_short_name,route_long_name,route_desc,route_type  
AB,DTA,,Airport - Bullfrog,,3  
BFC,DTA,,Bullfrog - Furnace Creek Resort,,3  
STBA,DTA,,Stagecoach - Airport Shuttle,,3  
CITY,DTA,,City,,3  
AAMV,DTA,,Airport - Amargosa Valley,,3  
 
trip_id,arrival_time,departure_time,stop_id,stop_sequence  
STBA,6:00:00,6:00:00,STAGECOACH,1  
STBA,6:20:00,6:20:00,BEATTY_AIRPORT,2  
CITY1,6:00:00,6:00:00,STAGECOACH,1  
CITY1,6:05:00,6:07:00,NANAA,2  
CITY1,6:12:00,6:14:00,NADAV,3  
CITY1,6:19:00,6:21:00,DADAN,4  
CITY1,6:26:00,6:28:00,EMSI,5  
CITY2,6:28:00,6:30:00,EMSI,1  
CITY2,6:35:00,6:37:00,DADAN,2  
CITY2,6:42:00,6:44:00,NADAV,3  
CITY2,6:49:00,6:51:00,NANAA,4  
CITY2,6:56:00,6:58:00,STAGECOACH,5  
AB1,8:00:00,8:00:00,BEATTY_AIRPORT,1  
AB1,8:10:00,8:15:00,BULLFROG,2  
AB2,12:05:00,12:05:00,BULLFROG,1  
AB2,12:15:00,12:15:00,BEATTY_AIRPORT,2  
BFC1,8:20:00,8:20:00,BULLFROG,1  
BFC1,9:20:00,9:20:00,FUR_CREEK_RES,2  
BFC2,11:00:00,11:00:00,FUR_CREEK_RES,1  
BFC2,12:00:00,12:00:00,BULLFROG,2  
AAMV1,8:00:00,8:00:00,BEATTY_AIRPORT,1  
AAMV1,9:00:00,9:00:00,AMV,2  
AAMV2,10:00:00,10:00:00,AMV,1  
AAMV2,11:00:00,11:00:00,BEATTY_AIRPORT,2  
AAMV3,13:00:00,13:00:00,BEATTY_AIRPORT,1  
AAMV3,14:00:00,14:00:00,AMV,2  
AAMV4,15:00:00,15:00:00,AMV,1  
AAMV4,16:00:00,16:00:00,BEATTY_AIRPORT,2  
 
stop_id,stop_name,stop_desc,stop_lat,stop_lon  
FUR_CREEK_RES,Furnace Creek Resort (Demo),,36.425288,-117.133162  
BEATTY_AIRPORT,Nye County Airport (Demo),,36.868446,-116.784582  
BULLFROG,Bullfrog (Demo),,36.88108,-116.81797  
STAGECOACH,Stagecoach Hotel & Casino (Demo),,36.915682,-116.751677  
NADAV,North Ave / D Ave N (Demo),,36.914893,-116.76821  
NANAA,North Ave / N A Ave (Demo),,36.914944,-116.761472  
DADAN,Doing Ave / D Ave N (Demo),,36.909489,-116.768242  
EMSI,E Main St / S Irving St (Demo),,36.905697,-116.76218  
AMV,Amargosa Valley (Demo),,36.641496,-116.40094  
 
route_id,service_id,trip_id,trip_headsign,direction_id  
AB,FULLW,AB1,to Bullfrog,0  
AB,FULLW,AB2,to Airport,1  
STBA,FULLW,STBA,Shuttle  
CITY,FULLW,CITY1,,0  
CITY,FULLW,CITY2,,1  
BFC,FULLW,BFC1,to Furnace Creek Resort,0  
BFC,FULLW,BFC2,to Bullfrog,1  
AAMV,WE,AAMV1,to Amargosa Valley,0  
AAMV,WE,AAMV2,to Airport,1  
AAMV,WE,AAMV3,to Amargosa Valley,0  
AAMV,WE,AAMV4,to Airport,1  
 
agency_id,agency_name,agency_url,agency_timezone  
DTA,Demo Transit Authority,http://google.com,America/Los_Angeles  
fare_id,price,currency_type,payment_method,transfers,transfer_duration  
p,1.25,USD,0,0,  
a,5.25,USD,0,0,  
 
fare_id,route_id,origin_id,destination_id,contains_id  
p,AB,,,  
p,STBA,,,  
p,BFC,,,  
a,AAMV,,,  
 
trip_id,start_time,end_time,headway_secs  
STBA,6:00:00,22:00:00,1800  
CITY1,6:00:00,7:59:59,1800  
CITY2,6:00:00,7:59:59,1800  
CITY1,8:00:00,9:59:59,600  
CITY2,8:00:00,9:59:59,600  
CITY1,10:00:00,15:59:59,1800  
CITY2,10:00:00,15:59:59,1800  
CITY1,16:00:00,18:59:59,600  
CITY2,16:00:00,18:59:59,600  
CITY1,19:00:00,22:00:00,1800  
CITY2,19:00:00,22:00:00,1800  
route_id,agency_id,route_short_name,route_long_name,route_desc,route_type,route_url,route_color,route_text_color  
AB,DTA,,Airport - Bullfrog,,3,,,  
BFC,DTA,,Bullfrog - Furnace Creek Resort,,3,,,  
STBA,DTA,,Stagecoach - Airport Shuttle,,3,,,  
CITY,DTA,,City,,3,,,  
AAMV,DTA,,Airport - Amargosa Valley,,3,,,  
trip_id,arrival_time,departure_time,stop_id,stop_sequence,stop_headsign,pickup_type,drop_off_type,shape_dist_traveled  
STBA,6:00:00,6:00:00,STAGECOACH,1,,,,  
STBA,6:20:00,6:20:00,BEATTY_AIRPORT,2,,,,  
CITY1,6:00:00,6:00:00,STAGECOACH,1,,,,  
CITY1,6:05:00,6:07:00,NANAA,2,,,,  
CITY1,6:12:00,6:14:00,NADAV,3,,,,  
CITY1,6:19:00,6:21:00,DADAN,4,,,,  
CITY1,6:26:00,6:28:00,EMSI,5,,,,  
CITY2,6:28:00,6:30:00,EMSI,1,,,,  
CITY2,6:35:00,6:37:00,DADAN,2,,,,  
CITY2,6:42:00,6:44:00,NADAV,3,,,,  
CITY2,6:49:00,6:51:00,NANAA,4,,,,  
CITY2,6:56:00,6:58:00,STAGECOACH,5,,,,  
AB1,8:00:00,8:00:00,BEATTY_AIRPORT,1,,,,  
AB1,8:10:00,8:15:00,BULLFROG,2,,,,  
AB2,12:05:00,12:05:00,BULLFROG,1,,,,  
AB2,12:15:00,12:15:00,BEATTY_AIRPORT,2,,,,  
BFC1,8:20:00,8:20:00,BULLFROG,1,,,,  
BFC1,9:20:00,9:20:00,FUR_CREEK_RES,2,,,,  
BFC2,11:00:00,11:00:00,FUR_CREEK_RES,1,,,,  
BFC2,12:00:00,12:00:00,BULLFROG,2,,,,  
AAMV1,8:00:00,8:00:00,BEATTY_AIRPORT,1,,,,  
AAMV1,9:00:00,9:00:00,AMV,2,,,,  
AAMV2,10:00:00,10:00:00,AMV,1,,,,  
AAMV2,11:00:00,11:00:00,BEATTY_AIRPORT,2,,,,  
AAMV3,13:00:00,13:00:00,BEATTY_AIRPORT,1,,,,  
AAMV3,14:00:00,14:00:00,AMV,2,,,,  
AAMV4,15:00:00,15:00:00,AMV,1,,,,  
AAMV4,16:00:00,16:00:00,BEATTY_AIRPORT,2,,,,  
stop_id,stop_name,stop_desc,stop_lat,stop_lon,zone_id,stop_url  
FUR_CREEK_RES,Furnace Creek Resort (Demo),,36.425288,-117.133162,,  
BEATTY_AIRPORT,Nye County Airport (Demo),,36.868446,-116.784582,,  
BULLFROG,Bullfrog (Demo),,36.88108,-116.81797,,  
STAGECOACH,Stagecoach Hotel & Casino (Demo),,36.915682,-116.751677,,  
NADAV,North Ave / D Ave N (Demo),,36.914893,-116.76821,,  
NANAA,North Ave / N A Ave (Demo),,36.914944,-116.761472,,  
DADAN,Doing Ave / D Ave N (Demo),,36.909489,-116.768242,,  
EMSI,E Main St / S Irving St (Demo),,36.905697,-116.76218,,  
AMV,Amargosa Valley (Demo),,36.641496,-116.40094,,  
route_id,service_id,trip_id,trip_headsign,direction_id,block_id,shape_id  
AB,FULLW,AB1,to Bullfrog,0,1,  
AB,FULLW,AB2,to Airport,1,2,  
STBA,FULLW,STBA,Shuttle,,,  
CITY,FULLW,CITY1,,0,,  
CITY,FULLW,CITY2,,1,,  
BFC,FULLW,BFC1,to Furnace Creek Resort,0,1,  
BFC,FULLW,BFC2,to Bullfrog,1,2,  
AAMV,WE,AAMV1,to Amargosa Valley,0,,  
AAMV,WE,AAMV2,to Airport,1,,  
AAMV,WE,AAMV3,to Amargosa Valley,0,,  
AAMV,WE,AAMV4,to Airport,1,,  
agency_id,agency_url,agency_timezone  
DTA,http://google.com,America/Los_Angeles  
 
service_id,monday,tuesday,wednesday,thursday,friday,saturday,sunday,start_date,end_date  
FULLW,1,1,1,1,1,1,1,20070101,20101231  
WE,0,0,0,0,0,1,1,20070101,20101231  
service_id,date,exception_type  
FULLW,20070604,2  
fare_id,price,currency_type,payment_method,transfers,transfer_duration  
p,1.25,USD,0,0,  
a,5.25,USD,0,0,  
 
fare_id,route_id,origin_id,destination_id,contains_id  
p,AB,,,  
p,STBA,,,  
p,BFC,,,  
a,AAMV,,,  
 
trip_id,start_time,end_time,headway_secs  
STBA,6:00:00,22:00:00,1800  
CITY1,6:00:00,7:59:59,1800  
CITY2,6:00:00,7:59:59,1800  
CITY1,8:00:00,9:59:59,600  
CITY2,8:00:00,9:59:59,600  
CITY1,10:00:00,15:59:59,1800  
CITY2,10:00:00,15:59:59,1800  
CITY1,16:00:00,18:59:59,600  
CITY2,16:00:00,18:59:59,600  
CITY1,19:00:00,22:00:00,1800  
CITY2,19:00:00,22:00:00,1800  
route_id,agency_id,route_short_name,route_long_name,route_desc,route_type,route_url,route_color,route_text_color  
AB,DTA,,Airport - Bullfrog,,3  
BFC,DTA,,Bullfrog - Furnace Creek Resort,,3  
STBA,DTA,,Stagecoach - Airport Shuttle,,3  
CITY,DTA,,City,,3  
AAMV,DTA,,Airport - Amargosa Valley,,3  
trip_id,arrival_time,departure_time,stop_id,stop_sequence  
STBA,6:00:00,6:00:00,STAGECOACH,1  
STBA,6:20:00,6:20:00,BEATTY_AIRPORT,2  
CITY1,6:00:00,6:00:00,STAGECOACH,1  
CITY1,6:05:00,6:07:00,NANAA,2  
CITY1,6:12:00,6:14:00,NADAV,3  
CITY1,6:19:00,6:21:00,DADAN,4  
CITY1,6:26:00,6:28:00,EMSI,5  
CITY2,6:28:00,6:30:00,EMSI,1  
CITY2,6:35:00,6:37:00,DADAN,2  
CITY2,6:42:00,6:44:00,NADAV,3  
CITY2,6:49:00,6:51:00,NANAA,4  
CITY2,6:56:00,6:58:00,STAGECOACH,5  
AB1,8:00:00,8:00:00,BEATTY_AIRPORT,1  
AB1,8:10:00,8:15:00,BULLFROG,2  
AB2,12:05:00,12:05:00,BULLFROG,1  
AB2,12:15:00,12:15:00,BEATTY_AIRPORT,2  
BFC1,8:20:00,8:20:00,BULLFROG,1  
BFC1,9:20:00,9:20:00,FUR_CREEK_RES,2  
BFC2,11:00:00,11:00:00,FUR_CREEK_RES,1  
BFC2,12:00:00,12:00:00,BULLFROG,2  
AAMV1,8:00:00,8:00:00,BEATTY_AIRPORT,1  
AAMV1,9:00:00,9:00:00,AMV,2  
AAMV2,10:00:00,10:00:00,AMV,1  
AAMV2,11:00:00,11:00:00,BEATTY_AIRPORT,2  
AAMV3,13:00:00,13:00:00,BEATTY_AIRPORT,1  
AAMV3,14:00:00,14:00:00,AMV,2  
AAMV4,15:00:00,15:00:00,AMV,1  
AAMV4,16:00:00,16:00:00,BEATTY_AIRPORT,2  
 
stop_id,stop_name,stop_desc,stop_lat,stop_lon,zone_id,stop_url  
FUR_CREEK_RES,Furnace Creek Resort (Demo),,36.425288,-117.133162  
BEATTY_AIRPORT,Nye County Airport (Demo),,36.868446,-116.784582  
BULLFROG,Bullfrog (Demo),,36.88108,-116.81797  
STAGECOACH,Stagecoach Hotel & Casino (Demo),,36.915682,-116.751677  
NADAV,North Ave / D Ave N (Demo),,36.914893,-116.76821  
NANAA,North Ave / N A Ave (Demo),,36.914944,-116.761472  
DADAN,Doing Ave / D Ave N (Demo),,36.909489,-116.768242  
EMSI,E Main St / S Irving St (Demo),,36.905697,-116.76218  
AMV,Amargosa Valley (Demo),,36.641496,-116.40094  
route_id,service_id,trip_id,trip_headsign,direction_id,block_id,shape_id  
AB,FULLW,AB1,to Bullfrog,0,1  
AB,FULLW,AB2,to Airport,1,2  
STBA,FULLW,STBA,Shuttle  
CITY,FULLW,CITY1,,0  
CITY,FULLW,CITY2,,1  
BFC,FULLW,BFC1,to Furnace Creek Resort,0,1  
BFC,FULLW,BFC2,to Bullfrog,1,2  
AAMV,WE,AAMV1,to Amargosa Valley,0  
AAMV,WE,AAMV2,to Airport,1  
AAMV,WE,AAMV3,to Amargosa Valley,0  
AAMV,WE,AAMV4,to Airport,1  
agency_id,agency_name,agency_url,agency_timezone  
DTA,Autorité de passage de démonstration,http://google.com,America/Los_Angeles  
 
service_id,monday,tuesday,wednesday,thursday,friday,saturday,sunday,start_date,end_date  
FULLW,1,1,1,1,1,1,1,20070101,20101231  
 
route_id,agency_id,route_short_name,route_long_name,route_desc,route_type,route_url,route_color,route_text_color  
CITY,DTA,Ō,Bar Circle,Route with ĸool unicode shortname,3,,,  
 
trip_id,arrival_time,departure_time,stop_id,stop_sequence,stop_headsign,pickup_type,drop_off_type,shape_dist_traveled  
CITY1,6:00:00,6:00:00,STAGECOACH,0,,,,  
CITY1,6:12:00,,NADAV,10,,,,  
CITY1,6:19:00,6:21:00,DADAN,15,,,,  
CITY1,6:26:00,6:28:00,EMSI,20,,,,  
 
stop_id,stop_name,stop_desc,stop_lat,stop_lon,zone_id,stop_url,stop_code  
STAGECOACH,Stagecoach Hotel & Casino (Demo),,36.915682,-116.751677,,,1236  
NADAV,North Ave / D Ave N (Demo),,36.914893,-116.76821,,,1237  
NANAA,North Ave / N A Ave (Demo),,36.914944,-116.761472,,,1238  
DADAN,Doing Ave / D Ave N (Demo),,36.909489,-116.768242,,,  
EMSI,E Main St / S Irving St (Demo),,36.905697,-116.76218,,,  
 
route_id,service_id,trip_id,trip_headsign,direction_id,block_id,shape_id  
CITY,FULLW,CITY1,,0,,  
 
agency_id,agency_name,agency_url,agency_timezone  
DTA,Autorité de passage de démonstration,http://google.com,America/Los_Angeles  
 
service_id,monday,tuesday,wednesday,thursday,friday,saturday,sunday,start_date,end_date  
FULLW,1,1,1,1,1,1,1,20070101,20101231  
WE,0,0,0,0,0,1,1,20070101,20101231  
service_id,date,exception_type  
FULLW,20070604,2  
fare_id,price,currency_type,payment_method,transfers,transfer_duration  
p,1.25,USD,0,0,  
a,5.25,USD,0,0,  
 
fare_id,route_id,origin_id,destination_id,contains_id  
p,AB,,,  
p,STBA,,,  
p,BFC,,,  
a,AAMV,,,  
 
trip_id,start_time,end_time,headway_secs  
STBA,6:00:00,22:00:00,1800  
CITY1,6:00:00,7:59:59,1800  
CITY2,6:00:00,7:59:59,1800  
CITY1,8:00:00,9:59:59,600  
CITY2,8:00:00,9:59:59,600  
CITY1,10:00:00,15:59:59,1800  
CITY2,10:00:00,15:59:59,1800  
CITY1,16:00:00,18:59:59,600  
CITY2,16:00:00,18:59:59,600  
CITY1,19:00:00,22:00:00,1800  
CITY2,19:00:00,22:00:00,1800  
route_id,agency_id,route_short_name,route_long_name,route_desc,route_type,route_url,route_color,route_text_color  
AB,DTA,,Airport ⇒ Bullfrog,,3,,,  
BFC,DTA,,Bullfrog ⇒ Furnace Creek Resort,,3,,,  
STBA,DTA,,Stagecoach ⇒ Airport Shuttle,,3,,,  
CITY,DTA,Ō,Bar Circle,Route with ĸool unicode short name,3,,,  
AAMV,DTA,,Airport ⇒ Amargosa Valley,,3,,,  
 
trip_id,arrival_time,departure_time,stop_id,stop_sequence,stop_headsign,pickup_type,drop_off_type,shape_dist_traveled  
STBA,6:00:00,6:00:00,STAGECOACH,1,to airport,1,0,0.212  
STBA,6:20:00,6:20:00,BEATTY_AIRPORT,2,,0,0,1.043  
CITY1,6:00:00,6:00:00,STAGECOACH,1,,,,  
CITY1,6:05:00,6:07:00,NANAA,2,going to nadav,2,3,  
CITY1,6:12:00,6:14:00,NADAV,3,,,,  
CITY1,6:19:00,6:21:00,DADAN,4,,,,  
CITY1,6:26:00,6:28:00,EMSI,5,,,,  
CITY2,6:28:00,6:30:00,EMSI,1,,,,  
CITY2,6:35:00,6:37:00,DADAN,2,,,,  
CITY2,6:42:00,6:44:00,NADAV,3,,,,  
CITY2,6:49:00,6:51:00,NANAA,4,,,,  
CITY2,6:56:00,6:58:00,STAGECOACH,5,,,,  
AB1,8:00:00,8:00:00,BEATTY_AIRPORT,1,,,,  
AB1,8:10:00,8:15:00,BULLFROG,2,,,,  
AB2,,,BULLFROG,1,,,,  
AB2,12:15:00,12:15:00,BEATTY_AIRPORT,2,,,,  
BFC1,8:20:00,8:20:00,BULLFROG,1,,,,  
BFC1,9:20:00,9:20:00,FUR_CREEK_RES,2,,,,  
BFC2,11:00:00,11:00:00,FUR_CREEK_RES,1,,,,  
BFC2,,,BULLFROG,2,,,,  
AAMV1,8:00:00,8:00:00,BEATTY_AIRPORT,1,,,,  
AAMV1,9:00:00,9:00:00,AMV,2,,,,  
AAMV2,10:00:00,10:00:00,AMV,1,,,,  
AAMV2,11:00:00,11:00:00,BEATTY_AIRPORT,2,,,,  
AAMV3,13:00:00,13:00:00,BEATTY_AIRPORT,1,,,,  
AAMV3,14:00:00,14:00:00,AMV,2,,,,  
AAMV4,15:00:00,15:00:00,AMV,1,,,,  
AAMV4,16:00:00,16:00:00,BEATTY_AIRPORT,2,,,,  
 
stop_id,stop_name,stop_desc,stop_lat,stop_lon,zone_id,stop_url  
FUR_CREEK_RES,Furnace Creek Resort (Démonstration),,36.425288,-117.133162,,  
BEATTY_AIRPORT,Nye County Airport (Demo),,36.868446,-116.784582,,  
BULLFROG,Bullfrog (Demo),,36.88108,-116.81797,,  
STAGECOACH,Stagecoach Hotel & Casino (Demo),,36.915682,-116.751677,,  
NADAV,North Ave / D Ave N (Demo),,36.914893,-116.76821,,  
NANAA,North Ave / N A Ave (Demo),,36.914944,-116.761472,,  
DADAN,Doing Ave / D Ave N (Demo),,36.909489,-116.768242,,  
EMSI,E Main St / S Irving St (Demo),,36.905697,-116.76218,,  
AMV,Amargosa Valley (Demo),,36.641496,-116.40094,,  
 
route_id,service_id,trip_id,trip_headsign,direction_id,block_id,shape_id  
AB,FULLW,AB1,to Bullfrog,0,1,  
AB,FULLW,AB2,to Airport,1,2,  
STBA,FULLW,STBA,Shuttle,,,  
CITY,FULLW,CITY1,Ō,0,,  
CITY,FULLW,CITY2,Ō,1,,  
BFC,FULLW,BFC1,to Furnace Creek Resort,0,1,  
BFC,FULLW,BFC2,to Bullfrog,1,2,  
AAMV,WE,AAMV1,to Amargosa Valley,0,,  
AAMV,WE,AAMV2,to Airport,1,,  
AAMV,WE,AAMV3,to Amargosa Valley,0,,  
AAMV,WE,AAMV4,to Airport,1,,  
 
agency_id,agency_name,agency_url,agency_timezone  
DTA,Demo Transit Authority,http://google.com,America/Los_Angeles  
service_id,monday,tuesday,wednesday,thursday,friday,saturday,sunday,start_date,end_date  
FULLW,1,1,1,1,1,1,1,20070101,20101231  
WE,0,0,0,0,0,1,1,20070101,20101231  
service_id,date,exception_type  
FULLW,20070604,2  
fare_id,price,currency_type,payment_method,transfers,transfer_duration  
p,1.25,USD,0,0,  
a,5.25,USD,0,0,  
 
fare_id,route_id,origin_id,destination_id,contains_id  
p,AB,,,  
p,STBA,,,  
p,BFC,,,  
a,AAMV,,,  
 
trip_id,start_time,end_time,headway_secs  
STBA,6:00:00,22:00:00,1800  
CITY1,6:00:00,7:59:59,1800  
CITY2,6:00:00,7:59:59,1800  
CITY1,8:00:00,9:59:59,600  
CITY2,8:00:00,9:59:59,600  
CITY1,10:00:00,15:59:59,1800  
CITY2,10:00:00,15:59:59,1800  
CITY1,16:00:00,18:59:59,600  
CITY2,16:00:00,18:59:59,600  
CITY1,19:00:00,22:00:00,1800  
CITY2,19:00:00,22:00:00,1800  
trip_id,arrival_time,departure_time,stop_id,stop_sequence,stop_headsign,pickup_type,drop_off_type,shape_dist_traveled  
STBA,6:00:00,6:00:00,STAGECOACH,1,,,,  
STBA,6:20:00,6:20:00,BEATTY_AIRPORT,2,,,,  
CITY1,6:00:00,6:00:00,STAGECOACH,1,,,,  
CITY1,6:05:00,6:07:00,NANAA,2,,,,  
CITY1,6:12:00,6:14:00,NADAV,3,,,,  
CITY1,6:19:00,6:21:00,DADAN,4,,,,  
CITY1,6:26:00,6:28:00,EMSI,5,,,,  
CITY2,6:28:00,6:30:00,EMSI,1,,,,  
CITY2,6:35:00,6:37:00,DADAN,2,,,,  
CITY2,6:42:00,6:44:00,NADAV,3,,,,  
CITY2,6:49:00,6:51:00,NANAA,4,,,,  
CITY2,6:56:00,6:58:00,STAGECOACH,5,,,,  
AB1,8:00:00,8:00:00,BEATTY_AIRPORT,1,,,,  
AB1,8:10:00,8:15:00,BULLFROG,2,,,,  
AB2,12:05:00,12:05:00,BULLFROG,1,,,,  
AB2,12:15:00,12:15:00,BEATTY_AIRPORT,2,,,,  
BFC1,8:20:00,8:20:00,BULLFROG,1,,,,  
BFC1,9:20:00,9:20:00,FUR_CREEK_RES,2,,,,  
BFC2,11:00:00,11:00:00,FUR_CREEK_RES,1,,,,  
BFC2,12:00:00,12:00:00,BULLFROG,2,,,,  
AAMV1,8:00:00,8:00:00,BEATTY_AIRPORT,1,,,,  
AAMV1,9:00:00,9:00:00,AMV,2,,,,  
AAMV2,10:00:00,10:00:00,AMV,1,,,,  
AAMV2,11:00:00,11:00:00,BEATTY_AIRPORT,2,,,,  
AAMV3,13:00:00,13:00:00,BEATTY_AIRPORT,1,,,,  
AAMV3,14:00:00,14:00:00,AMV,2,,,,  
AAMV4,15:00:00,15:00:00,AMV,1,,,,  
AAMV4,16:00:00,16:00:00,BEATTY_AIRPORT,2,,,,  
stop_id,stop_name,stop_desc,stop_lat,stop_lon,zone_id,stop_url  
FUR_CREEK_RES,Furnace Creek Resort (Demo),,36.425288,-117.133162,,  
BEATTY_AIRPORT,Nye County Airport (Demo),,36.868446,-116.784582,,  
BULLFROG,Bullfrog (Demo),,36.88108,-116.81797,,  
STAGECOACH,Stagecoach Hotel & Casino (Demo),,36.915682,-116.751677,,  
NADAV,North Ave / D Ave N (Demo),,36.914893,-116.76821,,  
NANAA,North Ave / N A Ave (Demo),,36.914944,-116.761472,,  
DADAN,Doing Ave / D Ave N (Demo),,36.909489,-116.768242,,  
EMSI,E Main St / S Irving St (Demo),,36.905697,-116.76218,,  
AMV,Amargosa Valley (Demo),,36.641496,-116.40094,,  
route_id,service_id,trip_id,trip_headsign,direction_id,block_id,shape_id  
AB,FULLW,AB1,to Bullfrog,0,1,  
AB,FULLW,AB2,to Airport,1,2,  
STBA,FULLW,STBA,Shuttle,,,  
CITY,FULLW,CITY1,,0,,  
CITY,FULLW,CITY2,,1,,  
BFC,FULLW,BFC1,to Furnace Creek Resort,0,1,  
BFC,FULLW,BFC2,to Bullfrog,1,2,  
AAMV,WE,AAMV1,to Amargosa Valley,0,,  
AAMV,WE,AAMV2,to Airport,1,,  
AAMV,WE,AAMV3,to Amargosa Valley,0,,  
AAMV,WE,AAMV4,to Airport,1,,  
agency_id,agency_name,agency_url,agency_timezone  
DTA,Autorité de passage de démonstration,http://google.com,America/Los_Angeles  
 
service_id,monday,tuesday,wednesday,thursday,friday,saturday,sunday,start_date,end_date  
FULLW,1,1,1,1,1,1,1,20070101,20101231  
WE,0,0,0,0,0,1,1,20070101,20101231  
service_id,date,exception_type  
FULLW,20070604,2  
fare_id,price,currency_type,payment_method,transfers,transfer_duration  
p,1.25,USD,0,0,  
a,5.25,USD,0,0,  
 
fare_id,route_id,origin_id,destination_id,contains_id  
p,AB,,,  
p,STBA,,,  
p,BFC,,,  
a,AAMV,,,  
 
trip_id,start_time,end_time,headway_secs  
STBA,6:00:00,22:00:00,1800  
CITY1,6:00:00,7:59:59,1800  
CITY2,6:00:00,7:59:59,1800  
CITY1,8:00:00,9:59:59,600  
CITY2,8:00:00,9:59:59,600  
CITY1,10:00:00,15:59:59,1800  
CITY2,10:00:00,15:59:59,1800  
CITY1,16:00:00,18:59:59,600  
CITY2,16:00:00,18:59:59,600  
CITY1,19:00:00,22:00:00,1800  
CITY2,19:00:00,22:00:00,1800  
route_id,agency_id,route_short_name,route_long_name,route_desc,route_type,route_url  
AB,DTA,,Airport ⇒ Bullfrog,,3,  
BFC,DTA,,Bullfrog ⇒ Furnace Creek Resort,,3,http://google.com  
STBA,DTA,,Stagecoach ⇒ Airport Shuttle,,3  
CITY,DTA,Ō,Bar Circle,Route with ĸool unicode shortname,3,  
AAMV,DTA,,Airport ⇒ Amargosa Valley,,3,  
 
trip_id,arrival_time,departure_time,stop_id,stop_sequence,stop_headsign,pickup_type,drop_off_type,shape_dist_traveled  
STBA,6:00:00,6:00:00,STAGECOACH,0,to airport,1,0,0.212  
STBA,6:20:00,6:20:00,BEATTY_AIRPORT,2,,0,0,1.043  
CITY1,6:00:00,6:00:00,STAGECOACH,0,,,,  
CITY1,6:05:00,6:07:00,NANAA,5,going to nadav,2,3,  
CITY1,6:12:00,6:14:00,NADAV,10,,,,  
CITY1,6:19:00,6:21:00,DADAN,15,,,,  
CITY1,6:26:00,6:28:00,EMSI,20,,,,  
CITY2,6:28:00,6:30:00,EMSI,100,,,,  
CITY2,6:35:00,6:37:00,DADAN,200,,,,  
CITY2,6:42:00,6:44:00,NADAV,300,,,,  
CITY2,6:49:00,6:51:00,NANAA,400,,,,  
CITY2,6:56:00,6:58:00,STAGECOACH,500,,,,  
AB1,8:00:00,8:00:00,BEATTY_AIRPORT,1,,,,  
AB1,8:10:00,8:15:00,BULLFROG,2,,,,  
AB2,12:05:00,12:05:00,BULLFROG,1,,,,  
AB2,12:15:00,12:15:00,BEATTY_AIRPORT,2,,,,  
BFC1,8:20:00,8:20:00,BULLFROG,1,,,,  
BFC1,9:20:00,9:20:00,FUR_CREEK_RES,2,,,,  
BFC2,11:00:00,11:00:00,FUR_CREEK_RES,1,,,,  
BFC2,12:00:00,12:00:00,BULLFROG,2,,,,  
AAMV1,8:00:00,8:00:00,BEATTY_AIRPORT,1,,,,  
AAMV1,9:00:00,9:00:00,AMV,2,,,,  
AAMV2,10:00:00,10:00:00,AMV,1,,,,  
AAMV2,11:00:00,11:00:00,BEATTY_AIRPORT,2,,,,  
AAMV3,13:00:00,13:00:00,BEATTY_AIRPORT,1,,,,  
AAMV3,14:00:00,14:00:00,AMV,2,,,,  
AAMV4,15:00:00,15:00:00,AMV,1,,,,  
AAMV4,16:00:00,16:00:00,BEATTY_AIRPORT,2,,,,  
 
stop_id,stop_name,stop_desc,stop_lat,stop_lon  
FUR_CREEK_RES,Furnace Creek Resort (Démonstration),,36.425288,-117.13316  
BEATTY_AIRPORT,Nye County Airport (Demo),,36.868446,-116.784582  
BULLFROG,Bullfrog (Demo),,36.88108,-116.81797  
STAGECOACH,Stagecoach Hotel & Casino (Demo),,36.915682,-116.751677  
NADAV,North Ave / D Ave N (Demo),,36.914893,-116.76821  
NANAA,North Ave / N A Ave (Demo),,36.914944,-116.761472  
DADAN,Doing Ave / D Ave N (Demo),,36.909489,-116.768242  
EMSI,E Main St / S Irving St (Demo),,36.905697,-116.76218  
AMV,Amargosa Valley (Demo),,36.641496,-116.40094  
 
route_id,service_id,trip_id,trip_headsign,direction_id,block_id,shape_id  
AB,FULLW,AB1,to Bullfrog,0,1,  
AB,FULLW,AB2,to Airport,1,2,  
STBA,FULLW,STBA,Shuttle,,,  
CITY,FULLW,CITY1,Ō,,,  
CITY,FULLW,CITY2,Ō,,,  
BFC,FULLW,BFC1,to Furnace Creek Resort,0,1,  
BFC,FULLW,BFC2,to Bullfrog,1,2,  
AAMV,WE,AAMV1,to Amargosa Valley,0,,  
AAMV,WE,AAMV2,to Airport,1,,  
AAMV,WE,AAMV3,to Amargosa Valley,0,,  
AAMV,WE,AAMV4,to Airport,1,,  
 
agency_id,agency_name,agency_url,agency_timezone  
DTA,Demo Transit Authority,http://google.com,America/Los_Angeles  
service_id,monday,tuesday,wednesday,thursday,friday,saturday,sunday,start_date,end_date  
FULLW,1,1,1,1,1,1,1,20070101,20101231  
WE,0,0,0,0,0,1,1,20070101,20101231  
service_id,date,exception_type  
FULLW,20070604,2  
fare_id,price,currency_type,payment_method,transfers,transfer_duration  
p,1.25,USD,0,0,  
a,5.25,USD,0,0,  
 
fare_id,route_id,origin_id,destination_id,contains_id  
p,AB,,,  
p,STBA,,,  
p,BFC,,,  
a,AAMV,,,  
 
trip_id,start_time,end_time,headway_secs  
STBA,6:00:00,22:00:00,1800  
CITY1,6:00:00,7:59:59,1800  
CITY2,6:00:00,7:59:59,1800  
CITY1,8:00:00,9:59:59,600  
CITY2,8:00:00,9:59:59,600  
CITY1,10:00:00,15:59:59,1800  
CITY2,10:00:00,15:59:59,1800  
CITY1,16:00:00,18:59:59,600  
CITY2,16:00:00,18:59:59,600  
CITY1,19:00:00,22:00:00,1800  
CITY2,19:00:00,22:00:00,1800  
route_id,agency_id,route_short_name,route_long_name,route_desc,route_type,route_url,route_color,route_text_color  
AB,DTA,,Airport - Bullfrog,,3,,,  
BFC,DTA,,Bullfrog - Furnace Creek Resort,,3,,,  
STBA,DTA,,Stagecoach - Airport Shuttle,,3,,,  
CITY,DTA,,City,,3,,,  
AAMV,DTA,,Airport - Amargosa Valley,,3,,,  
stop_id,stop_name,stop_desc,stop_lat,stop_lon,zone_id,stop_url  
FUR_CREEK_RES,Furnace Creek Resort (Demo),,36.425288,-117.133162,,  
BEATTY_AIRPORT,Nye County Airport (Demo),,36.868446,-116.784582,,  
BULLFROG,Bullfrog (Demo),,36.88108,-116.81797,,  
STAGECOACH,Stagecoach Hotel & Casino (Demo),,36.915682,-116.751677,,  
NADAV,North Ave / D Ave N (Demo),,36.914893,-116.76821,,  
NANAA,North Ave / N A Ave (Demo),,36.914944,-116.761472,,  
DADAN,Doing Ave / D Ave N (Demo),,36.909489,-116.768242,,  
EMSI,E Main St / S Irving St (Demo),,36.905697,-116.76218,,  
AMV,Amargosa Valley (Demo),,36.641496,-116.40094,,  
route_id,service_id,trip_id,trip_headsign,direction_id,block_id,shape_id  
AB,FULLW,AB1,to Bullfrog,0,1,  
AB,FULLW,AB2,to Airport,1,2,  
STBA,FULLW,STBA,Shuttle,,,  
CITY,FULLW,CITY1,,0,,  
CITY,FULLW,CITY2,,1,,  
BFC,FULLW,BFC1,to Furnace Creek Resort,0,1,  
BFC,FULLW,BFC2,to Bullfrog,1,2,  
AAMV,WE,AAMV1,to Amargosa Valley,0,,  
AAMV,WE,AAMV2,to Airport,1,,  
AAMV,WE,AAMV3,to Amargosa Valley,0,,  
AAMV,WE,AAMV4,to Airport,1,,  
agency_id,agency_name,agency_url,agency_timezone  
DTA,Demo Transit Authority,http://google.com,America/Los_Angeles  
service_id,monday,tuesday,wednesday,thursday,friday,saturday,sunday,start_date,end_date  
FULLW,1,1,1,1,1,1,1,20070101,20101231  
WE,0,0,0,0,0,1,1,20070101,20101231  
service_id,date,exception_type  
FULLW,20070604,2  
fare_id,price,currency_type,payment_method,transfers,transfer_duration  
p,1.25,USD,0,0,  
a,5.25,USD,0,0,  
 
fare_id,route_id,origin_id,destination_id,contains_id  
p,AB,,,  
p,STBA,,,  
p,BFC,,,  
a,AAMV,,,  
 
trip_id,start_time,end_time,headway_secs  
STBA,6:00:00,22:00:00,1800  
CITY1,6:00:00,7:59:59,1800  
CITY2,6:00:00,7:59:59,1800  
CITY1,8:00:00,9:59:59,600  
CITY2,8:00:00,9:59:59,600  
CITY1,10:00:00,15:59:59,1800  
CITY2,10:00:00,15:59:59,1800  
CITY1,16:00:00,18:59:59,600  
CITY2,16:00:00,18:59:59,600  
CITY1,19:00:00,22:00:00,1800  
CITY2,19:00:00,22:00:00,1800  
route_id,agency_id,route_short_name,route_long_name,route_desc,route_type,route_url,route_color,route_text_color  
AB,DTA,,Airport - Bullfrog,,3  
BFC,DTA,,Bullfrog - Furnace Creek Resort,,3  
STBA,DTA,,Stagecoach - Airport Shuttle,,3  
CITY,DTA,,City,,3  
AAMV,DTA,,Airport - Amargosa Valley,,3  
trip_id,arrival_time,departure_time,stop_id,stop_sequence,stop_headsign,pickup_type,drop_off_type,shape_dist_traveled  
STBA,6:00:00,6:00:00,STAGECOACH,1,,,,  
STBA,6:20:00,6:20:00,BEATTY_AIRPORT,2,,,,  
CITY1,6:00:00,6:00:00,STAGECOACH,1,,,,  
CITY1,6:05:00,6:07:00,NANAA,2,,,,  
CITY1,6:12:00,6:14:00,NADAV,3,,,,  
CITY1,6:19:00,6:21:00,DADAN,4,,,,  
CITY1,6:26:00,6:28:00,EMSI,5,,,,  
CITY2,6:28:00,6:30:00,EMSI,1,,,,  
CITY2,6:35:00,6:37:00,DADAN,2,,,,  
CITY2,6:42:00,6:44:00,NADAV,3,,,,  
CITY2,6:49:00,6:51:00,NANAA,4,,,,  
CITY2,6:56:00,6:58:00,STAGECOACH,5,,,,  
AB1,8:00:00,8:00:00,BEATTY_AIRPORT,1,,,,  
AB1,8:10:00,8:15:00,BULLFROG,2,,,,  
AB2,12:05:00,12:05:00,BULLFROG,1,,,,  
AB2,12:15:00,12:15:00,BEATTY_AIRPORT,2,,,,  
BFC1,8:20:00,8:20:00,BULLFROG,1,,,,  
BFC1,9:20:00,9:20:00,FUR_CREEK_RES,2,,,,  
BFC2,11:00:00,11:00:00,FUR_CREEK_RES,1,,,,  
BFC2,12:00:00,12:00:00,BULLFROG,2,,,,  
AAMV1,8:00:00,8:00:00,BEATTY_AIRPORT,1,,,,  
AAMV1,9:00:00,9:00:00,AMV,2,,,,  
AAMV2,10:00:00,10:00:00,AMV,1,,,,  
AAMV2,11:00:00,11:00:00,BEATTY_AIRPORT,2,,,,  
AAMV3,13:00:00,13:00:00,BEATTY_AIRPORT,1,,,,  
AAMV3,14:00:00,14:00:00,AMV,2,,,,  
AAMV4,15:00:00,15:00:00,AMV,1,,,,  
AAMV4,16:00:00,16:00:00,BEATTY_AIRPORT,2,,,,  
route_id,service_id,trip_id,trip_headsign,direction_id,block_id,shape_id  
AB,FULLW,AB1,to Bullfrog,0,1,  
AB,FULLW,AB2,to Airport,1,2,  
STBA,FULLW,STBA,Shuttle,,,  
CITY,FULLW,CITY1,,0,,  
CITY,FULLW,CITY2,,1,,  
BFC,FULLW,BFC1,to Furnace Creek Resort,0,1,  
BFC,FULLW,BFC2,to Bullfrog,1,2,  
AAMV,WE,AAMV1,to Amargosa Valley,0,,  
AAMV,WE,AAMV2,to Airport,1,,  
AAMV,WE,AAMV3,to Amargosa Valley,0,,  
AAMV,WE,AAMV4,to Airport,1,,  
agency_id,agency_name,agency_url,agency_timezone  
DTA,Demo Transit Authority,http://google.com,America/Los_Angeles  
service_id,monday,tuesday,wednesday,thursday,friday,saturday,sunday,start_date,end_date  
FULLW,1,1,1,1,1,1,1,20070101,20101231  
WE,0,0,0,0,0,1,1,20070101,20101231  
service_id,date,exception_type  
FULLW,20070604,2  
route_id,agency_id,route_short_name,route_long_name,route_desc,route_type,route_url,route_color,route_text_color  
AB,DTA,,Airport - Bullfrog,,3,,,  
BFC,DTA,,Bullfrog - Furnace Creek Resort,,3,,,  
STBA,DTA,,Stagecoach - Airport Shuttle,,3,,,  
CITY,DTA,,City,,3,,,  
AAMV,DTA,,Airport - Amargosa Valley,,3,,,  
trip_id,arrival_time,departure_time,stop_id,stop_sequence,stop_headsign,pickup_type,drop_off_type,shape_dist_traveled  
STBA,6:00:00,6:00:00,STAGECOACH,1,,,,  
STBA,6:20:00,6:20:00,BEATTY_AIRPORT,2,,,,  
CITY1,6:00:00,6:00:00,STAGECOACH,1,,,,  
CITY1,6:05:00,6:07:00,NANAA,2,,,,  
CITY1,6:12:00,6:14:00,NADAV,3,,,,  
CITY1,6:19:00,6:21:00,DADAN,4,,,,  
CITY1,6:26:00,6:28:00,EMSI,5,,,,  
CITY2,6:28:00,6:30:00,EMSI,1,,,,  
CITY2,6:35:00,6:37:00,DADAN,2,,,,  
CITY2,6:42:00,6:44:00,NADAV,3,,,,  
CITY2,6:49:00,6:51:00,NANAA,4,,,,  
CITY2,6:56:00,6:58:00,STAGECOACH,5,,,,  
AB1,8:00:00,8:00:00,BEATTY_AIRPORT,1,,,,  
AB1,8:10:00,8:15:00,BULLFROG,2,,,,  
AB2,12:05:00,12:05:00,BULLFROG,1,,,,  
AB2,12:15:00,12:15:00,BEATTY_AIRPORT,2,,,,  
BFC1,8:20:00,8:20:00,BULLFROG,1,,,,  
BFC1,9:20:00,9:20:00,FUR_CREEK_RES,2,,,,  
BFC2,11:00:00,11:00:00,FUR_CREEK_RES,1,,,,  
BFC2,12:00:00,12:00:00,BULLFROG,2,,,,  
AAMV1,8:00:00,8:00:00,BEATTY_AIRPORT,1,,,,  
AAMV1,9:00:00,9:00:00,AMV,2,,,,  
AAMV2,10:00:00,10:00:00,AMV,1,,,,  
AAMV2,11:00:00,11:00:00,BEATTY_AIRPORT,2,,,,  
AAMV3,13:00:00,13:00:00,BEATTY_AIRPORT,1,,,,  
AAMV3,14:00:00,14:00:00,AMV,2,,,,  
AAMV4,15:00:00,15:00:00,AMV,1,,,,  
AAMV4,16:00:00,16:00:00,BEATTY_AIRPORT,2,,,,  
stop_id,stop_name,stop_desc,stop_lat,stop_lon,zone_id,stop_url  
FUR_CREEK_RES,Furnace Creek Resort (Demo),,36.425288,-117.133162,,  
BEATTY_AIRPORT,Nye County Airport (Demo),,36.868446,-116.784582,,  
BULLFROG,Bullfrog (Demo),,36.88108,-116.81797,,  
STAGECOACH,Stagecoach Hotel & Casino (Demo),,36.915682,-116.751677,,  
NADAV,North Ave / D Ave N (Demo),,36.914893,-116.76821,,  
NANAA,North Ave / N A Ave (Demo),,36.914944,-116.761472,,  
DADAN,Doing Ave / D Ave N (Demo),,36.909489,-116.768242,,  
EMSI,E Main St / S Irving St (Demo),,36.905697,-116.76218,,  
AMV,Amargosa Valley (Demo),,36.641496,-116.40094,,  
agency_id,agency_name,agency_url,agency_timezone  
DTA,Demo Transit Authority,http://google.com,America/Los_Angeles  
"service_id","monday","tuesday","wednesday","friday","saturday","sunday","start_date","end_date"  
"FULLW",1,1,1,1,1,1,20070101,20101231  
"WE",0,0,0,0,1,1,20070101,20101231  
 
service_id,date,exception_type  
FULLW,20070604,2  
fare_id,price,currency_type,payment_method,transfers,transfer_duration  
p,1.25,USD,0,0,  
a,5.25,USD,0,0,  
 
fare_id,route_id,origin_id,destination_id,contains_id  
p,AB,,,  
p,STBA,,,  
p,BFC,,,  
a,AAMV,,,  
 
trip_id,start_time,end_time,headway_secs  
STBA,6:00:00,22:00:00,1800  
CITY1,6:00:00,7:59:59,1800  
CITY2,6:00:00,7:59:59,1800  
CITY1,8:00:00,9:59:59,600  
CITY2,8:00:00,9:59:59,600  
CITY1,10:00:00,15:59:59,1800  
CITY2,10:00:00,15:59:59,1800  
CITY1,16:00:00,18:59:59,600  
CITY2,16:00:00,18:59:59,600  
CITY1,19:00:00,22:00:00,1800  
CITY2,19:00:00,22:00:00,1800  
route_id,agency_id,route_short_name,route_long_name,route_desc,route_type,route_url,route_color,route_text_color  
AB,DTA,,Airport - Bullfrog,,3  
BFC,DTA,,Bullfrog - Furnace Creek Resort,,3  
STBA,DTA,,Stagecoach - Airport Shuttle,,3  
CITY,DTA,,City,,3  
AAMV,DTA,,Airport - Amargosa Valley,,3  
trip_id,arrival_time,departure_time,stop_id,stop_sequence  
STBA,6:00:00,6:00:00,STAGECOACH,1  
STBA,6:20:00,6:20:00,BEATTY_AIRPORT,2  
CITY1,6:00:00,6:00:00,STAGECOACH,1  
CITY1,6:05:00,6:07:00,NANAA,2  
CITY1,6:12:00,6:14:00,NADAV,3  
CITY1,6:19:00,6:21:00,DADAN,4  
CITY1,6:26:00,6:28:00,EMSI,5  
CITY2,6:28:00,6:30:00,EMSI,1  
CITY2,6:35:00,6:37:00,DADAN,2  
CITY2,6:42:00,6:44:00,NADAV,3  
CITY2,6:49:00,6:51:00,NANAA,4  
CITY2,6:56:00,6:58:00,STAGECOACH,5  
AB1,8:00:00,8:00:00,BEATTY_AIRPORT,1  
AB1,8:10:00,8:15:00,BULLFROG,2  
AB2,12:05:00,12:05:00,BULLFROG,1  
AB2,12:15:00,12:15:00,BEATTY_AIRPORT,2  
BFC1,8:20:00,8:20:00,BULLFROG,1  
BFC1,9:20:00,9:20:00,FUR_CREEK_RES,2  
BFC2,11:00:00,11:00:00,FUR_CREEK_RES,1  
BFC2,12:00:00,12:00:00,BULLFROG,2  
AAMV1,8:00:00,8:00:00,BEATTY_AIRPORT,1  
AAMV1,9:00:00,9:00:00,AMV,2  
AAMV2,10:00:00,10:00:00,AMV,1  
AAMV2,11:00:00,11:00:00,BEATTY_AIRPORT,2  
AAMV3,13:00:00,13:00:00,BEATTY_AIRPORT,1  
AAMV3,14:00:00,14:00:00,AMV,2  
AAMV4,15:00:00,15:00:00,AMV,1  
AAMV4,16:00:00,16:00:00,BEATTY_AIRPORT,2  
 
stop_id,stop_name,stop_desc,stop_lat,stop_lon,zone_id,stop_url  
FUR_CREEK_RES,Furnace Creek Resort (Demo),,36.425288,-117.133162  
BEATTY_AIRPORT,Nye County Airport (Demo),,36.868446,-116.784582  
BULLFROG,Bullfrog (Demo),,36.88108,-116.81797  
STAGECOACH,Stagecoach Hotel & Casino (Demo),,36.915682,-116.751677  
NADAV,North Ave / D Ave N (Demo),,36.914893,-116.76821  
NANAA,North Ave / N A Ave (Demo),,36.914944,-116.761472  
DADAN,Doing Ave / D Ave N (Demo),,36.909489,-116.768242  
EMSI,E Main St / S Irving St (Demo),,36.905697,-116.76218  
AMV,Amargosa Valley (Demo),,36.641496,-116.40094  
route_id,service_id,trip_id,trip_headsign,direction_id,block_id,shape_id  
AB,FULLW,AB1,to Bullfrog,0,1  
AB,FULLW,AB2,to Airport,1,2  
STBA,FULLW,STBA,Shuttle  
CITY,FULLW,CITY1,,0  
CITY,FULLW,CITY2,,1  
BFC,FULLW,BFC1,to Furnace Creek Resort,0,1  
BFC,FULLW,BFC2,to Bullfrog,1,2  
AAMV,WE,AAMV1,to Amargosa Valley,0  
AAMV,WE,AAMV2,to Airport,1  
AAMV,WE,AAMV3,to Amargosa Valley,0  
AAMV,WE,AAMV4,to Airport,1  
agency_id,agency_name,agency_url,agency_timezone  
DTA,Demo Transit Authority,http://google.com,America/Los_Angeles  
service_id,monday,tuesday,wednesday,thursday,friday,saturday,sunday,start_date,end_date  
FULLW,1,1,1,1,1,1,1,20070101,20101231  
WE,0,0,0,0,0,1,1,20070101,20101231  
service_id,date,exception_type  
FULLW,20070604,2  
fare_id,price,currency_type,payment_method,transfers,transfer_duration  
p,1.25,USD,0,0,  
a,5.25,USD,0,0,  
 
fare_id,route_id,origin_id,destination_id,contains_id  
p,AB,,,  
p,STBA,,,  
p,BFC,,,  
a,AAMV,,,  
 
trip_id,start_time,end_time,headway_secs  
STBA,6:00:00,22:00:00,1800  
CITY1,6:00:00,7:59:59,1800  
CITY2,6:00:00,7:59:59,1800  
CITY1,8:00:00,9:59:59,600  
CITY2,8:00:00,9:59:59,600  
CITY1,10:00:00,15:59:59,1800  
CITY2,10:00:00,15:59:59,1800  
CITY1,16:00:00,18:59:59,600  
CITY2,16:00:00,18:59:59,600  
CITY1,19:00:00,22:00:00,1800  
CITY2,19:00:00,22:00:00,1800  
route_id,agency_id,route_short_name,route_long_name,route_desc,route_type,route_url,route_color,route_text_color  
AB,DTA,,Airport - Bullfrog,,3,,,  
BFC,DTA,,Bullfrog - Furnace Creek Resort,,3,,,  
STBA,DTA,,Stagecoach - Airport Shuttle,,3,,,  
CITY,DTA,,City,,3,,,  
AAMV,DTA,,Airport - Amargosa Valley,,3,,,  
trip_id,arrival_time,departure_time,stop_id,stop_sequence,stop_headsign,pickup_type,drop_off_type,shape_dist_traveled  
STBA,6:00:00,6:00:00,STAGECOACH,0,,,,  
STBA,6:20:00,6:20:00,BEATTY_AIRPORT,1,,,,  
CITY1,6:00:00,6:00:00,STAGECOACH,0,,,,  
CITY1,6:05:00,6:07:00,NANAA,1,,,,  
CITY1,6:12:00,6:14:00,NADAV,2,,,,  
CITY1,6:19:00,6:21:00,DADAN,3,,,,  
CITY1,6:26:00,6:28:00,EMSI,4,,,,  
CITY2,6:28:00,6:30:00,EMSI,-2,,,,  
CITY2,6:35:00,6:37:00,DADAN,1,,,,  
CITY2,6:42:00,6:44:00,NADAV,2,,,,  
CITY2,6:49:00,6:51:00,NANAA,3,,,,  
CITY2,6:56:00,6:58:00,STAGECOACH,4,,,,  
AB1,8:00:00,8:00:00,BEATTY_AIRPORT,0,,,,  
AB1,8:10:00,8:15:00,BULLFROG,1,,,,  
AB2,12:05:00,12:05:00,BULLFROG,0,,,,  
AB2,12:15:00,12:15:00,BEATTY_AIRPORT,1,,,,  
BFC1,8:20:00,8:20:00,BULLFROG,0,,,,  
BFC1,9:20:00,9:20:00,FUR_CREEK_RES,1,,,,  
BFC2,11:00:00,11:00:00,FUR_CREEK_RES,0,,,,  
BFC2,12:00:00,12:00:00,BULLFROG,1,,,,  
AAMV1,8:00:00,8:00:00,BEATTY_AIRPORT,0,,,,  
AAMV1,9:00:00,9:00:00,AMV,1,,,,  
AAMV2,10:00:00,10:00:00,AMV,0,,,,  
AAMV2,11:00:00,11:00:00,BEATTY_AIRPORT,1,,,,  
AAMV3,13:00:00,13:00:00,BEATTY_AIRPORT,0,,,,  
AAMV3,14:00:00,14:00:00,AMV,1,,,,  
AAMV4,15:00:00,15:00:00,AMV,0,,,,  
AAMV4,16:00:00,16:00:00,BEATTY_AIRPORT,1,,,,  
stop_id,stop_name,stop_desc,stop_lat,stop_lon,zone_id,stop_url  
FUR_CREEK_RES,Furnace Creek Resort (Demo),,36.425288,-117.133162,,  
BEATTY_AIRPORT,Nye County Airport (Demo),,36.868446,-116.784582,,  
BULLFROG,Bullfrog (Demo),,36.88108,-116.81797,,  
STAGECOACH,Stagecoach Hotel & Casino (Demo),,36.915682,-116.751677,,  
NADAV,North Ave / D Ave N (Demo),,36.914893,-116.76821,,  
NANAA,North Ave / N A Ave (Demo),,36.914944,-116.761472,,  
DADAN,Doing Ave / D Ave N (Demo),,36.909489,-116.768242,,  
EMSI,E Main St / S Irving St (Demo),,36.905697,-116.76218,,  
AMV,Amargosa Valley (Demo),,36.641496,-116.40094,,  
route_id,service_id,trip_id,trip_headsign,direction_id,block_id,shape_id  
AB,FULLW,AB1,to Bullfrog,0,1,  
AB,FULLW,AB2,to Airport,1,2,  
STBA,FULLW,STBA,Shuttle,,,  
CITY,FULLW,CITY1,,0,,  
CITY,FULLW,CITY2,,1,,  
BFC,FULLW,BFC1,to Furnace Creek Resort,0,1,  
BFC,FULLW,BFC2,to Bullfrog,1,2,  
AAMV,WE,AAMV1,to Amargosa Valley,0,,  
AAMV,WE,AAMV2,to Airport,1,,  
AAMV,WE,AAMV3,to Amargosa Valley,0,,  
AAMV,WE,AAMV4,to Airport,1,,  
<?xml version="1.0" encoding="UTF-8"?>  
<kml xmlns="http://earth.google.com/kml/2.0">  
<Document>  
<name>A test file with one placemark</name>  
<decription></decription>  
<Placemark>  
<name>Test</name>  
<description></description>  
<LineString>  
<coordinates>  
-93.238861,44.854240,0.000000  
-93.238708,44.853081,0.000000  
-93.237923,44.852638,0.000000  
</coordinates>  
</LineString>  
</Placemark>  
</Document>  
</kml>  
 
<?xml version="1.0" encoding="UTF-8"?>  
<kml xmlns="http://earth.google.com/kml/2.0">  
<Document>  
<name>A test file with one placemark</name>  
<decription></decription>  
<Placemark>  
<name>Stop Name</name>  
<description></description>  
<Point>  
<coordinates>-93.239037,44.854164,0.000000</coordinates>  
</Point>  
</Placemark>  
</Document>  
</kml>  
 
agency_id,agency_name,agency_url,agency_timezone  
DTA,Demo Transit Authority,http://google.com,America/Los_Angeles  
service_id,date,exception_type  
FULLW,20070604,1  
WE,20070605,1  
 
fare_id,price,currency_type,payment_method,transfers,transfer_duration  
p,1.25,USD,0,0,  
a,5.25,USD,0,0,  
 
fare_id,route_id,origin_id,destination_id,contains_id  
p,AB,,,  
p,STBA,,,  
p,BFC,,,  
a,AAMV,,,  
 
trip_id,start_time,end_time,headway_secs  
STBA,6:00:00,22:00:00,1800  
CITY1,6:00:00,7:59:59,1800  
CITY2,6:00:00,7:59:59,1800  
CITY1,8:00:00,9:59:59,600  
CITY2,8:00:00,9:59:59,600  
CITY1,10:00:00,15:59:59,1800  
CITY2,10:00:00,15:59:59,1800  
CITY1,16:00:00,18:59:59,600  
CITY2,16:00:00,18:59:59,600  
CITY1,19:00:00,22:00:00,1800  
CITY2,19:00:00,22:00:00,1800  
route_id,agency_id,route_short_name,route_long_name,route_desc,route_type,route_url,route_color,route_text_color  
AB,DTA,,Airport - Bullfrog,,3,,,  
BFC,DTA,,Bullfrog - Furnace Creek Resort,,3,,,  
STBA,DTA,,Stagecoach - Airport Shuttle,,3,,,  
CITY,DTA,,City,,3,,,  
AAMV,DTA,,Airport - Amargosa Valley,,3,,,  
trip_id,arrival_time,departure_time,stop_id,stop_sequence,stop_headsign,pickup_type,drop_off_type,shape_dist_traveled  
STBA,6:00:00,6:00:00,STAGECOACH,1,,,,  
STBA,6:20:00,6:20:00,BEATTY_AIRPORT,2,,,,  
CITY1,6:00:00,6:00:00,STAGECOACH,1,,,,  
CITY1,6:05:00,6:07:00,NANAA,2,,,,  
CITY1,6:12:00,6:14:00,NADAV,3,,,,  
CITY1,6:19:00,6:21:00,DADAN,4,,,,  
CITY1,6:26:00,6:28:00,EMSI,5,,,,  
CITY2,6:28:00,6:30:00,EMSI,1,,,,  
CITY2,6:35:00,6:37:00,DADAN,2,,,,  
CITY2,6:42:00,6:44:00,NADAV,3,,,,  
CITY2,6:49:00,6:51:00,NANAA,4,,,,  
CITY2,6:56:00,6:58:00,STAGECOACH,5,,,,  
AB1,8:00:00,8:00:00,BEATTY_AIRPORT,1,,,,  
AB1,8:10:00,8:15:00,BULLFROG,2,,,,  
AB2,12:05:00,12:05:00,BULLFROG,1,,,,  
AB2,12:15:00,12:15:00,BEATTY_AIRPORT,2,,,,  
BFC1,8:20:00,8:20:00,BULLFROG,1,,,,  
BFC1,9:20:00,9:20:00,FUR_CREEK_RES,2,,,,  
BFC2,11:00:00,11:00:00,FUR_CREEK_RES,1,,,,  
BFC2,12:00:00,12:00:00,BULLFROG,2,,,,  
AAMV1,8:00:00,8:00:00,BEATTY_AIRPORT,1,,,,  
AAMV1,9:00:00,9:00:00,AMV,2,,,,  
AAMV2,10:00:00,10:00:00,AMV,1,,,,  
AAMV2,11:00:00,11:00:00,BEATTY_AIRPORT,2,,,,  
AAMV3,13:00:00,13:00:00,BEATTY_AIRPORT,1,,,,  
AAMV3,14:00:00,14:00:00,AMV,2,,,,  
AAMV4,15:00:00,15:00:00,AMV,1,,,,  
AAMV4,16:00:00,16:00:00,BEATTY_AIRPORT,2,,,,  
stop_id,stop_name,stop_desc,stop_lat,stop_lon,zone_id,stop_url  
FUR_CREEK_RES,Furnace Creek Resort (Demo),,36.425288,-117.133162,,  
BEATTY_AIRPORT,Nye County Airport (Demo),,36.868446,-116.784582,,  
BULLFROG,Bullfrog (Demo),,36.88108,-116.81797,,  
STAGECOACH,Stagecoach Hotel & Casino (Demo),,36.915682,-116.751677,,  
NADAV,North Ave / D Ave N (Demo),,36.914893,-116.76821,,  
NANAA,North Ave / N A Ave (Demo),,36.914944,-116.761472,,  
DADAN,Doing Ave / D Ave N (Demo),,36.909489,-116.768242,,  
EMSI,E Main St / S Irving St (Demo),,36.905697,-116.76218,,  
AMV,Amargosa Valley (Demo),,36.641496,-116.40094,,  
route_id,service_id,trip_id,trip_headsign,direction_id,block_id,shape_id  
AB,FULLW,AB1,to Bullfrog,0,1,  
AB,FULLW,AB2,to Airport,1,2,  
STBA,FULLW,STBA,Shuttle,,,  
CITY,FULLW,CITY1,,0,,  
CITY,FULLW,CITY2,,1,,  
BFC,FULLW,BFC1,to Furnace Creek Resort,0,1,  
BFC,FULLW,BFC2,to Bullfrog,1,2,  
AAMV,WE,AAMV1,to Amargosa Valley,0,,  
AAMV,WE,AAMV2,to Airport,1,,  
AAMV,WE,AAMV3,to Amargosa Valley,0,,  
AAMV,WE,AAMV4,to Airport,1,,  
agency_id,agency_name,agency_url,agency_timezone  
DTA,Demo Transit Authority,http://google.com,America/Los_Angeles  
service_id,monday,tuesday,wednesday,thursday,friday,saturday,sunday,start_date,end_date  
FULLW,1,1,1,1,1,1,1,20070101,20101231  
WE,0,0,0,0,0,1,1,20070101,20101231  
service_id,date,exception_type  
FULLW,20070604,2  
fare_id,price,currency_type,payment_method,transfers,transfer_duration  
p,1.25,USD,0,0,  
a,5.25,USD,0,0,  
 
fare_id,route_id,origin_id,destination_id,contains_id  
p,AB,,,  
p,STBA,,,  
p,BFC,,,  
a,AAMV,,,  
 
trip_id,start_time,end_time,headway_secs  
STBA,6:00:00,22:00:00,1800  
CITY1,6:00:00,7:59:59,1800  
CITY2,6:00:00,7:59:59,1800  
CITY1,8:00:00,9:59:59,600  
CITY2,8:00:00,9:59:59,600  
CITY1,10:00:00,15:59:59,1800  
CITY2,10:00:00,15:59:59,1800  
CITY1,16:00:00,18:59:59,600  
CITY2,16:00:00,18:59:59,600  
CITY1,19:00:00,22:00:00,1800  
CITY2,19:00:00,22:00:00,1800  
route_id,agency_id,route_short_name,route_long_name,route_desc,route_type,route_url,route_color,route_text_color  
AB,DTA,,Airport ⇒ Bullfrog,,3,,,  
BFC,DTA,,Bullfrog ⇒ Furnace Creek Resort,,3,,,  
STBA,DTA,,Stagecoach ⇒ Airport Shuttle,,3,,,  
STBB,DTA,,Stagecoach ⇒ Airport Shuttle,,3,,,  
CITY,DTA,,City,,3,,,  
AAMV,DTA,,Airport ⇒ Amargosa Valley,,3,,,  
 
trip_id,arrival_time,departure_time,stop_id,stop_sequence,stop_headsign,pickup_type,drop_off_type,shape_dist_traveled  
STBA,6:00:00,6:00:00,STAGECOACH,1,,,,  
STBA,6:20:00,6:20:00,BEATTY_AIRPORT,2,,,,  
CITY1,6:00:00,6:00:00,STAGECOACH,1,,,,  
CITY1,6:05:00,6:07:00,NANAA,2,,,,  
CITY1,6:12:00,6:14:00,NADAV,3,,,,  
CITY1,6:19:00,6:21:00,DADAN,4,,,,  
CITY1,6:26:00,6:28:00,EMSI,5,,,,  
CITY2,6:28:00,6:30:00,EMSI,1,,,,  
CITY2,6:35:00,6:37:00,DADAN,2,,,,  
CITY2,6:42:00,6:44:00,NADAV,3,,,,  
CITY2,6:49:00,6:51:00,NANAA,4,,,,  
CITY2,6:56:00,6:58:00,STAGECOACH,5,,,,  
AB1,8:00:00,8:00:00,BEATTY_AIRPORT,1,,,,  
AB1,8:10:00,8:15:00,BULLFROG,2,,,,  
AB2,12:05:00,12:05:00,BULLFROG,1,,,,  
AB2,12:15:00,12:15:00,BEATTY_AIRPORT,2,,,,  
BFC1,8:20:00,8:20:00,BULLFROG,1,,,,  
BFC1,9:20:00,9:20:00,FUR_CREEK_RES,2,,,,  
BFC2,11:00:00,11:00:00,FUR_CREEK_RES,1,,,,  
BFC2,12:00:00,12:00:00,BULLFROG,2,,,,  
AAMV1,8:00:00,8:00:00,BEATTY_AIRPORT,1,,,,  
AAMV1,9:00:00,9:00:00,AMV,2,,,,  
AAMV2,10:00:00,10:00:00,AMV,1,,,,  
AAMV2,11:00:00,11:00:00,BEATTY_AIRPORT,2,,,,  
AAMV3,13:00:00,13:00:00,BEATTY_AIRPORT,1,,,,  
AAMV3,14:00:00,14:00:00,AMV,2,,,,  
AAMV4,15:00:00,15:00:00,AMV,1,,,,  
AAMV4,16:00:00,16:00:00,BEATTY_AIRPORT,2,,,,  
stop_id,stop_name,stop_desc,stop_lat,stop_lon,zone_id,stop_url  
FUR_CREEK_RES,Furnace Creek Resort (Demo),,36.425288,-117.133162,,  
BEATTY_AIRPORT,Nye County Airport (Demo),,36.868446,-116.784582,,  
BULLFROG,Bullfrog (Demo),,36.88108,-116.81797,,  
STAGECOACH,Stagecoach Hotel & Casino (Demo),,36.915682,-116.751677,,  
NADAV,North Ave / D Ave N (Demo),,36.914893,-116.76821,,  
NANAA,North Ave / N A Ave (Demo),,36.914944,-116.761472,,  
DADAN,Doing Ave / D Ave N (Demo),,36.909489,-116.768242,,  
EMSI,E Main St / S Irving St (Demo),,36.905697,-116.76218,,  
AMV,Amargosa Valley (Demo),,36.641496,-116.40094,,  
route_id,service_id,trip_id,trip_headsign,direction_id,block_id,shape_id  
AB,FULLW,AB1,to Bullfrog,0,1,  
AB,FULLW,AB2,to Airport,1,2,  
STBA,FULLW,STBA,Shuttle,,,  
CITY,FULLW,CITY1,,0,,  
CITY,FULLW,CITY2,,1,,  
BFC,FULLW,BFC1,to Furnace Creek Resort,0,1,  
BFC,FULLW,BFC2,to Bullfrog,1,2,  
AAMV,WE,AAMV1,to Amargosa Valley,0,,  
AAMV,WE,AAMV2,to Airport,1,,  
AAMV,WE,AAMV3,to Amargosa Valley,0,,  
AAMV,WE,AAMV4,to Airport,1,,  
agency_id,agency_name,agency_url,agency_timezone  
DTA,Demo Transit Authority,http://google.com,America/Los_Angeles  
service_id,monday,tuesday,wednesday,thursday,friday,saturday,sunday,start_date,end_date  
FULLW,1,1,1,1,1,1,1,20070101,20101231  
WE,0,0,0,0,0,1,1,20070101,20101231  
service_id,date,exception_type  
FULLW,20070604,2  
fare_id,price,currency_type,payment_method,transfers,transfer_duration  
p,1.25,USD,0,0,  
a,5.25,USD,0,0,  
 
fare_id,route_id,origin_id,destination_id,contains_id  
p,AB,,,  
p,STBA,,,  
p,BFC,,,  
a,AAMV,,,  
 
trip_id,start_time,end_time,headway_secs  
STBA,6:00:00,22:00:00,1800  
CITY1,6:00:00,7:59:59,1800  
CITY2,6:00:00,7:59:59,1800  
CITY1,8:00:00,9:59:59,600  
CITY2,8:00:00,9:59:59,600  
CITY1,10:00:00,15:59:59,1800  
CITY2,10:00:00,15:59:59,1800  
CITY1,16:00:00,18:59:59,600  
CITY2,16:00:00,18:59:59,600  
CITY1,19:00:00,22:00:00,1800  
CITY2,19:00:00,22:00:00,1800  
route_id,agency_id,route_short_name,route_long_name,route_desc,route_type,route_url,route_color,route_text_color  
AB,DTA,,Airport - Bullfrog,,3,,,  
BFC,DTA,,Bullfrog - Furnace Creek Resort,,3,,,  
STBA,DTA,,Stagecoach - Airport Shuttle,,3,,,  
CITY,DTA,City,City,,3,,,  
AAMV,DTA,,Airport - Amargosa Valley,,3,,,  
 
trip_id,arrival_time,departure_time,stop_id,stop_sequence,stop_headsign,pickup_type,drop_off_type,shape_dist_traveled  
STBA,6:00:00,6:00:00,STAGECOACH,1,,,,  
STBA,6:20:00,6:20:00,BEATTY_AIRPORT,2,,,,  
CITY1,6:00:00,6:00:00,STAGECOACH,1,,,,  
CITY1,6:05:00,6:07:00,NANAA,2,,,,  
CITY1,6:12:00,6:14:00,NADAV,3,,,,  
CITY1,6:19:00,6:21:00,DADAN,4,,,,  
CITY1,6:26:00,6:28:00,EMSI,5,,,,  
CITY2,6:28:00,6:30:00,EMSI,1,,,,  
CITY2,6:35:00,6:37:00,DADAN,2,,,,  
CITY2,6:42:00,6:44:00,NADAV,3,,,,  
CITY2,6:49:00,6:51:00,NANAA,4,,,,  
CITY2,6:56:00,6:58:00,STAGECOACH,5,,,,  
AB1,8:00:00,8:00:00,BEATTY_AIRPORT,1,,,,  
AB1,8:10:00,8:15:00,BULLFROG,2,,,,  
AB2,12:05:00,12:05:00,BULLFROG,1,,,,  
AB2,12:15:00,12:15:00,BEATTY_AIRPORT,2,,,,  
BFC1,8:20:00,8:20:00,BULLFROG,1,,,,  
BFC1,9:20:00,9:20:00,FUR_CREEK_RES,2,,,,  
BFC2,11:00:00,11:00:00,FUR_CREEK_RES,1,,,,  
BFC2,12:00:00,12:00:00,BULLFROG,2,,,,  
AAMV1,8:00:00,8:00:00,BEATTY_AIRPORT,1,,,,  
AAMV1,9:00:00,9:00:00,AMV,2,,,,  
AAMV2,10:00:00,10:00:00,AMV,1,,,,  
AAMV2,11:00:00,11:00:00,BEATTY_AIRPORT,2,,,,  
AAMV3,13:00:00,13:00:00,BEATTY_AIRPORT,1,,,,  
AAMV3,14:00:00,14:00:00,AMV,2,,,,  
AAMV4,15:00:00,15:00:00,AMV,1,,,,  
AAMV4,16:00:00,16:00:00,BEATTY_AIRPORT,2,,,,  
 
stop_id,stop_name,stop_desc,stop_lat,stop_lon,zone_id,stop_url  
FUR_CREEK_RES,Furnace Creek Resort (Demo),,36.425288,-117.133162,,  
BEATTY_AIRPORT,Nye County Airport (Demo),,36.868446,-116.784582,,  
BULLFROG,Bullfrog (Demo),,36.88108,-116.81797,,  
STAGECOACH,Stagecoach Hotel & Casino (Demo),,36.915682,-116.751677,,  
NADAV,North Ave / D Ave N (Demo),,36.914893,-116.76821,,  
NANAA,North Ave / N A Ave (Demo),,36.914944,-116.761472,,  
DADAN,Doing Ave / D Ave N (Demo),,36.909489,-116.768242,,  
EMSI,E Main St / S Irving St (Demo),,36.905697,-116.76218,,  
AMV,Amargosa Valley (Demo),,36.641496,-116.40094,,  
route_id,service_id,trip_id,trip_headsign,direction_id,block_id,shape_id  
AB,FULLW,AB1,to Bullfrog,0,1,  
AB,FULLW,AB2,to Airport,1,2,  
STBA,FULLW,STBA,Shuttle,,,  
CITY,FULLW,CITY1,,0,,  
CITY,FULLW,CITY2,,1,,  
BFC,FULLW,BFC1,to Furnace Creek Resort,0,1,  
BFC,FULLW,BFC2,to Bullfrog,1,2,  
AAMV,WE,AAMV1,to Amargosa Valley,0,,  
AAMV,WE,AAMV2,to Airport,1,,  
AAMV,WE,AAMV3,to Amargosa Valley,0,,  
AAMV,WE,AAMV4,to Airport,1,,  
agency_id,agency_name,agency_url,agency_timezone  
DTA,Demo Transit Authority,http://google.com,America/Los_Angeles  
service_id,monday,tuesday,wednesday,thursday,friday,saturday,sunday,start_date,end_date  
FULLW,1,1,1,1,1,1,1,20070101,20101231  
WE,0,0,0,0,0,1,1,20070101,20101231  
service_id,date,exception_type  
FULLW,20070604,2  
fare_id,price,currency_type,payment_method,transfers,transfer_duration  
p,1.25,USD,0,0,  
a,5.25,USD,0,0,  
 
fare_id,route_id,origin_id,destination_id,contains_id  
p,AB,,,  
p,STBA,,,  
p,BFC,,,  
a,AAMV,,,  
 
trip_id,start_time,end_time,headway_secs  
STBA,6:00:00,22:00:00,1800  
CITY1,6:00:00,7:59:59,1800  
CITY2,6:00:00,7:59:59,1800  
CITY1,8:00:00,9:59:59,600  
CITY2,8:00:00,9:59:59,600  
CITY1,10:00:00,15:59:59,1800  
CITY2,10:00:00,15:59:59,1800  
CITY1,16:00:00,18:59:59,600  
CITY2,16:00:00,18:59:59,600  
CITY1,19:00:00,22:00:00,1800  
CITY2,19:00:00,22:00:00,1800  
route_id,agency_id,route_short_name,route_long_name,route_desc,route_type,route_url,route_color,route_text_color  
AB,DTA,,Airport - Bullfrog,,3,,,  
BFC,DTA,,Bullfrog - Furnace Creek Resort,,3,,,  
STBA,DTA,,Stagecoach - Airport Shuttle,,3,,,  
CITY,DTA,,City,,3,,,  
AAMV,DTA,,Airport - Amargosa Valley,,3,,,  
trip_id,arrival_time,departure_time,stop_id,stop_sequence,stop_headsign,pickup_type,drop_off_type,shape_dist_traveled  
STBA,6:00:00,6:00:00,STAGECOACH,1,,,,  
STBA,6:20:00,6:20:00,BEATTY_AIRPORT,2,,,,  
CITY1,6:00:00,6:00:00,STAGECOACH,1,,,,  
CITY1,6:05:00,6:07:00,NANAA,2,,,,  
CITY1,6:12:00,6:14:00,NADAV,3,,,,  
CITY1,6:19:00,6:21:00,DADAN,4,,,,  
CITY1,6:26:00,6:28:00,EMSI,5,,,,  
CITY2,6:28:00,6:30:00,EMSI,1,,,,  
CITY2,6:35:00,6:37:00,DADAN,2,,,,  
CITY2,6:40:00,6:41:00,NADAR,3,,,,  
CITY2,6:42:00,6:44:00,NADAV,4,,,,  
CITY2,6:49:00,6:51:00,NANAA,5,,,,  
CITY2,6:56:00,6:58:00,STAGECOACH,6,,,,  
AB1,8:00:00,8:00:00,BEATTY_AIRPORT,1,,,,  
AB1,8:10:00,8:15:00,BULLFROG,2,,,,  
AB2,12:05:00,12:05:00,BULLFROG,1,,,,  
AB2,12:15:00,12:15:00,BEATTY_AIRPORT,2,,,,  
BFC1,8:20:00,8:20:00,BULLFROG,1,,,,  
BFC1,9:20:00,9:20:00,FUR_CREEK_RES,2,,,,  
BFC2,11:00:00,11:00:00,FUR_CREEK_RES,1,,,,  
BFC2,12:00:00,12:00:00,BULLFROG,2,,,,  
AAMV1,8:00:00,8:00:00,BEATTY_AIRPORT,1,,,,  
AAMV1,9:00:00,9:00:00,AMV,2,,,,  
AAMV2,10:00:00,10:00:00,AMV,1,,,,  
AAMV2,11:00:00,11:00:00,BEATTY_AIRPORT,2,,,,  
AAMV3,13:00:00,13:00:00,BEATTY_AIRPORT,1,,,,  
AAMV3,14:00:00,14:00:00,AMV,2,,,,  
AAMV4,15:00:00,15:00:00,AMV,1,,,,  
AAMV4,16:00:00,16:00:00,BEATTY_AIRPORT,2,,,,  
 
stop_id,stop_name,stop_desc,stop_lat,stop_lon,zone_id,stop_url  
FUR_CREEK_RES,Furnace Creek Resort (Demo),,36.425288,-117.133162,,  
BEATTY_AIRPORT,Nye County Airport (Demo),,36.868446,-116.784582,,  
BULLFROG,Bullfrog (Demo),,36.88108,-116.81797,,  
STAGECOACH,Stagecoach Hotel & Casino (Demo),,36.915682,-116.751677,,  
NADAV,North Ave / D Ave N (Demo),,36.914893,-116.76821,,  
NANAA,North Ave / N A Ave (Demo),,36.914944,-116.761472,,  
DADAN,Doing Ave / D Ave N (Demo),,36.909489,-116.768242,,  
EMSI,E Main St / S Irving St (Demo),,36.905697,-116.76218,,  
AMV,Amargosa Valley (Demo),,36.641496,-116.40094,,  
route_id,service_id,trip_id,trip_headsign,direction_id,block_id,shape_id  
AB,FULLW,AB1,to Bullfrog,0,1,  
AB,FULLW,AB2,to Airport,1,2,  
STBA,FULLW,STBA,Shuttle,,,  
CITY,FULLW,CITY1,,0,,  
CITY,FULLW,CITY2,,1,,  
BFC,FULLW,BFC1,to Furnace Creek Resort,0,1,  
BFC,FULLW,BFC2,to Bullfrog,1,2,  
AAMV,WE,AAMV1,to Amargosa Valley,0,,  
AAMV,WE,AAMV2,to Airport,1,,  
AAMV,WE,AAMV3,to Amargosa Valley,0,,  
AAMV,WE,AAMV4,to Airport,1,,  
agency_id,agency_name,agency_url,agency_timezone,agency_phone  
DTA,Autorité de passage de démonstration,http://google.com,America/Los_Angeles,123 12314  
 
service_id,monday,tuesday,wednesday,thursday,friday,saturday,sunday,start_date,end_date  
FULLW,1,1,1,1,1,1,1,20070101,20101231  
WE,0,0,0,0,0,1,1,20070101,20101231  
service_id,date,exception_type  
FULLW,20070604,2  
fare_id,price,currency_type,payment_method,transfers,transfer_duration  
p,1.25,USD,0,0,  
a,5.25,USD,0,0,  
 
fare_id,route_id,origin_id,destination_id,contains_id  
p,AB,,,  
p,STBA,,,  
p,BFC,,,  
a,AAMV,,,  
 
trip_id,start_time,end_time,headway_secs  
STBA,6:00:00,22:00:00,1800  
CITY1,6:00:00,7:59:59,1800  
CITY2,6:00:00,7:59:59,1800  
CITY1,8:00:00,9:59:59,600  
CITY2,8:00:00,9:59:59,600  
CITY1,10:00:00,15:59:59,1800  
CITY2,10:00:00,15:59:59,1800  
CITY1,16:00:00,18:59:59,600  
CITY2,16:00:00,18:59:59,600  
CITY1,19:00:00,22:00:00,1800  
CITY2,19:00:00,22:00:00,1800  
route_id,agency_id,route_short_name,route_long_name,route_desc,route_type,route_url,route_color,route_text_color  
AB,DTA,,Airport ⇒ Bullfrog,,3,,,  
BFC,DTA,,Bullfrog ⇒ Furnace Creek Resort,,3,,,  
STBA,DTA,,Stagecoach ⇒ Airport Shuttle,,3,,,  
CITY,DTA,Ō,Bar Circle,Route with ĸool unicode shortname,3,,,  
AAMV,DTA,,Airport ⇒ Amargosa Valley,,3,,,  
 
trip_id,arrival_time,departure_time,stop_id,stop_sequence,stop_headsign,pickup_type,drop_off_type,shape_dist_traveled  
STBA,6:00:00,6:00:00,STAGECOACH,0,to airport,1,0,0.212  
STBA,6:20:00,6:20:00,BEATTY_AIRPORT,2,,0,0,1.043  
CITY1,6:00:00,6:00:00,STAGECOACH,0,,,,  
CITY1,6:05:00,6:07:00,NANAA,5,going to nadav,2,3,  
CITY1,6:12:00,6:14:00,NADAV,10,,,,  
CITY1,6:19:00,6:21:00,DADAN,15,,,,  
CITY1,6:26:00,6:28:00,EMSI,20,,,,  
CITY2,6:28:00,6:30:00,EMSI,100,,,,  
CITY2,6:35:00,6:37:00,DADAN,200,,,,  
CITY2,6:42:00,6:44:00,NADAV,300,,,,  
CITY2,6:49:00,6:51:00,NANAA,400,,,,  
CITY2,6:56:00,6:58:00,STAGECOACH,500,,,,  
AB1,8:00:00,8:00:00,BEATTY_AIRPORT,1,,,,  
AB1,8:10:00,8:15:00,BULLFROG,2,,,,  
AB2,12:05:00,12:05:00,BULLFROG,1,,,,  
AB2,12:15:00,12:15:00,BEATTY_AIRPORT,2,,,,  
BFC1,8:20:00,8:20:00,BULLFROG,1,,,,  
BFC1,9:20:00,9:20:00,FUR_CREEK_RES,2,,,,  
BFC2,11:00:00,11:00:00,FUR_CREEK_RES,1,,,,  
BFC2,12:00:00,12:00:00,BULLFROG,2,,,,  
AAMV1,8:00:00,8:00:00,BEATTY_AIRPORT,1,,,,  
AAMV1,9:00:00,9:00:00,AMV,2,,,,  
AAMV2,10:00:00,10:00:00,AMV,1,,,,  
AAMV2,11:00:00,11:00:00,BEATTY_AIRPORT,2,,,,  
AAMV3,13:00:00,13:00:00,BEATTY_AIRPORT,1,,,,  
AAMV3,14:00:00,14:00:00,AMV,2,,,,  
AAMV4,15:00:00,15:00:00,AMV,1,,,,  
AAMV4,16:00:00,16:00:00,BEATTY_AIRPORT,2,,,,  
 
stop_id,stop_name,stop_desc,stop_lat,stop_lon,zone_id,stop_url,stop_code,location_type,parent_station  
FUR_CREEK_RES,Furnace Creek Resort (Demo),,36.425288,-117.133162,,,1234,,  
BEATTY_AIRPORT,Nye County Airport (Demo),,36.868446,-116.784582,,,1235,0,BEATTY_AIRPORT_STATION  
BEATTY_AIRPORT_STATION,Nye County Airport (Demo),,36.868446,-116.784582,,,1235,1,  
BULLFROG,Bullfrog (Demo),,36.88108,-116.81797,,,,,  
STAGECOACH,Stagecoach Hotel & Casino (Demo),,36.915682,-116.751677,,,1236,,  
NADAV,North Ave / D Ave N (Demo),,36.914893,-116.76821,,,1237,,  
NANAA,North Ave / N A Ave (Demo),,36.914944,-116.761472,,,1238,,  
DADAN,Doing Ave / D Ave N (Demo),,36.909489,-116.768242,,,,,  
EMSI,E Main St / S Irving St (Demo),,36.905697,-116.76218,,,,,  
AMV,Amargosa Valley (Demo),,36.641496,-116.40094,,,,,  
 
from_stop_id,to_stop_id,transfer_type,min_transfer_time  
NADAV,NANAA,3,  
EMSI,NANAA,2,1200  
 
route_id,service_id,trip_id,trip_headsign,direction_id,block_id,shape_id  
AB,FULLW,AB1,to Bullfrog,0,1,  
AB,FULLW,AB2,to Airport,1,2,  
STBA,FULLW,STBA,Shuttle,,,  
CITY,FULLW,CITY1,,0,,  
CITY,FULLW,CITY2,,1,,  
BFC,FULLW,BFC1,to Furnace Creek Resort,0,1,  
BFC,FULLW,BFC2,to Bullfrog,1,2,  
AAMV,WE,AAMV1,to Amargosa Valley,0,,  
AAMV,WE,AAMV2,to Airport,1,,  
AAMV,WE,AAMV3,to Amargosa Valley,0,,  
AAMV,WE,AAMV4,to Airport,1,,  
not a real zip file  
 
agency_id,agency_name,agency_url,agency_timezone,agency_lange  
DTA,Demo Transit Authority,http://google.com,America/Los_Angeles,en  
service_id,monday,tuesday,wednesday,thursday,friday,saturday,sunday,start_date,end_date,leap_day  
FULLW,1,1,1,1,1,1,1,20070101,20101231,  
WE,0,0,0,0,0,1,1,20070101,20101231,  
service_id,date,exception_type,leap_day  
FULLW,20070604,2,  
fare_id,price,currency_type,payment_method,transfers,transfer_time  
p,1.25,USD,0,0,  
a,5.25,USD,0,0,  
 
fare_id,route_id,source_id,destination_id,contains_id  
p,AB,,,  
p,STBA,,,  
p,BFC,,,  
a,AAMV,,,  
 
trip_id,start_time,end_time,headway_secs,superfluous  
STBA,6:00:00,22:00:00,1800,  
CITY1,6:00:00,7:59:59,1800,  
CITY2,6:00:00,7:59:59,1800,  
CITY1,8:00:00,9:59:59,600,  
CITY2,8:00:00,9:59:59,600,  
CITY1,10:00:00,15:59:59,1800,  
CITY2,10:00:00,15:59:59,1800,  
CITY1,16:00:00,18:59:59,600,  
CITY2,16:00:00,18:59:59,600,  
CITY1,19:00:00,22:00:00,1800,  
CITY2,19:00:00,22:00:00,1800,  
route_id,agency_id,route_short_name,route_long_name,route_desc,route_type,route_url,route_color,Route_Text_Color  
AB,DTA,,Airport - Bullfrog,,3,,,  
BFC,DTA,,Bullfrog - Furnace Creek Resort,,3,,,  
STBA,DTA,,Stagecoach - Airport Shuttle,,3,,,  
CITY,DTA,,City,,3,,,  
AAMV,DTA,,Airport - Amargosa Valley,,3,,,  
trip_id,arrival_time,departure_time,stop_id,stop_sequence,stop_headsign,pickup_type,drop_off_time,shapedisttraveled  
STBA,6:00:00,6:00:00,STAGECOACH,1,,,,  
STBA,6:20:00,6:20:00,BEATTY_AIRPORT,2,,,,  
CITY1,6:00:00,6:00:00,STAGECOACH,1,,,,  
CITY1,6:05:00,6:07:00,NANAA,2,,,,  
CITY1,6:12:00,6:14:00,NADAV,3,,,,  
CITY1,6:19:00,6:21:00,DADAN,4,,,,  
CITY1,6:26:00,6:28:00,EMSI,5,,,,  
CITY2,6:28:00,6:30:00,EMSI,1,,,,  
CITY2,6:35:00,6:37:00,DADAN,2,,,,  
CITY2,6:42:00,6:44:00,NADAV,3,,,,  
CITY2,6:49:00,6:51:00,NANAA,4,,,,  
CITY2,6:56:00,6:58:00,STAGECOACH,5,,,,  
AB1,8:00:00,8:00:00,BEATTY_AIRPORT,1,,,,  
AB1,8:10:00,8:15:00,BULLFROG,2,,,,  
AB2,12:05:00,12:05:00,BULLFROG,1,,,,  
AB2,12:15:00,12:15:00,BEATTY_AIRPORT,2,,,,  
BFC1,8:20:00,8:20:00,BULLFROG,1,,,,  
BFC1,9:20:00,9:20:00,FUR_CREEK_RES,2,,,,  
BFC2,11:00:00,11:00:00,FUR_CREEK_RES,1,,,,  
BFC2,12:00:00,12:00:00,BULLFROG,2,,,,  
AAMV1,8:00:00,8:00:00,BEATTY_AIRPORT,1,,,,  
AAMV1,9:00:00,9:00:00,AMV,2,,,,  
AAMV2,10:00:00,10:00:00,AMV,1,,,,  
AAMV2,11:00:00,11:00:00,BEATTY_AIRPORT,2,,,,  
AAMV3,13:00:00,13:00:00,BEATTY_AIRPORT,1,,,,  
AAMV3,14:00:00,14:00:00,AMV,2,,,,  
AAMV4,15:00:00,15:00:00,AMV,1,,,,  
AAMV4,16:00:00,16:00:00,BEATTY_AIRPORT,2,,,,  
 
stop_id,stop_name,stop_desc,stop_lat,stop_lon,zone_id,stop_uri  
FUR_CREEK_RES,Furnace Creek Resort (Demo),,36.425288,-117.133162,,  
BEATTY_AIRPORT,Nye County Airport (Demo),,36.868446,-116.784582,,  
BULLFROG,Bullfrog (Demo),,36.88108,-116.81797,,  
STAGECOACH,Stagecoach Hotel & Casino (Demo),,36.915682,-116.751677,,  
NADAV,North Ave / D Ave N (Demo),,36.914893,-116.76821,,  
NANAA,North Ave / N A Ave (Demo),,36.914944,-116.761472,,  
DADAN,Doing Ave / D Ave N (Demo),,36.909489,-116.768242,,  
EMSI,E Main St / S Irving St (Demo),,36.905697,-116.76218,,  
AMV,Amargosa Valley (Demo),,36.641496,-116.40094,,  
from_stop_id,to_stop_id,transfer_type,min_transfer_time,to_stop  
NADAV,NANAA,3,,  
 
route_id,service_id,trip_id,trip_headsign,direction_id,block_id,sharpe_id  
AB,FULLW,AB1,to Bullfrog,0,1,  
AB,FULLW,AB2,to Airport,1,2,  
STBA,FULLW,STBA,Shuttle,,,  
CITY,FULLW,CITY1,,0,,  
CITY,FULLW,CITY2,,1,,  
BFC,FULLW,BFC1,to Furnace Creek Resort,0,1,  
BFC,FULLW,BFC2,to Bullfrog,1,2,  
AAMV,WE,AAMV1,to Amargosa Valley,0,,  
AAMV,WE,AAMV2,to Airport,1,,  
AAMV,WE,AAMV3,to Amargosa Valley,0,,  
AAMV,WE,AAMV4,to Airport,1,,  
agency_id,agency_name,agency_url,agency_timezone  
DTA,Demo Transit Authority,http://google.com,America/Los_Angeles  
service_id,monday,tuesday,wednesday,thursday,friday,saturday,sunday,start_date,end_date  
FULLW,1,1,1,1,1,1,1,20070101,20101231  
WE,0,0,0,0,0,1,1,20070101,20101231  
service_id,date,exception_type  
FULLW,20070604,2  
fare_id,price,currency_type,payment_method,transfers,transfer_duration  
p,1.25,USD,0,0,  
a,5.25,USD,0,0,  
 
fare_id,route_id,origin_id,destination_id,contains_id  
p,AB,,,  
p,STBA,,,  
p,BFC,,,  
a,AAMV,,,  
 
trip_id,start_time,end_time,headway_secs  
STBA,6:00:00,22:00:00,1800  
CITY1,6:00:00,7:59:59,1800  
CITY2,6:00:00,7:59:59,1800  
CITY1,8:00:00,9:59:59,600  
CITY2,8:00:00,9:59:59,600  
CITY1,10:00:00,15:59:59,1800  
CITY2,10:00:00,15:59:59,1800  
CITY1,16:00:00,18:59:59,600  
CITY2,16:00:00,18:59:59,600  
CITY1,19:00:00,22:00:00,1800  
CITY2,19:00:00,22:00:00,1800  
route_id,agency_id,route_short_name,route_long_name,route_desc,route_type,route_url,route_color,route_text_color  
AB,DTA,,Airport - Bullfrog,,3,,,  
BFC,DTA,,Bullfrog - Furnace Creek Resort,,3,,,  
STBA,DTA,,Stagecoach - Airport Shuttle,,3,,,  
CITY,DTA,,City,,3,,,  
AAMV,DTA,,Airport - Amargosa Valley,,3,,,  
trip_id,arrival_time,departure_time,stop_id,stop_sequence,stop_headsign,pickup_type,drop_off_type,shape_dist_traveled  
STBA,6:00:00,6:00:00,STAGECOACH,1,,,,  
STBA,6:20:00,6:20:00,BEATTY_AIRPORT,2,,,,  
CITY1,6:00:00,6:00:00,STAGECOACH,1,,,,  
CITY1,6:05:00,6:07:00,NANAA,2,,,,  
CITY1,6:12:00,6:14:00,NADAV,3,,,,  
CITY1,6:19:00,6:21:00,DADAN,4,,,,  
CITY1,6:26:00,6:28:00,EMSI,5,,,,  
CITY2,6:28:00,6:30:00,EMSI,1,,,,  
CITY2,6:35:00,6:37:00,DADAN,2,,,,  
CITY2,6:42:00,6:44:00,NADAV,3,,,,  
CITY2,6:49:00,6:51:00,NANAA,4,,,,  
CITY2,6:56:00,6:58:00,STAGECOACH,5,,,,  
AB1,8:00:00,8:00:00,BEATTY_AIRPORT,1,,,,  
AB1,8:10:00,8:15:00,BULLFROG,2,,,,  
AB2,12:05:00,12:05:00,BULLFROG,1,,,,  
AB2,12:15:00,12:15:00,BEATTY_AIRPORT,2,,,,  
BFC1,8:20:00,8:20:00,BULLFROG,1,,,,  
BFC1,9:20:00,9:20:00,FUR_CREEK_RES,2,,,,  
BFC2,11:00:00,11:00:00,FUR_CREEK_RES,1,,,,  
BFC2,12:00:00,12:00:00,BULLFROG,2,,,,  
AAMV1,8:00:00,8:00:00,BEATTY_AIRPORT,1,,,,  
AAMV1,9:00:00,9:00:00,AMV,2,,,,  
AAMV2,10:00:00,10:00:00,AMV,1,,,,  
AAMV2,11:00:00,11:00:00,BEATTY_AIRPORT,2,,,,  
AAMV3,13:00:00,13:00:00,BEATTY_AIRPORT,1,,,,  
AAMV3,14:00:00,14:00:00,AMV,2,,,,  
AAMV4,15:00:00,15:00:00,AMV,1,,,,  
AAMV4,16:00:00,16:00:00,BEATTY_AIRPORT,2,,,,  
stop_id,stop_name,stop_desc,stop_lat,stop_lon,zone_id,stop_url  
FUR_CREEK_RES,Furnace Creek Resort (Demo),,36.425288,-117.133162,,  
BEATTY_AIRPORT,Nye County Airport (Demo),,36.868446,-116.784582,,  
BULLFROG,Bullfrog (Demo),,36.88108,-116.81797,,  
STAGECOACH,Stagecoach Hotel & Casino (Demo),,36.915682,-116.751677,,  
BOGUS,Bogus Stop (Demo),,36.914682,-116.750677,,  
NADAV,North Ave / D Ave N (Demo),,36.914893,-116.76821,,  
NANAA,North Ave / N A Ave (Demo),,36.914944,-116.761472,,  
DADAN,Doing Ave / D Ave N (Demo),,36.909489,-116.768242,,  
EMSI,E Main St / S Irving St (Demo),,36.905697,-116.76218,,  
AMV,Amargosa Valley (Demo),,36.641496,-116.40094,,  
 
route_id,service_id,trip_id,trip_headsign,direction_id,block_id,shape_id  
AB,FULLW,AB1,to Bullfrog,0,1,  
AB,FULLW,AB2,to Airport,1,2,  
STBA,FULLW,STBA,Shuttle,,,  
CITY,FULLW,CITY1,,0,,  
CITY,FULLW,CITY2,,1,,  
BFC,FULLW,BFC1,to Furnace Creek Resort,0,1,  
BFC,FULLW,BFC2,to Bullfrog,1,2,  
AAMV,WE,AAMV1,to Amargosa Valley,0,,  
AAMV,WE,AAMV2,to Airport,1,,  
AAMV,WE,AAMV3,to Amargosa Valley,0,,  
AAMV,WE,AAMV4,to Airport,1,,  
 Binary files a/origin-src/transitfeed-1.2.5/test/data/utf16/agency.txt and /dev/null differ
 Binary files a/origin-src/transitfeed-1.2.5/test/data/utf16/calendar.txt and /dev/null differ
 Binary files a/origin-src/transitfeed-1.2.5/test/data/utf16/calendar_dates.txt and /dev/null differ
 Binary files a/origin-src/transitfeed-1.2.5/test/data/utf16/fare_attributes.txt and /dev/null differ
 Binary files a/origin-src/transitfeed-1.2.5/test/data/utf16/fare_rules.txt and /dev/null differ
 Binary files a/origin-src/transitfeed-1.2.5/test/data/utf16/frequencies.txt and /dev/null differ
 Binary files a/origin-src/transitfeed-1.2.5/test/data/utf16/routes.txt and /dev/null differ
 Binary files a/origin-src/transitfeed-1.2.5/test/data/utf16/stop_times.txt and /dev/null differ
 Binary files a/origin-src/transitfeed-1.2.5/test/data/utf16/stops.txt and /dev/null differ
 Binary files a/origin-src/transitfeed-1.2.5/test/data/utf16/trips.txt and /dev/null differ
agency_id,agency_name,agency_url,agency_timezone  
DTA,Demo Transit Authority,http://google.com,America/Los_Angeles  
service_id,monday,tuesday,wednesday,thursday,friday,saturday,sunday,start_date,end_date  
FULLW,1,1,1,1,1,1,1,20070101,20101231  
WE,0,0,0,0,0,1,1,20070101,20101231  
service_id,date,exception_type  
FULLW,20070604,2  
fare_id,price,currency_type,payment_method,transfers,transfer_duration  
p,1.25,USD,0,0,  
a,5.25,USD,0,0,  
 
fare_id,route_id,origin_id,destination_id,contains_id  
p,AB,,,  
p,STBA,,,  
p,BFC,,,  
a,AAMV,,,  
 
trip_id,start_time,end_time,headway_secs  
STBA,6:00:00,22:00:00,1800  
CITY1,6:00:00,7:59:59,1800  
CITY2,6:00:00,7:59:59,1800  
CITY1,8:00:00,9:59:59,600  
CITY2,8:00:00,9:59:59,600  
CITY1,10:00:00,15:59:59,1800  
CITY2,10:00:00,15:59:59,1800  
CITY1,16:00:00,18:59:59,600  
CITY2,16:00:00,18:59:59,600  
CITY1,19:00:00,22:00:00,1800  
CITY2,19:00:00,22:00:00,1800  
route_id,agency_id,route_short_name,route_long_name,route_desc,route_type,route_url,route_color,route_text_color  
AB,DTA,,Airport - Bullfrog,,3,,,  
BFC,DTA,,Bullfrog - Furnace Creek Resort,,3,,,  
STBA,DTA,,Stagecoach - Airport Shuttle,,3,,,  
CITY,DTA,,City,,3,,,  
AAMV,DTA,,Airport - Amargosa Valley,,3,,,  
trip_id,arrival_time,departure_time,stop_id,stop_sequence,stop_headsign,pickup_type,drop_off_type,shape_dist_traveled  
STBA,6:00:00,6:00:00,STAGECOACH,1,,,,  
STBA,6:20:00,6:20:00,BEATTY_AIRPORT,2,,,,  
CITY1,6:00:00,6:00:00,STAGECOACH,1,,,,  
CITY1,6:05:00,6:07:00,NANAA,2,,,,  
CITY1,6:12:00,6:14:00,NADAV,3,,,,  
CITY1,6:19:00,6:21:00,DADAN,4,,,,  
CITY1,6:26:00,6:28:00,EMSI,5,,,,  
CITY2,6:28:00,6:30:00,EMSI,1,,,,  
CITY2,6:35:00,6:37:00,DADAN,2,,,,  
CITY2,6:42:00,6:44:00,NADAV,3,,,,  
CITY2,6:49:00,6:51:00,NANAA,4,,,,  
CITY2,6:56:00,6:58:00,STAGECOACH,5,,,,  
AB1,8:00:00,8:00:00,BEATTY_AIRPORT,1,,,,  
AB1,8:10:00,8:15:00,BULLFROG,2,,,,  
AB2,12:05:00,12:05:00,BULLFROG,1,,,,  
AB2,12:15:00,12:15:00,BEATTY_AIRPORT,2,,,,  
BFC1,8:20:00,8:20:00,BULLFROG,1,,,,  
BFC1,9:20:00,9:20:00,FUR_CREEK_RES,2,,,,  
BFC2,11:00:00,11:00:00,FUR_CREEK_RES,1,,,,  
BFC2,12:00:00,12:00:00,BULLFROG,2,,,,  
AAMV1,8:00:00,8:00:00,BEATTY_AIRPORT,1,,,,  
AAMV1,9:00:00,9:00:00,AMV,2,,,,  
AAMV2,10:00:00,10:00:00,AMV,1,,,,  
AAMV2,11:00:00,11:00:00,BEATTY_AIRPORT,2,,,,  
AAMV3,13:00:00,13:00:00,BEATTY_AIRPORT,1,,,,  
AAMV3,14:00:00,14:00:00,AMV,2,,,,  
AAMV4,15:00:00,15:00:00,AMV,1,,,,  
AAMV4,16:00:00,16:00:00,BEATTY_AIRPORT,2,,,,  
stop_id,stop_name,stop_desc,stop_lat,stop_lon,zone_id,stop_url  
FUR_CREEK_RES,Furnace Creek Resort (Demo),,36.425288,-117.133162,,  
BEATTY_AIRPORT,Nye County Airport (Demo),,36.868446,-116.784582,,  
BULLFROG,Bullfrog (Demo),,36.88108,-116.81797,,  
STAGECOACH,Stagecoach Hotel & Casino (Demo),,36.915682,-116.751677,,  
NADAV,North Ave / D Ave N (Demo),,36.914893,-116.76821,,  
NANAA,North Ave / N A Ave (Demo),,36.914944,-116.761472,,  
DADAN,Doing Ave / D Ave N (Demo),,36.909489,-116.768242,,  
EMSI,E Main St / S Irving St (Demo),,36.905697,-116.76218,,  
AMV,Amargosa Valley (Demo),,36.641496,-116.40094,,  
route_id,service_id,trip_id,trip_headsign,direction_id,block_id,shape_id  
AB,FULLW,AB1,to Bullfrog,0,1,  
AB,FULLW,AB2,to Airport,1,2,  
STBA,FULLW,STBA,Shuttle,,,  
CITY,FULLW,CITY1,,0,,  
CITY,FULLW,CITY2,,1,,  
BFC,FULLW,BFC1,to Furnace Creek Resort,0,1,  
BFC,FULLW,BFC2,to Bullfrog,1,2,  
AAMV,WE,AAMV1,to Amargosa Valley,0,,  
AAMV,WE,AAMV2,to Airport,1,,  
AAMV,WE,AAMV3,to Amargosa Valley,0,,  
AAMV,WE,AAMV4,to Airport,1,,  
#!/usr/bin/python2.5  
 
# Test the examples to make sure they are not broken  
 
import os  
import re  
import transitfeed  
import unittest  
import urllib  
import util  
 
class WikiExample(util.TempDirTestCaseBase):  
# Download example from wiki and run it  
def runTest(self):  
wiki_source = urllib.urlopen(  
'http://googletransitdatafeed.googlecode.com/svn/wiki/TransitFeed.wiki'  
).read()  
m = re.search(r'{{{(.*import transitfeed.*)}}}', wiki_source, re.DOTALL)  
if not m:  
raise Exception("Failed to find source code on wiki page")  
wiki_code = m.group(1)  
exec wiki_code  
 
 
class shuttle_from_xmlfeed(util.TempDirTestCaseBase):  
def runTest(self):  
self.CheckCallWithPath(  
[self.GetExamplePath('shuttle_from_xmlfeed.py'),  
'--input', 'file:' + self.GetExamplePath('shuttle_from_xmlfeed.xml'),  
'--output', 'shuttle-YYYYMMDD.zip',  
# save the path of the dated output to tempfilepath  
'--execute', 'echo %(path)s > outputpath'])  
 
dated_path = open('outputpath').read().strip()  
self.assertTrue(re.match(r'shuttle-20\d\d[01]\d[0123]\d.zip$', dated_path))  
if not os.path.exists(dated_path):  
raise Exception('did not create expected file')  
 
 
class table(util.TempDirTestCaseBase):  
def runTest(self):  
self.CheckCallWithPath(  
[self.GetExamplePath('table.py'),  
'--input', self.GetExamplePath('table.txt'),  
'--output', 'google_transit.zip'])  
if not os.path.exists('google_transit.zip'):  
raise Exception('should have created output')  
 
 
class small_builder(util.TempDirTestCaseBase):  
def runTest(self):  
self.CheckCallWithPath(  
[self.GetExamplePath('small_builder.py'),  
'--output', 'google_transit.zip'])  
if not os.path.exists('google_transit.zip'):  
raise Exception('should have created output')  
 
 
class google_random_queries(util.TempDirTestCaseBase):  
def testNormalRun(self):  
self.CheckCallWithPath(  
[self.GetExamplePath('google_random_queries.py'),  
'--output', 'queries.html',  
'--limit', '5',  
self.GetPath('test', 'data', 'good_feed')])  
if not os.path.exists('queries.html'):  
raise Exception('should have created output')  
 
def testInvalidFeedStillWorks(self):  
self.CheckCallWithPath(  
[self.GetExamplePath('google_random_queries.py'),  
'--output', 'queries.html',  
'--limit', '5',  
self.GetPath('test', 'data', 'invalid_route_agency')])  
if not os.path.exists('queries.html'):  
raise Exception('should have created output')  
 
def testBadArgs(self):  
self.CheckCallWithPath(  
[self.GetExamplePath('google_random_queries.py'),  
'--output', 'queries.html',  
'--limit', '5'],  
expected_retcode=2)  
if os.path.exists('queries.html'):  
raise Exception('should not have created output')  
 
 
class filter_unused_stops(util.TempDirTestCaseBase):  
def testNormalRun(self):  
unused_stop_path = self.GetPath('test', 'data', 'unused_stop')  
# Make sure load fails for input  
problem_reporter = transitfeed.ExceptionProblemReporter(raise_warnings=True)  
try:  
transitfeed.Loader(  
unused_stop_path,  
problems=problem_reporter, extra_validation=True).Load()  
self.fail('UnusedStop exception expected')  
except transitfeed.UnusedStop, e:  
pass  
(stdout, stderr) = self.CheckCallWithPath(  
[self.GetExamplePath('filter_unused_stops.py'),  
'--list_removed',  
unused_stop_path, 'output.zip'])  
# Extra stop was listed on stdout  
self.assertNotEqual(stdout.find('Bogus Stop'), -1)  
# Make sure unused stop was removed and another stop wasn't  
schedule = transitfeed.Loader(  
'output.zip', problems=problem_reporter, extra_validation=True).Load()  
schedule.GetStop('STAGECOACH')  
 
 
if __name__ == '__main__':  
unittest.main()  
 
 Binary files a/origin-src/transitfeed-1.2.5/test/testexamples.pyc and /dev/null differ
#!/usr/bin/python2.5  
 
# Copyright (C) 2007 Google Inc.  
#  
# Licensed under the Apache License, Version 2.0 (the "License");  
# you may not use this file except in compliance with the License.  
# You may obtain a copy of the License at  
#  
# http://www.apache.org/licenses/LICENSE-2.0  
#  
# Unless required by applicable law or agreed to in writing, software  
# distributed under the License is distributed on an "AS IS" BASIS,  
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.  
# See the License for the specific language governing permissions and  
# limitations under the License.  
 
# Smoke tests feed validator. Make sure it runs and returns the right things  
# for a valid feed and a feed with errors.  
 
import datetime  
import feedvalidator  
import os.path  
import re  
import StringIO  
import transitfeed  
import unittest  
from urllib2 import HTTPError, URLError  
import urllib2  
import util  
import zipfile  
 
 
class FullTests(util.TempDirTestCaseBase):  
def testGoodFeed(self):  
(out, err) = self.CheckCallWithPath(  
[self.GetPath('feedvalidator.py'), '-n', '--latest_version',  
transitfeed.__version__, self.GetPath('test', 'data', 'good_feed')])  
self.assertTrue(re.search(r'feed validated successfully', out))  
self.assertFalse(re.search(r'ERROR', out))  
htmlout = open('validation-results.html').read()  
self.assertTrue(re.search(r'feed validated successfully', htmlout))  
self.assertFalse(re.search(r'ERROR', htmlout))  
self.assertFalse(os.path.exists('transitfeedcrash.txt'))  
 
def testGoodFeedConsoleOutput(self):  
(out, err) = self.CheckCallWithPath(  
[self.GetPath('feedvalidator.py'), '-n', '--latest_version',  
transitfeed.__version__,  
'--output=CONSOLE', self.GetPath('test', 'data', 'good_feed')])  
self.assertTrue(re.search(r'feed validated successfully', out))  
self.assertFalse(re.search(r'ERROR', out))  
self.assertFalse(os.path.exists('validation-results.html'))  
self.assertFalse(os.path.exists('transitfeedcrash.txt'))  
 
def testMissingStops(self):  
(out, err) = self.CheckCallWithPath(  
[self.GetPath('feedvalidator.py'), '-n', '--latest_version',  
transitfeed.__version__,  
self.GetPath('test', 'data', 'missing_stops')],  
expected_retcode=1)  
self.assertTrue(re.search(r'ERROR', out))  
self.assertFalse(re.search(r'feed validated successfully', out))  
htmlout = open('validation-results.html').read()  
self.assertTrue(re.search(r'Invalid value BEATTY_AIRPORT', htmlout))  
self.assertFalse(re.search(r'feed validated successfully', htmlout))  
self.assertFalse(os.path.exists('transitfeedcrash.txt'))  
 
def testMissingStopsConsoleOutput(self):  
(out, err) = self.CheckCallWithPath(  
[self.GetPath('feedvalidator.py'), '-n', '-o', 'console',  
'--latest_version', transitfeed.__version__,  
self.GetPath('test', 'data', 'missing_stops')],  
expected_retcode=1)  
self.assertTrue(re.search(r'ERROR', out))  
self.assertFalse(re.search(r'feed validated successfully', out))  
self.assertTrue(re.search(r'Invalid value BEATTY_AIRPORT', out))  
self.assertFalse(os.path.exists('validation-results.html'))  
self.assertFalse(os.path.exists('transitfeedcrash.txt'))  
 
def testLimitedErrors(self):  
(out, err) = self.CheckCallWithPath(  
[self.GetPath('feedvalidator.py'), '-l', '2', '-n',  
'--latest_version', transitfeed.__version__,  
self.GetPath('test', 'data', 'missing_stops')],  
expected_retcode=1)  
self.assertTrue(re.search(r'ERROR', out))  
self.assertFalse(re.search(r'feed validated successfully', out))  
htmlout = open('validation-results.html').read()  
self.assertEquals(2, len(re.findall(r'class="problem">stop_id<', htmlout)))  
self.assertFalse(os.path.exists('transitfeedcrash.txt'))  
 
def testBadDateFormat(self):  
(out, err) = self.CheckCallWithPath(  
[self.GetPath('feedvalidator.py'), '-n', '--latest_version',  
transitfeed.__version__,  
self.GetPath('test', 'data', 'bad_date_format')],  
expected_retcode=1)  
self.assertTrue(re.search(r'ERROR', out))  
self.assertFalse(re.search(r'feed validated successfully', out))  
htmlout = open('validation-results.html').read()  
self.assertTrue(re.search(r'in field <code>start_date', htmlout))  
self.assertTrue(re.search(r'in field <code>date', htmlout))  
self.assertFalse(re.search(r'feed validated successfully', htmlout))  
self.assertFalse(os.path.exists('transitfeedcrash.txt'))  
 
def testBadUtf8(self):  
(out, err) = self.CheckCallWithPath(  
[self.GetPath('feedvalidator.py'), '-n', '--latest_version',  
transitfeed.__version__, self.GetPath('test', 'data', 'bad_utf8')],  
expected_retcode=1)  
self.assertTrue(re.search(r'ERROR', out))  
self.assertFalse(re.search(r'feed validated successfully', out))  
htmlout = open('validation-results.html').read()  
self.assertTrue(re.search(r'Unicode error', htmlout))  
self.assertFalse(re.search(r'feed validated successfully', htmlout))  
self.assertFalse(os.path.exists('transitfeedcrash.txt'))  
 
def testFileNotFound(self):  
(out, err) = self.CheckCallWithPath(  
[self.GetPath('feedvalidator.py'), '-n', '--latest_version',  
transitfeed.__version__, 'file-not-found.zip'],  
expected_retcode=1)  
self.assertFalse(os.path.exists('transitfeedcrash.txt'))  
 
def testBadOutputPath(self):  
(out, err) = self.CheckCallWithPath(  
[self.GetPath('feedvalidator.py'), '-n', '--latest_version',  
transitfeed.__version__, '-o', 'path/does/not/exist.html',  
self.GetPath('test', 'data', 'good_feed')],  
expected_retcode=2)  
self.assertFalse(os.path.exists('transitfeedcrash.txt'))  
 
def testCrashHandler(self):  
(out, err) = self.CheckCallWithPath(  
[self.GetPath('feedvalidator.py'), '-n', '--latest_version',  
transitfeed.__version__, 'IWantMyvalidation-crash.txt'],  
expected_retcode=127)  
self.assertTrue(re.search(r'Yikes', out))  
self.assertFalse(re.search(r'feed validated successfully', out))  
crashout = open('transitfeedcrash.txt').read()  
self.assertTrue(re.search(r'For testing the feed validator crash handler',  
crashout))  
 
def testCheckVersionIsRun(self):  
(out, err) = self.CheckCallWithPath(  
[self.GetPath('feedvalidator.py'), '-n', '--latest_version',  
'100.100.100', self.GetPath('test', 'data', 'good_feed')])  
self.assertTrue(re.search(r'feed validated successfully', out))  
self.assertTrue(re.search(r'A new version 100.100.100', out))  
htmlout = open('validation-results.html').read()  
self.assertTrue(re.search(r'A new version 100.100.100', htmlout))  
self.assertFalse(re.search(r'ERROR', htmlout))  
self.assertFalse(os.path.exists('transitfeedcrash.txt'))  
 
def testCheckVersionIsRunConsoleOutput(self):  
(out, err) = self.CheckCallWithPath(  
[self.GetPath('feedvalidator.py'), '-n', '-o', 'console',  
'--latest_version=100.100.100',  
self.GetPath('test', 'data', 'good_feed')])  
self.assertTrue(re.search(r'feed validated successfully', out))  
self.assertTrue(re.search(r'A new version 100.100.100', out))  
self.assertFalse(os.path.exists('validation-results.html'))  
self.assertFalse(os.path.exists('transitfeedcrash.txt'))  
 
def testUsage(self):  
(out, err) = self.CheckCallWithPath(  
[self.GetPath('feedvalidator.py'), '--invalid_opt'], expected_retcode=2)  
self.assertMatchesRegex(r'[Uu]sage: feedvalidator.py \[options\]', err)  
self.assertMatchesRegex(r'wiki/FeedValidator', err)  
self.assertMatchesRegex(r'--output', err) # output includes all usage info  
self.assertFalse(os.path.exists('transitfeedcrash.txt'))  
self.assertFalse(os.path.exists('validation-results.html'))  
 
 
# Regression tests to ensure that CalendarSummary works properly  
# even when the feed starts in the future or expires in less than  
# 60 days  
# See http://code.google.com/p/googletransitdatafeed/issues/detail?id=204  
class CalendarSummaryTestCase(unittest.TestCase):  
 
# Test feeds starting in the future  
def testFutureFeedDoesNotCrashCalendarSummary(self):  
today = datetime.date.today()  
start_date = today + datetime.timedelta(days=20)  
end_date = today + datetime.timedelta(days=80)  
 
schedule = transitfeed.Schedule()  
service_period = schedule.GetDefaultServicePeriod()  
 
service_period.SetStartDate(start_date.strftime("%Y%m%d"))  
service_period.SetEndDate(end_date.strftime("%Y%m%d"))  
service_period.SetWeekdayService(True)  
 
result = feedvalidator.CalendarSummary(schedule)  
 
self.assertEquals(0, result['max_trips'])  
self.assertEquals(0, result['min_trips'])  
self.assertTrue(re.search("40 service dates", result['max_trips_dates']))  
 
# Test feeds ending in less than 60 days  
def testShortFeedDoesNotCrashCalendarSummary(self):  
start_date = datetime.date.today()  
end_date = start_date + datetime.timedelta(days=15)  
 
schedule = transitfeed.Schedule()  
service_period = schedule.GetDefaultServicePeriod()  
 
service_period.SetStartDate(start_date.strftime("%Y%m%d"))  
service_period.SetEndDate(end_date.strftime("%Y%m%d"))  
service_period.SetWeekdayService(True)  
 
result = feedvalidator.CalendarSummary(schedule)  
 
self.assertEquals(0, result['max_trips'])  
self.assertEquals(0, result['min_trips'])  
self.assertTrue(re.search("15 service dates", result['max_trips_dates']))  
 
# Test feeds starting in the future *and* ending in less than 60 days  
def testFutureAndShortFeedDoesNotCrashCalendarSummary(self):  
today = datetime.date.today()  
start_date = today + datetime.timedelta(days=2)  
end_date = today + datetime.timedelta(days=3)  
 
schedule = transitfeed.Schedule()  
service_period = schedule.GetDefaultServicePeriod()  
 
service_period.SetStartDate(start_date.strftime("%Y%m%d"))  
service_period.SetEndDate(end_date.strftime("%Y%m%d"))  
service_period.SetWeekdayService(True)  
 
result = feedvalidator.CalendarSummary(schedule)  
 
self.assertEquals(0, result['max_trips'])  
self.assertEquals(0, result['min_trips'])  
self.assertTrue(re.search("1 service date", result['max_trips_dates']))  
 
# Test feeds without service days  
def testFeedWithNoDaysDoesNotCrashCalendarSummary(self):  
schedule = transitfeed.Schedule()  
result = feedvalidator.CalendarSummary(schedule)  
 
self.assertEquals({}, result)  
 
 
class MockOptions:  
"""Pretend to be an optparse options object suitable for testing."""  
def __init__(self):  
self.limit_per_type = 5  
self.memory_db = True  
self.check_duplicate_trips = True  
self.latest_version = transitfeed.__version__  
self.output = 'fake-filename.zip'  
self.manual_entry = False  
self.service_gap_interval = None  
 
 
class FeedValidatorTestCase(util.TempDirTestCaseBase):  
def testBadEolContext(self):  
"""Make sure the filename is included in the report of a bad eol."""  
zipfile_mem = StringIO.StringIO(open(  
self.GetPath('test', 'data', 'good_feed.zip'), 'rb').read())  
zip = zipfile.ZipFile(zipfile_mem, 'a')  
routes_txt = zip.read('routes.txt')  
# routes_txt_modified is invalid because the first line ends with \r\n.  
routes_txt_modified = routes_txt.replace('\n', '\r\n', 1)  
self.assertNotEquals(routes_txt_modified, routes_txt)  
zip.writestr('routes.txt', routes_txt_modified)  
zip.close()  
options = MockOptions()  
output_file = StringIO.StringIO()  
feedvalidator.RunValidationOutputToFile(zipfile_mem, options, output_file)  
self.assertMatchesRegex("routes.txt", output_file.getvalue())  
 
 
class LimitPerTypeProblemReporterTestCase(unittest.TestCase):  
def assertProblemsAttribute(self, problem_type, class_name, attribute_name,  
expected):  
"""Join the value of each exception's attribute_name in order."""  
problem_attribute_list = []  
for e in self.problems.ProblemList(problem_type, class_name).problems:  
problem_attribute_list.append(getattr(e, attribute_name))  
self.assertEquals(expected, " ".join(problem_attribute_list))  
 
def testLimitOtherProblems(self):  
"""The first N of each type should be kept."""  
self.problems = feedvalidator.LimitPerTypeProblemReporter(2)  
self.problems.OtherProblem("e1", type=transitfeed.TYPE_ERROR)  
self.problems.OtherProblem("w1", type=transitfeed.TYPE_WARNING)  
self.problems.OtherProblem("e2", type=transitfeed.TYPE_ERROR)  
self.problems.OtherProblem("e3", type=transitfeed.TYPE_ERROR)  
self.problems.OtherProblem("w2", type=transitfeed.TYPE_WARNING)  
self.assertEquals(2, self.problems.WarningCount())  
self.assertEquals(3, self.problems.ErrorCount())  
 
# These are BoundedProblemList objects  
warning_bounded_list = self.problems.ProblemList(  
transitfeed.TYPE_WARNING, "OtherProblem")  
error_bounded_list = self.problems.ProblemList(  
transitfeed.TYPE_ERROR, "OtherProblem")  
 
self.assertEquals(2, warning_bounded_list.count)  
self.assertEquals(3, error_bounded_list.count)  
 
self.assertEquals(0, warning_bounded_list.dropped_count)  
self.assertEquals(1, error_bounded_list.dropped_count)  
 
self.assertProblemsAttribute(transitfeed.TYPE_ERROR, "OtherProblem",  
"description", "e1 e2")  
self.assertProblemsAttribute(transitfeed.TYPE_WARNING, "OtherProblem",  
"description", "w1 w2")  
 
def testKeepUnsorted(self):  
"""An imperfect test that insort triggers ExceptionWithContext.__cmp__."""  
# If ExceptionWithContext.__cmp__ doesn't trigger TypeError in  
# bisect.insort then the default comparison of object id will be used. The  
# id values tend to be given out in order of creation so call  
# problems._Report with objects in a different order. This test should  
# break if ExceptionWithContext.__cmp__ is removed or changed to return 0  
# or cmp(id(self), id(y)).  
exceptions = []  
for i in range(20):  
exceptions.append(transitfeed.OtherProblem(description="e%i" % i))  
exceptions = exceptions[10:] + exceptions[:10]  
self.problems = feedvalidator.LimitPerTypeProblemReporter(3)  
for e in exceptions:  
self.problems._Report(e)  
 
self.assertEquals(0, self.problems.WarningCount())  
self.assertEquals(20, self.problems.ErrorCount())  
 
bounded_list = self.problems.ProblemList(  
transitfeed.TYPE_ERROR, "OtherProblem")  
self.assertEquals(20, bounded_list.count)  
self.assertEquals(17, bounded_list.dropped_count)  
self.assertProblemsAttribute(transitfeed.TYPE_ERROR, "OtherProblem",  
"description", "e10 e11 e12")  
 
def testLimitSortedTooFastTravel(self):  
"""Sort by decreasing distance, keeping the N greatest."""  
self.problems = feedvalidator.LimitPerTypeProblemReporter(3)  
self.problems.TooFastTravel("t1", "prev stop", "next stop", 11230.4, 5,  
None)  
self.problems.TooFastTravel("t2", "prev stop", "next stop", 1120.4, 5, None)  
self.problems.TooFastTravel("t3", "prev stop", "next stop", 1130.4, 5, None)  
self.problems.TooFastTravel("t4", "prev stop", "next stop", 1230.4, 5, None)  
self.assertEquals(0, self.problems.WarningCount())  
self.assertEquals(4, self.problems.ErrorCount())  
self.assertProblemsAttribute(transitfeed.TYPE_ERROR, "TooFastTravel",  
"trip_id", "t1 t4 t3")  
 
def testLimitSortedStopTooFarFromParentStation(self):  
"""Sort by decreasing distance, keeping the N greatest."""  
self.problems = feedvalidator.LimitPerTypeProblemReporter(3)  
for i, distance in enumerate((1000, 3002.0, 1500, 2434.1, 5023.21)):  
self.problems.StopTooFarFromParentStation(  
"s%d" % i, "S %d" % i, "p%d" % i, "P %d" % i, distance)  
self.assertEquals(5, self.problems.WarningCount())  
self.assertEquals(0, self.problems.ErrorCount())  
self.assertProblemsAttribute(transitfeed.TYPE_WARNING,  
"StopTooFarFromParentStation", "stop_id", "s4 s1 s3")  
 
def testLimitSortedStopsTooClose(self):  
"""Sort by increasing distance, keeping the N closest."""  
self.problems = feedvalidator.LimitPerTypeProblemReporter(3)  
for i, distance in enumerate((4.0, 3.0, 2.5, 2.2, 1.0, 0.0)):  
self.problems.StopsTooClose(  
"Sa %d" % i, "sa%d" % i, "Sb %d" % i, "sb%d" % i, distance)  
self.assertEquals(6, self.problems.WarningCount())  
self.assertEquals(0, self.problems.ErrorCount())  
self.assertProblemsAttribute(transitfeed.TYPE_WARNING,  
"StopsTooClose", "stop_id_a", "sa5 sa4 sa3")  
 
 
class CheckVersionTestCase(util.TempDirTestCaseBase):  
def setUp(self):  
self.mock = MockURLOpen()  
 
def tearDown(self):  
self.mock = None  
feedvalidator.urlopen = urllib2.urlopen  
 
def testAssignedDifferentVersion(self):  
problems = feedvalidator.CheckVersion('100.100.100')  
self.assertTrue(re.search(r'A new version 100.100.100', problems))  
 
def testAssignedSameVersion(self):  
problems = feedvalidator.CheckVersion(transitfeed.__version__)  
self.assertEquals(problems, None)  
 
def testGetCorrectReturns(self):  
feedvalidator.urlopen = self.mock.mockedConnectSuccess  
problems = feedvalidator.CheckVersion()  
self.assertTrue(re.search(r'A new version 100.0.1', problems))  
 
def testPageNotFound(self):  
feedvalidator.urlopen = self.mock.mockedPageNotFound  
problems = feedvalidator.CheckVersion()  
self.assertTrue(re.search(r'The server couldn\'t', problems))  
self.assertTrue(re.search(r'Error code: 404', problems))  
 
def testConnectionTimeOut(self):  
feedvalidator.urlopen = self.mock.mockedConnectionTimeOut  
problems = feedvalidator.CheckVersion()  
self.assertTrue(re.search(r'We failed to reach', problems))  
self.assertTrue(re.search(r'Reason: Connection timed', problems))  
 
def testGetAddrInfoFailed(self):  
feedvalidator.urlopen = self.mock.mockedGetAddrInfoFailed  
problems = feedvalidator.CheckVersion()  
self.assertTrue(re.search(r'We failed to reach', problems))  
self.assertTrue(re.search(r'Reason: Getaddrinfo failed', problems))  
 
def testEmptyIsReturned(self):  
feedvalidator.urlopen = self.mock.mockedEmptyIsReturned  
problems = feedvalidator.CheckVersion()  
self.assertTrue(re.search(r'We had trouble parsing', problems))  
 
 
class MockURLOpen:  
"""Pretend to be a urllib2.urlopen suitable for testing."""  
def mockedConnectSuccess(self, request):  
return StringIO.StringIO('<li><a href="transitfeed-1.0.0/">transitfeed-'  
'1.0.0/</a></li><li><a href=transitfeed-100.0.1/>'  
'transitfeed-100.0.1/</a></li>')  
 
def mockedPageNotFound(self, request):  
raise HTTPError(request.get_full_url(), 404, 'Not Found',  
request.header_items(), None)  
 
def mockedConnectionTimeOut(self, request):  
raise URLError('Connection timed out')  
 
def mockedGetAddrInfoFailed(self, request):  
raise URLError('Getaddrinfo failed')  
 
def mockedEmptyIsReturned(self, request):  
return StringIO.StringIO()  
 
 
if __name__ == '__main__':  
unittest.main()  
 
 Binary files a/origin-src/transitfeed-1.2.5/test/testfeedvalidator.pyc and /dev/null differ
#!/usr/bin/python2.5  
 
# Copyright (C) 2007 Google Inc.  
#  
# Licensed under the Apache License, Version 2.0 (the "License");  
# you may not use this file except in compliance with the License.  
# You may obtain a copy of the License at  
#  
# http://www.apache.org/licenses/LICENSE-2.0  
#  
# Unless required by applicable law or agreed to in writing, software  
# distributed under the License is distributed on an "AS IS" BASIS,  
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.  
# See the License for the specific language governing permissions and  
# limitations under the License.  
 
# Unit tests for the kmlparser module.  
 
import kmlparser  
import os.path  
import shutil  
from StringIO import StringIO  
import transitfeed  
import unittest  
import util  
 
 
class TestStopsParsing(util.GetPathTestCase):  
def testSingleStop(self):  
feed = transitfeed.Schedule()  
kmlFile = self.GetTestDataPath('one_stop.kml')  
kmlparser.KmlParser().Parse(kmlFile, feed)  
stops = feed.GetStopList()  
self.assertEqual(1, len(stops))  
stop = stops[0]  
self.assertEqual(u'Stop Name', stop.stop_name)  
self.assertAlmostEqual(-93.239037, stop.stop_lon)  
self.assertAlmostEqual(44.854164, stop.stop_lat)  
write_output = StringIO()  
feed.WriteGoogleTransitFeed(write_output)  
 
def testSingleShape(self):  
feed = transitfeed.Schedule()  
kmlFile = self.GetTestDataPath('one_line.kml')  
kmlparser.KmlParser().Parse(kmlFile, feed)  
shapes = feed.GetShapeList()  
self.assertEqual(1, len(shapes))  
shape = shapes[0]  
self.assertEqual(3, len(shape.points))  
self.assertAlmostEqual(44.854240, shape.points[0][0])  
self.assertAlmostEqual(-93.238861, shape.points[0][1])  
self.assertAlmostEqual(44.853081, shape.points[1][0])  
self.assertAlmostEqual(-93.238708, shape.points[1][1])  
self.assertAlmostEqual(44.852638, shape.points[2][0])  
self.assertAlmostEqual(-93.237923, shape.points[2][1])  
write_output = StringIO()  
feed.WriteGoogleTransitFeed(write_output)  
 
 
class FullTests(util.TempDirTestCaseBase):  
def testNormalRun(self):  
shutil.copyfile(self.GetTestDataPath('one_stop.kml'), 'one_stop.kml')  
(out, err) = self.CheckCallWithPath(  
[self.GetPath('kmlparser.py'), 'one_stop.kml', 'one_stop.zip'])  
# There will be lots of problems, but ignore them  
problems = util.RecordingProblemReporter(self)  
schedule = transitfeed.Loader('one_stop.zip', problems=problems).Load()  
self.assertEquals(len(schedule.GetStopList()), 1)  
self.assertFalse(os.path.exists('transitfeedcrash.txt'))  
 
def testCommandLineError(self):  
(out, err) = self.CheckCallWithPath([self.GetPath('kmlparser.py')],  
expected_retcode=2)  
self.assertMatchesRegex(r'did not provide .+ arguments', err)  
self.assertMatchesRegex(r'[Uu]sage:', err)  
self.assertFalse(os.path.exists('transitfeedcrash.txt'))  
 
def testCrashHandler(self):  
(out, err) = self.CheckCallWithPath(  
[self.GetPath('kmlparser.py'), 'IWantMyCrash', 'output.zip'],  
stdin_str="\n", expected_retcode=127)  
self.assertMatchesRegex(r'Yikes', out)  
crashout = open('transitfeedcrash.txt').read()  
self.assertMatchesRegex(r'For testCrashHandler', crashout)  
 
 
if __name__ == '__main__':  
unittest.main()  
 
 Binary files a/origin-src/transitfeed-1.2.5/test/testkmlparser.pyc and /dev/null differ
#!/usr/bin/python2.4  
#  
# Copyright 2008 Google Inc. All Rights Reserved.  
#  
# Licensed under the Apache License, Version 2.0 (the "License");  
# you may not use this file except in compliance with the License.  
# You may obtain a copy of the License at  
#  
# http://www.apache.org/licenses/LICENSE-2.0  
#  
# Unless required by applicable law or agreed to in writing, software  
# distributed under the License is distributed on an "AS IS" BASIS,  
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.  
# See the License for the specific language governing permissions and  
# limitations under the License.  
 
"""Unit tests for the kmlwriter module."""  
 
import os  
import StringIO  
import tempfile  
import unittest  
import kmlparser  
import kmlwriter  
import transitfeed  
import util  
 
try:  
import xml.etree.ElementTree as ET # python 2.5  
except ImportError, e:  
import elementtree.ElementTree as ET # older pythons  
 
 
def DataPath(path):  
"""Return the path to a given file in the test data directory.  
 
Args:  
path: The path relative to the test data directory.  
 
Returns:  
The absolute path.  
"""  
here = os.path.dirname(__file__)  
return os.path.join(here, 'data', path)  
 
 
def _ElementToString(root):  
"""Returns the node as an XML string.  
 
Args:  
root: The ElementTree.Element instance.  
 
Returns:  
The XML string.  
"""  
output = StringIO.StringIO()  
ET.ElementTree(root).write(output, 'utf-8')  
return output.getvalue()  
 
 
class TestKMLStopsRoundtrip(unittest.TestCase):  
"""Checks to see whether all stops are preserved when going to and from KML.  
"""  
 
def setUp(self):  
fd, self.kml_output = tempfile.mkstemp('kml')  
os.close(fd)  
 
def tearDown(self):  
os.remove(self.kml_output)  
 
def runTest(self):  
gtfs_input = DataPath('good_feed.zip')  
feed1 = transitfeed.Loader(gtfs_input).Load()  
kmlwriter.KMLWriter().Write(feed1, self.kml_output)  
feed2 = transitfeed.Schedule()  
kmlparser.KmlParser().Parse(self.kml_output, feed2)  
 
stop_name_mapper = lambda x: x.stop_name  
 
stops1 = set(map(stop_name_mapper, feed1.GetStopList()))  
stops2 = set(map(stop_name_mapper, feed2.GetStopList()))  
 
self.assertEqual(stops1, stops2)  
 
 
class TestKMLGeneratorMethods(unittest.TestCase):  
"""Tests the various KML element creation methods of KMLWriter."""  
 
def setUp(self):  
self.kmlwriter = kmlwriter.KMLWriter()  
self.parent = ET.Element('parent')  
 
def testCreateFolderVisible(self):  
element = self.kmlwriter._CreateFolder(self.parent, 'folder_name')  
self.assertEqual(_ElementToString(element),  
'<Folder><name>folder_name</name></Folder>')  
 
def testCreateFolderNotVisible(self):  
element = self.kmlwriter._CreateFolder(self.parent, 'folder_name',  
visible=False)  
self.assertEqual(_ElementToString(element),  
'<Folder><name>folder_name</name>'  
'<visibility>0</visibility></Folder>')  
 
def testCreateFolderWithDescription(self):  
element = self.kmlwriter._CreateFolder(self.parent, 'folder_name',  
description='folder_desc')  
self.assertEqual(_ElementToString(element),  
'<Folder><name>folder_name</name>'  
'<description>folder_desc</description></Folder>')  
 
def testCreatePlacemark(self):  
element = self.kmlwriter._CreatePlacemark(self.parent, 'abcdef')  
self.assertEqual(_ElementToString(element),  
'<Placemark><name>abcdef</name></Placemark>')  
 
def testCreatePlacemarkWithStyle(self):  
element = self.kmlwriter._CreatePlacemark(self.parent, 'abcdef',  
style_id='ghijkl')  
self.assertEqual(_ElementToString(element),  
'<Placemark><name>abcdef</name>'  
'<styleUrl>#ghijkl</styleUrl></Placemark>')  
 
def testCreatePlacemarkNotVisible(self):  
element = self.kmlwriter._CreatePlacemark(self.parent, 'abcdef',  
visible=False)  
self.assertEqual(_ElementToString(element),  
'<Placemark><name>abcdef</name>'  
'<visibility>0</visibility></Placemark>')  
 
def testCreatePlacemarkWithDescription(self):  
element = self.kmlwriter._CreatePlacemark(self.parent, 'abcdef',  
description='ghijkl')  
self.assertEqual(_ElementToString(element),  
'<Placemark><name>abcdef</name>'  
'<description>ghijkl</description></Placemark>')  
 
def testCreateLineString(self):  
coord_list = [(2.0, 1.0), (4.0, 3.0), (6.0, 5.0)]  
element = self.kmlwriter._CreateLineString(self.parent, coord_list)  
self.assertEqual(_ElementToString(element),  
'<LineString><tessellate>1</tessellate>'  
'<coordinates>%f,%f %f,%f %f,%f</coordinates>'  
'</LineString>' % (2.0, 1.0, 4.0, 3.0, 6.0, 5.0))  
 
def testCreateLineStringWithAltitude(self):  
coord_list = [(2.0, 1.0, 10), (4.0, 3.0, 20), (6.0, 5.0, 30.0)]  
element = self.kmlwriter._CreateLineString(self.parent, coord_list)  
self.assertEqual(_ElementToString(element),  
'<LineString><tessellate>1</tessellate>'  
'<altitudeMode>absolute</altitudeMode>'  
'<coordinates>%f,%f,%f %f,%f,%f %f,%f,%f</coordinates>'  
'</LineString>' %  
(2.0, 1.0, 10.0, 4.0, 3.0, 20.0, 6.0, 5.0, 30.0))  
 
def testCreateLineStringForShape(self):  
shape = transitfeed.Shape('shape')  
shape.AddPoint(1.0, 1.0)  
shape.AddPoint(2.0, 4.0)  
shape.AddPoint(3.0, 9.0)  
element = self.kmlwriter._CreateLineStringForShape(self.parent, shape)  
self.assertEqual(_ElementToString(element),  
'<LineString><tessellate>1</tessellate>'  
'<coordinates>%f,%f %f,%f %f,%f</coordinates>'  
'</LineString>' % (1.0, 1.0, 4.0, 2.0, 9.0, 3.0))  
 
 
class TestRouteKML(unittest.TestCase):  
"""Tests the routes folder KML generation methods of KMLWriter."""  
 
def setUp(self):  
self.feed = transitfeed.Loader(DataPath('flatten_feed')).Load()  
self.kmlwriter = kmlwriter.KMLWriter()  
self.parent = ET.Element('parent')  
 
def testCreateRoutePatternsFolderNoPatterns(self):  
folder = self.kmlwriter._CreateRoutePatternsFolder(  
self.parent, self.feed.GetRoute('route_7'))  
self.assert_(folder is None)  
 
def testCreateRoutePatternsFolderOnePattern(self):  
folder = self.kmlwriter._CreateRoutePatternsFolder(  
self.parent, self.feed.GetRoute('route_1'))  
placemarks = folder.findall('Placemark')  
self.assertEquals(len(placemarks), 1)  
 
def testCreateRoutePatternsFolderTwoPatterns(self):  
folder = self.kmlwriter._CreateRoutePatternsFolder(  
self.parent, self.feed.GetRoute('route_3'))  
placemarks = folder.findall('Placemark')  
self.assertEquals(len(placemarks), 2)  
 
def testCreateRoutePatternFolderTwoEqualPatterns(self):  
folder = self.kmlwriter._CreateRoutePatternsFolder(  
self.parent, self.feed.GetRoute('route_4'))  
placemarks = folder.findall('Placemark')  
self.assertEquals(len(placemarks), 1)  
 
def testCreateRouteShapesFolderOneTripOneShape(self):  
folder = self.kmlwriter._CreateRouteShapesFolder(  
self.feed, self.parent, self.feed.GetRoute('route_1'))  
self.assertEqual(len(folder.findall('Placemark')), 1)  
 
def testCreateRouteShapesFolderTwoTripsTwoShapes(self):  
folder = self.kmlwriter._CreateRouteShapesFolder(  
self.feed, self.parent, self.feed.GetRoute('route_2'))  
self.assertEqual(len(folder.findall('Placemark')), 2)  
 
def testCreateRouteShapesFolderTwoTripsOneShape(self):  
folder = self.kmlwriter._CreateRouteShapesFolder(  
self.feed, self.parent, self.feed.GetRoute('route_3'))  
self.assertEqual(len(folder.findall('Placemark')), 1)  
 
def testCreateRouteShapesFolderTwoTripsNoShapes(self):  
folder = self.kmlwriter._CreateRouteShapesFolder(  
self.feed, self.parent, self.feed.GetRoute('route_4'))  
self.assert_(folder is None)  
 
def assertRouteFolderContainsTrips(self, tripids, folder):  
"""Assert that the route folder contains exactly tripids"""  
actual_tripds = set()  
for placemark in folder.findall('Placemark'):  
actual_tripds.add(placemark.find('name').text)  
self.assertEquals(set(tripids), actual_tripds)  
 
def testCreateTripsFolderForRouteTwoTrips(self):  
route = self.feed.GetRoute('route_2')  
folder = self.kmlwriter._CreateRouteTripsFolder(self.parent, route)  
self.assertRouteFolderContainsTrips(['route_2_1', 'route_2_2'], folder)  
 
def testCreateTripsFolderForRouteDateFilterNone(self):  
self.kmlwriter.date_filter = None  
route = self.feed.GetRoute('route_8')  
folder = self.kmlwriter._CreateRouteTripsFolder(self.parent, route)  
self.assertRouteFolderContainsTrips(['route_8_1', 'route_8_2'], folder)  
 
def testCreateTripsFolderForRouteDateFilterSet(self):  
self.kmlwriter.date_filter = '20070604'  
route = self.feed.GetRoute('route_8')  
folder = self.kmlwriter._CreateRouteTripsFolder(self.parent, route)  
self.assertRouteFolderContainsTrips(['route_8_2'], folder)  
 
def _GetTripPlacemark(self, route_folder, trip_name):  
for trip_placemark in route_folder.findall('Placemark'):  
if trip_placemark.find('name').text == trip_name:  
return trip_placemark  
 
def testCreateRouteTripsFolderAltitude0(self):  
self.kmlwriter.altitude_per_sec = 0.0  
folder = self.kmlwriter._CreateRouteTripsFolder(  
self.parent, self.feed.GetRoute('route_4'))  
trip_placemark = self._GetTripPlacemark(folder, 'route_4_1')  
self.assertEqual(_ElementToString(trip_placemark.find('LineString')),  
'<LineString><tessellate>1</tessellate>'  
'<coordinates>-117.133162,36.425288 '  
'-116.784582,36.868446 '  
'-116.817970,36.881080</coordinates></LineString>')  
 
def testCreateRouteTripsFolderAltitude1(self):  
self.kmlwriter.altitude_per_sec = 0.5  
folder = self.kmlwriter._CreateRouteTripsFolder(  
self.parent, self.feed.GetRoute('route_4'))  
trip_placemark = self._GetTripPlacemark(folder, 'route_4_1')  
self.assertEqual(_ElementToString(trip_placemark.find('LineString')),  
'<LineString><tessellate>1</tessellate>'  
'<altitudeMode>absolute</altitudeMode>'  
'<coordinates>-117.133162,36.425288,3600.000000 '  
'-116.784582,36.868446,5400.000000 '  
'-116.817970,36.881080,7200.000000</coordinates>'  
'</LineString>')  
 
def testCreateRouteTripsFolderNoTrips(self):  
folder = self.kmlwriter._CreateRouteTripsFolder(  
self.parent, self.feed.GetRoute('route_7'))  
self.assert_(folder is None)  
 
def testCreateRoutesFolderNoRoutes(self):  
schedule = transitfeed.Schedule()  
folder = self.kmlwriter._CreateRoutesFolder(schedule, self.parent)  
self.assert_(folder is None)  
 
def testCreateRoutesFolderNoRoutesWithRouteType(self):  
folder = self.kmlwriter._CreateRoutesFolder(self.feed, self.parent, 999)  
self.assert_(folder is None)  
 
def _TestCreateRoutesFolder(self, show_trips):  
self.kmlwriter.show_trips = show_trips  
folder = self.kmlwriter._CreateRoutesFolder(self.feed, self.parent)  
self.assertEquals(folder.tag, 'Folder')  
styles = self.parent.findall('Style')  
self.assertEquals(len(styles), len(self.feed.GetRouteList()))  
route_folders = folder.findall('Folder')  
self.assertEquals(len(route_folders), len(self.feed.GetRouteList()))  
 
def testCreateRoutesFolder(self):  
self._TestCreateRoutesFolder(False)  
 
def testCreateRoutesFolderShowTrips(self):  
self._TestCreateRoutesFolder(True)  
 
def testCreateRoutesFolderWithRouteType(self):  
folder = self.kmlwriter._CreateRoutesFolder(self.feed, self.parent, 1)  
route_folders = folder.findall('Folder')  
self.assertEquals(len(route_folders), 1)  
 
 
class TestShapesKML(unittest.TestCase):  
"""Tests the shapes folder KML generation methods of KMLWriter."""  
 
def setUp(self):  
self.flatten_feed = transitfeed.Loader(DataPath('flatten_feed')).Load()  
self.good_feed = transitfeed.Loader(DataPath('good_feed.zip')).Load()  
self.kmlwriter = kmlwriter.KMLWriter()  
self.parent = ET.Element('parent')  
 
def testCreateShapesFolderNoShapes(self):  
folder = self.kmlwriter._CreateShapesFolder(self.good_feed, self.parent)  
self.assertEquals(folder, None)  
 
def testCreateShapesFolder(self):  
folder = self.kmlwriter._CreateShapesFolder(self.flatten_feed, self.parent)  
placemarks = folder.findall('Placemark')  
self.assertEquals(len(placemarks), 3)  
for placemark in placemarks:  
self.assert_(placemark.find('LineString') is not None)  
 
 
class TestStopsKML(unittest.TestCase):  
"""Tests the stops folder KML generation methods of KMLWriter."""  
 
def setUp(self):  
self.feed = transitfeed.Loader(DataPath('flatten_feed')).Load()  
self.kmlwriter = kmlwriter.KMLWriter()  
self.parent = ET.Element('parent')  
 
def testCreateStopsFolderNoStops(self):  
schedule = transitfeed.Schedule()  
folder = self.kmlwriter._CreateStopsFolder(schedule, self.parent)  
self.assert_(folder is None)  
 
def testCreateStopsFolder(self):  
folder = self.kmlwriter._CreateStopsFolder(self.feed, self.parent)  
placemarks = folder.findall('Placemark')  
self.assertEquals(len(placemarks), len(self.feed.GetStopList()))  
 
 
class TestShapePointsKML(unittest.TestCase):  
"""Tests the shape points folder KML generation methods of KMLWriter."""  
 
def setUp(self):  
self.flatten_feed = transitfeed.Loader(DataPath('flatten_feed')).Load()  
self.kmlwriter = kmlwriter.KMLWriter()  
self.kmlwriter.shape_points = True  
self.parent = ET.Element('parent')  
 
def testCreateShapePointsFolder(self):  
folder = self.kmlwriter._CreateShapesFolder(self.flatten_feed, self.parent)  
shape_point_folder = folder.find('Folder')  
self.assertEquals(shape_point_folder.find('name').text,  
'shape_1 Shape Points')  
placemarks = shape_point_folder.findall('Placemark')  
self.assertEquals(len(placemarks), 4)  
for placemark in placemarks:  
self.assert_(placemark.find('Point') is not None)  
 
 
class FullTests(util.TempDirTestCaseBase):  
def testNormalRun(self):  
(out, err) = self.CheckCallWithPath(  
[self.GetPath('kmlwriter.py'), self.GetTestDataPath('good_feed.zip'),  
'good_feed.kml'])  
self.assertFalse(os.path.exists('transitfeedcrash.txt'))  
self.assertTrue(os.path.exists('good_feed.kml'))  
 
def testCommandLineError(self):  
(out, err) = self.CheckCallWithPath(  
[self.GetPath('kmlwriter.py'), '--bad_flag'], expected_retcode=2)  
self.assertMatchesRegex(r'no such option.*--bad_flag', err)  
self.assertMatchesRegex(r'--showtrips', err)  
self.assertFalse(os.path.exists('transitfeedcrash.txt'))  
 
def testCrashHandler(self):  
(out, err) = self.CheckCallWithPath(  
[self.GetPath('kmlwriter.py'), 'IWantMyCrash', 'output.zip'],  
stdin_str="\n", expected_retcode=127)  
self.assertMatchesRegex(r'Yikes', out)  
crashout = open('transitfeedcrash.txt').read()  
self.assertMatchesRegex(r'For testCrashHandler', crashout)  
 
 
if __name__ == '__main__':  
unittest.main()  
 
 Binary files a/origin-src/transitfeed-1.2.5/test/testkmlwriter.pyc and /dev/null differ
#!/usr/bin/python2.4  
#  
# Copyright 2007 Google Inc. All Rights Reserved.  
#  
# Licensed under the Apache License, Version 2.0 (the "License");  
# you may not use this file except in compliance with the License.  
# You may obtain a copy of the License at  
#  
# http://www.apache.org/licenses/LICENSE-2.0  
#  
# Unless required by applicable law or agreed to in writing, software  
# distributed under the License is distributed on an "AS IS" BASIS,  
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.  
# See the License for the specific language governing permissions and  
# limitations under the License.  
 
"""Unit tests for the merge module."""  
 
 
__author__ = 'timothy.stranex@gmail.com (Timothy Stranex)'  
 
 
import merge  
import os.path  
import re  
import StringIO  
import transitfeed  
import unittest  
import util  
import zipfile  
 
 
def CheckAttribs(a, b, attrs, assertEquals):  
"""Checks that the objects a and b have the same values for the attributes  
given in attrs. These checks are done using the given assert function.  
 
Args:  
a: The first object.  
b: The second object.  
attrs: The list of attribute names (strings).  
assertEquals: The assertEquals method from unittest.TestCase.  
"""  
# For Stop objects (and maybe others in the future) Validate converts some  
# attributes from string to native type  
a.Validate()  
b.Validate()  
for k in attrs:  
assertEquals(getattr(a, k), getattr(b, k))  
 
 
def CreateAgency():  
"""Create an transitfeed.Agency object for testing.  
 
Returns:  
The agency object.  
"""  
return transitfeed.Agency(name='agency',  
url='http://agency',  
timezone='Africa/Johannesburg',  
id='agency')  
 
 
class TestingProblemReporter(merge.MergeProblemReporterBase):  
"""This problem reporter keeps track of all problems.  
 
Attributes:  
problems: The list of problems reported.  
"""  
 
def __init__(self):  
merge.MergeProblemReporterBase.__init__(self)  
self.problems = []  
self._expect_classes = []  
 
def _Report(self, problem):  
problem.FormatProblem() # Shouldn't crash  
self.problems.append(problem)  
for problem_class in self._expect_classes:  
if isinstance(problem, problem_class):  
return  
raise problem  
 
def CheckReported(self, problem_class):  
"""Checks if a problem of the given class was reported.  
 
Args:  
problem_class: The problem class, a class inheriting from  
MergeProblemWithContext.  
 
Returns:  
True if a matching problem was reported.  
"""  
for problem in self.problems:  
if isinstance(problem, problem_class):  
return True  
return False  
 
def ExpectProblemClass(self, problem_class):  
"""Supresses exception raising for problems inheriting from this class.  
 
Args:  
problem_class: The problem class, a class inheriting from  
MergeProblemWithContext.  
"""  
self._expect_classes.append(problem_class)  
 
def assertExpectedProblemsReported(self, testcase):  
"""Asserts that every expected problem class has been reported.  
 
The assertions are done using the assert_ method of the testcase.  
 
Args:  
testcase: The unittest.TestCase instance.  
"""  
for problem_class in self._expect_classes:  
testcase.assert_(self.CheckReported(problem_class))  
 
 
class TestApproximateDistanceBetweenPoints(unittest.TestCase):  
 
def _assertWithinEpsilon(self, a, b, epsilon=1.0):  
"""Asserts that a and b are equal to within an epsilon.  
 
Args:  
a: The first value (float).  
b: The second value (float).  
epsilon: The epsilon value (float).  
"""  
self.assert_(abs(a-b) < epsilon)  
 
def testDegenerate(self):  
p = (30.0, 30.0)  
self._assertWithinEpsilon(  
merge.ApproximateDistanceBetweenPoints(p, p), 0.0)  
 
def testFar(self):  
p1 = (30.0, 30.0)  
p2 = (40.0, 40.0)  
self.assert_(merge.ApproximateDistanceBetweenPoints(p1, p2) > 1e4)  
 
 
class TestSchemedMerge(unittest.TestCase):  
 
class TestEntity:  
"""A mock entity (like Route or Stop) for testing."""  
 
def __init__(self, x, y, z):  
self.x = x  
self.y = y  
self.z = z  
 
def setUp(self):  
a_schedule = transitfeed.Schedule()  
b_schedule = transitfeed.Schedule()  
merged_schedule = transitfeed.Schedule()  
self.fm = merge.FeedMerger(a_schedule, b_schedule,  
merged_schedule,  
TestingProblemReporter())  
self.ds = merge.DataSetMerger(self.fm)  
 
def Migrate(ent, sched, newid):  
"""A migration function for the mock entity."""  
return self.TestEntity(ent.x, ent.y, ent.z)  
self.ds._Migrate = Migrate  
 
def testMergeIdentical(self):  
class TestAttrib:  
"""An object that is equal to everything."""  
 
def __cmp__(self, b):  
return 0  
 
x = 99  
a = TestAttrib()  
b = TestAttrib()  
 
self.assert_(self.ds._MergeIdentical(x, x) == x)  
self.assert_(self.ds._MergeIdentical(a, b) is b)  
self.assertRaises(merge.MergeError, self.ds._MergeIdentical, 1, 2)  
 
def testMergeIdenticalCaseInsensitive(self):  
self.assert_(self.ds._MergeIdenticalCaseInsensitive('abc', 'ABC') == 'ABC')  
self.assert_(self.ds._MergeIdenticalCaseInsensitive('abc', 'AbC') == 'AbC')  
self.assertRaises(merge.MergeError,  
self.ds._MergeIdenticalCaseInsensitive, 'abc', 'bcd')  
self.assertRaises(merge.MergeError,  
self.ds._MergeIdenticalCaseInsensitive, 'abc', 'ABCD')  
 
def testMergeOptional(self):  
x = 99  
y = 100  
 
self.assertEquals(self.ds._MergeOptional(None, None), None)  
self.assertEquals(self.ds._MergeOptional(None, x), x)  
self.assertEquals(self.ds._MergeOptional(x, None), x)  
self.assertEquals(self.ds._MergeOptional(x, x), x)  
self.assertRaises(merge.MergeError, self.ds._MergeOptional, x, y)  
 
def testMergeSameAgency(self):  
kwargs = {'name': 'xxx',  
'agency_url': 'http://www.example.com',  
'agency_timezone': 'Europe/Zurich'}  
id1 = 'agency1'  
id2 = 'agency2'  
id3 = 'agency3'  
id4 = 'agency4'  
id5 = 'agency5'  
 
a = self.fm.a_schedule.NewDefaultAgency(id=id1, **kwargs)  
b = self.fm.b_schedule.NewDefaultAgency(id=id2, **kwargs)  
c = transitfeed.Agency(id=id3, **kwargs)  
self.fm.merged_schedule.AddAgencyObject(c)  
self.fm.Register(a, b, c)  
 
d = transitfeed.Agency(id=id4, **kwargs)  
e = transitfeed.Agency(id=id5, **kwargs)  
self.fm.a_schedule.AddAgencyObject(d)  
self.fm.merged_schedule.AddAgencyObject(e)  
self.fm.Register(d, None, e)  
 
self.assertEquals(self.ds._MergeSameAgency(id1, id2), id3)  
self.assertEquals(self.ds._MergeSameAgency(None, None), id3)  
self.assertEquals(self.ds._MergeSameAgency(id1, None), id3)  
self.assertEquals(self.ds._MergeSameAgency(None, id2), id3)  
 
# id1 is not a valid agency_id in the new schedule so it cannot be merged  
self.assertRaises(KeyError, self.ds._MergeSameAgency, id1, id1)  
 
# this fails because d (id4) and b (id2) don't map to the same agency  
# in the merged schedule  
self.assertRaises(merge.MergeError, self.ds._MergeSameAgency, id4, id2)  
 
def testSchemedMerge_Success(self):  
 
def Merger(a, b):  
return a + b  
 
scheme = {'x': Merger, 'y': Merger, 'z': Merger}  
a = self.TestEntity(1, 2, 3)  
b = self.TestEntity(4, 5, 6)  
c = self.ds._SchemedMerge(scheme, a, b)  
 
self.assertEquals(c.x, 5)  
self.assertEquals(c.y, 7)  
self.assertEquals(c.z, 9)  
 
def testSchemedMerge_Failure(self):  
 
def Merger(a, b):  
raise merge.MergeError()  
 
scheme = {'x': Merger, 'y': Merger, 'z': Merger}  
a = self.TestEntity(1, 2, 3)  
b = self.TestEntity(4, 5, 6)  
 
self.assertRaises(merge.MergeError, self.ds._SchemedMerge,  
scheme, a, b)  
 
def testSchemedMerge_NoNewId(self):  
class TestDataSetMerger(merge.DataSetMerger):  
def _Migrate(self, entity, schedule, newid):  
self.newid = newid  
return entity  
dataset_merger = TestDataSetMerger(self.fm)  
a = self.TestEntity(1, 2, 3)  
b = self.TestEntity(4, 5, 6)  
dataset_merger._SchemedMerge({}, a, b)  
self.assertEquals(dataset_merger.newid, False)  
 
def testSchemedMerge_ErrorTextContainsAttributeNameAndReason(self):  
reason = 'my reason'  
attribute_name = 'long_attribute_name'  
 
def GoodMerger(a, b):  
return a + b  
 
def BadMerger(a, b):  
raise merge.MergeError(reason)  
 
a = self.TestEntity(1, 2, 3)  
setattr(a, attribute_name, 1)  
b = self.TestEntity(4, 5, 6)  
setattr(b, attribute_name, 2)  
scheme = {'x': GoodMerger, 'y': GoodMerger, 'z': GoodMerger,  
attribute_name: BadMerger}  
 
try:  
self.ds._SchemedMerge(scheme, a, b)  
except merge.MergeError, merge_error:  
error_text = str(merge_error)  
self.assert_(reason in error_text)  
self.assert_(attribute_name in error_text)  
 
 
class TestFeedMerger(unittest.TestCase):  
 
class Merger:  
def __init__(self, test, n, should_fail=False):  
self.test = test  
self.n = n  
self.should_fail = should_fail  
 
def MergeDataSets(self):  
self.test.called.append(self.n)  
return not self.should_fail  
 
def setUp(self):  
a_schedule = transitfeed.Schedule()  
b_schedule = transitfeed.Schedule()  
merged_schedule = transitfeed.Schedule()  
self.fm = merge.FeedMerger(a_schedule, b_schedule,  
merged_schedule,  
TestingProblemReporter())  
self.called = []  
 
def testDefaultProblemReporter(self):  
feed_merger = merge.FeedMerger(self.fm.a_schedule,  
self.fm.b_schedule,  
self.fm.merged_schedule,  
None)  
self.assert_(isinstance(feed_merger.problem_reporter,  
merge.MergeProblemReporterBase))  
 
def testSequence(self):  
for i in range(10):  
self.fm.AddMerger(TestFeedMerger.Merger(self, i))  
self.assert_(self.fm.MergeSchedules())  
self.assertEquals(self.called, range(10))  
 
def testStopsAfterError(self):  
for i in range(10):  
self.fm.AddMerger(TestFeedMerger.Merger(self, i, i == 5))  
self.assert_(not self.fm.MergeSchedules())  
self.assertEquals(self.called, range(6))  
 
def testRegister(self):  
self.fm.Register(1, 2, 3)  
self.assertEquals(self.fm.a_merge_map, {1: 3})  
self.assertEquals(self.fm.b_merge_map, {2: 3})  
 
def testRegisterNone(self):  
self.fm.Register(None, 2, 3)  
self.assertEquals(self.fm.a_merge_map, {})  
self.assertEquals(self.fm.b_merge_map, {2: 3})  
 
def testGenerateId_Prefix(self):  
x = 'test'  
a = self.fm.GenerateId(x)  
b = self.fm.GenerateId(x)  
self.assertNotEqual(a, b)  
self.assert_(a.startswith(x))  
self.assert_(b.startswith(x))  
 
def testGenerateId_None(self):  
a = self.fm.GenerateId(None)  
b = self.fm.GenerateId(None)  
self.assertNotEqual(a, b)  
 
def testGenerateId_InitialCounter(self):  
a_schedule = transitfeed.Schedule()  
b_schedule = transitfeed.Schedule()  
merged_schedule = transitfeed.Schedule()  
 
for i in range(10):  
agency = transitfeed.Agency(name='agency', url='http://agency',  
timezone='Africa/Johannesburg',  
id='agency_%d' % i)  
if i % 2:  
b_schedule.AddAgencyObject(agency)  
else:  
a_schedule.AddAgencyObject(agency)  
 
feed_merger = merge.FeedMerger(a_schedule, b_schedule,  
merged_schedule,  
TestingProblemReporter())  
 
# check that the postfix number of any generated ids are greater than  
# the postfix numbers of any ids in the old and new schedules  
gen_id = feed_merger.GenerateId(None)  
postfix_num = int(gen_id[gen_id.rfind('_')+1:])  
self.assert_(postfix_num >= 10)  
 
def testGetMerger(self):  
class MergerA(merge.DataSetMerger):  
pass  
 
class MergerB(merge.DataSetMerger):  
pass  
 
a = MergerA(self.fm)  
b = MergerB(self.fm)  
 
self.fm.AddMerger(a)  
self.fm.AddMerger(b)  
 
self.assertEquals(self.fm.GetMerger(MergerA), a)  
self.assertEquals(self.fm.GetMerger(MergerB), b)  
 
def testGetMerger_Error(self):  
self.assertRaises(LookupError, self.fm.GetMerger, TestFeedMerger.Merger)  
 
 
class TestServicePeriodMerger(unittest.TestCase):  
 
def setUp(self):  
a_schedule = transitfeed.Schedule()  
b_schedule = transitfeed.Schedule()  
merged_schedule = transitfeed.Schedule()  
self.fm = merge.FeedMerger(a_schedule, b_schedule, merged_schedule,  
TestingProblemReporter())  
self.spm = merge.ServicePeriodMerger(self.fm)  
self.fm.AddMerger(self.spm)  
 
def _AddTwoPeriods(self, start1, end1, start2, end2):  
sp1fields = ['test1', start1, end1] + ['1']*7  
self.sp1 = transitfeed.ServicePeriod(field_list=sp1fields)  
sp2fields = ['test2', start2, end2] + ['1']*7  
self.sp2 = transitfeed.ServicePeriod(field_list=sp2fields)  
 
self.fm.a_schedule.AddServicePeriodObject(self.sp1)  
self.fm.b_schedule.AddServicePeriodObject(self.sp2)  
 
def testCheckDisjoint_True(self):  
self._AddTwoPeriods('20071213', '20071231',  
'20080101', '20080201')  
self.assert_(self.spm.CheckDisjointCalendars())  
 
def testCheckDisjoint_False1(self):  
self._AddTwoPeriods('20071213', '20080201',  
'20080101', '20080301')  
self.assert_(not self.spm.CheckDisjointCalendars())  
 
def testCheckDisjoint_False2(self):  
self._AddTwoPeriods('20080101', '20090101',  
'20070101', '20080601')  
self.assert_(not self.spm.CheckDisjointCalendars())  
 
def testCheckDisjoint_False3(self):  
self._AddTwoPeriods('20080301', '20080901',  
'20080101', '20090101')  
self.assert_(not self.spm.CheckDisjointCalendars())  
 
def testDisjoinCalendars(self):  
self._AddTwoPeriods('20071213', '20080201',  
'20080101', '20080301')  
self.spm.DisjoinCalendars('20080101')  
self.assertEquals(self.sp1.start_date, '20071213')  
self.assertEquals(self.sp1.end_date, '20071231')  
self.assertEquals(self.sp2.start_date, '20080101')  
self.assertEquals(self.sp2.end_date, '20080301')  
 
def testDisjoinCalendars_Dates(self):  
self._AddTwoPeriods('20071213', '20080201',  
'20080101', '20080301')  
self.sp1.SetDateHasService('20071201')  
self.sp1.SetDateHasService('20081231')  
self.sp2.SetDateHasService('20071201')  
self.sp2.SetDateHasService('20081231')  
 
self.spm.DisjoinCalendars('20080101')  
 
self.assert_('20071201' in self.sp1.date_exceptions.keys())  
self.assert_('20081231' not in self.sp1.date_exceptions.keys())  
self.assert_('20071201' not in self.sp2.date_exceptions.keys())  
self.assert_('20081231' in self.sp2.date_exceptions.keys())  
 
def testUnion(self):  
self._AddTwoPeriods('20071213', '20071231',  
'20080101', '20080201')  
self.fm.problem_reporter.ExpectProblemClass(merge.MergeNotImplemented)  
self.fm.MergeSchedules()  
merged_schedule = self.fm.GetMergedSchedule()  
self.assertEquals(len(merged_schedule.GetServicePeriodList()), 2)  
 
# make fields a copy of the service period attributes except service_id  
fields = list(transitfeed.ServicePeriod._DAYS_OF_WEEK)  
fields += ['start_date', 'end_date']  
 
# now check that these attributes are preserved in the merge  
CheckAttribs(self.sp1, self.fm.a_merge_map[self.sp1], fields,  
self.assertEquals)  
CheckAttribs(self.sp2, self.fm.b_merge_map[self.sp2], fields,  
self.assertEquals)  
 
self.fm.problem_reporter.assertExpectedProblemsReported(self)  
 
def testMerge_RequiredButNotDisjoint(self):  
self._AddTwoPeriods('20070101', '20090101',  
'20080101', '20100101')  
self.fm.problem_reporter.ExpectProblemClass(merge.CalendarsNotDisjoint)  
self.assertEquals(self.spm.MergeDataSets(), False)  
self.fm.problem_reporter.assertExpectedProblemsReported(self)  
 
def testMerge_NotRequiredAndNotDisjoint(self):  
self._AddTwoPeriods('20070101', '20090101',  
'20080101', '20100101')  
self.spm.require_disjoint_calendars = False  
self.fm.problem_reporter.ExpectProblemClass(merge.MergeNotImplemented)  
self.fm.MergeSchedules()  
self.fm.problem_reporter.assertExpectedProblemsReported(self)  
 
 
class TestAgencyMerger(unittest.TestCase):  
 
def setUp(self):  
a_schedule = transitfeed.Schedule()  
b_schedule = transitfeed.Schedule()  
merged_schedule = transitfeed.Schedule()  
self.fm = merge.FeedMerger(a_schedule, b_schedule, merged_schedule,  
TestingProblemReporter())  
self.am = merge.AgencyMerger(self.fm)  
self.fm.AddMerger(self.am)  
 
self.a1 = transitfeed.Agency(id='a1', agency_name='a1',  
agency_url='http://www.a1.com',  
agency_timezone='Africa/Johannesburg',  
agency_phone='123 456 78 90')  
self.a2 = transitfeed.Agency(id='a2', agency_name='a1',  
agency_url='http://www.a1.com',  
agency_timezone='Africa/Johannesburg',  
agency_phone='789 65 43 21')  
 
def testMerge(self):  
self.a2.agency_id = self.a1.agency_id  
self.fm.a_schedule.AddAgencyObject(self.a1)  
self.fm.b_schedule.AddAgencyObject(self.a2)  
self.fm.MergeSchedules()  
 
merged_schedule = self.fm.GetMergedSchedule()  
self.assertEquals(len(merged_schedule.GetAgencyList()), 1)  
self.assertEquals(merged_schedule.GetAgencyList()[0],  
self.fm.a_merge_map[self.a1])  
self.assertEquals(self.fm.a_merge_map[self.a1],  
self.fm.b_merge_map[self.a2])  
# differing values such as agency_phone should be taken from self.a2  
self.assertEquals(merged_schedule.GetAgencyList()[0], self.a2)  
self.assertEquals(self.am.GetMergeStats(), (1, 0, 0))  
 
# check that id is preserved  
self.assertEquals(self.fm.a_merge_map[self.a1].agency_id,  
self.a1.agency_id)  
 
def testNoMerge_DifferentId(self):  
self.fm.a_schedule.AddAgencyObject(self.a1)  
self.fm.b_schedule.AddAgencyObject(self.a2)  
self.fm.MergeSchedules()  
 
merged_schedule = self.fm.GetMergedSchedule()  
self.assertEquals(len(merged_schedule.GetAgencyList()), 2)  
 
self.assert_(self.fm.a_merge_map[self.a1] in  
merged_schedule.GetAgencyList())  
self.assert_(self.fm.b_merge_map[self.a2] in  
merged_schedule.GetAgencyList())  
self.assertEquals(self.a1, self.fm.a_merge_map[self.a1])  
self.assertEquals(self.a2, self.fm.b_merge_map[self.a2])  
self.assertEquals(self.am.GetMergeStats(), (0, 1, 1))  
 
# check that the ids are preserved  
self.assertEquals(self.fm.a_merge_map[self.a1].agency_id,  
self.a1.agency_id)  
self.assertEquals(self.fm.b_merge_map[self.a2].agency_id,  
self.a2.agency_id)  
 
def testNoMerge_SameId(self):  
# Force a1.agency_id to be unicode to make sure it is correctly encoded  
# to utf-8 before concatinating to the agency_name containing non-ascii  
# characters.  
self.a1.agency_id = unicode(self.a1.agency_id)  
self.a2.agency_id = str(self.a1.agency_id)  
self.a2.agency_name = 'different \xc3\xa9'  
self.fm.a_schedule.AddAgencyObject(self.a1)  
self.fm.b_schedule.AddAgencyObject(self.a2)  
 
self.fm.problem_reporter.ExpectProblemClass(merge.SameIdButNotMerged)  
self.fm.MergeSchedules()  
 
merged_schedule = self.fm.GetMergedSchedule()  
self.assertEquals(len(merged_schedule.GetAgencyList()), 2)  
self.assertEquals(self.am.GetMergeStats(), (0, 1, 1))  
 
# check that the merged entities have different ids  
self.assertNotEqual(self.fm.a_merge_map[self.a1].agency_id,  
self.fm.b_merge_map[self.a2].agency_id)  
 
self.fm.problem_reporter.assertExpectedProblemsReported(self)  
 
 
class TestStopMerger(unittest.TestCase):  
 
def setUp(self):  
a_schedule = transitfeed.Schedule()  
b_schedule = transitfeed.Schedule()  
merged_schedule = transitfeed.Schedule()  
self.fm = merge.FeedMerger(a_schedule, b_schedule, merged_schedule,  
TestingProblemReporter())  
self.sm = merge.StopMerger(self.fm)  
self.fm.AddMerger(self.sm)  
 
self.s1 = transitfeed.Stop(30.0, 30.0,  
u'Andr\202' , 's1')  
self.s1.stop_desc = 'stop 1'  
self.s1.stop_url = 'http://stop/1'  
self.s1.zone_id = 'zone1'  
self.s2 = transitfeed.Stop(30.0, 30.0, 's2', 's2')  
self.s2.stop_desc = 'stop 2'  
self.s2.stop_url = 'http://stop/2'  
self.s2.zone_id = 'zone1'  
 
def testMerge(self):  
self.s2.stop_id = self.s1.stop_id  
self.s2.stop_name = self.s1.stop_name  
self.s1.location_type = 1  
self.s2.location_type = 1  
 
self.fm.a_schedule.AddStopObject(self.s1)  
self.fm.b_schedule.AddStopObject(self.s2)  
self.fm.MergeSchedules()  
 
merged_schedule = self.fm.GetMergedSchedule()  
self.assertEquals(len(merged_schedule.GetStopList()), 1)  
self.assertEquals(merged_schedule.GetStopList()[0],  
self.fm.a_merge_map[self.s1])  
self.assertEquals(self.fm.a_merge_map[self.s1],  
self.fm.b_merge_map[self.s2])  
self.assertEquals(self.sm.GetMergeStats(), (1, 0, 0))  
 
# check that the remaining attributes are taken from the new stop  
fields = ['stop_name', 'stop_lat', 'stop_lon', 'stop_desc', 'stop_url',  
'location_type']  
CheckAttribs(self.fm.a_merge_map[self.s1], self.s2, fields,  
self.assertEquals)  
 
# check that the id is preserved  
self.assertEquals(self.fm.a_merge_map[self.s1].stop_id, self.s1.stop_id)  
 
# check that the zone_id is preserved  
self.assertEquals(self.fm.a_merge_map[self.s1].zone_id, self.s1.zone_id)  
 
def testNoMerge_DifferentId(self):  
self.fm.a_schedule.AddStopObject(self.s1)  
self.fm.b_schedule.AddStopObject(self.s2)  
self.fm.MergeSchedules()  
 
merged_schedule = self.fm.GetMergedSchedule()  
self.assertEquals(len(merged_schedule.GetStopList()), 2)  
self.assert_(self.fm.a_merge_map[self.s1] in merged_schedule.GetStopList())  
self.assert_(self.fm.b_merge_map[self.s2] in merged_schedule.GetStopList())  
self.assertEquals(self.sm.GetMergeStats(), (0, 1, 1))  
 
def testNoMerge_DifferentName(self):  
self.s2.stop_id = self.s1.stop_id  
self.fm.a_schedule.AddStopObject(self.s1)  
self.fm.b_schedule.AddStopObject(self.s2)  
self.fm.problem_reporter.ExpectProblemClass(merge.SameIdButNotMerged)  
self.fm.MergeSchedules()  
 
merged_schedule = self.fm.GetMergedSchedule()  
self.assertEquals(len(merged_schedule.GetStopList()), 2)  
self.assert_(self.fm.a_merge_map[self.s1] in merged_schedule.GetStopList())  
self.assert_(self.fm.b_merge_map[self.s2] in merged_schedule.GetStopList())  
self.assertEquals(self.sm.GetMergeStats(), (0, 1, 1))  
 
def testNoMerge_FarApart(self):  
self.s2.stop_id = self.s1.stop_id  
self.s2.stop_name = self.s1.stop_name  
self.s2.stop_lat = 40.0  
self.s2.stop_lon = 40.0  
 
self.fm.a_schedule.AddStopObject(self.s1)  
self.fm.b_schedule.AddStopObject(self.s2)  
self.fm.problem_reporter.ExpectProblemClass(merge.SameIdButNotMerged)  
self.fm.MergeSchedules()  
 
merged_schedule = self.fm.GetMergedSchedule()  
self.assertEquals(len(merged_schedule.GetStopList()), 2)  
self.assert_(self.fm.a_merge_map[self.s1] in merged_schedule.GetStopList())  
self.assert_(self.fm.b_merge_map[self.s2] in merged_schedule.GetStopList())  
self.assertEquals(self.sm.GetMergeStats(), (0, 1, 1))  
 
# check that the merged ids are different  
self.assertNotEquals(self.fm.a_merge_map[self.s1].stop_id,  
self.fm.b_merge_map[self.s2].stop_id)  
 
self.fm.problem_reporter.assertExpectedProblemsReported(self)  
 
def testMerge_CaseInsensitive(self):  
self.s2.stop_id = self.s1.stop_id  
self.s2.stop_name = self.s1.stop_name.upper()  
self.fm.a_schedule.AddStopObject(self.s1)  
self.fm.b_schedule.AddStopObject(self.s2)  
self.fm.MergeSchedules()  
merged_schedule = self.fm.GetMergedSchedule()  
self.assertEquals(len(merged_schedule.GetStopList()), 1)  
self.assertEquals(self.sm.GetMergeStats(), (1, 0, 0))  
 
def testNoMerge_ZoneId(self):  
self.s2.zone_id = 'zone2'  
self.fm.a_schedule.AddStopObject(self.s1)  
self.fm.b_schedule.AddStopObject(self.s2)  
self.fm.MergeSchedules()  
 
merged_schedule = self.fm.GetMergedSchedule()  
self.assertEquals(len(merged_schedule.GetStopList()), 2)  
 
self.assert_(self.s1.zone_id in self.fm.a_zone_map)  
self.assert_(self.s2.zone_id in self.fm.b_zone_map)  
self.assertEquals(self.sm.GetMergeStats(), (0, 1, 1))  
 
# check that the zones are still different  
self.assertNotEqual(self.fm.a_merge_map[self.s1].zone_id,  
self.fm.b_merge_map[self.s2].zone_id)  
 
def testZoneId_SamePreservation(self):  
# checks that if the zone_ids of some stops are the same before the  
# merge, they are still the same after.  
self.fm.a_schedule.AddStopObject(self.s1)  
self.fm.a_schedule.AddStopObject(self.s2)  
self.fm.MergeSchedules()  
self.assertEquals(self.fm.a_merge_map[self.s1].zone_id,  
self.fm.a_merge_map[self.s2].zone_id)  
 
def testZoneId_DifferentSchedules(self):  
# zone_ids may be the same in different schedules but unless the stops  
# are merged, they should map to different zone_ids  
self.fm.a_schedule.AddStopObject(self.s1)  
self.fm.b_schedule.AddStopObject(self.s2)  
self.fm.MergeSchedules()  
self.assertNotEquals(self.fm.a_merge_map[self.s1].zone_id,  
self.fm.b_merge_map[self.s2].zone_id)  
 
def testZoneId_MergePreservation(self):  
# check that if two stops are merged, the zone mapping is used for all  
# other stops too  
self.s2.stop_id = self.s1.stop_id  
self.s2.stop_name = self.s1.stop_name  
s3 = transitfeed.Stop(field_dict=self.s1)  
s3.stop_id = 'different'  
 
self.fm.a_schedule.AddStopObject(self.s1)  
self.fm.a_schedule.AddStopObject(s3)  
self.fm.b_schedule.AddStopObject(self.s2)  
self.fm.MergeSchedules()  
 
self.assertEquals(self.fm.a_merge_map[self.s1].zone_id,  
self.fm.a_merge_map[s3].zone_id)  
self.assertEquals(self.fm.a_merge_map[s3].zone_id,  
self.fm.b_merge_map[self.s2].zone_id)  
 
def testMergeStationType(self):  
self.s2.stop_id = self.s1.stop_id  
self.s2.stop_name = self.s1.stop_name  
self.s1.location_type = 1  
self.s2.location_type = 1  
self.fm.a_schedule.AddStopObject(self.s1)  
self.fm.b_schedule.AddStopObject(self.s2)  
self.fm.MergeSchedules()  
merged_stops = self.fm.GetMergedSchedule().GetStopList()  
self.assertEquals(len(merged_stops), 1)  
self.assertEquals(merged_stops[0].location_type, 1)  
 
def testMergeDifferentTypes(self):  
self.s2.stop_id = self.s1.stop_id  
self.s2.stop_name = self.s1.stop_name  
self.s2.location_type = 1  
self.fm.a_schedule.AddStopObject(self.s1)  
self.fm.b_schedule.AddStopObject(self.s2)  
try:  
self.fm.MergeSchedules()  
self.fail("Expecting MergeError")  
except merge.SameIdButNotMerged, merge_error:  
self.assertTrue(("%s" % merge_error).find("location_type") != -1)  
 
def AssertS1ParentIsS2(self):  
"""Assert that the merged s1 has parent s2."""  
new_s1 = self.fm.GetMergedObject(self.s1)  
new_s2 = self.fm.GetMergedObject(self.s2)  
self.assertEquals(new_s1.parent_station, new_s2.stop_id)  
self.assertEquals(new_s2.parent_station, None)  
self.assertEquals(new_s1.location_type, 0)  
self.assertEquals(new_s2.location_type, 1)  
 
def testMergeMaintainParentRelationship(self):  
self.s2.location_type = 1  
self.s1.parent_station = self.s2.stop_id  
self.fm.a_schedule.AddStopObject(self.s1)  
self.fm.a_schedule.AddStopObject(self.s2)  
self.fm.MergeSchedules()  
self.AssertS1ParentIsS2()  
 
def testParentRelationshipAfterMerge(self):  
s3 = transitfeed.Stop(field_dict=self.s1)  
s3.parent_station = self.s2.stop_id  
self.s2.location_type = 1  
self.fm.a_schedule.AddStopObject(self.s1)  
self.fm.b_schedule.AddStopObject(self.s2)  
self.fm.b_schedule.AddStopObject(s3)  
self.fm.MergeSchedules()  
self.AssertS1ParentIsS2()  
 
def testParentRelationshipWithNewParentid(self):  
self.s2.location_type = 1  
self.s1.parent_station = self.s2.stop_id  
# s3 will have a stop_id conflict with self.s2 so parent_id of the  
# migrated self.s1 will need to be updated  
s3 = transitfeed.Stop(field_dict=self.s2)  
s3.stop_lat = 45  
self.fm.a_schedule.AddStopObject(s3)  
self.fm.b_schedule.AddStopObject(self.s1)  
self.fm.b_schedule.AddStopObject(self.s2)  
self.fm.problem_reporter.ExpectProblemClass(merge.SameIdButNotMerged)  
self.fm.MergeSchedules()  
self.assertNotEquals(self.fm.GetMergedObject(s3).stop_id,  
self.fm.GetMergedObject(self.s2).stop_id)  
# Check that s3 got a new id  
self.assertNotEquals(self.s2.stop_id,  
self.fm.GetMergedObject(self.s2).stop_id)  
self.AssertS1ParentIsS2()  
 
def _AddStopsApart(self):  
"""Adds two stops to the schedules and returns the distance between them.  
 
Returns:  
The distance between the stops in metres, a value greater than zero.  
"""  
self.s2.stop_id = self.s1.stop_id  
self.s2.stop_name = self.s1.stop_name  
self.s2.stop_lat += 1.0e-3  
self.fm.a_schedule.AddStopObject(self.s1)  
self.fm.b_schedule.AddStopObject(self.s2)  
return transitfeed.ApproximateDistanceBetweenStops(self.s1, self.s2)  
 
def testSetLargestStopDistanceSmall(self):  
largest_stop_distance = self._AddStopsApart() * 0.5  
self.sm.SetLargestStopDistance(largest_stop_distance)  
self.assertEquals(self.sm.largest_stop_distance, largest_stop_distance)  
self.fm.problem_reporter.ExpectProblemClass(merge.SameIdButNotMerged)  
self.fm.MergeSchedules()  
self.assertEquals(len(self.fm.GetMergedSchedule().GetStopList()), 2)  
self.fm.problem_reporter.assertExpectedProblemsReported(self)  
 
def testSetLargestStopDistanceLarge(self):  
largest_stop_distance = self._AddStopsApart() * 2.0  
self.sm.SetLargestStopDistance(largest_stop_distance)  
self.assertEquals(self.sm.largest_stop_distance, largest_stop_distance)  
self.fm.MergeSchedules()  
self.assertEquals(len(self.fm.GetMergedSchedule().GetStopList()), 1)  
 
 
class TestRouteMerger(unittest.TestCase):  
 
fields = ['route_short_name', 'route_long_name', 'route_type',  
'route_url']  
 
def setUp(self):  
a_schedule = transitfeed.Schedule()  
b_schedule = transitfeed.Schedule()  
merged_schedule = transitfeed.Schedule()  
self.fm = merge.FeedMerger(a_schedule, b_schedule, merged_schedule,  
TestingProblemReporter())  
self.fm.AddMerger(merge.AgencyMerger(self.fm))  
self.rm = merge.RouteMerger(self.fm)  
self.fm.AddMerger(self.rm)  
 
akwargs = {'id': 'a1',  
'agency_name': 'a1',  
'agency_url': 'http://www.a1.com',  
'agency_timezone': 'Europe/Zurich'}  
self.a1 = transitfeed.Agency(**akwargs)  
self.a2 = transitfeed.Agency(**akwargs)  
a_schedule.AddAgencyObject(self.a1)  
b_schedule.AddAgencyObject(self.a2)  
 
rkwargs = {'route_id': 'r1',  
'agency_id': 'a1',  
'short_name': 'r1',  
'long_name': 'r1r1',  
'route_type': '0'}  
self.r1 = transitfeed.Route(**rkwargs)  
self.r2 = transitfeed.Route(**rkwargs)  
self.r2.route_url = 'http://route/2'  
 
def testMerge(self):  
self.fm.a_schedule.AddRouteObject(self.r1)  
self.fm.b_schedule.AddRouteObject(self.r2)  
self.fm.MergeSchedules()  
 
merged_schedule = self.fm.GetMergedSchedule()  
self.assertEquals(len(merged_schedule.GetRouteList()), 1)  
r = merged_schedule.GetRouteList()[0]  
self.assert_(self.fm.a_merge_map[self.r1] is r)  
self.assert_(self.fm.b_merge_map[self.r2] is r)  
CheckAttribs(self.r2, r, self.fields, self.assertEquals)  
self.assertEquals(r.agency_id, self.fm.a_merge_map[self.a1].agency_id)  
self.assertEquals(self.rm.GetMergeStats(), (1, 0, 0))  
 
# check that the id is preserved  
self.assertEquals(self.fm.a_merge_map[self.r1].route_id, self.r1.route_id)  
 
def testMergeNoAgency(self):  
self.r1.agency_id = None  
self.r2.agency_id = None  
self.fm.a_schedule.AddRouteObject(self.r1)  
self.fm.b_schedule.AddRouteObject(self.r2)  
self.fm.MergeSchedules()  
 
merged_schedule = self.fm.GetMergedSchedule()  
self.assertEquals(len(merged_schedule.GetRouteList()), 1)  
r = merged_schedule.GetRouteList()[0]  
CheckAttribs(self.r2, r, self.fields, self.assertEquals)  
# Merged route has copy of default agency_id  
self.assertEquals(r.agency_id, self.a1.agency_id)  
self.assertEquals(self.rm.GetMergeStats(), (1, 0, 0))  
 
# check that the id is preserved  
self.assertEquals(self.fm.a_merge_map[self.r1].route_id, self.r1.route_id)  
 
def testMigrateNoAgency(self):  
self.r1.agency_id = None  
self.fm.a_schedule.AddRouteObject(self.r1)  
self.fm.MergeSchedules()  
merged_schedule = self.fm.GetMergedSchedule()  
self.assertEquals(len(merged_schedule.GetRouteList()), 1)  
r = merged_schedule.GetRouteList()[0]  
CheckAttribs(self.r1, r, self.fields, self.assertEquals)  
# Migrated route has copy of default agency_id  
self.assertEquals(r.agency_id, self.a1.agency_id)  
 
def testNoMerge_DifferentId(self):  
self.r2.route_id = 'r2'  
self.fm.a_schedule.AddRouteObject(self.r1)  
self.fm.b_schedule.AddRouteObject(self.r2)  
self.fm.MergeSchedules()  
self.assertEquals(len(self.fm.GetMergedSchedule().GetRouteList()), 2)  
self.assertEquals(self.rm.GetMergeStats(), (0, 1, 1))  
 
def testNoMerge_SameId(self):  
self.r2.route_short_name = 'different'  
self.fm.a_schedule.AddRouteObject(self.r1)  
self.fm.b_schedule.AddRouteObject(self.r2)  
self.fm.problem_reporter.ExpectProblemClass(merge.SameIdButNotMerged)  
self.fm.MergeSchedules()  
self.assertEquals(len(self.fm.GetMergedSchedule().GetRouteList()), 2)  
self.assertEquals(self.rm.GetMergeStats(), (0, 1, 1))  
 
# check that the merged ids are different  
self.assertNotEquals(self.fm.a_merge_map[self.r1].route_id,  
self.fm.b_merge_map[self.r2].route_id)  
 
self.fm.problem_reporter.assertExpectedProblemsReported(self)  
 
 
class TestTripMerger(unittest.TestCase):  
 
def setUp(self):  
a_schedule = transitfeed.Schedule()  
b_schedule = transitfeed.Schedule()  
merged_schedule = transitfeed.Schedule()  
self.fm = merge.FeedMerger(a_schedule, b_schedule, merged_schedule,  
TestingProblemReporter())  
self.fm.AddDefaultMergers()  
self.tm = self.fm.GetMerger(merge.TripMerger)  
 
akwargs = {'id': 'a1',  
'agency_name': 'a1',  
'agency_url': 'http://www.a1.com',  
'agency_timezone': 'Europe/Zurich'}  
self.a1 = transitfeed.Agency(**akwargs)  
 
rkwargs = {'route_id': 'r1',  
'agency_id': 'a1',  
'short_name': 'r1',  
'long_name': 'r1r1',  
'route_type': '0'}  
self.r1 = transitfeed.Route(**rkwargs)  
 
self.s1 = transitfeed.ServicePeriod('s1')  
self.s1.start_date = '20071201'  
self.s1.end_date = '20071231'  
self.s1.SetWeekdayService()  
 
self.shape = transitfeed.Shape('shape1')  
self.shape.AddPoint(30.0, 30.0)  
 
self.t1 = transitfeed.Trip(service_period=self.s1,  
route=self.r1, trip_id='t1')  
self.t2 = transitfeed.Trip(service_period=self.s1,  
route=self.r1, trip_id='t2')  
# Must add self.t1 to a schedule before calling self.t1.AddStopTime  
a_schedule.AddTripObject(self.t1, validate=False)  
a_schedule.AddTripObject(self.t2, validate=False)  
self.t1.block_id = 'b1'  
self.t2.block_id = 'b1'  
self.t1.shape_id = 'shape1'  
 
self.stop = transitfeed.Stop(30.0, 30.0, stop_id='stop1')  
self.t1.AddStopTime(self.stop, arrival_secs=0, departure_secs=0)  
 
a_schedule.AddAgencyObject(self.a1)  
a_schedule.AddStopObject(self.stop)  
a_schedule.AddRouteObject(self.r1)  
a_schedule.AddServicePeriodObject(self.s1)  
a_schedule.AddShapeObject(self.shape)  
 
def testMigrate(self):  
self.fm.problem_reporter.ExpectProblemClass(merge.MergeNotImplemented)  
self.fm.MergeSchedules()  
self.fm.problem_reporter.assertExpectedProblemsReported(self)  
 
r = self.fm.a_merge_map[self.r1]  
s = self.fm.a_merge_map[self.s1]  
shape = self.fm.a_merge_map[self.shape]  
t1 = self.fm.a_merge_map[self.t1]  
t2 = self.fm.a_merge_map[self.t2]  
 
self.assertEquals(t1.route_id, r.route_id)  
self.assertEquals(t1.service_id, s.service_id)  
self.assertEquals(t1.shape_id, shape.shape_id)  
self.assertEquals(t1.block_id, t2.block_id)  
 
self.assertEquals(len(t1.GetStopTimes()), 1)  
st = t1.GetStopTimes()[0]  
self.assertEquals(st.stop, self.fm.a_merge_map[self.stop])  
 
def testReportsNotImplementedProblem(self):  
self.fm.problem_reporter.ExpectProblemClass(merge.MergeNotImplemented)  
self.fm.MergeSchedules()  
self.fm.problem_reporter.assertExpectedProblemsReported(self)  
 
def testMergeStats(self):  
self.assert_(self.tm.GetMergeStats() is None)  
 
def testConflictingTripid(self):  
a1_in_b = transitfeed.Agency(field_dict=self.a1)  
r1_in_b = transitfeed.Route(field_dict=self.r1)  
t1_in_b = transitfeed.Trip(field_dict=self.t1)  
shape_in_b = transitfeed.Shape('shape1')  
shape_in_b.AddPoint(30.0, 30.0)  
s_in_b = transitfeed.ServicePeriod('s1')  
s_in_b.start_date = '20080101'  
s_in_b.end_date = '20080131'  
s_in_b.SetWeekdayService()  
 
self.fm.b_schedule.AddAgencyObject(a1_in_b)  
self.fm.b_schedule.AddRouteObject(r1_in_b)  
self.fm.b_schedule.AddShapeObject(shape_in_b)  
self.fm.b_schedule.AddTripObject(t1_in_b, validate=False)  
self.fm.b_schedule.AddServicePeriodObject(s_in_b, validate=False)  
self.fm.problem_reporter.ExpectProblemClass(merge.MergeNotImplemented)  
self.fm.MergeSchedules()  
# 3 trips moved to merged_schedule: from a_schedule t1, t2 and from  
# b_schedule t1  
self.assertEquals(len(self.fm.merged_schedule.GetTripList()), 3)  
 
 
class TestFareMerger(unittest.TestCase):  
 
def setUp(self):  
a_schedule = transitfeed.Schedule()  
b_schedule = transitfeed.Schedule()  
merged_schedule = transitfeed.Schedule()  
self.fm = merge.FeedMerger(a_schedule, b_schedule, merged_schedule,  
TestingProblemReporter())  
self.faremerger = merge.FareMerger(self.fm)  
self.fm.AddMerger(self.faremerger)  
 
self.f1 = transitfeed.Fare('f1', '10', 'ZAR', '1', '0')  
self.f2 = transitfeed.Fare('f2', '10', 'ZAR', '1', '0')  
 
def testMerge(self):  
self.f2.fare_id = self.f1.fare_id  
self.fm.a_schedule.AddFareObject(self.f1)  
self.fm.b_schedule.AddFareObject(self.f2)  
self.fm.MergeSchedules()  
self.assertEquals(len(self.fm.merged_schedule.GetFareList()), 1)  
self.assertEquals(self.faremerger.GetMergeStats(), (1, 0, 0))  
 
# check that the id is preserved  
self.assertEquals(self.fm.a_merge_map[self.f1].fare_id, self.f1.fare_id)  
 
def testNoMerge_DifferentPrice(self):  
self.f2.fare_id = self.f1.fare_id  
self.f2.price = 11.0  
self.fm.a_schedule.AddFareObject(self.f1)  
self.fm.b_schedule.AddFareObject(self.f2)  
self.fm.problem_reporter.ExpectProblemClass(merge.SameIdButNotMerged)  
self.fm.MergeSchedules()  
self.assertEquals(len(self.fm.merged_schedule.GetFareList()), 2)  
self.assertEquals(self.faremerger.GetMergeStats(), (0, 1, 1))  
 
# check that the merged ids are different  
self.assertNotEquals(self.fm.a_merge_map[self.f1].fare_id,  
self.fm.b_merge_map[self.f2].fare_id)  
 
self.fm.problem_reporter.assertExpectedProblemsReported(self)  
 
def testNoMerge_DifferentId(self):  
self.fm.a_schedule.AddFareObject(self.f1)  
self.fm.b_schedule.AddFareObject(self.f2)  
self.fm.MergeSchedules()  
self.assertEquals(len(self.fm.merged_schedule.GetFareList()), 2)  
self.assertEquals(self.faremerger.GetMergeStats(), (0, 1, 1))  
 
# check that the ids are preserved  
self.assertEquals(self.fm.a_merge_map[self.f1].fare_id, self.f1.fare_id)  
self.assertEquals(self.fm.b_merge_map[self.f2].fare_id, self.f2.fare_id)  
 
 
class TestShapeMerger(unittest.TestCase):  
 
def setUp(self):  
a_schedule = transitfeed.Schedule()  
b_schedule = transitfeed.Schedule()  
merged_schedule = transitfeed.Schedule()  
self.fm = merge.FeedMerger(a_schedule, b_schedule, merged_schedule,  
TestingProblemReporter())  
self.sm = merge.ShapeMerger(self.fm)  
self.fm.AddMerger(self.sm)  
 
# setup some shapes  
# s1 and s2 have the same endpoints but take different paths  
# s3 has different endpoints to s1 and s2  
 
self.s1 = transitfeed.Shape('s1')  
self.s1.AddPoint(30.0, 30.0)  
self.s1.AddPoint(40.0, 30.0)  
self.s1.AddPoint(50.0, 50.0)  
 
self.s2 = transitfeed.Shape('s2')  
self.s2.AddPoint(30.0, 30.0)  
self.s2.AddPoint(40.0, 35.0)  
self.s2.AddPoint(50.0, 50.0)  
 
self.s3 = transitfeed.Shape('s3')  
self.s3.AddPoint(31.0, 31.0)  
self.s3.AddPoint(45.0, 35.0)  
self.s3.AddPoint(51.0, 51.0)  
 
def testMerge(self):  
self.s2.shape_id = self.s1.shape_id  
self.fm.a_schedule.AddShapeObject(self.s1)  
self.fm.b_schedule.AddShapeObject(self.s2)  
self.fm.MergeSchedules()  
self.assertEquals(len(self.fm.merged_schedule.GetShapeList()), 1)  
self.assertEquals(self.fm.merged_schedule.GetShapeList()[0], self.s2)  
self.assertEquals(self.sm.GetMergeStats(), (1, 0, 0))  
 
# check that the id is preserved  
self.assertEquals(self.fm.a_merge_map[self.s1].shape_id, self.s1.shape_id)  
 
def testNoMerge_DifferentId(self):  
self.fm.a_schedule.AddShapeObject(self.s1)  
self.fm.b_schedule.AddShapeObject(self.s2)  
self.fm.MergeSchedules()  
self.assertEquals(len(self.fm.merged_schedule.GetShapeList()), 2)  
self.assertEquals(self.s1, self.fm.a_merge_map[self.s1])  
self.assertEquals(self.s2, self.fm.b_merge_map[self.s2])  
self.assertEquals(self.sm.GetMergeStats(), (0, 1, 1))  
 
# check that the ids are preserved  
self.assertEquals(self.fm.a_merge_map[self.s1].shape_id, self.s1.shape_id)  
self.assertEquals(self.fm.b_merge_map[self.s2].shape_id, self.s2.shape_id)  
 
def testNoMerge_FarEndpoints(self):  
self.s3.shape_id = self.s1.shape_id  
self.fm.a_schedule.AddShapeObject(self.s1)  
self.fm.b_schedule.AddShapeObject(self.s3)  
self.fm.problem_reporter.ExpectProblemClass(merge.SameIdButNotMerged)  
self.fm.MergeSchedules()  
self.assertEquals(len(self.fm.merged_schedule.GetShapeList()), 2)  
self.assertEquals(self.s1, self.fm.a_merge_map[self.s1])  
self.assertEquals(self.s3, self.fm.b_merge_map[self.s3])  
self.assertEquals(self.sm.GetMergeStats(), (0, 1, 1))  
 
# check that the ids are different  
self.assertNotEquals(self.fm.a_merge_map[self.s1].shape_id,  
self.fm.b_merge_map[self.s3].shape_id)  
 
self.fm.problem_reporter.assertExpectedProblemsReported(self)  
 
def _AddShapesApart(self):  
"""Adds two shapes to the schedules.  
 
The maximum of the distances between the endpoints is returned.  
 
Returns:  
The distance in metres, a value greater than zero.  
"""  
self.s3.shape_id = self.s1.shape_id  
self.fm.a_schedule.AddShapeObject(self.s1)  
self.fm.b_schedule.AddShapeObject(self.s3)  
distance1 = merge.ApproximateDistanceBetweenPoints(  
self.s1.points[0][:2], self.s3.points[0][:2])  
distance2 = merge.ApproximateDistanceBetweenPoints(  
self.s1.points[-1][:2], self.s3.points[-1][:2])  
return max(distance1, distance2)  
 
def testSetLargestShapeDistanceSmall(self):  
largest_shape_distance = self._AddShapesApart() * 0.5  
self.sm.SetLargestShapeDistance(largest_shape_distance)  
self.assertEquals(self.sm.largest_shape_distance, largest_shape_distance)  
self.fm.problem_reporter.ExpectProblemClass(merge.SameIdButNotMerged)  
self.fm.MergeSchedules()  
self.assertEquals(len(self.fm.GetMergedSchedule().GetShapeList()), 2)  
self.fm.problem_reporter.assertExpectedProblemsReported(self)  
 
def testSetLargestShapeDistanceLarge(self):  
largest_shape_distance = self._AddShapesApart() * 2.0  
self.sm.SetLargestShapeDistance(largest_shape_distance)  
self.assertEquals(self.sm.largest_shape_distance, largest_shape_distance)  
self.fm.MergeSchedules()  
self.assertEquals(len(self.fm.GetMergedSchedule().GetShapeList()), 1)  
 
 
class TestFareRuleMerger(unittest.TestCase):  
 
def setUp(self):  
a_schedule = transitfeed.Schedule()  
b_schedule = transitfeed.Schedule()  
merged_schedule = transitfeed.Schedule()  
self.fm = merge.FeedMerger(a_schedule, b_schedule, merged_schedule,  
TestingProblemReporter())  
self.fm.AddDefaultMergers()  
self.fare_rule_merger = self.fm.GetMerger(merge.FareRuleMerger)  
 
akwargs = {'id': 'a1',  
'agency_name': 'a1',  
'agency_url': 'http://www.a1.com',  
'agency_timezone': 'Europe/Zurich'}  
self.a1 = transitfeed.Agency(**akwargs)  
self.a2 = transitfeed.Agency(**akwargs)  
 
rkwargs = {'route_id': 'r1',  
'agency_id': 'a1',  
'short_name': 'r1',  
'long_name': 'r1r1',  
'route_type': '0'}  
self.r1 = transitfeed.Route(**rkwargs)  
self.r2 = transitfeed.Route(**rkwargs)  
 
self.f1 = transitfeed.Fare('f1', '10', 'ZAR', '1', '0')  
self.f2 = transitfeed.Fare('f1', '10', 'ZAR', '1', '0')  
self.f3 = transitfeed.Fare('f3', '11', 'USD', '1', '0')  
 
self.fr1 = transitfeed.FareRule('f1', 'r1')  
self.fr2 = transitfeed.FareRule('f1', 'r1')  
self.fr3 = transitfeed.FareRule('f3', 'r1')  
 
self.fm.a_schedule.AddAgencyObject(self.a1)  
self.fm.a_schedule.AddRouteObject(self.r1)  
self.fm.a_schedule.AddFareObject(self.f1)  
self.fm.a_schedule.AddFareObject(self.f3)  
self.fm.a_schedule.AddFareRuleObject(self.fr1)  
self.fm.a_schedule.AddFareRuleObject(self.fr3)  
 
self.fm.b_schedule.AddAgencyObject(self.a2)  
self.fm.b_schedule.AddRouteObject(self.r2)  
self.fm.b_schedule.AddFareObject(self.f2)  
self.fm.b_schedule.AddFareRuleObject(self.fr2)  
 
def testMerge(self):  
self.fm.problem_reporter.ExpectProblemClass(merge.FareRulesBroken)  
self.fm.problem_reporter.ExpectProblemClass(merge.MergeNotImplemented)  
self.fm.MergeSchedules()  
 
self.assertEquals(len(self.fm.merged_schedule.GetFareList()), 2)  
 
fare_1 = self.fm.a_merge_map[self.f1]  
fare_2 = self.fm.a_merge_map[self.f3]  
 
self.assertEquals(len(fare_1.GetFareRuleList()), 1)  
fare_rule_1 = fare_1.GetFareRuleList()[0]  
self.assertEquals(len(fare_2.GetFareRuleList()), 1)  
fare_rule_2 = fare_2.GetFareRuleList()[0]  
 
self.assertEquals(fare_rule_1.fare_id,  
self.fm.a_merge_map[self.f1].fare_id)  
self.assertEquals(fare_rule_1.route_id,  
self.fm.a_merge_map[self.r1].route_id)  
self.assertEqual(fare_rule_2.fare_id,  
self.fm.a_merge_map[self.f3].fare_id)  
self.assertEqual(fare_rule_2.route_id,  
self.fm.a_merge_map[self.r1].route_id)  
 
self.fm.problem_reporter.assertExpectedProblemsReported(self)  
 
def testMergeStats(self):  
self.assert_(self.fare_rule_merger.GetMergeStats() is None)  
 
 
class TestExceptionProblemReporter(unittest.TestCase):  
 
def setUp(self):  
self.dataset_merger = merge.TripMerger(None)  
 
def testRaisesErrors(self):  
problem_reporter = merge.ExceptionProblemReporter()  
self.assertRaises(merge.CalendarsNotDisjoint,  
problem_reporter.CalendarsNotDisjoint,  
self.dataset_merger)  
 
def testNoRaiseWarnings(self):  
problem_reporter = merge.ExceptionProblemReporter()  
problem_reporter.MergeNotImplemented(self.dataset_merger)  
 
def testRaiseWarnings(self):  
problem_reporter = merge.ExceptionProblemReporter(True)  
self.assertRaises(merge.MergeNotImplemented,  
problem_reporter.MergeNotImplemented,  
self.dataset_merger)  
 
 
class TestHTMLProblemReporter(unittest.TestCase):  
 
def setUp(self):  
self.problem_reporter = merge.HTMLProblemReporter()  
a_schedule = transitfeed.Schedule()  
b_schedule = transitfeed.Schedule()  
merged_schedule = transitfeed.Schedule()  
self.feed_merger = merge.FeedMerger(a_schedule, b_schedule,  
merged_schedule,  
self.problem_reporter)  
self.dataset_merger = merge.TripMerger(None)  
 
def testGeneratesSomeHTML(self):  
self.problem_reporter.CalendarsNotDisjoint(self.dataset_merger)  
self.problem_reporter.MergeNotImplemented(self.dataset_merger)  
self.problem_reporter.FareRulesBroken(self.dataset_merger)  
self.problem_reporter.SameIdButNotMerged(self.dataset_merger,  
'test', 'unknown reason')  
 
output_file = StringIO.StringIO()  
old_feed_path = '/path/to/old/feed'  
new_feed_path = '/path/to/new/feed'  
merged_feed_path = '/path/to/merged/feed'  
self.problem_reporter.WriteOutput(output_file, self.feed_merger,  
old_feed_path, new_feed_path,  
merged_feed_path)  
 
html = output_file.getvalue()  
self.assert_(html.startswith('<html>'))  
self.assert_(html.endswith('</html>'))  
 
 
class MergeInSubprocessTestCase(util.TempDirTestCaseBase):  
def CopyAndModifyTestData(self, zip_path, modify_file, old, new):  
"""Return path of zip_path copy with old replaced by new in modify_file."""  
zipfile_mem = StringIO.StringIO(open(zip_path, 'rb').read())  
new_zip_path = os.path.join(self.tempdirpath, "modified.zip")  
zip = zipfile.ZipFile(zipfile_mem, 'a')  
modified_contents = zip.read(modify_file).replace(old, new)  
zip.writestr(modify_file, modified_contents)  
zip.close()  
open(new_zip_path, 'wb').write(zipfile_mem.getvalue())  
return new_zip_path  
 
def testCrashHandler(self):  
(out, err) = self.CheckCallWithPath(  
[self.GetPath('merge.py'), '--no_browser',  
'IWantMyCrash', 'file2', 'fileout.zip'],  
expected_retcode=127)  
self.assertMatchesRegex(r'Yikes', out)  
crashout = open('transitfeedcrash.txt').read()  
self.assertMatchesRegex(r'For testing the merge crash handler', crashout)  
 
def testMergeBadCommandLine(self):  
(out, err) = self.CheckCallWithPath(  
[self.GetPath('merge.py'), '--no_browser'],  
expected_retcode=2)  
self.assertFalse(out)  
self.assertMatchesRegex(r'command line arguments', err)  
self.assertFalse(os.path.exists('transitfeedcrash.txt'))  
 
def testMergeWithWarnings(self):  
# Make a copy of good_feed.zip which is not active until 20110101. This  
# avoids adding another test/data file. good_feed.zip needs to remain error  
# free so it can't start in the future.  
future_good_feed = self.CopyAndModifyTestData(  
self.GetPath('test/data/good_feed.zip'), 'calendar.txt',  
'20070101', '20110101')  
(out, err) = self.CheckCallWithPath(  
[self.GetPath('merge.py'), '--no_browser',  
self.GetPath('test/data/unused_stop'),  
future_good_feed,  
os.path.join(self.tempdirpath, 'merged-warnings.zip')],  
expected_retcode=0)  
 
def testMergeWithErrors(self):  
# Make a copy of good_feed.zip which is not active until 20110101. This  
# avoids adding another test/data file. good_feed.zip needs to remain error  
# free so it can't start in the future.  
future_good_feed = self.CopyAndModifyTestData(  
self.GetPath('test/data/good_feed.zip'), 'calendar.txt',  
'20070101', '20110101')  
(out, err) = self.CheckCallWithPath(  
[self.GetPath('merge.py'), '--no_browser',  
self.GetPath('test/data/unused_stop'),  
future_good_feed],  
expected_retcode=2)  
 
 
if __name__ == '__main__':  
unittest.main()  
 
 Binary files a/origin-src/transitfeed-1.2.5/test/testmerge.pyc and /dev/null differ
#!/usr/bin/python2.4  
#  
# Copyright (C) 2007 Google Inc.  
#  
# Licensed under the Apache License, Version 2.0 (the "License");  
# you may not use this file except in compliance with the License.  
# You may obtain a copy of the License at  
#  
# http://www.apache.org/licenses/LICENSE-2.0  
#  
# Unless required by applicable law or agreed to in writing, software  
# distributed under the License is distributed on an "AS IS" BASIS,  
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.  
# See the License for the specific language governing permissions and  
# limitations under the License.  
 
"""Tests for transitfeed.shapelib.py"""  
 
__author__ = 'chris.harrelson.code@gmail.com (Chris Harrelson)'  
 
import math  
from transitfeed import shapelib  
from transitfeed.shapelib import Point  
from transitfeed.shapelib import Poly  
from transitfeed.shapelib import PolyCollection  
from transitfeed.shapelib import PolyGraph  
import unittest  
 
 
def formatPoint(p, precision=12):  
formatString = "(%%.%df, %%.%df, %%.%df)" % (precision, precision, precision)  
return formatString % (p.x, p.y, p.z)  
 
 
def formatPoints(points):  
return "[%s]" % ", ".join([formatPoint(p, precision=4) for p in points])  
 
 
class ShapeLibTestBase(unittest.TestCase):  
def assertApproxEq(self, a, b):  
self.assertAlmostEqual(a, b, 8)  
 
def assertPointApproxEq(self, a, b):  
try:  
self.assertApproxEq(a.x, b.x)  
self.assertApproxEq(a.y, b.y)  
self.assertApproxEq(a.z, b.z)  
except AssertionError:  
print 'ERROR: %s != %s' % (formatPoint(a), formatPoint(b))  
raise  
 
def assertPointsApproxEq(self, points1, points2):  
try:  
self.assertEqual(len(points1), len(points2))  
except AssertionError:  
print "ERROR: %s != %s" % (formatPoints(points1), formatPoints(points2))  
raise  
for i in xrange(len(points1)):  
try:  
self.assertPointApproxEq(points1[i], points2[i])  
except AssertionError:  
print ('ERROR: points not equal in position %d\n%s != %s'  
% (i, formatPoints(points1), formatPoints(points2)))  
raise  
 
 
class TestPoints(ShapeLibTestBase):  
def testPoints(self):  
p = Point(1, 1, 1)  
 
self.assertApproxEq(p.DotProd(p), 3)  
 
self.assertApproxEq(p.Norm2(), math.sqrt(3))  
 
self.assertPointApproxEq(Point(1.5, 1.5, 1.5),  
p.Times(1.5))  
 
norm = 1.7320508075688772  
self.assertPointApproxEq(p.Normalize(),  
Point(1 / norm,  
1 / norm,  
1 / norm))  
 
p2 = Point(1, 0, 0)  
self.assertPointApproxEq(p2, p2.Normalize())  
 
def testCrossProd(self):  
p1 = Point(1, 0, 0).Normalize()  
p2 = Point(0, 1 ,0).Normalize()  
p1_cross_p2 = p1.CrossProd(p2)  
self.assertApproxEq(p1_cross_p2.x, 0)  
self.assertApproxEq(p1_cross_p2.y, 0)  
self.assertApproxEq(p1_cross_p2.z, 1)  
 
def testRobustCrossProd(self):  
p1 = Point(1, 0, 0)  
p2 = Point(1, 0, 0)  
self.assertPointApproxEq(Point(0, 0, 0),  
p1.CrossProd(p2))  
# only needs to be an arbitrary vector perpendicular to (1, 0, 0)  
self.assertPointApproxEq(  
Point(0.000000000000000, -0.998598452020993, 0.052925717957113),  
p1.RobustCrossProd(p2))  
 
def testS2LatLong(self):  
point = Point.FromLatLng(30, 40)  
self.assertPointApproxEq(Point(0.663413948169,  
0.556670399226,  
0.5), point)  
(lat, lng) = point.ToLatLng()  
self.assertApproxEq(30, lat)  
self.assertApproxEq(40, lng)  
 
def testOrtho(self):  
point = Point(1, 1, 1)  
ortho = point.Ortho()  
self.assertApproxEq(ortho.DotProd(point), 0)  
 
def testAngle(self):  
point1 = Point(1, 1, 0).Normalize()  
point2 = Point(0, 1, 0)  
self.assertApproxEq(45, point1.Angle(point2) * 360 / (2 * math.pi))  
self.assertApproxEq(point1.Angle(point2), point2.Angle(point1))  
 
def testGetDistanceMeters(self):  
point1 = Point.FromLatLng(40.536895,-74.203033)  
point2 = Point.FromLatLng(40.575239,-74.112825)  
self.assertApproxEq(8732.623770873237,  
point1.GetDistanceMeters(point2))  
 
 
class TestClosestPoint(ShapeLibTestBase):  
def testGetClosestPoint(self):  
x = Point(1, 1, 0).Normalize()  
a = Point(1, 0, 0)  
b = Point(0, 1, 0)  
 
closest = shapelib.GetClosestPoint(x, a, b)  
self.assertApproxEq(0.707106781187, closest.x)  
self.assertApproxEq(0.707106781187, closest.y)  
self.assertApproxEq(0.0, closest.z)  
 
 
class TestPoly(ShapeLibTestBase):  
def testGetClosestPointShape(self):  
poly = Poly()  
 
poly.AddPoint(Point(1, 1, 0).Normalize())  
self.assertPointApproxEq(Point(  
0.707106781187, 0.707106781187, 0), poly.GetPoint(0))  
 
point = Point(0, 1, 1).Normalize()  
self.assertPointApproxEq(Point(1, 1, 0).Normalize(),  
poly.GetClosestPoint(point)[0])  
 
poly.AddPoint(Point(0, 1, 1).Normalize())  
 
self.assertPointApproxEq(  
Point(0, 1, 1).Normalize(),  
poly.GetClosestPoint(point)[0])  
 
def testCutAtClosestPoint(self):  
poly = Poly()  
poly.AddPoint(Point(0, 1, 0).Normalize())  
poly.AddPoint(Point(0, 0.5, 0.5).Normalize())  
poly.AddPoint(Point(0, 0, 1).Normalize())  
 
(before, after) = \  
poly.CutAtClosestPoint(Point(0, 0.3, 0.7).Normalize())  
 
self.assert_(2 == before.GetNumPoints())  
self.assert_(2 == before.GetNumPoints())  
self.assertPointApproxEq(  
Point(0, 0.707106781187, 0.707106781187), before.GetPoint(1))  
 
self.assertPointApproxEq(  
Point(0, 0.393919298579, 0.919145030018), after.GetPoint(0))  
 
poly = Poly()  
poly.AddPoint(Point.FromLatLng(40.527035999999995, -74.191265999999999))  
poly.AddPoint(Point.FromLatLng(40.526859999999999, -74.191140000000004))  
poly.AddPoint(Point.FromLatLng(40.524681000000001, -74.189579999999992))  
poly.AddPoint(Point.FromLatLng(40.523128999999997, -74.188467000000003))  
poly.AddPoint(Point.FromLatLng(40.523054999999999, -74.188676000000001))  
pattern = Poly()  
pattern.AddPoint(Point.FromLatLng(40.52713,  
-74.191146000000003))  
self.assertApproxEq(14.564268281551, pattern.GreedyPolyMatchDist(poly))  
 
def testMergePolys(self):  
poly1 = Poly(name="Foo")  
poly1.AddPoint(Point(0, 1, 0).Normalize())  
poly1.AddPoint(Point(0, 0.5, 0.5).Normalize())  
poly1.AddPoint(Point(0, 0, 1).Normalize())  
poly1.AddPoint(Point(1, 1, 1).Normalize())  
 
poly2 = Poly()  
poly3 = Poly(name="Bar")  
poly3.AddPoint(Point(1, 1, 1).Normalize())  
poly3.AddPoint(Point(2, 0.5, 0.5).Normalize())  
 
merged1 = Poly.MergePolys([poly1, poly2])  
self.assertPointsApproxEq(poly1.GetPoints(), merged1.GetPoints())  
self.assertEqual("Foo;", merged1.GetName())  
 
merged2 = Poly.MergePolys([poly2, poly3])  
self.assertPointsApproxEq(poly3.GetPoints(), merged2.GetPoints())  
self.assertEqual(";Bar", merged2.GetName())  
 
merged3 = Poly.MergePolys([poly1, poly2, poly3], merge_point_threshold=0)  
mergedPoints = poly1.GetPoints()[:]  
mergedPoints.append(poly3.GetPoint(-1))  
self.assertPointsApproxEq(mergedPoints, merged3.GetPoints())  
self.assertEqual("Foo;;Bar", merged3.GetName())  
 
merged4 = Poly.MergePolys([poly2])  
self.assertEqual("", merged4.GetName())  
self.assertEqual(0, merged4.GetNumPoints())  
 
# test merging two nearby points  
newPoint = poly1.GetPoint(-1).Plus(Point(0.000001, 0, 0)).Normalize()  
poly1.AddPoint(newPoint)  
distance = poly1.GetPoint(-1).GetDistanceMeters(poly3.GetPoint(0))  
self.assertTrue(distance <= 10)  
self.assertTrue(distance > 5)  
 
merged5 = Poly.MergePolys([poly1, poly2, poly3], merge_point_threshold=10)  
mergedPoints = poly1.GetPoints()[:]  
mergedPoints.append(poly3.GetPoint(-1))  
self.assertPointsApproxEq(mergedPoints, merged5.GetPoints())  
self.assertEqual("Foo;;Bar", merged5.GetName())  
 
merged6 = Poly.MergePolys([poly1, poly2, poly3], merge_point_threshold=5)  
mergedPoints = poly1.GetPoints()[:]  
mergedPoints += poly3.GetPoints()  
self.assertPointsApproxEq(mergedPoints, merged6.GetPoints())  
self.assertEqual("Foo;;Bar", merged6.GetName())  
 
def testReversed(self):  
p1 = Point(1, 0, 0).Normalize()  
p2 = Point(0, 0.5, 0.5).Normalize()  
p3 = Point(0.3, 0.8, 0.5).Normalize()  
poly1 = Poly([p1, p2, p3])  
self.assertPointsApproxEq([p3, p2, p1], poly1.Reversed().GetPoints())  
 
def testLengthMeters(self):  
p1 = Point(1, 0, 0).Normalize()  
p2 = Point(0, 0.5, 0.5).Normalize()  
p3 = Point(0.3, 0.8, 0.5).Normalize()  
poly0 = Poly([p1])  
poly1 = Poly([p1, p2])  
poly2 = Poly([p1, p2, p3])  
try:  
poly0.LengthMeters()  
self.fail("Should have thrown AssertionError")  
except AssertionError:  
pass  
 
p1_p2 = p1.GetDistanceMeters(p2)  
p2_p3 = p2.GetDistanceMeters(p3)  
self.assertEqual(p1_p2, poly1.LengthMeters())  
self.assertEqual(p1_p2 + p2_p3, poly2.LengthMeters())  
self.assertEqual(p1_p2 + p2_p3, poly2.Reversed().LengthMeters())  
 
 
class TestCollection(ShapeLibTestBase):  
def testPolyMatch(self):  
poly = Poly()  
poly.AddPoint(Point(0, 1, 0).Normalize())  
poly.AddPoint(Point(0, 0.5, 0.5).Normalize())  
poly.AddPoint(Point(0, 0, 1).Normalize())  
 
collection = PolyCollection()  
collection.AddPoly(poly)  
match = collection.FindMatchingPolys(Point(0, 1, 0),  
Point(0, 0, 1))  
self.assert_(len(match) == 1 and match[0] == poly)  
 
match = collection.FindMatchingPolys(Point(0, 1, 0),  
Point(0, 1, 0))  
self.assert_(len(match) == 0)  
 
poly = Poly()  
poly.AddPoint(Point.FromLatLng(45.585212,-122.586136))  
poly.AddPoint(Point.FromLatLng(45.586654,-122.587595))  
collection = PolyCollection()  
collection.AddPoly(poly)  
 
match = collection.FindMatchingPolys(  
Point.FromLatLng(45.585212,-122.586136),  
Point.FromLatLng(45.586654,-122.587595))  
self.assert_(len(match) == 1 and match[0] == poly)  
 
match = collection.FindMatchingPolys(  
Point.FromLatLng(45.585219,-122.586136),  
Point.FromLatLng(45.586654,-122.587595))  
self.assert_(len(match) == 1 and match[0] == poly)  
 
self.assertApproxEq(0.0, poly.GreedyPolyMatchDist(poly))  
 
match = collection.FindMatchingPolys(  
Point.FromLatLng(45.587212,-122.586136),  
Point.FromLatLng(45.586654,-122.587595))  
self.assert_(len(match) == 0)  
 
 
class TestGraph(ShapeLibTestBase):  
def testReconstructPath(self):  
p1 = Point(1, 0, 0).Normalize()  
p2 = Point(0, 0.5, 0.5).Normalize()  
p3 = Point(0.3, 0.8, 0.5).Normalize()  
poly1 = Poly([p1, p2])  
poly2 = Poly([p3, p2])  
came_from = {  
p2: (p1, poly1),  
p3: (p2, poly2)  
}  
 
graph = PolyGraph()  
reconstructed1 = graph._ReconstructPath(came_from, p1)  
self.assertEqual(0, reconstructed1.GetNumPoints())  
 
reconstructed2 = graph._ReconstructPath(came_from, p2)  
self.assertPointsApproxEq([p1, p2], reconstructed2.GetPoints())  
 
reconstructed3 = graph._ReconstructPath(came_from, p3)  
self.assertPointsApproxEq([p1, p2, p3], reconstructed3.GetPoints())  
 
def testShortestPath(self):  
p1 = Point(1, 0, 0).Normalize()  
p2 = Point(0, 0.5, 0.5).Normalize()  
p3 = Point(0.3, 0.8, 0.5).Normalize()  
p4 = Point(0.7, 0.7, 0.5).Normalize()  
poly1 = Poly([p1, p2, p3], "poly1")  
poly2 = Poly([p4, p3], "poly2")  
poly3 = Poly([p4, p1], "poly3")  
graph = PolyGraph()  
graph.AddPoly(poly1)  
graph.AddPoly(poly2)  
graph.AddPoly(poly3)  
path = graph.ShortestPath(p1, p4)  
self.assert_(path is not None)  
self.assertPointsApproxEq([p1, p4], path.GetPoints())  
 
path = graph.ShortestPath(p1, p3)  
self.assert_(path is not None)  
self.assertPointsApproxEq([p1, p4, p3], path.GetPoints())  
 
path = graph.ShortestPath(p3, p1)  
self.assert_(path is not None)  
self.assertPointsApproxEq([p3, p4, p1], path.GetPoints())  
 
def testFindShortestMultiPointPath(self):  
p1 = Point(1, 0, 0).Normalize()  
p2 = Point(0.5, 0.5, 0).Normalize()  
p3 = Point(0.5, 0.5, 0.1).Normalize()  
p4 = Point(0, 1, 0).Normalize()  
poly1 = Poly([p1, p2, p3], "poly1")  
poly2 = Poly([p4, p3], "poly2")  
poly3 = Poly([p4, p1], "poly3")  
graph = PolyGraph()  
graph.AddPoly(poly1)  
graph.AddPoly(poly2)  
graph.AddPoly(poly3)  
path = graph.FindShortestMultiPointPath([p1, p3, p4])  
self.assert_(path is not None)  
self.assertPointsApproxEq([p1, p2, p3, p4], path.GetPoints())  
 
 
if __name__ == '__main__':  
unittest.main()  
 
 Binary files a/origin-src/transitfeed-1.2.5/test/testshapelib.pyc and /dev/null differ
#!/usr/bin/python2.5  
 
# Copyright (C) 2007 Google Inc.  
#  
# Licensed under the Apache License, Version 2.0 (the "License");  
# you may not use this file except in compliance with the License.  
# You may obtain a copy of the License at  
#  
# http://www.apache.org/licenses/LICENSE-2.0  
#  
# Unless required by applicable law or agreed to in writing, software  
# distributed under the License is distributed on an "AS IS" BASIS,  
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.  
# See the License for the specific language governing permissions and  
# limitations under the License.  
 
# Unit tests for the transitfeed module.  
 
import datetime  
from datetime import date  
import dircache  
import os.path  
import re  
import sys  
import tempfile  
import time  
import transitfeed  
import unittest  
import util  
from util import RecordingProblemReporter  
from StringIO import StringIO  
import zipfile  
import zlib  
 
 
def DataPath(path):  
here = os.path.dirname(__file__)  
return os.path.join(here, 'data', path)  
 
def GetDataPathContents():  
here = os.path.dirname(__file__)  
return dircache.listdir(os.path.join(here, 'data'))  
 
 
class ExceptionProblemReporterNoExpiration(  
transitfeed.ExceptionProblemReporter):  
"""Ignores feed expiration problems.  
 
Use TestFailureProblemReporter in new code because it fails more cleanly, is  
easier to extend and does more thorough checking.  
"""  
 
def __init__(self):  
transitfeed.ExceptionProblemReporter.__init__(self, raise_warnings=True)  
 
def ExpirationDate(self, expiration, context=None):  
pass # We don't want to give errors about our test data  
 
 
class TestFailureProblemReporter(transitfeed.ProblemReporter):  
"""Causes a test failure immediately on any problem."""  
def __init__(self, test_case, ignore_types=("ExpirationDate",)):  
transitfeed.ProblemReporter.__init__(self)  
self.test_case = test_case  
self._ignore_types = ignore_types or set()  
 
def _Report(self, e):  
# These should never crash  
formatted_problem = e.FormatProblem()  
formatted_context = e.FormatContext()  
exception_class = e.__class__.__name__  
if exception_class in self._ignore_types:  
return  
self.test_case.fail(  
"%s: %s\n%s" % (exception_class, formatted_problem, formatted_context))  
 
 
class UnrecognizedColumnRecorder(RecordingProblemReporter):  
"""Keeps track of unrecognized column errors."""  
def __init__(self, test_case):  
RecordingProblemReporter.__init__(self, test_case,  
ignore_types=("ExpirationDate",))  
self.column_errors = []  
 
def UnrecognizedColumn(self, file_name, column_name, context=None):  
self.column_errors.append((file_name, column_name))  
 
 
class RedirectStdOutTestCaseBase(unittest.TestCase):  
"""Save stdout to the StringIO buffer self.this_stdout"""  
def setUp(self):  
self.saved_stdout = sys.stdout  
self.this_stdout = StringIO()  
sys.stdout = self.this_stdout  
 
def tearDown(self):  
sys.stdout = self.saved_stdout  
self.this_stdout.close()  
 
 
# ensure that there are no exceptions when attempting to load  
# (so that the validator won't crash)  
class NoExceptionTestCase(RedirectStdOutTestCaseBase):  
def runTest(self):  
for feed in GetDataPathContents():  
loader = transitfeed.Loader(DataPath(feed),  
problems=transitfeed.ProblemReporter(),  
extra_validation=True)  
schedule = loader.Load()  
schedule.Validate()  
 
 
class EndOfLineCheckerTestCase(unittest.TestCase):  
def setUp(self):  
self.problems = RecordingProblemReporter(self)  
 
def RunEndOfLineChecker(self, end_of_line_checker):  
# Iterating using for calls end_of_line_checker.next() until a  
# StopIteration is raised. EndOfLineChecker does the final check for a mix  
# of CR LF and LF ends just before raising StopIteration.  
for line in end_of_line_checker:  
pass  
 
def testInvalidLineEnd(self):  
f = transitfeed.EndOfLineChecker(StringIO("line1\r\r\nline2"),  
"<StringIO>",  
self.problems)  
self.RunEndOfLineChecker(f)  
e = self.problems.PopException("InvalidLineEnd")  
self.assertEqual(e.file_name, "<StringIO>")  
self.assertEqual(e.row_num, 1)  
self.assertEqual(e.bad_line_end, r"\r\r\n")  
self.problems.AssertNoMoreExceptions()  
 
def testInvalidLineEndToo(self):  
f = transitfeed.EndOfLineChecker(  
StringIO("line1\nline2\r\nline3\r\r\r\n"),  
"<StringIO>", self.problems)  
self.RunEndOfLineChecker(f)  
e = self.problems.PopException("InvalidLineEnd")  
self.assertEqual(e.file_name, "<StringIO>")  
self.assertEqual(e.row_num, 3)  
self.assertEqual(e.bad_line_end, r"\r\r\r\n")  
e = self.problems.PopException("OtherProblem")  
self.assertEqual(e.file_name, "<StringIO>")  
self.assertTrue(e.description.find("consistent line end") != -1)  
self.problems.AssertNoMoreExceptions()  
 
def testEmbeddedCr(self):  
f = transitfeed.EndOfLineChecker(  
StringIO("line1\rline1b"),  
"<StringIO>", self.problems)  
self.RunEndOfLineChecker(f)  
e = self.problems.PopException("OtherProblem")  
self.assertEqual(e.file_name, "<StringIO>")  
self.assertEqual(e.row_num, 1)  
self.assertEqual(e.FormatProblem(),  
"Line contains ASCII Carriage Return 0x0D, \\r")  
self.problems.AssertNoMoreExceptions()  
 
def testEmbeddedUtf8NextLine(self):  
f = transitfeed.EndOfLineChecker(  
StringIO("line1b\xc2\x85"),  
"<StringIO>", self.problems)  
self.RunEndOfLineChecker(f)  
e = self.problems.PopException("OtherProblem")  
self.assertEqual(e.file_name, "<StringIO>")  
self.assertEqual(e.row_num, 1)  
self.assertEqual(e.FormatProblem(),  
"Line contains Unicode NEXT LINE SEPARATOR U+0085")  
self.problems.AssertNoMoreExceptions()  
 
def testEndOfLineMix(self):  
f = transitfeed.EndOfLineChecker(  
StringIO("line1\nline2\r\nline3\nline4"),  
"<StringIO>", self.problems)  
self.RunEndOfLineChecker(f)  
e = self.problems.PopException("OtherProblem")  
self.assertEqual(e.file_name, "<StringIO>")  
self.assertEqual(e.FormatProblem(),  
"Found 1 CR LF \"\\r\\n\" line end (line 2) and "  
"2 LF \"\\n\" line ends (lines 1, 3). A file must use a "  
"consistent line end.")  
self.problems.AssertNoMoreExceptions()  
 
def testEndOfLineManyMix(self):  
f = transitfeed.EndOfLineChecker(  
StringIO("1\n2\n3\n4\n5\n6\n7\r\n8\r\n9\r\n10\r\n11\r\n"),  
"<StringIO>", self.problems)  
self.RunEndOfLineChecker(f)  
e = self.problems.PopException("OtherProblem")  
self.assertEqual(e.file_name, "<StringIO>")  
self.assertEqual(e.FormatProblem(),  
"Found 5 CR LF \"\\r\\n\" line ends (lines 7, 8, 9, 10, "  
"11) and 6 LF \"\\n\" line ends (lines 1, 2, 3, 4, 5, "  
"...). A file must use a consistent line end.")  
self.problems.AssertNoMoreExceptions()  
 
def testLoad(self):  
loader = transitfeed.Loader(  
DataPath("bad_eol.zip"), problems=self.problems, extra_validation=True)  
loader.Load()  
 
e = self.problems.PopException("InvalidLineEnd")  
self.assertEqual(e.file_name, "routes.txt")  
self.assertEqual(e.row_num, 5)  
self.assertTrue(e.FormatProblem().find(r"\r\r\n") != -1)  
 
e = self.problems.PopException("OtherProblem")  
self.assertEqual(e.file_name, "calendar.txt")  
self.assertTrue(re.search(  
r"Found 1 CR LF.* \(line 2\) and 2 LF .*\(lines 1, 3\)",  
e.FormatProblem()))  
 
e = self.problems.PopException("OtherProblem")  
self.assertEqual(e.file_name, "trips.txt")  
self.assertEqual(e.row_num, 1)  
self.assertTrue(re.search(  
r"contains ASCII Form Feed",  
e.FormatProblem()))  
# TODO(Tom): avoid this duplicate error for the same issue  
e = self.problems.PopException("CsvSyntax")  
self.assertEqual(e.row_num, 1)  
self.assertTrue(re.search(  
r"header row should not contain any space char",  
e.FormatProblem()))  
 
self.problems.AssertNoMoreExceptions()  
 
 
class LoadTestCase(unittest.TestCase):  
def setUp(self):  
self.problems = RecordingProblemReporter(self, ("ExpirationDate",))  
 
def Load(self, feed_name):  
loader = transitfeed.Loader(  
DataPath(feed_name), problems=self.problems, extra_validation=True)  
loader.Load()  
 
def ExpectInvalidValue(self, feed_name, column_name):  
self.Load(feed_name)  
self.problems.PopInvalidValue(column_name)  
self.problems.AssertNoMoreExceptions()  
 
def ExpectMissingFile(self, feed_name, file_name):  
self.Load(feed_name)  
e = self.problems.PopException("MissingFile")  
self.assertEqual(file_name, e.file_name)  
# Don't call AssertNoMoreExceptions() because a missing file causes  
# many errors.  
 
 
class LoadFromZipTestCase(unittest.TestCase):  
def runTest(self):  
loader = transitfeed.Loader(  
DataPath('good_feed.zip'),  
problems = TestFailureProblemReporter(self),  
extra_validation = True)  
loader.Load()  
 
# now try using Schedule.Load  
schedule = transitfeed.Schedule(  
problem_reporter=ExceptionProblemReporterNoExpiration())  
schedule.Load(DataPath('good_feed.zip'), extra_validation=True)  
 
 
class LoadAndRewriteFromZipTestCase(unittest.TestCase):  
def runTest(self):  
schedule = transitfeed.Schedule(  
problem_reporter=ExceptionProblemReporterNoExpiration())  
schedule.Load(DataPath('good_feed.zip'), extra_validation=True)  
 
# Finally see if write crashes  
schedule.WriteGoogleTransitFeed(tempfile.TemporaryFile())  
 
 
class LoadFromDirectoryTestCase(unittest.TestCase):  
def runTest(self):  
loader = transitfeed.Loader(  
DataPath('good_feed'),  
problems = TestFailureProblemReporter(self),  
extra_validation = True)  
loader.Load()  
 
 
class LoadUnknownFeedTestCase(unittest.TestCase):  
def runTest(self):  
feed_name = DataPath('unknown_feed')  
loader = transitfeed.Loader(  
feed_name,  
problems = ExceptionProblemReporterNoExpiration(),  
extra_validation = True)  
try:  
loader.Load()  
self.fail('FeedNotFound exception expected')  
except transitfeed.FeedNotFound, e:  
self.assertEqual(feed_name, e.feed_name)  
 
class LoadUnknownFormatTestCase(unittest.TestCase):  
def runTest(self):  
feed_name = DataPath('unknown_format.zip')  
loader = transitfeed.Loader(  
feed_name,  
problems = ExceptionProblemReporterNoExpiration(),  
extra_validation = True)  
try:  
loader.Load()  
self.fail('UnknownFormat exception expected')  
except transitfeed.UnknownFormat, e:  
self.assertEqual(feed_name, e.feed_name)  
 
class LoadUnrecognizedColumnsTestCase(unittest.TestCase):  
def runTest(self):  
problems = UnrecognizedColumnRecorder(self)  
loader = transitfeed.Loader(DataPath('unrecognized_columns'),  
problems=problems)  
loader.Load()  
found_errors = set(problems.column_errors)  
expected_errors = set([  
('agency.txt', 'agency_lange'),  
('stops.txt', 'stop_uri'),  
('routes.txt', 'Route_Text_Color'),  
('calendar.txt', 'leap_day'),  
('calendar_dates.txt', 'leap_day'),  
('trips.txt', 'sharpe_id'),  
('stop_times.txt', 'shapedisttraveled'),  
('stop_times.txt', 'drop_off_time'),  
('fare_attributes.txt', 'transfer_time'),  
('fare_rules.txt', 'source_id'),  
('frequencies.txt', 'superfluous'),  
('transfers.txt', 'to_stop')  
])  
 
# Now make sure we got the unrecognized column errors that we expected.  
not_expected = found_errors.difference(expected_errors)  
self.failIf(not_expected, 'unexpected errors: %s' % str(not_expected))  
not_found = expected_errors.difference(found_errors)  
self.failIf(not_found, 'expected but not found: %s' % str(not_found))  
 
class LoadExtraCellValidationTestCase(LoadTestCase):  
"""Check that the validation detects too many cells in a row."""  
def runTest(self):  
self.Load('extra_row_cells')  
e = self.problems.PopException("OtherProblem")  
self.assertEquals("routes.txt", e.file_name)  
self.assertEquals(4, e.row_num)  
self.problems.AssertNoMoreExceptions()  
 
 
class LoadMissingCellValidationTestCase(LoadTestCase):  
"""Check that the validation detects missing cells in a row."""  
def runTest(self):  
self.Load('missing_row_cells')  
e = self.problems.PopException("OtherProblem")  
self.assertEquals("routes.txt", e.file_name)  
self.assertEquals(4, e.row_num)  
self.problems.AssertNoMoreExceptions()  
 
class LoadUnknownFileTestCase(unittest.TestCase):  
"""Check that the validation detects unknown files."""  
def runTest(self):  
feed_name = DataPath('unknown_file')  
self.problems = RecordingProblemReporter(self, ("ExpirationDate",))  
loader = transitfeed.Loader(  
feed_name,  
problems = self.problems,  
extra_validation = True)  
loader.Load()  
e = self.problems.PopException('UnknownFile')  
self.assertEqual('frecuencias.txt', e.file_name)  
self.problems.AssertNoMoreExceptions()  
 
class LoadUTF8BOMTestCase(unittest.TestCase):  
def runTest(self):  
loader = transitfeed.Loader(  
DataPath('utf8bom'),  
problems = TestFailureProblemReporter(self),  
extra_validation = True)  
loader.Load()  
 
 
class LoadUTF16TestCase(unittest.TestCase):  
def runTest(self):  
# utf16 generated by `recode utf8..utf16 *'  
loader = transitfeed.Loader(  
DataPath('utf16'),  
problems = transitfeed.ExceptionProblemReporter(),  
extra_validation = True)  
try:  
loader.Load()  
# TODO: make sure processing proceeds beyond the problem  
self.fail('FileFormat exception expected')  
except transitfeed.FileFormat, e:  
# make sure these don't raise an exception  
self.assertTrue(re.search(r'encoded in utf-16', e.FormatProblem()))  
e.FormatContext()  
 
 
class LoadNullTestCase(unittest.TestCase):  
def runTest(self):  
loader = transitfeed.Loader(  
DataPath('contains_null'),  
problems = transitfeed.ExceptionProblemReporter(),  
extra_validation = True)  
try:  
loader.Load()  
self.fail('FileFormat exception expected')  
except transitfeed.FileFormat, e:  
self.assertTrue(re.search(r'contains a null', e.FormatProblem()))  
# make sure these don't raise an exception  
e.FormatContext()  
 
 
class ProblemReporterTestCase(RedirectStdOutTestCaseBase):  
# Unittest for problem reporter  
def testContextWithBadUnicodeProblem(self):  
pr = transitfeed.ProblemReporter()  
# Context has valid unicode values  
pr.SetFileContext('filename.foo', 23,  
[u'Andr\202', u'Person \uc720 foo', None],  
[u'1\202', u'2\202', u'3\202'])  
pr.OtherProblem('test string')  
pr.OtherProblem(u'\xff\xfe\x80\x88')  
# Invalid ascii and utf-8. encode('utf-8') and decode('utf-8') will fail  
# for this value  
pr.OtherProblem('\xff\xfe\x80\x88')  
self.assertTrue(re.search(r"test string", self.this_stdout.getvalue()))  
self.assertTrue(re.search(r"filename.foo:23", self.this_stdout.getvalue()))  
 
def testNoContextWithBadUnicode(self):  
pr = transitfeed.ProblemReporter()  
pr.OtherProblem('test string')  
pr.OtherProblem(u'\xff\xfe\x80\x88')  
# Invalid ascii and utf-8. encode('utf-8') and decode('utf-8') will fail  
# for this value  
pr.OtherProblem('\xff\xfe\x80\x88')  
self.assertTrue(re.search(r"test string", self.this_stdout.getvalue()))  
 
def testBadUnicodeContext(self):  
pr = transitfeed.ProblemReporter()  
pr.SetFileContext('filename.foo', 23,  
[u'Andr\202', 'Person \xff\xfe\x80\x88 foo', None],  
[u'1\202', u'2\202', u'3\202'])  
pr.OtherProblem("help, my context isn't utf-8!")  
self.assertTrue(re.search(r"help, my context", self.this_stdout.getvalue()))  
self.assertTrue(re.search(r"filename.foo:23", self.this_stdout.getvalue()))  
 
def testLongWord(self):  
# Make sure LineWrap doesn't puke  
pr = transitfeed.ProblemReporter()  
pr.OtherProblem('1111untheontuhoenuthoentuhntoehuontehuntoehuntoehunto'  
'2222oheuntheounthoeunthoeunthoeuntheontuheontuhoue')  
self.assertTrue(re.search(r"1111.+2222", self.this_stdout.getvalue()))  
 
 
class BadProblemReporterTestCase(RedirectStdOutTestCaseBase):  
"""Make sure ProblemReporter doesn't crash when given bad unicode data and  
does find some error"""  
# tom.brown.code-utf8_weaknesses fixed a bug with problem reporter and bad  
# utf-8 strings  
def runTest(self):  
loader = transitfeed.Loader(  
DataPath('bad_utf8'),  
problems = transitfeed.ProblemReporter(),  
extra_validation = True)  
loader.Load()  
# raises exception if not found  
self.this_stdout.getvalue().index('Invalid value')  
 
 
class BadUtf8TestCase(LoadTestCase):  
def runTest(self):  
self.Load('bad_utf8')  
self.problems.PopException("UnrecognizedColumn")  
self.problems.PopInvalidValue("agency_name", "agency.txt")  
self.problems.PopInvalidValue("stop_name", "stops.txt")  
self.problems.PopInvalidValue("route_short_name", "routes.txt")  
self.problems.PopInvalidValue("route_long_name", "routes.txt")  
self.problems.PopInvalidValue("trip_headsign", "trips.txt")  
self.problems.PopInvalidValue("stop_headsign", "stop_times.txt")  
self.problems.AssertNoMoreExceptions()  
 
 
class LoadMissingAgencyTestCase(LoadTestCase):  
def runTest(self):  
self.ExpectMissingFile('missing_agency', 'agency.txt')  
 
 
class LoadMissingStopsTestCase(LoadTestCase):  
def runTest(self):  
self.ExpectMissingFile('missing_stops', 'stops.txt')  
 
 
class LoadMissingRoutesTestCase(LoadTestCase):  
def runTest(self):  
self.ExpectMissingFile('missing_routes', 'routes.txt')  
 
 
class LoadMissingTripsTestCase(LoadTestCase):  
def runTest(self):  
self.ExpectMissingFile('missing_trips', 'trips.txt')  
 
 
class LoadMissingStopTimesTestCase(LoadTestCase):  
def runTest(self):  
self.ExpectMissingFile('missing_stop_times', 'stop_times.txt')  
 
 
class LoadMissingCalendarTestCase(LoadTestCase):  
def runTest(self):  
self.ExpectMissingFile('missing_calendar', 'calendar.txt')  
 
 
class EmptyFileTestCase(unittest.TestCase):  
def runTest(self):  
loader = transitfeed.Loader(  
DataPath('empty_file'),  
problems = ExceptionProblemReporterNoExpiration(),  
extra_validation = True)  
try:  
loader.Load()  
self.fail('EmptyFile exception expected')  
except transitfeed.EmptyFile, e:  
self.assertEqual('agency.txt', e.file_name)  
 
 
class MissingColumnTestCase(unittest.TestCase):  
def runTest(self):  
loader = transitfeed.Loader(  
DataPath('missing_column'),  
problems = ExceptionProblemReporterNoExpiration(),  
extra_validation = True)  
try:  
loader.Load()  
self.fail('MissingColumn exception expected')  
except transitfeed.MissingColumn, e:  
self.assertEqual('agency.txt', e.file_name)  
self.assertEqual('agency_name', e.column_name)  
 
 
class ZeroBasedStopSequenceTestCase(LoadTestCase):  
def runTest(self):  
self.ExpectInvalidValue('negative_stop_sequence', 'stop_sequence')  
 
 
class DuplicateStopTestCase(unittest.TestCase):  
def runTest(self):  
schedule = transitfeed.Schedule(  
problem_reporter=ExceptionProblemReporterNoExpiration())  
try:  
schedule.Load(DataPath('duplicate_stop'), extra_validation=True)  
self.fail('OtherProblem exception expected')  
except transitfeed.OtherProblem:  
pass  
 
class DuplicateStopSequenceTestCase(unittest.TestCase):  
def runTest(self):  
problems = RecordingProblemReporter(self, ("ExpirationDate",))  
schedule = transitfeed.Schedule(problem_reporter=problems)  
schedule.Load(DataPath('duplicate_stop_sequence'), extra_validation=True)  
e = problems.PopException('InvalidValue')  
self.assertEqual('stop_sequence', e.column_name)  
problems.AssertNoMoreExceptions()  
 
 
class MissingEndpointTimesTestCase(unittest.TestCase):  
def runTest(self):  
schedule = transitfeed.Schedule(  
problem_reporter=ExceptionProblemReporterNoExpiration())  
try:  
schedule.Load(DataPath('missing_endpoint_times'), extra_validation=True)  
self.fail('InvalidValue exception expected')  
except transitfeed.InvalidValue, e:  
self.assertEqual('departure_time', e.column_name)  
self.assertEqual('', e.value)  
 
 
class DuplicateScheduleIDTestCase(unittest.TestCase):  
def runTest(self):  
schedule = transitfeed.Schedule(  
problem_reporter=ExceptionProblemReporterNoExpiration())  
try:  
schedule.Load(DataPath('duplicate_schedule_id'), extra_validation=True)  
self.fail('DuplicateID exception expected')  
except transitfeed.DuplicateID:  
pass  
 
class ColorLuminanceTestCase(unittest.TestCase):  
def runTest(self):  
self.assertEqual(transitfeed.ColorLuminance('000000'), 0,  
"ColorLuminance('000000') should be zero")  
self.assertEqual(transitfeed.ColorLuminance('FFFFFF'), 255,  
"ColorLuminance('FFFFFF') should be 255")  
RGBmsg = ("ColorLuminance('RRGGBB') should be "  
"0.299*<Red> + 0.587*<Green> + 0.114*<Blue>")  
decimal_places_tested = 8  
self.assertAlmostEqual(transitfeed.ColorLuminance('640000'), 29.9,  
decimal_places_tested, RGBmsg)  
self.assertAlmostEqual(transitfeed.ColorLuminance('006400'), 58.7,  
decimal_places_tested, RGBmsg)  
self.assertAlmostEqual(transitfeed.ColorLuminance('000064'), 11.4,  
decimal_places_tested, RGBmsg)  
self.assertAlmostEqual(transitfeed.ColorLuminance('1171B3'),  
0.299*17 + 0.587*113 + 0.114*179,  
decimal_places_tested, RGBmsg)  
 
INVALID_VALUE = Exception()  
class ValidationTestCase(util.TestCaseAsserts):  
def setUp(self):  
self.problems = RecordingProblemReporter(self, ("ExpirationDate",))  
 
def ExpectNoProblems(self, object):  
self.problems.AssertNoMoreExceptions()  
object.Validate(self.problems)  
self.problems.AssertNoMoreExceptions()  
 
# TODO: Get rid of Expect*Closure methods. With the  
# RecordingProblemReporter it is now possible to replace  
# self.ExpectMissingValueInClosure(lambda: o.method(...), foo)  
# with  
# o.method(...)  
# self.ExpectMissingValueInClosure(foo)  
# because problems don't raise an exception. This has the advantage of  
# making it easy and clear to test the return value of o.method(...) and  
# easier to test for a sequence of problems caused by one call.  
def ExpectMissingValue(self, object, column_name):  
self.ExpectMissingValueInClosure(column_name,  
lambda: object.Validate(self.problems))  
 
def ExpectMissingValueInClosure(self, column_name, c):  
self.problems.AssertNoMoreExceptions()  
rv = c()  
e = self.problems.PopException('MissingValue')  
self.assertEqual(column_name, e.column_name)  
# these should not throw any exceptions  
e.FormatProblem()  
e.FormatContext()  
self.problems.AssertNoMoreExceptions()  
 
def ExpectInvalidValue(self, object, column_name, value=INVALID_VALUE):  
self.ExpectInvalidValueInClosure(column_name, value,  
lambda: object.Validate(self.problems))  
 
def ExpectInvalidValueInClosure(self, column_name, value=INVALID_VALUE,  
c=None):  
self.problems.AssertNoMoreExceptions()  
rv = c()  
e = self.problems.PopException('InvalidValue')  
self.assertEqual(column_name, e.column_name)  
if value != INVALID_VALUE:  
self.assertEqual(value, e.value)  
# these should not throw any exceptions  
e.FormatProblem()  
e.FormatContext()  
self.problems.AssertNoMoreExceptions()  
 
def ExpectOtherProblem(self, object):  
self.ExpectOtherProblemInClosure(lambda: object.Validate(self.problems))  
 
def ExpectOtherProblemInClosure(self, c):  
self.problems.AssertNoMoreExceptions()  
rv = c()  
e = self.problems.PopException('OtherProblem')  
# these should not throw any exceptions  
e.FormatProblem()  
e.FormatContext()  
self.problems.AssertNoMoreExceptions()  
 
 
class AgencyValidationTestCase(ValidationTestCase):  
def runTest(self):  
# success case  
agency = transitfeed.Agency(name='Test Agency', url='http://example.com',  
timezone='America/Los_Angeles', id='TA',  
lang='xh')  
self.ExpectNoProblems(agency)  
 
# bad agency  
agency = transitfeed.Agency(name=' ', url='http://example.com',  
timezone='America/Los_Angeles', id='TA')  
self.ExpectMissingValue(agency, 'agency_name')  
 
# missing url  
agency = transitfeed.Agency(name='Test Agency',  
timezone='America/Los_Angeles', id='TA')  
self.ExpectMissingValue(agency, 'agency_url')  
 
# bad url  
agency = transitfeed.Agency(name='Test Agency', url='www.example.com',  
timezone='America/Los_Angeles', id='TA')  
self.ExpectInvalidValue(agency, 'agency_url')  
 
# bad time zone  
agency = transitfeed.Agency(name='Test Agency', url='http://example.com',  
timezone='America/Alviso', id='TA')  
agency.Validate(self.problems)  
e = self.problems.PopInvalidValue('agency_timezone')  
self.assertMatchesRegex('"America/Alviso" is not a common timezone',  
e.FormatProblem())  
self.problems.AssertNoMoreExceptions()  
 
# bad language code  
agency = transitfeed.Agency(name='Test Agency', url='http://example.com',  
timezone='America/Los_Angeles', id='TA',  
lang='English')  
self.ExpectInvalidValue(agency, 'agency_lang')  
 
# bad 2-letter lanugage code  
agency = transitfeed.Agency(name='Test Agency', url='http://example.com',  
timezone='America/Los_Angeles', id='TA',  
lang='xx')  
self.ExpectInvalidValue(agency, 'agency_lang')  
 
# capitalized language code is OK  
agency = transitfeed.Agency(name='Test Agency', url='http://example.com',  
timezone='America/Los_Angeles', id='TA',  
lang='EN')  
self.ExpectNoProblems(agency)  
 
# extra attribute in constructor is fine, only checked when loading a file  
agency = transitfeed.Agency(name='Test Agency', url='http://example.com',  
timezone='America/Los_Angeles',  
agency_mission='monorail you there')  
self.ExpectNoProblems(agency)  
 
# extra attribute in assigned later is also fine  
agency = transitfeed.Agency(name='Test Agency', url='http://example.com',  
timezone='America/Los_Angeles')  
agency.agency_mission='monorail you there'  
self.ExpectNoProblems(agency)  
 
# Multiple problems  
agency = transitfeed.Agency(name='Test Agency', url='www.example.com',  
timezone='America/West Coast', id='TA')  
self.assertEquals(False, agency.Validate(self.problems))  
e = self.problems.PopException('InvalidValue')  
self.assertEqual(e.column_name, 'agency_url')  
e = self.problems.PopException('InvalidValue')  
self.assertEqual(e.column_name, 'agency_timezone')  
self.problems.AssertNoMoreExceptions()  
 
 
 
class AgencyAttributesTestCase(ValidationTestCase):  
def testCopy(self):  
agency = transitfeed.Agency(field_dict={'agency_name': 'Test Agency',  
'agency_url': 'http://example.com',  
'timezone': 'America/Los_Angeles',  
'agency_mission': 'get you there'})  
self.assertEquals(agency.agency_mission, 'get you there')  
agency_copy = transitfeed.Agency(field_dict=agency)  
self.assertEquals(agency_copy.agency_mission, 'get you there')  
self.assertEquals(agency_copy['agency_mission'], 'get you there')  
 
def testEq(self):  
agency1 = transitfeed.Agency("Test Agency", "http://example.com",  
"America/Los_Angeles")  
agency2 = transitfeed.Agency("Test Agency", "http://example.com",  
"America/Los_Angeles")  
# Unknown columns, such as agency_mission, do affect equality  
self.assertEquals(agency1, agency2)  
agency1.agency_mission = "Get you there"  
self.assertNotEquals(agency1, agency2)  
agency2.agency_mission = "Move you"  
self.assertNotEquals(agency1, agency2)  
agency1.agency_mission = "Move you"  
self.assertEquals(agency1, agency2)  
# Private attributes don't affect equality  
agency1._private_attr = "My private message"  
self.assertEquals(agency1, agency2)  
agency2._private_attr = "Another private thing"  
self.assertEquals(agency1, agency2)  
 
def testDict(self):  
agency = transitfeed.Agency("Test Agency", "http://example.com",  
"America/Los_Angeles")  
agency._private_attribute = "blah"  
# Private attributes don't appear when iterating through an agency as a  
# dict but can be directly accessed.  
self.assertEquals("blah", agency._private_attribute)  
self.assertEquals("blah", agency["_private_attribute"])  
self.assertEquals(  
set("agency_name agency_url agency_timezone".split()),  
set(agency.keys()))  
self.assertEquals({"agency_name": "Test Agency",  
"agency_url": "http://example.com",  
"agency_timezone": "America/Los_Angeles"},  
dict(agency.iteritems()))  
 
 
class StopValidationTestCase(ValidationTestCase):  
def runTest(self):  
# success case  
stop = transitfeed.Stop()  
stop.stop_id = '45'  
stop.stop_name = 'Couch AT End Table'  
stop.stop_lat = 50.0  
stop.stop_lon = 50.0  
stop.stop_desc = 'Edge of the Couch'  
stop.zone_id = 'A'  
stop.stop_url = 'http://example.com'  
stop.Validate(self.problems)  
 
# latitude too large  
stop.stop_lat = 100.0  
self.ExpectInvalidValue(stop, 'stop_lat')  
stop.stop_lat = 50.0  
 
# latitude as a string works when it is valid  
stop.stop_lat = '50.0'  
stop.Validate(self.problems)  
self.problems.AssertNoMoreExceptions()  
stop.stop_lat = '10f'  
self.ExpectInvalidValue(stop, 'stop_lat')  
stop.stop_lat = 50.0  
 
# longitude too large  
stop.stop_lon = 200.0  
self.ExpectInvalidValue(stop, 'stop_lon')  
stop.stop_lon = 50.0  
 
# lat, lon too close to 0, 0  
stop.stop_lat = 0.0  
stop.stop_lon = 0.0  
self.ExpectInvalidValue(stop, 'stop_lat')  
stop.stop_lat = 50.0  
stop.stop_lon = 50.0  
 
# invalid stop_url  
stop.stop_url = 'www.example.com'  
self.ExpectInvalidValue(stop, 'stop_url')  
stop.stop_url = 'http://example.com'  
 
stop.stop_id = ' '  
self.ExpectMissingValue(stop, 'stop_id')  
stop.stop_id = '45'  
 
stop.stop_name = ''  
self.ExpectMissingValue(stop, 'stop_name')  
stop.stop_name = 'Couch AT End Table'  
 
# description same as name  
stop.stop_desc = 'Couch AT End Table'  
self.ExpectInvalidValue(stop, 'stop_desc')  
stop.stop_desc = 'Edge of the Couch'  
self.problems.AssertNoMoreExceptions()  
 
 
class StopAttributes(ValidationTestCase):  
def testWithoutSchedule(self):  
stop = transitfeed.Stop()  
stop.Validate(self.problems)  
for name in "stop_id stop_name stop_lat stop_lon".split():  
e = self.problems.PopException('MissingValue')  
self.assertEquals(name, e.column_name)  
self.problems.AssertNoMoreExceptions()  
 
stop = transitfeed.Stop()  
# Test behaviour for unset and unknown attribute  
self.assertEquals(stop['new_column'], '')  
try:  
t = stop.new_column  
self.fail('Expecting AttributeError')  
except AttributeError, e:  
pass # Expected  
stop.stop_id = 'a'  
stop.stop_name = 'my stop'  
stop.new_column = 'val'  
stop.stop_lat = 5.909  
stop.stop_lon = '40.02'  
self.assertEquals(stop.new_column, 'val')  
self.assertEquals(stop['new_column'], 'val')  
self.assertTrue(isinstance(stop['stop_lat'], basestring))  
self.assertAlmostEqual(float(stop['stop_lat']), 5.909)  
self.assertTrue(isinstance(stop['stop_lon'], basestring))  
self.assertAlmostEqual(float(stop['stop_lon']), 40.02)  
stop.Validate(self.problems)  
self.problems.AssertNoMoreExceptions()  
# After validation stop.stop_lon has been converted to a float  
self.assertAlmostEqual(stop.stop_lat, 5.909)  
self.assertAlmostEqual(stop.stop_lon, 40.02)  
self.assertEquals(stop.new_column, 'val')  
self.assertEquals(stop['new_column'], 'val')  
 
def testBlankAttributeName(self):  
stop1 = transitfeed.Stop(field_dict={"": "a"})  
stop2 = transitfeed.Stop(field_dict=stop1)  
self.assertEquals("a", getattr(stop1, ""))  
# The attribute "" is treated as private and not copied  
self.assertRaises(AttributeError, getattr, stop2, "")  
self.assertEquals(set(), set(stop1.keys()))  
self.assertEquals(set(), set(stop2.keys()))  
 
def testWithSchedule(self):  
schedule = transitfeed.Schedule(problem_reporter=self.problems)  
 
stop = transitfeed.Stop(field_dict={})  
# AddStopObject silently fails for Stop objects without stop_id  
schedule.AddStopObject(stop)  
self.assertFalse(schedule.GetStopList())  
self.assertFalse(stop._schedule)  
 
# Okay to add a stop with only stop_id  
stop = transitfeed.Stop(field_dict={"stop_id": "b"})  
schedule.AddStopObject(stop)  
stop.Validate(self.problems)  
for name in "stop_name stop_lat stop_lon".split():  
e = self.problems.PopException("MissingValue")  
self.assertEquals(name, e.column_name)  
self.problems.AssertNoMoreExceptions()  
 
stop.new_column = "val"  
self.assertTrue("new_column" in schedule.GetTableColumns("stops"))  
 
# Adding a duplicate stop_id fails  
schedule.AddStopObject(transitfeed.Stop(field_dict={"stop_id": "b"}))  
self.problems.PopException("DuplicateID")  
self.problems.AssertNoMoreExceptions()  
 
 
class StopTimeValidationTestCase(ValidationTestCase):  
def runTest(self):  
stop = transitfeed.Stop()  
self.ExpectInvalidValueInClosure('arrival_time', '1a:00:00',  
lambda: transitfeed.StopTime(self.problems, stop,  
arrival_time="1a:00:00"))  
self.ExpectInvalidValueInClosure('departure_time', '1a:00:00',  
lambda: transitfeed.StopTime(self.problems, stop,  
arrival_time="10:00:00",  
departure_time='1a:00:00'))  
self.ExpectInvalidValueInClosure('pickup_type', '7.8',  
lambda: transitfeed.StopTime(self.problems, stop,  
arrival_time="10:00:00",  
departure_time='10:05:00',  
pickup_type='7.8',  
drop_off_type='0'))  
self.ExpectInvalidValueInClosure('drop_off_type', 'a',  
lambda: transitfeed.StopTime(self.problems, stop,  
arrival_time="10:00:00",  
departure_time='10:05:00',  
pickup_type='3',  
drop_off_type='a'))  
self.ExpectInvalidValueInClosure('shape_dist_traveled', '$',  
lambda: transitfeed.StopTime(self.problems, stop,  
arrival_time="10:00:00",  
departure_time='10:05:00',  
pickup_type='3',  
drop_off_type='0',  
shape_dist_traveled='$'))  
self.ExpectInvalidValueInClosure('shape_dist_traveled', '0,53',  
lambda: transitfeed.StopTime(self.problems, stop,  
arrival_time="10:00:00",  
departure_time='10:05:00',  
pickup_type='3',  
drop_off_type='0',  
shape_dist_traveled='0,53'))  
self.ExpectOtherProblemInClosure(  
lambda: transitfeed.StopTime(self.problems, stop,  
pickup_type='1', drop_off_type='1'))  
self.ExpectInvalidValueInClosure('departure_time', '10:00:00',  
lambda: transitfeed.StopTime(self.problems, stop,  
arrival_time="11:00:00",  
departure_time="10:00:00"))  
self.ExpectMissingValueInClosure('arrival_time',  
lambda: transitfeed.StopTime(self.problems, stop,  
departure_time="10:00:00"))  
self.ExpectMissingValueInClosure('arrival_time',  
lambda: transitfeed.StopTime(self.problems, stop,  
departure_time="10:00:00",  
arrival_time=""))  
self.ExpectMissingValueInClosure('departure_time',  
lambda: transitfeed.StopTime(self.problems, stop,  
arrival_time="10:00:00"))  
self.ExpectMissingValueInClosure('departure_time',  
lambda: transitfeed.StopTime(self.problems, stop,  
arrival_time="10:00:00",  
departure_time=""))  
self.ExpectInvalidValueInClosure('departure_time', '10:70:00',  
lambda: transitfeed.StopTime(self.problems, stop,  
arrival_time="10:00:00",  
departure_time="10:70:00"))  
self.ExpectInvalidValueInClosure('departure_time', '10:00:62',  
lambda: transitfeed.StopTime(self.problems, stop,  
arrival_time="10:00:00",  
departure_time="10:00:62"))  
self.ExpectInvalidValueInClosure('arrival_time', '10:00:63',  
lambda: transitfeed.StopTime(self.problems, stop,  
arrival_time="10:00:63",  
departure_time="10:10:00"))  
self.ExpectInvalidValueInClosure('arrival_time', '10:60:00',  
lambda: transitfeed.StopTime(self.problems, stop,  
arrival_time="10:60:00",  
departure_time="11:02:00"))  
# The following should work  
transitfeed.StopTime(self.problems, stop, arrival_time="10:00:00",  
departure_time="10:05:00", pickup_type='1', drop_off_type='1')  
transitfeed.StopTime(self.problems, stop, arrival_time="1:00:00",  
departure_time="1:05:00")  
transitfeed.StopTime(self.problems, stop, arrival_time="24:59:00",  
departure_time="25:05:00")  
transitfeed.StopTime(self.problems, stop, arrival_time="101:01:00",  
departure_time="101:21:00")  
transitfeed.StopTime(self.problems, stop)  
self.problems.AssertNoMoreExceptions()  
 
class TooFastTravelTestCase(ValidationTestCase):  
def setUp(self):  
ValidationTestCase.setUp(self)  
self.schedule = transitfeed.Schedule(problem_reporter=self.problems)  
self.schedule.NewDefaultAgency(agency_name="Test Agency",  
agency_url="http://example.com",  
agency_timezone="America/Los_Angeles")  
self.route = self.schedule.AddRoute(short_name="54C",  
long_name="Polish Hill", route_type=3)  
service_period = self.schedule.GetDefaultServicePeriod()  
service_period.SetDateHasService("20070101")  
self.trip = self.route.AddTrip(self.schedule, 'via Polish Hill')  
 
def AddStopDistanceTime(self, dist_time_list):  
# latitude where each 0.01 degrees longitude is 1km  
magic_lat = 26.062468289  
stop = self.schedule.AddStop(magic_lat, 0, "Demo Stop 0")  
time = 0  
self.trip.AddStopTime(stop, arrival_secs=time, departure_secs=time)  
for i, (dist_delta, time_delta) in enumerate(dist_time_list):  
stop = self.schedule.AddStop(  
magic_lat, stop.stop_lon + dist_delta * 0.00001,  
"Demo Stop %d" % (i + 1))  
time += time_delta  
self.trip.AddStopTime(stop, arrival_secs=time, departure_secs=time)  
 
def testMovingTooFast(self):  
self.AddStopDistanceTime([(1691, 60),  
(1616, 60)])  
 
self.trip.Validate(self.problems)  
e = self.problems.PopException('TooFastTravel')  
self.assertMatchesRegex(r'High speed travel detected', e.FormatProblem())  
self.assertMatchesRegex(r'Stop 0 to Demo Stop 1', e.FormatProblem())  
self.assertMatchesRegex(r'1691 meters in 60 seconds', e.FormatProblem())  
self.assertMatchesRegex(r'\(101 km/h\)', e.FormatProblem())  
self.assertEqual(e.type, transitfeed.TYPE_WARNING)  
self.problems.AssertNoMoreExceptions()  
 
self.route.route_type = 4 # Ferry with max_speed 80  
self.trip.Validate(self.problems)  
e = self.problems.PopException('TooFastTravel')  
self.assertMatchesRegex(r'High speed travel detected', e.FormatProblem())  
self.assertMatchesRegex(r'Stop 0 to Demo Stop 1', e.FormatProblem())  
self.assertMatchesRegex(r'1691 meters in 60 seconds', e.FormatProblem())  
self.assertMatchesRegex(r'\(101 km/h\)', e.FormatProblem())  
self.assertEqual(e.type, transitfeed.TYPE_WARNING)  
e = self.problems.PopException('TooFastTravel')  
self.assertMatchesRegex(r'High speed travel detected', e.FormatProblem())  
self.assertMatchesRegex(r'Stop 1 to Demo Stop 2', e.FormatProblem())  
self.assertMatchesRegex(r'1616 meters in 60 seconds', e.FormatProblem())  
self.assertMatchesRegex(r'97 km/h', e.FormatProblem())  
self.assertEqual(e.type, transitfeed.TYPE_WARNING)  
self.problems.AssertNoMoreExceptions()  
 
# Run test without a route_type  
self.route.route_type = None  
self.trip.Validate(self.problems)  
e = self.problems.PopException('TooFastTravel')  
self.assertMatchesRegex(r'High speed travel detected', e.FormatProblem())  
self.assertMatchesRegex(r'Stop 0 to Demo Stop 1', e.FormatProblem())  
self.assertMatchesRegex(r'1691 meters in 60 seconds', e.FormatProblem())  
self.assertMatchesRegex(r'101 km/h', e.FormatProblem())  
self.assertEqual(e.type, transitfeed.TYPE_WARNING)  
self.problems.AssertNoMoreExceptions()  
 
def testNoTimeDelta(self):  
# See comments where TooFastTravel is called in transitfeed.py to  
# understand why was added.  
# Movement more than max_speed in 1 minute with no time change is a warning.  
self.AddStopDistanceTime([(1616, 0),  
(1000, 120),  
(1691, 0)])  
 
self.trip.Validate(self.problems)  
e = self.problems.PopException('TooFastTravel')  
self.assertMatchesRegex('High speed travel detected', e.FormatProblem())  
self.assertMatchesRegex('Stop 2 to Demo Stop 3', e.FormatProblem())  
self.assertMatchesRegex('1691 meters in 0 seconds', e.FormatProblem())  
self.assertEqual(e.type, transitfeed.TYPE_WARNING)  
self.problems.AssertNoMoreExceptions()  
 
self.route.route_type = 4 # Ferry with max_speed 80  
self.trip.Validate(self.problems)  
self.assertEqual(e.type, transitfeed.TYPE_WARNING)  
e = self.problems.PopException('TooFastTravel')  
self.assertMatchesRegex('High speed travel detected', e.FormatProblem())  
self.assertMatchesRegex('Stop 0 to Demo Stop 1', e.FormatProblem())  
self.assertMatchesRegex('1616 meters in 0 seconds', e.FormatProblem())  
self.assertEqual(e.type, transitfeed.TYPE_WARNING)  
e = self.problems.PopException('TooFastTravel')  
self.assertMatchesRegex('High speed travel detected', e.FormatProblem())  
self.assertMatchesRegex('Stop 2 to Demo Stop 3', e.FormatProblem())  
self.assertMatchesRegex('1691 meters in 0 seconds', e.FormatProblem())  
self.assertEqual(e.type, transitfeed.TYPE_WARNING)  
self.problems.AssertNoMoreExceptions()  
 
# Run test without a route_type  
self.route.route_type = None  
self.trip.Validate(self.problems)  
e = self.problems.PopException('TooFastTravel')  
self.assertMatchesRegex('High speed travel detected', e.FormatProblem())  
self.assertMatchesRegex('Stop 2 to Demo Stop 3', e.FormatProblem())  
self.assertMatchesRegex('1691 meters in 0 seconds', e.FormatProblem())  
self.assertEqual(e.type, transitfeed.TYPE_WARNING)  
self.problems.AssertNoMoreExceptions()  
 
def testNoTimeDeltaNotRounded(self):  
# See comments where TooFastTravel is called in transitfeed.py to  
# understand why was added.  
# Any movement with no time change and times not rounded to the nearest  
# minute causes a warning.  
self.AddStopDistanceTime([(500, 62),  
(10, 0)])  
 
self.trip.Validate(self.problems)  
e = self.problems.PopException('TooFastTravel')  
self.assertMatchesRegex('High speed travel detected', e.FormatProblem())  
self.assertMatchesRegex('Stop 1 to Demo Stop 2', e.FormatProblem())  
self.assertMatchesRegex('10 meters in 0 seconds', e.FormatProblem())  
self.assertEqual(e.type, transitfeed.TYPE_WARNING)  
self.problems.AssertNoMoreExceptions()  
 
 
class MemoryZipTestCase(util.TestCaseAsserts):  
def setUp(self):  
self.problems = RecordingProblemReporter(self, ("ExpirationDate",))  
self.zipfile = StringIO()  
self.zip = zipfile.ZipFile(self.zipfile, 'a')  
self.zip.writestr(  
"agency.txt",  
"agency_id,agency_name,agency_url,agency_timezone\n"  
"DTA,Demo Agency,http://google.com,America/Los_Angeles\n")  
self.zip.writestr(  
"calendar.txt",  
"service_id,monday,tuesday,wednesday,thursday,friday,saturday,sunday,"  
"start_date,end_date\n"  
"FULLW,1,1,1,1,1,1,1,20070101,20101231\n"  
"WE,0,0,0,0,0,1,1,20070101,20101231\n")  
self.zip.writestr(  
"routes.txt",  
"route_id,agency_id,route_short_name,route_long_name,route_type\n"  
"AB,DTA,,Airport Bullfrog,3\n")  
self.zip.writestr(  
"trips.txt",  
"route_id,service_id,trip_id\n"  
"AB,FULLW,AB1\n")  
self.zip.writestr(  
"stops.txt",  
"stop_id,stop_name,stop_lat,stop_lon\n"  
"BEATTY_AIRPORT,Airport,36.868446,-116.784582\n"  
"BULLFROG,Bullfrog,36.88108,-116.81797\n"  
"STAGECOACH,Stagecoach Hotel,36.915682,-116.751677\n")  
self.zip.writestr(  
"stop_times.txt",  
"trip_id,arrival_time,departure_time,stop_id,stop_sequence\n"  
"AB1,10:00:00,10:00:00,BEATTY_AIRPORT,1\n"  
"AB1,10:20:00,10:20:00,BULLFROG,2\n"  
"AB1,10:25:00,10:25:00,STAGECOACH,3\n")  
self.loader = transitfeed.Loader(  
problems=self.problems,  
extra_validation=True,  
zip=self.zip)  
 
def appendToZip(self, file, arcname, s):  
"""Append s to the arcname in the zip stored in a file object."""  
zip = zipfile.ZipFile(file, 'a')  
zip.writestr(arcname, zip.read(arcname) + s)  
zip.close()  
 
 
class CsvDictTestCase(unittest.TestCase):  
def setUp(self):  
self.problems = RecordingProblemReporter(self)  
self.zip = zipfile.ZipFile(StringIO(), 'a')  
self.loader = transitfeed.Loader(  
problems=self.problems,  
zip=self.zip)  
 
def testEmptyFile(self):  
self.zip.writestr("test.txt", "")  
results = list(self.loader._ReadCsvDict("test.txt", [], []))  
self.assertEquals([], results)  
self.problems.PopException("EmptyFile")  
self.problems.AssertNoMoreExceptions()  
 
def testHeaderOnly(self):  
self.zip.writestr("test.txt", "test_id,test_name")  
results = list(self.loader._ReadCsvDict("test.txt",  
["test_id", "test_name"], []))  
self.assertEquals([], results)  
self.problems.AssertNoMoreExceptions()  
 
def testHeaderAndNewLineOnly(self):  
self.zip.writestr("test.txt", "test_id,test_name\n")  
results = list(self.loader._ReadCsvDict("test.txt",  
["test_id", "test_name"], []))  
self.assertEquals([], results)  
self.problems.AssertNoMoreExceptions()  
 
def testHeaderWithSpaceBefore(self):  
self.zip.writestr("test.txt", " test_id, test_name\n")  
results = list(self.loader._ReadCsvDict("test.txt",  
["test_id", "test_name"], []))  
self.assertEquals([], results)  
self.problems.AssertNoMoreExceptions()  
 
def testHeaderWithSpaceBeforeAfter(self):  
self.zip.writestr("test.txt", "test_id , test_name\n")  
results = list(self.loader._ReadCsvDict("test.txt",  
["test_id", "test_name"], []))  
self.assertEquals([], results)  
e = self.problems.PopException("CsvSyntax")  
self.problems.AssertNoMoreExceptions()  
 
def testHeaderQuoted(self):  
self.zip.writestr("test.txt", "\"test_id\", \"test_name\"\n")  
results = list(self.loader._ReadCsvDict("test.txt",  
["test_id", "test_name"], []))  
self.assertEquals([], results)  
self.problems.AssertNoMoreExceptions()  
 
def testHeaderSpaceAfterQuoted(self):  
self.zip.writestr("test.txt", "\"test_id\" , \"test_name\"\n")  
results = list(self.loader._ReadCsvDict("test.txt",  
["test_id", "test_name"], []))  
self.assertEquals([], results)  
e = self.problems.PopException("CsvSyntax")  
self.problems.AssertNoMoreExceptions()  
 
def testHeaderSpaceInQuotesAfterValue(self):  
self.zip.writestr("test.txt", "\"test_id \",\"test_name\"\n")  
results = list(self.loader._ReadCsvDict("test.txt",  
["test_id", "test_name"], []))  
self.assertEquals([], results)  
e = self.problems.PopException("CsvSyntax")  
self.problems.AssertNoMoreExceptions()  
 
def testHeaderSpaceInQuotesBeforeValue(self):  
self.zip.writestr("test.txt", "\"test_id\",\" test_name\"\n")  
results = list(self.loader._ReadCsvDict("test.txt",  
["test_id", "test_name"], []))  
self.assertEquals([], results)  
e = self.problems.PopException("CsvSyntax")  
self.problems.AssertNoMoreExceptions()  
 
def testHeaderEmptyColumnName(self):  
self.zip.writestr("test.txt", 'test_id,test_name,\n')  
results = list(self.loader._ReadCsvDict("test.txt",  
["test_id", "test_name"], []))  
self.assertEquals([], results)  
e = self.problems.PopException("CsvSyntax")  
self.problems.AssertNoMoreExceptions()  
 
def testHeaderAllUnknownColumnNames(self):  
self.zip.writestr("test.txt", 'id,nam\n')  
results = list(self.loader._ReadCsvDict("test.txt",  
["test_id", "test_name"], []))  
self.assertEquals([], results)  
e = self.problems.PopException("CsvSyntax")  
self.assertTrue(e.FormatProblem().find("missing the header") != -1)  
self.problems.AssertNoMoreExceptions()  
 
def testFieldWithSpaces(self):  
self.zip.writestr("test.txt",  
"test_id,test_name\n"  
"id1 , my name\n")  
results = list(self.loader._ReadCsvDict("test.txt",  
["test_id", "test_name"], []))  
self.assertEquals([({"test_id": "id1 ", "test_name": "my name"}, 2,  
["test_id", "test_name"], ["id1 ","my name"])], results)  
self.problems.AssertNoMoreExceptions()  
 
def testFieldWithOnlySpaces(self):  
self.zip.writestr("test.txt",  
"test_id,test_name\n"  
"id1, \n") # spaces are skipped to yield empty field  
results = list(self.loader._ReadCsvDict("test.txt",  
["test_id", "test_name"], []))  
self.assertEquals([({"test_id": "id1", "test_name": ""}, 2,  
["test_id", "test_name"], ["id1",""])], results)  
self.problems.AssertNoMoreExceptions()  
 
def testQuotedFieldWithSpaces(self):  
self.zip.writestr("test.txt",  
'test_id,"test_name",test_size\n'  
'"id1" , "my name" , "234 "\n')  
results = list(self.loader._ReadCsvDict("test.txt",  
["test_id", "test_name",  
"test_size"], []))  
self.assertEquals(  
[({"test_id": "id1 ", "test_name": "my name ", "test_size": "234 "}, 2,  
["test_id", "test_name", "test_size"], ["id1 ", "my name ", "234 "])],  
results)  
self.problems.AssertNoMoreExceptions()  
 
def testQuotedFieldWithCommas(self):  
self.zip.writestr("test.txt",  
'id,name1,name2\n'  
'"1", "brown, tom", "brown, ""tom"""\n')  
results = list(self.loader._ReadCsvDict("test.txt",  
["id", "name1", "name2"], []))  
self.assertEquals(  
[({"id": "1", "name1": "brown, tom", "name2": "brown, \"tom\""}, 2,  
["id", "name1", "name2"], ["1", "brown, tom", "brown, \"tom\""])],  
results)  
self.problems.AssertNoMoreExceptions()  
 
def testUnknownColumn(self):  
# A small typo (omitting '_' in a header name) is detected  
self.zip.writestr("test.txt", "test_id,testname\n")  
results = list(self.loader._ReadCsvDict("test.txt",  
["test_id", "test_name"], []))  
self.assertEquals([], results)  
e = self.problems.PopException("UnrecognizedColumn")  
self.assertEquals("testname", e.column_name)  
self.problems.AssertNoMoreExceptions()  
 
def testMissingRequiredColumn(self):  
self.zip.writestr("test.txt", "test_id,test_size\n")  
results = list(self.loader._ReadCsvDict("test.txt",  
["test_id", "test_size"],  
["test_name"]))  
self.assertEquals([], results)  
e = self.problems.PopException("MissingColumn")  
self.assertEquals("test_name", e.column_name)  
self.problems.AssertNoMoreExceptions()  
 
def testRequiredNotInAllCols(self):  
self.zip.writestr("test.txt", "test_id,test_name,test_size\n")  
results = list(self.loader._ReadCsvDict("test.txt",  
["test_id", "test_size"],  
["test_name"]))  
self.assertEquals([], results)  
e = self.problems.PopException("UnrecognizedColumn")  
self.assertEquals("test_name", e.column_name)  
self.problems.AssertNoMoreExceptions()  
 
def testBlankLine(self):  
# line_num is increased for an empty line  
self.zip.writestr("test.txt",  
"test_id,test_name\n"  
"\n"  
"id1,my name\n")  
results = list(self.loader._ReadCsvDict("test.txt",  
["test_id", "test_name"], []))  
self.assertEquals([({"test_id": "id1", "test_name": "my name"}, 3,  
["test_id", "test_name"], ["id1","my name"])], results)  
self.problems.AssertNoMoreExceptions()  
 
def testExtraComma(self):  
self.zip.writestr("test.txt",  
"test_id,test_name\n"  
"id1,my name,\n")  
results = list(self.loader._ReadCsvDict("test.txt",  
["test_id", "test_name"], []))  
self.assertEquals([({"test_id": "id1", "test_name": "my name"}, 2,  
["test_id", "test_name"], ["id1","my name"])],  
results)  
e = self.problems.PopException("OtherProblem")  
self.assertTrue(e.FormatProblem().find("too many cells") != -1)  
self.problems.AssertNoMoreExceptions()  
 
def testMissingComma(self):  
self.zip.writestr("test.txt",  
"test_id,test_name\n"  
"id1 my name\n")  
results = list(self.loader._ReadCsvDict("test.txt",  
["test_id", "test_name"], []))  
self.assertEquals([({"test_id": "id1 my name"}, 2,  
["test_id", "test_name"], ["id1 my name"])], results)  
e = self.problems.PopException("OtherProblem")  
self.assertTrue(e.FormatProblem().find("missing cells") != -1)  
self.problems.AssertNoMoreExceptions()  
 
def testDetectsDuplicateHeaders(self):  
self.zip.writestr(  
"transfers.txt",  
"from_stop_id,from_stop_id,to_stop_id,transfer_type,min_transfer_time,"  
"min_transfer_time,min_transfer_time,min_transfer_time,unknown,"  
"unknown\n"  
"BEATTY_AIRPORT,BEATTY_AIRPORT,BULLFROG,3,,2,,,,\n"  
"BULLFROG,BULLFROG,BEATTY_AIRPORT,2,1200,1,,,,\n")  
 
list(self.loader._ReadCsvDict("transfers.txt",  
transitfeed.Transfer._FIELD_NAMES,  
transitfeed.Transfer._REQUIRED_FIELD_NAMES))  
 
self.problems.PopDuplicateColumn("transfers.txt","min_transfer_time",4)  
self.problems.PopDuplicateColumn("transfers.txt","from_stop_id",2)  
self.problems.PopDuplicateColumn("transfers.txt","unknown",2)  
e = self.problems.PopException("UnrecognizedColumn")  
self.assertEquals("unknown", e.column_name)  
self.problems.AssertNoMoreExceptions()  
 
 
class ReadCsvTestCase(unittest.TestCase):  
def setUp(self):  
self.problems = RecordingProblemReporter(self)  
self.zip = zipfile.ZipFile(StringIO(), 'a')  
self.loader = transitfeed.Loader(  
problems=self.problems,  
zip=self.zip)  
 
def testDetectsDuplicateHeaders(self):  
self.zip.writestr(  
"calendar.txt",  
"service_id,monday,tuesday,wednesday,thursday,friday,saturday,sunday,"  
"start_date,end_date,end_date,end_date,tuesday,unknown,unknown\n"  
"FULLW,1,1,1,1,1,1,1,20070101,20101231,,,,,\n")  
 
list(self.loader._ReadCSV("calendar.txt",  
transitfeed.ServicePeriod._FIELD_NAMES,  
transitfeed.ServicePeriod._FIELD_NAMES_REQUIRED))  
 
self.problems.PopDuplicateColumn("calendar.txt","end_date",3)  
self.problems.PopDuplicateColumn("calendar.txt","unknown",2)  
self.problems.PopDuplicateColumn("calendar.txt","tuesday",2)  
e = self.problems.PopException("UnrecognizedColumn")  
self.assertEquals("unknown", e.column_name)  
self.problems.AssertNoMoreExceptions()  
 
 
class BasicMemoryZipTestCase(MemoryZipTestCase):  
def runTest(self):  
self.loader.Load()  
self.problems.AssertNoMoreExceptions()  
 
 
class ZipCompressionTestCase(MemoryZipTestCase):  
def runTest(self):  
schedule = self.loader.Load()  
self.zip.close()  
write_output = StringIO()  
schedule.WriteGoogleTransitFeed(write_output)  
recompressedzip = zlib.compress(write_output.getvalue())  
write_size = len(write_output.getvalue())  
recompressedzip_size = len(recompressedzip)  
# If zlib can compress write_output it probably wasn't compressed  
self.assertFalse(  
recompressedzip_size < write_size * 0.60,  
"Are you sure WriteGoogleTransitFeed wrote a compressed zip? "  
"Orginial size: %d recompressed: %d" %  
(write_size, recompressedzip_size))  
 
 
class StopHierarchyTestCase(MemoryZipTestCase):  
def testParentAtSameLatLon(self):  
self.zip.writestr(  
"stops.txt",  
"stop_id,stop_name,stop_lat,stop_lon,location_type,parent_station\n"  
"BEATTY_AIRPORT,Airport,36.868446,-116.784582,,STATION\n"  
"STATION,Airport,36.868446,-116.784582,1,\n"  
"BULLFROG,Bullfrog,36.88108,-116.81797,,\n"  
"STAGECOACH,Stagecoach Hotel,36.915682,-116.751677,,\n")  
schedule = self.loader.Load()  
self.assertEquals(1, schedule.stops["STATION"].location_type)  
self.assertEquals(0, schedule.stops["BEATTY_AIRPORT"].location_type)  
self.problems.AssertNoMoreExceptions()  
 
def testBadLocationType(self):  
self.zip.writestr(  
"stops.txt",  
"stop_id,stop_name,stop_lat,stop_lon,location_type\n"  
"BEATTY_AIRPORT,Airport,36.868446,-116.784582,2\n"  
"BULLFROG,Bullfrog,36.88108,-116.81797,notvalid\n"  
"STAGECOACH,Stagecoach Hotel,36.915682,-116.751677,\n")  
schedule = self.loader.Load()  
e = self.problems.PopException("InvalidValue")  
self.assertEquals("location_type", e.column_name)  
self.assertEquals(2, e.row_num)  
self.assertEquals(1, e.type)  
e = self.problems.PopException("InvalidValue")  
self.assertEquals("location_type", e.column_name)  
self.assertEquals(3, e.row_num)  
self.assertEquals(0, e.type)  
self.problems.AssertNoMoreExceptions()  
 
def testBadLocationTypeAtSameLatLon(self):  
self.zip.writestr(  
"stops.txt",  
"stop_id,stop_name,stop_lat,stop_lon,location_type,parent_station\n"  
"BEATTY_AIRPORT,Airport,36.868446,-116.784582,,STATION\n"  
"STATION,Airport,36.868446,-116.784582,2,\n"  
"BULLFROG,Bullfrog,36.88108,-116.81797,,\n"  
"STAGECOACH,Stagecoach Hotel,36.915682,-116.751677,,\n")  
schedule = self.loader.Load()  
e = self.problems.PopException("InvalidValue")  
self.assertEquals("location_type", e.column_name)  
self.assertEquals(3, e.row_num)  
e = self.problems.PopException("InvalidValue")  
self.assertEquals("parent_station", e.column_name)  
self.problems.AssertNoMoreExceptions()  
 
def testStationUsed(self):  
self.zip.writestr(  
"stops.txt",  
"stop_id,stop_name,stop_lat,stop_lon,location_type\n"  
"BEATTY_AIRPORT,Airport,36.868446,-116.784582,1\n"  
"BULLFROG,Bullfrog,36.88108,-116.81797,\n"  
"STAGECOACH,Stagecoach Hotel,36.915682,-116.751677,\n")  
schedule = self.loader.Load()  
self.problems.PopException("UsedStation")  
self.problems.AssertNoMoreExceptions()  
 
def testParentNotFound(self):  
self.zip.writestr(  
"stops.txt",  
"stop_id,stop_name,stop_lat,stop_lon,location_type,parent_station\n"  
"BEATTY_AIRPORT,Airport,36.868446,-116.784582,,STATION\n"  
"BULLFROG,Bullfrog,36.88108,-116.81797,,\n"  
"STAGECOACH,Stagecoach Hotel,36.915682,-116.751677,,\n")  
schedule = self.loader.Load()  
e = self.problems.PopException("InvalidValue")  
self.assertEquals("parent_station", e.column_name)  
self.problems.AssertNoMoreExceptions()  
 
def testParentIsStop(self):  
self.zip.writestr(  
"stops.txt",  
"stop_id,stop_name,stop_lat,stop_lon,location_type,parent_station\n"  
"BEATTY_AIRPORT,Airport,36.868446,-116.784582,,BULLFROG\n"  
"BULLFROG,Bullfrog,36.88108,-116.81797,,\n"  
"STAGECOACH,Stagecoach Hotel,36.915682,-116.751677,,\n")  
schedule = self.loader.Load()  
e = self.problems.PopException("InvalidValue")  
self.assertEquals("parent_station", e.column_name)  
self.problems.AssertNoMoreExceptions()  
 
def testParentOfEntranceIsStop(self):  
self.zip.writestr(  
"stops.txt",  
"stop_id,stop_name,stop_lat,stop_lon,location_type,parent_station\n"  
"BEATTY_AIRPORT,Airport,36.868446,-116.784582,2,BULLFROG\n"  
"BULLFROG,Bullfrog,36.88108,-116.81797,,\n"  
"STAGECOACH,Stagecoach Hotel,36.915682,-116.751677,,\n")  
schedule = self.loader.Load()  
e = self.problems.PopException("InvalidValue")  
self.assertEquals("location_type", e.column_name)  
e = self.problems.PopException("InvalidValue")  
self.assertEquals("parent_station", e.column_name)  
self.assertTrue(e.FormatProblem().find("location_type=1") != -1)  
self.problems.AssertNoMoreExceptions()  
 
def testStationWithParent(self):  
self.zip.writestr(  
"stops.txt",  
"stop_id,stop_name,stop_lat,stop_lon,location_type,parent_station\n"  
"BEATTY_AIRPORT,Airport,36.868446,-116.784582,,STATION\n"  
"STATION,Airport,36.868446,-116.784582,1,STATION2\n"  
"STATION2,Airport 2,36.868000,-116.784000,1,\n"  
"BULLFROG,Bullfrog,36.868088,-116.784797,,STATION2\n"  
"STAGECOACH,Stagecoach Hotel,36.915682,-116.751677,,\n")  
schedule = self.loader.Load()  
e = self.problems.PopException("InvalidValue")  
self.assertEquals("parent_station", e.column_name)  
self.assertEquals(3, e.row_num)  
self.problems.AssertNoMoreExceptions()  
 
def testStationWithSelfParent(self):  
self.zip.writestr(  
"stops.txt",  
"stop_id,stop_name,stop_lat,stop_lon,location_type,parent_station\n"  
"BEATTY_AIRPORT,Airport,36.868446,-116.784582,,STATION\n"  
"STATION,Airport,36.868446,-116.784582,1,STATION\n"  
"BULLFROG,Bullfrog,36.88108,-116.81797,,\n"  
"STAGECOACH,Stagecoach Hotel,36.915682,-116.751677,,\n")  
schedule = self.loader.Load()  
e = self.problems.PopException("InvalidValue")  
self.assertEquals("parent_station", e.column_name)  
self.assertEquals(3, e.row_num)  
self.problems.AssertNoMoreExceptions()  
 
def testStopNearToNonParentStation(self):  
self.zip.writestr(  
"stops.txt",  
"stop_id,stop_name,stop_lat,stop_lon,location_type,parent_station\n"  
"BEATTY_AIRPORT,Airport,36.868446,-116.784582,,\n"  
"BULLFROG,Bullfrog,36.868446,-116.784582,,\n"  
"BULLFROG_ST,Bullfrog,36.868446,-116.784582,1,\n"  
"STAGECOACH,Stagecoach Hotel,36.915682,-116.751677,,\n")  
schedule = self.loader.Load()  
e = self.problems.PopException("DifferentStationTooClose")  
self.assertMatchesRegex(  
"The parent_station of stop \"Bullfrog\"", e.FormatProblem())  
e = self.problems.PopException("StopsTooClose")  
self.assertMatchesRegex("BEATTY_AIRPORT", e.FormatProblem())  
self.assertMatchesRegex("BULLFROG", e.FormatProblem())  
self.assertMatchesRegex("are 0.00m apart", e.FormatProblem())  
e = self.problems.PopException("DifferentStationTooClose")  
self.assertMatchesRegex(  
"The parent_station of stop \"Airport\"", e.FormatProblem())  
self.problems.AssertNoMoreExceptions()  
 
def testStopTooFarFromParentStation(self):  
self.zip.writestr(  
"stops.txt",  
"stop_id,stop_name,stop_lat,stop_lon,location_type,parent_station\n"  
"BULLFROG_ST,Bullfrog,36.880,-116.817,1,\n" # Parent station of all.  
"BEATTY_AIRPORT,Airport,36.880,-116.816,,BULLFROG_ST\n" # ~ 90m far  
"BULLFROG,Bullfrog,36.881,-116.818,,BULLFROG_ST\n" # ~ 150m far  
"STAGECOACH,Stagecoach,36.915,-116.751,,BULLFROG_ST\n") # > 3km far  
schedule = self.loader.Load()  
e = self.problems.PopException("StopTooFarFromParentStation")  
self.assertEqual(1, e.type) # Warning  
self.assertTrue(e.FormatProblem().find(  
"Bullfrog (ID BULLFROG) is too far from its parent"  
" station Bullfrog (ID BULLFROG_ST)") != -1)  
e = self.problems.PopException("StopTooFarFromParentStation")  
self.assertEqual(0, e.type) # Error  
self.assertTrue(e.FormatProblem().find(  
"Stagecoach (ID STAGECOACH) is too far from its parent"  
" station Bullfrog (ID BULLFROG_ST)") != -1)  
self.problems.AssertNoMoreExceptions()  
 
#Uncomment once validation is implemented  
#def testStationWithoutReference(self):  
# self.zip.writestr(  
# "stops.txt",  
# "stop_id,stop_name,stop_lat,stop_lon,location_type,parent_station\n"  
# "BEATTY_AIRPORT,Airport,36.868446,-116.784582,,\n"  
# "STATION,Airport,36.868446,-116.784582,1,\n"  
# "BULLFROG,Bullfrog,36.88108,-116.81797,,\n"  
# "STAGECOACH,Stagecoach Hotel,36.915682,-116.751677,,\n")  
# schedule = self.loader.Load()  
# e = self.problems.PopException("OtherProblem")  
# self.assertEquals("parent_station", e.column_name)  
# self.assertEquals(2, e.row_num)  
# self.problems.AssertNoMoreExceptions()  
 
 
class StopSpacesTestCase(MemoryZipTestCase):  
def testFieldsWithSpace(self):  
self.zip.writestr(  
"stops.txt",  
"stop_id,stop_code,stop_name,stop_lat,stop_lon,stop_url,location_type,"  
"parent_station\n"  
"BEATTY_AIRPORT, ,Airport,36.868446,-116.784582, , ,\n"  
"BULLFROG,,Bullfrog,36.88108,-116.81797,,,\n"  
"STAGECOACH,,Stagecoach Hotel,36.915682,-116.751677,,,\n")  
schedule = self.loader.Load()  
self.problems.AssertNoMoreExceptions()  
 
 
class StopBlankHeaders(MemoryZipTestCase):  
def testBlankHeaderValueAtEnd(self):  
# Modify the stops.txt added by MemoryZipTestCase.setUp. This allows the  
# original stops.txt to be changed without modifying anything in this test.  
# Add a column to the end of every row, leaving the header name blank.  
new = []  
for i, row in enumerate(self.zip.read("stops.txt").split("\n")):  
if i == 0:  
new.append(row + ",")  
elif row:  
new.append(row + "," + str(i)) # Put a junk value in data rows  
self.zip.writestr("stops.txt", "\n".join(new))  
schedule = self.loader.Load()  
e = self.problems.PopException("CsvSyntax")  
self.assertTrue(e.FormatProblem().  
find("header row should not contain any blank") != -1)  
self.problems.AssertNoMoreExceptions()  
 
def testBlankHeaderValueAtStart(self):  
# Modify the stops.txt added by MemoryZipTestCase.setUp. This allows the  
# original stops.txt to be changed without modifying anything in this test.  
# Add a column to the start of every row, leaving the header name blank.  
new = []  
for i, row in enumerate(self.zip.read("stops.txt").split("\n")):  
if i == 0:  
new.append("," + row)  
elif row:  
new.append(str(i) + "," + row) # Put a junk value in data rows  
self.zip.writestr("stops.txt", "\n".join(new))  
schedule = self.loader.Load()  
e = self.problems.PopException("CsvSyntax")  
self.assertTrue(e.FormatProblem().  
find("header row should not contain any blank") != -1)  
self.problems.AssertNoMoreExceptions()  
 
def testBlankHeaderValueInMiddle(self):  
# Modify the stops.txt added by MemoryZipTestCase.setUp. This allows the  
# original stops.txt to be changed without modifying anything in this test.  
# Add two columns to the start of every row, leaving the second header name  
# blank.  
new = []  
for i, row in enumerate(self.zip.read("stops.txt").split("\n")):  
if i == 0:  
new.append("test_name,," + row)  
elif row:  
# Put a junk value in data rows  
new.append(str(i) + "," + str(i) + "," + row)  
self.zip.writestr("stops.txt", "\n".join(new))  
schedule = self.loader.Load()  
e = self.problems.PopException("CsvSyntax")  
self.assertTrue(e.FormatProblem().  
find("header row should not contain any blank") != -1)  
e = self.problems.PopException("UnrecognizedColumn")  
self.assertEquals("test_name", e.column_name)  
self.problems.AssertNoMoreExceptions()  
 
 
class StopsNearEachOther(MemoryZipTestCase):  
def testTooNear(self):  
self.zip.writestr(  
"stops.txt",  
"stop_id,stop_name,stop_lat,stop_lon\n"  
"BEATTY_AIRPORT,Airport,48.20000,140\n"  
"BULLFROG,Bullfrog,48.20001,140\n"  
"STAGECOACH,Stagecoach Hotel,48.20016,140\n")  
schedule = self.loader.Load()  
e = self.problems.PopException('StopsTooClose')  
self.assertTrue(e.FormatProblem().find("1.11m apart") != -1)  
self.problems.AssertNoMoreExceptions()  
 
def testJustFarEnough(self):  
self.zip.writestr(  
"stops.txt",  
"stop_id,stop_name,stop_lat,stop_lon\n"  
"BEATTY_AIRPORT,Airport,48.20000,140\n"  
"BULLFROG,Bullfrog,48.20002,140\n"  
"STAGECOACH,Stagecoach Hotel,48.20016,140\n")  
schedule = self.loader.Load()  
# Stops are 2.2m apart  
self.problems.AssertNoMoreExceptions()  
 
def testSameLocation(self):  
self.zip.writestr(  
"stops.txt",  
"stop_id,stop_name,stop_lat,stop_lon\n"  
"BEATTY_AIRPORT,Airport,48.2,140\n"  
"BULLFROG,Bullfrog,48.2,140\n"  
"STAGECOACH,Stagecoach Hotel,48.20016,140\n")  
schedule = self.loader.Load()  
e = self.problems.PopException('StopsTooClose')  
self.assertTrue(e.FormatProblem().find("0.00m apart") != -1)  
self.problems.AssertNoMoreExceptions()  
 
def testStationsTooNear(self):  
self.zip.writestr(  
"stops.txt",  
"stop_id,stop_name,stop_lat,stop_lon,location_type,parent_station\n"  
"BEATTY_AIRPORT,Airport,48.20000,140,,BEATTY_AIRPORT_STATION\n"  
"BULLFROG,Bullfrog,48.20003,140,,BULLFROG_STATION\n"  
"BEATTY_AIRPORT_STATION,Airport,48.20001,140,1,\n"  
"BULLFROG_STATION,Bullfrog,48.20002,140,1,\n"  
"STAGECOACH,Stagecoach Hotel,48.20016,140,,\n")  
schedule = self.loader.Load()  
e = self.problems.PopException('StationsTooClose')  
self.assertTrue(e.FormatProblem().find("1.11m apart") != -1)  
self.assertTrue(e.FormatProblem().find("BEATTY_AIRPORT_STATION") != -1)  
self.problems.AssertNoMoreExceptions()  
 
def testStopNearNonParentStation(self):  
self.zip.writestr(  
"stops.txt",  
"stop_id,stop_name,stop_lat,stop_lon,location_type,parent_station\n"  
"BEATTY_AIRPORT,Airport,48.20000,140,,\n"  
"BULLFROG,Bullfrog,48.20005,140,,\n"  
"BULLFROG_STATION,Bullfrog,48.20006,140,1,\n"  
"STAGECOACH,Stagecoach Hotel,48.20016,140,,\n")  
schedule = self.loader.Load()  
e = self.problems.PopException('DifferentStationTooClose')  
fmt = e.FormatProblem()  
self.assertTrue(re.search(  
r"parent_station of.*BULLFROG.*station.*BULLFROG_STATION.* 1.11m apart",  
fmt), fmt)  
self.problems.AssertNoMoreExceptions()  
 
 
class BadLatLonInStopUnitTest(ValidationTestCase):  
def runTest(self):  
stop = transitfeed.Stop(field_dict={"stop_id": "STOP1",  
"stop_name": "Stop one",  
"stop_lat": "0x20",  
"stop_lon": "140.01"})  
self.ExpectInvalidValue(stop, "stop_lat")  
 
stop = transitfeed.Stop(field_dict={"stop_id": "STOP1",  
"stop_name": "Stop one",  
"stop_lat": "13.0",  
"stop_lon": "1e2"})  
self.ExpectInvalidValue(stop, "stop_lon")  
 
 
class BadLatLonInFileUnitTest(MemoryZipTestCase):  
def runTest(self):  
self.zip.writestr(  
"stops.txt",  
"stop_id,stop_name,stop_lat,stop_lon\n"  
"BEATTY_AIRPORT,Airport,0x20,140.00\n"  
"BULLFROG,Bullfrog,48.20001,140.0123\n"  
"STAGECOACH,Stagecoach Hotel,48.002,bogus\n")  
schedule = self.loader.Load()  
e = self.problems.PopException('InvalidValue')  
self.assertEquals(2, e.row_num)  
self.assertEquals("stop_lat", e.column_name)  
e = self.problems.PopException('InvalidValue')  
self.assertEquals(4, e.row_num)  
self.assertEquals("stop_lon", e.column_name)  
self.problems.AssertNoMoreExceptions()  
 
 
class LoadUnknownFileInZipTestCase(MemoryZipTestCase):  
def runTest(self):  
self.zip.writestr(  
"stpos.txt",  
"stop_id,stop_name,stop_lat,stop_lon,location_type,parent_station\n"  
"BEATTY_AIRPORT,Airport,36.868446,-116.784582,,STATION\n"  
"STATION,Airport,36.868446,-116.784582,1,\n"  
"BULLFROG,Bullfrog,36.88108,-116.81797,,\n"  
"STAGECOACH,Stagecoach Hotel,36.915682,-116.751677,,\n")  
schedule = self.loader.Load()  
e = self.problems.PopException('UnknownFile')  
self.assertEquals('stpos.txt', e.file_name)  
self.problems.AssertNoMoreExceptions()  
 
 
class TabDelimitedTestCase(MemoryZipTestCase):  
def runTest(self):  
# Create an extremely corrupt file by replacing each comma with a tab,  
# ignoring csv quoting.  
for arcname in self.zip.namelist():  
orig = self.zip.read(arcname)  
self.zip.writestr(arcname, orig.replace(",", "\t"))  
schedule = self.loader.Load()  
# Don't call self.problems.AssertNoMoreExceptions() because there are lots  
# of problems but I only care that the validator doesn't crash. In the  
# magical future the validator will stop when the csv is obviously hosed.  
 
 
class RouteMemoryZipTestCase(MemoryZipTestCase):  
def assertLoadAndCheckExtraValues(self, schedule_file):  
"""Load file-like schedule_file and check for extra route columns."""  
load_problems = TestFailureProblemReporter(  
self, ("ExpirationDate", "UnrecognizedColumn"))  
loaded_schedule = transitfeed.Loader(schedule_file,  
problems=load_problems,  
extra_validation=True).Load()  
self.assertEqual("foo", loaded_schedule.GetRoute("t")["t_foo"])  
self.assertEqual("", loaded_schedule.GetRoute("AB")["t_foo"])  
self.assertEqual("bar", loaded_schedule.GetRoute("n")["n_foo"])  
self.assertEqual("", loaded_schedule.GetRoute("AB")["n_foo"])  
# Uncomment the following lines to print the string in testExtraFileColumn  
# print repr(zipfile.ZipFile(schedule_file).read("routes.txt"))  
# self.fail()  
 
def testExtraObjectAttribute(self):  
"""Extra columns added to an object are preserved when writing."""  
schedule = self.loader.Load()  
# Add an attribute after AddRouteObject  
route_t = transitfeed.Route(short_name="T", route_type="Bus", route_id="t")  
schedule.AddRouteObject(route_t)  
route_t.t_foo = "foo"  
# Add an attribute before AddRouteObject  
route_n = transitfeed.Route(short_name="N", route_type="Bus", route_id="n")  
route_n.n_foo = "bar"  
schedule.AddRouteObject(route_n)  
saved_schedule_file = StringIO()  
schedule.WriteGoogleTransitFeed(saved_schedule_file)  
self.problems.AssertNoMoreExceptions()  
 
self.assertLoadAndCheckExtraValues(saved_schedule_file)  
 
def testExtraFileColumn(self):  
"""Extra columns loaded from a file are preserved when writing."""  
# Uncomment the code in assertLoadAndCheckExtraValues to generate this  
# string.  
self.zip.writestr(  
"routes.txt",  
"route_id,agency_id,route_short_name,route_long_name,route_type,"  
"t_foo,n_foo\n"  
"AB,DTA,,Airport Bullfrog,3,,\n"  
"t,DTA,T,,3,foo,\n"  
"n,DTA,N,,3,,bar\n")  
load1_problems = TestFailureProblemReporter(  
self, ("ExpirationDate", "UnrecognizedColumn"))  
schedule = transitfeed.Loader(problems=load1_problems,  
extra_validation=True,  
zip=self.zip).Load()  
saved_schedule_file = StringIO()  
schedule.WriteGoogleTransitFeed(saved_schedule_file)  
 
self.assertLoadAndCheckExtraValues(saved_schedule_file)  
 
 
class RouteConstructorTestCase(unittest.TestCase):  
def setUp(self):  
self.problems = RecordingProblemReporter(self)  
 
def testDefault(self):  
route = transitfeed.Route()  
repr(route)  
self.assertEqual({}, dict(route))  
route.Validate(self.problems)  
repr(route)  
self.assertEqual({}, dict(route))  
 
e = self.problems.PopException('MissingValue')  
self.assertEqual('route_id', e.column_name)  
e = self.problems.PopException('MissingValue')  
self.assertEqual('route_type', e.column_name)  
e = self.problems.PopException('InvalidValue')  
self.assertEqual('route_short_name', e.column_name)  
self.problems.AssertNoMoreExceptions()  
 
def testInitArgs(self):  
# route_type name  
route = transitfeed.Route(route_id='id1', short_name='22', route_type='Bus')  
repr(route)  
route.Validate(self.problems)  
self.problems.AssertNoMoreExceptions()  
self.assertEquals(3, route.route_type) # converted to an int  
self.assertEquals({'route_id': 'id1', 'route_short_name': '22',  
'route_type': '3'}, dict(route))  
 
# route_type as an int  
route = transitfeed.Route(route_id='i1', long_name='Twenty 2', route_type=1)  
repr(route)  
route.Validate(self.problems)  
self.problems.AssertNoMoreExceptions()  
self.assertEquals(1, route.route_type) # kept as an int  
self.assertEquals({'route_id': 'i1', 'route_long_name': 'Twenty 2',  
'route_type': '1'}, dict(route))  
 
# route_type as a string  
route = transitfeed.Route(route_id='id1', short_name='22', route_type='1')  
repr(route)  
route.Validate(self.problems)  
self.problems.AssertNoMoreExceptions()  
self.assertEquals(1, route.route_type) # converted to an int  
self.assertEquals({'route_id': 'id1', 'route_short_name': '22',  
'route_type': '1'}, dict(route))  
 
# route_type has undefined int value  
route = transitfeed.Route(route_id='id1', short_name='22',  
route_type='8')  
repr(route)  
route.Validate(self.problems)  
e = self.problems.PopException('InvalidValue')  
self.assertEqual('route_type', e.column_name)  
self.assertEqual(1, e.type)  
self.problems.AssertNoMoreExceptions()  
self.assertEquals({'route_id': 'id1', 'route_short_name': '22',  
'route_type': '8'}, dict(route))  
 
# route_type that doesn't parse  
route = transitfeed.Route(route_id='id1', short_name='22',  
route_type='1foo')  
repr(route)  
route.Validate(self.problems)  
e = self.problems.PopException('InvalidValue')  
self.assertEqual('route_type', e.column_name)  
self.problems.AssertNoMoreExceptions()  
self.assertEquals({'route_id': 'id1', 'route_short_name': '22',  
'route_type': '1foo'}, dict(route))  
 
# agency_id  
route = transitfeed.Route(route_id='id1', short_name='22', route_type=1,  
agency_id='myage')  
repr(route)  
route.Validate(self.problems)  
self.problems.AssertNoMoreExceptions()  
self.assertEquals({'route_id': 'id1', 'route_short_name': '22',  
'route_type': '1', 'agency_id': 'myage'}, dict(route))  
 
def testInitArgOrder(self):  
"""Call Route.__init__ without any names so a change in order is noticed."""  
route = transitfeed.Route('short', 'long name', 'Bus', 'r1', 'a1')  
self.assertEquals({'route_id': 'r1', 'route_short_name': 'short',  
'route_long_name': 'long name',  
'route_type': '3', 'agency_id': 'a1'}, dict(route))  
 
def testFieldDict(self):  
route = transitfeed.Route(field_dict={})  
self.assertEquals({}, dict(route))  
 
route = transitfeed.Route(field_dict={  
'route_id': 'id1', 'route_short_name': '22', 'agency_id': 'myage',  
'route_type': '1'})  
route.Validate(self.problems)  
self.problems.AssertNoMoreExceptions()  
self.assertEquals({'route_id': 'id1', 'route_short_name': '22',  
'agency_id': 'myage', 'route_type': '1'}, dict(route))  
 
route = transitfeed.Route(field_dict={  
'route_id': 'id1', 'route_short_name': '22', 'agency_id': 'myage',  
'route_type': '1', 'my_column': 'v'})  
route.Validate(self.problems)  
self.problems.AssertNoMoreExceptions()  
self.assertEquals({'route_id': 'id1', 'route_short_name': '22',  
'agency_id': 'myage', 'route_type': '1',  
'my_column':'v'}, dict(route))  
route._private = 0.3 # Isn't copied  
route_copy = transitfeed.Route(field_dict=route)  
self.assertEquals({'route_id': 'id1', 'route_short_name': '22',  
'agency_id': 'myage', 'route_type': '1',  
'my_column':'v'}, dict(route_copy))  
 
 
class RouteValidationTestCase(ValidationTestCase):  
def runTest(self):  
# success case  
route = transitfeed.Route()  
route.route_id = '054C'  
route.route_short_name = '54C'  
route.route_long_name = 'South Side - North Side'  
route.route_type = 7  
route.Validate(self.problems)  
 
# blank short & long names  
route.route_short_name = ''  
route.route_long_name = ' '  
self.ExpectInvalidValue(route, 'route_short_name')  
 
# short name too long  
route.route_short_name = 'South Side'  
route.route_long_name = ''  
self.ExpectInvalidValue(route, 'route_short_name')  
route.route_short_name = 'M7bis' # 5 is OK  
route.Validate(self.problems)  
 
# long name contains short name  
route.route_short_name = '54C'  
route.route_long_name = '54C South Side - North Side'  
self.ExpectInvalidValue(route, 'route_long_name')  
route.route_long_name = '54C(South Side - North Side)'  
self.ExpectInvalidValue(route, 'route_long_name')  
route.route_long_name = '54C-South Side - North Side'  
self.ExpectInvalidValue(route, 'route_long_name')  
 
# long name is same as short name  
route.route_short_name = '54C'  
route.route_long_name = '54C'  
self.ExpectInvalidValue(route, 'route_long_name')  
 
# route description is same as short name  
route.route_desc = '54C'  
route.route_short_name = '54C'  
route.route_long_name = ''  
self.ExpectInvalidValue(route, 'route_desc')  
route.route_desc = None  
 
# route description is same as long name  
route.route_desc = 'South Side - North Side'  
route.route_long_name = 'South Side - North Side'  
self.ExpectInvalidValue(route, 'route_desc')  
route.route_desc = None  
 
# invalid route types  
route.route_type = 8  
self.ExpectInvalidValue(route, 'route_type')  
route.route_type = -1  
self.ExpectInvalidValue(route, 'route_type')  
route.route_type = 7  
 
# invalid route URL  
route.route_url = 'www.example.com'  
self.ExpectInvalidValue(route, 'route_url')  
route.route_url = None  
 
# invalid route color  
route.route_color = 'orange'  
self.ExpectInvalidValue(route, 'route_color')  
route.route_color = None  
 
# invalid route text color  
route.route_text_color = 'orange'  
self.ExpectInvalidValue(route, 'route_text_color')  
route.route_text_color = None  
 
# missing route ID  
route.route_id = None  
self.ExpectMissingValue(route, 'route_id')  
route.route_id = '054C'  
 
# bad color contrast  
route.route_text_color = None # black  
route.route_color = '0000FF' # Bad  
self.ExpectInvalidValue(route, 'route_color')  
route.route_color = '00BF00' # OK  
route.Validate(self.problems)  
route.route_color = '005F00' # Bad  
self.ExpectInvalidValue(route, 'route_color')  
route.route_color = 'FF00FF' # OK  
route.Validate(self.problems)  
route.route_text_color = 'FFFFFF' # OK too  
route.Validate(self.problems)  
route.route_text_color = '00FF00' # think of color-blind people!  
self.ExpectInvalidValue(route, 'route_color')  
route.route_text_color = '007F00'  
route.route_color = 'FF0000'  
self.ExpectInvalidValue(route, 'route_color')  
route.route_color = '00FFFF' # OK  
route.Validate(self.problems)  
route.route_text_color = None # black  
route.route_color = None # white  
route.Validate(self.problems)  
self.problems.AssertNoMoreExceptions()  
 
 
class ShapeValidationTestCase(ValidationTestCase):  
def ExpectFailedAdd(self, shape, lat, lon, dist, column_name, value):  
self.ExpectInvalidValueInClosure(  
column_name, value,  
lambda: shape.AddPoint(lat, lon, dist, self.problems))  
 
def runTest(self):  
shape = transitfeed.Shape('TEST')  
repr(shape) # shouldn't crash  
self.ExpectOtherProblem(shape) # no points!  
 
self.ExpectFailedAdd(shape, 36.905019, -116.763207, -1,  
'shape_dist_traveled', -1)  
 
shape.AddPoint(36.915760, -116.751709, 0, self.problems)  
shape.AddPoint(36.905018, -116.763206, 5, self.problems)  
shape.Validate(self.problems)  
 
shape.shape_id = None  
self.ExpectMissingValue(shape, 'shape_id')  
shape.shape_id = 'TEST'  
 
self.ExpectFailedAdd(shape, 91, -116.751709, 6, 'shape_pt_lat', 91)  
self.ExpectFailedAdd(shape, -91, -116.751709, 6, 'shape_pt_lat', -91)  
 
self.ExpectFailedAdd(shape, 36.915760, -181, 6, 'shape_pt_lon', -181)  
self.ExpectFailedAdd(shape, 36.915760, 181, 6, 'shape_pt_lon', 181)  
 
self.ExpectFailedAdd(shape, 0.5, -0.5, 6, 'shape_pt_lat', 0.5)  
self.ExpectFailedAdd(shape, 0, 0, 6, 'shape_pt_lat', 0)  
 
# distance decreasing is bad, but staying the same is OK  
shape.AddPoint(36.905019, -116.763206, 4, self.problems)  
e = self.problems.PopException('InvalidValue')  
self.assertMatchesRegex('Each subsequent point', e.FormatProblem())  
self.assertMatchesRegex('distance was 5.000000.', e.FormatProblem())  
self.problems.AssertNoMoreExceptions()  
 
shape.AddPoint(36.925019, -116.764206, 5, self.problems)  
self.problems.AssertNoMoreExceptions()  
 
 
class FareValidationTestCase(ValidationTestCase):  
def runTest(self):  
fare = transitfeed.Fare()  
fare.fare_id = "normal"  
fare.price = 1.50  
fare.currency_type = "USD"  
fare.payment_method = 0  
fare.transfers = 1  
fare.transfer_duration = 7200  
fare.Validate(self.problems)  
 
fare.fare_id = None  
self.ExpectMissingValue(fare, "fare_id")  
fare.fare_id = ''  
self.ExpectMissingValue(fare, "fare_id")  
fare.fare_id = "normal"  
 
fare.price = "1.50"  
self.ExpectInvalidValue(fare, "price")  
fare.price = 1  
fare.Validate(self.problems)  
fare.price = None  
self.ExpectMissingValue(fare, "price")  
fare.price = 0.0  
fare.Validate(self.problems)  
fare.price = -1.50  
self.ExpectInvalidValue(fare, "price")  
fare.price = 1.50  
 
fare.currency_type = ""  
self.ExpectMissingValue(fare, "currency_type")  
fare.currency_type = None  
self.ExpectMissingValue(fare, "currency_type")  
fare.currency_type = "usd"  
self.ExpectInvalidValue(fare, "currency_type")  
fare.currency_type = "KML"  
self.ExpectInvalidValue(fare, "currency_type")  
fare.currency_type = "USD"  
 
fare.payment_method = "0"  
self.ExpectInvalidValue(fare, "payment_method")  
fare.payment_method = -1  
self.ExpectInvalidValue(fare, "payment_method")  
fare.payment_method = 1  
fare.Validate(self.problems)  
fare.payment_method = 2  
self.ExpectInvalidValue(fare, "payment_method")  
fare.payment_method = None  
self.ExpectMissingValue(fare, "payment_method")  
fare.payment_method = ""  
self.ExpectMissingValue(fare, "payment_method")  
fare.payment_method = 0  
 
fare.transfers = "1"  
self.ExpectInvalidValue(fare, "transfers")  
fare.transfers = -1  
self.ExpectInvalidValue(fare, "transfers")  
fare.transfers = 2  
fare.Validate(self.problems)  
fare.transfers = 3  
self.ExpectInvalidValue(fare, "transfers")  
fare.transfers = None  
fare.Validate(self.problems)  
fare.transfers = 1  
 
fare.transfer_duration = 0  
fare.Validate(self.problems)  
fare.transfer_duration = None  
fare.Validate(self.problems)  
fare.transfer_duration = -3600  
self.ExpectInvalidValue(fare, "transfer_duration")  
fare.transfers = 0 # no transfers allowed but duration specified!  
fare.transfer_duration = 3600  
self.ExpectInvalidValue(fare, "transfer_duration")  
fare.transfers = 1  
fare.transfer_duration = "3600"  
self.ExpectInvalidValue(fare, "transfer_duration")  
fare.transfer_duration = 7200  
self.problems.AssertNoMoreExceptions()  
 
class TransferValidationTestCase(ValidationTestCase):  
def runTest(self):  
# Totally bogus data shouldn't cause a crash  
transfer = transitfeed.Transfer(field_dict={"ignored": "foo"})  
self.assertEquals(0, transfer.transfer_type)  
 
transfer = transitfeed.Transfer(from_stop_id = "S1", to_stop_id = "S2",  
transfer_type = "1", min_transfer_time = 2)  
self.assertEquals("S1", transfer.from_stop_id)  
self.assertEquals("S2", transfer.to_stop_id)  
self.assertEquals(1, transfer.transfer_type)  
self.assertEquals(2, transfer.min_transfer_time)  
transfer.Validate(self.problems)  
self.assertEquals("S1", transfer.from_stop_id)  
self.assertEquals("S2", transfer.to_stop_id)  
self.assertEquals(1, transfer.transfer_type)  
self.assertEquals(2, transfer.min_transfer_time)  
self.problems.AssertNoMoreExceptions()  
 
transfer = transitfeed.Transfer(field_dict={"from_stop_id": "S1", \  
"to_stop_id": "S2", \  
"transfer_type": "0", \  
"min_transfer_time": "2"})  
self.assertEquals("S1", transfer.from_stop_id)  
self.assertEquals("S2", transfer.to_stop_id)  
self.assertEquals(0, transfer.transfer_type)  
self.assertEquals(2, transfer.min_transfer_time)  
transfer.Validate(self.problems)  
self.assertEquals("S1", transfer.from_stop_id)  
self.assertEquals("S2", transfer.to_stop_id)  
self.assertEquals(0, transfer.transfer_type)  
self.assertEquals(2, transfer.min_transfer_time)  
self.problems.AssertNoMoreExceptions()  
 
transfer = transitfeed.Transfer(field_dict={"from_stop_id": "S1", \  
"to_stop_id": "S2", \  
"transfer_type": "-4", \  
"min_transfer_time": "2"})  
self.assertEquals("S1", transfer.from_stop_id)  
self.assertEquals("S2", transfer.to_stop_id)  
self.assertEquals("-4", transfer.transfer_type)  
self.assertEquals(2, transfer.min_transfer_time)  
self.ExpectInvalidValue(transfer, "transfer_type")  
self.assertEquals("S1", transfer.from_stop_id)  
self.assertEquals("S2", transfer.to_stop_id)  
self.assertEquals("-4", transfer.transfer_type)  
self.assertEquals(2, transfer.min_transfer_time)  
 
transfer = transitfeed.Transfer(field_dict={"from_stop_id": "S1", \  
"to_stop_id": "S2", \  
"transfer_type": "", \  
"min_transfer_time": "-1"})  
self.assertEquals(0, transfer.transfer_type)  
self.ExpectInvalidValue(transfer, "min_transfer_time")  
 
# simple successes  
transfer = transitfeed.Transfer()  
transfer.from_stop_id = "S1"  
transfer.to_stop_id = "S2"  
transfer.transfer_type = 0  
repr(transfer) # shouldn't crash  
transfer.Validate(self.problems)  
transfer.transfer_type = 3  
transfer.Validate(self.problems)  
self.problems.AssertNoMoreExceptions()  
 
# transfer_type is out of range  
transfer.transfer_type = 4  
self.ExpectInvalidValue(transfer, "transfer_type")  
transfer.transfer_type = -1  
self.ExpectInvalidValue(transfer, "transfer_type")  
transfer.transfer_type = "text"  
self.ExpectInvalidValue(transfer, "transfer_type")  
transfer.transfer_type = 2  
 
# invalid min_transfer_time  
transfer.min_transfer_time = -1  
self.ExpectInvalidValue(transfer, "min_transfer_time")  
transfer.min_transfer_time = "text"  
self.ExpectInvalidValue(transfer, "min_transfer_time")  
transfer.min_transfer_time = 250  
transfer.Validate(self.problems)  
self.problems.AssertNoMoreExceptions()  
 
# missing stop ids  
transfer.from_stop_id = ""  
self.ExpectMissingValue(transfer, 'from_stop_id')  
transfer.from_stop_id = "S1"  
transfer.to_stop_id = None  
self.ExpectMissingValue(transfer, 'to_stop_id')  
transfer.to_stop_id = "S2"  
 
# stops are presented in schedule case  
schedule = transitfeed.Schedule()  
stop1 = schedule.AddStop(57.5, 30.2, "stop 1")  
stop2 = schedule.AddStop(57.5, 30.3, "stop 2")  
transfer = transitfeed.Transfer(schedule=schedule)  
transfer.from_stop_id = stop1.stop_id  
transfer.to_stop_id = stop2.stop_id  
transfer.transfer_type = 2  
transfer.min_transfer_time = 250  
repr(transfer) # shouldn't crash  
transfer.Validate(self.problems)  
self.problems.AssertNoMoreExceptions()  
 
# stops are not presented in schedule case  
schedule = transitfeed.Schedule()  
stop1 = schedule.AddStop(57.5, 30.2, "stop 1")  
transfer = transitfeed.Transfer(schedule=schedule)  
transfer.from_stop_id = stop1.stop_id  
transfer.to_stop_id = "unexist"  
transfer.transfer_type = 2  
transfer.min_transfer_time = 250  
self.ExpectInvalidValue(transfer, 'to_stop_id')  
transfer.from_stop_id = "unexist"  
transfer.to_stop_id = stop1.stop_id  
self.ExpectInvalidValue(transfer, "from_stop_id")  
self.problems.AssertNoMoreExceptions()  
 
# Transfer can only be added to a schedule once  
transfer = transitfeed.Transfer()  
transfer.from_stop_id = stop1.stop_id  
transfer.to_stop_id = stop1.stop_id  
schedule.AddTransferObject(transfer)  
self.assertRaises(AssertionError, schedule.AddTransferObject, transfer)  
 
 
class ServicePeriodValidationTestCase(ValidationTestCase):  
def runTest(self):  
# success case  
period = transitfeed.ServicePeriod()  
repr(period) # shouldn't crash  
period.service_id = 'WEEKDAY'  
period.start_date = '20070101'  
period.end_date = '20071231'  
period.day_of_week[0] = True  
repr(period) # shouldn't crash  
period.Validate(self.problems)  
 
# missing start_date. If one of start_date or end_date is None then  
# ServicePeriod.Validate assumes the required column is missing and already  
# generated an error. Instead set it to an empty string, such as when the  
# csv cell is empty. See also comment in ServicePeriod.Validate.  
period.start_date = ''  
self.ExpectMissingValue(period, 'start_date')  
period.start_date = '20070101'  
 
# missing end_date  
period.end_date = ''  
self.ExpectMissingValue(period, 'end_date')  
period.end_date = '20071231'  
 
# invalid start_date  
period.start_date = '2007-01-01'  
self.ExpectInvalidValue(period, 'start_date')  
period.start_date = '20070101'  
 
# impossible start_date  
period.start_date = '20070229'  
self.ExpectInvalidValue(period, 'start_date')  
period.start_date = '20070101'  
 
# invalid end_date  
period.end_date = '2007/12/31'  
self.ExpectInvalidValue(period, 'end_date')  
period.end_date = '20071231'  
 
# start & end dates out of order  
period.end_date = '20060101'  
self.ExpectInvalidValue(period, 'end_date')  
period.end_date = '20071231'  
 
# no service in period  
period.day_of_week[0] = False  
self.ExpectOtherProblem(period)  
period.day_of_week[0] = True  
 
# invalid exception date  
period.SetDateHasService('2007', False)  
self.ExpectInvalidValue(period, 'date', '2007')  
period.ResetDateToNormalService('2007')  
 
period2 = transitfeed.ServicePeriod(  
field_list=['serviceid1', '20060101', '20071231', '1', '0', 'h', '1',  
'1', '1', '1'])  
self.ExpectInvalidValue(period2, 'wednesday', 'h')  
repr(period) # shouldn't crash  
 
 
class ServicePeriodDateRangeTestCase(ValidationTestCase):  
def runTest(self):  
period = transitfeed.ServicePeriod()  
period.service_id = 'WEEKDAY'  
period.start_date = '20070101'  
period.end_date = '20071231'  
period.SetWeekdayService(True)  
period.SetDateHasService('20071231', False)  
period.Validate(self.problems)  
self.assertEqual(('20070101', '20071231'), period.GetDateRange())  
 
period2 = transitfeed.ServicePeriod()  
period2.service_id = 'HOLIDAY'  
period2.SetDateHasService('20071225', True)  
period2.SetDateHasService('20080101', True)  
period2.SetDateHasService('20080102', False)  
period2.Validate(self.problems)  
self.assertEqual(('20071225', '20080101'), period2.GetDateRange())  
 
period2.start_date = '20071201'  
period2.end_date = '20071225'  
period2.Validate(self.problems)  
self.assertEqual(('20071201', '20080101'), period2.GetDateRange())  
 
period3 = transitfeed.ServicePeriod()  
self.assertEqual((None, None), period3.GetDateRange())  
 
period4 = transitfeed.ServicePeriod()  
period4.service_id = 'halloween'  
period4.SetDateHasService('20051031', True)  
self.assertEqual(('20051031', '20051031'), period4.GetDateRange())  
period4.Validate(self.problems)  
 
schedule = transitfeed.Schedule(problem_reporter=self.problems)  
self.assertEqual((None, None), schedule.GetDateRange())  
schedule.AddServicePeriodObject(period)  
self.assertEqual(('20070101', '20071231'), schedule.GetDateRange())  
schedule.AddServicePeriodObject(period2)  
self.assertEqual(('20070101', '20080101'), schedule.GetDateRange())  
schedule.AddServicePeriodObject(period4)  
self.assertEqual(('20051031', '20080101'), schedule.GetDateRange())  
self.problems.AssertNoMoreExceptions()  
 
 
class ServicePeriodTestCase(unittest.TestCase):  
def testActive(self):  
"""Test IsActiveOn and ActiveDates"""  
period = transitfeed.ServicePeriod()  
period.service_id = 'WEEKDAY'  
period.start_date = '20071226'  
period.end_date = '20071231'  
period.SetWeekdayService(True)  
period.SetDateHasService('20071230', True)  
period.SetDateHasService('20071231', False)  
period.SetDateHasService('20080102', True)  
# December 2007  
# Su Mo Tu We Th Fr Sa  
# 23 24 25 26 27 28 29  
# 30 31  
 
# Some tests have named arguments and others do not to ensure that any  
# (possibly unwanted) changes to the API get caught  
 
# calendar_date exceptions near start date  
self.assertFalse(period.IsActiveOn(date='20071225'))  
self.assertFalse(period.IsActiveOn(date='20071225',  
date_object=date(2007, 12, 25)))  
self.assertTrue(period.IsActiveOn(date='20071226'))  
self.assertTrue(period.IsActiveOn(date='20071226',  
date_object=date(2007, 12, 26)))  
 
# calendar_date exceptions near end date  
self.assertTrue(period.IsActiveOn('20071230'))  
self.assertTrue(period.IsActiveOn('20071230', date(2007, 12, 30)))  
self.assertFalse(period.IsActiveOn('20071231'))  
self.assertFalse(period.IsActiveOn('20071231', date(2007, 12, 31)))  
 
# date just outside range, both weekday and an exception  
self.assertFalse(period.IsActiveOn('20080101'))  
self.assertFalse(period.IsActiveOn('20080101', date(2008, 1, 1)))  
self.assertTrue(period.IsActiveOn('20080102'))  
self.assertTrue(period.IsActiveOn('20080102', date(2008, 1, 2)))  
 
self.assertEquals(period.ActiveDates(),  
['20071226', '20071227', '20071228', '20071230',  
'20080102'])  
 
 
# Test of period without start_date, end_date  
period_dates = transitfeed.ServicePeriod()  
period_dates.SetDateHasService('20071230', True)  
period_dates.SetDateHasService('20071231', False)  
 
self.assertFalse(period_dates.IsActiveOn(date='20071229'))  
self.assertFalse(period_dates.IsActiveOn(date='20071229',  
date_object=date(2007, 12, 29)))  
self.assertTrue(period_dates.IsActiveOn('20071230'))  
self.assertTrue(period_dates.IsActiveOn('20071230', date(2007, 12, 30)))  
self.assertFalse(period_dates.IsActiveOn('20071231'))  
self.assertFalse(period_dates.IsActiveOn('20071231', date(2007, 12, 31)))  
self.assertEquals(period_dates.ActiveDates(), ['20071230'])  
 
# Test with an invalid ServicePeriod; one of start_date, end_date is set  
period_no_end = transitfeed.ServicePeriod()  
period_no_end.start_date = '20071226'  
self.assertFalse(period_no_end.IsActiveOn(date='20071231'))  
self.assertFalse(period_no_end.IsActiveOn(date='20071231',  
date_object=date(2007, 12, 31)))  
self.assertEquals(period_no_end.ActiveDates(), [])  
period_no_start = transitfeed.ServicePeriod()  
period_no_start.end_date = '20071230'  
self.assertFalse(period_no_start.IsActiveOn('20071229'))  
self.assertFalse(period_no_start.IsActiveOn('20071229', date(2007, 12, 29)))  
self.assertEquals(period_no_start.ActiveDates(), [])  
 
period_empty = transitfeed.ServicePeriod()  
self.assertFalse(period_empty.IsActiveOn('20071231'))  
self.assertFalse(period_empty.IsActiveOn('20071231', date(2007, 12, 31)))  
self.assertEquals(period_empty.ActiveDates(), [])  
 
 
class GetServicePeriodsActiveEachDateTestCase(unittest.TestCase):  
def testEmpty(self):  
schedule = transitfeed.Schedule()  
self.assertEquals(  
[],  
schedule.GetServicePeriodsActiveEachDate(date(2009, 1, 1),  
date(2009, 1, 1)))  
self.assertEquals(  
[(date(2008, 12, 31), []), (date(2009, 1, 1), [])],  
schedule.GetServicePeriodsActiveEachDate(date(2008, 12, 31),  
date(2009, 1, 2)))  
def testOneService(self):  
schedule = transitfeed.Schedule()  
sp1 = transitfeed.ServicePeriod()  
sp1.service_id = "sp1"  
sp1.SetDateHasService("20090101")  
sp1.SetDateHasService("20090102")  
schedule.AddServicePeriodObject(sp1)  
self.assertEquals(  
[],  
schedule.GetServicePeriodsActiveEachDate(date(2009, 1, 1),  
date(2009, 1, 1)))  
self.assertEquals(  
[(date(2008, 12, 31), []), (date(2009, 1, 1), [sp1])],  
schedule.GetServicePeriodsActiveEachDate(date(2008, 12, 31),  
date(2009, 1, 2)))  
 
def testTwoService(self):  
schedule = transitfeed.Schedule()  
sp1 = transitfeed.ServicePeriod()  
sp1.service_id = "sp1"  
sp1.SetDateHasService("20081231")  
sp1.SetDateHasService("20090101")  
 
schedule.AddServicePeriodObject(sp1)  
sp2 = transitfeed.ServicePeriod()  
sp2.service_id = "sp2"  
sp2.SetStartDate("20081201")  
sp2.SetEndDate("20081231")  
sp2.SetWeekendService()  
sp2.SetWeekdayService()  
schedule.AddServicePeriodObject(sp2)  
self.assertEquals(  
[],  
schedule.GetServicePeriodsActiveEachDate(date(2009, 1, 1),  
date(2009, 1, 1)))  
date_services = schedule.GetServicePeriodsActiveEachDate(date(2008, 12, 31),  
date(2009, 1, 2))  
self.assertEquals(  
[date(2008, 12, 31), date(2009, 1, 1)], [d for d, _ in date_services])  
self.assertEquals(set([sp1, sp2]), set(date_services[0][1]))  
self.assertEquals([sp1], date_services[1][1])  
 
 
class TripMemoryZipTestCase(MemoryZipTestCase):  
def assertLoadAndCheckExtraValues(self, schedule_file):  
"""Load file-like schedule_file and check for extra trip columns."""  
load_problems = TestFailureProblemReporter(  
self, ("ExpirationDate", "UnrecognizedColumn"))  
loaded_schedule = transitfeed.Loader(schedule_file,  
problems=load_problems,  
extra_validation=True).Load()  
self.assertEqual("foo", loaded_schedule.GetTrip("AB1")["t_foo"])  
self.assertEqual("", loaded_schedule.GetTrip("AB2")["t_foo"])  
self.assertEqual("", loaded_schedule.GetTrip("AB1")["n_foo"])  
self.assertEqual("bar", loaded_schedule.GetTrip("AB2")["n_foo"])  
# Uncomment the following lines to print the string in testExtraFileColumn  
# print repr(zipfile.ZipFile(schedule_file).read("trips.txt"))  
# self.fail()  
 
def testExtraObjectAttribute(self):  
"""Extra columns added to an object are preserved when writing."""  
schedule = self.loader.Load()  
# Add an attribute to an existing trip  
trip1 = schedule.GetTrip("AB1")  
trip1.t_foo = "foo"  
# Make a copy of trip_id=AB1 and add an attribute before AddTripObject  
trip2 = transitfeed.Trip(field_dict=trip1)  
trip2.trip_id = "AB2"  
trip2.t_foo = ""  
trip2.n_foo = "bar"  
schedule.AddTripObject(trip2)  
trip2.AddStopTime(stop=schedule.GetStop("BULLFROG"), stop_time="09:00:00")  
trip2.AddStopTime(stop=schedule.GetStop("STAGECOACH"), stop_time="09:30:00")  
saved_schedule_file = StringIO()  
schedule.WriteGoogleTransitFeed(saved_schedule_file)  
self.appendToZip(saved_schedule_file, "stop_times.txt","")  
self.problems.AssertNoMoreExceptions()  
 
self.assertLoadAndCheckExtraValues(saved_schedule_file)  
 
def testExtraFileColumn(self):  
"""Extra columns loaded from a file are preserved when writing."""  
# Uncomment the code in assertLoadAndCheckExtraValues to generate this  
# string.  
self.zip.writestr(  
"trips.txt",  
"route_id,service_id,trip_id,t_foo,n_foo\n"  
"AB,FULLW,AB1,foo,\n"  
"AB,FULLW,AB2,,bar\n")  
self.zip.writestr(  
"stop_times.txt",  
self.zip.read("stop_times.txt") +  
"AB2,09:00:00,09:00:00,BULLFROG,1\n"  
"AB2,09:30:00,09:30:00,STAGECOACH,2\n")  
load1_problems = TestFailureProblemReporter(  
self, ("ExpirationDate", "UnrecognizedColumn"))  
schedule = transitfeed.Loader(problems=load1_problems,  
extra_validation=True,  
zip=self.zip).Load()  
saved_schedule_file = StringIO()  
schedule.WriteGoogleTransitFeed(saved_schedule_file)  
 
self.assertLoadAndCheckExtraValues(saved_schedule_file)  
 
 
class TripValidationTestCase(ValidationTestCase):  
def runTest(self):  
trip = transitfeed.Trip()  
repr(trip) # shouldn't crash  
 
schedule = transitfeed.Schedule() # Needed to find StopTimes  
schedule.AddRouteObject(  
transitfeed.Route(short_name="54C", long_name="", route_type="Bus",  
route_id="054C",  
agency_id=schedule.GetDefaultAgency().agency_id))  
schedule.AddServicePeriodObject(transitfeed.ServicePeriod(id="WEEK"))  
schedule.GetDefaultServicePeriod().SetDateHasService('20070101')  
trip = transitfeed.Trip()  
repr(trip) # shouldn't crash  
 
trip = transitfeed.Trip()  
trip.trip_headsign = '\xBA\xDF\x0D' # Not valid ascii or utf8  
repr(trip) # shouldn't crash  
 
trip.route_id = '054C'  
trip.service_id = 'WEEK'  
trip.trip_id = '054C-00'  
trip.trip_headsign = 'via Polish Hill'  
trip.direction_id = '0'  
trip.block_id = None  
trip.shape_id = None  
trip.Validate(self.problems)  
self.problems.AssertNoMoreExceptions()  
repr(trip) # shouldn't crash  
 
# missing route ID  
trip.route_id = None  
self.ExpectMissingValue(trip, 'route_id')  
trip.route_id = '054C'  
 
# missing service ID  
trip.service_id = None  
self.ExpectMissingValue(trip, 'service_id')  
trip.service_id = 'WEEK'  
 
# missing trip ID  
trip.trip_id = None  
self.ExpectMissingValue(trip, 'trip_id')  
trip.trip_id = '054C-00'  
 
# invalid direction ID  
trip.direction_id = 'NORTH'  
self.ExpectInvalidValue(trip, 'direction_id')  
trip.direction_id = '0'  
 
# AddTripObject validates that route_id, service_id, .... are found in the  
# schedule. The Validate calls made by self.Expect... above can't make this  
# check because trip is not in a schedule.  
trip.route_id = '054C-notfound'  
schedule.AddTripObject(trip, self.problems)  
e = self.problems.PopException('InvalidValue')  
self.assertEqual('route_id', e.column_name)  
self.problems.AssertNoMoreExceptions()  
trip.route_id = '054C'  
 
# Make sure calling Trip.Validate validates that route_id and service_id  
# are found in the schedule.  
trip.service_id = 'WEEK-notfound'  
trip.Validate(self.problems)  
e = self.problems.PopException('InvalidValue')  
self.assertEqual('service_id', e.column_name)  
self.problems.AssertNoMoreExceptions()  
trip.service_id = 'WEEK'  
 
trip.Validate(self.problems)  
self.problems.AssertNoMoreExceptions()  
 
# expect no problems for non-overlapping periods  
trip.AddHeadwayPeriod("06:00:00", "12:00:00", 600)  
trip.AddHeadwayPeriod("01:00:00", "02:00:00", 1200)  
trip.AddHeadwayPeriod("04:00:00", "05:00:00", 1000)  
trip.AddHeadwayPeriod("12:00:00", "19:00:00", 700)  
trip.Validate(self.problems)  
self.problems.AssertNoMoreExceptions()  
trip.ClearHeadwayPeriods()  
 
# overlapping headway periods  
trip.AddHeadwayPeriod("00:00:00", "12:00:00", 600)  
trip.AddHeadwayPeriod("06:00:00", "18:00:00", 1200)  
self.ExpectOtherProblem(trip)  
trip.ClearHeadwayPeriods()  
trip.AddHeadwayPeriod("12:00:00", "20:00:00", 600)  
trip.AddHeadwayPeriod("06:00:00", "18:00:00", 1200)  
self.ExpectOtherProblem(trip)  
trip.ClearHeadwayPeriods()  
trip.AddHeadwayPeriod("06:00:00", "12:00:00", 600)  
trip.AddHeadwayPeriod("00:00:00", "25:00:00", 1200)  
self.ExpectOtherProblem(trip)  
trip.ClearHeadwayPeriods()  
trip.AddHeadwayPeriod("00:00:00", "20:00:00", 600)  
trip.AddHeadwayPeriod("06:00:00", "18:00:00", 1200)  
self.ExpectOtherProblem(trip)  
trip.ClearHeadwayPeriods()  
self.problems.AssertNoMoreExceptions()  
 
class TripSequenceValidationTestCase(ValidationTestCase):  
def runTest(self):  
trip = transitfeed.Trip()  
schedule = transitfeed.Schedule() # Needed to find StopTimes  
route = transitfeed.Route(short_name="54C", long_name="", route_type="Bus",  
route_id="054C")  
route.agency_id = schedule.GetDefaultAgency().agency_id  
schedule.AddRouteObject(route)  
service_period = transitfeed.ServicePeriod("WEEK")  
service_period.SetWeekdayService(True)  
schedule.AddServicePeriodObject(service_period)  
trip = transitfeed.Trip()  
trip.trip_headsign = '\xBA\xDF\x0D' # Not valid ascii or utf8  
trip.route_id = '054C'  
trip.service_id = 'WEEK'  
trip.trip_id = '054C-00'  
trip.trip_headsign = 'via Polish Hill'  
trip.direction_id = '0'  
trip.block_id = None  
trip.shape_id = None  
stop1 = transitfeed.Stop(36.425288, -117.133162, "Demo Stop 1", "STOP1")  
stop2 = transitfeed.Stop(36.425666, -117.133666, "Demo Stop 2", "STOP2")  
stop3 = transitfeed.Stop(36.425999, -117.133999, "Demo Stop 3", "STOP3")  
schedule.AddTripObject(trip)  
schedule.AddStopObject(stop1)  
schedule.AddStopObject(stop2)  
schedule.AddStopObject(stop3)  
stoptime1 = transitfeed.StopTime(self.problems, stop1,  
stop_time='12:00:00', stop_sequence=1)  
stoptime2 = transitfeed.StopTime(self.problems, stop2,  
stop_time='11:30:00', stop_sequence=2)  
stoptime3 = transitfeed.StopTime(self.problems, stop3,  
stop_time='12:15:00', stop_sequence=3)  
trip._AddStopTimeObjectUnordered(stoptime1, schedule)  
trip._AddStopTimeObjectUnordered(stoptime2, schedule)  
trip._AddStopTimeObjectUnordered(stoptime3, schedule)  
trip.Validate(self.problems)  
e = self.problems.PopException('OtherProblem')  
self.assertTrue(e.FormatProblem().find('Timetravel detected') != -1)  
self.assertTrue(e.FormatProblem().find('number 2 in trip 054C-00') != -1)  
self.problems.AssertNoMoreExceptions()  
 
 
class TripServiceIDValidationTestCase(ValidationTestCase):  
def runTest(self):  
schedule = transitfeed.Schedule(self.problems)  
schedule.AddAgency("Test Agency", "http://example.com",  
"America/Los_Angeles")  
service_period = transitfeed.ServicePeriod("WEEK")  
service_period.SetStartDate("20070101")  
service_period.SetEndDate("20071231")  
service_period.SetWeekdayService(True)  
schedule.AddServicePeriodObject(service_period)  
 
schedule.AddRouteObject(  
transitfeed.Route("54C", "Polish Hill", 3, "054C"))  
 
trip1 = transitfeed.Trip()  
trip1.route_id = "054C"  
trip1.service_id = "WEEKDAY"  
trip1.trip_id = "054C_WEEK"  
self.ExpectInvalidValueInClosure(column_name="service_id",  
value="WEEKDAY",  
c=lambda: schedule.AddTripObject(trip1))  
 
 
class TripHasStopTimeValidationTestCase(ValidationTestCase):  
def runTest(self):  
schedule = transitfeed.Schedule(self.problems)  
schedule.AddAgency("Test Agency", "http://example.com",  
"America/Los_Angeles")  
schedule.AddRouteObject(  
transitfeed.Route("54C", "Polish Hill", 3, "054C"))  
 
service_period = transitfeed.ServicePeriod("WEEK")  
service_period.SetStartDate("20070101")  
service_period.SetEndDate("20071231")  
service_period.SetWeekdayService(True)  
schedule.AddServicePeriodObject(service_period)  
 
trip = transitfeed.Trip()  
trip.route_id = '054C'  
trip.service_id = 'WEEK'  
trip.trip_id = '054C-00'  
trip.trip_headsign = 'via Polish Hill'  
trip.direction_id = '0'  
trip.block_id = None  
trip.shape_id = None  
schedule.AddTripObject(trip)  
 
# We should get an OtherProblem here because the trip has no stops.  
self.ExpectOtherProblem(schedule)  
 
# It should trigger a TYPE_ERROR if there are frequencies for the trip  
# but no stops  
trip.AddHeadwayPeriod("01:00:00","12:00:00", 600)  
schedule.Validate(self.problems)  
self.problems.PopException('OtherProblem') # pop first warning  
e = self.problems.PopException('OtherProblem') # pop frequency error  
self.assertTrue(e.FormatProblem().find('Frequencies defined, but') != -1)  
self.assertTrue(e.FormatProblem().find('given in trip 054C-00') != -1)  
self.assertEquals(transitfeed.TYPE_ERROR, e.type)  
self.problems.AssertNoMoreExceptions()  
trip.ClearHeadwayPeriods()  
 
# Add a stop, but with only one stop passengers have nowhere to exit!  
stop = transitfeed.Stop(36.425288, -117.133162, "Demo Stop 1", "STOP1")  
schedule.AddStopObject(stop)  
trip.AddStopTime(stop, arrival_time="5:11:00", departure_time="5:12:00")  
self.ExpectOtherProblem(schedule)  
 
# Add another stop, and then validation should be happy.  
stop = transitfeed.Stop(36.424288, -117.133142, "Demo Stop 2", "STOP2")  
schedule.AddStopObject(stop)  
trip.AddStopTime(stop, arrival_time="5:15:00", departure_time="5:16:00")  
schedule.Validate(self.problems)  
 
trip.AddStopTime(stop, stop_time="05:20:00")  
trip.AddStopTime(stop, stop_time="05:22:00")  
 
# Last stop must always have a time  
trip.AddStopTime(stop, arrival_secs=None, departure_secs=None)  
self.ExpectInvalidValueInClosure(  
'arrival_time', c=lambda: trip.GetEndTime(problems=self.problems))  
 
 
class ShapeDistTraveledOfStopTimeValidationTestCase(ValidationTestCase):  
def runTest(self):  
schedule = transitfeed.Schedule(self.problems)  
schedule.AddAgency("Test Agency", "http://example.com",  
"America/Los_Angeles")  
schedule.AddRouteObject(  
transitfeed.Route("54C", "Polish Hill", 3, "054C"))  
 
service_period = transitfeed.ServicePeriod("WEEK")  
service_period.SetStartDate("20070101")  
service_period.SetEndDate("20071231")  
service_period.SetWeekdayService(True)  
schedule.AddServicePeriodObject(service_period)  
 
shape = transitfeed.Shape("shape_1")  
shape.AddPoint(36.425288, -117.133162, 0)  
shape.AddPoint(36.424288, -117.133142, 1)  
schedule.AddShapeObject(shape)  
 
trip = transitfeed.Trip()  
trip.route_id = '054C'  
trip.service_id = 'WEEK'  
trip.trip_id = '054C-00'  
trip.trip_headsign = 'via Polish Hill'  
trip.direction_id = '0'  
trip.block_id = None  
trip.shape_id = 'shape_1'  
schedule.AddTripObject(trip)  
 
stop = transitfeed.Stop(36.425288, -117.133162, "Demo Stop 1", "STOP1")  
schedule.AddStopObject(stop)  
trip.AddStopTime(stop, arrival_time="5:11:00", departure_time="5:12:00",  
stop_sequence=0, shape_dist_traveled=0)  
stop = transitfeed.Stop(36.424288, -117.133142, "Demo Stop 2", "STOP2")  
schedule.AddStopObject(stop)  
trip.AddStopTime(stop, arrival_time="5:15:00", departure_time="5:16:00",  
stop_sequence=1, shape_dist_traveled=1)  
 
stop = transitfeed.Stop(36.423288, -117.133122, "Demo Stop 3", "STOP3")  
schedule.AddStopObject(stop)  
trip.AddStopTime(stop, arrival_time="5:18:00", departure_time="5:19:00",  
stop_sequence=2, shape_dist_traveled=2)  
self.problems.AssertNoMoreExceptions()  
schedule.Validate(self.problems)  
e = self.problems.PopException('OtherProblem')  
self.assertTrue(e.FormatProblem().find('shape_dist_traveled=2') != -1)  
self.problems.AssertNoMoreExceptions()  
 
# Error if the distance decreases.  
shape.AddPoint(36.421288, -117.133132, 2)  
stop = transitfeed.Stop(36.421288, -117.133122, "Demo Stop 4", "STOP4")  
schedule.AddStopObject(stop)  
stoptime = transitfeed.StopTime(self.problems, stop,  
arrival_time="5:29:00",  
departure_time="5:29:00",stop_sequence=3,  
shape_dist_traveled=1.7)  
trip.AddStopTimeObject(stoptime, schedule=schedule)  
self.problems.AssertNoMoreExceptions()  
schedule.Validate(self.problems)  
e = self.problems.PopException('InvalidValue')  
self.assertMatchesRegex('stop STOP4 has', e.FormatProblem())  
self.assertMatchesRegex('shape_dist_traveled=1.7', e.FormatProblem())  
self.assertMatchesRegex('distance was 2.0.', e.FormatProblem())  
self.assertEqual(e.type, transitfeed.TYPE_ERROR)  
self.problems.AssertNoMoreExceptions()  
 
# Warning if distance remains the same between two stop_times  
stoptime.shape_dist_traveled = 2.0  
trip.ReplaceStopTimeObject(stoptime, schedule=schedule)  
schedule.Validate(self.problems)  
e = self.problems.PopException('InvalidValue')  
self.assertMatchesRegex('stop STOP4 has', e.FormatProblem())  
self.assertMatchesRegex('shape_dist_traveled=2.0', e.FormatProblem())  
self.assertMatchesRegex('distance was 2.0.', e.FormatProblem())  
self.assertEqual(e.type, transitfeed.TYPE_WARNING)  
self.problems.AssertNoMoreExceptions()  
 
 
class StopMatchWithShapeTestCase(ValidationTestCase):  
def runTest(self):  
schedule = transitfeed.Schedule(self.problems)  
schedule.AddAgency("Test Agency", "http://example.com",  
"America/Los_Angeles")  
schedule.AddRouteObject(  
transitfeed.Route("54C", "Polish Hill", 3, "054C"))  
 
service_period = transitfeed.ServicePeriod("WEEK")  
service_period.SetDateHasService('20070101')  
schedule.AddServicePeriodObject(service_period)  
 
shape = transitfeed.Shape("shape_1")  
shape.AddPoint(36.425288, -117.133162, 0)  
shape.AddPoint(36.424288, -117.143142, 1)  
schedule.AddShapeObject(shape)  
 
trip = transitfeed.Trip()  
trip.route_id = '054C'  
trip.service_id = 'WEEK'  
trip.trip_id = '054C-00'  
trip.shape_id = 'shape_1'  
schedule.AddTripObject(trip)  
 
# Stop 1 is only 600 meters away from shape, which is allowed.  
stop = transitfeed.Stop(36.425288, -117.139162, "Demo Stop 1", "STOP1")  
schedule.AddStopObject(stop)  
trip.AddStopTime(stop, arrival_time="5:11:00", departure_time="5:12:00",  
stop_sequence=0, shape_dist_traveled=0)  
# Stop 2 is more than 1000 meters away from shape, which is not allowed.  
stop = transitfeed.Stop(36.424288, -117.158142, "Demo Stop 2", "STOP2")  
schedule.AddStopObject(stop)  
trip.AddStopTime(stop, arrival_time="5:15:00", departure_time="5:16:00",  
stop_sequence=1, shape_dist_traveled=1)  
 
schedule.Validate(self.problems)  
e = self.problems.PopException('StopTooFarFromShapeWithDistTraveled')  
self.assertTrue(e.FormatProblem().find('Demo Stop 2') != -1)  
self.assertTrue(e.FormatProblem().find('1344 meters away') != -1)  
self.problems.AssertNoMoreExceptions()  
 
 
class TripAddStopTimeObjectTestCase(ValidationTestCase):  
def runTest(self):  
schedule = transitfeed.Schedule(problem_reporter=self.problems)  
schedule.AddAgency("\xc8\x8b Fly Agency", "http://iflyagency.com",  
"America/Los_Angeles")  
service_period = schedule.GetDefaultServicePeriod().SetDateHasService('20070101')  
stop1 = schedule.AddStop(lng=140, lat=48.2, name="Stop 1")  
stop2 = schedule.AddStop(lng=140.001, lat=48.201, name="Stop 2")  
route = schedule.AddRoute("B", "Beta", "Bus")  
trip = route.AddTrip(schedule, "bus trip")  
trip.AddStopTimeObject(transitfeed.StopTime(self.problems, stop1,  
arrival_secs=10,  
departure_secs=10),  
schedule=schedule, problems=self.problems)  
trip.AddStopTimeObject(transitfeed.StopTime(self.problems, stop2,  
arrival_secs=20,  
departure_secs=20),  
schedule=schedule, problems=self.problems)  
# TODO: Factor out checks or use mock problems object  
self.ExpectOtherProblemInClosure(lambda:  
trip.AddStopTimeObject(transitfeed.StopTime(self.problems, stop1,  
arrival_secs=15,  
departure_secs=15),  
schedule=schedule, problems=self.problems))  
trip.AddStopTimeObject(transitfeed.StopTime(self.problems, stop1),  
schedule=schedule, problems=self.problems)  
self.ExpectOtherProblemInClosure(lambda:  
trip.AddStopTimeObject(transitfeed.StopTime(self.problems, stop1,  
arrival_secs=15,  
departure_secs=15),  
schedule=schedule, problems=self.problems))  
trip.AddStopTimeObject(transitfeed.StopTime(self.problems, stop1,  
arrival_secs=30,  
departure_secs=30),  
schedule=schedule, problems=self.problems)  
self.problems.AssertNoMoreExceptions()  
 
class DuplicateTripTestCase(ValidationTestCase):  
def runTest(self):  
 
schedule = transitfeed.Schedule(self.problems)  
schedule._check_duplicate_trips = True;  
 
agency = transitfeed.Agency('Demo agency', 'http://google.com',  
'America/Los_Angeles', 'agency1')  
schedule.AddAgencyObject(agency)  
 
service = schedule.GetDefaultServicePeriod()  
service.SetDateHasService('20070101')  
 
route1 = transitfeed.Route('Route1', 'route 1', 3, 'route_1', 'agency1')  
schedule.AddRouteObject(route1)  
route2 = transitfeed.Route('Route2', 'route 2', 3, 'route_2', 'agency1')  
schedule.AddRouteObject(route2)  
 
trip1 = transitfeed.Trip()  
trip1.route_id = 'route_1'  
trip1.trip_id = 't1'  
trip1.trip_headsign = 'via Polish Hill'  
trip1.direction_id = '0'  
trip1.service_id = service.service_id  
schedule.AddTripObject(trip1)  
 
trip2 = transitfeed.Trip()  
trip2.route_id = 'route_2'  
trip2.trip_id = 't2'  
trip2.trip_headsign = 'New'  
trip2.direction_id = '0'  
trip2.service_id = service.service_id  
schedule.AddTripObject(trip2)  
 
trip3 = transitfeed.Trip()  
trip3.route_id = 'route_1'  
trip3.trip_id = 't3'  
trip3.trip_headsign = 'New Demo'  
trip3.direction_id = '0'  
trip3.service_id = service.service_id  
schedule.AddTripObject(trip3)  
 
stop1 = transitfeed.Stop(36.425288, -117.139162, "Demo Stop 1", "STOP1")  
schedule.AddStopObject(stop1)  
trip1.AddStopTime(stop1, arrival_time="5:11:00", departure_time="5:12:00",  
stop_sequence=0, shape_dist_traveled=0)  
trip2.AddStopTime(stop1, arrival_time="5:11:00", departure_time="5:12:00",  
stop_sequence=0, shape_dist_traveled=0)  
trip3.AddStopTime(stop1, arrival_time="6:11:00", departure_time="6:12:00",  
stop_sequence=0, shape_dist_traveled=0)  
 
stop2 = transitfeed.Stop(36.424288, -117.158142, "Demo Stop 2", "STOP2")  
schedule.AddStopObject(stop2)  
trip1.AddStopTime(stop2, arrival_time="5:15:00", departure_time="5:16:00",  
stop_sequence=1, shape_dist_traveled=1)  
trip2.AddStopTime(stop2, arrival_time="5:25:00", departure_time="5:26:00",  
stop_sequence=1, shape_dist_traveled=1)  
trip3.AddStopTime(stop2, arrival_time="6:15:00", departure_time="6:16:00",  
stop_sequence=1, shape_dist_traveled=1)  
 
schedule.Validate(self.problems)  
e = self.problems.PopException('DuplicateTrip')  
self.assertTrue(e.FormatProblem().find('t1 of route') != -1)  
self.assertTrue(e.FormatProblem().find('t2 of route') != -1)  
self.problems.AssertNoMoreExceptions()  
 
 
class StopBelongsToBothSubwayAndBusTestCase(ValidationTestCase):  
def runTest(self):  
schedule = transitfeed.Schedule(self.problems)  
 
schedule.AddAgency("Demo Agency", "http://example.com",  
"America/Los_Angeles")  
route1 = schedule.AddRoute(short_name="route1", long_name="route_1",  
route_type=3)  
route2 = schedule.AddRoute(short_name="route2", long_name="route_2",  
route_type=1)  
 
service = schedule.GetDefaultServicePeriod()  
service.SetDateHasService("20070101")  
 
trip1 = route1.AddTrip(schedule, "trip1", service, "t1")  
trip2 = route2.AddTrip(schedule, "trip2", service, "t2")  
 
stop1 = schedule.AddStop(36.425288, -117.133162, "stop1")  
stop2 = schedule.AddStop(36.424288, -117.133142, "stop2")  
stop3 = schedule.AddStop(36.423288, -117.134142, "stop3")  
 
trip1.AddStopTime(stop1, arrival_time="5:11:00", departure_time="5:12:00")  
trip1.AddStopTime(stop2, arrival_time="5:21:00", departure_time="5:22:00")  
 
trip2.AddStopTime(stop1, arrival_time="6:11:00", departure_time="6:12:00")  
trip2.AddStopTime(stop3, arrival_time="6:21:00", departure_time="6:22:00")  
 
schedule.Validate(self.problems)  
e = self.problems.PopException("StopWithMultipleRouteTypes")  
self.assertTrue(e.FormatProblem().find("Stop stop1") != -1)  
self.assertTrue(e.FormatProblem().find("subway (ID=1)") != -1)  
self.assertTrue(e.FormatProblem().find("bus line (ID=0)") != -1)  
self.problems.AssertNoMoreExceptions()  
 
 
class TripReplaceStopTimeObjectTestCase(unittest.TestCase):  
def runTest(self):  
schedule = transitfeed.Schedule()  
schedule.AddAgency("\xc8\x8b Fly Agency", "http://iflyagency.com",  
"America/Los_Angeles")  
service_period = \  
schedule.GetDefaultServicePeriod().SetDateHasService('20070101')  
stop1 = schedule.AddStop(lng=140, lat=48.2, name="Stop 1")  
route = schedule.AddRoute("B", "Beta", "Bus")  
trip = route.AddTrip(schedule, "bus trip")  
stoptime = transitfeed.StopTime(transitfeed.default_problem_reporter, stop1,  
arrival_secs=10,  
departure_secs=10)  
trip.AddStopTimeObject(stoptime, schedule=schedule)  
stoptimes = trip.GetStopTimes()  
stoptime.departure_secs = 20  
trip.ReplaceStopTimeObject(stoptime, schedule=schedule)  
stoptimes = trip.GetStopTimes()  
self.assertEqual(len(stoptimes), 1)  
self.assertEqual(stoptimes[0].departure_secs, 20)  
 
unknown_stop = schedule.AddStop(lng=140, lat=48.2, name="unknown")  
unknown_stoptime = transitfeed.StopTime(  
transitfeed.default_problem_reporter, unknown_stop,  
arrival_secs=10,  
departure_secs=10)  
unknown_stoptime.stop_sequence = 5  
# Attempting to replace a non-existent StopTime raises an error  
self.assertRaises(transitfeed.Error, trip.ReplaceStopTimeObject,  
unknown_stoptime, schedule=schedule)  
 
class TripStopTimeAccessorsTestCase(unittest.TestCase):  
def runTest(self):  
schedule = transitfeed.Schedule(  
problem_reporter=ExceptionProblemReporterNoExpiration())  
schedule.NewDefaultAgency(agency_name="Test Agency",  
agency_url="http://example.com",  
agency_timezone="America/Los_Angeles")  
route = schedule.AddRoute(short_name="54C", long_name="Polish Hill", route_type=3)  
 
service_period = schedule.GetDefaultServicePeriod()  
service_period.SetDateHasService("20070101")  
 
trip = route.AddTrip(schedule, 'via Polish Hill')  
 
stop1 = schedule.AddStop(36.425288, -117.133162, "Demo Stop 1")  
stop2 = schedule.AddStop(36.424288, -117.133142, "Demo Stop 2")  
 
trip.AddStopTime(stop1, arrival_time="5:11:00", departure_time="5:12:00")  
trip.AddStopTime(stop2, arrival_time="5:15:00", departure_time="5:16:00")  
 
# Add some more stop times and test GetEndTime does the correct thing  
self.assertEqual(transitfeed.FormatSecondsSinceMidnight(trip.GetStartTime()),  
"05:11:00")  
self.assertEqual(transitfeed.FormatSecondsSinceMidnight(trip.GetEndTime()),  
"05:16:00")  
 
trip.AddStopTime(stop1, stop_time="05:20:00")  
self.assertEqual(transitfeed.FormatSecondsSinceMidnight(trip.GetEndTime()),  
"05:20:00")  
 
trip.AddStopTime(stop2, stop_time="05:22:00")  
self.assertEqual(transitfeed.FormatSecondsSinceMidnight(trip.GetEndTime()),  
"05:22:00")  
self.assertEqual(len(trip.GetStopTimesTuples()), 4)  
self.assertEqual(trip.GetStopTimesTuples()[0], (trip.trip_id, "05:11:00",  
"05:12:00", stop1.stop_id,  
1, '', '', '', ''))  
self.assertEqual(trip.GetStopTimesTuples()[3], (trip.trip_id, "05:22:00",  
"05:22:00", stop2.stop_id,  
4, '', '', '', ''))  
 
class TripClearStopTimesTestCase(unittest.TestCase):  
def runTest(self):  
schedule = transitfeed.Schedule(  
problem_reporter=ExceptionProblemReporterNoExpiration())  
schedule.NewDefaultAgency(agency_name="Test Agency",  
agency_timezone="America/Los_Angeles")  
route = schedule.AddRoute(short_name="54C", long_name="Hill", route_type=3)  
schedule.GetDefaultServicePeriod().SetDateHasService("20070101")  
stop1 = schedule.AddStop(36, -117.1, "Demo Stop 1")  
stop2 = schedule.AddStop(36, -117.2, "Demo Stop 2")  
stop3 = schedule.AddStop(36, -117.3, "Demo Stop 3")  
 
trip = route.AddTrip(schedule, "via Polish Hill")  
trip.ClearStopTimes()  
self.assertFalse(trip.GetStopTimes())  
trip.AddStopTime(stop1, stop_time="5:11:00")  
self.assertTrue(trip.GetStopTimes())  
trip.ClearStopTimes()  
self.assertFalse(trip.GetStopTimes())  
trip.AddStopTime(stop3, stop_time="4:00:00") # Can insert earlier time  
trip.AddStopTime(stop2, stop_time="4:15:00")  
trip.AddStopTime(stop1, stop_time="4:21:00")  
old_stop_times = trip.GetStopTimes()  
self.assertTrue(old_stop_times)  
trip.ClearStopTimes()  
self.assertFalse(trip.GetStopTimes())  
for st in old_stop_times:  
trip.AddStopTimeObject(st)  
self.assertEqual(trip.GetStartTime(), 4 * 3600)  
self.assertEqual(trip.GetEndTime(), 4 * 3600 + 21 * 60)  
 
 
class BasicParsingTestCase(unittest.TestCase):  
"""Checks that we're getting the number of child objects that we expect."""  
def assertLoadedCorrectly(self, schedule):  
"""Check that the good_feed looks correct"""  
self.assertEqual(1, len(schedule._agencies))  
self.assertEqual(5, len(schedule.routes))  
self.assertEqual(2, len(schedule.service_periods))  
self.assertEqual(10, len(schedule.stops))  
self.assertEqual(11, len(schedule.trips))  
self.assertEqual(0, len(schedule.fare_zones))  
 
def assertLoadedStopTimesCorrectly(self, schedule):  
self.assertEqual(5, len(schedule.GetTrip('CITY1').GetStopTimes()))  
self.assertEqual('to airport', schedule.GetTrip('STBA').GetStopTimes()[0].stop_headsign)  
self.assertEqual(2, schedule.GetTrip('CITY1').GetStopTimes()[1].pickup_type)  
self.assertEqual(3, schedule.GetTrip('CITY1').GetStopTimes()[1].drop_off_type)  
 
def test_MemoryDb(self):  
loader = transitfeed.Loader(  
DataPath('good_feed.zip'),  
problems=TestFailureProblemReporter(self),  
extra_validation=True,  
memory_db=True)  
schedule = loader.Load()  
self.assertLoadedCorrectly(schedule)  
self.assertLoadedStopTimesCorrectly(schedule)  
 
def test_TemporaryFile(self):  
loader = transitfeed.Loader(  
DataPath('good_feed.zip'),  
problems=TestFailureProblemReporter(self),  
extra_validation=True,  
memory_db=False)  
schedule = loader.Load()  
self.assertLoadedCorrectly(schedule)  
self.assertLoadedStopTimesCorrectly(schedule)  
 
def test_NoLoadStopTimes(self):  
problems = TestFailureProblemReporter(  
self, ignore_types=("ExpirationDate", "UnusedStop", "OtherProblem"))  
loader = transitfeed.Loader(  
DataPath('good_feed.zip'),  
problems=problems,  
extra_validation=True,  
load_stop_times=False)  
schedule = loader.Load()  
self.assertLoadedCorrectly(schedule)  
self.assertEqual(0, len(schedule.GetTrip('CITY1').GetStopTimes()))  
 
 
class RepeatedRouteNameTestCase(LoadTestCase):  
def runTest(self):  
self.ExpectInvalidValue('repeated_route_name', 'route_long_name')  
 
 
class InvalidRouteAgencyTestCase(LoadTestCase):  
def runTest(self):  
self.Load('invalid_route_agency')  
self.problems.PopInvalidValue("agency_id", "routes.txt")  
self.problems.PopInvalidValue("route_id", "trips.txt")  
self.problems.AssertNoMoreExceptions()  
 
 
class UndefinedStopAgencyTestCase(LoadTestCase):  
def runTest(self):  
self.ExpectInvalidValue('undefined_stop', 'stop_id')  
 
 
class SameShortLongNameTestCase(LoadTestCase):  
def runTest(self):  
self.ExpectInvalidValue('same_short_long_name', 'route_long_name')  
 
 
class UnusedStopAgencyTestCase(LoadTestCase):  
def runTest(self):  
self.Load('unused_stop'),  
e = self.problems.PopException("UnusedStop")  
self.assertEqual("Bogus Stop (Demo)", e.stop_name)  
self.assertEqual("BOGUS", e.stop_id)  
self.problems.AssertNoMoreExceptions()  
 
 
 
class OnlyCalendarDatesTestCase(LoadTestCase):  
def runTest(self):  
self.Load('only_calendar_dates'),  
self.problems.AssertNoMoreExceptions()  
 
 
class DuplicateServiceIdDateWarningTestCase(MemoryZipTestCase):  
def runTest(self):  
# Two lines with the same value of service_id and date.  
# Test for the warning.  
self.zip.writestr(  
'calendar_dates.txt',  
'service_id,date,exception_type\n'  
'FULLW,20100604,1\n'  
'FULLW,20100604,2\n')  
schedule = self.loader.Load()  
e = self.problems.PopException('DuplicateID')  
self.assertEquals('(service_id, date)', e.column_name)  
self.assertEquals('(FULLW, 20100604)', e.value)  
 
 
class AddStopTimeParametersTestCase(unittest.TestCase):  
def runTest(self):  
problem_reporter = TestFailureProblemReporter(self)  
schedule = transitfeed.Schedule(problem_reporter=problem_reporter)  
route = schedule.AddRoute(short_name="10", long_name="", route_type="Bus")  
stop = schedule.AddStop(40, -128, "My stop")  
# Stop must be added to schedule so that the call  
# AddStopTime -> AddStopTimeObject -> GetStopTimes -> GetStop can work  
trip = transitfeed.Trip()  
trip.route_id = route.route_id  
trip.service_id = schedule.GetDefaultServicePeriod().service_id  
trip.trip_id = "SAMPLE_TRIP"  
schedule.AddTripObject(trip)  
 
# First stop must have time  
trip.AddStopTime(stop, arrival_secs=300, departure_secs=360)  
trip.AddStopTime(stop)  
trip.AddStopTime(stop, arrival_time="00:07:00", departure_time="00:07:30")  
trip.Validate(problem_reporter)  
 
 
class ExpirationDateTestCase(unittest.TestCase):  
def runTest(self):  
problems = RecordingProblemReporter(self)  
schedule = transitfeed.Schedule(problem_reporter=problems)  
 
now = time.mktime(time.localtime())  
seconds_per_day = 60 * 60 * 24  
two_weeks_ago = time.localtime(now - 14 * seconds_per_day)  
two_weeks_from_now = time.localtime(now + 14 * seconds_per_day)  
two_months_from_now = time.localtime(now + 60 * seconds_per_day)  
date_format = "%Y%m%d"  
 
service_period = schedule.GetDefaultServicePeriod()  
service_period.SetWeekdayService(True)  
service_period.SetStartDate("20070101")  
 
service_period.SetEndDate(time.strftime(date_format, two_months_from_now))  
schedule.Validate() # should have no problems  
problems.AssertNoMoreExceptions()  
 
service_period.SetEndDate(time.strftime(date_format, two_weeks_from_now))  
schedule.Validate()  
e = problems.PopException('ExpirationDate')  
self.assertTrue(e.FormatProblem().index('will soon expire'))  
problems.AssertNoMoreExceptions()  
 
service_period.SetEndDate(time.strftime(date_format, two_weeks_ago))  
schedule.Validate()  
e = problems.PopException('ExpirationDate')  
self.assertTrue(e.FormatProblem().index('expired'))  
problems.AssertNoMoreExceptions()  
 
 
class FutureServiceStartDateTestCase(unittest.TestCase):  
def runTest(self):  
problems = RecordingProblemReporter(self)  
schedule = transitfeed.Schedule(problem_reporter=problems)  
 
today = datetime.date.today()  
yesterday = today - datetime.timedelta(days=1)  
tomorrow = today + datetime.timedelta(days=1)  
two_months_from_today = today + datetime.timedelta(days=60)  
 
service_period = schedule.GetDefaultServicePeriod()  
service_period.SetWeekdayService(True)  
service_period.SetWeekendService(True)  
service_period.SetEndDate(two_months_from_today.strftime("%Y%m%d"))  
 
service_period.SetStartDate(yesterday.strftime("%Y%m%d"))  
schedule.Validate()  
problems.AssertNoMoreExceptions()  
 
service_period.SetStartDate(today.strftime("%Y%m%d"))  
schedule.Validate()  
problems.AssertNoMoreExceptions()  
 
service_period.SetStartDate(tomorrow.strftime("%Y%m%d"))  
schedule.Validate()  
problems.PopException('FutureService')  
problems.AssertNoMoreExceptions()  
 
 
class CalendarTxtIntegrationTestCase(MemoryZipTestCase):  
def testBadEndDateFormat(self):  
# A badly formatted end_date used to generate an InvalidValue report from  
# Schedule.Validate and ServicePeriod.Validate. Test for the bug.  
self.zip.writestr(  
"calendar.txt",  
"service_id,monday,tuesday,wednesday,thursday,friday,saturday,sunday,"  
"start_date,end_date\n"  
"FULLW,1,1,1,1,1,1,1,20070101,20101232\n"  
"WE,0,0,0,0,0,1,1,20070101,20101231\n")  
schedule = self.loader.Load()  
e = self.problems.PopInvalidValue('end_date')  
self.problems.AssertNoMoreExceptions()  
 
def testBadStartDateFormat(self):  
self.zip.writestr(  
"calendar.txt",  
"service_id,monday,tuesday,wednesday,thursday,friday,saturday,sunday,"  
"start_date,end_date\n"  
"FULLW,1,1,1,1,1,1,1,200701xx,20101231\n"  
"WE,0,0,0,0,0,1,1,20070101,20101231\n")  
schedule = self.loader.Load()  
e = self.problems.PopInvalidValue('start_date')  
self.problems.AssertNoMoreExceptions()  
 
def testNoStartDateAndEndDate(self):  
"""Regression test for calendar.txt with empty start_date and end_date.  
 
See http://code.google.com/p/googletransitdatafeed/issues/detail?id=41  
"""  
self.zip.writestr(  
"calendar.txt",  
"service_id,monday,tuesday,wednesday,thursday,friday,saturday,sunday,"  
"start_date,end_date\n"  
"FULLW,1,1,1,1,1,1,1, ,\t\n"  
"WE,0,0,0,0,0,1,1,20070101,20101231\n")  
schedule = self.loader.Load()  
e = self.problems.PopException("MissingValue")  
self.assertEquals(2, e.row_num)  
self.assertEquals("start_date", e.column_name)  
e = self.problems.PopException("MissingValue")  
self.assertEquals(2, e.row_num)  
self.assertEquals("end_date", e.column_name)  
self.problems.AssertNoMoreExceptions()  
 
def testNoStartDateAndBadEndDate(self):  
self.zip.writestr(  
"calendar.txt",  
"service_id,monday,tuesday,wednesday,thursday,friday,saturday,sunday,"  
"start_date,end_date\n"  
"FULLW,1,1,1,1,1,1,1,,abc\n"  
"WE,0,0,0,0,0,1,1,20070101,20101231\n")  
schedule = self.loader.Load()  
e = self.problems.PopException("MissingValue")  
self.assertEquals(2, e.row_num)  
self.assertEquals("start_date", e.column_name)  
e = self.problems.PopInvalidValue("end_date")  
self.assertEquals(2, e.row_num)  
self.problems.AssertNoMoreExceptions()  
 
def testMissingEndDateColumn(self):  
self.zip.writestr(  
"calendar.txt",  
"service_id,monday,tuesday,wednesday,thursday,friday,saturday,sunday,"  
"start_date\n"  
"FULLW,1,1,1,1,1,1,1,20070101\n"  
"WE,0,0,0,0,0,1,1,20070101\n")  
schedule = self.loader.Load()  
e = self.problems.PopException("MissingColumn")  
self.assertEquals("end_date", e.column_name)  
self.problems.AssertNoMoreExceptions()  
 
 
class DuplicateTripIDValidationTestCase(unittest.TestCase):  
def runTest(self):  
schedule = transitfeed.Schedule(  
problem_reporter=ExceptionProblemReporterNoExpiration())  
schedule.AddAgency("Sample Agency", "http://example.com",  
"America/Los_Angeles")  
route = transitfeed.Route()  
route.route_id = "SAMPLE_ID"  
route.route_type = 3  
route.route_long_name = "Sample Route"  
schedule.AddRouteObject(route)  
 
service_period = transitfeed.ServicePeriod("WEEK")  
service_period.SetStartDate("20070101")  
service_period.SetEndDate("20071231")  
service_period.SetWeekdayService(True)  
schedule.AddServicePeriodObject(service_period)  
 
trip1 = transitfeed.Trip()  
trip1.route_id = "SAMPLE_ID"  
trip1.service_id = "WEEK"  
trip1.trip_id = "SAMPLE_TRIP"  
schedule.AddTripObject(trip1)  
 
trip2 = transitfeed.Trip()  
trip2.route_id = "SAMPLE_ID"  
trip2.service_id = "WEEK"  
trip2.trip_id = "SAMPLE_TRIP"  
try:  
schedule.AddTripObject(trip2)  
self.fail("Expected Duplicate ID validation failure")  
except transitfeed.DuplicateID, e:  
self.assertEqual("trip_id", e.column_name)  
self.assertEqual("SAMPLE_TRIP", e.value)  
 
 
class DuplicateStopValidationTestCase(ValidationTestCase):  
def runTest(self):  
schedule = transitfeed.Schedule(problem_reporter=self.problems)  
schedule.AddAgency("Sample Agency", "http://example.com",  
"America/Los_Angeles")  
route = transitfeed.Route()  
route.route_id = "SAMPLE_ID"  
route.route_type = 3  
route.route_long_name = "Sample Route"  
schedule.AddRouteObject(route)  
 
service_period = transitfeed.ServicePeriod("WEEK")  
service_period.SetStartDate("20070101")  
service_period.SetEndDate("20071231")  
service_period.SetWeekdayService(True)  
schedule.AddServicePeriodObject(service_period)  
 
trip = transitfeed.Trip()  
trip.route_id = "SAMPLE_ID"  
trip.service_id = "WEEK"  
trip.trip_id = "SAMPLE_TRIP"  
schedule.AddTripObject(trip)  
 
stop1 = transitfeed.Stop()  
stop1.stop_id = "STOP1"  
stop1.stop_name = "Stop 1"  
stop1.stop_lat = 78.243587  
stop1.stop_lon = 32.258937  
schedule.AddStopObject(stop1)  
trip.AddStopTime(stop1, arrival_time="12:00:00", departure_time="12:00:00")  
 
stop2 = transitfeed.Stop()  
stop2.stop_id = "STOP2"  
stop2.stop_name = "Stop 2"  
stop2.stop_lat = 78.253587  
stop2.stop_lon = 32.258937  
schedule.AddStopObject(stop2)  
trip.AddStopTime(stop2, arrival_time="12:05:00", departure_time="12:05:00")  
schedule.Validate()  
 
stop3 = transitfeed.Stop()  
stop3.stop_id = "STOP3"  
stop3.stop_name = "Stop 3"  
stop3.stop_lat = 78.243587  
stop3.stop_lon = 32.268937  
schedule.AddStopObject(stop3)  
trip.AddStopTime(stop3, arrival_time="12:10:00", departure_time="12:10:00")  
schedule.Validate()  
self.problems.AssertNoMoreExceptions()  
 
stop4 = transitfeed.Stop()  
stop4.stop_id = "STOP4"  
stop4.stop_name = "Stop 4"  
stop4.stop_lat = 78.243588  
stop4.stop_lon = 32.268936  
schedule.AddStopObject(stop4)  
trip.AddStopTime(stop4, arrival_time="12:15:00", departure_time="12:15:00")  
schedule.Validate()  
e = self.problems.PopException('StopsTooClose')  
self.problems.AssertNoMoreExceptions()  
 
 
class TempFileTestCaseBase(unittest.TestCase):  
"""  
Subclass of TestCase which sets self.tempfilepath to a valid temporary zip  
file name and removes the file if it exists when the test is done.  
"""  
def setUp(self):  
(fd, self.tempfilepath) = tempfile.mkstemp(".zip")  
# Open file handle causes an exception during remove in Windows  
os.close(fd)  
 
def tearDown(self):  
if os.path.exists(self.tempfilepath):  
os.remove(self.tempfilepath)  
 
 
class MinimalWriteTestCase(TempFileTestCaseBase):  
"""  
This test case simply constructs an incomplete feed with very few  
fields set and ensures that there are no exceptions when writing it out.  
 
This is very similar to TransitFeedSampleCodeTestCase below, but that one  
will no doubt change as the sample code is altered.  
"""  
def runTest(self):  
schedule = transitfeed.Schedule()  
schedule.AddAgency("Sample Agency", "http://example.com",  
"America/Los_Angeles")  
route = transitfeed.Route()  
route.route_id = "SAMPLE_ID"  
route.route_type = 3  
route.route_short_name = "66"  
route.route_long_name = "Sample Route acute letter e\202"  
schedule.AddRouteObject(route)  
 
service_period = transitfeed.ServicePeriod("WEEK")  
service_period.SetStartDate("20070101")  
service_period.SetEndDate("20071231")  
service_period.SetWeekdayService(True)  
schedule.AddServicePeriodObject(service_period)  
 
trip = transitfeed.Trip()  
trip.route_id = "SAMPLE_ID"  
trip.service_period = service_period  
trip.trip_id = "SAMPLE_TRIP"  
schedule.AddTripObject(trip)  
 
stop1 = transitfeed.Stop()  
stop1.stop_id = "STOP1"  
stop1.stop_name = u'Stop 1 acute letter e\202'  
stop1.stop_lat = 78.243587  
stop1.stop_lon = 32.258937  
schedule.AddStopObject(stop1)  
trip.AddStopTime(stop1, arrival_time="12:00:00", departure_time="12:00:00")  
 
stop2 = transitfeed.Stop()  
stop2.stop_id = "STOP2"  
stop2.stop_name = "Stop 2"  
stop2.stop_lat = 78.253587  
stop2.stop_lon = 32.258937  
schedule.AddStopObject(stop2)  
trip.AddStopTime(stop2, arrival_time="12:05:00", departure_time="12:05:00")  
 
schedule.Validate()  
schedule.WriteGoogleTransitFeed(self.tempfilepath)  
 
 
class TransitFeedSampleCodeTestCase(unittest.TestCase):  
"""  
This test should simply contain the sample code printed on the page:  
http://code.google.com/p/googletransitdatafeed/wiki/TransitFeed  
to ensure that it doesn't cause any exceptions.  
"""  
def runTest(self):  
import transitfeed  
 
schedule = transitfeed.Schedule()  
schedule.AddAgency("Sample Agency", "http://example.com",  
"America/Los_Angeles")  
route = transitfeed.Route()  
route.route_id = "SAMPLE_ID"  
route.route_type = 3  
route.route_short_name = "66"  
route.route_long_name = "Sample Route"  
schedule.AddRouteObject(route)  
 
service_period = transitfeed.ServicePeriod("WEEK")  
service_period.SetStartDate("20070101")  
service_period.SetEndDate("20071231")  
service_period.SetWeekdayService(True)  
schedule.AddServicePeriodObject(service_period)  
 
trip = transitfeed.Trip()  
trip.route_id = "SAMPLE_ID"  
trip.service_period = service_period  
trip.trip_id = "SAMPLE_TRIP"  
trip.direction_id = "0"  
trip.block_id = None  
schedule.AddTripObject(trip)  
 
stop1 = transitfeed.Stop()  
stop1.stop_id = "STOP1"  
stop1.stop_name = "Stop 1"  
stop1.stop_lat = 78.243587  
stop1.stop_lon = 32.258937  
schedule.AddStopObject(stop1)  
trip.AddStopTime(stop1, arrival_time="12:00:00", departure_time="12:00:00")  
 
stop2 = transitfeed.Stop()  
stop2.stop_id = "STOP2"  
stop2.stop_name = "Stop 2"  
stop2.stop_lat = 78.253587  
stop2.stop_lon = 32.258937  
schedule.AddStopObject(stop2)  
trip.AddStopTime(stop2, arrival_time="12:05:00", departure_time="12:05:00")  
 
schedule.Validate() # not necessary, but helpful for finding problems  
schedule.WriteGoogleTransitFeed("new_feed.zip")  
 
 
class AgencyIDValidationTestCase(unittest.TestCase):  
def runTest(self):  
schedule = transitfeed.Schedule(  
problem_reporter=ExceptionProblemReporterNoExpiration())  
route = transitfeed.Route()  
route.route_id = "SAMPLE_ID"  
route.route_type = 3  
route.route_long_name = "Sample Route"  
# no agency defined yet, failure.  
try:  
schedule.AddRouteObject(route)  
self.fail("Expected validation error")  
except transitfeed.InvalidValue, e:  
self.assertEqual('agency_id', e.column_name)  
self.assertEqual(None, e.value)  
 
# one agency defined, assume that the route belongs to it  
schedule.AddAgency("Test Agency", "http://example.com",  
"America/Los_Angeles", "TEST_AGENCY")  
schedule.AddRouteObject(route)  
 
schedule.AddAgency("Test Agency 2", "http://example.com",  
"America/Los_Angeles", "TEST_AGENCY_2")  
route = transitfeed.Route()  
route.route_id = "SAMPLE_ID_2"  
route.route_type = 3  
route.route_long_name = "Sample Route 2"  
# multiple agencies defined, don't know what omitted agency_id should be  
try:  
schedule.AddRouteObject(route)  
self.fail("Expected validation error")  
except transitfeed.InvalidValue, e:  
self.assertEqual('agency_id', e.column_name)  
self.assertEqual(None, e.value)  
 
# agency with no agency_id defined, matches route with no agency id  
schedule.AddAgency("Test Agency 3", "http://example.com",  
"America/Los_Angeles")  
schedule.AddRouteObject(route)  
 
 
class AddHeadwayPeriodValidationTestCase(ValidationTestCase):  
def ExpectInvalidValue(self, start_time, end_time, headway,  
column_name, value):  
try:  
trip = transitfeed.Trip()  
trip.AddHeadwayPeriod(start_time, end_time, headway)  
self.fail("Expected InvalidValue error on %s" % column_name)  
except transitfeed.InvalidValue, e:  
self.assertEqual(column_name, e.column_name)  
self.assertEqual(value, e.value)  
self.assertEqual(0, len(trip.GetHeadwayPeriodTuples()))  
 
def ExpectMissingValue(self, start_time, end_time, headway, column_name):  
try:  
trip = transitfeed.Trip()  
trip.AddHeadwayPeriod(start_time, end_time, headway)  
self.fail("Expected MissingValue error on %s" % column_name)  
except transitfeed.MissingValue, e:  
self.assertEqual(column_name, e.column_name)  
self.assertEqual(0, len(trip.GetHeadwayPeriodTuples()))  
 
def runTest(self):  
# these should work fine  
trip = transitfeed.Trip()  
trip.trip_id = "SAMPLE_ID"  
trip.AddHeadwayPeriod(0, 50, 1200)  
trip.AddHeadwayPeriod("01:00:00", "02:00:00", "600")  
trip.AddHeadwayPeriod(u"02:00:00", u"03:00:00", u"1800")  
headways = trip.GetHeadwayPeriodTuples()  
self.assertEqual(3, len(headways))  
self.assertEqual((0, 50, 1200), headways[0])  
self.assertEqual((3600, 7200, 600), headways[1])  
self.assertEqual((7200, 10800, 1800), headways[2])  
self.assertEqual([("SAMPLE_ID", "00:00:00", "00:00:50", "1200"),  
("SAMPLE_ID", "01:00:00", "02:00:00", "600"),  
("SAMPLE_ID", "02:00:00", "03:00:00", "1800")],  
trip.GetHeadwayPeriodOutputTuples())  
 
# now test invalid input  
self.ExpectMissingValue(None, 50, 1200, "start_time")  
self.ExpectMissingValue("", 50, 1200, "start_time")  
self.ExpectInvalidValue("midnight", 50, 1200, "start_time", "midnight")  
self.ExpectInvalidValue(-50, 50, 1200, "start_time", -50)  
self.ExpectMissingValue(0, None, 1200, "end_time")  
self.ExpectMissingValue(0, "", 1200, "end_time")  
self.ExpectInvalidValue(0, "noon", 1200, "end_time", "noon")  
self.ExpectInvalidValue(0, -50, 1200, "end_time", -50)  
self.ExpectMissingValue(0, 600, 0, "headway_secs")  
self.ExpectMissingValue(0, 600, None, "headway_secs")  
self.ExpectMissingValue(0, 600, "", "headway_secs")  
self.ExpectInvalidValue(0, 600, "test", "headway_secs", "test")  
self.ExpectInvalidValue(0, 600, -60, "headway_secs", -60)  
self.ExpectInvalidValue(0, 0, 1200, "end_time", 0)  
self.ExpectInvalidValue("12:00:00", "06:00:00", 1200, "end_time", 21600)  
 
 
class MinimalUtf8Builder(TempFileTestCaseBase):  
def runTest(self):  
problems = TestFailureProblemReporter(self)  
schedule = transitfeed.Schedule(problem_reporter=problems)  
schedule.AddAgency("\xc8\x8b Fly Agency", "http://iflyagency.com",  
"America/Los_Angeles")  
service_period = schedule.GetDefaultServicePeriod()  
service_period.SetDateHasService('20070101')  
# "u020b i with inverted accent breve" encoded in utf-8  
stop1 = schedule.AddStop(lng=140, lat=48.2, name="\xc8\x8b hub")  
# "u020b i with inverted accent breve" as unicode string  
stop2 = schedule.AddStop(lng=140.001, lat=48.201, name=u"remote \u020b station")  
route = schedule.AddRoute(u"\u03b2", "Beta", "Bus")  
trip = route.AddTrip(schedule, u"to remote \u020b station")  
repr(stop1)  
repr(stop2)  
repr(route)  
repr(trip)  
trip.AddStopTime(stop1, schedule=schedule, stop_time='10:00:00')  
trip.AddStopTime(stop2, stop_time='10:10:00')  
 
schedule.Validate(problems)  
schedule.WriteGoogleTransitFeed(self.tempfilepath)  
read_schedule = \  
transitfeed.Loader(self.tempfilepath, problems=problems,  
extra_validation=True).Load()  
 
 
class ScheduleBuilderTestCase(unittest.TestCase):  
def runTest(self):  
schedule = transitfeed.Schedule()  
 
schedule.AddAgency("Test Agency", "http://example.com",  
"America/Los_Angeles")  
 
service_period = schedule.GetDefaultServicePeriod()  
self.assertTrue(service_period.service_id)  
service_period.SetWeekdayService(has_service=True)  
service_period.SetStartDate("20070320")  
service_period.SetEndDate("20071231")  
 
stop1 = schedule.AddStop(lng=-140.12, lat=48.921,  
name="one forty at forty eight")  
stop2 = schedule.AddStop(lng=-140.22, lat=48.421, name="west and south")  
stop3 = schedule.AddStop(lng=-140.32, lat=48.121, name="more away")  
stop4 = schedule.AddStop(lng=-140.42, lat=48.021, name="more more away")  
 
route = schedule.AddRoute(short_name="R", long_name="My Route",  
route_type="Bus")  
self.assertTrue(route.route_id)  
self.assertEqual(route.route_short_name, "R")  
self.assertEqual(route.route_type, 3)  
 
trip = route.AddTrip(schedule, headsign="To The End",  
service_period=service_period)  
trip_id = trip.trip_id  
self.assertTrue(trip_id)  
trip = schedule.GetTrip(trip_id)  
self.assertEqual("To The End", trip.trip_headsign)  
self.assertEqual(service_period, trip.service_period)  
 
trip.AddStopTime(stop=stop1, arrival_secs=3600*8, departure_secs=3600*8)  
trip.AddStopTime(stop=stop2)  
trip.AddStopTime(stop=stop3, arrival_secs=3600*8 + 60*60,  
departure_secs=3600*8 + 60*60)  
trip.AddStopTime(stop=stop4, arrival_time="9:13:00",  
departure_secs=3600*8 + 60*103, stop_headsign="Last stop",  
pickup_type=1, drop_off_type=3)  
 
schedule.Validate()  
self.assertEqual(4, len(trip.GetTimeStops()))  
self.assertEqual(1, len(schedule.GetRouteList()))  
self.assertEqual(4, len(schedule.GetStopList()))  
 
 
class WriteSampleFeedTestCase(TempFileTestCaseBase):  
def assertEqualTimeString(self, a, b):  
"""Assert that a and b are equal, even if they don't have the same zero  
padding on the hour. IE 08:45:00 vs 8:45:00."""  
if a[1] == ':':  
a = '0' + a  
if b[1] == ':':  
b = '0' + b  
self.assertEqual(a, b)  
 
def assertEqualWithDefault(self, a, b, default):  
"""Assert that a and b are equal. Treat None and default as equal."""  
if a == b:  
return  
if a in (None, default) and b in (None, default):  
return  
self.assertTrue(False, "a=%s b=%s" % (a, b))  
 
def runTest(self):  
problems = RecordingProblemReporter(self, ignore_types=("ExpirationDate",))  
schedule = transitfeed.Schedule(problem_reporter=problems)  
agency = transitfeed.Agency()  
agency.agency_id = "DTA"  
agency.agency_name = "Demo Transit Authority"  
agency.agency_url = "http://google.com"  
agency.agency_timezone = "America/Los_Angeles"  
agency.agency_lang = 'en'  
# Test that unknown columns, such as agency_mission, are preserved  
agency.agency_mission = "Get You There"  
schedule.AddAgencyObject(agency)  
 
routes = []  
route_data = [  
("AB", "DTA", "10", "Airport - Bullfrog", 3),  
("BFC", "DTA", "20", "Bullfrog - Furnace Creek Resort", 3),  
("STBA", "DTA", "30", "Stagecoach - Airport Shuttle", 3),  
("CITY", "DTA", "40", "City", 3),  
("AAMV", "DTA", "50", "Airport - Amargosa Valley", 3)  
]  
 
for route_entry in route_data:  
route = transitfeed.Route()  
(route.route_id, route.agency_id, route.route_short_name,  
route.route_long_name, route.route_type) = route_entry  
routes.append(route)  
schedule.AddRouteObject(route)  
 
shape_data = [  
(36.915760, -116.751709),  
(36.905018, -116.763206),  
(36.902134, -116.777969),  
(36.904091, -116.788185),  
(36.883602, -116.814537),  
(36.874523, -116.795593),  
(36.873302, -116.786491),  
(36.869202, -116.784241),  
(36.868515, -116.784729),  
]  
 
shape = transitfeed.Shape("BFC1S")  
for (lat, lon) in shape_data:  
shape.AddPoint(lat, lon)  
schedule.AddShapeObject(shape)  
 
week_period = transitfeed.ServicePeriod()  
week_period.service_id = "FULLW"  
week_period.start_date = "20070101"  
week_period.end_date = "20071231"  
week_period.SetWeekdayService()  
week_period.SetWeekendService()  
week_period.SetDateHasService("20070604", False)  
schedule.AddServicePeriodObject(week_period)  
 
weekend_period = transitfeed.ServicePeriod()  
weekend_period.service_id = "WE"  
weekend_period.start_date = "20070101"  
weekend_period.end_date = "20071231"  
weekend_period.SetWeekendService()  
schedule.AddServicePeriodObject(weekend_period)  
 
stops = []  
stop_data = [  
("FUR_CREEK_RES", "Furnace Creek Resort (Demo)",  
36.425288, -117.133162, "zone-a", "1234"),  
("BEATTY_AIRPORT", "Nye County Airport (Demo)",  
36.868446, -116.784682, "zone-a", "1235"),  
("BULLFROG", "Bullfrog (Demo)", 36.88108, -116.81797, "zone-b", "1236"),  
("STAGECOACH", "Stagecoach Hotel & Casino (Demo)",  
36.915682, -116.751677, "zone-c", "1237"),  
("NADAV", "North Ave / D Ave N (Demo)", 36.914893, -116.76821, "", ""),  
("NANAA", "North Ave / N A Ave (Demo)", 36.914944, -116.761472, "", ""),  
("DADAN", "Doing AVe / D Ave N (Demo)", 36.909489, -116.768242, "", ""),  
("EMSI", "E Main St / S Irving St (Demo)",  
36.905697, -116.76218, "", ""),  
("AMV", "Amargosa Valley (Demo)", 36.641496, -116.40094, "", ""),  
]  
for stop_entry in stop_data:  
stop = transitfeed.Stop()  
(stop.stop_id, stop.stop_name, stop.stop_lat, stop.stop_lon,  
stop.zone_id, stop.stop_code) = stop_entry  
schedule.AddStopObject(stop)  
stops.append(stop)  
# Add a value to an unknown column and make sure it is preserved  
schedule.GetStop("BULLFROG").stop_sound = "croak!"  
 
trip_data = [  
("AB", "FULLW", "AB1", "to Bullfrog", "0", "1", None),  
("AB", "FULLW", "AB2", "to Airport", "1", "2", None),  
("STBA", "FULLW", "STBA", "Shuttle", None, None, None),  
("CITY", "FULLW", "CITY1", None, "0", None, None),  
("CITY", "FULLW", "CITY2", None, "1", None, None),  
("BFC", "FULLW", "BFC1", "to Furnace Creek Resort", "0", "1", "BFC1S"),  
("BFC", "FULLW", "BFC2", "to Bullfrog", "1", "2", None),  
("AAMV", "WE", "AAMV1", "to Amargosa Valley", "0", None, None),  
("AAMV", "WE", "AAMV2", "to Airport", "1", None, None),  
("AAMV", "WE", "AAMV3", "to Amargosa Valley", "0", None, None),  
("AAMV", "WE", "AAMV4", "to Airport", "1", None, None),  
]  
 
trips = []  
for trip_entry in trip_data:  
trip = transitfeed.Trip()  
(trip.route_id, trip.service_id, trip.trip_id, trip.trip_headsign,  
trip.direction_id, trip.block_id, trip.shape_id) = trip_entry  
trips.append(trip)  
schedule.AddTripObject(trip)  
 
stop_time_data = {  
"STBA": [("6:00:00", "6:00:00", "STAGECOACH", None, None, None, None),  
("6:20:00", "6:20:00", "BEATTY_AIRPORT", None, None, None, None)],  
"CITY1": [("6:00:00", "6:00:00", "STAGECOACH", 1.34, 0, 0, "stop 1"),  
("6:05:00", "6:07:00", "NANAA", 2.40, 1, 2, "stop 2"),  
("6:12:00", "6:14:00", "NADAV", 3.0, 2, 2, "stop 3"),  
("6:19:00", "6:21:00", "DADAN", 4, 2, 2, "stop 4"),  
("6:26:00", "6:28:00", "EMSI", 5.78, 2, 3, "stop 5")],  
"CITY2": [("6:28:00", "6:28:00", "EMSI", None, None, None, None),  
("6:35:00", "6:37:00", "DADAN", None, None, None, None),  
("6:42:00", "6:44:00", "NADAV", None, None, None, None),  
("6:49:00", "6:51:00", "NANAA", None, None, None, None),  
("6:56:00", "6:58:00", "STAGECOACH", None, None, None, None)],  
"AB1": [("8:00:00", "8:00:00", "BEATTY_AIRPORT", None, None, None, None),  
("8:10:00", "8:15:00", "BULLFROG", None, None, None, None)],  
"AB2": [("12:05:00", "12:05:00", "BULLFROG", None, None, None, None),  
("12:15:00", "12:15:00", "BEATTY_AIRPORT", None, None, None, None)],  
"BFC1": [("8:20:00", "8:20:00", "BULLFROG", None, None, None, None),  
("9:20:00", "9:20:00", "FUR_CREEK_RES", None, None, None, None)],  
"BFC2": [("11:00:00", "11:00:00", "FUR_CREEK_RES", None, None, None, None),  
("12:00:00", "12:00:00", "BULLFROG", None, None, None, None)],  
"AAMV1": [("8:00:00", "8:00:00", "BEATTY_AIRPORT", None, None, None, None),  
("9:00:00", "9:00:00", "AMV", None, None, None, None)],  
"AAMV2": [("10:00:00", "10:00:00", "AMV", None, None, None, None),  
("11:00:00", "11:00:00", "BEATTY_AIRPORT", None, None, None, None)],  
"AAMV3": [("13:00:00", "13:00:00", "BEATTY_AIRPORT", None, None, None, None),  
("14:00:00", "14:00:00", "AMV", None, None, None, None)],  
"AAMV4": [("15:00:00", "15:00:00", "AMV", None, None, None, None),  
("16:00:00", "16:00:00", "BEATTY_AIRPORT", None, None, None, None)],  
}  
 
for trip_id, stop_time_list in stop_time_data.items():  
for stop_time_entry in stop_time_list:  
(arrival_time, departure_time, stop_id, shape_dist_traveled,  
pickup_type, drop_off_type, stop_headsign) = stop_time_entry  
trip = schedule.GetTrip(trip_id)  
stop = schedule.GetStop(stop_id)  
trip.AddStopTime(stop, arrival_time=arrival_time,  
departure_time=departure_time,  
shape_dist_traveled=shape_dist_traveled,  
pickup_type=pickup_type, drop_off_type=drop_off_type,  
stop_headsign=stop_headsign)  
 
self.assertEqual(0, schedule.GetTrip("CITY1").GetStopTimes()[0].pickup_type)  
self.assertEqual(1, schedule.GetTrip("CITY1").GetStopTimes()[1].pickup_type)  
 
headway_data = [  
("STBA", "6:00:00", "22:00:00", 1800),  
("CITY1", "6:00:00", "7:59:59", 1800),  
("CITY2", "6:00:00", "7:59:59", 1800),  
("CITY1", "8:00:00", "9:59:59", 600),  
("CITY2", "8:00:00", "9:59:59", 600),  
("CITY1", "10:00:00", "15:59:59", 1800),  
("CITY2", "10:00:00", "15:59:59", 1800),  
("CITY1", "16:00:00", "18:59:59", 600),  
("CITY2", "16:00:00", "18:59:59", 600),  
("CITY1", "19:00:00", "22:00:00", 1800),  
("CITY2", "19:00:00", "22:00:00", 1800),  
]  
 
headway_trips = {}  
for headway_entry in headway_data:  
(trip_id, start_time, end_time, headway) = headway_entry  
headway_trips[trip_id] = [] # adding to set to check later  
trip = schedule.GetTrip(trip_id)  
trip.AddHeadwayPeriod(start_time, end_time, headway, problems)  
for trip_id in headway_trips:  
headway_trips[trip_id] = \  
schedule.GetTrip(trip_id).GetHeadwayPeriodTuples()  
 
fare_data = [  
("p", 1.25, "USD", 0, 0),  
("a", 5.25, "USD", 0, 0),  
]  
 
fares = []  
for fare_entry in fare_data:  
fare = transitfeed.Fare(fare_entry[0], fare_entry[1], fare_entry[2],  
fare_entry[3], fare_entry[4])  
fares.append(fare)  
schedule.AddFareObject(fare)  
 
fare_rule_data = [  
("p", "AB", "zone-a", "zone-b", None),  
("p", "STBA", "zone-a", None, "zone-c"),  
("p", "BFC", None, "zone-b", "zone-a"),  
("a", "AAMV", None, None, None),  
]  
 
for fare_id, route_id, orig_id, dest_id, contains_id in fare_rule_data:  
rule = transitfeed.FareRule(  
fare_id=fare_id, route_id=route_id, origin_id=orig_id,  
destination_id=dest_id, contains_id=contains_id)  
schedule.AddFareRuleObject(rule, problems)  
 
schedule.Validate(problems)  
problems.AssertNoMoreExceptions()  
schedule.WriteGoogleTransitFeed(self.tempfilepath)  
 
read_schedule = \  
transitfeed.Loader(self.tempfilepath, problems=problems,  
extra_validation=True).Load()  
e = problems.PopException("UnrecognizedColumn")  
self.assertEqual(e.file_name, "agency.txt")  
self.assertEqual(e.column_name, "agency_mission")  
e = problems.PopException("UnrecognizedColumn")  
self.assertEqual(e.file_name, "stops.txt")  
self.assertEqual(e.column_name, "stop_sound")  
problems.AssertNoMoreExceptions()  
 
self.assertEqual(1, len(read_schedule.GetAgencyList()))  
self.assertEqual(agency, read_schedule.GetAgency(agency.agency_id))  
 
self.assertEqual(len(routes), len(read_schedule.GetRouteList()))  
for route in routes:  
self.assertEqual(route, read_schedule.GetRoute(route.route_id))  
 
self.assertEqual(2, len(read_schedule.GetServicePeriodList()))  
self.assertEqual(week_period,  
read_schedule.GetServicePeriod(week_period.service_id))  
self.assertEqual(weekend_period,  
read_schedule.GetServicePeriod(weekend_period.service_id))  
 
self.assertEqual(len(stops), len(read_schedule.GetStopList()))  
for stop in stops:  
self.assertEqual(stop, read_schedule.GetStop(stop.stop_id))  
self.assertEqual("croak!", read_schedule.GetStop("BULLFROG").stop_sound)  
 
self.assertEqual(len(trips), len(read_schedule.GetTripList()))  
for trip in trips:  
self.assertEqual(trip, read_schedule.GetTrip(trip.trip_id))  
 
for trip_id in headway_trips:  
self.assertEqual(headway_trips[trip_id],  
read_schedule.GetTrip(trip_id).GetHeadwayPeriodTuples())  
 
for trip_id, stop_time_list in stop_time_data.items():  
trip = read_schedule.GetTrip(trip_id)  
read_stoptimes = trip.GetStopTimes()  
self.assertEqual(len(read_stoptimes), len(stop_time_list))  
for stop_time_entry, read_stoptime in zip(stop_time_list, read_stoptimes):  
(arrival_time, departure_time, stop_id, shape_dist_traveled,  
pickup_type, drop_off_type, stop_headsign) = stop_time_entry  
self.assertEqual(stop_id, read_stoptime.stop_id)  
self.assertEqual(read_schedule.GetStop(stop_id), read_stoptime.stop)  
self.assertEqualTimeString(arrival_time, read_stoptime.arrival_time)  
self.assertEqualTimeString(departure_time, read_stoptime.departure_time)  
self.assertEqual(shape_dist_traveled, read_stoptime.shape_dist_traveled)  
self.assertEqualWithDefault(pickup_type, read_stoptime.pickup_type, 0)  
self.assertEqualWithDefault(drop_off_type, read_stoptime.drop_off_type, 0)  
self.assertEqualWithDefault(stop_headsign, read_stoptime.stop_headsign, '')  
 
self.assertEqual(len(fares), len(read_schedule.GetFareList()))  
for fare in fares:  
self.assertEqual(fare, read_schedule.GetFare(fare.fare_id))  
 
read_fare_rules_data = []  
for fare in read_schedule.GetFareList():  
for rule in fare.GetFareRuleList():  
self.assertEqual(fare.fare_id, rule.fare_id)  
read_fare_rules_data.append((fare.fare_id, rule.route_id,  
rule.origin_id, rule.destination_id,  
rule.contains_id))  
fare_rule_data.sort()  
read_fare_rules_data.sort()  
self.assertEqual(len(read_fare_rules_data), len(fare_rule_data))  
for rf, f in zip(read_fare_rules_data, fare_rule_data):  
self.assertEqual(rf, f)  
 
 
self.assertEqual(1, len(read_schedule.GetShapeList()))  
self.assertEqual(shape, read_schedule.GetShape(shape.shape_id))  
 
# TODO: test GetPattern  
 
class DefaultAgencyTestCase(unittest.TestCase):  
def freeAgency(self, ex=''):  
agency = transitfeed.Agency()  
agency.agency_id = 'agencytestid' + ex  
agency.agency_name = 'Foo Bus Line' + ex  
agency.agency_url = 'http://gofoo.com/' + ex  
agency.agency_timezone='America/Los_Angeles'  
return agency  
 
def test_SetDefault(self):  
schedule = transitfeed.Schedule()  
agency = self.freeAgency()  
schedule.SetDefaultAgency(agency)  
self.assertEqual(agency, schedule.GetDefaultAgency())  
 
def test_NewDefaultAgency(self):  
schedule = transitfeed.Schedule()  
agency1 = schedule.NewDefaultAgency()  
self.assertTrue(agency1.agency_id)  
self.assertEqual(agency1.agency_id, schedule.GetDefaultAgency().agency_id)  
self.assertEqual(1, len(schedule.GetAgencyList()))  
agency2 = schedule.NewDefaultAgency()  
self.assertTrue(agency2.agency_id)  
self.assertEqual(agency2.agency_id, schedule.GetDefaultAgency().agency_id)  
self.assertEqual(2, len(schedule.GetAgencyList()))  
self.assertNotEqual(agency1, agency2)  
self.assertNotEqual(agency1.agency_id, agency2.agency_id)  
 
agency3 = schedule.NewDefaultAgency(agency_id='agency3',  
agency_name='Agency 3',  
agency_url='http://goagency')  
self.assertEqual(agency3.agency_id, 'agency3')  
self.assertEqual(agency3.agency_name, 'Agency 3')  
self.assertEqual(agency3.agency_url, 'http://goagency')  
self.assertEqual(agency3, schedule.GetDefaultAgency())  
self.assertEqual('agency3', schedule.GetDefaultAgency().agency_id)  
self.assertEqual(3, len(schedule.GetAgencyList()))  
 
def test_NoAgencyMakeNewDefault(self):  
schedule = transitfeed.Schedule()  
agency = schedule.GetDefaultAgency()  
self.assertTrue(isinstance(agency, transitfeed.Agency))  
self.assertTrue(agency.agency_id)  
self.assertEqual(1, len(schedule.GetAgencyList()))  
self.assertEqual(agency, schedule.GetAgencyList()[0])  
self.assertEqual(agency.agency_id, schedule.GetAgencyList()[0].agency_id)  
 
def test_AssumeSingleAgencyIsDefault(self):  
schedule = transitfeed.Schedule()  
agency1 = self.freeAgency()  
schedule.AddAgencyObject(agency1)  
agency2 = self.freeAgency('2') # don't add to schedule  
# agency1 is default because it is the only Agency in schedule  
self.assertEqual(agency1, schedule.GetDefaultAgency())  
 
def test_MultipleAgencyCausesNoDefault(self):  
schedule = transitfeed.Schedule()  
agency1 = self.freeAgency()  
schedule.AddAgencyObject(agency1)  
agency2 = self.freeAgency('2')  
schedule.AddAgencyObject(agency2)  
self.assertEqual(None, schedule.GetDefaultAgency())  
 
def test_OverwriteExistingAgency(self):  
schedule = transitfeed.Schedule()  
agency1 = self.freeAgency()  
agency1.agency_id = '1'  
schedule.AddAgencyObject(agency1)  
agency2 = schedule.NewDefaultAgency()  
# Make sure agency1 was not overwritten by the new default  
self.assertEqual(agency1, schedule.GetAgency(agency1.agency_id))  
self.assertNotEqual('1', agency2.agency_id)  
 
 
class FindUniqueIdTestCase(unittest.TestCase):  
def test_simple(self):  
d = {}  
for i in range(0, 5):  
d[transitfeed.FindUniqueId(d)] = 1  
k = d.keys()  
k.sort()  
self.assertEqual(('0', '1', '2', '3', '4'), tuple(k))  
 
def test_AvoidCollision(self):  
d = {'1': 1}  
d[transitfeed.FindUniqueId(d)] = 1  
self.assertEqual(2, len(d))  
self.assertFalse('2' in d, "Ops, next statement should add something to d")  
d['2'] = None  
d[transitfeed.FindUniqueId(d)] = 1  
self.assertEqual(4, len(d))  
 
 
class DefaultServicePeriodTestCase(unittest.TestCase):  
def test_SetDefault(self):  
schedule = transitfeed.Schedule()  
service1 = transitfeed.ServicePeriod()  
service1.SetDateHasService('20070101', True)  
service1.service_id = 'SERVICE1'  
schedule.SetDefaultServicePeriod(service1)  
self.assertEqual(service1, schedule.GetDefaultServicePeriod())  
self.assertEqual(service1, schedule.GetServicePeriod(service1.service_id))  
 
def test_NewDefault(self):  
schedule = transitfeed.Schedule()  
service1 = schedule.NewDefaultServicePeriod()  
self.assertTrue(service1.service_id)  
schedule.GetServicePeriod(service1.service_id)  
service1.SetDateHasService('20070101', True) # Make service1 different  
service2 = schedule.NewDefaultServicePeriod()  
schedule.GetServicePeriod(service2.service_id)  
self.assertTrue(service1.service_id)  
self.assertTrue(service2.service_id)  
self.assertNotEqual(service1, service2)  
self.assertNotEqual(service1.service_id, service2.service_id)  
 
def test_NoServicesMakesNewDefault(self):  
schedule = transitfeed.Schedule()  
service1 = schedule.GetDefaultServicePeriod()  
self.assertEqual(service1, schedule.GetServicePeriod(service1.service_id))  
 
def test_AssumeSingleServiceIsDefault(self):  
schedule = transitfeed.Schedule()  
service1 = transitfeed.ServicePeriod()  
service1.SetDateHasService('20070101', True)  
service1.service_id = 'SERVICE1'  
schedule.AddServicePeriodObject(service1)  
self.assertEqual(service1, schedule.GetDefaultServicePeriod())  
self.assertEqual(service1.service_id, schedule.GetDefaultServicePeriod().service_id)  
 
def test_MultipleServicesCausesNoDefault(self):  
schedule = transitfeed.Schedule()  
service1 = transitfeed.ServicePeriod()  
service1.service_id = 'SERVICE1'  
service1.SetDateHasService('20070101', True)  
schedule.AddServicePeriodObject(service1)  
service2 = transitfeed.ServicePeriod()  
service2.service_id = 'SERVICE2'  
service2.SetDateHasService('20070201', True)  
schedule.AddServicePeriodObject(service2)  
service_d = schedule.GetDefaultServicePeriod()  
self.assertEqual(service_d, None)  
 
 
class GetTripTimeTestCase(unittest.TestCase):  
"""Test for GetStopTimeTrips and GetTimeInterpolatedStops"""  
def setUp(self):  
problems = TestFailureProblemReporter(self)  
schedule = transitfeed.Schedule(problem_reporter=problems)  
self.schedule = schedule  
schedule.AddAgency("Agency", "http://iflyagency.com",  
"America/Los_Angeles")  
service_period = schedule.GetDefaultServicePeriod()  
service_period.SetDateHasService('20070101')  
self.stop1 = schedule.AddStop(lng=140.01, lat=0, name="140.01,0")  
self.stop2 = schedule.AddStop(lng=140.02, lat=0, name="140.02,0")  
self.stop3 = schedule.AddStop(lng=140.03, lat=0, name="140.03,0")  
self.stop4 = schedule.AddStop(lng=140.04, lat=0, name="140.04,0")  
self.stop5 = schedule.AddStop(lng=140.05, lat=0, name="140.05,0")  
self.route1 = schedule.AddRoute("1", "One", "Bus")  
 
self.trip1 = self.route1.AddTrip(schedule, "trip 1", trip_id='trip1')  
self.trip1.AddStopTime(self.stop1, schedule=schedule, departure_secs=100, arrival_secs=100)  
self.trip1.AddStopTime(self.stop2, schedule=schedule)  
self.trip1.AddStopTime(self.stop3, schedule=schedule)  
# loop back to stop2 to test that interpolated stops work ok even when  
# a stop between timepoints is further from the timepoint than the  
# preceding  
self.trip1.AddStopTime(self.stop2, schedule=schedule)  
self.trip1.AddStopTime(self.stop4, schedule=schedule, departure_secs=400, arrival_secs=400)  
 
self.trip2 = self.route1.AddTrip(schedule, "trip 2", trip_id='trip2')  
self.trip2.AddStopTime(self.stop2, schedule=schedule, departure_secs=500, arrival_secs=500)  
self.trip2.AddStopTime(self.stop3, schedule=schedule, departure_secs=600, arrival_secs=600)  
self.trip2.AddStopTime(self.stop4, schedule=schedule, departure_secs=700, arrival_secs=700)  
self.trip2.AddStopTime(self.stop3, schedule=schedule, departure_secs=800, arrival_secs=800)  
 
self.trip3 = self.route1.AddTrip(schedule, "trip 3", trip_id='trip3')  
 
def testGetTimeInterpolatedStops(self):  
rv = self.trip1.GetTimeInterpolatedStops()  
self.assertEqual(5, len(rv))  
(secs, stoptimes, istimepoints) = tuple(zip(*rv))  
 
self.assertEqual((100, 160, 220, 280, 400), secs)  
self.assertEqual(("140.01,0", "140.02,0", "140.03,0", "140.02,0", "140.04,0"),  
tuple([st.stop.stop_name for st in stoptimes]))  
self.assertEqual((True, False, False, False, True), istimepoints)  
 
self.assertEqual([], self.trip3.GetTimeInterpolatedStops())  
 
def testGetTimeInterpolatedStopsUntimedEnd(self):  
self.trip2.AddStopTime(self.stop3, schedule=self.schedule)  
self.assertRaises(ValueError, self.trip2.GetTimeInterpolatedStops)  
 
def testGetTimeInterpolatedStopsUntimedStart(self):  
# Temporarily replace the problem reporter so that adding the first  
# StopTime without a time doesn't throw an exception.  
old_problems = self.schedule.problem_reporter  
self.schedule.problem_reporter = TestFailureProblemReporter(  
self, ("OtherProblem",))  
self.trip3.AddStopTime(self.stop3, schedule=self.schedule)  
self.schedule.problem_reporter = old_problems  
self.trip3.AddStopTime(self.stop2, schedule=self.schedule,  
departure_secs=500, arrival_secs=500)  
self.assertRaises(ValueError, self.trip3.GetTimeInterpolatedStops)  
 
def testGetTimeInterpolatedStopsSingleStopTime(self):  
self.trip3.AddStopTime(self.stop3, schedule=self.schedule,  
departure_secs=500, arrival_secs=500)  
rv = self.trip3.GetTimeInterpolatedStops()  
self.assertEqual(1, len(rv))  
self.assertEqual(500, rv[0][0])  
self.assertEqual(True, rv[0][2])  
 
def testGetStopTimeTrips(self):  
stopa = self.schedule.GetNearestStops(lon=140.03, lat=0)[0]  
self.assertEqual("140.03,0", stopa.stop_name) # Got stop3?  
rv = stopa.GetStopTimeTrips(self.schedule)  
self.assertEqual(3, len(rv))  
(secs, trip_index, istimepoints) = tuple(zip(*rv))  
self.assertEqual((220, 600, 800), secs)  
self.assertEqual(("trip1", "trip2", "trip2"), tuple([ti[0].trip_id for ti in trip_index]))  
self.assertEqual((2, 1, 3), tuple([ti[1] for ti in trip_index]))  
self.assertEqual((False, True, True), istimepoints)  
 
def testStopTripIndex(self):  
trip_index = self.stop3.trip_index  
trip_ids = [t.trip_id for t, i in trip_index]  
self.assertEqual(["trip1", "trip2", "trip2"], trip_ids)  
self.assertEqual([2, 1, 3], [i for t, i in trip_index])  
 
def testGetTrips(self):  
self.assertEqual(set([t.trip_id for t in self.stop1.GetTrips(self.schedule)]),  
set([self.trip1.trip_id]))  
self.assertEqual(set([t.trip_id for t in self.stop2.GetTrips(self.schedule)]),  
set([self.trip1.trip_id, self.trip2.trip_id]))  
self.assertEqual(set([t.trip_id for t in self.stop3.GetTrips(self.schedule)]),  
set([self.trip1.trip_id, self.trip2.trip_id]))  
self.assertEqual(set([t.trip_id for t in self.stop4.GetTrips(self.schedule)]),  
set([self.trip1.trip_id, self.trip2.trip_id]))  
self.assertEqual(set([t.trip_id for t in self.stop5.GetTrips(self.schedule)]),  
set())  
 
 
class ApproximateDistanceBetweenStopsTestCase(unittest.TestCase):  
def testEquator(self):  
stop1 = transitfeed.Stop(lat=0, lng=100,  
name='Stop one', stop_id='1')  
stop2 = transitfeed.Stop(lat=0.01, lng=100.01,  
name='Stop two', stop_id='2')  
self.assertAlmostEqual(  
transitfeed.ApproximateDistanceBetweenStops(stop1, stop2),  
1570, -1) # Compare first 3 digits  
 
def testWhati(self):  
stop1 = transitfeed.Stop(lat=63.1, lng=-117.2,  
name='Stop whati one', stop_id='1')  
stop2 = transitfeed.Stop(lat=63.102, lng=-117.201,  
name='Stop whati two', stop_id='2')  
self.assertAlmostEqual(  
transitfeed.ApproximateDistanceBetweenStops(stop1, stop2),  
228, 0)  
 
 
class TimeConversionHelpersTestCase(unittest.TestCase):  
def testTimeToSecondsSinceMidnight(self):  
self.assertEqual(transitfeed.TimeToSecondsSinceMidnight("01:02:03"), 3723)  
self.assertEqual(transitfeed.TimeToSecondsSinceMidnight("00:00:00"), 0)  
self.assertEqual(transitfeed.TimeToSecondsSinceMidnight("25:24:23"), 91463)  
try:  
transitfeed.TimeToSecondsSinceMidnight("10:15:00am")  
except transitfeed.Error:  
pass # expected  
else:  
self.fail("Should have thrown Error")  
 
def testFormatSecondsSinceMidnight(self):  
self.assertEqual(transitfeed.FormatSecondsSinceMidnight(3723), "01:02:03")  
self.assertEqual(transitfeed.FormatSecondsSinceMidnight(0), "00:00:00")  
self.assertEqual(transitfeed.FormatSecondsSinceMidnight(91463), "25:24:23")  
 
def testDateStringToDateObject(self):  
self.assertEqual(transitfeed.DateStringToDateObject("20080901"),  
datetime.date(2008, 9, 1))  
try:  
transitfeed.DateStringToDateObject("20080841")  
except ValueError:  
pass # expected  
else:  
self.fail("Should have thrown ValueError")  
 
 
class NonNegIntStringToIntTestCase(unittest.TestCase):  
def runTest(self):  
self.assertEqual(0, transitfeed.NonNegIntStringToInt("0"))  
self.assertEqual(0, transitfeed.NonNegIntStringToInt(u"0"))  
self.assertEqual(1, transitfeed.NonNegIntStringToInt("1"))  
self.assertEqual(2, transitfeed.NonNegIntStringToInt("2"))  
self.assertEqual(10, transitfeed.NonNegIntStringToInt("10"))  
self.assertEqual(1234567890123456789,  
transitfeed.NonNegIntStringToInt("1234567890123456789"))  
self.assertRaises(ValueError, transitfeed.NonNegIntStringToInt, "")  
self.assertRaises(ValueError, transitfeed.NonNegIntStringToInt, "-1")  
self.assertRaises(ValueError, transitfeed.NonNegIntStringToInt, "+1")  
self.assertRaises(ValueError, transitfeed.NonNegIntStringToInt, "01")  
self.assertRaises(ValueError, transitfeed.NonNegIntStringToInt, "00")  
self.assertRaises(ValueError, transitfeed.NonNegIntStringToInt, "0x1")  
self.assertRaises(ValueError, transitfeed.NonNegIntStringToInt, "1.0")  
self.assertRaises(ValueError, transitfeed.NonNegIntStringToInt, "1e1")  
self.assertRaises(TypeError, transitfeed.NonNegIntStringToInt, 1)  
self.assertRaises(TypeError, transitfeed.NonNegIntStringToInt, None)  
 
 
class GetHeadwayTimesTestCase(unittest.TestCase):  
"""Test for GetHeadwayStartTimes and GetHeadwayStopTimes"""  
def setUp(self):  
problems = TestFailureProblemReporter(self)  
schedule = transitfeed.Schedule(problem_reporter=problems)  
self.schedule = schedule  
schedule.AddAgency("Agency", "http://iflyagency.com",  
"America/Los_Angeles")  
service_period = schedule.GetDefaultServicePeriod()  
service_period.SetStartDate("20080101")  
service_period.SetEndDate("20090101")  
service_period.SetWeekdayService(True)  
self.stop1 = schedule.AddStop(lng=140.01, lat=0, name="140.01,0")  
self.stop2 = schedule.AddStop(lng=140.02, lat=0, name="140.02,0")  
self.stop3 = schedule.AddStop(lng=140.03, lat=0, name="140.03,0")  
self.stop4 = schedule.AddStop(lng=140.04, lat=0, name="140.04,0")  
self.stop5 = schedule.AddStop(lng=140.05, lat=0, name="140.05,0")  
self.route1 = schedule.AddRoute("1", "One", "Bus")  
 
self.trip1 = self.route1.AddTrip(schedule, "trip 1", trip_id="trip1")  
# add different types of stop times  
self.trip1.AddStopTime(self.stop1, arrival_time="17:00:00", departure_time="17:01:00") # both arrival and departure time  
self.trip1.AddStopTime(self.stop2, schedule=schedule) # non timed  
self.trip1.AddStopTime(self.stop3, stop_time="17:45:00") # only stop_time  
 
# add headways starting before the trip  
self.trip1.AddHeadwayPeriod("16:00:00","18:00:00",1800) # each 30 min  
self.trip1.AddHeadwayPeriod("18:00:00","20:00:00",2700) # each 45 min  
 
def testGetHeadwayStartTimes(self):  
start_times = self.trip1.GetHeadwayStartTimes()  
self.assertEqual(  
["16:00:00", "16:30:00", "17:00:00", "17:30:00",  
"18:00:00", "18:45:00", "19:30:00"],  
[transitfeed.FormatSecondsSinceMidnight(secs) for secs in start_times])  
 
def testGetHeadwayStopTimes(self):  
stoptimes_list = self.trip1.GetHeadwayStopTimes()  
arrival_secs = []  
departure_secs = []  
for stoptimes in stoptimes_list:  
arrival_secs.append([st.arrival_secs for st in stoptimes])  
departure_secs.append([st.departure_secs for st in stoptimes])  
 
self.assertEqual(([57600,None,60300],[59400,None,62100],[61200,None,63900],  
[63000,None,65700],[64800,None,67500],[67500,None,70200],  
[70200,None,72900]),  
tuple(arrival_secs))  
self.assertEqual(([57660,None,60300],[59460,None,62100],[61260,None,63900],  
[63060,None,65700],[64860,None,67500],[67560,None,70200],  
[70260,None,72900]),  
tuple(departure_secs))  
 
# test if stoptimes are created with same parameters than the ones from the original trip  
stoptimes = self.trip1.GetStopTimes()  
for stoptimes_clone in stoptimes_list:  
self.assertEqual(len(stoptimes_clone), len(stoptimes))  
for st_clone, st in zip(stoptimes_clone, stoptimes):  
for name in st.__slots__:  
if name not in ('arrival_secs', 'departure_secs'):  
self.assertEqual(getattr(st, name), getattr(st_clone, name))  
 
 
class ServiceGapsTestCase(MemoryZipTestCase):  
 
def setUp(self):  
super(ServiceGapsTestCase, self).setUp()  
self.zip.writestr("calendar.txt",  
"service_id,monday,tuesday,wednesday,thursday,friday,"  
"saturday,sunday,start_date,end_date\n"  
"FULLW,1,1,1,1,1,1,1,20090601,20090610\n"  
"WE,0,0,0,0,0,1,1,20090718,20101231\n")  
self.zip.writestr("calendar_dates.txt",  
"service_id,date,exception_type\n"  
"WE,20090815,2\n"  
"WE,20090816,2\n"  
"WE,20090822,2\n"  
# The following two lines are a 12-day service gap.  
# Shouldn't issue a warning  
"WE,20090829,2\n"  
"WE,20090830,2\n"  
"WE,20100102,2\n"  
"WE,20100103,2\n"  
"WE,20100109,2\n"  
"WE,20100110,2\n"  
"WE,20100612,2\n"  
"WE,20100613,2\n"  
"WE,20100619,2\n"  
"WE,20100620,2\n")  
self.zip.writestr("trips.txt",  
"route_id,service_id,trip_id\n"  
"AB,WE,AB1\n"  
"AB,FULLW,AB2\n")  
self.zip.writestr(  
"stop_times.txt",  
"trip_id,arrival_time,departure_time,stop_id,stop_sequence\n"  
"AB1,10:00:00,10:00:00,BEATTY_AIRPORT,1\n"  
"AB1,10:20:00,10:20:00,BULLFROG,2\n"  
"AB2,10:25:00,10:25:00,STAGECOACH,1\n"  
"AB2,10:55:00,10:55:00,BULLFROG,2\n")  
loader = transitfeed.Loader(  
problems=self.problems,  
extra_validation=False,  
zip=self.zip)  
self.schedule = loader.Load()  
 
# If there is a service gap starting before today, and today has no service,  
# it should be found - even if tomorrow there is service  
def testServiceGapBeforeTodayIsDiscovered(self):  
self.schedule.Validate(today=date(2009, 7, 17),  
service_gap_interval=13)  
exception = self.problems.PopException("TooManyDaysWithoutService")  
self.assertEquals(date(2009, 7, 5),  
exception.first_day_without_service)  
self.assertEquals(date(2009, 7, 17),  
exception.last_day_without_service)  
 
self.AssertCommonExceptions(date(2010, 6, 25))  
 
# If today has service past service gaps should not appear  
def testNoServiceGapBeforeTodayIfTodayHasService(self):  
self.schedule.Validate(today=date(2009, 7, 18),  
service_gap_interval=13)  
 
self.AssertCommonExceptions(date(2010, 6, 25))  
 
# If the feed starts today NO previous service gap should be found  
# even if today does not have service  
def testNoServiceGapBeforeTodayIfTheFeedStartsToday(self):  
self.schedule.Validate(today=date(2009, 06, 01),  
service_gap_interval=13)  
 
# This service gap is the one between FULLW and WE  
exception = self.problems.PopException("TooManyDaysWithoutService")  
self.assertEquals(date(2009, 6, 11),  
exception.first_day_without_service)  
self.assertEquals(date(2009, 7, 17),  
exception.last_day_without_service)  
# The one-year period ends before the June 2010 gap, so that last  
# service gap should _not_ be found  
self.AssertCommonExceptions(None)  
 
# If there is a gap at the end of the one-year period we should find it  
def testGapAtTheEndOfTheOneYearPeriodIsDiscovered(self):  
self.schedule.Validate(today=date(2009, 06, 22),  
service_gap_interval=13)  
 
# This service gap is the one between FULLW and WE  
exception = self.problems.PopException("TooManyDaysWithoutService")  
self.assertEquals(date(2009, 6, 11),  
exception.first_day_without_service)  
self.assertEquals(date(2009, 7, 17),  
exception.last_day_without_service)  
 
self.AssertCommonExceptions(date(2010, 6, 21))  
 
# If we are right in the middle of a big service gap it should be  
# report as starting on "today - 12 days" and lasting until  
# service resumes  
def testCurrentServiceGapIsDiscovered(self):  
self.schedule.Validate(today=date(2009, 6, 30),  
service_gap_interval=13)  
exception = self.problems.PopException("TooManyDaysWithoutService")  
self.assertEquals(date(2009, 6, 18),  
exception.first_day_without_service)  
self.assertEquals(date(2009, 7, 17),  
exception.last_day_without_service)  
 
self.AssertCommonExceptions(date(2010, 6, 25))  
 
# Asserts the service gaps that appear towards the end of the calendar  
# and which are common to all the tests  
def AssertCommonExceptions(self, last_exception_date):  
exception = self.problems.PopException("TooManyDaysWithoutService")  
self.assertEquals(date(2009, 8, 10),  
exception.first_day_without_service)  
self.assertEquals(date(2009, 8, 22),  
exception.last_day_without_service)  
 
exception = self.problems.PopException("TooManyDaysWithoutService")  
self.assertEquals(date(2009, 12, 28),  
exception.first_day_without_service)  
self.assertEquals(date(2010, 1, 15),  
exception.last_day_without_service)  
 
if last_exception_date is not None:  
exception = self.problems.PopException("TooManyDaysWithoutService")  
self.assertEquals(date(2010, 6, 7),  
exception.first_day_without_service)  
self.assertEquals(last_exception_date,  
exception.last_day_without_service)  
 
self.problems.AssertNoMoreExceptions()  
 
 
if __name__ == '__main__':  
unittest.main()  
 
 Binary files a/origin-src/transitfeed-1.2.5/test/testtransitfeed.pyc and /dev/null differ
#!/usr/bin/python2.4  
#  
# Copyright (C) 2009 Google Inc.  
#  
# Licensed under the Apache License, Version 2.0 (the "License");  
# you may not use this file except in compliance with the License.  
# You may obtain a copy of the License at  
#  
# http://www.apache.org/licenses/LICENSE-2.0  
#  
# Unless required by applicable law or agreed to in writing, software  
# distributed under the License is distributed on an "AS IS" BASIS,  
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.  
# See the License for the specific language governing permissions and  
# limitations under the License.  
 
"""Tests for unusual_trip_filter.py"""  
 
__author__ = 'Jiri Semecky <jiri.semecky@gmail.com>'  
 
import unusual_trip_filter  
import transitfeed  
import unittest  
import util  
 
class UnusualTripFilterTestCase(util.TempDirTestCaseBase):  
"""Test of unusual trip filter functionality."""  
 
def testFilter(self):  
"""Test if filtering works properly."""  
expected_values = {  
'CITY1':0, 'CITY2':0, 'CITY3':0, 'CITY4' :0, 'CITY5' :0, 'CITY6' :0,  
'CITY7':0, 'CITY8':0, 'CITY9':0, 'CITY10':0, 'CITY11':1, 'CITY12':1,  
}  
filter = unusual_trip_filter.UnusualTripFilter(0.1, quiet=True)  
input = self.GetPath('test', 'data', 'filter_unusual_trips')  
loader = transitfeed.Loader(input, extra_validation=True)  
schedule = loader.Load()  
filter.filter(schedule)  
for trip_id, expected_trip_type in expected_values.items():  
actual_trip_type = schedule.trips[trip_id]['trip_type']  
try:  
self.assertEquals(int(actual_trip_type), expected_trip_type)  
except ValueError:  
self.assertEquals(actual_trip_type, '')  
 
def testFilterNoForceFilter(self):  
"""Test that force==False doesn't set default values"""  
filter = unusual_trip_filter.UnusualTripFilter(0.1, force=False, quiet=True)  
input = self.GetPath('test', 'data', 'filter_unusual_trips')  
loader = transitfeed.Loader(input, extra_validation=True)  
schedule = loader.Load()  
schedule.trips['CITY2'].trip_type = 'odd-trip'  
filter.filter(schedule)  
trip1 = schedule.trips['CITY1']  
self.assertEquals(trip1['trip_type'], '')  
trip2 = schedule.trips['CITY2']  
self.assertEquals(trip2['trip_type'], 'odd-trip')  
 
def testFilterForceFilter(self):  
"""Test that force==True does set default values"""  
filter = unusual_trip_filter.UnusualTripFilter(0.1, force=True, quiet=False)  
input = self.GetPath('test', 'data', 'filter_unusual_trips')  
loader = transitfeed.Loader(input, extra_validation=True)  
schedule = loader.Load()  
schedule.trips['CITY2'].trip_type = 'odd-trip'  
filter.filter(schedule)  
trip1 = schedule.trips['CITY1']  
self.assertEquals(trip1['trip_type'], '0')  
trip2 = schedule.trips['CITY2']  
self.assertEquals(trip2['trip_type'], '0')  
 
def testFilterAppliedForSpecifiedRouteType(self):  
"""Setting integer route_type filters trips of this route type."""  
filter = unusual_trip_filter.UnusualTripFilter(0.1, quiet=True,  
route_type=3)  
input = self.GetPath('test', 'data', 'filter_unusual_trips')  
loader = transitfeed.Loader(input, extra_validation=True)  
schedule = loader.Load()  
filter.filter(schedule)  
actual_trip_type = schedule.trips['CITY11']['trip_type']  
self.assertEquals(actual_trip_type, '1')  
 
def testFilterNotAppliedForUnspecifiedRouteType(self):  
"""Setting integer route_type filters trips of this route type."""  
filter = unusual_trip_filter.UnusualTripFilter(0.1, quiet=True,  
route_type=2)  
input = self.GetPath('test', 'data', 'filter_unusual_trips')  
loader = transitfeed.Loader(input, extra_validation=True)  
schedule = loader.Load()  
filter.filter(schedule)  
actual_trip_type = schedule.trips['CITY11']['trip_type']  
self.assertEquals(actual_trip_type, '')  
 
def testFilterAppliedForRouteTypeSpecifiedByName(self):  
"""Setting integer route_type filters trips of this route type."""  
filter = unusual_trip_filter.UnusualTripFilter(0.1, quiet=True,  
route_type='Bus')  
input = self.GetPath('test', 'data', 'filter_unusual_trips')  
loader = transitfeed.Loader(input, extra_validation=True)  
schedule = loader.Load()  
filter.filter(schedule)  
actual_trip_type = schedule.trips['CITY11']['trip_type']  
self.assertEquals(actual_trip_type, '1')  
 
def testFilterNotAppliedForDifferentRouteTypeSpecifiedByName(self):  
"""Setting integer route_type filters trips of this route type."""  
filter = unusual_trip_filter.UnusualTripFilter(0.1, quiet=True,  
route_type='Ferry')  
input = self.GetPath('test', 'data', 'filter_unusual_trips')  
loader = transitfeed.Loader(input, extra_validation=True)  
schedule = loader.Load()  
filter.filter(schedule)  
actual_trip_type = schedule.trips['CITY11']['trip_type']  
self.assertEquals(actual_trip_type, '')  
 
if __name__ == '__main__':  
unittest.main()  
 
 Binary files a/origin-src/transitfeed-1.2.5/test/testunusual_trip_filter.pyc and /dev/null differ
#!/usr/bin/python2.5  
 
# Copyright (C) 2007 Google Inc.  
#  
# Licensed under the Apache License, Version 2.0 (the "License");  
# you may not use this file except in compliance with the License.  
# You may obtain a copy of the License at  
#  
# http://www.apache.org/licenses/LICENSE-2.0  
#  
# Unless required by applicable law or agreed to in writing, software  
# distributed under the License is distributed on an "AS IS" BASIS,  
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.  
# See the License for the specific language governing permissions and  
# limitations under the License.  
 
# Code shared between tests.  
 
import os  
import os.path  
import re  
import cStringIO as StringIO  
import shutil  
import subprocess  
import sys  
import tempfile  
import traceback  
import transitfeed  
import unittest  
 
 
def check_call(cmd, expected_retcode=0, stdin_str="", **kwargs):  
"""Convenience function that is in the docs for subprocess but not  
installed on my system. Raises an Exception if the return code is not  
expected_retcode. Returns a tuple of strings, (stdout, stderr)."""  
try:  
if 'stdout' in kwargs or 'stderr' in kwargs or 'stdin' in kwargs:  
raise Exception("Don't pass stdout or stderr")  
p = subprocess.Popen(cmd, stdout=subprocess.PIPE,  
stderr=subprocess.PIPE, stdin=subprocess.PIPE,  
**kwargs)  
(out, err) = p.communicate(stdin_str)  
retcode = p.returncode  
except Exception, e:  
raise Exception("When running %s: %s" % (cmd, e))  
if retcode < 0:  
raise Exception(  
"Child '%s' was terminated by signal %d. Output:\n%s\n%s\n" %  
(cmd, -retcode, out, err))  
elif retcode != expected_retcode:  
raise Exception(  
"Child '%s' returned %d. Output:\n%s\n%s\n" %  
(cmd, retcode, out, err))  
return (out, err)  
 
 
class TestCaseAsserts(unittest.TestCase):  
def assertMatchesRegex(self, regex, string):  
"""Assert that regex is found in string."""  
if not re.search(regex, string):  
self.fail("string %r did not match regex %r" % (string, regex))  
 
 
class GetPathTestCase(TestCaseAsserts):  
"""TestCase with method to get paths to files in the distribution."""  
def setUp(self):  
TestCaseAsserts.setUp(self)  
self._origcwd = os.getcwd()  
 
def GetExamplePath(self, name):  
"""Return the full path of a file in the examples directory"""  
return self.GetPath('examples', name)  
 
def GetTestDataPath(self, *path):  
"""Return the full path of a file in the test/data directory"""  
return self.GetPath('test', 'data', *path)  
 
def GetPath(self, *path):  
"""Return absolute path of path. path is relative main source directory."""  
here = os.path.dirname(__file__) # Relative to _origcwd  
return os.path.join(self._origcwd, here, '..', *path)  
 
 
class TempDirTestCaseBase(GetPathTestCase):  
"""Make a temporary directory the current directory before running the test  
and remove it after the test.  
"""  
def setUp(self):  
GetPathTestCase.setUp(self)  
self.tempdirpath = tempfile.mkdtemp()  
os.chdir(self.tempdirpath)  
 
def tearDown(self):  
os.chdir(self._origcwd)  
shutil.rmtree(self.tempdirpath)  
GetPathTestCase.tearDown(self)  
 
def CheckCallWithPath(self, cmd, expected_retcode=0, stdin_str=""):  
"""Run python script cmd[0] with args cmd[1:], making sure 'import  
transitfeed' will use the module in this source tree. Raises an Exception  
if the return code is not expected_retcode. Returns a tuple of strings,  
(stdout, stderr)."""  
tf_path = transitfeed.__file__  
# Path of the directory containing transitfeed. When this is added to  
# sys.path importing transitfeed should work independent of if  
# transitfeed.__file__ is <parent>/transitfeed.py or  
# <parent>/transitfeed/__init__.py  
transitfeed_parent = tf_path[:tf_path.rfind("transitfeed")]  
transitfeed_parent = transitfeed_parent.replace("\\", "/").rstrip("/")  
script_path = cmd[0].replace("\\", "/")  
script_args = cmd[1:]  
 
# Propogate sys.path of this process to the subprocess. This is done  
# because I assume that if this process has a customized sys.path it is  
# meant to be used for all processes involved in the tests. The downside  
# of this is that the subprocess is no longer a clean version of what you  
# get when running "python" after installing transitfeed. Hopefully if this  
# process uses a customized sys.path you know what you are doing.  
env = {"PYTHONPATH": ":".join(sys.path)}  
 
# Instead of directly running the script make sure that the transitfeed  
# module in this source directory is at the front of sys.path. Then  
# adjust sys.argv so it looks like the script was run directly. This lets  
# OptionParser use the correct value for %proj.  
cmd = [sys.executable, "-c",  
"import sys; "  
"sys.path.insert(0,'%s'); "  
"sys.argv = ['%s'] + sys.argv[1:]; "  
"exec(open('%s'))" %  
(transitfeed_parent, script_path, script_path)] + script_args  
return check_call(cmd, expected_retcode=expected_retcode, shell=False,  
env=env, stdin_str=stdin_str)  
 
 
class RecordingProblemReporter(transitfeed.ProblemReporterBase):  
"""Save all problems for later inspection.  
 
Args:  
test_case: a unittest.TestCase object on which to report problems  
ignore_types: sequence of string type names that will be ignored by the  
ProblemReporter"""  
def __init__(self, test_case, ignore_types=None):  
transitfeed.ProblemReporterBase.__init__(self)  
self.exceptions = []  
self._test_case = test_case  
self._ignore_types = ignore_types or set()  
 
def _Report(self, e):  
# Ensure that these don't crash  
e.FormatProblem()  
e.FormatContext()  
if e.__class__.__name__ in self._ignore_types:  
return  
# Keep the 7 nearest stack frames. This should be enough to identify  
# the code path that created the exception while trimming off most of the  
# large test framework's stack.  
traceback_list = traceback.format_list(traceback.extract_stack()[-7:-1])  
self.exceptions.append((e, ''.join(traceback_list)))  
 
def PopException(self, type_name):  
"""Return the first exception, which must be a type_name."""  
e = self.exceptions.pop(0)  
e_name = e[0].__class__.__name__  
self._test_case.assertEqual(e_name, type_name,  
"%s != %s\n%s" %  
(e_name, type_name, self.FormatException(*e)))  
return e[0]  
 
def FormatException(self, exce, tb):  
return ("%s\nwith gtfs file context %s\nand traceback\n%s" %  
(exce.FormatProblem(), exce.FormatContext(), tb))  
 
def AssertNoMoreExceptions(self):  
exceptions_as_text = []  
for e, tb in self.exceptions:  
exceptions_as_text.append(self.FormatException(e, tb))  
self._test_case.assertFalse(self.exceptions, "\n".join(exceptions_as_text))  
 
def PopInvalidValue(self, column_name, file_name=None):  
e = self.PopException("InvalidValue")  
self._test_case.assertEquals(column_name, e.column_name)  
if file_name:  
self._test_case.assertEquals(file_name, e.file_name)  
return e  
 
def PopMissingValue(self, column_name, file_name=None):  
e = self.PopException("MissingValue")  
self._test_case.assertEquals(column_name, e.column_name)  
if file_name:  
self._test_case.assertEquals(file_name, e.file_name)  
return e  
 
def PopDuplicateColumn(self, file_name, header, count):  
e = self.PopException("DuplicateColumn")  
self._test_case.assertEquals(file_name, e.file_name)  
self._test_case.assertEquals(header, e.header)  
self._test_case.assertEquals(count, e.count)  
return e  
 
 
 Binary files a/origin-src/transitfeed-1.2.5/test/util.pyc and /dev/null differ
#!/usr/bin/python2.5  
 
# Copyright (C) 2007 Google Inc.  
#  
# Licensed under the Apache License, Version 2.0 (the "License");  
# you may not use this file except in compliance with the License.  
# You may obtain a copy of the License at  
#  
# http://www.apache.org/licenses/LICENSE-2.0  
#  
# Unless required by applicable law or agreed to in writing, software  
# distributed under the License is distributed on an "AS IS" BASIS,  
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.  
# See the License for the specific language governing permissions and  
# limitations under the License.  
 
"""Expose some modules in this package.  
 
Before transitfeed version 1.2.4 all our library code was distributed in a  
one file module, transitfeed.py, and could be used as  
 
import transitfeed  
schedule = transitfeed.Schedule()  
 
At that time the module (one file, transitfeed.py) was converted into a  
package (a directory named transitfeed containing __init__.py and multiple .py  
files). Classes and attributes exposed by the old module may still be imported  
in the same way. Indeed, code that depends on the library <em>should</em>  
continue to use import commands such as the above and ignore _transitfeed.  
"""  
 
from _transitfeed import *  
 
__version__ = _transitfeed.__version__  
 
 Binary files a/origin-src/transitfeed-1.2.5/transitfeed/__init__.pyc and /dev/null differ
#!/usr/bin/python2.5  
 
# Copyright (C) 2007 Google Inc.  
#  
# Licensed under the Apache License, Version 2.0 (the "License");  
# you may not use this file except in compliance with the License.  
# You may obtain a copy of the License at  
#  
# http://www.apache.org/licenses/LICENSE-2.0  
#  
# Unless required by applicable law or agreed to in writing, software  
# distributed under the License is distributed on an "AS IS" BASIS,  
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.  
# See the License for the specific language governing permissions and  
# limitations under the License.  
 
"""Easy interface for handling a Google Transit Feed file.  
 
Do not import this module directly. Thanks to __init__.py you should do  
something like:  
 
import transitfeed  
schedule = transitfeed.Schedule()  
...  
 
This module is a library to help you create, read and write Google  
Transit Feed files. Refer to the feed specification, available at  
http://code.google.com/transit/spec/transit_feed_specification.htm, for a  
complete description how the transit feed represents a transit schedule. This  
library supports all required parts of the specification but does not yet  
support all optional parts. Patches welcome!  
 
The specification describes several tables such as stops, routes and trips.  
In a feed file these are stored as comma separeted value files. This library  
represents each row of these tables with a single Python object. This object has  
attributes for each value on the row. For example, schedule.AddStop returns a  
Stop object which has attributes such as stop_lat and stop_name.  
 
Schedule: Central object of the parser  
GenericGTFSObject: A base class for each of the objects below  
Route: Represents a single route  
Trip: Represents a single trip  
Stop: Represents a single stop  
ServicePeriod: Represents a single service, a set of dates  
Agency: Represents the agency in this feed  
Transfer: Represents a single transfer rule  
TimeToSecondsSinceMidnight(): Convert HH:MM:SS into seconds since midnight.  
FormatSecondsSinceMidnight(s): Formats number of seconds past midnight into a string  
"""  
 
# TODO: Preserve arbitrary columns?  
 
import bisect  
import cStringIO as StringIO  
import codecs  
from transitfeed.util import defaultdict  
import csv  
import datetime  
import logging  
import math  
import os  
import random  
try:  
import sqlite3 as sqlite  
except ImportError:  
from pysqlite2 import dbapi2 as sqlite  
import re  
import tempfile  
import time  
import warnings  
# Objects in a schedule (Route, Trip, etc) should not keep a strong reference  
# to the Schedule object to avoid a reference cycle. Schedule needs to use  
# __del__ to cleanup its temporary file. The garbage collector can't handle  
# reference cycles containing objects with custom cleanup code.  
import weakref  
import zipfile  
 
OUTPUT_ENCODING = 'utf-8'  
MAX_DISTANCE_FROM_STOP_TO_SHAPE = 1000  
MAX_DISTANCE_BETWEEN_STOP_AND_PARENT_STATION_WARNING = 100.0  
MAX_DISTANCE_BETWEEN_STOP_AND_PARENT_STATION_ERROR = 1000.0  
 
__version__ = '1.2.5'  
 
 
def EncodeUnicode(text):  
"""  
Optionally encode text and return it. The result should be safe to print.  
"""  
if type(text) == type(u''):  
return text.encode(OUTPUT_ENCODING)  
else:  
return text  
 
 
# These are used to distinguish between errors (not allowed by the spec)  
# and warnings (not recommended) when reporting issues.  
TYPE_ERROR = 0  
TYPE_WARNING = 1  
 
 
class ProblemReporterBase:  
"""Base class for problem reporters. Tracks the current context and creates  
an exception object for each problem. Subclasses must implement  
_Report(self, e)"""  
 
def __init__(self):  
self.ClearContext()  
 
def ClearContext(self):  
"""Clear any previous context."""  
self._context = None  
 
def SetFileContext(self, file_name, row_num, row, headers):  
"""Save the current context to be output with any errors.  
 
Args:  
file_name: string  
row_num: int  
row: list of strings  
headers: list of column headers, its order corresponding to row's  
"""  
self._context = (file_name, row_num, row, headers)  
 
def FeedNotFound(self, feed_name, context=None):  
e = FeedNotFound(feed_name=feed_name, context=context,  
context2=self._context)  
self._Report(e)  
 
def UnknownFormat(self, feed_name, context=None):  
e = UnknownFormat(feed_name=feed_name, context=context,  
context2=self._context)  
self._Report(e)  
 
def FileFormat(self, problem, context=None):  
e = FileFormat(problem=problem, context=context,  
context2=self._context)  
self._Report(e)  
 
def MissingFile(self, file_name, context=None):  
e = MissingFile(file_name=file_name, context=context,  
context2=self._context)  
self._Report(e)  
 
def UnknownFile(self, file_name, context=None):  
e = UnknownFile(file_name=file_name, context=context,  
context2=self._context, type=TYPE_WARNING)  
self._Report(e)  
 
def EmptyFile(self, file_name, context=None):  
e = EmptyFile(file_name=file_name, context=context,  
context2=self._context)  
self._Report(e)  
 
def MissingColumn(self, file_name, column_name, context=None):  
e = MissingColumn(file_name=file_name, column_name=column_name,  
context=context, context2=self._context)  
self._Report(e)  
 
def UnrecognizedColumn(self, file_name, column_name, context=None):  
e = UnrecognizedColumn(file_name=file_name, column_name=column_name,  
context=context, context2=self._context,  
type=TYPE_WARNING)  
self._Report(e)  
 
def CsvSyntax(self, description=None, context=None, type=TYPE_ERROR):  
e = CsvSyntax(description=description, context=context,  
context2=self._context, type=type)  
self._Report(e)  
 
def DuplicateColumn(self, file_name, header, count, type=TYPE_ERROR,  
context=None):  
e = DuplicateColumn(file_name=file_name,  
header=header,  
count=count,  
type=type,  
context=context,  
context2=self._context)  
self._Report(e)  
 
def MissingValue(self, column_name, reason=None, context=None):  
e = MissingValue(column_name=column_name, reason=reason, context=context,  
context2=self._context)  
self._Report(e)  
 
def InvalidValue(self, column_name, value, reason=None, context=None,  
type=TYPE_ERROR):  
e = InvalidValue(column_name=column_name, value=value, reason=reason,  
context=context, context2=self._context, type=type)  
self._Report(e)  
 
def DuplicateID(self, column_names, values, context=None, type=TYPE_ERROR):  
if isinstance(column_names, tuple):  
column_names = '(' + ', '.join(column_names) + ')'  
if isinstance(values, tuple):  
values = '(' + ', '.join(values) + ')'  
e = DuplicateID(column_name=column_names, value=values,  
context=context, context2=self._context, type=type)  
self._Report(e)  
 
def UnusedStop(self, stop_id, stop_name, context=None):  
e = UnusedStop(stop_id=stop_id, stop_name=stop_name,  
context=context, context2=self._context, type=TYPE_WARNING)  
self._Report(e)  
 
def UsedStation(self, stop_id, stop_name, context=None):  
e = UsedStation(stop_id=stop_id, stop_name=stop_name,  
context=context, context2=self._context, type=TYPE_ERROR)  
self._Report(e)  
 
def StopTooFarFromParentStation(self, stop_id, stop_name, parent_stop_id,  
parent_stop_name, distance,  
type=TYPE_WARNING, context=None):  
e = StopTooFarFromParentStation(  
stop_id=stop_id, stop_name=stop_name,  
parent_stop_id=parent_stop_id,  
parent_stop_name=parent_stop_name, distance=distance,  
context=context, context2=self._context, type=type)  
self._Report(e)  
 
def StopsTooClose(self, stop_name_a, stop_id_a, stop_name_b, stop_id_b,  
distance, type=TYPE_WARNING, context=None):  
e = StopsTooClose(  
stop_name_a=stop_name_a, stop_id_a=stop_id_a, stop_name_b=stop_name_b,  
stop_id_b=stop_id_b, distance=distance, context=context,  
context2=self._context, type=type)  
self._Report(e)  
 
def StationsTooClose(self, stop_name_a, stop_id_a, stop_name_b, stop_id_b,  
distance, type=TYPE_WARNING, context=None):  
e = StationsTooClose(  
stop_name_a=stop_name_a, stop_id_a=stop_id_a, stop_name_b=stop_name_b,  
stop_id_b=stop_id_b, distance=distance, context=context,  
context2=self._context, type=type)  
self._Report(e)  
 
def DifferentStationTooClose(self, stop_name, stop_id,  
station_stop_name, station_stop_id,  
distance, type=TYPE_WARNING, context=None):  
e = DifferentStationTooClose(  
stop_name=stop_name, stop_id=stop_id,  
station_stop_name=station_stop_name, station_stop_id=station_stop_id,  
distance=distance, context=context, context2=self._context, type=type)  
self._Report(e)  
 
def StopTooFarFromShapeWithDistTraveled(self, trip_id, stop_name, stop_id,  
shape_dist_traveled, shape_id,  
distance, max_distance,  
type=TYPE_WARNING):  
e = StopTooFarFromShapeWithDistTraveled(  
trip_id=trip_id, stop_name=stop_name, stop_id=stop_id,  
shape_dist_traveled=shape_dist_traveled, shape_id=shape_id,  
distance=distance, max_distance=max_distance, type=type)  
self._Report(e)  
 
def ExpirationDate(self, expiration, context=None):  
e = ExpirationDate(expiration=expiration, context=context,  
context2=self._context, type=TYPE_WARNING)  
self._Report(e)  
 
def FutureService(self, start_date, context=None):  
e = FutureService(start_date=start_date, context=context,  
context2=self._context, type=TYPE_WARNING)  
self._Report(e)  
 
def InvalidLineEnd(self, bad_line_end, context=None):  
"""bad_line_end is a human readable string."""  
e = InvalidLineEnd(bad_line_end=bad_line_end, context=context,  
context2=self._context, type=TYPE_WARNING)  
self._Report(e)  
 
def TooFastTravel(self, trip_id, prev_stop, next_stop, dist, time, speed,  
type=TYPE_ERROR):  
e = TooFastTravel(trip_id=trip_id, prev_stop=prev_stop,  
next_stop=next_stop, time=time, dist=dist, speed=speed,  
context=None, context2=self._context, type=type)  
self._Report(e)  
 
def StopWithMultipleRouteTypes(self, stop_name, stop_id, route_id1, route_id2,  
context=None):  
e = StopWithMultipleRouteTypes(stop_name=stop_name, stop_id=stop_id,  
route_id1=route_id1, route_id2=route_id2,  
context=context, context2=self._context,  
type=TYPE_WARNING)  
self._Report(e)  
 
def DuplicateTrip(self, trip_id1, route_id1, trip_id2, route_id2,  
context=None):  
e = DuplicateTrip(trip_id1=trip_id1, route_id1=route_id1, trip_id2=trip_id2,  
route_id2=route_id2, context=context,  
context2=self._context, type=TYPE_WARNING)  
self._Report(e)  
 
def OtherProblem(self, description, context=None, type=TYPE_ERROR):  
e = OtherProblem(description=description,  
context=context, context2=self._context, type=type)  
self._Report(e)  
 
def TooManyDaysWithoutService(self,  
first_day_without_service,  
last_day_without_service,  
consecutive_days_without_service,  
context=None,  
type=TYPE_WARNING):  
e = TooManyDaysWithoutService(  
first_day_without_service=first_day_without_service,  
last_day_without_service=last_day_without_service,  
consecutive_days_without_service=consecutive_days_without_service,  
context=context,  
context2=self._context,  
type=type)  
self._Report(e)  
 
class ProblemReporter(ProblemReporterBase):  
"""This is a basic problem reporter that just prints to console."""  
def _Report(self, e):  
context = e.FormatContext()  
if context:  
print context  
print EncodeUnicode(self._LineWrap(e.FormatProblem(), 78))  
 
@staticmethod  
def _LineWrap(text, width):  
"""  
A word-wrap function that preserves existing line breaks  
and most spaces in the text. Expects that existing line  
breaks are posix newlines (\n).  
 
Taken from:  
http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/148061  
"""  
return reduce(lambda line, word, width=width: '%s%s%s' %  
(line,  
' \n'[(len(line) - line.rfind('\n') - 1 +  
len(word.split('\n', 1)[0]) >= width)],  
word),  
text.split(' ')  
)  
 
 
class ExceptionWithContext(Exception):  
def __init__(self, context=None, context2=None, **kwargs):  
"""Initialize an exception object, saving all keyword arguments in self.  
context and context2, if present, must be a tuple of (file_name, row_num,  
row, headers). context2 comes from ProblemReporter.SetFileContext. context  
was passed in with the keyword arguments. context2 is ignored if context  
is present."""  
Exception.__init__(self)  
 
if context:  
self.__dict__.update(self.ContextTupleToDict(context))  
elif context2:  
self.__dict__.update(self.ContextTupleToDict(context2))  
self.__dict__.update(kwargs)  
 
if ('type' in kwargs) and (kwargs['type'] == TYPE_WARNING):  
self._type = TYPE_WARNING  
else:  
self._type = TYPE_ERROR  
 
def GetType(self):  
return self._type  
 
def IsError(self):  
return self._type == TYPE_ERROR  
 
def IsWarning(self):  
return self._type == TYPE_WARNING  
 
CONTEXT_PARTS = ['file_name', 'row_num', 'row', 'headers']  
@staticmethod  
def ContextTupleToDict(context):  
"""Convert a tuple representing a context into a dict of (key, value) pairs"""  
d = {}  
if not context:  
return d  
for k, v in zip(ExceptionWithContext.CONTEXT_PARTS, context):  
if v != '' and v != None: # Don't ignore int(0), a valid row_num  
d[k] = v  
return d  
 
def __str__(self):  
return self.FormatProblem()  
 
def GetDictToFormat(self):  
"""Return a copy of self as a dict, suitable for passing to FormatProblem"""  
d = {}  
for k, v in self.__dict__.items():  
# TODO: Better handling of unicode/utf-8 within Schedule objects.  
# Concatinating a unicode and utf-8 str object causes an exception such  
# as "UnicodeDecodeError: 'ascii' codec can't decode byte ..." as python  
# tries to convert the str to a unicode. To avoid that happening within  
# the problem reporter convert all unicode attributes to utf-8.  
# Currently valid utf-8 fields are converted to unicode in _ReadCsvDict.  
# Perhaps all fields should be left as utf-8.  
d[k] = EncodeUnicode(v)  
return d  
 
def FormatProblem(self, d=None):  
"""Return a text string describing the problem.  
 
Args:  
d: map returned by GetDictToFormat with with formatting added  
"""  
if not d:  
d = self.GetDictToFormat()  
 
output_error_text = self.__class__.ERROR_TEXT % d  
if ('reason' in d) and d['reason']:  
return '%s\n%s' % (output_error_text, d['reason'])  
else:  
return output_error_text  
 
def FormatContext(self):  
"""Return a text string describing the context"""  
text = ''  
if hasattr(self, 'feed_name'):  
text += "In feed '%s': " % self.feed_name  
if hasattr(self, 'file_name'):  
text += self.file_name  
if hasattr(self, 'row_num'):  
text += ":%i" % self.row_num  
if hasattr(self, 'column_name'):  
text += " column %s" % self.column_name  
return text  
 
def __cmp__(self, y):  
"""Return an int <0/0/>0 when self is more/same/less significant than y.  
 
Subclasses should define this if exceptions should be listed in something  
other than the order they are reported.  
 
Args:  
y: object to compare to self  
 
Returns:  
An int which is negative if self is more significant than y, 0 if they  
are similar significance and positive if self is less significant than  
y. Returning a float won't work.  
 
Raises:  
TypeError by default, meaning objects of the type can not be compared.  
"""  
raise TypeError("__cmp__ not defined")  
 
 
class MissingFile(ExceptionWithContext):  
ERROR_TEXT = "File %(file_name)s is not found"  
 
class EmptyFile(ExceptionWithContext):  
ERROR_TEXT = "File %(file_name)s is empty"  
 
class UnknownFile(ExceptionWithContext):  
ERROR_TEXT = 'The file named %(file_name)s was not expected.\n' \  
'This may be a misspelled file name or the file may be ' \  
'included in a subdirectory. Please check spellings and ' \  
'make sure that there are no subdirectories within the feed'  
 
class FeedNotFound(ExceptionWithContext):  
ERROR_TEXT = 'Couldn\'t find a feed named %(feed_name)s'  
 
class UnknownFormat(ExceptionWithContext):  
ERROR_TEXT = 'The feed named %(feed_name)s had an unknown format:\n' \  
'feeds should be either .zip files or directories.'  
 
class FileFormat(ExceptionWithContext):  
ERROR_TEXT = 'Files must be encoded in utf-8 and may not contain ' \  
'any null bytes (0x00). %(file_name)s %(problem)s.'  
 
class MissingColumn(ExceptionWithContext):  
ERROR_TEXT = 'Missing column %(column_name)s in file %(file_name)s'  
 
class UnrecognizedColumn(ExceptionWithContext):  
ERROR_TEXT = 'Unrecognized column %(column_name)s in file %(file_name)s. ' \  
'This might be a misspelled column name (capitalization ' \  
'matters!). Or it could be extra information (such as a ' \  
'proposed feed extension) that the validator doesn\'t know ' \  
'about yet. Extra information is fine; this warning is here ' \  
'to catch misspelled optional column names.'  
 
class CsvSyntax(ExceptionWithContext):  
ERROR_TEXT = '%(description)s'  
 
class DuplicateColumn(ExceptionWithContext):  
ERROR_TEXT = 'Column %(header)s appears %(count)i times in file %(file_name)s'  
 
class MissingValue(ExceptionWithContext):  
ERROR_TEXT = 'Missing value for column %(column_name)s'  
 
class InvalidValue(ExceptionWithContext):  
ERROR_TEXT = 'Invalid value %(value)s in field %(column_name)s'  
 
class DuplicateID(ExceptionWithContext):  
ERROR_TEXT = 'Duplicate ID %(value)s in column %(column_name)s'  
 
class UnusedStop(ExceptionWithContext):  
ERROR_TEXT = "%(stop_name)s (ID %(stop_id)s) isn't used in any trips"  
 
class UsedStation(ExceptionWithContext):  
ERROR_TEXT = "%(stop_name)s (ID %(stop_id)s) has location_type=1 " \  
"(station) so it should not appear in stop_times"  
 
class StopTooFarFromParentStation(ExceptionWithContext):  
ERROR_TEXT = (  
"%(stop_name)s (ID %(stop_id)s) is too far from its parent station "  
"%(parent_stop_name)s (ID %(parent_stop_id)s) : %(distance).2f meters.")  
def __cmp__(self, y):  
# Sort in decreasing order because more distance is more significant.  
return cmp(y.distance, self.distance)  
 
 
class StopsTooClose(ExceptionWithContext):  
ERROR_TEXT = (  
"The stops \"%(stop_name_a)s\" (ID %(stop_id_a)s) and \"%(stop_name_b)s\""  
" (ID %(stop_id_b)s) are %(distance)0.2fm apart and probably represent "  
"the same location.")  
def __cmp__(self, y):  
# Sort in increasing order because less distance is more significant.  
return cmp(self.distance, y.distance)  
 
class StationsTooClose(ExceptionWithContext):  
ERROR_TEXT = (  
"The stations \"%(stop_name_a)s\" (ID %(stop_id_a)s) and "  
"\"%(stop_name_b)s\" (ID %(stop_id_b)s) are %(distance)0.2fm apart and "  
"probably represent the same location.")  
def __cmp__(self, y):  
# Sort in increasing order because less distance is more significant.  
return cmp(self.distance, y.distance)  
 
class DifferentStationTooClose(ExceptionWithContext):  
ERROR_TEXT = (  
"The parent_station of stop \"%(stop_name)s\" (ID %(stop_id)s) is not "  
"station \"%(station_stop_name)s\" (ID %(station_stop_id)s) but they are "  
"only %(distance)0.2fm apart.")  
def __cmp__(self, y):  
# Sort in increasing order because less distance is more significant.  
return cmp(self.distance, y.distance)  
 
class StopTooFarFromShapeWithDistTraveled(ExceptionWithContext):  
ERROR_TEXT = (  
"For trip %(trip_id)s the stop \"%(stop_name)s\" (ID %(stop_id)s) is "  
"%(distance).0f meters away from the corresponding point "  
"(shape_dist_traveled: %(shape_dist_traveled)f) on shape %(shape_id)s. "  
"It should be closer than %(max_distance).0f meters.")  
def __cmp__(self, y):  
# Sort in decreasing order because more distance is more significant.  
return cmp(y.distance, self.distance)  
 
 
class TooManyDaysWithoutService(ExceptionWithContext):  
ERROR_TEXT = "There are %(consecutive_days_without_service)i consecutive"\  
" days, from %(first_day_without_service)s to" \  
" %(last_day_without_service)s, without any scheduled service." \  
" Please ensure this is intentional."  
 
 
class ExpirationDate(ExceptionWithContext):  
def FormatProblem(self, d=None):  
if not d:  
d = self.GetDictToFormat()  
expiration = d['expiration']  
formatted_date = time.strftime("%B %d, %Y",  
time.localtime(expiration))  
if (expiration < time.mktime(time.localtime())):  
return "This feed expired on %s" % formatted_date  
else:  
return "This feed will soon expire, on %s" % formatted_date  
 
class FutureService(ExceptionWithContext):  
def FormatProblem(self, d=None):  
if not d:  
d = self.GetDictToFormat()  
formatted_date = time.strftime("%B %d, %Y", time.localtime(d['start_date']))  
return ("The earliest service date in this feed is in the future, on %s. "  
"Published feeds must always include the current date." %  
formatted_date)  
 
 
class InvalidLineEnd(ExceptionWithContext):  
ERROR_TEXT = "Each line must end with CR LF or LF except for the last line " \  
"of the file. This line ends with \"%(bad_line_end)s\"."  
 
class StopWithMultipleRouteTypes(ExceptionWithContext):  
ERROR_TEXT = "Stop %(stop_name)s (ID=%(stop_id)s) belongs to both " \  
"subway (ID=%(route_id1)s) and bus line (ID=%(route_id2)s)."  
 
class TooFastTravel(ExceptionWithContext):  
def FormatProblem(self, d=None):  
if not d:  
d = self.GetDictToFormat()  
if not d['speed']:  
return "High speed travel detected in trip %(trip_id)s: %(prev_stop)s" \  
" to %(next_stop)s. %(dist).0f meters in %(time)d seconds." % d  
else:  
return "High speed travel detected in trip %(trip_id)s: %(prev_stop)s" \  
" to %(next_stop)s. %(dist).0f meters in %(time)d seconds." \  
" (%(speed).0f km/h)." % d  
def __cmp__(self, y):  
# Sort in decreasing order because more distance is more significant. We  
# can't sort by speed because not all TooFastTravel objects have a speed.  
return cmp(y.dist, self.dist)  
 
class DuplicateTrip(ExceptionWithContext):  
ERROR_TEXT = "Trip %(trip_id1)s of route %(route_id1)s might be duplicated " \  
"with trip %(trip_id2)s of route %(route_id2)s. They go " \  
"through the same stops with same service."  
 
class OtherProblem(ExceptionWithContext):  
ERROR_TEXT = '%(description)s'  
 
 
class ExceptionProblemReporter(ProblemReporter):  
def __init__(self, raise_warnings=False):  
ProblemReporterBase.__init__(self)  
self.raise_warnings = raise_warnings  
 
def _Report(self, e):  
if self.raise_warnings or e.IsError():  
raise e  
else:  
ProblemReporter._Report(self, e)  
 
 
default_problem_reporter = ExceptionProblemReporter()  
 
# Add a default handler to send log messages to console  
console = logging.StreamHandler()  
console.setLevel(logging.WARNING)  
log = logging.getLogger("schedule_builder")  
log.addHandler(console)  
 
 
class Error(Exception):  
pass  
 
 
def IsValidURL(url):  
"""Checks the validity of a URL value."""  
# TODO: Add more thorough checking of URL  
return url.startswith(u'http://') or url.startswith(u'https://')  
 
 
def IsValidColor(color):  
"""Checks the validity of a hex color value."""  
return not re.match('^[0-9a-fA-F]{6}$', color) == None  
 
 
def ColorLuminance(color):  
"""Compute the brightness of an sRGB color using the formula from  
http://www.w3.org/TR/2000/WD-AERT-20000426#color-contrast.  
 
Args:  
color: a string of six hex digits in the format verified by IsValidColor().  
 
Returns:  
A floating-point number between 0.0 (black) and 255.0 (white). """  
r = int(color[0:2], 16)  
g = int(color[2:4], 16)  
b = int(color[4:6], 16)  
return (299*r + 587*g + 114*b) / 1000.0  
 
 
def IsEmpty(value):  
return value is None or (isinstance(value, basestring) and not value.strip())  
 
 
def FindUniqueId(dic):  
"""Return a string not used as a key in the dictionary dic"""  
name = str(len(dic))  
while name in dic:  
name = str(random.randint(1, 999999999))  
return name  
 
 
def TimeToSecondsSinceMidnight(time_string):  
"""Convert HHH:MM:SS into seconds since midnight.  
 
For example "01:02:03" returns 3723. The leading zero of the hours may be  
omitted. HH may be more than 23 if the time is on the following day."""  
m = re.match(r'(\d{1,3}):([0-5]\d):([0-5]\d)$', time_string)  
# ignored: matching for leap seconds  
if not m:  
raise Error, 'Bad HH:MM:SS "%s"' % time_string  
return int(m.group(1)) * 3600 + int(m.group(2)) * 60 + int(m.group(3))  
 
 
def FormatSecondsSinceMidnight(s):  
"""Formats an int number of seconds past midnight into a string  
as "HH:MM:SS"."""  
return "%02d:%02d:%02d" % (s / 3600, (s / 60) % 60, s % 60)  
 
 
def DateStringToDateObject(date_string):  
"""Return a date object for a string "YYYYMMDD"."""  
# If this becomes a bottleneck date objects could be cached  
return datetime.date(int(date_string[0:4]), int(date_string[4:6]),  
int(date_string[6:8]))  
 
 
def FloatStringToFloat(float_string):  
"""Convert a float as a string to a float or raise an exception"""  
# Will raise TypeError unless a string  
if not re.match(r"^[+-]?\d+(\.\d+)?$", float_string):  
raise ValueError()  
return float(float_string)  
 
 
def NonNegIntStringToInt(int_string):  
"""Convert an non-negative integer string to an int or raise an exception"""  
# Will raise TypeError unless a string  
if not re.match(r"^(?:0|[1-9]\d*)$", int_string):  
raise ValueError()  
return int(int_string)  
 
 
EARTH_RADIUS = 6378135 # in meters  
def ApproximateDistance(degree_lat1, degree_lng1, degree_lat2, degree_lng2):  
"""Compute approximate distance between two points in meters. Assumes the  
Earth is a sphere."""  
# TODO: change to ellipsoid approximation, such as  
# http://www.codeguru.com/Cpp/Cpp/algorithms/article.php/c5115/  
lat1 = math.radians(degree_lat1)  
lng1 = math.radians(degree_lng1)  
lat2 = math.radians(degree_lat2)  
lng2 = math.radians(degree_lng2)  
dlat = math.sin(0.5 * (lat2 - lat1))  
dlng = math.sin(0.5 * (lng2 - lng1))  
x = dlat * dlat + dlng * dlng * math.cos(lat1) * math.cos(lat2)  
return EARTH_RADIUS * (2 * math.atan2(math.sqrt(x),  
math.sqrt(max(0.0, 1.0 - x))))  
 
 
def ApproximateDistanceBetweenStops(stop1, stop2):  
"""Compute approximate distance between two stops in meters. Assumes the  
Earth is a sphere."""  
return ApproximateDistance(stop1.stop_lat, stop1.stop_lon,  
stop2.stop_lat, stop2.stop_lon)  
 
 
class GenericGTFSObject(object):  
"""Object with arbitrary attributes which may be added to a schedule.  
 
This class should be used as the base class for GTFS objects which may  
be stored in a Schedule. It defines some methods for reading and writing  
attributes. If self._schedule is None than the object is not in a Schedule.  
 
Subclasses must:  
* define an __init__ method which sets the _schedule member to None or a  
weakref to a Schedule  
* Set the _TABLE_NAME class variable to a name such as 'stops', 'agency', ...  
* define methods to validate objects of that type  
"""  
def __getitem__(self, name):  
"""Return a unicode or str representation of name or "" if not set."""  
if name in self.__dict__ and self.__dict__[name] is not None:  
return "%s" % self.__dict__[name]  
else:  
return ""  
 
def __getattr__(self, name):  
"""Return None or the default value if name is a known attribute.  
 
This method is only called when name is not found in __dict__.  
"""  
if name in self.__class__._FIELD_NAMES:  
return None  
else:  
raise AttributeError(name)  
 
def iteritems(self):  
"""Return a iterable for (name, value) pairs of public attributes."""  
for name, value in self.__dict__.iteritems():  
if (not name) or name[0] == "_":  
continue  
yield name, value  
 
def __setattr__(self, name, value):  
"""Set an attribute, adding name to the list of columns as needed."""  
object.__setattr__(self, name, value)  
if name[0] != '_' and self._schedule:  
self._schedule.AddTableColumn(self.__class__._TABLE_NAME, name)  
 
def __eq__(self, other):  
"""Return true iff self and other are equivalent"""  
if not other:  
return False  
 
if id(self) == id(other):  
return True  
 
for k in self.keys().union(other.keys()):  
# use __getitem__ which returns "" for missing columns values  
if self[k] != other[k]:  
return False  
return True  
 
def __ne__(self, other):  
return not self.__eq__(other)  
 
def __repr__(self):  
return "<%s %s>" % (self.__class__.__name__, sorted(self.iteritems()))  
 
def keys(self):  
"""Return iterable of columns used by this object."""  
columns = set()  
for name in vars(self):  
if (not name) or name[0] == "_":  
continue  
columns.add(name)  
return columns  
 
def _ColumnNames(self):  
return self.keys()  
 
 
class Stop(GenericGTFSObject):  
"""Represents a single stop. A stop must have a latitude, longitude and name.  
 
Callers may assign arbitrary values to instance attributes.  
Stop.ParseAttributes validates attributes according to GTFS and converts some  
into native types. ParseAttributes may delete invalid attributes.  
Accessing an attribute that is a column in GTFS will return None if this  
object does not have a value or it is ''.  
A Stop object acts like a dict with string values.  
 
Attributes:  
stop_lat: a float representing the latitude of the stop  
stop_lon: a float representing the longitude of the stop  
All other attributes are strings.  
"""  
_REQUIRED_FIELD_NAMES = ['stop_id', 'stop_name', 'stop_lat', 'stop_lon']  
_FIELD_NAMES = _REQUIRED_FIELD_NAMES + \  
['stop_desc', 'zone_id', 'stop_url', 'stop_code',  
'location_type', 'parent_station']  
_TABLE_NAME = 'stops'  
 
def __init__(self, lat=None, lng=None, name=None, stop_id=None,  
field_dict=None, stop_code=None):  
"""Initialize a new Stop object.  
 
Args:  
field_dict: A dictionary mapping attribute name to unicode string  
lat: a float, ignored when field_dict is present  
lng: a float, ignored when field_dict is present  
name: a string, ignored when field_dict is present  
stop_id: a string, ignored when field_dict is present  
stop_code: a string, ignored when field_dict is present  
"""  
self._schedule = None  
if field_dict:  
if isinstance(field_dict, Stop):  
# Special case so that we don't need to re-parse the attributes to  
# native types iteritems returns all attributes that don't start with _  
for k, v in field_dict.iteritems():  
self.__dict__[k] = v  
else:  
self.__dict__.update(field_dict)  
else:  
if lat is not None:  
self.stop_lat = lat  
if lng is not None:  
self.stop_lon = lng  
if name is not None:  
self.stop_name = name  
if stop_id is not None:  
self.stop_id = stop_id  
if stop_code is not None:  
self.stop_code = stop_code  
 
def GetTrips(self, schedule=None):  
"""Return iterable containing trips that visit this stop."""  
return [trip for trip, ss in self._GetTripSequence(schedule)]  
 
def _GetTripSequence(self, schedule=None):  
"""Return a list of (trip, stop_sequence) for all trips visiting this stop.  
 
A trip may be in the list multiple times with different index.  
stop_sequence is an integer.  
 
Args:  
schedule: Deprecated, do not use.  
"""  
if schedule is None:  
schedule = getattr(self, "_schedule", None)  
if schedule is None:  
warnings.warn("No longer supported. _schedule attribute is used to get "  
"stop_times table", DeprecationWarning)  
cursor = schedule._connection.cursor()  
cursor.execute("SELECT trip_id,stop_sequence FROM stop_times "  
"WHERE stop_id=?",  
(self.stop_id, ))  
return [(schedule.GetTrip(row[0]), row[1]) for row in cursor]  
 
def _GetTripIndex(self, schedule=None):  
"""Return a list of (trip, index).  
 
trip: a Trip object  
index: an offset in trip.GetStopTimes()  
"""  
trip_index = []  
for trip, sequence in self._GetTripSequence(schedule):  
for index, st in enumerate(trip.GetStopTimes()):  
if st.stop_sequence == sequence:  
trip_index.append((trip, index))  
break  
else:  
raise RuntimeError("stop_sequence %d not found in trip_id %s" %  
sequence, trip.trip_id)  
return trip_index  
 
def GetStopTimeTrips(self, schedule=None):  
"""Return a list of (time, (trip, index), is_timepoint).  
 
time: an integer. It might be interpolated.  
trip: a Trip object.  
index: the offset of this stop in trip.GetStopTimes(), which may be  
different from the stop_sequence.  
is_timepoint: a bool  
"""  
time_trips = []  
for trip, index in self._GetTripIndex(schedule):  
secs, stoptime, is_timepoint = trip.GetTimeInterpolatedStops()[index]  
time_trips.append((secs, (trip, index), is_timepoint))  
return time_trips  
 
def ParseAttributes(self, problems):  
"""Parse all attributes, calling problems as needed."""  
# Need to use items() instead of iteritems() because _CheckAndSetAttr may  
# modify self.__dict__  
for name, value in vars(self).items():  
if name[0] == "_":  
continue  
self._CheckAndSetAttr(name, value, problems)  
 
def _CheckAndSetAttr(self, name, value, problems):  
"""If value is valid for attribute name store it.  
 
If value is not valid call problems. Return a new value of the correct type  
or None if value couldn't be converted.  
"""  
if name == 'stop_lat':  
try:  
if isinstance(value, (float, int)):  
self.stop_lat = value  
else:  
self.stop_lat = FloatStringToFloat(value)  
except (ValueError, TypeError):  
problems.InvalidValue('stop_lat', value)  
del self.stop_lat  
else:  
if self.stop_lat > 90 or self.stop_lat < -90:  
problems.InvalidValue('stop_lat', value)  
elif name == 'stop_lon':  
try:  
if isinstance(value, (float, int)):  
self.stop_lon = value  
else:  
self.stop_lon = FloatStringToFloat(value)  
except (ValueError, TypeError):  
problems.InvalidValue('stop_lon', value)  
del self.stop_lon  
else:  
if self.stop_lon > 180 or self.stop_lon < -180:  
problems.InvalidValue('stop_lon', value)  
elif name == 'stop_url':  
if value and not IsValidURL(value):  
problems.InvalidValue('stop_url', value)  
del self.stop_url  
elif name == 'location_type':  
if value == '':  
self.location_type = 0  
else:  
try:  
self.location_type = int(value)  
except (ValueError, TypeError):  
problems.InvalidValue('location_type', value)  
del self.location_type  
else:  
if self.location_type not in (0, 1):  
problems.InvalidValue('location_type', value, type=TYPE_WARNING)  
 
def __getattr__(self, name):  
"""Return None or the default value if name is a known attribute.  
 
This method is only called when name is not found in __dict__.  
"""  
if name == "location_type":  
return 0  
elif name == "trip_index":  
return self._GetTripIndex()  
elif name in Stop._FIELD_NAMES:  
return None  
else:  
raise AttributeError(name)  
 
def Validate(self, problems=default_problem_reporter):  
# First check that all required fields are present because ParseAttributes  
# may remove invalid attributes.  
for required in Stop._REQUIRED_FIELD_NAMES:  
if IsEmpty(getattr(self, required, None)):  
# TODO: For now I'm keeping the API stable but it would be cleaner to  
# treat whitespace stop_id as invalid, instead of missing  
problems.MissingValue(required)  
 
# Check individual values and convert to native types  
self.ParseAttributes(problems)  
 
# Check that this object is consistent with itself  
if (self.stop_lat is not None and self.stop_lon is not None and  
abs(self.stop_lat) < 1.0) and (abs(self.stop_lon) < 1.0):  
problems.InvalidValue('stop_lat', self.stop_lat,  
'Stop location too close to 0, 0',  
type=TYPE_WARNING)  
if (self.stop_desc is not None and self.stop_name is not None and  
self.stop_desc and self.stop_name and  
not IsEmpty(self.stop_desc) and  
self.stop_name.strip().lower() == self.stop_desc.strip().lower()):  
problems.InvalidValue('stop_desc', self.stop_desc,  
'stop_desc should not be the same as stop_name')  
 
if self.parent_station and self.location_type == 1:  
problems.InvalidValue('parent_station', self.parent_station,  
'Stop row with location_type=1 (a station) must '  
'not have a parent_station')  
 
 
class Route(GenericGTFSObject):  
"""Represents a single route."""  
 
_REQUIRED_FIELD_NAMES = [  
'route_id', 'route_short_name', 'route_long_name', 'route_type'  
]  
_FIELD_NAMES = _REQUIRED_FIELD_NAMES + [  
'agency_id', 'route_desc', 'route_url', 'route_color', 'route_text_color'  
]  
_ROUTE_TYPES = {  
0: {'name':'Tram', 'max_speed':100},  
1: {'name':'Subway', 'max_speed':150},  
2: {'name':'Rail', 'max_speed':300},  
3: {'name':'Bus', 'max_speed':100},  
4: {'name':'Ferry', 'max_speed':80},  
5: {'name':'Cable Car', 'max_speed':50},  
6: {'name':'Gondola', 'max_speed':50},  
7: {'name':'Funicular', 'max_speed':50},  
}  
# Create a reverse lookup dict of route type names to route types.  
_ROUTE_TYPE_IDS = set(_ROUTE_TYPES.keys())  
_ROUTE_TYPE_NAMES = dict((v['name'], k) for k, v in _ROUTE_TYPES.items())  
_TABLE_NAME = 'routes'  
 
def __init__(self, short_name=None, long_name=None, route_type=None,  
route_id=None, agency_id=None, field_dict=None):  
self._schedule = None  
self._trips = []  
 
if not field_dict:  
field_dict = {}  
if short_name is not None:  
field_dict['route_short_name'] = short_name  
if long_name is not None:  
field_dict['route_long_name'] = long_name  
if route_type is not None:  
if route_type in Route._ROUTE_TYPE_NAMES:  
self.route_type = Route._ROUTE_TYPE_NAMES[route_type]  
else:  
field_dict['route_type'] = route_type  
if route_id is not None:  
field_dict['route_id'] = route_id  
if agency_id is not None:  
field_dict['agency_id'] = agency_id  
self.__dict__.update(field_dict)  
 
def AddTrip(self, schedule, headsign, service_period=None, trip_id=None):  
""" Adds a trip to this route.  
 
Args:  
headsign: headsign of the trip as a string  
 
Returns:  
a new Trip object  
"""  
if trip_id is None:  
trip_id = unicode(len(schedule.trips))  
if service_period is None:  
service_period = schedule.GetDefaultServicePeriod()  
trip = Trip(route=self, headsign=headsign, service_period=service_period,  
trip_id=trip_id)  
schedule.AddTripObject(trip)  
return trip  
 
def _AddTripObject(self, trip):  
# Only class Schedule may call this. Users of the API should call  
# Route.AddTrip or schedule.AddTripObject.  
self._trips.append(trip)  
 
def __getattr__(self, name):  
"""Return None or the default value if name is a known attribute.  
 
This method overrides GenericGTFSObject.__getattr__ to provide backwards  
compatible access to trips.  
"""  
if name == 'trips':  
return self._trips  
else:  
return GenericGTFSObject.__getattr__(self, name)  
 
def GetPatternIdTripDict(self):  
"""Return a dictionary that maps pattern_id to a list of Trip objects."""  
d = {}  
for t in self._trips:  
d.setdefault(t.pattern_id, []).append(t)  
return d  
 
def Validate(self, problems=default_problem_reporter):  
if IsEmpty(self.route_id):  
problems.MissingValue('route_id')  
if IsEmpty(self.route_type):  
problems.MissingValue('route_type')  
 
if IsEmpty(self.route_short_name) and IsEmpty(self.route_long_name):  
problems.InvalidValue('route_short_name',  
self.route_short_name,  
'Both route_short_name and '  
'route_long name are blank.')  
 
if self.route_short_name and len(self.route_short_name) > 6:  
problems.InvalidValue('route_short_name',  
self.route_short_name,  
'This route_short_name is relatively long, which '  
'probably means that it contains a place name. '  
'You should only use this field to hold a short '  
'code that riders use to identify a route. '  
'If this route doesn\'t have such a code, it\'s '  
'OK to leave this field empty.', type=TYPE_WARNING)  
 
if self.route_short_name and self.route_long_name:  
short_name = self.route_short_name.strip().lower()  
long_name = self.route_long_name.strip().lower()  
if (long_name.startswith(short_name + ' ') or  
long_name.startswith(short_name + '(') or  
long_name.startswith(short_name + '-')):  
problems.InvalidValue('route_long_name',  
self.route_long_name,  
'route_long_name shouldn\'t contain '  
'the route_short_name value, as both '  
'fields are often displayed '  
'side-by-side.', type=TYPE_WARNING)  
if long_name == short_name:  
problems.InvalidValue('route_long_name',  
self.route_long_name,  
'route_long_name shouldn\'t be the same '  
'the route_short_name value, as both '  
'fields are often displayed '  
'side-by-side. It\'s OK to omit either the '  
'short or long name (but not both).',  
type=TYPE_WARNING)  
if (self.route_desc and  
((self.route_desc == self.route_short_name) or  
(self.route_desc == self.route_long_name))):  
problems.InvalidValue('route_desc',  
self.route_desc,  
'route_desc shouldn\'t be the same as '  
'route_short_name or route_long_name')  
 
if self.route_type is not None:  
try:  
if not isinstance(self.route_type, int):  
self.route_type = NonNegIntStringToInt(self.route_type)  
except (TypeError, ValueError):  
problems.InvalidValue('route_type', self.route_type)  
else:  
if self.route_type not in Route._ROUTE_TYPE_IDS:  
problems.InvalidValue('route_type',  
self.route_type,  
type=TYPE_WARNING)  
 
if self.route_url and not IsValidURL(self.route_url):  
problems.InvalidValue('route_url', self.route_url)  
 
txt_lum = ColorLuminance('000000') # black (default)  
bg_lum = ColorLuminance('ffffff') # white (default)  
if self.route_color:  
if IsValidColor(self.route_color):  
bg_lum = ColorLuminance(self.route_color)  
else:  
problems.InvalidValue('route_color', self.route_color,  
'route_color should be a valid color description '  
'which consists of 6 hexadecimal characters '  
'representing the RGB values. Example: 44AA06')  
if self.route_text_color:  
if IsValidColor(self.route_text_color):  
txt_lum = ColorLuminance(self.route_text_color)  
else:  
problems.InvalidValue('route_text_color', self.route_text_color,  
'route_text_color should be a valid color '  
'description, which consists of 6 hexadecimal '  
'characters representing the RGB values. '  
'Example: 44AA06')  
if abs(txt_lum - bg_lum) < 510/7.:  
# http://www.w3.org/TR/2000/WD-AERT-20000426#color-contrast recommends  
# a threshold of 125, but that is for normal text and too harsh for  
# big colored logos like line names, so we keep the original threshold  
# from r541 (but note that weight has shifted between RGB components).  
problems.InvalidValue('route_color', self.route_color,  
'The route_text_color and route_color should '  
'be set to contrasting colors, as they are used '  
'as the text and background color (respectively) '  
'for displaying route names. When left blank, '  
'route_text_color defaults to 000000 (black) and '  
'route_color defaults to FFFFFF (white). A common '  
'source of issues here is setting route_color to '  
'a dark color, while leaving route_text_color set '  
'to black. In this case, route_text_color should '  
'be set to a lighter color like FFFFFF to ensure '  
'a legible contrast between the two.',  
type=TYPE_WARNING)  
 
 
def SortListOfTripByTime(trips):  
trips.sort(key=Trip.GetStartTime)  
 
 
class StopTime(object):  
"""  
Represents a single stop of a trip. StopTime contains most of the columns  
from the stop_times.txt file. It does not contain trip_id, which is implied  
by the Trip used to access it.  
 
See the Google Transit Feed Specification for the semantic details.  
 
stop: A Stop object  
arrival_time: str in the form HH:MM:SS; readonly after __init__  
departure_time: str in the form HH:MM:SS; readonly after __init__  
arrival_secs: int number of seconds since midnight  
departure_secs: int number of seconds since midnight  
stop_headsign: str  
pickup_type: int  
drop_off_type: int  
shape_dist_traveled: float  
stop_id: str; readonly  
stop_time: The only time given for this stop. If present, it is used  
for both arrival and departure time.  
stop_sequence: int  
"""  
_REQUIRED_FIELD_NAMES = ['trip_id', 'arrival_time', 'departure_time',  
'stop_id', 'stop_sequence']  
_OPTIONAL_FIELD_NAMES = ['stop_headsign', 'pickup_type',  
'drop_off_type', 'shape_dist_traveled']  
_FIELD_NAMES = _REQUIRED_FIELD_NAMES + _OPTIONAL_FIELD_NAMES  
_SQL_FIELD_NAMES = ['trip_id', 'arrival_secs', 'departure_secs',  
'stop_id', 'stop_sequence', 'stop_headsign',  
'pickup_type', 'drop_off_type', 'shape_dist_traveled']  
 
__slots__ = ('arrival_secs', 'departure_secs', 'stop_headsign', 'stop',  
'stop_headsign', 'pickup_type', 'drop_off_type',  
'shape_dist_traveled', 'stop_sequence')  
def __init__(self, problems, stop,  
arrival_time=None, departure_time=None,  
stop_headsign=None, pickup_type=None, drop_off_type=None,  
shape_dist_traveled=None, arrival_secs=None,  
departure_secs=None, stop_time=None, stop_sequence=None):  
if stop_time != None:  
arrival_time = departure_time = stop_time  
 
if arrival_secs != None:  
self.arrival_secs = arrival_secs  
elif arrival_time in (None, ""):  
self.arrival_secs = None # Untimed  
arrival_time = None  
else:  
try:  
self.arrival_secs = TimeToSecondsSinceMidnight(arrival_time)  
except Error:  
problems.InvalidValue('arrival_time', arrival_time)  
self.arrival_secs = None  
 
if departure_secs != None:  
self.departure_secs = departure_secs  
elif departure_time in (None, ""):  
self.departure_secs = None  
departure_time = None  
else:  
try:  
self.departure_secs = TimeToSecondsSinceMidnight(departure_time)  
except Error:  
problems.InvalidValue('departure_time', departure_time)  
self.departure_secs = None  
 
if not isinstance(stop, Stop):  
# Not quite correct, but better than letting the problem propagate  
problems.InvalidValue('stop', stop)  
self.stop = stop  
self.stop_headsign = stop_headsign  
 
if pickup_type in (None, ""):  
self.pickup_type = None  
else:  
try:  
pickup_type = int(pickup_type)  
except ValueError:  
problems.InvalidValue('pickup_type', pickup_type)  
else:  
if pickup_type < 0 or pickup_type > 3:  
problems.InvalidValue('pickup_type', pickup_type)  
self.pickup_type = pickup_type  
 
if drop_off_type in (None, ""):  
self.drop_off_type = None  
else:  
try:  
drop_off_type = int(drop_off_type)  
except ValueError:  
problems.InvalidValue('drop_off_type', drop_off_type)  
else:  
if drop_off_type < 0 or drop_off_type > 3:  
problems.InvalidValue('drop_off_type', drop_off_type)  
self.drop_off_type = drop_off_type  
 
if (self.pickup_type == 1 and self.drop_off_type == 1 and  
self.arrival_secs == None and self.departure_secs == None):  
problems.OtherProblem('This stop time has a pickup_type and '  
'drop_off_type of 1, indicating that riders '  
'can\'t get on or off here. Since it doesn\'t '  
'define a timepoint either, this entry serves no '  
'purpose and should be excluded from the trip.',  
type=TYPE_WARNING)  
 
if ((self.arrival_secs != None) and (self.departure_secs != None) and  
(self.departure_secs < self.arrival_secs)):  
problems.InvalidValue('departure_time', departure_time,  
'The departure time at this stop (%s) is before '  
'the arrival time (%s). This is often caused by '  
'problems in the feed exporter\'s time conversion')  
 
# If the caller passed a valid arrival time but didn't attempt to pass a  
# departure time complain  
if (self.arrival_secs != None and  
self.departure_secs == None and departure_time == None):  
# self.departure_secs might be None because departure_time was invalid,  
# so we need to check both  
problems.MissingValue('departure_time',  
'arrival_time and departure_time should either '  
'both be provided or both be left blank. '  
'It\'s OK to set them both to the same value.')  
# If the caller passed a valid departure time but didn't attempt to pass a  
# arrival time complain  
if (self.departure_secs != None and  
self.arrival_secs == None and arrival_time == None):  
problems.MissingValue('arrival_time',  
'arrival_time and departure_time should either '  
'both be provided or both be left blank. '  
'It\'s OK to set them both to the same value.')  
 
if shape_dist_traveled in (None, ""):  
self.shape_dist_traveled = None  
else:  
try:  
self.shape_dist_traveled = float(shape_dist_traveled)  
except ValueError:  
problems.InvalidValue('shape_dist_traveled', shape_dist_traveled)  
 
if stop_sequence is not None:  
self.stop_sequence = stop_sequence  
 
def GetFieldValuesTuple(self, trip_id):  
"""Return a tuple that outputs a row of _FIELD_NAMES.  
 
trip must be provided because it is not stored in StopTime.  
"""  
result = []  
for fn in StopTime._FIELD_NAMES:  
if fn == 'trip_id':  
result.append(trip_id)  
else:  
result.append(getattr(self, fn) or '' )  
return tuple(result)  
 
def GetSqlValuesTuple(self, trip_id):  
result = []  
for fn in StopTime._SQL_FIELD_NAMES:  
if fn == 'trip_id':  
result.append(trip_id)  
else:  
# This might append None, which will be inserted into SQLite as NULL  
result.append(getattr(self, fn))  
return tuple(result)  
 
def GetTimeSecs(self):  
"""Return the first of arrival_secs and departure_secs that is not None.  
If both are None return None."""  
if self.arrival_secs != None:  
return self.arrival_secs  
elif self.departure_secs != None:  
return self.departure_secs  
else:  
return None  
 
def __getattr__(self, name):  
if name == 'stop_id':  
return self.stop.stop_id  
elif name == 'arrival_time':  
return (self.arrival_secs != None and  
FormatSecondsSinceMidnight(self.arrival_secs) or '')  
elif name == 'departure_time':  
return (self.departure_secs != None and  
FormatSecondsSinceMidnight(self.departure_secs) or '')  
elif name == 'shape_dist_traveled':  
return ''  
raise AttributeError(name)  
 
 
class Trip(GenericGTFSObject):  
_REQUIRED_FIELD_NAMES = ['route_id', 'service_id', 'trip_id']  
_FIELD_NAMES = _REQUIRED_FIELD_NAMES + [  
'trip_headsign', 'direction_id', 'block_id', 'shape_id'  
]  
_FIELD_NAMES_HEADWAY = ['trip_id', 'start_time', 'end_time', 'headway_secs']  
_TABLE_NAME= "trips"  
 
def __init__(self, headsign=None, service_period=None,  
route=None, trip_id=None, field_dict=None):  
self._schedule = None  
self._headways = [] # [(start_time, end_time, headway_secs)]  
if not field_dict:  
field_dict = {}  
if headsign is not None:  
field_dict['trip_headsign'] = headsign  
if route:  
field_dict['route_id'] = route.route_id  
if trip_id is not None:  
field_dict['trip_id'] = trip_id  
if service_period is not None:  
field_dict['service_id'] = service_period.service_id  
# Earlier versions of transitfeed.py assigned self.service_period here  
# and allowed the caller to set self.service_id. Schedule.Validate  
# checked the service_id attribute if it was assigned and changed it to a  
# service_period attribute. Now only the service_id attribute is used and  
# it is validated by Trip.Validate.  
if service_period is not None:  
# For backwards compatibility  
self.service_id = service_period.service_id  
self.__dict__.update(field_dict)  
 
def GetFieldValuesTuple(self):  
return [getattr(self, fn) or '' for fn in Trip._FIELD_NAMES]  
 
def AddStopTime(self, stop, problems=None, schedule=None, **kwargs):  
"""Add a stop to this trip. Stops must be added in the order visited.  
 
Args:  
stop: A Stop object  
kwargs: remaining keyword args passed to StopTime.__init__  
 
Returns:  
None  
"""  
if problems is None:  
# TODO: delete this branch when StopTime.__init__ doesn't need a  
# ProblemReporter  
problems = default_problem_reporter  
stoptime = StopTime(problems=problems, stop=stop, **kwargs)  
self.AddStopTimeObject(stoptime, schedule)  
 
def _AddStopTimeObjectUnordered(self, stoptime, schedule):  
"""Add StopTime object to this trip.  
 
The trip isn't checked for duplicate sequence numbers so it must be  
validated later."""  
cursor = schedule._connection.cursor()  
insert_query = "INSERT INTO stop_times (%s) VALUES (%s);" % (  
','.join(StopTime._SQL_FIELD_NAMES),  
','.join(['?'] * len(StopTime._SQL_FIELD_NAMES)))  
cursor = schedule._connection.cursor()  
cursor.execute(  
insert_query, stoptime.GetSqlValuesTuple(self.trip_id))  
 
def ReplaceStopTimeObject(self, stoptime, schedule=None):  
"""Replace a StopTime object from this trip with the given one.  
 
Keys the StopTime object to be replaced by trip_id, stop_sequence  
and stop_id as 'stoptime', with the object 'stoptime'.  
"""  
 
if schedule is None:  
schedule = self._schedule  
 
new_secs = stoptime.GetTimeSecs()  
cursor = schedule._connection.cursor()  
cursor.execute("DELETE FROM stop_times WHERE trip_id=? and "  
"stop_sequence=? and stop_id=?",  
(self.trip_id, stoptime.stop_sequence, stoptime.stop_id))  
if cursor.rowcount == 0:  
raise Error, 'Attempted replacement of StopTime object which does not exist'  
self._AddStopTimeObjectUnordered(stoptime, schedule)  
 
def AddStopTimeObject(self, stoptime, schedule=None, problems=None):  
"""Add a StopTime object to the end of this trip.  
 
Args:  
stoptime: A StopTime object. Should not be reused in multiple trips.  
schedule: Schedule object containing this trip which must be  
passed to Trip.__init__ or here  
problems: ProblemReporter object for validating the StopTime in its new  
home  
 
Returns:  
None  
"""  
if schedule is None:  
schedule = self._schedule  
if schedule is None:  
warnings.warn("No longer supported. _schedule attribute is used to get "  
"stop_times table", DeprecationWarning)  
if problems is None:  
problems = schedule.problem_reporter  
 
new_secs = stoptime.GetTimeSecs()  
cursor = schedule._connection.cursor()  
cursor.execute("SELECT max(stop_sequence), max(arrival_secs), "  
"max(departure_secs) FROM stop_times WHERE trip_id=?",  
(self.trip_id,))  
row = cursor.fetchone()  
if row[0] is None:  
# This is the first stop_time of the trip  
stoptime.stop_sequence = 1  
if new_secs == None:  
problems.OtherProblem(  
'No time for first StopTime of trip_id "%s"' % (self.trip_id,))  
else:  
stoptime.stop_sequence = row[0] + 1  
prev_secs = max(row[1], row[2])  
if new_secs != None and new_secs < prev_secs:  
problems.OtherProblem(  
'out of order stop time for stop_id=%s trip_id=%s %s < %s' %  
(EncodeUnicode(stoptime.stop_id), EncodeUnicode(self.trip_id),  
FormatSecondsSinceMidnight(new_secs),  
FormatSecondsSinceMidnight(prev_secs)))  
self._AddStopTimeObjectUnordered(stoptime, schedule)  
 
def GetTimeStops(self):  
"""Return a list of (arrival_secs, departure_secs, stop) tuples.  
 
Caution: arrival_secs and departure_secs may be 0, a false value meaning a  
stop at midnight or None, a false value meaning the stop is untimed."""  
return [(st.arrival_secs, st.departure_secs, st.stop) for st in  
self.GetStopTimes()]  
 
def GetCountStopTimes(self):  
"""Return the number of stops made by this trip."""  
cursor = self._schedule._connection.cursor()  
cursor.execute(  
'SELECT count(*) FROM stop_times WHERE trip_id=?', (self.trip_id,))  
return cursor.fetchone()[0]  
 
def GetTimeInterpolatedStops(self):  
"""Return a list of (secs, stoptime, is_timepoint) tuples.  
 
secs will always be an int. If the StopTime object does not have explict  
times this method guesses using distance. stoptime is a StopTime object and  
is_timepoint is a bool.  
 
Raises:  
ValueError if this trip does not have the times needed to interpolate  
"""  
rv = []  
 
stoptimes = self.GetStopTimes()  
# If there are no stoptimes [] is the correct return value but if the start  
# or end are missing times there is no correct return value.  
if not stoptimes:  
return []  
if (stoptimes[0].GetTimeSecs() is None or  
stoptimes[-1].GetTimeSecs() is None):  
raise ValueError("%s must have time at first and last stop" % (self))  
 
cur_timepoint = None  
next_timepoint = None  
distance_between_timepoints = 0  
distance_traveled_between_timepoints = 0  
 
for i, st in enumerate(stoptimes):  
if st.GetTimeSecs() != None:  
cur_timepoint = st  
distance_between_timepoints = 0  
distance_traveled_between_timepoints = 0  
if i + 1 < len(stoptimes):  
k = i + 1  
distance_between_timepoints += ApproximateDistanceBetweenStops(stoptimes[k-1].stop, stoptimes[k].stop)  
while stoptimes[k].GetTimeSecs() == None:  
k += 1  
distance_between_timepoints += ApproximateDistanceBetweenStops(stoptimes[k-1].stop, stoptimes[k].stop)  
next_timepoint = stoptimes[k]  
rv.append( (st.GetTimeSecs(), st, True) )  
else:  
distance_traveled_between_timepoints += ApproximateDistanceBetweenStops(stoptimes[i-1].stop, st.stop)  
distance_percent = distance_traveled_between_timepoints / distance_between_timepoints  
total_time = next_timepoint.GetTimeSecs() - cur_timepoint.GetTimeSecs()  
time_estimate = distance_percent * total_time + cur_timepoint.GetTimeSecs()  
rv.append( (int(round(time_estimate)), st, False) )  
 
return rv  
 
def ClearStopTimes(self):  
"""Remove all stop times from this trip.  
 
StopTime objects previously returned by GetStopTimes are unchanged but are  
no longer associated with this trip.  
"""  
cursor = self._schedule._connection.cursor()  
cursor.execute('DELETE FROM stop_times WHERE trip_id=?', (self.trip_id,))  
 
def GetStopTimes(self, problems=None):  
"""Return a sorted list of StopTime objects for this trip."""  
# In theory problems=None should be safe because data from database has been  
# validated. See comment in _LoadStopTimes for why this isn't always true.  
cursor = self._schedule._connection.cursor()  
cursor.execute(  
'SELECT arrival_secs,departure_secs,stop_headsign,pickup_type,'  
'drop_off_type,shape_dist_traveled,stop_id,stop_sequence FROM '  
'stop_times WHERE '  
'trip_id=? ORDER BY stop_sequence', (self.trip_id,))  
stop_times = []  
for row in cursor.fetchall():  
stop = self._schedule.GetStop(row[6])  
stop_times.append(StopTime(problems=problems, stop=stop, arrival_secs=row[0],  
departure_secs=row[1],  
stop_headsign=row[2],  
pickup_type=row[3],  
drop_off_type=row[4],  
shape_dist_traveled=row[5],  
stop_sequence=row[7]))  
return stop_times  
 
def GetHeadwayStopTimes(self, problems=None):  
"""Return a list of StopTime objects for each headway-based run.  
 
Returns:  
a list of list of StopTime objects. Each list of StopTime objects  
represents one run. If this trip doesn't have headways returns an empty  
list.  
"""  
stoptimes_list = [] # list of stoptime lists to be returned  
stoptime_pattern = self.GetStopTimes()  
first_secs = stoptime_pattern[0].arrival_secs # first time of the trip  
# for each start time of a headway run  
for run_secs in self.GetHeadwayStartTimes():  
# stop time list for a headway run  
stoptimes = []  
# go through the pattern and generate stoptimes  
for st in stoptime_pattern:  
arrival_secs, departure_secs = None, None # default value if the stoptime is not timepoint  
if st.arrival_secs != None:  
arrival_secs = st.arrival_secs - first_secs + run_secs  
if st.departure_secs != None:  
departure_secs = st.departure_secs - first_secs + run_secs  
# append stoptime  
stoptimes.append(StopTime(problems=problems, stop=st.stop,  
arrival_secs=arrival_secs,  
departure_secs=departure_secs,  
stop_headsign=st.stop_headsign,  
pickup_type=st.pickup_type,  
drop_off_type=st.drop_off_type,  
shape_dist_traveled=st.shape_dist_traveled,  
stop_sequence=st.stop_sequence))  
# add stoptimes to the stoptimes_list  
stoptimes_list.append ( stoptimes )  
return stoptimes_list  
 
def GetStartTime(self, problems=default_problem_reporter):  
"""Return the first time of the trip. TODO: For trips defined by frequency  
return the first time of the first trip."""  
cursor = self._schedule._connection.cursor()  
cursor.execute(  
'SELECT arrival_secs,departure_secs FROM stop_times WHERE '  
'trip_id=? ORDER BY stop_sequence LIMIT 1', (self.trip_id,))  
(arrival_secs, departure_secs) = cursor.fetchone()  
if arrival_secs != None:  
return arrival_secs  
elif departure_secs != None:  
return departure_secs  
else:  
problems.InvalidValue('departure_time', '',  
'The first stop_time in trip %s is missing '  
'times.' % self.trip_id)  
 
def GetHeadwayStartTimes(self):  
"""Return a list of start time for each headway-based run.  
 
Returns:  
a sorted list of seconds since midnight, the start time of each run. If  
this trip doesn't have headways returns an empty list."""  
start_times = []  
# for each headway period of the trip  
for start_secs, end_secs, headway_secs in self.GetHeadwayPeriodTuples():  
# reset run secs to the start of the timeframe  
run_secs = start_secs  
while run_secs < end_secs:  
start_times.append(run_secs)  
# increment current run secs by headway secs  
run_secs += headway_secs  
return start_times  
 
def GetEndTime(self, problems=default_problem_reporter):  
"""Return the last time of the trip. TODO: For trips defined by frequency  
return the last time of the last trip."""  
cursor = self._schedule._connection.cursor()  
cursor.execute(  
'SELECT arrival_secs,departure_secs FROM stop_times WHERE '  
'trip_id=? ORDER BY stop_sequence DESC LIMIT 1', (self.trip_id,))  
(arrival_secs, departure_secs) = cursor.fetchone()  
if departure_secs != None:  
return departure_secs  
elif arrival_secs != None:  
return arrival_secs  
else:  
problems.InvalidValue('arrival_time', '',  
'The last stop_time in trip %s is missing '  
'times.' % self.trip_id)  
 
def _GenerateStopTimesTuples(self):  
"""Generator for rows of the stop_times file"""  
stoptimes = self.GetStopTimes()  
for i, st in enumerate(stoptimes):  
yield st.GetFieldValuesTuple(self.trip_id)  
 
def GetStopTimesTuples(self):  
results = []  
for time_tuple in self._GenerateStopTimesTuples():  
results.append(time_tuple)  
return results  
 
def GetPattern(self):  
"""Return a tuple of Stop objects, in the order visited"""  
stoptimes = self.GetStopTimes()  
return tuple(st.stop for st in stoptimes)  
 
def AddHeadwayPeriod(self, start_time, end_time, headway_secs,  
problem_reporter=default_problem_reporter):  
"""Adds a period to this trip during which the vehicle travels  
at regular intervals (rather than specifying exact times for each stop).  
 
Args:  
start_time: The time at which this headway period starts, either in  
numerical seconds since midnight or as "HH:MM:SS" since midnight.  
end_time: The time at which this headway period ends, either in  
numerical seconds since midnight or as "HH:MM:SS" since midnight.  
This value should be larger than start_time.  
headway_secs: The amount of time, in seconds, between occurences of  
this trip.  
problem_reporter: Optional parameter that can be used to select  
how any errors in the other input parameters will be reported.  
Returns:  
None  
"""  
if start_time == None or start_time == '': # 0 is OK  
problem_reporter.MissingValue('start_time')  
return  
if isinstance(start_time, basestring):  
try:  
start_time = TimeToSecondsSinceMidnight(start_time)  
except Error:  
problem_reporter.InvalidValue('start_time', start_time)  
return  
elif start_time < 0:  
problem_reporter.InvalidValue('start_time', start_time)  
 
if end_time == None or end_time == '':  
problem_reporter.MissingValue('end_time')  
return  
if isinstance(end_time, basestring):  
try:  
end_time = TimeToSecondsSinceMidnight(end_time)  
except Error:  
problem_reporter.InvalidValue('end_time', end_time)  
return  
elif end_time < 0:  
problem_reporter.InvalidValue('end_time', end_time)  
return  
 
if not headway_secs:  
problem_reporter.MissingValue('headway_secs')  
return  
try:  
headway_secs = int(headway_secs)  
except ValueError:  
problem_reporter.InvalidValue('headway_secs', headway_secs)  
return  
 
if headway_secs <= 0:  
problem_reporter.InvalidValue('headway_secs', headway_secs)  
return  
 
if end_time <= start_time:  
problem_reporter.InvalidValue('end_time', end_time,  
'should be greater than start_time')  
 
self._headways.append((start_time, end_time, headway_secs))  
 
def ClearHeadwayPeriods(self):  
self._headways = []  
 
def _HeadwayOutputTuple(self, headway):  
return (self.trip_id,  
FormatSecondsSinceMidnight(headway[0]),  
FormatSecondsSinceMidnight(headway[1]),  
unicode(headway[2]))  
 
def GetHeadwayPeriodOutputTuples(self):  
tuples = []  
for headway in self._headways:  
tuples.append(self._HeadwayOutputTuple(headway))  
return tuples  
 
def GetHeadwayPeriodTuples(self):  
return self._headways  
 
def __getattr__(self, name):  
if name == 'service_period':  
assert self._schedule, "Must be in a schedule to get service_period"  
return self._schedule.GetServicePeriod(self.service_id)  
elif name == 'pattern_id':  
if '_pattern_id' not in self.__dict__:  
self.__dict__['_pattern_id'] = hash(self.GetPattern())  
return self.__dict__['_pattern_id']  
else:  
return GenericGTFSObject.__getattr__(self, name)  
 
def Validate(self, problems, validate_children=True):  
"""Validate attributes of this object.  
 
Check that this object has all required values set to a valid value without  
reference to the rest of the schedule. If the _schedule attribute is set  
then check that references such as route_id and service_id are correct.  
 
Args:  
problems: A ProblemReporter object  
validate_children: if True and the _schedule attribute is set than call  
ValidateChildren  
"""  
if IsEmpty(self.route_id):  
problems.MissingValue('route_id')  
if 'service_period' in self.__dict__:  
# Some tests assign to the service_period attribute. Patch up self before  
# proceeding with validation. See also comment in Trip.__init__.  
self.service_id = self.__dict__['service_period'].service_id  
del self.service_period  
if IsEmpty(self.service_id):  
problems.MissingValue('service_id')  
if IsEmpty(self.trip_id):  
problems.MissingValue('trip_id')  
if hasattr(self, 'direction_id') and (not IsEmpty(self.direction_id)) and \  
(self.direction_id != '0') and (self.direction_id != '1'):  
problems.InvalidValue('direction_id', self.direction_id,  
'direction_id must be "0" or "1"')  
if self._schedule:  
if self.shape_id and self.shape_id not in self._schedule._shapes:  
problems.InvalidValue('shape_id', self.shape_id)  
if self.route_id and self.route_id not in self._schedule.routes:  
problems.InvalidValue('route_id', self.route_id)  
if (self.service_id and  
self.service_id not in self._schedule.service_periods):  
problems.InvalidValue('service_id', self.service_id)  
 
if validate_children:  
self.ValidateChildren(problems)  
 
def ValidateChildren(self, problems):  
"""Validate StopTimes and headways of this trip."""  
assert self._schedule, "Trip must be in a schedule to ValidateChildren"  
# TODO: validate distance values in stop times (if applicable)  
cursor = self._schedule._connection.cursor()  
cursor.execute("SELECT COUNT(stop_sequence) AS a FROM stop_times "  
"WHERE trip_id=? GROUP BY stop_sequence HAVING a > 1",  
(self.trip_id,))  
for row in cursor:  
problems.InvalidValue('stop_sequence', row[0],  
'Duplicate stop_sequence in trip_id %s' %  
self.trip_id)  
 
stoptimes = self.GetStopTimes(problems)  
if stoptimes:  
if stoptimes[0].arrival_time is None and stoptimes[0].departure_time is None:  
problems.OtherProblem(  
'No time for start of trip_id "%s""' % (self.trip_id))  
if stoptimes[-1].arrival_time is None and stoptimes[-1].departure_time is None:  
problems.OtherProblem(  
'No time for end of trip_id "%s""' % (self.trip_id))  
 
# Sorts the stoptimes by sequence and then checks that the arrival time  
# for each time point is after the departure time of the previous.  
stoptimes.sort(key=lambda x: x.stop_sequence)  
prev_departure = 0  
prev_stop = None  
prev_distance = None  
try:  
route_type = self._schedule.GetRoute(self.route_id).route_type  
max_speed = Route._ROUTE_TYPES[route_type]['max_speed']  
except KeyError, e:  
# If route_type cannot be found, assume it is 0 (Tram) for checking  
# speeds between stops.  
max_speed = Route._ROUTE_TYPES[0]['max_speed']  
for timepoint in stoptimes:  
# Distance should be a nonnegative float number, so it should be  
# always larger than None.  
distance = timepoint.shape_dist_traveled  
if distance is not None:  
if distance > prev_distance and distance >= 0:  
prev_distance = distance  
else:  
if distance == prev_distance:  
type = TYPE_WARNING  
else:  
type = TYPE_ERROR  
problems.InvalidValue('stoptimes.shape_dist_traveled', distance,  
'For the trip %s the stop %s has shape_dist_traveled=%s, '  
'which should be larger than the previous ones. In this '  
'case, the previous distance was %s.' %  
(self.trip_id, timepoint.stop_id, distance, prev_distance),  
type=type)  
 
if timepoint.arrival_secs is not None:  
self._CheckSpeed(prev_stop, timepoint.stop, prev_departure,  
timepoint.arrival_secs, max_speed, problems)  
 
if timepoint.arrival_secs >= prev_departure:  
prev_departure = timepoint.departure_secs  
prev_stop = timepoint.stop  
else:  
problems.OtherProblem('Timetravel detected! Arrival time '  
'is before previous departure '  
'at sequence number %s in trip %s' %  
(timepoint.stop_sequence, self.trip_id))  
 
if self.shape_id and self.shape_id in self._schedule._shapes:  
shape = self._schedule.GetShape(self.shape_id)  
max_shape_dist = shape.max_distance  
st = stoptimes[-1]  
if (st.shape_dist_traveled and  
st.shape_dist_traveled > max_shape_dist):  
problems.OtherProblem(  
'In stop_times.txt, the stop with trip_id=%s and '  
'stop_sequence=%d has shape_dist_traveled=%f, which is larger '  
'than the max shape_dist_traveled=%f of the corresponding '  
'shape (shape_id=%s)' %  
(self.trip_id, st.stop_sequence, st.shape_dist_traveled,  
max_shape_dist, self.shape_id), type=TYPE_WARNING)  
 
# shape_dist_traveled is valid in shape if max_shape_dist larger than  
# 0.  
if max_shape_dist > 0:  
for st in stoptimes:  
if st.shape_dist_traveled is None:  
continue  
pt = shape.GetPointWithDistanceTraveled(st.shape_dist_traveled)  
if pt:  
stop = self._schedule.GetStop(st.stop_id)  
distance = ApproximateDistance(stop.stop_lat, stop.stop_lon,  
pt[0], pt[1])  
if distance > MAX_DISTANCE_FROM_STOP_TO_SHAPE:  
problems.StopTooFarFromShapeWithDistTraveled(  
self.trip_id, stop.stop_name, stop.stop_id, pt[2],  
self.shape_id, distance, MAX_DISTANCE_FROM_STOP_TO_SHAPE)  
 
# O(n^2), but we don't anticipate many headway periods per trip  
for headway_index, headway in enumerate(self._headways[0:-1]):  
for other in self._headways[headway_index + 1:]:  
if (other[0] < headway[1]) and (other[1] > headway[0]):  
problems.OtherProblem('Trip contains overlapping headway periods '  
'%s and %s' %  
(self._HeadwayOutputTuple(headway),  
self._HeadwayOutputTuple(other)))  
 
def _CheckSpeed(self, prev_stop, next_stop, depart_time,  
arrive_time, max_speed, problems):  
# Checks that the speed between two stops is not faster than max_speed  
if prev_stop != None:  
try:  
time_between_stops = arrive_time - depart_time  
except TypeError:  
return  
 
try:  
dist_between_stops = \  
ApproximateDistanceBetweenStops(next_stop, prev_stop)  
except TypeError, e:  
return  
 
if time_between_stops == 0:  
# HASTUS makes it hard to output GTFS with times to the nearest second;  
# it rounds times to the nearest minute. Therefore stop_times at the  
# same time ending in :00 are fairly common. These times off by no more  
# than 30 have not caused a problem. See  
# http://code.google.com/p/googletransitdatafeed/issues/detail?id=193  
# Show a warning if times are not rounded to the nearest minute or  
# distance is more than max_speed for one minute.  
if depart_time % 60 != 0 or dist_between_stops / 1000 * 60 > max_speed:  
problems.TooFastTravel(self.trip_id,  
prev_stop.stop_name,  
next_stop.stop_name,  
dist_between_stops,  
time_between_stops,  
speed=None,  
type=TYPE_WARNING)  
return  
# This needs floating point division for precision.  
speed_between_stops = ((float(dist_between_stops) / 1000) /  
(float(time_between_stops) / 3600))  
if speed_between_stops > max_speed:  
problems.TooFastTravel(self.trip_id,  
prev_stop.stop_name,  
next_stop.stop_name,  
dist_between_stops,  
time_between_stops,  
speed_between_stops,  
type=TYPE_WARNING)  
 
# TODO: move these into a separate file  
class ISO4217(object):  
"""Represents the set of currencies recognized by the ISO-4217 spec."""  
codes = { # map of alpha code to numerical code  
'AED': 784, 'AFN': 971, 'ALL': 8, 'AMD': 51, 'ANG': 532, 'AOA': 973,  
'ARS': 32, 'AUD': 36, 'AWG': 533, 'AZN': 944, 'BAM': 977, 'BBD': 52,  
'BDT': 50, 'BGN': 975, 'BHD': 48, 'BIF': 108, 'BMD': 60, 'BND': 96,  
'BOB': 68, 'BOV': 984, 'BRL': 986, 'BSD': 44, 'BTN': 64, 'BWP': 72,  
'BYR': 974, 'BZD': 84, 'CAD': 124, 'CDF': 976, 'CHE': 947, 'CHF': 756,  
'CHW': 948, 'CLF': 990, 'CLP': 152, 'CNY': 156, 'COP': 170, 'COU': 970,  
'CRC': 188, 'CUP': 192, 'CVE': 132, 'CYP': 196, 'CZK': 203, 'DJF': 262,  
'DKK': 208, 'DOP': 214, 'DZD': 12, 'EEK': 233, 'EGP': 818, 'ERN': 232,  
'ETB': 230, 'EUR': 978, 'FJD': 242, 'FKP': 238, 'GBP': 826, 'GEL': 981,  
'GHC': 288, 'GIP': 292, 'GMD': 270, 'GNF': 324, 'GTQ': 320, 'GYD': 328,  
'HKD': 344, 'HNL': 340, 'HRK': 191, 'HTG': 332, 'HUF': 348, 'IDR': 360,  
'ILS': 376, 'INR': 356, 'IQD': 368, 'IRR': 364, 'ISK': 352, 'JMD': 388,  
'JOD': 400, 'JPY': 392, 'KES': 404, 'KGS': 417, 'KHR': 116, 'KMF': 174,  
'KPW': 408, 'KRW': 410, 'KWD': 414, 'KYD': 136, 'KZT': 398, 'LAK': 418,  
'LBP': 422, 'LKR': 144, 'LRD': 430, 'LSL': 426, 'LTL': 440, 'LVL': 428,  
'LYD': 434, 'MAD': 504, 'MDL': 498, 'MGA': 969, 'MKD': 807, 'MMK': 104,  
'MNT': 496, 'MOP': 446, 'MRO': 478, 'MTL': 470, 'MUR': 480, 'MVR': 462,  
'MWK': 454, 'MXN': 484, 'MXV': 979, 'MYR': 458, 'MZN': 943, 'NAD': 516,  
'NGN': 566, 'NIO': 558, 'NOK': 578, 'NPR': 524, 'NZD': 554, 'OMR': 512,  
'PAB': 590, 'PEN': 604, 'PGK': 598, 'PHP': 608, 'PKR': 586, 'PLN': 985,  
'PYG': 600, 'QAR': 634, 'ROL': 642, 'RON': 946, 'RSD': 941, 'RUB': 643,  
'RWF': 646, 'SAR': 682, 'SBD': 90, 'SCR': 690, 'SDD': 736, 'SDG': 938,  
'SEK': 752, 'SGD': 702, 'SHP': 654, 'SKK': 703, 'SLL': 694, 'SOS': 706,  
'SRD': 968, 'STD': 678, 'SYP': 760, 'SZL': 748, 'THB': 764, 'TJS': 972,  
'TMM': 795, 'TND': 788, 'TOP': 776, 'TRY': 949, 'TTD': 780, 'TWD': 901,  
'TZS': 834, 'UAH': 980, 'UGX': 800, 'USD': 840, 'USN': 997, 'USS': 998,  
'UYU': 858, 'UZS': 860, 'VEB': 862, 'VND': 704, 'VUV': 548, 'WST': 882,  
'XAF': 950, 'XAG': 961, 'XAU': 959, 'XBA': 955, 'XBB': 956, 'XBC': 957,  
'XBD': 958, 'XCD': 951, 'XDR': 960, 'XFO': None, 'XFU': None, 'XOF': 952,  
'XPD': 964, 'XPF': 953, 'XPT': 962, 'XTS': 963, 'XXX': 999, 'YER': 886,  
'ZAR': 710, 'ZMK': 894, 'ZWD': 716,  
}  
 
 
class Fare(object):  
"""Represents a fare type."""  
_REQUIRED_FIELD_NAMES = ['fare_id', 'price', 'currency_type',  
'payment_method', 'transfers']  
_FIELD_NAMES = _REQUIRED_FIELD_NAMES + ['transfer_duration']  
 
def __init__(self,  
fare_id=None, price=None, currency_type=None,  
payment_method=None, transfers=None, transfer_duration=None,  
field_list=None):  
self.rules = []  
(self.fare_id, self.price, self.currency_type, self.payment_method,  
self.transfers, self.transfer_duration) = \  
(fare_id, price, currency_type, payment_method,  
transfers, transfer_duration)  
if field_list:  
(self.fare_id, self.price, self.currency_type, self.payment_method,  
self.transfers, self.transfer_duration) = field_list  
 
try:  
self.price = float(self.price)  
except (TypeError, ValueError):  
pass  
try:  
self.payment_method = int(self.payment_method)  
except (TypeError, ValueError):  
pass  
if self.transfers == None or self.transfers == "":  
self.transfers = None  
else:  
try:  
self.transfers = int(self.transfers)  
except (TypeError, ValueError):  
pass  
if self.transfer_duration == None or self.transfer_duration == "":  
self.transfer_duration = None  
else:  
try:  
self.transfer_duration = int(self.transfer_duration)  
except (TypeError, ValueError):  
pass  
 
def GetFareRuleList(self):  
return self.rules  
 
def ClearFareRules(self):  
self.rules = []  
 
def GetFieldValuesTuple(self):  
return [getattr(self, fn) for fn in Fare._FIELD_NAMES]  
 
def __getitem__(self, name):  
return getattr(self, name)  
 
def __eq__(self, other):  
if not other:  
return False  
 
if id(self) == id(other):  
return True  
 
if self.GetFieldValuesTuple() != other.GetFieldValuesTuple():  
return False  
 
self_rules = [r.GetFieldValuesTuple() for r in self.GetFareRuleList()]  
self_rules.sort()  
other_rules = [r.GetFieldValuesTuple() for r in other.GetFareRuleList()]  
other_rules.sort()  
return self_rules == other_rules  
 
def __ne__(self, other):  
return not self.__eq__(other)  
 
def Validate(self, problems=default_problem_reporter):  
if IsEmpty(self.fare_id):  
problems.MissingValue("fare_id")  
 
if self.price == None:  
problems.MissingValue("price")  
elif not isinstance(self.price, float) and not isinstance(self.price, int):  
problems.InvalidValue("price", self.price)  
elif self.price < 0:  
problems.InvalidValue("price", self.price)  
 
if IsEmpty(self.currency_type):  
problems.MissingValue("currency_type")  
elif self.currency_type not in ISO4217.codes:  
problems.InvalidValue("currency_type", self.currency_type)  
 
if self.payment_method == "" or self.payment_method == None:  
problems.MissingValue("payment_method")  
elif (not isinstance(self.payment_method, int) or  
self.payment_method not in range(0, 2)):  
problems.InvalidValue("payment_method", self.payment_method)  
 
if not ((self.transfers == None) or  
(isinstance(self.transfers, int) and  
self.transfers in range(0, 3))):  
problems.InvalidValue("transfers", self.transfers)  
 
if ((self.transfer_duration != None) and  
not isinstance(self.transfer_duration, int)):  
problems.InvalidValue("transfer_duration", self.transfer_duration)  
if self.transfer_duration and (self.transfer_duration < 0):  
problems.InvalidValue("transfer_duration", self.transfer_duration)  
if (self.transfer_duration and (self.transfer_duration > 0) and  
self.transfers == 0):  
problems.InvalidValue("transfer_duration", self.transfer_duration,  
"can't have a nonzero transfer_duration for "  
"a fare that doesn't allow transfers!")  
 
 
class FareRule(object):  
"""This class represents a rule that determines which itineraries a  
fare rule applies to."""  
_REQUIRED_FIELD_NAMES = ['fare_id']  
_FIELD_NAMES = _REQUIRED_FIELD_NAMES + ['route_id',  
'origin_id', 'destination_id',  
'contains_id']  
 
def __init__(self, fare_id=None, route_id=None,  
origin_id=None, destination_id=None, contains_id=None,  
field_list=None):  
(self.fare_id, self.route_id, self.origin_id, self.destination_id,  
self.contains_id) = \  
(fare_id, route_id, origin_id, destination_id, contains_id)  
if field_list:  
(self.fare_id, self.route_id, self.origin_id, self.destination_id,  
self.contains_id) = field_list  
 
# canonicalize non-content values as None  
if not self.route_id:  
self.route_id = None  
if not self.origin_id:  
self.origin_id = None  
if not self.destination_id:  
self.destination_id = None  
if not self.contains_id:  
self.contains_id = None  
 
def GetFieldValuesTuple(self):  
return [getattr(self, fn) for fn in FareRule._FIELD_NAMES]  
 
def __getitem__(self, name):  
return getattr(self, name)  
 
def __eq__(self, other):  
if not other:  
return False  
 
if id(self) == id(other):  
return True  
 
return self.GetFieldValuesTuple() == other.GetFieldValuesTuple()  
 
def __ne__(self, other):  
return not self.__eq__(other)  
 
 
class Shape(object):  
"""This class represents a geographic shape that corresponds to the route  
taken by one or more Trips."""  
_REQUIRED_FIELD_NAMES = ['shape_id', 'shape_pt_lat', 'shape_pt_lon',  
'shape_pt_sequence']  
_FIELD_NAMES = _REQUIRED_FIELD_NAMES + ['shape_dist_traveled']  
def __init__(self, shape_id):  
# List of shape point tuple (lat, lng, shape_dist_traveled), where lat and  
# lon is the location of the shape point, and shape_dist_traveled is an  
# increasing metric representing the distance traveled along the shape.  
self.points = []  
# An ID that uniquely identifies a shape in the dataset.  
self.shape_id = shape_id  
# The max shape_dist_traveled of shape points in this shape.  
self.max_distance = 0  
# List of shape_dist_traveled of each shape point.  
self.distance = []  
 
def AddPoint(self, lat, lon, distance=None,  
problems=default_problem_reporter):  
 
try:  
lat = float(lat)  
if abs(lat) > 90.0:  
problems.InvalidValue('shape_pt_lat', lat)  
return  
except (TypeError, ValueError):  
problems.InvalidValue('shape_pt_lat', lat)  
return  
 
try:  
lon = float(lon)  
if abs(lon) > 180.0:  
problems.InvalidValue('shape_pt_lon', lon)  
return  
except (TypeError, ValueError):  
problems.InvalidValue('shape_pt_lon', lon)  
return  
 
if (abs(lat) < 1.0) and (abs(lon) < 1.0):  
problems.InvalidValue('shape_pt_lat', lat,  
'Point location too close to 0, 0, which means '  
'that it\'s probably an incorrect location.',  
type=TYPE_WARNING)  
return  
 
if distance == '': # canonicalizing empty string to None for comparison  
distance = None  
 
if distance != None:  
try:  
distance = float(distance)  
if (distance < self.max_distance and not  
(len(self.points) == 0 and distance == 0)): # first one can be 0  
problems.InvalidValue('shape_dist_traveled', distance,  
'Each subsequent point in a shape should '  
'have a distance value that\'s at least as '  
'large as the previous ones. In this case, '  
'the previous distance was %f.' %  
self.max_distance)  
return  
else:  
self.max_distance = distance  
self.distance.append(distance)  
except (TypeError, ValueError):  
problems.InvalidValue('shape_dist_traveled', distance,  
'This value should be a positive number.')  
return  
 
self.points.append((lat, lon, distance))  
 
def ClearPoints(self):  
self.points = []  
 
def __eq__(self, other):  
if not other:  
return False  
 
if id(self) == id(other):  
return True  
 
return self.points == other.points  
 
def __ne__(self, other):  
return not self.__eq__(other)  
 
def __repr__(self):  
return "<Shape %s>" % self.__dict__  
 
def Validate(self, problems=default_problem_reporter):  
if IsEmpty(self.shape_id):  
problems.MissingValue('shape_id')  
 
if not self.points:  
problems.OtherProblem('The shape with shape_id "%s" contains no points.' %  
self.shape_id, type=TYPE_WARNING)  
 
def GetPointWithDistanceTraveled(self, shape_dist_traveled):  
"""Returns a point on the shape polyline with the input shape_dist_traveled.  
 
Args:  
shape_dist_traveled: The input shape_dist_traveled.  
 
Returns:  
The shape point as a tuple (lat, lng, shape_dist_traveled), where lat and  
lng is the location of the shape point, and shape_dist_traveled is an  
increasing metric representing the distance traveled along the shape.  
Returns None if there is data error in shape.  
"""  
if not self.distance:  
return None  
if shape_dist_traveled <= self.distance[0]:  
return self.points[0]  
if shape_dist_traveled >= self.distance[-1]:  
return self.points[-1]  
 
index = bisect.bisect(self.distance, shape_dist_traveled)  
(lat0, lng0, dist0) = self.points[index - 1]  
(lat1, lng1, dist1) = self.points[index]  
 
# Interpolate if shape_dist_traveled does not equal to any of the point  
# in shape segment.  
# (lat0, lng0) (lat, lng) (lat1, lng1)  
# -----|--------------------|---------------------|------  
# dist0 shape_dist_traveled dist1  
# \------- ca --------/ \-------- bc -------/  
# \----------------- ba ------------------/  
ca = shape_dist_traveled - dist0  
bc = dist1 - shape_dist_traveled  
ba = bc + ca  
if ba == 0:  
# This only happens when there's data error in shapes and should have been  
# catched before. Check to avoid crash.  
return None  
# This won't work crossing longitude 180 and is only an approximation which  
# works well for short distance.  
lat = (lat1 * ca + lat0 * bc) / ba  
lng = (lng1 * ca + lng0 * bc) / ba  
return (lat, lng, shape_dist_traveled)  
 
 
class ISO639(object):  
# Set of all the 2-letter ISO 639-1 language codes.  
codes_2letter = set([  
'aa', 'ab', 'ae', 'af', 'ak', 'am', 'an', 'ar', 'as', 'av', 'ay', 'az',  
'ba', 'be', 'bg', 'bh', 'bi', 'bm', 'bn', 'bo', 'br', 'bs', 'ca', 'ce',  
'ch', 'co', 'cr', 'cs', 'cu', 'cv', 'cy', 'da', 'de', 'dv', 'dz', 'ee',  
'el', 'en', 'eo', 'es', 'et', 'eu', 'fa', 'ff', 'fi', 'fj', 'fo', 'fr',  
'fy', 'ga', 'gd', 'gl', 'gn', 'gu', 'gv', 'ha', 'he', 'hi', 'ho', 'hr',  
'ht', 'hu', 'hy', 'hz', 'ia', 'id', 'ie', 'ig', 'ii', 'ik', 'io', 'is',  
'it', 'iu', 'ja', 'jv', 'ka', 'kg', 'ki', 'kj', 'kk', 'kl', 'km', 'kn',  
'ko', 'kr', 'ks', 'ku', 'kv', 'kw', 'ky', 'la', 'lb', 'lg', 'li', 'ln',  
'lo', 'lt', 'lu', 'lv', 'mg', 'mh', 'mi', 'mk', 'ml', 'mn', 'mo', 'mr',  
'ms', 'mt', 'my', 'na', 'nb', 'nd', 'ne', 'ng', 'nl', 'nn', 'no', 'nr',  
'nv', 'ny', 'oc', 'oj', 'om', 'or', 'os', 'pa', 'pi', 'pl', 'ps', 'pt',  
'qu', 'rm', 'rn', 'ro', 'ru', 'rw', 'sa', 'sc', 'sd', 'se', 'sg', 'si',  
'sk', 'sl', 'sm', 'sn', 'so', 'sq', 'sr', 'ss', 'st', 'su', 'sv', 'sw',  
'ta', 'te', 'tg', 'th', 'ti', 'tk', 'tl', 'tn', 'to', 'tr', 'ts', 'tt',  
'tw', 'ty', 'ug', 'uk', 'ur', 'uz', 've', 'vi', 'vo', 'wa', 'wo', 'xh',  
'yi', 'yo', 'za', 'zh', 'zu',  
])  
 
 
class Agency(GenericGTFSObject):  
"""Represents an agency in a schedule.  
 
Callers may assign arbitrary values to instance attributes. __init__ makes no  
attempt at validating the attributes. Call Validate() to check that  
attributes are valid and the agency object is consistent with itself.  
 
Attributes:  
All attributes are strings.  
"""  
_REQUIRED_FIELD_NAMES = ['agency_name', 'agency_url', 'agency_timezone']  
_FIELD_NAMES = _REQUIRED_FIELD_NAMES + ['agency_id', 'agency_lang',  
'agency_phone']  
_TABLE_NAME = 'agency'  
 
def __init__(self, name=None, url=None, timezone=None, id=None,  
field_dict=None, lang=None, **kwargs):  
"""Initialize a new Agency object.  
 
Args:  
field_dict: A dictionary mapping attribute name to unicode string  
name: a string, ignored when field_dict is present  
url: a string, ignored when field_dict is present  
timezone: a string, ignored when field_dict is present  
id: a string, ignored when field_dict is present  
kwargs: arbitrary keyword arguments may be used to add attributes to the  
new object, ignored when field_dict is present  
"""  
self._schedule = None  
 
if not field_dict:  
if name:  
kwargs['agency_name'] = name  
if url:  
kwargs['agency_url'] = url  
if timezone:  
kwargs['agency_timezone'] = timezone  
if id:  
kwargs['agency_id'] = id  
if lang:  
kwargs['agency_lang'] = lang  
field_dict = kwargs  
 
self.__dict__.update(field_dict)  
 
def Validate(self, problems=default_problem_reporter):  
"""Validate attribute values and this object's internal consistency.  
 
Returns:  
True iff all validation checks passed.  
"""  
found_problem = False  
for required in Agency._REQUIRED_FIELD_NAMES:  
if IsEmpty(getattr(self, required, None)):  
problems.MissingValue(required)  
found_problem = True  
 
if self.agency_url and not IsValidURL(self.agency_url):  
problems.InvalidValue('agency_url', self.agency_url)  
found_problem = True  
 
if (not IsEmpty(self.agency_lang) and  
self.agency_lang.lower() not in ISO639.codes_2letter):  
problems.InvalidValue('agency_lang', self.agency_lang)  
found_problem = True  
 
try:  
import pytz  
if self.agency_timezone not in pytz.common_timezones:  
problems.InvalidValue(  
'agency_timezone',  
self.agency_timezone,  
'"%s" is not a common timezone name according to pytz version %s' %  
(self.agency_timezone, pytz.VERSION))  
found_problem = True  
except ImportError: # no pytz  
print ("Timezone not checked "  
"(install pytz package for timezone validation)")  
return not found_problem  
 
 
class Transfer(object):  
"""Represents a transfer in a schedule"""  
_REQUIRED_FIELD_NAMES = ['from_stop_id', 'to_stop_id', 'transfer_type']  
_FIELD_NAMES = _REQUIRED_FIELD_NAMES + ['min_transfer_time']  
 
def __init__(self, schedule=None, from_stop_id=None, to_stop_id=None, transfer_type=None,  
min_transfer_time=None, field_dict=None):  
if schedule is not None:  
self._schedule = weakref.proxy(schedule) # See weakref comment at top  
else:  
self._schedule = None  
if field_dict:  
self.__dict__.update(field_dict)  
else:  
self.from_stop_id = from_stop_id  
self.to_stop_id = to_stop_id  
self.transfer_type = transfer_type  
self.min_transfer_time = min_transfer_time  
 
if getattr(self, 'transfer_type', None) in ("", None):  
# Use the default, recommended transfer, if attribute is not set or blank  
self.transfer_type = 0  
else:  
try:  
self.transfer_type = NonNegIntStringToInt(self.transfer_type)  
except (TypeError, ValueError):  
pass  
 
if hasattr(self, 'min_transfer_time'):  
try:  
self.min_transfer_time = NonNegIntStringToInt(self.min_transfer_time)  
except (TypeError, ValueError):  
pass  
else:  
self.min_transfer_time = None  
 
def GetFieldValuesTuple(self):  
return [getattr(self, fn) for fn in Transfer._FIELD_NAMES]  
 
def __getitem__(self, name):  
return getattr(self, name)  
 
def __eq__(self, other):  
if not other:  
return False  
 
if id(self) == id(other):  
return True  
 
return self.GetFieldValuesTuple() == other.GetFieldValuesTuple()  
 
def __ne__(self, other):  
return not self.__eq__(other)  
 
def __repr__(self):  
return "<Transfer %s>" % self.__dict__  
 
def Validate(self, problems=default_problem_reporter):  
if IsEmpty(self.from_stop_id):  
problems.MissingValue('from_stop_id')  
elif self._schedule:  
if self.from_stop_id not in self._schedule.stops.keys():  
problems.InvalidValue('from_stop_id', self.from_stop_id)  
 
if IsEmpty(self.to_stop_id):  
problems.MissingValue('to_stop_id')  
elif self._schedule:  
if self.to_stop_id not in self._schedule.stops.keys():  
problems.InvalidValue('to_stop_id', self.to_stop_id)  
 
if not IsEmpty(self.transfer_type):  
if (not isinstance(self.transfer_type, int)) or \  
(self.transfer_type not in range(0, 4)):  
problems.InvalidValue('transfer_type', self.transfer_type)  
 
if not IsEmpty(self.min_transfer_time):  
if (not isinstance(self.min_transfer_time, int)) or \  
self.min_transfer_time < 0:  
problems.InvalidValue('min_transfer_time', self.min_transfer_time)  
 
 
class ServicePeriod(object):  
"""Represents a service, which identifies a set of dates when one or more  
trips operate."""  
_DAYS_OF_WEEK = [  
'monday', 'tuesday', 'wednesday', 'thursday', 'friday',  
'saturday', 'sunday'  
]  
_FIELD_NAMES_REQUIRED = [  
'service_id', 'start_date', 'end_date'  
] + _DAYS_OF_WEEK  
_FIELD_NAMES = _FIELD_NAMES_REQUIRED # no optional fields in this one  
_FIELD_NAMES_CALENDAR_DATES = ['service_id', 'date', 'exception_type']  
 
def __init__(self, id=None, field_list=None):  
self.original_day_values = []  
if field_list:  
self.service_id = field_list[self._FIELD_NAMES.index('service_id')]  
self.day_of_week = [False] * len(self._DAYS_OF_WEEK)  
 
for day in self._DAYS_OF_WEEK:  
value = field_list[self._FIELD_NAMES.index(day)] or '' # can be None  
self.original_day_values += [value.strip()]  
self.day_of_week[self._DAYS_OF_WEEK.index(day)] = (value == u'1')  
 
self.start_date = field_list[self._FIELD_NAMES.index('start_date')]  
self.end_date = field_list[self._FIELD_NAMES.index('end_date')]  
else:  
self.service_id = id  
self.day_of_week = [False] * 7  
self.start_date = None  
self.end_date = None  
self.date_exceptions = {} # Map from 'YYYYMMDD' to 1 (add) or 2 (remove)  
 
def _IsValidDate(self, date):  
if re.match('^\d{8}$', date) == None:  
return False  
 
try:  
time.strptime(date, "%Y%m%d")  
return True  
except ValueError:  
return False  
 
def GetDateRange(self):  
"""Return the range over which this ServicePeriod is valid.  
 
The range includes exception dates that add service outside of  
(start_date, end_date), but doesn't shrink the range if exception  
dates take away service at the edges of the range.  
 
Returns:  
A tuple of "YYYYMMDD" strings, (start date, end date) or (None, None) if  
no dates have been given.  
"""  
start = self.start_date  
end = self.end_date  
 
for date in self.date_exceptions:  
if self.date_exceptions[date] == 2:  
continue  
if not start or (date < start):  
start = date  
if not end or (date > end):  
end = date  
if start is None:  
start = end  
elif end is None:  
end = start  
# If start and end are None we did a little harmless shuffling  
return (start, end)  
 
def GetCalendarFieldValuesTuple(self):  
"""Return the tuple of calendar.txt values or None if this ServicePeriod  
should not be in calendar.txt ."""  
if self.start_date and self.end_date:  
return [getattr(self, fn) for fn in ServicePeriod._FIELD_NAMES]  
 
def GenerateCalendarDatesFieldValuesTuples(self):  
"""Generates tuples of calendar_dates.txt values. Yield zero tuples if  
this ServicePeriod should not be in calendar_dates.txt ."""  
for date, exception_type in self.date_exceptions.items():  
yield (self.service_id, date, unicode(exception_type))  
 
def GetCalendarDatesFieldValuesTuples(self):  
"""Return a list of date execeptions"""  
result = []  
for date_tuple in self.GenerateCalendarDatesFieldValuesTuples():  
result.append(date_tuple)  
result.sort() # helps with __eq__  
return result  
 
def SetDateHasService(self, date, has_service=True, problems=None):  
if date in self.date_exceptions and problems:  
problems.DuplicateID(('service_id', 'date'),  
(self.service_id, date),  
type=TYPE_WARNING)  
self.date_exceptions[date] = has_service and 1 or 2  
 
def ResetDateToNormalService(self, date):  
if date in self.date_exceptions:  
del self.date_exceptions[date]  
 
def SetStartDate(self, start_date):  
"""Set the first day of service as a string in YYYYMMDD format"""  
self.start_date = start_date  
 
def SetEndDate(self, end_date):  
"""Set the last day of service as a string in YYYYMMDD format"""  
self.end_date = end_date  
 
def SetDayOfWeekHasService(self, dow, has_service=True):  
"""Set service as running (or not) on a day of the week. By default the  
service does not run on any days.  
 
Args:  
dow: 0 for Monday through 6 for Sunday  
has_service: True if this service operates on dow, False if it does not.  
 
Returns:  
None  
"""  
assert(dow >= 0 and dow < 7)  
self.day_of_week[dow] = has_service  
 
def SetWeekdayService(self, has_service=True):  
"""Set service as running (or not) on all of Monday through Friday."""  
for i in range(0, 5):  
self.SetDayOfWeekHasService(i, has_service)  
 
def SetWeekendService(self, has_service=True):  
"""Set service as running (or not) on Saturday and Sunday."""  
self.SetDayOfWeekHasService(5, has_service)  
self.SetDayOfWeekHasService(6, has_service)  
 
def SetServiceId(self, service_id):  
"""Set the service_id for this schedule. Generally the default will  
suffice so you won't need to call this method."""  
self.service_id = service_id  
 
def IsActiveOn(self, date, date_object=None):  
"""Test if this service period is active on a date.  
 
Args:  
date: a string of form "YYYYMMDD"  
date_object: a date object representing the same date as date.  
This parameter is optional, and present only for performance  
reasons.  
If the caller constructs the date string from a date object  
that date object can be passed directly, thus avoiding the  
costly conversion from string to date object.  
 
Returns:  
True iff this service is active on date.  
"""  
if date in self.date_exceptions:  
if self.date_exceptions[date] == 1:  
return True  
else:  
return False  
if (self.start_date and self.end_date and self.start_date <= date and  
date <= self.end_date):  
if date_object is None:  
date_object = DateStringToDateObject(date)  
return self.day_of_week[date_object.weekday()]  
return False  
 
def ActiveDates(self):  
"""Return dates this service period is active as a list of "YYYYMMDD"."""  
(earliest, latest) = self.GetDateRange()  
if earliest is None:  
return []  
dates = []  
date_it = DateStringToDateObject(earliest)  
date_end = DateStringToDateObject(latest)  
delta = datetime.timedelta(days=1)  
while date_it <= date_end:  
date_it_string = date_it.strftime("%Y%m%d")  
if self.IsActiveOn(date_it_string, date_it):  
dates.append(date_it_string)  
date_it = date_it + delta  
return dates  
 
def __getattr__(self, name):  
try:  
# Return 1 if value in day_of_week is True, 0 otherwise  
return (self.day_of_week[ServicePeriod._DAYS_OF_WEEK.index(name)]  
and 1 or 0)  
except KeyError:  
pass  
except ValueError: # not a day of the week  
pass  
raise AttributeError(name)  
 
def __getitem__(self, name):  
return getattr(self, name)  
 
def __eq__(self, other):  
if not other:  
return False  
 
if id(self) == id(other):  
return True  
 
if (self.GetCalendarFieldValuesTuple() !=  
other.GetCalendarFieldValuesTuple()):  
return False  
 
if (self.GetCalendarDatesFieldValuesTuples() !=  
other.GetCalendarDatesFieldValuesTuples()):  
return False  
 
return True  
 
def __ne__(self, other):  
return not self.__eq__(other)  
 
def Validate(self, problems=default_problem_reporter):  
if IsEmpty(self.service_id):  
problems.MissingValue('service_id')  
# self.start_date/self.end_date is None in 3 cases:  
# ServicePeriod created by loader and  
# 1a) self.service_id wasn't in calendar.txt  
# 1b) calendar.txt didn't have a start_date/end_date column  
# ServicePeriod created directly and  
# 2) start_date/end_date wasn't set  
# In case 1a no problem is reported. In case 1b the missing required column  
# generates an error in _ReadCSV so this method should not report another  
# problem. There is no way to tell the difference between cases 1b and 2  
# so case 2 is ignored because making the feedvalidator pretty is more  
# important than perfect validation when an API users makes a mistake.  
start_date = None  
if self.start_date is not None:  
if IsEmpty(self.start_date):  
problems.MissingValue('start_date')  
elif self._IsValidDate(self.start_date):  
start_date = self.start_date  
else:  
problems.InvalidValue('start_date', self.start_date)  
end_date = None  
if self.end_date is not None:  
if IsEmpty(self.end_date):  
problems.MissingValue('end_date')  
elif self._IsValidDate(self.end_date):  
end_date = self.end_date  
else:  
problems.InvalidValue('end_date', self.end_date)  
if start_date and end_date and end_date < start_date:  
problems.InvalidValue('end_date', end_date,  
'end_date of %s is earlier than '  
'start_date of "%s"' %  
(end_date, start_date))  
if self.original_day_values:  
index = 0  
for value in self.original_day_values:  
column_name = self._DAYS_OF_WEEK[index]  
if IsEmpty(value):  
problems.MissingValue(column_name)  
elif (value != u'0') and (value != '1'):  
problems.InvalidValue(column_name, value)  
index += 1  
if (True not in self.day_of_week and  
1 not in self.date_exceptions.values()):  
problems.OtherProblem('Service period with service_id "%s" '  
'doesn\'t have service on any days '  
'of the week.' % self.service_id,  
type=TYPE_WARNING)  
for date in self.date_exceptions:  
if not self._IsValidDate(date):  
problems.InvalidValue('date', date)  
 
 
class CsvUnicodeWriter:  
"""  
Create a wrapper around a csv writer object which can safely write unicode  
values. Passes all arguments to csv.writer.  
"""  
def __init__(self, *args, **kwargs):  
self.writer = csv.writer(*args, **kwargs)  
 
def writerow(self, row):  
"""Write row to the csv file. Any unicode strings in row are encoded as  
utf-8."""  
encoded_row = []  
for s in row:  
if isinstance(s, unicode):  
encoded_row.append(s.encode("utf-8"))  
else:  
encoded_row.append(s)  
try:  
self.writer.writerow(encoded_row)  
except Exception, e:  
print 'error writing %s as %s' % (row, encoded_row)  
raise e  
 
def writerows(self, rows):  
"""Write rows to the csv file. Any unicode strings in rows are encoded as  
utf-8."""  
for row in rows:  
self.writerow(row)  
 
def __getattr__(self, name):  
return getattr(self.writer, name)  
 
 
class Schedule:  
"""Represents a Schedule, a collection of stops, routes, trips and  
an agency. This is the main class for this module."""  
 
def __init__(self, problem_reporter=default_problem_reporter,  
memory_db=True, check_duplicate_trips=False):  
# Map from table name to list of columns present in this schedule  
self._table_columns = {}  
 
self._agencies = {}  
self.stops = {}  
self.routes = {}  
self.trips = {}  
self.service_periods = {}  
self.fares = {}  
self.fare_zones = {} # represents the set of all known fare zones  
self._shapes = {} # shape_id to Shape  
self._transfers = [] # list of transfers  
self._default_service_period = None  
self._default_agency = None  
self.problem_reporter = problem_reporter  
self._check_duplicate_trips = check_duplicate_trips  
self.ConnectDb(memory_db)  
 
def AddTableColumn(self, table, column):  
"""Add column to table if it is not already there."""  
if column not in self._table_columns[table]:  
self._table_columns[table].append(column)  
 
def AddTableColumns(self, table, columns):  
"""Add columns to table if they are not already there.  
 
Args:  
table: table name as a string  
columns: an iterable of column names"""  
table_columns = self._table_columns.setdefault(table, [])  
for attr in columns:  
if attr not in table_columns:  
table_columns.append(attr)  
 
def GetTableColumns(self, table):  
"""Return list of columns in a table."""  
return self._table_columns[table]  
 
def __del__(self):  
if hasattr(self, '_temp_db_filename'):  
os.remove(self._temp_db_filename)  
 
def ConnectDb(self, memory_db):  
if memory_db:  
self._connection = sqlite.connect(":memory:")  
else:  
try:  
self._temp_db_file = tempfile.NamedTemporaryFile()  
self._connection = sqlite.connect(self._temp_db_file.name)  
except sqlite.OperationalError:  
# Windows won't let a file be opened twice. mkstemp does not remove the  
# file when all handles to it are closed.  
self._temp_db_file = None  
(fd, self._temp_db_filename) = tempfile.mkstemp(".db")  
os.close(fd)  
self._connection = sqlite.connect(self._temp_db_filename)  
 
cursor = self._connection.cursor()  
cursor.execute("""CREATE TABLE stop_times (  
trip_id CHAR(50),  
arrival_secs INTEGER,  
departure_secs INTEGER,  
stop_id CHAR(50),  
stop_sequence INTEGER,  
stop_headsign VAR CHAR(100),  
pickup_type INTEGER,  
drop_off_type INTEGER,  
shape_dist_traveled FLOAT);""")  
cursor.execute("""CREATE INDEX trip_index ON stop_times (trip_id);""")  
cursor.execute("""CREATE INDEX stop_index ON stop_times (stop_id);""")  
 
def GetStopBoundingBox(self):  
return (min(s.stop_lat for s in self.stops.values()),  
min(s.stop_lon for s in self.stops.values()),  
max(s.stop_lat for s in self.stops.values()),  
max(s.stop_lon for s in self.stops.values()),  
)  
 
def AddAgency(self, name, url, timezone, agency_id=None):  
"""Adds an agency to this schedule."""  
agency = Agency(name, url, timezone, agency_id)  
self.AddAgencyObject(agency)  
return agency  
 
def AddAgencyObject(self, agency, problem_reporter=None, validate=True):  
assert agency._schedule is None  
 
if not problem_reporter:  
problem_reporter = self.problem_reporter  
 
if agency.agency_id in self._agencies:  
problem_reporter.DuplicateID('agency_id', agency.agency_id)  
return  
 
self.AddTableColumns('agency', agency._ColumnNames())  
agency._schedule = weakref.proxy(self)  
 
if validate:  
agency.Validate(problem_reporter)  
self._agencies[agency.agency_id] = agency  
 
def GetAgency(self, agency_id):  
"""Return Agency with agency_id or throw a KeyError"""  
return self._agencies[agency_id]  
 
def GetDefaultAgency(self):  
"""Return the default Agency. If no default Agency has been set select the  
default depending on how many Agency objects are in the Schedule. If there  
are 0 make a new Agency the default, if there is 1 it becomes the default,  
if there is more than 1 then return None.  
"""  
if not self._default_agency:  
if len(self._agencies) == 0:  
self.NewDefaultAgency()  
elif len(self._agencies) == 1:  
self._default_agency = self._agencies.values()[0]  
return self._default_agency  
 
def NewDefaultAgency(self, **kwargs):  
"""Create a new Agency object and make it the default agency for this Schedule"""  
agency = Agency(**kwargs)  
if not agency.agency_id:  
agency.agency_id = FindUniqueId(self._agencies)  
self._default_agency = agency  
self.SetDefaultAgency(agency, validate=False) # Blank agency won't validate  
return agency  
 
def SetDefaultAgency(self, agency, validate=True):  
"""Make agency the default and add it to the schedule if not already added"""  
assert isinstance(agency, Agency)  
self._default_agency = agency  
if agency.agency_id not in self._agencies:  
self.AddAgencyObject(agency, validate=validate)  
 
def GetAgencyList(self):  
"""Returns the list of Agency objects known to this Schedule."""  
return self._agencies.values()  
 
def GetServicePeriod(self, service_id):  
"""Returns the ServicePeriod object with the given ID."""  
return self.service_periods[service_id]  
 
def GetDefaultServicePeriod(self):  
"""Return the default ServicePeriod. If no default ServicePeriod has been  
set select the default depending on how many ServicePeriod objects are in  
the Schedule. If there are 0 make a new ServicePeriod the default, if there  
is 1 it becomes the default, if there is more than 1 then return None.  
"""  
if not self._default_service_period:  
if len(self.service_periods) == 0:  
self.NewDefaultServicePeriod()  
elif len(self.service_periods) == 1:  
self._default_service_period = self.service_periods.values()[0]  
return self._default_service_period  
 
def NewDefaultServicePeriod(self):  
"""Create a new ServicePeriod object, make it the default service period and  
return it. The default service period is used when you create a trip without  
providing an explict service period. """  
service_period = ServicePeriod()  
service_period.service_id = FindUniqueId(self.service_periods)  
# blank service won't validate in AddServicePeriodObject  
self.SetDefaultServicePeriod(service_period, validate=False)  
return service_period  
 
def SetDefaultServicePeriod(self, service_period, validate=True):  
assert isinstance(service_period, ServicePeriod)  
self._default_service_period = service_period  
if service_period.service_id not in self.service_periods:  
self.AddServicePeriodObject(service_period, validate=validate)  
 
def AddServicePeriodObject(self, service_period, problem_reporter=None,  
validate=True):  
if not problem_reporter:  
problem_reporter = self.problem_reporter  
 
if service_period.service_id in self.service_periods:  
problem_reporter.DuplicateID('service_id', service_period.service_id)  
return  
 
if validate:  
service_period.Validate(problem_reporter)  
self.service_periods[service_period.service_id] = service_period  
 
def GetServicePeriodList(self):  
return self.service_periods.values()  
 
def GetDateRange(self):  
"""Returns a tuple of (earliest, latest) dates on which the service  
periods in the schedule define service, in YYYYMMDD form."""  
 
ranges = [period.GetDateRange() for period in self.GetServicePeriodList()]  
starts = filter(lambda x: x, [item[0] for item in ranges])  
ends = filter(lambda x: x, [item[1] for item in ranges])  
 
if not starts or not ends:  
return (None, None)  
 
return (min(starts), max(ends))  
 
def GetServicePeriodsActiveEachDate(self, date_start, date_end):  
"""Return a list of tuples (date, [period1, period2, ...]).  
 
For each date in the range [date_start, date_end) make list of each  
ServicePeriod object which is active.  
 
Args:  
date_start: The first date in the list, a date object  
date_end: The first date after the list, a date object  
 
Returns:  
A list of tuples. Each tuple contains a date object and a list of zero or  
more ServicePeriod objects.  
"""  
date_it = date_start  
one_day = datetime.timedelta(days=1)  
date_service_period_list = []  
while date_it < date_end:  
periods_today = []  
date_it_string = date_it.strftime("%Y%m%d")  
for service in self.GetServicePeriodList():  
if service.IsActiveOn(date_it_string, date_it):  
periods_today.append(service)  
date_service_period_list.append((date_it, periods_today))  
date_it += one_day  
return date_service_period_list  
 
 
def AddStop(self, lat, lng, name):  
"""Add a stop to this schedule.  
 
A new stop_id is created for this stop. Do not use this method unless all  
stops in this Schedule are created with it. See source for details.  
 
Args:  
lat: Latitude of the stop as a float or string  
lng: Longitude of the stop as a float or string  
name: Name of the stop, which will appear in the feed  
 
Returns:  
A new Stop object  
"""  
# TODO: stop_id isn't guarenteed to be unique and conflicts are not  
# handled. Please fix.  
stop_id = unicode(len(self.stops))  
stop = Stop(stop_id=stop_id, lat=lat, lng=lng, name=name)  
self.AddStopObject(stop)  
return stop  
 
def AddStopObject(self, stop, problem_reporter=None):  
"""Add Stop object to this schedule if stop_id is non-blank."""  
assert stop._schedule is None  
if not problem_reporter:  
problem_reporter = self.problem_reporter  
 
if not stop.stop_id:  
return  
 
if stop.stop_id in self.stops:  
problem_reporter.DuplicateID('stop_id', stop.stop_id)  
return  
 
stop._schedule = weakref.proxy(self)  
self.AddTableColumns('stops', stop._ColumnNames())  
self.stops[stop.stop_id] = stop  
if hasattr(stop, 'zone_id') and stop.zone_id:  
self.fare_zones[stop.zone_id] = True  
 
def GetStopList(self):  
return self.stops.values()  
 
def AddRoute(self, short_name, long_name, route_type):  
"""Add a route to this schedule.  
 
Args:  
short_name: Short name of the route, such as "71L"  
long_name: Full name of the route, such as "NW 21st Ave/St Helens Rd"  
route_type: A type such as "Tram", "Subway" or "Bus"  
Returns:  
A new Route object  
"""  
route_id = unicode(len(self.routes))  
route = Route(short_name=short_name, long_name=long_name,  
route_type=route_type, route_id=route_id)  
route.agency_id = self.GetDefaultAgency().agency_id  
self.AddRouteObject(route)  
return route  
 
def AddRouteObject(self, route, problem_reporter=None):  
if not problem_reporter:  
problem_reporter = self.problem_reporter  
 
route.Validate(problem_reporter)  
 
if route.route_id in self.routes:  
problem_reporter.DuplicateID('route_id', route.route_id)  
return  
 
if route.agency_id not in self._agencies:  
if not route.agency_id and len(self._agencies) == 1:  
# we'll just assume that the route applies to the only agency  
pass  
else:  
problem_reporter.InvalidValue('agency_id', route.agency_id,  
'Route uses an unknown agency_id.')  
return  
 
self.AddTableColumns('routes', route._ColumnNames())  
route._schedule = weakref.proxy(self)  
self.routes[route.route_id] = route  
 
def GetRouteList(self):  
return self.routes.values()  
 
def GetRoute(self, route_id):  
return self.routes[route_id]  
 
def AddShapeObject(self, shape, problem_reporter=None):  
if not problem_reporter:  
problem_reporter = self.problem_reporter  
 
shape.Validate(problem_reporter)  
 
if shape.shape_id in self._shapes:  
problem_reporter.DuplicateID('shape_id', shape.shape_id)  
return  
 
self._shapes[shape.shape_id] = shape  
 
def GetShapeList(self):  
return self._shapes.values()  
 
def GetShape(self, shape_id):  
return self._shapes[shape_id]  
 
def AddTripObject(self, trip, problem_reporter=None, validate=True):  
if not problem_reporter:  
problem_reporter = self.problem_reporter  
 
if trip.trip_id in self.trips:  
problem_reporter.DuplicateID('trip_id', trip.trip_id)  
return  
 
self.AddTableColumns('trips', trip._ColumnNames())  
trip._schedule = weakref.proxy(self)  
self.trips[trip.trip_id] = trip  
 
# Call Trip.Validate after setting trip._schedule so that references  
# are checked. trip.ValidateChildren will be called directly by  
# schedule.Validate, after stop_times has been loaded.  
if validate:  
if not problem_reporter:  
problem_reporter = self.problem_reporter  
trip.Validate(problem_reporter, validate_children=False)  
try:  
self.routes[trip.route_id]._AddTripObject(trip)  
except KeyError:  
# Invalid route_id was reported in the Trip.Validate call above  
pass  
 
def GetTripList(self):  
return self.trips.values()  
 
def GetTrip(self, trip_id):  
return self.trips[trip_id]  
 
def AddFareObject(self, fare, problem_reporter=None):  
if not problem_reporter:  
problem_reporter = self.problem_reporter  
fare.Validate(problem_reporter)  
 
if fare.fare_id in self.fares:  
problem_reporter.DuplicateID('fare_id', fare.fare_id)  
return  
 
self.fares[fare.fare_id] = fare  
 
def GetFareList(self):  
return self.fares.values()  
 
def GetFare(self, fare_id):  
return self.fares[fare_id]  
 
def AddFareRuleObject(self, rule, problem_reporter=None):  
if not problem_reporter:  
problem_reporter = self.problem_reporter  
 
if IsEmpty(rule.fare_id):  
problem_reporter.MissingValue('fare_id')  
return  
 
if rule.route_id and rule.route_id not in self.routes:  
problem_reporter.InvalidValue('route_id', rule.route_id)  
if rule.origin_id and rule.origin_id not in self.fare_zones:  
problem_reporter.InvalidValue('origin_id', rule.origin_id)  
if rule.destination_id and rule.destination_id not in self.fare_zones:  
problem_reporter.InvalidValue('destination_id', rule.destination_id)  
if rule.contains_id and rule.contains_id not in self.fare_zones:  
problem_reporter.InvalidValue('contains_id', rule.contains_id)  
 
if rule.fare_id in self.fares:  
self.GetFare(rule.fare_id).rules.append(rule)  
else:  
problem_reporter.InvalidValue('fare_id', rule.fare_id,  
'(This fare_id doesn\'t correspond to any '  
'of the IDs defined in the '  
'fare attributes.)')  
 
def AddTransferObject(self, transfer, problem_reporter=None):  
assert transfer._schedule is None, "only add Transfer to a schedule once"  
transfer._schedule = weakref.proxy(self) # See weakref comment at top  
if not problem_reporter:  
problem_reporter = self.problem_reporter  
 
transfer.Validate(problem_reporter)  
self._transfers.append(transfer)  
 
def GetTransferList(self):  
return self._transfers  
 
def GetStop(self, id):  
return self.stops[id]  
 
def GetFareZones(self):  
"""Returns the list of all fare zones that have been identified by  
the stops that have been added."""  
return self.fare_zones.keys()  
 
def GetNearestStops(self, lat, lon, n=1):  
"""Return the n nearest stops to lat,lon"""  
dist_stop_list = []  
for s in self.stops.values():  
# TODO: Use ApproximateDistanceBetweenStops?  
dist = (s.stop_lat - lat)**2 + (s.stop_lon - lon)**2  
if len(dist_stop_list) < n:  
bisect.insort(dist_stop_list, (dist, s))  
elif dist < dist_stop_list[-1][0]:  
bisect.insort(dist_stop_list, (dist, s))  
dist_stop_list.pop() # Remove stop with greatest distance  
return [stop for dist, stop in dist_stop_list]  
 
def GetStopsInBoundingBox(self, north, east, south, west, n):  
"""Return a sample of up to n stops in a bounding box"""  
stop_list = []  
for s in self.stops.values():  
if (s.stop_lat <= north and s.stop_lat >= south and  
s.stop_lon <= east and s.stop_lon >= west):  
stop_list.append(s)  
if len(stop_list) == n:  
break  
return stop_list  
 
def Load(self, feed_path, extra_validation=False):  
loader = Loader(feed_path, self, problems=self.problem_reporter,  
extra_validation=extra_validation)  
loader.Load()  
 
def _WriteArchiveString(self, archive, filename, stringio):  
zi = zipfile.ZipInfo(filename)  
# See  
# http://stackoverflow.com/questions/434641/how-do-i-set-permissions-attributes-on-a-file-in-a-zip-file-using-pythons-zipf  
zi.external_attr = 0666 << 16L # Set unix permissions to -rw-rw-rw  
# ZIP_DEFLATED requires zlib. zlib comes with Python 2.4 and 2.5  
zi.compress_type = zipfile.ZIP_DEFLATED  
archive.writestr(zi, stringio.getvalue())  
 
def WriteGoogleTransitFeed(self, file):  
"""Output this schedule as a Google Transit Feed in file_name.  
 
Args:  
file: path of new feed file (a string) or a file-like object  
 
Returns:  
None  
"""  
# Compression type given when adding each file  
archive = zipfile.ZipFile(file, 'w')  
 
if 'agency' in self._table_columns:  
agency_string = StringIO.StringIO()  
writer = CsvUnicodeWriter(agency_string)  
columns = self.GetTableColumns('agency')  
writer.writerow(columns)  
for a in self._agencies.values():  
writer.writerow([EncodeUnicode(a[c]) for c in columns])  
self._WriteArchiveString(archive, 'agency.txt', agency_string)  
 
calendar_dates_string = StringIO.StringIO()  
writer = CsvUnicodeWriter(calendar_dates_string)  
writer.writerow(ServicePeriod._FIELD_NAMES_CALENDAR_DATES)  
has_data = False  
for period in self.service_periods.values():  
for row in period.GenerateCalendarDatesFieldValuesTuples():  
has_data = True  
writer.writerow(row)  
wrote_calendar_dates = False  
if has_data:  
wrote_calendar_dates = True  
self._WriteArchiveString(archive, 'calendar_dates.txt',  
calendar_dates_string)  
 
calendar_string = StringIO.StringIO()  
writer = CsvUnicodeWriter(calendar_string)  
writer.writerow(ServicePeriod._FIELD_NAMES)  
has_data = False  
for s in self.service_periods.values():  
row = s.GetCalendarFieldValuesTuple()  
if row:  
has_data = True  
writer.writerow(row)  
if has_data or not wrote_calendar_dates:  
self._WriteArchiveString(archive, 'calendar.txt', calendar_string)  
 
if 'stops' in self._table_columns:  
stop_string = StringIO.StringIO()  
writer = CsvUnicodeWriter(stop_string)  
columns = self.GetTableColumns('stops')  
writer.writerow(columns)  
for s in self.stops.values():  
writer.writerow([EncodeUnicode(s[c]) for c in columns])  
self._WriteArchiveString(archive, 'stops.txt', stop_string)  
 
if 'routes' in self._table_columns:  
route_string = StringIO.StringIO()  
writer = CsvUnicodeWriter(route_string)  
columns = self.GetTableColumns('routes')  
writer.writerow(columns)  
for r in self.routes.values():  
writer.writerow([EncodeUnicode(r[c]) for c in columns])  
self._WriteArchiveString(archive, 'routes.txt', route_string)  
 
if 'trips' in self._table_columns:  
trips_string = StringIO.StringIO()  
writer = CsvUnicodeWriter(trips_string)  
columns = self.GetTableColumns('trips')  
writer.writerow(columns)  
for t in self.trips.values():  
writer.writerow([EncodeUnicode(t[c]) for c in columns])  
self._WriteArchiveString(archive, 'trips.txt', trips_string)  
 
# write frequencies.txt (if applicable)  
headway_rows = []  
for trip in self.GetTripList():  
headway_rows += trip.GetHeadwayPeriodOutputTuples()  
if headway_rows:  
headway_string = StringIO.StringIO()  
writer = CsvUnicodeWriter(headway_string)  
writer.writerow(Trip._FIELD_NAMES_HEADWAY)  
writer.writerows(headway_rows)  
self._WriteArchiveString(archive, 'frequencies.txt', headway_string)  
 
# write fares (if applicable)  
if self.GetFareList():  
fare_string = StringIO.StringIO()  
writer = CsvUnicodeWriter(fare_string)  
writer.writerow(Fare._FIELD_NAMES)  
writer.writerows(f.GetFieldValuesTuple() for f in self.GetFareList())  
self._WriteArchiveString(archive, 'fare_attributes.txt', fare_string)  
 
# write fare rules (if applicable)  
rule_rows = []  
for fare in self.GetFareList():  
for rule in fare.GetFareRuleList():  
rule_rows.append(rule.GetFieldValuesTuple())  
if rule_rows:  
rule_string = StringIO.StringIO()  
writer = CsvUnicodeWriter(rule_string)  
writer.writerow(FareRule._FIELD_NAMES)  
writer.writerows(rule_rows)  
self._WriteArchiveString(archive, 'fare_rules.txt', rule_string)  
stop_times_string = StringIO.StringIO()  
writer = CsvUnicodeWriter(stop_times_string)  
writer.writerow(StopTime._FIELD_NAMES)  
for t in self.trips.values():  
writer.writerows(t._GenerateStopTimesTuples())  
self._WriteArchiveString(archive, 'stop_times.txt', stop_times_string)  
 
# write shapes (if applicable)  
shape_rows = []  
for shape in self.GetShapeList():  
seq = 1  
for (lat, lon, dist) in shape.points:  
shape_rows.append((shape.shape_id, lat, lon, seq, dist))  
seq += 1  
if shape_rows:  
shape_string = StringIO.StringIO()  
writer = CsvUnicodeWriter(shape_string)  
writer.writerow(Shape._FIELD_NAMES)  
writer.writerows(shape_rows)  
self._WriteArchiveString(archive, 'shapes.txt', shape_string)  
 
# write transfers (if applicable)  
if self.GetTransferList():  
transfer_string = StringIO.StringIO()  
writer = CsvUnicodeWriter(transfer_string)  
writer.writerow(Transfer._FIELD_NAMES)  
writer.writerows(f.GetFieldValuesTuple() for f in self.GetTransferList())  
self._WriteArchiveString(archive, 'transfers.txt', transfer_string)  
 
archive.close()  
 
def GenerateDateTripsDeparturesList(self, date_start, date_end):  
"""Return a list of (date object, number of trips, number of departures).  
 
The list is generated for dates in the range [date_start, date_end).  
 
Args:  
date_start: The first date in the list, a date object  
date_end: The first date after the list, a date object  
 
Returns:  
a list of (date object, number of trips, number of departures) tuples  
"""  
 
service_id_to_trips = defaultdict(lambda: 0)  
service_id_to_departures = defaultdict(lambda: 0)  
for trip in self.GetTripList():  
headway_start_times = trip.GetHeadwayStartTimes()  
if headway_start_times:  
trip_runs = len(headway_start_times)  
else:  
trip_runs = 1  
 
service_id_to_trips[trip.service_id] += trip_runs  
service_id_to_departures[trip.service_id] += (  
(trip.GetCountStopTimes() - 1) * trip_runs)  
 
date_services = self.GetServicePeriodsActiveEachDate(date_start, date_end)  
date_trips = []  
 
for date, services in date_services:  
day_trips = sum(service_id_to_trips[s.service_id] for s in services)  
day_departures = sum(  
service_id_to_departures[s.service_id] for s in services)  
date_trips.append((date, day_trips, day_departures))  
return date_trips  
 
def ValidateFeedStartAndExpirationDates(self,  
problems,  
first_date,  
last_date,  
today):  
"""Validate the start and expiration dates of the feed.  
Issue a warning if it only starts in the future, or if  
it expires within 60 days.  
 
Args:  
problems: The problem reporter object  
first_date: A date object representing the first day the feed is active  
last_date: A date object representing the last day the feed is active  
today: A date object representing the date the validation is being run on  
 
Returns:  
None  
"""  
warning_cutoff = today + datetime.timedelta(days=60)  
if last_date < warning_cutoff:  
problems.ExpirationDate(time.mktime(last_date.timetuple()))  
 
if first_date > today:  
problems.FutureService(time.mktime(first_date.timetuple()))  
 
def ValidateServiceGaps(self,  
problems,  
validation_start_date,  
validation_end_date,  
service_gap_interval):  
"""Validate consecutive dates without service in the feed.  
Issue a warning if it finds service gaps of at least  
"service_gap_interval" consecutive days in the date range  
[validation_start_date, last_service_date)  
 
Args:  
problems: The problem reporter object  
validation_start_date: A date object representing the date from which the  
validation should take place  
validation_end_date: A date object representing the first day the feed is  
active  
service_gap_interval: An integer indicating how many consecutive days the  
service gaps need to have for a warning to be issued  
 
Returns:  
None  
"""  
if service_gap_interval is None:  
return  
 
departures = self.GenerateDateTripsDeparturesList(validation_start_date,  
validation_end_date)  
 
# The first day without service of the _current_ gap  
first_day_without_service = validation_start_date  
# The last day without service of the _current_ gap  
last_day_without_service = validation_start_date  
 
consecutive_days_without_service = 0  
 
for day_date, day_trips, _ in departures:  
if day_trips == 0:  
if consecutive_days_without_service == 0:  
first_day_without_service = day_date  
consecutive_days_without_service += 1  
last_day_without_service = day_date  
else:  
if consecutive_days_without_service >= service_gap_interval:  
problems.TooManyDaysWithoutService(first_day_without_service,  
last_day_without_service,  
consecutive_days_without_service)  
 
consecutive_days_without_service = 0  
 
# We have to check if there is a gap at the end of the specified date range  
if consecutive_days_without_service >= service_gap_interval:  
problems.TooManyDaysWithoutService(first_day_without_service,  
last_day_without_service,  
consecutive_days_without_service)  
 
def Validate(self,  
problems=None,  
validate_children=True,  
today=None,  
service_gap_interval=None):  
"""Validates various holistic aspects of the schedule  
(mostly interrelationships between the various data sets)."""  
 
if today is None:  
today = datetime.date.today()  
 
if not problems:  
problems = self.problem_reporter  
 
(start_date, end_date) = self.GetDateRange()  
if not end_date or not start_date:  
problems.OtherProblem('This feed has no effective service dates!',  
type=TYPE_WARNING)  
else:  
try:  
last_service_day = datetime.datetime(  
*(time.strptime(end_date, "%Y%m%d")[0:6])).date()  
first_service_day = datetime.datetime(  
*(time.strptime(start_date, "%Y%m%d")[0:6])).date()  
 
except ValueError:  
# Format of start_date and end_date checked in class ServicePeriod  
pass  
 
else:  
 
self.ValidateFeedStartAndExpirationDates(problems,  
first_service_day,  
last_service_day,  
today)  
 
# We start checking for service gaps a bit in the past if the  
# feed was active then. See  
# http://code.google.com/p/googletransitdatafeed/issues/detail?id=188  
#  
# We subtract 1 from service_gap_interval so that if today has  
# service no warning is issued.  
#  
# Service gaps are searched for only up to one year from today  
if service_gap_interval is not None:  
service_gap_timedelta = datetime.timedelta(  
days=service_gap_interval - 1)  
one_year = datetime.timedelta(days=365)  
self.ValidateServiceGaps(  
problems,  
max(first_service_day,  
today - service_gap_timedelta),  
min(last_service_day,  
today + one_year),  
service_gap_interval)  
 
# TODO: Check Trip fields against valid values  
 
# Check for stops that aren't referenced by any trips and broken  
# parent_station references. Also check that the parent station isn't too  
# far from its child stops.  
for stop in self.stops.values():  
if validate_children:  
stop.Validate(problems)  
cursor = self._connection.cursor()  
cursor.execute("SELECT count(*) FROM stop_times WHERE stop_id=? LIMIT 1",  
(stop.stop_id,))  
count = cursor.fetchone()[0]  
if stop.location_type == 0 and count == 0:  
problems.UnusedStop(stop.stop_id, stop.stop_name)  
elif stop.location_type == 1 and count != 0:  
problems.UsedStation(stop.stop_id, stop.stop_name)  
 
if stop.location_type != 1 and stop.parent_station:  
if stop.parent_station not in self.stops:  
problems.InvalidValue("parent_station",  
EncodeUnicode(stop.parent_station),  
"parent_station '%s' not found for stop_id "  
"'%s' in stops.txt" %  
(EncodeUnicode(stop.parent_station),  
EncodeUnicode(stop.stop_id)))  
elif self.stops[stop.parent_station].location_type != 1:  
problems.InvalidValue("parent_station",  
EncodeUnicode(stop.parent_station),  
"parent_station '%s' of stop_id '%s' must "  
"have location_type=1 in stops.txt" %  
(EncodeUnicode(stop.parent_station),  
EncodeUnicode(stop.stop_id)))  
else:  
parent_station = self.stops[stop.parent_station]  
distance = ApproximateDistanceBetweenStops(stop, parent_station)  
if distance > MAX_DISTANCE_BETWEEN_STOP_AND_PARENT_STATION_ERROR:  
problems.StopTooFarFromParentStation(  
stop.stop_id, stop.stop_name, parent_station.stop_id,  
parent_station.stop_name, distance, TYPE_ERROR)  
elif distance > MAX_DISTANCE_BETWEEN_STOP_AND_PARENT_STATION_WARNING:  
problems.StopTooFarFromParentStation(  
stop.stop_id, stop.stop_name, parent_station.stop_id,  
parent_station.stop_name, distance, TYPE_WARNING)  
 
#TODO: check that every station is used.  
# Then uncomment testStationWithoutReference.  
 
# Check for stops that might represent the same location (specifically,  
# stops that are less that 2 meters apart) First filter out stops without a  
# valid lat and lon. Then sort by latitude, then find the distance between  
# each pair of stations within 2 meters latitude of each other. This avoids  
# doing n^2 comparisons in the average case and doesn't need a spatial  
# index.  
sorted_stops = filter(lambda s: s.stop_lat and s.stop_lon,  
self.GetStopList())  
sorted_stops.sort(key=(lambda x: x.stop_lat))  
TWO_METERS_LAT = 0.000018  
for index, stop in enumerate(sorted_stops[:-1]):  
index += 1  
while ((index < len(sorted_stops)) and  
((sorted_stops[index].stop_lat - stop.stop_lat) < TWO_METERS_LAT)):  
distance = ApproximateDistanceBetweenStops(stop, sorted_stops[index])  
if distance < 2:  
other_stop = sorted_stops[index]  
if stop.location_type == 0 and other_stop.location_type == 0:  
problems.StopsTooClose(  
EncodeUnicode(stop.stop_name),  
EncodeUnicode(stop.stop_id),  
EncodeUnicode(other_stop.stop_name),  
EncodeUnicode(other_stop.stop_id), distance)  
elif stop.location_type == 1 and other_stop.location_type == 1:  
problems.StationsTooClose(  
EncodeUnicode(stop.stop_name), EncodeUnicode(stop.stop_id),  
EncodeUnicode(other_stop.stop_name),  
EncodeUnicode(other_stop.stop_id), distance)  
elif (stop.location_type in (0, 1) and  
other_stop.location_type in (0, 1)):  
if stop.location_type == 0 and other_stop.location_type == 1:  
this_stop = stop  
this_station = other_stop  
elif stop.location_type == 1 and other_stop.location_type == 0:  
this_stop = other_stop  
this_station = stop  
if this_stop.parent_station != this_station.stop_id:  
problems.DifferentStationTooClose(  
EncodeUnicode(this_stop.stop_name),  
EncodeUnicode(this_stop.stop_id),  
EncodeUnicode(this_station.stop_name),  
EncodeUnicode(this_station.stop_id), distance)  
index += 1  
 
# Check for multiple routes using same short + long name  
route_names = {}  
for route in self.routes.values():  
if validate_children:  
route.Validate(problems)  
short_name = ''  
if not IsEmpty(route.route_short_name):  
short_name = route.route_short_name.lower().strip()  
long_name = ''  
if not IsEmpty(route.route_long_name):  
long_name = route.route_long_name.lower().strip()  
name = (short_name, long_name)  
if name in route_names:  
problems.InvalidValue('route_long_name',  
long_name,  
'The same combination of '  
'route_short_name and route_long_name '  
'shouldn\'t be used for more than one '  
'route, as it is for the for the two routes '  
'with IDs "%s" and "%s".' %  
(route.route_id, route_names[name].route_id),  
type=TYPE_WARNING)  
else:  
route_names[name] = route  
 
stop_types = {} # a dict mapping stop_id to [route_id, route_type, is_match]  
trips = {} # a dict mapping tuple to (route_id, trip_id)  
for trip in sorted(self.trips.values()):  
if trip.route_id not in self.routes:  
continue  
route_type = self.GetRoute(trip.route_id).route_type  
arrival_times = []  
stop_ids = []  
for index, st in enumerate(trip.GetStopTimes(problems)):  
stop_id = st.stop.stop_id  
arrival_times.append(st.arrival_time)  
stop_ids.append(stop_id)  
# Check a stop if which belongs to both subway and bus.  
if (route_type == Route._ROUTE_TYPE_NAMES['Subway'] or  
route_type == Route._ROUTE_TYPE_NAMES['Bus']):  
if stop_id not in stop_types:  
stop_types[stop_id] = [trip.route_id, route_type, 0]  
elif (stop_types[stop_id][1] != route_type and  
stop_types[stop_id][2] == 0):  
stop_types[stop_id][2] = 1  
if stop_types[stop_id][1] == Route._ROUTE_TYPE_NAMES['Subway']:  
subway_route_id = stop_types[stop_id][0]  
bus_route_id = trip.route_id  
else:  
subway_route_id = trip.route_id  
bus_route_id = stop_types[stop_id][0]  
problems.StopWithMultipleRouteTypes(st.stop.stop_name, stop_id,  
subway_route_id, bus_route_id)  
 
# Check duplicate trips which go through the same stops with same  
# service and start times.  
if self._check_duplicate_trips:  
if not stop_ids or not arrival_times:  
continue  
key = (trip.service_id, min(arrival_times), str(stop_ids))  
if key not in trips:  
trips[key] = (trip.route_id, trip.trip_id)  
else:  
problems.DuplicateTrip(trips[key][1], trips[key][0], trip.trip_id,  
trip.route_id)  
 
# Check that routes' agency IDs are valid, if set  
for route in self.routes.values():  
if (not IsEmpty(route.agency_id) and  
not route.agency_id in self._agencies):  
problems.InvalidValue('agency_id',  
route.agency_id,  
'The route with ID "%s" specifies agency_id '  
'"%s", which doesn\'t exist.' %  
(route.route_id, route.agency_id))  
 
# Make sure all trips have stop_times  
# We're doing this here instead of in Trip.Validate() so that  
# Trips can be validated without error during the reading of trips.txt  
for trip in self.trips.values():  
trip.ValidateChildren(problems)  
count_stop_times = trip.GetCountStopTimes()  
if not count_stop_times:  
problems.OtherProblem('The trip with the trip_id "%s" doesn\'t have '  
'any stop times defined.' % trip.trip_id,  
type=TYPE_WARNING)  
if len(trip._headways) > 0: # no stoptimes, but there are headways  
problems.OtherProblem('Frequencies defined, but no stop times given '  
'in trip %s' % trip.trip_id, type=TYPE_ERROR)  
elif count_stop_times == 1:  
problems.OtherProblem('The trip with the trip_id "%s" only has one '  
'stop on it; it should have at least one more '  
'stop so that the riders can leave!' %  
trip.trip_id, type=TYPE_WARNING)  
else:  
# These methods report InvalidValue if there's no first or last time  
trip.GetStartTime(problems=problems)  
trip.GetEndTime(problems=problems)  
 
# Check for unused shapes  
known_shape_ids = set(self._shapes.keys())  
used_shape_ids = set()  
for trip in self.GetTripList():  
used_shape_ids.add(trip.shape_id)  
unused_shape_ids = known_shape_ids - used_shape_ids  
if unused_shape_ids:  
problems.OtherProblem('The shapes with the following shape_ids aren\'t '  
'used by any trips: %s' %  
', '.join(unused_shape_ids),  
type=TYPE_WARNING)  
 
 
# Map from literal string that should never be found in the csv data to a human  
# readable description  
INVALID_LINE_SEPARATOR_UTF8 = {  
"\x0c": "ASCII Form Feed 0x0C",  
# May be part of end of line, but not found elsewhere  
"\x0d": "ASCII Carriage Return 0x0D, \\r",  
"\xe2\x80\xa8": "Unicode LINE SEPARATOR U+2028",  
"\xe2\x80\xa9": "Unicode PARAGRAPH SEPARATOR U+2029",  
"\xc2\x85": "Unicode NEXT LINE SEPARATOR U+0085",  
}  
 
class EndOfLineChecker:  
"""Wrapper for a file-like object that checks for consistent line ends.  
 
The check for consistent end of lines (all CR LF or all LF) only happens if  
next() is called until it raises StopIteration.  
"""  
def __init__(self, f, name, problems):  
"""Create new object.  
 
Args:  
f: file-like object to wrap  
name: name to use for f. StringIO objects don't have a name attribute.  
problems: a ProblemReporterBase object  
"""  
self._f = f  
self._name = name  
self._crlf = 0  
self._crlf_examples = []  
self._lf = 0  
self._lf_examples = []  
self._line_number = 0 # first line will be number 1  
self._problems = problems  
 
def __iter__(self):  
return self  
 
def next(self):  
"""Return next line without end of line marker or raise StopIteration."""  
try:  
next_line = self._f.next()  
except StopIteration:  
self._FinalCheck()  
raise  
 
self._line_number += 1  
m_eol = re.search(r"[\x0a\x0d]*$", next_line)  
if m_eol.group() == "\x0d\x0a":  
self._crlf += 1  
if self._crlf <= 5:  
self._crlf_examples.append(self._line_number)  
elif m_eol.group() == "\x0a":  
self._lf += 1  
if self._lf <= 5:  
self._lf_examples.append(self._line_number)  
elif m_eol.group() == "":  
# Should only happen at the end of the file  
try:  
self._f.next()  
raise RuntimeError("Unexpected row without new line sequence")  
except StopIteration:  
# Will be raised again when EndOfLineChecker.next() is next called  
pass  
else:  
self._problems.InvalidLineEnd(  
codecs.getencoder('string_escape')(m_eol.group())[0],  
(self._name, self._line_number))  
next_line_contents = next_line[0:m_eol.start()]  
for seq, name in INVALID_LINE_SEPARATOR_UTF8.items():  
if next_line_contents.find(seq) != -1:  
self._problems.OtherProblem(  
"Line contains %s" % name,  
context=(self._name, self._line_number))  
return next_line_contents  
 
def _FinalCheck(self):  
if self._crlf > 0 and self._lf > 0:  
crlf_plural = self._crlf > 1 and "s" or ""  
crlf_lines = ", ".join(["%s" % e for e in self._crlf_examples])  
if self._crlf > len(self._crlf_examples):  
crlf_lines += ", ..."  
lf_plural = self._lf > 1 and "s" or ""  
lf_lines = ", ".join(["%s" % e for e in self._lf_examples])  
if self._lf > len(self._lf_examples):  
lf_lines += ", ..."  
 
self._problems.OtherProblem(  
"Found %d CR LF \"\\r\\n\" line end%s (line%s %s) and "  
"%d LF \"\\n\" line end%s (line%s %s). A file must use a "  
"consistent line end." % (self._crlf, crlf_plural, crlf_plural,  
crlf_lines, self._lf, lf_plural,  
lf_plural, lf_lines),  
(self._name,))  
# Prevent _FinalCheck() from reporting the problem twice, in the unlikely  
# case that it is run twice  
self._crlf = 0  
self._lf = 0  
 
 
# Filenames specified in GTFS spec  
KNOWN_FILENAMES = [  
'agency.txt',  
'stops.txt',  
'routes.txt',  
'trips.txt',  
'stop_times.txt',  
'calendar.txt',  
'calendar_dates.txt',  
'fare_attributes.txt',  
'fare_rules.txt',  
'shapes.txt',  
'frequencies.txt',  
'transfers.txt',  
]  
 
class Loader:  
def __init__(self,  
feed_path=None,  
schedule=None,  
problems=default_problem_reporter,  
extra_validation=False,  
load_stop_times=True,  
memory_db=True,  
zip=None,  
check_duplicate_trips=False):  
"""Initialize a new Loader object.  
 
Args:  
feed_path: string path to a zip file or directory  
schedule: a Schedule object or None to have one created  
problems: a ProblemReporter object, the default reporter raises an  
exception for each problem  
extra_validation: True if you would like extra validation  
load_stop_times: load the stop_times table, used to speed load time when  
times are not needed. The default is True.  
memory_db: if creating a new Schedule object use an in-memory sqlite  
database instead of creating one in a temporary file  
zip: a zipfile.ZipFile object, optionally used instead of path  
"""  
if not schedule:  
schedule = Schedule(problem_reporter=problems, memory_db=memory_db,  
check_duplicate_trips=check_duplicate_trips)  
self._extra_validation = extra_validation  
self._schedule = schedule  
self._problems = problems  
self._path = feed_path  
self._zip = zip  
self._load_stop_times = load_stop_times  
 
def _DetermineFormat(self):  
"""Determines whether the feed is in a form that we understand, and  
if so, returns True."""  
if self._zip:  
# If zip was passed to __init__ then path isn't used  
assert not self._path  
return True  
 
if not isinstance(self._path, basestring) and hasattr(self._path, 'read'):  
# A file-like object, used for testing with a StringIO file  
self._zip = zipfile.ZipFile(self._path, mode='r')  
return True  
 
if not os.path.exists(self._path):  
self._problems.FeedNotFound(self._path)  
return False  
 
if self._path.endswith('.zip'):  
try:  
self._zip = zipfile.ZipFile(self._path, mode='r')  
except IOError: # self._path is a directory  
pass  
except zipfile.BadZipfile:  
self._problems.UnknownFormat(self._path)  
return False  
 
if not self._zip and not os.path.isdir(self._path):  
self._problems.UnknownFormat(self._path)  
return False  
 
return True  
 
def _GetFileNames(self):  
"""Returns a list of file names in the feed."""  
if self._zip:  
return self._zip.namelist()  
else:  
return os.listdir(self._path)  
 
def _CheckFileNames(self):  
filenames = self._GetFileNames()  
for feed_file in filenames:  
if feed_file not in KNOWN_FILENAMES:  
if not feed_file.startswith('.'):  
# Don't worry about .svn files and other hidden files  
# as this will break the tests.  
self._problems.UnknownFile(feed_file)  
 
def _GetUtf8Contents(self, file_name):  
"""Check for errors in file_name and return a string for csv reader."""  
contents = self._FileContents(file_name)  
if not contents: # Missing file  
return  
 
# Check for errors that will prevent csv.reader from working  
if len(contents) >= 2 and contents[0:2] in (codecs.BOM_UTF16_BE,  
codecs.BOM_UTF16_LE):  
self._problems.FileFormat("appears to be encoded in utf-16", (file_name, ))  
# Convert and continue, so we can find more errors  
contents = codecs.getdecoder('utf-16')(contents)[0].encode('utf-8')  
 
null_index = contents.find('\0')  
if null_index != -1:  
# It is easier to get some surrounding text than calculate the exact  
# row_num  
m = re.search(r'.{,20}\0.{,20}', contents, re.DOTALL)  
self._problems.FileFormat(  
"contains a null in text \"%s\" at byte %d" %  
(codecs.getencoder('string_escape')(m.group()), null_index + 1),  
(file_name, ))  
return  
 
# strip out any UTF-8 Byte Order Marker (otherwise it'll be  
# treated as part of the first column name, causing a mis-parse)  
contents = contents.lstrip(codecs.BOM_UTF8)  
return contents  
 
def _ReadCsvDict(self, file_name, all_cols, required):  
"""Reads lines from file_name, yielding a dict of unicode values."""  
assert file_name.endswith(".txt")  
table_name = file_name[0:-4]  
contents = self._GetUtf8Contents(file_name)  
if not contents:  
return  
 
eol_checker = EndOfLineChecker(StringIO.StringIO(contents),  
file_name, self._problems)  
# The csv module doesn't provide a way to skip trailing space, but when I  
# checked 15/675 feeds had trailing space in a header row and 120 had spaces  
# after fields. Space after header fields can cause a serious parsing  
# problem, so warn. Space after body fields can cause a problem time,  
# integer and id fields; they will be validated at higher levels.  
reader = csv.reader(eol_checker, skipinitialspace=True)  
 
raw_header = reader.next()  
header_occurrences = defaultdict(lambda: 0)  
header = []  
valid_columns = [] # Index into raw_header and raw_row  
for i, h in enumerate(raw_header):  
h_stripped = h.strip()  
if not h_stripped:  
self._problems.CsvSyntax(  
description="The header row should not contain any blank values. "  
"The corresponding column will be skipped for the "  
"entire file.",  
context=(file_name, 1, [''] * len(raw_header), raw_header),  
type=TYPE_ERROR)  
continue  
elif h != h_stripped:  
self._problems.CsvSyntax(  
description="The header row should not contain any "  
"space characters.",  
context=(file_name, 1, [''] * len(raw_header), raw_header),  
type=TYPE_WARNING)  
header.append(h_stripped)  
valid_columns.append(i)  
header_occurrences[h_stripped] += 1  
 
for name, count in header_occurrences.items():  
if count > 1:  
self._problems.DuplicateColumn(  
header=name,  
file_name=file_name,  
count=count)  
 
self._schedule._table_columns[table_name] = header  
 
# check for unrecognized columns, which are often misspellings  
unknown_cols = set(header) - set(all_cols)  
if len(unknown_cols) == len(header):  
self._problems.CsvSyntax(  
description="The header row did not contain any known column "  
"names. The file is most likely missing the header row "  
"or not in the expected CSV format.",  
context=(file_name, 1, [''] * len(raw_header), raw_header),  
type=TYPE_ERROR)  
else:  
for col in unknown_cols:  
# this is provided in order to create a nice colored list of  
# columns in the validator output  
context = (file_name, 1, [''] * len(header), header)  
self._problems.UnrecognizedColumn(file_name, col, context)  
 
missing_cols = set(required) - set(header)  
for col in missing_cols:  
# this is provided in order to create a nice colored list of  
# columns in the validator output  
context = (file_name, 1, [''] * len(header), header)  
self._problems.MissingColumn(file_name, col, context)  
 
line_num = 1 # First line read by reader.next() above  
for raw_row in reader:  
line_num += 1  
if len(raw_row) == 0: # skip extra empty lines in file  
continue  
 
if len(raw_row) > len(raw_header):  
self._problems.OtherProblem('Found too many cells (commas) in line '  
'%d of file "%s". Every row in the file '  
'should have the same number of cells as '  
'the header (first line) does.' %  
(line_num, file_name),  
(file_name, line_num),  
type=TYPE_WARNING)  
 
if len(raw_row) < len(raw_header):  
self._problems.OtherProblem('Found missing cells (commas) in line '  
'%d of file "%s". Every row in the file '  
'should have the same number of cells as '  
'the header (first line) does.' %  
(line_num, file_name),  
(file_name, line_num),  
type=TYPE_WARNING)  
 
# raw_row is a list of raw bytes which should be valid utf-8. Convert each  
# valid_columns of raw_row into Unicode.  
valid_values = []  
unicode_error_columns = [] # index of valid_values elements with an error  
for i in valid_columns:  
try:  
valid_values.append(raw_row[i].decode('utf-8'))  
except UnicodeDecodeError:  
# Replace all invalid characters with REPLACEMENT CHARACTER (U+FFFD)  
valid_values.append(codecs.getdecoder("utf8")  
(raw_row[i], errors="replace")[0])  
unicode_error_columns.append(len(valid_values) - 1)  
except IndexError:  
break  
 
# The error report may contain a dump of all values in valid_values so  
# problems can not be reported until after converting all of raw_row to  
# Unicode.  
for i in unicode_error_columns:  
self._problems.InvalidValue(header[i], valid_values[i],  
'Unicode error',  
(file_name, line_num,  
valid_values, header))  
 
 
d = dict(zip(header, valid_values))  
yield (d, line_num, header, valid_values)  
 
# TODO: Add testing for this specific function  
def _ReadCSV(self, file_name, cols, required):  
"""Reads lines from file_name, yielding a list of unicode values  
corresponding to the column names in cols."""  
contents = self._GetUtf8Contents(file_name)  
if not contents:  
return  
 
eol_checker = EndOfLineChecker(StringIO.StringIO(contents),  
file_name, self._problems)  
reader = csv.reader(eol_checker) # Use excel dialect  
 
header = reader.next()  
header = map(lambda x: x.strip(), header) # trim any whitespace  
header_occurrences = defaultdict(lambda: 0)  
for column_header in header:  
header_occurrences[column_header] += 1  
 
for name, count in header_occurrences.items():  
if count > 1:  
self._problems.DuplicateColumn(  
header=name,  
file_name=file_name,  
count=count)  
 
# check for unrecognized columns, which are often misspellings  
unknown_cols = set(header).difference(set(cols))  
for col in unknown_cols:  
# this is provided in order to create a nice colored list of  
# columns in the validator output  
context = (file_name, 1, [''] * len(header), header)  
self._problems.UnrecognizedColumn(file_name, col, context)  
 
col_index = [-1] * len(cols)  
for i in range(len(cols)):  
if cols[i] in header:  
col_index[i] = header.index(cols[i])  
elif cols[i] in required:  
self._problems.MissingColumn(file_name, cols[i])  
 
row_num = 1  
for row in reader:  
row_num += 1  
if len(row) == 0: # skip extra empty lines in file  
continue  
 
if len(row) > len(header):  
self._problems.OtherProblem('Found too many cells (commas) in line '  
'%d of file "%s". Every row in the file '  
'should have the same number of cells as '  
'the header (first line) does.' %  
(row_num, file_name), (file_name, row_num),  
type=TYPE_WARNING)  
 
if len(row) < len(header):  
self._problems.OtherProblem('Found missing cells (commas) in line '  
'%d of file "%s". Every row in the file '  
'should have the same number of cells as '  
'the header (first line) does.' %  
(row_num, file_name), (file_name, row_num),  
type=TYPE_WARNING)  
 
result = [None] * len(cols)  
unicode_error_columns = [] # A list of column numbers with an error  
for i in range(len(cols)):  
ci = col_index[i]  
if ci >= 0:  
if len(row) <= ci: # handle short CSV rows  
result[i] = u''  
else:  
try:  
result[i] = row[ci].decode('utf-8').strip()  
except UnicodeDecodeError:  
# Replace all invalid characters with  
# REPLACEMENT CHARACTER (U+FFFD)  
result[i] = codecs.getdecoder("utf8")(row[ci],  
errors="replace")[0].strip()  
unicode_error_columns.append(i)  
 
for i in unicode_error_columns:  
self._problems.InvalidValue(cols[i], result[i],  
'Unicode error',  
(file_name, row_num, result, cols))  
yield (result, row_num, cols)  
 
def _HasFile(self, file_name):  
"""Returns True if there's a file in the current feed with the  
given file_name in the current feed."""  
if self._zip:  
return file_name in self._zip.namelist()  
else:  
file_path = os.path.join(self._path, file_name)  
return os.path.exists(file_path) and os.path.isfile(file_path)  
 
def _FileContents(self, file_name):  
results = None  
if self._zip:  
try:  
results = self._zip.read(file_name)  
except KeyError: # file not found in archve  
self._problems.MissingFile(file_name)  
return None  
else:  
try:  
data_file = open(os.path.join(self._path, file_name), 'rb')  
results = data_file.read()  
except IOError: # file not found  
self._problems.MissingFile(file_name)  
return None  
 
if not results:  
self._problems.EmptyFile(file_name)  
return results  
 
def _LoadAgencies(self):  
for (d, row_num, header, row) in self._ReadCsvDict('agency.txt',  
Agency._FIELD_NAMES,  
Agency._REQUIRED_FIELD_NAMES):  
self._problems.SetFileContext('agency.txt', row_num, row, header)  
agency = Agency(field_dict=d)  
self._schedule.AddAgencyObject(agency, self._problems)  
self._problems.ClearContext()  
 
def _LoadStops(self):  
for (d, row_num, header, row) in self._ReadCsvDict(  
'stops.txt',  
Stop._FIELD_NAMES,  
Stop._REQUIRED_FIELD_NAMES):  
self._problems.SetFileContext('stops.txt', row_num, row, header)  
 
stop = Stop(field_dict=d)  
stop.Validate(self._problems)  
self._schedule.AddStopObject(stop, self._problems)  
 
self._problems.ClearContext()  
 
def _LoadRoutes(self):  
for (d, row_num, header, row) in self._ReadCsvDict(  
'routes.txt',  
Route._FIELD_NAMES,  
Route._REQUIRED_FIELD_NAMES):  
self._problems.SetFileContext('routes.txt', row_num, row, header)  
 
route = Route(field_dict=d)  
self._schedule.AddRouteObject(route, self._problems)  
 
self._problems.ClearContext()  
 
def _LoadCalendar(self):  
file_name = 'calendar.txt'  
file_name_dates = 'calendar_dates.txt'  
if not self._HasFile(file_name) and not self._HasFile(file_name_dates):  
self._problems.MissingFile(file_name)  
return  
 
# map period IDs to (period object, (file_name, row_num, row, cols))  
periods = {}  
 
# process calendar.txt  
if self._HasFile(file_name):  
has_useful_contents = False  
for (row, row_num, cols) in \  
self._ReadCSV(file_name,  
ServicePeriod._FIELD_NAMES,  
ServicePeriod._FIELD_NAMES_REQUIRED):  
context = (file_name, row_num, row, cols)  
self._problems.SetFileContext(*context)  
 
period = ServicePeriod(field_list=row)  
 
if period.service_id in periods:  
self._problems.DuplicateID('service_id', period.service_id)  
else:  
periods[period.service_id] = (period, context)  
self._problems.ClearContext()  
 
# process calendar_dates.txt  
if self._HasFile(file_name_dates):  
# ['service_id', 'date', 'exception_type']  
fields = ServicePeriod._FIELD_NAMES_CALENDAR_DATES  
for (row, row_num, cols) in self._ReadCSV(file_name_dates,  
fields, fields):  
context = (file_name_dates, row_num, row, cols)  
self._problems.SetFileContext(*context)  
 
service_id = row[0]  
 
period = None  
if service_id in periods:  
period = periods[service_id][0]  
else:  
period = ServicePeriod(service_id)  
periods[period.service_id] = (period, context)  
 
exception_type = row[2]  
if exception_type == u'1':  
period.SetDateHasService(row[1], True, self._problems)  
elif exception_type == u'2':  
period.SetDateHasService(row[1], False, self._problems)  
else:  
self._problems.InvalidValue('exception_type', exception_type)  
self._problems.ClearContext()  
 
# Now insert the periods into the schedule object, so that they're  
# validated with both calendar and calendar_dates info present  
for period, context in periods.values():  
self._problems.SetFileContext(*context)  
self._schedule.AddServicePeriodObject(period, self._problems)  
self._problems.ClearContext()  
 
def _LoadShapes(self):  
if not self._HasFile('shapes.txt'):  
return  
 
shapes = {} # shape_id to tuple  
for (row, row_num, cols) in self._ReadCSV('shapes.txt',  
Shape._FIELD_NAMES,  
Shape._REQUIRED_FIELD_NAMES):  
file_context = ('shapes.txt', row_num, row, cols)  
self._problems.SetFileContext(*file_context)  
 
(shape_id, lat, lon, seq, dist) = row  
if IsEmpty(shape_id):  
self._problems.MissingValue('shape_id')  
continue  
try:  
seq = int(seq)  
except (TypeError, ValueError):  
self._problems.InvalidValue('shape_pt_sequence', seq,  
'Value should be a number (0 or higher)')  
continue  
 
shapes.setdefault(shape_id, []).append((seq, lat, lon, dist, file_context))  
self._problems.ClearContext()  
 
for shape_id, points in shapes.items():  
shape = Shape(shape_id)  
points.sort()  
if points and points[0][0] < 0:  
self._problems.InvalidValue('shape_pt_sequence', points[0][0],  
'In shape %s, a negative sequence number '  
'%d was found; sequence numbers should be '  
'0 or higher.' % (shape_id, points[0][0]))  
 
last_seq = None  
for (seq, lat, lon, dist, file_context) in points:  
if (seq == last_seq):  
self._problems.SetFileContext(*file_context)  
self._problems.InvalidValue('shape_pt_sequence', seq,  
'The sequence number %d occurs more '  
'than once in shape %s.' %  
(seq, shape_id))  
last_seq = seq  
shape.AddPoint(lat, lon, dist, self._problems)  
self._problems.ClearContext()  
 
self._schedule.AddShapeObject(shape, self._problems)  
 
def _LoadTrips(self):  
for (d, row_num, header, row) in self._ReadCsvDict(  
'trips.txt',  
Trip._FIELD_NAMES,  
Trip._REQUIRED_FIELD_NAMES):  
self._problems.SetFileContext('trips.txt', row_num, row, header)  
 
trip = Trip(field_dict=d)  
self._schedule.AddTripObject(trip, self._problems)  
 
self._problems.ClearContext()  
 
def _LoadFares(self):  
if not self._HasFile('fare_attributes.txt'):  
return  
for (row, row_num, cols) in self._ReadCSV('fare_attributes.txt',  
Fare._FIELD_NAMES,  
Fare._REQUIRED_FIELD_NAMES):  
self._problems.SetFileContext('fare_attributes.txt', row_num, row, cols)  
 
fare = Fare(field_list=row)  
self._schedule.AddFareObject(fare, self._problems)  
 
self._problems.ClearContext()  
 
def _LoadFareRules(self):  
if not self._HasFile('fare_rules.txt'):  
return  
for (row, row_num, cols) in self._ReadCSV('fare_rules.txt',  
FareRule._FIELD_NAMES,  
FareRule._REQUIRED_FIELD_NAMES):  
self._problems.SetFileContext('fare_rules.txt', row_num, row, cols)  
 
rule = FareRule(field_list=row)  
self._schedule.AddFareRuleObject(rule, self._problems)  
 
self._problems.ClearContext()  
 
def _LoadHeadways(self):  
file_name = 'frequencies.txt'  
if not self._HasFile(file_name): # headways are an optional feature  
return  
 
# ['trip_id', 'start_time', 'end_time', 'headway_secs']  
fields = Trip._FIELD_NAMES_HEADWAY  
modified_trips = {}  
for (row, row_num, cols) in self._ReadCSV(file_name, fields, fields):  
self._problems.SetFileContext(file_name, row_num, row, cols)  
(trip_id, start_time, end_time, headway_secs) = row  
try:  
trip = self._schedule.GetTrip(trip_id)  
trip.AddHeadwayPeriod(start_time, end_time, headway_secs,  
self._problems)  
modified_trips[trip_id] = trip  
except KeyError:  
self._problems.InvalidValue('trip_id', trip_id)  
self._problems.ClearContext()  
 
for trip in modified_trips.values():  
trip.Validate(self._problems)  
 
def _LoadStopTimes(self):  
for (row, row_num, cols) in self._ReadCSV('stop_times.txt',  
StopTime._FIELD_NAMES,  
StopTime._REQUIRED_FIELD_NAMES):  
file_context = ('stop_times.txt', row_num, row, cols)  
self._problems.SetFileContext(*file_context)  
 
(trip_id, arrival_time, departure_time, stop_id, stop_sequence,  
stop_headsign, pickup_type, drop_off_type, shape_dist_traveled) = row  
 
try:  
sequence = int(stop_sequence)  
except (TypeError, ValueError):  
self._problems.InvalidValue('stop_sequence', stop_sequence,  
'This should be a number.')  
continue  
if sequence < 0:  
self._problems.InvalidValue('stop_sequence', sequence,  
'Sequence numbers should be 0 or higher.')  
 
if stop_id not in self._schedule.stops:  
self._problems.InvalidValue('stop_id', stop_id,  
'This value wasn\'t defined in stops.txt')  
continue  
stop = self._schedule.stops[stop_id]  
if trip_id not in self._schedule.trips:  
self._problems.InvalidValue('trip_id', trip_id,  
'This value wasn\'t defined in trips.txt')  
continue  
trip = self._schedule.trips[trip_id]  
 
# If self._problems.Report returns then StopTime.__init__ will return  
# even if the StopTime object has an error. Thus this code may add a  
# StopTime that didn't validate to the database.  
# Trip.GetStopTimes then tries to make a StopTime from the invalid data  
# and calls the problem reporter for errors. An ugly solution is to  
# wrap problems and a better solution is to move all validation out of  
# __init__. For now make sure Trip.GetStopTimes gets a problem reporter  
# when called from Trip.Validate.  
stop_time = StopTime(self._problems, stop, arrival_time,  
departure_time, stop_headsign,  
pickup_type, drop_off_type,  
shape_dist_traveled, stop_sequence=sequence)  
trip._AddStopTimeObjectUnordered(stop_time, self._schedule)  
self._problems.ClearContext()  
 
# stop_times are validated in Trip.ValidateChildren, called by  
# Schedule.Validate  
 
def _LoadTransfers(self):  
file_name = 'transfers.txt'  
if not self._HasFile(file_name): # transfers are an optional feature  
return  
for (d, row_num, header, row) in self._ReadCsvDict(file_name,  
Transfer._FIELD_NAMES,  
Transfer._REQUIRED_FIELD_NAMES):  
self._problems.SetFileContext(file_name, row_num, row, header)  
transfer = Transfer(field_dict=d)  
self._schedule.AddTransferObject(transfer, self._problems)  
self._problems.ClearContext()  
 
def Load(self):  
self._problems.ClearContext()  
if not self._DetermineFormat():  
return self._schedule  
 
self._CheckFileNames()  
 
self._LoadAgencies()  
self._LoadStops()  
self._LoadRoutes()  
self._LoadCalendar()  
self._LoadShapes()  
self._LoadTrips()  
self._LoadHeadways()  
if self._load_stop_times:  
self._LoadStopTimes()  
self._LoadFares()  
self._LoadFareRules()  
self._LoadTransfers()  
 
if self._zip:  
self._zip.close()  
self._zip = None  
 
if self._extra_validation:  
self._schedule.Validate(self._problems, validate_children=False)  
 
return self._schedule  
 
 
class ShapeLoader(Loader):  
"""A subclass of Loader that only loads the shapes from a GTFS file."""  
 
def __init__(self, *args, **kwargs):  
"""Initialize a new ShapeLoader object.  
 
See Loader.__init__ for argument documentation.  
"""  
Loader.__init__(self, *args, **kwargs)  
 
def Load(self):  
self._LoadShapes()  
return self._schedule  
 
 Binary files a/origin-src/transitfeed-1.2.5/transitfeed/_transitfeed.pyc and /dev/null differ
#!/usr/bin/python2.4  
#  
# Copyright 2007 Google Inc. All Rights Reserved.  
#  
# Licensed under the Apache License, Version 2.0 (the "License");  
# you may not use this file except in compliance with the License.  
# You may obtain a copy of the License at  
#  
# http://www.apache.org/licenses/LICENSE-2.0  
#  
# Unless required by applicable law or agreed to in writing, software  
# distributed under the License is distributed on an "AS IS" BASIS,  
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.  
# See the License for the specific language governing permissions and  
# limitations under the License.  
 
"""A library for manipulating points and polylines.  
 
This is a library for creating and manipulating points on the unit  
sphere, as an approximate model of Earth. The primary use of this  
library is to make manipulation and matching of polylines easy in the  
transitfeed library.  
 
NOTE: in this library, Earth is modelled as a sphere, whereas  
GTFS specifies that latitudes and longitudes are in WGS84. For the  
purpose of comparing and matching latitudes and longitudes that  
are relatively close together on the surface of the earth, this  
is adequate; for other purposes, this library may not be accurate  
enough.  
"""  
 
__author__ = 'chris.harrelson.code@gmail.com (Chris Harrelson)'  
 
import copy  
import decimal  
import heapq  
import math  
 
class ShapeError(Exception):  
"""Thrown whenever there is a shape parsing error."""  
pass  
 
 
EARTH_RADIUS_METERS = 6371010.0  
 
 
class Point(object):  
"""  
A class representing a point on the unit sphere in three dimensions.  
"""  
def __init__(self, x, y, z):  
self.x = x  
self.y = y  
self.z = z  
 
def __hash__(self):  
return hash((self.x, self.y, self.z))  
 
def __cmp__(self, other):  
if not isinstance(other, Point):  
raise TypeError('Point.__cmp__(x,y) requires y to be a "Point", '  
'not a "%s"' % type(other).__name__)  
return cmp((self.x, self.y, self.z), (other.x, other.y, other.z))  
 
def __str__(self):  
return "(%.15f, %.15f, %.15f) " % (self.x, self.y, self.z)  
 
def Norm2(self):  
"""  
Returns the L_2 (Euclidean) norm of self.  
"""  
sum = self.x * self.x + self.y * self.y + self.z * self.z  
return math.sqrt(float(sum))  
 
def IsUnitLength(self):  
return abs(self.Norm2() - 1.0) < 1e-14  
 
def Plus(self, other):  
"""  
Returns a new point which is the pointwise sum of self and other.  
"""  
return Point(self.x + other.x,  
self.y + other.y,  
self.z + other.z)  
 
def Minus(self, other):  
"""  
Returns a new point which is the pointwise subtraction of other from  
self.  
"""  
return Point(self.x - other.x,  
self.y - other.y,  
self.z - other.z)  
 
def DotProd(self, other):  
"""  
Returns the (scalar) dot product of self with other.  
"""  
return self.x * other.x + self.y * other.y + self.z * other.z  
 
def Times(self, val):  
"""  
Returns a new point which is pointwise multiplied by val.  
"""  
return Point(self.x * val, self.y * val, self.z * val)  
 
def Normalize(self):  
"""  
Returns a unit point in the same direction as self.  
"""  
return self.Times(1 / self.Norm2())  
 
def RobustCrossProd(self, other):  
"""  
A robust version of cross product. If self and other  
are not nearly the same point, returns the same value  
as CrossProd() modulo normalization. Otherwise returns  
an arbitrary unit point orthogonal to self.  
"""  
assert(self.IsUnitLength() and other.IsUnitLength())  
x = self.Plus(other).CrossProd(other.Minus(self))  
if abs(x.x) > 1e-15 or abs(x.y) > 1e-15 or abs(x.z) > 1e-15:  
return x.Normalize()  
else:  
return self.Ortho()  
 
def LargestComponent(self):  
"""  
Returns (i, val) where i is the component index (0 - 2)  
which has largest absolute value and val is the value  
of the component.  
"""  
if abs(self.x) > abs(self.y):  
if abs(self.x) > abs(self.z):  
return (0, self.x)  
else:  
return (2, self.z)  
else:  
if abs(self.y) > abs(self.z):  
return (1, self.y)  
else:  
return (2, self.z)  
 
def Ortho(self):  
"""Returns a unit-length point orthogonal to this point"""  
(index, val) = self.LargestComponent()  
index = index - 1  
if index < 0:  
index = 2  
temp = Point(0.012, 0.053, 0.00457)  
if index == 0:  
temp.x = 1  
elif index == 1:  
temp.y = 1  
elif index == 2:  
temp.z = 1  
return self.CrossProd(temp).Normalize()  
 
def CrossProd(self, other):  
"""  
Returns the cross product of self and other.  
"""  
return Point(  
self.y * other.z - self.z * other.y,  
self.z * other.x - self.x * other.z,  
self.x * other.y - self.y * other.x)  
 
@staticmethod  
def _approxEq(a, b):  
return abs(a - b) < 1e-11  
 
def Equals(self, other):  
"""  
Returns true of self and other are approximately equal.  
"""  
return (self._approxEq(self.x, other.x)  
and self._approxEq(self.y, other.y)  
and self._approxEq(self.z, other.z))  
 
def Angle(self, other):  
"""  
Returns the angle in radians between self and other.  
"""  
return math.atan2(self.CrossProd(other).Norm2(),  
self.DotProd(other))  
 
def ToLatLng(self):  
"""  
Returns that latitude and longitude that this point represents  
under a spherical Earth model.  
"""  
rad_lat = math.atan2(self.z, math.sqrt(self.x * self.x + self.y * self.y))  
rad_lng = math.atan2(self.y, self.x)  
return (rad_lat * 180.0 / math.pi, rad_lng * 180.0 / math.pi)  
 
@staticmethod  
def FromLatLng(lat, lng):  
"""  
Returns a new point representing this latitude and longitude under  
a spherical Earth model.  
"""  
phi = lat * (math.pi / 180.0)  
theta = lng * (math.pi / 180.0)  
cosphi = math.cos(phi)  
return Point(math.cos(theta) * cosphi,  
math.sin(theta) * cosphi,  
math.sin(phi))  
 
def GetDistanceMeters(self, other):  
assert(self.IsUnitLength() and other.IsUnitLength())  
return self.Angle(other) * EARTH_RADIUS_METERS  
 
 
def SimpleCCW(a, b, c):  
"""  
Returns true if the triangle abc is oriented counterclockwise.  
"""  
return c.CrossProd(a).DotProd(b) > 0  
 
def GetClosestPoint(x, a, b):  
"""  
Returns the point on the great circle segment ab closest to x.  
"""  
assert(x.IsUnitLength())  
assert(a.IsUnitLength())  
assert(b.IsUnitLength())  
 
a_cross_b = a.RobustCrossProd(b)  
# project to the great circle going through a and b  
p = x.Minus(  
a_cross_b.Times(  
x.DotProd(a_cross_b) / a_cross_b.Norm2()))  
 
# if p lies between a and b, return it  
if SimpleCCW(a_cross_b, a, p) and SimpleCCW(p, b, a_cross_b):  
return p.Normalize()  
 
# otherwise return the closer of a or b  
if x.Minus(a).Norm2() <= x.Minus(b).Norm2():  
return a  
else:  
return b  
 
 
class Poly(object):  
"""  
A class representing a polyline.  
"""  
def __init__(self, points = [], name=None):  
self._points = list(points)  
self._name = name  
 
def AddPoint(self, p):  
"""  
Adds a new point to the end of the polyline.  
"""  
assert(p.IsUnitLength())  
self._points.append(p)  
 
def GetName(self):  
return self._name  
 
def GetPoint(self, i):  
return self._points[i]  
 
def GetPoints(self):  
return self._points  
 
def GetNumPoints(self):  
return len(self._points)  
 
def _GetPointSafe(self, i):  
try:  
return self.GetPoint(i)  
except IndexError:  
return None  
 
def GetClosestPoint(self, p):  
"""  
Returns (closest_p, closest_i), where closest_p is the closest point  
to p on the piecewise linear curve represented by the polyline,  
and closest_i is the index of the point on the polyline just before  
the polyline segment that contains closest_p.  
"""  
assert(len(self._points) > 0)  
closest_point = self._points[0]  
closest_i = 0  
 
for i in range(0, len(self._points) - 1):  
(a, b) = (self._points[i], self._points[i+1])  
cur_closest_point = GetClosestPoint(p, a, b)  
if p.Angle(cur_closest_point) < p.Angle(closest_point):  
closest_point = cur_closest_point.Normalize()  
closest_i = i  
 
return (closest_point, closest_i)  
 
def LengthMeters(self):  
"""Return length of this polyline in meters."""  
assert(len(self._points) > 0)  
length = 0  
for i in range(0, len(self._points) - 1):  
length += self._points[i].GetDistanceMeters(self._points[i+1])  
return length  
 
def Reversed(self):  
"""Return a polyline that is the reverse of this polyline."""  
return Poly(reversed(self.GetPoints()), self.GetName())  
 
def CutAtClosestPoint(self, p):  
"""  
Let x be the point on the polyline closest to p. Then  
CutAtClosestPoint returns two new polylines, one representing  
the polyline from the beginning up to x, and one representing  
x onwards to the end of the polyline. x is the first point  
returned in the second polyline.  
"""  
(closest, i) = self.GetClosestPoint(p)  
 
tmp = [closest]  
tmp.extend(self._points[i+1:])  
return (Poly(self._points[0:i+1]),  
Poly(tmp))  
 
def GreedyPolyMatchDist(self, shape):  
"""  
Tries a greedy matching algorithm to match self to the  
given shape. Returns the maximum distance in meters of  
any point in self to its matched point in shape under the  
algorithm.  
 
Args: shape, a Poly object.  
"""  
tmp_shape = Poly(shape.GetPoints())  
max_radius = 0  
for (i, point) in enumerate(self._points):  
tmp_shape = tmp_shape.CutAtClosestPoint(point)[1]  
dist = tmp_shape.GetPoint(0).GetDistanceMeters(point)  
max_radius = max(max_radius, dist)  
return max_radius  
 
@staticmethod  
def MergePolys(polys, merge_point_threshold=10):  
"""  
Merge multiple polylines, in the order that they were passed in.  
Merged polyline will have the names of their component parts joined by ';'.  
Example: merging [a,b], [c,d] and [e,f] will result in [a,b,c,d,e,f].  
However if the endpoints of two adjacent polylines are less than  
merge_point_threshold meters apart, we will only use the first endpoint in  
the merged polyline.  
"""  
name = ";".join((p.GetName(), '')[p.GetName() is None] for p in polys)  
merged = Poly([], name)  
if polys:  
first_poly = polys[0]  
for p in first_poly.GetPoints():  
merged.AddPoint(p)  
last_point = merged._GetPointSafe(-1)  
for poly in polys[1:]:  
first_point = poly._GetPointSafe(0)  
if (last_point and first_point and  
last_point.GetDistanceMeters(first_point) <= merge_point_threshold):  
points = poly.GetPoints()[1:]  
else:  
points = poly.GetPoints()  
for p in points:  
merged.AddPoint(p)  
last_point = merged._GetPointSafe(-1)  
return merged  
 
 
def __str__(self):  
return self._ToString(str)  
 
def ToLatLngString(self):  
return self._ToString(lambda p: str(p.ToLatLng()))  
 
def _ToString(self, pointToStringFn):  
return "%s: %s" % (self.GetName() or "",  
", ".join([pointToStringFn(p) for p in self._points]))  
 
 
class PolyCollection(object):  
"""  
A class representing a collection of polylines.  
"""  
def __init__(self):  
self._name_to_shape = {}  
pass  
 
def AddPoly(self, poly, smart_duplicate_handling=True):  
"""  
Adds a new polyline to the collection.  
"""  
inserted_name = poly.GetName()  
if poly.GetName() in self._name_to_shape:  
if not smart_duplicate_handling:  
raise ShapeError("Duplicate shape found: " + poly.GetName())  
 
print ("Warning: duplicate shape id being added to collection: " +  
poly.GetName())  
if poly.GreedyPolyMatchDist(self._name_to_shape[poly.GetName()]) < 10:  
print " (Skipping as it apears to be an exact duplicate)"  
else:  
print " (Adding new shape variant with uniquified name)"  
inserted_name = "%s-%d" % (inserted_name, len(self._name_to_shape))  
self._name_to_shape[inserted_name] = poly  
 
def NumPolys(self):  
return len(self._name_to_shape)  
 
def FindMatchingPolys(self, start_point, end_point, max_radius=150):  
"""  
Returns a list of polylines in the collection that have endpoints  
within max_radius of the given start and end points.  
"""  
matches = []  
for shape in self._name_to_shape.itervalues():  
if start_point.GetDistanceMeters(shape.GetPoint(0)) < max_radius and \  
end_point.GetDistanceMeters(shape.GetPoint(-1)) < max_radius:  
matches.append(shape)  
return matches  
 
class PolyGraph(PolyCollection):  
"""  
A class representing a graph where the edges are polylines.  
"""  
def __init__(self):  
PolyCollection.__init__(self)  
self._nodes = {}  
 
def AddPoly(self, poly, smart_duplicate_handling=True):  
PolyCollection.AddPoly(self, poly, smart_duplicate_handling)  
start_point = poly.GetPoint(0)  
end_point = poly.GetPoint(-1)  
self._AddNodeWithEdge(start_point, poly)  
self._AddNodeWithEdge(end_point, poly)  
 
def _AddNodeWithEdge(self, point, edge):  
if point in self._nodes:  
self._nodes[point].add(edge)  
else:  
self._nodes[point] = set([edge])  
 
def ShortestPath(self, start, goal):  
"""Uses the A* algorithm to find a shortest path between start and goal.  
 
For more background see http://en.wikipedia.org/wiki/A-star_algorithm  
 
Some definitions:  
g(x): The actual shortest distance traveled from initial node to current  
node.  
h(x): The estimated (or "heuristic") distance from current node to goal.  
We use the distance on Earth from node to goal as the heuristic.  
This heuristic is both admissible and monotonic (see wikipedia for  
more details).  
f(x): The sum of g(x) and h(x), used to prioritize elements to look at.  
 
Arguments:  
start: Point that is in the graph, start point of the search.  
goal: Point that is in the graph, end point for the search.  
 
Returns:  
A Poly object representing the shortest polyline through the graph from  
start to goal, or None if no path found.  
"""  
 
assert start in self._nodes  
assert goal in self._nodes  
closed_set = set() # Set of nodes already evaluated.  
open_heap = [(0, start)] # Nodes to visit, heapified by f(x).  
open_set = set([start]) # Same as open_heap, but a set instead of a heap.  
g_scores = { start: 0 } # Distance from start along optimal path  
came_from = {} # Map to reconstruct optimal path once we're done.  
while open_set:  
(f_x, x) = heapq.heappop(open_heap)  
open_set.remove(x)  
if x == goal:  
return self._ReconstructPath(came_from, goal)  
closed_set.add(x)  
edges = self._nodes[x]  
for edge in edges:  
if edge.GetPoint(0) == x:  
y = edge.GetPoint(-1)  
else:  
y = edge.GetPoint(0)  
if y in closed_set:  
continue  
tentative_g_score = g_scores[x] + edge.LengthMeters()  
tentative_is_better = False  
if y not in open_set:  
h_y = y.GetDistanceMeters(goal)  
f_y = tentative_g_score + h_y  
open_set.add(y)  
heapq.heappush(open_heap, (f_y, y))  
tentative_is_better = True  
elif tentative_g_score < g_scores[y]:  
tentative_is_better = True  
if tentative_is_better:  
came_from[y] = (x, edge)  
g_scores[y] = tentative_g_score  
return None  
 
def _ReconstructPath(self, came_from, current_node):  
"""  
Helper method for ShortestPath, to reconstruct path.  
 
Arguments:  
came_from: a dictionary mapping Point to (Point, Poly) tuples.  
This dictionary keeps track of the previous neighbor to a node, and  
the edge used to get from the previous neighbor to the node.  
current_node: the current Point in the path.  
 
Returns:  
A Poly that represents the path through the graph from the start of the  
search to current_node.  
"""  
if current_node in came_from:  
(previous_node, previous_edge) = came_from[current_node]  
if previous_edge.GetPoint(0) == current_node:  
previous_edge = previous_edge.Reversed()  
p = self._ReconstructPath(came_from, previous_node)  
return Poly.MergePolys([p, previous_edge], merge_point_threshold=0)  
else:  
return Poly([], '')  
 
def FindShortestMultiPointPath(self, points, max_radius=150, keep_best_n=10,  
verbosity=0):  
"""  
Return a polyline, representing the shortest path through this graph that  
has edge endpoints on each of a given list of points in sequence. We allow  
fuzziness in matching of input points to points in this graph.  
 
We limit ourselves to a view of the best keep_best_n paths at any time, as a  
greedy optimization.  
"""  
assert len(points) > 1  
nearby_points = []  
paths_found = [] # A heap sorted by inverse path length.  
 
for i, point in enumerate(points):  
nearby = [p for p in self._nodes.iterkeys()  
if p.GetDistanceMeters(point) < max_radius]  
if verbosity >= 2:  
print ("Nearby points for point %d %s: %s"  
% (i + 1,  
str(point.ToLatLng()),  
", ".join([str(n.ToLatLng()) for n in nearby])))  
if nearby:  
nearby_points.append(nearby)  
else:  
print "No nearby points found for point %s" % str(point.ToLatLng())  
return None  
 
pathToStr = lambda start, end, path: (" Best path %s -> %s: %s"  
% (str(start.ToLatLng()),  
str(end.ToLatLng()),  
path and path.GetName() or  
"None"))  
if verbosity >= 3:  
print "Step 1"  
step = 2  
 
start_points = nearby_points[0]  
end_points = nearby_points[1]  
 
for start in start_points:  
for end in end_points:  
path = self.ShortestPath(start, end)  
if verbosity >= 3:  
print pathToStr(start, end, path)  
PolyGraph._AddPathToHeap(paths_found, path, keep_best_n)  
 
for possible_points in nearby_points[2:]:  
if verbosity >= 3:  
print "\nStep %d" % step  
step += 1  
new_paths_found = []  
 
start_end_paths = {} # cache of shortest paths between (start, end) pairs  
for score, path in paths_found:  
start = path.GetPoint(-1)  
for end in possible_points:  
if (start, end) in start_end_paths:  
new_segment = start_end_paths[(start, end)]  
else:  
new_segment = self.ShortestPath(start, end)  
if verbosity >= 3:  
print pathToStr(start, end, new_segment)  
start_end_paths[(start, end)] = new_segment  
 
if new_segment:  
new_path = Poly.MergePolys([path, new_segment],  
merge_point_threshold=0)  
PolyGraph._AddPathToHeap(new_paths_found, new_path, keep_best_n)  
paths_found = new_paths_found  
 
if paths_found:  
best_score, best_path = max(paths_found)  
return best_path  
else:  
return None  
 
@staticmethod  
def _AddPathToHeap(heap, path, keep_best_n):  
if path and path.GetNumPoints():  
new_item = (-path.LengthMeters(), path)  
if new_item not in heap:  
if len(heap) < keep_best_n:  
heapq.heappush(heap, new_item)  
else:  
heapq.heapreplace(heap, new_item)  
 
#!/usr/bin/python2.5  
 
# Copyright (C) 2009 Google Inc.  
#  
# Licensed under the Apache License, Version 2.0 (the "License");  
# you may not use this file except in compliance with the License.  
# You may obtain a copy of the License at  
#  
# http://www.apache.org/licenses/LICENSE-2.0  
#  
# Unless required by applicable law or agreed to in writing, software  
# distributed under the License is distributed on an "AS IS" BASIS,  
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.  
# See the License for the specific language governing permissions and  
# limitations under the License.  
 
 
import optparse  
import sys  
 
 
class OptionParserLongError(optparse.OptionParser):  
"""OptionParser subclass that includes list of options above error message."""  
def error(self, msg):  
print >>sys.stderr, self.format_help()  
print >>sys.stderr, '\n\n%s: error: %s\n\n' % (self.get_prog_name(), msg)  
sys.exit(2)  
 
 
def RunWithCrashHandler(f):  
try:  
exit_code = f()  
sys.exit(exit_code)  
except (SystemExit, KeyboardInterrupt):  
raise  
except:  
import inspect  
import traceback  
 
# Save trace and exception now. These calls look at the most recently  
# raised exception. The code that makes the report might trigger other  
# exceptions.  
original_trace = inspect.trace(3)[1:]  
formatted_exception = traceback.format_exception_only(*(sys.exc_info()[:2]))  
 
apology = """Yikes, the program threw an unexpected exception!  
 
Hopefully a complete report has been saved to transitfeedcrash.txt,  
though if you are seeing this message we've already disappointed you once  
today. Please include the report in a new issue at  
http://code.google.com/p/googletransitdatafeed/issues/entry  
or an email to the public group googletransitdatafeed@googlegroups.com. Sorry!  
 
"""  
dashes = '%s\n' % ('-' * 60)  
dump = []  
dump.append(apology)  
dump.append(dashes)  
try:  
import transitfeed  
dump.append("transitfeed version %s\n\n" % transitfeed.__version__)  
except NameError:  
# Oh well, guess we won't put the version in the report  
pass  
 
for (frame_obj, filename, line_num, fun_name, context_lines,  
context_index) in original_trace:  
dump.append('File "%s", line %d, in %s\n' % (filename, line_num,  
fun_name))  
if context_lines:  
for (i, line) in enumerate(context_lines):  
if i == context_index:  
dump.append(' --> %s' % line)  
else:  
dump.append(' %s' % line)  
for local_name, local_val in frame_obj.f_locals.items():  
try:  
truncated_val = str(local_val)[0:500]  
except Exception, e:  
dump.append(' Exception in str(%s): %s' % (local_name, e))  
else:  
if len(truncated_val) >= 500:  
truncated_val = '%s...' % truncated_val[0:499]  
dump.append(' %s = %s\n' % (local_name, truncated_val))  
dump.append('\n')  
 
dump.append(''.join(formatted_exception))  
 
open('transitfeedcrash.txt', 'w').write(''.join(dump))  
 
print ''.join(dump)  
print  
print dashes  
print apology  
 
try:  
raw_input('Press enter to continue...')  
except EOFError:  
# Ignore stdin being closed. This happens during some tests.  
pass  
sys.exit(127)  
 
 
# Pick one of two defaultdict implementations. A native version was added to  
# the collections library in python 2.5. If that is not available use Jason's  
# pure python recipe. He gave us permission to distribute it.  
 
# On Mon, Nov 30, 2009 at 07:27, jason kirtland <jek at discorporate.us> wrote:  
# >  
# > Hi Tom, sure thing! It's not easy to find on the cookbook site, but the  
# > recipe is under the Python license.  
# >  
# > Cheers,  
# > Jason  
# >  
# > On Thu, Nov 26, 2009 at 3:03 PM, Tom Brown <tom.brown.code@gmail.com> wrote:  
# >  
# >> I would like to include http://code.activestate.com/recipes/523034/ in  
# >> http://code.google.com/p/googletransitdatafeed/wiki/TransitFeedDistribution  
# >> which is distributed under the Apache License, Version 2.0 with Copyright  
# >> Google. May we include your code with a comment in the source pointing at  
# >> the original URL? Thanks, Tom Brown  
 
try:  
# Try the native implementation first  
from collections import defaultdict  
except:  
# Fallback for python2.4, which didn't include collections.defaultdict  
class defaultdict(dict):  
def __init__(self, default_factory=None, *a, **kw):  
if (default_factory is not None and  
not hasattr(default_factory, '__call__')):  
raise TypeError('first argument must be callable')  
dict.__init__(self, *a, **kw)  
self.default_factory = default_factory  
def __getitem__(self, key):  
try:  
return dict.__getitem__(self, key)  
except KeyError:  
return self.__missing__(key)  
def __missing__(self, key):  
if self.default_factory is None:  
raise KeyError(key)  
self[key] = value = self.default_factory()  
return value  
def __reduce__(self):  
if self.default_factory is None:  
args = tuple()  
else:  
args = self.default_factory,  
return type(self), args, None, None, self.items()  
def copy(self):  
return self.__copy__()  
def __copy__(self):  
return type(self)(self.default_factory, self)  
def __deepcopy__(self, memo):  
import copy  
return type(self)(self.default_factory,  
copy.deepcopy(self.items()))  
def __repr__(self):  
return 'defaultdict(%s, %s)' % (self.default_factory,  
dict.__repr__(self))  
 
 Binary files a/origin-src/transitfeed-1.2.5/transitfeed/util.pyc and /dev/null differ
#!/usr/bin/python2.5  
 
# Copyright (C) 2007 Google Inc.  
#  
# Licensed under the Apache License, Version 2.0 (the "License");  
# you may not use this file except in compliance with the License.  
# You may obtain a copy of the License at  
#  
# http://www.apache.org/licenses/LICENSE-2.0  
#  
# Unless required by applicable law or agreed to in writing, software  
# distributed under the License is distributed on an "AS IS" BASIS,  
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.  
# See the License for the specific language governing permissions and  
# limitations under the License.  
 
"""  
Filters out trips which are not on the defualt routes and  
set their trip_typeattribute accordingly.  
 
For usage information run unusual_trip_filter.py --help  
"""  
 
__author__ = 'Jiri Semecky <jiri.semecky@gmail.com>'  
 
import codecs  
import os  
import os.path  
import sys  
import time  
import transitfeed  
from transitfeed import util  
 
 
class UnusualTripFilter(object):  
"""Class filtering trips going on unusual paths.  
 
Those are usually trips going to/from depot or changing to another route  
in the middle. Sets the 'trip_type' attribute of the trips.txt dataset  
so that non-standard trips are marked as special (value 1)  
instead of regular (default value 0).  
"""  
 
def __init__ (self, threshold=0.1, force=False, quiet=False, route_type=None):  
self._threshold = threshold  
self._quiet = quiet  
self._force = force  
if route_type in transitfeed.Route._ROUTE_TYPE_NAMES:  
self._route_type = transitfeed.Route._ROUTE_TYPE_NAMES[route_type]  
elif route_type is None:  
self._route_type = None  
else:  
self._route_type = int(route_type)  
 
def filter_line(self, route):  
"""Mark unusual trips for the given route."""  
if self._route_type is not None and self._route_type != route.route_type:  
self.info('Skipping route %s due to different route_type value (%s)' %  
(route['route_id'], route['route_type']))  
return  
self.info('Filtering infrequent trips for route %s.' % route.route_id)  
trip_count = len(route.trips)  
for pattern_id, pattern in route.GetPatternIdTripDict().items():  
ratio = float(1.0 * len(pattern) / trip_count)  
if not self._force:  
if (ratio < self._threshold):  
self.info("\t%d trips on route %s with headsign '%s' recognized "  
"as unusual (ratio %f)" %  
(len(pattern),  
route['route_short_name'],  
pattern[0]['trip_headsign'],  
ratio))  
for trip in pattern:  
trip.trip_type = 1 # special  
self.info("\t\tsetting trip_type of trip %s as special" %  
trip.trip_id)  
else:  
self.info("\t%d trips on route %s with headsign '%s' recognized "  
"as %s (ratio %f)" %  
(len(pattern),  
route['route_short_name'],  
pattern[0]['trip_headsign'],  
('regular', 'unusual')[ratio < self._threshold],  
ratio))  
for trip in pattern:  
trip.trip_type = ('0','1')[ratio < self._threshold]  
self.info("\t\tsetting trip_type of trip %s as %s" %  
(trip.trip_id,  
('regular', 'unusual')[ratio < self._threshold]))  
 
def filter(self, dataset):  
"""Mark unusual trips for all the routes in the dataset."""  
self.info('Going to filter infrequent routes in the dataset')  
for route in dataset.routes.values():  
self.filter_line(route)  
 
def info(self, text):  
if not self._quiet:  
print text.encode("utf-8")  
 
 
def main():  
usage = \  
'''%prog [options] <GTFS.zip>  
 
Filters out trips which do not follow the most common stop sequences and  
sets their trip_type attribute accordingly. <GTFS.zip> is overwritten with  
the modifed GTFS file unless the --output option is used.  
'''  
parser = util.OptionParserLongError(  
usage=usage, version='%prog '+transitfeed.__version__)  
parser.add_option('-o', '--output', dest='output', metavar='FILE',  
help='Name of the output GTFS file (writing to input feed if omitted).')  
parser.add_option('-m', '--memory_db', dest='memory_db', action='store_true',  
help='Force use of in-memory sqlite db.')  
parser.add_option('-t', '--threshold', default=0.1,  
dest='threshold', type='float',  
help='Frequency threshold for considering pattern as non-regular.')  
parser.add_option('-r', '--route_type', default=None,  
dest='route_type', type='string',  
help='Filter only selected route type (specified by number'  
'or one of the following names: ' + \  
', '.join(transitfeed.Route._ROUTE_TYPE_NAMES) + ').')  
parser.add_option('-f', '--override_trip_type', default=False,  
dest='override_trip_type', action='store_true',  
help='Forces overwrite of current trip_type values.')  
parser.add_option('-q', '--quiet', dest='quiet',  
default=False, action='store_true',  
help='Suppress information output.')  
 
(options, args) = parser.parse_args()  
if len(args) != 1:  
parser.error('You must provide the path of a single feed.')  
 
filter = UnusualTripFilter(float(options.threshold),  
force=options.override_trip_type,  
quiet=options.quiet,  
route_type=options.route_type)  
feed_name = args[0]  
feed_name = feed_name.strip()  
filter.info('Loading %s' % feed_name)  
loader = transitfeed.Loader(feed_name, extra_validation=True,  
memory_db=options.memory_db)  
data = loader.Load()  
filter.filter(data)  
print 'Saving data'  
 
# Write the result  
if options.output is None:  
data.WriteGoogleTransitFeed(feed_name)  
else:  
data.WriteGoogleTransitFeed(options.output)  
 
 
if __name__ == '__main__':  
util.RunWithCrashHandler(main)  
 
 
<html>  
<head>  
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">  
<title>FeedValidator: good_feed.zip</title>  
<style>  
body {font-family: Georgia, serif; background-color: white}  
.path {color: gray}  
div.problem {max-width: 500px}  
table.dump td,th {background-color: khaki; padding: 2px; font-family:monospace}  
table.dump td.problem,th.problem {background-color: dc143c; color: white; padding: 2px; font-family:monospace}  
table.count_outside td {vertical-align: top}  
table.count_outside {border-spacing: 0px; }  
table {border-spacing: 5px 0px; margin-top: 3px}  
h3.issueHeader {padding-left: 0.5em}  
h4.issueHeader {padding-left: 1em}  
.pass {background-color: lightgreen}  
.fail {background-color: yellow}  
.pass, .fail {font-size: 16pt}  
.header {background-color: white; font-family: Georgia, serif; padding: 0px}  
th.header {text-align: right; font-weight: normal; color: gray}  
.footer {font-size: 10pt}  
</style>  
</head>  
<body>  
GTFS validation results for feed:<br>  
<code><span class="path">test/data/</span><b>good_feed.zip</b></code>  
<br><br>  
<table>  
<tr><th class="header">Agencies:</th><td class="header"><a href="http://google.com">Autorité de passage de démonstration</a></td></tr>  
<tr><th class="header">Routes:</th><td class="header">5</td></tr>  
<tr><th class="header">Stops:</th><td class="header">10</td></tr>  
<tr><th class="header">Trips:</th><td class="header">11</td></tr>  
<tr><th class="header">Shapes:</th><td class="header">0</td></tr>  
<tr><th class="header">Effective:</th><td class="header">January 01, 2007 to December 31, 2011</td></tr>  
</table>  
<br>  
During the upcoming service dates Sun Apr 18 to Wed Jun 16:  
<table>  
<tr><th class="header">Average trips per date:</th><td class="header">141</td></tr>  
<tr><th class="header">Most trips on a date:</th><td class="header">144, on 17 service dates (Sun Apr 18, Sat Apr 24, Sun Apr 25, ...)</td></tr>  
<tr><th class="header">Least trips on a date:</th><td class="header">140, on 43 service dates (Mon Apr 19, Tue Apr 20, Wed Apr 21, ...)</td></tr>  
</table>  
<br>  
<span class="pass">feed validated successfully</span>  
<br><br>  
 
<div class="footer">  
Generated by <a href="http://code.google.com/p/googletransitdatafeed/wiki/FeedValidator">  
FeedValidator</a> version 1.2.5 on April 18, 2010 at 06:12 PM EST.  
</div>  
</body>  
</html>  
 
  Apache License
  Version 2.0, January 2004
  http://www.apache.org/licenses/
 
  TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
 
  1. Definitions.
 
  "License" shall mean the terms and conditions for use, reproduction,
  and distribution as defined by Sections 1 through 9 of this document.
 
  "Licensor" shall mean the copyright owner or entity authorized by
  the copyright owner that is granting the License.
 
  "Legal Entity" shall mean the union of the acting entity and all
  other entities that control, are controlled by, or are under common
  control with that entity. For the purposes of this definition,
  "control" means (i) the power, direct or indirect, to cause the
  direction or management of such entity, whether by contract or
  otherwise, or (ii) ownership of fifty percent (50%) or more of the
  outstanding shares, or (iii) beneficial ownership of such entity.
 
  "You" (or "Your") shall mean an individual or Legal Entity
  exercising permissions granted by this License.
 
  "Source" form shall mean the preferred form for making modifications,
  including but not limited to software source code, documentation
  source, and configuration files.
 
  "Object" form shall mean any form resulting from mechanical
  transformation or translation of a Source form, including but
  not limited to compiled object code, generated documentation,
  and conversions to other media types.
 
  "Work" shall mean the work of authorship, whether in Source or
  Object form, made available under the License, as indicated by a
  copyright notice that is included in or attached to the work
  (an example is provided in the Appendix below).
 
  "Derivative Works" shall mean any work, whether in Source or Object
  form, that is based on (or derived from) the Work and for which the
  editorial revisions, annotations, elaborations, or other modifications
  represent, as a whole, an original work of authorship. For the purposes
  of this License, Derivative Works shall not include works that remain
  separable from, or merely link (or bind by name) to the interfaces of,
  the Work and Derivative Works thereof.
 
  "Contribution" shall mean any work of authorship, including
  the original version of the Work and any modifications or additions
  to that Work or Derivative Works thereof, that is intentionally
  submitted to Licensor for inclusion in the Work by the copyright owner
  or by an individual or Legal Entity authorized to submit on behalf of
  the copyright owner. For the purposes of this definition, "submitted"
  means any form of electronic, verbal, or written communication sent
  to the Licensor or its representatives, including but not limited to
  communication on electronic mailing lists, source code control systems,
  and issue tracking systems that are managed by, or on behalf of, the
  Licensor for the purpose of discussing and improving the Work, but
  excluding communication that is conspicuously marked or otherwise
  designated in writing by the copyright owner as "Not a Contribution."
 
  "Contributor" shall mean Licensor and any individual or Legal Entity
  on behalf of whom a Contribution has been received by Licensor and
  subsequently incorporated within the Work.
 
  2. Grant of Copyright License. Subject to the terms and conditions of
  this License, each Contributor hereby grants to You a perpetual,
  worldwide, non-exclusive, no-charge, royalty-free, irrevocable
  copyright license to reproduce, prepare Derivative Works of,
  publicly display, publicly perform, sublicense, and distribute the
  Work and such Derivative Works in Source or Object form.
 
  3. Grant of Patent License. Subject to the terms and conditions of
  this License, each Contributor hereby grants to You a perpetual,
  worldwide, non-exclusive, no-charge, royalty-free, irrevocable
  (except as stated in this section) patent license to make, have made,
  use, offer to sell, sell, import, and otherwise transfer the Work,
  where such license applies only to those patent claims licensable
  by such Contributor that are necessarily infringed by their
  Contribution(s) alone or by combination of their Contribution(s)
  with the Work to which such Contribution(s) was submitted. If You
  institute patent litigation against any entity (including a
  cross-claim or counterclaim in a lawsuit) alleging that the Work
  or a Contribution incorporated within the Work constitutes direct
  or contributory patent infringement, then any patent licenses
  granted to You under this License for that Work shall terminate
  as of the date such litigation is filed.
 
  4. Redistribution. You may reproduce and distribute copies of the
  Work or Derivative Works thereof in any medium, with or without
  modifications, and in Source or Object form, provided that You
  meet the following conditions:
 
  (a) You must give any other recipients of the Work or
  Derivative Works a copy of this License; and
 
  (b) You must cause any modified files to carry prominent notices
  stating that You changed the files; and
 
  (c) You must retain, in the Source form of any Derivative Works
  that You distribute, all copyright, patent, trademark, and
  attribution notices from the Source form of the Work,
  excluding those notices that do not pertain to any part of
  the Derivative Works; and
 
  (d) If the Work includes a "NOTICE" text file as part of its
  distribution, then any Derivative Works that You distribute must
  include a readable copy of the attribution notices contained
  within such NOTICE file, excluding those notices that do not
  pertain to any part of the Derivative Works, in at least one
  of the following places: within a NOTICE text file distributed
  as part of the Derivative Works; within the Source form or
  documentation, if provided along with the Derivative Works; or,
  within a display generated by the Derivative Works, if and
  wherever such third-party notices normally appear. The contents
  of the NOTICE file are for informational purposes only and
  do not modify the License. You may add Your own attribution
  notices within Derivative Works that You distribute, alongside
  or as an addendum to the NOTICE text from the Work, provided
  that such additional attribution notices cannot be construed
  as modifying the License.
 
  You may add Your own copyright statement to Your modifications and
  may provide additional or different license terms and conditions
  for use, reproduction, or distribution of Your modifications, or
  for any such Derivative Works as a whole, provided Your use,
  reproduction, and distribution of the Work otherwise complies with
  the conditions stated in this License.
 
  5. Submission of Contributions. Unless You explicitly state otherwise,
  any Contribution intentionally submitted for inclusion in the Work
  by You to the Licensor shall be under the terms and conditions of
  this License, without any additional terms or conditions.
  Notwithstanding the above, nothing herein shall supersede or modify
  the terms of any separate license agreement you may have executed
  with Licensor regarding such Contributions.
 
  6. Trademarks. This License does not grant permission to use the trade
  names, trademarks, service marks, or product names of the Licensor,
  except as required for reasonable and customary use in describing the
  origin of the Work and reproducing the content of the NOTICE file.
 
  7. Disclaimer of Warranty. Unless required by applicable law or
  agreed to in writing, Licensor provides the Work (and each
  Contributor provides its Contributions) on an "AS IS" BASIS,
  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
  implied, including, without limitation, any warranties or conditions
  of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
  PARTICULAR PURPOSE. You are solely responsible for determining the
  appropriateness of using or redistributing the Work and assume any
  risks associated with Your exercise of permissions under this License.
 
  8. Limitation of Liability. In no event and under no legal theory,
  whether in tort (including negligence), contract, or otherwise,
  unless required by applicable law (such as deliberate and grossly
  negligent acts) or agreed to in writing, shall any Contributor be
  liable to You for damages, including any direct, indirect, special,
  incidental, or consequential damages of any character arising as a
  result of this License or out of the use or inability to use the
  Work (including but not limited to damages for loss of goodwill,
  work stoppage, computer failure or malfunction, or any and all
  other commercial damages or losses), even if such Contributor
  has been advised of the possibility of such damages.
 
  9. Accepting Warranty or Additional Liability. While redistributing
  the Work or Derivative Works thereof, You may choose to offer,
  and charge a fee for, acceptance of support, warranty, indemnity,
  or other liability obligations and/or rights consistent with this
  License. However, in accepting such obligations, You may act only
  on Your own behalf and on Your sole responsibility, not on behalf
  of any other Contributor, and only if You agree to indemnify,
  defend, and hold each Contributor harmless for any liability
  incurred by, or claims asserted against, such Contributor by reason
  of your accepting any such warranty or additional liability.
 
  END OF TERMS AND CONDITIONS
 
  APPENDIX: How to apply the Apache License to your work.
 
  To apply the Apache License to your work, attach the following
  boilerplate notice, with the fields enclosed by brackets "[]"
  replaced with your own identifying information. (Don't include
  the brackets!) The text should be enclosed in the appropriate
  comment syntax for the file format. We also recommend that a
  file or class name and description of purpose be included on the
  same "printed page" as the copyright notice for easier
  identification within third-party archives.
 
  Copyright [yyyy] [name of copyright owner]
 
  Licensed under the Apache License, Version 2.0 (the "License");
  you may not use this file except in compliance with the License.
  You may obtain a copy of the License at
 
  http://www.apache.org/licenses/LICENSE-2.0
 
  Unless required by applicable law or agreed to in writing, software
  distributed under the License is distributed on an "AS IS" BASIS,
  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  See the License for the specific language governing permissions and
  limitations under the License.
 
  INSTALL file for transitfeed distribution
 
 
 
  To download and install in one step make sure you have easy-install installed and run
  easy_install transitfeed
 
 
 
  Since you got this far chances are you have downloaded a copy of the source
  code. Install with the command
 
  python setup.py install
 
 
 
  If you don't want to install you may be able to run the scripts from this
  directory. For example, try running
 
  ./feedvalidator.py -n test/data/good_feed.zip
 
 
  Metadata-Version: 1.0
  Name: transitfeed
  Version: 1.2.6
  Summary: Google Transit Feed Specification library and tools
  Home-page: http://code.google.com/p/googletransitdatafeed/
  Author: Tom Brown
  Author-email: tom.brown.code@gmail.com
  License: Apache License, Version 2.0
  Download-URL: http://googletransitdatafeed.googlecode.com/files/transitfeed-1.2.6.tar.gz
  Description: This module provides a library for reading, writing and validating Google Transit Feed Specification files. It includes some scripts that validate a feed, display it using the Google Maps API and the start of a KML importer and exporter.
  Platform: OS Independent
  Classifier: Development Status :: 4 - Beta
  Classifier: Intended Audience :: Developers
  Classifier: Intended Audience :: Information Technology
  Classifier: Intended Audience :: Other Audience
  Classifier: License :: OSI Approved :: Apache Software License
  Classifier: Operating System :: OS Independent
  Classifier: Programming Language :: Python
  Classifier: Topic :: Scientific/Engineering :: GIS
  Classifier: Topic :: Software Development :: Libraries :: Python Modules
 
  README file for transitfeed distribution
 
 
 
  This distribution contains a library to help you parse and generate Google
  Transit Feed files. It also contains some sample tools that demonstrate the
  library and are useful in their own right when maintaining Google
  Transit Feed files. You may fetch the specification from
  http://code.google.com/transit/spec/transit_feed_specification.htm
 
 
  See INSTALL for installation instructions
 
  The most recent source can be downloaded from our subversion repository at
  http://googletransitdatafeed.googlecode.com/svn/trunk/python/
 
  See http://code.google.com/p/googletransitdatafeed/wiki/TransitFeedDistribution
  for more information.
 
  #!/usr/bin/python2.5
 
  # Copyright (C) 2007 Google Inc.
  #
  # Licensed under the Apache License, Version 2.0 (the "License");
  # you may not use this file except in compliance with the License.
  # You may obtain a copy of the License at
  #
  # http://www.apache.org/licenses/LICENSE-2.0
  #
  # Unless required by applicable law or agreed to in writing, software
  # distributed under the License is distributed on an "AS IS" BASIS,
  # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  # See the License for the specific language governing permissions and
  # limitations under the License.
 
 
  """Filter the unused stops out of a transit feed file."""
 
  import optparse
  import sys
  import transitfeed
 
 
  def main():
  parser = optparse.OptionParser(
  usage="usage: %prog [options] input_feed output_feed",
  version="%prog "+transitfeed.__version__)
  parser.add_option("-l", "--list_removed", dest="list_removed",
  default=False,
  action="store_true",
  help="Print removed stops to stdout")
  (options, args) = parser.parse_args()
  if len(args) != 2:
  print >>sys.stderr, parser.format_help()
  print >>sys.stderr, "\n\nYou must provide input_feed and output_feed\n\n"
  sys.exit(2)
  input_path = args[0]
  output_path = args[1]
 
  loader = transitfeed.Loader(input_path)
  schedule = loader.Load()
 
  print "Removing unused stops..."
  removed = 0
  for stop_id, stop in schedule.stops.items():
  if not stop.GetTrips(schedule):
  removed += 1
  del schedule.stops[stop_id]
  if options.list_removed:
  print "Removing %s (%s)" % (stop_id, stop.stop_name)
  if removed == 0:
  print "No unused stops."
  elif removed == 1:
  print "Removed 1 stop"
  else:
  print "Removed %d stops" % removed
 
  schedule.WriteGoogleTransitFeed(output_path)
 
  if __name__ == "__main__":
  main()
 
  #!/usr/bin/python2.5
 
  # Copyright (C) 2007 Google Inc.
  #
  # Licensed under the Apache License, Version 2.0 (the "License");
  # you may not use this file except in compliance with the License.
  # You may obtain a copy of the License at
  #
  # http://www.apache.org/licenses/LICENSE-2.0
  #
  # Unless required by applicable law or agreed to in writing, software
  # distributed under the License is distributed on an "AS IS" BASIS,
  # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  # See the License for the specific language governing permissions and
  # limitations under the License.
 
 
  """Output Google Transit URLs for queries near stops.
 
  The output can be used to speed up manual testing. Load the output from this
  file and then open many of the links in new tabs. In each result check that the
  polyline looks okay (no unnecassary loops, no jumps to a far away location) and
  look at the time of each leg. Also check the route names and headsigns are
  formatted correctly and not redundant.
  """
 
  from datetime import datetime
  from datetime import timedelta
  import math
  import optparse
  import os.path
  import random
  import sys
  import transitfeed
  import urllib
  import urlparse
 
 
  def Distance(lat0, lng0, lat1, lng1):
  """
  Compute the geodesic distance in meters between two points on the
  surface of the Earth. The latitude and longitude angles are in
  degrees.
 
  Approximate geodesic distance function (Haversine Formula) assuming
  a perfect sphere of radius 6367 km (see "What are some algorithms
  for calculating the distance between 2 points?" in the GIS Faq at
  http://www.census.gov/geo/www/faq-index.html). The approximate
  radius is adequate for our needs here, but a more sophisticated
  geodesic function should be used if greater accuracy is required
  (see "When is it NOT okay to assume the Earth is a sphere?" in the
  same faq).
  """
  deg2rad = math.pi / 180.0
  lat0 = lat0 * deg2rad
  lng0 = lng0 * deg2rad
  lat1 = lat1 * deg2rad
  lng1 = lng1 * deg2rad
  dlng = lng1 - lng0
  dlat = lat1 - lat0
  a = math.sin(dlat*0.5)
  b = math.sin(dlng*0.5)
  a = a * a + math.cos(lat0) * math.cos(lat1) * b * b
  c = 2.0 * math.atan2(math.sqrt(a), math.sqrt(1.0 - a))
  return 6367000.0 * c
 
 
  def AddNoiseToLatLng(lat, lng):
  """Add up to 500m of error to each coordinate of lat, lng."""
  m_per_tenth_lat = Distance(lat, lng, lat + 0.1, lng)
  m_per_tenth_lng = Distance(lat, lng, lat, lng + 0.1)
  lat_per_100m = 1 / m_per_tenth_lat * 10
  lng_per_100m = 1 / m_per_tenth_lng * 10
  return (lat + (lat_per_100m * 5 * (random.random() * 2 - 1)),
  lng + (lng_per_100m * 5 * (random.random() * 2 - 1)))
 
 
  def GetRandomLocationsNearStops(schedule):
  """Return a list of (lat, lng) tuples."""
  locations = []
  for s in schedule.GetStopList():
  locations.append(AddNoiseToLatLng(s.stop_lat, s.stop_lon))
  return locations
 
 
  def GetRandomDatetime():
  """Return a datetime in the next week."""
  seconds_offset = random.randint(0, 60 * 60 * 24 * 7)
  dt = datetime.today() + timedelta(seconds=seconds_offset)
  return dt.replace(second=0, microsecond=0)
 
 
  def FormatLatLng(lat_lng):
  """Format a (lat, lng) tuple into a string for maps.google.com."""
  return "%0.6f,%0.6f" % lat_lng
 
 
  def LatLngsToGoogleUrl(source, destination, dt):
  """Return a URL for routing between two (lat, lng) at a datetime."""
  params = {"saddr": FormatLatLng(source),
  "daddr": FormatLatLng(destination),
  "time": dt.strftime("%I:%M%p"),
  "date": dt.strftime("%Y-%m-%d"),
  "dirflg": "r",
  "ie": "UTF8",
  "oe": "UTF8"}
  url = urlparse.urlunsplit(("http", "maps.google.com", "/maps",
  urllib.urlencode(params), ""))
  return url
 
 
  def LatLngsToGoogleLink(source, destination):
  """Return a string "<a ..." for a trip at a random time."""
  dt = GetRandomDatetime()
  return "<a href='%s'>from:%s to:%s on %s</a>" % (
  LatLngsToGoogleUrl(source, destination, dt),
  FormatLatLng(source), FormatLatLng(destination),
  dt.ctime())
 
 
  def WriteOutput(title, locations, limit, f):
  """Write html to f for up to limit trips between locations.
 
  Args:
  title: String used in html title
  locations: list of (lat, lng) tuples
  limit: maximum number of queries in the html
  f: a file object
  """
  output_prefix = """
  <html>
  <head>
  <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
  <title>%(title)s</title>
  </head>
  <body>
  Random queries for %(title)s<p>
  This list of random queries should speed up important manual testing. Here are
  some things to check when looking at the results of a query.
  <ul>
  <li> Check the agency attribution under the trip results:
  <ul>
  <li> has correct name and spelling of the agency
  <li> opens a page with general information about the service
  </ul>
  <li> For each alternate trip check that each of these is reasonable:
  <ul>
  <li> the total time of the trip
  <li> the time for each leg. Bad data frequently results in a leg going a long
  way in a few minutes.
  <li> the icons and mode names (Tram, Bus, etc) are correct for each leg
  <li> the route names and headsigns are correctly formatted and not
  redundant.
  For a good example see <a
  href="http://code.google.com/transit/spec/transit_feed_specification.html#transitScreenshots">the
  screenshots in the Google Transit Feed Specification</a>.
  <li> the shape line on the map looks correct. Make sure the polyline does
  not zig-zag, loop, skip stops or jump far away unless the trip does the
  same thing.
  <li> the route is active on the day the trip planner returns
  </ul>
  </ul>
  If you find a problem be sure to save the URL. This file is generated randomly.
  <ol>
  """ % locals()
 
  output_suffix = """
  </ol>
  </body>
  </html>
  """ % locals()
 
  f.write(transitfeed.EncodeUnicode(output_prefix))
  for source, destination in zip(locations[0:limit], locations[1:limit + 1]):
  f.write(transitfeed.EncodeUnicode("<li>%s\n" %
  LatLngsToGoogleLink(source, destination)))
  f.write(transitfeed.EncodeUnicode(output_suffix))
 
 
  def ParentAndBaseName(path):
  """Given a path return only the parent name and file name as a string."""
  dirname, basename = os.path.split(path)
  dirname = dirname.rstrip(os.path.sep)
  if os.path.altsep:
  dirname = dirname.rstrip(os.path.altsep)
  _, parentname = os.path.split(dirname)
  return os.path.join(parentname, basename)
 
 
  def main():
  usage = \
  """%prog [options] <input GTFS.zip>
 
  Create an HTML page of random URLs for the Google Maps transit trip
  planner. The queries go between places near stops listed in a <input GTFS.zip>.
  By default 50 random URLs are saved to google_random_queries.html.
 
  For more information see
  http://code.google.com/p/googletransitdatafeed/wiki/GoogleRandomQueries
  """
 
  parser = optparse.OptionParser(
  usage=usage,
  version="%prog "+transitfeed.__version__)
  parser.add_option("-l", "--limit", dest="limit", type="int",
  help="Maximum number of URLs to generate")
  parser.add_option("-o", "--output", dest="output", metavar="HTML_OUTPUT_PATH",
  help="write HTML output to HTML_OUTPUT_PATH")
  parser.set_defaults(output="google_random_queries.html", limit=50)
  (options, args) = parser.parse_args()
  if len(args) != 1:
  print >>sys.stderr, parser.format_help()
  print >>sys.stderr, "\n\nYou must provide the path of a single feed\n\n"
  sys.exit(2)
  feed_path = args[0]
 
  # ProblemReporter prints problems on console.
  loader = transitfeed.Loader(feed_path, problems=transitfeed.ProblemReporter(),
  load_stop_times=False)
  schedule = loader.Load()
  locations = GetRandomLocationsNearStops(schedule)
  random.shuffle(locations)
  agencies = ", ".join([a.agency_name for a in schedule.GetAgencyList()])
  title = "%s (%s)" % (agencies, ParentAndBaseName(feed_path))
 
  WriteOutput(title,
  locations,
  options.limit,
  open(options.output, "w"))
  print ("Load %s in your web browser. It contains more instructions." %
  options.output)
 
 
  if __name__ == "__main__":
  main()
 
  #!/usr/bin/python2.5
 
  # Copyright (C) 2007 Google Inc.
  #
  # Licensed under the Apache License, Version 2.0 (the "License");
  # you may not use this file except in compliance with the License.
  # You may obtain a copy of the License at
  #
  # http://www.apache.org/licenses/LICENSE-2.0
  #
  # Unless required by applicable law or agreed to in writing, software
  # distributed under the License is distributed on an "AS IS" BASIS,
  # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  # See the License for the specific language governing permissions and
  # limitations under the License.
 
  """Google has a homegrown database for managing the company shuttle. The
  database dumps its contents in XML. This scripts converts the proprietary XML
  format into a Google Transit Feed Specification file.
  """
 
  import datetime
  from optparse import OptionParser
  import os.path
  import re
  import transitfeed
  import urllib
 
  try:
  import xml.etree.ElementTree as ET # python 2.5
  except ImportError, e:
  import elementtree.ElementTree as ET # older pythons
 
 
  class NoUnusedStopExceptionProblemReporter(transitfeed.ProblemReporter):
  """The company shuttle database has a few unused stops for reasons unrelated
  to this script. Ignore them.
  """
 
  def __init__(self):
  accumulator = transitfeed.ExceptionProblemAccumulator()
  transitfeed.ProblemReporter.__init__(self, accumulator)
 
  def UnusedStop(self, stop_id, stop_name):
  pass
 
  def SaveFeed(input, output):
  tree = ET.parse(urllib.urlopen(input))
 
  schedule = transitfeed.Schedule()
  service_period = schedule.GetDefaultServicePeriod()
  service_period.SetWeekdayService()
  service_period.SetStartDate('20070314')
  service_period.SetEndDate('20071231')
  # Holidays for 2007
  service_period.SetDateHasService('20070528', has_service=False)
  service_period.SetDateHasService('20070704', has_service=False)
  service_period.SetDateHasService('20070903', has_service=False)
  service_period.SetDateHasService('20071122', has_service=False)
  service_period.SetDateHasService('20071123', has_service=False)
  service_period.SetDateHasService('20071224', has_service=False)
  service_period.SetDateHasService('20071225', has_service=False)
  service_period.SetDateHasService('20071226', has_service=False)
  service_period.SetDateHasService('20071231', has_service=False)
 
  stops = {} # Map from xml stop id to python Stop object
  agency = schedule.NewDefaultAgency(name='GBus', url='http://shuttle/',
  timezone='America/Los_Angeles')
 
  for xml_stop in tree.getiterator('stop'):
  stop = schedule.AddStop(lat=float(xml_stop.attrib['lat']),
  lng=float(xml_stop.attrib['lng']),
  name=xml_stop.attrib['name'])
  stops[xml_stop.attrib['id']] = stop
 
  for xml_shuttleGroup in tree.getiterator('shuttleGroup'):
  if xml_shuttleGroup.attrib['name'] == 'Test':
  continue
  r = schedule.AddRoute(short_name="",
  long_name=xml_shuttleGroup.attrib['name'], route_type='Bus')
  for xml_route in xml_shuttleGroup.getiterator('route'):
  t = r.AddTrip(schedule=schedule, headsign=xml_route.attrib['name'],
  trip_id=xml_route.attrib['id'])
  trip_stops = [] # Build a list of (time, Stop) tuples
  for xml_schedule in xml_route.getiterator('schedule'):
  trip_stops.append( (int(xml_schedule.attrib['time']) / 1000,
  stops[xml_schedule.attrib['stopId']]) )
  trip_stops.sort() # Sort by time
  for (time, stop) in trip_stops:
  t.AddStopTime(stop=stop, arrival_secs=time, departure_secs=time)
 
  schedule.Validate(problems=NoUnusedStopExceptionProblemReporter())
  schedule.WriteGoogleTransitFeed(output)
 
 
  def main():
  parser = OptionParser()
  parser.add_option('--input', dest='input',
  help='Path or URL of input')
  parser.add_option('--output', dest='output',
  help='Path of output file. Should end in .zip and if it '
  'contains the substring YYYYMMDD it will be replaced with '
  'today\'s date. It is impossible to include the literal '
  'string YYYYYMMDD in the path of the output file.')
  parser.add_option('--execute', dest='execute',
  help='Commands to run to copy the output. %(path)s is '
  'replaced with full path of the output and %(name)s is '
  'replaced with name part of the path. Try '
  'scp %(path)s myhost:www/%(name)s',
  action='append')
  parser.set_defaults(input=None, output=None, execute=[])
  (options, args) = parser.parse_args()
 
  today = datetime.date.today().strftime('%Y%m%d')
  options.output = re.sub(r'YYYYMMDD', today, options.output)
  (_, name) = os.path.split(options.output)
  path = options.output
 
  SaveFeed(options.input, options.output)
 
  for command in options.execute:
  import subprocess
  def check_call(cmd):
  """Convenience function that is in the docs for subprocess but not
  installed on my system."""
  retcode = subprocess.call(cmd, shell=True)
  if retcode < 0:
  raise Exception("Child '%s' was terminated by signal %d" % (cmd,
  -retcode))
  elif retcode != 0:
  raise Exception("Child '%s' returned %d" % (cmd, retcode))
 
  # path_output and filename_current can be used to run arbitrary commands
  check_call(command % locals())
 
  if __name__ == '__main__':
  main()
 
  <shuttle><office id="us-nye" name="US Nye County">
  <stops>
  <stop id="1" name="Stagecoach Hotel and Casino" shortName="Stagecoach" lat="36.915682" lng="-116.751677" />
  <stop id="2" name="North Ave / N A Ave" shortName="N Ave / A Ave N" lat="36.914944" lng="-116.761472" />
  <stop id="3" name="North Ave / D Ave N" shortName="N Ave / D Ave N" lat="36.914893" lng="-116.76821" />
  <stop id="4" name="Doing Ave / D Ave N" shortName="Doing / D Ave N" lat="36.909489" lng="-116.768242" />
  <stop id="5" name="E Main St / S Irving St" shortName="E Main / S Irving" lat="36.905697" lng="-116.76218" />
  </stops>
  <shuttleGroups>
  <shuttleGroup id="4" name="Bar Circle Loop" >
  <routes>
  <route id="1" name="Outbound">
  <schedules>
  <schedule id="164" stopId="1" time="60300000"/>
  <schedule id="165" stopId="2" time="60600000"/>
  <schedule id="166" stopId="3" time="60720000"/>
  <schedule id="167" stopId="4" time="60780000"/>
  <schedule id="168" stopId="5" time="60900000"/>
  </schedules><meta></meta></route>
  <route id="2" name="Inbound">
  <schedules>
  <schedule id="260" stopId="5" time="30000000"/>
  <schedule id="261" stopId="4" time="30120000"/>
  <schedule id="262" stopId="3" time="30180000"/>
  <schedule id="263" stopId="2" time="30300000"/>
  <schedule id="264" stopId="1" time="30600000"/>
  </schedules><meta></meta></route></routes>
  </shuttleGroup>
  </shuttleGroups></office></shuttle>
 
  #!/usr/bin/python2.5
 
  # A really simple example of using transitfeed to build a Google Transit
  # Feed Specification file.
 
  import transitfeed
  from optparse import OptionParser
 
 
  parser = OptionParser()
  parser.add_option('--output', dest='output',
  help='Path of output file. Should end in .zip')
  parser.set_defaults(output='google_transit.zip')
  (options, args) = parser.parse_args()
 
  schedule = transitfeed.Schedule()
  schedule.AddAgency("Fly Agency", "http://iflyagency.com",
  "America/Los_Angeles")
 
  service_period = schedule.GetDefaultServicePeriod()
  service_period.SetWeekdayService(True)
  service_period.SetDateHasService('20070704')
 
  stop1 = schedule.AddStop(lng=-122, lat=37.2, name="Suburbia")
  stop2 = schedule.AddStop(lng=-122.001, lat=37.201, name="Civic Center")
 
  route = schedule.AddRoute(short_name="22", long_name="Civic Center Express",
  route_type="Bus")
 
  trip = route.AddTrip(schedule, headsign="To Downtown")
  trip.AddStopTime(stop1, stop_time='09:00:00')
  trip.AddStopTime(stop2, stop_time='09:15:00')
 
  trip = route.AddTrip(schedule, headsign="To Suburbia")
  trip.AddStopTime(stop1, stop_time='17:30:00')
  trip.AddStopTime(stop2, stop_time='17:45:00')
 
  schedule.Validate()
  schedule.WriteGoogleTransitFeed(options.output)
 
  #!/usr/bin/python2.5
 
  # Copyright (C) 2007 Google Inc.
  #
  # Licensed under the Apache License, Version 2.0 (the "License");
  # you may not use this file except in compliance with the License.
  # You may obtain a copy of the License at
  #
  # http://www.apache.org/licenses/LICENSE-2.0
  #
  # Unless required by applicable law or agreed to in writing, software
  # distributed under the License is distributed on an "AS IS" BASIS,
  # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  # See the License for the specific language governing permissions and
  # limitations under the License.
 
  # An example script that demonstrates converting a proprietary format to a
  # Google Transit Feed Specification file.
  #
  # You can load table.txt, the example input, in Excel. It contains three
  # sections:
  # 1) A list of global options, starting with a line containing the word
  # 'options'. Each option has an name in the first column and most options
  # have a value in the second column.
  # 2) A table of stops, starting with a line containing the word 'stops'. Each
  # row of the table has 3 columns: name, latitude, longitude
  # 3) A list of routes. There is an empty row between each route. The first row
  # for a route lists the short_name and long_name. After the first row the
  # left-most column lists the stop names visited by the route. Each column
  # contains the times a single trip visits the stops.
  #
  # This is very simple example which you could use as a base for your own
  # transit feed builder.
 
  import transitfeed
  from optparse import OptionParser
  import re
 
  stops = {}
 
  # table is a list of lists in this form
  # [ ['Short Name', 'Long Name'],
  # ['Stop 1', 'Stop 2', ...]
  # [time_at_1, time_at_2, ...] # times for trip 1
  # [time_at_1, time_at_2, ...] # times for trip 2
  # ... ]
  def AddRouteToSchedule(schedule, table):
  if len(table) >= 2:
  r = schedule.AddRoute(short_name=table[0][0], long_name=table[0][1], route_type='Bus')
  for trip in table[2:]:
  if len(trip) > len(table[1]):
  print "ignoring %s" % trip[len(table[1]):]
  trip = trip[0:len(table[1])]
  t = r.AddTrip(schedule, headsign='My headsign')
  trip_stops = [] # Build a list of (time, stopname) tuples
  for i in range(0, len(trip)):
  if re.search(r'\S', trip[i]):
  trip_stops.append( (transitfeed.TimeToSecondsSinceMidnight(trip[i]), table[1][i]) )
  trip_stops.sort() # Sort by time
  for (time, stopname) in trip_stops:
  t.AddStopTime(stop=stops[stopname.lower()], arrival_secs=time,
  departure_secs=time)
 
  def TransposeTable(table):
  """Transpose a list of lists, using None to extend all input lists to the
  same length.
 
  For example:
  >>> TransposeTable(
  [ [11, 12, 13],
  [21, 22],
  [31, 32, 33, 34]])
 
  [ [11, 21, 31],
  [12, 22, 32],
  [13, None, 33],
  [None, None, 34]]
  """
  transposed = []
  rows = len(table)
  cols = max(len(row) for row in table)
  for x in range(cols):
  transposed.append([])
  for y in range(rows):
  if x < len(table[y]):
  transposed[x].append(table[y][x])
  else:
  transposed[x].append(None)
  return transposed
 
  def ProcessOptions(schedule, table):
  service_period = schedule.GetDefaultServicePeriod()
  agency_name, agency_url, agency_timezone = (None, None, None)
 
  for row in table[1:]:
  command = row[0].lower()
  if command == 'weekday':
  service_period.SetWeekdayService()
  elif command == 'start_date':
  service_period.SetStartDate(row[1])
  elif command == 'end_date':
  service_period.SetEndDate(row[1])
  elif command == 'add_date':
  service_period.SetDateHasService(date=row[1])
  elif command == 'remove_date':
  service_period.SetDateHasService(date=row[1], has_service=False)
  elif command == 'agency_name':
  agency_name = row[1]
  elif command == 'agency_url':
  agency_url = row[1]
  elif command == 'agency_timezone':
  agency_timezone = row[1]
 
  if not (agency_name and agency_url and agency_timezone):
  print "You must provide agency information"
 
  schedule.NewDefaultAgency(agency_name=agency_name, agency_url=agency_url,
  agency_timezone=agency_timezone)
 
 
  def AddStops(schedule, table):
  for name, lat_str, lng_str in table[1:]:
  stop = schedule.AddStop(lat=float(lat_str), lng=float(lng_str), name=name)
  stops[name.lower()] = stop
 
 
  def ProcessTable(schedule, table):
  if table[0][0].lower() == 'options':
  ProcessOptions(schedule, table)
  elif table[0][0].lower() == 'stops':
  AddStops(schedule, table)
  else:
  transposed = [table[0]] # Keep route_short_name and route_long_name on first row
 
  # Transpose rest of table. Input contains the stop names in table[x][0], x
  # >= 1 with trips found in columns, so we need to transpose table[1:].
  # As a diagram Transpose from
  # [['stop 1', '10:00', '11:00', '12:00'],
  # ['stop 2', '10:10', '11:10', '12:10'],
  # ['stop 3', '10:20', '11:20', '12:20']]
  # to
  # [['stop 1', 'stop 2', 'stop 3'],
  # ['10:00', '10:10', '10:20'],
  # ['11:00', '11:11', '11:20'],
  # ['12:00', '12:12', '12:20']]
  transposed.extend(TransposeTable(table[1:]))
  AddRouteToSchedule(schedule, transposed)
 
 
  def main():
  parser = OptionParser()
  parser.add_option('--input', dest='input',
  help='Path of input file')
  parser.add_option('--output', dest='output',
  help='Path of output file, should end in .zip')
  parser.set_defaults(output='feed.zip')
  (options, args) = parser.parse_args()
 
  schedule = transitfeed.Schedule()
 
  table = []
  for line in open(options.input):
  line = line.rstrip()
  if not line:
  ProcessTable(schedule, table)
  table = []
  else:
  table.append(line.split('\t'))
 
  ProcessTable(schedule, table)
 
  schedule.WriteGoogleTransitFeed(options.output)
 
 
  if __name__ == '__main__':
  main()
 
  options
  weekday
  start_date 20070315
  end_date 20071215
  remove_date 20070704
  agency_name Gbus
  agency_url http://shuttle/
  agency_timezone America/Los_Angeles
 
  stops
  Stagecoach 36.915682 -116.751677
  N Ave / A Ave N 36.914944 -116.761472
  N Ave / D Ave N 36.914893 -116.76821
  Doing / D Ave N 36.909489 -116.768242
  E Main / S Irving 36.905697 -116.76218
 
  O in Bar Circle Inbound
  Stagecoach 9:00:00 9:30:00 10:00:00 12:00:00
  N Ave / A Ave N 9:05:00 9:35:00 10:05:00 12:05:00
  N Ave / D Ave N 9:07:00 9:37:00 10:07:00 12:07:00
  Doing / D Ave N 9:09:00 9:39:00 10:09:00 12:09:00
  E Main / S Irving 9:11:00 9:41:00 10:11:00 12:11:00
 
  O out Bar Circle Outbound
  E Main / S Irving 15:00:00 15:30:00 16:00:00 18:00:00
  Doing / D Ave N 15:05:00 15:35:00 16:05:00 18:05:00
  N Ave / D Ave N 15:07:00 15:37:00 16:07:00 18:07:00
  N Ave / A Ave N 15:09:00 15:39:00 16:09:00 18:09:00
  Stagecoach 15:11:00 15:41:00 16:11:00 18:11:00
 
  #!/usr/bin/python2.5
 
  # Copyright (C) 2007 Google Inc.
  #
  # Licensed under the Apache License, Version 2.0 (the "License");
  # you may not use this file except in compliance with the License.
  # You may obtain a copy of the License at
  #
  # http://www.apache.org/licenses/LICENSE-2.0
  #
  # Unless required by applicable law or agreed to in writing, software
  # distributed under the License is distributed on an "AS IS" BASIS,
  # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  # See the License for the specific language governing permissions and
  # limitations under the License.
 
 
  """Validates a GTFS file.
 
  For usage information run feedvalidator.py --help
  """
 
  import bisect
  import codecs
  import datetime
  from transitfeed.util import defaultdict
  import optparse
  import os
  import os.path
  import re
  import socket
  import sys
  import time
  import transitfeed
  from transitfeed import TYPE_ERROR, TYPE_WARNING
  from urllib2 import Request, urlopen, HTTPError, URLError
  from transitfeed import util
  import webbrowser
 
  SVN_TAG_URL = 'http://googletransitdatafeed.googlecode.com/svn/tags/'
 
 
  def MaybePluralizeWord(count, word):
  if count == 1:
  return word
  else:
  return word + 's'
 
 
  def PrettyNumberWord(count, word):
  return '%d %s' % (count, MaybePluralizeWord(count, word))
 
 
  def UnCamelCase(camel):
  return re.sub(r'([a-z])([A-Z])', r'\1 \2', camel)
 
 
  def ProblemCountText(error_count, warning_count):
  results = []
  if error_count:
  results.append(PrettyNumberWord(error_count, 'error'))
  if warning_count:
  results.append(PrettyNumberWord(warning_count, 'warning'))
 
  return ' and '.join(results)
 
 
  def CalendarSummary(schedule):
  today = datetime.date.today()
  summary_end_date = today + datetime.timedelta(days=60)
  start_date, end_date = schedule.GetDateRange()
 
  if not start_date or not end_date:
  return {}
 
  try:
  start_date_object = transitfeed.DateStringToDateObject(start_date)
  end_date_object = transitfeed.DateStringToDateObject(end_date)
  except ValueError:
  return {}
 
  # Get the list of trips only during the period the feed is active.
  # As such we have to check if it starts in the future and/or if
  # if it ends in less than 60 days.
  date_trips_departures = schedule.GenerateDateTripsDeparturesList(
  max(today, start_date_object),
  min(summary_end_date, end_date_object))
 
  if not date_trips_departures:
  return {}
 
  # Check that the dates which will be shown in summary agree with these
  # calculations. Failure implies a bug which should be fixed. It isn't good
  # for users to discover assertion failures but means it will likely be fixed.
  assert start_date <= date_trips_departures[0][0].strftime("%Y%m%d")
  assert end_date >= date_trips_departures[-1][0].strftime("%Y%m%d")
 
  # Generate a map from int number of trips in a day to a list of date objects
  # with that many trips. The list of dates is sorted.
  trips_dates = defaultdict(lambda: [])
  trips = 0
  for date, day_trips, day_departures in date_trips_departures:
  trips += day_trips
  trips_dates[day_trips].append(date)
  mean_trips = trips / len(date_trips_departures)
  max_trips = max(trips_dates.keys())
  min_trips = min(trips_dates.keys())
 
  calendar_summary = {}
  calendar_summary['mean_trips'] = mean_trips
  calendar_summary['max_trips'] = max_trips
  calendar_summary['max_trips_dates'] = FormatDateList(trips_dates[max_trips])
  calendar_summary['min_trips'] = min_trips
  calendar_summary['min_trips_dates'] = FormatDateList(trips_dates[min_trips])
  calendar_summary['date_trips_departures'] = date_trips_departures
  calendar_summary['date_summary_range'] = "%s to %s" % (
  date_trips_departures[0][0].strftime("%a %b %d"),
  date_trips_departures[-1][0].strftime("%a %b %d"))
 
  return calendar_summary
 
 
  def FormatDateList(dates):
  if not dates:
  return "0 service dates"
 
  formatted = [d.strftime("%a %b %d") for d in dates[0:3]]
  if len(dates) > 3:
  formatted.append("...")
  return "%s (%s)" % (PrettyNumberWord(len(dates), "service date"),
  ", ".join(formatted))
 
 
  def MaxVersion(versions):
  versions = filter(None, versions)
  versions.sort(lambda x,y: -cmp([int(item) for item in x.split('.')],
  [int(item) for item in y.split('.')]))
  if len(versions) > 0:
  return versions[0]
 
 
  class CountingConsoleProblemAccumulator(transitfeed.SimpleProblemAccumulator):
  def __init__(self):
  self._error_count = 0
  self._warning_count = 0
 
  def _Report(self, e):
  transitfeed.SimpleProblemAccumulator._Report(self, e)
  if e.IsError():
  self._error_count += 1
  else:
  self._warning_count += 1
 
  def ErrorCount(self):
  return self._error_count
 
  def WarningCount(self):
  return self._warning_count
 
  def FormatCount(self):
  return ProblemCountText(self.ErrorCount(), self.WarningCount())
 
  def HasIssues(self):
  return self.ErrorCount() or self.WarningCount()
 
 
  class BoundedProblemList(object):
  """A list of one type of ExceptionWithContext objects with bounded size."""
  def __init__(self, size_bound):
  self._count = 0
  self._exceptions = []
  self._size_bound = size_bound
 
  def Add(self, e):
  self._count += 1
  try:
  bisect.insort(self._exceptions, e)
  except TypeError:
  # The base class ExceptionWithContext raises this exception in __cmp__
  # to signal that an object is not comparable. Instead of keeping the most
  # significant issue keep the first reported.
  if self._count <= self._size_bound:
  self._exceptions.append(e)
  else:
  # self._exceptions is in order. Drop the least significant if the list is
  # now too long.
  if self._count > self._size_bound:
  del self._exceptions[-1]
 
  def _GetDroppedCount(self):
  return self._count - len(self._exceptions)
 
  def __repr__(self):
  return "<BoundedProblemList %s>" % repr(self._exceptions)
 
  count = property(lambda s: s._count)
  dropped_count = property(_GetDroppedCount)
  problems = property(lambda s: s._exceptions)
 
 
  class LimitPerTypeProblemAccumulator(transitfeed.ProblemAccumulatorInterface):
  def __init__(self, limit_per_type):
  # {TYPE_WARNING: {"ClassName": BoundedProblemList()}}
  self._type_to_name_to_problist = {
  TYPE_WARNING: defaultdict(lambda: BoundedProblemList(limit_per_type)),
  TYPE_ERROR: defaultdict(lambda: BoundedProblemList(limit_per_type))
  }
 
  def HasIssues(self):
  return (self._type_to_name_to_problist[TYPE_ERROR] or
  self._type_to_name_to_problist[TYPE_WARNING])
 
  def _Report(self, e):
  self._type_to_name_to_problist[e.GetType()][e.__class__.__name__].Add(e)
 
  def ErrorCount(self):
  error_sets = self._type_to_name_to_problist[TYPE_ERROR].values()
  return sum(map(lambda v: v.count, error_sets))
 
  def WarningCount(self):
  warning_sets = self._type_to_name_to_problist[TYPE_WARNING].values()
  return sum(map(lambda v: v.count, warning_sets))
 
  def ProblemList(self, problem_type, class_name):
  """Return the BoundedProblemList object for given type and class."""
  return self._type_to_name_to_problist[problem_type][class_name]
 
  def ProblemListMap(self, problem_type):
  """Return the map from class name to BoundedProblemList object."""
  return self._type_to_name_to_problist[problem_type]
 
 
  class HTMLCountingProblemAccumulator(LimitPerTypeProblemAccumulator):
  def FormatType(self, f, level_name, class_problist):
  """Write the HTML dumping all problems of one type.
 
  Args:
  f: file object open for writing
  level_name: string such as "Error" or "Warning"
  class_problist: sequence of tuples (class name,
  BoundedProblemList object)
  """
  class_problist.sort()
  output = []
  for classname, problist in class_problist:
  output.append('<h4 class="issueHeader"><a name="%s%s">%s</a></h4><ul>\n' %
  (level_name, classname, UnCamelCase(classname)))
  for e in problist.problems:
  self.FormatException(e, output)
  if problist.dropped_count:
  output.append('<li>and %d more of this type.' %
  (problist.dropped_count))
  output.append('</ul>\n')
  f.write(''.join(output))
 
  def FormatTypeSummaryTable(self, level_name, name_to_problist):
  """Return an HTML table listing the number of problems by class name.
 
  Args:
  level_name: string such as "Error" or "Warning"
  name_to_problist: dict mapping class name to an BoundedProblemList object
 
  Returns:
  HTML in a string
  """
  output = []
  output.append('<table>')
  for classname in sorted(name_to_problist.keys()):
  problist = name_to_problist[classname]
  human_name = MaybePluralizeWord(problist.count, UnCamelCase(classname))
  output.append('<tr><td>%d</td><td><a href="#%s%s">%s</a></td></tr>\n' %
  (problist.count, level_name, classname, human_name))
  output.append('</table>\n')
  return ''.join(output)
 
  def FormatException(self, e, output):
  """Append HTML version of e to list output."""
  d = e.GetDictToFormat()
  for k in ('file_name', 'feedname', 'column_name'):
  if k in d.keys():
  d[k] = '<code>%s</code>' % d[k]
  problem_text = e.FormatProblem(d).replace('\n', '<br>')
  output.append('<li>')
  output.append('<div class="problem">%s</div>' %
  transitfeed.EncodeUnicode(problem_text))
  try:
  if hasattr(e, 'row_num'):
  line_str = 'line %d of ' % e.row_num
  else:
  line_str = ''
  output.append('in %s<code>%s</code><br>\n' %
  (line_str, e.file_name))
  row = e.row
  headers = e.headers
  column_name = e.column_name
  table_header = '' # HTML
  table_data = '' # HTML
  for header, value in zip(headers, row):
  attributes = ''
  if header == column_name:
  attributes = ' class="problem"'
  table_header += '<th%s>%s</th>' % (attributes, header)
  table_data += '<td%s>%s</td>' % (attributes, value)
  # Make sure output is encoded into UTF-8
  output.append('<table class="dump"><tr>%s</tr>\n' %
  transitfeed.EncodeUnicode(table_header))
  output.append('<tr>%s</tr></table>\n' %
  transitfeed.EncodeUnicode(table_data))
  except AttributeError, e:
  pass # Hope this was getting an attribute from e ;-)
  output.append('<br></li>\n')
 
  def FormatCount(self):
  return ProblemCountText(self.ErrorCount(), self.WarningCount())
 
  def CountTable(self):
  output = []
  output.append('<table class="count_outside">\n')
  output.append('<tr>')
  if self.ProblemListMap(TYPE_ERROR):
  output.append('<td><span class="fail">%s</span></td>' %
  PrettyNumberWord(self.ErrorCount(), "error"))
  if self.ProblemListMap(TYPE_WARNING):
  output.append('<td><span class="fail">%s</span></td>' %
  PrettyNumberWord(self.WarningCount(), "warning"))
  output.append('</tr>\n<tr>')
  if self.ProblemListMap(TYPE_ERROR):
  output.append('<td>\n')
  output.append(self.FormatTypeSummaryTable("Error",
  self.ProblemListMap(TYPE_ERROR)))
  output.append('</td>\n')
  if self.ProblemListMap(TYPE_WARNING):
  output.append('<td>\n')
  output.append(self.FormatTypeSummaryTable("Warning",
  self.ProblemListMap(TYPE_WARNING)))
  output.append('</td>\n')
  output.append('</table>')
  return ''.join(output)
 
  def WriteOutput(self, feed_location, f, schedule, other_problems):
  """Write the html output to f."""
  if self.HasIssues():
  if self.ErrorCount() + self.WarningCount() == 1:
  summary = ('<span class="fail">Found this problem:</span>\n%s' %
  self.CountTable())
  else:
  summary = ('<span class="fail">Found these problems:</span>\n%s' %
  self.CountTable())
  else:
  summary = '<span class="pass">feed validated successfully</span>'
  if other_problems is not None:
  summary = ('<span class="fail">\n%s</span><br><br>' %
  other_problems) + summary
 
  basename = os.path.basename(feed_location)
  feed_path = (feed_location[:feed_location.rfind(basename)], basename)
 
  agencies = ', '.join(['<a href="%s">%s</a>' % (a.agency_url, a.agency_name)
  for a in schedule.GetAgencyList()])
  if not agencies:
  agencies = '?'
 
  dates = "No valid service dates found"
  (start, end) = schedule.GetDateRange()
  if start and end:
  def FormatDate(yyyymmdd):
  src_format = "%Y%m%d"
  dst_format = "%B %d, %Y"
  try:
  return time.strftime(dst_format,
  time.strptime(yyyymmdd, src_format))
  except ValueError:
  return yyyymmdd
 
  formatted_start = FormatDate(start)
  formatted_end = FormatDate(end)
  dates = "%s to %s" % (formatted_start, formatted_end)
 
  calendar_summary = CalendarSummary(schedule)
  if calendar_summary:
  calendar_summary_html = """<br>
  During the upcoming service dates %(date_summary_range)s:
  <table>
  <tr><th class="header">Average trips per date:</th><td class="header">%(mean_trips)s</td></tr>
  <tr><th class="header">Most trips on a date:</th><td class="header">%(max_trips)s, on %(max_trips_dates)s</td></tr>
  <tr><th class="header">Least trips on a date:</th><td class="header">%(min_trips)s, on %(min_trips_dates)s</td></tr>
  </table>""" % calendar_summary
  else:
  calendar_summary_html = ""
 
  output_prefix = """
  <html>
  <head>
  <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
  <title>FeedValidator: %(feed_file)s</title>
  <style>
  body {font-family: Georgia, serif; background-color: white}
  .path {color: gray}
  div.problem {max-width: 500px}
  table.dump td,th {background-color: khaki; padding: 2px; font-family:monospace}
  table.dump td.problem,th.problem {background-color: dc143c; color: white; padding: 2px; font-family:monospace}
  table.count_outside td {vertical-align: top}
  table.count_outside {border-spacing: 0px; }
  table {border-spacing: 5px 0px; margin-top: 3px}
  h3.issueHeader {padding-left: 0.5em}
  h4.issueHeader {padding-left: 1em}
  .pass {background-color: lightgreen}
  .fail {background-color: yellow}
  .pass, .fail {font-size: 16pt}
  .header {background-color: white; font-family: Georgia, serif; padding: 0px}
  th.header {text-align: right; font-weight: normal; color: gray}
  .footer {font-size: 10pt}
  </style>
  </head>
  <body>
  GTFS validation results for feed:<br>
  <code><span class="path">%(feed_dir)s</span><b>%(feed_file)s</b></code>
  <br><br>
  <table>
  <tr><th class="header">Agencies:</th><td class="header">%(agencies)s</td></tr>
  <tr><th class="header">Routes:</th><td class="header">%(routes)s</td></tr>
  <tr><th class="header">Stops:</th><td class="header">%(stops)s</td></tr>
  <tr><th class="header">Trips:</th><td class="header">%(trips)s</td></tr>
  <tr><th class="header">Shapes:</th><td class="header">%(shapes)s</td></tr>
  <tr><th class="header">Effective:</th><td class="header">%(dates)s</td></tr>
  </table>
  %(calendar_summary)s
  <br>
  %(problem_summary)s
  <br><br>
  """ % { "feed_file": feed_path[1],
  "feed_dir": feed_path[0],
  "agencies": agencies,
  "routes": len(schedule.GetRouteList()),
  "stops": len(schedule.GetStopList()),
  "trips": len(schedule.GetTripList()),
  "shapes": len(schedule.GetShapeList()),
  "dates": dates,
  "problem_summary": summary,
  "calendar_summary": calendar_summary_html}
 
  # In output_suffix string
  # time.strftime() returns a regular local time string (not a Unicode one) with
  # default system encoding. And decode() will then convert this time string back
  # into a Unicode string. We use decode() here because we don't want the operating
  # system to do any system encoding (which may cause some problem if the string
  # contains some non-English characters) for the string. Therefore we decode it
  # back to its original Unicode code print.
 
  time_unicode = (time.strftime('%B %d, %Y at %I:%M %p %Z').
  decode(sys.getfilesystemencoding()))
  output_suffix = """
  <div class="footer">
  Generated by <a href="http://code.google.com/p/googletransitdatafeed/wiki/FeedValidator">
  FeedValidator</a> version %s on %s.
  </div>
  </body>
  </html>""" % (transitfeed.__version__, time_unicode)
 
  f.write(transitfeed.EncodeUnicode(output_prefix))
  if self.ProblemListMap(TYPE_ERROR):
  f.write('<h3 class="issueHeader">Errors:</h3>')
  self.FormatType(f, "Error",
  self.ProblemListMap(TYPE_ERROR).items())
  if self.ProblemListMap(TYPE_WARNING):
  f.write('<h3 class="issueHeader">Warnings:</h3>')
  self.FormatType(f, "Warning",
  self.ProblemListMap(TYPE_WARNING).items())
  f.write(transitfeed.EncodeUnicode(output_suffix))
 
 
  def RunValidationOutputFromOptions(feed, options):
  """Validate feed, output results per options and return an exit code."""
  if options.output.upper() == "CONSOLE":
  return RunValidationOutputToConsole(feed, options)
  else:
  return RunValidationOutputToFilename(feed, options, options.output)
 
 
  def RunValidationOutputToFilename(feed, options, output_filename):
  """Validate feed, save HTML at output_filename and return an exit code."""
  try:
  output_file = open(output_filename, 'w')
  exit_code = RunValidationOutputToFile(feed, options, output_file)
  output_file.close()
  except IOError, e:
  print 'Error while writing %s: %s' % (output_filename, e)
  output_filename = None
  exit_code = 2
 
  if options.manual_entry and output_filename:
  webbrowser.open('file://%s' % os.path.abspath(output_filename))
 
  return exit_code
 
 
  def RunValidationOutputToFile(feed, options, output_file):
  """Validate feed, write HTML to output_file and return an exit code."""
  accumulator = HTMLCountingProblemAccumulator(options.limit_per_type)
  problems = transitfeed.ProblemReporter(accumulator)
  schedule, exit_code, other_problems_string = RunValidation(feed, options,
  problems)
  if isinstance(feed, basestring):
  feed_location = feed
  else:
  feed_location = getattr(feed, 'name', repr(feed))
  accumulator.WriteOutput(feed_location, output_file, schedule,
  other_problems_string)
  return exit_code
 
 
  def RunValidationOutputToConsole(feed, options):
  """Validate feed, print reports and return an exit code."""
  accumulator = CountingConsoleProblemAccumulator()
  problems = transitfeed.ProblemReporter(accumulator)
  _, exit_code, _ = RunValidation(feed, options, problems)
  return exit_code
 
 
  def RunValidation(feed, options, problems):
  """Validate feed, returning the loaded Schedule and exit code.
 
  Args:
  feed: GTFS file, either path of the file as a string or a file object
  options: options object returned by optparse
  problems: transitfeed.ProblemReporter instance
 
  Returns:
  a transitfeed.Schedule object, exit code and plain text string of other
  problems
  Exit code is 2 if an extension is provided but can't be loaded, 1 if
  problems are found and 0 if the Schedule is problem free.
  plain text string is '' if no other problems are found.
  """
  other_problems_string = CheckVersion(latest_version=options.latest_version)
 
  # TODO: Add tests for this flag in testfeedvalidator.py
  if options.extension:
  try:
  __import__(options.extension)
  extension_module = sys.modules[options.extension]
  except ImportError:
  # TODO: Document extensions in a wiki page, place link here
  print("Could not import extension %s! Please ensure it is a proper "
  "Python module." % options.extension)
  exit(2)
  else:
  extension_module = transitfeed
 
  gtfs_factory = extension_module.GetGtfsFactory()
 
  print 'validating %s' % feed
  loader = gtfs_factory.Loader(feed, problems=problems, extra_validation=False,
  memory_db=options.memory_db,
  check_duplicate_trips=\
  options.check_duplicate_trips,
  gtfs_factory=gtfs_factory)
  schedule = loader.Load()
  schedule.Validate(service_gap_interval=options.service_gap_interval)
 
  if feed == 'IWantMyvalidation-crash.txt':
  # See test/testfeedvalidator.py
  raise Exception('For testing the feed validator crash handler.')
 
  if other_problems_string:
  print other_problems_string
 
  accumulator = problems.GetAccumulator()
  if accumulator.HasIssues():
  print 'ERROR: %s found' % accumulator.FormatCount()
  return schedule, 1, other_problems_string
  else:
  print 'feed validated successfully'
  return schedule, 0, other_problems_string
 
 
  def CheckVersion(latest_version=''):
  """
  Check there is newer version of this project.
 
  Codes are based on http://www.voidspace.org.uk/python/articles/urllib2.shtml
  Already got permission from the copyright holder.
  """
  current_version = transitfeed.__version__
  if not latest_version:
  timeout = 20
  socket.setdefaulttimeout(timeout)
  request = Request(SVN_TAG_URL)
 
  try:
  response = urlopen(request)
  content = response.read()
  versions = re.findall(r'>transitfeed-([\d\.]+)\/<\/a>', content)
  latest_version = MaxVersion(versions)
 
  except HTTPError, e:
  return('The server couldn\'t fulfill the request. Error code: %s.'
  % e.code)
  except URLError, e:
  return('We failed to reach transitfeed server. Reason: %s.' % e.reason)
 
  if not latest_version:
  return('We had trouble parsing the contents of %s.' % SVN_TAG_URL)
 
  newest_version = MaxVersion([latest_version, current_version])
  if current_version != newest_version:
  return('A new version %s of transitfeed is available. Please visit '
  'http://code.google.com/p/googletransitdatafeed and download.'
  % newest_version)
 
 
  def main():
  usage = \
  '''%prog [options] [<input GTFS.zip>]
 
  Validates GTFS file (or directory) <input GTFS.zip> and writes a HTML
  report of the results to validation-results.html.
 
  If <input GTFS.zip> is ommited the filename is read from the console. Dragging
  a file into the console may enter the filename.
 
  For more information see
  http://code.google.com/p/googletransitdatafeed/wiki/FeedValidator
  '''
 
  parser = util.OptionParserLongError(
  usage=usage, version='%prog '+transitfeed.__version__)
  parser.add_option('-n', '--noprompt', action='store_false',
  dest='manual_entry',
  help='do not prompt for feed location or load output in '
  'browser')
  parser.add_option('-o', '--output', dest='output', metavar='FILE',
  help='write html output to FILE or --output=CONSOLE to '
  'print all errors and warnings to the command console')
  parser.add_option('-p', '--performance', action='store_true',
  dest='performance',
  help='output memory and time performance (Availability: '
  'Unix')
  parser.add_option('-m', '--memory_db', dest='memory_db', action='store_true',
  help='Use in-memory sqlite db instead of a temporary file. '
  'It is faster but uses more RAM.')
  parser.add_option('-d', '--duplicate_trip_check',
  dest='check_duplicate_trips', action='store_true',
  help='Check for duplicate trips which go through the same '
  'stops with same service and start times')
  parser.add_option('-l', '--limit_per_type',
  dest='limit_per_type', action='store', type='int',
  help='Maximum number of errors and warnings to keep of '
  'each type')
  parser.add_option('--latest_version', dest='latest_version',
  action='store',
  help='a version number such as 1.2.1 or None to get the '
  'latest version from code.google.com. Output a warning if '
  'transitfeed.py is older than this version.')
  parser.add_option('--service_gap_interval',
  dest='service_gap_interval',
  action='store',
  type='int',
  help='the number of consecutive days to search for with no '
  'scheduled service. For each interval with no service '
  'having this number of days or more a warning will be '
  'issued')
  parser.add_option('--extension',
  dest='extension',
  help='the name of the Python module that containts a GTFS '
  'extension that is to be loaded and used while validating '
  'the specified feed.')
 
  parser.set_defaults(manual_entry=True, output='validation-results.html',
  memory_db=False, check_duplicate_trips=False,
  limit_per_type=5, latest_version='',
  service_gap_interval=13)
  (options, args) = parser.parse_args()
 
  if not len(args) == 1:
  if options.manual_entry:
  feed = raw_input('Enter Feed Location: ')
  else:
  parser.error('You must provide the path of a single feed')
  else:
  feed = args[0]
 
  feed = feed.strip('"')
 
  if options.performance:
  return ProfileRunValidationOutputFromOptions(feed, options)
  else:
  return RunValidationOutputFromOptions(feed, options)
 
 
  def ProfileRunValidationOutputFromOptions(feed, options):
  """Run RunValidationOutputFromOptions, print profile and return exit code."""
  import cProfile
  import pstats
  # runctx will modify a dict, but not locals(). We need a way to get rv back.
  locals_for_exec = locals()
  cProfile.runctx('rv = RunValidationOutputFromOptions(feed, options)',
  globals(), locals_for_exec, 'validate-stats')
 
  # Only available on Unix, http://docs.python.org/lib/module-resource.html
  import resource
  print "Time: %d seconds" % (
  resource.getrusage(resource.RUSAGE_SELF).ru_utime +
  resource.getrusage(resource.RUSAGE_SELF).ru_stime)
 
  # http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/286222
  # http://aspn.activestate.com/ASPN/Cookbook/ "The recipes are freely
  # available for review and use."
  def _VmB(VmKey):
  """Return size from proc status in bytes."""
  _proc_status = '/proc/%d/status' % os.getpid()
  _scale = {'kB': 1024.0, 'mB': 1024.0*1024.0,
  'KB': 1024.0, 'MB': 1024.0*1024.0}
 
  # get pseudo file /proc/<pid>/status
  try:
  t = open(_proc_status)
  v = t.read()
  t.close()
  except:
  raise Exception("no proc file %s" % _proc_status)
  return 0 # non-Linux?
  # get VmKey line e.g. 'VmRSS: 9999 kB\n ...'
  try:
  i = v.index(VmKey)
  v = v[i:].split(None, 3) # whitespace
  except:
  return 0 # v is empty
 
  if len(v) < 3:
  raise Exception("%s" % v)
  return 0 # invalid format?
  # convert Vm value to bytes
  return int(float(v[1]) * _scale[v[2]])
 
  # I ran this on over a hundred GTFS files, comparing VmSize to VmRSS
  # (resident set size). The difference was always under 2% or 3MB.
  print "Virtual Memory Size: %d bytes" % _VmB('VmSize:')
 
  # Output report of where CPU time was spent.
  p = pstats.Stats('validate-stats')
  p.strip_dirs()
  p.sort_stats('cumulative').print_stats(30)
  p.sort_stats('cumulative').print_callers(30)
  return locals_for_exec['rv']
 
 
  if __name__ == '__main__':
  util.RunWithCrashHandler(main)
 
  __doc__ = """
  Package holding files for Google Transit Feed Specification Schedule Viewer.
  """
  # This package contains the data files for schedule_viewer.py, a script that
  # comes with the transitfeed distribution. According to the thread
  # "[Distutils] distutils data_files and setuptools.pkg_resources are driving
  # me crazy" this is the easiest way to include data files. My experience
  # agrees. - Tom 2007-05-29
 
  // ===================================================================
  // Author: Matt Kruse <matt@mattkruse.com>
  // WWW: http://www.mattkruse.com/
  //
  // NOTICE: You may use this code for any purpose, commercial or
  // private, without any further permission from the author. You may
  // remove this notice from your final code if you wish, however it is
  // appreciated by the author if at least my web site address is kept.
  //
  // You may *NOT* re-distribute this code in any way except through its
  // use. That means, you can include it in your product, or your web
  // site, or any other form where the code is actually being used. You
  // may not put the plain javascript up on your site for download or
  // include it in your javascript libraries for download.
  // If you wish to share this code with others, please just point them
  // to the URL instead.
  // Please DO NOT link directly to my .js files from your site. Copy
  // the files to your server and use them there. Thank you.
  // ===================================================================
 
 
  /* SOURCE FILE: AnchorPosition.js */
 
  /*
  AnchorPosition.js
  Author: Matt Kruse
  Last modified: 10/11/02
 
  DESCRIPTION: These functions find the position of an <A> tag in a document,
  so other elements can be positioned relative to it.
 
  COMPATABILITY: Netscape 4.x,6.x,Mozilla, IE 5.x,6.x on Windows. Some small
  positioning errors - usually with Window positioning - occur on the
  Macintosh platform.
 
  FUNCTIONS:
  getAnchorPosition(anchorname)
  Returns an Object() having .x and .y properties of the pixel coordinates
  of the upper-left corner of the anchor. Position is relative to the PAGE.
 
  getAnchorWindowPosition(anchorname)
  Returns an Object() having .x and .y properties of the pixel coordinates
  of the upper-left corner of the anchor, relative to the WHOLE SCREEN.
 
  NOTES:
 
  1) For popping up separate browser windows, use getAnchorWindowPosition.
  Otherwise, use getAnchorPosition
 
  2) Your anchor tag MUST contain both NAME and ID attributes which are the
  same. For example:
  <A NAME="test" ID="test"> </A>
 
  3) There must be at least a space between <A> </A> for IE5.5 to see the
  anchor tag correctly. Do not do <A></A> with no space.
  */
 
  // getAnchorPosition(anchorname)
  // This function returns an object having .x and .y properties which are the coordinates
  // of the named anchor, relative to the page.
  function getAnchorPosition(anchorname) {
  // This function will return an Object with x and y properties
  var useWindow=false;
  var coordinates=new Object();
  var x=0,y=0;
  // Browser capability sniffing
  var use_gebi=false, use_css=false, use_layers=false;
  if (document.getElementById) { use_gebi=true; }
  else if (document.all) { use_css=true; }
  else if (document.layers) { use_layers=true; }
  // Logic to find position
  if (use_gebi && document.all) {
  x=AnchorPosition_getPageOffsetLeft(document.all[anchorname]);
  y=AnchorPosition_getPageOffsetTop(document.all[anchorname]);
  }
  else if (use_gebi) {
  var o=document.getElementById(anchorname);
  x=AnchorPosition_getPageOffsetLeft(o);
  y=AnchorPosition_getPageOffsetTop(o);
  }
  else if (use_css) {
  x=AnchorPosition_getPageOffsetLeft(document.all[anchorname]);
  y=AnchorPosition_getPageOffsetTop(document.all[anchorname]);
  }
  else if (use_layers) {
  var found=0;
  for (var i=0; i<document.anchors.length; i++) {
  if (document.anchors[i].name==anchorname) { found=1; break; }
  }
  if (found==0) {
  coordinates.x=0; coordinates.y=0; return coordinates;
  }
  x=document.anchors[i].x;
  y=document.anchors[i].y;
  }
  else {
  coordinates.x=0; coordinates.y=0; return coordinates;
  }
  coordinates.x=x;
  coordinates.y=y;
  return coordinates;
  }
 
  // getAnchorWindowPosition(anchorname)
  // This function returns an object having .x and .y properties which are the coordinates
  // of the named anchor, relative to the window
  function getAnchorWindowPosition(anchorname) {
  var coordinates=getAnchorPosition(anchorname);
  var x=0;
  var y=0;
  if (document.getElementById) {
  if (isNaN(window.screenX)) {
  x=coordinates.x-document.body.scrollLeft+window.screenLeft;
  y=coordinates.y-document.body.scrollTop+window.screenTop;
  }
  else {
  x=coordinates.x+window.screenX+(window.outerWidth-window.innerWidth)-window.pageXOffset;
  y=coordinates.y+window.screenY+(window.outerHeight-24-window.innerHeight)-window.pageYOffset;
  }
  }
  else if (document.all) {
  x=coordinates.x-document.body.scrollLeft+window.screenLeft;
  y=coordinates.y-document.body.scrollTop+window.screenTop;
  }
  else if (document.layers) {
  x=coordinates.x+window.screenX+(window.outerWidth-window.innerWidth)-window.pageXOffset;
  y=coordinates.y+window.screenY+(window.outerHeight-24-window.innerHeight)-window.pageYOffset;
  }
  coordinates.x=x;
  coordinates.y=y;
  return coordinates;
  }
 
  // Functions for IE to get position of an object
  function AnchorPosition_getPageOffsetLeft (el) {
  var ol=el.offsetLeft;
  while ((el=el.offsetParent) != null) { ol += el.offsetLeft; }
  return ol;
  }
  function AnchorPosition_getWindowOffsetLeft (el) {
  return AnchorPosition_getPageOffsetLeft(el)-document.body.scrollLeft;
  }
  function AnchorPosition_getPageOffsetTop (el) {
  var ot=el.offsetTop;
  while((el=el.offsetParent) != null) { ot += el.offsetTop; }
  return ot;
  }
  function AnchorPosition_getWindowOffsetTop (el) {
  return AnchorPosition_getPageOffsetTop(el)-document.body.scrollTop;
  }
 
  /* SOURCE FILE: date.js */
 
  // HISTORY
  // ------------------------------------------------------------------
  // May 17, 2003: Fixed bug in parseDate() for dates <1970
  // March 11, 2003: Added parseDate() function
  // March 11, 2003: Added "NNN" formatting option. Doesn't match up
  // perfectly with SimpleDateFormat formats, but
  // backwards-compatability was required.
 
  // ------------------------------------------------------------------
  // These functions use the same 'format' strings as the
  // java.text.SimpleDateFormat class, with minor exceptions.
  // The format string consists of the following abbreviations:
  //
  // Field | Full Form | Short Form
  // -------------+--------------------+-----------------------
  // Year | yyyy (4 digits) | yy (2 digits), y (2 or 4 digits)
  // Month | MMM (name or abbr.)| MM (2 digits), M (1 or 2 digits)
  // | NNN (abbr.) |
  // Day of Month | dd (2 digits) | d (1 or 2 digits)
  // Day of Week | EE (name) | E (abbr)
  // Hour (1-12) | hh (2 digits) | h (1 or 2 digits)
  // Hour (0-23) | HH (2 digits) | H (1 or 2 digits)
  // Hour (0-11) | KK (2 digits) | K (1 or 2 digits)
  // Hour (1-24) | kk (2 digits) | k (1 or 2 digits)
  // Minute | mm (2 digits) | m (1 or 2 digits)
  // Second | ss (2 digits) | s (1 or 2 digits)
  // AM/PM | a |
  //
  // NOTE THE DIFFERENCE BETWEEN MM and mm! Month=MM, not mm!
  // Examples:
  // "MMM d, y" matches: January 01, 2000
  // Dec 1, 1900
  // Nov 20, 00
  // "M/d/yy" matches: 01/20/00
  // 9/2/00
  // "MMM dd, yyyy hh:mm:ssa" matches: "January 01, 2000 12:30:45AM"
  // ------------------------------------------------------------------
 
  var MONTH_NAMES=new Array('January','February','March','April','May','June','July','August','September','October','November','December','Jan','Feb','Mar','Apr','May','Jun','Jul','Aug','Sep','Oct','Nov','Dec');
  var DAY_NAMES=new Array('Sunday','Monday','Tuesday','Wednesday','Thursday','Friday','Saturday','Sun','Mon','Tue','Wed','Thu','Fri','Sat');
  function LZ(x) {return(x<0||x>9?"":"0")+x}
 
  // ------------------------------------------------------------------
  // isDate ( date_string, format_string )
  // Returns true if date string matches format of format string and
  // is a valid date. Else returns false.
  // It is recommended that you trim whitespace around the value before
  // passing it to this function, as whitespace is NOT ignored!
  // ------------------------------------------------------------------
  function isDate(val,format) {
  var date=getDateFromFormat(val,format);
  if (date==0) { return false; }
  return true;
  }
 
  // -------------------------------------------------------------------
  // compareDates(date1,date1format,date2,date2format)
  // Compare two date strings to see which is greater.
  // Returns:
  // 1 if date1 is greater than date2
  // 0 if date2 is greater than date1 of if they are the same
  // -1 if either of the dates is in an invalid format
  // -------------------------------------------------------------------
  function compareDates(date1,dateformat1,date2,dateformat2) {
  var d1=getDateFromFormat(date1,dateformat1);
  var d2=getDateFromFormat(date2,dateformat2);
  if (d1==0 || d2==0) {
  return -1;
  }
  else if (d1 > d2) {
  return 1;
  }
  return 0;
  }
 
  // ------------------------------------------------------------------
  // formatDate (date_object, format)
  // Returns a date in the output format specified.
  // The format string uses the same abbreviations as in getDateFromFormat()
  // ------------------------------------------------------------------
  function formatDate(date,format) {
  format=format+"";
  var result="";
  var i_format=0;
  var c="";
  var token="";
  var y=date.getYear()+"";
  var M=date.getMonth()+1;
  var d=date.getDate();
  var E=date.getDay();
  var H=date.getHours();
  var m=date.getMinutes();
  var s=date.getSeconds();
  var yyyy,yy,MMM,MM,dd,hh,h,mm,ss,ampm,HH,H,KK,K,kk,k;
  // Convert real date parts into formatted versions
  var value=new Object();
  if (y.length < 4) {y=""+(y-0+1900);}
  value["y"]=""+y;
  value["yyyy"]=y;
  value["yy"]=y.substring(2,4);
  value["M"]=M;
  value["MM"]=LZ(M);
  value["MMM"]=MONTH_NAMES[M-1];
  value["NNN"]=MONTH_NAMES[M+11];
  value["d"]=d;
  value["dd"]=LZ(d);
  value["E"]=DAY_NAMES[E+7];
  value["EE"]=DAY_NAMES[E];
  value["H"]=H;
  value["HH"]=LZ(H);
  if (H==0){value["h"]=12;}
  else if (H>12){value["h"]=H-12;}
  else {value["h"]=H;}
  value["hh"]=LZ(value["h"]);
  if (H>11){value["K"]=H-12;} else {value["K"]=H;}
  value["k"]=H+1;
  value["KK"]=LZ(value["K"]);
  value["kk"]=LZ(value["k"]);
  if (H > 11) { value["a"]="PM"; }
  else { value["a"]="AM"; }
  value["m"]=m;
  value["mm"]=LZ(m);
  value["s"]=s;
  value["ss"]=LZ(s);
  while (i_format < format.length) {
  c=format.charAt(i_format);
  token="";
  while ((format.charAt(i_format)==c) && (i_format < format.length)) {
  token += format.charAt(i_format++);
  }
  if (value[token] != null) { result=result + value[token]; }
  else { result=result + token; }
  }
  return result;
  }
 
  // ------------------------------------------------------------------
  // Utility functions for parsing in getDateFromFormat()
  // ------------------------------------------------------------------
  function _isInteger(val) {
  var digits="1234567890";
  for (var i=0; i < val.length; i++) {
  if (digits.indexOf(val.charAt(i))==-1) { return false; }
  }
  return true;
  }
  function _getInt(str,i,minlength,maxlength) {
  for (var x=maxlength; x>=minlength; x--) {
  var token=str.substring(i,i+x);
  if (token.length < minlength) { return null; }
  if (_isInteger(token)) { return token; }
  }
  return null;
  }
 
  // ------------------------------------------------------------------
  // getDateFromFormat( date_string , format_string )
  //
  // This function takes a date string and a format string. It matches
  // If the date string matches the format string, it returns the
  // getTime() of the date. If it does not match, it returns 0.
  // ------------------------------------------------------------------
  function getDateFromFormat(val,format) {
  val=val+"";
  format=format+"";
  var i_val=0;
  var i_format=0;
  var c="";
  var token="";
  var token2="";
  var x,y;
  var now=new Date();
  var year=now.getYear();
  var month=now.getMonth()+1;
  var date=1;
  var hh=now.getHours();
  var mm=now.getMinutes();
  var ss=now.getSeconds();
  var ampm="";
 
  while (i_format < format.length) {
  // Get next token from format string
  c=format.charAt(i_format);
  token="";
  while ((format.charAt(i_format)==c) && (i_format < format.length)) {
  token += format.charAt(i_format++);
  }
  // Extract contents of value based on format token
  if (token=="yyyy" || token=="yy" || token=="y") {
  if (token=="yyyy") { x=4;y=4; }
  if (token=="yy") { x=2;y=2; }
  if (token=="y") { x=2;y=4; }
  year=_getInt(val,i_val,x,y);
  if (year==null) { return 0; }
  i_val += year.length;
  if (year.length==2) {
  if (year > 70) { year=1900+(year-0); }
  else { year=2000+(year-0); }
  }
  }
  else if (token=="MMM"||token=="NNN"){
  month=0;
  for (var i=0; i<MONTH_NAMES.length; i++) {
  var month_name=MONTH_NAMES[i];
  if (val.substring(i_val,i_val+month_name.length).toLowerCase()==month_name.toLowerCase()) {
  if (token=="MMM"||(token=="NNN"&&i>11)) {
  month=i+1;
  if (month>12) { month -= 12; }
  i_val += month_name.length;
  break;
  }
  }
  }
  if ((month < 1)||(month>12)){return 0;}
  }
  else if (token=="EE"||token=="E"){
  for (var i=0; i<DAY_NAMES.length; i++) {
  var day_name=DAY_NAMES[i];
  if (val.substring(i_val,i_val+day_name.length).toLowerCase()==day_name.toLowerCase()) {
  i_val += day_name.length;
  break;
  }
  }
  }
  else if (token=="MM"||token=="M") {
  month=_getInt(val,i_val,token.length,2);
  if(month==null||(month<1)||(month>12)){return 0;}
  i_val+=month.length;}
  else if (token=="dd"||token=="d") {
  date=_getInt(val,i_val,token.length,2);
  if(date==null||(date<1)||(date>31)){return 0;}
  i_val+=date.length;}
  else if (token=="hh"||token=="h") {
  hh=_getInt(val,i_val,token.length,2);
  if(hh==null||(hh<1)||(hh>12)){return 0;}
  i_val+=hh.length;}
  else if (token=="HH"||token=="H") {
  hh=_getInt(val,i_val,token.length,2);
  if(hh==null||(hh<0)||(hh>23)){return 0;}
  i_val+=hh.length;}
  else if (token=="KK"||token=="K") {
  hh=_getInt(val,i_val,token.length,2);
  if(hh==null||(hh<0)||(hh>11)){return 0;}
  i_val+=hh.length;}
  else if (token=="kk"||token=="k") {
  hh=_getInt(val,i_val,token.length,2);
  if(hh==null||(hh<1)||(hh>24)){return 0;}
  i_val+=hh.length;hh--;}
  else if (token=="mm"||token=="m") {
  mm=_getInt(val,i_val,token.length,2);
  if(mm==null||(mm<0)||(mm>59)){return 0;}
  i_val+=mm.length;}
  else if (token=="ss"||token=="s") {
  ss=_getInt(val,i_val,token.length,2);
  if(ss==null||(ss<0)||(ss>59)){return 0;}
  i_val+=ss.length;}
  else if (token=="a") {
  if (val.substring(i_val,i_val+2).toLowerCase()=="am") {ampm="AM";}
  else if (val.substring(i_val,i_val+2).toLowerCase()=="pm") {ampm="PM";}
  else {return 0;}
  i_val+=2;}
  else {
  if (val.substring(i_val,i_val+token.length)!=token) {return 0;}
  else {i_val+=token.length;}
  }
  }
  // If there are any trailing characters left in the value, it doesn't match
  if (i_val != val.length) { return 0; }
  // Is date valid for month?
  if (month==2) {
  // Check for leap year
  if ( ( (year%4==0)&&(year%100 != 0) ) || (year%400==0) ) { // leap year
  if (date > 29){ return 0; }
  }
  else { if (date > 28) { return 0; } }
  }
  if ((month==4)||(month==6)||(month==9)||(month==11)) {
  if (date > 30) { return 0; }
  }
  // Correct hours value
  if (hh<12 && ampm=="PM") { hh=hh-0+12; }
  else if (hh>11 && ampm=="AM") { hh-=12; }
  var newdate=new Date(year,month-1,date,hh,mm,ss);
  return newdate.getTime();
  }
 
  // ------------------------------------------------------------------
  // parseDate( date_string [, prefer_euro_format] )
  //
  // This function takes a date string and tries to match it to a
  // number of possible date formats to get the value. It will try to
  // match against the following international formats, in this order:
  // y-M-d MMM d, y MMM d,y y-MMM-d d-MMM-y MMM d
  // M/d/y M-d-y M.d.y MMM-d M/d M-d
  // d/M/y d-M-y d.M.y d-MMM d/M d-M
  // A second argument may be passed to instruct the method to search
  // for formats like d/M/y (european format) before M/d/y (American).
  // Returns a Date object or null if no patterns match.
  // ------------------------------------------------------------------
  function parseDate(val) {
  var preferEuro=(arguments.length==2)?arguments[1]:false;
  generalFormats=new Array('y-M-d','MMM d, y','MMM d,y','y-MMM-d','d-MMM-y','MMM d');
  monthFirst=new Array('M/d/y','M-d-y','M.d.y','MMM-d','M/d','M-d');
  dateFirst =new Array('d/M/y','d-M-y','d.M.y','d-MMM','d/M','d-M');
  var checkList=new Array('generalFormats',preferEuro?'dateFirst':'monthFirst',preferEuro?'monthFirst':'dateFirst');
  var d=null;
  for (var i=0; i<checkList.length; i++) {
  var l=window[checkList[i]];
  for (var j=0; j<l.length; j++) {
  d=getDateFromFormat(val,l[j]);
  if (d!=0) { return new Date(d); }
  }
  }
  return null;
  }
 
  /* SOURCE FILE: PopupWindow.js */
 
  /*
  PopupWindow.js
  Author: Matt Kruse
  Last modified: 02/16/04
 
  DESCRIPTION: This object allows you to easily and quickly popup a window
  in a certain place. The window can either be a DIV or a separate browser
  window.
 
  COMPATABILITY: Works with Netscape 4.x, 6.x, IE 5.x on Windows. Some small
  positioning errors - usually with Window positioning - occur on the
  Macintosh platform. Due to bugs in Netscape 4.x, populating the popup
  window with <STYLE> tags may cause errors.
 
  USAGE:
  // Create an object for a WINDOW popup
  var win = new PopupWindow();
 
  // Create an object for a DIV window using the DIV named 'mydiv'
  var win = new PopupWindow('mydiv');
 
  // Set the window to automatically hide itself when the user clicks
  // anywhere else on the page except the popup
  win.autoHide();
 
  // Show the window relative to the anchor name passed in
  win.showPopup(anchorname);
 
  // Hide the popup
  win.hidePopup();
 
  // Set the size of the popup window (only applies to WINDOW popups
  win.setSize(width,height);
 
  // Populate the contents of the popup window that will be shown. If you
  // change the contents while it is displayed, you will need to refresh()
  win.populate(string);
 
  // set the URL of the window, rather than populating its contents
  // manually
  win.setUrl("http://www.site.com/");
 
  // Refresh the contents of the popup
  win.refresh();
 
  // Specify how many pixels to the right of the anchor the popup will appear
  win.offsetX = 50;
 
  // Specify how many pixels below the anchor the popup will appear
  win.offsetY = 100;
 
  NOTES:
  1) Requires the functions in AnchorPosition.js
 
  2) Your anchor tag MUST contain both NAME and ID attributes which are the
  same. For example:
  <A NAME="test" ID="test"> </A>
 
  3) There must be at least a space between <A> </A> for IE5.5 to see the
  anchor tag correctly. Do not do <A></A> with no space.
 
  4) When a PopupWindow object is created, a handler for 'onmouseup' is
  attached to any event handler you may have already defined. Do NOT define
  an event handler for 'onmouseup' after you define a PopupWindow object or
  the autoHide() will not work correctly.
  */
 
  // Set the position of the popup window based on the anchor
  function PopupWindow_getXYPosition(anchorname) {
  var coordinates;
  if (this.type == "WINDOW") {
  coordinates = getAnchorWindowPosition(anchorname);
  }
  else {
  coordinates = getAnchorPosition(anchorname);
  }
  this.x = coordinates.x;
  this.y = coordinates.y;
  }
  // Set width/height of DIV/popup window
  function PopupWindow_setSize(width,height) {
  this.width = width;
  this.height = height;
  }
  // Fill the window with contents
  function PopupWindow_populate(contents) {
  this.contents = contents;
  this.populated = false;
  }
  // Set the URL to go to
  function PopupWindow_setUrl(url) {
  this.url = url;
  }
  // Set the window popup properties
  function PopupWindow_setWindowProperties(props) {
  this.windowProperties = props;
  }
  // Refresh the displayed contents of the popup
  function PopupWindow_refresh() {
  if (this.divName != null) {
  // refresh the DIV object
  if (this.use_gebi) {
  document.getElementById(this.divName).innerHTML = this.contents;
  }
  else if (this.use_css) {
  document.all[this.divName].innerHTML = this.contents;
  }
  else if (this.use_layers) {
  var d = document.layers[this.divName];
  d.document.open();
  d.document.writeln(this.contents);
  d.document.close();
  }
  }
  else {
  if (this.popupWindow != null && !this.popupWindow.closed) {
  if (this.url!="") {
  this.popupWindow.location.href=this.url;
  }
  else {
  this.popupWindow.document.open();
  this.popupWindow.document.writeln(this.contents);
  this.popupWindow.document.close();
  }
  this.popupWindow.focus();
  }
  }
  }
  // Position and show the popup, relative to an anchor object
  function PopupWindow_showPopup(anchorname) {
  this.getXYPosition(anchorname);
  this.x += this.offsetX;
  this.y += this.offsetY;
  if (!this.populated && (this.contents != "")) {
  this.populated = true;
  this.refresh();
  }
  if (this.divName != null) {
  // Show the DIV object
  if (this.use_gebi) {
  document.getElementById(this.divName).style.left = this.x + "px";
  document.getElementById(this.divName).style.top = this.y + "px";
  document.getElementById(this.divName).style.visibility = "visible";
  }
  else if (this.use_css) {
  document.all[this.divName].style.left = this.x;
  document.all[this.divName].style.top = this.y;
  document.all[this.divName].style.visibility = "visible";
  }
  else if (this.use_layers) {
  document.layers[this.divName].left = this.x;
  document.layers[this.divName].top = this.y;
  document.layers[this.divName].visibility = "visible";
  }
  }
  else {
  if (this.popupWindow == null || this.popupWindow.closed) {
  // If the popup window will go off-screen, move it so it doesn't
  if (this.x<0) { this.x=0; }
  if (this.y<0) { this.y=0; }
  if (screen && screen.availHeight) {
  if ((this.y + this.height) > screen.availHeight) {
  this.y = screen.availHeight - this.height;
  }
  }
  if (screen && screen.availWidth) {
  if ((this.x + this.width) > screen.availWidth) {
  this.x = screen.availWidth - this.width;
  }
  }
  var avoidAboutBlank = window.opera || ( document.layers && !navigator.mimeTypes['*'] ) || navigator.vendor == 'KDE' || ( document.childNodes && !document.all && !navigator.taintEnabled );
  this.popupWindow = window.open(avoidAboutBlank?"":"about:blank","window_"+anchorname,this.windowProperties+",width="+this.width+",height="+this.height+",screenX="+this.x+",left="+this.x+",screenY="+this.y+",top="+this.y+"");
  }
  this.refresh();
  }
  }
  // Hide the popup
  function PopupWindow_hidePopup() {
  if (this.divName != null) {
  if (this.use_gebi) {
  document.getElementById(this.divName).style.visibility = "hidden";
  }
  else if (this.use_css) {
  document.all[this.divName].style.visibility = "hidden";
  }
  else if (this.use_layers) {
  document.layers[this.divName].visibility = "hidden";
  }
  }
  else {
  if (this.popupWindow && !this.popupWindow.closed) {
  this.popupWindow.close();
  this.popupWindow = null;
  }
  }
  }
  // Pass an event and return whether or not it was the popup DIV that was clicked
  function PopupWindow_isClicked(e) {
  if (this.divName != null) {
  if (this.use_layers) {
  var clickX = e.pageX;
  var clickY = e.pageY;
  var t = document.layers[this.divName];
  if ((clickX > t.left) && (clickX < t.left+t.clip.width) && (clickY > t.top) && (clickY < t.top+t.clip.height)) {
  return true;
  }
  else { return false; }
  }
  else if (document.all) { // Need to hard-code this to trap IE for error-handling
  var t = window.event.srcElement;
  while (t.parentElement != null) {
  if (t.id==this.divName) {
  return true;
  }
  t = t.parentElement;
  }
  return false;
  }
  else if (this.use_gebi && e) {
  var t = e.originalTarget;
  while (t.parentNode != null) {
  if (t.id==this.divName) {
  return true;
  }
  t = t.parentNode;
  }
  return false;
  }
  return false;
  }
  return false;
  }
 
  // Check an onMouseDown event to see if we should hide
  function PopupWindow_hideIfNotClicked(e) {
  if (this.autoHideEnabled && !this.isClicked(e)) {
  this.hidePopup();
  }
  }
  // Call this to make the DIV disable automatically when mouse is clicked outside it
  function PopupWindow_autoHide() {
  this.autoHideEnabled = true;
  }
  // This global function checks all PopupWindow objects onmouseup to see if they should be hidden
  function PopupWindow_hidePopupWindows(e) {
  for (var i=0; i<popupWindowObjects.length; i++) {
  if (popupWindowObjects[i] != null) {
  var p = popupWindowObjects[i];
  p.hideIfNotClicked(e);
  }
  }
  }
  // Run this immediately to attach the event listener
  function PopupWindow_attachListener() {
  if (document.layers) {
  document.captureEvents(Event.MOUSEUP);
  }
  window.popupWindowOldEventListener = document.onmouseup;
  if (window.popupWindowOldEventListener != null) {
  document.onmouseup = new Function("window.popupWindowOldEventListener(); PopupWindow_hidePopupWindows();");
  }
  else {
  document.onmouseup = PopupWindow_hidePopupWindows;
  }
  }
  // CONSTRUCTOR for the PopupWindow object
  // Pass it a DIV name to use a DHTML popup, otherwise will default to window popup
  function PopupWindow() {
  if (!window.popupWindowIndex) { window.popupWindowIndex = 0; }
  if (!window.popupWindowObjects) { window.popupWindowObjects = new Array(); }
  if (!window.listenerAttached) {
  window.listenerAttached = true;
  PopupWindow_attachListener();
  }
  this.index = popupWindowIndex++;
  popupWindowObjects[this.index] = this;
  this.divName = null;
  this.popupWindow = null;
  this.width=0;
  this.height=0;
  this.populated = false;
  this.visible = false;
  this.autoHideEnabled = false;
 
  this.contents = "";
  this.url="";
  this.windowProperties="toolbar=no,location=no,status=no,menubar=no,scrollbars=auto,resizable,alwaysRaised,dependent,titlebar=no";
  if (arguments.length>0) {
  this.type="DIV";
  this.divName = arguments[0];
  }
  else {
  this.type="WINDOW";
  }
  this.use_gebi = false;
  this.use_css = false;
  this.use_layers = false;
  if (document.getElementById) { this.use_gebi = true; }
  else if (document.all) { this.use_css = true; }
  else if (document.layers) { this.use_layers = true; }
  else { this.type = "WINDOW"; }
  this.offsetX = 0;
  this.offsetY = 0;
  // Method mappings
  this.getXYPosition = PopupWindow_getXYPosition;
  this.populate = PopupWindow_populate;
  this.setUrl = PopupWindow_setUrl;
  this.setWindowProperties = PopupWindow_setWindowProperties;
  this.refresh = PopupWindow_refresh;
  this.showPopup = PopupWindow_showPopup;
  this.hidePopup = PopupWindow_hidePopup;
  this.setSize = PopupWindow_setSize;
  this.isClicked = PopupWindow_isClicked;
  this.autoHide = PopupWindow_autoHide;
  this.hideIfNotClicked = PopupWindow_hideIfNotClicked;
  }
 
  /* SOURCE FILE: CalendarPopup.js */
 
  // HISTORY
  // ------------------------------------------------------------------
  // Feb 7, 2005: Fixed a CSS styles to use px unit
  // March 29, 2004: Added check in select() method for the form field
  // being disabled. If it is, just return and don't do anything.
  // March 24, 2004: Fixed bug - when month name and abbreviations were
  // changed, date format still used original values.
  // January 26, 2004: Added support for drop-down month and year
  // navigation (Thanks to Chris Reid for the idea)
  // September 22, 2003: Fixed a minor problem in YEAR calendar with
  // CSS prefix.
  // August 19, 2003: Renamed the function to get styles, and made it
  // work correctly without an object reference
  // August 18, 2003: Changed showYearNavigation and
  // showYearNavigationInput to optionally take an argument of
  // true or false
  // July 31, 2003: Added text input option for year navigation.
  // Added a per-calendar CSS prefix option to optionally use
  // different styles for different calendars.
  // July 29, 2003: Fixed bug causing the Today link to be clickable
  // even though today falls in a disabled date range.
  // Changed formatting to use pure CSS, allowing greater control
  // over look-and-feel options.
  // June 11, 2003: Fixed bug causing the Today link to be unselectable
  // under certain cases when some days of week are disabled
  // March 14, 2003: Added ability to disable individual dates or date
  // ranges, display as light gray and strike-through
  // March 14, 2003: Removed dependency on graypixel.gif and instead
  /// use table border coloring
  // March 12, 2003: Modified showCalendar() function to allow optional
  // start-date parameter
  // March 11, 2003: Modified select() function to allow optional
  // start-date parameter
  /*
  DESCRIPTION: This object implements a popup calendar to allow the user to
  select a date, month, quarter, or year.
 
  COMPATABILITY: Works with Netscape 4.x, 6.x, IE 5.x on Windows. Some small
  positioning errors - usually with Window positioning - occur on the
  Macintosh platform.
  The calendar can be modified to work for any location in the world by
  changing which weekday is displayed as the first column, changing the month
  names, and changing the column headers for each day.
 
  USAGE:
  // Create a new CalendarPopup object of type WINDOW
  var cal = new CalendarPopup();
 
  // Create a new CalendarPopup object of type DIV using the DIV named 'mydiv'
  var cal = new CalendarPopup('mydiv');
 
  // Easy method to link the popup calendar with an input box.
  cal.select(inputObject, anchorname, dateFormat);
  // Same method, but passing a default date other than the field's current value
  cal.select(inputObject, anchorname, dateFormat, '01/02/2000');
  // This is an example call to the popup calendar from a link to populate an
  // input box. Note that to use this, date.js must also be included!!
  <A HREF="#" onClick="cal.select(document.forms[0].date,'anchorname','MM/dd/yyyy'); return false;">Select</A>
 
  // Set the type of date select to be used. By default it is 'date'.
  cal.setDisplayType(type);
 
  // When a date, month, quarter, or year is clicked, a function is called and
  // passed the details. You must write this function, and tell the calendar
  // popup what the function name is.
  // Function to be called for 'date' select receives y, m, d
  cal.setReturnFunction(functionname);
  // Function to be called for 'month' select receives y, m
  cal.setReturnMonthFunction(functionname);
  // Function to be called for 'quarter' select receives y, q
  cal.setReturnQuarterFunction(functionname);
  // Function to be called for 'year' select receives y
  cal.setReturnYearFunction(functionname);
 
  // Show the calendar relative to a given anchor
  cal.showCalendar(anchorname);
 
  // Hide the calendar. The calendar is set to autoHide automatically
  cal.hideCalendar();
 
  // Set the month names to be used. Default are English month names
  cal.setMonthNames("January","February","March",...);
 
  // Set the month abbreviations to be used. Default are English month abbreviations
  cal.setMonthAbbreviations("Jan","Feb","Mar",...);
 
  // Show navigation for changing by the year, not just one month at a time
  cal.showYearNavigation();
 
  // Show month and year dropdowns, for quicker selection of month of dates
  cal.showNavigationDropdowns();
 
  // Set the text to be used above each day column. The days start with
  // sunday regardless of the value of WeekStartDay
  cal.setDayHeaders("S","M","T",...);
 
  // Set the day for the first column in the calendar grid. By default this
  // is Sunday (0) but it may be changed to fit the conventions of other
  // countries.
  cal.setWeekStartDay(1); // week is Monday - Sunday
 
  // Set the weekdays which should be disabled in the 'date' select popup. You can
  // then allow someone to only select week end dates, or Tuedays, for example
  cal.setDisabledWeekDays(0,1); // To disable selecting the 1st or 2nd days of the week
 
  // Selectively disable individual days or date ranges. Disabled days will not
  // be clickable, and show as strike-through text on current browsers.
  // Date format is any format recognized by parseDate() in date.js
  // Pass a single date to disable:
  cal.addDisabledDates("2003-01-01");
  // Pass null as the first parameter to mean "anything up to and including" the
  // passed date:
  cal.addDisabledDates(null, "01/02/03");
  // Pass null as the second parameter to mean "including the passed date and
  // anything after it:
  cal.addDisabledDates("Jan 01, 2003", null);
  // Pass two dates to disable all dates inbetween and including the two
  cal.addDisabledDates("January 01, 2003", "Dec 31, 2003");
 
  // When the 'year' select is displayed, set the number of years back from the
  // current year to start listing years. Default is 2.
  // This is also used for year drop-down, to decide how many years +/- to display
  cal.setYearSelectStartOffset(2);
 
  // Text for the word "Today" appearing on the calendar
  cal.setTodayText("Today");
 
  // The calendar uses CSS classes for formatting. If you want your calendar to
  // have unique styles, you can set the prefix that will be added to all the
  // classes in the output.
  // For example, normal output may have this:
  // <SPAN CLASS="cpTodayTextDisabled">Today<SPAN>
  // But if you set the prefix like this:
  cal.setCssPrefix("Test");
  // The output will then look like:
  // <SPAN CLASS="TestcpTodayTextDisabled">Today<SPAN>
  // And you can define that style somewhere in your page.
 
  // When using Year navigation, you can make the year be an input box, so
  // the user can manually change it and jump to any year
  cal.showYearNavigationInput();
 
  // Set the calendar offset to be different than the default. By default it
  // will appear just below and to the right of the anchorname. So if you have
  // a text box where the date will go and and anchor immediately after the
  // text box, the calendar will display immediately under the text box.
  cal.offsetX = 20;
  cal.offsetY = 20;
 
  NOTES:
  1) Requires the functions in AnchorPosition.js and PopupWindow.js
 
  2) Your anchor tag MUST contain both NAME and ID attributes which are the
  same. For example:
  <A NAME="test" ID="test"> </A>
 
  3) There must be at least a space between <A> </A> for IE5.5 to see the
  anchor tag correctly. Do not do <A></A> with no space.
 
  4) When a CalendarPopup object is created, a handler for 'onmouseup' is
  attached to any event handler you may have already defined. Do NOT define
  an event handler for 'onmouseup' after you define a CalendarPopup object
  or the autoHide() will not work correctly.
 
  5) The calendar popup display uses style sheets to make it look nice.
 
  */
 
  // Quick fix for FF3
  function CP_stop(e) { if (e && e.stopPropagation) { e.stopPropagation(); } }
 
  // CONSTRUCTOR for the CalendarPopup Object
  function CalendarPopup() {
  var c;
  if (arguments.length>0) {
  c = new PopupWindow(arguments[0]);
  }
  else {
  c = new PopupWindow();
  c.setSize(150,175);
  }
  c.offsetX = -152;
  c.offsetY = 25;
  c.autoHide();
  // Calendar-specific properties
  c.monthNames = new Array("January","February","March","April","May","June","July","August","September","October","November","December");
  c.monthAbbreviations = new Array("Jan","Feb","Mar","Apr","May","Jun","Jul","Aug","Sep","Oct","Nov","Dec");
  c.dayHeaders = new Array("S","M","T","W","T","F","S");
  c.returnFunction = "CP_tmpReturnFunction";
  c.returnMonthFunction = "CP_tmpReturnMonthFunction";
  c.returnQuarterFunction = "CP_tmpReturnQuarterFunction";
  c.returnYearFunction = "CP_tmpReturnYearFunction";
  c.weekStartDay = 0;
  c.isShowYearNavigation = false;
  c.displayType = "date";
  c.disabledWeekDays = new Object();
  c.disabledDatesExpression = "";
  c.yearSelectStartOffset = 2;
  c.currentDate = null;
  c.todayText="Today";
  c.cssPrefix="";
  c.isShowNavigationDropdowns=false;
  c.isShowYearNavigationInput=false;
  window.CP_calendarObject = null;
  window.CP_targetInput = null;
  window.CP_dateFormat = "MM/dd/yyyy";
  // Method mappings
  c.copyMonthNamesToWindow = CP_copyMonthNamesToWindow;
  c.setReturnFunction = CP_setReturnFunction;
  c.setReturnMonthFunction = CP_setReturnMonthFunction;
  c.setReturnQuarterFunction = CP_setReturnQuarterFunction;
  c.setReturnYearFunction = CP_setReturnYearFunction;
  c.setMonthNames = CP_setMonthNames;
  c.setMonthAbbreviations = CP_setMonthAbbreviations;
  c.setDayHeaders = CP_setDayHeaders;
  c.setWeekStartDay = CP_setWeekStartDay;
  c.setDisplayType = CP_setDisplayType;
  c.setDisabledWeekDays = CP_setDisabledWeekDays;
  c.addDisabledDates = CP_addDisabledDates;
  c.setYearSelectStartOffset = CP_setYearSelectStartOffset;
  c.setTodayText = CP_setTodayText;
  c.showYearNavigation = CP_showYearNavigation;
  c.showCalendar = CP_showCalendar;
  c.hideCalendar = CP_hideCalendar;
  c.getStyles = getCalendarStyles;
  c.refreshCalendar = CP_refreshCalendar;
  c.getCalendar = CP_getCalendar;
  c.select = CP_select;
  c.setCssPrefix = CP_setCssPrefix;
  c.showNavigationDropdowns = CP_showNavigationDropdowns;
  c.showYearNavigationInput = CP_showYearNavigationInput;
  c.copyMonthNamesToWindow();
  // Return the object
  return c;
  }
  function CP_copyMonthNamesToWindow() {
  // Copy these values over to the date.js
  if (typeof(window.MONTH_NAMES)!="undefined" && window.MONTH_NAMES!=null) {
  window.MONTH_NAMES = new Array();
  for (var i=0; i<this.monthNames.length; i++) {
  window.MONTH_NAMES[window.MONTH_NAMES.length] = this.monthNames[i];
  }
  for (var i=0; i<this.monthAbbreviations.length; i++) {
  window.MONTH_NAMES[window.MONTH_NAMES.length] = this.monthAbbreviations[i];
  }
  }
  }
  // Temporary default functions to be called when items clicked, so no error is thrown
  function CP_tmpReturnFunction(y,m,d) {
  if (window.CP_targetInput!=null) {
  var dt = new Date(y,m-1,d,0,0,0);
  if (window.CP_calendarObject!=null) { window.CP_calendarObject.copyMonthNamesToWindow(); }
  window.CP_targetInput.value = formatDate(dt,window.CP_dateFormat);
  }
  else {
  alert('Use setReturnFunction() to define which function will get the clicked results!');
  }
  }
  function CP_tmpReturnMonthFunction(y,m) {
  alert('Use setReturnMonthFunction() to define which function will get the clicked results!\nYou clicked: year='+y+' , month='+m);
  }
  function CP_tmpReturnQuarterFunction(y,q) {
  alert('Use setReturnQuarterFunction() to define which function will get the clicked results!\nYou clicked: year='+y+' , quarter='+q);
  }
  function CP_tmpReturnYearFunction(y) {
  alert('Use setReturnYearFunction() to define which function will get the clicked results!\nYou clicked: year='+y);
  }
 
  // Set the name of the functions to call to get the clicked item
  function CP_setReturnFunction(name) { this.returnFunction = name; }
  function CP_setReturnMonthFunction(name) { this.returnMonthFunction = name; }
  function CP_setReturnQuarterFunction(name) { this.returnQuarterFunction = name; }
  function CP_setReturnYearFunction(name) { this.returnYearFunction = name; }
 
  // Over-ride the built-in month names
  function CP_setMonthNames() {
  for (var i=0; i<arguments.length; i++) { this.monthNames[i] = arguments[i]; }
  this.copyMonthNamesToWindow();
  }
 
  // Over-ride the built-in month abbreviations
  function CP_setMonthAbbreviations() {
  for (var i=0; i<arguments.length; i++) { this.monthAbbreviations[i] = arguments[i]; }
  this.copyMonthNamesToWindow();
  }
 
  // Over-ride the built-in column headers for each day
  function CP_setDayHeaders() {
  for (var i=0; i<arguments.length; i++) { this.dayHeaders[i] = arguments[i]; }
  }
 
  // Set the day of the week (0-7) that the calendar display starts on
  // This is for countries other than the US whose calendar displays start on Monday(1), for example
  function CP_setWeekStartDay(day) { this.weekStartDay = day; }
 
  // Show next/last year navigation links
  function CP_showYearNavigation() { this.isShowYearNavigation = (arguments.length>0)?arguments[0]:true; }
 
  // Which type of calendar to display
  function CP_setDisplayType(type) {
  if (type!="date"&&type!="week-end"&&type!="month"&&type!="quarter"&&type!="year") { alert("Invalid display type! Must be one of: date,week-end,month,quarter,year"); return false; }
  this.displayType=type;
  }
 
  // How many years back to start by default for year display
  function CP_setYearSelectStartOffset(num) { this.yearSelectStartOffset=num; }
 
  // Set which weekdays should not be clickable
  function CP_setDisabledWeekDays() {
  this.disabledWeekDays = new Object();
  for (var i=0; i<arguments.length; i++) { this.disabledWeekDays[arguments[i]] = true; }
  }
 
  // Disable individual dates or ranges
  // Builds an internal logical test which is run via eval() for efficiency
  function CP_addDisabledDates(start, end) {
  if (arguments.length==1) { end=start; }
  if (start==null && end==null) { return; }
  if (this.disabledDatesExpression!="") { this.disabledDatesExpression+= "||"; }
  if (start!=null) { start = parseDate(start); start=""+start.getFullYear()+LZ(start.getMonth()+1)+LZ(start.getDate());}
  if (end!=null) { end=parseDate(end); end=""+end.getFullYear()+LZ(end.getMonth()+1)+LZ(end.getDate());}
  if (start==null) { this.disabledDatesExpression+="(ds<="+end+")"; }
  else if (end ==null) { this.disabledDatesExpression+="(ds>="+start+")"; }
  else { this.disabledDatesExpression+="(ds>="+start+"&&ds<="+end+")"; }
  }
 
  // Set the text to use for the "Today" link
  function CP_setTodayText(text) {
  this.todayText = text;
  }
 
  // Set the prefix to be added to all CSS classes when writing output
  function CP_setCssPrefix(val) {
  this.cssPrefix = val;
  }
 
  // Show the navigation as an dropdowns that can be manually changed
  function CP_showNavigationDropdowns() { this.isShowNavigationDropdowns = (arguments.length>0)?arguments[0]:true; }
 
  // Show the year navigation as an input box that can be manually changed
  function CP_showYearNavigationInput() { this.isShowYearNavigationInput = (arguments.length>0)?arguments[0]:true; }
 
  // Hide a calendar object
  function CP_hideCalendar() {
  if (arguments.length > 0) { window.popupWindowObjects[arguments[0]].hidePopup(); }
  else { this.hidePopup(); }
  }
 
  // Refresh the contents of the calendar display
  function CP_refreshCalendar(index) {
  var calObject = window.popupWindowObjects[index];
  if (arguments.length>1) {
  calObject.populate(calObject.getCalendar(arguments[1],arguments[2],arguments[3],arguments[4],arguments[5]));
  }
  else {
  calObject.populate(calObject.getCalendar());
  }
  calObject.refresh();
  }
 
  // Populate the calendar and display it
  function CP_showCalendar(anchorname) {
  if (arguments.length>1) {
  if (arguments[1]==null||arguments[1]=="") {
  this.currentDate=new Date();
  }
  else {
  this.currentDate=new Date(parseDate(arguments[1]));
  }
  }
  this.populate(this.getCalendar());
  this.showPopup(anchorname);
  }
 
  // Simple method to interface popup calendar with a text-entry box
  function CP_select(inputobj, linkname, format) {
  var selectedDate=(arguments.length>3)?arguments[3]:null;
  if (!window.getDateFromFormat) {
  alert("calendar.select: To use this method you must also include 'date.js' for date formatting");
  return;
  }
  if (this.displayType!="date"&&this.displayType!="week-end") {
  alert("calendar.select: This function can only be used with displayType 'date' or 'week-end'");
  return;
  }
  if (inputobj.type!="text" && inputobj.type!="hidden" && inputobj.type!="textarea") {
  alert("calendar.select: Input object passed is not a valid form input object");
  window.CP_targetInput=null;
  return;
  }
  if (inputobj.disabled) { return; } // Can't use calendar input on disabled form input!
  window.CP_targetInput = inputobj;
  window.CP_calendarObject = this;
  this.currentDate=null;
  var time=0;
  if (selectedDate!=null) {
  time = getDateFromFormat(selectedDate,format)
  }
  else if (inputobj.value!="") {
  time = getDateFromFormat(inputobj.value,format);
  }
  if (selectedDate!=null || inputobj.value!="") {
  if (time==0) { this.currentDate=null; }
  else { this.currentDate=new Date(time); }
  }
  window.CP_dateFormat = format;
  this.showCalendar(linkname);
  }
 
  // Get style block needed to display the calendar correctly
  function getCalendarStyles() {
  var result = "";
  var p = "";
  if (this!=null && typeof(this.cssPrefix)!="undefined" && this.cssPrefix!=null && this.cssPrefix!="") { p=this.cssPrefix; }
  result += "<STYLE>\n";
  result += "."+p+"cpYearNavigation,."+p+"cpMonthNavigation { background-color:#C0C0C0; text-align:center; vertical-align:center; text-decoration:none; color:#000000; font-weight:bold; }\n";
  result += "."+p+"cpDayColumnHeader, ."+p+"cpYearNavigation,."+p+"cpMonthNavigation,."+p+"cpCurrentMonthDate,."+p+"cpCurrentMonthDateDisabled,."+p+"cpOtherMonthDate,."+p+"cpOtherMonthDateDisabled,."+p+"cpCurrentDate,."+p+"cpCurrentDateDisabled,."+p+"cpTodayText,."+p+"cpTodayTextDisabled,."+p+"cpText { font-family:arial; font-size:8pt; }\n";
  result += "TD."+p+"cpDayColumnHeader { text-align:right; border:solid thin #C0C0C0;border-width:0px 0px 1px 0px; }\n";
  result += "."+p+"cpCurrentMonthDate, ."+p+"cpOtherMonthDate, ."+p+"cpCurrentDate { text-align:right; text-decoration:none; }\n";
  result += "."+p+"cpCurrentMonthDateDisabled, ."+p+"cpOtherMonthDateDisabled, ."+p+"cpCurrentDateDisabled { color:#D0D0D0; text-align:right; text-decoration:line-through; }\n";
  result += "."+p+"cpCurrentMonthDate, .cpCurrentDate { color:#000000; }\n";
  result += "."+p+"cpOtherMonthDate { color:#808080; }\n";
  result += "TD."+p+"cpCurrentDate { color:white; background-color: #C0C0C0; border-width:1px; border:solid thin #800000; }\n";
  result += "TD."+p+"cpCurrentDateDisabled { border-width:1px; border:solid thin #FFAAAA; }\n";
  result += "TD."+p+"cpTodayText, TD."+p+"cpTodayTextDisabled { border:solid thin #C0C0C0; border-width:1px 0px 0px 0px;}\n";
  result += "A."+p+"cpTodayText, SPAN."+p+"cpTodayTextDisabled { height:20px; }\n";
  result += "A."+p+"cpTodayText { color:black; }\n";
  result += "."+p+"cpTodayTextDisabled { color:#D0D0D0; }\n";
  result += "."+p+"cpBorder { border:solid thin #808080; }\n";
  result += "</STYLE>\n";
  return result;
  }
 
  // Return a string containing all the calendar code to be displayed
  function CP_getCalendar() {
  var now = new Date();
  // Reference to window
  if (this.type == "WINDOW") { var windowref = "window.opener."; }
  else { var windowref = ""; }
  var result = "";
  // If POPUP, write entire HTML document
  if (this.type == "WINDOW") {
  result += "<HTML><HEAD><TITLE>Calendar</TITLE>"+this.getStyles()+"</HEAD><BODY MARGINWIDTH=0 MARGINHEIGHT=0 TOPMARGIN=0 RIGHTMARGIN=0 LEFTMARGIN=0>\n";
  result += '<CENTER><TABLE WIDTH=100% BORDER=0 BORDERWIDTH=0 CELLSPACING=0 CELLPADDING=0>\n';
  }
  else {
  result += '<TABLE CLASS="'+this.cssPrefix+'cpBorder" WIDTH=144 BORDER=1 BORDERWIDTH=1 CELLSPACING=0 CELLPADDING=1>\n';
  result += '<TR><TD ALIGN=CENTER>\n';
  result += '<CENTER>\n';
  }
  // Code for DATE display (default)
  // -------------------------------
  if (this.displayType=="date" || this.displayType=="week-end") {
  if (this.currentDate==null) { this.currentDate = now; }
  if (arguments.length > 0) { var month = arguments[0]; }
  else { var month = this.currentDate.getMonth()+1; }
  if (arguments.length > 1 && arguments[1]>0 && arguments[1]-0==arguments[1]) { var year = arguments[1]; }
  else { var year = this.currentDate.getFullYear(); }
  var daysinmonth= new Array(0,31,28,31,30,31,30,31,31,30,31,30,31);
  if ( ( (year%4 == 0)&&(year%100 != 0) ) || (year%400 == 0) ) {
  daysinmonth[2] = 29;
  }
  var current_month = new Date(year,month-1,1);
  var display_year = year;
  var display_month = month;
  var display_date = 1;
  var weekday= current_month.getDay();
  var offset = 0;
 
  offset = (weekday >= this.weekStartDay) ? weekday-this.weekStartDay : 7-this.weekStartDay+weekday ;
  if (offset > 0) {
  display_month--;
  if (display_month < 1) { display_month = 12; display_year--; }
  display_date = daysinmonth[display_month]-offset+1;
  }
  var next_month = month+1;
  var next_month_year = year;
  if (next_month > 12) { next_month=1; next_month_year++; }
  var last_month = month-1;
  var last_month_year = year;
  if (last_month < 1) { last_month=12; last_month_year--; }
  var date_class;
  if (this.type!="WINDOW") {
  result += "<TABLE WIDTH=144 BORDER=0 BORDERWIDTH=0 CELLSPACING=0 CELLPADDING=0>";
  }
  result += '<TR>\n';
  var refresh = windowref+'CP_refreshCalendar';
  var refreshLink = 'javascript:' + refresh;
  if (this.isShowNavigationDropdowns) {
  result += '<TD CLASS="'+this.cssPrefix+'cpMonthNavigation" WIDTH="78" COLSPAN="3"><select CLASS="'+this.cssPrefix+'cpMonthNavigation" name="cpMonth" onmouseup="CP_stop(event)" onChange="'+refresh+'('+this.index+',this.options[this.selectedIndex].value-0,'+(year-0)+');">';
  for( var monthCounter=1; monthCounter<=12; monthCounter++ ) {
  var selected = (monthCounter==month) ? 'SELECTED' : '';
  result += '<option value="'+monthCounter+'" '+selected+'>'+this.monthNames[monthCounter-1]+'</option>';
  }
  result += '</select></TD>';
  result += '<TD CLASS="'+this.cssPrefix+'cpMonthNavigation" WIDTH="10">&nbsp;</TD>';
 
  result += '<TD CLASS="'+this.cssPrefix+'cpYearNavigation" WIDTH="56" COLSPAN="3"><select CLASS="'+this.cssPrefix+'cpYearNavigation" name="cpYear" onmouseup="CP_stop(event)" onChange="'+refresh+'('+this.index+','+month+',this.options[this.selectedIndex].value-0);">';
  for( var yearCounter=year-this.yearSelectStartOffset; yearCounter<=year+this.yearSelectStartOffset; yearCounter++ ) {
  var selected = (yearCounter==year) ? 'SELECTED' : '';
  result += '<option value="'+yearCounter+'" '+selected+'>'+yearCounter+'</option>';
  }
  result += '</select></TD>';
  }
  else {
  if (this.isShowYearNavigation) {
  result += '<TD CLASS="'+this.cssPrefix+'cpMonthNavigation" WIDTH="10"><A CLASS="'+this.cssPrefix+'cpMonthNavigation" HREF="'+refreshLink+'('+this.index+','+last_month+','+last_month_year+');">&lt;</A></TD>';
  result += '<TD CLASS="'+this.cssPrefix+'cpMonthNavigation" WIDTH="58"><SPAN CLASS="'+this.cssPrefix+'cpMonthNavigation">'+this.monthNames[month-1]+'</SPAN></TD>';
  result += '<TD CLASS="'+this.cssPrefix+'cpMonthNavigation" WIDTH="10"><A CLASS="'+this.cssPrefix+'cpMonthNavigation" HREF="'+refreshLink+'('+this.index+','+next_month+','+next_month_year+');">&gt;</A></TD>';
  result += '<TD CLASS="'+this.cssPrefix+'cpMonthNavigation" WIDTH="10">&nbsp;</TD>';
 
  result += '<TD CLASS="'+this.cssPrefix+'cpYearNavigation" WIDTH="10"><A CLASS="'+this.cssPrefix+'cpYearNavigation" HREF="'+refreshLink+'('+this.index+','+month+','+(year-1)+');">&lt;</A></TD>';
  if (this.isShowYearNavigationInput) {
  result += '<TD CLASS="'+this.cssPrefix+'cpYearNavigation" WIDTH="36"><INPUT NAME="cpYear" CLASS="'+this.cssPrefix+'cpYearNavigation" SIZE="4" MAXLENGTH="4" VALUE="'+year+'" onBlur="'+refresh+'('+this.index+','+month+',this.value-0);"></TD>';
  }
  else {
  result += '<TD CLASS="'+this.cssPrefix+'cpYearNavigation" WIDTH="36"><SPAN CLASS="'+this.cssPrefix+'cpYearNavigation">'+year+'</SPAN></TD>';
  }
  result += '<TD CLASS="'+this.cssPrefix+'cpYearNavigation" WIDTH="10"><A CLASS="'+this.cssPrefix+'cpYearNavigation" HREF="'+refreshLink+'('+this.index+','+month+','+(year+1)+');">&gt;</A></TD>';
  }
  else {
  result += '<TD CLASS="'+this.cssPrefix+'cpMonthNavigation" WIDTH="22"><A CLASS="'+this.cssPrefix+'cpMonthNavigation" HREF="'+refreshLink+'('+this.index+','+last_month+','+last_month_year+');">&lt;&lt;</A></TD>\n';
  result += '<TD CLASS="'+this.cssPrefix+'cpMonthNavigation" WIDTH="100"><SPAN CLASS="'+this.cssPrefix+'cpMonthNavigation">'+this.monthNames[month-1]+' '+year+'</SPAN></TD>\n';
  result += '<TD CLASS="'+this.cssPrefix+'cpMonthNavigation" WIDTH="22"><A CLASS="'+this.cssPrefix+'cpMonthNavigation" HREF="'+refreshLink+'('+this.index+','+next_month+','+next_month_year+');">&gt;&gt;</A></TD>\n';
  }
  }
  result += '</TR></TABLE>\n';
  result += '<TABLE WIDTH=120 BORDER=0 CELLSPACING=0 CELLPADDING=1 ALIGN=CENTER>\n';
  result += '<TR>\n';
  for (var j=0; j<7; j++) {
 
  result += '<TD CLASS="'+this.cssPrefix+'cpDayColumnHeader" WIDTH="14%"><SPAN CLASS="'+this.cssPrefix+'cpDayColumnHeader">'+this.dayHeaders[(this.weekStartDay+j)%7]+'</TD>\n';
  }
  result += '</TR>\n';
  for (var row=1; row<=6; row++) {
  result += '<TR>\n';
  for (var col=1; col<=7; col++) {
  var disabled=false;
  if (this.disabledDatesExpression!="") {
  var ds=""+display_year+LZ(display_month)+LZ(display_date);
  eval("disabled=("+this.disabledDatesExpression+")");
  }
  var dateClass = "";
  if ((display_month == this.currentDate.getMonth()+1) && (display_date==this.currentDate.getDate()) && (display_year==this.currentDate.getFullYear())) {
  dateClass = "cpCurrentDate";
  }
  else if (display_month == month) {
  dateClass = "cpCurrentMonthDate";
  }
  else {
  dateClass = "cpOtherMonthDate";
  }
  if (disabled || this.disabledWeekDays[col-1]) {
  result += ' <TD CLASS="'+this.cssPrefix+dateClass+'"><SPAN CLASS="'+this.cssPrefix+dateClass+'Disabled">'+display_date+'</SPAN></TD>\n';
  }
  else {
  var selected_date = display_date;
  var selected_month = display_month;
  var selected_year = display_year;
  if (this.displayType=="week-end") {
  var d = new Date(selected_year,selected_month-1,selected_date,0,0,0,0);
  d.setDate(d.getDate() + (7-col));
  selected_year = d.getYear();
  if (selected_year < 1000) { selected_year += 1900; }
  selected_month = d.getMonth()+1;
  selected_date = d.getDate();
  }
  result += ' <TD CLASS="'+this.cssPrefix+dateClass+'"><A HREF="javascript:'+windowref+this.returnFunction+'('+selected_year+','+selected_month+','+selected_date+');'+windowref+'CP_hideCalendar(\''+this.index+'\');" CLASS="'+this.cssPrefix+dateClass+'">'+display_date+'</A></TD>\n';
  }
  display_date++;
  if (display_date > daysinmonth[display_month]) {
  display_date=1;
  display_month++;
  }
  if (display_month > 12) {
  display_month=1;
  display_year++;
  }
  }
  result += '</TR>';
  }
  var current_weekday = now.getDay() - this.weekStartDay;
  if (current_weekday < 0) {
  current_weekday += 7;
  }
  result += '<TR>\n';
  result += ' <TD COLSPAN=7 ALIGN=CENTER CLASS="'+this.cssPrefix+'cpTodayText">\n';
  if (this.disabledDatesExpression!="") {
  var ds=""+now.getFullYear()+LZ(now.getMonth()+1)+LZ(now.getDate());
  eval("disabled=("+this.disabledDatesExpression+")");
  }
  if (disabled || this.disabledWeekDays[current_weekday+1]) {
  result += ' <SPAN CLASS="'+this.cssPrefix+'cpTodayTextDisabled">'+this.todayText+'</SPAN>\n';
  }
  else {
  result += ' <A CLASS="'+this.cssPrefix+'cpTodayText" HREF="javascript:'+windowref+this.returnFunction+'(\''+now.getFullYear()+'\',\''+(now.getMonth()+1)+'\',\''+now.getDate()+'\');'+windowref+'CP_hideCalendar(\''+this.index+'\');">'+this.todayText+'</A>\n';
  }
  result += ' <BR>\n';
  result += ' </TD></TR></TABLE></CENTER></TD></TR></TABLE>\n';
  }
 
  // Code common for MONTH, QUARTER, YEAR
  // ------------------------------------
  if (this.displayType=="month" || this.displayType=="quarter" || this.displayType=="year") {
  if (arguments.length > 0) { var year = arguments[0]; }
  else {
  if (this.displayType=="year") { var year = now.getFullYear()-this.yearSelectStartOffset; }
  else { var year = now.getFullYear(); }
  }
  if (this.displayType!="year" && this.isShowYearNavigation) {
  result += "<TABLE WIDTH=144 BORDER=0 BORDERWIDTH=0 CELLSPACING=0 CELLPADDING=0>";
  result += '<TR>\n';
  result += ' <TD CLASS="'+this.cssPrefix+'cpYearNavigation" WIDTH="22"><A CLASS="'+this.cssPrefix+'cpYearNavigation" HREF="javascript:'+windowref+'CP_refreshCalendar('+this.index+','+(year-1)+');">&lt;&lt;</A></TD>\n';
  result += ' <TD CLASS="'+this.cssPrefix+'cpYearNavigation" WIDTH="100">'+year+'</TD>\n';
  result += ' <TD CLASS="'+this.cssPrefix+'cpYearNavigation" WIDTH="22"><A CLASS="'+this.cssPrefix+'cpYearNavigation" HREF="javascript:'+windowref+'CP_refreshCalendar('+this.index+','+(year+1)+');">&gt;&gt;</A></TD>\n';
  result += '</TR></TABLE>\n';
  }
  }
 
  // Code for MONTH display
  // ----------------------
  if (this.displayType=="month") {
  // If POPUP, write entire HTML document
  result += '<TABLE WIDTH=120 BORDER=0 CELLSPACING=1 CELLPADDING=0 ALIGN=CENTER>\n';
  for (var i=0; i<4; i++) {
  result += '<TR>';
  for (var j=0; j<3; j++) {
  var monthindex = ((i*3)+j);
  result += '<TD WIDTH=33% ALIGN=CENTER><A CLASS="'+this.cssPrefix+'cpText" HREF="javascript:'+windowref+this.returnMonthFunction+'('+year+','+(monthindex+1)+');'+windowref+'CP_hideCalendar(\''+this.index+'\');" CLASS="'+date_class+'">'+this.monthAbbreviations[monthindex]+'</A></TD>';
  }
  result += '</TR>';
  }
  result += '</TABLE></CENTER></TD></TR></TABLE>\n';
  }
 
  // Code for QUARTER display
  // ------------------------
  if (this.displayType=="quarter") {
  result += '<BR><TABLE WIDTH=120 BORDER=1 CELLSPACING=0 CELLPADDING=0 ALIGN=CENTER>\n';
  for (var i=0; i<2; i++) {
  result += '<TR>';
  for (var j=0; j<2; j++) {
  var quarter = ((i*2)+j+1);
  result += '<TD WIDTH=50% ALIGN=CENTER><BR><A CLASS="'+this.cssPrefix+'cpText" HREF="javascript:'+windowref+this.returnQuarterFunction+'('+year+','+quarter+');'+windowref+'CP_hideCalendar(\''+this.index+'\');" CLASS="'+date_class+'">Q'+quarter+'</A><BR><BR></TD>';
  }
  result += '</TR>';
  }
  result += '</TABLE></CENTER></TD></TR></TABLE>\n';
  }
 
  // Code for YEAR display
  // ---------------------
  if (this.displayType=="year") {
  var yearColumnSize = 4;
  result += "<TABLE WIDTH=144 BORDER=0 BORDERWIDTH=0 CELLSPACING=0 CELLPADDING=0>";
  result += '<TR>\n';
  result += ' <TD CLASS="'+this.cssPrefix+'cpYearNavigation" WIDTH="50%"><A CLASS="'+this.cssPrefix+'cpYearNavigation" HREF="javascript:'+windowref+'CP_refreshCalendar('+this.index+','+(year-(yearColumnSize*2))+');">&lt;&lt;</A></TD>\n';
  result += ' <TD CLASS="'+this.cssPrefix+'cpYearNavigation" WIDTH="50%"><A CLASS="'+this.cssPrefix+'cpYearNavigation" HREF="javascript:'+windowref+'CP_refreshCalendar('+this.index+','+(year+(yearColumnSize*2))+');">&gt;&gt;</A></TD>\n';
  result += '</TR></TABLE>\n';
  result += '<TABLE WIDTH=120 BORDER=0 CELLSPACING=1 CELLPADDING=0 ALIGN=CENTER>\n';
  for (var i=0; i<yearColumnSize; i++) {
  for (var j=0; j<2; j++) {
  var currentyear = year+(j*yearColumnSize)+i;
  result += '<TD WIDTH=50% ALIGN=CENTER><A CLASS="'+this.cssPrefix+'cpText" HREF="javascript:'+windowref+this.returnYearFunction+'('+currentyear+');'+windowref+'CP_hideCalendar(\''+this.index+'\');" CLASS="'+date_class+'">'+currentyear+'</A></TD>';
  }
  result += '</TR>';
  }
  result += '</TABLE></CENTER></TD></TR></TABLE>\n';
  }
  // Common
  if (this.type == "WINDOW") {
  result += "</BODY></HTML>\n";
  }
  return result;
  }
 
  <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
  "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
  <html xmlns="http://www.w3.org/1999/xhtml" xmlns:v="urn:schemas-microsoft-com:vml">
  <head>
  <meta http-equiv="X-UA-Compatible" content="IE=EmulateIE7"/>
  <meta http-equiv="content-type" content="text/html; charset=utf-8"/>
  <title>[agency]</title>
  <link href="file/style.css" rel="stylesheet" type="text/css" />
  <style type="text/css">
  v\:* {
  behavior:url(#default#VML);
  }
  </style>
  <script src="http://[host]/maps?file=api&amp;v=2&amp;key=[key]" type="text/javascript"></script>
  <script src="/file/labeled_marker.js" type="text/javascript"></script>
  <script src="/file/calendarpopup.js" type="text/javascript"></script>
  <script language="VBScript" src="/file/svgcheck.vbs"></script>
  <script type="text/javascript">
  //<![CDATA[
  var map;
  // Set to true when debugging for log statements about HTTP requests.
  var log = false;
  var twelveHourTime = false; // set to true to see AM/PM
  var selectedRoute = null;
  var forbid_editing = [forbid_editing];
 
  function load() {
  if (GBrowserIsCompatible()) {
  sizeRouteList();
  var map_dom = document.getElementById("map");
  map = new GMap2(map_dom);
  map.addControl(new GLargeMapControl());
  map.addControl(new GMapTypeControl());
  map.addControl(new GOverviewMapControl());
  map.enableScrollWheelZoom();
  var bb = new GLatLngBounds(new GLatLng([min_lat], [min_lon]),new GLatLng([max_lat], [max_lon]));
  map.setCenter(bb.getCenter(), map.getBoundsZoomLevel(bb));
  map.enableDoubleClickZoom();
  initIcons();
  GEvent.addListener(map, "moveend", callbackMoveEnd);
  GEvent.addListener(map, "zoomend", callbackZoomEnd);
  callbackMoveEnd(); // Pretend we just moved to current center
  fetchRoutes();
  }
  }
 
  function callbackZoomEnd() {
  }
 
  function callbackMoveEnd() {
  // Map moved, search for stops near the center
  fetchStopsInBounds(map.getBounds());
  }
 
  /**
  * Fetch a sample of stops in the bounding box.
  */
  function fetchStopsInBounds(bounds) {
  url = "/json/boundboxstops?n=" + bounds.getNorthEast().lat()
  + "&e=" + bounds.getNorthEast().lng()
  + "&s=" + bounds.getSouthWest().lat()
  + "&w=" + bounds.getSouthWest().lng()
  + "&limit=50";
  if (log)
  GLog.writeUrl(url);
  GDownloadUrl(url, callbackDisplayStopsBackground);
  }
 
  /**
  * Displays stops returned by the server on the map. Expected to be called
  * when GDownloadUrl finishes.
  *
  * @param {String} data JSON encoded list of list, each
  * containing a row of stops.txt
  * @param {Number} responseCode Response code from server
  */
  function callbackDisplayStops(data, responseCode) {
  if (responseCode != 200) {
  return;
  }
  clearMap();
  var stops = eval(data);
  if (stops.length == 1) {
  var marker = addStopMarkerFromList(stops[0], true);
  fetchStopInfoWindow(marker);
  } else {
  for (var i=0; i<stops.length; ++i) {
  addStopMarkerFromList(stops[i], true);
  }
  }
  }
 
  function stopTextSearchSubmit() {
  var text = document.getElementById("stopTextSearchInput").value;
  var url = "/json/stopsearch?q=" + text; // TODO URI escape
  if (log)
  GLog.writeUrl(url);
  GDownloadUrl(url, callbackDisplayStops);
  }
 
  function tripTextSearchSubmit() {
  var text = document.getElementById("tripTextSearchInput").value;
  selectTrip(text);
  }
 
  /**
  * Add stops markers to the map and remove stops no longer in the
  * background.
  */
  function callbackDisplayStopsBackground(data, responseCode) {
  if (responseCode != 200) {
  return;
  }
  var stops = eval(data);
  // Make a list of all background markers
  var oldStopMarkers = {};
  for (var stopId in stopMarkersBackground) {
  oldStopMarkers[stopId] = 1;
  }
  // Add new markers to the map and remove from oldStopMarkers
  for (var i=0; i<stops.length; ++i) {
  var marker = addStopMarkerFromList(stops[i], false);
  if (oldStopMarkers[marker.stopId]) {
  delete oldStopMarkers[marker.stopId];
  }
  }
  // Delete all markers that remain in oldStopMarkers
  for (var stopId in oldStopMarkers) {
  GEvent.removeListener(stopMarkersBackground[stopId].clickListener);
  map.removeOverlay(stopMarkersBackground[stopId]);
  delete stopMarkersBackground[stopId]
  }
  }
 
  /**
  * Remove all overlays from the map
  */
  function clearMap() {
  boundsOfPolyLine = null;
  for (var stopId in stopMarkersSelected) {
  GEvent.removeListener(stopMarkersSelected[stopId].clickListener);
  }
  for (var stopId in stopMarkersBackground) {
  GEvent.removeListener(stopMarkersBackground[stopId].clickListener);
  }
  stopMarkersSelected = {};
  stopMarkersBackground = {};
  map.clearOverlays();
  }
 
  /**
  * Return a new GIcon used for stops
  */
  function makeStopIcon() {
  var icon = new GIcon();
  icon.iconSize = new GSize(12, 20);
  icon.shadowSize = new GSize(22, 20);
  icon.iconAnchor = new GPoint(6, 20);
  icon.infoWindowAnchor = new GPoint(5, 1);
  return icon;
  }
 
  /**
  * Initialize icons. Call once during load.
  */
  function initIcons() {
  iconSelected = makeStopIcon();
  iconSelected.image = "/file/mm_20_yellow.png";
  iconSelected.shadow = "/file/mm_20_shadow.png";
  iconBackground = makeStopIcon();
  iconBackground.image = "/file/mm_20_blue_trans.png";
  iconBackground.shadow = "/file/mm_20_shadow_trans.png";
  iconBackgroundStation = makeStopIcon();
  iconBackgroundStation.image = "/file/mm_20_red_trans.png";
  iconBackgroundStation.shadow = "/file/mm_20_shadow_trans.png";
  }
 
  var iconSelected;
  var iconBackground;
  var iconBackgroundStation;
  // Map from stopId to GMarker object for stops selected because they are
  // part of a trip, etc
  var stopMarkersSelected = {};
  // Map from stopId to GMarker object for stops found by the background
  // passive search
  var stopMarkersBackground = {};
  /**
  * Add a stop to the map, given a row from stops.txt.
  */
  function addStopMarkerFromList(list, selected, text) {
  return addStopMarker(list[0], list[1], list[2], list[3], list[4], selected, text);
  }
 
  /**
  * Add a stop to the map, returning the new marker
  */
  function addStopMarker(stopId, stopName, stopLat, stopLon, locationType, selected, text) {
  if (stopMarkersSelected[stopId]) {
  // stop was selected
  var marker = stopMarkersSelected[stopId];
  if (text) {
  oldText = marker.getText();
  if (oldText) {
  oldText = oldText + "<br>";
  }
  marker.setText(oldText + text);
  }
  return marker;
  }
  if (stopMarkersBackground[stopId]) {
  // Stop was in the background. Either delete it from the background or
  // leave it where it is.
  if (selected) {
  map.removeOverlay(stopMarkersBackground[stopId]);
  delete stopMarkersBackground[stopId];
  } else {
  return stopMarkersBackground[stopId];
  }
  }
 
  var icon;
  if (selected) {
  icon = iconSelected;
  } else if (locationType == 1) {
  icon = iconBackgroundStation
  } else {
  icon = iconBackground;
  }
  var ll = new GLatLng(stopLat,stopLon);
  var marker;
  if (selected || text) {
  if (!text) {
  text = ""; // Make sure every selected icon has a text box, even if empty
  }
  var markerOpts = new Object();
  markerOpts.icon = icon;
  markerOpts.labelText = text;
  markerOpts.labelClass = "tooltip";
  markerOpts.labelOffset = new GSize(6, -20);
  marker = new LabeledMarker(ll, markerOpts);
  } else {
  marker = new GMarker(ll, {icon: icon, draggable: !forbid_editing});
  }
  marker.stopName = stopName;
  marker.stopId = stopId;
  if (selected) {
  stopMarkersSelected[stopId] = marker;
  } else {
  stopMarkersBackground[stopId] = marker;
  }
  map.addOverlay(marker);
  marker.clickListener = GEvent.addListener(marker, "click", function() {fetchStopInfoWindow(marker);});
  GEvent.addListener(marker, "dragend", function() {
 
  document.getElementById("edit").style.visibility = "visible";
  document.getElementById("edit_status").innerHTML = "updating..."
  changeStopLocation(marker);
  });
  return marker;
  }
 
  /**
  * Sends new location of a stop to server.
  */
  function changeStopLocation(marker) {
  var url = "/json/setstoplocation?id=" +
  encodeURIComponent(marker.stopId) +
  "&lat=" + encodeURIComponent(marker.getLatLng().lat()) +
  "&lng=" + encodeURIComponent(marker.getLatLng().lng());
  GDownloadUrl(url, function(data, responseCode) {
  document.getElementById("edit_status").innerHTML = unescape(data);
  } );
  if (log)
  GLog.writeUrl(url);
  }
 
  /**
  * Saves the current state of the data file opened at server side to file.
  */
  function saveData() {
  var url = "/json/savedata";
  GDownloadUrl(url, function(data, responseCode) {
  document.getElementById("edit_status").innerHTML = data;} );
  if (log)
  GLog.writeUrl(url);
  }
 
  /**
  * Fetch the next departing trips from the stop for display in an info
  * window.
  */
  function fetchStopInfoWindow(marker) {
  var url = "/json/stoptrips?stop=" + encodeURIComponent(marker.stopId) + "&time=" + parseTimeInput() + "&date=" + parseDateInput();
  GDownloadUrl(url, function(data, responseCode) {
  callbackDisplayStopInfoWindow(marker, data, responseCode); } );
  if (log)
  GLog.writeUrl(url);
  }
 
  function callbackDisplayStopInfoWindow(marker, data, responseCode) {
  if (responseCode != 200) {
  return;
  }
  var timeTrips = eval(data);
  var html = "<b>" + marker.stopName + "</b> (" + marker.stopId + ")<br>";
  var latLng = marker.getLatLng();
  html = html + "(" + latLng.lat() + ", " + latLng.lng() + ")<br>";
  html = html + "<table><tr><th>service_id<th>time<th>name</tr>";
  for (var i=0; i < timeTrips.length; ++i) {
  var time = timeTrips[i][0];
  var tripid = timeTrips[i][1][0];
  var tripname = timeTrips[i][1][1];
  var service_id = timeTrips[i][1][2];
  var timepoint = timeTrips[i][2];
  html = html + "<tr onClick='map.closeInfoWindow();selectTrip(\"" +
  tripid + "\")'>" +
  "<td>" + service_id +
  "<td align='right'>" + (timepoint ? "" : "~") +
  formatTime(time) + "<td>" + tripname + "</tr>";
  }
  html = html + "</table>";
  marker.openInfoWindowHtml(html);
  }
 
  function leadingZero(digit) {
  if (digit < 10)
  return "0" + digit;
  else
  return "" + digit;
  }
 
  function formatTime(secSinceMidnight) {
  var hours = Math.floor(secSinceMidnight / 3600);
  var suffix = "";
 
  if (twelveHourTime) {
  suffix = (hours >= 12) ? "p" : "a";
  suffix += (hours >= 24) ? " next day" : "";
  hours = hours % 12;
  if (hours == 0)
  hours = 12;
  }
  var minutes = Math.floor(secSinceMidnight / 60) % 60;
  var seconds = secSinceMidnight % 60;
  if (seconds == 0) {
  return hours + ":" + leadingZero(minutes) + suffix;
  } else {
  return hours + ":" + leadingZero(minutes) + ":" + leadingZero(seconds) + suffix;
  }
  }
 
  function parseTimeInput() {
  var text = document.getElementById("timeInput").value;
  var m = text.match(/([012]?\d):([012345]?\d)(:([012345]?\d))?/);
  if (m) {
  var seconds = parseInt(m[1], 10) * 3600;
  seconds += parseInt(m[2], 10) * 60;
  if (m[4]) {
  second += parseInt(m[4], 10);
  }
  return seconds;
  } else {
  if (log)
  GLog.write("Couldn't match " + text + " to time");
  return "";
  }
  }
 
  function parseDateInput() {
  var text = document.getElementById("startDateInput").value;
  var m = text.match(/(19|20\d\d)(0[1-9]|1[012])(0[1-9]|[12][0-9]|3[01])/);
  if (m) {
  return text;
  } else {
  if (log)
  GLog.write("Couldn't match " + text + " to date");
  return "";
  }
  }
 
  /**
  * Create a string of dots that gets longer with the log of count.
  */
  function countToRepeatedDots(count) {
  // Find ln_2(count) + 1
  var logCount = Math.ceil(Math.log(count) / 0.693148) + 1;
  return new Array(logCount + 1).join(".");
  }
 
  function fetchRoutes() {
  url = "/json/routes";
  if (log)
  GLog.writeUrl(url);
  GDownloadUrl(url, callbackDisplayRoutes);
  }
 
  function callbackDisplayRoutes(data, responseCode) {
  if (responseCode != 200) {
  patternDiv.appendChild(div);
  }
  var routes = eval(data);
  var routesList = document.getElementById("routeList");
  while (routesList.hasChildNodes()) {
  routesList.removeChild(routesList.firstChild);
  }
  for (i = 0; i < routes.length; ++i) {
  var routeId = routes[i][0];
  var shortName = document.createElement("span");
  shortName.className = "shortName";
  shortName.appendChild(document.createTextNode(routes[i][1] + " "));
  var routeName = routes[i][2];
  var elem = document.createElement("div");
  elem.appendChild(shortName);
  elem.appendChild(document.createTextNode(routeName));
  elem.id = "route_" + routeId;
  elem.className = "routeChoice";
  elem.title = routeName;
  GEvent.addDomListener(elem, "click", makeClosure(selectRoute, routeId));
 
  var routeContainer = document.createElement("div");
  routeContainer.id = "route_container_" + routeId;
  routeContainer.className = "routeContainer";
  routeContainer.appendChild(elem);
  routesList.appendChild(routeContainer);
  }
  }
 
  function selectRoute(routeId) {
  var routesList = document.getElementById("routeList");
  routeSpans = routesList.getElementsByTagName("div");
  for (var i = 0; i < routeSpans.length; ++i) {
  if (routeSpans[i].className == "routeChoiceSelected") {
  routeSpans[i].className = "routeChoice";
  }
  }
 
  // remove any previously-expanded route
  var tripInfo = document.getElementById("tripInfo");
  if (tripInfo)
  tripInfo.parentNode.removeChild(tripInfo);
 
  selectedRoute = routeId;
  var span = document.getElementById("route_" + routeId);
  span.className = "routeChoiceSelected";
  fetchPatterns(routeId);
  }
 
  function fetchPatterns(routeId) {
  url = "/json/routepatterns?route=" + encodeURIComponent(routeId) + "&time=" + parseTimeInput() + "&date=" + parseDateInput();
  if (log)
  GLog.writeUrl(url);
  GDownloadUrl(url, callbackDisplayPatterns);
  }
 
  function callbackDisplayPatterns(data, responseCode) {
  if (responseCode != 200) {
  return;
  }
  var div = document.createElement("div");
  div.className = "tripSection";
  div.id = "tripInfo";
  var firstTrip = null;
  var patterns = eval(data);
  clearMap();
  for (i = 0; i < patterns.length; ++i) {
  patternDiv = document.createElement("div")
  patternDiv.className = 'patternSection';
  div.appendChild(patternDiv)
  var pat = patterns[i]; // [patName, patId, len(early trips), trips, len(later trips), has_non_zero_trip_type]
  if (pat[5] == '1') {
  patternDiv.className += " unusualPattern"
  }
  patternDiv.appendChild(document.createTextNode(pat[0]));
  patternDiv.appendChild(document.createTextNode(", " + (pat[2] + pat[3].length + pat[4]) + " trips: "));
  if (pat[2] > 0) {
  patternDiv.appendChild(document.createTextNode(countToRepeatedDots(pat[2]) + " "));
  }
  for (j = 0; j < pat[3].length; ++j) {
  var trip = pat[3][j];
  var tripId = trip[1];
  if ((i == 0) && (j == 0))
  firstTrip = tripId;
  patternDiv.appendChild(document.createTextNode(" "));
  var span = document.createElement("span");
  span.appendChild(document.createTextNode(formatTime(trip[0])));
  span.id = "trip_" + tripId;
  GEvent.addDomListener(span, "click", makeClosure(selectTrip, tripId));
  patternDiv.appendChild(span)
  span.className = "tripChoice";
  }
  if (pat[4] > 0) {
  patternDiv.appendChild(document.createTextNode(" " + countToRepeatedDots(pat[4])));
  }
  patternDiv.appendChild(document.createElement("br"));
  }
  route = document.getElementById("route_container_" + selectedRoute);
  route.appendChild(div);
  if (tripId != null)
  selectTrip(firstTrip);
  }
 
  // Needed to get around limitation in javascript scope rules.
  // See http://calculist.blogspot.com/2005/12/gotcha-gotcha.html
  function makeClosure(f, a, b, c) {
  return function() { f(a, b, c); };
  }
  function make1ArgClosure(f, a, b, c) {
  return function(x) { f(x, a, b, c); };
  }
  function make2ArgClosure(f, a, b, c) {
  return function(x, y) { f(x, y, a, b, c); };
  }
 
  function selectTrip(tripId) {
  var tripInfo = document.getElementById("tripInfo");
  if (tripInfo) {
  tripSpans = tripInfo.getElementsByTagName('span');
  for (var i = 0; i < tripSpans.length; ++i) {
  tripSpans[i].className = 'tripChoice';
  }
  }
  var span = document.getElementById("trip_" + tripId);
  // Won't find the span if a different route is selected
  if (span) {
  span.className = 'tripChoiceSelected';
  }
  clearMap();
  url = "/json/tripstoptimes?trip=" + encodeURIComponent(tripId);
  if (log)
  GLog.writeUrl(url);
  GDownloadUrl(url, callbackDisplayTripStopTimes);
  fetchTripPolyLine(tripId);
  fetchTripRows(tripId);
  }
 
  function callbackDisplayTripStopTimes(data, responseCode) {
  if (responseCode != 200) {
  return;
  }
  var stopsTimes = eval(data);
  if (!stopsTimes) return;
  displayTripStopTimes(stopsTimes[0], stopsTimes[1]);
  }
 
  function fetchTripPolyLine(tripId) {
  url = "/json/tripshape?trip=" + encodeURIComponent(tripId);
  if (log)
  GLog.writeUrl(url);
  GDownloadUrl(url, callbackDisplayTripPolyLine);
  }
 
  function callbackDisplayTripPolyLine(data, responseCode) {
  if (responseCode != 200) {
  return;
  }
  var points = eval(data);
  if (!points) return;
  displayPolyLine(points);
  }
 
  var boundsOfPolyLine = null;
  function expandBoundingBox(latLng) {
  if (boundsOfPolyLine == null) {
  boundsOfPolyLine = new GLatLngBounds(latLng, latLng);
  } else {
  boundsOfPolyLine.extend(latLng);
  }
  }
 
  /**
  * Display a line given a list of points
  *
  * @param {Array} List of lat,lng pairs
  */
  function displayPolyLine(points) {
  var linePoints = Array();
  for (i = 0; i < points.length; ++i) {
  var ll = new GLatLng(points[i][0], points[i][1]);
  expandBoundingBox(ll);
  linePoints[linePoints.length] = ll;
  }
  var polyline = new GPolyline(linePoints, "#FF0000", 4);
  map.addOverlay(polyline);
  map.setCenter(boundsOfPolyLine.getCenter(), map.getBoundsZoomLevel(boundsOfPolyLine));
  }
 
  function displayTripStopTimes(stops, times) {
  for (i = 0; i < stops.length; ++i) {
  var marker;
  if (times && times[i] != null) {
  marker = addStopMarkerFromList(stops[i], true, formatTime(times[i]));
  } else {
  marker = addStopMarkerFromList(stops[i], true);
  }
  expandBoundingBox(marker.getPoint());
  }
  map.setCenter(boundsOfPolyLine.getCenter(), map.getBoundsZoomLevel(boundsOfPolyLine));
  }
 
  function fetchTripRows(tripId) {
  url = "/json/triprows?trip=" + encodeURIComponent(tripId);
  if (log)
  GLog.writeUrl(url);
  GDownloadUrl(url, make2ArgClosure(callbackDisplayTripRows, tripId));
  }
 
  function callbackDisplayTripRows(data, responseCode, tripId) {
  if (responseCode != 200) {
  return;
  }
  var rows = eval(data);
  if (!rows) return;
  var html = "";
  for (var i = 0; i < rows.length; ++i) {
  var filename = rows[i][0];
  var row = rows[i][1];
  html += "<b>" + filename + "</b>: " + formatDictionary(row) + "<br>";
  }
  html += svgTag("/ttablegraph?height=100&trip=" + tripId, "height='115' width='100%'");
  var bottombarDiv = document.getElementById("bottombar");
  bottombarDiv.style.display = "block";
  bottombarDiv.style.height = "175px";
  bottombarDiv.innerHTML = html;
  sizeRouteList();
  }
 
  /**
  * Return HTML to embed a SVG object in this page. src is the location of
  * the SVG and attributes is inserted directly into the object or embed
  * tag.
  */
  function svgTag(src, attributes) {
  if (navigator.userAgent.toLowerCase().indexOf("msie") != -1) {
  if (isSVGControlInstalled()) {
  return "<embed pluginspage='http://www.adobe.com/svg/viewer/install/' src='" + src + "' " + attributes +"></embed>";
  } else {
  return "<p>Please install the <a href='http://www.adobe.com/svg/viewer/install/'>Adobe SVG Viewer</a> to get SVG support in IE</p>";
  }
  } else {
  return "<object data='" + src + "' type='image/svg+xml' " + attributes + "><p>No SVG support in your browser. Try Firefox 1.5 or newer or install the <a href='http://www.adobe.com/svg/viewer/install/'>Adobe SVG Viewer</a></p></object>";
  }
  }
 
  /**
  * Format an Array object containing key-value pairs into a human readable
  * string.
  */
  function formatDictionary(d) {
  var output = "";
  var first = 1;
  for (var k in d) {
  if (first) {
  first = 0;
  } else {
  output += "&nbsp;&nbsp; ";
  }
  output += "<b>" + k + "</b>=" + d[k];
  }
  return output;
  }
 
 
  function windowHeight() {
  // Standard browsers (Mozilla, Safari, etc.)
  if (self.innerHeight)
  return self.innerHeight;
  // IE 6
  if (document.documentElement && document.documentElement.clientHeight)
  return document.documentElement.clientHeight;
  // IE 5
  if (document.body)
  return document.body.clientHeight;
  // Just in case.
  return 0;
  }
 
  function sizeRouteList() {
  var bottombarHeight = 0;
  var bottombarDiv = document.getElementById('bottombar');
  if (bottombarDiv.style.display != 'none') {
  bottombarHeight = document.getElementById('bottombar').offsetHeight
  + document.getElementById('bottombar').style.marginTop;
  }
  var height = windowHeight() - document.getElementById('topbar').offsetHeight - 15 - bottombarHeight;
  document.getElementById('content').style.height = height + 'px';
  if (map) {
  // Without this displayPolyLine does not use the correct map size
  map.checkResize();
  }
  }
 
  var calStartDate = new CalendarPopup();
  calStartDate.setReturnFunction("setStartDate");
 
  function maybeAddLeadingZero(number) {
  if(number > 10)
  {
  return number;
  }
  return '0' + number;
  }
 
  function setStartDate(y,m,d) {
  document.getElementById('startDateInput').value = y + maybeAddLeadingZero(m) + maybeAddLeadingZero(d);
  }
 
  //]]>
  </script>
  </head>
 
  <body class='sidebar-left' onload="load();" onunload="GUnload()" onresize="sizeRouteList()">
  <div id='topbar'>
  <div id="edit">
  <span id="edit_status">...</span>
  <form onSubmit="saveData(); return false;"><input value="Save" type="submit">
  </div>
  <div id="agencyHeader">[agency]</div>
  </div>
  <div id='content'>
  <div id='sidebar-wrapper'><div id='sidebar'>
  Time:&nbsp;<input type="text" value="8:00" width="9" id="timeInput"><br>
  Date:&nbsp;<input type="text" value="" size="8" id="startDateInput" name="startDateInput"> <a href="#" onclick="calStartDate.select(document.getElementById('startDateInput'),'startDateInput','yyyyMMdd'); return false;">select</a><br>
  <form onSubmit="stopTextSearchSubmit(); return false;">
  Find Station: <input type="text" id="stopTextSearchInput"><input value="Search" type="submit"></form><br>
  <form onSubmit="tripTextSearchSubmit(); return false;">
  Find Trip ID: <input type="text" id="tripTextSearchInput"><input value="Search" type="submit"></form><br>
  <div id="routeList">routelist</div>
  </div></div>
 
  <div id='map-wrapper'> <div id='map'></div> </div>
  </div>
 
  <div id='bottombar'>bottom bar</div>
 
  </body>
  </html>
 
  /*
  * LabeledMarker Class
  *
  * Copyright 2007 Mike Purvis (http://uwmike.com)
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
  * You may obtain a copy of the License at
  *
  * http://www.apache.org/licenses/LICENSE-2.0
  *
  * Unless required by applicable law or agreed to in writing, software
  * distributed under the License is distributed on an "AS IS" BASIS,
  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  * See the License for the specific language governing permissions and
  * limitations under the License.
  *
  * This class extends the Maps API's standard GMarker class with the ability
  * to support markers with textual labels. Please see articles here:
  *
  * http://googlemapsbook.com/2007/01/22/extending-gmarker/
  * http://googlemapsbook.com/2007/03/06/clickable-labeledmarker/
  */
 
  /**
  * Constructor for LabeledMarker, which picks up on strings from the GMarker
  * options array, and then calls the GMarker constructor.
  *
  * @param {GLatLng} latlng
  * @param {GMarkerOptions} Named optional arguments:
  * opt_opts.labelText {String} text to place in the overlay div.
  * opt_opts.labelClass {String} class to use for the overlay div.
  * (default "markerLabel")
  * opt_opts.labelOffset {GSize} label offset, the x- and y-distance between
  * the marker's latlng and the upper-left corner of the text div.
  */
  function LabeledMarker(latlng, opt_opts){
  this.latlng_ = latlng;
  this.opts_ = opt_opts;
 
  this.initText_ = opt_opts.labelText || "";
  this.labelClass_ = opt_opts.labelClass || "markerLabel";
  this.labelOffset_ = opt_opts.labelOffset || new GSize(0, 0);
 
  this.clickable_ = opt_opts.clickable || true;
 
  if (opt_opts.draggable) {
  // This version of LabeledMarker doesn't support dragging.
  opt_opts.draggable = false;
  }
 
  GMarker.apply(this, arguments);
  }
 
 
  // It's a limitation of JavaScript inheritance that we can't conveniently
  // inherit from GMarker without having to run its constructor. In order for
  // the constructor to run, it requires some dummy GLatLng.
  LabeledMarker.prototype = new GMarker(new GLatLng(0, 0));
 
  /**
  * Is called by GMap2's addOverlay method. Creates the text div and adds it
  * to the relevant parent div.
  *
  * @param {GMap2} map the map that has had this labeledmarker added to it.
  */
  LabeledMarker.prototype.initialize = function(map) {
  // Do the GMarker constructor first.
  GMarker.prototype.initialize.apply(this, arguments);
 
  this.map_ = map;
  this.setText(this.initText_);
  }
 
  /**
  * Create a new div for this label.
  */
  LabeledMarker.prototype.makeDiv_ = function(map) {
  if (this.div_) {
  return;
  }
  this.div_ = document.createElement("div");
  this.div_.className = this.labelClass_;
  this.div_.style.position = "absolute";
  this.div_.style.cursor = "pointer";
  this.map_.getPane(G_MAP_MARKER_PANE).appendChild(this.div_);
 
  if (this.clickable_) {
  /**
  * Creates a closure for passing events through to the source marker
  * This is located in here to avoid cluttering the global namespace.
  * The downside is that the local variables from initialize() continue
  * to occupy space on the stack.
  *
  * @param {Object} object to receive event trigger.
  * @param {GEventListener} event to be triggered.
  */
  function newEventPassthru(obj, event) {
  return function() {
  GEvent.trigger(obj, event);
  };
  }
 
  // Pass through events fired on the text div to the marker.
  var eventPassthrus = ['click', 'dblclick', 'mousedown', 'mouseup', 'mouseover', 'mouseout'];
  for(var i = 0; i < eventPassthrus.length; i++) {
  var name = eventPassthrus[i];
  GEvent.addDomListener(this.div_, name, newEventPassthru(this, name));
  }
  }
  }
 
  /**
  * Return the html in the div of this label, or "" if none is set
  */
  LabeledMarker.prototype.getText = function(text) {
  if (this.div_) {
  return this.div_.innerHTML;
  } else {
  return "";
  }
  }
 
  /**
  * Set the html in the div of this label to text. If text is "" or null remove
  * the div.
  */
  LabeledMarker.prototype.setText = function(text) {
  if (this.div_) {
  if (text) {
  this.div_.innerHTML = text;
  } else {
  // remove div
  GEvent.clearInstanceListeners(this.div_);
  this.div_.parentNode.removeChild(this.div_);
  this.div_ = null;
  }
  } else {
  if (text) {
  this.makeDiv_();
  this.div_.innerHTML = text;
  this.redraw();
  }
  }
  }
 
  /**
  * Move the text div based on current projection and zoom level, call the redraw()
  * handler in GMarker.
  *
  * @param {Boolean} force will be true when pixel coordinates need to be recomputed.
  */
  LabeledMarker.prototype.redraw = function(force) {
  GMarker.prototype.redraw.apply(this, arguments);
 
  if (this.div_) {
  // Calculate the DIV coordinates of two opposite corners of our bounds to
  // get the size and position of our rectangle
  var p = this.map_.fromLatLngToDivPixel(this.latlng_);
  var z = GOverlay.getZIndex(this.latlng_.lat());
 
  // Now position our div based on the div coordinates of our bounds
  this.div_.style.left = (p.x + this.labelOffset_.width) + "px";
  this.div_.style.top = (p.y + this.labelOffset_.height) + "px";
  this.div_.style.zIndex = z; // in front of the marker
  }
  }
 
  /**
  * Remove the text div from the map pane, destroy event passthrus, and calls the
  * default remove() handler in GMarker.
  */
  LabeledMarker.prototype.remove = function() {
  this.setText(null);
  GMarker.prototype.remove.apply(this, arguments);
  }
 
  /**
  * Return a copy of this overlay, for the parent Map to duplicate itself in full. This
  * is part of the Overlay interface and is used, for example, to copy everything in the
  * main view into the mini-map.
  */
  LabeledMarker.prototype.copy = function() {
  return new LabeledMarker(this.latlng_, this.opt_opts_);
  }
 
 Binary files /dev/null and b/origin-src/transitfeed-1.2.6/gtfsscheduleviewer/files/mm_20_blue.png differ
 Binary files /dev/null and b/origin-src/transitfeed-1.2.6/gtfsscheduleviewer/files/mm_20_blue_trans.png differ
 Binary files /dev/null and b/origin-src/transitfeed-1.2.6/gtfsscheduleviewer/files/mm_20_red_trans.png differ
 Binary files /dev/null and b/origin-src/transitfeed-1.2.6/gtfsscheduleviewer/files/mm_20_shadow.png differ
 Binary files /dev/null and b/origin-src/transitfeed-1.2.6/gtfsscheduleviewer/files/mm_20_shadow_trans.png differ
 Binary files /dev/null and b/origin-src/transitfeed-1.2.6/gtfsscheduleviewer/files/mm_20_yellow.png differ
  html { overflow: hidden; }
 
  html, body {
  margin: 0;
  padding: 0;
  height: 100%;
  }
 
  body { margin: 5px; }
 
  #content {
  position: relative;
  margin-top: 5px;
  }
 
  #map-wrapper {
  position: relative;
  height: 100%;
  width: auto;
  left: 0;
  top: 0;
  z-index: 100;
  }
 
  #map {
  position: relative;
  height: 100%;
  width: auto;
  border: 1px solid #aaa;
  }
 
  #sidebar-wrapper {
  position: absolute;
  height: 100%;
  width: 220px;
  top: 0;
  border: 1px solid #aaa;
  overflow: auto;
  z-index: 300;
  }
 
  #sidebar {
  position: relative;
  width: auto;
  padding: 4px;
  overflow: hidden;
  }
 
  #topbar {
  position: relative;
  padding: 2px;
  border: 1px solid #aaa;
  margin: 0;
  }
 
  #topbar h1 {
  white-space: nowrap;
  overflow: hidden;
  font-size: 14pt;
  font-weight: bold;
  font-face:
  margin: 0;
  }
 
 
  body.sidebar-right #map-wrapper { margin-right: 229px; }
  body.sidebar-right #sidebar-wrapper { right: 0; }
 
  body.sidebar-left #map { margin-left: 229px; }
  body.sidebar-left #sidebar { left: 0; }
 
  body.nosidebar #map { margin: 0; }
  body.nosidebar #sidebar { display: none; }
 
  #bottombar {
  position: relative;
  padding: 2px;
  border: 1px solid #aaa;
  margin-top: 5px;
  display: none;
  }
 
  /* holly hack for IE to get position:bottom right
  see: http://www.positioniseverything.net/abs_relbugs.html
  \*/
  * html #topbar { height: 1px; }
  /* */
 
  body {
  font-family:helvetica,arial,sans, sans-serif;
  }
  h1 {
  margin-top: 0.5em;
  margin-bottom: 0.5em;
  }
  h2 {
  margin-top: 0.2em;
  margin-bottom: 0.2em;
  }
  h3 {
  margin-top: 0.2em;
  margin-bottom: 0.2em;
  }
  .tooltip {
  white-space: nowrap;
  padding: 2px;
  color: black;
  font-size: 12px;
  background-color: white;
  border: 1px solid black;
  cursor: pointer;
  filter:alpha(opacity=60);
  -moz-opacity: 0.6;
  opacity: 0.6;
  }
  #routeList {
  border: 1px solid black;
  overflow: auto;
  }
  .shortName {
  font-size: bigger;
  font-weight: bold;
  }
  .routeChoice,.tripChoice,.routeChoiceSelected,.tripChoiceSelected {
  white-space: nowrap;
  cursor: pointer;
  padding: 0px 2px;
  color: black;
  line-height: 1.4em;
  font-size: smaller;
  overflow: hidden;
  }
  .tripChoice {
  color: blue;
  }
  .routeChoiceSelected,.tripChoiceSelected {
  background-color: blue;
  color: white;
  }
  .tripSection {
  padding-left: 0px;
  font-size: 10pt;
  background-color: lightblue;
  }
  .patternSection {
  margin-left: 8px;
  padding-left: 2px;
  border-bottom: 1px solid grey;
  }
  .unusualPattern {
  background-color: #aaa;
  color: #444;
  }
  /* Following styles are used by location_editor.py */
  #edit {
  visibility: hidden;
  float: right;
  font-size: 80%;
  }
  #edit form {
  display: inline;
  }
  ' Copyright 1999-2000 Adobe Systems Inc. All rights reserved. Permission to redistribute
  ' granted provided that this file is not modified in any way. This file is provided with
  ' absolutely no warranties of any kind.
  Function isSVGControlInstalled()
  on error resume next
  isSVGControlInstalled = IsObject(CreateObject("Adobe.SVGCtl"))
  end Function
 
  #!/usr/bin/python2.5
  #
  # Copyright (C) 2007 Google Inc.
  #
  # Licensed under the Apache License, Version 2.0 (the "License");
  # you may not use this file except in compliance with the License.
  # You may obtain a copy of the License at
  #
  # http://www.apache.org/licenses/LICENSE-2.0
  #
  # Unless required by applicable law or agreed to in writing, software
  # distributed under the License is distributed on an "AS IS" BASIS,
  # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  # See the License for the specific language governing permissions and
  # limitations under the License.
 
  """Output svg/xml data for a marey graph
 
  Marey graphs are a visualization form typically used for timetables. Time
  is on the x-axis and position on the y-axis. This module reads data from a
  transitfeed.Schedule and creates a marey graph in svg/xml format. The graph
  shows the speed between stops for each trip of a route.
 
  TODO: This module was taken from an internal Google tool. It works but is not
  well intergrated into transitfeed and schedule_viewer. Also, it has lots of
  ugly hacks to compensate set canvas size and so on which could be cleaned up.
 
  For a little more information see (I didn't make this URL ;-)
  http://transliteracies.english.ucsb.edu/post/research-project/research-clearinghouse-individual/research-reports/the-indexical-imagination-marey%e2%80%99s-graphic-method-and-the-technological-transformation-of-writing-in-the-nineteenth-century
 
  MareyGraph: Class, keeps cache of graph data and graph properties
  and draws marey graphs in svg/xml format on request.
 
  """
 
  import itertools
  import transitfeed
 
 
  class MareyGraph:
  """Produces and caches marey graph from transit feed data."""
 
  _MAX_ZOOM = 5.0 # change docstring of ChangeScaleFactor if this changes
  _DUMMY_SEPARATOR = 10 #pixel
 
  def __init__(self):
  # Timetablerelated state
  self._cache = str()
  self._stoplist = []
  self._tlist = []
  self._stations = []
  self._decorators = []
 
  # TODO: Initialize default values via constructor parameters
  # or via a class constants
 
  # Graph properties
  self._tspan = 30 # number of hours to display
  self._offset = 0 # starting hour
  self._hour_grid = 60 # number of pixels for an hour
  self._min_grid = 5 # number of pixels between subhour lines
 
  # Canvas properties
  self._zoomfactor = 0.9 # svg Scaling factor
  self._xoffset = 0 # move graph horizontally
  self._yoffset = 0 # move graph veritcally
  self._bgcolor = "lightgrey"
 
  # height/width of graph canvas before transform
  self._gwidth = self._tspan * self._hour_grid
 
  def Draw(self, stoplist=None, triplist=None, height=520):
  """Main interface for drawing the marey graph.
 
  If called without arguments, the data generated in the previous call
  will be used. New decorators can be added between calls.
 
  Args:
  # Class Stop is defined in transitfeed.py
  stoplist: [Stop, Stop, ...]
  # Class Trip is defined in transitfeed.py
  triplist: [Trip, Trip, ...]
 
  Returns:
  # A string that contain a svg/xml web-page with a marey graph.
  " <svg width="1440" height="520" version="1.1" ... "
  """
  output = str()
  if not triplist:
  triplist = []
  if not stoplist:
  stoplist = []
 
  if not self._cache or triplist or stoplist:
  self._gheight = height
  self._tlist=triplist
  self._slist=stoplist
  self._decorators = []
  self._stations = self._BuildStations(stoplist)
  self._cache = "%s %s %s %s" % (self._DrawBox(),
  self._DrawHours(),
  self._DrawStations(),
  self._DrawTrips(triplist))
 
 
 
  output = "%s %s %s %s" % (self._DrawHeader(),
  self._cache,
  self._DrawDecorators(),
  self._DrawFooter())
  return output
 
  def _DrawHeader(self):
  svg_header = """
  <svg width="%s" height="%s" version="1.1"
  xmlns="http://www.w3.org/2000/svg">
  <script type="text/ecmascript"><![CDATA[
  function init(evt) {
  if ( window.svgDocument == null )
  svgDocument = evt.target.ownerDocument;
  }
  var oldLine = 0;
  var oldStroke = 0;
  var hoffset= %s; // Data from python
 
  function parseLinePoints(pointnode){
  var wordlist = pointnode.split(" ");
  var xlist = new Array();
  var h;
  var m;
  // TODO: add linebreaks as appropriate
  var xstr = " Stop Times :";
  for (i=0;i<wordlist.length;i=i+2){
  var coord = wordlist[i].split(",");
  h = Math.floor(parseInt((coord[0])-20)/60);
  m = parseInt((coord[0]-20))%%60;
  xstr = xstr +" "+ (hoffset+h) +":"+m;
  }
 
  return xstr;
  }
 
  function LineClick(tripid, x) {
  var line = document.getElementById(tripid);
  if (oldLine)
  oldLine.setAttribute("stroke",oldStroke);
  oldLine = line;
  oldStroke = line.getAttribute("stroke");
 
  line.setAttribute("stroke","#fff");
 
  var dynTxt = document.getElementById("dynamicText");
  var tripIdTxt = document.createTextNode(x);
  while (dynTxt.hasChildNodes()){
  dynTxt.removeChild(dynTxt.firstChild);
  }
  dynTxt.appendChild(tripIdTxt);
  }
  ]]> </script>
  <style type="text/css"><![CDATA[
  .T { fill:none; stroke-width:1.5 }
  .TB { fill:none; stroke:#e20; stroke-width:2 }
  .Station { fill:none; stroke-width:1 }
  .Dec { fill:none; stroke-width:1.5 }
  .FullHour { fill:none; stroke:#eee; stroke-width:1 }
  .SubHour { fill:none; stroke:#ddd; stroke-width:1 }
  .Label { fill:#aaa; font-family:Helvetica,Arial,sans;
  text-anchor:middle }
  .Info { fill:#111; font-family:Helvetica,Arial,sans;
  text-anchor:start; }
  ]]></style>
  <text class="Info" id="dynamicText" x="0" y="%d"></text>
  <g id="mcanvas" transform="translate(%s,%s)">
  <g id="zcanvas" transform="scale(%s)">
 
  """ % (self._gwidth + self._xoffset + 20, self._gheight + 15,
  self._offset, self._gheight + 10,
  self._xoffset, self._yoffset, self._zoomfactor)
 
  return svg_header
 
  def _DrawFooter(self):
  return "</g></g></svg>"
 
  def _DrawDecorators(self):
  """Used to draw fancy overlays on trip graphs."""
  return " ".join(self._decorators)
 
  def _DrawBox(self):
  tmpstr = """<rect x="%s" y="%s" width="%s" height="%s"
  fill="lightgrey" stroke="%s" stroke-width="2" />
  """ % (0, 0, self._gwidth + 20, self._gheight, self._bgcolor)
  return tmpstr
 
  def _BuildStations(self, stoplist):
  """Dispatches the best algorithm for calculating station line position.
 
  Args:
  # Class Stop is defined in transitfeed.py
  stoplist: [Stop, Stop, ...]
  # Class Trip is defined in transitfeed.py
  triplist: [Trip, Trip, ...]
 
  Returns:
  # One integer y-coordinate for each station normalized between
  # 0 and X, where X is the height of the graph in pixels
  [0, 33, 140, ... , X]
  """
  stations = []
  dists = self._EuclidianDistances(stoplist)
  stations = self._CalculateYLines(dists)
  return stations
 
  def _EuclidianDistances(self,slist):
  """Calculate euclidian distances between stops.
 
  Uses the stoplists long/lats to approximate distances
  between stations and build a list with y-coordinates for the
  horizontal lines in the graph.
 
  Args:
  # Class Stop is defined in transitfeed.py
  stoplist: [Stop, Stop, ...]
 
  Returns:
  # One integer for each pair of stations
  # indicating the approximate distance
  [0,33,140, ... ,X]
  """
  e_dists2 = [transitfeed.ApproximateDistanceBetweenStops(stop, tail) for
  (stop,tail) in itertools.izip(slist, slist[1:])]
 
  return e_dists2
 
  def _CalculateYLines(self, dists):
  """Builds a list with y-coordinates for the horizontal lines in the graph.
 
  Args:
  # One integer for each pair of stations
  # indicating the approximate distance
  dists: [0,33,140, ... ,X]
 
  Returns:
  # One integer y-coordinate for each station normalized between
  # 0 and X, where X is the height of the graph in pixels
  [0, 33, 140, ... , X]
  """
  tot_dist = sum(dists)
  if tot_dist > 0:
  pixel_dist = [float(d * (self._gheight-20))/tot_dist for d in dists]
  pixel_grid = [0]+[int(pd + sum(pixel_dist[0:i])) for i,pd in
  enumerate(pixel_dist)]
  else:
  pixel_grid = []
 
  return pixel_grid
 
  def _TravelTimes(self,triplist,index=0):
  """ Calculate distances and plot stops.
 
  Uses a timetable to approximate distances
  between stations
 
  Args:
  # Class Trip is defined in transitfeed.py
  triplist: [Trip, Trip, ...]
  # (Optional) Index of Triplist prefered for timetable Calculation
  index: 3
 
  Returns:
  # One integer for each pair of stations
  # indicating the approximate distance
  [0,33,140, ... ,X]
  """
 
  def DistanceInTravelTime(dep_secs, arr_secs):
  t_dist = arr_secs-dep_secs
  if t_dist<0:
  t_dist = self._DUMMY_SEPARATOR # min separation
  return t_dist
 
  if not triplist:
  return []
 
  if 0 < index < len(triplist):
  trip = triplist[index]
  else:
  trip = triplist[0]
 
  t_dists2 = [DistanceInTravelTime(stop[3],tail[2]) for (stop,tail)
  in itertools.izip(trip.GetTimeStops(),trip.GetTimeStops()[1:])]
  return t_dists2
 
  def _AddWarning(self, str):
  print str
 
  def _DrawTrips(self,triplist,colpar=""):
  """Generates svg polylines for each transit trip.
 
  Args:
  # Class Trip is defined in transitfeed.py
  [Trip, Trip, ...]
 
  Returns:
  # A string containing a polyline tag for each trip
  ' <polyline class="T" stroke="#336633" points="433,0 ...'
  """
 
  stations = []
  if not self._stations and triplist:
  self._stations = self._CalculateYLines(self._TravelTimes(triplist))
  if not self._stations:
  self._AddWarning("Failed to use traveltimes for graph")
  self._stations = self._CalculateYLines(self._Uniform(triplist))
  if not self._stations:
  self._AddWarning("Failed to calculate station distances")
  return
 
  stations = self._stations
  tmpstrs = []
  servlist = []
  for t in triplist:
  if not colpar:
  if t.service_id not in servlist:
  servlist.append(t.service_id)
  shade = int(servlist.index(t.service_id) * (200/len(servlist))+55)
  color = "#00%s00" % hex(shade)[2:4]
  else:
  color=colpar
 
  start_offsets = [0]
  first_stop = t.GetTimeStops()[0]
 
  for j,freq_offset in enumerate(start_offsets):
  if j>0 and not colpar:
  color="purple"
  scriptcall = 'onmouseover="LineClick(\'%s\',\'Trip %s starting %s\')"' % (t.trip_id,
  t.trip_id, transitfeed.FormatSecondsSinceMidnight(t.GetStartTime()))
  tmpstrhead = '<polyline class="T" id="%s" stroke="%s" %s points="' % \
  (str(t.trip_id),color, scriptcall)
  tmpstrs.append(tmpstrhead)
 
  for i, s in enumerate(t.GetTimeStops()):
  arr_t = s[0]
  dep_t = s[1]
  if arr_t is None or dep_t is None:
  continue
  arr_x = int(arr_t/3600.0 * self._hour_grid) - self._hour_grid * self._offset
  dep_x = int(dep_t/3600.0 * self._hour_grid) - self._hour_grid * self._offset
  tmpstrs.append("%s,%s " % (int(arr_x+20), int(stations[i]+20)))
  tmpstrs.append("%s,%s " % (int(dep_x+20), int(stations[i]+20)))
  tmpstrs.append('" />')
  return "".join(tmpstrs)
 
  def _Uniform(self, triplist):
  """Fallback to assuming uniform distance between stations"""
  # This should not be neseccary, but we are in fallback mode
  longest = max([len(t.GetTimeStops()) for t in triplist])
  return [100] * longest
 
  def _DrawStations(self, color="#aaa"):
  """Generates svg with a horizontal line for each station/stop.
 
  Args:
  # Class Stop is defined in transitfeed.py
  stations: [Stop, Stop, ...]
 
  Returns:
  # A string containing a polyline tag for each stop
  " <polyline class="Station" stroke="#336633" points="20,0 ..."
  """
  stations=self._stations
  tmpstrs = []
  for y in stations:
  tmpstrs.append(' <polyline class="Station" stroke="%s" \
  points="%s,%s, %s,%s" />' %(color,20,20+y+.5,self._gwidth+20,20+y+.5))
  return "".join(tmpstrs)
 
  def _DrawHours(self):
  """Generates svg to show a vertical hour and sub-hour grid
 
  Returns:
  # A string containing a polyline tag for each grid line
  " <polyline class="FullHour" points="20,0 ..."
  """
  tmpstrs = []
  for i in range(0, self._gwidth, self._min_grid):
  if i % self._hour_grid == 0:
  tmpstrs.append('<polyline class="FullHour" points="%d,%d, %d,%d" />' \
  % (i + .5 + 20, 20, i + .5 + 20, self._gheight))
  tmpstrs.append('<text class="Label" x="%d" y="%d">%d</text>'
  % (i + 20, 20,
  (i / self._hour_grid + self._offset) % 24))
  else:
  tmpstrs.append('<polyline class="SubHour" points="%d,%d,%d,%d" />' \
  % (i + .5 + 20, 20, i + .5 + 20, self._gheight))
  return "".join(tmpstrs)
 
  def AddStationDecoration(self, index, color="#f00"):
  """Flushes existing decorations and highlights the given station-line.
 
  Args:
  # Integer, index of stop to be highlighted.
  index: 4
  # An optional string with a html color code
  color: "#fff"
  """
  tmpstr = str()
  num_stations = len(self._stations)
  ind = int(index)
  if self._stations:
  if 0<ind<num_stations:
  y = self._stations[ind]
  tmpstr = '<polyline class="Dec" stroke="%s" points="%s,%s,%s,%s" />' \
  % (color, 20, 20+y+.5, self._gwidth+20, 20+y+.5)
  self._decorators.append(tmpstr)
 
  def AddTripDecoration(self, triplist, color="#f00"):
  """Flushes existing decorations and highlights the given trips.
 
  Args:
  # Class Trip is defined in transitfeed.py
  triplist: [Trip, Trip, ...]
  # An optional string with a html color code
  color: "#fff"
  """
  tmpstr = self._DrawTrips(triplist,color)
  self._decorators.append(tmpstr)
 
  def ChangeScaleFactor(self, newfactor):
  """Changes the zoom of the graph manually.
 
  1.0 is the original canvas size.
 
  Args:
  # float value between 0.0 and 5.0
  newfactor: 0.7
  """
  if float(newfactor) > 0 and float(newfactor) < self._MAX_ZOOM:
  self._zoomfactor = newfactor
 
  def ScaleLarger(self):
  """Increases the zoom of the graph one step (0.1 units)."""
  newfactor = self._zoomfactor + 0.1
  if float(newfactor) > 0 and float(newfactor) < self._MAX_ZOOM:
  self._zoomfactor = newfactor
 
  def ScaleSmaller(self):
  """Decreases the zoom of the graph one step(0.1 units)."""
  newfactor = self._zoomfactor - 0.1
  if float(newfactor) > 0 and float(newfactor) < self._MAX_ZOOM:
  self._zoomfactor = newfactor
 
  def ClearDecorators(self):
  """Removes all the current decorators.
  """
  self._decorators = []
 
  def AddTextStripDecoration(self,txtstr):
  tmpstr = '<text class="Info" x="%d" y="%d">%s</text>' % (0,
  20 + self._gheight, txtstr)
  self._decorators.append(tmpstr)
 
  def SetSpan(self, first_arr, last_arr, mint=5 ,maxt=30):
  s_hour = (first_arr / 3600) - 1
  e_hour = (last_arr / 3600) + 1
  self._offset = max(min(s_hour, 23), 0)
  self._tspan = max(min(e_hour - s_hour, maxt), mint)
  self._gwidth = self._tspan * self._hour_grid
 
  #!/usr/bin/python2.5
 
  # Copyright (C) 2007 Google Inc.
  #
  # Licensed under the Apache License, Version 2.0 (the "License");
  # you may not use this file except in compliance with the License.
  # You may obtain a copy of the License at
  #
  # http://www.apache.org/licenses/LICENSE-2.0
  #
  # Unless required by applicable law or agreed to in writing, software
  # distributed under the License is distributed on an "AS IS" BASIS,
  # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  # See the License for the specific language governing permissions and
  # limitations under the License.
 
  """
  This package provides implementation of a converter from a kml
  file format into Google transit feed format.
 
  The KmlParser class is the main class implementing the parser.
 
  Currently only information about stops is extracted from a kml file.
  The extractor expects the stops to be represented as placemarks with
  a single point.
  """
 
  import re
  import string
  import sys
  import transitfeed
  from transitfeed import util
  import xml.dom.minidom as minidom
  import zipfile
 
 
  class Placemark(object):
  def __init__(self):
  self.name = ""
  self.coordinates = []
 
  def IsPoint(self):
  return len(self.coordinates) == 1
 
  def IsLine(self):
  return len(self.coordinates) > 1
 
  class KmlParser(object):
  def __init__(self, stopNameRe = '(.*)'):
  """
  Args:
  stopNameRe - a regular expression to extract a stop name from a
  placemaker name
  """
  self.stopNameRe = re.compile(stopNameRe)
 
  def Parse(self, filename, feed):
  """
  Reads the kml file, parses it and updated the Google transit feed
  object with the extracted information.
 
  Args:
  filename - kml file name
  feed - an instance of Schedule class to be updated
  """
  dom = minidom.parse(filename)
  self.ParseDom(dom, feed)
 
  def ParseDom(self, dom, feed):
  """
  Parses the given kml dom tree and updates the Google transit feed object.
 
  Args:
  dom - kml dom tree
  feed - an instance of Schedule class to be updated
  """
  shape_num = 0
  for node in dom.getElementsByTagName('Placemark'):
  p = self.ParsePlacemark(node)
  if p.IsPoint():
  (lon, lat) = p.coordinates[0]
  m = self.stopNameRe.search(p.name)
  feed.AddStop(lat, lon, m.group(1))
  elif p.IsLine():
  shape_num = shape_num + 1
  shape = transitfeed.Shape("kml_shape_" + str(shape_num))
  for (lon, lat) in p.coordinates:
  shape.AddPoint(lat, lon)
  feed.AddShapeObject(shape)
 
  def ParsePlacemark(self, node):
  ret = Placemark()
  for child in node.childNodes:
  if child.nodeName == 'name':
  ret.name = self.ExtractText(child)
  if child.nodeName == 'Point' or child.nodeName == 'LineString':
  ret.coordinates = self.ExtractCoordinates(child)
  return ret
 
  def ExtractText(self, node):
  for child in node.childNodes:
  if child.nodeType == child.TEXT_NODE:
  return child.wholeText # is a unicode string
  return ""
 
  def ExtractCoordinates(self, node):
  coordinatesText = ""
  for child in node.childNodes:
  if child.nodeName == 'coordinates':
  coordinatesText = self.ExtractText(child)
  break
  ret = []
  for point in coordinatesText.split():
  coords = point.split(',')
  ret.append((float(coords[0]), float(coords[1])))
  return ret
 
 
  def main():
  usage = \
  """%prog <input.kml> <output GTFS.zip>
 
  Reads KML file <input.kml> and creates GTFS file <output GTFS.zip> with
  placemarks in the KML represented as stops.
  """
 
  parser = util.OptionParserLongError(
  usage=usage, version='%prog '+transitfeed.__version__)
  (options, args) = parser.parse_args()
  if len(args) != 2:
  parser.error('You did not provide all required command line arguments.')
 
  if args[0] == 'IWantMyCrash':
  raise Exception('For testCrashHandler')
 
  parser = KmlParser()
  feed = transitfeed.Schedule()
  feed.save_all_stops = True
  parser.Parse(args[0], feed)
  feed.WriteGoogleTransitFeed(args[1])
 
  print "Done."
 
 
  if __name__ == '__main__':
  util.RunWithCrashHandler(main)
 
  #!/usr/bin/python2.5
  #
  # Copyright 2008 Google Inc. All Rights Reserved.
  #
  # Licensed under the Apache License, Version 2.0 (the "License");
  # you may not use this file except in compliance with the License.
  # You may obtain a copy of the License at
  #
  # http://www.apache.org/licenses/LICENSE-2.0
  #
  # Unless required by applicable law or agreed to in writing, software
  # distributed under the License is distributed on an "AS IS" BASIS,
  # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  # See the License for the specific language governing permissions and
  # limitations under the License.
 
  """A module for writing GTFS feeds out into Google Earth KML format.
 
  For usage information run kmlwriter.py --help
 
  If no output filename is specified, the output file will be given the same
  name as the feed file (with ".kml" appended) and will be placed in the same
  directory as the input feed.
 
  The resulting KML file has a folder hierarchy which looks like this:
 
  - Stops
  * stop1
  * stop2
  - Routes
  - route1
  - Shapes
  * shape1
  * shape2
  - Patterns
  - pattern1
  - pattern2
  - Trips
  * trip1
  * trip2
  - Shapes
  * shape1
  - Shape Points
  * shape_point1
  * shape_point2
  * shape2
  - Shape Points
  * shape_point1
  * shape_point2
 
  where the hyphens represent folders and the asteriks represent placemarks.
 
  In a trip, a vehicle visits stops in a certain sequence. Such a sequence of
  stops is called a pattern. A pattern is represented by a linestring connecting
  the stops. The "Shapes" subfolder of a route folder contains placemarks for
  each shape used by a trip in the route. The "Patterns" subfolder contains a
  placemark for each unique pattern used by a trip in the route. The "Trips"
  subfolder contains a placemark for each trip in the route.
 
  Since there can be many trips and trips for the same route are usually similar,
  they are not exported unless the --showtrips option is used. There is also
  another option --splitroutes that groups the routes by vehicle type resulting
  in a folder hierarchy which looks like this at the top level:
 
  - Stops
  - Routes - Bus
  - Routes - Tram
  - Routes - Rail
  - Shapes
  """
 
  try:
  import xml.etree.ElementTree as ET # python 2.5
  except ImportError, e:
  import elementtree.ElementTree as ET # older pythons
  import optparse
  import os.path
  import sys
  import transitfeed
  from transitfeed import util
 
 
  class KMLWriter(object):
  """This class knows how to write out a transit feed as KML.
 
  Sample usage:
  KMLWriter().Write(<transitfeed.Schedule object>, <output filename>)
 
  Attributes:
  show_trips: True if the individual trips should be included in the routes.
  show_trips: True if the individual trips should be placed on ground.
  split_routes: True if the routes should be split by type.
  shape_points: True if individual shape points should be plotted.
  """
 
  def __init__(self):
  """Initialise."""
  self.show_trips = False
  self.split_routes = False
  self.shape_points = False
  self.altitude_per_sec = 0.0
  self.date_filter = None
 
  def _SetIndentation(self, elem, level=0):
  """Indented the ElementTree DOM.
 
  This is the recommended way to cause an ElementTree DOM to be
  prettyprinted on output, as per: http://effbot.org/zone/element-lib.htm
 
  Run this on the root element before outputting the tree.
 
  Args:
  elem: The element to start indenting from, usually the document root.
  level: Current indentation level for recursion.
  """
  i = "\n" + level*" "
  if len(elem):
  if not elem.text or not elem.text.strip():
  elem.text = i + " "
  for elem in elem:
  self._SetIndentation(elem, level+1)
  if not elem.tail or not elem.tail.strip():
  elem.tail = i
  else:
  if level and (not elem.tail or not elem.tail.strip()):
  elem.tail = i
 
  def _CreateFolder(self, parent, name, visible=True, description=None):
  """Create a KML Folder element.
 
  Args:
  parent: The parent ElementTree.Element instance.
  name: The folder name as a string.
  visible: Whether the folder is initially visible or not.
  description: A description string or None.
 
  Returns:
  The folder ElementTree.Element instance.
  """
  folder = ET.SubElement(parent, 'Folder')
  name_tag = ET.SubElement(folder, 'name')
  name_tag.text = name
  if description is not None:
  desc_tag = ET.SubElement(folder, 'description')
  desc_tag.text = description
  if not visible:
  visibility = ET.SubElement(folder, 'visibility')
  visibility.text = '0'
  return folder
 
  def _CreateStyleForRoute(self, doc, route):
  """Create a KML Style element for the route.
 
  The style sets the line colour if the route colour is specified. The
  line thickness is set depending on the vehicle type.
 
  Args:
  doc: The KML Document ElementTree.Element instance.
  route: The transitfeed.Route to create the style for.
 
  Returns:
  The id of the style as a string.
  """
  style_id = 'route_%s' % route.route_id
  style = ET.SubElement(doc, 'Style', {'id': style_id})
  linestyle = ET.SubElement(style, 'LineStyle')
  width = ET.SubElement(linestyle, 'width')
  type_to_width = {0: '3', # Tram
  1: '3', # Subway
  2: '5', # Rail
  3: '1'} # Bus
  width.text = type_to_width.get(route.route_type, '1')
  if route.route_color:
  color = ET.SubElement(linestyle, 'color')
  red = route.route_color[0:2].lower()
  green = route.route_color[2:4].lower()
  blue = route.route_color[4:6].lower()
  color.text = 'ff%s%s%s' % (blue, green, red)
  return style_id
 
  def _CreatePlacemark(self, parent, name, style_id=None, visible=True,
  description=None):
  """Create a KML Placemark element.
 
  Args:
  parent: The parent ElementTree.Element instance.
  name: The placemark name as a string.
  style_id: If not None, the id of a style to use for the placemark.
  visible: Whether the placemark is initially visible or not.
  description: A description string or None.
 
  Returns:
  The placemark ElementTree.Element instance.
  """
  placemark = ET.SubElement(parent, 'Placemark')
  placemark_name = ET.SubElement(placemark, 'name')
  placemark_name.text = name
  if description is not None:
  desc_tag = ET.SubElement(placemark, 'description')
  desc_tag.text = description
  if style_id is not None:
  styleurl = ET.SubElement(placemark, 'styleUrl')
  styleurl.text = '#%s' % style_id
  if not visible:
  visibility = ET.SubElement(placemark, 'visibility')
  visibility.text = '0'
  return placemark
 
  def _CreateLineString(self, parent, coordinate_list):
  """Create a KML LineString element.
 
  The points of the string are given in coordinate_list. Every element of
  coordinate_list should be one of a tuple (longitude, latitude) or a tuple
  (longitude, latitude, altitude).
 
  Args:
  parent: The parent ElementTree.Element instance.
  coordinate_list: The list of coordinates.
 
  Returns:
  The LineString ElementTree.Element instance or None if coordinate_list is
  empty.
  """
  if not coordinate_list:
  return None
  linestring = ET.SubElement(parent, 'LineString')
  tessellate = ET.SubElement(linestring, 'tessellate')
  tessellate.text = '1'
  if len(coordinate_list[0]) == 3:
  altitude_mode = ET.SubElement(linestring, 'altitudeMode')
  altitude_mode.text = 'absolute'
  coordinates = ET.SubElement(linestring, 'coordinates')
  if len(coordinate_list[0]) == 3:
  coordinate_str_list = ['%f,%f,%f' % t for t in coordinate_list]
  else:
  coordinate_str_list = ['%f,%f' % t for t in coordinate_list]
  coordinates.text = ' '.join(coordinate_str_list)
  return linestring
 
  def _CreateLineStringForShape(self, parent, shape):
  """Create a KML LineString using coordinates from a shape.
 
  Args:
  parent: The parent ElementTree.Element instance.
  shape: The transitfeed.Shape instance.
 
  Returns:
  The LineString ElementTree.Element instance or None if coordinate_list is
  empty.
  """
  coordinate_list = [(longitude, latitude) for
  (latitude, longitude, distance) in shape.points]
  return self._CreateLineString(parent, coordinate_list)
 
  def _CreateStopsFolder(self, schedule, doc):
  """Create a KML Folder containing placemarks for each stop in the schedule.
 
  If there are no stops in the schedule then no folder is created.
 
  Args:
  schedule: The transitfeed.Schedule instance.
  doc: The KML Document ElementTree.Element instance.
 
  Returns:
  The Folder ElementTree.Element instance or None if there are no stops.
  """
  if not schedule.GetStopList():
  return None
  stop_folder = self._CreateFolder(doc, 'Stops')
  stops = list(schedule.GetStopList())
  stops.sort(key=lambda x: x.stop_name)
  for stop in stops:
  desc_items = []
  if stop.stop_desc:
  desc_items.append(stop.stop_desc)
  if stop.stop_url:
  desc_items.append('Stop info page: <a href="%s">%s</a>' % (
  stop.stop_url, stop.stop_url))
  description = '<br/>'.join(desc_items) or None
  placemark = self._CreatePlacemark(stop_folder, stop.stop_name,
  description=description)
  point = ET.SubElement(placemark, 'Point')
  coordinates = ET.SubElement(point, 'coordinates')
  coordinates.text = '%.6f,%.6f' % (stop.stop_lon, stop.stop_lat)
  return stop_folder
 
  def _CreateRoutePatternsFolder(self, parent, route,
  style_id=None, visible=True):
  """Create a KML Folder containing placemarks for each pattern in the route.
 
  A pattern is a sequence of stops used by one of the trips in the route.
 
  If there are not patterns for the route then no folder is created and None
  is returned.
 
  Args:
  parent: The parent ElementTree.Element instance.
  route: The transitfeed.Route instance.
  style_id: The id of a style to use if not None.
  visible: Whether the folder is initially visible or not.
 
  Returns:
  The Folder ElementTree.Element instance or None if there are no patterns.
  """
  pattern_id_to_trips = route.GetPatternIdTripDict()
  if not pattern_id_to_trips:
  return None
 
  # sort by number of trips using the pattern
  pattern_trips = pattern_id_to_trips.values()
  pattern_trips.sort(lambda a, b: cmp(len(b), len(a)))
 
  folder = self._CreateFolder(parent, 'Patterns', visible)
  for n, trips in enumerate(pattern_trips):
  trip_ids = [trip.trip_id for trip in trips]
  name = 'Pattern %d (trips: %d)' % (n+1, len(trips))
  description = 'Trips using this pattern (%d in total): %s' % (
  len(trips), ', '.join(trip_ids))
  placemark = self._CreatePlacemark(folder, name, style_id, visible,
  description)
  coordinates = [(stop.stop_lon, stop.stop_lat)
  for stop in trips[0].GetPattern()]
  self._CreateLineString(placemark, coordinates)
  return folder
 
  def _CreateRouteShapesFolder(self, schedule, parent, route,
  style_id=None, visible=True):
  """Create a KML Folder for the shapes of a route.
 
  The folder contains a placemark for each shape referenced by a trip in the
  route. If there are no such shapes, no folder is created and None is
  returned.
 
  Args:
  schedule: The transitfeed.Schedule instance.
  parent: The parent ElementTree.Element instance.
  route: The transitfeed.Route instance.
  style_id: The id of a style to use if not None.
  visible: Whether the placemark is initially visible or not.
 
  Returns:
  The Folder ElementTree.Element instance or None.
  """
  shape_id_to_trips = {}
  for trip in route.trips:
  if trip.shape_id:
  shape_id_to_trips.setdefault(trip.shape_id, []).append(trip)
  if not shape_id_to_trips:
  return None
 
  # sort by the number of trips using the shape
  shape_id_to_trips_items = shape_id_to_trips.items()
  shape_id_to_trips_items.sort(lambda a, b: cmp(len(b[1]), len(a[1])))
 
  folder = self._CreateFolder(parent, 'Shapes', visible)
  for shape_id, trips in shape_id_to_trips_items:
  trip_ids = [trip.trip_id for trip in trips]
  name = '%s (trips: %d)' % (shape_id, len(trips))
  description = 'Trips using this shape (%d in total): %s' % (
  len(trips), ', '.join(trip_ids))
  placemark = self._CreatePlacemark(folder, name, style_id, visible,
  description)
  self._CreateLineStringForShape(placemark, schedule.GetShape(shape_id))
  return folder
 
  def _CreateRouteTripsFolder(self, parent, route, style_id=None, schedule=None):
  """Create a KML Folder containing all the trips in the route.
 
  The folder contains a placemark for each of these trips. If there are no
  trips in the route, no folder is created and None is returned.
 
  Args:
  parent: The parent ElementTree.Element instance.
  route: The transitfeed.Route instance.
  style_id: A style id string for the placemarks or None.
 
  Returns:
  The Folder ElementTree.Element instance or None.
  """
  if not route.trips:
  return None
  trips = list(route.trips)
  trips.sort(key=lambda x: x.trip_id)
  trips_folder = self._CreateFolder(parent, 'Trips', visible=False)
  for trip in trips:
  if (self.date_filter and
  not trip.service_period.IsActiveOn(self.date_filter)):
  continue
 
  if trip.trip_headsign:
  description = 'Headsign: %s' % trip.trip_headsign
  else:
  description = None
 
  coordinate_list = []
  for secs, stoptime, tp in trip.GetTimeInterpolatedStops():
  if self.altitude_per_sec > 0:
  coordinate_list.append((stoptime.stop.stop_lon, stoptime.stop.stop_lat,
  (secs - 3600 * 4) * self.altitude_per_sec))
  else:
  coordinate_list.append((stoptime.stop.stop_lon,
  stoptime.stop.stop_lat))
  placemark = self._CreatePlacemark(trips_folder,
  trip.trip_id,
  style_id=style_id,
  visible=False,
  description=description)
  self._CreateLineString(placemark, coordinate_list)
  return trips_folder
 
  def _CreateRoutesFolder(self, schedule, doc, route_type=None):
  """Create a KML Folder containing routes in a schedule.
 
  The folder contains a subfolder for each route in the schedule of type
  route_type. If route_type is None, then all routes are selected. Each
  subfolder contains a flattened graph placemark, a route shapes placemark
  and, if show_trips is True, a subfolder containing placemarks for each of
  the trips in the route.
 
  If there are no routes in the schedule then no folder is created and None
  is returned.
 
  Args:
  schedule: The transitfeed.Schedule instance.
  doc: The KML Document ElementTree.Element instance.
  route_type: The route type integer or None.
 
  Returns:
  The Folder ElementTree.Element instance or None.
  """
 
  def GetRouteName(route):
  """Return a placemark name for the route.
 
  Args:
  route: The transitfeed.Route instance.
 
  Returns:
  The name as a string.
  """
  name_parts = []
  if route.route_short_name:
  name_parts.append('<b>%s</b>' % route.route_short_name)
  if route.route_long_name:
  name_parts.append(route.route_long_name)
  return ' - '.join(name_parts) or route.route_id
 
  def GetRouteDescription(route):
  """Return a placemark description for the route.
 
  Args:
  route: The transitfeed.Route instance.
 
  Returns:
  The description as a string.
  """
  desc_items = []
  if route.route_desc:
  desc_items.append(route.route_desc)
  if route.route_url:
  desc_items.append('Route info page: <a href="%s">%s</a>' % (
  route.route_url, route.route_url))
  description = '<br/>'.join(desc_items)
  return description or None
 
  routes = [route for route in schedule.GetRouteList()
  if route_type is None or route.route_type == route_type]
  if not routes:
  return None
  routes.sort(key=lambda x: GetRouteName(x))
 
  if route_type is not None:
  route_type_names = {0: 'Tram, Streetcar or Light rail',
  1: 'Subway or Metro',
  2: 'Rail',
  3: 'Bus',
  4: 'Ferry',
  5: 'Cable car',
  6: 'Gondola or suspended cable car',
  7: 'Funicular'}
  type_name = route_type_names.get(route_type, str(route_type))
  folder_name = 'Routes - %s' % type_name
  else:
  folder_name = 'Routes'
  routes_folder = self._CreateFolder(doc, folder_name, visible=False)
 
  for route in routes:
  style_id = self._CreateStyleForRoute(doc, route)
  route_folder = self._CreateFolder(routes_folder,
  GetRouteName(route),
  description=GetRouteDescription(route))
  self._CreateRouteShapesFolder(schedule, route_folder, route,
  style_id, False)
  self._CreateRoutePatternsFolder(route_folder, route, style_id, False)
  if self.show_trips:
  self._CreateRouteTripsFolder(route_folder, route, style_id, schedule)
  return routes_folder
 
  def _CreateShapesFolder(self, schedule, doc):
  """Create a KML Folder containing all the shapes in a schedule.
 
  The folder contains a placemark for each shape. If there are no shapes in
  the schedule then the folder is not created and None is returned.
 
  Args:
  schedule: The transitfeed.Schedule instance.
  doc: The KML Document ElementTree.Element instance.
 
  Returns:
  The Folder ElementTree.Element instance or None.
  """
  if not schedule.GetShapeList():
  return None
  shapes_folder = self._CreateFolder(doc, 'Shapes')
  shapes = list(schedule.GetShapeList())
  shapes.sort(key=lambda x: x.shape_id)
  for shape in shapes:
  placemark = self._CreatePlacemark(shapes_folder, shape.shape_id)
  self._CreateLineStringForShape(placemark, shape)
  if self.shape_points:
  self._CreateShapePointFolder(shapes_folder, shape)
  return shapes_folder
 
  def _CreateShapePointFolder(self, shapes_folder, shape):
  """Create a KML Folder containing all the shape points in a shape.
 
  The folder contains placemarks for each shapepoint.
 
  Args:
  shapes_folder: A KML Shape Folder ElementTree.Element instance
  shape: The shape to plot.
 
  Returns:
  The Folder ElementTree.Element instance or None.
  """
 
  folder_name = shape.shape_id + ' Shape Points'
  folder = self._CreateFolder(shapes_folder, folder_name, visible=False)
  for (index, (lat, lon, dist)) in enumerate(shape.points):
  placemark = self._CreatePlacemark(folder, str(index+1))
  point = ET.SubElement(placemark, 'Point')
  coordinates = ET.SubElement(point, 'coordinates')
  coordinates.text = '%.6f,%.6f' % (lon, lat)
  return folder
 
  def Write(self, schedule, output_file):
  """Writes out a feed as KML.
 
  Args:
  schedule: A transitfeed.Schedule object containing the feed to write.
  output_file: The name of the output KML file, or file object to use.
  """
  # Generate the DOM to write
  root = ET.Element('kml')
  root.attrib['xmlns'] = 'http://earth.google.com/kml/2.1'
  doc = ET.SubElement(root, 'Document')
  open_tag = ET.SubElement(doc, 'open')
  open_tag.text = '1'
  self._CreateStopsFolder(schedule, doc)
  if self.split_routes:
  route_types = set()
  for route in schedule.GetRouteList():
  route_types.add(route.route_type)
  route_types = list(route_types)
  route_types.sort()
  for route_type in route_types:
  self._CreateRoutesFolder(schedule, doc, route_type)
  else:
  self._CreateRoutesFolder(schedule, doc)
  self._CreateShapesFolder(schedule, doc)
 
  # Make sure we pretty-print
  self._SetIndentation(root)
 
  # Now write the output
  if isinstance(output_file, file):
  output = output_file
  else:
  output = open(output_file, 'w')
  output.write("""<?xml version="1.0" encoding="UTF-8"?>\n""")
  ET.ElementTree(root).write(output, 'utf-8')
 
 
  def main():
  usage = \
  '''%prog [options] <input GTFS.zip> [<output.kml>]
 
  Reads GTFS file or directory <input GTFS.zip> and creates a KML file
  <output.kml> that contains the geographical features of the input. If
  <output.kml> is omitted a default filename is picked based on
  <input GTFS.zip>. By default the KML contains all stops and shapes.
 
  For more information see
  http://code.google.com/p/googletransitdatafeed/wiki/KMLWriter
  '''
 
  parser = util.OptionParserLongError(
  usage=usage, version='%prog '+transitfeed.__version__)
  parser.add_option('-t', '--showtrips', action='store_true',
  dest='show_trips',
  help='include the individual trips for each route')
  parser.add_option('-a', '--altitude_per_sec', action='store', type='float',
  dest='altitude_per_sec',
  help='if greater than 0 trips are drawn with time axis '
  'set to this many meters high for each second of time')
  parser.add_option('-s', '--splitroutes', action='store_true',
  dest='split_routes',
  help='split the routes by type')
  parser.add_option('-d', '--date_filter', action='store', type='string',
  dest='date_filter',
  help='Restrict to trips active on date YYYYMMDD')
  parser.add_option('-p', '--display_shape_points', action='store_true',
  dest='shape_points',
  help='shows the actual points along shapes')
 
  parser.set_defaults(altitude_per_sec=1.0)
  options, args = parser.parse_args()
 
  if len(args) < 1:
  parser.error('You must provide the path of an input GTFS file.')
 
  if args[0] == 'IWantMyCrash':
  raise Exception('For testCrashHandler')
 
  input_path = args[0]
  if len(args) >= 2:
  output_path = args[1]
  else:
  path = os.path.normpath(input_path)
  (feed_dir, feed) = os.path.split(path)
  if '.' in feed:
  feed = feed.rsplit('.', 1)[0] # strip extension
  output_filename = '%s.kml' % feed
  output_path = os.path.join(feed_dir, output_filename)
 
  loader = transitfeed.Loader(input_path,
  problems=transitfeed.ProblemReporter())
  feed = loader.Load()
  print "Writing %s" % output_path
  writer = KMLWriter()
  writer.show_trips = options.show_trips
  writer.altitude_per_sec = options.altitude_per_sec
  writer.split_routes = options.split_routes
  writer.date_filter = options.date_filter
  writer.shape_points = options.shape_points
  writer.Write(feed, output_path)
 
 
  if __name__ == '__main__':
  util.RunWithCrashHandler(main)
 
  #!/usr/bin/python2.5
  #
  # Copyright 2007 Google Inc. All Rights Reserved.
  #
  # Licensed under the Apache License, Version 2.0 (the "License");
  # you may not use this file except in compliance with the License.
  # You may obtain a copy of the License at
  #
  # http://www.apache.org/licenses/LICENSE-2.0
  #
  # Unless required by applicable law or agreed to in writing, software
  # distributed under the License is distributed on an "AS IS" BASIS,
  # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  # See the License for the specific language governing permissions and
  # limitations under the License.
 
  """A tool for merging two Google Transit feeds.
 
  Given two Google Transit feeds intending to cover two disjoint calendar
  intervals, this tool will attempt to produce a single feed by merging as much
  of the two feeds together as possible.
 
  For example, most stops remain the same throughout the year. Therefore, many
  of the stops given in stops.txt for the first feed represent the same stops
  given in the second feed. This tool will try to merge these stops so they
  only appear once in the resultant feed.
 
  A note on terminology: The first schedule is referred to as the "old" schedule;
  the second as the "new" schedule. The resultant schedule is referred to as
  the "merged" schedule. Names of things in the old schedule are variations of
  the letter "a" while names of things from the new schedule are variations of
  "b". The objects that represents routes, agencies and so on are called
  "entities".
 
  usage: merge.py [options] old_feed_path new_feed_path merged_feed_path
 
  Run merge.py --help for a list of the possible options.
  """
 
 
  __author__ = 'timothy.stranex@gmail.com (Timothy Stranex)'
 
 
  import datetime
  import optparse
  import os
  import re
  import sys
  import time
  import transitfeed
  from transitfeed import util
  import webbrowser
 
 
  # TODO:
  # 1. write unit tests that use actual data
  # 2. write a proper trip and stop_times merger
  # 3. add a serialised access method for stop_times and shapes to transitfeed
  # 4. add support for merging schedules which have some service period overlap
 
 
  def ApproximateDistanceBetweenPoints(pa, pb):
  """Finds the distance between two points on the Earth's surface.
 
  This is an approximate distance based on assuming that the Earth is a sphere.
  The points are specified by their lattitude and longitude.
 
  Args:
  pa: the first (lat, lon) point tuple
  pb: the second (lat, lon) point tuple
 
  Returns:
  The distance as a float in metres.
  """
  alat, alon = pa
  blat, blon = pb
  sa = transitfeed.Stop(lat=alat, lng=alon)
  sb = transitfeed.Stop(lat=blat, lng=blon)
  return transitfeed.ApproximateDistanceBetweenStops(sa, sb)
 
 
  class Error(Exception):
  """The base exception class for this module."""
 
 
  class MergeError(Error):
  """An error produced when two entities could not be merged."""
 
 
  class MergeProblemWithContext(transitfeed.ExceptionWithContext):
  """The base exception class for problem reporting in the merge module.
 
  Attributes:
  dataset_merger: The DataSetMerger that generated this problem.
  entity_type_name: The entity type of the dataset_merger. This is just
  dataset_merger.ENTITY_TYPE_NAME.
  ERROR_TEXT: The text used for generating the problem message.
  """
 
  def __init__(self, dataset_merger, problem_type=transitfeed.TYPE_WARNING,
  **kwargs):
  """Initialise the exception object.
 
  Args:
  dataset_merger: The DataSetMerger instance that generated this problem.
  problem_type: The problem severity. This should be set to one of the
  corresponding constants in transitfeed.
  kwargs: Keyword arguments to be saved as instance attributes.
  """
  kwargs['type'] = problem_type
  kwargs['entity_type_name'] = dataset_merger.ENTITY_TYPE_NAME
  transitfeed.ExceptionWithContext.__init__(self, None, None, **kwargs)
  self.dataset_merger = dataset_merger
 
  def FormatContext(self):
  return "In files '%s'" % self.dataset_merger.FILE_NAME
 
 
  class SameIdButNotMerged(MergeProblemWithContext):
  ERROR_TEXT = ("There is a %(entity_type_name)s in the old feed with id "
  "'%(id)s' and one from the new feed with the same id but "
  "they could not be merged:")
 
 
  class CalendarsNotDisjoint(MergeProblemWithContext):
  ERROR_TEXT = ("The service periods could not be merged since they are not "
  "disjoint.")
 
 
  class MergeNotImplemented(MergeProblemWithContext):
  ERROR_TEXT = ("The feed merger does not currently support merging in this "
  "file. The entries have been duplicated instead.")
 
 
  class FareRulesBroken(MergeProblemWithContext):
  ERROR_TEXT = ("The feed merger is currently unable to handle fare rules "
  "properly.")
 
 
  class MergeProblemReporter(transitfeed.ProblemReporter):
  """The base problem reporter class for the merge module."""
 
  def __init__(self, accumulator):
  transitfeed.ProblemReporter.__init__(self, accumulator)
 
  def SameIdButNotMerged(self, dataset, entity_id, reason):
  self.AddToAccumulator(
  SameIdButNotMerged(dataset, id=entity_id, reason=reason))
 
  def CalendarsNotDisjoint(self, dataset):
  self.AddToAccumulator(
  CalendarsNotDisjoint(dataset, problem_type=transitfeed.TYPE_ERROR))
 
  def MergeNotImplemented(self, dataset):
  self.AddToAccumulator(MergeNotImplemented(dataset))
 
  def FareRulesBroken(self, dataset):
  self.AddToAccumulator(FareRulesBroken(dataset))
 
 
  class HTMLProblemAccumulator(transitfeed.ProblemAccumulatorInterface):
  """A problem reporter which generates HTML output."""
 
  def __init__(self):
  """Initialise."""
  self._dataset_warnings = {} # a map from DataSetMergers to their warnings
  self._dataset_errors = {}
  self._warning_count = 0
  self._error_count = 0
 
  def _Report(self, merge_problem):
  if merge_problem.IsWarning():
  dataset_problems = self._dataset_warnings
  self._warning_count += 1
  else:
  dataset_problems = self._dataset_errors
  self._error_count += 1
 
  problem_html = '<li>%s</li>' % (
  merge_problem.FormatProblem().replace('\n', '<br>'))
  dataset_problems.setdefault(merge_problem.dataset_merger, []).append(
  problem_html)
 
  def _GenerateStatsTable(self, feed_merger):
  """Generate an HTML table of merge statistics.
 
  Args:
  feed_merger: The FeedMerger instance.
 
  Returns:
  The generated HTML as a string.
  """
  rows = []
  rows.append('<tr><th class="header"/><th class="header">Merged</th>'
  '<th class="header">Copied from old feed</th>'
  '<th class="header">Copied from new feed</th></tr>')
  for merger in feed_merger.GetMergerList():
  stats = merger.GetMergeStats()
  if stats is None:
  continue
  merged, not_merged_a, not_merged_b = stats
  rows.append('<tr><th class="header">%s</th>'
  '<td class="header">%d</td>'
  '<td class="header">%d</td>'
  '<td class="header">%d</td></tr>' %
  (merger.DATASET_NAME, merged, not_merged_a, not_merged_b))
  return '<table>%s</table>' % '\n'.join(rows)
 
  def _GenerateSection(self, problem_type):
  """Generate a listing of the given type of problems.
 
  Args:
  problem_type: The type of problem. This is one of the problem type
  constants from transitfeed.
 
  Returns:
  The generated HTML as a string.
  """
  if problem_type == transitfeed.TYPE_WARNING:
  dataset_problems = self._dataset_warnings
  heading = 'Warnings'
  else:
  dataset_problems = self._dataset_errors
  heading = 'Errors'
 
  if not dataset_problems:
  return ''
 
  prefix = '<h2 class="issueHeader">%s:</h2>' % heading
  dataset_sections = []
  for dataset_merger, problems in dataset_problems.items():
  dataset_sections.append('<h3>%s</h3><ol>%s</ol>' % (
  dataset_merger.FILE_NAME, '\n'.join(problems)))
  body = '\n'.join(dataset_sections)
  return prefix + body
 
  def _GenerateSummary(self):
  """Generate a summary of the warnings and errors.
 
  Returns:
  The generated HTML as a string.
  """
  items = []
  if self._dataset_errors:
  items.append('errors: %d' % self._error_count)
  if self._dataset_warnings:
  items.append('warnings: %d' % self._warning_count)
 
  if items:
  return '<p><span class="fail">%s</span></p>' % '<br>'.join(items)
  else:
  return '<p><span class="pass">feeds merged successfully</span></p>'
 
  def WriteOutput(self, output_file, feed_merger,
  old_feed_path, new_feed_path, merged_feed_path):
  """Write the HTML output to a file.
 
  Args:
  output_file: The file object that the HTML output will be written to.
  feed_merger: The FeedMerger instance.
  old_feed_path: The path to the old feed file as a string.
  new_feed_path: The path to the new feed file as a string
  merged_feed_path: The path to the merged feed file as a string. This
  may be None if no merged feed was written.
  """
  if merged_feed_path is None:
  html_merged_feed_path = ''
  else:
  html_merged_feed_path = '<p>Merged feed created: <code>%s</code></p>' % (
  merged_feed_path)
 
  html_header = """<html>
  <head>
  <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/>
  <title>Feed Merger Results</title>
  <style>
  body {font-family: Georgia, serif; background-color: white}
  .path {color: gray}
  div.problem {max-width: 500px}
  td,th {background-color: khaki; padding: 2px; font-family:monospace}
  td.problem,th.problem {background-color: dc143c; color: white; padding: 2px;
  font-family:monospace}
  table {border-spacing: 5px 0px; margin-top: 3px}
  h3.issueHeader {padding-left: 1em}
  span.pass {background-color: lightgreen}
  span.fail {background-color: yellow}
  .pass, .fail {font-size: 16pt; padding: 3px}
  ol,.unused {padding-left: 40pt}
  .header {background-color: white; font-family: Georgia, serif; padding: 0px}
  th.header {text-align: right; font-weight: normal; color: gray}
  .footer {font-size: 10pt}
  </style>
  </head>
  <body>
  <h1>Feed merger results</h1>
  <p>Old feed: <code>%(old_feed_path)s</code></p>
  <p>New feed: <code>%(new_feed_path)s</code></p>
  %(html_merged_feed_path)s""" % locals()
 
  html_stats = self._GenerateStatsTable(feed_merger)
  html_summary = self._GenerateSummary()
  html_errors = self._GenerateSection(transitfeed.TYPE_ERROR)
  html_warnings = self._GenerateSection(transitfeed.TYPE_WARNING)
 
  html_footer = """
  <div class="footer">
  Generated using transitfeed version %s on %s.
  </div>
  </body>
  </html>""" % (transitfeed.__version__,
  time.strftime('%B %d, %Y at %I:%M %p %Z'))
 
  output_file.write(transitfeed.EncodeUnicode(html_header))
  output_file.write(transitfeed.EncodeUnicode(html_stats))
  output_file.write(transitfeed.EncodeUnicode(html_summary))
  output_file.write(transitfeed.EncodeUnicode(html_errors))
  output_file.write(transitfeed.EncodeUnicode(html_warnings))
  output_file.write(transitfeed.EncodeUnicode(html_footer))
 
 
  def LoadWithoutErrors(path, memory_db):
  """"Return a Schedule object loaded from path; sys.exit for any error."""
  accumulator = transitfeed.ExceptionProblemAccumulator()
  loading_problem_handler = MergeProblemReporter(accumulator)
  try:
  schedule = transitfeed.Loader(path,
  memory_db=memory_db,
  problems=loading_problem_handler).Load()
  except transitfeed.ExceptionWithContext, e:
  print >>sys.stderr, (
  "\n\nFeeds to merge must load without any errors.\n"
  "While loading %s the following error was found:\n%s\n%s\n" %
  (path, e.FormatContext(), transitfeed.EncodeUnicode(e.FormatProblem())))
  sys.exit(1)
  return schedule
 
 
  class DataSetMerger(object):
  """A DataSetMerger is in charge of merging a set of entities.
 
  This is an abstract class and should be subclassed for each different entity
  type.
 
  Attributes:
  ENTITY_TYPE_NAME: The name of the entity type like 'agency' or 'stop'.
  FILE_NAME: The name of the file containing this data set like 'agency.txt'.
  DATASET_NAME: A name for the dataset like 'Agencies' or 'Stops'.
  """
 
  def __init__(self, feed_merger):
  """Initialise.
 
  Args:
  feed_merger: The FeedMerger.
  """
  self.feed_merger = feed_merger
  self._num_merged = 0
  self._num_not_merged_a = 0
  self._num_not_merged_b = 0
 
  def _MergeIdentical(self, a, b):
  """Tries to merge two values. The values are required to be identical.
 
  Args:
  a: The first value.
  b: The second value.
 
  Returns:
  The trivially merged value.
 
  Raises:
  MergeError: The values were not identical.
  """
  if a != b:
  raise MergeError("values must be identical ('%s' vs '%s')" %
  (transitfeed.EncodeUnicode(a),
  transitfeed.EncodeUnicode(b)))
  return b
 
  def _MergeIdenticalCaseInsensitive(self, a, b):
  """Tries to merge two strings.
 
  The string are required to be the same ignoring case. The second string is
  always used as the merged value.
 
  Args:
  a: The first string.
  b: The second string.
 
  Returns:
  The merged string. This is equal to the second string.
 
  Raises:
  MergeError: The strings were not the same ignoring case.
  """
  if a.lower() != b.lower():
  raise MergeError("values must be the same (case insensitive) "
  "('%s' vs '%s')" % (transitfeed.EncodeUnicode(a),
  transitfeed.EncodeUnicode(b)))
  return b
 
  def _MergeOptional(self, a, b):
  """Tries to merge two values which may be None.
 
  If both values are not None, they are required to be the same and the
  merge is trivial. If one of the values is None and the other is not None,
  the merge results in the one which is not None. If both are None, the merge
  results in None.
 
  Args:
  a: The first value.
  b: The second value.
 
  Returns:
  The merged value.
 
  Raises:
  MergeError: If both values are not None and are not the same.
  """
  if a and b:
  if a != b:
  raise MergeError("values must be identical if both specified "
  "('%s' vs '%s')" % (transitfeed.EncodeUnicode(a),
  transitfeed.EncodeUnicode(b)))
  return a or b
 
  def _MergeSameAgency(self, a_agency_id, b_agency_id):
  """Merge agency ids to the corresponding agency id in the merged schedule.
 
  Args:
  a_agency_id: an agency id from the old schedule
  b_agency_id: an agency id from the new schedule
 
  Returns:
  The agency id of the corresponding merged agency.
 
  Raises:
  MergeError: If a_agency_id and b_agency_id do not correspond to the same
  merged agency.
  KeyError: Either aaid or baid is not a valid agency id.
  """
  a_agency_id = (a_agency_id or
  self.feed_merger.a_schedule.GetDefaultAgency().agency_id)
  b_agency_id = (b_agency_id or
  self.feed_merger.b_schedule.GetDefaultAgency().agency_id)
  a_agency = self.feed_merger.a_schedule.GetAgency(
  a_agency_id)._migrated_entity
  b_agency = self.feed_merger.b_schedule.GetAgency(
  b_agency_id)._migrated_entity
  if a_agency != b_agency:
  raise MergeError('agency must be the same')
  return a_agency.agency_id
 
  def _SchemedMerge(self, scheme, a, b):
  """Tries to merge two entities according to a merge scheme.
 
  A scheme is specified by a map where the keys are entity attributes and the
  values are merge functions like Merger._MergeIdentical or
  Merger._MergeOptional. The entity is first migrated to the merged schedule.
  Then the attributes are individually merged as specified by the scheme.
 
  Args:
  scheme: The merge scheme, a map from entity attributes to merge
  functions.
  a: The entity from the old schedule.
  b: The entity from the new schedule.
 
  Returns:
  The migrated and merged entity.
 
  Raises:
  MergeError: One of the attributes was not able to be merged.
  """
  migrated = self._Migrate(b, self.feed_merger.b_schedule, False)
  for attr, merger in scheme.items():
  a_attr = getattr(a, attr, None)
  b_attr = getattr(b, attr, None)
  try:
  merged_attr = merger(a_attr, b_attr)
  except MergeError, merge_error:
  raise MergeError("Attribute '%s' could not be merged: %s." % (
  attr, merge_error))
  setattr(migrated, attr, merged_attr)
  return migrated
 
  def _MergeSameId(self):
  """Tries to merge entities based on their ids.
 
  This tries to merge only the entities from the old and new schedules which
  have the same id. These are added into the merged schedule. Entities which
  do not merge or do not have the same id as another entity in the other
  schedule are simply migrated into the merged schedule.
 
  This method is less flexible than _MergeDifferentId since it only tries
  to merge entities which have the same id while _MergeDifferentId tries to
  merge everything. However, it is faster and so should be used whenever
  possible.
 
  This method makes use of various methods like _Merge and _Migrate which
  are not implemented in the abstract DataSetMerger class. These method
  should be overwritten in a subclass to allow _MergeSameId to work with
  different entity types.
 
  Returns:
  The number of merged entities.
  """
  a_not_merged = []
  b_not_merged = []
 
  for a in self._GetIter(self.feed_merger.a_schedule):
  try:
  b = self._GetById(self.feed_merger.b_schedule, self._GetId(a))
  except KeyError:
  # there was no entity in B with the same id as a
  a_not_merged.append(a)
  continue
  try:
  self._Add(a, b, self._MergeEntities(a, b))
  self._num_merged += 1
  except MergeError, merge_error:
  a_not_merged.append(a)
  b_not_merged.append(b)
  self._ReportSameIdButNotMerged(self._GetId(a), merge_error)
 
  for b in self._GetIter(self.feed_merger.b_schedule):
  try:
  a = self._GetById(self.feed_merger.a_schedule, self._GetId(b))
  except KeyError:
  # there was no entity in A with the same id as b
  b_not_merged.append(b)
 
  # migrate the remaining entities
  for a in a_not_merged:
  newid = self._HasId(self.feed_merger.b_schedule, self._GetId(a))
  self._Add(a, None, self._Migrate(a, self.feed_merger.a_schedule, newid))
  for b in b_not_merged:
  newid = self._HasId(self.feed_merger.a_schedule, self._GetId(b))
  self._Add(None, b, self._Migrate(b, self.feed_merger.b_schedule, newid))
 
  self._num_not_merged_a = len(a_not_merged)
  self._num_not_merged_b = len(b_not_merged)
  return self._num_merged
 
  def _MergeByIdKeepNew(self):
  """Migrate all entities, discarding duplicates from the old/a schedule.
 
  This method migrates all entities from the new/b schedule. It then migrates
  entities in the old schedule where there isn't already an entity with the
  same ID.
 
  Unlike _MergeSameId this method migrates entities to the merged schedule
  before comparing their IDs. This allows transfers to be compared when they
  refer to stops that had their ID updated by migration.
 
  This method makes use of various methods like _Migrate and _Add which
  are not implemented in the abstract DataSetMerger class. These methods
  should be overwritten in a subclass to allow _MergeByIdKeepNew to work with
  different entity types.
 
  Returns:
  The number of merged entities.
  """
  # Maps from migrated ID to tuple(original object, migrated object)
  a_orig_migrated = {}
  b_orig_migrated = {}
 
  for orig in self._GetIter(self.feed_merger.a_schedule):
  migrated = self._Migrate(orig, self.feed_merger.a_schedule)
  a_orig_migrated[self._GetId(migrated)] = (orig, migrated)
 
  for orig in self._GetIter(self.feed_merger.b_schedule):
  migrated = self._Migrate(orig, self.feed_merger.b_schedule)
  b_orig_migrated[self._GetId(migrated)] = (orig, migrated)
 
  for migrated_id, (orig, migrated) in b_orig_migrated.items():
  self._Add(None, orig, migrated)
  self._num_not_merged_b += 1
 
  for migrated_id, (orig, migrated) in a_orig_migrated.items():
  if migrated_id not in b_orig_migrated:
  self._Add(orig, None, migrated)
  self._num_not_merged_a += 1
  return self._num_merged
 
  def _MergeDifferentId(self):
  """Tries to merge all possible combinations of entities.
 
  This tries to merge every entity in the old schedule with every entity in
  the new schedule. Unlike _MergeSameId, the ids do not need to match.
  However, _MergeDifferentId is much slower than _MergeSameId.
 
  This method makes use of various methods like _Merge and _Migrate which
  are not implemented in the abstract DataSetMerger class. These method
  should be overwritten in a subclass to allow _MergeSameId to work with
  different entity types.
 
  Returns:
  The number of merged entities.
  """
  # TODO: The same entity from A could merge with multiple from B.
  # This should either generate an error or should be prevented from
  # happening.
  for a in self._GetIter(self.feed_merger.a_schedule):
  for b in self._GetIter(self.feed_merger.b_schedule):
  try:
  self._Add(a, b, self._MergeEntities(a, b))
  self._num_merged += 1
  except MergeError:
  continue
 
  for a in self._GetIter(self.feed_merger.a_schedule):
  if a not in self.feed_merger.a_merge_map:
  self._num_not_merged_a += 1
  newid = self._HasId(self.feed_merger.b_schedule, self._GetId(a))
  self._Add(a, None,
  self._Migrate(a, self.feed_merger.a_schedule, newid))
  for b in self._GetIter(self.feed_merger.b_schedule):
  if b not in self.feed_merger.b_merge_map:
  self._num_not_merged_b += 1
  newid = self._HasId(self.feed_merger.a_schedule, self._GetId(b))
  self._Add(None, b,
  self._Migrate(b, self.feed_merger.b_schedule, newid))
 
  return self._num_merged
 
  def _ReportSameIdButNotMerged(self, entity_id, reason):
  """Report that two entities have the same id but could not be merged.
 
  Args:
  entity_id: The id of the entities.
  reason: A string giving a reason why they could not be merged.
  """
  self.feed_merger.problem_reporter.SameIdButNotMerged(self,
  entity_id,
  reason)
 
  def _GetIter(self, schedule):
  """Returns an iterator of entities for this data set in the given schedule.
 
  This method usually corresponds to one of the methods from
  transitfeed.Schedule like GetAgencyList() or GetRouteList().
 
  Note: This method must be overwritten in a subclass if _MergeSameId or
  _MergeDifferentId are to be used.
 
  Args:
  schedule: Either the old or new schedule from the FeedMerger.
 
  Returns:
  An iterator of entities.
  """
  raise NotImplementedError()
 
  def _GetById(self, schedule, entity_id):
  """Returns an entity given its id.
 
  This method usually corresponds to one of the methods from
  transitfeed.Schedule like GetAgency() or GetRoute().
 
  Note: This method must be overwritten in a subclass if _MergeSameId or
  _MergeDifferentId are to be used.
 
  Args:
  schedule: Either the old or new schedule from the FeedMerger.
  entity_id: The id string of the entity.
 
  Returns:
  The entity with the given id.
 
  Raises:
  KeyError: There is not entity with the given id.
  """
  raise NotImplementedError()
 
  def _HasId(self, schedule, entity_id):
  """Check if the schedule has an entity with the given id.
 
  Args:
  schedule: The transitfeed.Schedule instance to look in.
  entity_id: The id of the entity.
 
  Returns:
  True if the schedule has an entity with the id or False if not.
  """
  try:
  self._GetById(schedule, entity_id)
  has = True
  except KeyError:
  has = False
  return has
 
  def _MergeEntities(self, a, b):
  """Tries to merge the two entities.
 
  Note: This method must be overwritten in a subclass if _MergeSameId or
  _MergeDifferentId are to be used.
 
  Args:
  a: The entity from the old schedule.
  b: The entity from the new schedule.
 
  Returns:
  The merged migrated entity.
 
  Raises:
  MergeError: The entities were not able to be merged.
  """
  raise NotImplementedError()
 
  def _Migrate(self, entity, schedule, newid):
  """Migrates the entity to the merge schedule.
 
  This involves copying the entity and updating any ids to point to the
  corresponding entities in the merged schedule. If newid is True then
  a unique id is generated for the migrated entity using the original id
  as a prefix.
 
  Note: This method must be overwritten in a subclass if _MergeSameId or
  _MergeDifferentId are to be used.
 
  Args:
  entity: The entity to migrate.
  schedule: The schedule from the FeedMerger that contains ent.
  newid: Whether to generate a new id (True) or keep the original (False).
 
  Returns:
  The migrated entity.
  """
  raise NotImplementedError()
 
  def _Add(self, a, b, migrated):
  """Adds the migrated entity to the merged schedule.
 
  If a and b are both not None, it means that a and b were merged to create
  migrated. If one of a or b is None, it means that the other was not merged
  but has been migrated. This mapping is registered with the FeedMerger.
 
  Note: This method must be overwritten in a subclass if _MergeSameId or
  _MergeDifferentId are to be used.
 
  Args:
  a: The original entity from the old schedule.
  b: The original entity from the new schedule.
  migrated: The migrated entity for the merged schedule.
  """
  raise NotImplementedError()
 
  def _GetId(self, entity):
  """Returns the id of the given entity.
 
  Note: This method must be overwritten in a subclass if _MergeSameId or
  _MergeDifferentId are to be used.
 
  Args:
  entity: The entity.
 
  Returns:
  The id of the entity as a string or None.
  """
  raise NotImplementedError()
 
  def MergeDataSets(self):
  """Merge the data sets.
 
  This method is called in FeedMerger.MergeSchedule().
 
  Note: This method must be overwritten in a subclass.
 
  Returns:
  A boolean which is False if the dataset was unable to be merged and
  as a result the entire merge should be aborted. In this case, the problem
  will have been reported using the FeedMerger's problem reporter.
  """
  raise NotImplementedError()
 
  def GetMergeStats(self):
  """Returns some merge statistics.
 
  These are given as a tuple (merged, not_merged_a, not_merged_b) where
  "merged" is the number of merged entities, "not_merged_a" is the number of
  entities from the old schedule that were not merged and "not_merged_b" is
  the number of entities from the new schedule that were not merged.
 
  The return value can also be None. This means that there are no statistics
  for this entity type.
 
  The statistics are only available after MergeDataSets() has been called.
 
  Returns:
  Either the statistics tuple or None.
  """
  return (self._num_merged, self._num_not_merged_a, self._num_not_merged_b)
 
 
  class AgencyMerger(DataSetMerger):
  """A DataSetMerger for agencies."""
 
  ENTITY_TYPE_NAME = 'agency'
  FILE_NAME = 'agency.txt'
  DATASET_NAME = 'Agencies'
 
  def _GetIter(self, schedule):
  return schedule.GetAgencyList()
 
  def _GetById(self, schedule, agency_id):
  return schedule.GetAgency(agency_id)
 
  def _MergeEntities(self, a, b):
  """Merges two agencies.
 
  To be merged, they are required to have the same id, name, url and
  timezone. The remaining language attribute is taken from the new agency.
 
  Args:
  a: The first agency.
  b: The second agency.
 
  Returns:
  The merged agency.
 
  Raises:
  MergeError: The agencies could not be merged.
  """
 
  def _MergeAgencyId(a_agency_id, b_agency_id):
  """Merge two agency ids.
 
  The only difference between this and _MergeIdentical() is that the values
  None and '' are regarded as being the same.
 
  Args:
  a_agency_id: The first agency id.
  b_agency_id: The second agency id.
 
  Returns:
  The merged agency id.
 
  Raises:
  MergeError: The agency ids could not be merged.
  """
  a_agency_id = a_agency_id or None
  b_agency_id = b_agency_id or None
  return self._MergeIdentical(a_agency_id, b_agency_id)
 
  scheme = {'agency_id': _MergeAgencyId,
  'agency_name': self._MergeIdentical,
  'agency_url': self._MergeIdentical,
  'agency_timezone': self._MergeIdentical}
  return self._SchemedMerge(scheme, a, b)
 
  def _Migrate(self, entity, schedule, newid):
  a = transitfeed.Agency(field_dict=entity)
  if newid:
  a.agency_id = self.feed_merger.GenerateId(entity.agency_id)
  return a
 
  def _Add(self, a, b, migrated):
  self.feed_merger.Register(a, b, migrated)
  self.feed_merger.merged_schedule.AddAgencyObject(migrated)
 
  def _GetId(self, entity):
  return entity.agency_id
 
  def MergeDataSets(self):
  self._MergeSameId()
  return True
 
 
  class StopMerger(DataSetMerger):
  """A DataSetMerger for stops.
 
  Attributes:
  largest_stop_distance: The largest distance allowed between stops that
  will be merged in metres.
  """
 
  ENTITY_TYPE_NAME = 'stop'
  FILE_NAME = 'stops.txt'
  DATASET_NAME = 'Stops'
 
  largest_stop_distance = 10.0
 
  def __init__(self, feed_merger):
  DataSetMerger.__init__(self, feed_merger)
  self._merged = []
  self._a_not_merged = []
  self._b_not_merged = []
 
  def SetLargestStopDistance(self, distance):
  """Sets largest_stop_distance."""
  self.largest_stop_distance = distance
 
  def _GetIter(self, schedule):
  return schedule.GetStopList()
 
  def _GetById(self, schedule, stop_id):
  return schedule.GetStop(stop_id)
 
  def _MergeEntities(self, a, b):
  """Merges two stops.
 
  For the stops to be merged, they must have:
  - the same stop_id
  - the same stop_name (case insensitive)
  - the same zone_id
  - locations less than largest_stop_distance apart
  The other attributes can have arbitary changes. The merged attributes are
  taken from the new stop.
 
  Args:
  a: The first stop.
  b: The second stop.
 
  Returns:
  The merged stop.
 
  Raises:
  MergeError: The stops could not be merged.
  """
  distance = transitfeed.ApproximateDistanceBetweenStops(a, b)
  if distance > self.largest_stop_distance:
  raise MergeError("Stops are too far apart: %.1fm "
  "(largest_stop_distance is %.1fm)." %
  (distance, self.largest_stop_distance))
  scheme = {'stop_id': self._MergeIdentical,
  'stop_name': self._MergeIdenticalCaseInsensitive,
  'zone_id': self._MergeIdentical,
  'location_type': self._MergeIdentical}
  return self._SchemedMerge(scheme, a, b)
 
  def _Migrate(self, entity, schedule, newid):
  migrated_stop = transitfeed.Stop(field_dict=entity)
  if newid:
  migrated_stop.stop_id = self.feed_merger.GenerateId(entity.stop_id)
  return migrated_stop
 
  def _Add(self, a, b, migrated_stop):
  self.feed_merger.Register(a, b, migrated_stop)
 
  # The migrated_stop will be added to feed_merger.merged_schedule later
  # since adding must be done after the zone_ids have been finalized.
  if a and b:
  self._merged.append((a, b, migrated_stop))
  elif a:
  self._a_not_merged.append((a, migrated_stop))
  elif b:
  self._b_not_merged.append((b, migrated_stop))
 
  def _GetId(self, entity):
  return entity.stop_id
 
  def MergeDataSets(self):
  num_merged = self._MergeSameId()
  fm = self.feed_merger
 
  # now we do all the zone_id and parent_station mapping
 
  # the zone_ids for merged stops can be preserved
  for (a, b, merged_stop) in self._merged:
  assert a.zone_id == b.zone_id
  fm.a_zone_map[a.zone_id] = a.zone_id
  fm.b_zone_map[b.zone_id] = b.zone_id
  merged_stop.zone_id = a.zone_id
  if merged_stop.parent_station:
  # Merged stop has a parent. Update it to be the parent it had in b.
  parent_in_b = fm.b_schedule.GetStop(b.parent_station)
  merged_stop.parent_station = fm.b_merge_map[parent_in_b].stop_id
  fm.merged_schedule.AddStopObject(merged_stop)
 
  self._UpdateAndMigrateUnmerged(self._a_not_merged, fm.a_zone_map,
  fm.a_merge_map, fm.a_schedule)
  self._UpdateAndMigrateUnmerged(self._b_not_merged, fm.b_zone_map,
  fm.b_merge_map, fm.b_schedule)
 
  print 'Stops merged: %d of %d, %d' % (
  num_merged,
  len(fm.a_schedule.GetStopList()),
  len(fm.b_schedule.GetStopList()))
  return True
 
  def _UpdateAndMigrateUnmerged(self, not_merged_stops, zone_map, merge_map,
  schedule):
  """Correct references in migrated unmerged stops and add to merged_schedule.
 
  For stops migrated from one of the input feeds to the output feed update the
  parent_station and zone_id references to point to objects in the output
  feed. Then add the migrated stop to the new schedule.
 
  Args:
  not_merged_stops: list of stops from one input feed that have not been
  merged
  zone_map: map from zone_id in the input feed to zone_id in the output feed
  merge_map: map from Stop objects in the input feed to Stop objects in
  the output feed
  schedule: the input Schedule object
  """
  # for the unmerged stops, we use an already mapped zone_id if possible
  # if not, we generate a new one and add it to the map
  for stop, migrated_stop in not_merged_stops:
  if stop.zone_id in zone_map:
  migrated_stop.zone_id = zone_map[stop.zone_id]
  else:
  migrated_stop.zone_id = self.feed_merger.GenerateId(stop.zone_id)
  zone_map[stop.zone_id] = migrated_stop.zone_id
  if stop.parent_station:
  parent_original = schedule.GetStop(stop.parent_station)
  migrated_stop.parent_station = merge_map[parent_original].stop_id
  self.feed_merger.merged_schedule.AddStopObject(migrated_stop)
 
 
  class RouteMerger(DataSetMerger):
  """A DataSetMerger for routes."""
 
  ENTITY_TYPE_NAME = 'route'
  FILE_NAME = 'routes.txt'
  DATASET_NAME = 'Routes'
 
  def _GetIter(self, schedule):
  return schedule.GetRouteList()
 
  def _GetById(self, schedule, route_id):
  return schedule.GetRoute(route_id)
 
  def _MergeEntities(self, a, b):
  scheme = {'route_short_name': self._MergeIdentical,
  'route_long_name': self._MergeIdentical,
  'agency_id': self._MergeSameAgency,
  'route_type': self._MergeIdentical,
  'route_id': self._MergeIdentical,
  'route_url': self._MergeOptional,
  'route_color': self._MergeOptional,
  'route_text_color': self._MergeOptional}
  return self._SchemedMerge(scheme, a, b)
 
  def _Migrate(self, entity, schedule, newid):
  migrated_route = transitfeed.Route(field_dict=entity)
  if newid:
  migrated_route.route_id = self.feed_merger.GenerateId(entity.route_id)
  if entity.agency_id:
  original_agency = schedule.GetAgency(entity.agency_id)
  else:
  original_agency = schedule.GetDefaultAgency()
 
  migrated_route.agency_id = original_agency._migrated_entity.agency_id
  return migrated_route
 
  def _Add(self, a, b, migrated_route):
  self.feed_merger.Register(a, b, migrated_route)
  self.feed_merger.merged_schedule.AddRouteObject(migrated_route)
 
  def _GetId(self, entity):
  return entity.route_id
 
  def MergeDataSets(self):
  self._MergeSameId()
  return True
 
 
  class ServicePeriodMerger(DataSetMerger):
  """A DataSetMerger for service periods.
 
  Attributes:
  require_disjoint_calendars: A boolean specifying whether to require
  disjoint calendars when merging (True) or not (False).
  """
 
  ENTITY_TYPE_NAME = 'service period'
  FILE_NAME = 'calendar.txt/calendar_dates.txt'
  DATASET_NAME = 'Service Periods'
 
  def __init__(self, feed_merger):
  DataSetMerger.__init__(self, feed_merger)
  self.require_disjoint_calendars = True
 
  def _ReportSameIdButNotMerged(self, entity_id, reason):
  pass
 
  def _GetIter(self, schedule):
  return schedule.GetServicePeriodList()
 
  def _GetById(self, schedule, service_id):
  return schedule.GetServicePeriod(service_id)
 
  def _MergeEntities(self, a, b):
  """Tries to merge two service periods.
 
  Note: Currently this just raises a MergeError since service periods cannot
  be merged.
 
  Args:
  a: The first service period.
  b: The second service period.
 
  Returns:
  The merged service period.
 
  Raises:
  MergeError: When the service periods could not be merged.
  """
  raise MergeError('Cannot merge service periods')
 
  def _Migrate(self, original_service_period, schedule, newid):
  migrated_service_period = transitfeed.ServicePeriod()
  migrated_service_period.day_of_week = list(
  original_service_period.day_of_week)
  migrated_service_period.start_date = original_service_period.start_date
  migrated_service_period.end_date = original_service_period.end_date
  migrated_service_period.date_exceptions = dict(
  original_service_period.date_exceptions)
  if newid:
  migrated_service_period.service_id = self.feed_merger.GenerateId(
  original_service_period.service_id)
  else:
  migrated_service_period.service_id = original_service_period.service_id
  return migrated_service_period
 
  def _Add(self, a, b, migrated_service_period):
  self.feed_merger.Register(a, b, migrated_service_period)
  self.feed_merger.merged_schedule.AddServicePeriodObject(
  migrated_service_period)
 
  def _GetId(self, entity):
  return entity.service_id
 
  def MergeDataSets(self):
  if self.require_disjoint_calendars and not self.CheckDisjointCalendars():
  self.feed_merger.problem_reporter.CalendarsNotDisjoint(self)
  return False
  self._MergeSameId()
  self.feed_merger.problem_reporter.MergeNotImplemented(self)
  return True
 
  def DisjoinCalendars(self, cutoff):
  """Forces the old and new calendars to be disjoint about a cutoff date.
 
  This truncates the service periods of the old schedule so that service
  stops one day before the given cutoff date and truncates the new schedule
  so that service only begins on the cutoff date.
 
  Args:
  cutoff: The cutoff date as a string in YYYYMMDD format. The timezone
  is the same as used in the calendar.txt file.
  """
 
  def TruncatePeriod(service_period, start, end):
  """Truncate the service period to into the range [start, end].
 
  Args:
  service_period: The service period to truncate.
  start: The start date as a string in YYYYMMDD format.
  end: The end date as a string in YYYYMMDD format.
  """
  service_period.start_date = max(service_period.start_date, start)
  service_period.end_date = min(service_period.end_date, end)
  dates_to_delete = []
  for k in service_period.date_exceptions:
  if (k < start) or (k > end):
  dates_to_delete.append(k)
  for k in dates_to_delete:
  del service_period.date_exceptions[k]
 
  # find the date one day before cutoff
  year = int(cutoff[:4])
  month = int(cutoff[4:6])
  day = int(cutoff[6:8])
  cutoff_date = datetime.date(year, month, day)
  one_day_delta = datetime.timedelta(days=1)
  before = (cutoff_date - one_day_delta).strftime('%Y%m%d')
 
  for a in self.feed_merger.a_schedule.GetServicePeriodList():
  TruncatePeriod(a, 0, before)
  for b in self.feed_merger.b_schedule.GetServicePeriodList():
  TruncatePeriod(b, cutoff, '9'*8)
 
  def CheckDisjointCalendars(self):
  """Check whether any old service periods intersect with any new ones.
 
  This is a rather coarse check based on
  transitfeed.SevicePeriod.GetDateRange.
 
  Returns:
  True if the calendars are disjoint or False if not.
  """
  # TODO: Do an exact check here.
 
  a_service_periods = self.feed_merger.a_schedule.GetServicePeriodList()
  b_service_periods = self.feed_merger.b_schedule.GetServicePeriodList()
 
  for a_service_period in a_service_periods:
  a_start, a_end = a_service_period.GetDateRange()
  for b_service_period in b_service_periods:
  b_start, b_end = b_service_period.GetDateRange()
  overlap_start = max(a_start, b_start)
  overlap_end = min(a_end, b_end)
  if overlap_end >= overlap_start:
  return False
  return True
 
  def GetMergeStats(self):
  return None
 
 
  class FareMerger(DataSetMerger):
  """A DataSetMerger for fares."""
 
  ENTITY_TYPE_NAME = 'fare attribute'
  FILE_NAME = 'fare_attributes.txt'
  DATASET_NAME = 'Fares'
 
  def _GetIter(self, schedule):
  return schedule.GetFareAttributeList()
 
  def _GetById(self, schedule, fare_id):
  return schedule.GetFareAttribute(fare_id)
 
  def _MergeEntities(self, a, b):
  """Merges the fares if all the attributes are the same."""
  scheme = {'price': self._MergeIdentical,
  'currency_type': self._MergeIdentical,
  'payment_method': self._MergeIdentical,
  'transfers': self._MergeIdentical,
  'transfer_duration': self._MergeIdentical}
  return self._SchemedMerge(scheme, a, b)
 
  def _Migrate(self, original_fare, schedule, newid):
  migrated_fare = transitfeed.FareAttribute(
  field_dict=original_fare)
  if newid:
  migrated_fare.fare_id = self.feed_merger.GenerateId(
  original_fare.fare_id)
  return migrated_fare
 
  def _Add(self, a, b, migrated_fare):
  self.feed_merger.Register(a, b, migrated_fare)
  self.feed_merger.merged_schedule.AddFareAttributeObject(migrated_fare)
 
  def _GetId(self, fare):
  return fare.fare_id
 
  def MergeDataSets(self):
  num_merged = self._MergeSameId()
  print 'Fares merged: %d of %d, %d' % (
  num_merged,
  len(self.feed_merger.a_schedule.GetFareAttributeList()),
  len(self.feed_merger.b_schedule.GetFareAttributeList()))
  return True
 
 
  class TransferMerger(DataSetMerger):
  """A DataSetMerger for transfers.
 
  Copy every transfer from the a/old and b/new schedules into the merged
  schedule, translating from_stop_id and to_stop_id. Where a transfer ID is
  found in both source schedules only the one from the b/new schedule is
  migrated.
 
  Only one transfer is processed per ID. Duplicates within a schedule are
  ignored."""
 
  ENTITY_TYPE_NAME = 'transfer'
  FILE_NAME = 'transfers.txt'
  DATASET_NAME = 'Transfers'
 
  def _GetIter(self, schedule):
  return schedule.GetTransferIter()
 
  def _GetId(self, transfer):
  return transfer._ID()
 
  def _Migrate(self, original_transfer, schedule):
  # Make a copy of the original and then fix the stop_id references.
  migrated_transfer = transitfeed.Transfer(field_dict=original_transfer)
  if original_transfer.from_stop_id:
  migrated_transfer.from_stop_id = schedule.GetStop(
  original_transfer.from_stop_id)._migrated_entity.stop_id
  if migrated_transfer.to_stop_id:
  migrated_transfer.to_stop_id = schedule.GetStop(
  original_transfer.to_stop_id)._migrated_entity.stop_id
  return migrated_transfer
 
  def _Add(self, a, b, migrated_transfer):
  self.feed_merger.Register(a, b, migrated_transfer)
  self.feed_merger.merged_schedule.AddTransferObject(migrated_transfer)
 
  def MergeDataSets(self):
  # If both schedules contain rows with equivalent from_stop_id and
  # to_stop_id but different transfer_type or min_transfer_time only the
  # transfer from b will be in the output.
  self._MergeByIdKeepNew()
  print 'Transfers merged: %d of %d, %d' % (
  self._num_merged,
  # http://mail.python.org/pipermail/baypiggies/2008-August/003817.html
  # claims this is a good way to find number of items in an iterable.
  sum(1 for _ in self.feed_merger.a_schedule.GetTransferIter()),
  sum(1 for _ in self.feed_merger.b_schedule.GetTransferIter()))
  return True
 
 
  class ShapeMerger(DataSetMerger):
  """A DataSetMerger for shapes.
 
  In this implementation, merging shapes means just taking the new shape.
  The only conditions for a merge are that the shape_ids are the same and
  the endpoints of the old and new shapes are no further than
  largest_shape_distance apart.
 
  Attributes:
  largest_shape_distance: The largest distance between the endpoints of two
  shapes allowed for them to be merged in metres.
  """
 
  ENTITY_TYPE_NAME = 'shape'
  FILE_NAME = 'shapes.txt'
  DATASET_NAME = 'Shapes'
 
  largest_shape_distance = 10.0
 
  def SetLargestShapeDistance(self, distance):
  """Sets largest_shape_distance."""
  self.largest_shape_distance = distance
 
  def _GetIter(self, schedule):
  return schedule.GetShapeList()
 
  def _GetById(self, schedule, shape_id):
  return schedule.GetShape(shape_id)
 
  def _MergeEntities(self, a, b):
  """Merges the shapes by taking the new shape.
 
  Args:
  a: The first transitfeed.Shape instance.
  b: The second transitfeed.Shape instance.
 
  Returns:
  The merged shape.
 
  Raises:
  MergeError: If the ids are different or if the endpoints are further
  than largest_shape_distance apart.
  """
  if a.shape_id != b.shape_id:
  raise MergeError('shape_id must be the same')
 
  distance = max(ApproximateDistanceBetweenPoints(a.points[0][:2],
  b.points[0][:2]),
  ApproximateDistanceBetweenPoints(a.points[-1][:2],
  b.points[-1][:2]))
  if distance > self.largest_shape_distance:
  raise MergeError('The shape endpoints are too far away: %.1fm '
  '(largest_shape_distance is %.1fm)' %
  (distance, self.largest_shape_distance))
 
  return self._Migrate(b, self.feed_merger.b_schedule, False)
 
  def _Migrate(self, original_shape, schedule, newid):
  migrated_shape = transitfeed.Shape(original_shape.shape_id)
  if newid:
  migrated_shape.shape_id = self.feed_merger.GenerateId(
  original_shape.shape_id)
  for (lat, lon, dist) in original_shape.points:
  migrated_shape.AddPoint(lat=lat, lon=lon, distance=dist)
  return migrated_shape
 
  def _Add(self, a, b, migrated_shape):
  self.feed_merger.Register(a, b, migrated_shape)
  self.feed_merger.merged_schedule.AddShapeObject(migrated_shape)
 
  def _GetId(self, shape):
  return shape.shape_id
 
  def MergeDataSets(self):
  self._MergeSameId()
  return True
 
 
  class TripMerger(DataSetMerger):
  """A DataSetMerger for trips.
 
  This implementation makes no attempt to merge trips, it simply migrates
  them all to the merged feed.
  """
 
  ENTITY_TYPE_NAME = 'trip'
  FILE_NAME = 'trips.txt'
  DATASET_NAME = 'Trips'
 
  def _ReportSameIdButNotMerged(self, trip_id, reason):
  pass
 
  def _GetIter(self, schedule):
  return schedule.GetTripList()
 
  def _GetById(self, schedule, trip_id):
  return schedule.GetTrip(trip_id)
 
  def _MergeEntities(self, a, b):
  """Raises a MergeError because currently trips cannot be merged."""
  raise MergeError('Cannot merge trips')
 
  def _Migrate(self, original_trip, schedule, newid):
  migrated_trip = transitfeed.Trip(field_dict=original_trip)
  # Make new trip_id first. AddTripObject reports a problem if it conflicts
  # with an existing id.
  if newid:
  migrated_trip.trip_id = self.feed_merger.GenerateId(
  original_trip.trip_id)
  # Need to add trip to schedule before copying stoptimes
  self.feed_merger.merged_schedule.AddTripObject(migrated_trip,
  validate=False)
 
  if schedule == self.feed_merger.a_schedule:
  merge_map = self.feed_merger.a_merge_map
  else:
  merge_map = self.feed_merger.b_merge_map
 
  original_route = schedule.GetRoute(original_trip.route_id)
  migrated_trip.route_id = merge_map[original_route].route_id
 
  original_service_period = schedule.GetServicePeriod(
  original_trip.service_id)
  migrated_trip.service_id = merge_map[original_service_period].service_id
 
  if original_trip.block_id:
  migrated_trip.block_id = '%s_%s' % (
  self.feed_merger.GetScheduleName(schedule),
  original_trip.block_id)
 
  if original_trip.shape_id:
  original_shape = schedule.GetShape(original_trip.shape_id)
  migrated_trip.shape_id = merge_map[original_shape].shape_id
 
  for original_stop_time in original_trip.GetStopTimes():
  migrated_stop_time = transitfeed.StopTime(
  None,
  merge_map[original_stop_time.stop],
  original_stop_time.arrival_time,
  original_stop_time.departure_time,
  original_stop_time.stop_headsign,
  original_stop_time.pickup_type,
  original_stop_time.drop_off_type,
  original_stop_time.shape_dist_traveled,
  original_stop_time.arrival_secs,
  original_stop_time.departure_secs)
  migrated_trip.AddStopTimeObject(migrated_stop_time)
 
  for headway_period in original_trip.GetFrequencyTuples():
  migrated_trip.AddFrequency(*headway_period)
 
  return migrated_trip
 
  def _Add(self, a, b, migrated_trip):
  # Validate now, since it wasn't done in _Migrate
  migrated_trip.Validate(self.feed_merger.merged_schedule.problem_reporter)
  self.feed_merger.Register(a, b, migrated_trip)
 
  def _GetId(self, trip):
  return trip.trip_id
 
  def MergeDataSets(self):
  self._MergeSameId()
  self.feed_merger.problem_reporter.MergeNotImplemented(self)
  return True
 
  def GetMergeStats(self):
  return None
 
 
  class FareRuleMerger(DataSetMerger):
  """A DataSetMerger for fare rules."""
 
  ENTITY_TYPE_NAME = 'fare rule'
  FILE_NAME = 'fare_rules.txt'
  DATASET_NAME = 'Fare Rules'
 
  def MergeDataSets(self):
  """Merge the fare rule datasets.
 
  The fare rules are first migrated. Merging is done by removing any
  duplicate rules.
 
  Returns:
  True since fare rules can always be merged.
  """
  rules = set()
  for (schedule, merge_map, zone_map) in ([self.feed_merger.a_schedule,
  self.feed_merger.a_merge_map,
  self.feed_merger.a_zone_map],
  [self.feed_merger.b_schedule,
  self.feed_merger.b_merge_map,
  self.feed_merger.b_zone_map]):
  for fare in schedule.GetFareAttributeList():
  for fare_rule in fare.GetFareRuleList():
  fare_id = merge_map[
  schedule.GetFareAttribute(fare_rule.fare_id)].fare_id
  route_id = (fare_rule.route_id and
  merge_map[schedule.GetRoute(fare_rule.route_id)].route_id)
  origin_id = (fare_rule.origin_id and
  zone_map[fare_rule.origin_id])
  destination_id = (fare_rule.destination_id and
  zone_map[fare_rule.destination_id])
  contains_id = (fare_rule.contains_id and
  zone_map[fare_rule.contains_id])
  rules.add((fare_id, route_id, origin_id, destination_id,
  contains_id))
  for fare_rule_tuple in rules:
  migrated_fare_rule = transitfeed.FareRule(*fare_rule_tuple)
  self.feed_merger.merged_schedule.AddFareRuleObject(migrated_fare_rule)
 
  if rules:
  self.feed_merger.problem_reporter.FareRulesBroken(self)
  print 'Fare Rules: union has %d fare rules' % len(rules)
  return True
 
  def GetMergeStats(self):
  return None
 
 
  class FeedMerger(object):
  """A class for merging two whole feeds.
 
  This class takes two instances of transitfeed.Schedule and uses
  DataSetMerger instances to merge the feeds and produce the resultant
  merged feed.
 
  Attributes:
  a_schedule: The old transitfeed.Schedule instance.
  b_schedule: The new transitfeed.Schedule instance.
  problem_reporter: The merge problem reporter.
  merged_schedule: The merged transitfeed.Schedule instance.
  a_merge_map: A map from old entities to merged entities.
  b_merge_map: A map from new entities to merged entities.
  a_zone_map: A map from old zone ids to merged zone ids.
  b_zone_map: A map from new zone ids to merged zone ids.
  """
 
  def __init__(self, a_schedule, b_schedule, merged_schedule,
  problem_reporter):
  """Initialise the merger.
 
  Once this initialiser has been called, a_schedule and b_schedule should
  not be modified.
 
  Args:
  a_schedule: The old schedule, an instance of transitfeed.Schedule.
  b_schedule: The new schedule, an instance of transitfeed.Schedule.
  problem_reporter: The problem reporter, an instance of
  transitfeed.ProblemReporter.
  """
  self.a_schedule = a_schedule
  self.b_schedule = b_schedule
  self.merged_schedule = merged_schedule
  self.a_merge_map = {}
  self.b_merge_map = {}
  self.a_zone_map = {}
  self.b_zone_map = {}
  self._mergers = []
  self._idnum = max(self._FindLargestIdPostfixNumber(self.a_schedule),
  self._FindLargestIdPostfixNumber(self.b_schedule))
 
  self.problem_reporter = problem_reporter
 
  def _FindLargestIdPostfixNumber(self, schedule):
  """Finds the largest integer used as the ending of an id in the schedule.
 
  Args:
  schedule: The schedule to check.
 
  Returns:
  The maximum integer used as an ending for an id.
  """
  postfix_number_re = re.compile('(\d+)$')
 
  def ExtractPostfixNumber(entity_id):
  """Try to extract an integer from the end of entity_id.
 
  If entity_id is None or if there is no integer ending the id, zero is
  returned.
 
  Args:
  entity_id: An id string or None.
 
  Returns:
  An integer ending the entity_id or zero.
  """
  if entity_id is None:
  return 0
  match = postfix_number_re.search(entity_id)
  if match is not None:
  return int(match.group(1))
  else:
  return 0
 
  id_data_sets = {'agency_id': schedule.GetAgencyList(),
  'stop_id': schedule.GetStopList(),
  'route_id': schedule.GetRouteList(),
  'trip_id': schedule.GetTripList(),
  'service_id': schedule.GetServicePeriodList(),
  'fare_id': schedule.GetFareAttributeList(),
  'shape_id': schedule.GetShapeList()}
 
  max_postfix_number = 0
  for id_name, entity_list in id_data_sets.items():
  for entity in entity_list:
  entity_id = getattr(entity, id_name)
  postfix_number = ExtractPostfixNumber(entity_id)
  max_postfix_number = max(max_postfix_number, postfix_number)
  return max_postfix_number
 
  def GetScheduleName(self, schedule):
  """Returns a single letter identifier for the schedule.
 
  This only works for the old and new schedules which return 'a' and 'b'
  respectively. The purpose of such identifiers is for generating ids.
 
  Args:
  schedule: The transitfeed.Schedule instance.
 
  Returns:
  The schedule identifier.
 
  Raises:
  KeyError: schedule is not the old or new schedule.
  """
  return {self.a_schedule: 'a', self.b_schedule: 'b'}[schedule]
 
  def GenerateId(self, entity_id=None):
  """Generate a unique id based on the given id.
 
  This is done by appending a counter which is then incremented. The
  counter is initialised at the maximum number used as an ending for
  any id in the old and new schedules.
 
  Args:
  entity_id: The base id string. This is allowed to be None.
 
  Returns:
  The generated id.
  """
  self._idnum += 1
  if entity_id:
  return '%s_merged_%d' % (entity_id, self._idnum)
  else:
  return 'merged_%d' % self._idnum
 
  def Register(self, a, b, migrated_entity):
  """Registers a merge mapping.
 
  If a and b are both not None, this means that entities a and b were merged
  to produce migrated_entity. If one of a or b are not None, then it means
  it was not merged but simply migrated.
 
  The effect of a call to register is to update a_merge_map and b_merge_map
  according to the merge. Also the private attributes _migrated_entity of a
  and b are set to migrated_entity.
 
  Args:
  a: The entity from the old feed or None.
  b: The entity from the new feed or None.
  migrated_entity: The migrated entity.
  """
  # There are a few places where code needs to find the corresponding
  # migrated entity of an object without knowing in which original schedule
  # the entity started. With a_merge_map and b_merge_map both have to be
  # checked. Use of the _migrated_entity attribute allows the migrated entity
  # to be directly found without the schedule. The merge maps also require
  # that all objects be hashable. GenericGTFSObject is at the moment, but
  # this is a bug. See comment in transitfeed.GenericGTFSObject.
  if a is not None:
  self.a_merge_map[a] = migrated_entity
  a._migrated_entity = migrated_entity
  if b is not None:
  self.b_merge_map[b] = migrated_entity
  b._migrated_entity = migrated_entity
 
  def AddMerger(self, merger):
  """Add a DataSetMerger to be run by Merge().
 
  Args:
  merger: The DataSetMerger instance.
  """
  self._mergers.append(merger)
 
  def AddDefaultMergers(self):
  """Adds the default DataSetMergers defined in this module."""
  self.AddMerger(AgencyMerger(self))
  self.AddMerger(StopMerger(self))
  self.AddMerger(RouteMerger(self))
  self.AddMerger(ServicePeriodMerger(self))
  self.AddMerger(FareMerger(self))
  self.AddMerger(ShapeMerger(self))
  self.AddMerger(TripMerger(self))
  self.AddMerger(FareRuleMerger(self))
 
  def GetMerger(self, cls):
  """Looks for an added DataSetMerger derived from the given class.
 
  Args:
  cls: A class derived from DataSetMerger.
 
  Returns:
  The matching DataSetMerger instance.
 
  Raises:
  LookupError: No matching DataSetMerger has been added.
  """
  for merger in self._mergers:
  if isinstance(merger, cls):
  return merger
  raise LookupError('No matching DataSetMerger found')
 
  def GetMergerList(self):
  """Returns the list of DataSetMerger instances that have been added."""
  return self._mergers
 
  def MergeSchedules(self):
  """Merge the schedules.
 
  This is done by running the DataSetMergers that have been added with
  AddMerger() in the order that they were added.
 
  Returns:
  True if the merge was successful.
  """
  for merger in self._mergers:
  if not merger.MergeDataSets():
  return False
  return True
 
  def GetMergedSchedule(self):
  """Returns the merged schedule.
 
  This will be empty before MergeSchedules() is called.
 
  Returns:
  The merged schedule.
  """
  return self.merged_schedule
 
 
  def main():
  """Run the merge driver program."""
  usage = \
  """%prog [options] <input GTFS a.zip> <input GTFS b.zip> <output GTFS.zip>
 
  Merges <input GTFS a.zip> and <input GTFS b.zip> into a new GTFS file
  <output GTFS.zip>.
 
  For more information see
  http://code.google.com/p/googletransitdatafeed/wiki/Merge
  """
 
  parser = util.OptionParserLongError(
  usage=usage, version='%prog '+transitfeed.__version__)
  parser.add_option('--cutoff_date',
  dest='cutoff_date',
  default=None,
  help='a transition date from the old feed to the new '
  'feed in the format YYYYMMDD')
  parser.add_option('--largest_stop_distance',
  dest='largest_stop_distance',
  default=StopMerger.largest_stop_distance,
  help='the furthest distance two stops can be apart and '
  'still be merged, in metres')
  parser.add_option('--largest_shape_distance',
  dest='largest_shape_distance',
  default=ShapeMerger.largest_shape_distance,
  help='the furthest distance the endpoints of two shapes '
  'can be apart and the shape still be merged, in metres')
  parser.add_option('--html_output_path',
  dest='html_output_path',
  default='merge-results.html',
  help='write the html output to this file')
  parser.add_option('--no_browser',
  dest='no_browser',
  action='store_true',
  help='prevents the merge results from being opened in a '
  'browser')
  parser.add_option('-m', '--memory_db', dest='memory_db', action='store_true',
  help='Use in-memory sqlite db instead of a temporary file. '
  'It is faster but uses more RAM.')
  parser.set_defaults(memory_db=False)
  (options, args) = parser.parse_args()
 
  if len(args) != 3:
  parser.error('You did not provide all required command line arguments.')
 
  old_feed_path = os.path.abspath(args[0])
  new_feed_path = os.path.abspath(args[1])
  merged_feed_path = os.path.abspath(args[2])
 
  if old_feed_path.find("IWantMyCrash") != -1:
  # See test/testmerge.py
  raise Exception('For testing the merge crash handler.')
 
  a_schedule = LoadWithoutErrors(old_feed_path, options.memory_db)
  b_schedule = LoadWithoutErrors(new_feed_path, options.memory_db)
  merged_schedule = transitfeed.Schedule(memory_db=options.memory_db)
  accumulator = HTMLProblemAccumulator()
  problem_reporter = MergeProblemReporter(accumulator)
  feed_merger = FeedMerger(a_schedule, b_schedule, merged_schedule,
  problem_reporter)
  feed_merger.AddDefaultMergers()
 
  feed_merger.GetMerger(StopMerger).SetLargestStopDistance(float(
  options.largest_stop_distance))
  feed_merger.GetMerger(ShapeMerger).SetLargestShapeDistance(float(
  options.largest_shape_distance))
 
  if options.cutoff_date is not None:
  service_period_merger = feed_merger.GetMerger(ServicePeriodMerger)
  service_period_merger.DisjoinCalendars(options.cutoff_date)
 
  if feed_merger.MergeSchedules():
  feed_merger.GetMergedSchedule().WriteGoogleTransitFeed(merged_feed_path)
  else:
  merged_feed_path = None
 
  output_file = file(options.html_output_path, 'w')
  accumulator.WriteOutput(output_file, feed_merger,
  old_feed_path, new_feed_path, merged_feed_path)
  output_file.close()
 
  if not options.no_browser:
  webbrowser.open('file://%s' % os.path.abspath(options.html_output_path))
 
 
  if __name__ == '__main__':
  util.RunWithCrashHandler(main)
 
  #!/usr/bin/python2.5
 
  # Copyright (C) 2007 Google Inc.
  #
  # Licensed under the Apache License, Version 2.0 (the "License");
  # you may not use this file except in compliance with the License.
  # You may obtain a copy of the License at
  #
  # http://www.apache.org/licenses/LICENSE-2.0
  #
  # Unless required by applicable law or agreed to in writing, software
  # distributed under the License is distributed on an "AS IS" BASIS,
  # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  # See the License for the specific language governing permissions and
  # limitations under the License.
 
  """
  An example application that uses the transitfeed module.
 
  You must provide a Google Maps API key.
  """
 
 
  import BaseHTTPServer, sys, urlparse
  import bisect
  from gtfsscheduleviewer.marey_graph import MareyGraph
  import gtfsscheduleviewer
  import mimetypes
  import os.path
  import re
  import signal
  import simplejson
  import socket
  import time
  import transitfeed
  from transitfeed import util
  import urllib
 
 
  # By default Windows kills Python with Ctrl+Break. Instead make Ctrl+Break
  # raise a KeyboardInterrupt.
  if hasattr(signal, 'SIGBREAK'):
  signal.signal(signal.SIGBREAK, signal.default_int_handler)
 
 
  mimetypes.add_type('text/plain', '.vbs')
 
 
  class ResultEncoder(simplejson.JSONEncoder):
  def default(self, obj):
  try:
  iterable = iter(obj)
  except TypeError:
  pass
  else:
  return list(iterable)
  return simplejson.JSONEncoder.default(self, obj)
 
  # Code taken from
  # http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/425210/index_txt
  # An alternate approach is shown at
  # http://mail.python.org/pipermail/python-list/2003-July/212751.html
  # but it requires multiple threads. A sqlite object can only be used from one
  # thread.
  class StoppableHTTPServer(BaseHTTPServer.HTTPServer):
  def server_bind(self):
  BaseHTTPServer.HTTPServer.server_bind(self)
  self.socket.settimeout(1)
  self._run = True
 
  def get_request(self):
  while self._run:
  try:
  sock, addr = self.socket.accept()
  sock.settimeout(None)
  return (sock, addr)
  except socket.timeout:
  pass
 
  def stop(self):
  self._run = False
 
  def serve(self):
  while self._run:
  self.handle_request()
 
 
  def StopToTuple(stop):
  """Return tuple as expected by javascript function addStopMarkerFromList"""
  return (stop.stop_id, stop.stop_name, float(stop.stop_lat),
  float(stop.stop_lon), stop.location_type)
 
 
  class ScheduleRequestHandler(BaseHTTPServer.BaseHTTPRequestHandler):
  def do_GET(self):
  scheme, host, path, x, params, fragment = urlparse.urlparse(self.path)
  parsed_params = {}
  for k in params.split('&'):
  k = urllib.unquote(k)
  if '=' in k:
  k, v = k.split('=', 1)
  parsed_params[k] = unicode(v, 'utf8')
  else:
  parsed_params[k] = ''
 
  if path == '/':
  return self.handle_GET_home()
 
  m = re.match(r'/json/([a-z]{1,64})', path)
  if m:
  handler_name = 'handle_json_GET_%s' % m.group(1)
  handler = getattr(self, handler_name, None)
  if callable(handler):
  return self.handle_json_wrapper_GET(handler, parsed_params)
 
  # Restrict allowable file names to prevent relative path attacks etc
  m = re.match(r'/file/([a-z0-9_-]{1,64}\.?[a-z0-9_-]{1,64})$', path)
  if m and m.group(1):
  try:
  f, mime_type = self.OpenFile(m.group(1))
  return self.handle_static_file_GET(f, mime_type)
  except IOError, e:
  print "Error: unable to open %s" % m.group(1)
  # Ignore and treat as 404
 
  m = re.match(r'/([a-z]{1,64})', path)
  if m:
  handler_name = 'handle_GET_%s' % m.group(1)
  handler = getattr(self, handler_name, None)
  if callable(handler):
  return handler(parsed_params)
 
  return self.handle_GET_default(parsed_params, path)
 
  def OpenFile(self, filename):
  """Try to open filename in the static files directory of this server.
  Return a tuple (file object, string mime_type) or raise an exception."""
  (mime_type, encoding) = mimetypes.guess_type(filename)
  assert mime_type
  # A crude guess of when we should use binary mode. Without it non-unix
  # platforms may corrupt binary files.
  if mime_type.startswith('text/'):
  mode = 'r'
  else:
  mode = 'rb'
  return open(os.path.join(self.server.file_dir, filename), mode), mime_type
 
  def handle_GET_default(self, parsed_params, path):
  self.send_error(404)
 
  def handle_static_file_GET(self, fh, mime_type):
  content = fh.read()
  self.send_response(200)
  self.send_header('Content-Type', mime_type)
  self.send_header('Content-Length', str(len(content)))
  self.end_headers()
  self.wfile.write(content)
 
  def AllowEditMode(self):
  return False
 
  def handle_GET_home(self):
  schedule = self.server.schedule
  (min_lat, min_lon, max_lat, max_lon) = schedule.GetStopBoundingBox()
  forbid_editing = ('true', 'false')[self.AllowEditMode()]
 
  agency = ', '.join(a.agency_name for a in schedule.GetAgencyList()).encode('utf-8')
 
  key = self.server.key
  host = self.server.host
 
  # A very simple template system. For a fixed set of values replace [xxx]
  # with the value of local variable xxx
  f, _ = self.OpenFile('index.html')
  content = f.read()
  for v in ('agency', 'min_lat', 'min_lon', 'max_lat', 'max_lon', 'key',
  'host', 'forbid_editing'):
  content = content.replace('[%s]' % v, str(locals()[v]))
 
  self.send_response(200)
  self.send_header('Content-Type', 'text/html')
  self.send_header('Content-Length', str(len(content)))
  self.end_headers()
  self.wfile.write(content)
 
  def handle_json_GET_routepatterns(self, params):
  """Given a route_id generate a list of patterns of the route. For each
  pattern include some basic information and a few sample trips."""
  schedule = self.server.schedule
  route = schedule.GetRoute(params.get('route', None))
  if not route:
  self.send_error(404)
  return
  time = int(params.get('time', 0))
  date = params.get('date', "")
  sample_size = 3 # For each pattern return the start time for this many trips
 
  pattern_id_trip_dict = route.GetPatternIdTripDict()
  patterns = []
 
  for pattern_id, trips in pattern_id_trip_dict.items():
  time_stops = trips[0].GetTimeStops()
  if not time_stops:
  continue
  has_non_zero_trip_type = False;
 
  # Iterating over a copy so we can remove from trips inside the loop
  trips_with_service = []
  for trip in trips:
  service_id = trip.service_id
  service_period = schedule.GetServicePeriod(service_id)
 
  if date and not service_period.IsActiveOn(date):
  continue
  trips_with_service.append(trip)
 
  if trip['trip_type'] and trip['trip_type'] != '0':
  has_non_zero_trip_type = True
 
  # We're only interested in the trips that do run on the specified date
  trips = trips_with_service
 
  name = u'%s to %s, %d stops' % (time_stops[0][2].stop_name, time_stops[-1][2].stop_name, len(time_stops))
  transitfeed.SortListOfTripByTime(trips)
 
  num_trips = len(trips)
  if num_trips <= sample_size:
  start_sample_index = 0
  num_after_sample = 0
  else:
  # Will return sample_size trips that start after the 'time' param.
 
  # Linear search because I couldn't find a built-in way to do a binary
  # search with a custom key.
  start_sample_index = len(trips)
  for i, trip in enumerate(trips):
  if trip.GetStartTime() >= time:
  start_sample_index = i
  break
 
  num_after_sample = num_trips - (start_sample_index + sample_size)
  if num_after_sample < 0:
  # Less than sample_size trips start after 'time' so return all the
  # last sample_size trips.
  num_after_sample = 0
  start_sample_index = num_trips - sample_size
 
  sample = []
  for t in trips[start_sample_index:start_sample_index + sample_size]:
  sample.append( (t.GetStartTime(), t.trip_id) )
 
  patterns.append((name, pattern_id, start_sample_index, sample,
  num_after_sample, (0,1)[has_non_zero_trip_type]))
 
  patterns.sort()
  return patterns
 
  def handle_json_wrapper_GET(self, handler, parsed_params):
  """Call handler and output the return value in JSON."""
  schedule = self.server.schedule
  result = handler(parsed_params)
  content = ResultEncoder().encode(result)
  self.send_response(200)
  self.send_header('Content-Type', 'text/plain')
  self.send_header('Content-Length', str(len(content)))
  self.end_headers()
  self.wfile.write(content)
 
  def handle_json_GET_routes(self, params):
  """Return a list of all routes."""
  schedule = self.server.schedule
  result = []
  for r in schedule.GetRouteList():
  result.append( (r.route_id, r.route_short_name, r.route_long_name) )
  result.sort(key = lambda x: x[1:3])
  return result
 
  def handle_json_GET_routerow(self, params):
  schedule = self.server.schedule
  route = schedule.GetRoute(params.get('route', None))
  return [transitfeed.Route._FIELD_NAMES, route.GetFieldValuesTuple()]
 
  def handle_json_GET_triprows(self, params):
  """Return a list of rows from the feed file that are related to this
  trip."""
  schedule = self.server.schedule
  try:
  trip = schedule.GetTrip(params.get('trip', None))
  except KeyError:
  # if a non-existent trip is searched for, the return nothing
  return
  route = schedule.GetRoute(trip.route_id)
  trip_row = dict(trip.iteritems())
  route_row = dict(route.iteritems())
  return [['trips.txt', trip_row], ['routes.txt', route_row]]
 
  def handle_json_GET_tripstoptimes(self, params):
  schedule = self.server.schedule
  try:
  trip = schedule.GetTrip(params.get('trip'))
  except KeyError:
  # if a non-existent trip is searched for, the return nothing
  return
  time_stops = trip.GetTimeStops()
  stops = []
  times = []
  for arr,dep,stop in time_stops:
  stops.append(StopToTuple(stop))
  times.append(arr)
  return [stops, times]
 
  def handle_json_GET_tripshape(self, params):
  schedule = self.server.schedule
  try:
  trip = schedule.GetTrip(params.get('trip'))
  except KeyError:
  # if a non-existent trip is searched for, the return nothing
  return
  points = []
  if trip.shape_id:
  shape = schedule.GetShape(trip.shape_id)
  for (lat, lon, dist) in shape.points:
  points.append((lat, lon))
  else:
  time_stops = trip.GetTimeStops()
  for arr,dep,stop in time_stops:
  points.append((stop.stop_lat, stop.stop_lon))
  return points
 
  def handle_json_GET_neareststops(self, params):
  """Return a list of the nearest 'limit' stops to 'lat', 'lon'"""
  schedule = self.server.schedule
  lat = float(params.get('lat'))
  lon = float(params.get('lon'))
  limit = int(params.get('limit'))
  stops = schedule.GetNearestStops(lat=lat, lon=lon, n=limit)
  return [StopToTuple(s) for s in stops]
 
  def handle_json_GET_boundboxstops(self, params):
  """Return a list of up to 'limit' stops within bounding box with 'n','e'
  and 's','w' in the NE and SW corners. Does not handle boxes crossing
  longitude line 180."""
  schedule = self.server.schedule
  n = float(params.get('n'))
  e = float(params.get('e'))
  s = float(params.get('s'))
  w = float(params.get('w'))
  limit = int(params.get('limit'))
  stops = schedule.GetStopsInBoundingBox(north=n, east=e, south=s, west=w, n=limit)
  return [StopToTuple(s) for s in stops]
 
  def handle_json_GET_stopsearch(self, params):
  schedule = self.server.schedule
  query = params.get('q', None).lower()
  matches = []
  for s in schedule.GetStopList():
  if s.stop_id.lower().find(query) != -1 or s.stop_name.lower().find(query) != -1:
  matches.append(StopToTuple(s))
  return matches
 
  def handle_json_GET_stoptrips(self, params):
  """Given a stop_id and time in seconds since midnight return the next
  trips to visit the stop."""
  schedule = self.server.schedule
  stop = schedule.GetStop(params.get('stop', None))
  time = int(params.get('time', 0))
  date = params.get('date', "")
 
  time_trips = stop.GetStopTimeTrips(schedule)
  time_trips.sort() # OPT: use bisect.insort to make this O(N*ln(N)) -> O(N)
  # Keep the first 5 after param 'time'.
  # Need make a tuple to find correct bisect point
  time_trips = time_trips[bisect.bisect_left(time_trips, (time, 0)):]
  time_trips = time_trips[:5]
  # TODO: combine times for a route to show next 2 departure times
  result = []
  for time, (trip, index), tp in time_trips:
  service_id = trip.service_id
  service_period = schedule.GetServicePeriod(service_id)
  if date and not service_period.IsActiveOn(date):
  continue
  headsign = None
  # Find the most recent headsign from the StopTime objects
  for stoptime in trip.GetStopTimes()[index::-1]:
  if stoptime.stop_headsign:
  headsign = stoptime.stop_headsign
  break
  # If stop_headsign isn't found, look for a trip_headsign
  if not headsign:
  headsign = trip.trip_headsign
  route = schedule.GetRoute(trip.route_id)
  trip_name = ''
  if route.route_short_name:
  trip_name += route.route_short_name
  if route.route_long_name:
  if len(trip_name):
  trip_name += " - "
  trip_name += route.route_long_name
  if headsign:
  trip_name += " (Direction: %s)" % headsign
 
  result.append((time, (trip.trip_id, trip_name, trip.service_id), tp))
  return result
 
  def handle_GET_ttablegraph(self,params):
  """Draw a Marey graph in SVG for a pattern (collection of trips in a route
  that visit the same sequence of stops)."""
  schedule = self.server.schedule
  marey = MareyGraph()
  trip = schedule.GetTrip(params.get('trip', None))
  route = schedule.GetRoute(trip.route_id)
  height = int(params.get('height', 300))
 
  if not route:
  print 'no such route'
  self.send_error(404)
  return
 
  pattern_id_trip_dict = route.GetPatternIdTripDict()
  pattern_id = trip.pattern_id
  if pattern_id not in pattern_id_trip_dict:
  print 'no pattern %s found in %s' % (pattern_id, pattern_id_trip_dict.keys())
  self.send_error(404)
  return
  triplist = pattern_id_trip_dict[pattern_id]
 
  pattern_start_time = min((t.GetStartTime() for t in triplist))
  pattern_end_time = max((t.GetEndTime() for t in triplist))
 
  marey.SetSpan(pattern_start_time,pattern_end_time)
  marey.Draw(triplist[0].GetPattern(), triplist, height)
 
  content = marey.Draw()
 
  self.send_response(200)
  self.send_header('Content-Type', 'image/svg+xml')
  self.send_header('Content-Length', str(len(content)))
  self.end_headers()
  self.wfile.write(content)
 
 
  def FindPy2ExeBase():
  """If this is running in py2exe return the install directory else return
  None"""
  # py2exe puts gtfsscheduleviewer in library.zip. For py2exe setup.py is
  # configured to put the data next to library.zip.
  windows_ending = gtfsscheduleviewer.__file__.find('\\library.zip\\')
  if windows_ending != -1:
  return transitfeed.__file__[:windows_ending]
  else:
  return None
 
 
  def FindDefaultFileDir():
  """Return the path of the directory containing the static files. By default
  the directory is called 'files'. The location depends on where setup.py put
  it."""
  base = FindPy2ExeBase()
  if base:
  return os.path.join(base, 'schedule_viewer_files')
  else:
  # For all other distributions 'files' is in the gtfsscheduleviewer
  # directory.
  base = os.path.dirname(gtfsscheduleviewer.__file__) # Strip __init__.py
  return os.path.join(base, 'files')
 
 
  def GetDefaultKeyFilePath():
  """In py2exe return absolute path of file in the base directory and in all
  other distributions return relative path 'key.txt'"""
  windows_base = FindPy2ExeBase()
  if windows_base:
  return os.path.join(windows_base, 'key.txt')
  else:
  return 'key.txt'
 
 
  def main(RequestHandlerClass = ScheduleRequestHandler):
  usage = \
  '''%prog [options] [<input GTFS.zip>]
 
  Runs a webserver that lets you explore a <input GTFS.zip> in your browser.
 
  If <input GTFS.zip> is omited the filename is read from the console. Dragging
  a file into the console may enter the filename.
 
  For more information see
  http://code.google.com/p/googletransitdatafeed/wiki/ScheduleViewer
  '''
  parser = util.OptionParserLongError(
  usage=usage, version='%prog '+transitfeed.__version__)
  parser.add_option('--feed_filename', '--feed', dest='feed_filename',
  help='file name of feed to load')
  parser.add_option('--key', dest='key',
  help='Google Maps API key or the name '
  'of a text file that contains an API key')
  parser.add_option('--host', dest='host', help='Host name of Google Maps')
  parser.add_option('--port', dest='port', type='int',
  help='port on which to listen')
  parser.add_option('--file_dir', dest='file_dir',
  help='directory containing static files')
  parser.add_option('-n', '--noprompt', action='store_false',
  dest='manual_entry',
  help='disable interactive prompts')
  parser.set_defaults(port=8765,
  host='maps.google.com',
  file_dir=FindDefaultFileDir(),
  manual_entry=True)
  (options, args) = parser.parse_args()
 
  if not os.path.isfile(os.path.join(options.file_dir, 'index.html')):
  print "Can't find index.html with --file_dir=%s" % options.file_dir
  exit(1)
 
  if not options.feed_filename and len(args) == 1:
  options.feed_filename = args[0]
 
  if not options.feed_filename and options.manual_entry:
  options.feed_filename = raw_input('Enter Feed Location: ').strip('"')
 
  default_key_file = GetDefaultKeyFilePath()
  if not options.key and os.path.isfile(default_key_file):
  options.key = open(default_key_file).read().strip()
 
  if options.key and os.path.isfile(options.key):
  options.key = open(options.key).read().strip()
 
  schedule = transitfeed.Schedule(problem_reporter=transitfeed.ProblemReporter())
  print 'Loading data from feed "%s"...' % options.feed_filename
  print '(this may take a few minutes for larger cities)'
  schedule.Load(options.feed_filename)
 
  server = StoppableHTTPServer(server_address=('', options.port),
  RequestHandlerClass=RequestHandlerClass)
  server.key = options.key
  server.schedule = schedule
  server.file_dir = options.file_dir
  server.host = options.host
  server.feed_path = options.feed_filename
 
  print ("To view, point your browser at http://localhost:%d/" %
  (server.server_port))
  server.serve_forever()
 
 
  if __name__ == '__main__':
  main()
 
  #!/usr/bin/python2.5
 
  # Copyright (C) 2007 Google Inc.
  #
  # Licensed under the Apache License, Version 2.0 (the "License");
  # you may not use this file except in compliance with the License.
  # You may obtain a copy of the License at
  #
  # http://www.apache.org/licenses/LICENSE-2.0
  #
  # Unless required by applicable law or agreed to in writing, software
  # distributed under the License is distributed on an "AS IS" BASIS,
  # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  # See the License for the specific language governing permissions and
  # limitations under the License.
 
  """
  This script can be used to create a source distribution, binary distribution
  or Windows executable files. The output is put in dist/
 
  See
  http://code.google.com/p/googletransitdatafeed/wiki/BuildingPythonWindowsExecutables
  for help on creating Windows executables.
  """
 
  from distutils.core import setup
  import glob
  import os.path
  from transitfeed import __version__ as VERSION
 
  try:
  import py2exe
  has_py2exe = True
  except ImportError, e:
  # Won't be able to generate win32 exe
  has_py2exe = False
 
 
  # py2exe doesn't automatically include pytz dependency because it is optional
  options = {'py2exe': {'packages': ['pytz']}}
  scripts_for_py2exe = ['feedvalidator.py', 'schedule_viewer.py', 'kmlparser.py',
  'kmlwriter.py', 'merge.py', 'unusual_trip_filter.py']
  # On Nov 23, 2009 Tom Brown said: I'm not confident that we can include a
  # working copy of this script in the py2exe distribution because it depends on
  # ogr. I do want it included in the source tar.gz.
  scripts_for_source_only = ['shape_importer.py']
  kwargs = {}
 
  if has_py2exe:
  kwargs['console'] = scripts_for_py2exe
  # py2exe seems to ignore package_data and not add marey_graph. This makes it
  # work.
  kwargs['data_files'] = \
  [('schedule_viewer_files',
  glob.glob(os.path.join('gtfsscheduleviewer', 'files', '*')))]
  options['py2exe'] = {'dist_dir': 'transitfeed-windows-binary-%s' % VERSION}
 
  setup(
  version=VERSION,
  name='transitfeed',
  url='http://code.google.com/p/googletransitdatafeed/',
  download_url='http://googletransitdatafeed.googlecode.com/'
  'files/transitfeed-%s.tar.gz' % VERSION,
  maintainer='Tom Brown',
  maintainer_email='tom.brown.code@gmail.com',
  description='Google Transit Feed Specification library and tools',
  long_description='This module provides a library for reading, writing and '
  'validating Google Transit Feed Specification files. It includes some '
  'scripts that validate a feed, display it using the Google Maps API and '
  'the start of a KML importer and exporter.',
  platforms='OS Independent',
  license='Apache License, Version 2.0',
  packages=['gtfsscheduleviewer', 'transitfeed'],
  # Also need to list package_data contents in MANIFEST.in for it to be
  # included in sdist. See "[Distutils] package_data not used by sdist
  # command" Feb 2, 2007
  package_data={'gtfsscheduleviewer': ['files/*']},
  scripts=scripts_for_py2exe + scripts_for_source_only,
  zip_safe=False,
  classifiers=[
  'Development Status :: 4 - Beta',
  'Intended Audience :: Developers',
  'Intended Audience :: Information Technology',
  'Intended Audience :: Other Audience',
  'License :: OSI Approved :: Apache Software License',
  'Operating System :: OS Independent',
  'Programming Language :: Python',
  'Topic :: Scientific/Engineering :: GIS',
  'Topic :: Software Development :: Libraries :: Python Modules'
  ],
  options=options,
  **kwargs
  )
 
  if has_py2exe:
  # Sometime between pytz-2008a and pytz-2008i common_timezones started to
  # include only names of zones with a corresponding data file in zoneinfo.
  # pytz installs the zoneinfo directory tree in the same directory
  # as the pytz/__init__.py file. These data files are loaded using
  # pkg_resources.resource_stream. py2exe does not copy this to library.zip so
  # resource_stream can't find the files and common_timezones is empty when
  # read in the py2exe executable.
  # This manually copies zoneinfo into the zip. See also
  # http://code.google.com/p/googletransitdatafeed/issues/detail?id=121
  import pytz
  import zipfile
  # Make sure the layout of pytz hasn't changed
  assert (pytz.__file__.endswith('__init__.pyc') or
  pytz.__file__.endswith('__init__.py')), pytz.__file__
  zoneinfo_dir = os.path.join(os.path.dirname(pytz.__file__), 'zoneinfo')
  # '..\\Lib\\pytz\\__init__.py' -> '..\\Lib'
  disk_basedir = os.path.dirname(os.path.dirname(pytz.__file__))
  zipfile_path = os.path.join(options['py2exe']['dist_dir'], 'library.zip')
  z = zipfile.ZipFile(zipfile_path, 'a')
  for absdir, directories, filenames in os.walk(zoneinfo_dir):
  assert absdir.startswith(disk_basedir), (absdir, disk_basedir)
  zip_dir = absdir[len(disk_basedir):]
  for f in filenames:
  z.write(os.path.join(absdir, f), os.path.join(zip_dir, f))
  z.close()
 
  #!/usr/bin/python2.4
  #
  # Copyright 2007 Google Inc. All Rights Reserved.
  #
  # Licensed under the Apache License, Version 2.0 (the "License");
  # you may not use this file except in compliance with the License.
  # You may obtain a copy of the License at
  #
  # http://www.apache.org/licenses/LICENSE-2.0
  #
  # Unless required by applicable law or agreed to in writing, software
  # distributed under the License is distributed on an "AS IS" BASIS,
  # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  # See the License for the specific language governing permissions and
  # limitations under the License.
 
  """A utility program to help add shapes to an existing GTFS feed.
 
  Requires the ogr python package.
  """
 
  __author__ = 'chris.harrelson.code@gmail.com (Chris Harrelson)'
 
  import csv
  import glob
  import ogr
  import os
  import shutil
  import sys
  import tempfile
  import transitfeed
  from transitfeed import shapelib
  from transitfeed import util
  import zipfile
 
 
  class ShapeImporterError(Exception):
  pass
 
 
  def PrintColumns(shapefile):
  """
  Print the columns of layer 0 of the shapefile to the screen.
  """
  ds = ogr.Open(shapefile)
  layer = ds.GetLayer(0)
  if len(layer) == 0:
  raise ShapeImporterError("Layer 0 has no elements!")
 
  feature = layer.GetFeature(0)
  print "%d features" % feature.GetFieldCount()
  for j in range(0, feature.GetFieldCount()):
  print '--' + feature.GetFieldDefnRef(j).GetName() + \
  ': ' + feature.GetFieldAsString(j)
 
 
  def AddShapefile(shapefile, graph, key_cols):
  """
  Adds shapes found in the given shape filename to the given polyline
  graph object.
  """
  ds = ogr.Open(shapefile)
  layer = ds.GetLayer(0)
 
  for i in range(0, len(layer)):
  feature = layer.GetFeature(i)
 
  geometry = feature.GetGeometryRef()
 
  if key_cols:
  key_list = []
  for col in key_cols:
  key_list.append(str(feature.GetField(col)))
  shape_id = '-'.join(key_list)
  else:
  shape_id = '%s-%d' % (shapefile, i)
 
  poly = shapelib.Poly(name=shape_id)
  for j in range(0, geometry.GetPointCount()):
  (lat, lng) = (round(geometry.GetY(j), 15), round(geometry.GetX(j), 15))
  poly.AddPoint(shapelib.Point.FromLatLng(lat, lng))
  graph.AddPoly(poly)
 
  return graph
 
 
  def GetMatchingShape(pattern_poly, trip, matches, max_distance, verbosity=0):
  """
  Tries to find a matching shape for the given pattern Poly object,
  trip, and set of possibly matching Polys from which to choose a match.
  """
  if len(matches) == 0:
  print ('No matching shape found within max-distance %d for trip %s '
  % (max_distance, trip.trip_id))
  return None
 
  if verbosity >= 1:
  for match in matches:
  print "match: size %d" % match.GetNumPoints()
  scores = [(pattern_poly.GreedyPolyMatchDist(match), match)
  for match in matches]
 
  scores.sort()
 
  if scores[0][0] > max_distance:
  print ('No matching shape found within max-distance %d for trip %s '
  '(min score was %f)'
  % (max_distance, trip.trip_id, scores[0][0]))
  return None
 
  return scores[0][1]
 
  def AddExtraShapes(extra_shapes_txt, graph):
  """
  Add extra shapes into our input set by parsing them out of a GTFS-formatted
  shapes.txt file. Useful for manually adding lines to a shape file, since it's
  a pain to edit .shp files.
  """
 
  print "Adding extra shapes from %s" % extra_shapes_txt
  try:
  tmpdir = tempfile.mkdtemp()
  shutil.copy(extra_shapes_txt, os.path.join(tmpdir, 'shapes.txt'))
  loader = transitfeed.ShapeLoader(tmpdir)
  schedule = loader.Load()
  for shape in schedule.GetShapeList():
  print "Adding extra shape: %s" % shape.shape_id
  graph.AddPoly(ShapeToPoly(shape))
  finally:
  if tmpdir:
  shutil.rmtree(tmpdir)
 
 
  # Note: this method lives here to avoid cross-dependencies between
  # shapelib and transitfeed.
  def ShapeToPoly(shape):
  poly = shapelib.Poly(name=shape.shape_id)
  for lat, lng, distance in shape.points:
  point = shapelib.Point.FromLatLng(round(lat, 15), round(lng, 15))
  poly.AddPoint(point)
  return poly
 
 
  def ValidateArgs(options_parser, options, args):
  if not (args and options.source_gtfs and options.dest_gtfs):
  options_parser.error("You must specify a source and dest GTFS file, "
  "and at least one source shapefile")
 
 
  def DefineOptions():
  usage = \
  """%prog [options] --source_gtfs=<input GTFS.zip> --dest_gtfs=<output GTFS.zip>\
  <input.shp> [<input.shp>...]
 
  Try to match shapes in one or more SHP files to trips in a GTFS file."""
  options_parser = util.OptionParserLongError(
  usage=usage, version='%prog '+transitfeed.__version__)
  options_parser.add_option("--print_columns",
  action="store_true",
  default=False,
  dest="print_columns",
  help="Print column names in shapefile DBF and exit")
  options_parser.add_option("--keycols",
  default="",
  dest="keycols",
  help="Comma-separated list of the column names used"
  "to index shape ids")
  options_parser.add_option("--max_distance",
  type="int",
  default=150,
  dest="max_distance",
  help="Max distance from a shape to which to match")
  options_parser.add_option("--source_gtfs",
  default="",
  dest="source_gtfs",
  metavar="FILE",
  help="Read input GTFS from FILE")
  options_parser.add_option("--dest_gtfs",
  default="",
  dest="dest_gtfs",
  metavar="FILE",
  help="Write output GTFS with shapes to FILE")
  options_parser.add_option("--extra_shapes",
  default="",
  dest="extra_shapes",
  metavar="FILE",
  help="Extra shapes.txt (CSV) formatted file")
  options_parser.add_option("--verbosity",
  type="int",
  default=0,
  dest="verbosity",
  help="Verbosity level. Higher is more verbose")
  return options_parser
 
 
  def main(key_cols):
  print 'Parsing shapefile(s)...'
  graph = shapelib.PolyGraph()
  for arg in args:
  print ' ' + arg
  AddShapefile(arg, graph, key_cols)
 
  if options.extra_shapes:
  AddExtraShapes(options.extra_shapes, graph)
 
  print 'Loading GTFS from %s...' % options.source_gtfs
  schedule = transitfeed.Loader(options.source_gtfs).Load()
  shape_count = 0
  pattern_count = 0
 
  verbosity = options.verbosity
 
  print 'Matching shapes to trips...'
  for route in schedule.GetRouteList():
  print 'Processing route', route.route_short_name
  patterns = route.GetPatternIdTripDict()
  for pattern_id, trips in patterns.iteritems():
  pattern_count += 1
  pattern = trips[0].GetPattern()
 
  poly_points = [shapelib.Point.FromLatLng(p.stop_lat, p.stop_lon)
  for p in pattern]
  if verbosity >= 2:
  print "\npattern %d, %d points:" % (pattern_id, len(poly_points))
  for i, (stop, point) in enumerate(zip(pattern, poly_points)):
  print "Stop %d '%s': %s" % (i + 1, stop.stop_name, point.ToLatLng())
 
  # First, try to find polys that run all the way from
  # the start of the trip to the end.
  matches = graph.FindMatchingPolys(poly_points[0], poly_points[-1],
  options.max_distance)
  if not matches:
  # Try to find a path through the graph, joining
  # multiple edges to find a path that covers all the
  # points in the trip. Some shape files are structured
  # this way, with a polyline for each segment between
  # stations instead of a polyline covering an entire line.
  shortest_path = graph.FindShortestMultiPointPath(poly_points,
  options.max_distance,
  verbosity=verbosity)
  if shortest_path:
  matches = [shortest_path]
  else:
  matches = []
 
  pattern_poly = shapelib.Poly(poly_points)
  shape_match = GetMatchingShape(pattern_poly, trips[0],
  matches, options.max_distance,
  verbosity=verbosity)
  if shape_match:
  shape_count += 1
  # Rename shape for readability.
  shape_match = shapelib.Poly(points=shape_match.GetPoints(),
  name="shape_%d" % shape_count)
  for trip in trips:
  try:
  shape = schedule.GetShape(shape_match.GetName())
  except KeyError:
  shape = transitfeed.Shape(shape_match.GetName())
  for point in shape_match.GetPoints():
  (lat, lng) = point.ToLatLng()
  shape.AddPoint(lat, lng)
  schedule.AddShapeObject(shape)
  trip.shape_id = shape.shape_id
 
  print "Matched %d shapes out of %d patterns" % (shape_count, pattern_count)
  schedule.WriteGoogleTransitFeed(options.dest_gtfs)
 
 
  if __name__ == '__main__':
  # Import psyco if available for better performance.
  try:
  import psyco
  psyco.full()
  except ImportError:
  pass
 
  options_parser = DefineOptions()
  (options, args) = options_parser.parse_args()
 
  ValidateArgs(options_parser, options, args)
 
  if options.print_columns:
  for arg in args:
  PrintColumns(arg)
  sys.exit(0)
 
  key_cols = options.keycols.split(',')
 
  main(key_cols)
 
  agency_id,agency_name,agency_url,agency_timezone,agency_phone
  DTA,Autorité de passage de démonstration,http://google.com,America/Los_Angeles,123 12314
 
  service_id,monday,tuesday,wednesday,thursday,friday,saturday,sunday,start_date,end_date
  FULLW,1,1,1,1,1,1,1,2007.01.01,20101231
  WE,0,0,0,0,0,1,1,20070101,20101231
 
  service_id,date,exception_type
  FULLW,2007-06-04,2
 
  fare_id,price,currency_type,payment_method,transfers,transfer_duration
  p,1.25,USD,0,0,
  a,5.25,USD,0,0,
 
  fare_id,route_id,origin_id,destination_id,contains_id
  p,AB,,,
  p,STBA,,,
  p,BFC,,,
  a,AAMV,,,
 
  route_id,agency_id,route_short_name,route_long_name,route_desc,route_type,route_url,route_color,route_text_color
  AB,DTA,,Airport ⇒ Bullfrog,,3,,,
  BFC,DTA,,Bullfrog ⇒ Furnace Creek Resort,,3,,,
  STBA,DTA,,Stagecoach ⇒ Airport Shuttle,,3,,,
  CITY,DTA,Ō,Bar Circle,Route with ĸool unicode shortname,3,,,
  AAMV,DTA,,Airport ⇒ Amargosa Valley,,3,,,
 
  trip_id,arrival_time,departure_time,stop_id,stop_sequence,stop_headsign,pickup_type,drop_off_type,shape_dist_traveled
  STBA,6:00:00,6:00:00,STAGECOACH,0,to airport,1,0,0.212
  STBA,6:20:00,6:20:00,BEATTY_AIRPORT,2,,0,0,1.043
  CITY1,6:00:00,6:00:00,STAGECOACH,0,,,,
  CITY1,6:05:00,6:07:00,NANAA,5,going to nadav,2,3,
  CITY1,6:12:00,6:14:00,NADAV,10,,,,
  CITY1,6:19:00,6:21:00,DADAN,15,,,,
  CITY1,6:26:00,6:28:00,EMSI,20,,,,
  CITY2,6:28:00,6:30:00,EMSI,100,,,,
  CITY2,6:35:00,6:37:00,DADAN,200,,,,
  CITY2,6:42:00,6:44:00,NADAV,300,,,,
  CITY2,6:49:00,6:51:00,NANAA,400,,,,
  CITY2,6:56:00,6:58:00,STAGECOACH,500,,,,
  AB1,8:00:00,8:00:00,BEATTY_AIRPORT,1,,,,
  AB1,8:10:00,8:15:00,BULLFROG,2,,,,
  AB2,12:05:00,12:05:00,BULLFROG,1,,,,
  AB2,12:15:00,12:15:00,BEATTY_AIRPORT,2,,,,
  BFC1,8:20:00,8:20:00,BULLFROG,1,,,,
  BFC1,9:20:00,9:20:00,FUR_CREEK_RES,2,,,,
  BFC2,11:00:00,11:00:00,FUR_CREEK_RES,1,,,,
  BFC2,12:00:00,12:00:00,BULLFROG,2,,,,
  AAMV1,8:00:00,8:00:00,BEATTY_AIRPORT,1,,,,
  AAMV1,9:00:00,9:00:00,AMV,2,,,,
  AAMV2,10:00:00,10:00:00,AMV,1,,,,
  AAMV2,11:00:00,11:00:00,BEATTY_AIRPORT,2,,,,
  AAMV3,13:00:00,13:00:00,BEATTY_AIRPORT,1,,,,
  AAMV3,14:00:00,14:00:00,AMV,2,,,,
  AAMV4,15:00:00,15:00:00,AMV,1,,,,
  AAMV4,16:00:00,16:00:00,BEATTY_AIRPORT,2,,,,
 
  stop_id,stop_name,stop_desc,stop_lat,stop_lon,zone_id,stop_url,stop_code,location_type,parent_station
  FUR_CREEK_RES,Furnace Creek Resort (Demo),,36.425288,-117.133162,,,1234,,
  BEATTY_AIRPORT,Nye County Airport (Demo),,36.868446,-116.784582,,,1235,0,BEATTY_AIRPORT_STATION
  BEATTY_AIRPORT_STATION,Nye County Airport (Demo),,36.868446,-116.784582,,,1235,1,
  BULLFROG,Bullfrog (Demo),,36.88108,-116.81797,,,,,
  STAGECOACH,Stagecoach Hotel & Casino (Demo),,36.915682,-116.751677,,,1236,,
  NADAV,North Ave / D Ave N (Demo),,36.914893,-116.76821,,,1237,,
  NANAA,North Ave / N A Ave (Demo),,36.914944,-116.761472,,,1238,,
  DADAN,Doing Ave / D Ave N (Demo),,36.909489,-116.768242,,,,,
  EMSI,E Main St / S Irving St (Demo),,36.905697,-116.76218,,,,,
  AMV,Amargosa Valley (Demo),,36.641496,-116.40094,,,,,
 
  from_stop_id,to_stop_id,transfer_type,min_transfer_time
  NADAV,NANAA,3,
  EMSI,NANAA,2,1200
 
  route_id,service_id,trip_id,trip_headsign,direction_id,block_id,shape_id
  AB,FULLW,AB1,to Bullfrog,0,1,
  AB,FULLW,AB2,to Airport,1,2,
  STBA,FULLW,STBA,Shuttle,,,
  CITY,FULLW,CITY1,,0,,
  CITY,FULLW,CITY2,,1,,
  BFC,FULLW,BFC1,to Furnace Creek Resort,0,1,
  BFC,FULLW,BFC2,to Bullfrog,1,2,
  AAMV,WE,AAMV1,to Amargosa Valley,0,,
  AAMV,WE,AAMV2,to Airport,1,,
  AAMV,WE,AAMV3,to Amargosa Valley,0,,
  AAMV,WE,AAMV4,to Airport,1,,
 Binary files /dev/null and b/origin-src/transitfeed-1.2.6/test/data/bad_eol.zip differ
  service_id,monday,tuesday,wednesday,thursday,friday,saturday,sunday,start_date,end_date
  FULLW,1,1,1,1,1,1,1,20070101,20101231
  WE,0,0,0,0,0,1,1,20070101,20101231
  service_id,date,exception_type
  FULLW,20070604,2
  fare_id,price,currency_type,payment_method,transfers,transfer_duration
  p,1.25,USD,0,0,
  a,5.25,USD,0,0,
 
  fare_id,route_id,origin_id,destination_id,contains_id
  p,AB,,,
  p,STBA,,,
  p,BFC,,,
  a,AAMV,,,
 
  trip_id,start_time,end_time,headway_secs
  STBA,6:00:00,22:00:00,1800
  CITY1,6:00:00,7:59:59,1800
  CITY2,6:00:00,7:59:59,1800
  CITY1,8:00:00,9:59:59,600
  CITY2,8:00:00,9:59:59,600
  CITY1,10:00:00,15:59:59,1800
  CITY2,10:00:00,15:59:59,1800
  CITY1,16:00:00,18:59:59,600
  CITY2,16:00:00,18:59:59,600
  CITY1,19:00:00,22:00:00,1800
  CITY2,19:00:00,22:00:00,1800
  route_id,agency_id,route_short_name,route_long_name,route_desc,route_type,route_url,route_color,route_text_color
  AB,DTA,,Airport - Bullfrog,,3,,,
  BFC,DTA,,Bullfrog - Furnace Creek Resort,,3,,,
  STBA,DTA,,Stagecoach - Airport Shuttle,,3,,,
 
  AAMV,DTA,,Airport - Amargosa Valley,,3,,,
 
  trip_id,arrival_time,departure_time,stop_id,stop_sequence,stop_headsign,pickup_type,drop_off_type,shape_dist_traveled
 
  STBA,6:20:00,6:20:00,BEATTY_AIRPORT,2,,0,0,1.043
  CITY1,6:00:00,6:00:00,STAGECOACH,0,,,,
  CITY1,6:05:00,6:07:00,NANAA,5,going to nadav,2,3,
  CITY1,6:12:00,6:14:00,NADAV,10,,,,
  CITY1,6:19:00,6:21:00,DADAN,15,,,,
  CITY1,6:26:00,6:28:00,EMSI,20,,,,
  CITY2,6:28:00,6:30:00,EMSI,100,,,,
  CITY2,6:35:00,6:37:00,DADAN,200,,,,
  CITY2,6:42:00,6:44:00,NADAV,300,,,,
  CITY2,6:49:00,6:51:00,NANAA,400,,,,
  CITY2,6:56:00,6:58:00,STAGECOACH,500,,,,
  AB1,8:00:00,8:00:00,BEATTY_AIRPORT,1,,,,
  AB1,8:10:00,8:15:00,BULLFROG,2,,,,
  AB2,12:05:00,12:05:00,BULLFROG,1,,,,
  AB2,12:15:00,12:15:00,BEATTY_AIRPORT,2,,,,
  BFC1,8:20:00,8:20:00,BULLFROG,1,,,,
  BFC1,9:20:00,9:20:00,FUR_CREEK_RES,2,,,,
  BFC2,11:00:00,11:00:00,FUR_CREEK_RES,1,,,,
  BFC2,12:00:00,12:00:00,BULLFROG,2,,,,
  AAMV1,8:00:00,8:00:00,BEATTY_AIRPORT,1,,,,
  AAMV1,9:00:00,9:00:00,AMV,2,,,,
  AAMV2,10:00:00,10:00:00,AMV,1,,,,
  AAMV2,11:00:00,11:00:00,BEATTY_AIRPORT,2,,,,
  AAMV3,13:00:00,13:00:00,BEATTY_AIRPORT,1,,,,
  AAMV3,14:00:00,14:00:00,AMV,2,,,,
  AAMV4,15:00:00,15:00:00,AMV,1,,,,
  AAMV4,16:00:00,16:00:00,BEATTY_AIRPORT,2,,,,
 
  stop_id,stop_name,stop_desc,stop_lat,stop_lon,zone_id,stop_url
 
  BEATTY_AIRPORT,Nye County Airport (Demo),,36.868446,-116.784582,,
  BULLFROG,Bullfrog (Demo),,36.88108,-116.81797,,
  STAGECOACH,Stagecoach Hotel & Casino (Demo),,36.915682,-116.751677,,
  NADAV,North Ave / D Ave N (Demo),,36.914893,-116.76821,,
  NANAA,North Ave / N A Ave (Demo),,36.914944,-116.761472,,
  DADAN,Doing Ave / D Ave N (Demo),,36.909489,-116.768242,,
  EMSI,E Main St / S Irving St (Demo),,36.905697,-116.76218,,
  AMV,Amargosa Valley (Demo),,36.641496,-116.40094,,
 
  route_id,service_id,trip_id,trip_headsign,direction_id,block_id,shape_id
 
  AB,FULLW,AB2,to Airport,1,2,
  STBA,FULLW,STBA,Shuttle,,,
  CITY,FULLW,CITY1,,0,,
  CITY,FULLW,CITY2,,1,,
  BFC,FULLW,BFC1,to Furnace Creek Resort,0,1,
  BFC,FULLW,BFC2,to Bullfrog,1,2,
  AAMV,WE,AAMV1,to Amargosa Valley,0,,
  AAMV,WE,AAMV2,to Airport,1,,
  AAMV,WE,AAMV3,to Amargosa Valley,0,,
  AAMV,WE,AAMV4,to Airport,1,,
 
  agency_id,agency_name,agency_url,agency_timezone
  DTA,Autorité de passage de démonstration,http://google.com,America/Los_Angeles
 
  service_id,monday,tuesday,wednesday,thursday,friday,saturday,sunday,start_date,end_date
  FULLW,1,1,1,1,1,1,1,20070101,20101231
  WE,0,0,0,0,0,1,1,20070101,20101231
  service_id,date,exception_type
  FULLW,20070604,2
  fare_id,price,currency_type,payment_method,transfers,transfer_duration
  p,1.25,USD,0,0,
  a,5.25,USD,0,0,
 
  fare_id,route_id,origin_id,destination_id,contains_id
  p,AB,,,
  p,STBA,,,
  p,BFC,,,
  a,AAMV,,,
 
  trip_id,start_time,end_time,headway_secs
  STBA,6:00:00,22:00:00,1800
  CITY1,6:00:00,7:59:59,1800
  CITY2,6:00:00,7:59:59,1800
  CITY1,8:00:00,9:59:59,600
  CITY2,8:00:00,9:59:59,600
  CITY1,10:00:00,15:59:59,1800
  CITY2,10:00:00,15:59:59,1800
  CITY1,16:00:00,18:59:59,600
  CITY2,16:00:00,18:59:59,600
  CITY1,19:00:00,22:00:00,1800
  CITY2,19:00:00,22:00:00,1800
  route_id,agency_id,route_short_name,route_long_name,route_desc,route_type,route_url,route_color,route_text_color
  AB,DTA,,Airport ⇒ Bullfrog,,3
  BFC,DTA,,Bullfrog ⇒ Furnace Creek Resort,,3
  STBA,DTA,,Stagecoach ⇒ Airport Shuttle,,3
  CITY,DTA,Ō,Bar Circle,Route with ĸool unicode short name,3
  AAMV,DTA,,Airport ⇒ Amargosa Valley,,3
 
  trip_id,arrival_time,departure_time,stop_id,stop_sequence,stop_headsign,pickup_type,drop_off_type,shape_dist_traveled
  STBA,6:00:00,6:00:00,STAGECOACH,1,to airport,1,0,0.212
  STBA,6:20:00,6:20:00,BEATTY_AIRPORT,2,,0,0,1.043
  CITY1,6:00:00,6:00:00,STAGECOACH,1
  CITY1,6:05:00,6:07:00,NANAA,2,going to nadav,2,3,
  CITY1,6:12:00,6:14:00,NADAV,3
  CITY1,6:19:00,6:21:00,DADAN,4
  CITY1,6:26:00,6:28:00,EMSI,5
  CITY2,6:28:00,6:30:00,EMSI,1
  CITY2,6:35:00,6:37:00,DADAN,2
  CITY2,6:42:00,6:44:00,NADAV,3
  CITY2,6:49:00,6:51:00,NANAA,4
  CITY2,6:56:00,6:58:00,STAGECOACH,5
  AB1,8:00:00,8:00:00,BEATTY_AIRPORT,1
  AB1,8:10:00,8:15:00,BULLFROG,2
  AB2,12:05:00,12:05:00,BULLFROG,1
  AB2,12:15:00,12:15:00,BEATTY_AIRPORT,2
  BFC1,8:20:00,8:20:00,BULLFROG,1
  BFC1,9:20:00,9:20:00,FUR_CREEK_RES,2
  BFC2,11:00:00,11:00:00,FUR_CREEK_RES,1
  BFC2,12:00:00,12:00:00,BULLFROG,2
  AAMV1,8:00:00,8:00:00,BEATTY_AIRPORT,1
  AAMV1,9:00:00,9:00:00,AMV,2
  AAMV2,10:00:00,10:00:00,AMV,1
  AAMV2,11:00:00,11:00:00,BEATTY_AIRPORT,2
  AAMV3,13:00:00,13:00:00,BEATTY_AIRPORT,1
  AAMV3,14:00:00,14:00:00,AMV,2
  AAMV4,15:00:00,15:00:00,AMV,1
  AAMV4,16:00:00,16:00:00,BEATTY_AIRPORT,2
 
 Binary files /dev/null and b/origin-src/transitfeed-1.2.6/test/data/contains_null/stops.txt differ
  route_id,service_id,trip_id,trip_headsign,direction_id,block_id,shape_id
  AB,FULLW,AB1,to Bullfrog,0,1
  AB,FULLW,AB2,to Airport,1,2
  STBA,FULLW,STBA,Shuttle
  CITY,FULLW,CITY1,Ō,0
  CITY,FULLW,CITY2,Ō,1
  BFC,FULLW,BFC1,to Furnace Creek Resort,0,1
  BFC,FULLW,BFC2,to Bullfrog,1,2
  AAMV,WE,AAMV1,to Amargosa Valley,0
  AAMV,WE,AAMV2,to Airport,1
  AAMV,WE,AAMV3,to Amargosa Valley,0
  AAMV,WE,AAMV4,to Airport,1
 
  agency_id,agency_name,agency_url,agency_timezone
  DTA,Autorité de passage de démonstration,http://google.com,America/Los_Angeles
 
  service_id,monday,tuesday,wednesday,thursday,friday,saturday,sunday,start_date,end_date
  FULLW,1,1,1,1,1,1,1,20070101,20101231
  WE,0,0,0,0,0,1,1,20070101,20101231
  WE,0,0,0,0,0,1,1,20070101,20101231
 
  service_id,date,exception_type
  FULLW,20070604,2
  fare_id,price,currency_type,payment_method,transfers,transfer_duration
  p,1.25,USD,0,0,
  a,5.25,USD,0,0,
 
  fare_id,route_id,origin_id,destination_id,contains_id
  p,AB,,,
  p,STBA,,,
  p,BFC,,,
  a,AAMV,,,
 
  trip_id,start_time,end_time,headway_secs
  STBA,6:00:00,22:00:00,1800
  CITY1,6:00:00,7:59:59,1800
  CITY2,6:00:00,7:59:59,1800
  CITY1,8:00:00,9:59:59,600
  CITY2,8:00:00,9:59:59,600
  CITY1,10:00:00,15:59:59,1800
  CITY2,10:00:00,15:59:59,1800
  CITY1,16:00:00,18:59:59,600
  CITY2,16:00:00,18:59:59,600
  CITY1,19:00:00,22:00:00,1800
  CITY2,19:00:00,22:00:00,1800
  route_id,agency_id,route_short_name,route_long_name,route_desc,route_type,route_url,route_color,route_text_color
  AB,DTA,,Airport ⇒ Bullfrog,,3,,,
  BFC,DTA,,Bullfrog ⇒ Furnace Creek Resort,,3,,,
  STBA,DTA,,Stagecoach ⇒ Airport Shuttle,,3,,,
  CITY,DTA,Ō,Bar Circle,Route with ĸool unicode short name,3,,,
  AAMV,DTA,,Airport ⇒ Amargosa Valley,,3,,,
 
  trip_id,arrival_time,departure_time,stop_id,stop_sequence,stop_headsign,pickup_type,drop_off_type,shape_dist_traveled
  STBA,6:00:00,6:00:00,STAGECOACH,1,to airport,1,0,0.212
  STBA,6:20:00,6:20:00,BEATTY_AIRPORT,2,,0,0,1.043
  CITY1,6:00:00,6:00:00,STAGECOACH,1,,,,
  CITY1,6:05:00,6:07:00,NANAA,2,going to nadav,2,3,
  CITY1,6:12:00,6:14:00,NADAV,3,,,,
  CITY1,6:19:00,6:21:00,DADAN,4,,,,
  CITY1,6:26:00,6:28:00,EMSI,5,,,,
  CITY2,6:28:00,6:30:00,EMSI,1,,,,
  CITY2,6:35:00,6:37:00,DADAN,2,,,,
  CITY2,6:42:00,6:44:00,NADAV,3,,,,
  CITY2,6:49:00,6:51:00,NANAA,4,,,,
  CITY2,6:56:00,6:58:00,STAGECOACH,5,,,,
  AB1,8:00:00,8:00:00,BEATTY_AIRPORT,1,,,,
  AB1,8:10:00,8:15:00,BULLFROG,2,,,,
  AB2,12:05:00,12:05:00,BULLFROG,1,,,,
  AB2,12:15:00,12:15:00,BEATTY_AIRPORT,2,,,,
  BFC1,8:20:00,8:20:00,BULLFROG,1,,,,
  BFC1,9:20:00,9:20:00,FUR_CREEK_RES,2,,,,
  BFC2,11:00:00,11:00:00,FUR_CREEK_RES,1,,,,
  BFC2,12:00:00,12:00:00,BULLFROG,2,,,,
  AAMV1,8:00:00,8:00:00,BEATTY_AIRPORT,1,,,,
  AAMV1,9:00:00,9:00:00,AMV,2,,,,
  AAMV2,10:00:00,10:00:00,AMV,1,,,,
  AAMV2,11:00:00,11:00:00,BEATTY_AIRPORT,2,,,,
  AAMV3,13:00:00,13:00:00,BEATTY_AIRPORT,1,,,,
  AAMV3,14:00:00,14:00:00,AMV,2,,,,
  AAMV4,15:00:00,15:00:00,AMV,1,,,,
  AAMV4,16:00:00,16:00:00,BEATTY_AIRPORT,2,,,,
 
  stop_id,stop_name,stop_desc,stop_lat,stop_lon,zone_id,stop_url
  FUR_CREEK_RES,Furnace Creek Resort (Démonstration),,36.425288,-117.133162,,
  BEATTY_AIRPORT,Nye County Airport (Demo),,36.868446,-116.784582,,
  BULLFROG,Bullfrog (Demo),,36.88108,-116.81797,,
  STAGECOACH,Stagecoach Hotel & Casino (Demo),,36.915682,-116.751677,,
  NADAV,North Ave / D Ave N (Demo),,36.914893,-116.76821,,
  NANAA,North Ave / N A Ave (Demo),,36.914944,-116.761472,,
  DADAN,Doing Ave / D Ave N (Demo),,36.909489,-116.768242,,
  EMSI,E Main St / S Irving St (Demo),,36.905697,-116.76218,,
  AMV,Amargosa Valley (Demo),,36.641496,-116.40094,,
 
  route_id,service_id,trip_id,trip_headsign,direction_id,block_id,shape_id
  AB,FULLW,AB1,to Bullfrog,0,1,
  AB,FULLW,AB2,to Airport,1,2,
  STBA,FULLW,STBA,Shuttle,,,
  CITY,FULLW,CITY1,Ō,0,,
  CITY,FULLW,CITY2,Ō,1,,
  BFC,FULLW,BFC1,to Furnace Creek Resort,0,1,
  BFC,FULLW,BFC2,to Bullfrog,1,2,
  AAMV,WE,AAMV1,to Amargosa Valley,0,,
  AAMV,WE,AAMV2,to Airport,1,,
  AAMV,WE,AAMV3,to Amargosa Valley,0,,
  AAMV,WE,AAMV4,to Airport,1,,
 
  agency_id,agency_name,agency_url,agency_timezone
  DTA,Demo Transit Authority,http://google.com,America/Los_Angeles
  service_id,monday,tuesday,wednesday,thursday,friday,saturday,sunday,start_date,end_date
  FULLW,1,1,1,1,1,1,1,20070101,20101231
  WE,0,0,0,0,0,1,1,20070101,20101231
  service_id,date,exception_type
  FULLW,20070604,2
  fare_id,price,currency_type,payment_method,transfers,transfer_duration
  p,1.25,USD,0,0,
  a,5.25,USD,0,0,
 
  fare_id,route_id,origin_id,destination_id,contains_id
  p,AB,,,
  p,STBA,,,
  p,BFC,,,
  a,AAMV,,,
 
  trip_id,start_time,end_time,headway_secs
  STBA,6:00:00,22:00:00,1800
  CITY1,6:00:00,7:59:59,1800
  CITY2,6:00:00,7:59:59,1800
  CITY1,8:00:00,9:59:59,600
  CITY2,8:00:00,9:59:59,600
  CITY1,10:00:00,15:59:59,1800
  CITY2,10:00:00,15:59:59,1800
  CITY1,16:00:00,18:59:59,600
  CITY2,16:00:00,18:59:59,600
  CITY1,19:00:00,22:00:00,1800
  CITY2,19:00:00,22:00:00,1800
  route_id,agency_id,route_short_name,route_long_name,route_desc,route_type,route_url,route_color,route_text_color
  AB,DTA,,Airport - Bullfrog,,3
  BFC,DTA,,Bullfrog - Furnace Creek Resort,,3
  STBA,DTA,,Stagecoach - Airport Shuttle,,3
  CITY,DTA,,City,,3
  AAMV,DTA,,Airport - Amargosa Valley,,3
  trip_id,arrival_time,departure_time,stop_id,stop_sequence,stop_headsign,pickup_type,drop_off_type,shape_dist_traveled
  STBA,6:00:00,6:00:00,STAGECOACH,1
  STBA,6:20:00,6:20:00,BEATTY_AIRPORT,2
  CITY1,6:00:00,6:00:00,STAGECOACH,1
  CITY1,6:05:00,6:07:00,NANAA,2
  CITY1,6:12:00,6:14:00,NADAV,3
  CITY1,6:19:00,6:21:00,DADAN,4
  CITY1,6:26:00,6:28:00,EMSI,5
  CITY2,6:28:00,6:30:00,EMSI,1
  CITY2,6:35:00,6:37:00,DADAN,2
  CITY2,6:42:00,6:44:00,NADAV,3
  CITY2,6:49:00,6:51:00,NANAA,4
  CITY2,6:56:00,6:58:00,STAGECOACH,5
  AB1,8:00:00,8:00:00,BEATTY_AIRPORT,1
  AB1,8:10:00,8:15:00,BULLFROG,2
  AB2,12:05:00,12:05:00,BULLFROG,1
  AB2,12:15:00,12:15:00,BEATTY_AIRPORT,2
  BFC1,8:20:00,8:20:00,FROG,1
  BFC1,9:20:00,9:20:00,FUR_CREEK_RES,2
  BFC2,11:00:00,11:00:00,FUR_CREEK_RES,1
  BFC2,12:00:00,12:00:00,BULLFROG,2
  AAMV1,8:00:00,8:00:00,BEATTY_AIRPORT,1
  AAMV1,9:00:00,9:00:00,AMV,2
  AAMV2,10:00:00,10:00:00,AMV,1
  AAMV2,11:00:00,11:00:00,BEATTY_AIRPORT,2
  AAMV3,13:00:00,13:00:00,BEATTY_AIRPORT,1
  AAMV3,14:00:00,14:00:00,AMV,2
  AAMV4,15:00:00,15:00:00,AMV,1
  AAMV4,16:00:00,16:00:00,BEATTY_AIRPORT,2
  stop_id,stop_name,stop_desc,stop_lat,stop_lon,zone_id,stop_url
  FUR_CREEK_RES,Furnace Creek Resort (Demo),,36.425288,-117.133162
  BEATTY_AIRPORT,Nye County Airport (Demo),,36.868446,-116.784582
  BULLFROG,Bullfrog (Demo),,36.88108,-116.81797
  FROG,Bull Frog,,36.881083,-116.817968
  STAGECOACH,Stagecoach Hotel & Casino (Demo),,36.915682,-116.751677
  NADAV,North Ave / D Ave N (Demo),,36.914893,-116.76821
  NANAA,North Ave / N A Ave (Demo),,36.914944,-116.761472
  DADAN,Doing Ave / D Ave N (Demo),,36.909489,-116.768242
  EMSI,E Main St / S Irving St (Demo),,36.905697,-116.76218
  AMV,Amargosa Valley (Demo),,36.641496,-116.40094
  route_id,service_id,trip_id,trip_headsign,direction_id,block_id,shape_id
  AB,FULLW,AB1,to Bullfrog,0,1
  AB,FULLW,AB2,to Airport,1,2
  STBA,FULLW,STBA,Shuttle
  CITY,FULLW,CITY1,,0
  CITY,FULLW,CITY2,,1
  BFC,FULLW,BFC1,to Furnace Creek Resort,0,1
  BFC,FULLW,BFC2,to Bullfrog,1,2
  AAMV,WE,AAMV1,to Amargosa Valley,0
  AAMV,WE,AAMV2,to Airport,1
  AAMV,WE,AAMV3,to Amargosa Valley,0
  AAMV,WE,AAMV4,to Airport,1
  agency_id,agency_name,agency_url,agency_timezone
  DTA,Autorité de passage de démonstration,http://google.com,America/Los_Angeles
 
  service_id,monday,tuesday,wednesday,thursday,friday,saturday,sunday,start_date,end_date
  FULLW,1,1,1,1,1,1,1,20070101,20101231
 
  route_id,agency_id,route_short_name,route_long_name,route_desc,route_type,route_url,route_color,route_text_color
  CITY,DTA,Ō,Bar Circle,Route with ĸool unicode shortname,3,,,
 
  trip_id,arrival_time,departure_time,stop_id,stop_sequence,stop_headsign,pickup_type,drop_off_type,shape_dist_traveled
  CITY1,6:00:00,6:00:00,STAGECOACH,0,,,,
  CITY1,6:05:00,6:07:00,NANAA,10,going to nadav,2,3,
  CITY1,6:12:00,6:14:00,NADAV,10,,,,
  CITY1,6:19:00,6:21:00,DADAN,15,,,,
  CITY1,6:26:00,6:28:00,EMSI,20,,,,
 
  stop_id,stop_name,stop_desc,stop_lat,stop_lon,zone_id,stop_url,stop_code
  STAGECOACH,Stagecoach Hotel & Casino (Demo),,36.915682,-116.751677,,,1236
  NADAV,North Ave / D Ave N (Demo),,36.914893,-116.76821,,,1237
  NANAA,North Ave / N A Ave (Demo),,36.914944,-116.761472,,,1238
  DADAN,Doing Ave / D Ave N (Demo),,36.909489,-116.768242,,,
  EMSI,E Main St / S Irving St (Demo),,36.905697,-116.76218,,,
 
  route_id,service_id,trip_id,trip_headsign,direction_id,block_id,shape_id
  CITY,FULLW,CITY1,,0,,
 
  service_id,monday,tuesday,wednesday,thursday,friday,saturday,sunday,start_date,end_date
  FULLW,1,1,1,1,1,1,1,20070101,20101231
  WE,0,0,0,0,0,1,1,20070101,20101231
  service_id,date,exception_type
  FULLW,20070604,2
  fare_id,price,currency_type,payment_method,transfers,transfer_duration
  p,1.25,USD,0,0,
  a,5.25,USD,0,0,
 
  fare_id,route_id,origin_id,destination_id,contains_id
  p,AB,,,
  p,STBA,,,
  p,BFC,,,
  a,AAMV,,,
 
  trip_id,start_time,end_time,headway_secs
  STBA,6:00:00,22:00:00,1800
  CITY1,6:00:00,7:59:59,1800
  CITY2,6:00:00,7:59:59,1800
  CITY1,8:00:00,9:59:59,600
  CITY2,8:00:00,9:59:59,600
  CITY1,10:00:00,15:59:59,1800
  CITY2,10:00:00,15:59:59,1800
  CITY1,16:00:00,18:59:59,600
  CITY2,16:00:00,18:59:59,600
  CITY1,19:00:00,22:00:00,1800
  CITY2,19:00:00,22:00:00,1800
  route_id,agency_id,route_short_name,route_long_name,route_desc,route_type,route_url,route_color,route_text_color
  AB,DTA,,Airport - Bullfrog,,3
  BFC,DTA,,Bullfrog - Furnace Creek Resort,,3
  STBA,DTA,,Stagecoach - Airport Shuttle,,3
  CITY,DTA,,City,,3
  AAMV,DTA,,Airport - Amargosa Valley,,3
  trip_id,arrival_time,departure_time,stop_id,stop_sequence
  STBA,6:00:00,6:00:00,STAGECOACH,1
  STBA,6:20:00,6:20:00,BEATTY_AIRPORT,2
  CITY1,6:00:00,6:00:00,STAGECOACH,1
  CITY1,6:05:00,6:07:00,NANAA,2
  CITY1,6:12:00,6:14:00,NADAV,3
  CITY1,6:19:00,6:21:00,DADAN,4
  CITY1,6:26:00,6:28:00,EMSI,5
  CITY2,6:28:00,6:30:00,EMSI,1
  CITY2,6:35:00,6:37:00,DADAN,2
  CITY2,6:42:00,6:44:00,NADAV,3
  CITY2,6:49:00,6:51:00,NANAA,4
  CITY2,6:56:00,6:58:00,STAGECOACH,5
  AB1,8:00:00,8:00:00,BEATTY_AIRPORT,1
  AB1,8:10:00,8:15:00,BULLFROG,2
  AB2,12:05:00,12:05:00,BULLFROG,1
  AB2,12:15:00,12:15:00,BEATTY_AIRPORT,2
  BFC1,8:20:00,8:20:00,BULLFROG,1
  BFC1,9:20:00,9:20:00,FUR_CREEK_RES,2
  BFC2,11:00:00,11:00:00,FUR_CREEK_RES,1
  BFC2,12:00:00,12:00:00,BULLFROG,2
  AAMV1,8:00:00,8:00:00,BEATTY_AIRPORT,1
  AAMV1,9:00:00,9:00:00,AMV,2
  AAMV2,10:00:00,10:00:00,AMV,1
  AAMV2,11:00:00,11:00:00,BEATTY_AIRPORT,2
  AAMV3,13:00:00,13:00:00,BEATTY_AIRPORT,1
  AAMV3,14:00:00,14:00:00,AMV,2
  AAMV4,15:00:00,15:00:00,AMV,1
  AAMV4,16:00:00,16:00:00,BEATTY_AIRPORT,2
 
  stop_id,stop_name,stop_desc,stop_lat,stop_lon,zone_id,stop_url
  FUR_CREEK_RES,Furnace Creek Resort (Demo),,36.425288,-117.133162
  BEATTY_AIRPORT,Nye County Airport (Demo),,36.868446,-116.784582
  BULLFROG,Bullfrog (Demo),,36.88108,-116.81797
  STAGECOACH,Stagecoach Hotel & Casino (Demo),,36.915682,-116.751677
  NADAV,North Ave / D Ave N (Demo),,36.914893,-116.76821
  NANAA,North Ave / N A Ave (Demo),,36.914944,-116.761472
  DADAN,Doing Ave / D Ave N (Demo),,36.909489,-116.768242
  EMSI,E Main St / S Irving St (Demo),,36.905697,-116.76218
  AMV,Amargosa Valley (Demo),,36.641496,-116.40094
  route_id,service_id,trip_id,trip_headsign,direction_id,block_id,shape_id
  AB,FULLW,AB1,to Bullfrog,0,1
  AB,FULLW,AB2,to Airport,1,2
  STBA,FULLW,STBA,Shuttle
  CITY,FULLW,CITY1,,0
  CITY,FULLW,CITY2,,1
  BFC,FULLW,BFC1,to Furnace Creek Resort,0,1
  BFC,FULLW,BFC2,to Bullfrog,1,2
  AAMV,WE,AAMV1,to Amargosa Valley,0
  AAMV,WE,AAMV2,to Airport,1
  AAMV,WE,AAMV3,to Amargosa Valley,0
  AAMV,WE,AAMV4,to Airport,1
  agency_id,agency_name,agency_url,agency_timezone
  DTA,Demo Transit Authority,http://google.com,America/Los_Angeles
  service_id,monday,tuesday,wednesday,thursday,friday,saturday,sunday,start_date,end_date
  FULLW,1,1,1,1,1,1,1,20070101,20101231
  WE,0,0,0,0,0,1,1,20070101,20101231
  service_id,date,exception_type
  FULLW,20070604,2
  fare_id,price,currency_type,payment_method,transfers,transfer_duration
  p,1.25,USD,0,0,
  a,5.25,USD,0,0,
 
  fare_id,route_id,origin_id,destination_id,contains_id
  p,AB,,,
  p,STBA,,,
  p,BFC,,,
  a,AAMV,,,
 
  trip_id,start_time,end_time,headway_secs
  STBA,6:00:00,22:00:00,1800
  CITY1,6:00:00,7:59:59,1800
  CITY2,6:00:00,7:59:59,1800
  CITY1,8:00:00,9:59:59,600
  CITY2,8:00:00,9:59:59,600
  CITY1,10:00:00,15:59:59,1800
  CITY2,10:00:00,15:59:59,1800
  CITY1,16:00:00,18:59:59,600
  CITY2,16:00:00,18:59:59,600
  CITY1,19:00:00,22:00:00,1800
  CITY2,19:00:00,22:00:00,1800
  route_id,agency_id,route_short_name,route_long_name,route_desc,route_type
  AB,DTA,,Airport - Bullfrog,,3
  BFC,DTA,,Bullfrog - Furnace Creek Resort,,3
  STBA,DTA,,Stagecoach - Airport Shuttle,,3,
  CITY,DTA,,City,,3
  AAMV,DTA,,Airport - Amargosa Valley,,3
 
  trip_id,arrival_time,departure_time,stop_id,stop_sequence
  STBA,6:00:00,6:00:00,STAGECOACH,1
  STBA,6:20:00,6:20:00,BEATTY_AIRPORT,2
  CITY1,6:00:00,6:00:00,STAGECOACH,1
  CITY1,6:05:00,6:07:00,NANAA,2
  CITY1,6:12:00,6:14:00,NADAV,3
  CITY1,6:19:00,6:21:00,DADAN,4
  CITY1,6:26:00,6:28:00,EMSI,5
  CITY2,6:28:00,6:30:00,EMSI,1
  CITY2,6:35:00,6:37:00,DADAN,2
  CITY2,6:42:00,6:44:00,NADAV,3
  CITY2,6:49:00,6:51:00,NANAA,4
  CITY2,6:56:00,6:58:00,STAGECOACH,5
  AB1,8:00:00,8:00:00,BEATTY_AIRPORT,1
  AB1,8:10:00,8:15:00,BULLFROG,2
  AB2,12:05:00,12:05:00,BULLFROG,1
  AB2,12:15:00,12:15:00,BEATTY_AIRPORT,2
  BFC1,8:20:00,8:20:00,BULLFROG,1
  BFC1,9:20:00,9:20:00,FUR_CREEK_RES,2
  BFC2,11:00:00,11:00:00,FUR_CREEK_RES,1
  BFC2,12:00:00,12:00:00,BULLFROG,2
  AAMV1,8:00:00,8:00:00,BEATTY_AIRPORT,1
  AAMV1,9:00:00,9:00:00,AMV,2
  AAMV2,10:00:00,10:00:00,AMV,1
  AAMV2,11:00:00,11:00:00,BEATTY_AIRPORT,2
  AAMV3,13:00:00,13:00:00,BEATTY_AIRPORT,1
  AAMV3,14:00:00,14:00:00,AMV,2
  AAMV4,15:00:00,15:00:00,AMV,1
  AAMV4,16:00:00,16:00:00,BEATTY_AIRPORT,2
 
  stop_id,stop_name,stop_desc,stop_lat,stop_lon
  FUR_CREEK_RES,Furnace Creek Resort (Demo),,36.425288,-117.133162
  BEATTY_AIRPORT,Nye County Airport (Demo),,36.868446,-116.784582
  BULLFROG,Bullfrog (Demo),,36.88108,-116.81797
  STAGECOACH,Stagecoach Hotel & Casino (Demo),,36.915682,-116.751677
  NADAV,North Ave / D Ave N (Demo),,36.914893,-116.76821
  NANAA,North Ave / N A Ave (Demo),,36.914944,-116.761472
  DADAN,Doing Ave / D Ave N (Demo),,36.909489,-116.768242
  EMSI,E Main St / S Irving St (Demo),,36.905697,-116.76218
  AMV,Amargosa Valley (Demo),,36.641496,-116.40094
 
  route_id,service_id,trip_id,trip_headsign,direction_id,block_id
  AB,FULLW,AB1,to Bullfrog,0,1
  AB,FULLW,AB2,to Airport,1,2
  STBA,FULLW,STBA,Shuttle,1,
  CITY,FULLW,CITY1,,0,
  CITY,FULLW,CITY2,,1,
  BFC,FULLW,BFC1,to Furnace Creek Resort,0,1
  BFC,FULLW,BFC2,to Bullfrog,1,2
  AAMV,WE,AAMV1,to Amargosa Valley,0,
  AAMV,WE,AAMV2,to Airport,1,
  AAMV,WE,AAMV3,to Amargosa Valley,0,
  AAMV,WE,AAMV4,to Airport,1,
 
  agency_id,agency_name,agency_url,agency_timezone
  DTA,Demo Transit Authority,http://google.com,America/Los_Angeles
  service_id,monday,tuesday,wednesday,thursday,friday,saturday,sunday,start_date,end_date
  FULLW,1,1,1,1,1,1,1,20070101,20101231
  WE,0,0,0,0,0,1,1,20070101,20101231
  service_id,date,exception_type
  FULLW,20070604,2
  fare_id,price,currency_type,payment_method,transfers,transfer_duration
  p,1.25,USD,0,0,
  a,5.25,USD,0,0,
  fare_id,route_id,origin_id,destination_id,contains_id
  p,AB,,,
  p,STBA,,,
  p,BFC,,,
  a,AAMV,,,
  trip_id,start_time,end_time,headway_secs
  STBA,6:00:00,22:00:00,1800
  CITY1,6:00:00,7:59:59,1800
  CITY2,6:00:00,7:59:59,1800
  CITY1,8:00:00,9:59:59,600
  CITY2,8:00:00,9:59:59,600
  CITY1,10:00:00,15:59:59,1800
  CITY2,10:00:00,15:59:59,1800
  CITY1,16:00:00,18:59:59,600
  CITY2,16:00:00,18:59:59,600
  CITY1,19:00:00,22:00:00,1800
  CITY2,19:00:00,22:00:00,1800
  route_id,agency_id,route_short_name,route_long_name,route_desc,route_type,route_url,route_color,route_text_color
  AB,DTA,10,Airport - Bullfrog,,3,,,
  BFC,DTA,20,Bullfrog - Furnace Creek Resort,,3,,,
  STBA,DTA,30,Stagecoach - Airport Shuttle,,3,,,
  CITY,DTA,40,City,,3,,,
  AAMV,DTA,50,Airport - Amargosa Valley,,3,,,
  shape_id,shape_pt_lat,shape_pt_lon,shape_pt_sequence,shape_dist_traveled
  trip_id,arrival_time,departure_time,stop_id,stop_sequence,stop_headsign,pickup_type,shape_dist_traveled
  STBA,6:00:00,6:00:00,STAGECOACH,1,,,
  STBA,6:20:00,6:20:00,BEATTY_AIRPORT,2,,,
  CITY1,6:00:00,6:00:00,STAGECOACH,1,,,
  CITY1,6:05:00,6:07:00,NANAA,2,,,
  CITY1,6:12:00,6:14:00,NADAV,3,,,
  CITY1,6:19:00,6:21:00,DADAN,4,,,
  CITY1,6:26:00,6:28:00,EMSI,5,,,
  CITY2,6:28:00,6:30:00,EMSI,1,,,
  CITY2,6:35:00,6:37:00,DADAN,2,,,
  CITY2,6:42:00,6:44:00,NADAV,3,,,
  CITY2,6:49:00,6:51:00,NANAA,4,,,
  CITY2,6:56:00,6:58:00,STAGECOACH,5,,,
  CITY3,6:00:00,6:00:00,STAGECOACH,1,,,
  CITY3,6:05:00,6:07:00,NANAA,2,,,
  CITY3,6:12:00,6:14:00,NADAV,3,,,
  CITY3,6:19:00,6:21:00,DADAN,4,,,
  CITY3,6:26:00,6:28:00,EMSI,5,,,
  CITY4,6:28:00,6:30:00,EMSI,1,,,
  CITY4,6:35:00,6:37:00,DADAN,2,,,
  CITY4,6:42:00,6:44:00,NADAV,3,,,
  CITY4,6:49:00,6:51:00,NANAA,4,,,
  CITY4,6:56:00,6:58:00,STAGECOACH,5,,,
  CITY5,6:00:00,6:00:00,STAGECOACH,1,,,
  CITY5,6:05:00,6:07:00,NANAA,2,,,
  CITY5,6:12:00,6:14:00,NADAV,3,,,
  CITY5,6:19:00,6:21:00,DADAN,4,,,
  CITY5,6:26:00,6:28:00,EMSI,5,,,
  CITY6,6:28:00,6:30:00,EMSI,1,,,
  CITY6,6:35:00,6:37:00,DADAN,2,,,
  CITY6,6:42:00,6:44:00,NADAV,3,,,
  CITY6,6:49:00,6:51:00,NANAA,4,,,
  CITY6,6:56:00,6:58:00,STAGECOACH,5,,,
  CITY7,6:00:00,6:00:00,STAGECOACH,1,,,
  CITY7,6:05:00,6:07:00,NANAA,2,,,
  CITY7,6:12:00,6:14:00,NADAV,3,,,
  CITY7,6:19:00,6:21:00,DADAN,4,,,
  CITY7,6:26:00,6:28:00,EMSI,5,,,
  CITY8,6:28:00,6:30:00,EMSI,1,,,
  CITY8,6:35:00,6:37:00,DADAN,2,,,
  CITY8,6:42:00,6:44:00,NADAV,3,,,
  CITY8,6:49:00,6:51:00,NANAA,4,,,
  CITY8,6:56:00,6:58:00,STAGECOACH,5,,,
  CITY9,6:00:00,6:00:00,STAGECOACH,1,,,
  CITY9,6:05:00,6:07:00,NANAA,2,,,
  CITY9,6:12:00,6:14:00,NADAV,3,,,
  CITY9,6:19:00,6:21:00,DADAN,4,,,
  CITY9,6:26:00,6:28:00,EMSI,5,,,
  CITY10,6:28:00,6:30:00,EMSI,1,,,
  CITY10,6:35:00,6:37:00,DADAN,2,,,
  CITY10,6:42:00,6:44:00,NADAV,3,,,
  CITY10,6:49:00,6:51:00,NANAA,4,,,
  CITY10,6:56:00,6:58:00,STAGECOACH,5,,,
  CITY11,6:00:00,6:00:00,NANAA,1,,,
  CITY11,6:05:00,6:07:00,BEATTY_AIRPORT,2,,,
  CITY11,6:12:00,6:14:00,BULLFROG,3,,,
  CITY11,6:19:00,6:21:00,DADAN,4,,,
  CITY11,6:26:00,6:28:00,EMSI,5,,,
  CITY12,6:28:00,6:30:00,EMSI,1,,,
  CITY12,6:35:00,6:37:00,DADAN,2,,,
  CITY12,7:07:00,7:09:00,AMV,3,,,
  CITY12,7:39:00,7:41:00,BEATTY_AIRPORT,4,,,
  CITY12,7:46:00,7:48:00,STAGECOACH,5,,,
  AB1,8:00:00,8:00:00,BEATTY_AIRPORT,1,,,
  AB1,8:10:00,8:15:00,BULLFROG,2,,,
  AB2,12:05:00,12:05:00,BULLFROG,1,,,
  AB2,12:15:00,12:15:00,BEATTY_AIRPORT,2,,,
  BFC1,8:20:00,8:20:00,BULLFROG,1,,,
  BFC1,9:20:00,9:20:00,FUR_CREEK_RES,2,,,
  BFC2,11:00:00,11:00:00,FUR_CREEK_RES,1,,,
  BFC2,12:00:00,12:00:00,BULLFROG,2,,,
  AAMV1,8:00:00,8:00:00,BEATTY_AIRPORT,1,,,
  AAMV1,9:00:00,9:00:00,AMV,2,,,
  AAMV2,10:00:00,10:00:00,AMV,1,,,
  AAMV2,11:00:00,11:00:00,BEATTY_AIRPORT,2,,,
  AAMV3,13:00:00,13:00:00,BEATTY_AIRPORT,1,,,
  AAMV3,14:00:00,14:00:00,AMV,2,,,
  AAMV4,15:00:00,15:00:00,AMV,1,,,
  AAMV4,16:00:00,16:00:00,BEATTY_AIRPORT,2,,,
 
  stop_id,stop_name,stop_desc,stop_lat,stop_lon,zone_id,stop_url
  FUR_CREEK_RES,Furnace Creek Resort (Demo),,36.425288,-117.133162,,
  BEATTY_AIRPORT,Nye County Airport (Demo),,36.868446,-116.784582,,
  BULLFROG,Bullfrog (Demo),,36.88108,-116.81797,,
  STAGECOACH,Stagecoach Hotel & Casino (Demo),,36.915682,-116.751677,,
  NADAV,North Ave / D Ave N (Demo),,36.914893,-116.76821,,
  NANAA,North Ave / N A Ave (Demo),,36.914944,-116.761472,,
  DADAN,Doing Ave / D Ave N (Demo),,36.909489,-116.768242,,
  EMSI,E Main St / S Irving St (Demo),,36.905697,-116.76218,,
  AMV,Amargosa Valley (Demo),,36.641496,-116.40094,,
  route_id,service_id,trip_id,trip_headsign,direction_id,block_id,shape_id
  AB,FULLW,AB1,to Bullfrog,0,1,
  AB,FULLW,AB2,to Airport,1,2,
  STBA,FULLW,STBA,Shuttle,,,
  CITY,FULLW,CITY1,,0,,
  CITY,FULLW,CITY2,,1,,
  CITY,FULLW,CITY3,,0,,
  CITY,FULLW,CITY4,,1,,
  CITY,FULLW,CITY5,,0,,
  CITY,FULLW,CITY6,,1,,
  CITY,FULLW,CITY7,,0,,
  CITY,FULLW,CITY8,,1,,
  CITY,FULLW,CITY9,,0,,
  CITY,FULLW,CITY10,,1,,
  CITY,FULLW,CITY11,,0,,
  CITY,FULLW,CITY12,,1,,
  BFC,FULLW,BFC1,to Furnace Creek Resort,0,1,
  BFC,FULLW,BFC2,to Bullfrog,1,2,
  AAMV,WE,AAMV1,to Amargosa Valley,0,,
  AAMV,WE,AAMV2,to Airport,1,,
  AAMV,WE,AAMV3,to Amargosa Valley,0,,
  AAMV,WE,AAMV4,to Airport,1,,
  agency_id,agency_name,agency_url,agency_timezone
  DTA,Demo Transit Authority,http://google.com,America/Los_Angeles
  service_id,monday,tuesday,wednesday,thursday,friday,saturday,sunday,start_date,end_date
  FULLW,1,1,1,1,1,1,1,20070101,20101231
  WE,0,0,0,0,0,1,1,20070101,20101231
  service_id,date,exception_type
  FULLW,20070604,2
  WE,20070604,1
 
  fare_id,price,currency_type,payment_method,transfers,transfer_duration
  p,1.25,USD,0,0,
  a,5.25,USD,0,0,
 
  route_id,agency_id,route_short_name,route_long_name,route_desc,route_type,route_url,route_color,route_text_color
  route_1,DTA,1,route with a single trip,,0,http://routes.com/route_1,FF0000,
  route_2,DTA,2,route with two trips and one component,test route desc 2,1,,00FF00,
  route_3,DTA,3,route with two trips and two components,test route desc 3,2,http://routes.com/route_3,,
  route_4,DTA,4,route with two equal trips,test route desc 4,3,http://routes.com/route_4,FFFF00,
  route_5,DTA,5,route with two trip but no graph,test route desc 5,4,http://routes.com/route_5,FF00FF,
  route_6,DTA,6,route with one trip and no stops,test route desc 6,5,http://routes.com/route_6,00FFFF,
  route_7,DTA,7,route with no trips,test route desc 7,6,http://routes.com/route_7,,
  route_8,DTA,8,route with a cyclic pattern,test route desc 8,7,http://routes.com/route_8,,
 
  shape_id,shape_pt_sequence,shape_pt_lat,shape_pt_lon
  shape_1,1,1,1
  shape_1,2,2,4
  shape_1,3,3,9
  shape_1,4,4,16
  shape_2,1,11,11
  shape_2,2,12,14
  shape_2,3,13,19
  shape_2,4,14,26
  shape_3,1,21,21
  shape_3,2,22,24
  shape_3,3,23,29
  shape_3,4,24,36
 
  trip_id,arrival_time,departure_time,stop_id,stop_sequence
  route_1_1,6:00:00,6:00:00,stop1,1
  route_1_1,7:00:00,7:00:00,stop2,2
  route_1_1,8:00:00,8:00:00,stop3,3
  route_2_1,6:00:00,6:00:00,stop1,1
  route_2_1,7:00:00,7:00:00,stop2,2
  route_2_1,8:00:00,8:00:00,stop3,3
  route_2_2,6:00:00,6:00:00,stop2,1
  route_2_2,7:00:00,7:00:00,stop4,2
  route_2_2,8:00:00,8:00:00,stop5,3
  route_3_1,6:00:00,6:00:00,stop1,1
  route_3_1,7:00:00,7:00:00,stop2,2
  route_3_1,8:00:00,8:00:00,stop3,3
  route_3_2,6:00:00,6:00:00,stop4,1
  route_3_2,7:00:00,7:00:00,stop5,2
  route_3_2,8:00:00,8:00:00,stop6,3
  route_4_1,6:00:00,6:00:00,stop1,1
  route_4_1,7:00:00,7:00:00,stop2,2
  route_4_1,8:00:00,8:00:00,stop3,3
  route_4_2,6:00:00,6:00:00,stop1,1
  route_4_2,7:00:00,7:00:00,stop2,2
  route_4_2,8:00:00,8:00:00,stop3,3
  route_5_1,6:00:00,6:00:00,stop1,1
  route_5_2,6:00:00,6:00:00,stop2,1
  route_8_1,6:00:00,6:00:00,stop1,1
  route_8_1,7:00:00,7:00:00,stop2,2
  route_8_1,8:00:00,8:00:00,stop3,3
  route_8_1,9:00:00,9:00:00,stop1,4
 
  stop_id,stop_name,stop_desc,stop_lat,stop_lon,zone_id,stop_url
  stop1,Furnace Creek Resort (Demo),,36.425288,-117.133162,,http://stops.com/stop1
  stop2,Nye County Airport (Demo),the stop at Nye County Airport,36.868446,-116.784582,,
  stop3,Bullfrog (Demo),the stop at Bullfrog,36.88108,-116.81797,,http://stops.com/stop3
  stop4,Stagecoach Hotel & Casino (Demo),the stop at Stagecoach Hotel & Casino,36.915682,-116.751677,,http://stops.com/stop4
  stop5,North Ave / D Ave N (Demo),the stop at North Ave / D Ave N,36.914893,-116.76821,,http://stops.com/stop5
  stop6,North Ave / N A Ave (Demo),the stop at North Ave / N A Ave,36.914944,-116.761472,,http://stops.com/stop6
  stop7,Doing Ave / D Ave N (Demo),the stop at Doing Ave / D Ave N,36.909489,-116.768242,,http://stops.com/stop7
  stop8,E Main St / S Irving St (Demo),the stop at E Main St / S Irving St,36.905697,-116.76218,,http://stops.com/stop8
  stop9,Amargosa Valley (Demo),the stop at Amargosa Valley,36.641496,-116.40094,,http://stops.com/stop9
 
  route_id,service_id,trip_id,shape_id
  route_1,FULLW,route_1_1,shape_1
  route_2,FULLW,route_2_1,shape_2
  route_2,FULLW,route_2_2,shape_3
  route_3,FULLW,route_3_1,shape_1
  route_3,FULLW,route_3_2,shape_1
  route_4,FULLW,route_4_1,
  route_4,FULLW,route_4_2,
  route_5,FULLW,route_5_1,
  route_5,FULLW,route_5_2,
  route_8,FULLW,route_8_1,
  route_8,WE,route_8_2,
 
 Binary files /dev/null and b/origin-src/transitfeed-1.2.6/test/data/good_feed.zip differ
  agency_id,agency_name,agency_url,agency_timezone,agency_phone
  DTA,Autorité de passage de démonstration,http://google.com,America/Los_Angeles,123 12314
 
  service_id,monday,tuesday,wednesday,thursday,friday,saturday,sunday,start_date,end_date
  FULLW,1,1,1,1,1,1,1,20070101,20111231
  WE,0,0,0,0,0,1,1,20070101,20111231
 
  service_id,date,exception_type
  FULLW,20070604,2
  fare_id,price,currency_type,payment_method,transfers,transfer_duration
  p,1.25,USD,0,0,
  a,5.25,USD,0,0,
 
  fare_id,route_id,origin_id,destination_id,contains_id
  p,AB,,,
  p,STBA,,,
  p,BFC,,,
  a,AAMV,,,
 
  trip_id,start_time,end_time,headway_secs
  STBA,6:00:00,22:00:00,1800
  CITY1,6:00:00,7:59:59,1800
  CITY2,6:00:00,7:59:59,1800
  CITY1,8:00:00,9:59:59,600
  CITY2,8:00:00,9:59:59,600
  CITY1,10:00:00,15:59:59,1800
  CITY2,10:00:00,15:59:59,1800
  CITY1,16:00:00,18:59:59,600
  CITY2,16:00:00,18:59:59,600
  CITY1,19:00:00,22:00:00,1800
  CITY2,19:00:00,22:00:00,1800
  route_id,agency_id,route_short_name,route_long_name,route_desc,route_type,route_url,route_color,route_text_color
  AB,DTA,,Airport ⇒ Bullfrog,,3,,,
  BFC,DTA,,Bullfrog ⇒ Furnace Creek Resort,,3,,,
  STBA,DTA,,Stagecoach ⇒ Airport Shuttle,,3,,,
  CITY,DTA,Ō,Bar Circle,Route with ĸool unicode shortname,3,,,
  AAMV,DTA,,Airport ⇒ Amargosa Valley,,3,,,
 
  trip_id,arrival_time,departure_time,stop_id,stop_sequence,stop_headsign,pickup_type,drop_off_type,shape_dist_traveled
  STBA,6:00:00,6:00:00,STAGECOACH,0,to airport,1,0,0.212
  STBA,6:20:00,6:20:00,BEATTY_AIRPORT,2,,0,0,1.043
  CITY1,6:00:00,6:00:00,STAGECOACH,0,,,,
  CITY1,6:05:00,6:07:00,NANAA,5,going to nadav,2,3,
  CITY1,6:12:00,6:14:00,NADAV,10,,,,
  CITY1,6:19:00,6:21:00,DADAN,15,,,,
  CITY1,6:26:00,6:28:00,EMSI,20,,,,
  CITY2,6:28:00,6:30:00,EMSI,100,,,,
  CITY2,6:35:00,6:37:00,DADAN,200,,,,
  CITY2,6:42:00,6:44:00,NADAV,300,,,,
  CITY2,6:49:00,6:51:00,NANAA,400,,,,
  CITY2,6:56:00,6:58:00,STAGECOACH,500,,,,
  AB1,8:00:00,8:00:00,BEATTY_AIRPORT,1,,,,
  AB1,8:10:00,8:15:00,BULLFROG,2,,,,
  AB2,12:05:00,12:05:00,BULLFROG,1,,,,
  AB2,12:15:00,12:15:00,BEATTY_AIRPORT,2,,,,
  BFC1,8:20:00,8:20:00,BULLFROG,1,,,,
  BFC1,9:20:00,9:20:00,FUR_CREEK_RES,2,,,,
  BFC2,11:00:00,11:00:00,FUR_CREEK_RES,1,,,,
  BFC2,12:00:00,12:00:00,BULLFROG,2,,,,
  AAMV1,8:00:00,8:00:00,BEATTY_AIRPORT,1,,,,
  AAMV1,9:00:00,9:00:00,AMV,2,,,,
  AAMV2,10:00:00,10:00:00,AMV,1,,,,
  AAMV2,11:00:00,11:00:00,BEATTY_AIRPORT,2,,,,
  AAMV3,13:00:00,13:00:00,BEATTY_AIRPORT,1,,,,
  AAMV3,14:00:00,14:00:00,AMV,2,,,,
  AAMV4,15:00:00,15:00:00,AMV,1,,,,
  AAMV4,16:00:00,16:00:00,BEATTY_AIRPORT,2,,,,
 
  stop_id,stop_name,stop_desc,stop_lat,stop_lon,zone_id,stop_url,stop_code,location_type,parent_station
  FUR_CREEK_RES,Furnace Creek Resort (Demo),,36.425288,-117.133162,,,1234,,
  BEATTY_AIRPORT,Nye County Airport (Demo),,36.868446,-116.784582,,,1235,0,BEATTY_AIRPORT_STATION
  BEATTY_AIRPORT_STATION,Nye County Airport (Demo),,36.868446,-116.784582,,,1235,1,
  BULLFROG,Bullfrog (Demo),,36.88108,-116.81797,,,,,
  STAGECOACH,Stagecoach Hotel & Casino (Demo),,36.915682,-116.751677,,,1236,,
  NADAV,North Ave / D Ave N (Demo),,36.914893,-116.76821,,,1237,,
  NANAA,North Ave / N A Ave (Demo),,36.914944,-116.761472,,,1238,,
  DADAN,Doing Ave / D Ave N (Demo),,36.909489,-116.768242,,,,,
  EMSI,E Main St / S Irving St (Demo),,36.905697,-116.76218,,,,,
  AMV,Amargosa Valley (Demo),,36.641496,-116.40094,,,,,
 
  from_stop_id,to_stop_id,transfer_type,min_transfer_time
  NADAV,NANAA,3,
  EMSI,NANAA,2,1200
 
  route_id,service_id,trip_id,trip_headsign,direction_id,block_id,shape_id
  AB,FULLW,AB1,to Bullfrog,0,1,
  AB,FULLW,AB2,to Airport,1,2,
  STBA,FULLW,STBA,Shuttle,,,
  CITY,FULLW,CITY1,,0,,
  CITY,FULLW,CITY2,,1,,
  BFC,FULLW,BFC1,to Furnace Creek Resort,0,1,
  BFC,FULLW,BFC2,to Bullfrog,1,2,
  AAMV,WE,AAMV1,to Amargosa Valley,0,,
  AAMV,WE,AAMV2,to Airport,1,,
  AAMV,WE,AAMV3,to Amargosa Valley,0,,
  AAMV,WE,AAMV4,to Airport,1,,
  agency_id,agency_name,agency_url,agency_timezone
  DTA,Demo Transit Authority,http://google.com,America/Los_Angeles
  service_id,monday,tuesday,wednesday,thursday,friday,saturday,sunday,start_date,end_date
  FULLW,1,1,1,1,1,1,1,20070101,20101231
  WE,0,0,0,0,0,1,1,20070101,20101231
  service_id,date,exception_type
  FULLW,20070604,2
  route_id,agency_id,route_short_name,route_long_name,route_desc,route_type,route_url,route_color,route_text_color
  AB,DTA,,Airport - Bullfrog,,3,,,
  BFC,DTA,,Bullfrog - Furnace Creek Resort,,3,,,
  STBA,DVT,,Stagecoach - Airport Shuttle,,3,,,
  CITY,DTA,,City,,3,,,
  AAMV,DTA,,Airport - Amargosa Valley,,3,,,
 
  trip_id,arrival_time,departure_time,stop_id,stop_sequence,stop_headsign,pickup_type,drop_off_type,shape_dist_traveled
  STBA,6:00:00,6:00:00,STAGECOACH,1,,,,
  STBA,6:20:00,6:20:00,BEATTY_AIRPORT,2,,,,
  CITY1,6:00:00,6:00:00,STAGECOACH,1,,,,
  CITY1,6:05:00,6:07:00,NANAA,2,,,,
  CITY1,6:12:00,6:14:00,NADAV,3,,,,
  CITY1,6:19:00,6:21:00,DADAN,4,,,,
  CITY1,6:26:00,6:28:00,EMSI,5,,,,
  CITY2,6:28:00,6:30:00,EMSI,1,,,,
  CITY2,6:35:00,6:37:00,DADAN,2,,,,
  CITY2,6:42:00,6:44:00,NADAV,3,,,,
  CITY2,6:49:00,6:51:00,NANAA,4,,,,
  CITY2,6:56:00,6:58:00,STAGECOACH,5,,,,
  AB1,8:00:00,8:00:00,BEATTY_AIRPORT,1,,,,
  AB1,8:10:00,8:15:00,BULLFROG,2,,,,
  AB2,12:05:00,12:05:00,BULLFROG,1,,,,
  AB2,12:15:00,12:15:00,BEATTY_AIRPORT,2,,,,
  BFC1,8:20:00,8:20:00,BULLFROG,1,,,,
  BFC1,9:20:00,9:20:00,FUR_CREEK_RES,2,,,,
  BFC2,11:00:00,11:00:00,FUR_CREEK_RES,1,,,,
  BFC2,12:00:00,12:00:00,BULLFROG,2,,,,
  AAMV1,8:00:00,8:00:00,BEATTY_AIRPORT,1,,,,
  AAMV1,9:00:00,9:00:00,AMV,2,,,,
  AAMV2,10:00:00,10:00:00,AMV,1,,,,
  AAMV2,11:00:00,11:00:00,BEATTY_AIRPORT,2,,,,
  AAMV3,13:00:00,13:00:00,BEATTY_AIRPORT,1,,,,
  AAMV3,14:00:00,14:00:00,AMV,2,,,,
  AAMV4,15:00:00,15:00:00,AMV,1,,,,
  AAMV4,16:00:00,16:00:00,BEATTY_AIRPORT,2,,,,
  stop_id,stop_name,stop_desc,stop_lat,stop_lon,zone_id,stop_url
  FUR_CREEK_RES,Furnace Creek Resort (Demo),,36.425288,-117.133162,,
  BEATTY_AIRPORT,Nye County Airport (Demo),,36.868446,-116.784582,,
  BULLFROG,Bullfrog (Demo),,36.88108,-116.81797,,
  STAGECOACH,Stagecoach Hotel & Casino (Demo),,36.915682,-116.751677,,
  NADAV,North Ave / D Ave N (Demo),,36.914893,-116.76821,,
  NANAA,North Ave / N A Ave (Demo),,36.914944,-116.761472,,
  DADAN,Doing Ave / D Ave N (Demo),,36.909489,-116.768242,,
  EMSI,E Main St / S Irving St (Demo),,36.905697,-116.76218,,
  AMV,Amargosa Valley (Demo),,36.641496,-116.40094,,
  route_id,service_id,trip_id,trip_headsign,direction_id,block_id,shape_id
  AB,FULLW,AB1,to Bullfrog,0,1,
  AB,FULLW,AB2,to Airport,1,2,
  STBA,FULLW,STBA,Shuttle,,,
  CITY,FULLW,CITY1,,0,,
  CITY,FULLW,CITY2,,1,,
  BFC,FULLW,BFC1,to Furnace Creek Resort,0,1,
  BFC,FULLW,BFC2,to Bullfrog,1,2,
  AAMV,WE,AAMV1,to Amargosa Valley,0,,
  AAMV,WE,AAMV2,to Airport,1,,
  AAMV,WE,AAMV3,to Amargosa Valley,0,,
  AAMV,WE,AAMV4,to Airport,1,,
  service_id,monday,tuesday,wednesday,thursday,friday,saturday,sunday,start_date,end_date
  FULLW,1,1,1,1,1,1,1,20070101,20101231
  WE,0,0,0,0,0,1,1,20070101,20101231
  service_id,date,exception_type
  FULLW,20070604,2
  fare_id,price,currency_type,payment_method,transfers,transfer_duration
  p,1.25,USD,0,0,
  a,5.25,USD,0,0,
 
  fare_id,route_id,origin_id,destination_id,contains_id
  p,AB,,,
  p,STBA,,,
  p,BFC,,,
  a,AAMV,,,
 
  trip_id,start_time,end_time,headway_secs
  STBA,6:00:00,22:00:00,1800
  CITY1,6:00:00,7:59:59,1800
  CITY2,6:00:00,7:59:59,1800
  CITY1,8:00:00,9:59:59,600
  CITY2,8:00:00,9:59:59,600
  CITY1,10:00:00,15:59:59,1800
  CITY2,10:00:00,15:59:59,1800
  CITY1,16:00:00,18:59:59,600
  CITY2,16:00:00,18:59:59,600
  CITY1,19:00:00,22:00:00,1800
  CITY2,19:00:00,22:00:00,1800
  route_id,agency_id,route_short_name,route_long_name,route_desc,route_type
  AB,DTA,,Airport - Bullfrog,,3
  BFC,DTA,,Bullfrog - Furnace Creek Resort,,3
  STBA,DTA,,Stagecoach - Airport Shuttle,,3
  CITY,DTA,,City,,3
  AAMV,DTA,,Airport - Amargosa Valley,,3
 
  trip_id,arrival_time,departure_time,stop_id,stop_sequence
  STBA,6:00:00,6:00:00,STAGECOACH,1
  STBA,6:20:00,6:20:00,BEATTY_AIRPORT,2
  CITY1,6:00:00,6:00:00,STAGECOACH,1
  CITY1,6:05:00,6:07:00,NANAA,2
  CITY1,6:12:00,6:14:00,NADAV,3
  CITY1,6:19:00,6:21:00,DADAN,4
  CITY1,6:26:00,6:28:00,EMSI,5
  CITY2,6:28:00,6:30:00,EMSI,1
  CITY2,6:35:00,6:37:00,DADAN,2
  CITY2,6:42:00,6:44:00,NADAV,3
  CITY2,6:49:00,6:51:00,NANAA,4
  CITY2,6:56:00,6:58:00,STAGECOACH,5
  AB1,8:00:00,8:00:00,BEATTY_AIRPORT,1
  AB1,8:10:00,8:15:00,BULLFROG,2
  AB2,12:05:00,12:05:00,BULLFROG,1
  AB2,12:15:00,12:15:00,BEATTY_AIRPORT,2
  BFC1,8:20:00,8:20:00,BULLFROG,1
  BFC1,9:20:00,9:20:00,FUR_CREEK_RES,2
  BFC2,11:00:00,11:00:00,FUR_CREEK_RES,1
  BFC2,12:00:00,12:00:00,BULLFROG,2
  AAMV1,8:00:00,8:00:00,BEATTY_AIRPORT,1
  AAMV1,9:00:00,9:00:00,AMV,2
  AAMV2,10:00:00,10:00:00,AMV,1
  AAMV2,11:00:00,11:00:00,BEATTY_AIRPORT,2
  AAMV3,13:00:00,13:00:00,BEATTY_AIRPORT,1
  AAMV3,14:00:00,14:00:00,AMV,2
  AAMV4,15:00:00,15:00:00,AMV,1
  AAMV4,16:00:00,16:00:00,BEATTY_AIRPORT,2
 
  stop_id,stop_name,stop_desc,stop_lat,stop_lon
  FUR_CREEK_RES,Furnace Creek Resort (Demo),,36.425288,-117.133162
  BEATTY_AIRPORT,Nye County Airport (Demo),,36.868446,-116.784582
  BULLFROG,Bullfrog (Demo),,36.88108,-116.81797
  STAGECOACH,Stagecoach Hotel & Casino (Demo),,36.915682,-116.751677
  NADAV,North Ave / D Ave N (Demo),,36.914893,-116.76821
  NANAA,North Ave / N A Ave (Demo),,36.914944,-116.761472
  DADAN,Doing Ave / D Ave N (Demo),,36.909489,-116.768242
  EMSI,E Main St / S Irving St (Demo),,36.905697,-116.76218
  AMV,Amargosa Valley (Demo),,36.641496,-116.40094
 
  route_id,service_id,trip_id,trip_headsign,direction_id
  AB,FULLW,AB1,to Bullfrog,0
  AB,FULLW,AB2,to Airport,1
  STBA,FULLW,STBA,Shuttle
  CITY,FULLW,CITY1,,0
  CITY,FULLW,CITY2,,1
  BFC,FULLW,BFC1,to Furnace Creek Resort,0
  BFC,FULLW,BFC2,to Bullfrog,1
  AAMV,WE,AAMV1,to Amargosa Valley,0
  AAMV,WE,AAMV2,to Airport,1
  AAMV,WE,AAMV3,to Amargosa Valley,0
  AAMV,WE,AAMV4,to Airport,1
 
  agency_id,agency_name,agency_url,agency_timezone
  DTA,Demo Transit Authority,http://google.com,America/Los_Angeles
  fare_id,price,currency_type,payment_method,transfers,transfer_duration
  p,1.25,USD,0,0,
  a,5.25,USD,0,0,
 
  fare_id,route_id,origin_id,destination_id,contains_id
  p,AB,,,
  p,STBA,,,
  p,BFC,,,
  a,AAMV,,,
 
  trip_id,start_time,end_time,headway_secs
  STBA,6:00:00,22:00:00,1800
  CITY1,6:00:00,7:59:59,1800
  CITY2,6:00:00,7:59:59,1800
  CITY1,8:00:00,9:59:59,600
  CITY2,8:00:00,9:59:59,600
  CITY1,10:00:00,15:59:59,1800
  CITY2,10:00:00,15:59:59,1800
  CITY1,16:00:00,18:59:59,600
  CITY2,16:00:00,18:59:59,600
  CITY1,19:00:00,22:00:00,1800
  CITY2,19:00:00,22:00:00,1800
  route_id,agency_id,route_short_name,route_long_name,route_desc,route_type,route_url,route_color,route_text_color
  AB,DTA,,Airport - Bullfrog,,3,,,
  BFC,DTA,,Bullfrog - Furnace Creek Resort,,3,,,
  STBA,DTA,,Stagecoach - Airport Shuttle,,3,,,
  CITY,DTA,,City,,3,,,
  AAMV,DTA,,Airport - Amargosa Valley,,3,,,
  trip_id,arrival_time,departure_time,stop_id,stop_sequence,stop_headsign,pickup_type,drop_off_type,shape_dist_traveled
  STBA,6:00:00,6:00:00,STAGECOACH,1,,,,
  STBA,6:20:00,6:20:00,BEATTY_AIRPORT,2,,,,
  CITY1,6:00:00,6:00:00,STAGECOACH,1,,,,
  CITY1,6:05:00,6:07:00,NANAA,2,,,,
  CITY1,6:12:00,6:14:00,NADAV,3,,,,
  CITY1,6:19:00,6:21:00,DADAN,4,,,,
  CITY1,6:26:00,6:28:00,EMSI,5,,,,
  CITY2,6:28:00,6:30:00,EMSI,1,,,,
  CITY2,6:35:00,6:37:00,DADAN,2,,,,
  CITY2,6:42:00,6:44:00,NADAV,3,,,,
  CITY2,6:49:00,6:51:00,NANAA,4,,,,
  CITY2,6:56:00,6:58:00,STAGECOACH,5,,,,
  AB1,8:00:00,8:00:00,BEATTY_AIRPORT,1,,,,
  AB1,8:10:00,8:15:00,BULLFROG,2,,,,
  AB2,12:05:00,12:05:00,BULLFROG,1,,,,
  AB2,12:15:00,12:15:00,BEATTY_AIRPORT,2,,,,
  BFC1,8:20:00,8:20:00,BULLFROG,1,,,,
  BFC1,9:20:00,9:20:00,FUR_CREEK_RES,2,,,,
  BFC2,11:00:00,11:00:00,FUR_CREEK_RES,1,,,,
  BFC2,12:00:00,12:00:00,BULLFROG,2,,,,
  AAMV1,8:00:00,8:00:00,BEATTY_AIRPORT,1,,,,
  AAMV1,9:00:00,9:00:00,AMV,2,,,,
  AAMV2,10:00:00,10:00:00,AMV,1,,,,
  AAMV2,11:00:00,11:00:00,BEATTY_AIRPORT,2,,,,
  AAMV3,13:00:00,13:00:00,BEATTY_AIRPORT,1,,,,
  AAMV3,14:00:00,14:00:00,AMV,2,,,,
  AAMV4,15:00:00,15:00:00,AMV,1,,,,
  AAMV4,16:00:00,16:00:00,BEATTY_AIRPORT,2,,,,
  stop_id,stop_name,stop_desc,stop_lat,stop_lon,zone_id,stop_url
  FUR_CREEK_RES,Furnace Creek Resort (Demo),,36.425288,-117.133162,,
  BEATTY_AIRPORT,Nye County Airport (Demo),,36.868446,-116.784582,,
  BULLFROG,Bullfrog (Demo),,36.88108,-116.81797,,
  STAGECOACH,Stagecoach Hotel & Casino (Demo),,36.915682,-116.751677,,
  NADAV,North Ave / D Ave N (Demo),,36.914893,-116.76821,,
  NANAA,North Ave / N A Ave (Demo),,36.914944,-116.761472,,
  DADAN,Doing Ave / D Ave N (Demo),,36.909489,-116.768242,,
  EMSI,E Main St / S Irving St (Demo),,36.905697,-116.76218,,
  AMV,Amargosa Valley (Demo),,36.641496,-116.40094,,
  route_id,service_id,trip_id,trip_headsign,direction_id,block_id,shape_id
  AB,FULLW,AB1,to Bullfrog,0,1,
  AB,FULLW,AB2,to Airport,1,2,
  STBA,FULLW,STBA,Shuttle,,,
  CITY,FULLW,CITY1,,0,,
  CITY,FULLW,CITY2,,1,,
  BFC,FULLW,BFC1,to Furnace Creek Resort,0,1,
  BFC,FULLW,BFC2,to Bullfrog,1,2,
  AAMV,WE,AAMV1,to Amargosa Valley,0,,
  AAMV,WE,AAMV2,to Airport,1,,
  AAMV,WE,AAMV3,to Amargosa Valley,0,,
  AAMV,WE,AAMV4,to Airport,1,,
  agency_id,agency_url,agency_timezone
  DTA,http://google.com,America/Los_Angeles
 
  service_id,monday,tuesday,wednesday,thursday,friday,saturday,sunday,start_date,end_date
  FULLW,1,1,1,1,1,1,1,20070101,20101231
  WE,0,0,0,0,0,1,1,20070101,20101231
  service_id,date,exception_type
  FULLW,20070604,2
  fare_id,price,currency_type,payment_method,transfers,transfer_duration
  p,1.25,USD,0,0,
  a,5.25,USD,0,0,
 
  fare_id,route_id,origin_id,destination_id,contains_id
  p,AB,,,
  p,STBA,,,
  p,BFC,,,
  a,AAMV,,,
 
  trip_id,start_time,end_time,headway_secs
  STBA,6:00:00,22:00:00,1800
  CITY1,6:00:00,7:59:59,1800
  CITY2,6:00:00,7:59:59,1800
  CITY1,8:00:00,9:59:59,600
  CITY2,8:00:00,9:59:59,600
  CITY1,10:00:00,15:59:59,1800
  CITY2,10:00:00,15:59:59,1800
  CITY1,16:00:00,18:59:59,600
  CITY2,16:00:00,18:59:59,600
  CITY1,19:00:00,22:00:00,1800
  CITY2,19:00:00,22:00:00,1800
  route_id,agency_id,route_short_name,route_long_name,route_desc,route_type,route_url,route_color,route_text_color
  AB,DTA,,Airport - Bullfrog,,3
  BFC,DTA,,Bullfrog - Furnace Creek Resort,,3
  STBA,DTA,,Stagecoach - Airport Shuttle,,3
  CITY,DTA,,City,,3
  AAMV,DTA,,Airport - Amargosa Valley,,3
  trip_id,arrival_time,departure_time,stop_id,stop_sequence
  STBA,6:00:00,6:00:00,STAGECOACH,1
  STBA,6:20:00,6:20:00,BEATTY_AIRPORT,2
  CITY1,6:00:00,6:00:00,STAGECOACH,1
  CITY1,6:05:00,6:07:00,NANAA,2
  CITY1,6:12:00,6:14:00,NADAV,3
  CITY1,6:19:00,6:21:00,DADAN,4
  CITY1,6:26:00,6:28:00,EMSI,5
  CITY2,6:28:00,6:30:00,EMSI,1
  CITY2,6:35:00,6:37:00,DADAN,2
  CITY2,6:42:00,6:44:00,NADAV,3
  CITY2,6:49:00,6:51:00,NANAA,4
  CITY2,6:56:00,6:58:00,STAGECOACH,5
  AB1,8:00:00,8:00:00,BEATTY_AIRPORT,1
  AB1,8:10:00,8:15:00,BULLFROG,2
  AB2,12:05:00,12:05:00,BULLFROG,1
  AB2,12:15:00,12:15:00,BEATTY_AIRPORT,2
  BFC1,8:20:00,8:20:00,BULLFROG,1
  BFC1,9:20:00,9:20:00,FUR_CREEK_RES,2
  BFC2,11:00:00,11:00:00,FUR_CREEK_RES,1
  BFC2,12:00:00,12:00:00,BULLFROG,2
  AAMV1,8:00:00,8:00:00,BEATTY_AIRPORT,1
  AAMV1,9:00:00,9:00:00,AMV,2
  AAMV2,10:00:00,10:00:00,AMV,1
  AAMV2,11:00:00,11:00:00,BEATTY_AIRPORT,2
  AAMV3,13:00:00,13:00:00,BEATTY_AIRPORT,1
  AAMV3,14:00:00,14:00:00,AMV,2
  AAMV4,15:00:00,15:00:00,AMV,1
  AAMV4,16:00:00,16:00:00,BEATTY_AIRPORT,2
 
  stop_id,stop_name,stop_desc,stop_lat,stop_lon,zone_id,stop_url
  FUR_CREEK_RES,Furnace Creek Resort (Demo),,36.425288,-117.133162
  BEATTY_AIRPORT,Nye County Airport (Demo),,36.868446,-116.784582
  BULLFROG,Bullfrog (Demo),,36.88108,-116.81797
  STAGECOACH,Stagecoach Hotel & Casino (Demo),,36.915682,-116.751677
  NADAV,North Ave / D Ave N (Demo),,36.914893,-116.76821
  NANAA,North Ave / N A Ave (Demo),,36.914944,-116.761472
  DADAN,Doing Ave / D Ave N (Demo),,36.909489,-116.768242
  EMSI,E Main St / S Irving St (Demo),,36.905697,-116.76218
  AMV,Amargosa Valley (Demo),,36.641496,-116.40094
  route_id,service_id,trip_id,trip_headsign,direction_id,block_id,shape_id
  AB,FULLW,AB1,to Bullfrog,0,1
  AB,FULLW,AB2,to Airport,1,2
  STBA,FULLW,STBA,Shuttle
  CITY,FULLW,CITY1,,0
  CITY,FULLW,CITY2,,1
  BFC,FULLW,BFC1,to Furnace Creek Resort,0,1
  BFC,FULLW,BFC2,to Bullfrog,1,2
  AAMV,WE,AAMV1,to Amargosa Valley,0
  AAMV,WE,AAMV2,to Airport,1
  AAMV,WE,AAMV3,to Amargosa Valley,0
  AAMV,WE,AAMV4,to Airport,1
  agency_id,agency_name,agency_url,agency_timezone
  DTA,Autorité de passage de démonstration,http://google.com,America/Los_Angeles
 
  service_id,monday,tuesday,wednesday,thursday,friday,saturday,sunday,start_date,end_date
  FULLW,1,1,1,1,1,1,1,20070101,20101231
 
  route_id,agency_id,route_short_name,route_long_name,route_desc,route_type,route_url,route_color,route_text_color
  CITY,DTA,Ō,Bar Circle,Route with ĸool unicode shortname,3,,,
 
  trip_id,arrival_time,departure_time,stop_id,stop_sequence,stop_headsign,pickup_type,drop_off_type,shape_dist_traveled
  CITY1,6:00:00,6:00:00,STAGECOACH,0,,,,
  CITY1,6:12:00,,NADAV,10,,,,
  CITY1,6:19:00,6:21:00,DADAN,15,,,,
  CITY1,6:26:00,6:28:00,EMSI,20,,,,
 
  stop_id,stop_name,stop_desc,stop_lat,stop_lon,zone_id,stop_url,stop_code
  STAGECOACH,Stagecoach Hotel & Casino (Demo),,36.915682,-116.751677,,,1236
  NADAV,North Ave / D Ave N (Demo),,36.914893,-116.76821,,,1237
  NANAA,North Ave / N A Ave (Demo),,36.914944,-116.761472,,,1238
  DADAN,Doing Ave / D Ave N (Demo),,36.909489,-116.768242,,,
  EMSI,E Main St / S Irving St (Demo),,36.905697,-116.76218,,,
 
  route_id,service_id,trip_id,trip_headsign,direction_id,block_id,shape_id
  CITY,FULLW,CITY1,,0,,
 
  agency_id,agency_name,agency_url,agency_timezone
  DTA,Autorité de passage de démonstration,http://google.com,America/Los_Angeles
 
  service_id,monday,tuesday,wednesday,thursday,friday,saturday,sunday,start_date,end_date
  FULLW,1,1,1,1,1,1,1,20070101,20101231
  WE,0,0,0,0,0,1,1,20070101,20101231
  service_id,date,exception_type
  FULLW,20070604,2
  fare_id,price,currency_type,payment_method,transfers,transfer_duration
  p,1.25,USD,0,0,
  a,5.25,USD,0,0,
 
  fare_id,route_id,origin_id,destination_id,contains_id
  p,AB,,,
  p,STBA,,,
  p,BFC,,,
  a,AAMV,,,
 
  trip_id,start_time,end_time,headway_secs
  STBA,6:00:00,22:00:00,1800
  CITY1,6:00:00,7:59:59,1800
  CITY2,6:00:00,7:59:59,1800
  CITY1,8:00:00,9:59:59,600
  CITY2,8:00:00,9:59:59,600
  CITY1,10:00:00,15:59:59,1800
  CITY2,10:00:00,15:59:59,1800
  CITY1,16:00:00,18:59:59,600
  CITY2,16:00:00,18:59:59,600
  CITY1,19:00:00,22:00:00,1800
  CITY2,19:00:00,22:00:00,1800
  route_id,agency_id,route_short_name,route_long_name,route_desc,route_type,route_url,route_color,route_text_color
  AB,DTA,,Airport ⇒ Bullfrog,,3,,,
  BFC,DTA,,Bullfrog ⇒ Furnace Creek Resort,,3,,,
  STBA,DTA,,Stagecoach ⇒ Airport Shuttle,,3,,,
  CITY,DTA,Ō,Bar Circle,Route with ĸool unicode short name,3,,,
  AAMV,DTA,,Airport ⇒ Amargosa Valley,,3,,,
 
  trip_id,arrival_time,departure_time,stop_id,stop_sequence,stop_headsign,pickup_type,drop_off_type,shape_dist_traveled
  STBA,6:00:00,6:00:00,STAGECOACH,1,to airport,1,0,0.212
  STBA,6:20:00,6:20:00,BEATTY_AIRPORT,2,,0,0,1.043
  CITY1,6:00:00,6:00:00,STAGECOACH,1,,,,
  CITY1,6:05:00,6:07:00,NANAA,2,going to nadav,2,3,
  CITY1,6:12:00,6:14:00,NADAV,3,,,,
  CITY1,6:19:00,6:21:00,DADAN,4,,,,
  CITY1,6:26:00,6:28:00,EMSI,5,,,,
  CITY2,6:28:00,6:30:00,EMSI,1,,,,
  CITY2,6:35:00,6:37:00,DADAN,2,,,,
  CITY2,6:42:00,6:44:00,NADAV,3,,,,
  CITY2,6:49:00,6:51:00,NANAA,4,,,,
  CITY2,6:56:00,6:58:00,STAGECOACH,5,,,,
  AB1,8:00:00,8:00:00,BEATTY_AIRPORT,1,,,,
  AB1,8:10:00,8:15:00,BULLFROG,2,,,,
  AB2,,,BULLFROG,1,,,,
  AB2,12:15:00,12:15:00,BEATTY_AIRPORT,2,,,,
  BFC1,8:20:00,8:20:00,BULLFROG,1,,,,
  BFC1,9:20:00,9:20:00,FUR_CREEK_RES,2,,,,
  BFC2,11:00:00,11:00:00,FUR_CREEK_RES,1,,,,
  BFC2,,,BULLFROG,2,,,,
  AAMV1,8:00:00,8:00:00,BEATTY_AIRPORT,1,,,,
  AAMV1,9:00:00,9:00:00,AMV,2,,,,
  AAMV2,10:00:00,10:00:00,AMV,1,,,,
  AAMV2,11:00:00,11:00:00,BEATTY_AIRPORT,2,,,,
  AAMV3,13:00:00,13:00:00,BEATTY_AIRPORT,1,,,,
  AAMV3,14:00:00,14:00:00,AMV,2,,,,
  AAMV4,15:00:00,15:00:00,AMV,1,,,,
  AAMV4,16:00:00,16:00:00,BEATTY_AIRPORT,2,,,,
 
  stop_id,stop_name,stop_desc,stop_lat,stop_lon,zone_id,stop_url
  FUR_CREEK_RES,Furnace Creek Resort (Démonstration),,36.425288,-117.133162,,
  BEATTY_AIRPORT,Nye County Airport (Demo),,36.868446,-116.784582,,
  BULLFROG,Bullfrog (Demo),,36.88108,-116.81797,,
  STAGECOACH,Stagecoach Hotel & Casino (Demo),,36.915682,-116.751677,,
  NADAV,North Ave / D Ave N (Demo),,36.914893,-116.76821,,
  NANAA,North Ave / N A Ave (Demo),,36.914944,-116.761472,,
  DADAN,Doing Ave / D Ave N (Demo),,36.909489,-116.768242,,
  EMSI,E Main St / S Irving St (Demo),,36.905697,-116.76218,,
  AMV,Amargosa Valley (Demo),,36.641496,-116.40094,,
 
  route_id,service_id,trip_id,trip_headsign,direction_id,block_id,shape_id
  AB,FULLW,AB1,to Bullfrog,0,1,
  AB,FULLW,AB2,to Airport,1,2,
  STBA,FULLW,STBA,Shuttle,,,
  CITY,FULLW,CITY1,Ō,0,,
  CITY,FULLW,CITY2,Ō,1,,
  BFC,FULLW,BFC1,to Furnace Creek Resort,0,1,
  BFC,FULLW,BFC2,to Bullfrog,1,2,
  AAMV,WE,AAMV1,to Amargosa Valley,0,,
  AAMV,WE,AAMV2,to Airport,1,,
  AAMV,WE,AAMV3,to Amargosa Valley,0,,
  AAMV,WE,AAMV4,to Airport,1,,
 
  agency_id,agency_name,agency_url,agency_timezone
  DTA,Demo Transit Authority,http://google.com,America/Los_Angeles
  service_id,monday,tuesday,wednesday,thursday,friday,saturday,sunday,start_date,end_date
  FULLW,1,1,1,1,1,1,1,20070101,20101231
  WE,0,0,0,0,0,1,1,20070101,20101231
  service_id,date,exception_type
  FULLW,20070604,2
  fare_id,price,currency_type,payment_method,transfers,transfer_duration
  p,1.25,USD,0,0,
  a,5.25,USD,0,0,
 
  fare_id,route_id,origin_id,destination_id,contains_id
  p,AB,,,
  p,STBA,,,
  p,BFC,,,
  a,AAMV,,,
 
  trip_id,start_time,end_time,headway_secs
  STBA,6:00:00,22:00:00,1800
  CITY1,6:00:00,7:59:59,1800
  CITY2,6:00:00,7:59:59,1800
  CITY1,8:00:00,9:59:59,600
  CITY2,8:00:00,9:59:59,600
  CITY1,10:00:00,15:59:59,1800
  CITY2,10:00:00,15:59:59,1800
  CITY1,16:00:00,18:59:59,600
  CITY2,16:00:00,18:59:59,600
  CITY1,19:00:00,22:00:00,1800
  CITY2,19:00:00,22:00:00,1800
  trip_id,arrival_time,departure_time,stop_id,stop_sequence,stop_headsign,pickup_type,drop_off_type,shape_dist_traveled
  STBA,6:00:00,6:00:00,STAGECOACH,1,,,,
  STBA,6:20:00,6:20:00,BEATTY_AIRPORT,2,,,,
  CITY1,6:00:00,6:00:00,STAGECOACH,1,,,,
  CITY1,6:05:00,6:07:00,NANAA,2,,,,
  CITY1,6:12:00,6:14:00,NADAV,3,,,,
  CITY1,6:19:00,6:21:00,DADAN,4,,,,
  CITY1,6:26:00,6:28:00,EMSI,5,,,,
  CITY2,6:28:00,6:30:00,EMSI,1,,,,
  CITY2,6:35:00,6:37:00,DADAN,2,,,,
  CITY2,6:42:00,6:44:00,NADAV,3,,,,
  CITY2,6:49:00,6:51:00,NANAA,4,,,,
  CITY2,6:56:00,6:58:00,STAGECOACH,5,,,,
  AB1,8:00:00,8:00:00,BEATTY_AIRPORT,1,,,,
  AB1,8:10:00,8:15:00,BULLFROG,2,,,,
  AB2,12:05:00,12:05:00,BULLFROG,1,,,,
  AB2,12:15:00,12:15:00,BEATTY_AIRPORT,2,,,,
  BFC1,8:20:00,8:20:00,BULLFROG,1,,,,
  BFC1,9:20:00,9:20:00,FUR_CREEK_RES,2,,,,
  BFC2,11:00:00,11:00:00,FUR_CREEK_RES,1,,,,
  BFC2,12:00:00,12:00:00,BULLFROG,2,,,,
  AAMV1,8:00:00,8:00:00,BEATTY_AIRPORT,1,,,,
  AAMV1,9:00:00,9:00:00,AMV,2,,,,
  AAMV2,10:00:00,10:00:00,AMV,1,,,,
  AAMV2,11:00:00,11:00:00,BEATTY_AIRPORT,2,,,,
  AAMV3,13:00:00,13:00:00,BEATTY_AIRPORT,1,,,,
  AAMV3,14:00:00,14:00:00,AMV,2,,,,
  AAMV4,15:00:00,15:00:00,AMV,1,,,,
  AAMV4,16:00:00,16:00:00,BEATTY_AIRPORT,2,,,,
  stop_id,stop_name,stop_desc,stop_lat,stop_lon,zone_id,stop_url
  FUR_CREEK_RES,Furnace Creek Resort (Demo),,36.425288,-117.133162,,
  BEATTY_AIRPORT,Nye County Airport (Demo),,36.868446,-116.784582,,
  BULLFROG,Bullfrog (Demo),,36.88108,-116.81797,,
  STAGECOACH,Stagecoach Hotel & Casino (Demo),,36.915682,-116.751677,,
  NADAV,North Ave / D Ave N (Demo),,36.914893,-116.76821,,
  NANAA,North Ave / N A Ave (Demo),,36.914944,-116.761472,,
  DADAN,Doing Ave / D Ave N (Demo),,36.909489,-116.768242,,
  EMSI,E Main St / S Irving St (Demo),,36.905697,-116.76218,,
  AMV,Amargosa Valley (Demo),,36.641496,-116.40094,,
  route_id,service_id,trip_id,trip_headsign,direction_id,block_id,shape_id
  AB,FULLW,AB1,to Bullfrog,0,1,
  AB,FULLW,AB2,to Airport,1,2,
  STBA,FULLW,STBA,Shuttle,,,
  CITY,FULLW,CITY1,,0,,
  CITY,FULLW,CITY2,,1,,
  BFC,FULLW,BFC1,to Furnace Creek Resort,0,1,
  BFC,FULLW,BFC2,to Bullfrog,1,2,
  AAMV,WE,AAMV1,to Amargosa Valley,0,,
  AAMV,WE,AAMV2,to Airport,1,,
  AAMV,WE,AAMV3,to Amargosa Valley,0,,
  AAMV,WE,AAMV4,to Airport,1,,
  agency_id,agency_name,agency_url,agency_timezone
  DTA,Autorité de passage de démonstration,http://google.com,America/Los_Angeles
 
  service_id,monday,tuesday,wednesday,thursday,friday,saturday,sunday,start_date,end_date
  FULLW,1,1,1,1,1,1,1,20070101,20101231
  WE,0,0,0,0,0,1,1,20070101,20101231
  service_id,date,exception_type
  FULLW,20070604,2
  fare_id,price,currency_type,payment_method,transfers,transfer_duration
  p,1.25,USD,0,0,
  a,5.25,USD,0,0,
 
  fare_id,route_id,origin_id,destination_id,contains_id
  p,AB,,,
  p,STBA,,,
  p,BFC,,,
  a,AAMV,,,
 
  trip_id,start_time,end_time,headway_secs
  STBA,6:00:00,22:00:00,1800
  CITY1,6:00:00,7:59:59,1800
  CITY2,6:00:00,7:59:59,1800
  CITY1,8:00:00,9:59:59,600
  CITY2,8:00:00,9:59:59,600
  CITY1,10:00:00,15:59:59,1800
  CITY2,10:00:00,15:59:59,1800
  CITY1,16:00:00,18:59:59,600
  CITY2,16:00:00,18:59:59,600
  CITY1,19:00:00,22:00:00,1800
  CITY2,19:00:00,22:00:00,1800
  route_id,agency_id,route_short_name,route_long_name,route_desc,route_type,route_url
  AB,DTA,,Airport ⇒ Bullfrog,,3,
  BFC,DTA,,Bullfrog ⇒ Furnace Creek Resort,,3,http://google.com
  STBA,DTA,,Stagecoach ⇒ Airport Shuttle,,3
  CITY,DTA,Ō,Bar Circle,Route with ĸool unicode shortname,3,
  AAMV,DTA,,Airport ⇒ Amargosa Valley,,3,
 
  trip_id,arrival_time,departure_time,stop_id,stop_sequence,stop_headsign,pickup_type,drop_off_type,shape_dist_traveled
  STBA,6:00:00,6:00:00,STAGECOACH,0,to airport,1,0,0.212
  STBA,6:20:00,6:20:00,BEATTY_AIRPORT,2,,0,0,1.043
  CITY1,6:00:00,6:00:00,STAGECOACH,0,,,,
  CITY1,6:05:00,6:07:00,NANAA,5,going to nadav,2,3,
  CITY1,6:12:00,6:14:00,NADAV,10,,,,
  CITY1,6:19:00,6:21:00,DADAN,15,,,,
  CITY1,6:26:00,6:28:00,EMSI,20,,,,
  CITY2,6:28:00,6:30:00,EMSI,100,,,,
  CITY2,6:35:00,6:37:00,DADAN,200,,,,
  CITY2,6:42:00,6:44:00,NADAV,300,,,,
  CITY2,6:49:00,6:51:00,NANAA,400,,,,
  CITY2,6:56:00,6:58:00,STAGECOACH,500,,,,
  AB1,8:00:00,8:00:00,BEATTY_AIRPORT,1,,,,
  AB1,8:10:00,8:15:00,BULLFROG,2,,,,
  AB2,12:05:00,12:05:00,BULLFROG,1,,,,
  AB2,12:15:00,12:15:00,BEATTY_AIRPORT,2,,,,
  BFC1,8:20:00,8:20:00,BULLFROG,1,,,,
  BFC1,9:20:00,9:20:00,FUR_CREEK_RES,2,,,,
  BFC2,11:00:00,11:00:00,FUR_CREEK_RES,1,,,,
  BFC2,12:00:00,12:00:00,BULLFROG,2,,,,
  AAMV1,8:00:00,8:00:00,BEATTY_AIRPORT,1,,,,
  AAMV1,9:00:00,9:00:00,AMV,2,,,,
  AAMV2,10:00:00,10:00:00,AMV,1,,,,
  AAMV2,11:00:00,11:00:00,BEATTY_AIRPORT,2,,,,
  AAMV3,13:00:00,13:00:00,BEATTY_AIRPORT,1,,,,
  AAMV3,14:00:00,14:00:00,AMV,2,,,,
  AAMV4,15:00:00,15:00:00,AMV,1,,,,
  AAMV4,16:00:00,16:00:00,BEATTY_AIRPORT,2,,,,
 
  stop_id,stop_name,stop_desc,stop_lat,stop_lon
  FUR_CREEK_RES,Furnace Creek Resort (Démonstration),,36.425288,-117.13316
  BEATTY_AIRPORT,Nye County Airport (Demo),,36.868446,-116.784582
  BULLFROG,Bullfrog (Demo),,36.88108,-116.81797
  STAGECOACH,Stagecoach Hotel & Casino (Demo),,36.915682,-116.751677
  NADAV,North Ave / D Ave N (Demo),,36.914893,-116.76821
  NANAA,North Ave / N A Ave (Demo),,36.914944,-116.761472
  DADAN,Doing Ave / D Ave N (Demo),,36.909489,-116.768242
  EMSI,E Main St / S Irving St (Demo),,36.905697,-116.76218
  AMV,Amargosa Valley (Demo),,36.641496,-116.40094
 
  route_id,service_id,trip_id,trip_headsign,direction_id,block_id,shape_id
  AB,FULLW,AB1,to Bullfrog,0,1,
  AB,FULLW,AB2,to Airport,1,2,
  STBA,FULLW,STBA,Shuttle,,,
  CITY,FULLW,CITY1,Ō,,,
  CITY,FULLW,CITY2,Ō,,,
  BFC,FULLW,BFC1,to Furnace Creek Resort,0,1,
  BFC,FULLW,BFC2,to Bullfrog,1,2,
  AAMV,WE,AAMV1,to Amargosa Valley,0,,
  AAMV,WE,AAMV2,to Airport,1,,
  AAMV,WE,AAMV3,to Amargosa Valley,0,,
  AAMV,WE,AAMV4,to Airport,1,,
 
  agency_id,agency_name,agency_url,agency_timezone
  DTA,Demo Transit Authority,http://google.com,America/Los_Angeles
  service_id,monday,tuesday,wednesday,thursday,friday,saturday,sunday,start_date,end_date
  FULLW,1,1,1,1,1,1,1,20070101,20101231
  WE,0,0,0,0,0,1,1,20070101,20101231
  service_id,date,exception_type
  FULLW,20070604,2
  fare_id,price,currency_type,payment_method,transfers,transfer_duration
  p,1.25,USD,0,0,
  a,5.25,USD,0,0,
 
  fare_id,route_id,origin_id,destination_id,contains_id
  p,AB,,,
  p,STBA,,,
  p,BFC,,,
  a,AAMV,,,
 
  trip_id,start_time,end_time,headway_secs
  STBA,6:00:00,22:00:00,1800
  CITY1,6:00:00,7:59:59,1800
  CITY2,6:00:00,7:59:59,1800
  CITY1,8:00:00,9:59:59,600
  CITY2,8:00:00,9:59:59,600
  CITY1,10:00:00,15:59:59,1800
  CITY2,10:00:00,15:59:59,1800
  CITY1,16:00:00,18:59:59,600
  CITY2,16:00:00,18:59:59,600
  CITY1,19:00:00,22:00:00,1800
  CITY2,19:00:00,22:00:00,1800
  route_id,agency_id,route_short_name,route_long_name,route_desc,route_type,route_url,route_color,route_text_color
  AB,DTA,,Airport - Bullfrog,,3,,,
  BFC,DTA,,Bullfrog - Furnace Creek Resort,,3,,,
  STBA,DTA,,Stagecoach - Airport Shuttle,,3,,,
  CITY,DTA,,City,,3,,,
  AAMV,DTA,,Airport - Amargosa Valley,,3,,,
  stop_id,stop_name,stop_desc,stop_lat,stop_lon,zone_id,stop_url
  FUR_CREEK_RES,Furnace Creek Resort (Demo),,36.425288,-117.133162,,
  BEATTY_AIRPORT,Nye County Airport (Demo),,36.868446,-116.784582,,
  BULLFROG,Bullfrog (Demo),,36.88108,-116.81797,,
  STAGECOACH,Stagecoach Hotel & Casino (Demo),,36.915682,-116.751677,,
  NADAV,North Ave / D Ave N (Demo),,36.914893,-116.76821,,
  NANAA,North Ave / N A Ave (Demo),,36.914944,-116.761472,,
  DADAN,Doing Ave / D Ave N (Demo),,36.909489,-116.768242,,
  EMSI,E Main St / S Irving St (Demo),,36.905697,-116.76218,,
  AMV,Amargosa Valley (Demo),,36.641496,-116.40094,,
  route_id,service_id,trip_id,trip_headsign,direction_id,block_id,shape_id
  AB,FULLW,AB1,to Bullfrog,0,1,
  AB,FULLW,AB2,to Airport,1,2,
  STBA,FULLW,STBA,Shuttle,,,
  CITY,FULLW,CITY1,,0,,
  CITY,FULLW,CITY2,,1,,
  BFC,FULLW,BFC1,to Furnace Creek Resort,0,1,
  BFC,FULLW,BFC2,to Bullfrog,1,2,
  AAMV,WE,AAMV1,to Amargosa Valley,0,,
  AAMV,WE,AAMV2,to Airport,1,,
  AAMV,WE,AAMV3,to Amargosa Valley,0,,
  AAMV,WE,AAMV4,to Airport,1,,
  agency_id,agency_name,agency_url,agency_timezone
  DTA,Demo Transit Authority,http://google.com,America/Los_Angeles
  service_id,monday,tuesday,wednesday,thursday,friday,saturday,sunday,start_date,end_date
  FULLW,1,1,1,1,1,1,1,20070101,20101231
  WE,0,0,0,0,0,1,1,20070101,20101231
  service_id,date,exception_type
  FULLW,20070604,2
  fare_id,price,currency_type,payment_method,transfers,transfer_duration
  p,1.25,USD,0,0,
  a,5.25,USD,0,0,
 
  fare_id,route_id,origin_id,destination_id,contains_id
  p,AB,,,
  p,STBA,,,
  p,BFC,,,
  a,AAMV,,,
 
  trip_id,start_time,end_time,headway_secs
  STBA,6:00:00,22:00:00,1800
  CITY1,6:00:00,7:59:59,1800
  CITY2,6:00:00,7:59:59,1800
  CITY1,8:00:00,9:59:59,600
  CITY2,8:00:00,9:59:59,600
  CITY1,10:00:00,15:59:59,1800
  CITY2,10:00:00,15:59:59,1800
  CITY1,16:00:00,18:59:59,600
  CITY2,16:00:00,18:59:59,600
  CITY1,19:00:00,22:00:00,1800
  CITY2,19:00:00,22:00:00,1800
  route_id,agency_id,route_short_name,route_long_name,route_desc,route_type,route_url,route_color,route_text_color
  AB,DTA,,Airport - Bullfrog,,3
  BFC,DTA,,Bullfrog - Furnace Creek Resort,,3
  STBA,DTA,,Stagecoach - Airport Shuttle,,3
  CITY,DTA,,City,,3
  AAMV,DTA,,Airport - Amargosa Valley,,3
  trip_id,arrival_time,departure_time,stop_id,stop_sequence,stop_headsign,pickup_type,drop_off_type,shape_dist_traveled
  STBA,6:00:00,6:00:00,STAGECOACH,1,,,,
  STBA,6:20:00,6:20:00,BEATTY_AIRPORT,2,,,,
  CITY1,6:00:00,6:00:00,STAGECOACH,1,,,,
  CITY1,6:05:00,6:07:00,NANAA,2,,,,
  CITY1,6:12:00,6:14:00,NADAV,3,,,,
  CITY1,6:19:00,6:21:00,DADAN,4,,,,
  CITY1,6:26:00,6:28:00,EMSI,5,,,,
  CITY2,6:28:00,6:30:00,EMSI,1,,,,
  CITY2,6:35:00,6:37:00,DADAN,2,,,,
  CITY2,6:42:00,6:44:00,NADAV,3,,,,
  CITY2,6:49:00,6:51:00,NANAA,4,,,,
  CITY2,6:56:00,6:58:00,STAGECOACH,5,,,,
  AB1,8:00:00,8:00:00,BEATTY_AIRPORT,1,,,,
  AB1,8:10:00,8:15:00,BULLFROG,2,,,,
  AB2,12:05:00,12:05:00,BULLFROG,1,,,,
  AB2,12:15:00,12:15:00,BEATTY_AIRPORT,2,,,,
  BFC1,8:20:00,8:20:00,BULLFROG,1,,,,
  BFC1,9:20:00,9:20:00,FUR_CREEK_RES,2,,,,
  BFC2,11:00:00,11:00:00,FUR_CREEK_RES,1,,,,
  BFC2,12:00:00,12:00:00,BULLFROG,2,,,,
  AAMV1,8:00:00,8:00:00,BEATTY_AIRPORT,1,,,,
  AAMV1,9:00:00,9:00:00,AMV,2,,,,
  AAMV2,10:00:00,10:00:00,AMV,1,,,,
  AAMV2,11:00:00,11:00:00,BEATTY_AIRPORT,2,,,,
  AAMV3,13:00:00,13:00:00,BEATTY_AIRPORT,1,,,,
  AAMV3,14:00:00,14:00:00,AMV,2,,,,
  AAMV4,15:00:00,15:00:00,AMV,1,,,,
  AAMV4,16:00:00,16:00:00,BEATTY_AIRPORT,2,,,,
  route_id,service_id,trip_id,trip_headsign,direction_id,block_id,shape_id
  AB,FULLW,AB1,to Bullfrog,0,1,
  AB,FULLW,AB2,to Airport,1,2,
  STBA,FULLW,STBA,Shuttle,,,
  CITY,FULLW,CITY1,,0,,
  CITY,FULLW,CITY2,,1,,
  BFC,FULLW,BFC1,to Furnace Creek Resort,0,1,
  BFC,FULLW,BFC2,to Bullfrog,1,2,
  AAMV,WE,AAMV1,to Amargosa Valley,0,,
  AAMV,WE,AAMV2,to Airport,1,,
  AAMV,WE,AAMV3,to Amargosa Valley,0,,
  AAMV,WE,AAMV4,to Airport,1,,
  agency_id,agency_name,agency_url,agency_timezone
  DTA,Demo Transit Authority,http://google.com,America/Los_Angeles
  service_id,monday,tuesday,wednesday,thursday,friday,saturday,sunday,start_date,end_date
  FULLW,1,1,1,1,1,1,1,20070101,20101231
  WE,0,0,0,0,0,1,1,20070101,20101231
  service_id,date,exception_type
  FULLW,20070604,2
  route_id,agency_id,route_short_name,route_long_name,route_desc,route_type,route_url,route_color,route_text_color
  AB,DTA,,Airport - Bullfrog,,3,,,
  BFC,DTA,,Bullfrog - Furnace Creek Resort,,3,,,
  STBA,DTA,,Stagecoach - Airport Shuttle,,3,,,
  CITY,DTA,,City,,3,,,
  AAMV,DTA,,Airport - Amargosa Valley,,3,,,
  trip_id,arrival_time,departure_time,stop_id,stop_sequence,stop_headsign,pickup_type,drop_off_type,shape_dist_traveled
  STBA,6:00:00,6:00:00,STAGECOACH,1,,,,
  STBA,6:20:00,6:20:00,BEATTY_AIRPORT,2,,,,
  CITY1,6:00:00,6:00:00,STAGECOACH,1,,,,
  CITY1,6:05:00,6:07:00,NANAA,2,,,,
  CITY1,6:12:00,6:14:00,NADAV,3,,,,
  CITY1,6:19:00,6:21:00,DADAN,4,,,,
  CITY1,6:26:00,6:28:00,EMSI,5,,,,
  CITY2,6:28:00,6:30:00,EMSI,1,,,,
  CITY2,6:35:00,6:37:00,DADAN,2,,,,
  CITY2,6:42:00,6:44:00,NADAV,3,,,,
  CITY2,6:49:00,6:51:00,NANAA,4,,,,
  CITY2,6:56:00,6:58:00,STAGECOACH,5,,,,
  AB1,8:00:00,8:00:00,BEATTY_AIRPORT,1,,,,
  AB1,8:10:00,8:15:00,BULLFROG,2,,,,
  AB2,12:05:00,12:05:00,BULLFROG,1,,,,
  AB2,12:15:00,12:15:00,BEATTY_AIRPORT,2,,,,
  BFC1,8:20:00,8:20:00,BULLFROG,1,,,,
  BFC1,9:20:00,9:20:00,FUR_CREEK_RES,2,,,,
  BFC2,11:00:00,11:00:00,FUR_CREEK_RES,1,,,,
  BFC2,12:00:00,12:00:00,BULLFROG,2,,,,
  AAMV1,8:00:00,8:00:00,BEATTY_AIRPORT,1,,,,
  AAMV1,9:00:00,9:00:00,AMV,2,,,,
  AAMV2,10:00:00,10:00:00,AMV,1,,,,
  AAMV2,11:00:00,11:00:00,BEATTY_AIRPORT,2,,,,
  AAMV3,13:00:00,13:00:00,BEATTY_AIRPORT,1,,,,
  AAMV3,14:00:00,14:00:00,AMV,2,,,,
  AAMV4,15:00:00,15:00:00,AMV,1,,,,
  AAMV4,16:00:00,16:00:00,BEATTY_AIRPORT,2,,,,
  stop_id,stop_name,stop_desc,stop_lat,stop_lon,zone_id,stop_url
  FUR_CREEK_RES,Furnace Creek Resort (Demo),,36.425288,-117.133162,,
  BEATTY_AIRPORT,Nye County Airport (Demo),,36.868446,-116.784582,,
  BULLFROG,Bullfrog (Demo),,36.88108,-116.81797,,
  STAGECOACH,Stagecoach Hotel & Casino (Demo),,36.915682,-116.751677,,
  NADAV,North Ave / D Ave N (Demo),,36.914893,-116.76821,,
  NANAA,North Ave / N A Ave (Demo),,36.914944,-116.761472,,
  DADAN,Doing Ave / D Ave N (Demo),,36.909489,-116.768242,,
  EMSI,E Main St / S Irving St (Demo),,36.905697,-116.76218,,
  AMV,Amargosa Valley (Demo),,36.641496,-116.40094,,
  agency_id,agency_name,agency_url,agency_timezone
  DTA,Demo Transit Authority,http://google.com,America/Los_Angeles
  "service_id","monday","tuesday","wednesday","friday","saturday","sunday","start_date","end_date"
  "FULLW",1,1,1,1,1,1,20070101,20101231
  "WE",0,0,0,0,1,1,20070101,20101231
 
  service_id,date,exception_type
  FULLW,20070604,2
  fare_id,price,currency_type,payment_method,transfers,transfer_duration
  p,1.25,USD,0,0,
  a,5.25,USD,0,0,
 
  fare_id,route_id,origin_id,destination_id,contains_id
  p,AB,,,
  p,STBA,,,
  p,BFC,,,
  a,AAMV,,,
 
  trip_id,start_time,end_time,headway_secs
  STBA,6:00:00,22:00:00,1800
  CITY1,6:00:00,7:59:59,1800
  CITY2,6:00:00,7:59:59,1800
  CITY1,8:00:00,9:59:59,600
  CITY2,8:00:00,9:59:59,600
  CITY1,10:00:00,15:59:59,1800
  CITY2,10:00:00,15:59:59,1800
  CITY1,16:00:00,18:59:59,600
  CITY2,16:00:00,18:59:59,600
  CITY1,19:00:00,22:00:00,1800
  CITY2,19:00:00,22:00:00,1800
  route_id,agency_id,route_short_name,route_long_name,route_desc,route_type,route_url,route_color,route_text_color
  AB,DTA,,Airport - Bullfrog,,3
  BFC,DTA,,Bullfrog - Furnace Creek Resort,,3
  STBA,DTA,,Stagecoach - Airport Shuttle,,3
  CITY,DTA,,City,,3
  AAMV,DTA,,Airport - Amargosa Valley,,3
  trip_id,arrival_time,departure_time,stop_id,stop_sequence
  STBA,6:00:00,6:00:00,STAGECOACH,1
  STBA,6:20:00,6:20:00,BEATTY_AIRPORT,2
  CITY1,6:00:00,6:00:00,STAGECOACH,1
  CITY1,6:05:00,6:07:00,NANAA,2
  CITY1,6:12:00,6:14:00,NADAV,3
  CITY1,6:19:00,6:21:00,DADAN,4
  CITY1,6:26:00,6:28:00,EMSI,5
  CITY2,6:28:00,6:30:00,EMSI,1
  CITY2,6:35:00,6:37:00,DADAN,2
  CITY2,6:42:00,6:44:00,NADAV,3
  CITY2,6:49:00,6:51:00,NANAA,4
  CITY2,6:56:00,6:58:00,STAGECOACH,5
  AB1,8:00:00,8:00:00,BEATTY_AIRPORT,1
  AB1,8:10:00,8:15:00,BULLFROG,2
  AB2,12:05:00,12:05:00,BULLFROG,1
  AB2,12:15:00,12:15:00,BEATTY_AIRPORT,2
  BFC1,8:20:00,8:20:00,BULLFROG,1
  BFC1,9:20:00,9:20:00,FUR_CREEK_RES,2
  BFC2,11:00:00,11:00:00,FUR_CREEK_RES,1
  BFC2,12:00:00,12:00:00,BULLFROG,2
  AAMV1,8:00:00,8:00:00,BEATTY_AIRPORT,1
  AAMV1,9:00:00,9:00:00,AMV,2
  AAMV2,10:00:00,10:00:00,AMV,1
  AAMV2,11:00:00,11:00:00,BEATTY_AIRPORT,2
  AAMV3,13:00:00,13:00:00,BEATTY_AIRPORT,1
  AAMV3,14:00:00,14:00:00,AMV,2
  AAMV4,15:00:00,15:00:00,AMV,1
  AAMV4,16:00:00,16:00:00,BEATTY_AIRPORT,2
 
  stop_id,stop_name,stop_desc,stop_lat,stop_lon,zone_id,stop_url
  FUR_CREEK_RES,Furnace Creek Resort (Demo),,36.425288,-117.133162
  BEATTY_AIRPORT,Nye County Airport (Demo),,36.868446,-116.784582
  BULLFROG,Bullfrog (Demo),,36.88108,-116.81797
  STAGECOACH,Stagecoach Hotel & Casino (Demo),,36.915682,-116.751677
  NADAV,North Ave / D Ave N (Demo),,36.914893,-116.76821
  NANAA,North Ave / N A Ave (Demo),,36.914944,-116.761472
  DADAN,Doing Ave / D Ave N (Demo),,36.909489,-116.768242
  EMSI,E Main St / S Irving St (Demo),,36.905697,-116.76218
  AMV,Amargosa Valley (Demo),,36.641496,-116.40094
  route_id,service_id,trip_id,trip_headsign,direction_id,block_id,shape_id
  AB,FULLW,AB1,to Bullfrog,0,1
  AB,FULLW,AB2,to Airport,1,2
  STBA,FULLW,STBA,Shuttle
  CITY,FULLW,CITY1,,0
  CITY,FULLW,CITY2,,1
  BFC,FULLW,BFC1,to Furnace Creek Resort,0,1
  BFC,FULLW,BFC2,to Bullfrog,1,2
  AAMV,WE,AAMV1,to Amargosa Valley,0
  AAMV,WE,AAMV2,to Airport,1
  AAMV,WE,AAMV3,to Amargosa Valley,0
  AAMV,WE,AAMV4,to Airport,1
  agency_id,agency_name,agency_url,agency_timezone
  DTA,Demo Transit Authority,http://google.com,America/Los_Angeles
  service_id,monday,tuesday,wednesday,thursday,friday,saturday,sunday,start_date,end_date
  FULLW,1,1,1,1,1,1,1,20070101,20101231
  WE,0,0,0,0,0,1,1,20070101,20101231
  service_id,date,exception_type
  FULLW,20070604,2
  fare_id,price,currency_type,payment_method,transfers,transfer_duration
  p,1.25,USD,0,0,
  a,5.25,USD,0,0,
 
  fare_id,route_id,origin_id,destination_id,contains_id
  p,AB,,,
  p,STBA,,,
  p,BFC,,,
  a,AAMV,,,
 
  trip_id,start_time,end_time,headway_secs
  STBA,6:00:00,22:00:00,1800
  CITY1,6:00:00,7:59:59,1800
  CITY2,6:00:00,7:59:59,1800
  CITY1,8:00:00,9:59:59,600
  CITY2,8:00:00,9:59:59,600
  CITY1,10:00:00,15:59:59,1800
  CITY2,10:00:00,15:59:59,1800
  CITY1,16:00:00,18:59:59,600
  CITY2,16:00:00,18:59:59,600
  CITY1,19:00:00,22:00:00,1800
  CITY2,19:00:00,22:00:00,1800
  route_id,agency_id,route_short_name,route_long_name,route_desc,route_type,route_url,route_color,route_text_color
  AB,DTA,,Airport - Bullfrog,,3,,,
  BFC,DTA,,Bullfrog - Furnace Creek Resort,,3,,,
  STBA,DTA,,Stagecoach - Airport Shuttle,,3,,,
  CITY,DTA,,City,,3,,,
  AAMV,DTA,,Airport - Amargosa Valley,,3,,,
  trip_id,arrival_time,departure_time,stop_id,stop_sequence,stop_headsign,pickup_type,drop_off_type,shape_dist_traveled
  STBA,6:00:00,6:00:00,STAGECOACH,0,,,,
  STBA,6:20:00,6:20:00,BEATTY_AIRPORT,1,,,,
  CITY1,6:00:00,6:00:00,STAGECOACH,0,,,,
  CITY1,6:05:00,6:07:00,NANAA,1,,,,
  CITY1,6:12:00,6:14:00,NADAV,2,,,,
  CITY1,6:19:00,6:21:00,DADAN,3,,,,
  CITY1,6:26:00,6:28:00,EMSI,4,,,,
  CITY2,6:28:00,6:30:00,EMSI,-2,,,,
  CITY2,6:35:00,6:37:00,DADAN,1,,,,
  CITY2,6:42:00,6:44:00,NADAV,2,,,,
  CITY2,6:49:00,6:51:00,NANAA,3,,,,
  CITY2,6:56:00,6:58:00,STAGECOACH,4,,,,
  AB1,8:00:00,8:00:00,BEATTY_AIRPORT,0,,,,
  AB1,8:10:00,8:15:00,BULLFROG,1,,,,
  AB2,12:05:00,12:05:00,BULLFROG,0,,,,
  AB2,12:15:00,12:15:00,BEATTY_AIRPORT,1,,,,
  BFC1,8:20:00,8:20:00,BULLFROG,0,,,,
  BFC1,9:20:00,9:20:00,FUR_CREEK_RES,1,,,,
  BFC2,11:00:00,11:00:00,FUR_CREEK_RES,0,,,,
  BFC2,12:00:00,12:00:00,BULLFROG,1,,,,
  AAMV1,8:00:00,8:00:00,BEATTY_AIRPORT,0,,,,
  AAMV1,9:00:00,9:00:00,AMV,1,,,,
  AAMV2,10:00:00,10:00:00,AMV,0,,,,
  AAMV2,11:00:00,11:00:00,BEATTY_AIRPORT,1,,,,
  AAMV3,13:00:00,13:00:00,BEATTY_AIRPORT,0,,,,
  AAMV3,14:00:00,14:00:00,AMV,1,,,,
  AAMV4,15:00:00,15:00:00,AMV,0,,,,
  AAMV4,16:00:00,16:00:00,BEATTY_AIRPORT,1,,,,
  stop_id,stop_name,stop_desc,stop_lat,stop_lon,zone_id,stop_url
  FUR_CREEK_RES,Furnace Creek Resort (Demo),,36.425288,-117.133162,,
  BEATTY_AIRPORT,Nye County Airport (Demo),,36.868446,-116.784582,,
  BULLFROG,Bullfrog (Demo),,36.88108,-116.81797,,
  STAGECOACH,Stagecoach Hotel & Casino (Demo),,36.915682,-116.751677,,
  NADAV,North Ave / D Ave N (Demo),,36.914893,-116.76821,,
  NANAA,North Ave / N A Ave (Demo),,36.914944,-116.761472,,
  DADAN,Doing Ave / D Ave N (Demo),,36.909489,-116.768242,,
  EMSI,E Main St / S Irving St (Demo),,36.905697,-116.76218,,
  AMV,Amargosa Valley (Demo),,36.641496,-116.40094,,
  route_id,service_id,trip_id,trip_headsign,direction_id,block_id,shape_id
  AB,FULLW,AB1,to Bullfrog,0,1,
  AB,FULLW,AB2,to Airport,1,2,
  STBA,FULLW,STBA,Shuttle,,,
  CITY,FULLW,CITY1,,0,,
  CITY,FULLW,CITY2,,1,,
  BFC,FULLW,BFC1,to Furnace Creek Resort,0,1,
  BFC,FULLW,BFC2,to Bullfrog,1,2,
  AAMV,WE,AAMV1,to Amargosa Valley,0,,
  AAMV,WE,AAMV2,to Airport,1,,
  AAMV,WE,AAMV3,to Amargosa Valley,0,,
  AAMV,WE,AAMV4,to Airport,1,,
  <?xml version="1.0" encoding="UTF-8"?>
  <kml xmlns="http://earth.google.com/kml/2.0">
  <Document>
  <name>A test file with one placemark</name>
  <decription></decription>
  <Placemark>
  <name>Test</name>
  <description></description>
  <LineString>
  <coordinates>
  -93.238861,44.854240,0.000000
  -93.238708,44.853081,0.000000
  -93.237923,44.852638,0.000000
  </coordinates>
  </LineString>
  </Placemark>
  </Document>
  </kml>
 
  <?xml version="1.0" encoding="UTF-8"?>
  <kml xmlns="http://earth.google.com/kml/2.0">
  <Document>
  <name>A test file with one placemark</name>
  <decription></decription>
  <Placemark>
  <name>Stop Name</name>
  <description></description>
  <Point>
  <coordinates>-93.239037,44.854164,0.000000</coordinates>
  </Point>
  </Placemark>
  </Document>
  </kml>
 
  agency_id,agency_name,agency_url,agency_timezone
  DTA,Demo Transit Authority,http://google.com,America/Los_Angeles
  service_id,date,exception_type
  FULLW,20070604,1
  WE,20070605,1
 
  fare_id,price,currency_type,payment_method,transfers,transfer_duration
  p,1.25,USD,0,0,
  a,5.25,USD,0,0,
 
  fare_id,route_id,origin_id,destination_id,contains_id
  p,AB,,,
  p,STBA,,,
  p,BFC,,,
  a,AAMV,,,
 
  trip_id,start_time,end_time,headway_secs
  STBA,6:00:00,22:00:00,1800
  CITY1,6:00:00,7:59:59,1800
  CITY2,6:00:00,7:59:59,1800
  CITY1,8:00:00,9:59:59,600
  CITY2,8:00:00,9:59:59,600
  CITY1,10:00:00,15:59:59,1800
  CITY2,10:00:00,15:59:59,1800
  CITY1,16:00:00,18:59:59,600
  CITY2,16:00:00,18:59:59,600
  CITY1,19:00:00,22:00:00,1800
  CITY2,19:00:00,22:00:00,1800
  route_id,agency_id,route_short_name,route_long_name,route_desc,route_type,route_url,route_color,route_text_color
  AB,DTA,,Airport - Bullfrog,,3,,,
  BFC,DTA,,Bullfrog - Furnace Creek Resort,,3,,,
  STBA,DTA,,Stagecoach - Airport Shuttle,,3,,,
  CITY,DTA,,City,,3,,,
  AAMV,DTA,,Airport - Amargosa Valley,,3,,,
  trip_id,arrival_time,departure_time,stop_id,stop_sequence,stop_headsign,pickup_type,drop_off_type,shape_dist_traveled
  STBA,6:00:00,6:00:00,STAGECOACH,1,,,,
  STBA,6:20:00,6:20:00,BEATTY_AIRPORT,2,,,,
  CITY1,6:00:00,6:00:00,STAGECOACH,1,,,,
  CITY1,6:05:00,6:07:00,NANAA,2,,,,
  CITY1,6:12:00,6:14:00,NADAV,3,,,,
  CITY1,6:19:00,6:21:00,DADAN,4,,,,
  CITY1,6:26:00,6:28:00,EMSI,5,,,,
  CITY2,6:28:00,6:30:00,EMSI,1,,,,
  CITY2,6:35:00,6:37:00,DADAN,2,,,,
  CITY2,6:42:00,6:44:00,NADAV,3,,,,
  CITY2,6:49:00,6:51:00,NANAA,4,,,,
  CITY2,6:56:00,6:58:00,STAGECOACH,5,,,,
  AB1,8:00:00,8:00:00,BEATTY_AIRPORT,1,,,,
  AB1,8:10:00,8:15:00,BULLFROG,2,,,,
  AB2,12:05:00,12:05:00,BULLFROG,1,,,,
  AB2,12:15:00,12:15:00,BEATTY_AIRPORT,2,,,,
  BFC1,8:20:00,8:20:00,BULLFROG,1,,,,
  BFC1,9:20:00,9:20:00,FUR_CREEK_RES,2,,,,
  BFC2,11:00:00,11:00:00,FUR_CREEK_RES,1,,,,
  BFC2,12:00:00,12:00:00,BULLFROG,2,,,,
  AAMV1,8:00:00,8:00:00,BEATTY_AIRPORT,1,,,,
  AAMV1,9:00:00,9:00:00,AMV,2,,,,
  AAMV2,10:00:00,10:00:00,AMV,1,,,,
  AAMV2,11:00:00,11:00:00,BEATTY_AIRPORT,2,,,,
  AAMV3,13:00:00,13:00:00,BEATTY_AIRPORT,1,,,,
  AAMV3,14:00:00,14:00:00,AMV,2,,,,
  AAMV4,15:00:00,15:00:00,AMV,1,,,,
  AAMV4,16:00:00,16:00:00,BEATTY_AIRPORT,2,,,,
  stop_id,stop_name,stop_desc,stop_lat,stop_lon,zone_id,stop_url
  FUR_CREEK_RES,Furnace Creek Resort (Demo),,36.425288,-117.133162,,
  BEATTY_AIRPORT,Nye County Airport (Demo),,36.868446,-116.784582,,
  BULLFROG,Bullfrog (Demo),,36.88108,-116.81797,,
  STAGECOACH,Stagecoach Hotel & Casino (Demo),,36.915682,-116.751677,,
  NADAV,North Ave / D Ave N (Demo),,36.914893,-116.76821,,
  NANAA,North Ave / N A Ave (Demo),,36.914944,-116.761472,,
  DADAN,Doing Ave / D Ave N (Demo),,36.909489,-116.768242,,
  EMSI,E Main St / S Irving St (Demo),,36.905697,-116.76218,,
  AMV,Amargosa Valley (Demo),,36.641496,-116.40094,,
  route_id,service_id,trip_id,trip_headsign,direction_id,block_id,shape_id
  AB,FULLW,AB1,to Bullfrog,0,1,
  AB,FULLW,AB2,to Airport,1,2,
  STBA,FULLW,STBA,Shuttle,,,
  CITY,FULLW,CITY1,,0,,
  CITY,FULLW,CITY2,,1,,
  BFC,FULLW,BFC1,to Furnace Creek Resort,0,1,
  BFC,FULLW,BFC2,to Bullfrog,1,2,
  AAMV,WE,AAMV1,to Amargosa Valley,0,,
  AAMV,WE,AAMV2,to Airport,1,,
  AAMV,WE,AAMV3,to Amargosa Valley,0,,
  AAMV,WE,AAMV4,to Airport,1,,
  agency_id,agency_name,agency_url,agency_timezone
  DTA,Demo Transit Authority,http://google.com,America/Los_Angeles
  service_id,monday,tuesday,wednesday,thursday,friday,saturday,sunday,start_date,end_date
  FULLW,1,1,1,1,1,1,1,20070101,20101231
  WE,0,0,0,0,0,1,1,20070101,20101231
  service_id,date,exception_type
  FULLW,20070604,2
  fare_id,price,currency_type,payment_method,transfers,transfer_duration
  p,1.25,USD,0,0,
  a,5.25,USD,0,0,
 
  fare_id,route_id,origin_id,destination_id,contains_id
  p,AB,,,
  p,STBA,,,
  p,BFC,,,
  a,AAMV,,,
 
  trip_id,start_time,end_time,headway_secs
  STBA,6:00:00,22:00:00,1800
  CITY1,6:00:00,7:59:59,1800
  CITY2,6:00:00,7:59:59,1800
  CITY1,8:00:00,9:59:59,600
  CITY2,8:00:00,9:59:59,600
  CITY1,10:00:00,15:59:59,1800
  CITY2,10:00:00,15:59:59,1800
  CITY1,16:00:00,18:59:59,600
  CITY2,16:00:00,18:59:59,600
  CITY1,19:00:00,22:00:00,1800
  CITY2,19:00:00,22:00:00,1800
  route_id,agency_id,route_short_name,route_long_name,route_desc,route_type,route_url,route_color,route_text_color
  AB,DTA,,Airport ⇒ Bullfrog,,3,,,
  BFC,DTA,,Bullfrog ⇒ Furnace Creek Resort,,3,,,
  STBA,DTA,,Stagecoach ⇒ Airport Shuttle,,3,,,
  STBB,DTA,,Stagecoach ⇒ Airport Shuttle,,3,,,
  CITY,DTA,,City,,3,,,
  AAMV,DTA,,Airport ⇒ Amargosa Valley,,3,,,
 
  trip_id,arrival_time,departure_time,stop_id,stop_sequence,stop_headsign,pickup_type,drop_off_type,shape_dist_traveled
  STBA,6:00:00,6:00:00,STAGECOACH,1,,,,
  STBA,6:20:00,6:20:00,BEATTY_AIRPORT,2,,,,
  CITY1,6:00:00,6:00:00,STAGECOACH,1,,,,
  CITY1,6:05:00,6:07:00,NANAA,2,,,,
  CITY1,6:12:00,6:14:00,NADAV,3,,,,
  CITY1,6:19:00,6:21:00,DADAN,4,,,,
  CITY1,6:26:00,6:28:00,EMSI,5,,,,
  CITY2,6:28:00,6:30:00,EMSI,1,,,,
  CITY2,6:35:00,6:37:00,DADAN,2,,,,
  CITY2,6:42:00,6:44:00,NADAV,3,,,,
  CITY2,6:49:00,6:51:00,NANAA,4,,,,
  CITY2,6:56:00,6:58:00,STAGECOACH,5,,,,
  AB1,8:00:00,8:00:00,BEATTY_AIRPORT,1,,,,
  AB1,8:10:00,8:15:00,BULLFROG,2,,,,
  AB2,12:05:00,12:05:00,BULLFROG,1,,,,
  AB2,12:15:00,12:15:00,BEATTY_AIRPORT,2,,,,
  BFC1,8:20:00,8:20:00,BULLFROG,1,,,,
  BFC1,9:20:00,9:20:00,FUR_CREEK_RES,2,,,,
  BFC2,11:00:00,11:00:00,FUR_CREEK_RES,1,,,,
  BFC2,12:00:00,12:00:00,BULLFROG,2,,,,
  AAMV1,8:00:00,8:00:00,BEATTY_AIRPORT,1,,,,
  AAMV1,9:00:00,9:00:00,AMV,2,,,,
  AAMV2,10:00:00,10:00:00,AMV,1,,,,
  AAMV2,11:00:00,11:00:00,BEATTY_AIRPORT,2,,,,
  AAMV3,13:00:00,13:00:00,BEATTY_AIRPORT,1,,,,
  AAMV3,14:00:00,14:00:00,AMV,2,,,,
  AAMV4,15:00:00,15:00:00,AMV,1,,,,
  AAMV4,16:00:00,16:00:00,BEATTY_AIRPORT,2,,,,
  stop_id,stop_name,stop_desc,stop_lat,stop_lon,zone_id,stop_url
  FUR_CREEK_RES,Furnace Creek Resort (Demo),,36.425288,-117.133162,,
  BEATTY_AIRPORT,Nye County Airport (Demo),,36.868446,-116.784582,,
  BULLFROG,Bullfrog (Demo),,36.88108,-116.81797,,
  STAGECOACH,Stagecoach Hotel & Casino (Demo),,36.915682,-116.751677,,
  NADAV,North Ave / D Ave N (Demo),,36.914893,-116.76821,,
  NANAA,North Ave / N A Ave (Demo),,36.914944,-116.761472,,
  DADAN,Doing Ave / D Ave N (Demo),,36.909489,-116.768242,,
  EMSI,E Main St / S Irving St (Demo),,36.905697,-116.76218,,
  AMV,Amargosa Valley (Demo),,36.641496,-116.40094,,
  route_id,service_id,trip_id,trip_headsign,direction_id,block_id,shape_id
  AB,FULLW,AB1,to Bullfrog,0,1,
  AB,FULLW,AB2,to Airport,1,2,
  STBA,FULLW,STBA,Shuttle,,,
  CITY,FULLW,CITY1,,0,,
  CITY,FULLW,CITY2,,1,,
  BFC,FULLW,BFC1,to Furnace Creek Resort,0,1,
  BFC,FULLW,BFC2,to Bullfrog,1,2,
  AAMV,WE,AAMV1,to Amargosa Valley,0,,
  AAMV,WE,AAMV2,to Airport,1,,
  AAMV,WE,AAMV3,to Amargosa Valley,0,,
  AAMV,WE,AAMV4,to Airport,1,,
  agency_id,agency_name,agency_url,agency_timezone
  DTA,Demo Transit Authority,http://google.com,America/Los_Angeles
  service_id,monday,tuesday,wednesday,thursday,friday,saturday,sunday,start_date,end_date
  FULLW,1,1,1,1,1,1,1,20070101,20101231
  WE,0,0,0,0,0,1,1,20070101,20101231
  service_id,date,exception_type
  FULLW,20070604,2
  fare_id,price,currency_type,payment_method,transfers,transfer_duration
  p,1.25,USD,0,0,
  a,5.25,USD,0,0,
 
  fare_id,route_id,origin_id,destination_id,contains_id
  p,AB,,,
  p,STBA,,,
  p,BFC,,,
  a,AAMV,,,
 
  trip_id,start_time,end_time,headway_secs
  STBA,6:00:00,22:00:00,1800
  CITY1,6:00:00,7:59:59,1800
  CITY2,6:00:00,7:59:59,1800
  CITY1,8:00:00,9:59:59,600
  CITY2,8:00:00,9:59:59,600
  CITY1,10:00:00,15:59:59,1800
  CITY2,10:00:00,15:59:59,1800
  CITY1,16:00:00,18:59:59,600
  CITY2,16:00:00,18:59:59,600
  CITY1,19:00:00,22:00:00,1800
  CITY2,19:00:00,22:00:00,1800
  route_id,agency_id,route_short_name,route_long_name,route_desc,route_type,route_url,route_color,route_text_color
  AB,DTA,,Airport - Bullfrog,,3,,,
  BFC,DTA,,Bullfrog - Furnace Creek Resort,,3,,,
  STBA,DTA,,Stagecoach - Airport Shuttle,,3,,,
  CITY,DTA,City,City,,3,,,
  AAMV,DTA,,Airport - Amargosa Valley,,3,,,
 
  trip_id,arrival_time,departure_time,stop_id,stop_sequence,stop_headsign,pickup_type,drop_off_type,shape_dist_traveled
  STBA,6:00:00,6:00:00,STAGECOACH,1,,,,
  STBA,6:20:00,6:20:00,BEATTY_AIRPORT,2,,,,
  CITY1,6:00:00,6:00:00,STAGECOACH,1,,,,
  CITY1,6:05:00,6:07:00,NANAA,2,,,,
  CITY1,6:12:00,6:14:00,NADAV,3,,,,
  CITY1,6:19:00,6:21:00,DADAN,4,,,,
  CITY1,6:26:00,6:28:00,EMSI,5,,,,
  CITY2,6:28:00,6:30:00,EMSI,1,,,,
  CITY2,6:35:00,6:37:00,DADAN,2,,,,
  CITY2,6:42:00,6:44:00,NADAV,3,,,,
  CITY2,6:49:00,6:51:00,NANAA,4,,,,
  CITY2,6:56:00,6:58:00,STAGECOACH,5,,,,
  AB1,8:00:00,8:00:00,BEATTY_AIRPORT,1,,,,
  AB1,8:10:00,8:15:00,BULLFROG,2,,,,
  AB2,12:05:00,12:05:00,BULLFROG,1,,,,
  AB2,12:15:00,12:15:00,BEATTY_AIRPORT,2,,,,
  BFC1,8:20:00,8:20:00,BULLFROG,1,,,,
  BFC1,9:20:00,9:20:00,FUR_CREEK_RES,2,,,,
  BFC2,11:00:00,11:00:00,FUR_CREEK_RES,1,,,,
  BFC2,12:00:00,12:00:00,BULLFROG,2,,,,
  AAMV1,8:00:00,8:00:00,BEATTY_AIRPORT,1,,,,
  AAMV1,9:00:00,9:00:00,AMV,2,,,,
  AAMV2,10:00:00,10:00:00,AMV,1,,,,
  AAMV2,11:00:00,11:00:00,BEATTY_AIRPORT,2,,,,
  AAMV3,13:00:00,13:00:00,BEATTY_AIRPORT,1,,,,
  AAMV3,14:00:00,14:00:00,AMV,2,,,,
  AAMV4,15:00:00,15:00:00,AMV,1,,,,
  AAMV4,16:00:00,16:00:00,BEATTY_AIRPORT,2,,,,
 
  stop_id,stop_name,stop_desc,stop_lat,stop_lon,zone_id,stop_url
  FUR_CREEK_RES,Furnace Creek Resort (Demo),,36.425288,-117.133162,,
  BEATTY_AIRPORT,Nye County Airport (Demo),,36.868446,-116.784582,,
  BULLFROG,Bullfrog (Demo),,36.88108,-116.81797,,
  STAGECOACH,Stagecoach Hotel & Casino (Demo),,36.915682,-116.751677,,
  NADAV,North Ave / D Ave N (Demo),,36.914893,-116.76821,,
  NANAA,North Ave / N A Ave (Demo),,36.914944,-116.761472,,
  DADAN,Doing Ave / D Ave N (Demo),,36.909489,-116.768242,,
  EMSI,E Main St / S Irving St (Demo),,36.905697,-116.76218,,
  AMV,Amargosa Valley (Demo),,36.641496,-116.40094,,
  route_id,service_id,trip_id,trip_headsign,direction_id,block_id,shape_id
  AB,FULLW,AB1,to Bullfrog,0,1,
  AB,FULLW,AB2,to Airport,1,2,
  STBA,FULLW,STBA,Shuttle,,,
  CITY,FULLW,CITY1,,0,,
  CITY,FULLW,CITY2,,1,,
  BFC,FULLW,BFC1,to Furnace Creek Resort,0,1,
  BFC,FULLW,BFC2,to Bullfrog,1,2,
  AAMV,WE,AAMV1,to Amargosa Valley,0,,
  AAMV,WE,AAMV2,to Airport,1,,
  AAMV,WE,AAMV3,to Amargosa Valley,0,,
  AAMV,WE,AAMV4,to Airport,1,,
  agency_id,agency_name,agency_url,agency_timezone
  DTA,Demo Transit Authority,http://google.com,America/Los_Angeles
  service_id,monday,tuesday,wednesday,thursday,friday,saturday,sunday,start_date,end_date
  FULLW,1,1,1,1,1,1,1,20070101,20101231
  WE,0,0,0,0,0,1,1,20070101,20101231
  service_id,date,exception_type
  FULLW,20070604,2
  fare_id,price,currency_type,payment_method,transfers,transfer_duration
  p,1.25,USD,0,0,
  a,5.25,USD,0,0,
 
  fare_id,route_id,origin_id,destination_id,contains_id
  p,AB,,,
  p,STBA,,,
  p,BFC,,,
  a,AAMV,,,
 
  trip_id,start_time,end_time,headway_secs
  STBA,6:00:00,22:00:00,1800
  CITY1,6:00:00,7:59:59,1800
  CITY2,6:00:00,7:59:59,1800
  CITY1,8:00:00,9:59:59,600
  CITY2,8:00:00,9:59:59,600
  CITY1,10:00:00,15:59:59,1800
  CITY2,10:00:00,15:59:59,1800
  CITY1,16:00:00,18:59:59,600
  CITY2,16:00:00,18:59:59,600
  CITY1,19:00:00,22:00:00,1800
  CITY2,19:00:00,22:00:00,1800
  route_id,agency_id,route_short_name,route_long_name,route_desc,route_type,route_url,route_color,route_text_color
  AB,DTA,,Airport - Bullfrog,,3,,,
  BFC,DTA,,Bullfrog - Furnace Creek Resort,,3,,,
  STBA,DTA,,Stagecoach - Airport Shuttle,,3,,,
  CITY,DTA,,City,,3,,,
  AAMV,DTA,,Airport - Amargosa Valley,,3,,,
  trip_id,arrival_time,departure_time,stop_id,stop_sequence,stop_headsign,pickup_type,drop_off_type,shape_dist_traveled
  STBA,6:00:00,6:00:00,STAGECOACH,1,,,,
  STBA,6:20:00,6:20:00,BEATTY_AIRPORT,2,,,,
  CITY1,6:00:00,6:00:00,STAGECOACH,1,,,,
  CITY1,6:05:00,6:07:00,NANAA,2,,,,
  CITY1,6:12:00,6:14:00,NADAV,3,,,,
  CITY1,6:19:00,6:21:00,DADAN,4,,,,
  CITY1,6:26:00,6:28:00,EMSI,5,,,,
  CITY2,6:28:00,6:30:00,EMSI,1,,,,
  CITY2,6:35:00,6:37:00,DADAN,2,,,,
  CITY2,6:40:00,6:41:00,NADAR,3,,,,
  CITY2,6:42:00,6:44:00,NADAV,4,,,,
  CITY2,6:49:00,6:51:00,NANAA,5,,,,
  CITY2,6:56:00,6:58:00,STAGECOACH,6,,,,
  AB1,8:00:00,8:00:00,BEATTY_AIRPORT,1,,,,
  AB1,8:10:00,8:15:00,BULLFROG,2,,,,
  AB2,12:05:00,12:05:00,BULLFROG,1,,,,
  AB2,12:15:00,12:15:00,BEATTY_AIRPORT,2,,,,
  BFC1,8:20:00,8:20:00,BULLFROG,1,,,,
  BFC1,9:20:00,9:20:00,FUR_CREEK_RES,2,,,,
  BFC2,11:00:00,11:00:00,FUR_CREEK_RES,1,,,,
  BFC2,12:00:00,12:00:00,BULLFROG,2,,,,
  AAMV1,8:00:00,8:00:00,BEATTY_AIRPORT,1,,,,
  AAMV1,9:00:00,9:00:00,AMV,2,,,,
  AAMV2,10:00:00,10:00:00,AMV,1,,,,
  AAMV2,11:00:00,11:00:00,BEATTY_AIRPORT,2,,,,
  AAMV3,13:00:00,13:00:00,BEATTY_AIRPORT,1,,,,
  AAMV3,14:00:00,14:00:00,AMV,2,,,,
  AAMV4,15:00:00,15:00:00,AMV,1,,,,
  AAMV4,16:00:00,16:00:00,BEATTY_AIRPORT,2,,,,
 
  stop_id,stop_name,stop_desc,stop_lat,stop_lon,zone_id,stop_url
  FUR_CREEK_RES,Furnace Creek Resort (Demo),,36.425288,-117.133162,,
  BEATTY_AIRPORT,Nye County Airport (Demo),,36.868446,-116.784582,,
  BULLFROG,Bullfrog (Demo),,36.88108,-116.81797,,
  STAGECOACH,Stagecoach Hotel & Casino (Demo),,36.915682,-116.751677,,
  NADAV,North Ave / D Ave N (Demo),,36.914893,-116.76821,,
  NANAA,North Ave / N A Ave (Demo),,36.914944,-116.761472,,
  DADAN,Doing Ave / D Ave N (Demo),,36.909489,-116.768242,,
  EMSI,E Main St / S Irving St (Demo),,36.905697,-116.76218,,
  AMV,Amargosa Valley (Demo),,36.641496,-116.40094,,
  route_id,service_id,trip_id,trip_headsign,direction_id,block_id,shape_id
  AB,FULLW,AB1,to Bullfrog,0,1,
  AB,FULLW,AB2,to Airport,1,2,
  STBA,FULLW,STBA,Shuttle,,,
  CITY,FULLW,CITY1,,0,,
  CITY,FULLW,CITY2,,1,,
  BFC,FULLW,BFC1,to Furnace Creek Resort,0,1,
  BFC,FULLW,BFC2,to Bullfrog,1,2,
  AAMV,WE,AAMV1,to Amargosa Valley,0,,
  AAMV,WE,AAMV2,to Airport,1,,
  AAMV,WE,AAMV3,to Amargosa Valley,0,,
  AAMV,WE,AAMV4,to Airport,1,,
  agency_id,agency_name,agency_url,agency_timezone,agency_phone
  DTA,Autorité de passage de démonstration,http://google.com,America/Los_Angeles,123 12314
 
  service_id,monday,tuesday,wednesday,thursday,friday,saturday,sunday,start_date,end_date
  FULLW,1,1,1,1,1,1,1,20070101,20101231
  WE,0,0,0,0,0,1,1,20070101,20101231
  service_id,date,exception_type
  FULLW,20070604,2
  fare_id,price,currency_type,payment_method,transfers,transfer_duration
  p,1.25,USD,0,0,
  a,5.25,USD,0,0,
 
  fare_id,route_id,origin_id,destination_id,contains_id
  p,AB,,,
  p,STBA,,,
  p,BFC,,,
  a,AAMV,,,
 
  trip_id,start_time,end_time,headway_secs
  STBA,6:00:00,22:00:00,1800
  CITY1,6:00:00,7:59:59,1800
  CITY2,6:00:00,7:59:59,1800
  CITY1,8:00:00,9:59:59,600
  CITY2,8:00:00,9:59:59,600
  CITY1,10:00:00,15:59:59,1800
  CITY2,10:00:00,15:59:59,1800
  CITY1,16:00:00,18:59:59,600
  CITY2,16:00:00,18:59:59,600
  CITY1,19:00:00,22:00:00,1800
  CITY2,19:00:00,22:00:00,1800
  route_id,agency_id,route_short_name,route_long_name,route_desc,route_type,route_url,route_color,route_text_color
  AB,DTA,,Airport ⇒ Bullfrog,,3,,,
  BFC,DTA,,Bullfrog ⇒ Furnace Creek Resort,,3,,,
  STBA,DTA,,Stagecoach ⇒ Airport Shuttle,,3,,,
  CITY,DTA,Ō,Bar Circle,Route with ĸool unicode shortname,3,,,
  AAMV,DTA,,Airport ⇒ Amargosa Valley,,3,,,
 
  trip_id,arrival_time,departure_time,stop_id,stop_sequence,stop_headsign,pickup_type,drop_off_type,shape_dist_traveled
  STBA,6:00:00,6:00:00,STAGECOACH,0,to airport,1,0,0.212
  STBA,6:20:00,6:20:00,BEATTY_AIRPORT,2,,0,0,1.043
  CITY1,6:00:00,6:00:00,STAGECOACH,0,,,,
  CITY1,6:05:00,6:07:00,NANAA,5,going to nadav,2,3,
  CITY1,6:12:00,6:14:00,NADAV,10,,,,
  CITY1,6:19:00,6:21:00,DADAN,15,,,,
  CITY1,6:26:00,6:28:00,EMSI,20,,,,
  CITY2,6:28:00,6:30:00,EMSI,100,,,,
  CITY2,6:35:00,6:37:00,DADAN,200,,,,
  CITY2,6:42:00,6:44:00,NADAV,300,,,,
  CITY2,6:49:00,6:51:00,NANAA,400,,,,
  CITY2,6:56:00,6:58:00,STAGECOACH,500,,,,
  AB1,8:00:00,8:00:00,BEATTY_AIRPORT,1,,,,
  AB1,8:10:00,8:15:00,BULLFROG,2,,,,
  AB2,12:05:00,12:05:00,BULLFROG,1,,,,
  AB2,12:15:00,12:15:00,BEATTY_AIRPORT,2,,,,
  BFC1,8:20:00,8:20:00,BULLFROG,1,,,,
  BFC1,9:20:00,9:20:00,FUR_CREEK_RES,2,,,,
  BFC2,11:00:00,11:00:00,FUR_CREEK_RES,1,,,,
  BFC2,12:00:00,12:00:00,BULLFROG,2,,,,
  AAMV1,8:00:00,8:00:00,BEATTY_AIRPORT,1,,,,
  AAMV1,9:00:00,9:00:00,AMV,2,,,,
  AAMV2,10:00:00,10:00:00,AMV,1,,,,
  AAMV2,11:00:00,11:00:00,BEATTY_AIRPORT,2,,,,
  AAMV3,13:00:00,13:00:00,BEATTY_AIRPORT,1,,,,
  AAMV3,14:00:00,14:00:00,AMV,2,,,,
  AAMV4,15:00:00,15:00:00,AMV,1,,,,
  AAMV4,16:00:00,16:00:00,BEATTY_AIRPORT,2,,,,
 
  stop_id,stop_name,stop_desc,stop_lat,stop_lon,zone_id,stop_url,stop_code,location_type,parent_station
  FUR_CREEK_RES,Furnace Creek Resort (Demo),,36.425288,-117.133162,,,1234,,
  BEATTY_AIRPORT,Nye County Airport (Demo),,36.868446,-116.784582,,,1235,0,BEATTY_AIRPORT_STATION
  BEATTY_AIRPORT_STATION,Nye County Airport (Demo),,36.868446,-116.784582,,,1235,1,
  BULLFROG,Bullfrog (Demo),,36.88108,-116.81797,,,,,
  STAGECOACH,Stagecoach Hotel & Casino (Demo),,36.915682,-116.751677,,,1236,,
  NADAV,North Ave / D Ave N (Demo),,36.914893,-116.76821,,,1237,,
  NANAA,North Ave / N A Ave (Demo),,36.914944,-116.761472,,,1238,,
  DADAN,Doing Ave / D Ave N (Demo),,36.909489,-116.768242,,,,,
  EMSI,E Main St / S Irving St (Demo),,36.905697,-116.76218,,,,,
  AMV,Amargosa Valley (Demo),,36.641496,-116.40094,,,,,
 
  from_stop_id,to_stop_id,transfer_type,min_transfer_time
  NADAV,NANAA,3,
  EMSI,NANAA,2,1200
 
  route_id,service_id,trip_id,trip_headsign,direction_id,block_id,shape_id
  AB,FULLW,AB1,to Bullfrog,0,1,
  AB,FULLW,AB2,to Airport,1,2,
  STBA,FULLW,STBA,Shuttle,,,
  CITY,FULLW,CITY1,,0,,
  CITY,FULLW,CITY2,,1,,
  BFC,FULLW,BFC1,to Furnace Creek Resort,0,1,
  BFC,FULLW,BFC2,to Bullfrog,1,2,
  AAMV,WE,AAMV1,to Amargosa Valley,0,,
  AAMV,WE,AAMV2,to Airport,1,,
  AAMV,WE,AAMV3,to Amargosa Valley,0,,
  AAMV,WE,AAMV4,to Airport,1,,
  agency_id,agency_name,agency_url,agency_timezone,agency_lange
  DTA,Demo Transit Authority,http://google.com,America/Los_Angeles,en
  service_id,monday,tuesday,wednesday,thursday,friday,saturday,sunday,start_date,end_date,leap_day
  FULLW,1,1,1,1,1,1,1,20070101,20101231,
  WE,0,0,0,0,0,1,1,20070101,20101231,
  service_id,date,exception_type,leap_day
  FULLW,20070604,2,
  fare_id,price,currency_type,payment_method,transfers,transfer_time
  p,1.25,USD,0,0,
  a,5.25,USD,0,0,
 
  fare_id,route_id,source_id,destination_id,contains_id
  p,AB,,,
  p,STBA,,,
  p,BFC,,,
  a,AAMV,,,
 
  trip_id,start_time,end_time,headway_secs,superfluous
  STBA,6:00:00,22:00:00,1800,
  CITY1,6:00:00,7:59:59,1800,
  CITY2,6:00:00,7:59:59,1800,
  CITY1,8:00:00,9:59:59,600,
  CITY2,8:00:00,9:59:59,600,
  CITY1,10:00:00,15:59:59,1800,
  CITY2,10:00:00,15:59:59,1800,
  CITY1,16:00:00,18:59:59,600,
  CITY2,16:00:00,18:59:59,600,
  CITY1,19:00:00,22:00:00,1800,
  CITY2,19:00:00,22:00:00,1800,
  route_id,agency_id,route_short_name,route_long_name,route_desc,route_type,route_url,route_color,Route_Text_Color
  AB,DTA,,Airport - Bullfrog,,3,,,
  BFC,DTA,,Bullfrog - Furnace Creek Resort,,3,,,
  STBA,DTA,,Stagecoach - Airport Shuttle,,3,,,
  CITY,DTA,,City,,3,,,
  AAMV,DTA,,Airport - Amargosa Valley,,3,,,
  trip_id,arrival_time,departure_time,stop_id,stop_sequence,stop_headsign,pickup_type,drop_off_time,shapedisttraveled
  STBA,6:00:00,6:00:00,STAGECOACH,1,,,,
  STBA,6:20:00,6:20:00,BEATTY_AIRPORT,2,,,,
  CITY1,6:00:00,6:00:00,STAGECOACH,1,,,,
  CITY1,6:05:00,6:07:00,NANAA,2,,,,
  CITY1,6:12:00,6:14:00,NADAV,3,,,,
  CITY1,6:19:00,6:21:00,DADAN,4,,,,
  CITY1,6:26:00,6:28:00,EMSI,5,,,,
  CITY2,6:28:00,6:30:00,EMSI,1,,,,
  CITY2,6:35:00,6:37:00,DADAN,2,,,,
  CITY2,6:42:00,6:44:00,NADAV,3,,,,
  CITY2,6:49:00,6:51:00,NANAA,4,,,,
  CITY2,6:56:00,6:58:00,STAGECOACH,5,,,,
  AB1,8:00:00,8:00:00,BEATTY_AIRPORT,1,,,,
  AB1,8:10:00,8:15:00,BULLFROG,2,,,,
  AB2,12:05:00,12:05:00,BULLFROG,1,,,,
  AB2,12:15:00,12:15:00,BEATTY_AIRPORT,2,,,,
  BFC1,8:20:00,8:20:00,BULLFROG,1,,,,
  BFC1,9:20:00,9:20:00,FUR_CREEK_RES,2,,,,
  BFC2,11:00:00,11:00:00,FUR_CREEK_RES,1,,,,
  BFC2,12:00:00,12:00:00,BULLFROG,2,,,,
  AAMV1,8:00:00,8:00:00,BEATTY_AIRPORT,1,,,,
  AAMV1,9:00:00,9:00:00,AMV,2,,,,
  AAMV2,10:00:00,10:00:00,AMV,1,,,,
  AAMV2,11:00:00,11:00:00,BEATTY_AIRPORT,2,,,,
  AAMV3,13:00:00,13:00:00,BEATTY_AIRPORT,1,,,,
  AAMV3,14:00:00,14:00:00,AMV,2,,,,
  AAMV4,15:00:00,15:00:00,AMV,1,,,,
  AAMV4,16:00:00,16:00:00,BEATTY_AIRPORT,2,,,,
 
  stop_id,stop_name,stop_desc,stop_lat,stop_lon,zone_id,stop_uri
  FUR_CREEK_RES,Furnace Creek Resort (Demo),,36.425288,-117.133162,,
  BEATTY_AIRPORT,Nye County Airport (Demo),,36.868446,-116.784582,,
  BULLFROG,Bullfrog (Demo),,36.88108,-116.81797,,
  STAGECOACH,Stagecoach Hotel & Casino (Demo),,36.915682,-116.751677,,
  NADAV,North Ave / D Ave N (Demo),,36.914893,-116.76821,,
  NANAA,North Ave / N A Ave (Demo),,36.914944,-116.761472,,
  DADAN,Doing Ave / D Ave N (Demo),,36.909489,-116.768242,,
  EMSI,E Main St / S Irving St (Demo),,36.905697,-116.76218,,
  AMV,Amargosa Valley (Demo),,36.641496,-116.40094,,
  from_stop_id,to_stop_id,transfer_type,min_transfer_time,to_stop
  NADAV,NANAA,3,,
 
  route_id,service_id,trip_id,trip_headsign,direction_id,block_id,sharpe_id
  AB,FULLW,AB1,to Bullfrog,0,1,
  AB,FULLW,AB2,to Airport,1,2,
  STBA,FULLW,STBA,Shuttle,,,
  CITY,FULLW,CITY1,,0,,
  CITY,FULLW,CITY2,,1,,
  BFC,FULLW,BFC1,to Furnace Creek Resort,0,1,
  BFC,FULLW,BFC2,to Bullfrog,1,2,
  AAMV,WE,AAMV1,to Amargosa Valley,0,,
  AAMV,WE,AAMV2,to Airport,1,,
  AAMV,WE,AAMV3,to Amargosa Valley,0,,
  AAMV,WE,AAMV4,to Airport,1,,
  agency_id,agency_name,agency_url,agency_timezone
  DTA,Demo Transit Authority,http://google.com,America/Los_Angeles
  service_id,monday,tuesday,wednesday,thursday,friday,saturday,sunday,start_date,end_date
  FULLW,1,1,1,1,1,1,1,20070101,20101231
  WE,0,0,0,0,0,1,1,20070101,20101231
  service_id,date,exception_type
  FULLW,20070604,2
  fare_id,price,currency_type,payment_method,transfers,transfer_duration
  p,1.25,USD,0,0,
  a,5.25,USD,0,0,
 
  fare_id,route_id,origin_id,destination_id,contains_id
  p,AB,,,
  p,STBA,,,
  p,BFC,,,
  a,AAMV,,,
 
  trip_id,start_time,end_time,headway_secs
  STBA,6:00:00,22:00:00,1800
  CITY1,6:00:00,7:59:59,1800
  CITY2,6:00:00,7:59:59,1800
  CITY1,8:00:00,9:59:59,600
  CITY2,8:00:00,9:59:59,600
  CITY1,10:00:00,15:59:59,1800
  CITY2,10:00:00,15:59:59,1800
  CITY1,16:00:00,18:59:59,600
  CITY2,16:00:00,18:59:59,600
  CITY1,19:00:00,22:00:00,1800
  CITY2,19:00:00,22:00:00,1800
  route_id,agency_id,route_short_name,route_long_name,route_desc,route_type,route_url,route_color,route_text_color
  AB,DTA,,Airport - Bullfrog,,3,,,
  BFC,DTA,,Bullfrog - Furnace Creek Resort,,3,,,
  STBA,DTA,,Stagecoach - Airport Shuttle,,3,,,
  CITY,DTA,,City,,3,,,
  AAMV,DTA,,Airport - Amargosa Valley,,3,,,
  trip_id,arrival_time,departure_time,stop_id,stop_sequence,stop_headsign,pickup_type,drop_off_type,shape_dist_traveled
  STBA,6:00:00,6:00:00,STAGECOACH,1,,,,
  STBA,6:20:00,6:20:00,BEATTY_AIRPORT,2,,,,
  CITY1,6:00:00,6:00:00,STAGECOACH,1,,,,
  CITY1,6:05:00,6:07:00,NANAA,2,,,,
  CITY1,6:12:00,6:14:00,NADAV,3,,,,
  CITY1,6:19:00,6:21:00,DADAN,4,,,,
  CITY1,6:26:00,6:28:00,EMSI,5,,,,
  CITY2,6:28:00,6:30:00,EMSI,1,,,,
  CITY2,6:35:00,6:37:00,DADAN,2,,,,
  CITY2,6:42:00,6:44:00,NADAV,3,,,,
  CITY2,6:49:00,6:51:00,NANAA,4,,,,
  CITY2,6:56:00,6:58:00,STAGECOACH,5,,,,
  AB1,8:00:00,8:00:00,BEATTY_AIRPORT,1,,,,
  AB1,8:10:00,8:15:00,BULLFROG,2,,,,
  AB2,12:05:00,12:05:00,BULLFROG,1,,,,
  AB2,12:15:00,12:15:00,BEATTY_AIRPORT,2,,,,
  BFC1,8:20:00,8:20:00,BULLFROG,1,,,,
  BFC1,9:20:00,9:20:00,FUR_CREEK_RES,2,,,,
  BFC2,11:00:00,11:00:00,FUR_CREEK_RES,1,,,,
  BFC2,12:00:00,12:00:00,BULLFROG,2,,,,
  AAMV1,8:00:00,8:00:00,BEATTY_AIRPORT,1,,,,
  AAMV1,9:00:00,9:00:00,AMV,2,,,,
  AAMV2,10:00:00,10:00:00,AMV,1,,,,
  AAMV2,11:00:00,11:00:00,BEATTY_AIRPORT,2,,,,
  AAMV3,13:00:00,13:00:00,BEATTY_AIRPORT,1,,,,
  AAMV3,14:00:00,14:00:00,AMV,2,,,,
  AAMV4,15:00:00,15:00:00,AMV,1,,,,
  AAMV4,16:00:00,16:00:00,BEATTY_AIRPORT,2,,,,
  stop_id,stop_name,stop_desc,stop_lat,stop_lon,zone_id,stop_url
  FUR_CREEK_RES,Furnace Creek Resort (Demo),,36.425288,-117.133162,,
  BEATTY_AIRPORT,Nye County Airport (Demo),,36.868446,-116.784582,,
  BULLFROG,Bullfrog (Demo),,36.88108,-116.81797,,
  STAGECOACH,Stagecoach Hotel & Casino (Demo),,36.915682,-116.751677,,
  BOGUS,Bogus Stop (Demo),,36.914682,-116.750677,,
  NADAV,North Ave / D Ave N (Demo),,36.914893,-116.76821,,
  NANAA,North Ave / N A Ave (Demo),,36.914944,-116.761472,,
  DADAN,Doing Ave / D Ave N (Demo),,36.909489,-116.768242,,
  EMSI,E Main St / S Irving St (Demo),,36.905697,-116.76218,,
  AMV,Amargosa Valley (Demo),,36.641496,-116.40094,,
 
  route_id,service_id,trip_id,trip_headsign,direction_id,block_id,shape_id
  AB,FULLW,AB1,to Bullfrog,0,1,
  AB,FULLW,AB2,to Airport,1,2,
  STBA,FULLW,STBA,Shuttle,,,
  CITY,FULLW,CITY1,,0,,
  CITY,FULLW,CITY2,,1,,
  BFC,FULLW,BFC1,to Furnace Creek Resort,0,1,
  BFC,FULLW,BFC2,to Bullfrog,1,2,
  AAMV,WE,AAMV1,to Amargosa Valley,0,,
  AAMV,WE,AAMV2,to Airport,1,,
  AAMV,WE,AAMV3,to Amargosa Valley,0,,
  AAMV,WE,AAMV4,to Airport,1,,
 Binary files /dev/null and b/origin-src/transitfeed-1.2.6/test/data/utf16/agency.txt differ
 Binary files /dev/null and b/origin-src/transitfeed-1.2.6/test/data/utf16/calendar.txt differ
 Binary files /dev/null and b/origin-src/transitfeed-1.2.6/test/data/utf16/calendar_dates.txt differ
 Binary files /dev/null and b/origin-src/transitfeed-1.2.6/test/data/utf16/fare_attributes.txt differ
 Binary files /dev/null and b/origin-src/transitfeed-1.2.6/test/data/utf16/fare_rules.txt differ
 Binary files /dev/null and b/origin-src/transitfeed-1.2.6/test/data/utf16/frequencies.txt differ
 Binary files /dev/null and b/origin-src/transitfeed-1.2.6/test/data/utf16/routes.txt differ
 Binary files /dev/null and b/origin-src/transitfeed-1.2.6/test/data/utf16/stop_times.txt differ
 Binary files /dev/null and b/origin-src/transitfeed-1.2.6/test/data/utf16/stops.txt differ
 Binary files /dev/null and b/origin-src/transitfeed-1.2.6/test/data/utf16/trips.txt differ
  agency_id,agency_name,agency_url,agency_timezone
  DTA,Demo Transit Authority,http://google.com,America/Los_Angeles
  service_id,monday,tuesday,wednesday,thursday,friday,saturday,sunday,start_date,end_date
  FULLW,1,1,1,1,1,1,1,20070101,20101231
  WE,0,0,0,0,0,1,1,20070101,20101231
  service_id,date,exception_type
  FULLW,20070604,2
  fare_id,price,currency_type,payment_method,transfers,transfer_duration
  p,1.25,USD,0,0,
  a,5.25,USD,0,0,
 
  fare_id,route_id,origin_id,destination_id,contains_id
  p,AB,,,
  p,STBA,,,
  p,BFC,,,
  a,AAMV,,,
 
  trip_id,start_time,end_time,headway_secs
  STBA,6:00:00,22:00:00,1800
  CITY1,6:00:00,7:59:59,1800
  CITY2,6:00:00,7:59:59,1800
  CITY1,8:00:00,9:59:59,600
  CITY2,8:00:00,9:59:59,600
  CITY1,10:00:00,15:59:59,1800
  CITY2,10:00:00,15:59:59,1800
  CITY1,16:00:00,18:59:59,600
  CITY2,16:00:00,18:59:59,600
  CITY1,19:00:00,22:00:00,1800
  CITY2,19:00:00,22:00:00,1800
  route_id,agency_id,route_short_name,route_long_name,route_desc,route_type,route_url,route_color,route_text_color
  AB,DTA,,Airport - Bullfrog,,3,,,
  BFC,DTA,,Bullfrog - Furnace Creek Resort,,3,,,
  STBA,DTA,,Stagecoach - Airport Shuttle,,3,,,
  CITY,DTA,,City,,3,,,
  AAMV,DTA,,Airport - Amargosa Valley,,3,,,
  trip_id,arrival_time,departure_time,stop_id,stop_sequence,stop_headsign,pickup_type,drop_off_type,shape_dist_traveled
  STBA,6:00:00,6:00:00,STAGECOACH,1,,,,
  STBA,6:20:00,6:20:00,BEATTY_AIRPORT,2,,,,
  CITY1,6:00:00,6:00:00,STAGECOACH,1,,,,
  CITY1,6:05:00,6:07:00,NANAA,2,,,,
  CITY1,6:12:00,6:14:00,NADAV,3,,,,
  CITY1,6:19:00,6:21:00,DADAN,4,,,,
  CITY1,6:26:00,6:28:00,EMSI,5,,,,
  CITY2,6:28:00,6:30:00,EMSI,1,,,,
  CITY2,6:35:00,6:37:00,DADAN,2,,,,
  CITY2,6:42:00,6:44:00,NADAV,3,,,,
  CITY2,6:49:00,6:51:00,NANAA,4,,,,
  CITY2,6:56:00,6:58:00,STAGECOACH,5,,,,
  AB1,8:00:00,8:00:00,BEATTY_AIRPORT,1,,,,
  AB1,8:10:00,8:15:00,BULLFROG,2,,,,
  AB2,12:05:00,12:05:00,BULLFROG,1,,,,
  AB2,12:15:00,12:15:00,BEATTY_AIRPORT,2,,,,
  BFC1,8:20:00,8:20:00,BULLFROG,1,,,,
  BFC1,9:20:00,9:20:00,FUR_CREEK_RES,2,,,,
  BFC2,11:00:00,11:00:00,FUR_CREEK_RES,1,,,,
  BFC2,12:00:00,12:00:00,BULLFROG,2,,,,
  AAMV1,8:00:00,8:00:00,BEATTY_AIRPORT,1,,,,
  AAMV1,9:00:00,9:00:00,AMV,2,,,,
  AAMV2,10:00:00,10:00:00,AMV,1,,,,
  AAMV2,11:00:00,11:00:00,BEATTY_AIRPORT,2,,,,
  AAMV3,13:00:00,13:00:00,BEATTY_AIRPORT,1,,,,
  AAMV3,14:00:00,14:00:00,AMV,2,,,,
  AAMV4,15:00:00,15:00:00,AMV,1,,,,
  AAMV4,16:00:00,16:00:00,BEATTY_AIRPORT,2,,,,
  stop_id,stop_name,stop_desc,stop_lat,stop_lon,zone_id,stop_url
  FUR_CREEK_RES,Furnace Creek Resort (Demo),,36.425288,-117.133162,,
  BEATTY_AIRPORT,Nye County Airport (Demo),,36.868446,-116.784582,,
  BULLFROG,Bullfrog (Demo),,36.88108,-116.81797,,
  STAGECOACH,Stagecoach Hotel & Casino (Demo),,36.915682,-116.751677,,
  NADAV,North Ave / D Ave N (Demo),,36.914893,-116.76821,,
  NANAA,North Ave / N A Ave (Demo),,36.914944,-116.761472,,
  DADAN,Doing Ave / D Ave N (Demo),,36.909489,-116.768242,,
  EMSI,E Main St / S Irving St (Demo),,36.905697,-116.76218,,
  AMV,Amargosa Valley (Demo),,36.641496,-116.40094,,
  route_id,service_id,trip_id,trip_headsign,direction_id,block_id,shape_id
  AB,FULLW,AB1,to Bullfrog,0,1,
  AB,FULLW,AB2,to Airport,1,2,
  STBA,FULLW,STBA,Shuttle,,,
  CITY,FULLW,CITY1,,0,,
  CITY,FULLW,CITY2,,1,,
  BFC,FULLW,BFC1,to Furnace Creek Resort,0,1,
  BFC,FULLW,BFC2,to Bullfrog,1,2,
  AAMV,WE,AAMV1,to Amargosa Valley,0,,
  AAMV,WE,AAMV2,to Airport,1,,
  AAMV,WE,AAMV3,to Amargosa Valley,0,,
  AAMV,WE,AAMV4,to Airport,1,,
  #!/usr/bin/python2.5
 
  # Test the examples to make sure they are not broken
 
  import os
  import re
  import transitfeed
  import unittest
  import urllib
  import util
 
  class WikiExample(util.TempDirTestCaseBase):
  # Download example from wiki and run it
  def runTest(self):
  wiki_source = urllib.urlopen(
  'http://googletransitdatafeed.googlecode.com/svn/wiki/TransitFeed.wiki'
  ).read()
  m = re.search(r'{{{(.*import transitfeed.*)}}}', wiki_source, re.DOTALL)
  if not m:
  raise Exception("Failed to find source code on wiki page")
  wiki_code = m.group(1)
  exec wiki_code
 
 
  class shuttle_from_xmlfeed(util.TempDirTestCaseBase):
  def runTest(self):
  self.CheckCallWithPath(
  [self.GetExamplePath('shuttle_from_xmlfeed.py'),
  '--input', 'file:' + self.GetExamplePath('shuttle_from_xmlfeed.xml'),
  '--output', 'shuttle-YYYYMMDD.zip',
  # save the path of the dated output to tempfilepath
  '--execute', 'echo %(path)s > outputpath'])
 
  dated_path = open('outputpath').read().strip()
  self.assertTrue(re.match(r'shuttle-20\d\d[01]\d[0123]\d.zip$', dated_path))
  if not os.path.exists(dated_path):
  raise Exception('did not create expected file')
 
 
  class table(util.TempDirTestCaseBase):
  def runTest(self):
  self.CheckCallWithPath(
  [self.GetExamplePath('table.py'),
  '--input', self.GetExamplePath('table.txt'),
  '--output', 'google_transit.zip'])
  if not os.path.exists('google_transit.zip'):
  raise Exception('should have created output')
 
 
  class small_builder(util.TempDirTestCaseBase):
  def runTest(self):
  self.CheckCallWithPath(
  [self.GetExamplePath('small_builder.py'),
  '--output', 'google_transit.zip'])
  if not os.path.exists('google_transit.zip'):
  raise Exception('should have created output')
 
 
  class google_random_queries(util.TempDirTestCaseBase):
  def testNormalRun(self):
  self.CheckCallWithPath(
  [self.GetExamplePath('google_random_queries.py'),
  '--output', 'queries.html',
  '--limit', '5',
  self.GetPath('test', 'data', 'good_feed')])
  if not os.path.exists('queries.html'):
  raise Exception('should have created output')
 
  def testInvalidFeedStillWorks(self):
  self.CheckCallWithPath(
  [self.GetExamplePath('google_random_queries.py'),
  '--output', 'queries.html',
  '--limit', '5',
  self.GetPath('test', 'data', 'invalid_route_agency')])
  if not os.path.exists('queries.html'):
  raise Exception('should have created output')
 
  def testBadArgs(self):
  self.CheckCallWithPath(
  [self.GetExamplePath('google_random_queries.py'),
  '--output', 'queries.html',
  '--limit', '5'],
  expected_retcode=2)
  if os.path.exists('queries.html'):
  raise Exception('should not have created output')
 
 
  class filter_unused_stops(util.TempDirTestCaseBase):
  def testNormalRun(self):
  unused_stop_path = self.GetPath('test', 'data', 'unused_stop')
  # Make sure load fails for input
  accumulator = transitfeed.ExceptionProblemAccumulator(raise_warnings=True)
  problem_reporter = transitfeed.ProblemReporter(accumulator)
  try:
  transitfeed.Loader(
  unused_stop_path,
  problems=problem_reporter, extra_validation=True).Load()
  self.fail('UnusedStop exception expected')
  except transitfeed.UnusedStop, e:
  pass
  (stdout, stderr) = self.CheckCallWithPath(
  [self.GetExamplePath('filter_unused_stops.py'),
  '--list_removed',
  unused_stop_path, 'output.zip'])
  # Extra stop was listed on stdout
  self.assertNotEqual(stdout.find('Bogus Stop'), -1)
  # Make sure unused stop was removed and another stop wasn't
  schedule = transitfeed.Loader(
  'output.zip', problems=problem_reporter, extra_validation=True).Load()
  schedule.GetStop('STAGECOACH')
 
 
  if __name__ == '__main__':
  unittest.main()
 
  #!/usr/bin/python2.5
 
  # Copyright (C) 2007 Google Inc.
  #
  # Licensed under the Apache License, Version 2.0 (the "License");
  # you may not use this file except in compliance with the License.
  # You may obtain a copy of the License at
  #
  # http://www.apache.org/licenses/LICENSE-2.0
  #
  # Unless required by applicable law or agreed to in writing, software
  # distributed under the License is distributed on an "AS IS" BASIS,
  # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  # See the License for the specific language governing permissions and
  # limitations under the License.
 
  # Smoke tests feed validator. Make sure it runs and returns the right things
  # for a valid feed and a feed with errors.
 
  import datetime
  import feedvalidator
  import os.path
  import re
  import StringIO
  import transitfeed
  import unittest
  from urllib2 import HTTPError, URLError
  import urllib2
  import util
  import zipfile
 
 
  class FullTests(util.TempDirTestCaseBase):
  def testGoodFeed(self):
  (out, err) = self.CheckCallWithPath(
  [self.GetPath('feedvalidator.py'), '-n', '--latest_version',
  transitfeed.__version__, self.GetPath('test', 'data', 'good_feed')])
  self.assertTrue(re.search(r'feed validated successfully', out))
  self.assertFalse(re.search(r'ERROR', out))
  htmlout = open('validation-results.html').read()
  self.assertTrue(re.search(r'feed validated successfully', htmlout))
  self.assertFalse(re.search(r'ERROR', htmlout))
  self.assertFalse(os.path.exists('transitfeedcrash.txt'))
 
  def testGoodFeedConsoleOutput(self):
  (out, err) = self.CheckCallWithPath(
  [self.GetPath('feedvalidator.py'), '-n', '--latest_version',
  transitfeed.__version__,
  '--output=CONSOLE', self.GetPath('test', 'data', 'good_feed')])
  self.assertTrue(re.search(r'feed validated successfully', out))
  self.assertFalse(re.search(r'ERROR', out))
  self.assertFalse(os.path.exists('validation-results.html'))
  self.assertFalse(os.path.exists('transitfeedcrash.txt'))
 
  def testMissingStops(self):
  (out, err) = self.CheckCallWithPath(
  [self.GetPath('feedvalidator.py'), '-n', '--latest_version',
  transitfeed.__version__,
  self.GetPath('test', 'data', 'missing_stops')],
  expected_retcode=1)
  self.assertTrue(re.search(r'ERROR', out))
  self.assertFalse(re.search(r'feed validated successfully', out))
  htmlout = open('validation-results.html').read()
  self.assertTrue(re.search(r'Invalid value BEATTY_AIRPORT', htmlout))
  self.assertFalse(re.search(r'feed validated successfully', htmlout))
  self.assertFalse(os.path.exists('transitfeedcrash.txt'))
 
  def testMissingStopsConsoleOutput(self):
  (out, err) = self.CheckCallWithPath(
  [self.GetPath('feedvalidator.py'), '-n', '-o', 'console',
  '--latest_version', transitfeed.__version__,
  self.GetPath('test', 'data', 'missing_stops')],
  expected_retcode=1)
  self.assertTrue(re.search(r'ERROR', out))
  self.assertFalse(re.search(r'feed validated successfully', out))
  self.assertTrue(re.search(r'Invalid value BEATTY_AIRPORT', out))
  self.assertFalse(os.path.exists('validation-results.html'))
  self.assertFalse(os.path.exists('transitfeedcrash.txt'))
 
  def testLimitedErrors(self):
  (out, err) = self.CheckCallWithPath(
  [self.GetPath('feedvalidator.py'), '-l', '2', '-n',
  '--latest_version', transitfeed.__version__,
  self.GetPath('test', 'data', 'missing_stops')],
  expected_retcode=1)
  self.assertTrue(re.search(r'ERROR', out))
  self.assertFalse(re.search(r'feed validated successfully', out))
  htmlout = open('validation-results.html').read()
  self.assertEquals(2, len(re.findall(r'class="problem">stop_id<', htmlout)))
  self.assertFalse(os.path.exists('transitfeedcrash.txt'))
 
  def testBadDateFormat(self):
  (out, err) = self.CheckCallWithPath(
  [self.GetPath('feedvalidator.py'), '-n', '--latest_version',
  transitfeed.__version__,
  self.GetPath('test', 'data', 'bad_date_format')],
  expected_retcode=1)
  self.assertTrue(re.search(r'ERROR', out))
  self.assertFalse(re.search(r'feed validated successfully', out))
  htmlout = open('validation-results.html').read()
  self.assertTrue(re.search(r'in field <code>start_date', htmlout))
  self.assertTrue(re.search(r'in field <code>date', htmlout))
  self.assertFalse(re.search(r'feed validated successfully', htmlout))
  self.assertFalse(os.path.exists('transitfeedcrash.txt'))
 
  def testBadUtf8(self):
  (out, err) = self.CheckCallWithPath(
  [self.GetPath('feedvalidator.py'), '-n', '--latest_version',
  transitfeed.__version__, self.GetPath('test', 'data', 'bad_utf8')],
  expected_retcode=1)
  self.assertTrue(re.search(r'ERROR', out))
  self.assertFalse(re.search(r'feed validated successfully', out))
  htmlout = open('validation-results.html').read()
  self.assertTrue(re.search(r'Unicode error', htmlout))
  self.assertFalse(re.search(r'feed validated successfully', htmlout))
  self.assertFalse(os.path.exists('transitfeedcrash.txt'))
 
  def testFileNotFound(self):
  (out, err) = self.CheckCallWithPath(
  [self.GetPath('feedvalidator.py'), '-n', '--latest_version',
  transitfeed.__version__, 'file-not-found.zip'],
  expected_retcode=1)
  self.assertFalse(os.path.exists('transitfeedcrash.txt'))
 
  def testBadOutputPath(self):
  (out, err) = self.CheckCallWithPath(
  [self.GetPath('feedvalidator.py'), '-n', '--latest_version',
  transitfeed.__version__, '-o', 'path/does/not/exist.html',
  self.GetPath('test', 'data', 'good_feed')],
  expected_retcode=2)
  self.assertFalse(os.path.exists('transitfeedcrash.txt'))
 
  def testCrashHandler(self):
  (out, err) = self.CheckCallWithPath(
  [self.GetPath('feedvalidator.py'), '-n', '--latest_version',
  transitfeed.__version__, 'IWantMyvalidation-crash.txt'],
  expected_retcode=127)
  self.assertTrue(re.search(r'Yikes', out))
  self.assertFalse(re.search(r'feed validated successfully', out))
  crashout = open('transitfeedcrash.txt').read()
  self.assertTrue(re.search(r'For testing the feed validator crash handler',
  crashout))
 
  def testCheckVersionIsRun(self):
  (out, err) = self.CheckCallWithPath(
  [self.GetPath('feedvalidator.py'), '-n', '--latest_version',
  '100.100.100', self.GetPath('test', 'data', 'good_feed')])
  self.assertTrue(re.search(r'feed validated successfully', out))
  self.assertTrue(re.search(r'A new version 100.100.100', out))
  htmlout = open('validation-results.html').read()
  self.assertTrue(re.search(r'A new version 100.100.100', htmlout))
  self.assertFalse(re.search(r'ERROR', htmlout))
  self.assertFalse(os.path.exists('transitfeedcrash.txt'))
 
  def testCheckVersionIsRunConsoleOutput(self):
  (out, err) = self.CheckCallWithPath(
  [self.GetPath('feedvalidator.py'), '-n', '-o', 'console',
  '--latest_version=100.100.100',
  self.GetPath('test', 'data', 'good_feed')])
  self.assertTrue(re.search(r'feed validated successfully', out))
  self.assertTrue(re.search(r'A new version 100.100.100', out))
  self.assertFalse(os.path.exists('validation-results.html'))
  self.assertFalse(os.path.exists('transitfeedcrash.txt'))
 
  def testUsage(self):
  (out, err) = self.CheckCallWithPath(
  [self.GetPath('feedvalidator.py'), '--invalid_opt'], expected_retcode=2)
  self.assertMatchesRegex(r'[Uu]sage: feedvalidator.py \[options\]', err)
  self.assertMatchesRegex(r'wiki/FeedValidator', err)
  self.assertMatchesRegex(r'--output', err) # output includes all usage info
  self.assertFalse(os.path.exists('transitfeedcrash.txt'))
  self.assertFalse(os.path.exists('validation-results.html'))
 
 
  # Regression tests to ensure that CalendarSummary works properly
  # even when the feed starts in the future or expires in less than
  # 60 days
  # See http://code.google.com/p/googletransitdatafeed/issues/detail?id=204
  class CalendarSummaryTestCase(util.TestCase):
 
  # Test feeds starting in the future
  def testFutureFeedDoesNotCrashCalendarSummary(self):
  today = datetime.date.today()
  start_date = today + datetime.timedelta(days=20)
  end_date = today + datetime.timedelta(days=80)
 
  schedule = transitfeed.Schedule()
  service_period = schedule.GetDefaultServicePeriod()
 
  service_period.SetStartDate(start_date.strftime("%Y%m%d"))
  service_period.SetEndDate(end_date.strftime("%Y%m%d"))
  service_period.SetWeekdayService(True)
 
  result = feedvalidator.CalendarSummary(schedule)
 
  self.assertEquals(0, result['max_trips'])
  self.assertEquals(0, result['min_trips'])
  self.assertTrue(re.search("40 service dates", result['max_trips_dates']))
 
  # Test feeds ending in less than 60 days
  def testShortFeedDoesNotCrashCalendarSummary(self):
  start_date = datetime.date.today()
  end_date = start_date + datetime.timedelta(days=15)
 
  schedule = transitfeed.Schedule()
  service_period = schedule.GetDefaultServicePeriod()
 
  service_period.SetStartDate(start_date.strftime("%Y%m%d"))
  service_period.SetEndDate(end_date.strftime("%Y%m%d"))
  service_period.SetWeekdayService(True)
 
  result = feedvalidator.CalendarSummary(schedule)
 
  self.assertEquals(0, result['max_trips'])
  self.assertEquals(0, result['min_trips'])
  self.assertTrue(re.search("15 service dates", result['max_trips_dates']))
 
  # Test feeds starting in the future *and* ending in less than 60 days
  def testFutureAndShortFeedDoesNotCrashCalendarSummary(self):
  today = datetime.date.today()
  start_date = today + datetime.timedelta(days=2)
  end_date = today + datetime.timedelta(days=3)
 
  schedule = transitfeed.Schedule()
  service_period = schedule.GetDefaultServicePeriod()
 
  service_period.SetStartDate(start_date.strftime("%Y%m%d"))
  service_period.SetEndDate(end_date.strftime("%Y%m%d"))
  service_period.SetWeekdayService(True)
 
  result = feedvalidator.CalendarSummary(schedule)
 
  self.assertEquals(0, result['max_trips'])
  self.assertEquals(0, result['min_trips'])
  self.assertTrue(re.search("1 service date", result['max_trips_dates']))
 
  # Test feeds without service days
  def testFeedWithNoDaysDoesNotCrashCalendarSummary(self):
  schedule = transitfeed.Schedule()
  result = feedvalidator.CalendarSummary(schedule)
 
  self.assertEquals({}, result)
 
 
  class MockOptions:
  """Pretend to be an optparse options object suitable for testing."""
  def __init__(self):
  self.limit_per_type = 5
  self.memory_db = True
  self.check_duplicate_trips = True
  self.latest_version = transitfeed.__version__
  self.output = 'fake-filename.zip'
  self.manual_entry = False
  self.service_gap_interval = None
  self.extension = None
 
 
  class FeedValidatorTestCase(util.TempDirTestCaseBase):
  def testBadEolContext(self):
  """Make sure the filename is included in the report of a bad eol."""
 
  filename = "routes.txt"
  old_zip = zipfile.ZipFile(
  self.GetPath('test', 'data', 'good_feed.zip'), 'r')
  content_dict = self.ConvertZipToDict(old_zip)
  old_routes = content_dict[filename]
  new_routes = old_routes.replace('\n', '\r\n', 1)
  self.assertNotEquals(old_routes, new_routes)
  content_dict[filename] = new_routes
  new_zipfile_mem = self.ConvertDictToZip(content_dict)
 
  options = MockOptions()
  output_file = StringIO.StringIO()
  feedvalidator.RunValidationOutputToFile(
  new_zipfile_mem, options, output_file)
  self.assertMatchesRegex(filename, output_file.getvalue())
 
 
  class LimitPerTypeProblemReporterTestCase(util.TestCase):
 
  def CreateLimitPerTypeProblemReporter(self, limit):
  accumulator = feedvalidator.LimitPerTypeProblemAccumulator(limit)
  problems = transitfeed.ProblemReporter(accumulator)
  return problems
 
  def assertProblemsAttribute(self, problem_type, class_name, attribute_name,
  expected):
  """Join the value of each exception's attribute_name in order."""
  problem_attribute_list = []
  for e in self.problems.GetAccumulator().ProblemList(
  problem_type, class_name).problems:
  problem_attribute_list.append(getattr(e, attribute_name))
  self.assertEquals(expected, " ".join(problem_attribute_list))
 
  def testLimitOtherProblems(self):
  """The first N of each type should be kept."""
  self.problems = self.CreateLimitPerTypeProblemReporter(2)
  self.accumulator = self.problems.GetAccumulator()
 
  self.problems.OtherProblem("e1", type=transitfeed.TYPE_ERROR)
  self.problems.OtherProblem("w1", type=transitfeed.TYPE_WARNING)
  self.problems.OtherProblem("e2", type=transitfeed.TYPE_ERROR)
  self.problems.OtherProblem("e3", type=transitfeed.TYPE_ERROR)
  self.problems.OtherProblem("w2", type=transitfeed.TYPE_WARNING)
  self.assertEquals(2, self.accumulator.WarningCount())
  self.assertEquals(3, self.accumulator.ErrorCount())
 
  # These are BoundedProblemList objects
  warning_bounded_list = self.accumulator.ProblemList(
  transitfeed.TYPE_WARNING, "OtherProblem")
  error_bounded_list = self.accumulator.ProblemList(
  transitfeed.TYPE_ERROR, "OtherProblem")
 
  self.assertEquals(2, warning_bounded_list.count)
  self.assertEquals(3, error_bounded_list.count)
 
  self.assertEquals(0, warning_bounded_list.dropped_count)
  self.assertEquals(1, error_bounded_list.dropped_count)
 
  self.assertProblemsAttribute(transitfeed.TYPE_ERROR, "OtherProblem",
  "description", "e1 e2")
  self.assertProblemsAttribute(transitfeed.TYPE_WARNING, "OtherProblem",
  "description", "w1 w2")
 
  def testKeepUnsorted(self):
  """An imperfect test that insort triggers ExceptionWithContext.__cmp__."""
  # If ExceptionWithContext.__cmp__ doesn't trigger TypeError in
  # bisect.insort then the default comparison of object id will be used. The
  # id values tend to be given out in order of creation so call
  # problems._Report with objects in a different order. This test should
  # break if ExceptionWithContext.__cmp__ is removed or changed to return 0
  # or cmp(id(self), id(y)).
  exceptions = []
  for i in range(20):
  exceptions.append(transitfeed.OtherProblem(description="e%i" % i))
  exceptions = exceptions[10:] + exceptions[:10]
  self.problems = self.CreateLimitPerTypeProblemReporter(3)
  self.accumulator = self.problems.GetAccumulator()
  for e in exceptions:
  self.problems.AddToAccumulator(e)
 
  self.assertEquals(0, self.accumulator.WarningCount())
  self.assertEquals(20, self.accumulator.ErrorCount())
 
  bounded_list = self.accumulator.ProblemList(
  transitfeed.TYPE_ERROR, "OtherProblem")
  self.assertEquals(20, bounded_list.count)
  self.assertEquals(17, bounded_list.dropped_count)
  self.assertProblemsAttribute(transitfeed.TYPE_ERROR, "OtherProblem",
  "description", "e10 e11 e12")
 
  def testLimitSortedTooFastTravel(self):
  """Sort by decreasing distance, keeping the N greatest."""
  self.problems = self.CreateLimitPerTypeProblemReporter(3)
  self.accumulator = self.problems.GetAccumulator()
  self.problems.TooFastTravel("t1", "prev stop", "next stop", 11230.4, 5,
  None)
  self.problems.TooFastTravel("t2", "prev stop", "next stop", 1120.4, 5, None)
  self.problems.TooFastTravel("t3", "prev stop", "next stop", 1130.4, 5, None)
  self.problems.TooFastTravel("t4", "prev stop", "next stop", 1230.4, 5, None)
  self.assertEquals(0, self.accumulator.WarningCount())
  self.assertEquals(4, self.accumulator.ErrorCount())
  self.assertProblemsAttribute(transitfeed.TYPE_ERROR, "TooFastTravel",
  "trip_id", "t1 t4 t3")
 
  def testLimitSortedStopTooFarFromParentStation(self):
  """Sort by decreasing distance, keeping the N greatest."""
  self.problems = self.CreateLimitPerTypeProblemReporter(3)
  self.accumulator = self.problems.GetAccumulator()
  for i, distance in enumerate((1000, 3002.0, 1500, 2434.1, 5023.21)):
  self.problems.StopTooFarFromParentStation(
  "s%d" % i, "S %d" % i, "p%d" % i, "P %d" % i, distance)
  self.assertEquals(5, self.accumulator.WarningCount())
  self.assertEquals(0, self.accumulator.ErrorCount())
  self.assertProblemsAttribute(transitfeed.TYPE_WARNING,
  "StopTooFarFromParentStation", "stop_id", "s4 s1 s3")
 
  def testLimitSortedStopsTooClose(self):
  """Sort by increasing distance, keeping the N closest."""
  self.problems = self.CreateLimitPerTypeProblemReporter(3)
  self.accumulator = self.problems.GetAccumulator()
  for i, distance in enumerate((4.0, 3.0, 2.5, 2.2, 1.0, 0.0)):
  self.problems.StopsTooClose(
  "Sa %d" % i, "sa%d" % i, "Sb %d" % i, "sb%d" % i, distance)
  self.assertEquals(6, self.accumulator.WarningCount())
  self.assertEquals(0, self.accumulator.ErrorCount())
  self.assertProblemsAttribute(transitfeed.TYPE_WARNING,
  "StopsTooClose", "stop_id_a", "sa5 sa4 sa3")
 
 
  class CheckVersionTestCase(util.TempDirTestCaseBase):
  def setUp(self):
  self.mock = MockURLOpen()
 
  def tearDown(self):
  self.mock = None
  feedvalidator.urlopen = urllib2.urlopen
 
  def testAssignedDifferentVersion(self):
  problems = feedvalidator.CheckVersion('100.100.100')
  self.assertTrue(re.search(r'A new version 100.100.100', problems))
 
  def testAssignedSameVersion(self):
  problems = feedvalidator.CheckVersion(transitfeed.__version__)
  self.assertEquals(problems, None)
 
  def testGetCorrectReturns(self):
  feedvalidator.urlopen = self.mock.mockedConnectSuccess
  problems = feedvalidator.CheckVersion()
  self.assertTrue(re.search(r'A new version 100.0.1', problems))
 
  def testPageNotFound(self):
  feedvalidator.urlopen = self.mock.mockedPageNotFound
  problems = feedvalidator.CheckVersion()
  self.assertTrue(re.search(r'The server couldn\'t', problems))
  self.assertTrue(re.search(r'Error code: 404', problems))
 
  def testConnectionTimeOut(self):
  feedvalidator.urlopen = self.mock.mockedConnectionTimeOut
  problems = feedvalidator.CheckVersion()
  self.assertTrue(re.search(r'We failed to reach', problems))
  self.assertTrue(re.search(r'Reason: Connection timed', problems))
 
  def testGetAddrInfoFailed(self):
  feedvalidator.urlopen = self.mock.mockedGetAddrInfoFailed
  problems = feedvalidator.CheckVersion()
  self.assertTrue(re.search(r'We failed to reach', problems))
  self.assertTrue(re.search(r'Reason: Getaddrinfo failed', problems))
 
  def testEmptyIsReturned(self):
  feedvalidator.urlopen = self.mock.mockedEmptyIsReturned
  problems = feedvalidator.CheckVersion()
  self.assertTrue(re.search(r'We had trouble parsing', problems))
 
 
  class MockURLOpen:
  """Pretend to be a urllib2.urlopen suitable for testing."""
  def mockedConnectSuccess(self, request):
  return StringIO.StringIO('<li><a href="transitfeed-1.0.0/">transitfeed-'
  '1.0.0/</a></li><li><a href=transitfeed-100.0.1/>'
  'transitfeed-100.0.1/</a></li>')
 
  def mockedPageNotFound(self, request):
  raise HTTPError(request.get_full_url(), 404, 'Not Found',
  request.header_items(), None)
 
  def mockedConnectionTimeOut(self, request):
  raise URLError('Connection timed out')
 
  def mockedGetAddrInfoFailed(self, request):
  raise URLError('Getaddrinfo failed')
 
  def mockedEmptyIsReturned(self, request):
  return StringIO.StringIO()
 
 
  if __name__ == '__main__':
  unittest.main()
 
  #!/usr/bin/python2.5
 
  # Copyright (C) 2007 Google Inc.
  #
  # Licensed under the Apache License, Version 2.0 (the "License");
  # you may not use this file except in compliance with the License.
  # You may obtain a copy of the License at
  #
  # http://www.apache.org/licenses/LICENSE-2.0
  #
  # Unless required by applicable law or agreed to in writing, software
  # distributed under the License is distributed on an "AS IS" BASIS,
  # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  # See the License for the specific language governing permissions and
  # limitations under the License.
 
  # Unit tests for the kmlparser module.
 
  import kmlparser
  import os.path
  import shutil
  from StringIO import StringIO
  import transitfeed
  import unittest
  import util
 
 
  class TestStopsParsing(util.GetPathTestCase):
  def testSingleStop(self):
  feed = transitfeed.Schedule()
  kmlFile = self.GetTestDataPath('one_stop.kml')
  kmlparser.KmlParser().Parse(kmlFile, feed)
  stops = feed.GetStopList()
  self.assertEqual(1, len(stops))
  stop = stops[0]
  self.assertEqual(u'Stop Name', stop.stop_name)
  self.assertAlmostEqual(-93.239037, stop.stop_lon)
  self.assertAlmostEqual(44.854164, stop.stop_lat)
  write_output = StringIO()
  feed.WriteGoogleTransitFeed(write_output)
 
  def testSingleShape(self):
  feed = transitfeed.Schedule()
  kmlFile = self.GetTestDataPath('one_line.kml')
  kmlparser.KmlParser().Parse(kmlFile, feed)
  shapes = feed.GetShapeList()
  self.assertEqual(1, len(shapes))
  shape = shapes[0]
  self.assertEqual(3, len(shape.points))
  self.assertAlmostEqual(44.854240, shape.points[0][0])
  self.assertAlmostEqual(-93.238861, shape.points[0][1])
  self.assertAlmostEqual(44.853081, shape.points[1][0])
  self.assertAlmostEqual(-93.238708, shape.points[1][1])
  self.assertAlmostEqual(44.852638, shape.points[2][0])
  self.assertAlmostEqual(-93.237923, shape.points[2][1])
  write_output = StringIO()
  feed.WriteGoogleTransitFeed(write_output)
 
 
  class FullTests(util.TempDirTestCaseBase):
  def testNormalRun(self):
  shutil.copyfile(self.GetTestDataPath('one_stop.kml'), 'one_stop.kml')
  (out, err) = self.CheckCallWithPath(
  [self.GetPath('kmlparser.py'), 'one_stop.kml', 'one_stop.zip'])
  # There will be lots of problems, but ignore them
  accumulator = util.RecordingProblemAccumulator(self)
  problems = transitfeed.ProblemReporter(accumulator)
  schedule = transitfeed.Loader('one_stop.zip', problems=problems).Load()
  self.assertEquals(len(schedule.GetStopList()), 1)
  self.assertFalse(os.path.exists('transitfeedcrash.txt'))
 
  def testCommandLineError(self):
  (out, err) = self.CheckCallWithPath([self.GetPath('kmlparser.py')],
  expected_retcode=2)
  self.assertMatchesRegex(r'did not provide .+ arguments', err)
  self.assertMatchesRegex(r'[Uu]sage:', err)
  self.assertFalse(os.path.exists('transitfeedcrash.txt'))
 
  def testCrashHandler(self):
  (out, err) = self.CheckCallWithPath(
  [self.GetPath('kmlparser.py'), 'IWantMyCrash', 'output.zip'],
  stdin_str="\n", expected_retcode=127)
  self.assertMatchesRegex(r'Yikes', out)
  crashout = open('transitfeedcrash.txt').read()
  self.assertMatchesRegex(r'For testCrashHandler', crashout)
 
 
  if __name__ == '__main__':
  unittest.main()
 
  #!/usr/bin/python2.4
  #
  # Copyright 2008 Google Inc. All Rights Reserved.
  #
  # Licensed under the Apache License, Version 2.0 (the "License");
  # you may not use this file except in compliance with the License.
  # You may obtain a copy of the License at
  #
  # http://www.apache.org/licenses/LICENSE-2.0
  #
  # Unless required by applicable law or agreed to in writing, software
  # distributed under the License is distributed on an "AS IS" BASIS,
  # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  # See the License for the specific language governing permissions and
  # limitations under the License.
 
  """Unit tests for the kmlwriter module."""
 
  import os
  import StringIO
  import tempfile
  import unittest
  import kmlparser
  import kmlwriter
  import transitfeed
  import util
 
  try:
  import xml.etree.ElementTree as ET # python 2.5
  except ImportError, e:
  import elementtree.ElementTree as ET # older pythons
 
 
  def DataPath(path):
  """Return the path to a given file in the test data directory.
 
  Args:
  path: The path relative to the test data directory.
 
  Returns:
  The absolute path.
  """
  here = os.path.dirname(__file__)
  return os.path.join(here, 'data', path)
 
 
  def _ElementToString(root):
  """Returns the node as an XML string.
 
  Args:
  root: The ElementTree.Element instance.
 
  Returns:
  The XML string.
  """
  output = StringIO.StringIO()
  ET.ElementTree(root).write(output, 'utf-8')
  return output.getvalue()
 
 
  class TestKMLStopsRoundtrip(util.TestCase):
  """Checks to see whether all stops are preserved when going to and from KML.
  """
 
  def setUp(self):
  fd, self.kml_output = tempfile.mkstemp('kml')
  os.close(fd)
 
  def tearDown(self):
  os.remove(self.kml_output)
 
  def runTest(self):
  gtfs_input = DataPath('good_feed.zip')
  feed1 = transitfeed.Loader(gtfs_input).Load()
  kmlwriter.KMLWriter().Write(feed1, self.kml_output)
  feed2 = transitfeed.Schedule()
  kmlparser.KmlParser().Parse(self.kml_output, feed2)
 
  stop_name_mapper = lambda x: x.stop_name
 
  stops1 = set(map(stop_name_mapper, feed1.GetStopList()))
  stops2 = set(map(stop_name_mapper, feed2.GetStopList()))
 
  self.assertEqual(stops1, stops2)
 
 
  class TestKMLGeneratorMethods(util.TestCase):
  """Tests the various KML element creation methods of KMLWriter."""
 
  def setUp(self):
  self.kmlwriter = kmlwriter.KMLWriter()
  self.parent = ET.Element('parent')
 
  def testCreateFolderVisible(self):
  element = self.kmlwriter._CreateFolder(self.parent, 'folder_name')
  self.assertEqual(_ElementToString(element),
  '<Folder><name>folder_name</name></Folder>')
 
  def testCreateFolderNotVisible(self):
  element = self.kmlwriter._CreateFolder(self.parent, 'folder_name',
  visible=False)
  self.assertEqual(_ElementToString(element),
  '<Folder><name>folder_name</name>'
  '<visibility>0</visibility></Folder>')
 
  def testCreateFolderWithDescription(self):
  element = self.kmlwriter._CreateFolder(self.parent, 'folder_name',
  description='folder_desc')
  self.assertEqual(_ElementToString(element),
  '<Folder><name>folder_name</name>'
  '<description>folder_desc</description></Folder>')
 
  def testCreatePlacemark(self):
  element = self.kmlwriter._CreatePlacemark(self.parent, 'abcdef')
  self.assertEqual(_ElementToString(element),
  '<Placemark><name>abcdef</name></Placemark>')
 
  def testCreatePlacemarkWithStyle(self):
  element = self.kmlwriter._CreatePlacemark(self.parent, 'abcdef',
  style_id='ghijkl')
  self.assertEqual(_ElementToString(element),
  '<Placemark><name>abcdef</name>'
  '<styleUrl>#ghijkl</styleUrl></Placemark>')
 
  def testCreatePlacemarkNotVisible(self):
  element = self.kmlwriter._CreatePlacemark(self.parent, 'abcdef',
  visible=False)
  self.assertEqual(_ElementToString(element),
  '<Placemark><name>abcdef</name>'
  '<visibility>0</visibility></Placemark>')
 
  def testCreatePlacemarkWithDescription(self):
  element = self.kmlwriter._CreatePlacemark(self.parent, 'abcdef',
  description='ghijkl')
  self.assertEqual(_ElementToString(element),
  '<Placemark><name>abcdef</name>'
  '<description>ghijkl</description></Placemark>')
 
  def testCreateLineString(self):
  coord_list = [(2.0, 1.0), (4.0, 3.0), (6.0, 5.0)]
  element = self.kmlwriter._CreateLineString(self.parent, coord_list)
  self.assertEqual(_ElementToString(element),
  '<LineString><tessellate>1</tessellate>'
  '<coordinates>%f,%f %f,%f %f,%f</coordinates>'
  '</LineString>' % (2.0, 1.0, 4.0, 3.0, 6.0, 5.0))
 
  def testCreateLineStringWithAltitude(self):
  coord_list = [(2.0, 1.0, 10), (4.0, 3.0, 20), (6.0, 5.0, 30.0)]
  element = self.kmlwriter._CreateLineString(self.parent, coord_list)
  self.assertEqual(_ElementToString(element),
  '<LineString><tessellate>1</tessellate>'
  '<altitudeMode>absolute</altitudeMode>'
  '<coordinates>%f,%f,%f %f,%f,%f %f,%f,%f</coordinates>'
  '</LineString>' %
  (2.0, 1.0, 10.0, 4.0, 3.0, 20.0, 6.0, 5.0, 30.0))
 
  def testCreateLineStringForShape(self):
  shape = transitfeed.Shape('shape')
  shape.AddPoint(1.0, 1.0)
  shape.AddPoint(2.0, 4.0)
  shape.AddPoint(3.0, 9.0)
  element = self.kmlwriter._CreateLineStringForShape(self.parent, shape)
  self.assertEqual(_ElementToString(element),
  '<LineString><tessellate>1</tessellate>'
  '<coordinates>%f,%f %f,%f %f,%f</coordinates>'
  '</LineString>' % (1.0, 1.0, 4.0, 2.0, 9.0, 3.0))
 
 
  class TestRouteKML(util.TestCase):
  """Tests the routes folder KML generation methods of KMLWriter."""
 
  def setUp(self):
  self.feed = transitfeed.Loader(DataPath('flatten_feed')).Load()
  self.kmlwriter = kmlwriter.KMLWriter()
  self.parent = ET.Element('parent')
 
  def testCreateRoutePatternsFolderNoPatterns(self):
  folder = self.kmlwriter._CreateRoutePatternsFolder(
  self.parent, self.feed.GetRoute('route_7'))
  self.assert_(folder is None)
 
  def testCreateRoutePatternsFolderOnePattern(self):
  folder = self.kmlwriter._CreateRoutePatternsFolder(
  self.parent, self.feed.GetRoute('route_1'))
  placemarks = folder.findall('Placemark')
  self.assertEquals(len(placemarks), 1)
 
  def testCreateRoutePatternsFolderTwoPatterns(self):
  folder = self.kmlwriter._CreateRoutePatternsFolder(
  self.parent, self.feed.GetRoute('route_3'))
  placemarks = folder.findall('Placemark')
  self.assertEquals(len(placemarks), 2)
 
  def testCreateRoutePatternFolderTwoEqualPatterns(self):
  folder = self.kmlwriter._CreateRoutePatternsFolder(
  self.parent, self.feed.GetRoute('route_4'))
  placemarks = folder.findall('Placemark')
  self.assertEquals(len(placemarks), 1)
 
  def testCreateRouteShapesFolderOneTripOneShape(self):
  folder = self.kmlwriter._CreateRouteShapesFolder(
  self.feed, self.parent, self.feed.GetRoute('route_1'))
  self.assertEqual(len(folder.findall('Placemark')), 1)
 
  def testCreateRouteShapesFolderTwoTripsTwoShapes(self):
  folder = self.kmlwriter._CreateRouteShapesFolder(
  self.feed, self.parent, self.feed.GetRoute('route_2'))
  self.assertEqual(len(folder.findall('Placemark')), 2)
 
  def testCreateRouteShapesFolderTwoTripsOneShape(self):
  folder = self.kmlwriter._CreateRouteShapesFolder(
  self.feed, self.parent, self.feed.GetRoute('route_3'))
  self.assertEqual(len(folder.findall('Placemark')), 1)
 
  def testCreateRouteShapesFolderTwoTripsNoShapes(self):
  folder = self.kmlwriter._CreateRouteShapesFolder(
  self.feed, self.parent, self.feed.GetRoute('route_4'))
  self.assert_(folder is None)
 
  def assertRouteFolderContainsTrips(self, tripids, folder):
  """Assert that the route folder contains exactly tripids"""
  actual_tripds = set()
  for placemark in folder.findall('Placemark'):
  actual_tripds.add(placemark.find('name').text)
  self.assertEquals(set(tripids), actual_tripds)
 
  def testCreateTripsFolderForRouteTwoTrips(self):
  route = self.feed.GetRoute('route_2')
  folder = self.kmlwriter._CreateRouteTripsFolder(self.parent, route)
  self.assertRouteFolderContainsTrips(['route_2_1', 'route_2_2'], folder)
 
  def testCreateTripsFolderForRouteDateFilterNone(self):
  self.kmlwriter.date_filter = None
  route = self.feed.GetRoute('route_8')
  folder = self.kmlwriter._CreateRouteTripsFolder(self.parent, route)
  self.assertRouteFolderContainsTrips(['route_8_1', 'route_8_2'], folder)
 
  def testCreateTripsFolderForRouteDateFilterSet(self):
  self.kmlwriter.date_filter = '20070604'
  route = self.feed.GetRoute('route_8')
  folder = self.kmlwriter._CreateRouteTripsFolder(self.parent, route)
  self.assertRouteFolderContainsTrips(['route_8_2'], folder)
 
  def _GetTripPlacemark(self, route_folder, trip_name):
  for trip_placemark in route_folder.findall('Placemark'):
  if trip_placemark.find('name').text == trip_name:
  return trip_placemark
 
  def testCreateRouteTripsFolderAltitude0(self):
  self.kmlwriter.altitude_per_sec = 0.0
  folder = self.kmlwriter._CreateRouteTripsFolder(
  self.parent, self.feed.GetRoute('route_4'))
  trip_placemark = self._GetTripPlacemark(folder, 'route_4_1')
  self.assertEqual(_ElementToString(trip_placemark.find('LineString')),
  '<LineString><tessellate>1</tessellate>'
  '<coordinates>-117.133162,36.425288 '
  '-116.784582,36.868446 '
  '-116.817970,36.881080</coordinates></LineString>')
 
  def testCreateRouteTripsFolderAltitude1(self):
  self.kmlwriter.altitude_per_sec = 0.5
  folder = self.kmlwriter._CreateRouteTripsFolder(
  self.parent, self.feed.GetRoute('route_4'))
  trip_placemark = self._GetTripPlacemark(folder, 'route_4_1')
  self.assertEqual(_ElementToString(trip_placemark.find('LineString')),
  '<LineString><tessellate>1</tessellate>'
  '<altitudeMode>absolute</altitudeMode>'
  '<coordinates>-117.133162,36.425288,3600.000000 '
  '-116.784582,36.868446,5400.000000 '
  '-116.817970,36.881080,7200.000000</coordinates>'
  '</LineString>')
 
  def testCreateRouteTripsFolderNoTrips(self):
  folder = self.kmlwriter._CreateRouteTripsFolder(
  self.parent, self.feed.GetRoute('route_7'))
  self.assert_(folder is None)
 
  def testCreateRoutesFolderNoRoutes(self):
  schedule = transitfeed.Schedule()
  folder = self.kmlwriter._CreateRoutesFolder(schedule, self.parent)
  self.assert_(folder is None)
 
  def testCreateRoutesFolderNoRoutesWithRouteType(self):
  folder = self.kmlwriter._CreateRoutesFolder(self.feed, self.parent, 999)
  self.assert_(folder is None)
 
  def _TestCreateRoutesFolder(self, show_trips):
  self.kmlwriter.show_trips = show_trips
  folder = self.kmlwriter._CreateRoutesFolder(self.feed, self.parent)
  self.assertEquals(folder.tag, 'Folder')
  styles = self.parent.findall('Style')
  self.assertEquals(len(styles), len(self.feed.GetRouteList()))
  route_folders = folder.findall('Folder')
  self.assertEquals(len(route_folders), len(self.feed.GetRouteList()))
 
  def testCreateRoutesFolder(self):
  self._TestCreateRoutesFolder(False)
 
  def testCreateRoutesFolderShowTrips(self):
  self._TestCreateRoutesFolder(True)
 
  def testCreateRoutesFolderWithRouteType(self):
  folder = self.kmlwriter._CreateRoutesFolder(self.feed, self.parent, 1)
  route_folders = folder.findall('Folder')
  self.assertEquals(len(route_folders), 1)
 
 
  class TestShapesKML(util.TestCase):
  """Tests the shapes folder KML generation methods of KMLWriter."""
 
  def setUp(self):
  self.flatten_feed = transitfeed.Loader(DataPath('flatten_feed')).Load()
  self.good_feed = transitfeed.Loader(DataPath('good_feed.zip')).Load()
  self.kmlwriter = kmlwriter.KMLWriter()
  self.parent = ET.Element('parent')
 
  def testCreateShapesFolderNoShapes(self):
  folder = self.kmlwriter._CreateShapesFolder(self.good_feed, self.parent)
  self.assertEquals(folder, None)
 
  def testCreateShapesFolder(self):
  folder = self.kmlwriter._CreateShapesFolder(self.flatten_feed, self.parent)
  placemarks = folder.findall('Placemark')
  self.assertEquals(len(placemarks), 3)
  for placemark in placemarks:
  self.assert_(placemark.find('LineString') is not None)
 
 
  class TestStopsKML(util.TestCase):
  """Tests the stops folder KML generation methods of KMLWriter."""
 
  def setUp(self):
  self.feed = transitfeed.Loader(DataPath('flatten_feed')).Load()
  self.kmlwriter = kmlwriter.KMLWriter()
  self.parent = ET.Element('parent')
 
  def testCreateStopsFolderNoStops(self):
  schedule = transitfeed.Schedule()
  folder = self.kmlwriter._CreateStopsFolder(schedule, self.parent)
  self.assert_(folder is None)
 
  def testCreateStopsFolder(self):
  folder = self.kmlwriter._CreateStopsFolder(self.feed, self.parent)
  placemarks = folder.findall('Placemark')
  self.assertEquals(len(placemarks), len(self.feed.GetStopList()))
 
 
  class TestShapePointsKML(util.TestCase):
  """Tests the shape points folder KML generation methods of KMLWriter."""
 
  def setUp(self):
  self.flatten_feed = transitfeed.Loader(DataPath('flatten_feed')).Load()
  self.kmlwriter = kmlwriter.KMLWriter()
  self.kmlwriter.shape_points = True
  self.parent = ET.Element('parent')
 
  def testCreateShapePointsFolder(self):
  folder = self.kmlwriter._CreateShapesFolder(self.flatten_feed, self.parent)
  shape_point_folder = folder.find('Folder')
  self.assertEquals(shape_point_folder.find('name').text,
  'shape_1 Shape Points')
  placemarks = shape_point_folder.findall('Placemark')
  self.assertEquals(len(placemarks), 4)
  for placemark in placemarks:
  self.assert_(placemark.find('Point') is not None)
 
 
  class FullTests(util.TempDirTestCaseBase):
  def testNormalRun(self):
  (out, err) = self.CheckCallWithPath(
  [self.GetPath('kmlwriter.py'), self.GetTestDataPath('good_feed.zip'),
  'good_feed.kml'])
  self.assertFalse(os.path.exists('transitfeedcrash.txt'))
  self.assertTrue(os.path.exists('good_feed.kml'))
 
  def testCommandLineError(self):
  (out, err) = self.CheckCallWithPath(
  [self.GetPath('kmlwriter.py'), '--bad_flag'], expected_retcode=2)
  self.assertMatchesRegex(r'no such option.*--bad_flag', err)
  self.assertMatchesRegex(r'--showtrips', err)
  self.assertFalse(os.path.exists('transitfeedcrash.txt'))
 
  def testCrashHandler(self):
  (out, err) = self.CheckCallWithPath(
  [self.GetPath('kmlwriter.py'), 'IWantMyCrash', 'output.zip'],
  stdin_str="\n", expected_retcode=127)
  self.assertMatchesRegex(r'Yikes', out)
  crashout = open('transitfeedcrash.txt').read()
  self.assertMatchesRegex(r'For testCrashHandler', crashout)
 
 
  if __name__ == '__main__':
  unittest.main()
 
  #!/usr/bin/python2.4
  #
  # Copyright 2007 Google Inc. All Rights Reserved.
  #
  # Licensed under the Apache License, Version 2.0 (the "License");
  # you may not use this file except in compliance with the License.
  # You may obtain a copy of the License at
  #
  # http://www.apache.org/licenses/LICENSE-2.0
  #
  # Unless required by applicable law or agreed to in writing, software
  # distributed under the License is distributed on an "AS IS" BASIS,
  # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  # See the License for the specific language governing permissions and
  # limitations under the License.
 
  """Unit tests for the merge module."""
 
 
  __author__ = 'timothy.stranex@gmail.com (Timothy Stranex)'
 
 
  import merge
  import os.path
  import re
  import StringIO
  import transitfeed
  import unittest
  import util
  import zipfile
 
 
  def CheckAttribs(a, b, attrs, assertEquals):
  """Checks that the objects a and b have the same values for the attributes
  given in attrs. These checks are done using the given assert function.
 
  Args:
  a: The first object.
  b: The second object.
  attrs: The list of attribute names (strings).
  assertEquals: The assertEquals method from unittest.TestCase.
  """
  # For Stop objects (and maybe others in the future) Validate converts some
  # attributes from string to native type
  a.Validate()
  b.Validate()
  for k in attrs:
  assertEquals(getattr(a, k), getattr(b, k))
 
 
  def CreateAgency():
  """Create an transitfeed.Agency object for testing.
 
  Returns:
  The agency object.
  """
  return transitfeed.Agency(name='agency',
  url='http://agency',
  timezone='Africa/Johannesburg',
  id='agency')
 
 
  class TestingProblemReporter(merge.MergeProblemReporter):
  def __init__(self, accumulator):
  merge.MergeProblemReporter.__init__(self, accumulator)
 
 
  class TestingProblemAccumulator(transitfeed.ProblemAccumulatorInterface):
  """This problem reporter keeps track of all problems.
 
  Attributes:
  problems: The list of problems reported.
  """
 
  def __init__(self):
  self.problems = []
  self._expect_classes = []
 
  def _Report(self, problem):
  problem.FormatProblem() # Shouldn't crash
  self.problems.append(problem)
  for problem_class in self._expect_classes:
  if isinstance(problem, problem_class):
  return
  raise problem
 
  def CheckReported(self, problem_class):
  """Checks if a problem of the given class was reported.
 
  Args:
  problem_class: The problem class, a class inheriting from
  MergeProblemWithContext.
 
  Returns:
  True if a matching problem was reported.
  """
  for problem in self.problems:
  if isinstance(problem, problem_class):
  return True
  return False
 
  def ExpectProblemClass(self, problem_class):
  """Supresses exception raising for problems inheriting from this class.
 
  Args:
  problem_class: The problem class, a class inheriting from
  MergeProblemWithContext.
  """
  self._expect_classes.append(problem_class)
 
  def assertExpectedProblemsReported(self, testcase):
  """Asserts that every expected problem class has been reported.
 
  The assertions are done using the assert_ method of the testcase.
 
  Args:
  testcase: The unittest.TestCase instance.
  """
  for problem_class in self._expect_classes:
  testcase.assert_(self.CheckReported(problem_class))
 
 
  class TestApproximateDistanceBetweenPoints(util.TestCase):
 
  def _assertWithinEpsilon(self, a, b, epsilon=1.0):
  """Asserts that a and b are equal to within an epsilon.
 
  Args:
  a: The first value (float).
  b: The second value (float).
  epsilon: The epsilon value (float).
  """
  self.assert_(abs(a-b) < epsilon)
 
  def testDegenerate(self):
  p = (30.0, 30.0)
  self._assertWithinEpsilon(
  merge.ApproximateDistanceBetweenPoints(p, p), 0.0)
 
  def testFar(self):
  p1 = (30.0, 30.0)
  p2 = (40.0, 40.0)
  self.assert_(merge.ApproximateDistanceBetweenPoints(p1, p2) > 1e4)
 
 
  class TestSchemedMerge(util.TestCase):
 
  class TestEntity:
  """A mock entity (like Route or Stop) for testing."""
 
  def __init__(self, x, y, z):
  self.x = x
  self.y = y
  self.z = z
 
  def setUp(self):
  a_schedule = transitfeed.Schedule()
  b_schedule = transitfeed.Schedule()
  merged_schedule = transitfeed.Schedule()
  accumulator = TestingProblemAccumulator()
  self.fm = merge.FeedMerger(a_schedule, b_schedule,
  merged_schedule,
  TestingProblemReporter(accumulator))
  self.ds = merge.DataSetMerger(self.fm)
 
  def Migrate(ent, sched, newid):
  """A migration function for the mock entity."""
  return self.TestEntity(ent.x, ent.y, ent.z)
  self.ds._Migrate = Migrate
 
  def testMergeIdentical(self):
  class TestAttrib:
  """An object that is equal to everything."""
 
  def __cmp__(self, b):
  return 0
 
  x = 99
  a = TestAttrib()
  b = TestAttrib()
 
  self.assert_(self.ds._MergeIdentical(x, x) == x)
  self.assert_(self.ds._MergeIdentical(a, b) is b)
  self.assertRaises(merge.MergeError, self.ds._MergeIdentical, 1, 2)
 
  def testMergeIdenticalCaseInsensitive(self):
  self.assert_(self.ds._MergeIdenticalCaseInsensitive('abc', 'ABC') == 'ABC')
  self.assert_(self.ds._MergeIdenticalCaseInsensitive('abc', 'AbC') == 'AbC')
  self.assertRaises(merge.MergeError,
  self.ds._MergeIdenticalCaseInsensitive, 'abc', 'bcd')
  self.assertRaises(merge.MergeError,
  self.ds._MergeIdenticalCaseInsensitive, 'abc', 'ABCD')
 
  def testMergeOptional(self):
  x = 99
  y = 100
 
  self.assertEquals(self.ds._MergeOptional(None, None), None)
  self.assertEquals(self.ds._MergeOptional(None, x), x)
  self.assertEquals(self.ds._MergeOptional(x, None), x)
  self.assertEquals(self.ds._MergeOptional(x, x), x)
  self.assertRaises(merge.MergeError, self.ds._MergeOptional, x, y)
 
  def testMergeSameAgency(self):
  kwargs = {'name': 'xxx',
  'agency_url': 'http://www.example.com',
  'agency_timezone': 'Europe/Zurich'}
  id1 = 'agency1'
  id2 = 'agency2'
  id3 = 'agency3'
  id4 = 'agency4'
  id5 = 'agency5'
 
  a = self.fm.a_schedule.NewDefaultAgency(id=id1, **kwargs)
  b = self.fm.b_schedule.NewDefaultAgency(id=id2, **kwargs)
  c = transitfeed.Agency(id=id3, **kwargs)
  self.fm.merged_schedule.AddAgencyObject(c)
  self.fm.Register(a, b, c)
 
  d = transitfeed.Agency(id=id4, **kwargs)
  e = transitfeed.Agency(id=id5, **kwargs)
  self.fm.a_schedule.AddAgencyObject(d)
  self.fm.merged_schedule.AddAgencyObject(e)
  self.fm.Register(d, None, e)
 
  self.assertEquals(self.ds._MergeSameAgency(id1, id2), id3)
  self.assertEquals(self.ds._MergeSameAgency(None, None), id3)
  self.assertEquals(self.ds._MergeSameAgency(id1, None), id3)
  self.assertEquals(self.ds._MergeSameAgency(None, id2), id3)
 
  # id1 is not a valid agency_id in the new schedule so it cannot be merged
  self.assertRaises(KeyError, self.ds._MergeSameAgency, id1, id1)
 
  # this fails because d (id4) and b (id2) don't map to the same agency
  # in the merged schedule
  self.assertRaises(merge.MergeError, self.ds._MergeSameAgency, id4, id2)
 
  def testSchemedMerge_Success(self):
 
  def Merger(a, b):
  return a + b
 
  scheme = {'x': Merger, 'y': Merger, 'z': Merger}
  a = self.TestEntity(1, 2, 3)
  b = self.TestEntity(4, 5, 6)
  c = self.ds._SchemedMerge(scheme, a, b)
 
  self.assertEquals(c.x, 5)
  self.assertEquals(c.y, 7)
  self.assertEquals(c.z, 9)
 
  def testSchemedMerge_Failure(self):
 
  def Merger(a, b):
  raise merge.MergeError()
 
  scheme = {'x': Merger, 'y': Merger, 'z': Merger}
  a = self.TestEntity(1, 2, 3)
  b = self.TestEntity(4, 5, 6)
 
  self.assertRaises(merge.MergeError, self.ds._SchemedMerge,
  scheme, a, b)
 
  def testSchemedMerge_NoNewId(self):
  class TestDataSetMerger(merge.DataSetMerger):
  def _Migrate(self, entity, schedule, newid):
  self.newid = newid
  return entity
  dataset_merger = TestDataSetMerger(self.fm)
  a = self.TestEntity(1, 2, 3)
  b = self.TestEntity(4, 5, 6)
  dataset_merger._SchemedMerge({}, a, b)
  self.assertEquals(dataset_merger.newid, False)
 
  def testSchemedMerge_ErrorTextContainsAttributeNameAndReason(self):
  reason = 'my reason'
  attribute_name = 'long_attribute_name'
 
  def GoodMerger(a, b):
  return a + b
 
  def BadMerger(a, b):
  raise merge.MergeError(reason)
 
  a = self.TestEntity(1, 2, 3)
  setattr(a, attribute_name, 1)
  b = self.TestEntity(4, 5, 6)
  setattr(b, attribute_name, 2)
  scheme = {'x': GoodMerger, 'y': GoodMerger, 'z': GoodMerger,
  attribute_name: BadMerger}
 
  try:
  self.ds._SchemedMerge(scheme, a, b)
  except merge.MergeError, merge_error:
  error_text = str(merge_error)
  self.assert_(reason in error_text)
  self.assert_(attribute_name in error_text)
 
 
  class TestFeedMerger(util.TestCase):
 
  class Merger:
  def __init__(self, test, n, should_fail=False):
  self.test = test
  self.n = n
  self.should_fail = should_fail
 
  def MergeDataSets(self):
  self.test.called.append(self.n)
  return not self.should_fail
 
  def setUp(self):
  a_schedule = transitfeed.Schedule()
  b_schedule = transitfeed.Schedule()
  merged_schedule = transitfeed.Schedule()
  accumulator = TestingProblemAccumulator()
  self.fm = merge.FeedMerger(a_schedule, b_schedule,
  merged_schedule,
  TestingProblemReporter(accumulator))
  self.called = []
 
  def testSequence(self):
  for i in range(10):
  self.fm.AddMerger(TestFeedMerger.Merger(self, i))
  self.assert_(self.fm.MergeSchedules())
  self.assertEquals(self.called, range(10))
 
  def testStopsAfterError(self):
  for i in range(10):
  self.fm.AddMerger(TestFeedMerger.Merger(self, i, i == 5))
  self.assert_(not self.fm.MergeSchedules())
  self.assertEquals(self.called, range(6))
 
  def testRegister(self):
  s1 = transitfeed.Stop(stop_id='1')
  s2 = transitfeed.Stop(stop_id='2')
  s3 = transitfeed.Stop(stop_id='3')
  self.fm.Register(s1, s2, s3)
  self.assertEquals(self.fm.a_merge_map, {s1: s3})
  self.assertEquals('3', s1._migrated_entity.stop_id)
  self.assertEquals(self.fm.b_merge_map, {s2: s3})
  self.assertEquals('3', s2._migrated_entity.stop_id)
 
  def testRegisterNone(self):
  s2 = transitfeed.Stop(stop_id='2')
  s3 = transitfeed.Stop(stop_id='3')
  self.fm.Register(None, s2, s3)
  self.assertEquals(self.fm.a_merge_map, {})
  self.assertEquals(self.fm.b_merge_map, {s2: s3})
  self.assertEquals('3', s2._migrated_entity.stop_id)
 
  def testGenerateId_Prefix(self):
  x = 'test'
  a = self.fm.GenerateId(x)
  b = self.fm.GenerateId(x)
  self.assertNotEqual(a, b)
  self.assert_(a.startswith(x))
  self.assert_(b.startswith(x))
 
  def testGenerateId_None(self):
  a = self.fm.GenerateId(None)
  b = self.fm.GenerateId(None)
  self.assertNotEqual(a, b)
 
  def testGenerateId_InitialCounter(self):
  a_schedule = transitfeed.Schedule()
  b_schedule = transitfeed.Schedule()
  merged_schedule = transitfeed.Schedule()
 
  for i in range(10):
  agency = transitfeed.Agency(name='agency', url='http://agency',
  timezone='Africa/Johannesburg',
  id='agency_%d' % i)
  if i % 2:
  b_schedule.AddAgencyObject(agency)
  else:
  a_schedule.AddAgencyObject(agency)
  accumulator = TestingProblemAccumulator()
  feed_merger = merge.FeedMerger(a_schedule, b_schedule,
  merged_schedule,
  TestingProblemReporter(accumulator))
 
  # check that the postfix number of any generated ids are greater than
  # the postfix numbers of any ids in the old and new schedules
  gen_id = feed_merger.GenerateId(None)
  postfix_num = int(gen_id[gen_id.rfind('_')+1:])
  self.assert_(postfix_num >= 10)
 
  def testGetMerger(self):
  class MergerA(merge.DataSetMerger):
  pass
 
  class MergerB(merge.DataSetMerger):
  pass
 
  a = MergerA(self.fm)
  b = MergerB(self.fm)
 
  self.fm.AddMerger(a)
  self.fm.AddMerger(b)
 
  self.assertEquals(self.fm.GetMerger(MergerA), a)
  self.assertEquals(self.fm.GetMerger(MergerB), b)
 
  def testGetMerger_Error(self):
  self.assertRaises(LookupError, self.fm.GetMerger, TestFeedMerger.Merger)
 
 
  class TestServicePeriodMerger(util.TestCase):
 
  def setUp(self):
  a_schedule = transitfeed.Schedule()
  b_schedule = transitfeed.Schedule()
  merged_schedule = transitfeed.Schedule()
  self.accumulator = TestingProblemAccumulator()
  self.problem_reporter = TestingProblemReporter(self.accumulator)
  self.fm = merge.FeedMerger(a_schedule, b_schedule, merged_schedule,
  self.problem_reporter)
  self.spm = merge.ServicePeriodMerger(self.fm)
  self.fm.AddMerger(self.spm)
 
  def _AddTwoPeriods(self, start1, end1, start2, end2):
  sp1fields = ['test1', start1, end1] + ['1']*7
  self.sp1 = transitfeed.ServicePeriod(field_list=sp1fields)
  sp2fields = ['test2', start2, end2] + ['1']*7
  self.sp2 = transitfeed.ServicePeriod(field_list=sp2fields)
 
  self.fm.a_schedule.AddServicePeriodObject(self.sp1)
  self.fm.b_schedule.AddServicePeriodObject(self.sp2)
 
  def testCheckDisjoint_True(self):
  self._AddTwoPeriods('20071213', '20071231',
  '20080101', '20080201')
  self.assert_(self.spm.CheckDisjointCalendars())
 
  def testCheckDisjoint_False1(self):
  self._AddTwoPeriods('20071213', '20080201',
  '20080101', '20080301')
  self.assert_(not self.spm.CheckDisjointCalendars())
 
  def testCheckDisjoint_False2(self):
  self._AddTwoPeriods('20080101', '20090101',
  '20070101', '20080601')
  self.assert_(not self.spm.CheckDisjointCalendars())
 
  def testCheckDisjoint_False3(self):
  self._AddTwoPeriods('20080301', '20080901',
  '20080101', '20090101')
  self.assert_(not self.spm.CheckDisjointCalendars())
 
  def testDisjoinCalendars(self):
  self._AddTwoPeriods('20071213', '20080201',
  '20080101', '20080301')
  self.spm.DisjoinCalendars('20080101')
  self.assertEquals(self.sp1.start_date, '20071213')
  self.assertEquals(self.sp1.end_date, '20071231')
  self.assertEquals(self.sp2.start_date, '20080101')
  self.assertEquals(self.sp2.end_date, '20080301')
 
  def testDisjoinCalendars_Dates(self):
  self._AddTwoPeriods('20071213', '20080201',
  '20080101', '20080301')
  self.sp1.SetDateHasService('20071201')
  self.sp1.SetDateHasService('20081231')
  self.sp2.SetDateHasService('20071201')
  self.sp2.SetDateHasService('20081231')
 
  self.spm.DisjoinCalendars('20080101')
 
  self.assert_('20071201' in self.sp1.date_exceptions.keys())
  self.assert_('20081231' not in self.sp1.date_exceptions.keys())
  self.assert_('20071201' not in self.sp2.date_exceptions.keys())
  self.assert_('20081231' in self.sp2.date_exceptions.keys())
 
  def testUnion(self):
  self._AddTwoPeriods('20071213', '20071231',
  '20080101', '20080201')
  self.accumulator.ExpectProblemClass(merge.MergeNotImplemented)
  self.fm.MergeSchedules()
  merged_schedule = self.fm.GetMergedSchedule()
  self.assertEquals(len(merged_schedule.GetServicePeriodList()), 2)
 
  # make fields a copy of the service period attributes except service_id
  fields = list(transitfeed.ServicePeriod._DAYS_OF_WEEK)
  fields += ['start_date', 'end_date']
 
  # now check that these attributes are preserved in the merge
  CheckAttribs(self.sp1, self.fm.a_merge_map[self.sp1], fields,
  self.assertEquals)
  CheckAttribs(self.sp2, self.fm.b_merge_map[self.sp2], fields,
  self.assertEquals)
 
  self.accumulator.assertExpectedProblemsReported(self)
 
  def testMerge_RequiredButNotDisjoint(self):
  self._AddTwoPeriods('20070101', '20090101',
  '20080101', '20100101')
  self.accumulator.ExpectProblemClass(merge.CalendarsNotDisjoint)
  self.assertEquals(self.spm.MergeDataSets(), False)
  self.accumulator.assertExpectedProblemsReported(self)
 
  def testMerge_NotRequiredAndNotDisjoint(self):
  self._AddTwoPeriods('20070101', '20090101',
  '20080101', '20100101')
  self.spm.require_disjoint_calendars = False
  self.accumulator.ExpectProblemClass(merge.MergeNotImplemented)
  self.fm.MergeSchedules()
  self.accumulator.assertExpectedProblemsReported(self)
 
 
  class TestAgencyMerger(util.TestCase):
 
  def setUp(self):
  a_schedule = transitfeed.Schedule()
  b_schedule = transitfeed.Schedule()
  merged_schedule = transitfeed.Schedule()
  self.accumulator = TestingProblemAccumulator()
  self.problem_reporter = TestingProblemReporter(self.accumulator)
  self.fm = merge.FeedMerger(a_schedule, b_schedule, merged_schedule,
  self.problem_reporter)
  self.am = merge.AgencyMerger(self.fm)
  self.fm.AddMerger(self.am)
 
  self.a1 = transitfeed.Agency(id='a1', agency_name='a1',
  agency_url='http://www.a1.com',
  agency_timezone='Africa/Johannesburg',
  agency_phone='123 456 78 90')
  self.a2 = transitfeed.Agency(id='a2', agency_name='a1',
  agency_url='http://www.a1.com',
  agency_timezone='Africa/Johannesburg',
  agency_phone='789 65 43 21')
 
  def testMerge(self):
  self.a2.agency_id = self.a1.agency_id
  self.fm.a_schedule.AddAgencyObject(self.a1)
  self.fm.b_schedule.AddAgencyObject(self.a2)
  self.fm.MergeSchedules()
 
  merged_schedule = self.fm.GetMergedSchedule()
  self.assertEquals(len(merged_schedule.GetAgencyList()), 1)
  self.assertEquals(merged_schedule.GetAgencyList()[0],
  self.fm.a_merge_map[self.a1])
  self.assertEquals(self.fm.a_merge_map[self.a1],
  self.fm.b_merge_map[self.a2])
  # differing values such as agency_phone should be taken from self.a2
  self.assertEquals(merged_schedule.GetAgencyList()[0], self.a2)
  self.assertEquals(self.am.GetMergeStats(), (1, 0, 0))
 
  # check that id is preserved
  self.assertEquals(self.fm.a_merge_map[self.a1].agency_id,
  self.a1.agency_id)
 
  def testNoMerge_DifferentId(self):
  self.fm.a_schedule.AddAgencyObject(self.a1)
  self.fm.b_schedule.AddAgencyObject(self.a2)
  self.fm.MergeSchedules()
 
  merged_schedule = self.fm.GetMergedSchedule()
  self.assertEquals(len(merged_schedule.GetAgencyList()), 2)
 
  self.assert_(self.fm.a_merge_map[self.a1] in
  merged_schedule.GetAgencyList())
  self.assert_(self.fm.b_merge_map[self.a2] in
  merged_schedule.GetAgencyList())
  self.assertEquals(self.a1, self.fm.a_merge_map[self.a1])
  self.assertEquals(self.a2, self.fm.b_merge_map[self.a2])
  self.assertEquals(self.am.GetMergeStats(), (0, 1, 1))
 
  # check that the ids are preserved
  self.assertEquals(self.fm.a_merge_map[self.a1].agency_id,
  self.a1.agency_id)
  self.assertEquals(self.fm.b_merge_map[self.a2].agency_id,
  self.a2.agency_id)
 
  def testNoMerge_SameId(self):
  # Force a1.agency_id to be unicode to make sure it is correctly encoded
  # to utf-8 before concatinating to the agency_name containing non-ascii
  # characters.
  self.a1.agency_id = unicode(self.a1.agency_id)
  self.a2.agency_id = str(self.a1.agency_id)
  self.a2.agency_name = 'different \xc3\xa9'
  self.fm.a_schedule.AddAgencyObject(self.a1)
  self.fm.b_schedule.AddAgencyObject(self.a2)
 
  self.accumulator.ExpectProblemClass(merge.SameIdButNotMerged)
  self.fm.MergeSchedules()
 
  merged_schedule = self.fm.GetMergedSchedule()
  self.assertEquals(len(merged_schedule.GetAgencyList()), 2)
  self.assertEquals(self.am.GetMergeStats(), (0, 1, 1))
 
  # check that the merged entities have different ids
  self.assertNotEqual(self.fm.a_merge_map[self.a1].agency_id,
  self.fm.b_merge_map[self.a2].agency_id)
 
  self.accumulator.assertExpectedProblemsReported(self)
 
 
  class TestStopMerger(util.TestCase):
 
  def setUp(self):
  a_schedule = transitfeed.Schedule()
  b_schedule = transitfeed.Schedule()
  merged_schedule = transitfeed.Schedule()
  self.accumulator = TestingProblemAccumulator()
  self.problem_reporter = TestingProblemReporter(self.accumulator)
  self.fm = merge.FeedMerger(a_schedule, b_schedule, merged_schedule,
  self.problem_reporter)
  self.sm = merge.StopMerger(self.fm)
  self.fm.AddMerger(self.sm)
 
  self.s1 = transitfeed.Stop(30.0, 30.0,
  u'Andr\202' , 's1')
  self.s1.stop_desc = 'stop 1'
  self.s1.stop_url = 'http://stop/1'
  self.s1.zone_id = 'zone1'
  self.s2 = transitfeed.Stop(30.0, 30.0, 's2', 's2')
  self.s2.stop_desc = 'stop 2'
  self.s2.stop_url = 'http://stop/2'
  self.s2.zone_id = 'zone1'
 
  def testMerge(self):
  self.s2.stop_id = self.s1.stop_id
  self.s2.stop_name = self.s1.stop_name
  self.s1.location_type = 1
  self.s2.location_type = 1
 
  self.fm.a_schedule.AddStopObject(self.s1)
  self.fm.b_schedule.AddStopObject(self.s2)
  self.fm.MergeSchedules()
 
  merged_schedule = self.fm.GetMergedSchedule()
  self.assertEquals(len(merged_schedule.GetStopList()), 1)
  self.assertEquals(merged_schedule.GetStopList()[0],
  self.fm.a_merge_map[self.s1])
  self.assertEquals(self.fm.a_merge_map[self.s1],
  self.fm.b_merge_map[self.s2])
  self.assertEquals(self.sm.GetMergeStats(), (1, 0, 0))
 
  # check that the remaining attributes are taken from the new stop
  fields = ['stop_name', 'stop_lat', 'stop_lon', 'stop_desc', 'stop_url',
  'location_type']
  CheckAttribs(self.fm.a_merge_map[self.s1], self.s2, fields,
  self.assertEquals)
 
  # check that the id is preserved
  self.assertEquals(self.fm.a_merge_map[self.s1].stop_id, self.s1.stop_id)
 
  # check that the zone_id is preserved
  self.assertEquals(self.fm.a_merge_map[self.s1].zone_id, self.s1.zone_id)
 
  def testNoMerge_DifferentId(self):
  self.fm.a_schedule.AddStopObject(self.s1)
  self.fm.b_schedule.AddStopObject(self.s2)
  self.fm.MergeSchedules()
 
  merged_schedule = self.fm.GetMergedSchedule()
  self.assertEquals(len(merged_schedule.GetStopList()), 2)
  self.assert_(self.fm.a_merge_map[self.s1] in merged_schedule.GetStopList())
  self.assert_(self.fm.b_merge_map[self.s2] in merged_schedule.GetStopList())
  self.assertEquals(self.sm.GetMergeStats(), (0, 1, 1))
 
  def testNoMerge_DifferentName(self):
  self.s2.stop_id = self.s1.stop_id
  self.fm.a_schedule.AddStopObject(self.s1)
  self.fm.b_schedule.AddStopObject(self.s2)
  self.accumulator.ExpectProblemClass(merge.SameIdButNotMerged)
  self.fm.MergeSchedules()
 
  merged_schedule = self.fm.GetMergedSchedule()
  self.assertEquals(len(merged_schedule.GetStopList()), 2)
  self.assert_(self.fm.a_merge_map[self.s1] in merged_schedule.GetStopList())
  self.assert_(self.fm.b_merge_map[self.s2] in merged_schedule.GetStopList())
  self.assertEquals(self.sm.GetMergeStats(), (0, 1, 1))
 
  def testNoMerge_FarApart(self):
  self.s2.stop_id = self.s1.stop_id
  self.s2.stop_name = self.s1.stop_name
  self.s2.stop_lat = 40.0
  self.s2.stop_lon = 40.0
 
  self.fm.a_schedule.AddStopObject(self.s1)
  self.fm.b_schedule.AddStopObject(self.s2)
  self.accumulator.ExpectProblemClass(merge.SameIdButNotMerged)
  self.fm.MergeSchedules()
 
  merged_schedule = self.fm.GetMergedSchedule()
  self.assertEquals(len(merged_schedule.GetStopList()), 2)
  self.assert_(self.fm.a_merge_map[self.s1] in merged_schedule.GetStopList())
  self.assert_(self.fm.b_merge_map[self.s2] in merged_schedule.GetStopList())
  self.assertEquals(self.sm.GetMergeStats(), (0, 1, 1))
 
  # check that the merged ids are different
  self.assertNotEquals(self.fm.a_merge_map[self.s1].stop_id,
  self.fm.b_merge_map[self.s2].stop_id)
 
  self.accumulator.assertExpectedProblemsReported(self)
 
  def testMerge_CaseInsensitive(self):
  self.s2.stop_id = self.s1.stop_id
  self.s2.stop_name = self.s1.stop_name.upper()
  self.fm.a_schedule.AddStopObject(self.s1)
  self.fm.b_schedule.AddStopObject(self.s2)
  self.fm.MergeSchedules()
  merged_schedule = self.fm.GetMergedSchedule()
  self.assertEquals(len(merged_schedule.GetStopList()), 1)
  self.assertEquals(self.sm.GetMergeStats(), (1, 0, 0))
 
  def testNoMerge_ZoneId(self):
  self.s2.zone_id = 'zone2'
  self.fm.a_schedule.AddStopObject(self.s1)
  self.fm.b_schedule.AddStopObject(self.s2)
  self.fm.MergeSchedules()
 
  merged_schedule = self.fm.GetMergedSchedule()
  self.assertEquals(len(merged_schedule.GetStopList()), 2)
 
  self.assert_(self.s1.zone_id in self.fm.a_zone_map)
  self.assert_(self.s2.zone_id in self.fm.b_zone_map)
  self.assertEquals(self.sm.GetMergeStats(), (0, 1, 1))
 
  # check that the zones are still different
  self.assertNotEqual(self.fm.a_merge_map[self.s1].zone_id,
  self.fm.b_merge_map[self.s2].zone_id)
 
  def testZoneId_SamePreservation(self):
  # checks that if the zone_ids of some stops are the same before the
  # merge, they are still the same after.
  self.fm.a_schedule.AddStopObject(self.s1)
  self.fm.a_schedule.AddStopObject(self.s2)
  self.fm.MergeSchedules()
  self.assertEquals(self.fm.a_merge_map[self.s1].zone_id,
  self.fm.a_merge_map[self.s2].zone_id)
 
  def testZoneId_DifferentSchedules(self):
  # zone_ids may be the same in different schedules but unless the stops
  # are merged, they should map to different zone_ids
  self.fm.a_schedule.AddStopObject(self.s1)
  self.fm.b_schedule.AddStopObject(self.s2)
  self.fm.MergeSchedules()
  self.assertNotEquals(self.fm.a_merge_map[self.s1].zone_id,
  self.fm.b_merge_map[self.s2].zone_id)
 
  def testZoneId_MergePreservation(self):
  # check that if two stops are merged, the zone mapping is used for all
  # other stops too
  self.s2.stop_id = self.s1.stop_id
  self.s2.stop_name = self.s1.stop_name
  s3 = transitfeed.Stop(field_dict=self.s1)
  s3.stop_id = 'different'
 
  self.fm.a_schedule.AddStopObject(self.s1)
  self.fm.a_schedule.AddStopObject(s3)
  self.fm.b_schedule.AddStopObject(self.s2)
  self.fm.MergeSchedules()
 
  self.assertEquals(self.fm.a_merge_map[self.s1].zone_id,
  self.fm.a_merge_map[s3].zone_id)
  self.assertEquals(self.fm.a_merge_map[s3].zone_id,
  self.fm.b_merge_map[self.s2].zone_id)
 
  def testMergeStationType(self):
  self.s2.stop_id = self.s1.stop_id
  self.s2.stop_name = self.s1.stop_name
  self.s1.location_type = 1
  self.s2.location_type = 1
  self.fm.a_schedule.AddStopObject(self.s1)
  self.fm.b_schedule.AddStopObject(self.s2)
  self.fm.MergeSchedules()
  merged_stops = self.fm.GetMergedSchedule().GetStopList()
  self.assertEquals(len(merged_stops), 1)
  self.assertEquals(merged_stops[0].location_type, 1)
 
  def testMergeDifferentTypes(self):
  self.s2.stop_id = self.s1.stop_id
  self.s2.stop_name = self.s1.stop_name
  self.s2.location_type = 1
  self.fm.a_schedule.AddStopObject(self.s1)
  self.fm.b_schedule.AddStopObject(self.s2)
  try:
  self.fm.MergeSchedules()
  self.fail("Expecting MergeError")
  except merge.SameIdButNotMerged, merge_error:
  self.assertTrue(("%s" % merge_error).find("location_type") != -1)
 
  def AssertS1ParentIsS2(self):
  """Assert that the merged s1 has parent s2."""
  new_s1 = self.s1._migrated_entity
  new_s2 = self.s2._migrated_entity
  self.assertEquals(new_s1.parent_station, new_s2.stop_id)
  self.assertEquals(new_s2.parent_station, None)
  self.assertEquals(new_s1.location_type, 0)
  self.assertEquals(new_s2.location_type, 1)
 
  def testMergeMaintainParentRelationship(self):
  self.s2.location_type = 1
  self.s1.parent_station = self.s2.stop_id
  self.fm.a_schedule.AddStopObject(self.s1)
  self.fm.a_schedule.AddStopObject(self.s2)
  self.fm.MergeSchedules()
  self.AssertS1ParentIsS2()
 
  def testParentRelationshipAfterMerge(self):
  s3 = transitfeed.Stop(field_dict=self.s1)
  s3.parent_station = self.s2.stop_id
  self.s2.location_type = 1
  self.fm.a_schedule.AddStopObject(self.s1)
  self.fm.b_schedule.AddStopObject(self.s2)
  self.fm.b_schedule.AddStopObject(s3)
  self.fm.MergeSchedules()
  self.AssertS1ParentIsS2()
 
  def testParentRelationshipWithNewParentid(self):
  self.s2.location_type = 1
  self.s1.parent_station = self.s2.stop_id
  # s3 will have a stop_id conflict with self.s2 so parent_id of the
  # migrated self.s1 will need to be updated
  s3 = transitfeed.Stop(field_dict=self.s2)
  s3.stop_lat = 45
  self.fm.a_schedule.AddStopObject(s3)
  self.fm.b_schedule.AddStopObject(self.s1)
  self.fm.b_schedule.AddStopObject(self.s2)
  self.accumulator.ExpectProblemClass(merge.SameIdButNotMerged)
  self.fm.MergeSchedules()
  self.assertNotEquals(s3._migrated_entity.stop_id,
  self.s2._migrated_entity.stop_id)
  # Check that s2 got a new id
  self.assertNotEquals(self.s2.stop_id,
  self.s2._migrated_entity.stop_id)
  self.AssertS1ParentIsS2()
 
  def _AddStopsApart(self):
  """Adds two stops to the schedules and returns the distance between them.
 
  Returns:
  The distance between the stops in metres, a value greater than zero.
  """
  self.s2.stop_id = self.s1.stop_id
  self.s2.stop_name = self.s1.stop_name
  self.s2.stop_lat += 1.0e-3
  self.fm.a_schedule.AddStopObject(self.s1)
  self.fm.b_schedule.AddStopObject(self.s2)
  return transitfeed.ApproximateDistanceBetweenStops(self.s1, self.s2)
 
  def testSetLargestStopDistanceSmall(self):
  largest_stop_distance = self._AddStopsApart() * 0.5
  self.sm.SetLargestStopDistance(largest_stop_distance)
  self.assertEquals(self.sm.largest_stop_distance, largest_stop_distance)
  self.accumulator.ExpectProblemClass(merge.SameIdButNotMerged)
  self.fm.MergeSchedules()
  self.assertEquals(len(self.fm.GetMergedSchedule().GetStopList()), 2)
  self.accumulator.assertExpectedProblemsReported(self)
 
  def testSetLargestStopDistanceLarge(self):
  largest_stop_distance = self._AddStopsApart() * 2.0
  self.sm.SetLargestStopDistance(largest_stop_distance)
  self.assertEquals(self.sm.largest_stop_distance, largest_stop_distance)
  self.fm.MergeSchedules()
  self.assertEquals(len(self.fm.GetMergedSchedule().GetStopList()), 1)
 
 
  class TestRouteMerger(util.TestCase):
 
  fields = ['route_short_name', 'route_long_name', 'route_type',
  'route_url']
 
  def setUp(self):
  a_schedule = transitfeed.Schedule()
  b_schedule = transitfeed.Schedule()
  merged_schedule = transitfeed.Schedule()
  self.accumulator = TestingProblemAccumulator()
  self.problem_reporter = TestingProblemReporter(self.accumulator)
  self.fm = merge.FeedMerger(a_schedule, b_schedule, merged_schedule,
  self.problem_reporter)
  self.fm.AddMerger(merge.AgencyMerger(self.fm))
  self.rm = merge.RouteMerger(self.fm)
  self.fm.AddMerger(self.rm)
 
  akwargs = {'id': 'a1',
  'agency_name': 'a1',
  'agency_url': 'http://www.a1.com',
  'agency_timezone': 'Europe/Zurich'}
  self.a1 = transitfeed.Agency(**akwargs)
  self.a2 = transitfeed.Agency(**akwargs)
  a_schedule.AddAgencyObject(self.a1)
  b_schedule.AddAgencyObject(self.a2)
 
  rkwargs = {'route_id': 'r1',
  'agency_id': 'a1',
  'short_name': 'r1',
  'long_name': 'r1r1',
  'route_type': '0'}
  self.r1 = transitfeed.Route(**rkwargs)
  self.r2 = transitfeed.Route(**rkwargs)
  self.r2.route_url = 'http://route/2'
 
  def testMerge(self):
  self.fm.a_schedule.AddRouteObject(self.r1)
  self.fm.b_schedule.AddRouteObject(self.r2)
  self.fm.MergeSchedules()
 
  merged_schedule = self.fm.GetMergedSchedule()
  self.assertEquals(len(merged_schedule.GetRouteList()), 1)
  r = merged_schedule.GetRouteList()[0]
  self.assert_(self.fm.a_merge_map[self.r1] is r)
  self.assert_(self.fm.b_merge_map[self.r2] is r)
  CheckAttribs(self.r2, r, self.fields, self.assertEquals)
  self.assertEquals(r.agency_id, self.fm.a_merge_map[self.a1].agency_id)
  self.assertEquals(self.rm.GetMergeStats(), (1, 0, 0))
 
  # check that the id is preserved
  self.assertEquals(self.fm.a_merge_map[self.r1].route_id, self.r1.route_id)
 
  def testMergeNoAgency(self):
  self.r1.agency_id = None
  self.r2.agency_id = None
  self.fm.a_schedule.AddRouteObject(self.r1)
  self.fm.b_schedule.AddRouteObject(self.r2)
  self.fm.MergeSchedules()
 
  merged_schedule = self.fm.GetMergedSchedule()
  self.assertEquals(len(merged_schedule.GetRouteList()), 1)
  r = merged_schedule.GetRouteList()[0]
  CheckAttribs(self.r2, r, self.fields, self.assertEquals)
  # Merged route has copy of default agency_id
  self.assertEquals(r.agency_id, self.a1.agency_id)
  self.assertEquals(self.rm.GetMergeStats(), (1, 0, 0))
 
  # check that the id is preserved
  self.assertEquals(self.fm.a_merge_map[self.r1].route_id, self.r1.route_id)
 
  def testMigrateNoAgency(self):
  self.r1.agency_id = None
  self.fm.a_schedule.AddRouteObject(self.r1)
  self.fm.MergeSchedules()
  merged_schedule = self.fm.GetMergedSchedule()
  self.assertEquals(len(merged_schedule.GetRouteList()), 1)
  r = merged_schedule.GetRouteList()[0]
  CheckAttribs(self.r1, r, self.fields, self.assertEquals)
  # Migrated route has copy of default agency_id
  self.assertEquals(r.agency_id, self.a1.agency_id)
 
  def testNoMerge_DifferentId(self):
  self.r2.route_id = 'r2'
  self.fm.a_schedule.AddRouteObject(self.r1)
  self.fm.b_schedule.AddRouteObject(self.r2)
  self.fm.MergeSchedules()
  self.assertEquals(len(self.fm.GetMergedSchedule().GetRouteList()), 2)
  self.assertEquals(self.rm.GetMergeStats(), (0, 1, 1))
 
  def testNoMerge_SameId(self):
  self.r2.route_short_name = 'different'
  self.fm.a_schedule.AddRouteObject(self.r1)
  self.fm.b_schedule.AddRouteObject(self.r2)
  self.accumulator.ExpectProblemClass(merge.SameIdButNotMerged)
  self.fm.MergeSchedules()
  self.assertEquals(len(self.fm.GetMergedSchedule().GetRouteList()), 2)
  self.assertEquals(self.rm.GetMergeStats(), (0, 1, 1))
 
  # check that the merged ids are different
  self.assertNotEquals(self.fm.a_merge_map[self.r1].route_id,
  self.fm.b_merge_map[self.r2].route_id)
 
  self.accumulator.assertExpectedProblemsReported(self)
 
 
  class TestTripMerger(util.TestCase):
 
  def setUp(self):
  a_schedule = transitfeed.Schedule()
  b_schedule = transitfeed.Schedule()
  merged_schedule = transitfeed.Schedule()
  self.accumulator = TestingProblemAccumulator()
  self.problem_reporter = TestingProblemReporter(self.accumulator)
  self.fm = merge.FeedMerger(a_schedule, b_schedule, merged_schedule,
  self.problem_reporter)
  self.fm.AddDefaultMergers()
  self.tm = self.fm.GetMerger(merge.TripMerger)
 
  akwargs = {'id': 'a1',
  'agency_name': 'a1',
  'agency_url': 'http://www.a1.com',
  'agency_timezone': 'Europe/Zurich'}
  self.a1 = transitfeed.Agency(**akwargs)
 
  rkwargs = {'route_id': 'r1',
  'agency_id': 'a1',
  'short_name': 'r1',
  'long_name': 'r1r1',
  'route_type': '0'}
  self.r1 = transitfeed.Route(**rkwargs)
 
  self.s1 = transitfeed.ServicePeriod('s1')
  self.s1.start_date = '20071201'
  self.s1.end_date = '20071231'
  self.s1.SetWeekdayService()
 
  self.shape = transitfeed.Shape('shape1')
  self.shape.AddPoint(30.0, 30.0)
 
  self.t1 = transitfeed.Trip(service_period=self.s1,
  route=self.r1, trip_id='t1')
  self.t2 = transitfeed.Trip(service_period=self.s1,
  route=self.r1, trip_id='t2')
  # Must add self.t1 to a schedule before calling self.t1.AddStopTime
  a_schedule.AddTripObject(self.t1, validate=False)
  a_schedule.AddTripObject(self.t2, validate=False)
  self.t1.block_id = 'b1'
  self.t2.block_id = 'b1'
  self.t1.shape_id = 'shape1'
 
  self.stop = transitfeed.Stop(30.0, 30.0, stop_id='stop1')
  self.t1.AddStopTime(self.stop, arrival_secs=0, departure_secs=0)
 
  a_schedule.AddAgencyObject(self.a1)
  a_schedule.AddStopObject(self.stop)
  a_schedule.AddRouteObject(self.r1)
  a_schedule.AddServicePeriodObject(self.s1)
  a_schedule.AddShapeObject(self.shape)
 
  def testMigrate(self):
  self.accumulator.ExpectProblemClass(merge.MergeNotImplemented)
  self.fm.MergeSchedules()
  self.accumulator.assertExpectedProblemsReported(self)
 
  r = self.fm.a_merge_map[self.r1]
  s = self.fm.a_merge_map[self.s1]
  shape = self.fm.a_merge_map[self.shape]
  t1 = self.fm.a_merge_map[self.t1]
  t2 = self.fm.a_merge_map[self.t2]
 
  self.assertEquals(t1.route_id, r.route_id)
  self.assertEquals(t1.service_id, s.service_id)
  self.assertEquals(t1.shape_id, shape.shape_id)
  self.assertEquals(t1.block_id, t2.block_id)
 
  self.assertEquals(len(t1.GetStopTimes()), 1)
  st = t1.GetStopTimes()[0]
  self.assertEquals(st.stop, self.fm.a_merge_map[self.stop])
 
  def testReportsNotImplementedProblem(self):
  self.accumulator.ExpectProblemClass(merge.MergeNotImplemented)
  self.fm.MergeSchedules()
  self.accumulator.assertExpectedProblemsReported(self)
 
  def testMergeStats(self):
  self.assert_(self.tm.GetMergeStats() is None)
 
  def testConflictingTripid(self):
  a1_in_b = transitfeed.Agency(field_dict=self.a1)
  r1_in_b = transitfeed.Route(field_dict=self.r1)
  t1_in_b = transitfeed.Trip(field_dict=self.t1)
  shape_in_b = transitfeed.Shape('shape1')
  shape_in_b.AddPoint(30.0, 30.0)
  s_in_b = transitfeed.ServicePeriod('s1')
  s_in_b.start_date = '20080101'
  s_in_b.end_date = '20080131'
  s_in_b.SetWeekdayService()
 
  self.fm.b_schedule.AddAgencyObject(a1_in_b)
  self.fm.b_schedule.AddRouteObject(r1_in_b)
  self.fm.b_schedule.AddShapeObject(shape_in_b)
  self.fm.b_schedule.AddTripObject(t1_in_b, validate=False)
  self.fm.b_schedule.AddServicePeriodObject(s_in_b, validate=False)
  self.accumulator.ExpectProblemClass(merge.MergeNotImplemented)
  self.fm.MergeSchedules()
  # 3 trips moved to merged_schedule: from a_schedule t1, t2 and from
  # b_schedule t1
  self.assertEquals(len(self.fm.merged_schedule.GetTripList()), 3)
 
 
  class TestFareMerger(util.TestCase):
 
  def setUp(self):
  a_schedule = transitfeed.Schedule()
  b_schedule = transitfeed.Schedule()
  merged_schedule = transitfeed.Schedule()
  self.accumulator = TestingProblemAccumulator()
  self.problem_reporter = TestingProblemReporter(self.accumulator)
  self.fm = merge.FeedMerger(a_schedule, b_schedule, merged_schedule,
  self.problem_reporter)
  self.faremerger = merge.FareMerger(self.fm)
  self.fm.AddMerger(self.faremerger)
 
  self.f1 = transitfeed.FareAttribute('f1', '10', 'ZAR', '1', '0')
  self.f2 = transitfeed.FareAttribute('f2', '10', 'ZAR', '1', '0')
 
  def testMerge(self):
  self.f2.fare_id = self.f1.fare_id
  self.fm.a_schedule.AddFareAttributeObject(self.f1)
  self.fm.b_schedule.AddFareAttributeObject(self.f2)
  self.fm.MergeSchedules()
  self.assertEquals(len(self.fm.merged_schedule.GetFareAttributeList()), 1)
  self.assertEquals(self.faremerger.GetMergeStats(), (1, 0, 0))
 
  # check that the id is preserved
  self.assertEquals(self.fm.a_merge_map[self.f1].fare_id, self.f1.fare_id)
 
  def testNoMerge_DifferentPrice(self):
  self.f2.fare_id = self.f1.fare_id
  self.f2.price = 11.0
  self.fm.a_schedule.AddFareAttributeObject(self.f1)
  self.fm.b_schedule.AddFareAttributeObject(self.f2)
  self.accumulator.ExpectProblemClass(merge.SameIdButNotMerged)
  self.fm.MergeSchedules()
  self.assertEquals(len(self.fm.merged_schedule.GetFareAttributeList()), 2)
  self.assertEquals(self.faremerger.GetMergeStats(), (0, 1, 1))
 
  # check that the merged ids are different
  self.assertNotEquals(self.fm.a_merge_map[self.f1].fare_id,
  self.fm.b_merge_map[self.f2].fare_id)
 
  self.accumulator.assertExpectedProblemsReported(self)
 
  def testNoMerge_DifferentId(self):
  self.fm.a_schedule.AddFareAttributeObject(self.f1)
  self.fm.b_schedule.AddFareAttributeObject(self.f2)
  self.fm.MergeSchedules()
  self.assertEquals(len(self.fm.merged_schedule.GetFareAttributeList()), 2)
  self.assertEquals(self.faremerger.GetMergeStats(), (0, 1, 1))
 
  # check that the ids are preserved
  self.assertEquals(self.fm.a_merge_map[self.f1].fare_id, self.f1.fare_id)
  self.assertEquals(self.fm.b_merge_map[self.f2].fare_id, self.f2.fare_id)
 
 
  class TestShapeMerger(util.TestCase):
 
  def setUp(self):
  a_schedule = transitfeed.Schedule()
  b_schedule = transitfeed.Schedule()
  merged_schedule = transitfeed.Schedule()
  self.accumulator = TestingProblemAccumulator()
  self.problem_reporter = TestingProblemReporter(self.accumulator)
  self.fm = merge.FeedMerger(a_schedule, b_schedule, merged_schedule,
  self.problem_reporter)
  self.sm = merge.ShapeMerger(self.fm)
  self.fm.AddMerger(self.sm)
 
  # setup some shapes
  # s1 and s2 have the same endpoints but take different paths
  # s3 has different endpoints to s1 and s2
 
  self.s1 = transitfeed.Shape('s1')
  self.s1.AddPoint(30.0, 30.0)
  self.s1.AddPoint(40.0, 30.0)
  self.s1.AddPoint(50.0, 50.0)
 
  self.s2 = transitfeed.Shape('s2')
  self.s2.AddPoint(30.0, 30.0)
  self.s2.AddPoint(40.0, 35.0)
  self.s2.AddPoint(50.0, 50.0)
 
  self.s3 = transitfeed.Shape('s3')
  self.s3.AddPoint(31.0, 31.0)
  self.s3.AddPoint(45.0, 35.0)
  self.s3.AddPoint(51.0, 51.0)
 
  def testMerge(self):
  self.s2.shape_id = self.s1.shape_id
  self.fm.a_schedule.AddShapeObject(self.s1)
  self.fm.b_schedule.AddShapeObject(self.s2)
  self.fm.MergeSchedules()
  self.assertEquals(len(self.fm.merged_schedule.GetShapeList()), 1)
  self.assertEquals(self.fm.merged_schedule.GetShapeList()[0], self.s2)
  self.assertEquals(self.sm.GetMergeStats(), (1, 0, 0))
 
  # check that the id is preserved
  self.assertEquals(self.fm.a_merge_map[self.s1].shape_id, self.s1.shape_id)
 
  def testNoMerge_DifferentId(self):
  self.fm.a_schedule.AddShapeObject(self.s1)
  self.fm.b_schedule.AddShapeObject(self.s2)
  self.fm.MergeSchedules()
  self.assertEquals(len(self.fm.merged_schedule.GetShapeList()), 2)
  self.assertEquals(self.s1, self.fm.a_merge_map[self.s1])
  self.assertEquals(self.s2, self.fm.b_merge_map[self.s2])
  self.assertEquals(self.sm.GetMergeStats(), (0, 1, 1))
 
  # check that the ids are preserved
  self.assertEquals(self.fm.a_merge_map[self.s1].shape_id, self.s1.shape_id)
  self.assertEquals(self.fm.b_merge_map[self.s2].shape_id, self.s2.shape_id)
 
  def testNoMerge_FarEndpoints(self):
  self.s3.shape_id = self.s1.shape_id
  self.fm.a_schedule.AddShapeObject(self.s1)
  self.fm.b_schedule.AddShapeObject(self.s3)
  self.accumulator.ExpectProblemClass(merge.SameIdButNotMerged)
  self.fm.MergeSchedules()
  self.assertEquals(len(self.fm.merged_schedule.GetShapeList()), 2)
  self.assertEquals(self.s1, self.fm.a_merge_map[self.s1])
  self.assertEquals(self.s3, self.fm.b_merge_map[self.s3])
  self.assertEquals(self.sm.GetMergeStats(), (0, 1, 1))
 
  # check that the ids are different
  self.assertNotEquals(self.fm.a_merge_map[self.s1].shape_id,
  self.fm.b_merge_map[self.s3].shape_id)
 
  self.accumulator.assertExpectedProblemsReported(self)
 
  def _AddShapesApart(self):
  """Adds two shapes to the schedules.
 
  The maximum of the distances between the endpoints is returned.
 
  Returns:
  The distance in metres, a value greater than zero.
  """
  self.s3.shape_id = self.s1.shape_id
  self.fm.a_schedule.AddShapeObject(self.s1)
  self.fm.b_schedule.AddShapeObject(self.s3)
  distance1 = merge.ApproximateDistanceBetweenPoints(
  self.s1.points[0][:2], self.s3.points[0][:2])
  distance2 = merge.ApproximateDistanceBetweenPoints(
  self.s1.points[-1][:2], self.s3.points[-1][:2])
  return max(distance1, distance2)
 
  def testSetLargestShapeDistanceSmall(self):
  largest_shape_distance = self._AddShapesApart() * 0.5
  self.sm.SetLargestShapeDistance(largest_shape_distance)
  self.assertEquals(self.sm.largest_shape_distance, largest_shape_distance)
  self.accumulator.ExpectProblemClass(merge.SameIdButNotMerged)
  self.fm.MergeSchedules()
  self.assertEquals(len(self.fm.GetMergedSchedule().GetShapeList()), 2)
  self.accumulator.assertExpectedProblemsReported(self)
 
  def testSetLargestShapeDistanceLarge(self):
  largest_shape_distance = self._AddShapesApart() * 2.0
  self.sm.SetLargestShapeDistance(largest_shape_distance)
  self.assertEquals(self.sm.largest_shape_distance, largest_shape_distance)
  self.fm.MergeSchedules()
  self.assertEquals(len(self.fm.GetMergedSchedule().GetShapeList()), 1)
 
 
  class TestFareRuleMerger(util.TestCase):
 
  def setUp(self):
  a_schedule = transitfeed.Schedule()
  b_schedule = transitfeed.Schedule()
  merged_schedule = transitfeed.Schedule()
  self.accumulator = TestingProblemAccumulator()
  self.problem_reporter = TestingProblemReporter(self.accumulator)
  self.fm = merge.FeedMerger(a_schedule, b_schedule, merged_schedule,
  self.problem_reporter)
  self.fm.AddDefaultMergers()
  self.fare_rule_merger = self.fm.GetMerger(merge.FareRuleMerger)
 
  akwargs = {'id': 'a1',
  'agency_name': 'a1',
  'agency_url': 'http://www.a1.com',
  'agency_timezone': 'Europe/Zurich'}
  self.a1 = transitfeed.Agency(**akwargs)
  self.a2 = transitfeed.Agency(**akwargs)
 
  rkwargs = {'route_id': 'r1',
  'agency_id': 'a1',
  'short_name': 'r1',
  'long_name': 'r1r1',
  'route_type': '0'}
  self.r1 = transitfeed.Route(**rkwargs)
  self.r2 = transitfeed.Route(**rkwargs)
 
  self.f1 = transitfeed.FareAttribute('f1', '10', 'ZAR', '1', '0')
  self.f2 = transitfeed.FareAttribute('f1', '10', 'ZAR', '1', '0')
  self.f3 = transitfeed.FareAttribute('f3', '11', 'USD', '1', '0')
 
  self.fr1 = transitfeed.FareRule('f1', 'r1')
  self.fr2 = transitfeed.FareRule('f1', 'r1')
  self.fr3 = transitfeed.FareRule('f3', 'r1')
 
  self.fm.a_schedule.AddAgencyObject(self.a1)
  self.fm.a_schedule.AddRouteObject(self.r1)
  self.fm.a_schedule.AddFareAttributeObject(self.f1)
  self.fm.a_schedule.AddFareAttributeObject(self.f3)
  self.fm.a_schedule.AddFareRuleObject(self.fr1)
  self.fm.a_schedule.AddFareRuleObject(self.fr3)
 
  self.fm.b_schedule.AddAgencyObject(self.a2)
  self.fm.b_schedule.AddRouteObject(self.r2)
  self.fm.b_schedule.AddFareAttributeObject(self.f2)
  self.fm.b_schedule.AddFareRuleObject(self.fr2)
 
  def testMerge(self):
  self.accumulator.ExpectProblemClass(merge.FareRulesBroken)
  self.accumulator.ExpectProblemClass(merge.MergeNotImplemented)
  self.fm.MergeSchedules()
 
  self.assertEquals(len(self.fm.merged_schedule.GetFareAttributeList()), 2)
 
  fare_1 = self.fm.a_merge_map[self.f1]
  fare_2 = self.fm.a_merge_map[self.f3]
 
  self.assertEquals(len(fare_1.GetFareRuleList()), 1)
  fare_rule_1 = fare_1.GetFareRuleList()[0]
  self.assertEquals(len(fare_2.GetFareRuleList()), 1)
  fare_rule_2 = fare_2.GetFareRuleList()[0]
 
  self.assertEquals(fare_rule_1.fare_id,
  self.fm.a_merge_map[self.f1].fare_id)
  self.assertEquals(fare_rule_1.route_id,
  self.fm.a_merge_map[self.r1].route_id)
  self.assertEqual(fare_rule_2.fare_id,
  self.fm.a_merge_map[self.f3].fare_id)
  self.assertEqual(fare_rule_2.route_id,
  self.fm.a_merge_map[self.r1].route_id)
 
  self.accumulator.assertExpectedProblemsReported(self)
 
  def testMergeStats(self):
  self.assert_(self.fare_rule_merger.GetMergeStats() is None)
 
 
  class TestTransferMerger(util.TestCase):
  def setUp(self):
  a_schedule = transitfeed.Schedule()
  b_schedule = transitfeed.Schedule()
  merged_schedule = transitfeed.Schedule()
  self.accumulator = TestingProblemAccumulator()
  self.problem_reporter = TestingProblemReporter(self.accumulator)
  self.fm = merge.FeedMerger(a_schedule, b_schedule, merged_schedule,
  self.problem_reporter)
 
  def testStopsMerged(self):
  stop0 = transitfeed.Stop(lat=30.0, lng=30.0, name="0", stop_id="0")
  stop1 = transitfeed.Stop(lat=30.1, lng=30.1, name="1", stop_id="1")
  self.fm.a_schedule.AddStopObject(transitfeed.Stop(field_dict=stop0))
  self.fm.b_schedule.AddStopObject(transitfeed.Stop(field_dict=stop0))
 
  self.fm.a_schedule.AddStopObject(transitfeed.Stop(field_dict=stop1))
  self.fm.b_schedule.AddStopObject(transitfeed.Stop(field_dict=stop1))
  self.fm.a_schedule.AddTransferObject(transitfeed.Transfer(from_stop_id="0",
  to_stop_id="1"))
  self.fm.b_schedule.AddTransferObject(transitfeed.Transfer(from_stop_id="0",
  to_stop_id="1"))
  self.fm.AddMerger(merge.StopMerger(self.fm))
  self.fm.AddMerger(merge.TransferMerger(self.fm))
  self.fm.MergeSchedules()
  transfers = self.fm.merged_schedule.GetTransferList()
  self.assertEquals(1, len(transfers))
  self.assertEquals("0", transfers[0].from_stop_id)
  self.assertEquals("1", transfers[0].to_stop_id)
 
  def testToStopNotMerged(self):
  """When stops aren't merged transfer is duplicated."""
  self.accumulator.ExpectProblemClass(merge.SameIdButNotMerged)
  stop0 = transitfeed.Stop(lat=30.0, lng=30.0, name="0", stop_id="0")
  stop1a = transitfeed.Stop(lat=30.1, lng=30.1, name="1a", stop_id="1")
  stop1b = transitfeed.Stop(lat=30.1, lng=30.1, name="1b", stop_id="1")
 
  # a_schedule and b_schedule both have a transfer with to_stop_id=1 but the
  # stops are not merged so the transfer must be duplicated. Create a copy
  # of the Stop objects to add to the schedules.
  self.fm.a_schedule.AddStopObject(transitfeed.Stop(field_dict=stop0))
  self.fm.a_schedule.AddStopObject(transitfeed.Stop(field_dict=stop1a))
  self.fm.a_schedule.AddTransferObject(
  transitfeed.Transfer(from_stop_id="0", to_stop_id="1"))
  self.fm.b_schedule.AddStopObject(transitfeed.Stop(field_dict=stop0))
  self.fm.b_schedule.AddStopObject(transitfeed.Stop(field_dict=stop1b))
  self.fm.b_schedule.AddTransferObject(
  transitfeed.Transfer(from_stop_id="0", to_stop_id="1"))
  self.fm.AddMerger(merge.StopMerger(self.fm))
  self.fm.AddMerger(merge.TransferMerger(self.fm))
  self.fm.MergeSchedules()
 
  transfers = self.fm.merged_schedule.GetTransferList()
  self.assertEquals(2, len(transfers))
  self.assertEquals("0", transfers[0].from_stop_id)
  self.assertEquals("0", transfers[1].from_stop_id)
  # transfers are not ordered so allow the migrated to_stop_id values to
  # appear in either order.
  def MergedScheduleStopName(stop_id):
  return self.fm.merged_schedule.GetStop(stop_id).stop_name
  if MergedScheduleStopName(transfers[0].to_stop_id) == "1a":
  self.assertEquals("1b", MergedScheduleStopName(transfers[1].to_stop_id))
  else:
  self.assertEquals("1b", MergedScheduleStopName(transfers[0].to_stop_id))
  self.assertEquals("1a", MergedScheduleStopName(transfers[1].to_stop_id))
 
  def testFromStopNotMerged(self):
  """When stops aren't merged transfer is duplicated."""
  self.accumulator.ExpectProblemClass(merge.SameIdButNotMerged)
  stop0 = transitfeed.Stop(lat=30.0, lng=30.0, name="0", stop_id="0")
  stop1a = transitfeed.Stop(lat=30.1, lng=30.1, name="1a", stop_id="1")
  stop1b = transitfeed.Stop(lat=30.1, lng=30.1, name="1b", stop_id="1")
 
  # a_schedule and b_schedule both have a transfer with from_stop_id=1 but the
  # stops are not merged so the transfer must be duplicated. Create a copy
  # of the Stop objects to add to the schedules.
  self.fm.a_schedule.AddStopObject(transitfeed.Stop(field_dict=stop0))
  self.fm.a_schedule.AddStopObject(transitfeed.Stop(field_dict=stop1a))
  self.fm.a_schedule.AddTransferObject(
  transitfeed.Transfer(from_stop_id="1", to_stop_id="0"))
  self.fm.b_schedule.AddStopObject(transitfeed.Stop(field_dict=stop0))
  self.fm.b_schedule.AddStopObject(transitfeed.Stop(field_dict=stop1b))
  self.fm.b_schedule.AddTransferObject(
  transitfeed.Transfer(from_stop_id="1", to_stop_id="0"))
  self.fm.AddMerger(merge.StopMerger(self.fm))
  self.fm.AddMerger(merge.TransferMerger(self.fm))
  self.fm.MergeSchedules()
 
  transfers = self.fm.merged_schedule.GetTransferList()
  self.assertEquals(2, len(transfers))
  self.assertEquals("0", transfers[0].to_stop_id)
  self.assertEquals("0", transfers[1].to_stop_id)
  # transfers are not ordered so allow the migrated from_stop_id values to
  # appear in either order.
  def MergedScheduleStopName(stop_id):
  return self.fm.merged_schedule.GetStop(stop_id).stop_name
  if MergedScheduleStopName(transfers[0].from_stop_id) == "1a":
  self.assertEquals("1b", MergedScheduleStopName(transfers[1].from_stop_id))
  else:
  self.assertEquals("1b", MergedScheduleStopName(transfers[0].from_stop_id))
  self.assertEquals("1a", MergedScheduleStopName(transfers[1].from_stop_id))
 
 
  class TestExceptionProblemAccumulator(util.TestCase):
 
  def setUp(self):
  self.dataset_merger = merge.TripMerger(None)
 
  def testRaisesErrors(self):
  accumulator = transitfeed.ExceptionProblemAccumulator()
  problem_reporter = merge.MergeProblemReporter(accumulator)
  self.assertRaises(merge.CalendarsNotDisjoint,
  problem_reporter.CalendarsNotDisjoint,
  self.dataset_merger)
 
  def testNoRaiseWarnings(self):
  accumulator = transitfeed.ExceptionProblemAccumulator()
  problem_reporter = merge.MergeProblemReporter(accumulator)
  problem_reporter.MergeNotImplemented(self.dataset_merger)
 
  def testRaiseWarnings(self):
  accumulator = transitfeed.ExceptionProblemAccumulator(True)
  problem_reporter = merge.MergeProblemReporter(accumulator)
  self.assertRaises(merge.MergeNotImplemented,
  problem_reporter.MergeNotImplemented,
  self.dataset_merger)
 
 
  class TestHTMLProblemAccumulator(util.TestCase):
 
  def setUp(self):
  self.accumulator = merge.HTMLProblemAccumulator()
  self.problem_reporter = merge.MergeProblemReporter(self.accumulator)
  a_schedule = transitfeed.Schedule()
  b_schedule = transitfeed.Schedule()
  merged_schedule = transitfeed.Schedule()
  self.feed_merger = merge.FeedMerger(a_schedule, b_schedule,
  merged_schedule,
  self.problem_reporter)
  self.dataset_merger = merge.TripMerger(None)
 
  def testGeneratesSomeHTML(self):
  self.problem_reporter.CalendarsNotDisjoint(self.dataset_merger)
  self.problem_reporter.MergeNotImplemented(self.dataset_merger)
  self.problem_reporter.FareRulesBroken(self.dataset_merger)
  self.problem_reporter.SameIdButNotMerged(self.dataset_merger,
  'test', 'unknown reason')
 
  output_file = StringIO.StringIO()
  old_feed_path = '/path/to/old/feed'
  new_feed_path = '/path/to/new/feed'
  merged_feed_path = '/path/to/merged/feed'
  self.accumulator.WriteOutput(output_file, self.feed_merger,
  old_feed_path, new_feed_path,
  merged_feed_path)
 
  html = output_file.getvalue()
  self.assert_(html.startswith('<html>'))
  self.assert_(html.endswith('</html>'))
 
 
  class MergeInSubprocessTestCase(util.TempDirTestCaseBase):
  def CopyAndModifyTestData(self, zip_path, modify_file, old, new):
  """Return path of zip_path copy with old replaced by new in modify_file."""
  zipfile_mem = StringIO.StringIO(open(zip_path, 'rb').read())
  old_zip = zipfile.ZipFile(zipfile_mem, 'r')
 
  content_dict = self.ConvertZipToDict(old_zip)
  content_dict[modify_file] = content_dict[modify_file].replace(old, new)
  new_zipfile_mem = self.ConvertDictToZip(content_dict)
 
  new_zip_path = os.path.join(self.tempdirpath, "modified.zip")
  open(new_zip_path, 'wb').write(new_zipfile_mem.getvalue())
  return new_zip_path
 
  def testCrashHandler(self):
  (out, err) = self.CheckCallWithPath(
  [self.GetPath('merge.py'), '--no_browser',
  'IWantMyCrash', 'file2', 'fileout.zip'],
  expected_retcode=127)
  self.assertMatchesRegex(r'Yikes', out)
  crashout = open('transitfeedcrash.txt').read()
  self.assertMatchesRegex(r'For testing the merge crash handler', crashout)
 
  def testMergeBadCommandLine(self):
  (out, err) = self.CheckCallWithPath(
  [self.GetPath('merge.py'), '--no_browser'],
  expected_retcode=2)
  self.assertFalse(out)
  self.assertMatchesRegex(r'command line arguments', err)
  self.assertFalse(os.path.exists('transitfeedcrash.txt'))
 
  def testMergeWithWarnings(self):
  # Make a copy of good_feed.zip which is not active until 20110101. This
  # avoids adding another test/data file. good_feed.zip needs to remain error
  # free so it can't start in the future.
  future_good_feed = self.CopyAndModifyTestData(
  self.GetPath('test/data/good_feed.zip'), 'calendar.txt',
  '20070101', '20110101')
  (out, err) = self.CheckCallWithPath(
  [self.GetPath('merge.py'), '--no_browser',
  self.GetPath('test/data/unused_stop'),
  future_good_feed,
  os.path.join(self.tempdirpath, 'merged-warnings.zip')],
  expected_retcode=0)
 
  def testMergeWithErrors(self):
  # Make a copy of good_feed.zip which is not active until 20110101. This
  # avoids adding another test/data file. good_feed.zip needs to remain error
  # free so it can't start in the future.
  future_good_feed = self.CopyAndModifyTestData(
  self.GetPath('test/data/good_feed.zip'), 'calendar.txt',
  '20070101', '20110101')
  (out, err) = self.CheckCallWithPath(
  [self.GetPath('merge.py'), '--no_browser',
  self.GetPath('test/data/unused_stop'),
  future_good_feed],
  expected_retcode=2)
 
 
  if __name__ == '__main__':
  unittest.main()
 
  #!/usr/bin/python2.4
  #
  # Copyright (C) 2007 Google Inc.
  #
  # Licensed under the Apache License, Version 2.0 (the "License");
  # you may not use this file except in compliance with the License.
  # You may obtain a copy of the License at
  #
  # http://www.apache.org/licenses/LICENSE-2.0
  #
  # Unless required by applicable law or agreed to in writing, software
  # distributed under the License is distributed on an "AS IS" BASIS,
  # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  # See the License for the specific language governing permissions and
  # limitations under the License.
 
  """Tests for transitfeed.shapelib.py"""
 
  __author__ = 'chris.harrelson.code@gmail.com (Chris Harrelson)'
 
  import math
  from transitfeed import shapelib
  from transitfeed.shapelib import Point
  from transitfeed.shapelib import Poly
  from transitfeed.shapelib import PolyCollection
  from transitfeed.shapelib import PolyGraph
  import unittest
  import util
 
 
  def formatPoint(p, precision=12):
  formatString = "(%%.%df, %%.%df, %%.%df)" % (precision, precision, precision)
  return formatString % (p.x, p.y, p.z)
 
 
  def formatPoints(points):
  return "[%s]" % ", ".join([formatPoint(p, precision=4) for p in points])
 
 
  class ShapeLibTestBase(util.TestCase):
  def assertApproxEq(self, a, b):
  self.assertAlmostEqual(a, b, 8)
 
  def assertPointApproxEq(self, a, b):
  try:
  self.assertApproxEq(a.x, b.x)
  self.assertApproxEq(a.y, b.y)
  self.assertApproxEq(a.z, b.z)
  except AssertionError:
  print 'ERROR: %s != %s' % (formatPoint(a), formatPoint(b))
  raise
 
  def assertPointsApproxEq(self, points1, points2):
  try:
  self.assertEqual(len(points1), len(points2))
  except AssertionError:
  print "ERROR: %s != %s" % (formatPoints(points1), formatPoints(points2))
  raise
  for i in xrange(len(points1)):
  try:
  self.assertPointApproxEq(points1[i], points2[i])
  except AssertionError:
  print ('ERROR: points not equal in position %d\n%s != %s'
  % (i, formatPoints(points1), formatPoints(points2)))
  raise
 
 
  class TestPoints(ShapeLibTestBase):
  def testPoints(self):
  p = Point(1, 1, 1)
 
  self.assertApproxEq(p.DotProd(p), 3)
 
  self.assertApproxEq(p.Norm2(), math.sqrt(3))
 
  self.assertPointApproxEq(Point(1.5, 1.5, 1.5),
  p.Times(1.5))
 
  norm = 1.7320508075688772
  self.assertPointApproxEq(p.Normalize(),
  Point(1 / norm,
  1 / norm,
  1 / norm))
 
  p2 = Point(1, 0, 0)
  self.assertPointApproxEq(p2, p2.Normalize())
 
  def testCrossProd(self):
  p1 = Point(1, 0, 0).Normalize()
  p2 = Point(0, 1 ,0).Normalize()
  p1_cross_p2 = p1.CrossProd(p2)
  self.assertApproxEq(p1_cross_p2.x, 0)
  self.assertApproxEq(p1_cross_p2.y, 0)
  self.assertApproxEq(p1_cross_p2.z, 1)
 
  def testRobustCrossProd(self):
  p1 = Point(1, 0, 0)
  p2 = Point(1, 0, 0)
  self.assertPointApproxEq(Point(0, 0, 0),
  p1.CrossProd(p2))
  # only needs to be an arbitrary vector perpendicular to (1, 0, 0)
  self.assertPointApproxEq(
  Point(0.000000000000000, -0.998598452020993, 0.052925717957113),
  p1.RobustCrossProd(p2))
 
  def testS2LatLong(self):
  point = Point.FromLatLng(30, 40)
  self.assertPointApproxEq(Point(0.663413948169,
  0.556670399226,
  0.5), point)
  (lat, lng) = point.ToLatLng()
  self.assertApproxEq(30, lat)
  self.assertApproxEq(40, lng)
 
  def testOrtho(self):
  point = Point(1, 1, 1)
  ortho = point.Ortho()
  self.assertApproxEq(ortho.DotProd(point), 0)
 
  def testAngle(self):
  point1 = Point(1, 1, 0).Normalize()
  point2 = Point(0, 1, 0)
  self.assertApproxEq(45, point1.Angle(point2) * 360 / (2 * math.pi))
  self.assertApproxEq(point1.Angle(point2), point2.Angle(point1))
 
  def testGetDistanceMeters(self):
  point1 = Point.FromLatLng(40.536895,-74.203033)
  point2 = Point.FromLatLng(40.575239,-74.112825)
  self.assertApproxEq(8732.623770873237,
  point1.GetDistanceMeters(point2))
 
 
  class TestClosestPoint(ShapeLibTestBase):
  def testGetClosestPoint(self):
  x = Point(1, 1, 0).Normalize()
  a = Point(1, 0, 0)
  b = Point(0, 1, 0)
 
  closest = shapelib.GetClosestPoint(x, a, b)
  self.assertApproxEq(0.707106781187, closest.x)
  self.assertApproxEq(0.707106781187, closest.y)
  self.assertApproxEq(0.0, closest.z)
 
 
  class TestPoly(ShapeLibTestBase):
  def testGetClosestPointShape(self):
  poly = Poly()
 
  poly.AddPoint(Point(1, 1, 0).Normalize())
  self.assertPointApproxEq(Point(
  0.707106781187, 0.707106781187, 0), poly.GetPoint(0))
 
  point = Point(0, 1, 1).Normalize()
  self.assertPointApproxEq(Point(1, 1, 0).Normalize(),
  poly.GetClosestPoint(point)[0])
 
  poly.AddPoint(Point(0, 1, 1).Normalize())
 
  self.assertPointApproxEq(
  Point(0, 1, 1).Normalize(),
  poly.GetClosestPoint(point)[0])
 
  def testCutAtClosestPoint(self):
  poly = Poly()
  poly.AddPoint(Point(0, 1, 0).Normalize())
  poly.AddPoint(Point(0, 0.5, 0.5).Normalize())
  poly.AddPoint(Point(0, 0, 1).Normalize())
 
  (before, after) = \
  poly.CutAtClosestPoint(Point(0, 0.3, 0.7).Normalize())
 
  self.assert_(2 == before.GetNumPoints())
  self.assert_(2 == before.GetNumPoints())
  self.assertPointApproxEq(
  Point(0, 0.707106781187, 0.707106781187), before.GetPoint(1))
 
  self.assertPointApproxEq(
  Point(0, 0.393919298579, 0.919145030018), after.GetPoint(0))
 
  poly = Poly()
  poly.AddPoint(Point.FromLatLng(40.527035999999995, -74.191265999999999))
  poly.AddPoint(Point.FromLatLng(40.526859999999999, -74.191140000000004))
  poly.AddPoint(Point.FromLatLng(40.524681000000001, -74.189579999999992))
  poly.AddPoint(Point.FromLatLng(40.523128999999997, -74.188467000000003))
  poly.AddPoint(Point.FromLatLng(40.523054999999999, -74.188676000000001))
  pattern = Poly()
  pattern.AddPoint(Point.FromLatLng(40.52713,
  -74.191146000000003))
  self.assertApproxEq(14.564268281551, pattern.GreedyPolyMatchDist(poly))
 
  def testMergePolys(self):
  poly1 = Poly(name="Foo")
  poly1.AddPoint(Point(0, 1, 0).Normalize())
  poly1.AddPoint(Point(0, 0.5, 0.5).Normalize())
  poly1.AddPoint(Point(0, 0, 1).Normalize())
  poly1.AddPoint(Point(1, 1, 1).Normalize())
 
  poly2 = Poly()
  poly3 = Poly(name="Bar")
  poly3.AddPoint(Point(1, 1, 1).Normalize())
  poly3.AddPoint(Point(2, 0.5, 0.5).Normalize())
 
  merged1 = Poly.MergePolys([poly1, poly2])
  self.assertPointsApproxEq(poly1.GetPoints(), merged1.GetPoints())
  self.assertEqual("Foo;", merged1.GetName())
 
  merged2 = Poly.MergePolys([poly2, poly3])
  self.assertPointsApproxEq(poly3.GetPoints(), merged2.GetPoints())
  self.assertEqual(";Bar", merged2.GetName())
 
  merged3 = Poly.MergePolys([poly1, poly2, poly3], merge_point_threshold=0)
  mergedPoints = poly1.GetPoints()[:]
  mergedPoints.append(poly3.GetPoint(-1))
  self.assertPointsApproxEq(mergedPoints, merged3.GetPoints())
  self.assertEqual("Foo;;Bar", merged3.GetName())
 
  merged4 = Poly.MergePolys([poly2])
  self.assertEqual("", merged4.GetName())
  self.assertEqual(0, merged4.GetNumPoints())
 
  # test merging two nearby points
  newPoint = poly1.GetPoint(-1).Plus(Point(0.000001, 0, 0)).Normalize()
  poly1.AddPoint(newPoint)
  distance = poly1.GetPoint(-1).GetDistanceMeters(poly3.GetPoint(0))
  self.assertTrue(distance <= 10)
  self.assertTrue(distance > 5)
 
  merged5 = Poly.MergePolys([poly1, poly2, poly3], merge_point_threshold=10)
  mergedPoints = poly1.GetPoints()[:]
  mergedPoints.append(poly3.GetPoint(-1))
  self.assertPointsApproxEq(mergedPoints, merged5.GetPoints())
  self.assertEqual("Foo;;Bar", merged5.GetName())
 
  merged6 = Poly.MergePolys([poly1, poly2, poly3], merge_point_threshold=5)
  mergedPoints = poly1.GetPoints()[:]
  mergedPoints += poly3.GetPoints()
  self.assertPointsApproxEq(mergedPoints, merged6.GetPoints())
  self.assertEqual("Foo;;Bar", merged6.GetName())
 
  def testReversed(self):
  p1 = Point(1, 0, 0).Normalize()
  p2 = Point(0, 0.5, 0.5).Normalize()
  p3 = Point(0.3, 0.8, 0.5).Normalize()
  poly1 = Poly([p1, p2, p3])
  self.assertPointsApproxEq([p3, p2, p1], poly1.Reversed().GetPoints())
 
  def testLengthMeters(self):
  p1 = Point(1, 0, 0).Normalize()
  p2 = Point(0, 0.5, 0.5).Normalize()
  p3 = Point(0.3, 0.8, 0.5).Normalize()
  poly0 = Poly([p1])
  poly1 = Poly([p1, p2])
  poly2 = Poly([p1, p2, p3])
  try:
  poly0.LengthMeters()
  self.fail("Should have thrown AssertionError")
  except AssertionError:
  pass
 
  p1_p2 = p1.GetDistanceMeters(p2)
  p2_p3 = p2.GetDistanceMeters(p3)
  self.assertEqual(p1_p2, poly1.LengthMeters())
  self.assertEqual(p1_p2 + p2_p3, poly2.LengthMeters())
  self.assertEqual(p1_p2 + p2_p3, poly2.Reversed().LengthMeters())
 
 
  class TestCollection(ShapeLibTestBase):
  def testPolyMatch(self):
  poly = Poly()
  poly.AddPoint(Point(0, 1, 0).Normalize())
  poly.AddPoint(Point(0, 0.5, 0.5).Normalize())
  poly.AddPoint(Point(0, 0, 1).Normalize())
 
  collection = PolyCollection()
  collection.AddPoly(poly)
  match = collection.FindMatchingPolys(Point(0, 1, 0),
  Point(0, 0, 1))
  self.assert_(len(match) == 1 and match[0] == poly)
 
  match = collection.FindMatchingPolys(Point(0, 1, 0),
  Point(0, 1, 0))
  self.assert_(len(match) == 0)
 
  poly = Poly()
  poly.AddPoint(Point.FromLatLng(45.585212,-122.586136))
  poly.AddPoint(Point.FromLatLng(45.586654,-122.587595))
  collection = PolyCollection()
  collection.AddPoly(poly)
 
  match = collection.FindMatchingPolys(
  Point.FromLatLng(45.585212,-122.586136),
  Point.FromLatLng(45.586654,-122.587595))
  self.assert_(len(match) == 1 and match[0] == poly)
 
  match = collection.FindMatchingPolys(
  Point.FromLatLng(45.585219,-122.586136),
  Point.FromLatLng(45.586654,-122.587595))
  self.assert_(len(match) == 1 and match[0] == poly)
 
  self.assertApproxEq(0.0, poly.GreedyPolyMatchDist(poly))
 
  match = collection.FindMatchingPolys(
  Point.FromLatLng(45.587212,-122.586136),
  Point.FromLatLng(45.586654,-122.587595))
  self.assert_(len(match) == 0)
 
 
  class TestGraph(ShapeLibTestBase):
  def testReconstructPath(self):
  p1 = Point(1, 0, 0).Normalize()
  p2 = Point(0, 0.5, 0.5).Normalize()
  p3 = Point(0.3, 0.8, 0.5).Normalize()
  poly1 = Poly([p1, p2])
  poly2 = Poly([p3, p2])
  came_from = {
  p2: (p1, poly1),
  p3: (p2, poly2)
  }
 
  graph = PolyGraph()
  reconstructed1 = graph._ReconstructPath(came_from, p1)
  self.assertEqual(0, reconstructed1.GetNumPoints())
 
  reconstructed2 = graph._ReconstructPath(came_from, p2)
  self.assertPointsApproxEq([p1, p2], reconstructed2.GetPoints())
 
  reconstructed3 = graph._ReconstructPath(came_from, p3)
  self.assertPointsApproxEq([p1, p2, p3], reconstructed3.GetPoints())
 
  def testShortestPath(self):
  p1 = Point(1, 0, 0).Normalize()
  p2 = Point(0, 0.5, 0.5).Normalize()
  p3 = Point(0.3, 0.8, 0.5).Normalize()
  p4 = Point(0.7, 0.7, 0.5).Normalize()
  poly1 = Poly([p1, p2, p3], "poly1")
  poly2 = Poly([p4, p3], "poly2")
  poly3 = Poly([p4, p1], "poly3")
  graph = PolyGraph()
  graph.AddPoly(poly1)
  graph.AddPoly(poly2)
  graph.AddPoly(poly3)
  path = graph.ShortestPath(p1, p4)
  self.assert_(path is not None)
  self.assertPointsApproxEq([p1, p4], path.GetPoints())
 
  path = graph.ShortestPath(p1, p3)
  self.assert_(path is not None)
  self.assertPointsApproxEq([p1, p4, p3], path.GetPoints())
 
  path = graph.ShortestPath(p3, p1)
  self.assert_(path is not None)
  self.assertPointsApproxEq([p3, p4, p1], path.GetPoints())
 
  def testFindShortestMultiPointPath(self):
  p1 = Point(1, 0, 0).Normalize()
  p2 = Point(0.5, 0.5, 0).Normalize()
  p3 = Point(0.5, 0.5, 0.1).Normalize()
  p4 = Point(0, 1, 0).Normalize()
  poly1 = Poly([p1, p2, p3], "poly1")
  poly2 = Poly([p4, p3], "poly2")
  poly3 = Poly([p4, p1], "poly3")
  graph = PolyGraph()
  graph.AddPoly(poly1)
  graph.AddPoly(poly2)
  graph.AddPoly(poly3)
  path = graph.FindShortestMultiPointPath([p1, p3, p4])
  self.assert_(path is not None)
  self.assertPointsApproxEq([p1, p2, p3, p4], path.GetPoints())
 
 
  if __name__ == '__main__':
  unittest.main()
 
  #!/usr/bin/python2.5
 
  # Copyright (C) 2007 Google Inc.
  #
  # Licensed under the Apache License, Version 2.0 (the "License");
  # you may not use this file except in compliance with the License.
  # You may obtain a copy of the License at
  #
  # http://www.apache.org/licenses/LICENSE-2.0
  #
  # Unless required by applicable law or agreed to in writing, software
  # distributed under the License is distributed on an "AS IS" BASIS,
  # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  # See the License for the specific language governing permissions and
  # limitations under the License.
 
  # Unit tests for the transitfeed module.
 
  import datetime
  from datetime import date
  import dircache
  import os.path
  import re
  import sys
  import tempfile
  import time
  import transitfeed
  import types
  import unittest
  import util
  from util import RecordingProblemAccumulator
  from StringIO import StringIO
  import zipfile
  import zlib
 
 
  def DataPath(path):
  here = os.path.dirname(__file__)
  return os.path.join(here, 'data', path)
 
  def GetDataPathContents():
  here = os.path.dirname(__file__)
  return dircache.listdir(os.path.join(here, 'data'))
 
 
  class ExceptionProblemReporterNoExpiration(transitfeed.ProblemReporter):
  """Ignores feed expiration problems.
 
  Use TestFailureProblemReporter in new code because it fails more cleanly, is
  easier to extend and does more thorough checking.
  """
 
  def __init__(self):
  accumulator = transitfeed.ExceptionProblemAccumulator(raise_warnings=True)
  transitfeed.ProblemReporter.__init__(self, accumulator)
 
  def ExpirationDate(self, expiration, context=None):
  pass # We don't want to give errors about our test data
 
 
  def GetTestFailureProblemReporter(test_case,
  ignore_types=("ExpirationDate",)):
  accumulator = TestFailureProblemAccumulator(test_case, ignore_types)
  problems = transitfeed.ProblemReporter(accumulator)
  return problems
 
 
  class TestFailureProblemAccumulator(transitfeed.ProblemAccumulatorInterface):
  """Causes a test failure immediately on any problem."""
  def __init__(self, test_case, ignore_types=("ExpirationDate",)):
  self.test_case = test_case
  self._ignore_types = ignore_types or set()
 
  def _Report(self, e):
  # These should never crash
  formatted_problem = e.FormatProblem()
  formatted_context = e.FormatContext()
  exception_class = e.__class__.__name__
  if exception_class in self._ignore_types:
  return
  self.test_case.fail(
  "%s: %s\n%s" % (exception_class, formatted_problem, formatted_context))
 
 
  class UnrecognizedColumnRecorder(transitfeed.ProblemReporter):
  """Keeps track of unrecognized column errors."""
  def __init__(self, test_case):
  self.accumulator = RecordingProblemAccumulator(test_case,
  ignore_types=("ExpirationDate",))
  self.column_errors = []
 
  def UnrecognizedColumn(self, file_name, column_name, context=None):
  self.column_errors.append((file_name, column_name))
 
 
  class RedirectStdOutTestCaseBase(util.TestCase):
  """Save stdout to the StringIO buffer self.this_stdout"""
  def setUp(self):
  self.saved_stdout = sys.stdout
  self.this_stdout = StringIO()
  sys.stdout = self.this_stdout
 
  def tearDown(self):
  sys.stdout = self.saved_stdout
  self.this_stdout.close()
 
 
  # ensure that there are no exceptions when attempting to load
  # (so that the validator won't crash)
  class NoExceptionTestCase(RedirectStdOutTestCaseBase):
  def runTest(self):
  for feed in GetDataPathContents():
  loader = transitfeed.Loader(DataPath(feed),
  problems=transitfeed.ProblemReporter(),
  extra_validation=True)
  schedule = loader.Load()
  schedule.Validate()
 
 
  class EndOfLineCheckerTestCase(util.TestCase):
  def setUp(self):
  self.accumulator = RecordingProblemAccumulator(self)
  self.problems = transitfeed.ProblemReporter(self.accumulator)
 
  def RunEndOfLineChecker(self, end_of_line_checker):
  # Iterating using for calls end_of_line_checker.next() until a
  # StopIteration is raised. EndOfLineChecker does the final check for a mix
  # of CR LF and LF ends just before raising StopIteration.
  for line in end_of_line_checker:
  pass
 
  def testInvalidLineEnd(self):
  f = transitfeed.EndOfLineChecker(StringIO("line1\r\r\nline2"),
  "<StringIO>",
  self.problems)
  self.RunEndOfLineChecker(f)
  e = self.accumulator.PopException("InvalidLineEnd")
  self.assertEqual(e.file_name, "<StringIO>")
  self.assertEqual(e.row_num, 1)
  self.assertEqual(e.bad_line_end, r"\r\r\n")
  self.accumulator.AssertNoMoreExceptions()
 
  def testInvalidLineEndToo(self):
  f = transitfeed.EndOfLineChecker(
  StringIO("line1\nline2\r\nline3\r\r\r\n"),
  "<StringIO>", self.problems)
  self.RunEndOfLineChecker(f)
  e = self.accumulator.PopException("InvalidLineEnd")
  self.assertEqual(e.file_name, "<StringIO>")
  self.assertEqual(e.row_num, 3)
  self.assertEqual(e.bad_line_end, r"\r\r\r\n")
  e = self.accumulator.PopException("OtherProblem")
  self.assertEqual(e.file_name, "<StringIO>")
  self.assertTrue(e.description.find("consistent line end") != -1)
  self.accumulator.AssertNoMoreExceptions()
 
  def testEmbeddedCr(self):
  f = transitfeed.EndOfLineChecker(
  StringIO("line1\rline1b"),
  "<StringIO>", self.problems)
  self.RunEndOfLineChecker(f)
  e = self.accumulator.PopException("OtherProblem")
  self.assertEqual(e.file_name, "<StringIO>")
  self.assertEqual(e.row_num, 1)
  self.assertEqual(e.FormatProblem(),
  "Line contains ASCII Carriage Return 0x0D, \\r")
  self.accumulator.AssertNoMoreExceptions()
 
  def testEmbeddedUtf8NextLine(self):
  f = transitfeed.EndOfLineChecker(
  StringIO("line1b\xc2\x85"),
  "<StringIO>", self.problems)
  self.RunEndOfLineChecker(f)
  e = self.accumulator.PopException("OtherProblem")
  self.assertEqual(e.file_name, "<StringIO>")
  self.assertEqual(e.row_num, 1)
  self.assertEqual(e.FormatProblem(),
  "Line contains Unicode NEXT LINE SEPARATOR U+0085")
  self.accumulator.AssertNoMoreExceptions()
 
  def testEndOfLineMix(self):
  f = transitfeed.EndOfLineChecker(
  StringIO("line1\nline2\r\nline3\nline4"),
  "<StringIO>", self.problems)
  self.RunEndOfLineChecker(f)
  e = self.accumulator.PopException("OtherProblem")
  self.assertEqual(e.file_name, "<StringIO>")
  self.assertEqual(e.FormatProblem(),
  "Found 1 CR LF \"\\r\\n\" line end (line 2) and "
  "2 LF \"\\n\" line ends (lines 1, 3). A file must use a "
  "consistent line end.")
  self.accumulator.AssertNoMoreExceptions()
 
  def testEndOfLineManyMix(self):
  f = transitfeed.EndOfLineChecker(
  StringIO("1\n2\n3\n4\n5\n6\n7\r\n8\r\n9\r\n10\r\n11\r\n"),
  "<StringIO>", self.problems)
  self.RunEndOfLineChecker(f)
  e = self.accumulator.PopException("OtherProblem")
  self.assertEqual(e.file_name, "<StringIO>")
  self.assertEqual(e.FormatProblem(),
  "Found 5 CR LF \"\\r\\n\" line ends (lines 7, 8, 9, 10, "
  "11) and 6 LF \"\\n\" line ends (lines 1, 2, 3, 4, 5, "
  "...). A file must use a consistent line end.")
  self.accumulator.AssertNoMoreExceptions()
 
  def testLoad(self):
  loader = transitfeed.Loader(
  DataPath("bad_eol.zip"), problems=self.problems, extra_validation=True)
  loader.Load()
 
  e = self.accumulator.PopException("OtherProblem")
  self.assertEqual(e.file_name, "calendar.txt")
  self.assertTrue(re.search(
  r"Found 1 CR LF.* \(line 2\) and 2 LF .*\(lines 1, 3\)",
  e.FormatProblem()))
 
  e = self.accumulator.PopException("InvalidLineEnd")
  self.assertEqual(e.file_name, "routes.txt")
  self.assertEqual(e.row_num, 5)
  self.assertTrue(e.FormatProblem().find(r"\r\r\n") != -1)
 
  e = self.accumulator.PopException("OtherProblem")
  self.assertEqual(e.file_name, "trips.txt")
  self.assertEqual(e.row_num, 1)
  self.assertTrue(re.search(
  r"contains ASCII Form Feed",
  e.FormatProblem()))
  # TODO(Tom): avoid this duplicate error for the same issue
  e = self.accumulator.PopException("CsvSyntax")
  self.assertEqual(e.row_num, 1)
  self.assertTrue(re.search(
  r"header row should not contain any space char",
  e.FormatProblem()))
 
  self.accumulator.AssertNoMoreExceptions()
 
 
  class LoadTestCase(util.TestCase):
  def setUp(self):
  self.accumulator = RecordingProblemAccumulator(self, ("ExpirationDate",))
  self.problems = transitfeed.ProblemReporter(self.accumulator)
 
  def Load(self, feed_name):
  loader = transitfeed.Loader(
  DataPath(feed_name), problems=self.problems, extra_validation=True)
  loader.Load()
 
  def ExpectInvalidValue(self, feed_name, column_name):
  self.Load(feed_name)
  self.accumulator.PopInvalidValue(column_name)
  self.accumulator.AssertNoMoreExceptions()
 
  def ExpectMissingFile(self, feed_name, file_name):
  self.Load(feed_name)
  e = self.accumulator.PopException("MissingFile")
  self.assertEqual(file_name, e.file_name)
  # Don't call AssertNoMoreExceptions() because a missing file causes
  # many errors.
 
 
  class LoadFromZipTestCase(util.TestCase):
  def runTest(self):
  loader = transitfeed.Loader(
  DataPath('good_feed.zip'),
  problems = GetTestFailureProblemReporter(self),
  extra_validation = True)
  loader.Load()
 
  # now try using Schedule.Load
  schedule = transitfeed.Schedule(
  problem_reporter=ExceptionProblemReporterNoExpiration())
  schedule.Load(DataPath('good_feed.zip'), extra_validation=True)
 
 
  class LoadAndRewriteFromZipTestCase(util.TestCase):
  def runTest(self):
  schedule = transitfeed.Schedule(
  problem_reporter=ExceptionProblemReporterNoExpiration())
  schedule.Load(DataPath('good_feed.zip'), extra_validation=True)
 
  # Finally see if write crashes
  schedule.WriteGoogleTransitFeed(tempfile.TemporaryFile())
 
 
  class LoadFromDirectoryTestCase(util.TestCase):
  def runTest(self):
  loader = transitfeed.Loader(
  DataPath('good_feed'),
  problems = GetTestFailureProblemReporter(self),
  extra_validation = True)
  loader.Load()
 
 
  class LoadUnknownFeedTestCase(util.TestCase):
  def runTest(self):
  feed_name = DataPath('unknown_feed')
  loader = transitfeed.Loader(
  feed_name,
  problems = ExceptionProblemReporterNoExpiration(),
  extra_validation = True)
  try:
  loader.Load()
  self.fail('FeedNotFound exception expected')
  except transitfeed.FeedNotFound, e:
  self.assertEqual(feed_name, e.feed_name)
 
  class LoadUnknownFormatTestCase(util.TestCase):
  def runTest(self):
  feed_name = DataPath('unknown_format.zip')
  loader = transitfeed.Loader(
  feed_name,
  problems = ExceptionProblemReporterNoExpiration(),
  extra_validation = True)
  try:
  loader.Load()
  self.fail('UnknownFormat exception expected')
  except transitfeed.UnknownFormat, e:
  self.assertEqual(feed_name, e.feed_name)
 
  class LoadUnrecognizedColumnsTestCase(util.TestCase):
  def runTest(self):
  problems = UnrecognizedColumnRecorder(self)
  loader = transitfeed.Loader(DataPath('unrecognized_columns'),
  problems=problems)
  loader.Load()
  found_errors = set(problems.column_errors)
  expected_errors = set([
  ('agency.txt', 'agency_lange'),
  ('stops.txt', 'stop_uri'),
  ('routes.txt', 'Route_Text_Color'),
  ('calendar.txt', 'leap_day'),
  ('calendar_dates.txt', 'leap_day'),
  ('trips.txt', 'sharpe_id'),
  ('stop_times.txt', 'shapedisttraveled'),
  ('stop_times.txt', 'drop_off_time'),
  ('fare_attributes.txt', 'transfer_time'),
  ('fare_rules.txt', 'source_id'),
  ('frequencies.txt', 'superfluous'),
  ('transfers.txt', 'to_stop')
  ])
 
  # Now make sure we got the unrecognized column errors that we expected.
  not_expected = found_errors.difference(expected_errors)
  self.failIf(not_expected, 'unexpected errors: %s' % str(not_expected))
  not_found = expected_errors.difference(found_errors)
  self.failIf(not_found, 'expected but not found: %s' % str(not_found))
 
  class LoadExtraCellValidationTestCase(LoadTestCase):
  """Check that the validation detects too many cells in a row."""
  def runTest(self):
  self.Load('extra_row_cells')
  e = self.accumulator.PopException("OtherProblem")
  self.assertEquals("routes.txt", e.file_name)
  self.assertEquals(4, e.row_num)
  self.accumulator.AssertNoMoreExceptions()
 
 
  class LoadMissingCellValidationTestCase(LoadTestCase):
  """Check that the validation detects missing cells in a row."""
  def runTest(self):
  self.Load('missing_row_cells')
  e = self.accumulator.PopException("OtherProblem")
  self.assertEquals("routes.txt", e.file_name)
  self.assertEquals(4, e.row_num)
  self.accumulator.AssertNoMoreExceptions()
 
  class LoadUnknownFileTestCase(util.TestCase):
  """Check that the validation detects unknown files."""
  def runTest(self):
  feed_name = DataPath('unknown_file')
  self.accumulator = RecordingProblemAccumulator(self, ("ExpirationDate",))
  self.problems = transitfeed.ProblemReporter(self.accumulator)
  loader = transitfeed.Loader(
  feed_name,
  problems = self.problems,
  extra_validation = True)
  loader.Load()
  e = self.accumulator.PopException('UnknownFile')
  self.assertEqual('frecuencias.txt', e.file_name)
  self.accumulator.AssertNoMoreExceptions()
 
  class LoadUTF8BOMTestCase(util.TestCase):
  def runTest(self):
  loader = transitfeed.Loader(
  DataPath('utf8bom'),
  problems = GetTestFailureProblemReporter(self),
  extra_validation = True)
  loader.Load()
 
 
  class LoadUTF16TestCase(util.TestCase):
  def runTest(self):
  # utf16 generated by `recode utf8..utf16 *'
  accumulator = transitfeed.ExceptionProblemAccumulator()
  problem_reporter = transitfeed.ProblemReporter(accumulator)
  loader = transitfeed.Loader(
  DataPath('utf16'),
  problems = problem_reporter,
  extra_validation = True)
  try:
  loader.Load()
  # TODO: make sure processing proceeds beyond the problem
  self.fail('FileFormat exception expected')
  except transitfeed.FileFormat, e:
  # make sure these don't raise an exception
  self.assertTrue(re.search(r'encoded in utf-16', e.FormatProblem()))
  e.FormatContext()
 
 
  class LoadNullTestCase(util.TestCase):
  def runTest(self):
  accumulator = transitfeed.ExceptionProblemAccumulator()
  problem_reporter = transitfeed.ProblemReporter(accumulator)
  loader = transitfeed.Loader(
  DataPath('contains_null'),
  problems = problem_reporter,
  extra_validation = True)
  try:
  loader.Load()
  self.fail('FileFormat exception expected')
  except transitfeed.FileFormat, e:
  self.assertTrue(re.search(r'contains a null', e.FormatProblem()))
  # make sure these don't raise an exception
  e.FormatContext()
 
 
  class ProblemReporterTestCase(RedirectStdOutTestCaseBase):
  # Unittest for problem reporter
  def testContextWithBadUnicodeProblem(self):
  pr = transitfeed.ProblemReporter()
  # Context has valid unicode values
  pr.SetFileContext('filename.foo', 23,
  [u'Andr\202', u'Person \uc720 foo', None],
  [u'1\202', u'2\202', u'3\202'])
  pr.OtherProblem('test string')
  pr.OtherProblem(u'\xff\xfe\x80\x88')
  # Invalid ascii and utf-8. encode('utf-8') and decode('utf-8') will fail
  # for this value
  pr.OtherProblem('\xff\xfe\x80\x88')
  self.assertTrue(re.search(r"test string", self.this_stdout.getvalue()))
  self.assertTrue(re.search(r"filename.foo:23", self.this_stdout.getvalue()))
 
  def testNoContextWithBadUnicode(self):
  pr = transitfeed.ProblemReporter()
  pr.OtherProblem('test string')
  pr.OtherProblem(u'\xff\xfe\x80\x88')
  # Invalid ascii and utf-8. encode('utf-8') and decode('utf-8') will fail
  # for this value
  pr.OtherProblem('\xff\xfe\x80\x88')
  self.assertTrue(re.search(r"test string", self.this_stdout.getvalue()))
 
  def testBadUnicodeContext(self):
  pr = transitfeed.ProblemReporter()
  pr.SetFileContext('filename.foo', 23,
  [u'Andr\202', 'Person \xff\xfe\x80\x88 foo', None],
  [u'1\202', u'2\202', u'3\202'])
  pr.OtherProblem("help, my context isn't utf-8!")
  self.assertTrue(re.search(r"help, my context", self.this_stdout.getvalue()))
  self.assertTrue(re.search(r"filename.foo:23", self.this_stdout.getvalue()))
 
  def testLongWord(self):
  # Make sure LineWrap doesn't puke
  pr = transitfeed.ProblemReporter()
  pr.OtherProblem('1111untheontuhoenuthoentuhntoehuontehuntoehuntoehunto'
  '2222oheuntheounthoeunthoeunthoeuntheontuheontuhoue')
  self.assertTrue(re.search(r"1111.+2222", self.this_stdout.getvalue()))
 
 
  class BadProblemReporterTestCase(RedirectStdOutTestCaseBase):
  """Make sure ProblemReporter doesn't crash when given bad unicode data and
  does find some error"""
  # tom.brown.code-utf8_weaknesses fixed a bug with problem reporter and bad
  # utf-8 strings
  def runTest(self):
  loader = transitfeed.Loader(
  DataPath('bad_utf8'),
  problems = transitfeed.ProblemReporter(),
  extra_validation = True)
  loader.Load()
  # raises exception if not found
  self.this_stdout.getvalue().index('Invalid value')
 
 
  class BadUtf8TestCase(LoadTestCase):
  def runTest(self):
  self.Load('bad_utf8')
  self.accumulator.PopException("UnrecognizedColumn")
  self.accumulator.PopInvalidValue("agency_name", "agency.txt")
  self.accumulator.PopInvalidValue("stop_name", "stops.txt")
  self.accumulator.PopInvalidValue("route_short_name", "routes.txt")
  self.accumulator.PopInvalidValue("route_long_name", "routes.txt")
  self.accumulator.PopInvalidValue("trip_headsign", "trips.txt")
  self.accumulator.PopInvalidValue("stop_headsign", "stop_times.txt")
  self.accumulator.AssertNoMoreExceptions()
 
 
  class LoadMissingAgencyTestCase(LoadTestCase):
  def runTest(self):
  self.ExpectMissingFile('missing_agency', 'agency.txt')
 
 
  class LoadMissingStopsTestCase(LoadTestCase):
  def runTest(self):
  self.ExpectMissingFile('missing_stops', 'stops.txt')
 
 
  class LoadMissingRoutesTestCase(LoadTestCase):
  def runTest(self):
  self.ExpectMissingFile('missing_routes', 'routes.txt')
 
 
  class LoadMissingTripsTestCase(LoadTestCase):
  def runTest(self):
  self.ExpectMissingFile('missing_trips', 'trips.txt')
 
 
  class LoadMissingStopTimesTestCase(LoadTestCase):
  def runTest(self):
  self.ExpectMissingFile('missing_stop_times', 'stop_times.txt')
 
 
  class LoadMissingCalendarTestCase(LoadTestCase):
  def runTest(self):
  self.ExpectMissingFile('missing_calendar', 'calendar.txt')
 
 
  class EmptyFileTestCase(util.TestCase):
  def runTest(self):
  loader = transitfeed.Loader(
  DataPath('empty_file'),
  problems = ExceptionProblemReporterNoExpiration(),
  extra_validation = True)
  try:
  loader.Load()
  self.fail('EmptyFile exception expected')
  except transitfeed.EmptyFile, e:
  self.assertEqual('agency.txt', e.file_name)
 
 
  class MissingColumnTestCase(util.TestCase):
  def runTest(self):
  loader = transitfeed.Loader(
  DataPath('missing_column'),
  problems = ExceptionProblemReporterNoExpiration(),
  extra_validation = True)
  try:
  loader.Load()
  self.fail('MissingColumn exception expected')
  except transitfeed.MissingColumn, e:
  self.assertEqual('agency.txt', e.file_name)
  self.assertEqual('agency_name', e.column_name)
 
 
  class ZeroBasedStopSequenceTestCase(LoadTestCase):
  def runTest(self):
  self.ExpectInvalidValue('negative_stop_sequence', 'stop_sequence')
 
 
  class DuplicateStopTestCase(util.TestCase):
  def runTest(self):
  schedule = transitfeed.Schedule(
  problem_reporter=ExceptionProblemReporterNoExpiration())
  try:
  schedule.Load(DataPath('duplicate_stop'), extra_validation=True)
  self.fail('OtherProblem exception expected')
  except transitfeed.OtherProblem:
  pass
 
  class DuplicateStopSequenceTestCase(util.TestCase):
  def runTest(self):
  accumulator = RecordingProblemAccumulator(self, ("ExpirationDate",
  "NoServiceExceptions"))
  problems = transitfeed.ProblemReporter(accumulator)
  schedule = transitfeed.Schedule(problem_reporter=problems)
  schedule.Load(DataPath('duplicate_stop_sequence'), extra_validation=True)
  e = accumulator.PopException('InvalidValue')
  self.assertEqual('stop_sequence', e.column_name)
  accumulator.AssertNoMoreExceptions()
 
 
  class MissingEndpointTimesTestCase(util.TestCase):
  def runTest(self):
  problems = ExceptionProblemReporterNoExpiration()
  schedule = transitfeed.Schedule(problem_reporter=problems)
  try:
  schedule.Load(DataPath('missing_endpoint_times'), extra_validation=True)
  self.fail('InvalidValue exception expected')
  except transitfeed.InvalidValue, e:
  self.assertEqual('departure_time', e.column_name)
  self.assertEqual('', e.value)
 
 
  class DuplicateScheduleIDTestCase(util.TestCase):
  def runTest(self):
  schedule = transitfeed.Schedule(
  problem_reporter=ExceptionProblemReporterNoExpiration())
  try:
  schedule.Load(DataPath('duplicate_schedule_id'), extra_validation=True)
  self.fail('DuplicateID exception expected')
  except transitfeed.DuplicateID:
  pass
 
  class OverlappingBlockSchedule(transitfeed.Schedule):
  """Special Schedule subclass that counts the number of calls to
  GetServicePeriod() so we can verify service period overlap calculation
  caching"""
 
  _get_service_period_call_count = 0
 
  def GetServicePeriod(self, service_id):
  self._get_service_period_call_count += 1
  return transitfeed.Schedule.GetServicePeriod(self,service_id)
 
  def GetServicePeriodCallCount(self):
  return self._get_service_period_call_count
 
  class OverlappingBlockTripsTestCase(util.TestCase):
  """Builds a simple schedule for testing of overlapping block trips"""
 
  def setUp(self):
  self.accumulator = RecordingProblemAccumulator(
  self, ("ExpirationDate", "NoServiceExceptions"))
  self.problems = transitfeed.ProblemReporter(self.accumulator)
 
  schedule = OverlappingBlockSchedule(problem_reporter=self.problems)
  schedule.AddAgency("Demo Transit Authority", "http://dta.org",
  "America/Los_Angeles")
 
  sp1 = transitfeed.ServicePeriod("SID1")
  sp1.SetWeekdayService(True)
  sp1.SetStartDate("20070605")
  sp1.SetEndDate("20080605")
  schedule.AddServicePeriodObject(sp1)
 
  sp2 = transitfeed.ServicePeriod("SID2")
  sp2.SetDayOfWeekHasService(0)
  sp2.SetDayOfWeekHasService(2)
  sp2.SetDayOfWeekHasService(4)
  sp2.SetStartDate("20070605")
  sp2.SetEndDate("20080605")
  schedule.AddServicePeriodObject(sp2)
 
  sp3 = transitfeed.ServicePeriod("SID3")
  sp3.SetWeekendService(True)
  sp3.SetStartDate("20070605")
  sp3.SetEndDate("20080605")
  schedule.AddServicePeriodObject(sp3)
 
  self.stop1 = schedule.AddStop(lng=-116.75167,
  lat=36.915682,
  name="Stagecoach Hotel & Casino",
  stop_id="S1")
 
  self.stop2 = schedule.AddStop(lng=-116.76218,
  lat=36.905697,
  name="E Main St / S Irving St",
  stop_id="S2")
 
  self.route = schedule.AddRoute("", "City", "Bus", route_id="CITY")
 
  self.schedule = schedule
  self.sp1 = sp1
  self.sp2 = sp2
  self.sp3 = sp3
 
  def testNoOverlap(self):
 
  schedule, route, sp1 = self.schedule, self.route, self.sp1
 
  trip1 = route.AddTrip(schedule, service_period=sp1, trip_id="CITY1")
  trip1.block_id = "BLOCK"
  trip1.AddStopTime(self.stop1, stop_time="6:00:00")
  trip1.AddStopTime(self.stop2, stop_time="6:30:00")
 
  trip2 = route.AddTrip(schedule, service_period=sp1, trip_id="CITY2")
  trip2.block_id = "BLOCK"
  trip2.AddStopTime(self.stop2, stop_time="6:30:00")
  trip2.AddStopTime(self.stop1, stop_time="7:00:00")
 
  schedule.Validate(self.problems)
 
  self.accumulator.AssertNoMoreExceptions()
 
  def testOverlapSameServicePeriod(self):
 
  schedule, route, sp1 = self.schedule, self.route, self.sp1
 
  trip1 = route.AddTrip(schedule, service_period=sp1, trip_id="CITY1")
  trip1.block_id = "BLOCK"
  trip1.AddStopTime(self.stop1, stop_time="6:00:00")
  trip1.AddStopTime(self.stop2, stop_time="6:30:00")
 
  trip2 = route.AddTrip(schedule, service_period=sp1, trip_id="CITY2")
  trip2.block_id = "BLOCK"
  trip2.AddStopTime(self.stop2, stop_time="6:20:00")
  trip2.AddStopTime(self.stop1, stop_time="6:50:00")
 
  schedule.Validate(self.problems)
 
  e = self.accumulator.PopException('OverlappingTripsInSameBlock')
  self.assertEqual(e.trip_id1, 'CITY1')
  self.assertEqual(e.trip_id2, 'CITY2')
  self.assertEqual(e.block_id, 'BLOCK')
 
  self.accumulator.AssertNoMoreExceptions()
 
  def testOverlapDifferentServicePeriods(self):
 
  schedule, route, sp1, sp2 = self.schedule, self.route, self.sp1, self.sp2
 
  trip1 = route.AddTrip(schedule, service_period=sp1, trip_id="CITY1")
  trip1.block_id = "BLOCK"
  trip1.AddStopTime(self.stop1, stop_time="6:00:00")
  trip1.AddStopTime(self.stop2, stop_time="6:30:00")
 
  trip2 = route.AddTrip(schedule, service_period=sp2, trip_id="CITY2")
  trip2.block_id = "BLOCK"
  trip2.AddStopTime(self.stop2, stop_time="6:20:00")
  trip2.AddStopTime(self.stop1, stop_time="6:50:00")
 
  trip3 = route.AddTrip(schedule, service_period=sp1, trip_id="CITY3")
  trip3.block_id = "BLOCK"
  trip3.AddStopTime(self.stop1, stop_time="7:00:00")
  trip3.AddStopTime(self.stop2, stop_time="7:30:00")
 
  trip4 = route.AddTrip(schedule, service_period=sp2, trip_id="CITY4")
  trip4.block_id = "BLOCK"
  trip4.AddStopTime(self.stop2, stop_time="7:20:00")
  trip4.AddStopTime(self.stop1, stop_time="7:50:00")
 
  schedule.Validate(self.problems)
 
  e = self.accumulator.PopException('OverlappingTripsInSameBlock')
  self.assertEqual(e.trip_id1, 'CITY1')
  self.assertEqual(e.trip_id2, 'CITY2')
  self.assertEqual(e.block_id, 'BLOCK')
 
  e = self.accumulator.PopException('OverlappingTripsInSameBlock')
  self.assertEqual(e.trip_id1, 'CITY3')
  self.assertEqual(e.trip_id2, 'CITY4')
  self.assertEqual(e.block_id, 'BLOCK')
 
  self.accumulator.AssertNoMoreExceptions()
 
  # If service period overlap calculation caching is working correctly,
  # we expect only two calls to GetServicePeriod(), one each for sp1 and
  # sp2, as oppossed four calls total for the four overlapping trips
  self.assertEquals(2,schedule.GetServicePeriodCallCount())
 
  def testNoOverlapDifferentServicePeriods(self):
 
  schedule, route, sp1, sp3 = self.schedule, self.route, self.sp1, self.sp3
 
  trip1 = route.AddTrip(schedule, service_period=sp1, trip_id="CITY1")
  trip1.block_id = "BLOCK"
  trip1.AddStopTime(self.stop1, stop_time="6:00:00")
  trip1.AddStopTime(self.stop2, stop_time="6:30:00")
 
  trip2 = route.AddTrip(schedule, service_period=sp3, trip_id="CITY2")
  trip2.block_id = "BLOCK"
  trip2.AddStopTime(self.stop2, stop_time="6:20:00")
  trip2.AddStopTime(self.stop1, stop_time="6:50:00")
 
  schedule.Validate(self.problems)
 
  self.accumulator.AssertNoMoreExceptions()
 
  class ColorLuminanceTestCase(util.TestCase):
  def runTest(self):
  self.assertEqual(transitfeed.ColorLuminance('000000'), 0,
  "ColorLuminance('000000') should be zero")
  self.assertEqual(transitfeed.ColorLuminance('FFFFFF'), 255,
  "ColorLuminance('FFFFFF') should be 255")
  RGBmsg = ("ColorLuminance('RRGGBB') should be "
  "0.299*<Red> + 0.587*<Green> + 0.114*<Blue>")
  decimal_places_tested = 8
  self.assertAlmostEqual(transitfeed.ColorLuminance('640000'), 29.9,
  decimal_places_tested, RGBmsg)
  self.assertAlmostEqual(transitfeed.ColorLuminance('006400'), 58.7,
  decimal_places_tested, RGBmsg)
  self.assertAlmostEqual(transitfeed.ColorLuminance('000064'), 11.4,
  decimal_places_tested, RGBmsg)
  self.assertAlmostEqual(transitfeed.ColorLuminance('1171B3'),
  0.299*17 + 0.587*113 + 0.114*179,
  decimal_places_tested, RGBmsg)
 
  INVALID_VALUE = Exception()
  class ValidationTestCase(util.TestCase):
  def setUp(self):
  self.accumulator = RecordingProblemAccumulator(
  self, ("ExpirationDate", "NoServiceExceptions"))
  self.problems = transitfeed.ProblemReporter(self.accumulator)
 
  def tearDown(self):
  self.accumulator.TearDownAssertNoMoreExceptions()
 
  def ExpectNoProblems(self, object):
  self.accumulator.AssertNoMoreExceptions()
  object.Validate(self.problems)
  self.accumulator.AssertNoMoreExceptions()
 
  # TODO: Get rid of Expect*Closure methods. With the
  # RecordingProblemAccumulator it is now possible to replace
  # self.ExpectMissingValueInClosure(lambda: o.method(...), foo)
  # with
  # o.method(...)
  # self.ExpectMissingValueInClosure(foo)
  # because problems don't raise an exception. This has the advantage of
  # making it easy and clear to test the return value of o.method(...) and
  # easier to test for a sequence of problems caused by one call.
  def ExpectMissingValue(self, object, column_name):
  self.ExpectMissingValueInClosure(column_name,
  lambda: object.Validate(self.problems))
 
  def ExpectMissingValueInClosure(self, column_name, c):
  self.accumulator.AssertNoMoreExceptions()
  rv = c()
  e = self.accumulator.PopException('MissingValue')
  self.assertEqual(column_name, e.column_name)
  # these should not throw any exceptions
  e.FormatProblem()
  e.FormatContext()
  self.accumulator.AssertNoMoreExceptions()
 
  def ExpectInvalidValue(self, object, column_name, value=INVALID_VALUE):
  self.ExpectInvalidValueInClosure(column_name, value,
  lambda: object.Validate(self.problems))
 
  def ExpectInvalidValueInClosure(self, column_name, value=INVALID_VALUE,
  c=None):
  self.accumulator.AssertNoMoreExceptions()
  rv = c()
  e = self.accumulator.PopException('InvalidValue')
  self.assertEqual(column_name, e.column_name)
  if value != INVALID_VALUE:
  self.assertEqual(value, e.value)
  # these should not throw any exceptions
  e.FormatProblem()
  e.FormatContext()
  self.accumulator.AssertNoMoreExceptions()
 
  def ExpectInvalidFloatValue(self, object, value):
  self.ExpectInvalidFloatValueInClosure(value,
  lambda: object.Validate(self.problems))
 
  def ExpectInvalidFloatValueInClosure(self, value,
  c=None):
  self.accumulator.AssertNoMoreExceptions()
  rv = c()
  e = self.accumulator.PopException('InvalidFloatValue')
  self.assertEqual(value, e.value)
  # these should not throw any exceptions
  e.FormatProblem()
  e.FormatContext()
  self.accumulator.AssertNoMoreExceptions()
 
  def ExpectOtherProblem(self, object):
  self.ExpectOtherProblemInClosure(lambda: object.Validate(self.problems))
 
  def ExpectOtherProblemInClosure(self, c):
  self.accumulator.AssertNoMoreExceptions()
  rv = c()
  e = self.accumulator.PopException('OtherProblem')
  # these should not throw any exceptions
  e.FormatProblem()
  e.FormatContext()
  self.accumulator.AssertNoMoreExceptions()
 
  def SimpleSchedule(self):
  """Return a minimum schedule that will load without warnings."""
  schedule = transitfeed.Schedule(problem_reporter=self.problems)
  schedule.AddAgency("Fly Agency", "http://iflyagency.com",
  "America/Los_Angeles")
  service_period = transitfeed.ServicePeriod("WEEK")
  service_period.SetWeekdayService(True)
  service_period.SetStartDate("20091203")
  service_period.SetEndDate("20111203")
  service_period.SetDateHasService("20091203")
  schedule.AddServicePeriodObject(service_period)
  stop1 = schedule.AddStop(lng=1.00, lat=48.2, name="Stop 1", stop_id="stop1")
  stop2 = schedule.AddStop(lng=1.01, lat=48.2, name="Stop 2", stop_id="stop2")
  stop3 = schedule.AddStop(lng=1.03, lat=48.2, name="Stop 3", stop_id="stop3")
  route = schedule.AddRoute("54C", "", "Bus", route_id="054C")
  trip = route.AddTrip(schedule, "bus trip", trip_id="CITY1")
  trip.AddStopTime(stop1, stop_time="12:00:00")
  trip.AddStopTime(stop2, stop_time="12:00:45")
  trip.AddStopTime(stop3, stop_time="12:02:30")
  return schedule
 
 
  class AgencyValidationTestCase(ValidationTestCase):
  def runTest(self):
  # success case
  agency = transitfeed.Agency(name='Test Agency', url='http://example.com',
  timezone='America/Los_Angeles', id='TA',
  lang='xh')
  self.ExpectNoProblems(agency)
 
  # bad agency
  agency = transitfeed.Agency(name=' ', url='http://example.com',
  timezone='America/Los_Angeles', id='TA')
  self.ExpectMissingValue(agency, 'agency_name')
 
  # missing url
  agency = transitfeed.Agency(name='Test Agency',
  timezone='America/Los_Angeles', id='TA')
  self.ExpectMissingValue(agency, 'agency_url')
 
  # bad url
  agency = transitfeed.Agency(name='Test Agency', url='www.example.com',
  timezone='America/Los_Angeles', id='TA')
  self.ExpectInvalidValue(agency, 'agency_url')
 
  # bad time zone
  agency = transitfeed.Agency(name='Test Agency', url='http://example.com',
  timezone='America/Alviso', id='TA')
  agency.Validate(self.problems)
  e = self.accumulator.PopInvalidValue('agency_timezone')
  self.assertMatchesRegex('"America/Alviso" is not a common timezone',
  e.FormatProblem())
  self.accumulator.AssertNoMoreExceptions()
 
  # bad language code
  agency = transitfeed.Agency(name='Test Agency', url='http://example.com',
  timezone='America/Los_Angeles', id='TA',
  lang='English')
  self.ExpectInvalidValue(agency, 'agency_lang')
 
  # bad 2-letter lanugage code
  agency = transitfeed.Agency(name='Test Agency', url='http://example.com',
  timezone='America/Los_Angeles', id='TA',
  lang='xx')
  self.ExpectInvalidValue(agency, 'agency_lang')
 
  # capitalized language code is OK
  agency = transitfeed.Agency(name='Test Agency', url='http://example.com',
  timezone='America/Los_Angeles', id='TA',
  lang='EN')
  self.ExpectNoProblems(agency)
 
  # extra attribute in constructor is fine, only checked when loading a file
  agency = transitfeed.Agency(name='Test Agency', url='http://example.com',
  timezone='America/Los_Angeles',
  agency_mission='monorail you there')
  self.ExpectNoProblems(agency)
 
  # extra attribute in assigned later is also fine
  agency = transitfeed.Agency(name='Test Agency', url='http://example.com',
  timezone='America/Los_Angeles')
  agency.agency_mission='monorail you there'
  self.ExpectNoProblems(agency)
 
  # Multiple problems
  agency = transitfeed.Agency(name='Test Agency', url='www.example.com',
  timezone='America/West Coast', id='TA')
  self.assertEquals(False, agency.Validate(self.problems))
  e = self.accumulator.PopException('InvalidValue')
  self.assertEqual(e.column_name, 'agency_url')
  e = self.accumulator.PopException('InvalidValue')
  self.assertEqual(e.column_name, 'agency_timezone')
  self.accumulator.AssertNoMoreExceptions()
 
 
 
  class AgencyAttributesTestCase(ValidationTestCase):
  def testCopy(self):
  agency = transitfeed.Agency(field_dict={'agency_name': 'Test Agency',
  'agency_url': 'http://example.com',
  'timezone': 'America/Los_Angeles',
  'agency_mission': 'get you there'})
  self.assertEquals(agency.agency_mission, 'get you there')
  agency_copy = transitfeed.Agency(field_dict=agency)
  self.assertEquals(agency_copy.agency_mission, 'get you there')
  self.assertEquals(agency_copy['agency_mission'], 'get you there')
 
  def testEq(self):
  agency1 = transitfeed.Agency("Test Agency", "http://example.com",
  "America/Los_Angeles")
  agency2 = transitfeed.Agency("Test Agency", "http://example.com",
  "America/Los_Angeles")
  # Unknown columns, such as agency_mission, do affect equality
  self.assertEquals(agency1, agency2)
  agency1.agency_mission = "Get you there"
  self.assertNotEquals(agency1, agency2)
  agency2.agency_mission = "Move you"
  self.assertNotEquals(agency1, agency2)
  agency1.agency_mission = "Move you"
  self.assertEquals(agency1, agency2)
  # Private attributes don't affect equality
  agency1._private_attr = "My private message"
  self.assertEquals(agency1, agency2)
  agency2._private_attr = "Another private thing"
  self.assertEquals(agency1, agency2)
 
  def testDict(self):
  agency = transitfeed.Agency("Test Agency", "http://example.com",
  "America/Los_Angeles")
  agency._private_attribute = "blah"
  # Private attributes don't appear when iterating through an agency as a
  # dict but can be directly accessed.
  self.assertEquals("blah", agency._private_attribute)
  self.assertEquals("blah", agency["_private_attribute"])
  self.assertEquals(
  set("agency_name agency_url agency_timezone".split()),
  set(agency.keys()))
  self.assertEquals({"agency_name": "Test Agency",
  "agency_url": "http://example.com",
  "agency_timezone": "America/Los_Angeles"},
  dict(agency.iteritems()))
 
 
  class StopValidationTestCase(ValidationTestCase):
  def runTest(self):
  # success case
  stop = transitfeed.Stop()
  stop.stop_id = '45'
  stop.stop_name = 'Couch AT End Table'
  stop.stop_lat = 50.0
  stop.stop_lon = 50.0
  stop.stop_desc = 'Edge of the Couch'
  stop.zone_id = 'A'
  stop.stop_url = 'http://example.com'
  stop.Validate(self.problems)
 
  # latitude too large
  stop.stop_lat = 100.0
  self.ExpectInvalidValue(stop, 'stop_lat')
  stop.stop_lat = 50.0
 
  # latitude as a string works when it is valid
  stop.stop_lat = '50.0'
  stop.Validate(self.problems)
  self.accumulator.AssertNoMoreExceptions()
  stop.stop_lat = '10f'
  self.ExpectInvalidValue(stop, 'stop_lat')
  stop.stop_lat = 50.0
 
  # longitude too large
  stop.stop_lon = 200.0
  self.ExpectInvalidValue(stop, 'stop_lon')
  stop.stop_lon = 50.0
 
  # lat, lon too close to 0, 0
  stop.stop_lat = 0.0
  stop.stop_lon = 0.0
  self.ExpectInvalidValue(stop, 'stop_lat')
  stop.stop_lat = 50.0
  stop.stop_lon = 50.0
 
  # invalid stop_url
  stop.stop_url = 'www.example.com'
  self.ExpectInvalidValue(stop, 'stop_url')
  stop.stop_url = 'http://example.com'
 
  stop.stop_id = ' '
  self.ExpectMissingValue(stop, 'stop_id')
  stop.stop_id = '45'
 
  stop.stop_name = ''
  self.ExpectMissingValue(stop, 'stop_name')
  stop.stop_name = 'Couch AT End Table'
 
  # description same as name
  stop.stop_desc = 'Couch AT End Table'
  self.ExpectInvalidValue(stop, 'stop_desc')
  stop.stop_desc = 'Edge of the Couch'
  self.accumulator.AssertNoMoreExceptions()
 
 
  class StopAttributes(ValidationTestCase):
  def testWithoutSchedule(self):
  stop = transitfeed.Stop()
  stop.Validate(self.problems)
  for name in "stop_id stop_name stop_lat stop_lon".split():
  e = self.accumulator.PopException('MissingValue')
  self.assertEquals(name, e.column_name)
  self.accumulator.AssertNoMoreExceptions()
 
  stop = transitfeed.Stop()
  # Test behaviour for unset and unknown attribute
  self.assertEquals(stop['new_column'], '')
  try:
  t = stop.new_column
  self.fail('Expecting AttributeError')
  except AttributeError, e:
  pass # Expected
  stop.stop_id = 'a'
  stop.stop_name = 'my stop'
  stop.new_column = 'val'
  stop.stop_lat = 5.909
  stop.stop_lon = '40.02'
  self.assertEquals(stop.new_column, 'val')
  self.assertEquals(stop['new_column'], 'val')
  self.assertTrue(isinstance(stop['stop_lat'], basestring))
  self.assertAlmostEqual(float(stop['stop_lat']), 5.909)
  self.assertTrue(isinstance(stop['stop_lon'], basestring))
  self.assertAlmostEqual(float(stop['stop_lon']), 40.02)
  stop.Validate(self.problems)
  self.accumulator.AssertNoMoreExceptions()
  # After validation stop.stop_lon has been converted to a float
  self.assertAlmostEqual(stop.stop_lat, 5.909)
  self.assertAlmostEqual(stop.stop_lon, 40.02)
  self.assertEquals(stop.new_column, 'val')
  self.assertEquals(stop['new_column'], 'val')
 
  def testBlankAttributeName(self):
  stop1 = transitfeed.Stop(field_dict={"": "a"})
  stop2 = transitfeed.Stop(field_dict=stop1)
  self.assertEquals("a", getattr(stop1, ""))
  # The attribute "" is treated as private and not copied
  self.assertRaises(AttributeError, getattr, stop2, "")
  self.assertEquals(set(), set(stop1.keys()))
  self.assertEquals(set(), set(stop2.keys()))
 
  def testWithSchedule(self):
  schedule = transitfeed.Schedule(problem_reporter=self.problems)
 
  stop = transitfeed.Stop(field_dict={})
  # AddStopObject silently fails for Stop objects without stop_id
  schedule.AddStopObject(stop)
  self.assertFalse(schedule.GetStopList())
  self.assertFalse(stop._schedule)
 
  # Okay to add a stop with only stop_id
  stop = transitfeed.Stop(field_dict={"stop_id": "b"})
  schedule.AddStopObject(stop)
  stop.Validate(self.problems)
  for name in "stop_name stop_lat stop_lon".split():
  e = self.accumulator.PopException("MissingValue")
  self.assertEquals(name, e.column_name)
  self.accumulator.AssertNoMoreExceptions()
 
  stop.new_column = "val"
  self.assertTrue("new_column" in schedule.GetTableColumns("stops"))
 
  # Adding a duplicate stop_id fails
  schedule.AddStopObject(transitfeed.Stop(field_dict={"stop_id": "b"}))
  self.accumulator.PopException("DuplicateID")
  self.accumulator.AssertNoMoreExceptions()
 
 
  class StopTimeValidationTestCase(ValidationTestCase):
  def runTest(self):
  stop = transitfeed.Stop()
  self.ExpectInvalidValueInClosure('arrival_time', '1a:00:00',
  lambda: transitfeed.StopTime(self.problems, stop,
  arrival_time="1a:00:00"))
  self.ExpectInvalidValueInClosure('departure_time', '1a:00:00',
  lambda: transitfeed.StopTime(self.problems, stop,
  arrival_time="10:00:00",
  departure_time='1a:00:00'))
  self.ExpectInvalidValueInClosure('pickup_type', '7.8',
  lambda: transitfeed.StopTime(self.problems, stop,
  arrival_time="10:00:00",
  departure_time='10:05:00',
  pickup_type='7.8',
  drop_off_type='0'))
  self.ExpectInvalidValueInClosure('drop_off_type', 'a',
  lambda: transitfeed.StopTime(self.problems, stop,
  arrival_time="10:00:00",
  departure_time='10:05:00',
  pickup_type='3',
  drop_off_type='a'))
  self.ExpectInvalidValueInClosure('shape_dist_traveled', '$',
  lambda: transitfeed.StopTime(self.problems, stop,
  arrival_time="10:00:00",
  departure_time='10:05:00',
  pickup_type='3',
  drop_off_type='0',
  shape_dist_traveled='$'))
  self.ExpectInvalidValueInClosure('shape_dist_traveled', '0,53',
  lambda: transitfeed.StopTime(self.problems, stop,
  arrival_time="10:00:00",
  departure_time='10:05:00',
  pickup_type='3',
  drop_off_type='0',
  shape_dist_traveled='0,53'))
  self.ExpectOtherProblemInClosure(
  lambda: transitfeed.StopTime(self.problems, stop,
  pickup_type='1', drop_off_type='1'))
  self.ExpectInvalidValueInClosure('departure_time', '10:00:00',
  lambda: transitfeed.StopTime(self.problems, stop,
  arrival_time="11:00:00",
  departure_time="10:00:00"))
  self.ExpectMissingValueInClosure('arrival_time',
  lambda: transitfeed.StopTime(self.problems, stop,
  departure_time="10:00:00"))
  self.ExpectMissingValueInClosure('arrival_time',
  lambda: transitfeed.StopTime(self.problems, stop,
  departure_time="10:00:00",
  arrival_time=""))
  self.ExpectMissingValueInClosure('departure_time',
  lambda: transitfeed.StopTime(self.problems, stop,
  arrival_time="10:00:00"))
  self.ExpectMissingValueInClosure('departure_time',
  lambda: transitfeed.StopTime(self.problems, stop,
  arrival_time="10:00:00",
  departure_time=""))
  self.ExpectInvalidValueInClosure('departure_time', '10:70:00',
  lambda: transitfeed.StopTime(self.problems, stop,
  arrival_time="10:00:00",
  departure_time="10:70:00"))
  self.ExpectInvalidValueInClosure('departure_time', '10:00:62',
  lambda: transitfeed.StopTime(self.problems, stop,
  arrival_time="10:00:00",
  departure_time="10:00:62"))
  self.ExpectInvalidValueInClosure('arrival_time', '10:00:63',
  lambda: transitfeed.StopTime(self.problems, stop,
  arrival_time="10:00:63",
  departure_time="10:10:00"))
  self.ExpectInvalidValueInClosure('arrival_time', '10:60:00',
  lambda: transitfeed.StopTime(self.problems, stop,
  arrival_time="10:60:00",
  departure_time="11:02:00"))
  self.ExpectInvalidValueInClosure('stop', "id",
  lambda: transitfeed.StopTime(self.problems, "id",
  arrival_time="10:00:00",
  departure_time="11:02:00"))
  self.ExpectInvalidValueInClosure('stop', "3",
  lambda: transitfeed.StopTime(self.problems, "3",
  arrival_time="10:00:00",
  departure_time="11:02:00"))
  self.ExpectInvalidValueInClosure('stop', None,
  lambda: transitfeed.StopTime(self.problems, None,
  arrival_time="10:00:00",
  departure_time="11:02:00"))
 
  # The following should work
  transitfeed.StopTime(self.problems, stop, arrival_time="10:00:00",
  departure_time="10:05:00", pickup_type='1', drop_off_type='1')
  transitfeed.StopTime(self.problems, stop, arrival_time="10:00:00",
  departure_time="10:05:00", pickup_type='1', drop_off_type='1')
  transitfeed.StopTime(self.problems, stop, arrival_time="1:00:00",
  departure_time="1:05:00")
  transitfeed.StopTime(self.problems, stop, arrival_time="24:59:00",
  departure_time="25:05:00")
  transitfeed.StopTime(self.problems, stop, arrival_time="101:01:00",
  departure_time="101:21:00")
  transitfeed.StopTime(self.problems, stop)
  self.accumulator.AssertNoMoreExceptions()
 
  class TooFastTravelTestCase(ValidationTestCase):
  def setUp(self):
  super(TooFastTravelTestCase, self).setUp()
  self.schedule = self.SimpleSchedule()
  self.route = self.schedule.GetRoute("054C")
  self.trip = self.route.AddTrip()
 
  def AddStopDistanceTime(self, dist_time_list):
  # latitude where each 0.01 degrees longitude is 1km
  magic_lat = 26.062468289
  stop = self.schedule.AddStop(magic_lat, 0, "Demo Stop 0")
  time = 0
  self.trip.AddStopTime(stop, arrival_secs=time, departure_secs=time)
  for i, (dist_delta, time_delta) in enumerate(dist_time_list):
  stop = self.schedule.AddStop(
  magic_lat, stop.stop_lon + dist_delta * 0.00001,
  "Demo Stop %d" % (i + 1))
  time += time_delta
  self.trip.AddStopTime(stop, arrival_secs=time, departure_secs=time)
 
  def testMovingTooFast(self):
  self.AddStopDistanceTime([(1691, 60),
  (1616, 60)])
 
  self.trip.Validate(self.problems)
  e = self.accumulator.PopException('TooFastTravel')
  self.assertMatchesRegex(r'High speed travel detected', e.FormatProblem())
  self.assertMatchesRegex(r'Stop 0 to Demo Stop 1', e.FormatProblem())
  self.assertMatchesRegex(r'1691 meters in 60 seconds', e.FormatProblem())
  self.assertMatchesRegex(r'\(101 km/h\)', e.FormatProblem())
  self.assertEqual(e.type, transitfeed.TYPE_WARNING)
  self.accumulator.AssertNoMoreExceptions()
 
  self.route.route_type = 4 # Ferry with max_speed 80
  self.trip.Validate(self.problems)
  e = self.accumulator.PopException('TooFastTravel')
  self.assertMatchesRegex(r'High speed travel detected', e.FormatProblem())
  self.assertMatchesRegex(r'Stop 0 to Demo Stop 1', e.FormatProblem())
  self.assertMatchesRegex(r'1691 meters in 60 seconds', e.FormatProblem())
  self.assertMatchesRegex(r'\(101 km/h\)', e.FormatProblem())
  self.assertEqual(e.type, transitfeed.TYPE_WARNING)
  e = self.accumulator.PopException('TooFastTravel')
  self.assertMatchesRegex(r'High speed travel detected', e.FormatProblem())
  self.assertMatchesRegex(r'Stop 1 to Demo Stop 2', e.FormatProblem())
  self.assertMatchesRegex(r'1616 meters in 60 seconds', e.FormatProblem())
  self.assertMatchesRegex(r'97 km/h', e.FormatProblem())
  self.assertEqual(e.type, transitfeed.TYPE_WARNING)
  self.accumulator.AssertNoMoreExceptions()
 
  # Run test without a route_type
  self.route.route_type = None
  self.trip.Validate(self.problems)
  e = self.accumulator.PopException('TooFastTravel')
  self.assertMatchesRegex(r'High speed travel detected', e.FormatProblem())
  self.assertMatchesRegex(r'Stop 0 to Demo Stop 1', e.FormatProblem())
  self.assertMatchesRegex(r'1691 meters in 60 seconds', e.FormatProblem())
  self.assertMatchesRegex(r'101 km/h', e.FormatProblem())
  self.assertEqual(e.type, transitfeed.TYPE_WARNING)
  self.accumulator.AssertNoMoreExceptions()
 
  def testNoTimeDelta(self):
  # See comments where TooFastTravel is called in transitfeed.py to
  # understand why was added.
  # Movement more than max_speed in 1 minute with no time change is a warning.
  self.AddStopDistanceTime([(1616, 0),
  (1000, 120),
  (1691, 0)])
 
  self.trip.Validate(self.problems)
  e = self.accumulator.PopException('TooFastTravel')
  self.assertMatchesRegex('High speed travel detected', e.FormatProblem())
  self.assertMatchesRegex('Stop 2 to Demo Stop 3', e.FormatProblem())
  self.assertMatchesRegex('1691 meters in 0 seconds', e.FormatProblem())
  self.assertEqual(e.type, transitfeed.TYPE_WARNING)
  self.accumulator.AssertNoMoreExceptions()
 
  self.route.route_type = 4 # Ferry with max_speed 80
  self.trip.Validate(self.problems)
  self.assertEqual(e.type, transitfeed.TYPE_WARNING)
  e = self.accumulator.PopException('TooFastTravel')
  self.assertMatchesRegex('High speed travel detected', e.FormatProblem())
  self.assertMatchesRegex('Stop 0 to Demo Stop 1', e.FormatProblem())
  self.assertMatchesRegex('1616 meters in 0 seconds', e.FormatProblem())
  self.assertEqual(e.type, transitfeed.TYPE_WARNING)
  e = self.accumulator.PopException('TooFastTravel')
  self.assertMatchesRegex('High speed travel detected', e.FormatProblem())
  self.assertMatchesRegex('Stop 2 to Demo Stop 3', e.FormatProblem())
  self.assertMatchesRegex('1691 meters in 0 seconds', e.FormatProblem())
  self.assertEqual(e.type, transitfeed.TYPE_WARNING)
  self.accumulator.AssertNoMoreExceptions()
 
  # Run test without a route_type
  self.route.route_type = None
  self.trip.Validate(self.problems)
  e = self.accumulator.PopException('TooFastTravel')
  self.assertMatchesRegex('High speed travel detected', e.FormatProblem())
  self.assertMatchesRegex('Stop 2 to Demo Stop 3', e.FormatProblem())
  self.assertMatchesRegex('1691 meters in 0 seconds', e.FormatProblem())
  self.assertEqual(e.type, transitfeed.TYPE_WARNING)
  self.accumulator.AssertNoMoreExceptions()
 
  def testNoTimeDeltaNotRounded(self):
  # See comments where TooFastTravel is called in transitfeed.py to
  # understand why was added.
  # Any movement with no time change and times not rounded to the nearest
  # minute causes a warning.
  self.AddStopDistanceTime([(500, 62),
  (10, 0)])
 
  self.trip.Validate(self.problems)
  e = self.accumulator.PopException('TooFastTravel')
  self.assertMatchesRegex('High speed travel detected', e.FormatProblem())
  self.assertMatchesRegex('Stop 1 to Demo Stop 2', e.FormatProblem())
  self.assertMatchesRegex('10 meters in 0 seconds', e.FormatProblem())
  self.assertEqual(e.type, transitfeed.TYPE_WARNING)
  self.accumulator.AssertNoMoreExceptions()
 
 
  class MemoryZipTestCase(util.TestCase):
  """Base for TestCase classes which read from an in-memory zip file.
 
  A test that loads data from this zip file exercises almost all the code used
  when the feedvalidator runs, but does not touch disk. Unfortunately it is very
  difficult to add new stops to the default stops.txt because a new stop will
  break tests in StopHierarchyTestCase and StopsNearEachOther."""
 
  def setUp(self):
  self.accumulator = RecordingProblemAccumulator(self, ("ExpirationDate",))
  self.problems = transitfeed.ProblemReporter(self.accumulator)
  self.zip_contents = {}
  self.SetArchiveContents(
  "agency.txt",
  "agency_id,agency_name,agency_url,agency_timezone\n"
  "DTA,Demo Agency,http://google.com,America/Los_Angeles\n")
  self.SetArchiveContents(
  "calendar.txt",
  "service_id,monday,tuesday,wednesday,thursday,friday,saturday,sunday,"
  "start_date,end_date\n"
  "FULLW,1,1,1,1,1,1,1,20070101,20101231\n"
  "WE,0,0,0,0,0,1,1,20070101,20101231\n")
  self.SetArchiveContents(
  "calendar_dates.txt",
  "service_id,date,exception_type\n"
  "FULLW,20070101,1\n")
  self.SetArchiveContents(
  "routes.txt",
  "route_id,agency_id,route_short_name,route_long_name,route_type\n"
  "AB,DTA,,Airport Bullfrog,3\n")
  self.SetArchiveContents(
  "trips.txt",
  "route_id,service_id,trip_id\n"
  "AB,FULLW,AB1\n")
  self.SetArchiveContents(
  "stops.txt",
  "stop_id,stop_name,stop_lat,stop_lon\n"
  "BEATTY_AIRPORT,Airport,36.868446,-116.784582\n"
  "BULLFROG,Bullfrog,36.88108,-116.81797\n"
  "STAGECOACH,Stagecoach Hotel,36.915682,-116.751677\n")
  self.SetArchiveContents(
  "stop_times.txt",
  "trip_id,arrival_time,departure_time,stop_id,stop_sequence\n"
  "AB1,10:00:00,10:00:00,BEATTY_AIRPORT,1\n"
  "AB1,10:20:00,10:20:00,BULLFROG,2\n"
  "AB1,10:25:00,10:25:00,STAGECOACH,3\n")
 
  def MakeLoaderAndLoad(self,
  problems=None,
  extra_validation=True):
  """Returns a Schedule loaded with the contents of the file dict."""
  if problems is None:
  problems = self.problems
  self.CreateZip()
  self.loader = transitfeed.Loader(
  problems=problems,
  extra_validation=extra_validation,
  zip=self.zip)
  return self.loader.Load()
 
  def AppendToArchiveContents(self, arcname, s):
  """Append string s to file arcname in the file dict.
 
  All calls to this function, if any, should be made before calling
  MakeLoaderAndLoad."""
  current_contents = self.zip_contents[arcname]
  self.zip_contents[arcname] = current_contents + s
 
  def SetArchiveContents(self, arcname, contents):
  """Set the contents of file arcname in the file dict.
 
  All calls to this function, if any, should be made before calling
  MakeLoaderAndLoad."""
  self.zip_contents[arcname] = contents
 
  def GetArchiveContents(self, arcname):
  """Get the contents of file arcname in the file dict."""
  return self.zip_contents[arcname]
 
  def RemoveArchive(self, arcname):
  """Remove file arcname from the file dict.
 
  All calls to this function, if any, should be made before calling
  MakeLoaderAndLoad."""
  del self.zip_contents[arcname]
 
  def GetArchiveNames(self):
  """Get a list of all the archive names in the file dict."""
  return self.zip_contents.keys()
 
  def CreateZip(self):
  """Create an in-memory GTFS zipfile from the contents of the file dict."""
  self.zipfile = StringIO()
  self.zip = zipfile.ZipFile(self.zipfile, 'a')
  for (arcname, contents) in self.zip_contents.items():
  self.zip.writestr(arcname, contents)
 
  def DumpZipFile(self, zf):
  """Print the contents of something zipfile can open, such as a StringIO."""
  # Handy for debugging
  z = zipfile.ZipFile(zf)
  for n in z.namelist():
  print "--\n%s\n%s" % (n, z.read(n))
 
 
  class CsvDictTestCase(util.TestCase):
  def setUp(self):
  self.accumulator = RecordingProblemAccumulator(self)
  self.problems = transitfeed.ProblemReporter(self.accumulator)
  self.zip = zipfile.ZipFile(StringIO(), 'a')
  self.loader = transitfeed.Loader(
  problems=self.problems,
  zip=self.zip)
 
  def tearDown(self):
  self.accumulator.TearDownAssertNoMoreExceptions()
 
  def testEmptyFile(self):
  self.zip.writestr("test.txt", "")
  results = list(self.loader._ReadCsvDict("test.txt", [], []))
  self.assertEquals([], results)
  self.accumulator.PopException("EmptyFile")
  self.accumulator.AssertNoMoreExceptions()
 
  def testHeaderOnly(self):
  self.zip.writestr("test.txt", "test_id,test_name")
  results = list(self.loader._ReadCsvDict("test.txt",
  ["test_id", "test_name"], []))
  self.assertEquals([], results)
  self.accumulator.AssertNoMoreExceptions()
 
  def testHeaderAndNewLineOnly(self):
  self.zip.writestr("test.txt", "test_id,test_name\n")
  results = list(self.loader._ReadCsvDict("test.txt",
  ["test_id", "test_name"], []))
  self.assertEquals([], results)
  self.accumulator.AssertNoMoreExceptions()
 
  def testHeaderWithSpaceBefore(self):
  self.zip.writestr("test.txt", " test_id, test_name\n")
  results = list(self.loader._ReadCsvDict("test.txt",
  ["test_id", "test_name"], []))
  self.assertEquals([], results)
  self.accumulator.AssertNoMoreExceptions()
 
  def testHeaderWithSpaceBeforeAfter(self):
  self.zip.writestr("test.txt", "test_id , test_name\n")
  results = list(self.loader._ReadCsvDict("test.txt",
  ["test_id", "test_name"], []))
  self.assertEquals([], results)
  e = self.accumulator.PopException("CsvSyntax")
  self.accumulator.AssertNoMoreExceptions()
 
  def testHeaderQuoted(self):
  self.zip.writestr("test.txt", "\"test_id\", \"test_name\"\n")
  results = list(self.loader._ReadCsvDict("test.txt",
  ["test_id", "test_name"], []))
  self.assertEquals([], results)
  self.accumulator.AssertNoMoreExceptions()
 
  def testHeaderSpaceAfterQuoted(self):
  self.zip.writestr("test.txt", "\"test_id\" , \"test_name\"\n")
  results = list(self.loader._ReadCsvDict("test.txt",
  ["test_id", "test_name"], []))
  self.assertEquals([], results)
  e = self.accumulator.PopException("CsvSyntax")
  self.accumulator.AssertNoMoreExceptions()
 
  def testHeaderSpaceInQuotesAfterValue(self):
  self.zip.writestr("test.txt", "\"test_id \",\"test_name\"\n")
  results = list(self.loader._ReadCsvDict("test.txt",
  ["test_id", "test_name"], []))
  self.assertEquals([], results)
  e = self.accumulator.PopException("CsvSyntax")
  self.accumulator.AssertNoMoreExceptions()
 
  def testHeaderSpaceInQuotesBeforeValue(self):
  self.zip.writestr("test.txt", "\"test_id\",\" test_name\"\n")
  results = list(self.loader._ReadCsvDict("test.txt",
  ["test_id", "test_name"], []))
  self.assertEquals([], results)
  e = self.accumulator.PopException("CsvSyntax")
  self.accumulator.AssertNoMoreExceptions()
 
  def testHeaderEmptyColumnName(self):
  self.zip.writestr("test.txt", 'test_id,test_name,\n')
  results = list(self.loader._ReadCsvDict("test.txt",
  ["test_id", "test_name"], []))
  self.assertEquals([], results)
  e = self.accumulator.PopException("CsvSyntax")
  self.accumulator.AssertNoMoreExceptions()
 
  def testHeaderAllUnknownColumnNames(self):
  self.zip.writestr("test.txt", 'id,nam\n')
  results = list(self.loader._ReadCsvDict("test.txt",
  ["test_id", "test_name"], []))
  self.assertEquals([], results)
  e = self.accumulator.PopException("CsvSyntax")
  self.assertTrue(e.FormatProblem().find("missing the header") != -1)
  self.accumulator.AssertNoMoreExceptions()
 
  def testFieldWithSpaces(self):
  self.zip.writestr("test.txt",
  "test_id,test_name\n"
  "id1 , my name\n")
  results = list(self.loader._ReadCsvDict("test.txt",
  ["test_id", "test_name"], []))
  self.assertEquals([({"test_id": "id1 ", "test_name": "my name"}, 2,
  ["test_id", "test_name"], ["id1 ","my name"])], results)
  self.accumulator.AssertNoMoreExceptions()
 
  def testFieldWithOnlySpaces(self):
  self.zip.writestr("test.txt",
  "test_id,test_name\n"
  "id1, \n") # spaces are skipped to yield empty field
  results = list(self.loader._ReadCsvDict("test.txt",
  ["test_id", "test_name"], []))
  self.assertEquals([({"test_id": "id1", "test_name": ""}, 2,
  ["test_id", "test_name"], ["id1",""])], results)
  self.accumulator.AssertNoMoreExceptions()
 
  def testQuotedFieldWithSpaces(self):
  self.zip.writestr("test.txt",
  'test_id,"test_name",test_size\n'
  '"id1" , "my name" , "234 "\n')
  results = list(self.loader._ReadCsvDict("test.txt",
  ["test_id", "test_name",
  "test_size"], []))
  self.assertEquals(
  [({"test_id": "id1 ", "test_name": "my name ", "test_size": "234 "}, 2,
  ["test_id", "test_name", "test_size"], ["id1 ", "my name ", "234 "])],
  results)
  self.accumulator.AssertNoMoreExceptions()
 
  def testQuotedFieldWithCommas(self):
  self.zip.writestr("test.txt",
  'id,name1,name2\n'
  '"1", "brown, tom", "brown, ""tom"""\n')
  results = list(self.loader._ReadCsvDict("test.txt",
  ["id", "name1", "name2"], []))
  self.assertEquals(
  [({"id": "1", "name1": "brown, tom", "name2": "brown, \"tom\""}, 2,
  ["id", "name1", "name2"], ["1", "brown, tom", "brown, \"tom\""])],
  results)
  self.accumulator.AssertNoMoreExceptions()
 
  def testUnknownColumn(self):
  # A small typo (omitting '_' in a header name) is detected
  self.zip.writestr("test.txt", "test_id,testname\n")
  results = list(self.loader._ReadCsvDict("test.txt",
  ["test_id", "test_name"], []))
  self.assertEquals([], results)
  e = self.accumulator.PopException("UnrecognizedColumn")
  self.assertEquals("testname", e.column_name)
  self.accumulator.AssertNoMoreExceptions()
 
  def testMissingRequiredColumn(self):
  self.zip.writestr("test.txt", "test_id,test_size\n")
  results = list(self.loader._ReadCsvDict("test.txt",
  ["test_id", "test_size"],
  ["test_name"]))
  self.assertEquals([], results)
  e = self.accumulator.PopException("MissingColumn")
  self.assertEquals("test_name", e.column_name)
  self.accumulator.AssertNoMoreExceptions()
 
  def testRequiredNotInAllCols(self):
  self.zip.writestr("test.txt", "test_id,test_name,test_size\n")
  results = list(self.loader._ReadCsvDict("test.txt",
  ["test_id", "test_size"],
  ["test_name"]))
  self.assertEquals([], results)
  e = self.accumulator.PopException("UnrecognizedColumn")
  self.assertEquals("test_name", e.column_name)
  self.accumulator.AssertNoMoreExceptions()
 
  def testBlankLine(self):
  # line_num is increased for an empty line
  self.zip.writestr("test.txt",
  "test_id,test_name\n"
  "\n"
  "id1,my name\n")
  results = list(self.loader._ReadCsvDict("test.txt",
  ["test_id", "test_name"], []))
  self.assertEquals([({"test_id": "id1", "test_name": "my name"}, 3,
  ["test_id", "test_name"], ["id1","my name"])], results)
  self.accumulator.AssertNoMoreExceptions()
 
  def testExtraComma(self):
  self.zip.writestr("test.txt",
  "test_id,test_name\n"
  "id1,my name,\n")
  results = list(self.loader._ReadCsvDict("test.txt",
  ["test_id", "test_name"], []))
  self.assertEquals([({"test_id": "id1", "test_name": "my name"}, 2,
  ["test_id", "test_name"], ["id1","my name"])],
  results)
  e = self.accumulator.PopException("OtherProblem")
  self.assertTrue(e.FormatProblem().find("too many cells") != -1)
  self.accumulator.AssertNoMoreExceptions()
 
  def testMissingComma(self):
  self.zip.writestr("test.txt",
  "test_id,test_name\n"
  "id1 my name\n")
  results = list(self.loader._ReadCsvDict("test.txt",
  ["test_id", "test_name"], []))
  self.assertEquals([({"test_id": "id1 my name"}, 2,
  ["test_id", "test_name"], ["id1 my name"])], results)
  e = self.accumulator.PopException("OtherProblem")
  self.assertTrue(e.FormatProblem().find("missing cells") != -1)
  self.accumulator.AssertNoMoreExceptions()
 
  def testDetectsDuplicateHeaders(self):
  self.zip.writestr(
  "transfers.txt",
  "from_stop_id,from_stop_id,to_stop_id,transfer_type,min_transfer_time,"
  "min_transfer_time,min_transfer_time,min_transfer_time,unknown,"
  "unknown\n"
  "BEATTY_AIRPORT,BEATTY_AIRPORT,BULLFROG,3,,2,,,,\n"
  "BULLFROG,BULLFROG,BEATTY_AIRPORT,2,1200,1,,,,\n")
 
  list(self.loader._ReadCsvDict("transfers.txt",
  transitfeed.Transfer._FIELD_NAMES,
  transitfeed.Transfer._REQUIRED_FIELD_NAMES))
 
  self.accumulator.PopDuplicateColumn("transfers.txt","min_transfer_time",4)
  self.accumulator.PopDuplicateColumn("transfers.txt","from_stop_id",2)
  self.accumulator.PopDuplicateColumn("transfers.txt","unknown",2)
  e = self.accumulator.PopException("UnrecognizedColumn")
  self.assertEquals("unknown", e.column_name)
  self.accumulator.AssertNoMoreExceptions()
 
 
  class ReadCsvTestCase(util.TestCase):
  def setUp(self):
  self.accumulator = RecordingProblemAccumulator(self)
  self.problems = transitfeed.ProblemReporter(self.accumulator)
  self.zip = zipfile.ZipFile(StringIO(), 'a')
  self.loader = transitfeed.Loader(
  problems=self.problems,
  zip=self.zip)
 
  def tearDown(self):
  self.accumulator.TearDownAssertNoMoreExceptions()
 
  def testDetectsDuplicateHeaders(self):
  self.zip.writestr(
  "calendar.txt",
  "service_id,monday,tuesday,wednesday,thursday,friday,saturday,sunday,"
  "start_date,end_date,end_date,end_date,tuesday,unknown,unknown\n"
  "FULLW,1,1,1,1,1,1,1,20070101,20101231,,,,,\n")
 
  list(self.loader._ReadCSV("calendar.txt",
  transitfeed.ServicePeriod._FIELD_NAMES,
  transitfeed.ServicePeriod._FIELD_NAMES_REQUIRED))
 
  self.accumulator.PopDuplicateColumn("calendar.txt","end_date",3)
  self.accumulator.PopDuplicateColumn("calendar.txt","unknown",2)
  self.accumulator.PopDuplicateColumn("calendar.txt","tuesday",2)
  e = self.accumulator.PopException("UnrecognizedColumn")
  self.assertEquals("unknown", e.column_name)
  self.accumulator.AssertNoMoreExceptions()
 
 
  class BasicMemoryZipTestCase(MemoryZipTestCase):
  def runTest(self):
  self.MakeLoaderAndLoad()
  self.accumulator.AssertNoMoreExceptions()
 
  class ZipCompressionTestCase(MemoryZipTestCase):
  def runTest(self):
  schedule = self.MakeLoaderAndLoad()
  self.zip.close()
  write_output = StringIO()
  schedule.WriteGoogleTransitFeed(write_output)
  recompressedzip = zlib.compress(write_output.getvalue())
  write_size = len(write_output.getvalue())
  recompressedzip_size = len(recompressedzip)
  # If zlib can compress write_output it probably wasn't compressed
  self.assertFalse(
  recompressedzip_size < write_size * 0.60,
  "Are you sure WriteGoogleTransitFeed wrote a compressed zip? "
  "Orginial size: %d recompressed: %d" %
  (write_size, recompressedzip_size))
 
 
  class StopHierarchyTestCase(MemoryZipTestCase):
  def testParentAtSameLatLon(self):
  self.SetArchiveContents(
  "stops.txt",
  "stop_id,stop_name,stop_lat,stop_lon,location_type,parent_station\n"
  "BEATTY_AIRPORT,Airport,36.868446,-116.784582,,STATION\n"
  "STATION,Airport,36.868446,-116.784582,1,\n"
  "BULLFROG,Bullfrog,36.88108,-116.81797,,\n"
  "STAGECOACH,Stagecoach Hotel,36.915682,-116.751677,,\n")
  schedule = self.MakeLoaderAndLoad()
  self.assertEquals(1, schedule.stops["STATION"].location_type)
  self.assertEquals(0, schedule.stops["BEATTY_AIRPORT"].location_type)
  self.accumulator.AssertNoMoreExceptions()
 
  def testBadLocationType(self):
  self.SetArchiveContents(
  "stops.txt",
  "stop_id,stop_name,stop_lat,stop_lon,location_type\n"
  "BEATTY_AIRPORT,Airport,36.868446,-116.784582,2\n"
  "BULLFROG,Bullfrog,36.88108,-116.81797,notvalid\n"
  "STAGECOACH,Stagecoach Hotel,36.915682,-116.751677,\n")
  schedule = self.MakeLoaderAndLoad()
  e = self.accumulator.PopException("InvalidValue")
  self.assertEquals("location_type", e.column_name)
  self.assertEquals(2, e.row_num)
  self.assertEquals(1, e.type)
  e = self.accumulator.PopException("InvalidValue")
  self.assertEquals("location_type", e.column_name)
  self.assertEquals(3, e.row_num)
  self.assertEquals(0, e.type)
  self.accumulator.AssertNoMoreExceptions()
 
  def testBadLocationTypeAtSameLatLon(self):
  self.SetArchiveContents(
  "stops.txt",
  "stop_id,stop_name,stop_lat,stop_lon,location_type,parent_station\n"
  "BEATTY_AIRPORT,Airport,36.868446,-116.784582,,STATION\n"
  "STATION,Airport,36.868446,-116.784582,2,\n"
  "BULLFROG,Bullfrog,36.88108,-116.81797,,\n"
  "STAGECOACH,Stagecoach Hotel,36.915682,-116.751677,,\n")
  schedule = self.MakeLoaderAndLoad()
  e = self.accumulator.PopException("InvalidValue")
  self.assertEquals("location_type", e.column_name)
  self.assertEquals(3, e.row_num)
  e = self.accumulator.PopException("InvalidValue")
  self.assertEquals("parent_station", e.column_name)
  self.accumulator.AssertNoMoreExceptions()
 
  def testStationUsed(self):
  self.SetArchiveContents(
  "stops.txt",
  "stop_id,stop_name,stop_lat,stop_lon,location_type\n"
  "BEATTY_AIRPORT,Airport,36.868446,-116.784582,1\n"
  "BULLFROG,Bullfrog,36.88108,-116.81797,\n"
  "STAGECOACH,Stagecoach Hotel,36.915682,-116.751677,\n")
  schedule = self.MakeLoaderAndLoad()
  self.accumulator.PopException("UsedStation")
  self.accumulator.AssertNoMoreExceptions()
 
  def testParentNotFound(self):
  self.SetArchiveContents(
  "stops.txt",
  "stop_id,stop_name,stop_lat,stop_lon,location_type,parent_station\n"
  "BEATTY_AIRPORT,Airport,36.868446,-116.784582,,STATION\n"
  "BULLFROG,Bullfrog,36.88108,-116.81797,,\n"
  "STAGECOACH,Stagecoach Hotel,36.915682,-116.751677,,\n")
  schedule = self.MakeLoaderAndLoad()
  e = self.accumulator.PopException("InvalidValue")
  self.assertEquals("parent_station", e.column_name)
  self.accumulator.AssertNoMoreExceptions()
 
  def testParentIsStop(self):
  self.SetArchiveContents(
  "stops.txt",
  "stop_id,stop_name,stop_lat,stop_lon,location_type,parent_station\n"
  "BEATTY_AIRPORT,Airport,36.868446,-116.784582,,BULLFROG\n"
  "BULLFROG,Bullfrog,36.88108,-116.81797,,\n"
  "STAGECOACH,Stagecoach Hotel,36.915682,-116.751677,,\n")
  schedule = self.MakeLoaderAndLoad()
  e = self.accumulator.PopException("InvalidValue")
  self.assertEquals("parent_station", e.column_name)
  self.accumulator.AssertNoMoreExceptions()
 
  def testParentOfEntranceIsStop(self):
  self.SetArchiveContents(
  "stops.txt",
  "stop_id,stop_name,stop_lat,stop_lon,location_type,parent_station\n"
  "BEATTY_AIRPORT,Airport,36.868446,-116.784582,2,BULLFROG\n"
  "BULLFROG,Bullfrog,36.88108,-116.81797,,\n"
  "STAGECOACH,Stagecoach Hotel,36.915682,-116.751677,,\n")
  schedule = self.MakeLoaderAndLoad()
  e = self.accumulator.PopException("InvalidValue")
  self.assertEquals("location_type", e.column_name)
  e = self.accumulator.PopException("InvalidValue")
  self.assertEquals("parent_station", e.column_name)
  self.assertTrue(e.FormatProblem().find("location_type=1") != -1)
  self.accumulator.AssertNoMoreExceptions()
 
  def testStationWithParent(self):
  self.SetArchiveContents(
  "stops.txt",
  "stop_id,stop_name,stop_lat,stop_lon,location_type,parent_station\n"
  "BEATTY_AIRPORT,Airport,36.868446,-116.784582,,STATION\n"
  "STATION,Airport,36.868446,-116.784582,1,STATION2\n"
  "STATION2,Airport 2,36.868000,-116.784000,1,\n"
  "BULLFROG,Bullfrog,36.868088,-116.784797,,STATION2\n"
  "STAGECOACH,Stagecoach Hotel,36.915682,-116.751677,,\n")
  schedule = self.MakeLoaderAndLoad()
  e = self.accumulator.PopException("InvalidValue")
  self.assertEquals("parent_station", e.column_name)
  self.assertEquals(3, e.row_num)
  self.accumulator.AssertNoMoreExceptions()
 
  def testStationWithSelfParent(self):
  self.SetArchiveContents(
  "stops.txt",
  "stop_id,stop_name,stop_lat,stop_lon,location_type,parent_station\n"
  "BEATTY_AIRPORT,Airport,36.868446,-116.784582,,STATION\n"
  "STATION,Airport,36.868446,-116.784582,1,STATION\n"
  "BULLFROG,Bullfrog,36.88108,-116.81797,,\n"
  "STAGECOACH,Stagecoach Hotel,36.915682,-116.751677,,\n")
  schedule = self.MakeLoaderAndLoad()
  e = self.accumulator.PopException("InvalidValue")
  self.assertEquals("parent_station", e.column_name)
  self.assertEquals(3, e.row_num)
  self.accumulator.AssertNoMoreExceptions()
 
  def testStopNearToNonParentStation(self):
  self.SetArchiveContents(
  "stops.txt",
  "stop_id,stop_name,stop_lat,stop_lon,location_type,parent_station\n"
  "BEATTY_AIRPORT,Airport,36.868446,-116.784582,,\n"
  "BULLFROG,Bullfrog,36.868446,-116.784582,,\n"
  "BULLFROG_ST,Bullfrog,36.868446,-116.784582,1,\n"
  "STAGECOACH,Stagecoach Hotel,36.915682,-116.751677,,\n")
  schedule = self.MakeLoaderAndLoad()
  e = self.accumulator.PopException("DifferentStationTooClose")
  self.assertMatchesRegex(
  "The parent_station of stop \"Bullfrog\"", e.FormatProblem())
  e = self.accumulator.PopException("StopsTooClose")
  self.assertMatchesRegex("BEATTY_AIRPORT", e.FormatProblem())
  self.assertMatchesRegex("BULLFROG", e.FormatProblem())
  self.assertMatchesRegex("are 0.00m apart", e.FormatProblem())
  e = self.accumulator.PopException("DifferentStationTooClose")
  self.assertMatchesRegex(
  "The parent_station of stop \"Airport\"", e.FormatProblem())
  self.accumulator.AssertNoMoreExceptions()
 
  def testStopTooFarFromParentStation(self):
  self.SetArchiveContents(
  "stops.txt",
  "stop_id,stop_name,stop_lat,stop_lon,location_type,parent_station\n"
  "BULLFROG_ST,Bullfrog,36.880,-116.817,1,\n" # Parent station of all.
  "BEATTY_AIRPORT,Airport,36.880,-116.816,,BULLFROG_ST\n" # ~ 90m far
  "BULLFROG,Bullfrog,36.881,-116.818,,BULLFROG_ST\n" # ~ 150m far
  "STAGECOACH,Stagecoach,36.915,-116.751,,BULLFROG_ST\n") # > 3km far
  schedule = self.MakeLoaderAndLoad()
  e = self.accumulator.PopException("StopTooFarFromParentStation")
  self.assertEqual(1, e.type) # Warning
  self.assertTrue(e.FormatProblem().find(
  "Bullfrog (ID BULLFROG) is too far from its parent"
  " station Bullfrog (ID BULLFROG_ST)") != -1)
  e = self.accumulator.PopException("StopTooFarFromParentStation")
  self.assertEqual(0, e.type) # Error
  self.assertTrue(e.FormatProblem().find(
  "Stagecoach (ID STAGECOACH) is too far from its parent"
  " station Bullfrog (ID BULLFROG_ST)") != -1)
  self.accumulator.AssertNoMoreExceptions()
 
  #Uncomment once validation is implemented
  #def testStationWithoutReference(self):
  # self.SetArchiveContents(
  # "stops.txt",
  # "stop_id,stop_name,stop_lat,stop_lon,location_type,parent_station\n"
  # "BEATTY_AIRPORT,Airport,36.868446,-116.784582,,\n"
  # "STATION,Airport,36.868446,-116.784582,1,\n"
  # "BULLFROG,Bullfrog,36.88108,-116.81797,,\n"
  # "STAGECOACH,Stagecoach Hotel,36.915682,-116.751677,,\n")
  # schedule = self.MakeLoaderAndLoad()
  # e = self.accumulator.PopException("OtherProblem")
  # self.assertEquals("parent_station", e.column_name)
  # self.assertEquals(2, e.row_num)
  # self.accumulator.AssertNoMoreExceptions()
 
 
  class StopSpacesTestCase(MemoryZipTestCase):
  def testFieldsWithSpace(self):
  self.SetArchiveContents(
  "stops.txt",
  "stop_id,stop_code,stop_name,stop_lat,stop_lon,stop_url,location_type,"
  "parent_station\n"
  "BEATTY_AIRPORT, ,Airport,36.868446,-116.784582, , ,\n"
  "BULLFROG,,Bullfrog,36.88108,-116.81797,,,\n"
  "STAGECOACH,,Stagecoach Hotel,36.915682,-116.751677,,,\n")
  schedule = self.MakeLoaderAndLoad()
  self.accumulator.AssertNoMoreExceptions()
 
 
  class StopBlankHeaders(MemoryZipTestCase):
  def testBlankHeaderValueAtEnd(self):
  # Modify the stops.txt added by MemoryZipTestCase.setUp. This allows the
  # original stops.txt to be changed without modifying anything in this test.
  # Add a column to the end of every row, leaving the header name blank.
  new = []
  for i, row in enumerate(
  self.GetArchiveContents("stops.txt").split("\n")):
  if i == 0:
  new.append(row + ",")
  elif row:
  new.append(row + "," + str(i)) # Put a junk value in data rows
  self.SetArchiveContents("stops.txt", "\n".join(new))
  schedule = self.MakeLoaderAndLoad()
  e = self.accumulator.PopException("CsvSyntax")
  self.assertTrue(e.FormatProblem().
  find("header row should not contain any blank") != -1)
  self.accumulator.AssertNoMoreExceptions()
 
  def testBlankHeaderValueAtStart(self):
  # Modify the stops.txt added by MemoryZipTestCase.setUp. This allows the
  # original stops.txt to be changed without modifying anything in this test.
  # Add a column to the start of every row, leaving the header name blank.
  new = []
  for i, row in enumerate(
  self.GetArchiveContents("stops.txt").split("\n")):
  if i == 0:
  new.append("," + row)
  elif row:
  new.append(str(i) + "," + row) # Put a junk value in data rows
  self.SetArchiveContents("stops.txt", "\n".join(new))
  schedule = self.MakeLoaderAndLoad()
  e = self.accumulator.PopException("CsvSyntax")
  self.assertTrue(e.FormatProblem().
  find("header row should not contain any blank") != -1)
  self.accumulator.AssertNoMoreExceptions()
 
  def testBlankHeaderValueInMiddle(self):
  # Modify the stops.txt added by MemoryZipTestCase.setUp. This allows the
  # original stops.txt to be changed without modifying anything in this test.
  # Add two columns to the start of every row, leaving the second header name
  # blank.
  new = []
  for i, row in enumerate(
  self.GetArchiveContents("stops.txt").split("\n")):
  if i == 0:
  new.append("test_name,," + row)
  elif row:
  # Put a junk value in data rows
  new.append(str(i) + "," + str(i) + "," + row)
  self.SetArchiveContents("stops.txt", "\n".join(new))
  schedule = self.MakeLoaderAndLoad()
  e = self.accumulator.PopException("CsvSyntax")
  self.assertTrue(e.FormatProblem().
  find("header row should not contain any blank") != -1)
  e = self.accumulator.PopException("UnrecognizedColumn")
  self.assertEquals("test_name", e.column_name)
  self.accumulator.AssertNoMoreExceptions()
 
 
  class StopsNearEachOther(MemoryZipTestCase):
  def testTooNear(self):
  self.SetArchiveContents(
  "stops.txt",
  "stop_id,stop_name,stop_lat,stop_lon\n"
  "BEATTY_AIRPORT,Airport,48.20000,140\n"
  "BULLFROG,Bullfrog,48.20001,140\n"
  "STAGECOACH,Stagecoach Hotel,48.20016,140\n")
  schedule = self.MakeLoaderAndLoad()
  e = self.accumulator.PopException('StopsTooClose')
  self.assertTrue(e.FormatProblem().find("1.11m apart") != -1)
  self.accumulator.AssertNoMoreExceptions()
 
  def testJustFarEnough(self):
  self.SetArchiveContents(
  "stops.txt",
  "stop_id,stop_name,stop_lat,stop_lon\n"
  "BEATTY_AIRPORT,Airport,48.20000,140\n"
  "BULLFROG,Bullfrog,48.20002,140\n"
  "STAGECOACH,Stagecoach Hotel,48.20016,140\n")
  schedule = self.MakeLoaderAndLoad()
  # Stops are 2.2m apart
  self.accumulator.AssertNoMoreExceptions()
 
  def testSameLocation(self):
  self.SetArchiveContents(
  "stops.txt",
  "stop_id,stop_name,stop_lat,stop_lon\n"
  "BEATTY_AIRPORT,Airport,48.2,140\n"
  "BULLFROG,Bullfrog,48.2,140\n"
  "STAGECOACH,Stagecoach Hotel,48.20016,140\n")
  schedule = self.MakeLoaderAndLoad()
  e = self.accumulator.PopException('StopsTooClose')
  self.assertTrue(e.FormatProblem().find("0.00m apart") != -1)
  self.accumulator.AssertNoMoreExceptions()
 
  def testStationsTooNear(self):
  self.SetArchiveContents(
  "stops.txt",
  "stop_id,stop_name,stop_lat,stop_lon,location_type,parent_station\n"
  "BEATTY_AIRPORT,Airport,48.20000,140,,BEATTY_AIRPORT_STATION\n"
  "BULLFROG,Bullfrog,48.20003,140,,BULLFROG_STATION\n"
  "BEATTY_AIRPORT_STATION,Airport,48.20001,140,1,\n"
  "BULLFROG_STATION,Bullfrog,48.20002,140,1,\n"
  "STAGECOACH,Stagecoach Hotel,48.20016,140,,\n")
  schedule = self.MakeLoaderAndLoad()
  e = self.accumulator.PopException('StationsTooClose')
  self.assertTrue(e.FormatProblem().find("1.11m apart") != -1)
  self.assertTrue(e.FormatProblem().find("BEATTY_AIRPORT_STATION") != -1)
  self.accumulator.AssertNoMoreExceptions()
 
  def testStopNearNonParentStation(self):
  self.SetArchiveContents(
  "stops.txt",
  "stop_id,stop_name,stop_lat,stop_lon,location_type,parent_station\n"
  "BEATTY_AIRPORT,Airport,48.20000,140,,\n"
  "BULLFROG,Bullfrog,48.20005,140,,\n"
  "BULLFROG_STATION,Bullfrog,48.20006,140,1,\n"
  "STAGECOACH,Stagecoach Hotel,48.20016,140,,\n")
  schedule = self.MakeLoaderAndLoad()
  e = self.accumulator.PopException('DifferentStationTooClose')
  fmt = e.FormatProblem()
  self.assertTrue(re.search(
  r"parent_station of.*BULLFROG.*station.*BULLFROG_STATION.* 1.11m apart",
  fmt), fmt)
  self.accumulator.AssertNoMoreExceptions()
 
 
  class BadLatLonInStopUnitTest(ValidationTestCase):
  def runTest(self):
  stop = transitfeed.Stop(field_dict={"stop_id": "STOP1",
  "stop_name": "Stop one",
  "stop_lat": "0x20",
  "stop_lon": "140.01"})
  self.ExpectInvalidValue(stop, "stop_lat")
 
  stop = transitfeed.Stop(field_dict={"stop_id": "STOP1",
  "stop_name": "Stop one",
  "stop_lat": "13.0",
  "stop_lon": "1e2"})
  self.ExpectInvalidFloatValue(stop, "1e2")
 
 
  class BadLatLonInFileUnitTest(MemoryZipTestCase):
  def runTest(self):
  self.SetArchiveContents(
  "stops.txt",
  "stop_id,stop_name,stop_lat,stop_lon\n"
  "BEATTY_AIRPORT,Airport,0x20,140.00\n"
  "BULLFROG,Bullfrog,48.20001,140.0123\n"
  "STAGECOACH,Stagecoach Hotel,48.002,bogus\n")
  schedule = self.MakeLoaderAndLoad()
  e = self.accumulator.PopException('InvalidValue')
  self.assertEquals(2, e.row_num)
  self.assertEquals("stop_lat", e.column_name)
  e = self.accumulator.PopException('InvalidValue')
  self.assertEquals(4, e.row_num)
  self.assertEquals("stop_lon", e.column_name)
  self.accumulator.AssertNoMoreExceptions()
 
 
  class LoadUnknownFileInZipTestCase(MemoryZipTestCase):
  def runTest(self):
  self.SetArchiveContents(
  "stpos.txt",
  "stop_id,stop_name,stop_lat,stop_lon,location_type,parent_station\n"
  "BEATTY_AIRPORT,Airport,36.868446,-116.784582,,STATION\n"
  "STATION,Airport,36.868446,-116.784582,1,\n"
  "BULLFROG,Bullfrog,36.88108,-116.81797,,\n"
  "STAGECOACH,Stagecoach Hotel,36.915682,-116.751677,,\n")
  schedule = self.MakeLoaderAndLoad()
  e = self.accumulator.PopException('UnknownFile')
  self.assertEquals('stpos.txt', e.file_name)
  self.accumulator.AssertNoMoreExceptions()
 
 
  class TabDelimitedTestCase(MemoryZipTestCase):
  def runTest(self):
  # Create an extremely corrupt file by replacing each comma with a tab,
  # ignoring csv quoting.
  for arcname in self.GetArchiveNames():
  contents = self.GetArchiveContents(arcname)
  self.SetArchiveContents(arcname, contents.replace(",", "\t"))
  schedule = self.MakeLoaderAndLoad()
  # Don't call self.accumulator.AssertNoMoreExceptions() because there are
  # lots of problems but I only care that the validator doesn't crash. In the
  # magical future the validator will stop when the csv is obviously hosed.
 
 
  class RouteMemoryZipTestCase(MemoryZipTestCase):
  def assertLoadAndCheckExtraValues(self, schedule_file):
  """Load file-like schedule_file and check for extra route columns."""
  load_problems = GetTestFailureProblemReporter(
  self, ("ExpirationDate", "UnrecognizedColumn"))
  loaded_schedule = transitfeed.Loader(schedule_file,
  problems=load_problems,
  extra_validation=True).Load()
  self.assertEqual("foo", loaded_schedule.GetRoute("t")["t_foo"])
  self.assertEqual("", loaded_schedule.GetRoute("AB")["t_foo"])
  self.assertEqual("bar", loaded_schedule.GetRoute("n")["n_foo"])
  self.assertEqual("", loaded_schedule.GetRoute("AB")["n_foo"])
  # Uncomment the following lines to print the string in testExtraFileColumn
  # print repr(zipfile.ZipFile(schedule_file).read("routes.txt"))
  # self.fail()
 
  def testExtraObjectAttribute(self):
  """Extra columns added to an object are preserved when writing."""
  schedule = self.MakeLoaderAndLoad()
  # Add an attribute after AddRouteObject
  route_t = transitfeed.Route(short_name="T", route_type="Bus", route_id="t")
  schedule.AddRouteObject(route_t)
  route_t.t_foo = "foo"
  # Add an attribute before AddRouteObject
  route_n = transitfeed.Route(short_name="N", route_type="Bus", route_id="n")
  route_n.n_foo = "bar"
  schedule.AddRouteObject(route_n)
  saved_schedule_file = StringIO()
  schedule.WriteGoogleTransitFeed(saved_schedule_file)
  self.accumulator.AssertNoMoreExceptions()
 
  self.assertLoadAndCheckExtraValues(saved_schedule_file)
 
  def testExtraFileColumn(self):
  """Extra columns loaded from a file are preserved when writing."""
  # Uncomment the code in assertLoadAndCheckExtraValues to generate this
  # string.
  self.SetArchiveContents(
  "routes.txt",
  "route_id,agency_id,route_short_name,route_long_name,route_type,"
  "t_foo,n_foo\n"
  "AB,DTA,,Airport Bullfrog,3,,\n"
  "t,DTA,T,,3,foo,\n"
  "n,DTA,N,,3,,bar\n")
  load1_problems = GetTestFailureProblemReporter(
  self, ("ExpirationDate", "UnrecognizedColumn"))
  schedule = self.MakeLoaderAndLoad(problems=load1_problems)
  saved_schedule_file = StringIO()
  schedule.WriteGoogleTransitFeed(saved_schedule_file)
 
  self.assertLoadAndCheckExtraValues(saved_schedule_file)
 
 
  class RouteConstructorTestCase(util.TestCase):
  def setUp(self):
  self.accumulator = RecordingProblemAccumulator(self)
  self.problems = transitfeed.ProblemReporter(self.accumulator)
 
  def tearDown(self):
  self.accumulator.TearDownAssertNoMoreExceptions()
 
  def testDefault(self):
  route = transitfeed.Route()
  repr(route)
  self.assertEqual({}, dict(route))
  route.Validate(self.problems)
  repr(route)
  self.assertEqual({}, dict(route))
 
  e = self.accumulator.PopException('MissingValue')
  self.assertEqual('route_id', e.column_name)
  e = self.accumulator.PopException('MissingValue')
  self.assertEqual('route_type', e.column_name)
  e = self.accumulator.PopException('InvalidValue')
  self.assertEqual('route_short_name', e.column_name)
  self.accumulator.AssertNoMoreExceptions()
 
  def testInitArgs(self):
  # route_type name
  route = transitfeed.Route(route_id='id1', short_name='22', route_type='Bus')
  repr(route)
  route.Validate(self.problems)
  self.accumulator.AssertNoMoreExceptions()
  self.assertEquals(3, route.route_type) # converted to an int
  self.assertEquals({'route_id': 'id1', 'route_short_name': '22',
  'route_type': '3'}, dict(route))
 
  # route_type as an int
  route = transitfeed.Route(route_id='i1', long_name='Twenty 2', route_type=1)
  repr(route)
  route.Validate(self.problems)
  self.accumulator.AssertNoMoreExceptions()
  self.assertEquals(1, route.route_type) # kept as an int
  self.assertEquals({'route_id': 'i1', 'route_long_name': 'Twenty 2',
  'route_type': '1'}, dict(route))
 
  # route_type as a string
  route = transitfeed.Route(route_id='id1', short_name='22', route_type='1')
  repr(route)
  route.Validate(self.problems)
  self.accumulator.AssertNoMoreExceptions()
  self.assertEquals(1, route.route_type) # converted to an int
  self.assertEquals({'route_id': 'id1', 'route_short_name': '22',
  'route_type': '1'}, dict(route))
 
  # route_type has undefined int value
  route = transitfeed.Route(route_id='id1', short_name='22',
  route_type='8')
  repr(route)
  route.Validate(self.problems)
  e = self.accumulator.PopException('InvalidValue')
  self.assertEqual('route_type', e.column_name)
  self.assertEqual(1, e.type)
  self.accumulator.AssertNoMoreExceptions()
  self.assertEquals({'route_id': 'id1', 'route_short_name': '22',
  'route_type': '8'}, dict(route))
 
  # route_type that doesn't parse
  route = transitfeed.Route(route_id='id1', short_name='22',
  route_type='1foo')
  repr(route)
  route.Validate(self.problems)
  e = self.accumulator.PopException('InvalidValue')
  self.assertEqual('route_type', e.column_name)
  self.accumulator.AssertNoMoreExceptions()
  self.assertEquals({'route_id': 'id1', 'route_short_name': '22',
  'route_type': '1foo'}, dict(route))
 
  # agency_id
  route = transitfeed.Route(route_id='id1', short_name='22', route_type=1,
  agency_id='myage')
  repr(route)
  route.Validate(self.problems)
  self.accumulator.AssertNoMoreExceptions()
  self.assertEquals({'route_id': 'id1', 'route_short_name': '22',
  'route_type': '1', 'agency_id': 'myage'}, dict(route))
 
  def testInitArgOrder(self):
  """Call Route.__init__ without any names so a change in order is noticed."""
  route = transitfeed.Route('short', 'long name', 'Bus', 'r1', 'a1')
  self.assertEquals({'route_id': 'r1', 'route_short_name': 'short',
  'route_long_name': 'long name',
  'route_type': '3', 'agency_id': 'a1'}, dict(route))
 
  def testFieldDict(self):
  route = transitfeed.Route(field_dict={})
  self.assertEquals({}, dict(route))
 
  route = transitfeed.Route(field_dict={
  'route_id': 'id1', 'route_short_name': '22', 'agency_id': 'myage',
  'route_type': '1'})
  route.Validate(self.problems)
  self.accumulator.AssertNoMoreExceptions()
  self.assertEquals({'route_id': 'id1', 'route_short_name': '22',
  'agency_id': 'myage', 'route_type': '1'}, dict(route))
 
  route = transitfeed.Route(field_dict={
  'route_id': 'id1', 'route_short_name': '22', 'agency_id': 'myage',
  'route_type': '1', 'my_column': 'v'})
  route.Validate(self.problems)
  self.accumulator.AssertNoMoreExceptions()
  self.assertEquals({'route_id': 'id1', 'route_short_name': '22',
  'agency_id': 'myage', 'route_type': '1',
  'my_column':'v'}, dict(route))
  route._private = 0.3 # Isn't copied
  route_copy = transitfeed.Route(field_dict=route)
  self.assertEquals({'route_id': 'id1', 'route_short_name': '22',
  'agency_id': 'myage', 'route_type': '1',
  'my_column':'v'}, dict(route_copy))
 
 
  class RouteValidationTestCase(ValidationTestCase):
  def runTest(self):
  # success case
  route = transitfeed.Route()
  route.route_id = '054C'
  route.route_short_name = '54C'
  route.route_long_name = 'South Side - North Side'
  route.route_type = 7
  route.Validate(self.problems)
 
  # blank short & long names
  route.route_short_name = ''
  route.route_long_name = ' '
  self.ExpectInvalidValue(route, 'route_short_name')
 
  # short name too long
  route.route_short_name = 'South Side'
  route.route_long_name = ''
  self.ExpectInvalidValue(route, 'route_short_name')
  route.route_short_name = 'M7bis' # 5 is OK
  route.Validate(self.problems)
 
  # long name contains short name
  route.route_short_name = '54C'
  route.route_long_name = '54C South Side - North Side'
  self.ExpectInvalidValue(route, 'route_long_name')
  route.route_long_name = '54C(South Side - North Side)'
  self.ExpectInvalidValue(route, 'route_long_name')
  route.route_long_name = '54C-South Side - North Side'
  self.ExpectInvalidValue(route, 'route_long_name')
 
  # long name is same as short name
  route.route_short_name = '54C'
  route.route_long_name = '54C'
  self.ExpectInvalidValue(route, 'route_long_name')
 
  # route description is same as short name
  route.route_desc = '54C'
  route.route_short_name = '54C'
  route.route_long_name = ''
  self.ExpectInvalidValue(route, 'route_desc')
  route.route_desc = None
 
  # route description is same as long name
  route.route_desc = 'South Side - North Side'
  route.route_long_name = 'South Side - North Side'
  self.ExpectInvalidValue(route, 'route_desc')
  route.route_desc = None
 
  # invalid route types
  route.route_type = 8
  self.ExpectInvalidValue(route, 'route_type')
  route.route_type = -1
  self.ExpectInvalidValue(route, 'route_type')
  route.route_type = 7
 
  # invalid route URL
  route.route_url = 'www.example.com'
  self.ExpectInvalidValue(route, 'route_url')
  route.route_url = None
 
  # invalid route color
  route.route_color = 'orange'
  self.ExpectInvalidValue(route, 'route_color')
  route.route_color = None
 
  # invalid route text color
  route.route_text_color = 'orange'
  self.ExpectInvalidValue(route, 'route_text_color')
  route.route_text_color = None
 
  # missing route ID
  route.route_id = None
  self.ExpectMissingValue(route, 'route_id')
  route.route_id = '054C'
 
  # bad color contrast
  route.route_text_color = None # black
  route.route_color = '0000FF' # Bad
  self.ExpectInvalidValue(route, 'route_color')
  route.route_color = '00BF00' # OK
  route.Validate(self.problems)
  route.route_color = '005F00' # Bad
  self.ExpectInvalidValue(route, 'route_color')
  route.route_color = 'FF00FF' # OK
  route.Validate(self.problems)
  route.route_text_color = 'FFFFFF' # OK too
  route.Validate(self.problems)
  route.route_text_color = '00FF00' # think of color-blind people!
  self.ExpectInvalidValue(route, 'route_color')
  route.route_text_color = '007F00'
  route.route_color = 'FF0000'
  self.ExpectInvalidValue(route, 'route_color')
  route.route_color = '00FFFF' # OK
  route.Validate(self.problems)
  route.route_text_color = None # black
  route.route_color = None # white
  route.Validate(self.problems)
  self.accumulator.AssertNoMoreExceptions()
 
 
  class ShapeValidationTestCase(ValidationTestCase):
  def ExpectFailedAdd(self, shape, lat, lon, dist, column_name, value):
  self.ExpectInvalidValueInClosure(
  column_name, value,
  lambda: shape.AddPoint(lat, lon, dist, self.problems))
 
  def runTest(self):
  shape = transitfeed.Shape('TEST')
  repr(shape) # shouldn't crash
  self.ExpectOtherProblem(shape) # no points!
 
  self.ExpectFailedAdd(shape, 36.905019, -116.763207, -1,
  'shape_dist_traveled', -1)
 
  shape.AddPoint(36.915760, -116.751709, 0, self.problems)
  shape.AddPoint(36.905018, -116.763206, 5, self.problems)
  shape.Validate(self.problems)
 
  shape.shape_id = None
  self.ExpectMissingValue(shape, 'shape_id')
  shape.shape_id = 'TEST'
 
  self.ExpectFailedAdd(shape, 91, -116.751709, 6, 'shape_pt_lat', 91)
  self.ExpectFailedAdd(shape, -91, -116.751709, 6, 'shape_pt_lat', -91)
 
  self.ExpectFailedAdd(shape, 36.915760, -181, 6, 'shape_pt_lon', -181)
  self.ExpectFailedAdd(shape, 36.915760, 181, 6, 'shape_pt_lon', 181)
 
  self.ExpectFailedAdd(shape, 0.5, -0.5, 6, 'shape_pt_lat', 0.5)
  self.ExpectFailedAdd(shape, 0, 0, 6, 'shape_pt_lat', 0)
 
  # distance decreasing is bad, but staying the same is OK
  shape.AddPoint(36.905019, -116.763206, 4, self.problems)
  e = self.accumulator.PopException('InvalidValue')
  self.assertMatchesRegex('Each subsequent point', e.FormatProblem())
  self.assertMatchesRegex('distance was 5.000000.', e.FormatProblem())
  self.accumulator.AssertNoMoreExceptions()
 
  shape.AddPoint(36.925019, -116.764206, 6, self.problems)
  self.accumulator.AssertNoMoreExceptions()
 
  shapepoint = transitfeed.ShapePoint('TEST', 36.915760, -116.7156, 6, 8)
  shape.AddShapePointObjectUnsorted(shapepoint, self.problems)
  shapepoint = transitfeed.ShapePoint('TEST', 36.915760, -116.7156, 5, 10)
  shape.AddShapePointObjectUnsorted(shapepoint, self.problems)
  e = self.accumulator.PopException('InvalidValue')
  self.assertMatchesRegex('Each subsequent point', e.FormatProblem())
  self.assertMatchesRegex('distance was 8.000000.', e.FormatProblem())
  self.accumulator.AssertNoMoreExceptions()
 
  shapepoint = transitfeed.ShapePoint('TEST', 36.915760, -116.7156, 6, 11)
  shape.AddShapePointObjectUnsorted(shapepoint, self.problems)
  e = self.accumulator.PopException('InvalidValue')
  self.assertMatchesRegex('The sequence number 6 occurs ', e.FormatProblem())
  self.assertMatchesRegex('once in shape TEST.', e.FormatProblem())
  self.accumulator.AssertNoMoreExceptions()
 
 
  class ShapePointValidationTestCase(ValidationTestCase):
  def runTest(self):
  shapepoint = transitfeed.ShapePoint('', 36.915720, -116.7156, 0, 0)
  self.ExpectMissingValueInClosure('shape_id',
  lambda: shapepoint.ParseAttributes(self.problems))
 
  shapepoint = transitfeed.ShapePoint('T', '36.9151', '-116.7611', '00', '0')
  shapepoint.ParseAttributes(self.problems)
  e = self.accumulator.PopException('InvalidNonNegativeIntegerValue')
  self.assertMatchesRegex('not have a leading zero', e.FormatProblem())
  self.accumulator.AssertNoMoreExceptions()
 
  shapepoint = transitfeed.ShapePoint('T', '36.9151', '-116.7611', -1, '0')
  shapepoint.ParseAttributes(self.problems)
  e = self.accumulator.PopException('InvalidValue')
  self.assertMatchesRegex('Value should be a number', e.FormatProblem())
  self.accumulator.AssertNoMoreExceptions()
 
  shapepoint = transitfeed.ShapePoint('T', '0.1', '0.1', '1', '0')
  shapepoint.ParseAttributes(self.problems)
  e = self.accumulator.PopException('InvalidValue')
  self.assertMatchesRegex('too close to 0, 0,', e.FormatProblem())
  self.accumulator.AssertNoMoreExceptions()
 
  shapepoint = transitfeed.ShapePoint('T', '36.9151', '-116.7611', '0', '')
  shapepoint.ParseAttributes(self.problems)
  shapepoint = transitfeed.ShapePoint('T', '36.9151', '-116.7611', '0', '-1')
  shapepoint.ParseAttributes(self.problems)
  e = self.accumulator.PopException('InvalidValue')
  self.assertMatchesRegex('Invalid value -1.0', e.FormatProblem())
  self.assertMatchesRegex('should be a positive number', e.FormatProblem())
  self.accumulator.AssertNoMoreExceptions()
 
 
  class FareAttributeValidationTestCase(ValidationTestCase):
  def runTest(self):
  fare = transitfeed.FareAttribute()
  fare.fare_id = "normal"
  fare.price = 1.50
  fare.currency_type = "USD"
  fare.payment_method = 0
  fare.transfers = 1
  fare.transfer_duration = 7200
  fare.Validate(self.problems)
 
  fare.fare_id = None
  self.ExpectMissingValue(fare, "fare_id")
  fare.fare_id = ''
  self.ExpectMissingValue(fare, "fare_id")
  fare.fare_id = "normal"
 
  fare.price = "1.50"
  self.ExpectInvalidValue(fare, "price")
  fare.price = 1
  fare.Validate(self.problems)
  fare.price = None
  self.ExpectMissingValue(fare, "price")
  fare.price = 0.0
  fare.Validate(self.problems)
  fare.price = -1.50
  self.ExpectInvalidValue(fare, "price")
  fare.price = 1.50
 
  fare.currency_type = ""
  self.ExpectMissingValue(fare, "currency_type")
  fare.currency_type = None
  self.ExpectMissingValue(fare, "currency_type")
  fare.currency_type = "usd"
  self.ExpectInvalidValue(fare, "currency_type")
  fare.currency_type = "KML"
  self.ExpectInvalidValue(fare, "currency_type")
  fare.currency_type = "USD"
 
  fare.payment_method = "0"
  self.ExpectInvalidValue(fare, "payment_method")
  fare.payment_method = -1
  self.ExpectInvalidValue(fare, "payment_method")
  fare.payment_method = 1
  fare.Validate(self.problems)
  fare.payment_method = 2
  self.ExpectInvalidValue(fare, "payment_method")
  fare.payment_method = None
  self.ExpectMissingValue(fare, "payment_method")
  fare.payment_method = ""
  self.ExpectMissingValue(fare, "payment_method")
  fare.payment_method = 0
 
  fare.transfers = "1"
  self.ExpectInvalidValue(fare, "transfers")
  fare.transfers = -1
  self.ExpectInvalidValue(fare, "transfers")
  fare.transfers = 2
  fare.Validate(self.problems)
  fare.transfers = 3
  self.ExpectInvalidValue(fare, "transfers")
  fare.transfers = None
  fare.Validate(self.problems)
  fare.transfers = 1
 
  fare.transfer_duration = 0
  fare.Validate(self.problems)
  fare.transfer_duration = None
  fare.Validate(self.problems)
  fare.transfer_duration = -3600
  self.ExpectInvalidValue(fare, "transfer_duration")
  fare.transfers = 0 # no transfers allowed and duration specified!
  fare.transfer_duration = 3600
  fare.Validate(self.problems)
  fare.transfers = 1
  fare.transfer_duration = "3600"
  self.ExpectInvalidValue(fare, "transfer_duration")
  fare.transfer_duration = 7200
  self.accumulator.AssertNoMoreExceptions()
 
 
  class TransferObjectTestCase(ValidationTestCase):
  def testValidation(self):
  # Totally bogus data shouldn't cause a crash
  transfer = transitfeed.Transfer(field_dict={"ignored": "foo"})
  self.assertEquals(0, transfer.transfer_type)
 
  transfer = transitfeed.Transfer(from_stop_id = "S1", to_stop_id = "S2",
  transfer_type = "1")
  self.assertEquals("S1", transfer.from_stop_id)
  self.assertEquals("S2", transfer.to_stop_id)
  self.assertEquals(1, transfer.transfer_type)
  self.assertEquals(None, transfer.min_transfer_time)
  # references to other tables aren't checked without schedule so this
  # validates even though from_stop_id and to_stop_id are invalid.
  transfer.Validate(self.problems)
  self.accumulator.AssertNoMoreExceptions()
  self.assertEquals("S1", transfer.from_stop_id)
  self.assertEquals("S2", transfer.to_stop_id)
  self.assertEquals(1, transfer.transfer_type)
  self.assertEquals(None, transfer.min_transfer_time)
  self.accumulator.AssertNoMoreExceptions()
 
  transfer = transitfeed.Transfer(field_dict={"from_stop_id": "S1", \
  "to_stop_id": "S2", \
  "transfer_type": "2", \
  "min_transfer_time": "2"})
  self.assertEquals("S1", transfer.from_stop_id)
  self.assertEquals("S2", transfer.to_stop_id)
  self.assertEquals(2, transfer.transfer_type)
  self.assertEquals(2, transfer.min_transfer_time)
  transfer.Validate(self.problems)
  self.assertEquals("S1", transfer.from_stop_id)
  self.assertEquals("S2", transfer.to_stop_id)
  self.assertEquals(2, transfer.transfer_type)
  self.assertEquals(2, transfer.min_transfer_time)
  self.accumulator.AssertNoMoreExceptions()
 
  transfer = transitfeed.Transfer(field_dict={"from_stop_id": "S1", \
  "to_stop_id": "S2", \
  "transfer_type": "-4", \
  "min_transfer_time": "2"})
  self.assertEquals("S1", transfer.from_stop_id)
  self.assertEquals("S2", transfer.to_stop_id)
  self.assertEquals("-4", transfer.transfer_type)
  self.assertEquals(2, transfer.min_transfer_time)
  transfer.Validate(self.problems)
  e = self.accumulator.PopInvalidValue("transfer_type")
  e = self.accumulator.PopException(
  "MinimumTransferTimeSetWithInvalidTransferType")
  self.assertEquals("S1", transfer.from_stop_id)
  self.assertEquals("S2", transfer.to_stop_id)
  self.assertEquals("-4", transfer.transfer_type)
  self.assertEquals(2, transfer.min_transfer_time)
 
  transfer = transitfeed.Transfer(field_dict={"from_stop_id": "S1", \
  "to_stop_id": "S2", \
  "transfer_type": "", \
  "min_transfer_time": "-1"})
  self.assertEquals(0, transfer.transfer_type)
  transfer.Validate(self.problems)
  # It's negative *and* transfer_type is not 2
  e = self.accumulator.PopException(
  "MinimumTransferTimeSetWithInvalidTransferType")
  e = self.accumulator.PopInvalidValue("min_transfer_time")
 
  # Non-integer min_transfer_time with transfer_type == 2
  transfer = transitfeed.Transfer(field_dict={"from_stop_id": "S1", \
  "to_stop_id": "S2", \
  "transfer_type": "2", \
  "min_transfer_time": "foo"})
  self.assertEquals("foo", transfer.min_transfer_time)
  transfer.Validate(self.problems)
  e = self.accumulator.PopInvalidValue("min_transfer_time")
 
  # Non-integer min_transfer_time with transfer_type != 2
  transfer = transitfeed.Transfer(field_dict={"from_stop_id": "S1", \
  "to_stop_id": "S2", \
  "transfer_type": "1", \
  "min_transfer_time": "foo"})
  self.assertEquals("foo", transfer.min_transfer_time)
  transfer.Validate(self.problems)
  # It's not an integer *and* transfer_type is not 2
  e = self.accumulator.PopException(
  "MinimumTransferTimeSetWithInvalidTransferType")
  e = self.accumulator.PopInvalidValue("min_transfer_time")
 
  # Fractional min_transfer_time with transfer_type == 2
  transfer = transitfeed.Transfer(field_dict={"from_stop_id": "S1", \
  "to_stop_id": "S2", \
  "transfer_type": "2", \
  "min_transfer_time": "2.5"})
  self.assertEquals("2.5", transfer.min_transfer_time)
  transfer.Validate(self.problems)
  e = self.accumulator.PopInvalidValue("min_transfer_time")
 
  # Fractional min_transfer_time with transfer_type != 2
  transfer = transitfeed.Transfer(field_dict={"from_stop_id": "S1", \
  "to_stop_id": "S2", \
  "transfer_type": "1", \
  "min_transfer_time": "2.5"})
  self.assertEquals("2.5", transfer.min_transfer_time)
  transfer.Validate(self.problems)
  # It's not an integer *and* transfer_type is not 2
  e = self.accumulator.PopException(
  "MinimumTransferTimeSetWithInvalidTransferType")
  e = self.accumulator.PopInvalidValue("min_transfer_time")
 
  # simple successes
  transfer = transitfeed.Transfer()
  transfer.from_stop_id = "S1"
  transfer.to_stop_id = "S2"
  transfer.transfer_type = 0
  repr(transfer) # shouldn't crash
  transfer.Validate(self.problems)
  transfer.transfer_type = 3
  transfer.Validate(self.problems)
  self.accumulator.AssertNoMoreExceptions()
 
  # transfer_type is out of range
  transfer.transfer_type = 4
  self.ExpectInvalidValue(transfer, "transfer_type")
  transfer.transfer_type = -1
  self.ExpectInvalidValue(transfer, "transfer_type")
  transfer.transfer_type = "text"
  self.ExpectInvalidValue(transfer, "transfer_type")
  transfer.transfer_type = 2
 
  # invalid min_transfer_time
  transfer.min_transfer_time = -1
  self.ExpectInvalidValue(transfer, "min_transfer_time")
  transfer.min_transfer_time = "text"
  self.ExpectInvalidValue(transfer, "min_transfer_time")
  transfer.min_transfer_time = 4*3600
  transfer.Validate(self.problems)
  e = self.accumulator.PopInvalidValue("min_transfer_time")
  self.assertEquals(e.type, transitfeed.TYPE_WARNING)
  transfer.min_transfer_time = 25*3600
  transfer.Validate(self.problems)
  e = self.accumulator.PopInvalidValue("min_transfer_time")
  self.assertEquals(e.type, transitfeed.TYPE_ERROR)
  transfer.min_transfer_time = 250
  transfer.Validate(self.problems)
  self.accumulator.AssertNoMoreExceptions()
 
  # missing stop ids
  transfer.from_stop_id = ""
  self.ExpectMissingValue(transfer, 'from_stop_id')
  transfer.from_stop_id = "S1"
  transfer.to_stop_id = None
  self.ExpectMissingValue(transfer, 'to_stop_id')
  transfer.to_stop_id = "S2"
 
  # from_stop_id and to_stop_id are present in schedule
  schedule = transitfeed.Schedule()
  # 597m appart
  stop1 = schedule.AddStop(57.5, 30.2, "stop 1")
  stop2 = schedule.AddStop(57.5, 30.21, "stop 2")
  transfer = transitfeed.Transfer(schedule=schedule)
  transfer.from_stop_id = stop1.stop_id
  transfer.to_stop_id = stop2.stop_id
  transfer.transfer_type = 2
  transfer.min_transfer_time = 600
  repr(transfer) # shouldn't crash
  transfer.Validate(self.problems)
  self.accumulator.AssertNoMoreExceptions()
 
  # only from_stop_id is present in schedule
  schedule = transitfeed.Schedule()
  stop1 = schedule.AddStop(57.5, 30.2, "stop 1")
  transfer = transitfeed.Transfer(schedule=schedule)
  transfer.from_stop_id = stop1.stop_id
  transfer.to_stop_id = "unexist"
  transfer.transfer_type = 2
  transfer.min_transfer_time = 250
  self.ExpectInvalidValue(transfer, 'to_stop_id')
  transfer.from_stop_id = "unexist"
  transfer.to_stop_id = stop1.stop_id
  self.ExpectInvalidValue(transfer, "from_stop_id")
  self.accumulator.AssertNoMoreExceptions()
 
  # Transfer can only be added to a schedule once because _schedule is set
  transfer = transitfeed.Transfer()
  transfer.from_stop_id = stop1.stop_id
  transfer.to_stop_id = stop1.stop_id
  schedule.AddTransferObject(transfer)
  self.assertRaises(AssertionError, schedule.AddTransferObject, transfer)
 
  def testValidationSpeedDistanceAllTransferTypes(self):
  schedule = transitfeed.Schedule()
  transfer = transitfeed.Transfer(schedule=schedule)
  stop1 = schedule.AddStop(1, 0, "stop 1")
  stop2 = schedule.AddStop(0, 1, "stop 2")
  transfer = transitfeed.Transfer(schedule=schedule)
  transfer.from_stop_id = stop1.stop_id
  transfer.to_stop_id = stop2.stop_id
  for transfer_type in [0, 1, 2, 3]:
  transfer.transfer_type = transfer_type
 
  # from_stop_id and to_stop_id are present in schedule
  # and a bit far away (should be warning)
  # 2303m appart
  stop1.stop_lat = 57.5
  stop1.stop_lon = 30.32
  stop2.stop_lat = 57.52
  stop2.stop_lon = 30.33
  transfer.min_transfer_time = 2500
  repr(transfer) # shouldn't crash
  transfer.Validate(self.problems)
  if transfer_type != 2:
  e = self.accumulator.PopException(
  "MinimumTransferTimeSetWithInvalidTransferType")
  self.assertEquals(e.transfer_type, transfer.transfer_type)
  e = self.accumulator.PopException('TransferDistanceTooBig')
  self.assertEquals(e.type, transitfeed.TYPE_WARNING)
  self.assertEquals(e.from_stop_id, stop1.stop_id)
  self.assertEquals(e.to_stop_id, stop2.stop_id)
  self.accumulator.AssertNoMoreExceptions()
 
  # from_stop_id and to_stop_id are present in schedule
  # and too far away (should be error)
  # 11140m appart
  stop1.stop_lat = 57.5
  stop1.stop_lon = 30.32
  stop2.stop_lat = 57.4
  stop2.stop_lon = 30.33
  transfer.min_transfer_time = 3600
  repr(transfer) # shouldn't crash
  transfer.Validate(self.problems)
  if transfer_type != 2:
  e = self.accumulator.PopException(
  "MinimumTransferTimeSetWithInvalidTransferType")
  self.assertEquals(e.transfer_type, transfer.transfer_type)
  e = self.accumulator.PopException('TransferDistanceTooBig')
  self.assertEquals(e.type, transitfeed.TYPE_ERROR)
  self.assertEquals(e.from_stop_id, stop1.stop_id)
  self.assertEquals(e.to_stop_id, stop2.stop_id)
  e = self.accumulator.PopException('TransferWalkingSpeedTooFast')
  self.assertEquals(e.type, transitfeed.TYPE_WARNING)
  self.assertEquals(e.from_stop_id, stop1.stop_id)
  self.assertEquals(e.to_stop_id, stop2.stop_id)
  self.accumulator.AssertNoMoreExceptions()
 
  def testSmallTransferTimeTriggersWarning(self):
  # from_stop_id and to_stop_id are present in schedule
  # and transfer time is too small
  schedule = transitfeed.Schedule()
  # 298m appart
  stop1 = schedule.AddStop(57.5, 30.2, "stop 1")
  stop2 = schedule.AddStop(57.5, 30.205, "stop 2")
  transfer = transitfeed.Transfer(schedule=schedule)
  transfer.from_stop_id = stop1.stop_id
  transfer.to_stop_id = stop2.stop_id
  transfer.transfer_type = 2
  transfer.min_transfer_time = 1
  repr(transfer) # shouldn't crash
  transfer.Validate(self.problems)
  e = self.accumulator.PopException('TransferWalkingSpeedTooFast')
  self.assertEquals(e.type, transitfeed.TYPE_WARNING)
  self.assertEquals(e.from_stop_id, stop1.stop_id)
  self.assertEquals(e.to_stop_id, stop2.stop_id)
  self.accumulator.AssertNoMoreExceptions()
 
  def testVeryCloseStationsDoNotTriggerWarning(self):
  # from_stop_id and to_stop_id are present in schedule
  # and transfer time is too small, but stations
  # are very close together.
  schedule = transitfeed.Schedule()
  # 239m appart
  stop1 = schedule.AddStop(57.5, 30.2, "stop 1")
  stop2 = schedule.AddStop(57.5, 30.204, "stop 2")
  transfer = transitfeed.Transfer(schedule=schedule)
  transfer.from_stop_id = stop1.stop_id
  transfer.to_stop_id = stop2.stop_id
  transfer.transfer_type = 2
  transfer.min_transfer_time = 1
  repr(transfer) # shouldn't crash
  transfer.Validate(self.problems)
  self.accumulator.AssertNoMoreExceptions()
 
  def testCustomAttribute(self):
  """Add unknown attributes to a Transfer and make sure they are saved."""
  transfer = transitfeed.Transfer()
  transfer.attr1 = "foo1"
  schedule = self.SimpleSchedule()
  transfer.to_stop_id = "stop1"
  transfer.from_stop_id = "stop1"
  schedule.AddTransferObject(transfer)
  transfer.attr2 = "foo2"
 
  saved_schedule_file = StringIO()
  schedule.WriteGoogleTransitFeed(saved_schedule_file)
  self.accumulator.AssertNoMoreExceptions()
 
  # Ignore NoServiceExceptions error to keep the test simple
  load_problems = GetTestFailureProblemReporter(
  self, ("ExpirationDate", "UnrecognizedColumn", "NoServiceExceptions"))
  loaded_schedule = transitfeed.Loader(saved_schedule_file,
  problems=load_problems,
  extra_validation=True).Load()
  transfers = loaded_schedule.GetTransferList()
  self.assertEquals(1, len(transfers))
  self.assertEquals("foo1", transfers[0].attr1)
  self.assertEquals("foo1", transfers[0]["attr1"])
  self.assertEquals("foo2", transfers[0].attr2)
  self.assertEquals("foo2", transfers[0]["attr2"])
 
  def testDuplicateId(self):
  schedule = self.SimpleSchedule()
  transfer1 = transitfeed.Transfer(from_stop_id="stop1", to_stop_id="stop2")
  schedule.AddTransferObject(transfer1)
  transfer2 = transitfeed.Transfer(field_dict=transfer1)
  transfer2.transfer_type = 3
  schedule.AddTransferObject(transfer2)
  transfer2.Validate()
  e = self.accumulator.PopException('DuplicateID')
  self.assertEquals('(from_stop_id, to_stop_id)', e.column_name)
  self.assertEquals('(stop1, stop2)', e.value)
  self.assertTrue(e.IsWarning())
  self.accumulator.AssertNoMoreExceptions()
  # Check that both transfers were kept
  self.assertEquals(transfer1, schedule.GetTransferList()[0])
  self.assertEquals(transfer2, schedule.GetTransferList()[1])
 
  # Adding a transfer with a different ID shouldn't cause a problem report.
  transfer3 = transitfeed.Transfer(from_stop_id="stop1", to_stop_id="stop3")
  schedule.AddTransferObject(transfer3)
  self.assertEquals(3, len(schedule.GetTransferList()))
  self.accumulator.AssertNoMoreExceptions()
 
  # GetTransferIter should return all Transfers
  transfer4 = transitfeed.Transfer(from_stop_id="stop1")
  schedule.AddTransferObject(transfer4)
  self.assertEquals(
  ",stop2,stop2,stop3",
  ",".join(sorted(t["to_stop_id"] for t in schedule.GetTransferIter())))
  self.accumulator.AssertNoMoreExceptions()
 
 
  class TransferValidationTestCase(MemoryZipTestCase):
  """Integration test for transfers."""
 
  def testInvalidStopIds(self):
  self.SetArchiveContents(
  "transfers.txt",
  "from_stop_id,to_stop_id,transfer_type\n"
  "DOESNOTEXIST,BULLFROG,2\n"
  ",BULLFROG,2\n"
  "BULLFROG,,2\n"
  "BULLFROG,DOESNOTEXISTEITHER,2\n"
  "DOESNOTEXIT,DOESNOTEXISTEITHER,2\n"
  ",,2\n")
  schedule = self.MakeLoaderAndLoad()
  # First row
  e = self.accumulator.PopInvalidValue('from_stop_id')
  # Second row
  e = self.accumulator.PopMissingValue('from_stop_id')
  # Third row
  e = self.accumulator.PopMissingValue('to_stop_id')
  # Fourth row
  e = self.accumulator.PopInvalidValue('to_stop_id')
  # Fifth row
  e = self.accumulator.PopInvalidValue('from_stop_id')
  e = self.accumulator.PopInvalidValue('to_stop_id')
  # Sixth row
  e = self.accumulator.PopMissingValue('from_stop_id')
  e = self.accumulator.PopMissingValue('to_stop_id')
  self.accumulator.AssertNoMoreExceptions()
 
  def testDuplicateTransfer(self):
  self.AppendToArchiveContents(
  "stops.txt",
  "BEATTY_AIRPORT_HANGER,Airport Hanger,36.868178,-116.784915\n"
  "BEATTY_AIRPORT_34,Runway 34,36.85352,-116.786316\n")
  self.AppendToArchiveContents(
  "trips.txt",
  "AB,FULLW,AIR1\n")
  self.AppendToArchiveContents(
  "stop_times.txt",
  "AIR1,7:00:00,7:00:00,BEATTY_AIRPORT_HANGER,1\n"
  "AIR1,7:05:00,7:05:00,BEATTY_AIRPORT_34,2\n"
  "AIR1,7:10:00,7:10:00,BEATTY_AIRPORT_HANGER,3\n")
  self.SetArchiveContents(
  "transfers.txt",
  "from_stop_id,to_stop_id,transfer_type\n"
  "BEATTY_AIRPORT,BEATTY_AIRPORT_HANGER,0\n"
  "BEATTY_AIRPORT,BEATTY_AIRPORT_HANGER,3")
  schedule = self.MakeLoaderAndLoad()
  e = self.accumulator.PopException('DuplicateID')
  self.assertEquals('(from_stop_id, to_stop_id)', e.column_name)
  self.assertEquals('(BEATTY_AIRPORT, BEATTY_AIRPORT_HANGER)', e.value)
  self.assertTrue(e.IsWarning())
  self.assertEquals('transfers.txt', e.file_name)
  self.assertEquals(3, e.row_num)
  self.accumulator.AssertNoMoreExceptions()
 
  saved_schedule_file = StringIO()
  schedule.WriteGoogleTransitFeed(saved_schedule_file)
  self.accumulator.AssertNoMoreExceptions()
  load_problems = GetTestFailureProblemReporter(
  self, ("ExpirationDate", "DuplicateID"))
  loaded_schedule = transitfeed.Loader(saved_schedule_file,
  problems=load_problems,
  extra_validation=True).Load()
  self.assertEquals(
  [0, 3],
  [int(t.transfer_type) for t in loaded_schedule.GetTransferIter()])
 
 
  class ServicePeriodValidationTestCase(ValidationTestCase):
  def runTest(self):
  # success case
  period = transitfeed.ServicePeriod()
  repr(period) # shouldn't crash
  period.service_id = 'WEEKDAY'
  period.start_date = '20070101'
  period.end_date = '20071231'
  period.day_of_week[0] = True
  repr(period) # shouldn't crash
  period.Validate(self.problems)
 
  # missing start_date. If one of start_date or end_date is None then
  # ServicePeriod.Validate assumes the required column is missing and already
  # generated an error. Instead set it to an empty string, such as when the
  # csv cell is empty. See also comment in ServicePeriod.Validate.
  period.start_date = ''
  self.ExpectMissingValue(period, 'start_date')
  period.start_date = '20070101'
 
  # missing end_date
  period.end_date = ''
  self.ExpectMissingValue(period, 'end_date')
  period.end_date = '20071231'
 
  # invalid start_date
  period.start_date = '2007-01-01'
  self.ExpectInvalidValue(period, 'start_date')
  period.start_date = '20070101'
 
  # impossible start_date
  period.start_date = '20070229'
  self.ExpectInvalidValue(period, 'start_date')
  period.start_date = '20070101'
 
  # invalid end_date
  period.end_date = '2007/12/31'
  self.ExpectInvalidValue(period, 'end_date')
  period.end_date = '20071231'
 
  # start & end dates out of order
  period.end_date = '20060101'
  self.ExpectInvalidValue(period, 'end_date')
  period.end_date = '20071231'
 
  # no service in period
  period.day_of_week[0] = False
  self.ExpectOtherProblem(period)
  period.day_of_week[0] = True
 
  # invalid exception date
  period.SetDateHasService('2007', False)
  self.ExpectInvalidValue(period, 'date', '2007')
  period.ResetDateToNormalService('2007')
 
  period2 = transitfeed.ServicePeriod(
  field_list=['serviceid1', '20060101', '20071231', '1', '0', 'h', '1',
  '1', '1', '1'])
  self.ExpectInvalidValue(period2, 'wednesday', 'h')
  repr(period) # shouldn't crash
 
  def testHasExceptions(self):
  # A new ServicePeriod object has no exceptions
  period = transitfeed.ServicePeriod()
  self.assertFalse(period.HasExceptions())
 
  # Only regular service, no exceptions
  period.service_id = 'WEEKDAY'
  period.start_date = '20070101'
  period.end_date = '20071231'
  period.day_of_week[0] = True
  self.assertFalse(period.HasExceptions())
 
  # Regular service + removed service exception
  period.SetDateHasService('20070101', False)
  self.assertTrue(period.HasExceptions())
 
  # Regular service + added service exception
  period.SetDateHasService('20070101', True)
  self.assertTrue(period.HasExceptions())
 
  # Only added service exception
  period = transitfeed.ServicePeriod()
  period.SetDateHasService('20070101', True)
  self.assertTrue(period.HasExceptions())
 
  # Only removed service exception
  period = transitfeed.ServicePeriod()
  period.SetDateHasService('20070101', False)
  self.assertTrue(period.HasExceptions())
 
 
  class ServicePeriodDateRangeTestCase(ValidationTestCase):
  def runTest(self):
  period = transitfeed.ServicePeriod()
  period.service_id = 'WEEKDAY'
  period.start_date = '20070101'
  period.end_date = '20071231'
  period.SetWeekdayService(True)
  period.SetDateHasService('20071231', False)
  period.Validate(self.problems)
  self.assertEqual(('20070101', '20071231'), period.GetDateRange())
 
  period2 = transitfeed.ServicePeriod()
  period2.service_id = 'HOLIDAY'
  period2.SetDateHasService('20071225', True)
  period2.SetDateHasService('20080101', True)
  period2.SetDateHasService('20080102', False)
  period2.Validate(self.problems)
  self.assertEqual(('20071225', '20080101'), period2.GetDateRange())
 
  period2.start_date = '20071201'
  period2.end_date = '20071225'
  period2.Validate(self.problems)
  self.assertEqual(('20071201', '20080101'), period2.GetDateRange())
 
  period3 = transitfeed.ServicePeriod()
  self.assertEqual((None, None), period3.GetDateRange())
 
  period4 = transitfeed.ServicePeriod()
  period4.service_id = 'halloween'
  period4.SetDateHasService('20051031', True)
  self.assertEqual(('20051031', '20051031'), period4.GetDateRange())
  period4.Validate(self.problems)
 
  schedule = transitfeed.Schedule(problem_reporter=self.problems)
  self.assertEqual((None, None), schedule.GetDateRange())
  schedule.AddServicePeriodObject(period)
  self.assertEqual(('20070101', '20071231'), schedule.GetDateRange())
  schedule.AddServicePeriodObject(period2)
  self.assertEqual(('20070101', '20080101'), schedule.GetDateRange())
  schedule.AddServicePeriodObject(period4)
  self.assertEqual(('20051031', '20080101'), schedule.GetDateRange())
  self.accumulator.AssertNoMoreExceptions()
 
 
  class NoServiceExceptionsTestCase(MemoryZipTestCase):
 
  def testNoCalendarDates(self):
  self.RemoveArchive("calendar_dates.txt")
  self.MakeLoaderAndLoad()
  e = self.accumulator.PopException("NoServiceExceptions")
  self.accumulator.AssertNoMoreExceptions()
 
  def testNoExceptionsWhenFeedActiveForShortPeriodOfTime(self):
  self.SetArchiveContents(
  "calendar.txt",
  "service_id,monday,tuesday,wednesday,thursday,friday,saturday,sunday,"
  "start_date,end_date\n"
  "FULLW,1,1,1,1,1,1,1,20070101,20070630\n"
  "WE,0,0,0,0,0,1,1,20070101,20070331\n")
  self.RemoveArchive("calendar_dates.txt")
  self.MakeLoaderAndLoad()
  self.accumulator.AssertNoMoreExceptions()
 
  def testEmptyCalendarDates(self):
  self.SetArchiveContents(
  "calendar_dates.txt",
  "")
  self.MakeLoaderAndLoad()
  e = self.accumulator.PopException("EmptyFile")
  e = self.accumulator.PopException("NoServiceExceptions")
  self.accumulator.AssertNoMoreExceptions()
 
  def testCalendarDatesWithHeaderOnly(self):
  self.SetArchiveContents(
  "calendar_dates.txt",
  "service_id,date,exception_type\n")
  self.MakeLoaderAndLoad()
  e = self.accumulator.PopException("NoServiceExceptions")
  self.accumulator.AssertNoMoreExceptions()
 
  def testCalendarDatesWithAddedServiceException(self):
  self.SetArchiveContents(
  "calendar_dates.txt",
  "service_id,date,exception_type\n"
  "FULLW,20070101,1\n")
  self.MakeLoaderAndLoad()
  self.accumulator.AssertNoMoreExceptions()
 
  def testCalendarDatesWithRemovedServiceException(self):
  self.SetArchiveContents(
  "calendar_dates.txt",
  "service_id,date,exception_type\n"
  "FULLW,20070101,2\n")
  self.MakeLoaderAndLoad()
  self.accumulator.AssertNoMoreExceptions()
 
 
  class ServicePeriodTestCase(util.TestCase):
  def testActive(self):
  """Test IsActiveOn and ActiveDates"""
  period = transitfeed.ServicePeriod()
  period.service_id = 'WEEKDAY'
  period.start_date = '20071226'
  period.end_date = '20071231'
  period.SetWeekdayService(True)
  period.SetDateHasService('20071230', True)
  period.SetDateHasService('20071231', False)
  period.SetDateHasService('20080102', True)
  # December 2007
  # Su Mo Tu We Th Fr Sa
  # 23 24 25 26 27 28 29
  # 30 31
 
  # Some tests have named arguments and others do not to ensure that any
  # (possibly unwanted) changes to the API get caught
 
  # calendar_date exceptions near start date
  self.assertFalse(period.IsActiveOn(date='20071225'))
  self.assertFalse(period.IsActiveOn(date='20071225',
  date_object=date(2007, 12, 25)))
  self.assertTrue(period.IsActiveOn(date='20071226'))
  self.assertTrue(period.IsActiveOn(date='20071226',
  date_object=date(2007, 12, 26)))
 
  # calendar_date exceptions near end date
  self.assertTrue(period.IsActiveOn('20071230'))
  self.assertTrue(period.IsActiveOn('20071230', date(2007, 12, 30)))
  self.assertFalse(period.IsActiveOn('20071231'))
  self.assertFalse(period.IsActiveOn('20071231', date(2007, 12, 31)))
 
  # date just outside range, both weekday and an exception
  self.assertFalse(period.IsActiveOn('20080101'))
  self.assertFalse(period.IsActiveOn('20080101', date(2008, 1, 1)))
  self.assertTrue(period.IsActiveOn('20080102'))
  self.assertTrue(period.IsActiveOn('20080102', date(2008, 1, 2)))
 
  self.assertEquals(period.ActiveDates(),
  ['20071226', '20071227', '20071228', '20071230',
  '20080102'])
 
 
  # Test of period without start_date, end_date
  period_dates = transitfeed.ServicePeriod()
  period_dates.SetDateHasService('20071230', True)
  period_dates.SetDateHasService('20071231', False)
 
  self.assertFalse(period_dates.IsActiveOn(date='20071229'))
  self.assertFalse(period_dates.IsActiveOn(date='20071229',
  date_object=date(2007, 12, 29)))
  self.assertTrue(period_dates.IsActiveOn('20071230'))
  self.assertTrue(period_dates.IsActiveOn('20071230', date(2007, 12, 30)))
  self.assertFalse(period_dates.IsActiveOn('20071231'))
  self.assertFalse(period_dates.IsActiveOn('20071231', date(2007, 12, 31)))
  self.assertEquals(period_dates.ActiveDates(), ['20071230'])
 
  # Test with an invalid ServicePeriod; one of start_date, end_date is set
  period_no_end = transitfeed.ServicePeriod()
  period_no_end.start_date = '20071226'
  self.assertFalse(period_no_end.IsActiveOn(date='20071231'))
  self.assertFalse(period_no_end.IsActiveOn(date='20071231',
  date_object=date(2007, 12, 31)))
  self.assertEquals(period_no_end.ActiveDates(), [])
  period_no_start = transitfeed.ServicePeriod()
  period_no_start.end_date = '20071230'
  self.assertFalse(period_no_start.IsActiveOn('20071229'))
  self.assertFalse(period_no_start.IsActiveOn('20071229', date(2007, 12, 29)))
  self.assertEquals(period_no_start.ActiveDates(), [])
 
  period_empty = transitfeed.ServicePeriod()
  self.assertFalse(period_empty.IsActiveOn('20071231'))
  self.assertFalse(period_empty.IsActiveOn('20071231', date(2007, 12, 31)))
  self.assertEquals(period_empty.ActiveDates(), [])
 
 
  class GetServicePeriodsActiveEachDateTestCase(util.TestCase):
  def testEmpty(self):
  schedule = transitfeed.Schedule()
  self.assertEquals(
  [],
  schedule.GetServicePeriodsActiveEachDate(date(2009, 1, 1),
  date(2009, 1, 1)))
  self.assertEquals(
  [(date(2008, 12, 31), []), (date(2009, 1, 1), [])],
  schedule.GetServicePeriodsActiveEachDate(date(2008, 12, 31),
  date(2009, 1, 2)))
  def testOneService(self):
  schedule = transitfeed.Schedule()
  sp1 = transitfeed.ServicePeriod()
  sp1.service_id = "sp1"
  sp1.SetDateHasService("20090101")
  sp1.SetDateHasService("20090102")
  schedule.AddServicePeriodObject(sp1)
  self.assertEquals(
  [],
  schedule.GetServicePeriodsActiveEachDate(date(2009, 1, 1),
  date(2009, 1, 1)))
  self.assertEquals(
  [(date(2008, 12, 31), []), (date(2009, 1, 1), [sp1])],
  schedule.GetServicePeriodsActiveEachDate(date(2008, 12, 31),
  date(2009, 1, 2)))
 
  def testTwoService(self):
  schedule = transitfeed.Schedule()
  sp1 = transitfeed.ServicePeriod()
  sp1.service_id = "sp1"
  sp1.SetDateHasService("20081231")
  sp1.SetDateHasService("20090101")
 
  schedule.AddServicePeriodObject(sp1)
  sp2 = transitfeed.ServicePeriod()
  sp2.service_id = "sp2"
  sp2.SetStartDate("20081201")
  sp2.SetEndDate("20081231")
  sp2.SetWeekendService()
  sp2.SetWeekdayService()
  schedule.AddServicePeriodObject(sp2)
  self.assertEquals(
  [],
  schedule.GetServicePeriodsActiveEachDate(date(2009, 1, 1),
  date(2009, 1, 1)))
  date_services = schedule.GetServicePeriodsActiveEachDate(date(2008, 12, 31),
  date(2009, 1, 2))
  self.assertEquals(
  [date(2008, 12, 31), date(2009, 1, 1)], [d for d, _ in date_services])
  self.assertEquals(set([sp1, sp2]), set(date_services[0][1]))
  self.assertEquals([sp1], date_services[1][1])
 
 
  class TripMemoryZipTestCase(MemoryZipTestCase):
  def assertLoadAndCheckExtraValues(self, schedule_file):
  """Load file-like schedule_file and check for extra trip columns."""
  load_problems = GetTestFailureProblemReporter(
  self, ("ExpirationDate", "UnrecognizedColumn"))
  loaded_schedule = transitfeed.Loader(schedule_file,
  problems=load_problems,
  extra_validation=True).Load()
  self.assertEqual("foo", loaded_schedule.GetTrip("AB1")["t_foo"])
  self.assertEqual("", loaded_schedule.GetTrip("AB2")["t_foo"])
  self.assertEqual("", loaded_schedule.GetTrip("AB1")["n_foo"])
  self.assertEqual("bar", loaded_schedule.GetTrip("AB2")["n_foo"])
  # Uncomment the following lines to print the string in testExtraFileColumn
  # print repr(zipfile.ZipFile(schedule_file).read("trips.txt"))
  # self.fail()
 
  def testExtraObjectAttribute(self):
  """Extra columns added to an object are preserved when writing."""
  schedule = self.MakeLoaderAndLoad()
  # Add an attribute to an existing trip
  trip1 = schedule.GetTrip("AB1")
  trip1.t_foo = "foo"
  # Make a copy of trip_id=AB1 and add an attribute before AddTripObject
  trip2 = transitfeed.Trip(field_dict=trip1)
  trip2.trip_id = "AB2"
  trip2.t_foo = ""
  trip2.n_foo = "bar"
  schedule.AddTripObject(trip2)
  trip2.AddStopTime(stop=schedule.GetStop("BULLFROG"), stop_time="09:00:00")
  trip2.AddStopTime(stop=schedule.GetStop("STAGECOACH"), stop_time="09:30:00")
  saved_schedule_file = StringIO()
  schedule.WriteGoogleTransitFeed(saved_schedule_file)
  self.accumulator.AssertNoMoreExceptions()
 
  self.assertLoadAndCheckExtraValues(saved_schedule_file)
 
  def testExtraFileColumn(self):
  """Extra columns loaded from a file are preserved when writing."""
  # Uncomment the code in assertLoadAndCheckExtraValues to generate this
  # string.
  self.SetArchiveContents(
  "trips.txt",
  "route_id,service_id,trip_id,t_foo,n_foo\n"
  "AB,FULLW,AB1,foo,\n"
  "AB,FULLW,AB2,,bar\n")
  self.AppendToArchiveContents(
  "stop_times.txt",
  "AB2,09:00:00,09:00:00,BULLFROG,1\n"
  "AB2,09:30:00,09:30:00,STAGECOACH,2\n")
  load1_problems = GetTestFailureProblemReporter(
  self, ("ExpirationDate", "UnrecognizedColumn"))
  schedule = self.MakeLoaderAndLoad(problems=load1_problems)
  saved_schedule_file = StringIO()
  schedule.WriteGoogleTransitFeed(saved_schedule_file)
 
  self.assertLoadAndCheckExtraValues(saved_schedule_file)
 
 
  class TripValidationTestCase(ValidationTestCase):
  def runTest(self):
  trip = transitfeed.Trip()
  repr(trip) # shouldn't crash
 
  schedule = self.SimpleSchedule()
  trip = transitfeed.Trip()
  repr(trip) # shouldn't crash
 
  trip = transitfeed.Trip()
  trip.trip_headsign = '\xBA\xDF\x0D' # Not valid ascii or utf8
  repr(trip) # shouldn't crash
 
  trip.route_id = '054C'
  trip.service_id = 'WEEK'
  trip.trip_id = '054C-00'
  trip.trip_headsign = 'via Polish Hill'
  trip.direction_id = '0'
  trip.block_id = None
  trip.shape_id = None
  trip.Validate(self.problems)
  self.accumulator.AssertNoMoreExceptions()
  repr(trip) # shouldn't crash
 
  # missing route ID
  trip.route_id = None
  self.ExpectMissingValue(trip, 'route_id')
  trip.route_id = '054C'
 
  # missing service ID
  trip.service_id = None
  self.ExpectMissingValue(trip, 'service_id')
  trip.service_id = 'WEEK'
 
  # missing trip ID
  trip.trip_id = None
  self.ExpectMissingValue(trip, 'trip_id')
  trip.trip_id = '054C-00'
 
  # invalid direction ID
  trip.direction_id = 'NORTH'
  self.ExpectInvalidValue(trip, 'direction_id')
  trip.direction_id = '0'
 
  # AddTripObject validates that route_id, service_id, .... are found in the
  # schedule. The Validate calls made by self.Expect... above can't make this
  # check because trip is not in a schedule.
  trip.route_id = '054C-notfound'
  schedule.AddTripObject(trip, self.problems, True)
  e = self.accumulator.PopException('InvalidValue')
  self.assertEqual('route_id', e.column_name)
  self.accumulator.AssertNoMoreExceptions()
  trip.route_id = '054C'
 
  # Make sure calling Trip.Validate validates that route_id and service_id
  # are found in the schedule.
  trip.service_id = 'WEEK-notfound'
  trip.Validate(self.problems)
  e = self.accumulator.PopException('InvalidValue')
  self.assertEqual('service_id', e.column_name)
  self.accumulator.AssertNoMoreExceptions()
  trip.service_id = 'WEEK'
 
  trip.Validate(self.problems)
  self.accumulator.AssertNoMoreExceptions()
 
  # expect no problems for non-overlapping periods
  trip.AddFrequency("06:00:00", "12:00:00", 600)
  trip.AddFrequency("01:00:00", "02:00:00", 1200)
  trip.AddFrequency("04:00:00", "05:00:00", 1000)
  trip.AddFrequency("12:00:00", "19:00:00", 700)
  trip.Validate(self.problems)
  self.accumulator.AssertNoMoreExceptions()
  trip.ClearFrequencies()
 
  # overlapping headway periods
  trip.AddFrequency("00:00:00", "12:00:00", 600)
  trip.AddFrequency("06:00:00", "18:00:00", 1200)
  self.ExpectOtherProblem(trip)
  trip.ClearFrequencies()
  trip.AddFrequency("12:00:00", "20:00:00", 600)
  trip.AddFrequency("06:00:00", "18:00:00", 1200)
  self.ExpectOtherProblem(trip)
  trip.ClearFrequencies()
  trip.AddFrequency("06:00:00", "12:00:00", 600)
  trip.AddFrequency("00:00:00", "25:00:00", 1200)
  self.ExpectOtherProblem(trip)
  trip.ClearFrequencies()
  trip.AddFrequency("00:00:00", "20:00:00", 600)
  trip.AddFrequency("06:00:00", "18:00:00", 1200)
  self.ExpectOtherProblem(trip)
  trip.ClearFrequencies()
  self.accumulator.AssertNoMoreExceptions()
 
 
  class FrequencyValidationTestCase(ValidationTestCase):
  def setUp(self):
  ValidationTestCase.setUp(self)
  self.schedule = self.SimpleSchedule()
  trip = transitfeed.Trip()
  trip.route_id = '054C'
  trip.service_id = 'WEEK'
  trip.trip_id = '054C-00'
  trip.trip_headsign = 'via Polish Hill'
  trip.direction_id = '0'
  trip.block_id = None
  trip.shape_id = None
  self.schedule.AddTripObject(trip, self.problems, True)
  self.trip = trip
 
  def testNonOverlappingPeriods(self):
  headway_period1 = transitfeed.Frequency({'trip_id': '054C-00',
  'start_time': '06:00:00',
  'end_time': '12:00:00',
  'headway_secs': 600,
  })
  headway_period2 = transitfeed.Frequency({'trip_id': '054C-00',
  'start_time': '01:00:00',
  'end_time': '02:00:00',
  'headway_secs': 1200,
  })
  headway_period3 = transitfeed.Frequency({'trip_id': '054C-00',
  'start_time': '04:00:00',
  'end_time': '05:00:00',
  'headway_secs': 1000,
  })
  headway_period4 = transitfeed.Frequency({'trip_id': '054C-00',
  'start_time': '12:00:00',
  'end_time': '19:00:00',
  'headway_secs': 700,
  })
 
  # expect no problems for non-overlapping periods
  headway_period1.AddToSchedule(self.schedule, self.problems)
  headway_period2.AddToSchedule(self.schedule, self.problems)
  headway_period3.AddToSchedule(self.schedule, self.problems)
  headway_period4.AddToSchedule(self.schedule, self.problems)
  self.trip.Validate(self.problems)
  self.accumulator.AssertNoMoreExceptions()
  self.trip.ClearFrequencies()
 
  def testOverlappingPeriods(self):
  # overlapping headway periods
  headway_period1 = transitfeed.Frequency({'trip_id': '054C-00',
  'start_time': '00:00:00',
  'end_time': '12:00:00',
  'headway_secs': 600,
  })
  headway_period2 = transitfeed.Frequency({'trip_id': '054C-00',
  'start_time': '06:00:00',
  'end_time': '18:00:00',
  'headway_secs': 1200,
  })
  headway_period1.AddToSchedule(self.schedule, self.problems)
  headway_period2.AddToSchedule(self.schedule, self.problems)
  self.ExpectOtherProblem(self.trip)
  self.trip.ClearFrequencies()
  self.accumulator.AssertNoMoreExceptions()
 
  def testPeriodWithInvalidTripId(self):
  headway_period1 = transitfeed.Frequency({'trip_id': 'foo',
  'start_time': '00:00:00',
  'end_time': '12:00:00',
  'headway_secs': 600,
  })
  headway_period1.AddToSchedule(self.schedule, self.problems)
  e = self.accumulator.PopException('InvalidValue')
  self.assertEqual('trip_id', e.column_name)
  self.trip.ClearFrequencies()
 
 
  class TripSequenceValidationTestCase(ValidationTestCase):
  def runTest(self):
  schedule = self.SimpleSchedule()
  # Make a new trip without any stop times
  trip = schedule.GetRoute("054C").AddTrip(trip_id="054C-00")
  stop1 = schedule.GetStop('stop1')
  stop2 = schedule.GetStop('stop2')
  stop3 = schedule.GetStop('stop3')
  stoptime1 = transitfeed.StopTime(self.problems, stop1,
  stop_time='12:00:00', stop_sequence=1)
  stoptime2 = transitfeed.StopTime(self.problems, stop2,
  stop_time='11:30:00', stop_sequence=2)
  stoptime3 = transitfeed.StopTime(self.problems, stop3,
  stop_time='12:15:00', stop_sequence=3)
  trip._AddStopTimeObjectUnordered(stoptime1, schedule)
  trip._AddStopTimeObjectUnordered(stoptime2, schedule)
  trip._AddStopTimeObjectUnordered(stoptime3, schedule)
  trip.Validate(self.problems)
  e = self.accumulator.PopException('OtherProblem')
  self.assertTrue(e.FormatProblem().find('Timetravel detected') != -1)
  self.assertTrue(e.FormatProblem().find('number 2 in trip 054C-00') != -1)
  self.accumulator.AssertNoMoreExceptions()
 
 
  class TripServiceIDValidationTestCase(ValidationTestCase):
  def runTest(self):
  schedule = self.SimpleSchedule()
  trip1 = transitfeed.Trip()
  trip1.route_id = "054C"
  trip1.service_id = "WEEKDAY"
  trip1.trip_id = "054C_WEEK"
  self.ExpectInvalidValueInClosure(column_name="service_id",
  value="WEEKDAY",
  c=lambda: schedule.AddTripObject(trip1,
  validate=True))
 
 
  class TripHasStopTimeValidationTestCase(ValidationTestCase):
  def runTest(self):
  schedule = self.SimpleSchedule()
  trip = schedule.GetRoute("054C").AddTrip(trip_id="054C-00")
 
  # We should get an OtherProblem here because the trip has no stops.
  self.ExpectOtherProblem(schedule)
 
  # It should trigger a TYPE_ERROR if there are frequencies for the trip
  # but no stops
  trip.AddFrequency("01:00:00","12:00:00", 600)
  schedule.Validate(self.problems)
  self.accumulator.PopException('OtherProblem') # pop first warning
  e = self.accumulator.PopException('OtherProblem') # pop frequency error
  self.assertTrue(e.FormatProblem().find('Frequencies defined, but') != -1)
  self.assertTrue(e.FormatProblem().find('given in trip 054C-00') != -1)
  self.assertEquals(transitfeed.TYPE_ERROR, e.type)
  self.accumulator.AssertNoMoreExceptions()
  trip.ClearFrequencies()
 
  # Add a stop, but with only one stop passengers have nowhere to exit!
  stop = transitfeed.Stop(36.425288, -117.133162, "Demo Stop 1", "STOP1")
  schedule.AddStopObject(stop)
  trip.AddStopTime(stop, arrival_time="5:11:00", departure_time="5:12:00")
  self.ExpectOtherProblem(schedule)
 
  # Add another stop, and then validation should be happy.
  stop = transitfeed.Stop(36.424288, -117.133142, "Demo Stop 2", "STOP2")
  schedule.AddStopObject(stop)
  trip.AddStopTime(stop, arrival_time="5:15:00", departure_time="5:16:00")
  schedule.Validate(self.problems)
 
  trip.AddStopTime(stop, stop_time="05:20:00")
  trip.AddStopTime(stop, stop_time="05:22:00")
 
  # Last stop must always have a time
  trip.AddStopTime(stop, arrival_secs=None, departure_secs=None)
  self.ExpectInvalidValueInClosure(
  'arrival_time', c=lambda: trip.GetEndTime(problems=self.problems))
 
 
  class ShapeDistTraveledOfStopTimeValidationTestCase(ValidationTestCase):
  def runTest(self):
  schedule = self.SimpleSchedule()
 
  shape = transitfeed.Shape("shape_1")
  shape.AddPoint(36.425288, -117.133162, 0)
  shape.AddPoint(36.424288, -117.133142, 1)
  schedule.AddShapeObject(shape)
 
  trip = schedule.GetRoute("054C").AddTrip(trip_id="054C-00")
  trip.shape_id = "shape_1"
 
  stop = transitfeed.Stop(36.425288, -117.133162, "Demo Stop 1", "STOP1")
  schedule.AddStopObject(stop)
  trip.AddStopTime(stop, arrival_time="5:11:00", departure_time="5:12:00",
  stop_sequence=0, shape_dist_traveled=0)
  stop = transitfeed.Stop(36.424288, -117.133142, "Demo Stop 2", "STOP2")
  schedule.AddStopObject(stop)
  trip.AddStopTime(stop, arrival_time="5:15:00", departure_time="5:16:00",
  stop_sequence=1, shape_dist_traveled=1)
 
  stop = transitfeed.Stop(36.423288, -117.133122, "Demo Stop 3", "STOP3")
  schedule.AddStopObject(stop)
  trip.AddStopTime(stop, arrival_time="5:18:00", departure_time="5:19:00",
  stop_sequence=2, shape_dist_traveled=2)
  self.accumulator.AssertNoMoreExceptions()
  schedule.Validate(self.problems)
  e = self.accumulator.PopException('OtherProblem')
  self.assertMatchesRegex('shape_dist_traveled=2', e.FormatProblem())
  self.accumulator.AssertNoMoreExceptions()
 
  # Error if the distance decreases.
  shape.AddPoint(36.421288, -117.133132, 2)
  stop = transitfeed.Stop(36.421288, -117.133122, "Demo Stop 4", "STOP4")
  schedule.AddStopObject(stop)
  stoptime = transitfeed.StopTime(self.problems, stop,
  arrival_time="5:29:00",
  departure_time="5:29:00",stop_sequence=3,
  shape_dist_traveled=1.7)
  trip.AddStopTimeObject(stoptime, schedule=schedule)
  self.accumulator.AssertNoMoreExceptions()
  schedule.Validate(self.problems)
  e = self.accumulator.PopException('InvalidValue')
  self.assertMatchesRegex('stop STOP4 has', e.FormatProblem())
  self.assertMatchesRegex('shape_dist_traveled=1.7', e.FormatProblem())
  self.assertMatchesRegex('distance was 2.0.', e.FormatProblem())
  self.assertEqual(e.type, transitfeed.TYPE_ERROR)
  self.accumulator.AssertNoMoreExceptions()
 
  # Warning if distance remains the same between two stop_times
  stoptime.shape_dist_traveled = 2.0
  trip.ReplaceStopTimeObject(stoptime, schedule=schedule)
  schedule.Validate(self.problems)
  e = self.accumulator.PopException('InvalidValue')
  self.assertMatchesRegex('stop STOP4 has', e.FormatProblem())
  self.assertMatchesRegex('shape_dist_traveled=2.0', e.FormatProblem())
  self.assertMatchesRegex('distance was 2.0.', e.FormatProblem())
  self.assertEqual(e.type, transitfeed.TYPE_WARNING)
  self.accumulator.AssertNoMoreExceptions()
 
 
  class StopMatchWithShapeTestCase(ValidationTestCase):
  def runTest(self):
  schedule = self.SimpleSchedule()
 
  shape = transitfeed.Shape("shape_1")
  shape.AddPoint(36.425288, -117.133162, 0)
  shape.AddPoint(36.424288, -117.143142, 1)
  schedule.AddShapeObject(shape)
 
  trip = schedule.GetRoute("054C").AddTrip(trip_id="054C-00")
  trip.shape_id = "shape_1"
 
  # Stop 1 is only 600 meters away from shape, which is allowed.
  stop = transitfeed.Stop(36.425288, -117.139162, "Demo Stop 1", "STOP1")
  schedule.AddStopObject(stop)
  trip.AddStopTime(stop, arrival_time="5:11:00", departure_time="5:12:00",
  stop_sequence=0, shape_dist_traveled=0)
  # Stop 2 is more than 1000 meters away from shape, which is not allowed.
  stop = transitfeed.Stop(36.424288, -117.158142, "Demo Stop 2", "STOP2")
  schedule.AddStopObject(stop)
  trip.AddStopTime(stop, arrival_time="5:15:00", departure_time="5:16:00",
  stop_sequence=1, shape_dist_traveled=1)
 
  schedule.Validate(self.problems)
  e = self.accumulator.PopException('StopTooFarFromShapeWithDistTraveled')
  self.assertTrue(e.FormatProblem().find('Demo Stop 2') != -1)
  self.assertTrue(e.FormatProblem().find('1344 meters away') != -1)
  self.accumulator.AssertNoMoreExceptions()
 
 
  class TripAddStopTimeObjectTestCase(ValidationTestCase):
  def runTest(self):
  schedule = transitfeed.Schedule(problem_reporter=self.problems)
  schedule.AddAgency("\xc8\x8b Fly Agency", "http://iflyagency.com",
  "America/Los_Angeles")
  service_period = schedule.GetDefaultServicePeriod().SetDateHasService('20070101')
  stop1 = schedule.AddStop(lng=140, lat=48.2, name="Stop 1")
  stop2 = schedule.AddStop(lng=140.001, lat=48.201, name="Stop 2")
  route = schedule.AddRoute("B", "Beta", "Bus")
  trip = route.AddTrip(schedule, "bus trip")
  trip.AddStopTimeObject(transitfeed.StopTime(self.problems, stop1,
  arrival_secs=10,
  departure_secs=10),
  schedule=schedule, problems=self.problems)
  trip.AddStopTimeObject(transitfeed.StopTime(self.problems, stop2,
  arrival_secs=20,
  departure_secs=20),
  schedule=schedule, problems=self.problems)
  # TODO: Factor out checks or use mock problems object
  self.ExpectOtherProblemInClosure(lambda:
  trip.AddStopTimeObject(transitfeed.StopTime(self.problems, stop1,
  arrival_secs=15,
  departure_secs=15),
  schedule=schedule, problems=self.problems))
  trip.AddStopTimeObject(transitfeed.StopTime(self.problems, stop1),
  schedule=schedule, problems=self.problems)
  self.ExpectOtherProblemInClosure(lambda:
  trip.AddStopTimeObject(transitfeed.StopTime(self.problems, stop1,
  arrival_secs=15,
  departure_secs=15),
  schedule=schedule, problems=self.problems))
  trip.AddStopTimeObject(transitfeed.StopTime(self.problems, stop1,
  arrival_secs=30,
  departure_secs=30),
  schedule=schedule, problems=self.problems)
  self.accumulator.AssertNoMoreExceptions()
 
  class DuplicateTripTestCase(ValidationTestCase):
  def runTest(self):
 
  schedule = transitfeed.Schedule(self.problems)
  schedule._check_duplicate_trips = True;
 
  agency = transitfeed.Agency('Demo agency', 'http://google.com',
  'America/Los_Angeles', 'agency1')
  schedule.AddAgencyObject(agency)
 
  service = schedule.GetDefaultServicePeriod()
  service.SetDateHasService('20070101')
 
  route1 = transitfeed.Route('Route1', 'route 1', 3, 'route_1', 'agency1')
  schedule.AddRouteObject(route1)
  route2 = transitfeed.Route('Route2', 'route 2', 3, 'route_2', 'agency1')
  schedule.AddRouteObject(route2)
 
  trip1 = transitfeed.Trip()
  trip1.route_id = 'route_1'
  trip1.trip_id = 't1'
  trip1.trip_headsign = 'via Polish Hill'
  trip1.direction_id = '0'
  trip1.service_id = service.service_id
  schedule.AddTripObject(trip1)
 
  trip2 = transitfeed.Trip()
  trip2.route_id = 'route_2'
  trip2.trip_id = 't2'
  trip2.trip_headsign = 'New'
  trip2.direction_id = '0'
  trip2.service_id = service.service_id
  schedule.AddTripObject(trip2)
 
  trip3 = transitfeed.Trip()
  trip3.route_id = 'route_1'
  trip3.trip_id = 't3'
  trip3.trip_headsign = 'New Demo'
  trip3.direction_id = '0'
  trip3.service_id = service.service_id
  schedule.AddTripObject(trip3)
 
  stop1 = transitfeed.Stop(36.425288, -117.139162, "Demo Stop 1", "STOP1")
  schedule.AddStopObject(stop1)
  trip1.AddStopTime(stop1, arrival_time="5:11:00", departure_time="5:12:00",
  stop_sequence=0, shape_dist_traveled=0)
  trip2.AddStopTime(stop1, arrival_time="5:11:00", departure_time="5:12:00",
  stop_sequence=0, shape_dist_traveled=0)
  trip3.AddStopTime(stop1, arrival_time="6:11:00", departure_time="6:12:00",
  stop_sequence=0, shape_dist_traveled=0)
 
  stop2 = transitfeed.Stop(36.424288, -117.158142, "Demo Stop 2", "STOP2")
  schedule.AddStopObject(stop2)
  trip1.AddStopTime(stop2, arrival_time="5:15:00", departure_time="5:16:00",
  stop_sequence=1, shape_dist_traveled=1)
  trip2.AddStopTime(stop2, arrival_time="5:25:00", departure_time="5:26:00",
  stop_sequence=1, shape_dist_traveled=1)
  trip3.AddStopTime(stop2, arrival_time="6:15:00", departure_time="6:16:00",
  stop_sequence=1, shape_dist_traveled=1)
 
  schedule.Validate(self.problems)
  e = self.accumulator.PopException('DuplicateTrip')
  self.assertTrue(e.FormatProblem().find('t1 of route') != -1)
  self.assertTrue(e.FormatProblem().find('t2 of route') != -1)
  self.accumulator.AssertNoMoreExceptions()
 
 
  class StopBelongsToBothSubwayAndBusTestCase(ValidationTestCase):
  def runTest(self):
  schedule = transitfeed.Schedule(self.problems)
 
  schedule.AddAgency("Demo Agency", "http://example.com",
  "America/Los_Angeles")
  route1 = schedule.AddRoute(short_name="route1", long_name="route_1",
  route_type=3)
  route2 = schedule.AddRoute(short_name="route2", long_name="route_2",
  route_type=1)
 
  service = schedule.GetDefaultServicePeriod()
  service.SetDateHasService("20070101")
 
  trip1 = route1.AddTrip(schedule, "trip1", service, "t1")
  trip2 = route2.AddTrip(schedule, "trip2", service, "t2")
 
  stop1 = schedule.AddStop(36.425288, -117.133162, "stop1")
  stop2 = schedule.AddStop(36.424288, -117.133142, "stop2")
  stop3 = schedule.AddStop(36.423288, -117.134142, "stop3")
 
  trip1.AddStopTime(stop1, arrival_time="5:11:00", departure_time="5:12:00")
  trip1.AddStopTime(stop2, arrival_time="5:21:00", departure_time="5:22:00")
 
  trip2.AddStopTime(stop1, arrival_time="6:11:00", departure_time="6:12:00")
  trip2.AddStopTime(stop3, arrival_time="6:21:00", departure_time="6:22:00")
 
  schedule.Validate(self.problems)
  e = self.accumulator.PopException("StopWithMultipleRouteTypes")
  self.assertTrue(e.FormatProblem().find("Stop stop1") != -1)
  self.assertTrue(e.FormatProblem().find("subway (ID=1)") != -1)
  self.assertTrue(e.FormatProblem().find("bus line (ID=0)") != -1)
  self.accumulator.AssertNoMoreExceptions()
 
 
  class TripReplaceStopTimeObjectTestCase(util.TestCase):
  def runTest(self):
  schedule = transitfeed.Schedule()
  schedule.AddAgency("\xc8\x8b Fly Agency", "http://iflyagency.com",
  "America/Los_Angeles")
  service_period = \
  schedule.GetDefaultServicePeriod().SetDateHasService('20070101')
  stop1 = schedule.AddStop(lng=140, lat=48.2, name="Stop 1")
  route = schedule.AddRoute("B", "Beta", "Bus")
  trip = route.AddTrip(schedule, "bus trip")
  stoptime = transitfeed.StopTime(transitfeed.default_problem_reporter, stop1,
  arrival_secs=10,
  departure_secs=10)
  trip.AddStopTimeObject(stoptime, schedule=schedule)
  stoptimes = trip.GetStopTimes()
  stoptime.departure_secs = 20
  trip.ReplaceStopTimeObject(stoptime, schedule=schedule)
  stoptimes = trip.GetStopTimes()
  self.assertEqual(len(stoptimes), 1)
  self.assertEqual(stoptimes[0].departure_secs, 20)
 
  unknown_stop = schedule.AddStop(lng=140, lat=48.2, name="unknown")
  unknown_stoptime = transitfeed.StopTime(
  transitfeed.default_problem_reporter, unknown_stop,
  arrival_secs=10,
  departure_secs=10)
  unknown_stoptime.stop_sequence = 5
  # Attempting to replace a non-existent StopTime raises an error
  self.assertRaises(transitfeed.Error, trip.ReplaceStopTimeObject,
  unknown_stoptime, schedule=schedule)
 
  class TripStopTimeAccessorsTestCase(util.TestCase):
  def runTest(self):
  schedule = transitfeed.Schedule(
  problem_reporter=ExceptionProblemReporterNoExpiration())
  schedule.NewDefaultAgency(agency_name="Test Agency",
  agency_url="http://example.com",
  agency_timezone="America/Los_Angeles")
  route = schedule.AddRoute(short_name="54C", long_name="Polish Hill", route_type=3)
 
  service_period = schedule.GetDefaultServicePeriod()
  service_period.SetDateHasService("20070101")
 
  trip = route.AddTrip(schedule, 'via Polish Hill')
 
  stop1 = schedule.AddStop(36.425288, -117.133162, "Demo Stop 1")
  stop2 = schedule.AddStop(36.424288, -117.133142, "Demo Stop 2")
 
  trip.AddStopTime(stop1, arrival_time="5:11:00", departure_time="5:12:00")
  trip.AddStopTime(stop2, arrival_time="5:15:00", departure_time="5:16:00")
 
  # Add some more stop times and test GetEndTime does the correct thing
  self.assertEqual(transitfeed.FormatSecondsSinceMidnight(trip.GetStartTime()),
  "05:11:00")
  self.assertEqual(transitfeed.FormatSecondsSinceMidnight(trip.GetEndTime()),
  "05:16:00")
 
  trip.AddStopTime(stop1, stop_time="05:20:00")
  self.assertEqual(transitfeed.FormatSecondsSinceMidnight(trip.GetEndTime()),
  "05:20:00")
 
  trip.AddStopTime(stop2, stop_time="05:22:00")
  self.assertEqual(transitfeed.FormatSecondsSinceMidnight(trip.GetEndTime()),
  "05:22:00")
  self.assertEqual(len(trip.GetStopTimesTuples()), 4)
  self.assertEqual(trip.GetStopTimesTuples()[0], (trip.trip_id, "05:11:00",
  "05:12:00", stop1.stop_id,
  1, '', '', '', ''))
  self.assertEqual(trip.GetStopTimesTuples()[3], (trip.trip_id, "05:22:00",
  "05:22:00", stop2.stop_id,
  4, '', '', '', ''))
 
  class TripClearStopTimesTestCase(util.TestCase):
  def runTest(self):
  schedule = transitfeed.Schedule(
  problem_reporter=ExceptionProblemReporterNoExpiration())
  schedule.NewDefaultAgency(agency_name="Test Agency",
  agency_timezone="America/Los_Angeles")
  route = schedule.AddRoute(short_name="54C", long_name="Hill", route_type=3)
  schedule.GetDefaultServicePeriod().SetDateHasService("20070101")
  stop1 = schedule.AddStop(36, -117.1, "Demo Stop 1")
  stop2 = schedule.AddStop(36, -117.2, "Demo Stop 2")
  stop3 = schedule.AddStop(36, -117.3, "Demo Stop 3")
 
  trip = route.AddTrip(schedule, "via Polish Hill")
  trip.ClearStopTimes()
  self.assertFalse(trip.GetStopTimes())
  trip.AddStopTime(stop1, stop_time="5:11:00")
  self.assertTrue(trip.GetStopTimes())
  trip.ClearStopTimes()
  self.assertFalse(trip.GetStopTimes())
  trip.AddStopTime(stop3, stop_time="4:00:00") # Can insert earlier time
  trip.AddStopTime(stop2, stop_time="4:15:00")
  trip.AddStopTime(stop1, stop_time="4:21:00")
  old_stop_times = trip.GetStopTimes()
  self.assertTrue(old_stop_times)
  trip.ClearStopTimes()
  self.assertFalse(trip.GetStopTimes())
  for st in old_stop_times:
  trip.AddStopTimeObject(st)
  self.assertEqual(trip.GetStartTime(), 4 * 3600)
  self.assertEqual(trip.GetEndTime(), 4 * 3600 + 21 * 60)
 
 
  class BasicParsingTestCase(util.TestCase):
  """Checks that we're getting the number of child objects that we expect."""
  def assertLoadedCorrectly(self, schedule):
  """Check that the good_feed looks correct"""
  self.assertEqual(1, len(schedule._agencies))
  self.assertEqual(5, len(schedule.routes))
  self.assertEqual(2, len(schedule.service_periods))
  self.assertEqual(10, len(schedule.stops))
  self.assertEqual(11, len(schedule.trips))
  self.assertEqual(0, len(schedule.fare_zones))
 
  def assertLoadedStopTimesCorrectly(self, schedule):
  self.assertEqual(5, len(schedule.GetTrip('CITY1').GetStopTimes()))
  self.assertEqual('to airport', schedule.GetTrip('STBA').GetStopTimes()[0].stop_headsign)
  self.assertEqual(2, schedule.GetTrip('CITY1').GetStopTimes()[1].pickup_type)
  self.assertEqual(3, schedule.GetTrip('CITY1').GetStopTimes()[1].drop_off_type)
 
  def test_MemoryDb(self):
  loader = transitfeed.Loader(
  DataPath('good_feed.zip'),
  problems=GetTestFailureProblemReporter(self),
  extra_validation=True,
  memory_db=True)
  schedule = loader.Load()
  self.assertLoadedCorrectly(schedule)
  self.assertLoadedStopTimesCorrectly(schedule)
 
  def test_TemporaryFile(self):
  loader = transitfeed.Loader(
  DataPath('good_feed.zip'),
  problems=GetTestFailureProblemReporter(self),
  extra_validation=True,
  memory_db=False)
  schedule = loader.Load()
  self.assertLoadedCorrectly(schedule)
  self.assertLoadedStopTimesCorrectly(schedule)
 
  def test_NoLoadStopTimes(self):
  problems = GetTestFailureProblemReporter(
  self, ignore_types=("ExpirationDate", "UnusedStop", "OtherProblem"))
  loader = transitfeed.Loader(
  DataPath('good_feed.zip'),
  problems=problems,
  extra_validation=True,
  load_stop_times=False)
  schedule = loader.Load()
  self.assertLoadedCorrectly(schedule)
  self.assertEqual(0, len(schedule.GetTrip('CITY1').GetStopTimes()))
 
 
  class RepeatedRouteNameTestCase(LoadTestCase):
  def runTest(self):
  self.ExpectInvalidValue('repeated_route_name', 'route_long_name')
 
 
  class InvalidRouteAgencyTestCase(LoadTestCase):
  def runTest(self):
  self.Load('invalid_route_agency')
  self.accumulator.PopInvalidValue("agency_id", "routes.txt")
  self.accumulator.PopInvalidValue("route_id", "trips.txt")
  self.accumulator.AssertNoMoreExceptions()
 
 
  class UndefinedStopAgencyTestCase(LoadTestCase):
  def runTest(self):
  self.ExpectInvalidValue('undefined_stop', 'stop_id')
 
 
  class SameShortLongNameTestCase(LoadTestCase):
  def runTest(self):
  self.ExpectInvalidValue('same_short_long_name', 'route_long_name')
 
 
  class UnusedStopAgencyTestCase(LoadTestCase):
  def runTest(self):
  self.Load('unused_stop'),
  e = self.accumulator.PopException("UnusedStop")
  self.assertEqual("Bogus Stop (Demo)", e.stop_name)
  self.assertEqual("BOGUS", e.stop_id)
  self.accumulator.AssertNoMoreExceptions()
 
 
 
  class OnlyCalendarDatesTestCase(LoadTestCase):
  def runTest(self):
  self.Load('only_calendar_dates'),
  self.accumulator.AssertNoMoreExceptions()
 
 
  class DuplicateServiceIdDateWarningTestCase(MemoryZipTestCase):
  def runTest(self):
  # Two lines with the same value of service_id and date.
  # Test for the warning.
  self.SetArchiveContents(
  'calendar_dates.txt',
  'service_id,date,exception_type\n'
  'FULLW,20100604,1\n'
  'FULLW,20100604,2\n')
  schedule = self.MakeLoaderAndLoad()
  e = self.accumulator.PopException('DuplicateID')
  self.assertEquals('(service_id, date)', e.column_name)
  self.assertEquals('(FULLW, 20100604)', e.value)
 
 
  class AddStopTimeParametersTestCase(util.TestCase):
  def runTest(self):
  problem_reporter = GetTestFailureProblemReporter(self)
  schedule = transitfeed.Schedule(problem_reporter=problem_reporter)
  route = schedule.AddRoute(short_name="10", long_name="", route_type="Bus")
  stop = schedule.AddStop(40, -128, "My stop")
  # Stop must be added to schedule so that the call
  # AddStopTime -> AddStopTimeObject -> GetStopTimes -> GetStop can work
  trip = transitfeed.Trip()
  trip.route_id = route.route_id
  trip.service_id = schedule.GetDefaultServicePeriod().service_id
  trip.trip_id = "SAMPLE_TRIP"
  schedule.AddTripObject(trip)
 
  # First stop must have time
  trip.AddStopTime(stop, arrival_secs=300, departure_secs=360)
  trip.AddStopTime(stop)
  trip.AddStopTime(stop, arrival_time="00:07:00", departure_time="00:07:30")
  trip.Validate(problem_reporter)
 
 
  class ExpirationDateTestCase(util.TestCase):
  def runTest(self):
  accumulator = RecordingProblemAccumulator(self, ("NoServiceExceptions"))
  problems = transitfeed.ProblemReporter(accumulator)
  schedule = transitfeed.Schedule(problem_reporter=problems)
 
  now = time.mktime(time.localtime())
  seconds_per_day = 60 * 60 * 24
  two_weeks_ago = time.localtime(now - 14 * seconds_per_day)
  two_weeks_from_now = time.localtime(now + 14 * seconds_per_day)
  two_months_from_now = time.localtime(now + 60 * seconds_per_day)
  date_format = "%Y%m%d"
 
  service_period = schedule.GetDefaultServicePeriod()
  service_period.SetWeekdayService(True)
  service_period.SetStartDate("20070101")
 
  service_period.SetEndDate(time.strftime(date_format, two_months_from_now))
  schedule.Validate() # should have no problems
  accumulator.AssertNoMoreExceptions()
 
  service_period.SetEndDate(time.strftime(date_format, two_weeks_from_now))
  schedule.Validate()
  e = accumulator.PopException('ExpirationDate')
  self.assertTrue(e.FormatProblem().index('will soon expire'))
  accumulator.AssertNoMoreExceptions()
 
  service_period.SetEndDate(time.strftime(date_format, two_weeks_ago))
  schedule.Validate()
  e = accumulator.PopException('ExpirationDate')
  self.assertTrue(e.FormatProblem().index('expired'))
  accumulator.AssertNoMoreExceptions()
 
 
  class FutureServiceStartDateTestCase(util.TestCase):
  def runTest(self):
  accumulator = RecordingProblemAccumulator(self)
  problems = transitfeed.ProblemReporter(accumulator)
  schedule = transitfeed.Schedule(problem_reporter=problems)
 
  today = datetime.date.today()
  yesterday = today - datetime.timedelta(days=1)
  tomorrow = today + datetime.timedelta(days=1)
  two_months_from_today = today + datetime.timedelta(days=60)
 
  service_period = schedule.GetDefaultServicePeriod()
  service_period.SetWeekdayService(True)
  service_period.SetWeekendService(True)
  service_period.SetEndDate(two_months_from_today.strftime("%Y%m%d"))
 
  service_period.SetStartDate(yesterday.strftime("%Y%m%d"))
  schedule.Validate()
  accumulator.AssertNoMoreExceptions()
 
  service_period.SetStartDate(today.strftime("%Y%m%d"))
  schedule.Validate()
  accumulator.AssertNoMoreExceptions()
 
  service_period.SetStartDate(tomorrow.strftime("%Y%m%d"))
  schedule.Validate()
  accumulator.PopException('FutureService')
  accumulator.AssertNoMoreExceptions()
 
 
  class CalendarTxtIntegrationTestCase(MemoryZipTestCase):
  def testBadEndDateFormat(self):
  # A badly formatted end_date used to generate an InvalidValue report from
  # Schedule.Validate and ServicePeriod.Validate. Test for the bug.
  self.SetArchiveContents(
  "calendar.txt",
  "service_id,monday,tuesday,wednesday,thursday,friday,saturday,sunday,"
  "start_date,end_date\n"
  "FULLW,1,1,1,1,1,1,1,20070101,20101232\n"
  "WE,0,0,0,0,0,1,1,20070101,20101231\n")
  schedule = self.MakeLoaderAndLoad()
  e = self.accumulator.PopInvalidValue('end_date')
  self.accumulator.AssertNoMoreExceptions()
 
  def testBadStartDateFormat(self):
  self.SetArchiveContents(
  "calendar.txt",
  "service_id,monday,tuesday,wednesday,thursday,friday,saturday,sunday,"
  "start_date,end_date\n"
  "FULLW,1,1,1,1,1,1,1,200701xx,20101231\n"
  "WE,0,0,0,0,0,1,1,20070101,20101231\n")
  schedule = self.MakeLoaderAndLoad()
  e = self.accumulator.PopInvalidValue('start_date')
  self.accumulator.AssertNoMoreExceptions()
 
  def testNoStartDateAndEndDate(self):
  """Regression test for calendar.txt with empty start_date and end_date.
 
  See http://code.google.com/p/googletransitdatafeed/issues/detail?id=41
  """
  self.SetArchiveContents(
  "calendar.txt",
  "service_id,monday,tuesday,wednesday,thursday,friday,saturday,sunday,"
  "start_date,end_date\n"
  "FULLW,1,1,1,1,1,1,1, ,\t\n"
  "WE,0,0,0,0,0,1,1,20070101,20101231\n")
  schedule = self.MakeLoaderAndLoad()
  e = self.accumulator.PopException("MissingValue")
  self.assertEquals(2, e.row_num)
  self.assertEquals("start_date", e.column_name)
  e = self.accumulator.PopException("MissingValue")
  self.assertEquals(2, e.row_num)
  self.assertEquals("end_date", e.column_name)
  self.accumulator.AssertNoMoreExceptions()
 
  def testNoStartDateAndBadEndDate(self):
  self.SetArchiveContents(
  "calendar.txt",
  "service_id,monday,tuesday,wednesday,thursday,friday,saturday,sunday,"
  "start_date,end_date\n"
  "FULLW,1,1,1,1,1,1,1,,abc\n"
  "WE,0,0,0,0,0,1,1,20070101,20101231\n")
  schedule = self.MakeLoaderAndLoad()
  e = self.accumulator.PopException("MissingValue")
  self.assertEquals(2, e.row_num)
  self.assertEquals("start_date", e.column_name)
  e = self.accumulator.PopInvalidValue("end_date")
  self.assertEquals(2, e.row_num)
  self.accumulator.AssertNoMoreExceptions()
 
  def testMissingEndDateColumn(self):
  self.SetArchiveContents(
  "calendar.txt",
  "service_id,monday,tuesday,wednesday,thursday,friday,saturday,sunday,"
  "start_date\n"
  "FULLW,1,1,1,1,1,1,1,20070101\n"
  "WE,0,0,0,0,0,1,1,20070101\n")
  schedule = self.MakeLoaderAndLoad()
  e = self.accumulator.PopException("MissingColumn")
  self.assertEquals("end_date", e.column_name)
  self.accumulator.AssertNoMoreExceptions()
 
 
  class DuplicateTripIDValidationTestCase(util.TestCase):
  def runTest(self):
  schedule = transitfeed.Schedule(
  problem_reporter=ExceptionProblemReporterNoExpiration())
  schedule.AddAgency("Sample Agency", "http://example.com",
  "America/Los_Angeles")
  route = transitfeed.Route()
  route.route_id = "SAMPLE_ID"
  route.route_type = 3
  route.route_long_name = "Sample Route"
  schedule.AddRouteObject(route)
 
  service_period = transitfeed.ServicePeriod("WEEK")
  service_period.SetStartDate("20070101")
  service_period.SetEndDate("20071231")
  service_period.SetWeekdayService(True)
  schedule.AddServicePeriodObject(service_period)
 
  trip1 = transitfeed.Trip()
  trip1.route_id = "SAMPLE_ID"
  trip1.service_id = "WEEK"
  trip1.trip_id = "SAMPLE_TRIP"
  schedule.AddTripObject(trip1)
 
  trip2 = transitfeed.Trip()
  trip2.route_id = "SAMPLE_ID"
  trip2.service_id = "WEEK"
  trip2.trip_id = "SAMPLE_TRIP"
  try:
  schedule.AddTripObject(trip2)
  self.fail("Expected Duplicate ID validation failure")
  except transitfeed.DuplicateID, e:
  self.assertEqual("trip_id", e.column_name)
  self.assertEqual("SAMPLE_TRIP", e.value)
 
 
  class DuplicateStopValidationTestCase(ValidationTestCase):
  def runTest(self):
  schedule = transitfeed.Schedule(problem_reporter=self.problems)
  schedule.AddAgency("Sample Agency", "http://example.com",
  "America/Los_Angeles")
  route = transitfeed.Route()
  route.route_id = "SAMPLE_ID"
  route.route_type = 3
  route.route_long_name = "Sample Route"
  schedule.AddRouteObject(route)
 
  service_period = transitfeed.ServicePeriod("WEEK")
  service_period.SetStartDate("20070101")
  service_period.SetEndDate("20071231")
  service_period.SetWeekdayService(True)
  schedule.AddServicePeriodObject(service_period)
 
  trip = transitfeed.Trip()
  trip.route_id = "SAMPLE_ID"
  trip.service_id = "WEEK"
  trip.trip_id = "SAMPLE_TRIP"
  schedule.AddTripObject(trip)
 
  stop1 = transitfeed.Stop()
  stop1.stop_id = "STOP1"
  stop1.stop_name = "Stop 1"
  stop1.stop_lat = 78.243587
  stop1.stop_lon = 32.258937
  schedule.AddStopObject(stop1)
  trip.AddStopTime(stop1, arrival_time="12:00:00", departure_time="12:00:00")
 
  stop2 = transitfeed.Stop()
  stop2.stop_id = "STOP2"
  stop2.stop_name = "Stop 2"
  stop2.stop_lat = 78.253587
  stop2.stop_lon = 32.258937
  schedule.AddStopObject(stop2)
  trip.AddStopTime(stop2, arrival_time="12:05:00", departure_time="12:05:00")
  schedule.Validate()
 
  stop3 = transitfeed.Stop()
  stop3.stop_id = "STOP3"
  stop3.stop_name = "Stop 3"
  stop3.stop_lat = 78.243587
  stop3.stop_lon = 32.268937
  schedule.AddStopObject(stop3)
  trip.AddStopTime(stop3, arrival_time="12:10:00", departure_time="12:10:00")
  schedule.Validate()
  self.accumulator.AssertNoMoreExceptions()
 
  stop4 = transitfeed.Stop()
  stop4.stop_id = "STOP4"
  stop4.stop_name = "Stop 4"
  stop4.stop_lat = 78.243588
  stop4.stop_lon = 32.268936
  schedule.AddStopObject(stop4)
  trip.AddStopTime(stop4, arrival_time="12:15:00", departure_time="12:15:00")
  schedule.Validate()
  e = self.accumulator.PopException('StopsTooClose')
  self.accumulator.AssertNoMoreExceptions()
 
 
  class TempFileTestCaseBase(util.TestCase):
  """
  Subclass of TestCase which sets self.tempfilepath to a valid temporary zip
  file name and removes the file if it exists when the test is done.
  """
  def setUp(self):
  (fd, self.tempfilepath) = tempfile.mkstemp(".zip")
  # Open file handle causes an exception during remove in Windows
  os.close(fd)
 
  def tearDown(self):
  if os.path.exists(self.tempfilepath):
  os.remove(self.tempfilepath)
 
 
  class MinimalWriteTestCase(TempFileTestCaseBase):
  """
  This test case simply constructs an incomplete feed with very few
  fields set and ensures that there are no exceptions when writing it out.
 
  This is very similar to TransitFeedSampleCodeTestCase below, but that one
  will no doubt change as the sample code is altered.
  """
  def runTest(self):
  schedule = transitfeed.Schedule()
  schedule.AddAgency("Sample Agency", "http://example.com",
  "America/Los_Angeles")
  route = transitfeed.Route()
  route.route_id = "SAMPLE_ID"
  route.route_type = 3
  route.route_short_name = "66"
  route.route_long_name = "Sample Route acute letter e\202"
  schedule.AddRouteObject(route)
 
  service_period = transitfeed.ServicePeriod("WEEK")
  service_period.SetStartDate("20070101")
  service_period.SetEndDate("20071231")
  service_period.SetWeekdayService(True)
  schedule.AddServicePeriodObject(service_period)
 
  trip = transitfeed.Trip()
  trip.route_id = "SAMPLE_ID"
  trip.service_period = service_period
  trip.trip_id = "SAMPLE_TRIP"
  schedule.AddTripObject(trip)
 
  stop1 = transitfeed.Stop()
  stop1.stop_id = "STOP1"
  stop1.stop_name = u'Stop 1 acute letter e\202'
  stop1.stop_lat = 78.243587
  stop1.stop_lon = 32.258937
  schedule.AddStopObject(stop1)
  trip.AddStopTime(stop1, arrival_time="12:00:00", departure_time="12:00:00")
 
  stop2 = transitfeed.Stop()
  stop2.stop_id = "STOP2"
  stop2.stop_name = "Stop 2"
  stop2.stop_lat = 78.253587
  stop2.stop_lon = 32.258937
  schedule.AddStopObject(stop2)
  trip.AddStopTime(stop2, arrival_time="12:05:00", departure_time="12:05:00")
 
  schedule.Validate()
  schedule.WriteGoogleTransitFeed(self.tempfilepath)
 
 
  class TransitFeedSampleCodeTestCase(util.TestCase):
  """
  This test should simply contain the sample code printed on the page:
  http://code.google.com/p/googletransitdatafeed/wiki/TransitFeed
  to ensure that it doesn't cause any exceptions.
  """
  def runTest(self):
  import transitfeed
 
  schedule = transitfeed.Schedule()
  schedule.AddAgency("Sample Agency", "http://example.com",
  "America/Los_Angeles")
  route = transitfeed.Route()
  route.route_id = "SAMPLE_ID"
  route.route_type = 3
  route.route_short_name = "66"
  route.route_long_name = "Sample Route"
  schedule.AddRouteObject(route)
 
  service_period = transitfeed.ServicePeriod("WEEK")
  service_period.SetStartDate("20070101")
  service_period.SetEndDate("20071231")
  service_period.SetWeekdayService(True)
  schedule.AddServicePeriodObject(service_period)
 
  trip = transitfeed.Trip()
  trip.route_id = "SAMPLE_ID"
  trip.service_period = service_period
  trip.trip_id = "SAMPLE_TRIP"
  trip.direction_id = "0"
  trip.block_id = None
  schedule.AddTripObject(trip)
 
  stop1 = transitfeed.Stop()
  stop1.stop_id = "STOP1"
  stop1.stop_name = "Stop 1"
  stop1.stop_lat = 78.243587
  stop1.stop_lon = 32.258937
  schedule.AddStopObject(stop1)
  trip.AddStopTime(stop1, arrival_time="12:00:00", departure_time="12:00:00")
 
  stop2 = transitfeed.Stop()
  stop2.stop_id = "STOP2"
  stop2.stop_name = "Stop 2"
  stop2.stop_lat = 78.253587
  stop2.stop_lon = 32.258937
  schedule.AddStopObject(stop2)
  trip.AddStopTime(stop2, arrival_time="12:05:00", departure_time="12:05:00")
 
  schedule.Validate() # not necessary, but helpful for finding problems
  schedule.WriteGoogleTransitFeed("new_feed.zip")
 
 
  class AgencyIDValidationTestCase(util.TestCase):
  def runTest(self):
  schedule = transitfeed.Schedule(
  problem_reporter=ExceptionProblemReporterNoExpiration())
  route = transitfeed.Route()
  route.route_id = "SAMPLE_ID"
  route.route_type = 3
  route.route_long_name = "Sample Route"
  # no agency defined yet, failure.
  try:
  schedule.AddRouteObject(route)
  self.fail("Expected validation error")
  except transitfeed.InvalidValue, e:
  self.assertEqual('agency_id', e.column_name)
  self.assertEqual(None, e.value)
 
  # one agency defined, assume that the route belongs to it
  schedule.AddAgency("Test Agency", "http://example.com",
  "America/Los_Angeles", "TEST_AGENCY")
  schedule.AddRouteObject(route)
 
  schedule.AddAgency("Test Agency 2", "http://example.com",
  "America/Los_Angeles", "TEST_AGENCY_2")
  route = transitfeed.Route()
  route.route_id = "SAMPLE_ID_2"
  route.route_type = 3
  route.route_long_name = "Sample Route 2"
  # multiple agencies defined, don't know what omitted agency_id should be
  try:
  schedule.AddRouteObject(route)
  self.fail("Expected validation error")
  except transitfeed.InvalidValue, e:
  self.assertEqual('agency_id', e.column_name)
  self.assertEqual(None, e.value)
 
  # agency with no agency_id defined, matches route with no agency id
  schedule.AddAgency("Test Agency 3", "http://example.com",
  "America/Los_Angeles")
  schedule.AddRouteObject(route)
 
 
  class AddFrequencyValidationTestCase(ValidationTestCase):
  def ExpectInvalidValue(self, start_time, end_time, headway,
  column_name, value):
  try:
  trip = transitfeed.Trip()
  trip.AddFrequency(start_time, end_time, headway)
  self.fail("Expected InvalidValue error on %s" % column_name)
  except transitfeed.InvalidValue, e:
  self.assertEqual(column_name, e.column_name)
  self.assertEqual(value, e.value)
  self.assertEqual(0, len(trip.GetFrequencyTuples()))
 
  def ExpectMissingValue(self, start_time, end_time, headway, column_name):
  try:
  trip = transitfeed.Trip()
  trip.AddFrequency(start_time, end_time, headway)
  self.fail("Expected MissingValue error on %s" % column_name)
  except transitfeed.MissingValue, e:
  self.assertEqual(column_name, e.column_name)
  self.assertEqual(0, len(trip.GetFrequencyTuples()))
 
  def runTest(self):
  # these should work fine
  trip = transitfeed.Trip()
  trip.trip_id = "SAMPLE_ID"
  trip.AddFrequency(0, 50, 1200)
  trip.AddFrequency("01:00:00", "02:00:00", "600")
  trip.AddFrequency(u"02:00:00", u"03:00:00", u"1800")
  headways = trip.GetFrequencyTuples()
  self.assertEqual(3, len(headways))
  self.assertEqual((0, 50, 1200), headways[0])
  self.assertEqual((3600, 7200, 600), headways[1])
  self.assertEqual((7200, 10800, 1800), headways[2])
  self.assertEqual([("SAMPLE_ID", "00:00:00", "00:00:50", "1200"),
  ("SAMPLE_ID", "01:00:00", "02:00:00", "600"),
  ("SAMPLE_ID", "02:00:00", "03:00:00", "1800")],
  trip.GetFrequencyOutputTuples())
 
  # now test invalid input
  self.ExpectMissingValue(None, 50, 1200, "start_time")
  self.ExpectMissingValue("", 50, 1200, "start_time")
  self.ExpectInvalidValue("midnight", 50, 1200, "start_time", "midnight")
  self.ExpectInvalidValue(-50, 50, 1200, "start_time", -50)
  self.ExpectMissingValue(0, None, 1200, "end_time")
  self.ExpectMissingValue(0, "", 1200, "end_time")
  self.ExpectInvalidValue(0, "noon", 1200, "end_time", "noon")
  self.ExpectInvalidValue(0, -50, 1200, "end_time", -50)
  self.ExpectMissingValue(0, 600, 0, "headway_secs")
  self.ExpectMissingValue(0, 600, None, "headway_secs")
  self.ExpectMissingValue(0, 600, "", "headway_secs")
  self.ExpectInvalidValue(0, 600, "test", "headway_secs", "test")
  self.ExpectInvalidValue(0, 600, -60, "headway_secs", -60)
  self.ExpectInvalidValue(0, 0, 1200, "end_time", 0)
  self.ExpectInvalidValue("12:00:00", "06:00:00", 1200, "end_time", 21600)
 
 
  class ScheduleBuilderTestCase(TempFileTestCaseBase):
  """Tests for using a Schedule object to build a GTFS file."""
 
  def testBuildFeedWithUtf8Names(self):
  problems = GetTestFailureProblemReporter(self)
  schedule = transitfeed.Schedule(problem_reporter=problems)
  schedule.AddAgency("\xc8\x8b Fly Agency", "http://iflyagency.com",
  "America/Los_Angeles")
  service_period = schedule.GetDefaultServicePeriod()
  service_period.SetDateHasService('20070101')
  # "u020b i with inverted accent breve" encoded in utf-8
  stop1 = schedule.AddStop(lng=140, lat=48.2, name="\xc8\x8b hub")
  # "u020b i with inverted accent breve" as unicode string
  stop2 = schedule.AddStop(lng=140.001, lat=48.201, name=u"remote \u020b station")
  route = schedule.AddRoute(u"\u03b2", "Beta", "Bus")
  trip = route.AddTrip(schedule, u"to remote \u020b station")
  repr(stop1)
  repr(stop2)
  repr(route)
  repr(trip)
  trip.AddStopTime(stop1, schedule=schedule, stop_time='10:00:00')
  trip.AddStopTime(stop2, stop_time='10:10:00')
 
  schedule.Validate(problems)
  schedule.WriteGoogleTransitFeed(self.tempfilepath)
  read_schedule = \
  transitfeed.Loader(self.tempfilepath, problems=problems,
  extra_validation=True).Load()
  self.assertEquals(u'\u020b Fly Agency',
  read_schedule.GetDefaultAgency().agency_name)
  self.assertEquals(u'\u03b2',
  read_schedule.GetRoute(route.route_id).route_short_name)
  self.assertEquals(u'to remote \u020b station',
  read_schedule.GetTrip(trip.trip_id).trip_headsign)
 
  def testBuildSimpleFeed(self):
  """Make a very simple feed using the Schedule class."""
  problems = GetTestFailureProblemReporter(self, ("ExpirationDate",
  "NoServiceExceptions"))
  schedule = transitfeed.Schedule(problem_reporter=problems)
 
  schedule.AddAgency("Test Agency", "http://example.com",
  "America/Los_Angeles")
 
  service_period = schedule.GetDefaultServicePeriod()
  self.assertTrue(service_period.service_id)
  service_period.SetWeekdayService(has_service=True)
  service_period.SetStartDate("20070320")
  service_period.SetEndDate("20071231")
 
  stop1 = schedule.AddStop(lng=-140.12, lat=48.921,
  name="one forty at forty eight")
  stop2 = schedule.AddStop(lng=-140.22, lat=48.421, name="west and south")
  stop3 = schedule.AddStop(lng=-140.32, lat=48.121, name="more away")
  stop4 = schedule.AddStop(lng=-140.42, lat=48.021, name="more more away")
 
  route = schedule.AddRoute(short_name="R", long_name="My Route",
  route_type="Bus")
  self.assertTrue(route.route_id)
  self.assertEqual(route.route_short_name, "R")
  self.assertEqual(route.route_type, 3)
 
  trip = route.AddTrip(schedule, headsign="To The End",
  service_period=service_period)
  trip_id = trip.trip_id
  self.assertTrue(trip_id)
  trip = schedule.GetTrip(trip_id)
  self.assertEqual("To The End", trip.trip_headsign)
  self.assertEqual(service_period, trip.service_period)
 
  trip.AddStopTime(stop=stop1, arrival_secs=3600*8, departure_secs=3600*8)
  trip.AddStopTime(stop=stop2)
  trip.AddStopTime(stop=stop3, arrival_secs=3600*8 + 60*60,
  departure_secs=3600*8 + 60*60)
  trip.AddStopTime(stop=stop4, arrival_time="9:13:00",
  departure_secs=3600*8 + 60*103, stop_headsign="Last stop",
  pickup_type=1, drop_off_type=3)
 
  schedule.Validate()
  schedule.WriteGoogleTransitFeed(self.tempfilepath)
  read_schedule = \
  transitfeed.Loader(self.tempfilepath, problems=problems,
  extra_validation=True).Load()
  self.assertEqual(4, len(read_schedule.GetTrip(trip_id).GetTimeStops()))
  self.assertEqual(1, len(read_schedule.GetRouteList()))
  self.assertEqual(4, len(read_schedule.GetStopList()))
 
  def testStopIdConflict(self):
  problems = GetTestFailureProblemReporter(self)
  schedule = transitfeed.Schedule(problem_reporter=problems)
  schedule.AddStop(lat=3, lng=4.1, name="stop1", stop_id="1")
  schedule.AddStop(lat=3, lng=4.0, name="stop0", stop_id="0")
  schedule.AddStop(lat=3, lng=4.2, name="stop2")
  schedule.AddStop(lat=3, lng=4.2, name="stop4", stop_id="4")
  # AddStop will try to use stop_id=4 first but it is taken
  schedule.AddStop(lat=3, lng=4.2, name="stop5")
  stop_list = sorted(schedule.GetStopList(), key=lambda s: s.stop_name)
  self.assertEqual("stop0 stop1 stop2 stop4 stop5",
  " ".join([s.stop_name for s in stop_list]))
  self.assertMatchesRegex(r"0 1 2 4 \d{7,9}",
  " ".join(s.stop_id for s in stop_list))
 
  def testRouteIdConflict(self):
  problems = GetTestFailureProblemReporter(self)
  schedule = transitfeed.Schedule(problem_reporter=problems)
  route0 = schedule.AddRoute("0", "Long Name", "Bus")
  route1 = schedule.AddRoute("1", "", "Bus", route_id="1")
  route3 = schedule.AddRoute("3", "", "Bus", route_id="3")
  route_rand = schedule.AddRoute("R", "LNR", "Bus")
  route4 = schedule.AddRoute("4", "GooCar", "Bus")
  route_list = schedule.GetRouteList()
  route_list.sort(key=lambda r: r.route_short_name)
  self.assertEqual("0 1 3 4 R",
  " ".join(r.route_short_name for r in route_list))
  self.assertMatchesRegex("0 1 3 4 \d{7,9}",
  " ".join(r.route_id for r in route_list))
  self.assertEqual("Long Name,,,GooCar,LNR",
  ",".join(r.route_long_name for r in route_list))
 
  def testTripIdConflict(self):
  problems = GetTestFailureProblemReporter(self)
  schedule = transitfeed.Schedule(problem_reporter=problems)
  service_period = schedule.GetDefaultServicePeriod()
  service_period.SetDateHasService("20070101")
  route = schedule.AddRoute("0", "Long Name", "Bus")
  route.AddTrip()
  route.AddTrip(schedule=schedule, headsign="hs1",
  service_period=service_period, trip_id="1")
  route.AddTrip(schedule, "hs2", service_period, "2")
  route.AddTrip(trip_id="4")
  route.AddTrip() # This will be given a random trip_id
  trip_list = sorted(schedule.GetTripList(), key=lambda t: int(t.trip_id))
  self.assertMatchesRegex("0 1 2 4 \d{7,9}",
  " ".join(t.trip_id for t in trip_list))
  self.assertEqual(",hs1,hs2,,",
  ",".join(t["trip_headsign"] for t in trip_list))
  for t in trip_list:
  self.assertEqual(service_period.service_id, t.service_id)
  self.assertEqual(route.route_id, t.route_id)
 
 
  class WriteSampleFeedTestCase(TempFileTestCaseBase):
  def assertEqualTimeString(self, a, b):
  """Assert that a and b are equal, even if they don't have the same zero
  padding on the hour. IE 08:45:00 vs 8:45:00."""
  if a[1] == ':':
  a = '0' + a
  if b[1] == ':':
  b = '0' + b
  self.assertEqual(a, b)
 
  def assertEqualWithDefault(self, a, b, default):
  """Assert that a and b are equal. Treat None and default as equal."""
  if a == b:
  return
  if a in (None, default) and b in (None, default):
  return
  self.assertTrue(False, "a=%s b=%s" % (a, b))
 
  def runTest(self):
  accumulator = RecordingProblemAccumulator(self,
  ignore_types=("ExpirationDate",))
  problems = transitfeed.ProblemReporter(accumulator)
  schedule = transitfeed.Schedule(problem_reporter=problems)
  agency = transitfeed.Agency()
  agency.agency_id = "DTA"
  agency.agency_name = "Demo Transit Authority"
  agency.agency_url = "http://google.com"
  agency.agency_timezone = "America/Los_Angeles"
  agency.agency_lang = 'en'
  # Test that unknown columns, such as agency_mission, are preserved
  agency.agency_mission = "Get You There"
  schedule.AddAgencyObject(agency)
 
  routes = []
  route_data = [
  ("AB", "DTA", "10", "Airport - Bullfrog", 3),
  ("BFC", "DTA", "20", "Bullfrog - Furnace Creek Resort", 3),
  ("STBA", "DTA", "30", "Stagecoach - Airport Shuttle", 3),
  ("CITY", "DTA", "40", "City", 3),
  ("AAMV", "DTA", "50", "Airport - Amargosa Valley", 3)
  ]
 
  for route_entry in route_data:
  route = transitfeed.Route()
  (route.route_id, route.agency_id, route.route_short_name,
  route.route_long_name, route.route_type) = route_entry
  routes.append(route)
  schedule.AddRouteObject(route)
 
  shape_data = [
  (36.915760, -116.751709),
  (36.905018, -116.763206),
  (36.902134, -116.777969),
  (36.904091, -116.788185),
  (36.883602, -116.814537),
  (36.874523, -116.795593),
  (36.873302, -116.786491),
  (36.869202, -116.784241),
  (36.868515, -116.784729),
  ]
 
  shape = transitfeed.Shape("BFC1S")
  for (lat, lon) in shape_data:
  shape.AddPoint(lat, lon)
  schedule.AddShapeObject(shape)
 
  week_period = transitfeed.ServicePeriod()
  week_period.service_id = "FULLW"
  week_period.start_date = "20070101"
  week_period.end_date = "20071231"
  week_period.SetWeekdayService()
  week_period.SetWeekendService()
  week_period.SetDateHasService("20070604", False)
  schedule.AddServicePeriodObject(week_period)
 
  weekend_period = transitfeed.ServicePeriod()
  weekend_period.service_id = "WE"
  weekend_period.start_date = "20070101"
  weekend_period.end_date = "20071231"
  weekend_period.SetWeekendService()
  schedule.AddServicePeriodObject(weekend_period)
 
  stops = []
  stop_data = [
  ("FUR_CREEK_RES", "Furnace Creek Resort (Demo)",
  36.425288, -117.133162, "zone-a", "1234"),
  ("BEATTY_AIRPORT", "Nye County Airport (Demo)",
  36.868446, -116.784682, "zone-a", "1235"),
  ("BULLFROG", "Bullfrog (Demo)", 36.88108, -116.81797, "zone-b", "1236"),
  ("STAGECOACH", "Stagecoach Hotel & Casino (Demo)",
  36.915682, -116.751677, "zone-c", "1237"),
  ("NADAV", "North Ave / D Ave N (Demo)", 36.914893, -116.76821, "", ""),
  ("NANAA", "North Ave / N A Ave (Demo)", 36.914944, -116.761472, "", ""),
  ("DADAN", "Doing AVe / D Ave N (Demo)", 36.909489, -116.768242, "", ""),
  ("EMSI", "E Main St / S Irving St (Demo)",
  36.905697, -116.76218, "", ""),
  ("AMV", "Amargosa Valley (Demo)", 36.641496, -116.40094, "", ""),
  ]
  for stop_entry in stop_data:
  stop = transitfeed.Stop()
  (stop.stop_id, stop.stop_name, stop.stop_lat, stop.stop_lon,
  stop.zone_id, stop.stop_code) = stop_entry
  schedule.AddStopObject(stop)
  stops.append(stop)
  # Add a value to an unknown column and make sure it is preserved
  schedule.GetStop("BULLFROG").stop_sound = "croak!"
 
  trip_data = [
  ("AB", "FULLW", "AB1", "to Bullfrog", "0", "1", None),
  ("AB", "FULLW", "AB2", "to Airport", "1", "2", None),
  ("STBA", "FULLW", "STBA", "Shuttle", None, None, None),
  ("CITY", "FULLW", "CITY1", None, "0", None, None),
  ("CITY", "FULLW", "CITY2", None, "1", None, None),
  ("BFC", "FULLW", "BFC1", "to Furnace Creek Resort", "0", "1", "BFC1S"),
  ("BFC", "FULLW", "BFC2", "to Bullfrog", "1", "2", None),
  ("AAMV", "WE", "AAMV1", "to Amargosa Valley", "0", None, None),
  ("AAMV", "WE", "AAMV2", "to Airport", "1", None, None),
  ("AAMV", "WE", "AAMV3", "to Amargosa Valley", "0", None, None),
  ("AAMV", "WE", "AAMV4", "to Airport", "1", None, None),
  ]
 
  trips = []
  for trip_entry in trip_data:
  trip = transitfeed.Trip()
  (trip.route_id, trip.service_id, trip.trip_id, trip.trip_headsign,
  trip.direction_id, trip.block_id, trip.shape_id) = trip_entry
  trips.append(trip)
  schedule.AddTripObject(trip)
 
  stop_time_data = {
  "STBA": [("6:00:00", "6:00:00", "STAGECOACH", None, None, None, None),
  ("6:20:00", "6:20:00", "BEATTY_AIRPORT", None, None, None, None)],
  "CITY1": [("6:00:00", "6:00:00", "STAGECOACH", 1.34, 0, 0, "stop 1"),
  ("6:05:00", "6:07:00", "NANAA", 2.40, 1, 2, "stop 2"),
  ("6:12:00", "6:14:00", "NADAV", 3.0, 2, 2, "stop 3"),
  ("6:19:00", "6:21:00", "DADAN", 4, 2, 2, "stop 4"),
  ("6:26:00", "6:28:00", "EMSI", 5.78, 2, 3, "stop 5")],
  "CITY2": [("6:28:00", "6:28:00", "EMSI", None, None, None, None),
  ("6:35:00", "6:37:00", "DADAN", None, None, None, None),
  ("6:42:00", "6:44:00", "NADAV", None, None, None, None),
  ("6:49:00", "6:51:00", "NANAA", None, None, None, None),
  ("6:56:00", "6:58:00", "STAGECOACH", None, None, None, None)],
  "AB1": [("8:00:00", "8:00:00", "BEATTY_AIRPORT", None, None, None, None),
  ("8:10:00", "8:15:00", "BULLFROG", None, None, None, None)],
  "AB2": [("12:05:00", "12:05:00", "BULLFROG", None, None, None, None),
  ("12:15:00", "12:15:00", "BEATTY_AIRPORT", None, None, None, None)],
  "BFC1": [("8:20:00", "8:20:00", "BULLFROG", None, None, None, None),
  ("9:20:00", "9:20:00", "FUR_CREEK_RES", None, None, None, None)],
  "BFC2": [("11:00:00", "11:00:00", "FUR_CREEK_RES", None, None, None, None),
  ("12:00:00", "12:00:00", "BULLFROG", None, None, None, None)],
  "AAMV1": [("8:00:00", "8:00:00", "BEATTY_AIRPORT", None, None, None, None),
  ("9:00:00", "9:00:00", "AMV", None, None, None, None)],
  "AAMV2": [("10:00:00", "10:00:00", "AMV", None, None, None, None),
  ("11:00:00", "11:00:00", "BEATTY_AIRPORT", None, None, None, None)],
  "AAMV3": [("13:00:00", "13:00:00", "BEATTY_AIRPORT", None, None, None, None),
  ("14:00:00", "14:00:00", "AMV", None, None, None, None)],
  "AAMV4": [("15:00:00", "15:00:00", "AMV", None, None, None, None),
  ("16:00:00", "16:00:00", "BEATTY_AIRPORT", None, None, None, None)],
  }
 
  for trip_id, stop_time_list in stop_time_data.items():
  for stop_time_entry in stop_time_list:
  (arrival_time, departure_time, stop_id, shape_dist_traveled,
  pickup_type, drop_off_type, stop_headsign) = stop_time_entry
  trip = schedule.GetTrip(trip_id)
  stop = schedule.GetStop(stop_id)
  trip.AddStopTime(stop, arrival_time=arrival_time,
  departure_time=departure_time,
  shape_dist_traveled=shape_dist_traveled,
  pickup_type=pickup_type, drop_off_type=drop_off_type,
  stop_headsign=stop_headsign)
 
  self.assertEqual(0, schedule.GetTrip("CITY1").GetStopTimes()[0].pickup_type)
  self.assertEqual(1, schedule.GetTrip("CITY1").GetStopTimes()[1].pickup_type)
 
  headway_data = [
  ("STBA", "6:00:00", "22:00:00", 1800),
  ("CITY1", "6:00:00", "7:59:59", 1800),
  ("CITY2", "6:00:00", "7:59:59", 1800),
  ("CITY1", "8:00:00", "9:59:59", 600),
  ("CITY2", "8:00:00", "9:59:59", 600),
  ("CITY1", "10:00:00", "15:59:59", 1800),
  ("CITY2", "10:00:00", "15:59:59", 1800),
  ("CITY1", "16:00:00", "18:59:59", 600),
  ("CITY2", "16:00:00", "18:59:59", 600),
  ("CITY1", "19:00:00", "22:00:00", 1800),
  ("CITY2", "19:00:00", "22:00:00", 1800),
  ]
 
  headway_trips = {}
  for headway_entry in headway_data:
  (trip_id, start_time, end_time, headway) = headway_entry
  headway_trips[trip_id] = [] # adding to set to check later
  trip = schedule.GetTrip(trip_id)
  trip.AddFrequency(start_time, end_time, headway, problems)
  for trip_id in headway_trips:
  headway_trips[trip_id] = \
  schedule.GetTrip(trip_id).GetFrequencyTuples()
 
  fare_data = [
  ("p", 1.25, "USD", 0, 0),
  ("a", 5.25, "USD", 0, 0),
  ]
 
  fares = []
  for fare_entry in fare_data:
  fare = transitfeed.FareAttribute(fare_entry[0], fare_entry[1],
  fare_entry[2], fare_entry[3],
  fare_entry[4])
  fares.append(fare)
  schedule.AddFareAttributeObject(fare)
 
  fare_rule_data = [
  ("p", "AB", "zone-a", "zone-b", None),
  ("p", "STBA", "zone-a", None, "zone-c"),
  ("p", "BFC", None, "zone-b", "zone-a"),
  ("a", "AAMV", None, None, None),
  ]
 
  for fare_id, route_id, orig_id, dest_id, contains_id in fare_rule_data:
  rule = transitfeed.FareRule(
  fare_id=fare_id, route_id=route_id, origin_id=orig_id,
  destination_id=dest_id, contains_id=contains_id)
  schedule.AddFareRuleObject(rule, problems)
 
  schedule.Validate(problems)
  accumulator.AssertNoMoreExceptions()
  schedule.WriteGoogleTransitFeed(self.tempfilepath)
 
  read_schedule = \
  transitfeed.Loader(self.tempfilepath, problems=problems,
  extra_validation=True).Load()
  e = accumulator.PopException("UnrecognizedColumn")
  self.assertEqual(e.file_name, "agency.txt")
  self.assertEqual(e.column_name, "agency_mission")
  e = accumulator.PopException("UnrecognizedColumn")
  self.assertEqual(e.file_name, "stops.txt")
  self.assertEqual(e.column_name, "stop_sound")
  accumulator.AssertNoMoreExceptions()
 
  self.assertEqual(1, len(read_schedule.GetAgencyList()))
  self.assertEqual(agency, read_schedule.GetAgency(agency.agency_id))
 
  self.assertEqual(len(routes), len(read_schedule.GetRouteList()))
  for route in routes:
  self.assertEqual(route, read_schedule.GetRoute(route.route_id))
 
  self.assertEqual(2, len(read_schedule.GetServicePeriodList()))
  self.assertEqual(week_period,
  read_schedule.GetServicePeriod(week_period.service_id))
  self.assertEqual(weekend_period,
  read_schedule.GetServicePeriod(weekend_period.service_id))
 
  self.assertEqual(len(stops), len(read_schedule.GetStopList()))
  for stop in stops:
  self.assertEqual(stop, read_schedule.GetStop(stop.stop_id))
  self.assertEqual("croak!", read_schedule.GetStop("BULLFROG").stop_sound)
 
  self.assertEqual(len(trips), len(read_schedule.GetTripList()))
  for trip in trips:
  self.assertEqual(trip, read_schedule.GetTrip(trip.trip_id))
 
  for trip_id in headway_trips:
  self.assertEqual(headway_trips[trip_id],
  read_schedule.GetTrip(trip_id).GetFrequencyTuples())
 
  for trip_id, stop_time_list in stop_time_data.items():
  trip = read_schedule.GetTrip(trip_id)
  read_stoptimes = trip.GetStopTimes()
  self.assertEqual(len(read_stoptimes), len(stop_time_list))
  for stop_time_entry, read_stoptime in zip(stop_time_list, read_stoptimes):
  (arrival_time, departure_time, stop_id, shape_dist_traveled,
  pickup_type, drop_off_type, stop_headsign) = stop_time_entry
  self.assertEqual(stop_id, read_stoptime.stop_id)
  self.assertEqual(read_schedule.GetStop(stop_id), read_stoptime.stop)
  self.assertEqualTimeString(arrival_time, read_stoptime.arrival_time)
  self.assertEqualTimeString(departure_time, read_stoptime.departure_time)
  self.assertEqual(shape_dist_traveled, read_stoptime.shape_dist_traveled)
  self.assertEqualWithDefault(pickup_type, read_stoptime.pickup_type, 0)
  self.assertEqualWithDefault(drop_off_type, read_stoptime.drop_off_type, 0)
  self.assertEqualWithDefault(stop_headsign, read_stoptime.stop_headsign, '')
 
  self.assertEqual(len(fares), len(read_schedule.GetFareAttributeList()))
  for fare in fares:
  self.assertEqual(fare, read_schedule.GetFareAttribute(fare.fare_id))
 
  read_fare_rules_data = []
  for fare in read_schedule.GetFareAttributeList():
  for rule in fare.GetFareRuleList():
  self.assertEqual(fare.fare_id, rule.fare_id)
  read_fare_rules_data.append((fare.fare_id, rule.route_id,
  rule.origin_id, rule.destination_id,
  rule.contains_id))
 
  fare_rule_data.sort()
  read_fare_rules_data.sort()
  self.assertEqual(len(read_fare_rules_data), len(fare_rule_data))
  for rf, f in zip(read_fare_rules_data, fare_rule_data):
  self.assertEqual(rf, f)
 
  self.assertEqual(1, len(read_schedule.GetShapeList()))
  self.assertEqual(shape, read_schedule.GetShape(shape.shape_id))
 
  # TODO: test GetPattern
 
  class DefaultAgencyTestCase(util.TestCase):
  def freeAgency(self, ex=''):
  agency = transitfeed.Agency()
  agency.agency_id = 'agencytestid' + ex
  agency.agency_name = 'Foo Bus Line' + ex
  agency.agency_url = 'http://gofoo.com/' + ex
  agency.agency_timezone='America/Los_Angeles'
  return agency
 
  def test_SetDefault(self):
  schedule = transitfeed.Schedule()
  agency = self.freeAgency()
  schedule.SetDefaultAgency(agency)
  self.assertEqual(agency, schedule.GetDefaultAgency())
 
  def test_NewDefaultAgency(self):
  schedule = transitfeed.Schedule()
  agency1 = schedule.NewDefaultAgency()
  self.assertTrue(agency1.agency_id)
  self.assertEqual(agency1.agency_id, schedule.GetDefaultAgency().agency_id)
  self.assertEqual(1, len(schedule.GetAgencyList()))
  agency2 = schedule.NewDefaultAgency()
  self.assertTrue(agency2.agency_id)
  self.assertEqual(agency2.agency_id, schedule.GetDefaultAgency().agency_id)
  self.assertEqual(2, len(schedule.GetAgencyList()))
  self.assertNotEqual(agency1, agency2)
  self.assertNotEqual(agency1.agency_id, agency2.agency_id)
 
  agency3 = schedule.NewDefaultAgency(agency_id='agency3',
  agency_name='Agency 3',
  agency_url='http://goagency')
  self.assertEqual(agency3.agency_id, 'agency3')
  self.assertEqual(agency3.agency_name, 'Agency 3')
  self.assertEqual(agency3.agency_url, 'http://goagency')
  self.assertEqual(agency3, schedule.GetDefaultAgency())
  self.assertEqual('agency3', schedule.GetDefaultAgency().agency_id)
  self.assertEqual(3, len(schedule.GetAgencyList()))
 
  def test_NoAgencyMakeNewDefault(self):
  schedule = transitfeed.Schedule()
  agency = schedule.GetDefaultAgency()
  self.assertTrue(isinstance(agency, transitfeed.Agency))
  self.assertTrue(agency.agency_id)
  self.assertEqual(1, len(schedule.GetAgencyList()))
  self.assertEqual(agency, schedule.GetAgencyList()[0])
  self.assertEqual(agency.agency_id, schedule.GetAgencyList()[0].agency_id)
 
  def test_AssumeSingleAgencyIsDefault(self):
  schedule = transitfeed.Schedule()
  agency1 = self.freeAgency()
  schedule.AddAgencyObject(agency1)
  agency2 = self.freeAgency('2') # don't add to schedule
  # agency1 is default because it is the only Agency in schedule
  self.assertEqual(agency1, schedule.GetDefaultAgency())
 
  def test_MultipleAgencyCausesNoDefault(self):
  schedule = transitfeed.Schedule()
  agency1 = self.freeAgency()
  schedule.AddAgencyObject(agency1)
  agency2 = self.freeAgency('2')
  schedule.AddAgencyObject(agency2)
  self.assertEqual(None, schedule.GetDefaultAgency())
 
  def test_OverwriteExistingAgency(self):
  schedule = transitfeed.Schedule()
  agency1 = self.freeAgency()
  agency1.agency_id = '1'
  schedule.AddAgencyObject(agency1)
  agency2 = schedule.NewDefaultAgency()
  # Make sure agency1 was not overwritten by the new default
  self.assertEqual(agency1, schedule.GetAgency(agency1.agency_id))
  self.assertNotEqual('1', agency2.agency_id)
 
 
  class FindUniqueIdTestCase(util.TestCase):
  def test_simple(self):
  d = {}
  for i in range(0, 5):
  d[transitfeed.FindUniqueId(d)] = 1
  k = d.keys()
  k.sort()
  self.assertEqual(('0', '1', '2', '3', '4'), tuple(k))
 
  def test_AvoidCollision(self):
  d = {'1': 1}
  d[transitfeed.FindUniqueId(d)] = 1
  self.assertEqual(2, len(d))
  self.assertFalse('3' in d, "Ops, next statement should add something to d")
  d['3'] = None
  d[transitfeed.FindUniqueId(d)] = 1
  self.assertEqual(4, len(d))
 
 
  class DefaultServicePeriodTestCase(util.TestCase):
  def test_SetDefault(self):
  schedule = transitfeed.Schedule()
  service1 = transitfeed.ServicePeriod()
  service1.SetDateHasService('20070101', True)
  service1.service_id = 'SERVICE1'
  schedule.SetDefaultServicePeriod(service1)
  self.assertEqual(service1, schedule.GetDefaultServicePeriod())
  self.assertEqual(service1, schedule.GetServicePeriod(service1.service_id))
 
  def test_NewDefault(self):
  schedule = transitfeed.Schedule()
  service1 = schedule.NewDefaultServicePeriod()
  self.assertTrue(service1.service_id)
  schedule.GetServicePeriod(service1.service_id)
  service1.SetDateHasService('20070101', True) # Make service1 different
  service2 = schedule.NewDefaultServicePeriod()
  schedule.GetServicePeriod(service2.service_id)
  self.assertTrue(service1.service_id)
  self.assertTrue(service2.service_id)
  self.assertNotEqual(service1, service2)
  self.assertNotEqual(service1.service_id, service2.service_id)
 
  def test_NoServicesMakesNewDefault(self):
  schedule = transitfeed.Schedule()
  service1 = schedule.GetDefaultServicePeriod()
  self.assertEqual(service1, schedule.GetServicePeriod(service1.service_id))
 
  def test_AssumeSingleServiceIsDefault(self):
  schedule = transitfeed.Schedule()
  service1 = transitfeed.ServicePeriod()
  service1.SetDateHasService('20070101', True)
  service1.service_id = 'SERVICE1'
  schedule.AddServicePeriodObject(service1)
  self.assertEqual(service1, schedule.GetDefaultServicePeriod())
  self.assertEqual(service1.service_id, schedule.GetDefaultServicePeriod().service_id)
 
  def test_MultipleServicesCausesNoDefault(self):
  schedule = transitfeed.Schedule()
  service1 = transitfeed.ServicePeriod()
  service1.service_id = 'SERVICE1'
  service1.SetDateHasService('20070101', True)
  schedule.AddServicePeriodObject(service1)
  service2 = transitfeed.ServicePeriod()
  service2.service_id = 'SERVICE2'
  service2.SetDateHasService('20070201', True)
  schedule.AddServicePeriodObject(service2)
  service_d = schedule.GetDefaultServicePeriod()
  self.assertEqual(service_d, None)
 
 
  class GetTripTimeTestCase(util.TestCase):
  """Test for GetStopTimeTrips and GetTimeInterpolatedStops"""
  def setUp(self):
  problems = GetTestFailureProblemReporter(self)
  schedule = transitfeed.Schedule(problem_reporter=problems)
  self.schedule = schedule
  schedule.AddAgency("Agency", "http://iflyagency.com",
  "America/Los_Angeles")
  service_period = schedule.GetDefaultServicePeriod()
  service_period.SetDateHasService('20070101')
  self.stop1 = schedule.AddStop(lng=140.01, lat=0, name="140.01,0")
  self.stop2 = schedule.AddStop(lng=140.02, lat=0, name="140.02,0")
  self.stop3 = schedule.AddStop(lng=140.03, lat=0, name="140.03,0")
  self.stop4 = schedule.AddStop(lng=140.04, lat=0, name="140.04,0")
  self.stop5 = schedule.AddStop(lng=140.05, lat=0, name="140.05,0")
  self.route1 = schedule.AddRoute("1", "One", "Bus")
 
  self.trip1 = self.route1.AddTrip(schedule, "trip 1", trip_id='trip1')
  self.trip1.AddStopTime(self.stop1, schedule=schedule, departure_secs=100, arrival_secs=100)
  self.trip1.AddStopTime(self.stop2, schedule=schedule)
  self.trip1.AddStopTime(self.stop3, schedule=schedule)
  # loop back to stop2 to test that interpolated stops work ok even when
  # a stop between timepoints is further from the timepoint than the
  # preceding
  self.trip1.AddStopTime(self.stop2, schedule=schedule)
  self.trip1.AddStopTime(self.stop4, schedule=schedule, departure_secs=400, arrival_secs=400)
 
  self.trip2 = self.route1.AddTrip(schedule, "trip 2", trip_id='trip2')
  self.trip2.AddStopTime(self.stop2, schedule=schedule, departure_secs=500, arrival_secs=500)
  self.trip2.AddStopTime(self.stop3, schedule=schedule, departure_secs=600, arrival_secs=600)
  self.trip2.AddStopTime(self.stop4, schedule=schedule, departure_secs=700, arrival_secs=700)
  self.trip2.AddStopTime(self.stop3, schedule=schedule, departure_secs=800, arrival_secs=800)
 
  self.trip3 = self.route1.AddTrip(schedule, "trip 3", trip_id='trip3')
 
  def testGetTimeInterpolatedStops(self):
  rv = self.trip1.GetTimeInterpolatedStops()
  self.assertEqual(5, len(rv))
  (secs, stoptimes, istimepoints) = tuple(zip(*rv))
 
  self.assertEqual((100, 160, 220, 280, 400), secs)
  self.assertEqual(("140.01,0", "140.02,0", "140.03,0", "140.02,0", "140.04,0"),
  tuple([st.stop.stop_name for st in stoptimes]))
  self.assertEqual((True, False, False, False, True), istimepoints)
 
  self.assertEqual([], self.trip3.GetTimeInterpolatedStops())
 
  def testGetTimeInterpolatedStopsUntimedEnd(self):
  self.trip2.AddStopTime(self.stop3, schedule=self.schedule)
  self.assertRaises(ValueError, self.trip2.GetTimeInterpolatedStops)
 
  def testGetTimeInterpolatedStopsUntimedStart(self):
  # Temporarily replace the problem reporter so that adding the first
  # StopTime without a time doesn't throw an exception.
  old_problems = self.schedule.problem_reporter
  self.schedule.problem_reporter = GetTestFailureProblemReporter(
  self, ("OtherProblem",))
  self.trip3.AddStopTime(self.stop3, schedule=self.schedule)
  self.schedule.problem_reporter = old_problems
  self.trip3.AddStopTime(self.stop2, schedule=self.schedule,
  departure_secs=500, arrival_secs=500)
  self.assertRaises(ValueError, self.trip3.GetTimeInterpolatedStops)
 
  def testGetTimeInterpolatedStopsSingleStopTime(self):
  self.trip3.AddStopTime(self.stop3, schedule=self.schedule,
  departure_secs=500, arrival_secs=500)
  rv = self.trip3.GetTimeInterpolatedStops()
  self.assertEqual(1, len(rv))
  self.assertEqual(500, rv[0][0])
  self.assertEqual(True, rv[0][2])
 
  def testGetStopTimeTrips(self):
  stopa = self.schedule.GetNearestStops(lon=140.03, lat=0)[0]
  self.assertEqual("140.03,0", stopa.stop_name) # Got stop3?
  rv = stopa.GetStopTimeTrips(self.schedule)
  self.assertEqual(3, len(rv))
  (secs, trip_index, istimepoints) = tuple(zip(*rv))
  self.assertEqual((220, 600, 800), secs)
  self.assertEqual(("trip1", "trip2", "trip2"), tuple([ti[0].trip_id for ti in trip_index]))
  self.assertEqual((2, 1, 3), tuple([ti[1] for ti in trip_index]))
  self.assertEqual((False, True, True), istimepoints)
 
  def testStopTripIndex(self):
  trip_index = self.stop3.trip_index
  trip_ids = [t.trip_id for t, i in trip_index]
  self.assertEqual(["trip1", "trip2", "trip2"], trip_ids)
  self.assertEqual([2, 1, 3], [i for t, i in trip_index])
 
  def testGetTrips(self):
  self.assertEqual(set([t.trip_id for t in self.stop1.GetTrips(self.schedule)]),
  set([self.trip1.trip_id]))
  self.assertEqual(set([t.trip_id for t in self.stop2.GetTrips(self.schedule)]),
  set([self.trip1.trip_id, self.trip2.trip_id]))
  self.assertEqual(set([t.trip_id for t in self.stop3.GetTrips(self.schedule)]),
  set([self.trip1.trip_id, self.trip2.trip_id]))
  self.assertEqual(set([t.trip_id for t in self.stop4.GetTrips(self.schedule)]),
  set([self.trip1.trip_id, self.trip2.trip_id]))
  self.assertEqual(set([t.trip_id for t in self.stop5.GetTrips(self.schedule)]),
  set())
 
 
  class ApproximateDistanceBetweenStopsTestCase(util.TestCase):
  def testEquator(self):
  stop1 = transitfeed.Stop(lat=0, lng=100,
  name='Stop one', stop_id='1')
  stop2 = transitfeed.Stop(lat=0.01, lng=100.01,
  name='Stop two', stop_id='2')
  self.assertAlmostEqual(
  transitfeed.ApproximateDistanceBetweenStops(stop1, stop2),
  1570, -1) # Compare first 3 digits
 
  def testWhati(self):
  stop1 = transitfeed.Stop(lat=63.1, lng=-117.2,
  name='Stop whati one', stop_id='1')
  stop2 = transitfeed.Stop(lat=63.102, lng=-117.201,
  name='Stop whati two', stop_id='2')
  self.assertAlmostEqual(
  transitfeed.ApproximateDistanceBetweenStops(stop1, stop2),
  228, 0)
 
 
  class TimeConversionHelpersTestCase(util.TestCase):
  def testTimeToSecondsSinceMidnight(self):
  self.assertEqual(transitfeed.TimeToSecondsSinceMidnight("01:02:03"), 3723)
  self.assertEqual(transitfeed.TimeToSecondsSinceMidnight("00:00:00"), 0)
  self.assertEqual(transitfeed.TimeToSecondsSinceMidnight("25:24:23"), 91463)
  try:
  transitfeed.TimeToSecondsSinceMidnight("10:15:00am")
  except transitfeed.Error:
  pass # expected
  else:
  self.fail("Should have thrown Error")
 
  def testFormatSecondsSinceMidnight(self):
  self.assertEqual(transitfeed.FormatSecondsSinceMidnight(3723), "01:02:03")
  self.assertEqual(transitfeed.FormatSecondsSinceMidnight(0), "00:00:00")
  self.assertEqual(transitfeed.FormatSecondsSinceMidnight(91463), "25:24:23")
 
  def testDateStringToDateObject(self):
  self.assertEqual(transitfeed.DateStringToDateObject("20080901"),
  datetime.date(2008, 9, 1))
  try:
  transitfeed.DateStringToDateObject("20080841")
  except ValueError:
  pass # expected
  else:
  self.fail("Should have thrown ValueError")
 
  class FloatStringToFloatTestCase(util.TestCase):
  def runTest(self):
  accumulator = RecordingProblemAccumulator(self)
  problems = transitfeed.ProblemReporter(accumulator)
 
  self.assertAlmostEqual(0, transitfeed.FloatStringToFloat("0", problems))
  self.assertAlmostEqual(0, transitfeed.FloatStringToFloat(u"0", problems))
  self.assertAlmostEqual(1, transitfeed.FloatStringToFloat("1", problems))
  self.assertAlmostEqual(1,
  transitfeed.FloatStringToFloat("1.00000", problems))
  self.assertAlmostEqual(1.5,
  transitfeed.FloatStringToFloat("1.500", problems))
  self.assertAlmostEqual(-2, transitfeed.FloatStringToFloat("-2.0", problems))
  self.assertAlmostEqual(-2.5,
  transitfeed.FloatStringToFloat("-2.5", problems))
  self.assertRaises(ValueError,
  transitfeed.FloatStringToFloat, ".", problems)
  self.assertRaises(ValueError,
  transitfeed.FloatStringToFloat, "0x20", problems)
  self.assertRaises(ValueError,
  transitfeed.FloatStringToFloat, "-0x20", problems)
  self.assertRaises(ValueError,
  transitfeed.FloatStringToFloat, "0b10", problems)
 
  # These should issue a warning, but otherwise parse successfully
  self.assertAlmostEqual(0.001,
  transitfeed.FloatStringToFloat("1E-3", problems))
  e = accumulator.PopException("InvalidFloatValue")
  self.assertAlmostEqual(0.001,
  transitfeed.FloatStringToFloat(".001", problems))
  e = accumulator.PopException("InvalidFloatValue")
  self.assertAlmostEqual(-0.001,
  transitfeed.FloatStringToFloat("-.001", problems))
  e = accumulator.PopException("InvalidFloatValue")
  self.assertAlmostEqual(0,
  transitfeed.FloatStringToFloat("0.", problems))
  e = accumulator.PopException("InvalidFloatValue")
 
  accumulator.AssertNoMoreExceptions()
 
 
  class NonNegIntStringToIntTestCase(util.TestCase):
  def runTest(self):
  accumulator = RecordingProblemAccumulator(self)
  problems = transitfeed.ProblemReporter(accumulator)
 
  self.assertEqual(0, transitfeed.NonNegIntStringToInt("0", problems))
  self.assertEqual(0, transitfeed.NonNegIntStringToInt(u"0", problems))
  self.assertEqual(1, transitfeed.NonNegIntStringToInt("1", problems))
  self.assertEqual(2, transitfeed.NonNegIntStringToInt("2", problems))
  self.assertEqual(10, transitfeed.NonNegIntStringToInt("10", problems))
  self.assertEqual(1234567890123456789,
  transitfeed.NonNegIntStringToInt("1234567890123456789",
  problems))
  self.assertRaises(ValueError,
  transitfeed.NonNegIntStringToInt, "", problems)
  self.assertRaises(ValueError,
  transitfeed.NonNegIntStringToInt, "-1", problems)
  self.assertRaises(ValueError,
  transitfeed.NonNegIntStringToInt, "0x1", problems)
  self.assertRaises(ValueError,
  transitfeed.NonNegIntStringToInt, "1.0", problems)
  self.assertRaises(ValueError,
  transitfeed.NonNegIntStringToInt, "1e1", problems)
  self.assertRaises(ValueError,
  transitfeed.NonNegIntStringToInt, "0x20", problems)
  self.assertRaises(ValueError,
  transitfeed.NonNegIntStringToInt, "0b10", problems)
  self.assertRaises(TypeError,
  transitfeed.NonNegIntStringToInt, 1, problems)
  self.assertRaises(TypeError,
  transitfeed.NonNegIntStringToInt, None, problems)
 
  # These should issue a warning, but otherwise parse successfully
  self.assertEqual(1, transitfeed.NonNegIntStringToInt("+1", problems))
  e = accumulator.PopException("InvalidNonNegativeIntegerValue")
 
  self.assertEqual(1, transitfeed.NonNegIntStringToInt("01", problems))
  e = accumulator.PopException("InvalidNonNegativeIntegerValue")
 
  self.assertEqual(0, transitfeed.NonNegIntStringToInt("00", problems))
  e = accumulator.PopException("InvalidNonNegativeIntegerValue")
 
  accumulator.AssertNoMoreExceptions()
 
 
  class GetFrequencyTimesTestCase(util.TestCase):
  """Test for GetFrequencyStartTimes and GetFrequencyStopTimes"""
  def setUp(self):
  problems = GetTestFailureProblemReporter(self)
  schedule = transitfeed.Schedule(problem_reporter=problems)
  self.schedule = schedule
  schedule.AddAgency("Agency", "http://iflyagency.com",
  "America/Los_Angeles")
  service_period = schedule.GetDefaultServicePeriod()
  service_period.SetStartDate("20080101")
  service_period.SetEndDate("20090101")
  service_period.SetWeekdayService(True)
  self.stop1 = schedule.AddStop(lng=140.01, lat=0, name="140.01,0")
  self.stop2 = schedule.AddStop(lng=140.02, lat=0, name="140.02,0")
  self.stop3 = schedule.AddStop(lng=140.03, lat=0, name="140.03,0")
  self.stop4 = schedule.AddStop(lng=140.04, lat=0, name="140.04,0")
  self.stop5 = schedule.AddStop(lng=140.05, lat=0, name="140.05,0")
  self.route1 = schedule.AddRoute("1", "One", "Bus")
 
  self.trip1 = self.route1.AddTrip(schedule, "trip 1", trip_id="trip1")
  # add different types of stop times
  self.trip1.AddStopTime(self.stop1, arrival_time="17:00:00", departure_time="17:01:00") # both arrival and departure time
  self.trip1.AddStopTime(self.stop2, schedule=schedule) # non timed
  self.trip1.AddStopTime(self.stop3, stop_time="17:45:00") # only stop_time
 
  # add headways starting before the trip
  self.trip1.AddFrequency("16:00:00","18:00:00",1800) # each 30 min
  self.trip1.AddFrequency("18:00:00","20:00:00",2700) # each 45 min
 
  def testGetFrequencyStartTimes(self):
  start_times = self.trip1.GetFrequencyStartTimes()
  self.assertEqual(
  ["16:00:00", "16:30:00", "17:00:00", "17:30:00",
  "18:00:00", "18:45:00", "19:30:00"],
  [transitfeed.FormatSecondsSinceMidnight(secs) for secs in start_times])
  # GetHeadwayStartTimes is deprecated, but should still return the same
  # result as GetFrequencyStartTimes
  self.assertEqual(start_times,
  self.trip1.GetFrequencyStartTimes())
 
  def testGetFrequencyStopTimes(self):
  stoptimes_list = self.trip1.GetFrequencyStopTimes()
  arrival_secs = []
  departure_secs = []
  for stoptimes in stoptimes_list:
  arrival_secs.append([st.arrival_secs for st in stoptimes])
  departure_secs.append([st.departure_secs for st in stoptimes])
 
  # GetHeadwayStopTimes is deprecated, but should still return the same
  # result as GetFrequencyStopTimes
  # StopTimes are instantiated as they're read from the DB so they can't be
  # compared directly, but checking {arrival,departure}_secs should be enough
  # to catch most errors.
  headway_stoptimes_list = self.trip1.GetFrequencyStopTimes()
  headway_arrival_secs = []
  headway_departure_secs = []
  for stoptimes in stoptimes_list:
  headway_arrival_secs.append([st.arrival_secs for st in stoptimes])
  headway_departure_secs.append([st.departure_secs for st in stoptimes])
  self.assertEqual(arrival_secs, headway_arrival_secs)
  self.assertEqual(departure_secs, headway_departure_secs)
 
  self.assertEqual(([57600,None,60300],[59400,None,62100],[61200,None,63900],
  [63000,None,65700],[64800,None,67500],[67500,None,70200],
  [70200,None,72900]),
  tuple(arrival_secs))
  self.assertEqual(([57660,None,60300],[59460,None,62100],[61260,None,63900],
  [63060,None,65700],[64860,None,67500],[67560,None,70200],
  [70260,None,72900]),
  tuple(departure_secs))
 
  # test if stoptimes are created with same parameters than the ones from the original trip
  stoptimes = self.trip1.GetStopTimes()
  for stoptimes_clone in stoptimes_list:
  self.assertEqual(len(stoptimes_clone), len(stoptimes))
  for st_clone, st in zip(stoptimes_clone, stoptimes):
  for name in st.__slots__:
  if name not in ('arrival_secs', 'departure_secs'):
  self.assertEqual(getattr(st, name), getattr(st_clone, name))
 
 
  class ServiceGapsTestCase(MemoryZipTestCase):
 
  def setUp(self):
  super(ServiceGapsTestCase, self).setUp()
  self.SetArchiveContents("calendar.txt",
  "service_id,monday,tuesday,wednesday,thursday,friday,"
  "saturday,sunday,start_date,end_date\n"
  "FULLW,1,1,1,1,1,1,1,20090601,20090610\n"
  "WE,0,0,0,0,0,1,1,20090718,20101231\n")
  self.SetArchiveContents("calendar_dates.txt",
  "service_id,date,exception_type\n"
  "WE,20090815,2\n"
  "WE,20090816,2\n"
  "WE,20090822,2\n"
  # The following two lines are a 12-day service gap.
  # Shouldn't issue a warning
  "WE,20090829,2\n"
  "WE,20090830,2\n"
  "WE,20100102,2\n"
  "WE,20100103,2\n"
  "WE,20100109,2\n"
  "WE,20100110,2\n"
  "WE,20100612,2\n"
  "WE,20100613,2\n"
  "WE,20100619,2\n"
  "WE,20100620,2\n")
  self.SetArchiveContents("trips.txt",
  "route_id,service_id,trip_id\n"
  "AB,WE,AB1\n"
  "AB,FULLW,AB2\n")
  self.SetArchiveContents(
  "stop_times.txt",
  "trip_id,arrival_time,departure_time,stop_id,stop_sequence\n"
  "AB1,10:00:00,10:00:00,BEATTY_AIRPORT,1\n"
  "AB1,10:20:00,10:20:00,BULLFROG,2\n"
  "AB2,10:25:00,10:25:00,STAGECOACH,1\n"
  "AB2,10:55:00,10:55:00,BULLFROG,2\n")
  self.schedule = self.MakeLoaderAndLoad(extra_validation=False)
 
  # If there is a service gap starting before today, and today has no service,
  # it should be found - even if tomorrow there is service
  def testServiceGapBeforeTodayIsDiscovered(self):
  self.schedule.Validate(today=date(2009, 7, 17),
  service_gap_interval=13)
  exception = self.accumulator.PopException("TooManyDaysWithoutService")
  self.assertEquals(date(2009, 7, 5),
  exception.first_day_without_service)
  self.assertEquals(date(2009, 7, 17),
  exception.last_day_without_service)
 
  self.AssertCommonExceptions(date(2010, 6, 25))
 
  # If today has service past service gaps should not appear
  def testNoServiceGapBeforeTodayIfTodayHasService(self):
  self.schedule.Validate(today=date(2009, 7, 18),
  service_gap_interval=13)
 
  self.AssertCommonExceptions(date(2010, 6, 25))
 
  # If the feed starts today NO previous service gap should be found
  # even if today does not have service
  def testNoServiceGapBeforeTodayIfTheFeedStartsToday(self):
  self.schedule.Validate(today=date(2009, 06, 01),
  service_gap_interval=13)
 
  # This service gap is the one between FULLW and WE
  exception = self.accumulator.PopException("TooManyDaysWithoutService")
  self.assertEquals(date(2009, 6, 11),
  exception.first_day_without_service)
  self.assertEquals(date(2009, 7, 17),
  exception.last_day_without_service)
  # The one-year period ends before the June 2010 gap, so that last
  # service gap should _not_ be found
  self.AssertCommonExceptions(None)
 
  # If there is a gap at the end of the one-year period we should find it
  def testGapAtTheEndOfTheOneYearPeriodIsDiscovered(self):
  self.schedule.Validate(today=date(2009, 06, 22),
  service_gap_interval=13)
 
  # This service gap is the one between FULLW and WE
  exception = self.accumulator.PopException("TooManyDaysWithoutService")
  self.assertEquals(date(2009, 6, 11),
  exception.first_day_without_service)
  self.assertEquals(date(2009, 7, 17),
  exception.last_day_without_service)
 
  self.AssertCommonExceptions(date(2010, 6, 21))
 
  # If we are right in the middle of a big service gap it should be
  # report as starting on "today - 12 days" and lasting until
  # service resumes
  def testCurrentServiceGapIsDiscovered(self):
  self.schedule.Validate(today=date(2009, 6, 30),
  service_gap_interval=13)
  exception = self.accumulator.PopException("TooManyDaysWithoutService")
  self.assertEquals(date(2009, 6, 18),
  exception.first_day_without_service)
  self.assertEquals(date(2009, 7, 17),
  exception.last_day_without_service)
 
  self.AssertCommonExceptions(date(2010, 6, 25))
 
  # Asserts the service gaps that appear towards the end of the calendar
  # and which are common to all the tests
  def AssertCommonExceptions(self, last_exception_date):
  exception = self.accumulator.PopException("TooManyDaysWithoutService")
  self.assertEquals(date(2009, 8, 10),
  exception.first_day_without_service)
  self.assertEquals(date(2009, 8, 22),
  exception.last_day_without_service)
 
  exception = self.accumulator.PopException("TooManyDaysWithoutService")
  self.assertEquals(date(2009, 12, 28),
  exception.first_day_without_service)
  self.assertEquals(date(2010, 1, 15),
  exception.last_day_without_service)
 
  if last_exception_date is not None:
  exception = self.accumulator.PopException("TooManyDaysWithoutService")
  self.assertEquals(date(2010, 6, 7),
  exception.first_day_without_service)
  self.assertEquals(last_exception_date,
  exception.last_day_without_service)
 
  self.accumulator.AssertNoMoreExceptions()
 
  class TestGtfsFactory(util.TestCase):
  def setUp(self):
  self._factory = transitfeed.GetGtfsFactory()
 
  def testCanUpdateMapping(self):
  self._factory.UpdateMapping("agency.txt",
  {"required": False,
  "classes": ["Foo"]})
  self._factory.RemoveClass("Agency")
  self._factory.AddClass("Foo", transitfeed.Stop)
  self._factory.UpdateMapping("calendar.txt",
  {"loading_order": -4, "classes": ["Bar"]})
  self._factory.AddClass("Bar", transitfeed.ServicePeriod)
  self.assertFalse(self._factory.IsFileRequired("agency.txt"))
  self.assertFalse(self._factory.IsFileRequired("calendar.txt"))
  self.assertTrue(self._factory.GetLoadingOrder()[0] == "calendar.txt")
  self.assertEqual(self._factory.Foo, transitfeed.Stop)
  self.assertEqual(self._factory.Bar, transitfeed.ServicePeriod)
  self.assertEqual(self._factory.GetGtfsClassByFileName("agency.txt"),
  transitfeed.Stop)
  self.assertFalse(self._factory.IsFileRequired("agency.txt"))
  known_filenames = self._factory.GetKnownFilenames()
  self.assertTrue("agency.txt" in known_filenames)
  self.assertTrue("calendar.txt" in known_filenames)
 
  def testCanAddMapping(self):
  self._factory.AddMapping("newrequiredfile.txt",
  { "required":True, "classes": ["NewRequiredClass"],
  "loading_order": -20})
  self._factory.AddClass("NewRequiredClass", transitfeed.Stop)
  self._factory.AddMapping("newfile.txt",
  { "required": False, "classes": ["NewClass"],
  "loading_order": -10})
  self._factory.AddClass("NewClass", transitfeed.FareAttribute)
  self.assertEqual(self._factory.NewClass, transitfeed.FareAttribute)
  self.assertEqual(self._factory.NewRequiredClass, transitfeed.Stop)
  self.assertTrue(self._factory.IsFileRequired("newrequiredfile.txt"))
  self.assertFalse(self._factory.IsFileRequired("newfile.txt"))
  known_filenames = self._factory.GetKnownFilenames()
  self.assertTrue("newfile.txt" in known_filenames)
  self.assertTrue("newrequiredfile.txt" in known_filenames)
  loading_order = self._factory.GetLoadingOrder()
  self.assertTrue(loading_order[0] == "newrequiredfile.txt")
  self.assertTrue(loading_order[1] == "newfile.txt")
 
  def testThrowsExceptionWhenAddingDuplicateMapping(self):
  self.assertRaises(transitfeed.DuplicateMapping,
  self._factory.AddMapping,
  "agency.txt",
  {"required": True, "classes": ["Stop"],
  "loading_order": -20})
 
  def testThrowsExceptionWhenAddingInvalidMapping(self):
  self.assertRaises(transitfeed.InvalidMapping,
  self._factory.AddMapping,
  "foo.txt",
  {"required": True,
  "loading_order": -20})
 
  def testThrowsExceptionWhenUpdatingNonexistentMapping(self):
  self.assertRaises(transitfeed.NonexistentMapping,
  self._factory.UpdateMapping,
  'doesnotexist.txt',
  {'required': False})
 
 
  def testCanRemoveFileFromLoadingOrder(self):
  self._factory.UpdateMapping("agency.txt",
  {"loading_order": None})
  self.assertTrue("agency.txt" not in self._factory.GetLoadingOrder())
 
  def testCanRemoveMapping(self):
  self._factory.RemoveMapping("agency.txt")
  self.assertFalse("agency.txt" in self._factory.GetKnownFilenames())
  self.assertFalse("agency.txt" in self._factory.GetLoadingOrder())
  self.assertEqual(self._factory.GetGtfsClassByFileName("agency.txt"),
  None)
  self.assertFalse(self._factory.IsFileRequired("agency.txt"))
 
  def testIsFileRequired(self):
  self.assertTrue(self._factory.IsFileRequired("agency.txt"))
  self.assertTrue(self._factory.IsFileRequired("stops.txt"))
  self.assertTrue(self._factory.IsFileRequired("routes.txt"))
  self.assertTrue(self._factory.IsFileRequired("trips.txt"))
  self.assertTrue(self._factory.IsFileRequired("stop_times.txt"))
 
  # We don't have yet a way to specify that one or the other (or both
  # simultaneously) might be provided, so we don't consider them as required
  # for now
  self.assertFalse(self._factory.IsFileRequired("calendar.txt"))
  self.assertFalse(self._factory.IsFileRequired("calendar_dates.txt"))
 
  self.assertFalse(self._factory.IsFileRequired("fare_attributes.txt"))
  self.assertFalse(self._factory.IsFileRequired("fare_rules.txt"))
  self.assertFalse(self._factory.IsFileRequired("shapes.txt"))
  self.assertFalse(self._factory.IsFileRequired("frequencies.txt"))
  self.assertFalse(self._factory.IsFileRequired("transfers.txt"))
 
  def testFactoryReturnsClassesAndNotInstances(self):
  for filename in ("agency.txt", "fare_attributes.txt",
  "fare_rules.txt", "frequencies.txt", "stops.txt", "stop_times.txt",
  "transfers.txt", "routes.txt", "trips.txt"):
  class_object = self._factory.GetGtfsClassByFileName(filename)
  self.assertTrue(isinstance(class_object,
  (types.TypeType, types.ClassType)),
  "The mapping from filenames to classes must return "
  "classes and not instances. This is not the case for " +
  filename)
 
  def testCanFindClassByClassName(self):
  self.assertEqual(transitfeed.Agency, self._factory.Agency)
  self.assertEqual(transitfeed.FareAttribute, self._factory.FareAttribute)
  self.assertEqual(transitfeed.FareRule, self._factory.FareRule)
  self.assertEqual(transitfeed.Frequency, self._factory.Frequency)
  self.assertEqual(transitfeed.Route, self._factory.Route)
  self.assertEqual(transitfeed.ServicePeriod, self._factory.ServicePeriod)
  self.assertEqual(transitfeed.Shape, self._factory.Shape)
  self.assertEqual(transitfeed.ShapePoint, self._factory.ShapePoint)
  self.assertEqual(transitfeed.Stop, self._factory.Stop)
  self.assertEqual(transitfeed.StopTime, self._factory.StopTime)
  self.assertEqual(transitfeed.Transfer, self._factory.Transfer)
  self.assertEqual(transitfeed.Trip, self._factory.Trip)
 
  def testCanFindClassByFileName(self):
  self.assertEqual(transitfeed.Agency,
  self._factory.GetGtfsClassByFileName('agency.txt'))
  self.assertEqual(transitfeed.FareAttribute,
  self._factory.GetGtfsClassByFileName(
  'fare_attributes.txt'))
  self.assertEqual(transitfeed.FareRule,
  self._factory.GetGtfsClassByFileName('fare_rules.txt'))
  self.assertEqual(transitfeed.Frequency,
  self._factory.GetGtfsClassByFileName('frequencies.txt'))
  self.assertEqual(transitfeed.Route,
  self._factory.GetGtfsClassByFileName('routes.txt'))
  self.assertEqual(transitfeed.ServicePeriod,
  self._factory.GetGtfsClassByFileName('calendar.txt'))
  self.assertEqual(transitfeed.ServicePeriod,
  self._factory.GetGtfsClassByFileName('calendar_dates.txt'))
  self.assertEqual(transitfeed.Stop,
  self._factory.GetGtfsClassByFileName('stops.txt'))
  self.assertEqual(transitfeed.StopTime,
  self._factory.GetGtfsClassByFileName('stop_times.txt'))
  self.assertEqual(transitfeed.Transfer,
  self._factory.GetGtfsClassByFileName('transfers.txt'))
  self.assertEqual(transitfeed.Trip,
  self._factory.GetGtfsClassByFileName('trips.txt'))
 
  def testClassFunctionsRaiseExceptions(self):
  self.assertRaises(transitfeed.NonexistentMapping,
  self._factory.RemoveClass,
  "Agenci")
  self.assertRaises(transitfeed.DuplicateMapping,
  self._factory.AddClass,
  "Agency", transitfeed.Agency)
  self.assertRaises(transitfeed.NonStandardMapping,
  self._factory.GetGtfsClassByFileName,
  'shapes.txt')
  self.assertRaises(transitfeed.NonexistentMapping,
  self._factory.UpdateClass,
  "Agenci", transitfeed.Agency)
 
 
  class TestGtfsFactoryUser(util.TestCase):
  def AssertDefaultFactoryIsReturnedIfNoneIsSet(self, instance):
  self.assertTrue(isinstance(instance.GetGtfsFactory(),
  transitfeed.GtfsFactory))
 
  def AssertFactoryIsSavedAndReturned(self, instance, factory):
  instance.SetGtfsFactory(factory)
  self.assertEquals(factory, instance.GetGtfsFactory())
 
  def testClasses(self):
  class FakeGtfsFactory(object):
  pass
 
  factory = transitfeed.GetGtfsFactory()
  gtfs_class_instances = [
  factory.Shape("id"),
  factory.ShapePoint(),
  ]
  gtfs_class_instances += [factory.GetGtfsClassByFileName(filename)() for
  filename in factory.GetLoadingOrder()]
 
  for instance in gtfs_class_instances:
  self.AssertDefaultFactoryIsReturnedIfNoneIsSet(instance)
  self.AssertFactoryIsSavedAndReturned(instance, FakeGtfsFactory())
 
 
  if __name__ == '__main__':
  unittest.main()
 
  #!/usr/bin/python2.4
  #
  # Copyright (C) 2009 Google Inc.
  #
  # Licensed under the Apache License, Version 2.0 (the "License");
  # you may not use this file except in compliance with the License.
  # You may obtain a copy of the License at
  #
  # http://www.apache.org/licenses/LICENSE-2.0
  #
  # Unless required by applicable law or agreed to in writing, software
  # distributed under the License is distributed on an "AS IS" BASIS,
  # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  # See the License for the specific language governing permissions and
  # limitations under the License.
 
  """Tests for unusual_trip_filter.py"""
 
  __author__ = 'Jiri Semecky <jiri.semecky@gmail.com>'
 
  import unusual_trip_filter
  import transitfeed
  import unittest
  import util
 
  class UnusualTripFilterTestCase(util.TempDirTestCaseBase):
  """Test of unusual trip filter functionality."""
 
  def testFilter(self):
  """Test if filtering works properly."""
  expected_values = {
  'CITY1':0, 'CITY2':0, 'CITY3':0, 'CITY4' :0, 'CITY5' :0, 'CITY6' :0,
  'CITY7':0, 'CITY8':0, 'CITY9':0, 'CITY10':0, 'CITY11':1, 'CITY12':1,
  }
  filter = unusual_trip_filter.UnusualTripFilter(0.1, quiet=True)
  input = self.GetPath('test', 'data', 'filter_unusual_trips')
  loader = transitfeed.Loader(input, extra_validation=True)
  schedule = loader.Load()
  filter.filter(schedule)
  for trip_id, expected_trip_type in expected_values.items():
  actual_trip_type = schedule.trips[trip_id]['trip_type']
  try:
  self.assertEquals(int(actual_trip_type), expected_trip_type)
  except ValueError:
  self.assertEquals(actual_trip_type, '')
 
  def testFilterNoForceFilter(self):
  """Test that force==False doesn't set default values"""
  filter = unusual_trip_filter.UnusualTripFilter(0.1, force=False, quiet=True)
  input = self.GetPath('test', 'data', 'filter_unusual_trips')
  loader = transitfeed.Loader(input, extra_validation=True)
  schedule = loader.Load()
  schedule.trips['CITY2'].trip_type = 'odd-trip'
  filter.filter(schedule)
  trip1 = schedule.trips['CITY1']
  self.assertEquals(trip1['trip_type'], '')
  trip2 = schedule.trips['CITY2']
  self.assertEquals(trip2['trip_type'], 'odd-trip')
 
  def testFilterForceFilter(self):
  """Test that force==True does set default values"""
  filter = unusual_trip_filter.UnusualTripFilter(0.1, force=True, quiet=False)
  input = self.GetPath('test', 'data', 'filter_unusual_trips')
  loader = transitfeed.Loader(input, extra_validation=True)
  schedule = loader.Load()
  schedule.trips['CITY2'].trip_type = 'odd-trip'
  filter.filter(schedule)
  trip1 = schedule.trips['CITY1']
  self.assertEquals(trip1['trip_type'], '0')
  trip2 = schedule.trips['CITY2']
  self.assertEquals(trip2['trip_type'], '0')
 
  def testFilterAppliedForSpecifiedRouteType(self):
  """Setting integer route_type filters trips of this route type."""
  filter = unusual_trip_filter.UnusualTripFilter(0.1, quiet=True,
  route_type=3)
  input = self.GetPath('test', 'data', 'filter_unusual_trips')
  loader = transitfeed.Loader(input, extra_validation=True)
  schedule = loader.Load()
  filter.filter(schedule)
  actual_trip_type = schedule.trips['CITY11']['trip_type']
  self.assertEquals(actual_trip_type, '1')
 
  def testFilterNotAppliedForUnspecifiedRouteType(self):
  """Setting integer route_type filters trips of this route type."""
  filter = unusual_trip_filter.UnusualTripFilter(0.1, quiet=True,
  route_type=2)
  input = self.GetPath('test', 'data', 'filter_unusual_trips')
  loader = transitfeed.Loader(input, extra_validation=True)
  schedule = loader.Load()
  filter.filter(schedule)
  actual_trip_type = schedule.trips['CITY11']['trip_type']
  self.assertEquals(actual_trip_type, '')
 
  def testFilterAppliedForRouteTypeSpecifiedByName(self):
  """Setting integer route_type filters trips of this route type."""
  filter = unusual_trip_filter.UnusualTripFilter(0.1, quiet=True,
  route_type='Bus')
  input = self.GetPath('test', 'data', 'filter_unusual_trips')
  loader = transitfeed.Loader(input, extra_validation=True)
  schedule = loader.Load()
  filter.filter(schedule)
  actual_trip_type = schedule.trips['CITY11']['trip_type']
  self.assertEquals(actual_trip_type, '1')
 
  def testFilterNotAppliedForDifferentRouteTypeSpecifiedByName(self):
  """Setting integer route_type filters trips of this route type."""
  filter = unusual_trip_filter.UnusualTripFilter(0.1, quiet=True,
  route_type='Ferry')
  input = self.GetPath('test', 'data', 'filter_unusual_trips')
  loader = transitfeed.Loader(input, extra_validation=True)
  schedule = loader.Load()
  filter.filter(schedule)
  actual_trip_type = schedule.trips['CITY11']['trip_type']
  self.assertEquals(actual_trip_type, '')
 
  if __name__ == '__main__':
  unittest.main()
 
  #!/usr/bin/python2.5
 
  # Copyright (C) 2007 Google Inc.
  #
  # Licensed under the Apache License, Version 2.0 (the "License");
  # you may not use this file except in compliance with the License.
  # You may obtain a copy of the License at
  #
  # http://www.apache.org/licenses/LICENSE-2.0
  #
  # Unless required by applicable law or agreed to in writing, software
  # distributed under the License is distributed on an "AS IS" BASIS,
  # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  # See the License for the specific language governing permissions and
  # limitations under the License.
 
  # Code shared between tests.
 
  import os
  import os.path
  import re
  import cStringIO as StringIO
  import shutil
  import subprocess
  import sys
  import tempfile
  import traceback
  import transitfeed
  import unittest
  import zipfile
 
 
  def check_call(cmd, expected_retcode=0, stdin_str="", **kwargs):
  """Convenience function that is in the docs for subprocess but not
  installed on my system. Raises an Exception if the return code is not
  expected_retcode. Returns a tuple of strings, (stdout, stderr)."""
  try:
  if 'stdout' in kwargs or 'stderr' in kwargs or 'stdin' in kwargs:
  raise Exception("Don't pass stdout or stderr")
  p = subprocess.Popen(cmd, stdout=subprocess.PIPE,
  stderr=subprocess.PIPE, stdin=subprocess.PIPE,
  **kwargs)
  (out, err) = p.communicate(stdin_str)
  retcode = p.returncode
  except Exception, e:
  raise Exception("When running %s: %s" % (cmd, e))
  if retcode < 0:
  raise Exception(
  "Child '%s' was terminated by signal %d. Output:\n%s\n%s\n" %
  (cmd, -retcode, out, err))
  elif retcode != expected_retcode:
  raise Exception(
  "Child '%s' returned %d. Output:\n%s\n%s\n" %
  (cmd, retcode, out, err))
  return (out, err)
 
 
  class TestCase(unittest.TestCase):
  """Base of every TestCase class in this project.
 
  This adds some methods that perhaps should be in unittest.TestCase.
  """
  # Note from Tom, Dec 9 2009: Be careful about adding setUp or tearDown
  # because they will be run a few hundred times.
 
  def assertMatchesRegex(self, regex, string):
  """Assert that regex is found in string."""
  if not re.search(regex, string):
  self.fail("string %r did not match regex %r" % (string, regex))
 
 
  class GetPathTestCase(TestCase):
  """TestCase with method to get paths to files in the distribution."""
  def setUp(self):
  super(GetPathTestCase, self).setUp()
  self._origcwd = os.getcwd()
 
  def GetExamplePath(self, name):
  """Return the full path of a file in the examples directory"""
  return self.GetPath('examples', name)
 
  def GetTestDataPath(self, *path):
  """Return the full path of a file in the test/data directory"""
  return self.GetPath('test', 'data', *path)
 
  def GetPath(self, *path):
  """Return absolute path of path. path is relative main source directory."""
  here = os.path.dirname(__file__) # Relative to _origcwd
  return os.path.join(self._origcwd, here, '..', *path)
 
 
  class TempDirTestCaseBase(GetPathTestCase):
  """Make a temporary directory the current directory before running the test
  and remove it after the test.
  """
  def setUp(self):
  GetPathTestCase.setUp(self)
  self.tempdirpath = tempfile.mkdtemp()
  os.chdir(self.tempdirpath)
 
  def tearDown(self):
  os.chdir(self._origcwd)
  shutil.rmtree(self.tempdirpath)
  GetPathTestCase.tearDown(self)
 
  def CheckCallWithPath(self, cmd, expected_retcode=0, stdin_str=""):
  """Run python script cmd[0] with args cmd[1:], making sure 'import
  transitfeed' will use the module in this source tree. Raises an Exception
  if the return code is not expected_retcode. Returns a tuple of strings,
  (stdout, stderr)."""
  tf_path = transitfeed.__file__
  # Path of the directory containing transitfeed. When this is added to
  # sys.path importing transitfeed should work independent of if
  # transitfeed.__file__ is <parent>/transitfeed.py or
  # <parent>/transitfeed/__init__.py
  transitfeed_parent = tf_path[:tf_path.rfind("transitfeed")]
  transitfeed_parent = transitfeed_parent.replace("\\", "/").rstrip("/")
  script_path = cmd[0].replace("\\", "/")
  script_args = cmd[1:]
 
  # Propogate sys.path of this process to the subprocess. This is done
  # because I assume that if this process has a customized sys.path it is
  # meant to be used for all processes involved in the tests. The downside
  # of this is that the subprocess is no longer a clean version of what you
  # get when running "python" after installing transitfeed. Hopefully if this
  # process uses a customized sys.path you know what you are doing.
  env = {"PYTHONPATH": ":".join(sys.path)}
 
  # Instead of directly running the script make sure that the transitfeed
  # module in this source directory is at the front of sys.path. Then
  # adjust sys.argv so it looks like the script was run directly. This lets
  # OptionParser use the correct value for %proj.
  cmd = [sys.executable, "-c",
  "import sys; "
  "sys.path.insert(0,'%s'); "
  "sys.argv = ['%s'] + sys.argv[1:]; "
  "exec(open('%s'))" %
  (transitfeed_parent, script_path, script_path)] + script_args
  return check_call(cmd, expected_retcode=expected_retcode, shell=False,
  env=env, stdin_str=stdin_str)
 
  def ConvertZipToDict(self, zip):
  """Converts a zip file into a dictionary.
 
  Arguments:
  zip: The zipfile whose contents are to be converted to a dictionary.
 
  Returns:
  A dictionary mapping filenames to file contents."""
 
  zip_dict = {}
  for archive_name in zip.namelist():
  zip_dict[archive_name] = zip.read(archive_name)
  zip.close()
  return zip_dict
 
  def ConvertDictToZip(self, dict):
  """Converts a dictionary to an in-memory zipfile.
 
  Arguments:
  dict: A dictionary mapping file names to file contents
 
  Returns:
  The new file's in-memory contents as a file-like object."""
  zipfile_mem = StringIO.StringIO()
  zip = zipfile.ZipFile(zipfile_mem, 'a')
  for arcname, contents in dict.items():
  zip.writestr(arcname, contents)
  zip.close()
  return zipfile_mem
 
  #TODO(anog): Revisit this after we implement proper per-exception level change
  class RecordingProblemAccumulator(transitfeed.ProblemAccumulatorInterface):
  """Save all problems for later inspection.
 
  Args:
  test_case: a unittest.TestCase object on which to report problems
  ignore_types: sequence of string type names that will be ignored by the
  ProblemAccumulator"""
  def __init__(self, test_case, ignore_types=None):
  self.exceptions = []
  self._test_case = test_case
  self._ignore_types = ignore_types or set()
 
  def _Report(self, e):
  # Ensure that these don't crash
  e.FormatProblem()
  e.FormatContext()
  if e.__class__.__name__ in self._ignore_types:
  return
  # Keep the 7 nearest stack frames. This should be enough to identify
  # the code path that created the exception while trimming off most of the
  # large test framework's stack.
  traceback_list = traceback.format_list(traceback.extract_stack()[-7:-1])
  self.exceptions.append((e, ''.join(traceback_list)))
 
  def PopException(self, type_name):
  """Return the first exception, which must be a type_name."""
  e = self.exceptions.pop(0)
  e_name = e[0].__class__.__name__
  self._test_case.assertEqual(e_name, type_name,
  "%s != %s\n%s" %
  (e_name, type_name, self.FormatException(*e)))
  return e[0]
 
  def FormatException(self, exce, tb):
  return ("%s\nwith gtfs file context %s\nand traceback\n%s" %
  (exce.FormatProblem(), exce.FormatContext(), tb))
 
  def TearDownAssertNoMoreExceptions(self):
  """Assert that there are no unexpected problems left after a test has run.
 
  This function should be called on a test's tearDown. For more information
  please see AssertNoMoreExceptions"""
  assert len(self.exceptions) == 0, \
  "see util.RecordingProblemAccumulator.AssertNoMoreExceptions"
 
  def AssertNoMoreExceptions(self):
  """Check that no unexpected problems were reported.
 
  Every test that uses a RecordingProblemReporter should end with a call to
  this method. If setUp creates a RecordingProblemReporter it is good for
  tearDown to double check that the exceptions list was emptied.
  """
  exceptions_as_text = []
  for e, tb in self.exceptions:
  exceptions_as_text.append(self.FormatException(e, tb))
  # If the assertFalse below fails the test will abort and tearDown is
  # called. Some tearDown methods assert that self.exceptions is empty as
  # protection against a test that doesn't end with AssertNoMoreExceptions
  # and has exceptions remaining in the RecordingProblemReporter. It would
  # be nice to trigger a normal test failure in tearDown but the idea was
  # rejected (http://bugs.python.org/issue5531).
  self.exceptions = []
  self._test_case.assertFalse(exceptions_as_text,
  "\n".join(exceptions_as_text))
 
  def PopInvalidValue(self, column_name, file_name=None):
  e = self.PopException("InvalidValue")
  self._test_case.assertEquals(column_name, e.column_name)
  if file_name:
  self._test_case.assertEquals(file_name, e.file_name)
  return e
 
  def PopMissingValue(self, column_name, file_name=None):
  e = self.PopException("MissingValue")
  self._test_case.assertEquals(column_name, e.column_name)
  if file_name:
  self._test_case.assertEquals(file_name, e.file_name)
  return e
 
  def PopDuplicateColumn(self, file_name, header, count):
  e = self.PopException("DuplicateColumn")
  self._test_case.assertEquals(file_name, e.file_name)
  self._test_case.assertEquals(header, e.header)
  self._test_case.assertEquals(count, e.count)
  return e
 
  #!/usr/bin/python2.5
 
  # Copyright (C) 2007 Google Inc.
  #
  # Licensed under the Apache License, Version 2.0 (the "License");
  # you may not use this file except in compliance with the License.
  # You may obtain a copy of the License at
  #
  # http://www.apache.org/licenses/LICENSE-2.0
  #
  # Unless required by applicable law or agreed to in writing, software
  # distributed under the License is distributed on an "AS IS" BASIS,
  # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  # See the License for the specific language governing permissions and
  # limitations under the License.
 
  """This module is a library to help you create, read and write Google
  Transit Feed files. Refer to the feed specification, available at
  http://code.google.com/transit/spec/transit_feed_specification.htm, for a
  complete description how the transit feed represents a transit schedule. This
  library supports all required parts of the specification but does not yet
  support all optional parts. Patches welcome!
 
  Before transitfeed version 1.2.4 all our library code was distributed in a
  one file module, transitfeed.py, and could be used as
 
  import transitfeed
  schedule = transitfeed.Schedule()
 
  At that time the module (one file, transitfeed.py) was converted into a
  package (a directory named transitfeed containing __init__.py and multiple .py
  files). Classes and attributes exposed by the old module may still be imported
  in the same way. Indeed, code that depends on the library <em>should</em>
  continue to use import commands such as the above and ignore _transitfeed.
 
  To import the transitfeed module you should do something like:
 
  import transitfeed
  schedule = transitfeed.Schedule()
  ...
 
  The specification describes several tables such as stops, routes and trips.
  In a feed file these are stored as comma separeted value files. This library
  represents each row of these tables with a single Python object. This object has
  attributes for each value on the row. For example, schedule.AddStop returns a
  Stop object which has attributes such as stop_lat and stop_name.
 
  Schedule: Central object of the parser
  GenericGTFSObject: A base class for each of the objects below
  Route: Represents a single route
  Trip: Represents a single trip
  Stop: Represents a single stop
  ServicePeriod: Represents a single service, a set of dates
  Agency: Represents the agency in this feed
  Transfer: Represents a single transfer rule
  TimeToSecondsSinceMidnight(): Convert HH:MM:SS into seconds since midnight.
  FormatSecondsSinceMidnight(s): Formats number of seconds past midnight into a string
  """
 
  # util needs to be imported before problems because otherwise the loading order
  # of this module is Agency -> Problems -> Util -> Trip and trip tries to
  # use problems.default_problem_reporter as a default argument (which fails
  # because problems.py isn't fully loaded yet). Loading util first solves this as
  # problems.py gets fully loaded right away.
  # TODO: Solve this problem cleanly
  from util import *
  from agency import *
  from fareattribute import *
  from farerule import *
  from frequency import *
  from gtfsfactory import *
  from gtfsfactoryuser import *
  from gtfsobjectbase import *
  from loader import *
  from problems import *
  from route import *
  from schedule import *
  from serviceperiod import *
  from shape import *
  from shapelib import *
  from shapeloader import *
  from shapepoint import *
  from stop import *
  from stoptime import *
  from transfer import *
  from trip import *
 
  __version__ = '1.2.6'
 
  #!/usr/bin/python2.5
 
  # Copyright (C) 2007 Google Inc.
  #
  # Licensed under the Apache License, Version 2.0 (the "License");
  # you may not use this file except in compliance with the License.
  # You may obtain a copy of the License at
  #
  # http://www.apache.org/licenses/LICENSE-2.0
  #
  # Unless required by applicable law or agreed to in writing, software
  # distributed under the License is distributed on an "AS IS" BASIS,
  # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  # See the License for the specific language governing permissions and
  # limitations under the License.
 
  from gtfsobjectbase import GtfsObjectBase
  from problems import default_problem_reporter
  import util
 
  class Agency(GtfsObjectBase):
  """Represents an agency in a schedule.
 
  Callers may assign arbitrary values to instance attributes. __init__ makes no
  attempt at validating the attributes. Call Validate() to check that
  attributes are valid and the agency object is consistent with itself.
 
  Attributes:
  All attributes are strings.
  """
  _REQUIRED_FIELD_NAMES = ['agency_name', 'agency_url', 'agency_timezone']
  _FIELD_NAMES = _REQUIRED_FIELD_NAMES + ['agency_id', 'agency_lang',
  'agency_phone']
  _TABLE_NAME = 'agency'
 
  def __init__(self, name=None, url=None, timezone=None, id=None,
  field_dict=None, lang=None, **kwargs):
  """Initialize a new Agency object.
 
  Args:
  field_dict: A dictionary mapping attribute name to unicode string
  name: a string, ignored when field_dict is present
  url: a string, ignored when field_dict is present
  timezone: a string, ignored when field_dict is present
  id: a string, ignored when field_dict is present
  kwargs: arbitrary keyword arguments may be used to add attributes to the
  new object, ignored when field_dict is present
  """
  self._schedule = None
 
  if not field_dict:
  if name:
  kwargs['agency_name'] = name
  if url:
  kwargs['agency_url'] = url
  if timezone:
  kwargs['agency_timezone'] = timezone
  if id:
  kwargs['agency_id'] = id
  if lang:
  kwargs['agency_lang'] = lang
  field_dict = kwargs
 
  self.__dict__.update(field_dict)
 
  def ValidateRequiredFieldNames(self, problems):
  for required in self._REQUIRED_FIELD_NAMES:
  if util.IsEmpty(getattr(self, required, None)):
  problems.MissingValue(required)
  return True
  return False
 
  def ValidateAgencyUrl(self, problems):
  if self.agency_url and not util.IsValidURL(self.agency_url):
  problems.InvalidValue('agency_url', self.agency_url)
  return True
  return False
 
  def ValidateAgencyLang(self, problems):
  if (not util.IsEmpty(self.agency_lang) and
  self.agency_lang.lower() not in ISO639.codes_2letter):
  problems.InvalidValue('agency_lang', self.agency_lang)
  return True
  return False
 
  def ValidateAgencyTimezone(self, problems):
  try:
  import pytz
  if self.agency_timezone not in pytz.common_timezones:
  problems.InvalidValue(
  'agency_timezone',
  self.agency_timezone,
  '"%s" is not a common timezone name according to pytz version %s' %
  (self.agency_timezone, pytz.VERSION))
  return True
  except ImportError: # no pytz
  print ("Timezone not checked "
  "(install pytz package for timezone validation)")
  return False
 
  def Validate(self, problems=default_problem_reporter):
  """Validate attribute values and this object's internal consistency.
 
  Returns:
  True iff all validation checks passed.
  """
  found_problem = False
  found_problem = self.ValidateRequiredFieldNames(problems) or found_problem
  found_problem = self.ValidateAgencyUrl(problems) or found_problem
  found_problem = self.ValidateAgencyLang(problems) or found_problem
  found_problem = self.ValidateAgencyTimezone(problems) or found_problem
 
  return not found_problem
 
  def ValidateBeforeAdd(self, problems):
  return True
 
  def ValidateAfterAdd(self, problems):
  self.Validate(problems)
 
  def AddToSchedule(self, schedule, problems):
  schedule.AddAgencyObject(self, problems)
 
  class ISO639(object):
  # Set of all the 2-letter ISO 639-1 language codes.
  codes_2letter = set([
  'aa', 'ab', 'ae', 'af', 'ak', 'am', 'an', 'ar', 'as', 'av', 'ay', 'az',
  'ba', 'be', 'bg', 'bh', 'bi', 'bm', 'bn', 'bo', 'br', 'bs', 'ca', 'ce',
  'ch', 'co', 'cr', 'cs', 'cu', 'cv', 'cy', 'da', 'de', 'dv', 'dz', 'ee',
  'el', 'en', 'eo', 'es', 'et', 'eu', 'fa', 'ff', 'fi', 'fj', 'fo', 'fr',
  'fy', 'ga', 'gd', 'gl', 'gn', 'gu', 'gv', 'ha', 'he', 'hi', 'ho', 'hr',
  'ht', 'hu', 'hy', 'hz', 'ia', 'id', 'ie', 'ig', 'ii', 'ik', 'io', 'is',
  'it', 'iu', 'ja', 'jv', 'ka', 'kg', 'ki', 'kj', 'kk', 'kl', 'km', 'kn',
  'ko', 'kr', 'ks', 'ku', 'kv', 'kw', 'ky', 'la', 'lb', 'lg', 'li', 'ln',
  'lo', 'lt', 'lu', 'lv', 'mg', 'mh', 'mi', 'mk', 'ml', 'mn', 'mo', 'mr',
  'ms', 'mt', 'my', 'na', 'nb', 'nd', 'ne', 'ng', 'nl', 'nn', 'no', 'nr',
  'nv', 'ny', 'oc', 'oj', 'om', 'or', 'os', 'pa', 'pi', 'pl', 'ps', 'pt',
  'qu', 'rm', 'rn', 'ro', 'ru', 'rw', 'sa', 'sc', 'sd', 'se', 'sg', 'si',
  'sk', 'sl', 'sm', 'sn', 'so', 'sq', 'sr', 'ss', 'st', 'su', 'sv', 'sw',
  'ta', 'te', 'tg', 'th', 'ti', 'tk', 'tl', 'tn', 'to', 'tr', 'ts', 'tt',
  'tw', 'ty', 'ug', 'uk', 'ur', 'uz', 've', 'vi', 'vo', 'wa', 'wo', 'xh',
  'yi', 'yo', 'za', 'zh', 'zu',
  ])
 
  #!/usr/bin/python2.5
 
  # Copyright (C) 2007 Google Inc.
  #
  # Licensed under the Apache License, Version 2.0 (the "License");
  # you may not use this file except in compliance with the License.
  # You may obtain a copy of the License at
  #
  # http://www.apache.org/licenses/LICENSE-2.0
  #
  # Unless required by applicable law or agreed to in writing, software
  # distributed under the License is distributed on an "AS IS" BASIS,
  # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  # See the License for the specific language governing permissions and
  # limitations under the License.
 
  from gtfsobjectbase import GtfsObjectBase
  from problems import default_problem_reporter
  import util
 
  class FareAttribute(GtfsObjectBase):
  """Represents a fare type."""
  _REQUIRED_FIELD_NAMES = ['fare_id', 'price', 'currency_type',
  'payment_method', 'transfers']
  _FIELD_NAMES = _REQUIRED_FIELD_NAMES + ['transfer_duration']
  _TABLE_NAME = "fare_attributes"
 
  def __init__(self,
  fare_id=None, price=None, currency_type=None,
  payment_method=None, transfers=None, transfer_duration=None,
  field_dict=None):
  self._schedule = None
  (self.fare_id, self.price, self.currency_type, self.payment_method,
  self.transfers, self.transfer_duration) = \
  (fare_id, price, currency_type, payment_method,
  transfers, transfer_duration)
 
  if field_dict:
  if isinstance(field_dict, FareAttribute):
  # Special case so that we don't need to re-parse the attributes to
  # native types iteritems returns all attributes that don't start with _
  for k, v in field_dict.iteritems():
  self.__dict__[k] = v
  else:
  self.__dict__.update(field_dict)
  self.rules = []
 
  try:
  self.price = float(self.price)
  except (TypeError, ValueError):
  pass
  try:
  self.payment_method = int(self.payment_method)
  except (TypeError, ValueError):
  pass
  if self.transfers == None or self.transfers == "":
  self.transfers = None
  else:
  try:
  self.transfers = int(self.transfers)
  except (TypeError, ValueError):
  pass
  if self.transfer_duration == None or self.transfer_duration == "":
  self.transfer_duration = None
  else:
  try:
  self.transfer_duration = int(self.transfer_duration)
  except (TypeError, ValueError):
  pass
 
  def GetFareRuleList(self):
  return self.rules
 
  def ClearFareRules(self):
  self.rules = []
 
  def GetFieldValuesTuple(self):
  return [getattr(self, fn) for fn in self._FIELD_NAMES]
 
  def __getitem__(self, name):
  return getattr(self, name)
 
  def __eq__(self, other):
  if not other:
  return False
 
  if id(self) == id(other):
  return True
 
  if self.GetFieldValuesTuple() != other.GetFieldValuesTuple():
  return False
 
  self_rules = [r.GetFieldValuesTuple() for r in self.GetFareRuleList()]
  self_rules.sort()
  other_rules = [r.GetFieldValuesTuple() for r in other.GetFareRuleList()]
  other_rules.sort()
  return self_rules == other_rules
 
  def __ne__(self, other):
  return not self.__eq__(other)
 
  def ValidateFareId(self, problems):
  if util.IsEmpty(self.fare_id):
  problems.MissingValue("fare_id")
 
  def ValidatePrice(self, problems):
  if self.price == None:
  problems.MissingValue("price")
  elif not isinstance(self.price, float) and not isinstance(self.price, int):
  problems.InvalidValue("price", self.price)
  elif self.price < 0:
  problems.InvalidValue("price", self.price)
 
  def ValidateCurrencyType(self, problems):
  if util.IsEmpty(self.currency_type):
  problems.MissingValue("currency_type")
  elif self.currency_type not in ISO4217.codes:
  problems.InvalidValue("currency_type", self.currency_type)
 
  def ValidatePaymentMethod(self, problems):
  if self.payment_method == "" or self.payment_method == None:
  problems.MissingValue("payment_method")
  elif (not isinstance(self.payment_method, int) or
  self.payment_method not in range(0, 2)):
  problems.InvalidValue("payment_method", self.payment_method)
 
  def ValidateTransfers(self, problems):
  if not ((self.transfers == None) or
  (isinstance(self.transfers, int) and
  self.transfers in range(0, 3))):
  problems.InvalidValue("transfers", self.transfers)
 
  def ValidateTransferDuration(self, problems):
  if ((self.transfer_duration != None) and
  not isinstance(self.transfer_duration, int)):
  problems.InvalidValue("transfer_duration", self.transfer_duration)
  if self.transfer_duration and (self.transfer_duration < 0):
  problems.InvalidValue("transfer_duration", self.transfer_duration)
 
  def Validate(self, problems=default_problem_reporter):
  self.ValidateFareId(problems)
  self.ValidatePrice(problems)
  self.ValidateCurrencyType(problems)
  self.ValidatePaymentMethod(problems)
  self.ValidateTransfers(problems)
  self.ValidateTransferDuration(problems)
 
  def ValidateBeforeAdd(self, problems):
  return True
 
  def ValidateAfterAdd(self, problems):
  return
 
  def AddToSchedule(self, schedule=None, problems=None):
  if schedule:
  schedule.AddFareAttributeObject(self, problems)
 
  # TODO: move these into a separate file
  class ISO4217(object):
  """Represents the set of currencies recognized by the ISO-4217 spec."""
  codes = { # map of alpha code to numerical code
  'AED': 784, 'AFN': 971, 'ALL': 8, 'AMD': 51, 'ANG': 532, 'AOA': 973,
  'ARS': 32, 'AUD': 36, 'AWG': 533, 'AZN': 944, 'BAM': 977, 'BBD': 52,
  'BDT': 50, 'BGN': 975, 'BHD': 48, 'BIF': 108, 'BMD': 60, 'BND': 96,
  'BOB': 68, 'BOV': 984, 'BRL': 986, 'BSD': 44, 'BTN': 64, 'BWP': 72,
  'BYR': 974, 'BZD': 84, 'CAD': 124, 'CDF': 976, 'CHE': 947, 'CHF': 756,
  'CHW': 948, 'CLF': 990, 'CLP': 152, 'CNY': 156, 'COP': 170, 'COU': 970,
  'CRC': 188, 'CUP': 192, 'CVE': 132, 'CYP': 196, 'CZK': 203, 'DJF': 262,
  'DKK': 208, 'DOP': 214, 'DZD': 12, 'EEK': 233, 'EGP': 818, 'ERN': 232,
  'ETB': 230, 'EUR': 978, 'FJD': 242, 'FKP': 238, 'GBP': 826, 'GEL': 981,
  'GHC': 288, 'GIP': 292, 'GMD': 270, 'GNF': 324, 'GTQ': 320, 'GYD': 328,
  'HKD': 344, 'HNL': 340, 'HRK': 191, 'HTG': 332, 'HUF': 348, 'IDR': 360,
  'ILS': 376, 'INR': 356, 'IQD': 368, 'IRR': 364, 'ISK': 352, 'JMD': 388,
  'JOD': 400, 'JPY': 392, 'KES': 404, 'KGS': 417, 'KHR': 116, 'KMF': 174,
  'KPW': 408, 'KRW': 410, 'KWD': 414, 'KYD': 136, 'KZT': 398, 'LAK': 418,
  'LBP': 422, 'LKR': 144, 'LRD': 430, 'LSL': 426, 'LTL': 440, 'LVL': 428,
  'LYD': 434, 'MAD': 504, 'MDL': 498, 'MGA': 969, 'MKD': 807, 'MMK': 104,
  'MNT': 496, 'MOP': 446, 'MRO': 478, 'MTL': 470, 'MUR': 480, 'MVR': 462,
  'MWK': 454, 'MXN': 484, 'MXV': 979, 'MYR': 458, 'MZN': 943, 'NAD': 516,
  'NGN': 566, 'NIO': 558, 'NOK': 578, 'NPR': 524, 'NZD': 554, 'OMR': 512,
  'PAB': 590, 'PEN': 604, 'PGK': 598, 'PHP': 608, 'PKR': 586, 'PLN': 985,
  'PYG': 600, 'QAR': 634, 'ROL': 642, 'RON': 946, 'RSD': 941, 'RUB': 643,
  'RWF': 646, 'SAR': 682, 'SBD': 90, 'SCR': 690, 'SDD': 736, 'SDG': 938,
  'SEK': 752, 'SGD': 702, 'SHP': 654, 'SKK': 703, 'SLL': 694, 'SOS': 706,
  'SRD': 968, 'STD': 678, 'SYP': 760, 'SZL': 748, 'THB': 764, 'TJS': 972,
  'TMM': 795, 'TND': 788, 'TOP': 776, 'TRY': 949, 'TTD': 780, 'TWD': 901,
  'TZS': 834, 'UAH': 980, 'UGX': 800, 'USD': 840, 'USN': 997, 'USS': 998,
  'UYU': 858, 'UZS': 860, 'VEB': 862, 'VND': 704, 'VUV': 548, 'WST': 882,
  'XAF': 950, 'XAG': 961, 'XAU': 959, 'XBA': 955, 'XBB': 956, 'XBC': 957,
  'XBD': 958, 'XCD': 951, 'XDR': 960, 'XFO': None, 'XFU': None, 'XOF': 952,
  'XPD': 964, 'XPF': 953, 'XPT': 962, 'XTS': 963, 'XXX': 999, 'YER': 886,
  'ZAR': 710, 'ZMK': 894, 'ZWD': 716,
  }
 
  #!/usr/bin/python2.5
 
  # Copyright (C) 2007 Google Inc.
  #
  # Licensed under the Apache License, Version 2.0 (the "License");
  # you may not use this file except in compliance with the License.
  # You may obtain a copy of the License at
  #
  # http://www.apache.org/licenses/LICENSE-2.0
  #
  # Unless required by applicable law or agreed to in writing, software
  # distributed under the License is distributed on an "AS IS" BASIS,
  # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  # See the License for the specific language governing permissions and
  # limitations under the License.
 
  from problems import default_problem_reporter
  from gtfsobjectbase import GtfsObjectBase
 
  class FareRule(GtfsObjectBase):
  """This class represents a rule that determines which itineraries a
  fare rule applies to."""
  _REQUIRED_FIELD_NAMES = ['fare_id']
  _FIELD_NAMES = _REQUIRED_FIELD_NAMES + ['route_id',
  'origin_id',
  'destination_id',
  'contains_id']
  _TABLE_NAME = "fare_rules"
 
  def __init__(self, fare_id=None, route_id=None,
  origin_id=None, destination_id=None, contains_id=None,
  field_dict=None):
  self._schedule = None
  (self.fare_id, self.route_id, self.origin_id, self.destination_id,
  self.contains_id) = \
  (fare_id, route_id, origin_id, destination_id, contains_id)
  if field_dict:
  if isinstance(field_dict, self.GetGtfsFactory().FareRule):
  # Special case so that we don't need to re-parse the attributes to
  # native types iteritems returns all attributes that don't start with _
  for k, v in field_dict.iteritems():
  self.__dict__[k] = v
  else:
  self.__dict__.update(field_dict)
 
  # canonicalize non-content values as None
  if not self.route_id:
  self.route_id = None
  if not self.origin_id:
  self.origin_id = None
  if not self.destination_id:
  self.destination_id = None
  if not self.contains_id:
  self.contains_id = None
 
  def GetFieldValuesTuple(self):
  return [getattr(self, fn) for fn in self._FIELD_NAMES]
 
  def __getitem__(self, name):
  return getattr(self, name)
 
  def __eq__(self, other):
  if not other:
  return False
 
  if id(self) == id(other):
  return True
 
  return self.GetFieldValuesTuple() == other.GetFieldValuesTuple()
 
  def __ne__(self, other):
  return not self.__eq__(other)
 
  def AddToSchedule(self, schedule, problems):
  self._schedule = schedule
  schedule.AddFareRuleObject(self, problems)
 
  def ValidateBeforeAdd(self, problems):
  return True
 
  def ValidateAfterAdd(self, problems):
  return
 
  #!/usr/bin/python2.5
 
  # Copyright (C) 2010 Google Inc.
  #
  # Licensed under the Apache License, Version 2.0 (the "License");
  # you may not use this file except in compliance with the License.
  # You may obtain a copy of the License at
  #
  # http://www.apache.org/licenses/LICENSE-2.0
  #
  # Unless required by applicable law or agreed to in writing, software
  # distributed under the License is distributed on an "AS IS" BASIS,
  # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  # See the License for the specific language governing permissions and
  # limitations under the License.
 
  from gtfsobjectbase import GtfsObjectBase
 
  class Frequency(GtfsObjectBase):
  """This class represents a period of a trip during which the vehicle travels
  at regular intervals (rather than specifying exact times for each stop)."""
 
  _REQUIRED_FIELD_NAMES = ['trip_id', 'start_time', 'end_time',
  'headway_secs']
  _FIELD_NAMES = _REQUIRED_FIELD_NAMES
  _TABLE_NAME = "frequencies"
 
  def __init__(self, field_dict=None):
  self._schedule = None
  if not field_dict:
  return
  self._trip_id = field_dict['trip_id']
  self._start_time = field_dict['start_time']
  self._end_time = field_dict['end_time']
  self._headway_secs = field_dict['headway_secs']
 
  def StartTime(self):
  return self._start_time
 
  def EndTime(self):
  return self._end_time
 
  def TripId(self):
  return self._trip_id
 
  def HeadwaySecs(self):
  return self._headway_secs
 
  def ValidateBeforeAdd(self, problems):
  return True
 
  def ValidateAfterAdd(self, problems):
  return
 
  def Validate(self, problems=None):
  return
 
  def AddToSchedule(self, schedule=None, problems=None):
  if schedule is None:
  return
  self._schedule = schedule
  try:
  trip = schedule.GetTrip(self._trip_id)
  except KeyError:
  problems.InvalidValue('trip_id', self._trip_id)
  return
  trip.AddFrequencyObject(self, problems)
 
  #!/usr/bin/python2.5
 
  # Copyright (C) 2010 Google Inc.
  #
  # Licensed under the Apache License, Version 2.0 (the "License");
  # you may not use this file except in compliance with the License.
  # You may obtain a copy of the License at
  #
  # http://www.apache.org/licenses/LICENSE-2.0
  #
  # Unless required by applicable law or agreed to in writing, software
  # distributed under the License is distributed on an "AS IS" BASIS,
  # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  # See the License for the specific language governing permissions and
  # limitations under the License.
 
  from agency import Agency
  from fareattribute import FareAttribute
  from farerule import FareRule
  from frequency import Frequency
  import loader
  import problems
  from route import Route
  import schedule
  from serviceperiod import ServicePeriod
  from shape import Shape
  from shapepoint import ShapePoint
  from stop import Stop
  from stoptime import StopTime
  from transfer import Transfer
  from trip import Trip
 
  class GtfsFactory(object):
  """A factory for the default GTFS objects"""
 
  _REQUIRED_MAPPING_FIELDS = ['classes', 'required', 'loading_order']
 
  def __init__(self):
 
  self._class_mapping = {
  'Agency': Agency,
  'ServicePeriod': ServicePeriod,
  'FareAttribute': FareAttribute,
  'FareRule': FareRule,
  'Frequency': Frequency,
  'Shape': Shape,
  'ShapePoint': ShapePoint,
  'Stop': Stop,
  'StopTime': StopTime,
  'Route': Route,
  'Transfer': Transfer,
  'Trip': Trip,
  }
 
  self._file_mapping = {
  'agency.txt': { 'required': True, 'loading_order': 0,
  'classes': ['Agency'] },
 
  'calendar.txt': { 'required': False, 'loading_order': None,
  'classes': ['ServicePeriod']},
 
  'calendar_dates.txt': { 'required': False, 'loading_order': None,
  'classes': ['ServicePeriod']},
 
  'fare_attributes.txt': { 'required': False, 'loading_order': 50,
  'classes': ['FareAttribute']},
 
  'fare_rules.txt': { 'required': False, 'loading_order': 60,
  'classes': ['FareRule']},
 
  'frequencies.txt': { 'required': False, 'loading_order': 70,
  'classes': ['Frequency']},
 
  'shapes.txt': { 'required': False, 'loading_order': None,
  'classes': ['Shape', 'ShapePoint']},
 
  'stops.txt': { 'required': True, 'loading_order': 10,
  'classes': ['Stop']},
 
  'stop_times.txt': { 'required': True, 'loading_order': None,
  'classes': ['StopTime']},
 
  'routes.txt': { 'required': True, 'loading_order': 20,
  'classes': ['Route']},
 
  'transfers.txt': { 'required': False, 'loading_order': 30,
  'classes': ['Transfer']},
 
  'trips.txt': { 'required': True, 'loading_order': 40,
  'classes': ['Trip']},
 
  }
 
  def __getattr__(self, name):
  if name == 'Schedule':
  return schedule.Schedule
 
  if name == 'Loader':
  return loader.Loader
 
  if name in self._class_mapping:
  return self._class_mapping[name]
 
  raise AttributeError(name)
 
  def GetGtfsClassByFileName(self, filename):
  """Returns the transitfeed class corresponding to a GTFS file.
 
  Args:
  filename: The filename whose class is to be returned
 
  Raises:
  NonStandardMapping if the specified filename has more than one
  corresponding class
  """
  if filename not in self._file_mapping:
  return None
  mapping = self._file_mapping[filename]
  class_list = mapping['classes']
  if len(class_list) > 1:
  raise problems.NonStandardMapping(filename)
  else:
  return self._class_mapping[class_list[0]]
 
  def GetLoadingOrder(self):
  """Returns a list of filenames sorted by loading order.
  Only includes files that Loader's standardized loading knows how to load"""
  result = {}
  for filename, mapping in self._file_mapping.iteritems():
  loading_order = mapping['loading_order']
  if loading_order is not None:
  result[loading_order] = filename
  return list(result[key] for key in sorted(result))
 
  def IsFileRequired(self, filename):
  """Returns true if a file is required by GTFS, false otherwise.
  Unknown files are, by definition, not required"""
  if filename not in self._file_mapping:
  return False
  mapping = self._file_mapping[filename]
  return mapping['required']
 
  def GetKnownFilenames(self):
  """Returns a list of all known filenames"""
  return self._file_mapping.keys()
 
  def RemoveMapping(self, filename):
  """Removes an entry from the list of known filenames.
  An entry is identified by its filename.
 
  filename: The filename whose mapping is to be updated.
  """
  if filename in self._file_mapping:
  del self._file_mapping[filename]
 
  def AddMapping(self, filename, new_mapping):
  """Adds an entry to the list of known filenames.
 
  Args:
  filename: The filename whose mapping is being added.
  new_mapping: A dictionary with the mapping to add. Must contain all
  fields in _REQUIRED_MAPPING_FIELDS.
  Raises:
  DuplicateMapping if the filename already exists in the mapping
  InvalidMapping if not all required fields are present
  """
  for field in self._REQUIRED_MAPPING_FIELDS:
  if field not in new_mapping:
  raise problems.InvalidMapping(field)
  if filename in self.GetKnownFilenames():
  raise problems.DuplicateMapping(filename)
  self._file_mapping[filename] = new_mapping
 
  def UpdateMapping(self, filename, mapping_update):
  """Updates an entry in the list of known filenames.
  An entry is identified by its filename.
 
  Args:
  filename: The filename whose mapping is to be updated
  mapping_update: A dictionary containing the fields to update and their
  new values.
  Raises:
  InexistentMapping if the filename does not exist in the mapping
  """
  if filename not in self._file_mapping:
  raise problems.NonexistentMapping(filename)
  mapping = self._file_mapping[filename]
  mapping.update(mapping_update)
 
  def AddClass(self, class_name, gtfs_class):
  """Adds an entry to the list of known classes.
 
  Args:
  class_name: A string with name through which gtfs_class is to be made
  accessible.
  gtfs_class: The class to be added.
  Raises:
  DuplicateMapping if class_name is already present in the class mapping.
  """
  if class_name in self._class_mapping:
  raise problems.DuplicateMapping(class_name)
  self._class_mapping[class_name] = gtfs_class
 
  def UpdateClass(self, class_name, gtfs_class):
  """Updates an entry in the list of known classes.
 
  Args:
  class_name: A string with the class name that is to be updated.
  gtfs_class: The new class
  Raises:
  NonexistentMapping if there is no class with the specified class_name.
  """
  if class_name not in self._class_mapping:
  raise problems.NonexistentMapping(class_name)
  self._class_mapping[class_name] = gtfs_class
 
  def RemoveClass(self, class_name):
  """Removes an entry from the list of known classes.
 
  Args:
  class_name: A string with the class name that is to be removed.
  Raises:
  NonexistentMapping if there is no class with the specified class_name.
  """
  if class_name not in self._class_mapping:
  raise problems.NonexistentMapping(class_name)
  del self._class_mapping[class_name]
 
  def GetProblemReporter(self):
  return problems.ProblemReporter()
 
  def GetGtfsFactory():
  """Called by FeedValidator to retrieve this extension's GtfsFactory.
  Extensions will most likely only need to create an instance of
  transitfeed.GtfsFactory, call {Remove,Add,Update}Mapping as needed, and
  return that instance"""
  return GtfsFactory()
 
  #!/usr/bin/python2.5
 
  # Copyright (C) 2010 Google Inc.
  #
  # Licensed under the Apache License, Version 2.0 (the "License");
  # you may not use this file except in compliance with the License.
  # You may obtain a copy of the License at
  #
  # http://www.apache.org/licenses/LICENSE-2.0
  #
  # Unless required by applicable law or agreed to in writing, software
  # distributed under the License is distributed on an "AS IS" BASIS,
  # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  # See the License for the specific language governing permissions and
  # limitations under the License.
 
  class GtfsFactoryUser(object):
  """Base class for objects that must store a GtfsFactory in order to
  be able to instantiate Gtfs classes.
 
  If a non-default GtfsFactory is to be used, it must be set explicitly."""
 
  _gtfs_factory = None
 
  def GetGtfsFactory(self):
  """Return the object's GTFS Factory.
 
  Returns:
  The GTFS Factory that was set for this object. If none was explicitly
  set, it first sets the object's factory to transitfeed's GtfsFactory
  and returns it"""
 
  if self._gtfs_factory is None:
  #TODO(anog): We really need to create a dependency graph and clean things
  # up, as the comment in __init__.py says.
  # Not having GenericGTFSObject as a leaf (with no other
  # imports) creates all sorts of circular import problems.
  # This is why the import is here and not at the top level.
  # When this runs, gtfsfactory should have already been loaded
  # by other modules, avoiding the circular imports.
  import gtfsfactory
  self._gtfs_factory = gtfsfactory.GetGtfsFactory()
  return self._gtfs_factory
 
  def SetGtfsFactory(self, factory):
  self._gtfs_factory = factory
 
  #!/usr/bin/python2.5
 
  # Copyright (C) 2007 Google Inc.
  #
  # Licensed under the Apache License, Version 2.0 (the "License");
  # you may not use this file except in compliance with the License.
  # You may obtain a copy of the License at
  #
  # http://www.apache.org/licenses/LICENSE-2.0
  #
  # Unless required by applicable law or agreed to in writing, software
  # distributed under the License is distributed on an "AS IS" BASIS,
  # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  # See the License for the specific language governing permissions and
  # limitations under the License.
 
  from gtfsfactoryuser import GtfsFactoryUser
 
  class GtfsObjectBase(GtfsFactoryUser):
  """Object with arbitrary attributes which may be added to a schedule.
 
  This class should be used as the base class for GTFS objects which may
  be stored in a Schedule. It defines some methods for reading and writing
  attributes. If self._schedule is None than the object is not in a Schedule.
 
  Subclasses must:
  * define an __init__ method which sets the _schedule member to None or a
  weakref to a Schedule
  * Set the _TABLE_NAME class variable to a name such as 'stops', 'agency', ...
  * define methods to validate objects of that type:
  * ValidateBeforeAdd, which is called before an object is added to a
  Schedule. With the default loader the object is added to the Schedule if
  this function returns True, and is not added if it returns False.
  * ValidateAfterAdd, which is called after an object is added to a Schedule.
  With the default Loader the return value, if any, is not used.
 
  """
  def __getitem__(self, name):
  """Return a unicode or str representation of name or "" if not set."""
  if name in self.__dict__ and self.__dict__[name] is not None:
  return "%s" % self.__dict__[name]
  else:
  return ""
 
  def __getattr__(self, name):
  """Return None or the default value if name is a known attribute.
 
  This method is only called when name is not found in __dict__.
  """
  if name in self.__class__._FIELD_NAMES:
  return None
  else:
  raise AttributeError(name)
 
  def iteritems(self):
  """Return a iterable for (name, value) pairs of public attributes."""
  for name, value in self.__dict__.iteritems():
  if (not name) or name[0] == "_":
  continue
  yield name, value
 
  def __setattr__(self, name, value):
  """Set an attribute, adding name to the list of columns as needed."""
  object.__setattr__(self, name, value)
  if name[0] != '_' and self._schedule:
  self._schedule.AddTableColumn(self.__class__._TABLE_NAME, name)
 
  def __eq__(self, other):
  """Return true iff self and other are equivalent"""
  if not other:
  return False
 
  if id(self) == id(other):
  return True
 
  for k in self.keys().union(other.keys()):
  # use __getitem__ which returns "" for missing columns values
  if self[k] != other[k]:
  return False
  return True
 
  def __ne__(self, other):
  return not self.__eq__(other)
 
  # TODO(Tom): According to
  # http://docs.python.org/reference/datamodel.html#object.__hash__
  # this class should set '__hash__ = None' because it defines __eq__. This
  # can't be fixed until the merger is changed to not use a/b_merge_map.
 
  def __repr__(self):
  return "<%s %s>" % (self.__class__.__name__, sorted(self.iteritems()))
 
  def keys(self):
  """Return iterable of columns used by this object."""
  columns = set()
  for name in vars(self):
  if (not name) or name[0] == "_":
  continue
  columns.add(name)
  return columns
 
  def _ColumnNames(self):
  return self.keys()
 
  #!/usr/bin/python2.5
 
  # Copyright (C) 2007 Google Inc.
  #
  # Licensed under the Apache License, Version 2.0 (the "License");
  # you may not use this file except in compliance with the License.
  # You may obtain a copy of the License at
  #
  # http://www.apache.org/licenses/LICENSE-2.0
  #
  # Unless required by applicable law or agreed to in writing, software
  # distributed under the License is distributed on an "AS IS" BASIS,
  # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  # See the License for the specific language governing permissions and
  # limitations under the License.
 
  import codecs
  import cStringIO as StringIO
  import csv
  import os
  import re
  import zipfile
 
  import gtfsfactory as gtfsfactory_module
  import problems
  import util
 
  class Loader:
  def __init__(self,
  feed_path=None,
  schedule=None,
  problems=problems.default_problem_reporter,
  extra_validation=False,
  load_stop_times=True,
  memory_db=True,
  zip=None,
  check_duplicate_trips=False,
  gtfs_factory=None):
  """Initialize a new Loader object.
 
  Args:
  feed_path: string path to a zip file or directory
  schedule: a Schedule object or None to have one created
  problems: a ProblemReporter object, the default reporter raises an
  exception for each problem
  extra_validation: True if you would like extra validation
  load_stop_times: load the stop_times table, used to speed load time when
  times are not needed. The default is True.
  memory_db: if creating a new Schedule object use an in-memory sqlite
  database instead of creating one in a temporary file
  zip: a zipfile.ZipFile object, optionally used instead of path
  """
  if gtfs_factory is None:
  gtfs_factory = gtfsfactory_module.GetGtfsFactory()
 
  if not schedule:
  schedule = gtfs_factory.Schedule(problem_reporter=problems,
  memory_db=memory_db, check_duplicate_trips=check_duplicate_trips)
 
  self._extra_validation = extra_validation
  self._schedule = schedule
  self._problems = problems
  self._path = feed_path
  self._zip = zip
  self._load_stop_times = load_stop_times
  self._gtfs_factory = gtfs_factory
 
  def _DetermineFormat(self):
  """Determines whether the feed is in a form that we understand, and
  if so, returns True."""
  if self._zip:
  # If zip was passed to __init__ then path isn't used
  assert not self._path
  return True
 
  if not isinstance(self._path, basestring) and hasattr(self._path, 'read'):
  # A file-like object, used for testing with a StringIO file
  self._zip = zipfile.ZipFile(self._path, mode='r')
  return True
 
  if not os.path.exists(self._path):
  self._problems.FeedNotFound(self._path)
  return False
 
  if self._path.endswith('.zip'):
  try:
  self._zip = zipfile.ZipFile(self._path, mode='r')
  except IOError: # self._path is a directory
  pass
  except zipfile.BadZipfile:
  self._problems.UnknownFormat(self._path)
  return False
 
  if not self._zip and not os.path.isdir(self._path):
  self._problems.UnknownFormat(self._path)
  return False
 
  return True
 
  def _GetFileNames(self):
  """Returns a list of file names in the feed."""
  if self._zip:
  return self._zip.namelist()
  else:
  return os.listdir(self._path)
 
  def _CheckFileNames(self):
  filenames = self._GetFileNames()
  known_filenames = self._gtfs_factory.GetKnownFilenames()
  for feed_file in filenames:
  if feed_file not in known_filenames:
  if not feed_file.startswith('.'):
  # Don't worry about .svn files and other hidden files
  # as this will break the tests.
  self._problems.UnknownFile(feed_file)
 
  def _GetUtf8Contents(self, file_name):
  """Check for errors in file_name and return a string for csv reader."""
  contents = self._FileContents(file_name)
  if not contents: # Missing file
  return
 
  # Check for errors that will prevent csv.reader from working
  if len(contents) >= 2 and contents[0:2] in (codecs.BOM_UTF16_BE,
  codecs.BOM_UTF16_LE):
  self._problems.FileFormat("appears to be encoded in utf-16", (file_name, ))
  # Convert and continue, so we can find more errors
  contents = codecs.getdecoder('utf-16')(contents)[0].encode('utf-8')
 
  null_index = contents.find('\0')
  if null_index != -1:
  # It is easier to get some surrounding text than calculate the exact
  # row_num
  m = re.search(r'.{,20}\0.{,20}', contents, re.DOTALL)
  self._problems.FileFormat(
  "contains a null in text \"%s\" at byte %d" %
  (codecs.getencoder('string_escape')(m.group()), null_index + 1),
  (file_name, ))
  return
 
  # strip out any UTF-8 Byte Order Marker (otherwise it'll be
  # treated as part of the first column name, causing a mis-parse)
  contents = contents.lstrip(codecs.BOM_UTF8)
  return contents
 
  def _ReadCsvDict(self, file_name, all_cols, required):
  """Reads lines from file_name, yielding a dict of unicode values."""
  assert file_name.endswith(".txt")
  table_name = file_name[0:-4]
  contents = self._GetUtf8Contents(file_name)
  if not contents:
  return
 
  eol_checker = util.EndOfLineChecker(StringIO.StringIO(contents),
  file_name, self._problems)
  # The csv module doesn't provide a way to skip trailing space, but when I
  # checked 15/675 feeds had trailing space in a header row and 120 had spaces
  # after fields. Space after header fields can cause a serious parsing
  # problem, so warn. Space after body fields can cause a problem time,
  # integer and id fields; they will be validated at higher levels.
  reader = csv.reader(eol_checker, skipinitialspace=True)
 
  raw_header = reader.next()
  header_occurrences = util.defaultdict(lambda: 0)
  header = []
  valid_columns = [] # Index into raw_header and raw_row
  for i, h in enumerate(raw_header):
  h_stripped = h.strip()
  if not h_stripped:
  self._problems.CsvSyntax(
  description="The header row should not contain any blank values. "
  "The corresponding column will be skipped for the "
  "entire file.",
  context=(file_name, 1, [''] * len(raw_header), raw_header),
  type=problems.TYPE_ERROR)
  continue
  elif h != h_stripped:
  self._problems.CsvSyntax(
  description="The header row should not contain any "
  "space characters.",
  context=(file_name, 1, [''] * len(raw_header), raw_header),
  type=problems.TYPE_WARNING)
  header.append(h_stripped)
  valid_columns.append(i)
  header_occurrences[h_stripped] += 1
 
  for name, count in header_occurrences.items():
  if count > 1:
  self._problems.DuplicateColumn(
  header=name,
  file_name=file_name,
  count=count)
 
  self._schedule._table_columns[table_name] = header
 
  # check for unrecognized columns, which are often misspellings
  unknown_cols = set(header) - set(all_cols)
  if len(unknown_cols) == len(header):
  self._problems.CsvSyntax(
  description="The header row did not contain any known column "
  "names. The file is most likely missing the header row "
  "or not in the expected CSV format.",
  context=(file_name, 1, [''] * len(raw_header), raw_header),
  type=problems.TYPE_ERROR)
  else:
  for col in unknown_cols:
  # this is provided in order to create a nice colored list of
  # columns in the validator output
  context = (file_name, 1, [''] * len(header), header)
  self._problems.UnrecognizedColumn(file_name, col, context)
 
  missing_cols = set(required) - set(header)
  for col in missing_cols:
  # this is provided in order to create a nice colored list of
  # columns in the validator output
  context = (file_name, 1, [''] * len(header), header)
  self._problems.MissingColumn(file_name, col, context)
 
  line_num = 1 # First line read by reader.next() above
  for raw_row in reader:
  line_num += 1
  if len(raw_row) == 0: # skip extra empty lines in file
  continue
 
  if len(raw_row) > len(raw_header):
  self._problems.OtherProblem('Found too many cells (commas) in line '
  '%d of file "%s". Every row in the file '
  'should have the same number of cells as '
  'the header (first line) does.' %
  (line_num, file_name),
  (file_name, line_num),
  type=problems.TYPE_WARNING)
 
  if len(raw_row) < len(raw_header):
  self._problems.OtherProblem('Found missing cells (commas) in line '
  '%d of file "%s". Every row in the file '
  'should have the same number of cells as '
  'the header (first line) does.' %
  (line_num, file_name),
  (file_name, line_num),
  type=problems.TYPE_WARNING)
 
  # raw_row is a list of raw bytes which should be valid utf-8. Convert each
  # valid_columns of raw_row into Unicode.
  valid_values = []
  unicode_error_columns = [] # index of valid_values elements with an error
  for i in valid_columns:
  try:
  valid_values.append(raw_row[i].decode('utf-8'))
  except UnicodeDecodeError:
  # Replace all invalid characters with REPLACEMENT CHARACTER (U+FFFD)
  valid_values.append(codecs.getdecoder("utf8")
  (raw_row[i], errors="replace")[0])
  unicode_error_columns.append(len(valid_values) - 1)
  except IndexError:
  break
 
  # The error report may contain a dump of all values in valid_values so
  # problems can not be reported until after converting all of raw_row to
  # Unicode.
  for i in unicode_error_columns:
  self._problems.InvalidValue(header[i], valid_values[i],
  'Unicode error',
  (file_name, line_num,
  valid_values, header))
 
 
  d = dict(zip(header, valid_values))
  yield (d, line_num, header, valid_values)
 
  # TODO: Add testing for this specific function
  def _ReadCSV(self, file_name, cols, required):
  """Reads lines from file_name, yielding a list of unicode values
  corresponding to the column names in cols."""
  contents = self._GetUtf8Contents(file_name)
  if not contents:
  return
 
  eol_checker = util.EndOfLineChecker(StringIO.StringIO(contents),
  file_name, self._problems)
  reader = csv.reader(eol_checker) # Use excel dialect
 
  header = reader.next()
  header = map(lambda x: x.strip(), header) # trim any whitespace
  header_occurrences = util.defaultdict(lambda: 0)
  for column_header in header:
  header_occurrences[column_header] += 1
 
  for name, count in header_occurrences.items():
  if count > 1:
  self._problems.DuplicateColumn(
  header=name,
  file_name=file_name,
  count=count)
 
  # check for unrecognized columns, which are often misspellings
  unknown_cols = set(header).difference(set(cols))
  for col in unknown_cols:
  # this is provided in order to create a nice colored list of
  # columns in the validator output
  context = (file_name, 1, [''] * len(header), header)
  self._problems.UnrecognizedColumn(file_name, col, context)
 
  col_index = [-1] * len(cols)
  for i in range(len(cols)):
  if cols[i] in header:
  col_index[i] = header.index(cols[i])
  elif cols[i] in required:
  self._problems.MissingColumn(file_name, cols[i])
 
  row_num = 1
  for row in reader:
  row_num += 1
  if len(row) == 0: # skip extra empty lines in file
  continue
 
  if len(row) > len(header):
  self._problems.OtherProblem('Found too many cells (commas) in line '
  '%d of file "%s". Every row in the file '
  'should have the same number of cells as '
  'the header (first line) does.' %
  (row_num, file_name), (file_name, row_num),
  type=problems.TYPE_WARNING)
 
  if len(row) < len(header):
  self._problems.OtherProblem('Found missing cells (commas) in line '
  '%d of file "%s". Every row in the file '
  'should have the same number of cells as '
  'the header (first line) does.' %
  (row_num, file_name), (file_name, row_num),
  type=problems.TYPE_WARNING)
 
  result = [None] * len(cols)
  unicode_error_columns = [] # A list of column numbers with an error
  for i in range(len(cols)):
  ci = col_index[i]
  if ci >= 0:
  if len(row) <= ci: # handle short CSV rows
  result[i] = u''
  else:
  try:
  result[i] = row[ci].decode('utf-8').strip()
  except UnicodeDecodeError:
  # Replace all invalid characters with
  # REPLACEMENT CHARACTER (U+FFFD)
  result[i] = codecs.getdecoder("utf8")(row[ci],
  errors="replace")[0].strip()
  unicode_error_columns.append(i)
 
  for i in unicode_error_columns:
  self._problems.InvalidValue(cols[i], result[i],
  'Unicode error',
  (file_name, row_num, result, cols))
  yield (result, row_num, cols)
 
  def _HasFile(self, file_name):
  """Returns True if there's a file in the current feed with the
  given file_name in the current feed."""
  if self._zip:
  return file_name in self._zip.namelist()
  else:
  file_path = os.path.join(self._path, file_name)
  return os.path.exists(file_path) and os.path.isfile(file_path)
 
  def _FileContents(self, file_name):
  results = None
  if self._zip:
  try:
  results = self._zip.read(file_name)
  except KeyError: # file not found in archve
  self._problems.MissingFile(file_name)
  return None
  else:
  try:
  data_file = open(os.path.join(self._path, file_name), 'rb')
  results = data_file.read()
  except IOError: # file not found
  self._problems.MissingFile(file_name)
  return None
 
  if not results:
  self._problems.EmptyFile(file_name)
  return results
 
  def _LoadFeed(self):
  loading_order = self._gtfs_factory.GetLoadingOrder()
  for filename in loading_order:
  if not self._gtfs_factory.IsFileRequired(filename) and \
  not self._HasFile(filename):
  pass # File is not required, and feed does not have it.
  else:
  object_class = self._gtfs_factory.GetGtfsClassByFileName(filename)
  for (d, row_num, header, row) in self._ReadCsvDict(
  filename,
  object_class._FIELD_NAMES,
  object_class._REQUIRED_FIELD_NAMES):
  self._problems.SetFileContext(filename, row_num, row, header)
  instance = object_class(field_dict=d)
  instance.SetGtfsFactory(self._gtfs_factory)
  if not instance.ValidateBeforeAdd(self._problems):
  continue
  instance.AddToSchedule(self._schedule, self._problems)
  instance.ValidateAfterAdd(self._problems)
  self._problems.ClearContext()
 
  def _LoadCalendar(self):
  file_name = 'calendar.txt'
  file_name_dates = 'calendar_dates.txt'
  if not self._HasFile(file_name) and not self._HasFile(file_name_dates):
  self._problems.MissingFile(file_name)
  return
 
  # map period IDs to (period object, (file_name, row_num, row, cols))
  periods = {}
 
  # process calendar.txt
  if self._HasFile(file_name):
  has_useful_contents = False
  for (row, row_num, cols) in \
  self._ReadCSV(file_name,
  self._gtfs_factory.ServicePeriod._FIELD_NAMES,
  self._gtfs_factory.ServicePeriod._FIELD_NAMES_REQUIRED):
  context = (file_name, row_num, row, cols)
  self._problems.SetFileContext(*context)
 
  period = self._gtfs_factory.ServicePeriod(field_list=row)
 
  if period.service_id in periods:
  self._problems.DuplicateID('service_id', period.service_id)
  else:
  periods[period.service_id] = (period, context)
  self._problems.ClearContext()
 
  # process calendar_dates.txt
  if self._HasFile(file_name_dates):
  # ['service_id', 'date', 'exception_type']
  fields = self._gtfs_factory.ServicePeriod._FIELD_NAMES_CALENDAR_DATES
  for (row, row_num, cols) in self._ReadCSV(file_name_dates,
  fields, fields):
  context = (file_name_dates, row_num, row, cols)
  self._problems.SetFileContext(*context)
 
  service_id = row[0]
 
  period = None
  if service_id in periods:
  period = periods[service_id][0]
  else:
  period = self._gtfs_factory.ServicePeriod(service_id)
  periods[period.service_id] = (period, context)
 
  exception_type = row[2]
  if exception_type == u'1':
  period.SetDateHasService(row[1], True, self._problems)
  elif exception_type == u'2':
  period.SetDateHasService(row[1], False, self._problems)
  else:
  self._problems.InvalidValue('exception_type', exception_type)
  self._problems.ClearContext()
 
  # Now insert the periods into the schedule object, so that they're
  # validated with both calendar and calendar_dates info present
  for period, context in periods.values():
  self._problems.SetFileContext(*context)
  self._schedule.AddServicePeriodObject(period, self._problems)
  self._problems.ClearContext()
 
  def _LoadShapes(self):
  file_name = 'shapes.txt'
  if not self._HasFile(file_name):
  return
  shapes = {} # shape_id to shape object
  for (d, row_num, header, row) in self._ReadCsvDict(
  file_name,
  self._gtfs_factory.Shape._FIELD_NAMES,
  self._gtfs_factory.Shape._REQUIRED_FIELD_NAMES):
  file_context = (file_name, row_num, row, header)
  self._problems.SetFileContext(*file_context)
 
  shapepoint = self._gtfs_factory.ShapePoint(field_dict=d)
  if not shapepoint.ParseAttributes(self._problems):
  continue
 
  if shapepoint.shape_id in shapes:
  shape = shapes[shapepoint.shape_id]
  else:
  shape = self._gtfs_factory.Shape(shapepoint.shape_id)
  shape.SetGtfsFactory(self._gtfs_factory)
  shapes[shapepoint.shape_id] = shape
 
  shape.AddShapePointObjectUnsorted(shapepoint, self._problems)
  self._problems.ClearContext()
 
  for shape_id, shape in shapes.items():
  self._schedule.AddShapeObject(shape, self._problems)
  del shapes[shape_id]
 
  def _LoadStopTimes(self):
  for (row, row_num, cols) in self._ReadCSV('stop_times.txt',
  self._gtfs_factory.StopTime._FIELD_NAMES,
  self._gtfs_factory.StopTime._REQUIRED_FIELD_NAMES):
  file_context = ('stop_times.txt', row_num, row, cols)
  self._problems.SetFileContext(*file_context)
 
  (trip_id, arrival_time, departure_time, stop_id, stop_sequence,
  stop_headsign, pickup_type, drop_off_type, shape_dist_traveled) = row
 
  try:
  sequence = int(stop_sequence)
  except (TypeError, ValueError):
  self._problems.InvalidValue('stop_sequence', stop_sequence,
  'This should be a number.')
  continue
  if sequence < 0:
  self._problems.InvalidValue('stop_sequence', sequence,
  'Sequence numbers should be 0 or higher.')
 
  if stop_id not in self._schedule.stops:
  self._problems.InvalidValue('stop_id', stop_id,
  'This value wasn\'t defined in stops.txt')
  continue
  stop = self._schedule.stops[stop_id]
  if trip_id not in self._schedule.trips:
  self._problems.InvalidValue('trip_id', trip_id,
  'This value wasn\'t defined in trips.txt')
  continue
  trip = self._schedule.trips[trip_id]
 
  # If self._problems.Report returns then StopTime.__init__ will return
  # even if the StopTime object has an error. Thus this code may add a
  # StopTime that didn't validate to the database.
  # Trip.GetStopTimes then tries to make a StopTime from the invalid data
  # and calls the problem reporter for errors. An ugly solution is to
  # wrap problems and a better solution is to move all validation out of
  # __init__. For now make sure Trip.GetStopTimes gets a problem reporter
  # when called from Trip.Validate.
  stop_time = self._gtfs_factory.StopTime(self._problems, stop,
  arrival_time, departure_time, stop_headsign, pickup_type,
  drop_off_type, shape_dist_traveled, stop_sequence=sequence)
  trip._AddStopTimeObjectUnordered(stop_time, self._schedule)
  self._problems.ClearContext()
 
  # stop_times are validated in Trip.ValidateChildren, called by
  # Schedule.Validate
 
  def Load(self):
  self._problems.ClearContext()
  if not self._DetermineFormat():
  return self._schedule
 
  self._CheckFileNames()
  self._LoadCalendar()
  self._LoadShapes()
  self._LoadFeed()
 
  if self._load_stop_times:
  self._LoadStopTimes()
 
  if self._zip:
  self._zip.close()
  self._zip = None
 
  if self._extra_validation:
  self._schedule.Validate(self._problems, validate_children=False)
 
  return self._schedule
 
  #!/usr/bin/python2.5
 
  # Copyright (C) 2007 Google Inc.
  #
  # Licensed under the Apache License, Version 2.0 (the "License");
  # you may not use this file except in compliance with the License.
  # You may obtain a copy of the License at
  #
  # http://www.apache.org/licenses/LICENSE-2.0
  #
  # Unless required by applicable law or agreed to in writing, software
  # distributed under the License is distributed on an "AS IS" BASIS,
  # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  # See the License for the specific language governing permissions and
  # limitations under the License.
 
  import logging
  import time
 
  import util
 
  # These are used to distinguish between errors (not allowed by the spec)
  # and warnings (not recommended) when reporting issues.
  TYPE_ERROR = 0
  TYPE_WARNING = 1
 
  MAX_DISTANCE_FROM_STOP_TO_SHAPE = 1000
  MAX_DISTANCE_BETWEEN_STOP_AND_PARENT_STATION_WARNING = 100.0
  MAX_DISTANCE_BETWEEN_STOP_AND_PARENT_STATION_ERROR = 1000.0
 
 
  class ProblemReporter(object):
  """Base class for problem reporters. Tracks the current context and creates
  an exception object for each problem. Exception objects are sent to a
  Problem Accumulator, which is responsible for handling them."""
 
  def __init__(self, accumulator=None):
  self.ClearContext()
  if accumulator is None:
  self.accumulator = SimpleProblemAccumulator()
  else:
  self.accumulator = accumulator
 
  def SetAccumulator(self, accumulator):
  self.accumulator = accumulator
 
  def GetAccumulator(self):
  return self.accumulator
 
  def ClearContext(self):
  """Clear any previous context."""
  self._context = None
 
  def SetFileContext(self, file_name, row_num, row, headers):
  """Save the current context to be output with any errors.
 
  Args:
  file_name: string
  row_num: int
  row: list of strings
  headers: list of column headers, its order corresponding to row's
  """
  self._context = (file_name, row_num, row, headers)
 
  def AddToAccumulator(self,e):
  """Report an exception to the Problem Accumulator"""
  self.accumulator._Report(e)
 
  def FeedNotFound(self, feed_name, context=None):
  e = FeedNotFound(feed_name=feed_name, context=context,
  context2=self._context)
  self.AddToAccumulator(e)
 
  def UnknownFormat(self, feed_name, context=None):
  e = UnknownFormat(feed_name=feed_name, context=context,
  context2=self._context)
  self.AddToAccumulator(e)
 
  def FileFormat(self, problem, context=None):
  e = FileFormat(problem=problem, context=context,
  context2=self._context)
  self.AddToAccumulator(e)
 
  def MissingFile(self, file_name, context=None):
  e = MissingFile(file_name=file_name, context=context,
  context2=self._context)
  self.AddToAccumulator(e)
 
  def UnknownFile(self, file_name, context=None):
  e = UnknownFile(file_name=file_name, context=context,
  context2=self._context, type=TYPE_WARNING)
  self.AddToAccumulator(e)
 
  def EmptyFile(self, file_name, context=None):
  e = EmptyFile(file_name=file_name, context=context,
  context2=self._context)
  self.AddToAccumulator(e)
 
  def MissingColumn(self, file_name, column_name, context=None):
  e = MissingColumn(file_name=file_name, column_name=column_name,
  context=context, context2=self._context)
  self.AddToAccumulator(e)
 
  def UnrecognizedColumn(self, file_name, column_name, context=None):
  e = UnrecognizedColumn(file_name=file_name, column_name=column_name,
  context=context, context2=self._context,
  type=TYPE_WARNING)
  self.AddToAccumulator(e)
 
  def CsvSyntax(self, description=None, context=None, type=TYPE_ERROR):
  e = CsvSyntax(description=description, context=context,
  context2=self._context, type=type)
  self.AddToAccumulator(e)
 
  def DuplicateColumn(self, file_name, header, count, type=TYPE_ERROR,
  context=None):
  e = DuplicateColumn(file_name=file_name,
  header=header,
  count=count,
  type=type,
  context=context,
  context2=self._context)
  self.AddToAccumulator(e)
 
  def MissingValue(self, column_name, reason=None, context=None):
  e = MissingValue(column_name=column_name, reason=reason, context=context,
  context2=self._context)
  self.AddToAccumulator(e)
 
  def InvalidValue(self, column_name, value, reason=None, context=None,
  type=TYPE_ERROR):
  e = InvalidValue(column_name=column_name, value=value, reason=reason,
  context=context, context2=self._context, type=type)
  self.AddToAccumulator(e)
 
  def InvalidFloatValue(self, value, reason=None, context=None,
  type=TYPE_WARNING):
  e = InvalidFloatValue(value=value, reason=reason, context=context,
  context2=self._context, type=type)
  self.AddToAccumulator(e)
 
  def InvalidNonNegativeIntegerValue(self, value, reason=None, context=None,
  type=TYPE_WARNING):
  e = InvalidNonNegativeIntegerValue(value=value, reason=reason,
  context=context, context2=self._context,
  type=type)
  self.AddToAccumulator(e)
 
  def DuplicateID(self, column_names, values, context=None, type=TYPE_ERROR):
  if isinstance(column_names, (tuple, list)):
  column_names = '(' + ', '.join(column_names) + ')'
  if isinstance(values, tuple):
  values = '(' + ', '.join(values) + ')'
  e = DuplicateID(column_name=column_names, value=values,
  context=context, context2=self._context, type=type)
  self.AddToAccumulator(e)
 
  def UnusedStop(self, stop_id, stop_name, context=None):
  e = UnusedStop(stop_id=stop_id, stop_name=stop_name,
  context=context, context2=self._context, type=TYPE_WARNING)
  self.AddToAccumulator(e)
 
  def UsedStation(self, stop_id, stop_name, context=None):
  e = UsedStation(stop_id=stop_id, stop_name=stop_name,
  context=context, context2=self._context, type=TYPE_ERROR)
  self.AddToAccumulator(e)
 
  def StopTooFarFromParentStation(self, stop_id, stop_name, parent_stop_id,
  parent_stop_name, distance,
  type=TYPE_WARNING, context=None):
  e = StopTooFarFromParentStation(
  stop_id=stop_id, stop_name=stop_name,
  parent_stop_id=parent_stop_id,
  parent_stop_name=parent_stop_name, distance=distance,
  context=context, context2=self._context, type=type)
  self.AddToAccumulator(e)
 
  def StopsTooClose(self, stop_name_a, stop_id_a, stop_name_b, stop_id_b,
  distance, type=TYPE_WARNING, context=None):
  e = StopsTooClose(
  stop_name_a=stop_name_a, stop_id_a=stop_id_a, stop_name_b=stop_name_b,
  stop_id_b=stop_id_b, distance=distance, context=context,
  context2=self._context, type=type)
  self.AddToAccumulator(e)
 
  def StationsTooClose(self, stop_name_a, stop_id_a, stop_name_b, stop_id_b,
  distance, type=TYPE_WARNING, context=None):
  e = StationsTooClose(
  stop_name_a=stop_name_a, stop_id_a=stop_id_a, stop_name_b=stop_name_b,
  stop_id_b=stop_id_b, distance=distance, context=context,
  context2=self._context, type=type)
  self.AddToAccumulator(e)
 
  def DifferentStationTooClose(self, stop_name, stop_id,
  station_stop_name, station_stop_id,
  distance, type=TYPE_WARNING, context=None):
  e = DifferentStationTooClose(
  stop_name=stop_name, stop_id=stop_id,
  station_stop_name=station_stop_name, station_stop_id=station_stop_id,
  distance=distance, context=context, context2=self._context, type=type)
  self.AddToAccumulator(e)
 
  def StopTooFarFromShapeWithDistTraveled(self, trip_id, stop_name, stop_id,
  shape_dist_traveled, shape_id,
  distance, max_distance,
  type=TYPE_WARNING):
  e = StopTooFarFromShapeWithDistTraveled(
  trip_id=trip_id, stop_name=stop_name, stop_id=stop_id,
  shape_dist_traveled=shape_dist_traveled, shape_id=shape_id,
  distance=distance, max_distance=max_distance, type=type)
  self.AddToAccumulator(e)
 
  def ExpirationDate(self, expiration, context=None):
  e = ExpirationDate(expiration=expiration, context=context,
  context2=self._context, type=TYPE_WARNING)
  self.AddToAccumulator(e)
 
  def FutureService(self, start_date, context=None):
  e = FutureService(start_date=start_date, context=context,
  context2=self._context, type=TYPE_WARNING)
  self.AddToAccumulator(e)
 
  def NoServiceExceptions(self, start, end, type=TYPE_WARNING, context=None):
  e = NoServiceExceptions(start=start, end=end, context=context,
  context2=self._context, type=type);
  self.AddToAccumulator(e)
 
  def InvalidLineEnd(self, bad_line_end, context=None):
  """bad_line_end is a human readable string."""
  e = InvalidLineEnd(bad_line_end=bad_line_end, context=context,
  context2=self._context, type=TYPE_WARNING)
  self.AddToAccumulator(e)
 
  def TooFastTravel(self, trip_id, prev_stop, next_stop, dist, time, speed,
  type=TYPE_ERROR):
  e = TooFastTravel(trip_id=trip_id, prev_stop=prev_stop,
  next_stop=next_stop, time=time, dist=dist, speed=speed,
  context=None, context2=self._context, type=type)
  self.AddToAccumulator(e)
 
  def StopWithMultipleRouteTypes(self, stop_name, stop_id, route_id1, route_id2,
  context=None):
  e = StopWithMultipleRouteTypes(stop_name=stop_name, stop_id=stop_id,
  route_id1=route_id1, route_id2=route_id2,
  context=context, context2=self._context,
  type=TYPE_WARNING)
  self.AddToAccumulator(e)
 
  def DuplicateTrip(self, trip_id1, route_id1, trip_id2, route_id2,
  context=None):
  e = DuplicateTrip(trip_id1=trip_id1, route_id1=route_id1, trip_id2=trip_id2,
  route_id2=route_id2, context=context,
  context2=self._context, type=TYPE_WARNING)
  self.AddToAccumulator(e)
 
  def OverlappingTripsInSameBlock(self,trip_id1,trip_id2,block_id,context=None):
  e = OverlappingTripsInSameBlock(trip_id1=trip_id1, trip_id2=trip_id2,
  block_id=block_id, context=context,
  context2=self._context,type=TYPE_WARNING)
  self.AddToAccumulator(e)
 
  def TransferDistanceTooBig(self, from_stop_id, to_stop_id, distance,
  context=None, type=TYPE_ERROR):
  e = TransferDistanceTooBig(from_stop_id=from_stop_id, to_stop_id=to_stop_id,
  distance=distance, context=context,
  context2=self._context, type=type)
  self.AddToAccumulator(e)
 
  def TransferWalkingSpeedTooFast(self, from_stop_id, to_stop_id, distance,
  transfer_time, context=None,
  type=TYPE_WARNING):
  e = TransferWalkingSpeedTooFast(from_stop_id=from_stop_id,
  transfer_time=transfer_time,
  distance=distance,
  to_stop_id=to_stop_id, context=context,
  context2=self._context, type=type)
  self.AddToAccumulator(e)
 
  def OtherProblem(self, description, context=None, type=TYPE_ERROR):
  e = OtherProblem(description=description,
  context=context, context2=self._context, type=type)
  self.AddToAccumulator(e)
 
  def TooManyDaysWithoutService(self,
  first_day_without_service,
  last_day_without_service,
  consecutive_days_without_service,
  context=None,
  type=TYPE_WARNING):
  e = TooManyDaysWithoutService(
  first_day_without_service=first_day_without_service,
  last_day_without_service=last_day_without_service,
  consecutive_days_without_service=consecutive_days_without_service,
  context=context,
  context2=self._context,
  type=type)
  self.AddToAccumulator(e)
 
  def MinimumTransferTimeSetWithInvalidTransferType(self,
  transfer_type=None,
  context=None,
  type=TYPE_ERROR):
  e = MinimumTransferTimeSetWithInvalidTransferType(context=context,
  context2=self._context, transfer_type=transfer_type, type=type)
  self.AddToAccumulator(e)
 
 
  class ProblemAccumulatorInterface(object):
  """The base class for Problem Accumulators, which defines their interface."""
 
  def _Report(self, e):
  raise NotImplementedError("Please use a concrete Problem Accumulator that "
  "implements error and warning handling.")
 
 
  class SimpleProblemAccumulator(ProblemAccumulatorInterface):
  """This is a basic problem accumulator that just prints to console."""
  def _Report(self, e):
  context = e.FormatContext()
  if context:
  print context
  print util.EncodeUnicode(self._LineWrap(e.FormatProblem(), 78))
 
  @staticmethod
  def _LineWrap(text, width):
  """
  A word-wrap function that preserves existing line breaks
  and most spaces in the text. Expects that existing line
  breaks are posix newlines (\n).
 
  Taken from:
  http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/148061
  """
  return reduce(lambda line, word, width=width: '%s%s%s' %
  (line,
  ' \n'[(len(line) - line.rfind('\n') - 1 +
  len(word.split('\n', 1)[0]) >= width)],
  word),
  text.split(' ')
  )
 
 
  class ExceptionWithContext(Exception):
  def __init__(self, context=None, context2=None, **kwargs):
  """Initialize an exception object, saving all keyword arguments in self.
  context and context2, if present, must be a tuple of (file_name, row_num,
  row, headers). context2 comes from ProblemReporter.SetFileContext. context
  was passed in with the keyword arguments. context2 is ignored if context
  is present."""
  Exception.__init__(self)
 
  if context:
  self.__dict__.update(self.ContextTupleToDict(context))
  elif context2:
  self.__dict__.update(self.ContextTupleToDict(context2))
  self.__dict__.update(kwargs)
 
  if ('type' in kwargs) and (kwargs['type'] == TYPE_WARNING):
  self._type = TYPE_WARNING
  else:
  self._type = TYPE_ERROR
 
  def GetType(self):
  return self._type
 
  def IsError(self):
  return self._type == TYPE_ERROR
 
  def IsWarning(self):
  return self._type == TYPE_WARNING
 
  CONTEXT_PARTS = ['file_name', 'row_num', 'row', 'headers']
  @staticmethod
  def ContextTupleToDict(context):
  """Convert a tuple representing a context into a dict of (key, value) pairs"""
  d = {}
  if not context:
  return d
  for k, v in zip(ExceptionWithContext.CONTEXT_PARTS, context):
  if v != '' and v != None: # Don't ignore int(0), a valid row_num
  d[k] = v
  return d
 
  def __str__(self):
  return self.FormatProblem()
 
  def GetDictToFormat(self):
  """Return a copy of self as a dict, suitable for passing to FormatProblem"""
  d = {}
  for k, v in self.__dict__.items():
  # TODO: Better handling of unicode/utf-8 within Schedule objects.
  # Concatinating a unicode and utf-8 str object causes an exception such
  # as "UnicodeDecodeError: 'ascii' codec can't decode byte ..." as python
  # tries to convert the str to a unicode. To avoid that happening within
  # the problem reporter convert all unicode attributes to utf-8.
  # Currently valid utf-8 fields are converted to unicode in _ReadCsvDict.
  # Perhaps all fields should be left as utf-8.
  d[k] = util.EncodeUnicode(v)
  return d
 
  def FormatProblem(self, d=None):
  """Return a text string describing the problem.
 
  Args:
  d: map returned by GetDictToFormat with with formatting added
  """
  if not d:
  d = self.GetDictToFormat()
 
  output_error_text = self.__class__.ERROR_TEXT % d
  if ('reason' in d) and d['reason']:
  return '%s\n%s' % (output_error_text, d['reason'])
  else:
  return output_error_text
 
  def FormatContext(self):
  """Return a text string describing the context"""
  text = ''
  if hasattr(self, 'feed_name'):
  text += "In feed '%s': " % self.feed_name
  if hasattr(self, 'file_name'):
  text += self.file_name
  if hasattr(self, 'row_num'):
  text += ":%i" % self.row_num
  if hasattr(self, 'column_name'):
  text += " column %s" % self.column_name
  return text
 
  def __cmp__(self, y):
  """Return an int <0/0/>0 when self is more/same/less significant than y.
 
  Subclasses should define this if exceptions should be listed in something
  other than the order they are reported.
 
  Args:
  y: object to compare to self
 
  Returns:
  An int which is negative if self is more significant than y, 0 if they
  are similar significance and positive if self is less significant than
  y. Returning a float won't work.
 
  Raises:
  TypeError by default, meaning objects of the type can not be compared.
  """
  raise TypeError("__cmp__ not defined")
 
 
  class MissingFile(ExceptionWithContext):
  ERROR_TEXT = "File %(file_name)s is not found"
 
  class EmptyFile(ExceptionWithContext):
  ERROR_TEXT = "File %(file_name)s is empty"
 
  class UnknownFile(ExceptionWithContext):
  ERROR_TEXT = 'The file named %(file_name)s was not expected.\n' \
  'This may be a misspelled file name or the file may be ' \
  'included in a subdirectory. Please check spellings and ' \
  'make sure that there are no subdirectories within the feed'
 
  class FeedNotFound(ExceptionWithContext):
  ERROR_TEXT = 'Couldn\'t find a feed named %(feed_name)s'
 
  class UnknownFormat(ExceptionWithContext):
  ERROR_TEXT = 'The feed named %(feed_name)s had an unknown format:\n' \
  'feeds should be either .zip files or directories.'
 
  class FileFormat(ExceptionWithContext):
  ERROR_TEXT = 'Files must be encoded in utf-8 and may not contain ' \
  'any null bytes (0x00). %(file_name)s %(problem)s.'
 
  class MissingColumn(ExceptionWithContext):
  ERROR_TEXT = 'Missing column %(column_name)s in file %(file_name)s'
 
  class UnrecognizedColumn(ExceptionWithContext):
  ERROR_TEXT = 'Unrecognized column %(column_name)s in file %(file_name)s. ' \
  'This might be a misspelled column name (capitalization ' \
  'matters!). Or it could be extra information (such as a ' \
  'proposed feed extension) that the validator doesn\'t know ' \
  'about yet. Extra information is fine; this warning is here ' \
  'to catch misspelled optional column names.'
 
  class CsvSyntax(ExceptionWithContext):
  ERROR_TEXT = '%(description)s'
 
  class DuplicateColumn(ExceptionWithContext):
  ERROR_TEXT = 'Column %(header)s appears %(count)i times in file %(file_name)s'
 
  class MissingValue(ExceptionWithContext):
  ERROR_TEXT = 'Missing value for column %(column_name)s'
 
  class InvalidValue(ExceptionWithContext):
  ERROR_TEXT = 'Invalid value %(value)s in field %(column_name)s'
 
  class InvalidFloatValue(ExceptionWithContext):
  ERROR_TEXT = (
  "Invalid numeric value %(value)s. "
  "Please ensure that the number includes an explicit whole "
  "number portion (ie. use 0.5 instead of .5), that you do not use the "
  "exponential notation (ie. use 0.001 instead of 1E-3), and "
  "that it is a properly formated decimal value.")
 
  class InvalidNonNegativeIntegerValue(ExceptionWithContext):
  ERROR_TEXT = (
  "Invalid numeric value %(value)s. "
  "Please ensure that the number does not have a leading zero (ie. use "
  "3 instead of 03), and that it is a properly formated integer value.")
 
  class DuplicateID(ExceptionWithContext):
  ERROR_TEXT = 'Duplicate ID %(value)s in column %(column_name)s'
 
  class UnusedStop(ExceptionWithContext):
  ERROR_TEXT = "%(stop_name)s (ID %(stop_id)s) isn't used in any trips"
 
  class UsedStation(ExceptionWithContext):
  ERROR_TEXT = "%(stop_name)s (ID %(stop_id)s) has location_type=1 " \
  "(station) so it should not appear in stop_times"
 
  class StopTooFarFromParentStation(ExceptionWithContext):
  ERROR_TEXT = (
  "%(stop_name)s (ID %(stop_id)s) is too far from its parent station "
  "%(parent_stop_name)s (ID %(parent_stop_id)s) : %(distance).2f meters.")
  def __cmp__(self, y):
  # Sort in decreasing order because more distance is more significant.
  return cmp(y.distance, self.distance)
 
 
  class StopsTooClose(ExceptionWithContext):
  ERROR_TEXT = (
  "The stops \"%(stop_name_a)s\" (ID %(stop_id_a)s) and \"%(stop_name_b)s\""
  " (ID %(stop_id_b)s) are %(distance)0.2fm apart and probably represent "
  "the same location.")
  def __cmp__(self, y):
  # Sort in increasing order because less distance is more significant.
  return cmp(self.distance, y.distance)
 
  class StationsTooClose(ExceptionWithContext):
  ERROR_TEXT = (
  "The stations \"%(stop_name_a)s\" (ID %(stop_id_a)s) and "
  "\"%(stop_name_b)s\" (ID %(stop_id_b)s) are %(distance)0.2fm apart and "
  "probably represent the same location.")
  def __cmp__(self, y):
  # Sort in increasing order because less distance is more significant.
  return cmp(self.distance, y.distance)
 
  class DifferentStationTooClose(ExceptionWithContext):
  ERROR_TEXT = (
  "The parent_station of stop \"%(stop_name)s\" (ID %(stop_id)s) is not "
  "station \"%(station_stop_name)s\" (ID %(station_stop_id)s) but they are "
  "only %(distance)0.2fm apart.")
  def __cmp__(self, y):
  # Sort in increasing order because less distance is more significant.
  return cmp(self.distance, y.distance)
 
  class StopTooFarFromShapeWithDistTraveled(ExceptionWithContext):
  ERROR_TEXT = (
  "For trip %(trip_id)s the stop \"%(stop_name)s\" (ID %(stop_id)s) is "
  "%(distance).0f meters away from the corresponding point "
  "(shape_dist_traveled: %(shape_dist_traveled)f) on shape %(shape_id)s. "
  "It should be closer than %(max_distance).0f meters.")
  def __cmp__(self, y):
  # Sort in decreasing order because more distance is more significant.
  return cmp(y.distance, self.distance)
 
 
  class TooManyDaysWithoutService(ExceptionWithContext):
  ERROR_TEXT = "There are %(consecutive_days_without_service)i consecutive"\
  " days, from %(first_day_without_service)s to" \
  " %(last_day_without_service)s, without any scheduled service." \
  " Please ensure this is intentional."
 
  class MinimumTransferTimeSetWithInvalidTransferType(ExceptionWithContext):
  ERROR_TEXT = "The field min_transfer_time should only be set when " \
  "transfer_type is set to 2, but it is set to %(transfer_type)s."
 
 
  class ExpirationDate(ExceptionWithContext):
  def FormatProblem(self, d=None):
  if not d:
  d = self.GetDictToFormat()
  expiration = d['expiration']
  formatted_date = time.strftime("%B %d, %Y",
  time.localtime(expiration))
  if (expiration < time.mktime(time.localtime())):
  return "This feed expired on %s" % formatted_date
  else:
  return "This feed will soon expire, on %s" % formatted_date
 
  class FutureService(ExceptionWithContext):
  def FormatProblem(self, d=None):
  if not d:
  d = self.GetDictToFormat()
  formatted_date = time.strftime("%B %d, %Y", time.localtime(d['start_date']))
  return ("The earliest service date in this feed is in the future, on %s. "
  "Published feeds must always include the current date." %
  formatted_date)
 
  class NoServiceExceptions(ExceptionWithContext):
  ERROR_TEXT = "All services are defined on a weekly basis from %(start)s " \
  "to %(end)s with no single day variations. If there are " \
  "exceptions such as holiday service dates please ensure they " \
  "are listed in calendar_dates.txt"
 
  class InvalidLineEnd(ExceptionWithContext):
  ERROR_TEXT = "Each line must end with CR LF or LF except for the last line " \
  "of the file. This line ends with \"%(bad_line_end)s\"."
 
  class StopWithMultipleRouteTypes(ExceptionWithContext):
  ERROR_TEXT = "Stop %(stop_name)s (ID=%(stop_id)s) belongs to both " \
  "subway (ID=%(route_id1)s) and bus line (ID=%(route_id2)s)."
 
  class TooFastTravel(ExceptionWithContext):
  def FormatProblem(self, d=None):
  if not d:
  d = self.GetDictToFormat()
  if not d['speed']:
  return "High speed travel detected in trip %(trip_id)s: %(prev_stop)s" \
  " to %(next_stop)s. %(dist).0f meters in %(time)d seconds." % d
  else:
  return "High speed travel detected in trip %(trip_id)s: %(prev_stop)s" \
  " to %(next_stop)s. %(dist).0f meters in %(time)d seconds." \
  " (%(speed).0f km/h)." % d
  def __cmp__(self, y):
  # Sort in decreasing order because more distance is more significant. We
  # can't sort by speed because not all TooFastTravel objects have a speed.
  return cmp(y.dist, self.dist)
 
  class DuplicateTrip(ExceptionWithContext):
  ERROR_TEXT = "Trip %(trip_id1)s of route %(route_id1)s might be duplicated " \
  "with trip %(trip_id2)s of route %(route_id2)s. They go " \
  "through the same stops with same service."
 
  class OverlappingTripsInSameBlock(ExceptionWithContext):
  ERROR_TEXT = "Trip %(trip_id1)s and trip %(trip_id2)s both are in the " \
  "same block %(block_id)s and have overlapping arrival times."
 
  class TransferDistanceTooBig(ExceptionWithContext):
  ERROR_TEXT = "Transfer from stop %(from_stop_id)s to stop " \
  "%(to_stop_id)s has a distance of %(distance)s meters."
 
  class TransferWalkingSpeedTooFast(ExceptionWithContext):
  ERROR_TEXT = "Riders transfering from stop %(from_stop_id)s to stop " \
  "%(to_stop_id)s would need to walk %(distance)s meters in " \
  "%(transfer_time)s seconds."
 
  class OtherProblem(ExceptionWithContext):
  ERROR_TEXT = '%(description)s'
 
 
  class ExceptionProblemAccumulator(ProblemAccumulatorInterface):
  """A problem accumulator that handles errors and optionally warnings by
  raising exceptions."""
  def __init__(self, raise_warnings=False):
  """Initialise.
 
  Args:
  raise_warnings: If this is True then warnings are also raised as
  exceptions.
  If it is false, warnings are printed to the console using
  SimpleProblemAccumulator.
  """
  self.raise_warnings = raise_warnings
  self.accumulator = SimpleProblemAccumulator()
 
  def _Report(self, e):
  if self.raise_warnings or e.IsError():
  raise e
  else:
  self.accumulator._Report(e)
 
 
  default_accumulator = ExceptionProblemAccumulator()
  default_problem_reporter = ProblemReporter(default_accumulator)
 
  # Add a default handler to send log messages to console
  console = logging.StreamHandler()
  console.setLevel(logging.WARNING)
  log = logging.getLogger("schedule_builder")
  log.addHandler(console)
 
 
  class Error(Exception):
  pass
 
  # Below are the exceptions related to loading and setting up Feed Validator
  # extensions
 
  class ExtensionException(Exception):
  pass
 
  class InvalidMapping(ExtensionException):
  def __init__(self, missing_field):
  self.missing_field = missing_field
 
  class NonexistentMapping(ExtensionException):
  def __init__(self, name):
  self.name = name
 
  class DuplicateMapping(ExtensionException):
  def __init__(self, name):
  self.name = name
 
  class NonStandardMapping(ExtensionException):
  def __init__(self, name):
  self.name = name
 
  #!/usr/bin/python2.5
 
  # Copyright (C) 2007 Google Inc.
  #
  # Licensed under the Apache License, Version 2.0 (the "License");
  # you may not use this file except in compliance with the License.
  # You may obtain a copy of the License at
  #
  # http://www.apache.org/licenses/LICENSE-2.0
  #
  # Unless required by applicable law or agreed to in writing, software
  # distributed under the License is distributed on an "AS IS" BASIS,
  # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  # See the License for the specific language governing permissions and
  # limitations under the License.
 
  from gtfsobjectbase import GtfsObjectBase
  import problems as problems_module
  import util
 
  class Route(GtfsObjectBase):
  """Represents a single route."""
 
  _REQUIRED_FIELD_NAMES = [
  'route_id', 'route_short_name', 'route_long_name', 'route_type'
  ]
  _FIELD_NAMES = _REQUIRED_FIELD_NAMES + [
  'agency_id', 'route_desc', 'route_url', 'route_color', 'route_text_color'
  ]
  _ROUTE_TYPES = {
  0: {'name':'Tram', 'max_speed':100},
  1: {'name':'Subway', 'max_speed':150},
  2: {'name':'Rail', 'max_speed':300},
  3: {'name':'Bus', 'max_speed':100},
  4: {'name':'Ferry', 'max_speed':80},
  5: {'name':'Cable Car', 'max_speed':50},
  6: {'name':'Gondola', 'max_speed':50},
  7: {'name':'Funicular', 'max_speed':50},
  }
  # Create a reverse lookup dict of route type names to route types.
  _ROUTE_TYPE_IDS = set(_ROUTE_TYPES.keys())
  _ROUTE_TYPE_NAMES = dict((v['name'], k) for k, v in _ROUTE_TYPES.items())
  _TABLE_NAME = 'routes'
 
  def __init__(self, short_name=None, long_name=None, route_type=None,
  route_id=None, agency_id=None, field_dict=None):
  self._schedule = None
  self._trips = []
 
  if not field_dict:
  field_dict = {}
  if short_name is not None:
  field_dict['route_short_name'] = short_name
  if long_name is not None:
  field_dict['route_long_name'] = long_name
  if route_type is not None:
  if route_type in self._ROUTE_TYPE_NAMES:
  self.route_type = self._ROUTE_TYPE_NAMES[route_type]
  else:
  field_dict['route_type'] = route_type
  if route_id is not None:
  field_dict['route_id'] = route_id
  if agency_id is not None:
  field_dict['agency_id'] = agency_id
  self.__dict__.update(field_dict)
 
  def AddTrip(self, schedule=None, headsign=None, service_period=None,
  trip_id=None):
  """Add a trip to this route.
 
  Args:
  schedule: a Schedule object which will hold the new trip or None to use
  the schedule of this route.
  headsign: headsign of the trip as a string
  service_period: a ServicePeriod object or None to use
  schedule.GetDefaultServicePeriod()
  trip_id: optional trip_id for the new trip
 
  Returns:
  a new Trip object
  """
  if schedule is None:
  assert self._schedule is not None
  schedule = self._schedule
  if trip_id is None:
  trip_id = util.FindUniqueId(schedule.trips)
  if service_period is None:
  service_period = schedule.GetDefaultServicePeriod()
  trip_class = self.GetGtfsFactory().Trip
  trip_obj = trip_class(route=self, headsign=headsign,
  service_period=service_period, trip_id=trip_id)
  schedule.AddTripObject(trip_obj)
  return trip_obj
 
  def _AddTripObject(self, trip):
  # Only class Schedule may call this. Users of the API should call
  # Route.AddTrip or schedule.AddTripObject.
  self._trips.append(trip)
 
  def __getattr__(self, name):
  """Return None or the default value if name is a known attribute.
 
  This method overrides GtfsObjectBase.__getattr__ to provide backwards
  compatible access to trips.
  """
  if name == 'trips':
  return self._trips
  else:
  return GtfsObjectBase.__getattr__(self, name)
 
  def GetPatternIdTripDict(self):
  """Return a dictionary that maps pattern_id to a list of Trip objects."""
  d = {}
  for t in self._trips:
  d.setdefault(t.pattern_id, []).append(t)
  return d
 
  def ValidateRouteIdIsPresent(self, problems):
  if util.IsEmpty(self.route_id):
  problems.MissingValue('route_id')
 
  def ValidateRouteTypeIsPresent(self, problems):
  if util.IsEmpty(self.route_type):
  problems.MissingValue('route_type')
 
  def ValidateRouteShortAndLongNamesAreNotBlank(self, problems):
  if util.IsEmpty(self.route_short_name) and \
  util.IsEmpty(self.route_long_name):
  problems.InvalidValue('route_short_name',
  self.route_short_name,
  'Both route_short_name and '
  'route_long name are blank.')
 
  def ValidateRouteShortNameIsNotTooLong(self, problems):
  if self.route_short_name and len(self.route_short_name) > 6:
  problems.InvalidValue('route_short_name',
  self.route_short_name,
  'This route_short_name is relatively long, which '
  'probably means that it contains a place name. '
  'You should only use this field to hold a short '
  'code that riders use to identify a route. '
  'If this route doesn\'t have such a code, it\'s '
  'OK to leave this field empty.',
  type=problems_module.TYPE_WARNING)
 
  def ValidateRouteLongNameDoesNotContainShortName(self, problems):
  if self.route_short_name and self.route_long_name:
  short_name = self.route_short_name.strip().lower()
  long_name = self.route_long_name.strip().lower()
  if (long_name.startswith(short_name + ' ') or
  long_name.startswith(short_name + '(') or
  long_name.startswith(short_name + '-')):
  problems.InvalidValue('route_long_name',
  self.route_long_name,
  'route_long_name shouldn\'t contain '
  'the route_short_name value, as both '
  'fields are often displayed '
  'side-by-side.',
  type=problems_module.TYPE_WARNING)
 
  def ValidateRouteShortAndLongNamesAreNotEqual(self, problems):
  if self.route_short_name and self.route_long_name:
  short_name = self.route_short_name.strip().lower()
  long_name = self.route_long_name.strip().lower()
  if long_name == short_name:
  problems.InvalidValue('route_long_name',
  self.route_long_name,
  'route_long_name shouldn\'t be the same '
  'the route_short_name value, as both '
  'fields are often displayed '
  'side-by-side. It\'s OK to omit either the '
  'short or long name (but not both).',
  type=problems_module.TYPE_WARNING)
 
  def ValidateRouteDescriptionNotTheSameAsRouteName(self, problems):
  if (self.route_desc and
  ((self.route_desc == self.route_short_name) or
  (self.route_desc == self.route_long_name))):
  problems.InvalidValue('route_desc',
  self.route_desc,
  'route_desc shouldn\'t be the same as '
  'route_short_name or route_long_name')
  def ValidateRouteTypeHasValidValue(self, problems):
  if self.route_type is not None:
  try:
  if not isinstance(self.route_type, int):
  self.route_type = util.NonNegIntStringToInt(self.route_type, problems)
  except (TypeError, ValueError):
  problems.InvalidValue('route_type', self.route_type)
  else:
  if self.route_type not in self._ROUTE_TYPE_IDS:
  problems.InvalidValue('route_type',
  self.route_type,
  type=problems_module.TYPE_WARNING)
 
  def ValidateRouteUrl(self, problems):
  if self.route_url and not util.IsValidURL(self.route_url):
  problems.InvalidValue('route_url', self.route_url)
 
  def ValidateRouteColor(self, problems):
  if self.route_color:
  if not util.IsValidColor(self.route_color):
  problems.InvalidValue('route_color', self.route_color,
  'route_color should be a valid color description '
  'which consists of 6 hexadecimal characters '
  'representing the RGB values. Example: 44AA06')
  self.route_color = None
 
  def ValidateRouteTextColor(self, problems):
  if self.route_text_color:
  if not util.IsValidColor(self.route_text_color):
  problems.InvalidValue('route_text_color', self.route_text_color,
  'route_text_color should be a valid color '
  'description, which consists of 6 hexadecimal '
  'characters representing the RGB values. '
  'Example: 44AA06')
  self.route_text_color = None
 
  def ValidateRouteAndTextColors(self, problems):
  if self.route_color:
  bg_lum = util.ColorLuminance(self.route_color)
  else:
  bg_lum = util.ColorLuminance('ffffff') # white (default)
  if self.route_text_color:
  txt_lum = util.ColorLuminance(self.route_text_color)
  else:
  txt_lum = util.ColorLuminance('000000') # black (default)
  if abs(txt_lum - bg_lum) < 510/7.:
  # http://www.w3.org/TR/2000/WD-AERT-20000426#color-contrast recommends
  # a threshold of 125, but that is for normal text and too harsh for
  # big colored logos like line names, so we keep the original threshold
  # from r541 (but note that weight has shifted between RGB components).
  problems.InvalidValue('route_color', self.route_color,
  'The route_text_color and route_color should '
  'be set to contrasting colors, as they are used '
  'as the text and background color (respectively) '
  'for displaying route names. When left blank, '
  'route_text_color defaults to 000000 (black) and '
  'route_color defaults to FFFFFF (white). A common '
  'source of issues here is setting route_color to '
  'a dark color, while leaving route_text_color set '
  'to black. In this case, route_text_color should '
  'be set to a lighter color like FFFFFF to ensure '
  'a legible contrast between the two.',
  type=problems_module.TYPE_WARNING)
 
  def ValidateBeforeAdd(self, problems):
  self.ValidateRouteIdIsPresent(problems)
  self.ValidateRouteTypeIsPresent(problems)
  self.ValidateRouteShortAndLongNamesAreNotBlank(problems)
  self.ValidateRouteShortNameIsNotTooLong(problems)
  self.ValidateRouteLongNameDoesNotContainShortName(problems)
  self.ValidateRouteShortAndLongNamesAreNotEqual(problems)
  self.ValidateRouteDescriptionNotTheSameAsRouteName(problems)
  self.ValidateRouteTypeHasValidValue(problems)
  self.ValidateRouteUrl(problems)
  self.ValidateRouteColor(problems)
  self.ValidateRouteTextColor(problems)
  self.ValidateRouteAndTextColors(problems)
 
  # None of these checks are blocking
  return True
 
  def ValidateAfterAdd(self, problems):
  return
 
  def AddToSchedule(self, schedule, problems):
  schedule.AddRouteObject(self, problems)
 
  def Validate(self, problems=problems_module.default_problem_reporter):
  self.ValidateBeforeAdd(problems)
  self.ValidateAfterAdd(problems)
 
  #!/usr/bin/python2.5
 
  # Copyright (C) 2007 Google Inc.
  #
  # Licensed under the Apache License, Version 2.0 (the "License");
  # you may not use this file except in compliance with the License.
  # You may obtain a copy of the License at
  #
  # http://www.apache.org/licenses/LICENSE-2.0
  #
  # Unless required by applicable law or agreed to in writing, software
  # distributed under the License is distributed on an "AS IS" BASIS,
  # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  # See the License for the specific language governing permissions and
  # limitations under the License.
 
  import bisect
  import cStringIO as StringIO
  import datetime
  import itertools
  import os
  try:
  import sqlite3 as sqlite
  except ImportError:
  from pysqlite2 import dbapi2 as sqlite
  import tempfile
  import time
  import warnings
  # Objects in a schedule (Route, Trip, etc) should not keep a strong reference
  # to the Schedule object to avoid a reference cycle. Schedule needs to use
  # __del__ to cleanup its temporary file. The garbage collector can't handle
  # reference cycles containing objects with custom cleanup code.
  import weakref
  import zipfile
 
  import gtfsfactory
  import problems as problems_module
  from transitfeed.util import defaultdict
  import util
 
  class Schedule:
  """Represents a Schedule, a collection of stops, routes, trips and
  an agency. This is the main class for this module."""
 
  def __init__(self, problem_reporter=None,
  memory_db=True, check_duplicate_trips=False,
  gtfs_factory=None):
  if gtfs_factory is None:
  gtfs_factory = gtfsfactory.GetGtfsFactory()
  self._gtfs_factory = gtfs_factory
 
  # Map from table name to list of columns present in this schedule
  self._table_columns = {}
 
  self._agencies = {}
  self.stops = {}
  self.routes = {}
  self.trips = {}
  self.service_periods = {}
  self.fares = {}
  self.fare_zones = {} # represents the set of all known fare zones
  self._shapes = {} # shape_id to Shape
  # A map from transfer._ID() to a list of transfers. A list is used so
  # there can be more than one transfer with each ID. Once GTFS explicitly
  # prohibits duplicate IDs this might be changed to a simple dict of
  # Transfers.
  self._transfers = defaultdict(lambda: [])
  self._default_service_period = None
  self._default_agency = None
  if problem_reporter is None:
  self.problem_reporter = problems_module.default_problem_reporter
  else:
  self.problem_reporter = problem_reporter
  self._check_duplicate_trips = check_duplicate_trips
  self.ConnectDb(memory_db)
 
  def AddTableColumn(self, table, column):
  """Add column to table if it is not already there."""
  if column not in self._table_columns[table]:
  self._table_columns[table].append(column)
 
  def AddTableColumns(self, table, columns):
  """Add columns to table if they are not already there.
 
  Args:
  table: table name as a string
  columns: an iterable of column names"""
  table_columns = self._table_columns.setdefault(table, [])
  for attr in columns:
  if attr not in table_columns:
  table_columns.append(attr)
 
  def GetTableColumns(self, table):
  """Return list of columns in a table."""
  return self._table_columns[table]
 
  def __del__(self):
  self._connection.cursor().close()
  self._connection.close()
  if hasattr(self, '_temp_db_filename'):
  os.remove(self._temp_db_filename)
 
  def ConnectDb(self, memory_db):
  if memory_db:
  self._connection = sqlite.connect(":memory:")
  else:
  try:
  self._temp_db_file = tempfile.NamedTemporaryFile()
  self._connection = sqlite.connect(self._temp_db_file.name)
  except sqlite.OperationalError:
  # Windows won't let a file be opened twice. mkstemp does not remove the
  # file when all handles to it are closed.
  self._temp_db_file = None
  (fd, self._temp_db_filename) = tempfile.mkstemp(".db")
  os.close(fd)
  self._connection = sqlite.connect(self._temp_db_filename)
 
  cursor = self._connection.cursor()
  cursor.execute("""CREATE TABLE stop_times (
  trip_id CHAR(50),
  arrival_secs INTEGER,
  departure_secs INTEGER,
  stop_id CHAR(50),
  stop_sequence INTEGER,
  stop_headsign VAR CHAR(100),
  pickup_type INTEGER,
  drop_off_type INTEGER,
  shape_dist_traveled FLOAT);""")
  cursor.execute("""CREATE INDEX trip_index ON stop_times (trip_id);""")
  cursor.execute("""CREATE INDEX stop_index ON stop_times (stop_id);""")
 
  def GetStopBoundingBox(self):
  return (min(s.stop_lat for s in self.stops.values()),
  min(s.stop_lon for s in self.stops.values()),
  max(s.stop_lat for s in self.stops.values()),
  max(s.stop_lon for s in self.stops.values()),
  )
 
  def AddAgency(self, name, url, timezone, agency_id=None):
  """Adds an agency to this schedule."""
  agency = self._gtfs_factory.Agency(name, url, timezone, agency_id)
  self.AddAgencyObject(agency)
  return agency
 
  def AddAgencyObject(self, agency, problem_reporter=None, validate=False):
  assert agency._schedule is None
 
  if not problem_reporter:
  problem_reporter = self.problem_reporter
 
  if agency.agency_id in self._agencies:
  problem_reporter.DuplicateID('agency_id', agency.agency_id)
  return
 
  self.AddTableColumns('agency', agency._ColumnNames())
  agency._schedule = weakref.proxy(self)
 
  if validate:
  agency.Validate(problem_reporter)
  self._agencies[agency.agency_id] = agency
 
  def GetAgency(self, agency_id):
  """Return Agency with agency_id or throw a KeyError"""
  return self._agencies[agency_id]
 
  def GetDefaultAgency(self):
  """Return the default Agency. If no default Agency has been set select the
  default depending on how many Agency objects are in the Schedule. If there
  are 0 make a new Agency the default, if there is 1 it becomes the default,
  if there is more than 1 then return None.
  """
  if not self._default_agency:
  if len(self._agencies) == 0:
  self.NewDefaultAgency()
  elif len(self._agencies) == 1:
  self._default_agency = self._agencies.values()[0]
  return self._default_agency
 
  def NewDefaultAgency(self, **kwargs):
  """Create a new Agency object and make it the default agency for this Schedule"""
  agency = self._gtfs_factory.Agency(**kwargs)
  if not agency.agency_id:
  agency.agency_id = util.FindUniqueId(self._agencies)
  self._default_agency = agency
  self.SetDefaultAgency(agency, validate=False) # Blank agency won't validate
  return agency
 
  def SetDefaultAgency(self, agency, validate=True):
  """Make agency the default and add it to the schedule if not already added"""
  assert isinstance(agency, self._gtfs_factory.Agency)
  self._default_agency = agency
  if agency.agency_id not in self._agencies:
  self.AddAgencyObject(agency, validate=validate)
 
  def GetAgencyList(self):
  """Returns the list of Agency objects known to this Schedule."""
  return self._agencies.values()
 
  def GetServicePeriod(self, service_id):
  """Returns the ServicePeriod object with the given ID."""
  return self.service_periods[service_id]
 
  def GetDefaultServicePeriod(self):
  """Return the default ServicePeriod. If no default ServicePeriod has been
  set select the default depending on how many ServicePeriod objects are in
  the Schedule. If there are 0 make a new ServicePeriod the default, if there
  is 1 it becomes the default, if there is more than 1 then return None.
  """
  if not self._default_service_period:
  if len(self.service_periods) == 0:
  self.NewDefaultServicePeriod()
  elif len(self.service_periods) == 1:
  self._default_service_period = self.service_periods.values()[0]
  return self._default_service_period
 
  def NewDefaultServicePeriod(self):
  """Create a new ServicePeriod object, make it the default service period and
  return it. The default service period is used when you create a trip without
  providing an explict service period. """
  service_period = self._gtfs_factory.ServicePeriod()
  service_period.service_id = util.FindUniqueId(self.service_periods)
  # blank service won't validate in AddServicePeriodObject
  self.SetDefaultServicePeriod(service_period, validate=False)
  return service_period
 
  def SetDefaultServicePeriod(self, service_period, validate=True):
  assert isinstance(service_period, self._gtfs_factory.ServicePeriod)
  self._default_service_period = service_period
  if service_period.service_id not in self.service_periods:
  self.AddServicePeriodObject(service_period, validate=validate)
 
  def AddServicePeriodObject(self, service_period, problem_reporter=None,
  validate=True):
  if not problem_reporter:
  problem_reporter = self.problem_reporter
 
  if service_period.service_id in self.service_periods:
  problem_reporter.DuplicateID('service_id', service_period.service_id)
  return
 
  if validate:
  service_period.Validate(problem_reporter)
  self.service_periods[service_period.service_id] = service_period
 
  def GetServicePeriodList(self):
  return self.service_periods.values()
 
  def GetDateRange(self):
  """Returns a tuple of (earliest, latest) dates on which the service
  periods in the schedule define service, in YYYYMMDD form."""
 
  ranges = [period.GetDateRange() for period in self.GetServicePeriodList()]
  starts = filter(lambda x: x, [item[0] for item in ranges])
  ends = filter(lambda x: x, [item[1] for item in ranges])
 
  if not starts or not ends:
  return (None, None)
 
  return (min(starts), max(ends))
 
  def GetServicePeriodsActiveEachDate(self, date_start, date_end):
  """Return a list of tuples (date, [period1, period2, ...]).
 
  For each date in the range [date_start, date_end) make list of each
  ServicePeriod object which is active.
 
  Args:
  date_start: The first date in the list, a date object
  date_end: The first date after the list, a date object
 
  Returns:
  A list of tuples. Each tuple contains a date object and a list of zero or
  more ServicePeriod objects.
  """
  date_it = date_start
  one_day = datetime.timedelta(days=1)
  date_service_period_list = []
  while date_it < date_end:
  periods_today = []
  date_it_string = date_it.strftime("%Y%m%d")
  for service in self.GetServicePeriodList():
  if service.IsActiveOn(date_it_string, date_it):
  periods_today.append(service)
  date_service_period_list.append((date_it, periods_today))
  date_it += one_day
  return date_service_period_list
 
 
  def AddStop(self, lat, lng, name, stop_id=None):
  """Add a stop to this schedule.
 
  Args:
  lat: Latitude of the stop as a float or string
  lng: Longitude of the stop as a float or string
  name: Name of the stop, which will appear in the feed
  stop_id: stop_id of the stop or None, in which case a unique id is picked
 
  Returns:
  A new Stop object
  """
  if stop_id is None:
  stop_id = util.FindUniqueId(self.stops)
  stop = self._gtfs_factory.Stop(stop_id=stop_id, lat=lat, lng=lng, name=name)
  self.AddStopObject(stop)
  return stop
 
  def AddStopObject(self, stop, problem_reporter=None):
  """Add Stop object to this schedule if stop_id is non-blank."""
  assert stop._schedule is None
  if not problem_reporter:
  problem_reporter = self.problem_reporter
 
  if not stop.stop_id:
  return
 
  if stop.stop_id in self.stops:
  problem_reporter.DuplicateID('stop_id', stop.stop_id)
  return
 
  stop._schedule = weakref.proxy(self)
  self.AddTableColumns('stops', stop._ColumnNames())
  self.stops[stop.stop_id] = stop
  if hasattr(stop, 'zone_id') and stop.zone_id:
  self.fare_zones[stop.zone_id] = True
 
  def GetStopList(self):
  return self.stops.values()
 
  def AddRoute(self, short_name, long_name, route_type, route_id=None):
  """Add a route to this schedule.
 
  Args:
  short_name: Short name of the route, such as "71L"
  long_name: Full name of the route, such as "NW 21st Ave/St Helens Rd"
  route_type: A type such as "Tram", "Subway" or "Bus"
  route_id: id of the route or None, in which case a unique id is picked
  Returns:
  A new Route object
  """
  if route_id is None:
  route_id = util.FindUniqueId(self.routes)
  route = self._gtfs_factory.Route(short_name=short_name, long_name=long_name,
  route_type=route_type, route_id=route_id)
  route.agency_id = self.GetDefaultAgency().agency_id
  self.AddRouteObject(route)
  return route
 
  def AddRouteObject(self, route, problem_reporter=None):
  if not problem_reporter:
  problem_reporter = self.problem_reporter
 
  if route.route_id in self.routes:
  problem_reporter.DuplicateID('route_id', route.route_id)
  return
 
  if route.agency_id not in self._agencies:
  if not route.agency_id and len(self._agencies) == 1:
  # we'll just assume that the route applies to the only agency
  pass
  else:
  problem_reporter.InvalidValue('agency_id', route.agency_id,
  'Route uses an unknown agency_id.')
  return
 
  self.AddTableColumns('routes', route._ColumnNames())
  route._schedule = weakref.proxy(self)
  self.routes[route.route_id] = route
 
  def GetRouteList(self):
  return self.routes.values()
 
  def GetRoute(self, route_id):
  return self.routes[route_id]
 
  def AddShapeObject(self, shape, problem_reporter=None):
  if not problem_reporter:
  problem_reporter = self.problem_reporter
 
  shape.Validate(problem_reporter)
 
  if shape.shape_id in self._shapes:
  problem_reporter.DuplicateID('shape_id', shape.shape_id)
  return
 
  self._shapes[shape.shape_id] = shape
 
  def GetShapeList(self):
  return self._shapes.values()
 
  def GetShape(self, shape_id):
  return self._shapes[shape_id]
 
  def AddTripObject(self, trip, problem_reporter=None, validate=False):
  if not problem_reporter:
  problem_reporter = self.problem_reporter
 
  if trip.trip_id in self.trips:
  problem_reporter.DuplicateID('trip_id', trip.trip_id)
  return
 
  self.AddTableColumns('trips', trip._ColumnNames())
  trip._schedule = weakref.proxy(self)
  self.trips[trip.trip_id] = trip
 
  # Call Trip.Validate after setting trip._schedule so that references
  # are checked. trip.ValidateChildren will be called directly by
  # schedule.Validate, after stop_times has been loaded.
  if validate:
  if not problem_reporter:
  problem_reporter = self.problem_reporter
  trip.Validate(problem_reporter, validate_children=False)
  try:
  self.routes[trip.route_id]._AddTripObject(trip)
  except KeyError:
  # Invalid route_id was reported in the Trip.Validate call above
  pass
 
  def GetTripList(self):
  return self.trips.values()
 
  def GetTrip(self, trip_id):
  return self.trips[trip_id]
 
  def AddFareObject(self, fare, problem_reporter=None):
  """Deprecated. Please use AddFareAttributeObject."""
  warnings.warn("No longer supported. The Fare class was renamed to "
  "FareAttribute, and all related functions were renamed "
  "accordingly.", DeprecationWarning)
  self.AddFareAttributeObject(fare, problem_reporter)
 
  def AddFareAttributeObject(self, fare, problem_reporter=None):
  if not problem_reporter:
  problem_reporter = self.problem_reporter
  fare.Validate(problem_reporter)
 
  if fare.fare_id in self.fares:
  problem_reporter.DuplicateID('fare_id', fare.fare_id)
  return
 
  self.fares[fare.fare_id] = fare
 
  def GetFareList(self):
  """Deprecated. Please use GetFareAttributeList instead"""
  warnings.warn("No longer supported. The Fare class was renamed to "
  "FareAttribute, and all related functions were renamed "
  "accordingly.", DeprecationWarning)
  return self.GetFareAttributeList()
 
  def GetFareAttributeList(self):
  return self.fares.values()
 
  def GetFare(self, fare_id):
  """Deprecated. Please use GetFareAttribute instead"""
  warnings.warn("No longer supported. The Fare class was renamed to "
  "FareAttribute, and all related functions were renamed "
  "accordingly.", DeprecationWarning)
  return self.GetFareAttribute(fare_id)
 
  def GetFareAttribute(self, fare_id):
  return self.fares[fare_id]
 
  def AddFareRuleObject(self, rule, problem_reporter=None):
  if not problem_reporter:
  problem_reporter = self.problem_reporter
 
  if util.IsEmpty(rule.fare_id):
  problem_reporter.MissingValue('fare_id')
  return
 
  if rule.route_id and rule.route_id not in self.routes:
  problem_reporter.InvalidValue('route_id', rule.route_id)
  if rule.origin_id and rule.origin_id not in self.fare_zones:
  problem_reporter.InvalidValue('origin_id', rule.origin_id)
  if rule.destination_id and rule.destination_id not in self.fare_zones:
  problem_reporter.InvalidValue('destination_id', rule.destination_id)
  if rule.contains_id and rule.contains_id not in self.fare_zones:
  problem_reporter.InvalidValue('contains_id', rule.contains_id)
 
  if rule.fare_id in self.fares:
  self.GetFareAttribute(rule.fare_id).rules.append(rule)
  else:
  problem_reporter.InvalidValue('fare_id', rule.fare_id,
  '(This fare_id doesn\'t correspond to any '
  'of the IDs defined in the '
  'fare attributes.)')
 
  def AddTransferObject(self, transfer, problem_reporter=None):
  assert transfer._schedule is None, "only add Transfer to a schedule once"
  if not problem_reporter:
  problem_reporter = self.problem_reporter
 
  transfer_id = transfer._ID()
 
  if transfer_id in self._transfers:
  self.problem_reporter.DuplicateID(self._gtfs_factory.Transfer._ID_COLUMNS,
  transfer_id,
  type=problems_module.TYPE_WARNING)
  # Duplicates are still added, while not prohibited by GTFS.
 
  transfer._schedule = weakref.proxy(self) # See weakref comment at top
  self.AddTableColumns('transfers', transfer._ColumnNames())
  self._transfers[transfer_id].append(transfer)
 
  def GetTransferIter(self):
  """Return an iterator for all Transfer objects in this schedule."""
  return itertools.chain(*self._transfers.values())
 
  def GetTransferList(self):
  """Return a list containing all Transfer objects in this schedule."""
  return list(self.GetTransferIter())
 
  def GetStop(self, id):
  return self.stops[id]
 
  def GetFareZones(self):
  """Returns the list of all fare zones that have been identified by
  the stops that have been added."""
  return self.fare_zones.keys()
 
  def GetNearestStops(self, lat, lon, n=1):
  """Return the n nearest stops to lat,lon"""
  dist_stop_list = []
  for s in self.stops.values():
  # TODO: Use util.ApproximateDistanceBetweenStops?
  dist = (s.stop_lat - lat)**2 + (s.stop_lon - lon)**2
  if len(dist_stop_list) < n:
  bisect.insort(dist_stop_list, (dist, s))
  elif dist < dist_stop_list[-1][0]:
  bisect.insort(dist_stop_list, (dist, s))
  dist_stop_list.pop() # Remove stop with greatest distance
  return [stop for dist, stop in dist_stop_list]
 
  def GetStopsInBoundingBox(self, north, east, south, west, n):
  """Return a sample of up to n stops in a bounding box"""
  stop_list = []
  for s in self.stops.values():
  if (s.stop_lat <= north and s.stop_lat >= south and
  s.stop_lon <= east and s.stop_lon >= west):
  stop_list.append(s)
  if len(stop_list) == n:
  break
  return stop_list
 
  def Load(self, feed_path, extra_validation=False):
  loader = self._gtfs_factory.Loader(feed_path,
  self, problems=self.problem_reporter,
  extra_validation=extra_validation)
  loader.Load()
 
  def _WriteArchiveString(self, archive, filename, stringio):
  zi = zipfile.ZipInfo(filename)
  # See
  # http://stackoverflow.com/questions/434641/how-do-i-set-permissions-attributes-on-a-file-in-a-zip-file-using-pythons-zipf
  zi.external_attr = 0666 << 16L # Set unix permissions to -rw-rw-rw
  # ZIP_DEFLATED requires zlib. zlib comes with Python 2.4 and 2.5
  zi.compress_type = zipfile.ZIP_DEFLATED
  archive.writestr(zi, stringio.getvalue())
 
  def WriteGoogleTransitFeed(self, file):
  """Output this schedule as a Google Transit Feed in file_name.
 
  Args:
  file: path of new feed file (a string) or a file-like object
 
  Returns:
  None
  """
  # Compression type given when adding each file
  archive = zipfile.ZipFile(file, 'w')
 
  if 'agency' in self._table_columns:
  agency_string = StringIO.StringIO()
  writer = util.CsvUnicodeWriter(agency_string)
  columns = self.GetTableColumns('agency')
  writer.writerow(columns)
  for a in self._agencies.values():
  writer.writerow([util.EncodeUnicode(a[c]) for c in columns])
  self._WriteArchiveString(archive, 'agency.txt', agency_string)
 
  calendar_dates_string = StringIO.StringIO()
  writer = util.CsvUnicodeWriter(calendar_dates_string)
  writer.writerow(
  self._gtfs_factory.ServicePeriod._FIELD_NAMES_CALENDAR_DATES)
  has_data = False
  for period in self.service_periods.values():
  for row in period.GenerateCalendarDatesFieldValuesTuples():
  has_data = True
  writer.writerow(row)
  wrote_calendar_dates = False
  if has_data:
  wrote_calendar_dates = True
  self._WriteArchiveString(archive, 'calendar_dates.txt',
  calendar_dates_string)
 
  calendar_string = StringIO.StringIO()
  writer = util.CsvUnicodeWriter(calendar_string)
  writer.writerow(self._gtfs_factory.ServicePeriod._FIELD_NAMES)
  has_data = False
  for s in self.service_periods.values():
  row = s.GetCalendarFieldValuesTuple()
  if row:
  has_data = True
  writer.writerow(row)
  if has_data or not wrote_calendar_dates:
  self._WriteArchiveString(archive, 'calendar.txt', calendar_string)
 
  if 'stops' in self._table_columns:
  stop_string = StringIO.StringIO()
  writer = util.CsvUnicodeWriter(stop_string)
  columns = self.GetTableColumns('stops')
  writer.writerow(columns)
  for s in self.stops.values():
  writer.writerow([util.EncodeUnicode(s[c]) for c in columns])
  self._WriteArchiveString(archive, 'stops.txt', stop_string)
 
  if 'routes' in self._table_columns:
  route_string = StringIO.StringIO()
  writer = util.CsvUnicodeWriter(route_string)
  columns = self.GetTableColumns('routes')
  writer.writerow(columns)
  for r in self.routes.values():
  writer.writerow([util.EncodeUnicode(r[c]) for c in columns])
  self._WriteArchiveString(archive, 'routes.txt', route_string)
 
  if 'trips' in self._table_columns:
  trips_string = StringIO.StringIO()
  writer = util.CsvUnicodeWriter(trips_string)
  columns = self.GetTableColumns('trips')
  writer.writerow(columns)
  for t in self.trips.values():
  writer.writerow([util.EncodeUnicode(t[c]) for c in columns])
  self._WriteArchiveString(archive, 'trips.txt', trips_string)
 
  # write frequencies.txt (if applicable)
  headway_rows = []
  for trip in self.GetTripList():
  headway_rows += trip.GetFrequencyOutputTuples()
  if headway_rows:
  headway_string = StringIO.StringIO()
  writer = util.CsvUnicodeWriter(headway_string)
  writer.writerow(self._gtfs_factory.Frequency._FIELD_NAMES)
  writer.writerows(headway_rows)
  self._WriteArchiveString(archive, 'frequencies.txt', headway_string)
 
  # write fares (if applicable)
  if self.GetFareAttributeList():
  fare_string = StringIO.StringIO()
  writer = util.CsvUnicodeWriter(fare_string)
  writer.writerow(self._gtfs_factory.FareAttribute._FIELD_NAMES)
  writer.writerows(
  f.GetFieldValuesTuple() for f in self.GetFareAttributeList())
  self._WriteArchiveString(archive, 'fare_attributes.txt', fare_string)
 
  # write fare rules (if applicable)
  rule_rows = []
  for fare in self.GetFareAttributeList():
  for rule in fare.GetFareRuleList():
  rule_rows.append(rule.GetFieldValuesTuple())
  if rule_rows:
  rule_string = StringIO.StringIO()
  writer = util.CsvUnicodeWriter(rule_string)
  writer.writerow(self._gtfs_factory.FareRule._FIELD_NAMES)
  writer.writerows(rule_rows)
  self._WriteArchiveString(archive, 'fare_rules.txt', rule_string)
  stop_times_string = StringIO.StringIO()
  writer = util.CsvUnicodeWriter(stop_times_string)
  writer.writerow(self._gtfs_factory.StopTime._FIELD_NAMES)
  for t in self.trips.values():
  writer.writerows(t._GenerateStopTimesTuples())
  self._WriteArchiveString(archive, 'stop_times.txt', stop_times_string)
 
  # write shapes (if applicable)
  shape_rows = []
  for shape in self.GetShapeList():
  seq = 1
  for (lat, lon, dist) in shape.points:
  shape_rows.append((shape.shape_id, lat, lon, seq, dist))
  seq += 1
  if shape_rows:
  shape_string = StringIO.StringIO()
  writer = util.CsvUnicodeWriter(shape_string)
  writer.writerow(self._gtfs_factory.Shape._FIELD_NAMES)
  writer.writerows(shape_rows)
  self._WriteArchiveString(archive, 'shapes.txt', shape_string)
 
  if 'transfers' in self._table_columns:
  transfer_string = StringIO.StringIO()
  writer = util.CsvUnicodeWriter(transfer_string)
  columns = self.GetTableColumns('transfers')
  writer.writerow(columns)
  for t in self.GetTransferIter():
  writer.writerow([util.EncodeUnicode(t[c]) for c in columns])
  self._WriteArchiveString(archive, 'transfers.txt', transfer_string)
 
  archive.close()
 
  def GenerateDateTripsDeparturesList(self, date_start, date_end):
  """Return a list of (date object, number of trips, number of departures).
 
  The list is generated for dates in the range [date_start, date_end).
 
  Args:
  date_start: The first date in the list, a date object
  date_end: The first date after the list, a date object
 
  Returns:
  a list of (date object, number of trips, number of departures) tuples
  """
 
  service_id_to_trips = defaultdict(lambda: 0)
  service_id_to_departures = defaultdict(lambda: 0)
  for trip in self.GetTripList():
  headway_start_times = trip.GetFrequencyStartTimes()
  if headway_start_times:
  trip_runs = len(headway_start_times)
  else:
  trip_runs = 1
 
  service_id_to_trips[trip.service_id] += trip_runs
  service_id_to_departures[trip.service_id] += (
  (trip.GetCountStopTimes() - 1) * trip_runs)
 
  date_services = self.GetServicePeriodsActiveEachDate(date_start, date_end)
  date_trips = []
 
  for date, services in date_services:
  day_trips = sum(service_id_to_trips[s.service_id] for s in services)
  day_departures = sum(
  service_id_to_departures[s.service_id] for s in services)
  date_trips.append((date, day_trips, day_departures))
  return date_trips
 
  def ValidateFeedStartAndExpirationDates(self,
  problems,
  first_date,
  last_date,
  today):
  """Validate the start and expiration dates of the feed.
  Issue a warning if it only starts in the future, or if
  it expires within 60 days.
 
  Args:
  problems: The problem reporter object
  first_date: A date object representing the first day the feed is active
  last_date: A date object representing the last day the feed is active
  today: A date object representing the date the validation is being run on
 
  Returns:
  None
  """
  warning_cutoff = today + datetime.timedelta(days=60)
  if last_date < warning_cutoff:
  problems.ExpirationDate(time.mktime(last_date.timetuple()))
 
  if first_date > today:
  problems.FutureService(time.mktime(first_date.timetuple()))
 
  def ValidateServiceGaps(self,
  problems,
  validation_start_date,
  validation_end_date,
  service_gap_interval):
  """Validate consecutive dates without service in the feed.
  Issue a warning if it finds service gaps of at least
  "service_gap_interval" consecutive days in the date range
  [validation_start_date, last_service_date)
 
  Args:
  problems: The problem reporter object
  validation_start_date: A date object representing the date from which the
  validation should take place
  validation_end_date: A date object representing the first day the feed is
  active
  service_gap_interval: An integer indicating how many consecutive days the
  service gaps need to have for a warning to be issued
 
  Returns:
  None
  """
  if service_gap_interval is None:
  return
 
  departures = self.GenerateDateTripsDeparturesList(validation_start_date,
  validation_end_date)
 
  # The first day without service of the _current_ gap
  first_day_without_service = validation_start_date
  # The last day without service of the _current_ gap
  last_day_without_service = validation_start_date
 
  consecutive_days_without_service = 0
 
  for day_date, day_trips, _ in departures:
  if day_trips == 0:
  if consecutive_days_without_service == 0:
  first_day_without_service = day_date
  consecutive_days_without_service += 1
  last_day_without_service = day_date
  else:
  if consecutive_days_without_service >= service_gap_interval:
  problems.TooManyDaysWithoutService(first_day_without_service,
  last_day_without_service,
  consecutive_days_without_service)
 
  consecutive_days_without_service = 0
 
  # We have to check if there is a gap at the end of the specified date range
  if consecutive_days_without_service >= service_gap_interval:
  problems.TooManyDaysWithoutService(first_day_without_service,
  last_day_without_service,
  consecutive_days_without_service)
 
  def ValidateServiceExceptions(self,
  problems,
  first_service_day,
  last_service_day):
  # good enough approximation
  six_months = datetime.timedelta(days=182)
  service_span = last_service_day - first_service_day
  if service_span < six_months:
  # We don't check for exceptions because the feed is
  # active for less than six months
  return
 
  for period in self.GetServicePeriodList():
  # If at least one ServicePeriod has service exceptions we don't issue the
  # warning, so we can stop looking at the list of ServicePeriods.
  if period.HasExceptions():
  return
  problems.NoServiceExceptions(start=first_service_day,
  end=last_service_day)
 
  def ValidateServiceRangeAndExceptions(self, problems, today,
  service_gap_interval):
  if today is None:
  today = datetime.date.today()
  (start_date, end_date) = self.GetDateRange()
  if not end_date or not start_date:
  problems.OtherProblem('This feed has no effective service dates!',
  type=problems_module.TYPE_WARNING)
  else:
  try:
  last_service_day = datetime.datetime(
  *(time.strptime(end_date, "%Y%m%d")[0:6])).date()
  first_service_day = datetime.datetime(
  *(time.strptime(start_date, "%Y%m%d")[0:6])).date()
 
  except ValueError:
  # Format of start_date and end_date checked in class ServicePeriod
  pass
 
  else:
  self.ValidateServiceExceptions(problems,
  first_service_day,
  last_service_day)
  self.ValidateFeedStartAndExpirationDates(problems,
  first_service_day,
  last_service_day,
  today)
 
  # We start checking for service gaps a bit in the past if the
  # feed was active then. See
  # http://code.google.com/p/googletransitdatafeed/issues/detail?id=188
  #
  # We subtract 1 from service_gap_interval so that if today has
  # service no warning is issued.
  #
  # Service gaps are searched for only up to one year from today
  if service_gap_interval is not None:
  service_gap_timedelta = datetime.timedelta(
  days=service_gap_interval - 1)
  one_year = datetime.timedelta(days=365)
  self.ValidateServiceGaps(
  problems,
  max(first_service_day,
  today - service_gap_timedelta),
  min(last_service_day,
  today + one_year),
  service_gap_interval)
 
  def ValidateStops(self, problems, validate_children):
  # Check for stops that aren't referenced by any trips and broken
  # parent_station references. Also check that the parent station isn't too
  # far from its child stops.
  for stop in self.stops.values():
  if validate_children:
  stop.Validate(problems)
  cursor = self._connection.cursor()
  cursor.execute("SELECT count(*) FROM stop_times WHERE stop_id=? LIMIT 1",
  (stop.stop_id,))
  count = cursor.fetchone()[0]
  if stop.location_type == 0 and count == 0:
  problems.UnusedStop(stop.stop_id, stop.stop_name)
  elif stop.location_type == 1 and count != 0:
  problems.UsedStation(stop.stop_id, stop.stop_name)
 
  if stop.location_type != 1 and stop.parent_station:
  if stop.parent_station not in self.stops:
  problems.InvalidValue("parent_station",
  util.EncodeUnicode(stop.parent_station),
  "parent_station '%s' not found for stop_id "
  "'%s' in stops.txt" %
  (util.EncodeUnicode(stop.parent_station),
  util.EncodeUnicode(stop.stop_id)))
  elif self.stops[stop.parent_station].location_type != 1:
  problems.InvalidValue("parent_station",
  util.EncodeUnicode(stop.parent_station),
  "parent_station '%s' of stop_id '%s' must "
  "have location_type=1 in stops.txt" %
  (util.EncodeUnicode(stop.parent_station),
  util.EncodeUnicode(stop.stop_id)))
  else:
  parent_station = self.stops[stop.parent_station]
  distance = util.ApproximateDistanceBetweenStops(stop, parent_station)
  if distance > problems_module.MAX_DISTANCE_BETWEEN_STOP_AND_PARENT_STATION_ERROR:
  problems.StopTooFarFromParentStation(
  stop.stop_id, stop.stop_name, parent_station.stop_id,
  parent_station.stop_name, distance, problems_module.TYPE_ERROR)
  elif distance > problems_module.MAX_DISTANCE_BETWEEN_STOP_AND_PARENT_STATION_WARNING:
  problems.StopTooFarFromParentStation(
  stop.stop_id, stop.stop_name, parent_station.stop_id,
  parent_station.stop_name, distance,
  problems_module.TYPE_WARNING)
 
  def ValidateNearbyStops(self, problems):
  # Check for stops that might represent the same location (specifically,
  # stops that are less that 2 meters apart) First filter out stops without a
  # valid lat and lon. Then sort by latitude, then find the distance between
  # each pair of stations within 2 meters latitude of each other. This avoids
  # doing n^2 comparisons in the average case and doesn't need a spatial
  # index.
  sorted_stops = filter(lambda s: s.stop_lat and s.stop_lon,
  self.GetStopList())
  sorted_stops.sort(key=(lambda x: x.stop_lat))
  TWO_METERS_LAT = 0.000018
  for index, stop in enumerate(sorted_stops[:-1]):
  index += 1
  while ((index < len(sorted_stops)) and
  ((sorted_stops[index].stop_lat - stop.stop_lat) < TWO_METERS_LAT)):
  distance = util.ApproximateDistanceBetweenStops(stop,
  sorted_stops[index])
  if distance < 2:
  other_stop = sorted_stops[index]
  if stop.location_type == 0 and other_stop.location_type == 0:
  problems.StopsTooClose(
  util.EncodeUnicode(stop.stop_name),
  util.EncodeUnicode(stop.stop_id),
  util.EncodeUnicode(other_stop.stop_name),
  util.EncodeUnicode(other_stop.stop_id), distance)
  elif stop.location_type == 1 and other_stop.location_type == 1:
  problems.StationsTooClose(
  util.EncodeUnicode(stop.stop_name),
  util.EncodeUnicode(stop.stop_id),
  util.EncodeUnicode(other_stop.stop_name),
  util.EncodeUnicode(other_stop.stop_id), distance)
  elif (stop.location_type in (0, 1) and
  other_stop.location_type in (0, 1)):
  if stop.location_type == 0 and other_stop.location_type == 1:
  this_stop = stop
  this_station = other_stop
  elif stop.location_type == 1 and other_stop.location_type == 0:
  this_stop = other_stop
  this_station = stop
  if this_stop.parent_station != this_station.stop_id:
  problems.DifferentStationTooClose(
  util.EncodeUnicode(this_stop.stop_name),
  util.EncodeUnicode(this_stop.stop_id),
  util.EncodeUnicode(this_station.stop_name),
  util.EncodeUnicode(this_station.stop_id), distance)
  index += 1
 
  def ValidateRouteNames(self, problems, validate_children):
  # Check for multiple routes using same short + long name
  route_names = {}
  for route in self.routes.values():
  if validate_children:
  route.Validate(problems)
  short_name = ''
  if not util.IsEmpty(route.route_short_name):
  short_name = route.route_short_name.lower().strip()
  long_name = ''
  if not util.IsEmpty(route.route_long_name):
  long_name = route.route_long_name.lower().strip()
  name = (short_name, long_name)
  if name in route_names:
  problems.InvalidValue('route_long_name',
  long_name,
  'The same combination of '
  'route_short_name and route_long_name '
  'shouldn\'t be used for more than one '
  'route, as it is for the for the two routes '
  'with IDs "%s" and "%s".' %
  (route.route_id, route_names[name].route_id),
  type=problems_module.TYPE_WARNING)
  else:
  route_names[name] = route
 
  def ValidateTrips(self, problems):
  stop_types = {} # a dict mapping stop_id to [route_id, route_type, is_match]
  trips = {} # a dict mapping tuple to (route_id, trip_id)
 
  # a dict mapping block_id to a list of tuple of
  # (trip_id, first_arrival_secs, last_arrival_secs)
  trip_intervals_by_block_id = defaultdict(lambda: [])
 
  for trip in sorted(self.trips.values()):
  if trip.route_id not in self.routes:
  continue
  route_type = self.GetRoute(trip.route_id).route_type
  stop_ids = []
  stop_times = trip.GetStopTimes(problems)
  for index, st in enumerate(stop_times):
  stop_id = st.stop.stop_id
  stop_ids.append(stop_id)
  # Check a stop if which belongs to both subway and bus.
  if (route_type == self._gtfs_factory.Route._ROUTE_TYPE_NAMES['Subway'] or
  route_type == self._gtfs_factory.Route._ROUTE_TYPE_NAMES['Bus']):
  if stop_id not in stop_types:
  stop_types[stop_id] = [trip.route_id, route_type, 0]
  elif (stop_types[stop_id][1] != route_type and
  stop_types[stop_id][2] == 0):
  stop_types[stop_id][2] = 1
  if stop_types[stop_id][1] == \
  self._gtfs_factory.Route._ROUTE_TYPE_NAMES['Subway']:
  subway_route_id = stop_types[stop_id][0]
  bus_route_id = trip.route_id
  else:
  subway_route_id = trip.route_id
  bus_route_id = stop_types[stop_id][0]
  problems.StopWithMultipleRouteTypes(st.stop.stop_name, stop_id,
  subway_route_id, bus_route_id)
 
  # We only care about trips with a block id
  if not util.IsEmpty(trip.block_id) and stop_times:
 
  first_arrival_secs = stop_times[0].arrival_secs
  last_departure_secs = stop_times[-1].departure_secs
 
  # The arrival and departure time of the first and last stop_time
  # SHOULD be set, but we need to handle the case where we're given
  # an invalid feed anyway
  if first_arrival_secs is not None and last_departure_secs is not None:
 
  # Create a trip interval tuple of the trip id and arrival time
  # intervals
  key = trip.block_id
  trip_intervals = trip_intervals_by_block_id[key]
  trip_interval = (trip, first_arrival_secs, last_departure_secs)
  trip_intervals.append(trip_interval)
 
  # Check duplicate trips which go through the same stops with same
  # service and start times.
  if self._check_duplicate_trips:
  if not stop_ids or not stop_times:
  continue
  key = (trip.service_id, stop_times[0].arrival_time, str(stop_ids))
  if key not in trips:
  trips[key] = (trip.route_id, trip.trip_id)
  else:
  problems.DuplicateTrip(trips[key][1], trips[key][0], trip.trip_id,
  trip.route_id)
 
  # Now that we've generated our block trip intervls, we can check for
  # overlaps in the intervals
  self.ValidateBlocks(problems, trip_intervals_by_block_id)
 
  def ValidateBlocks(self, problems, trip_intervals_by_block_id):
  # Expects trip_intervals_by_block_id to be a dict with a key of block ids
  # and a value of lists of tuples
  # (trip, min_arrival_secs, max_departure_secs)
 
  # Cache potentially expensive ServicePeriod overlap checks
  service_period_overlap_cache = {}
 
  for (block_id,trip_intervals) in trip_intervals_by_block_id.items():
 
  # Sort trip intervals by min arrival time
  trip_intervals.sort(key=(lambda x: x[1]))
 
  for xi in range(len(trip_intervals)):
  trip_interval_a = trip_intervals[xi]
  trip_a = trip_interval_a[0]
 
  for xj in range(xi+1,len(trip_intervals)):
  trip_interval_b = trip_intervals[xj]
  trip_b = trip_interval_b[0]
 
  # If the last departure of trip interval A is less than or equal
  # to the first arrival of trip interval B, stop checking
  if trip_interval_a[2] <= trip_interval_b[1]:
  break
 
  # We have an overlap between the times in two trip intervals in
  # the same block. Potentially a problem...
 
  # If they have the same service id, the trips run on the same
  # day, yet have overlapping stop times. Definitely a problem.
  if trip_a.service_id == trip_b.service_id:
  problems.OverlappingTripsInSameBlock(trip_a.trip_id,
  trip_b.trip_id, block_id)
  else:
  # Even if the the trips don't have the same service_id, their
  # service dates might still overlap. Since the ServicePeriod
  # overlap check is potentially expensive, we cache the
  # computation
 
  service_id_pair_key = tuple(sorted([trip_a.service_id,
  trip_b.service_id]))
 
  # If the serivce_id_pair_key is not in the cache, we do the
  # full service period comparison
  if service_id_pair_key not in service_period_overlap_cache:
 
  service_period_a = self.GetServicePeriod(trip_a.service_id)
  service_period_b = self.GetServicePeriod(trip_b.service_id)
 
  dates_a = service_period_a.ActiveDates()
  dates_b = service_period_b.ActiveDates()
 
  overlap = False
 
  for date in dates_a:
  if date in dates_b:
  overlap = True
  break
 
  service_period_overlap_cache[service_id_pair_key] = overlap
 
  if service_period_overlap_cache[service_id_pair_key]:
  problems.OverlappingTripsInSameBlock(trip_a.trip_id,
  trip_b.trip_id,
  block_id)
 
  def ValidateRouteAgencyId(self, problems):
  # Check that routes' agency IDs are valid, if set
  for route in self.routes.values():
  if (not util.IsEmpty(route.agency_id) and
  not route.agency_id in self._agencies):
  problems.InvalidValue('agency_id',
  route.agency_id,
  'The route with ID "%s" specifies agency_id '
  '"%s", which doesn\'t exist.' %
  (route.route_id, route.agency_id))
 
  def ValidateTripStopTimes(self, problems):
  # Make sure all trips have stop_times
  # We're doing this here instead of in Trip.Validate() so that
  # Trips can be validated without error during the reading of trips.txt
  for trip in self.trips.values():
  trip.ValidateChildren(problems)
  count_stop_times = trip.GetCountStopTimes()
  if not count_stop_times:
  problems.OtherProblem('The trip with the trip_id "%s" doesn\'t have '
  'any stop times defined.' % trip.trip_id,
  type=problems_module.TYPE_WARNING)
  if len(trip._headways) > 0: # no stoptimes, but there are headways
  problems.OtherProblem('Frequencies defined, but no stop times given '
  'in trip %s' % trip.trip_id,
  type=problems_module.TYPE_ERROR)
  elif count_stop_times == 1:
  problems.OtherProblem('The trip with the trip_id "%s" only has one '
  'stop on it; it should have at least one more '
  'stop so that the riders can leave!' %
  trip.trip_id, type=problems_module.TYPE_WARNING)
  else:
  # These methods report InvalidValue if there's no first or last time
  trip.GetStartTime(problems=problems)
  trip.GetEndTime(problems=problems)
 
  def ValidateUnusedShapes(self, problems):
  # Check for unused shapes
  known_shape_ids = set(self._shapes.keys())
  used_shape_ids = set()
  for trip in self.GetTripList():
  used_shape_ids.add(trip.shape_id)
  unused_shape_ids = known_shape_ids - used_shape_ids
  if unused_shape_ids:
  problems.OtherProblem('The shapes with the following shape_ids aren\'t '
  'used by any trips: %s' %
  ', '.join(unused_shape_ids),
  type=problems_module.TYPE_WARNING)
 
  def Validate(self,
  problems=None,
  validate_children=True,
  today=None,
  service_gap_interval=None):
  """Validates various holistic aspects of the schedule
  (mostly interrelationships between the various data sets)."""
 
  if not problems:
  problems = self.problem_reporter
 
  self.ValidateServiceRangeAndExceptions(problems, today,
  service_gap_interval)
  # TODO: Check Trip fields against valid values
  self.ValidateStops(problems, validate_children)
  #TODO: check that every station is used.
  # Then uncomment testStationWithoutReference.
  self.ValidateNearbyStops(problems)
  self.ValidateRouteNames(problems, validate_children)
  self.ValidateTrips(problems)
  self.ValidateRouteAgencyId(problems)
  self.ValidateTripStopTimes(problems)
  self.ValidateUnusedShapes(problems)
 
  #!/usr/bin/python2.5
 
  # Copyright (C) 2007 Google Inc.
  #
  # Licensed under the Apache License, Version 2.0 (the "License");
  # you may not use this file except in compliance with the License.
  # You may obtain a copy of the License at
  #
  # http://www.apache.org/licenses/LICENSE-2.0
  #
  # Unless required by applicable law or agreed to in writing, software
  # distributed under the License is distributed on an "AS IS" BASIS,
  # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  # See the License for the specific language governing permissions and
  # limitations under the License.
 
  import datetime
  import re
  import time
 
  import problems as problems_module
  import util
 
  class ServicePeriod(object):
  """Represents a service, which identifies a set of dates when one or more
  trips operate."""
  _DAYS_OF_WEEK = [
  'monday', 'tuesday', 'wednesday', 'thursday', 'friday',
  'saturday', 'sunday'
  ]
  _FIELD_NAMES_REQUIRED = [
  'service_id', 'start_date', 'end_date'
  ] + _DAYS_OF_WEEK
  _FIELD_NAMES = _FIELD_NAMES_REQUIRED # no optional fields in this one
  _FIELD_NAMES_CALENDAR_DATES = ['service_id', 'date', 'exception_type']
 
  def __init__(self, id=None, field_list=None):
  self.original_day_values = []
  if field_list:
  self.service_id = field_list[self._FIELD_NAMES.index('service_id')]
  self.day_of_week = [False] * len(self._DAYS_OF_WEEK)
 
  for day in self._DAYS_OF_WEEK:
  value = field_list[self._FIELD_NAMES.index(day)] or '' # can be None
  self.original_day_values += [value.strip()]
  self.day_of_week[self._DAYS_OF_WEEK.index(day)] = (value == u'1')
 
  self.start_date = field_list[self._FIELD_NAMES.index('start_date')]
  self.end_date = field_list[self._FIELD_NAMES.index('end_date')]
  else:
  self.service_id = id
  self.day_of_week = [False] * 7
  self.start_date = None
  self.end_date = None
  self.date_exceptions = {} # Map from 'YYYYMMDD' to 1 (add) or 2 (remove)
 
  def _IsValidDate(self, date):
  if re.match('^\d{8}$', date) == None:
  return False
 
  try:
  time.strptime(date, "%Y%m%d")
  return True
  except ValueError:
  return False
 
  def HasExceptions(self):
  """Checks if the ServicePeriod has service exceptions."""
  if self.date_exceptions:
  return True
  else:
  return False
 
  def GetDateRange(self):
  """Return the range over which this ServicePeriod is valid.
 
  The range includes exception dates that add service outside of
  (start_date, end_date), but doesn't shrink the range if exception
  dates take away service at the edges of the range.
 
  Returns:
  A tuple of "YYYYMMDD" strings, (start date, end date) or (None, None) if
  no dates have been given.
  """
  start = self.start_date
  end = self.end_date
 
  for date in self.date_exceptions:
  if self.date_exceptions[date] == 2:
  continue
  if not start or (date < start):
  start = date
  if not end or (date > end):
  end = date
  if start is None:
  start = end
  elif end is None:
  end = start
  # If start and end are None we did a little harmless shuffling
  return (start, end)
 
  def GetCalendarFieldValuesTuple(self):
  """Return the tuple of calendar.txt values or None if this ServicePeriod
  should not be in calendar.txt ."""
  if self.start_date and self.end_date:
  return [getattr(self, fn) for fn in self._FIELD_NAMES]
 
  def GenerateCalendarDatesFieldValuesTuples(self):
  """Generates tuples of calendar_dates.txt values. Yield zero tuples if
  this ServicePeriod should not be in calendar_dates.txt ."""
  for date, exception_type in self.date_exceptions.items():
  yield (self.service_id, date, unicode(exception_type))
 
  def GetCalendarDatesFieldValuesTuples(self):
  """Return a list of date execeptions"""
  result = []
  for date_tuple in self.GenerateCalendarDatesFieldValuesTuples():
  result.append(date_tuple)
  result.sort() # helps with __eq__
  return result
 
  def SetDateHasService(self, date, has_service=True, problems=None):
  if date in self.date_exceptions and problems:
  problems.DuplicateID(('service_id', 'date'),
  (self.service_id, date),
  type=problems_module.TYPE_WARNING)
  self.date_exceptions[date] = has_service and 1 or 2
 
  def ResetDateToNormalService(self, date):
  if date in self.date_exceptions:
  del self.date_exceptions[date]
 
  def SetStartDate(self, start_date):
  """Set the first day of service as a string in YYYYMMDD format"""
  self.start_date = start_date
 
  def SetEndDate(self, end_date):
  """Set the last day of service as a string in YYYYMMDD format"""
  self.end_date = end_date
 
  def SetDayOfWeekHasService(self, dow, has_service=True):
  """Set service as running (or not) on a day of the week. By default the
  service does not run on any days.
 
  Args:
  dow: 0 for Monday through 6 for Sunday
  has_service: True if this service operates on dow, False if it does not.
 
  Returns:
  None
  """
  assert(dow >= 0 and dow < 7)
  self.day_of_week[dow] = has_service
 
  def SetWeekdayService(self, has_service=True):
  """Set service as running (or not) on all of Monday through Friday."""
  for i in range(0, 5):
  self.SetDayOfWeekHasService(i, has_service)
 
  def SetWeekendService(self, has_service=True):
  """Set service as running (or not) on Saturday and Sunday."""
  self.SetDayOfWeekHasService(5, has_service)
  self.SetDayOfWeekHasService(6, has_service)
 
  def SetServiceId(self, service_id):
  """Set the service_id for this schedule. Generally the default will
  suffice so you won't need to call this method."""
  self.service_id = service_id
 
  def IsActiveOn(self, date, date_object=None):
  """Test if this service period is active on a date.
 
  Args:
  date: a string of form "YYYYMMDD"
  date_object: a date object representing the same date as date.
  This parameter is optional, and present only for performance
  reasons.
  If the caller constructs the date string from a date object
  that date object can be passed directly, thus avoiding the
  costly conversion from string to date object.
 
  Returns:
  True iff this service is active on date.
  """
  if date in self.date_exceptions:
  if self.date_exceptions[date] == 1:
  return True
  else:
  return False
  if (self.start_date and self.end_date and self.start_date <= date and
  date <= self.end_date):
  if date_object is None:
  date_object = util.DateStringToDateObject(date)
  return self.day_of_week[date_object.weekday()]
  return False
 
  def ActiveDates(self):
  """Return dates this service period is active as a list of "YYYYMMDD"."""
  (earliest, latest) = self.GetDateRange()
  if earliest is None:
  return []
  dates = []
  date_it = util.DateStringToDateObject(earliest)
  date_end = util.DateStringToDateObject(latest)
  delta = datetime.timedelta(days=1)
  while date_it <= date_end:
  date_it_string = date_it.strftime("%Y%m%d")
  if self.IsActiveOn(date_it_string, date_it):
  dates.append(date_it_string)
  date_it = date_it + delta
  return dates
 
  def __getattr__(self, name):
  try:
  # Return 1 if value in day_of_week is True, 0 otherwise
  return (self.day_of_week[self._DAYS_OF_WEEK.index(name)]
  and 1 or 0)
  except KeyError:
  pass
  except ValueError: # not a day of the week
  pass
  raise AttributeError(name)
 
  def __getitem__(self, name):
  return getattr(self, name)
 
  def __eq__(self, other):
  if not other:
  return False
 
  if id(self) == id(other):
  return True
 
  if (self.GetCalendarFieldValuesTuple() !=
  other.GetCalendarFieldValuesTuple()):
  return False
 
  if (self.GetCalendarDatesFieldValuesTuples() !=
  other.GetCalendarDatesFieldValuesTuples()):
  return False
 
  return True
 
  def __ne__(self, other):
  return not self.__eq__(other)
 
  def ValidateServiceId(self, problems):
  if util.IsEmpty(self.service_id):
  problems.MissingValue('service_id')
 
  def ValidateStartDate(self, problems):
  if self.start_date is not None:
  if util.IsEmpty(self.start_date):
  problems.MissingValue('start_date')
  self.start_date = None
  elif not self._IsValidDate(self.start_date):
  problems.InvalidValue('start_date', self.start_date)
  self.start_date = None
 
  def ValidateEndDate(self, problems):
  if self.end_date is not None:
  if util.IsEmpty(self.end_date):
  problems.MissingValue('end_date')
  self.end_date = None
  elif not self._IsValidDate(self.end_date):
  problems.InvalidValue('end_date', self.end_date)
  self.end_date = None
 
  def ValidateEndDateAfterStartDate(self, problems):
  if self.start_date and self.end_date and self.end_date < self.start_date:
  problems.InvalidValue('end_date', self.end_date,
  'end_date of %s is earlier than '
  'start_date of "%s"' %
  (self.end_date, self.start_date))
 
  def ValidateDaysOfWeek(self, problems):
  if self.original_day_values:
  index = 0
  for value in self.original_day_values:
  column_name = self._DAYS_OF_WEEK[index]
  if util.IsEmpty(value):
  problems.MissingValue(column_name)
  elif (value != u'0') and (value != '1'):
  problems.InvalidValue(column_name, value)
  index += 1
 
  def ValidateHasServiceAtLeastOnceAWeek(self, problems):
  if (True not in self.day_of_week and
  1 not in self.date_exceptions.values()):
  problems.OtherProblem('Service period with service_id "%s" '
  'doesn\'t have service on any days '
  'of the week.' % self.service_id,
  type=problems_module.TYPE_WARNING)
 
  def ValidateDates(self, problems):
  for date in self.date_exceptions:
  if not self._IsValidDate(date):
  problems.InvalidValue('date', date)
 
  def Validate(self, problems=problems_module.default_problem_reporter):
 
  self.ValidateServiceId(problems)
 
  # self.start_date/self.end_date is None in 3 cases:
  # ServicePeriod created by loader and
  # 1a) self.service_id wasn't in calendar.txt
  # 1b) calendar.txt didn't have a start_date/end_date column
  # ServicePeriod created directly and
  # 2) start_date/end_date wasn't set
  # In case 1a no problem is reported. In case 1b the missing required column
  # generates an error in _ReadCSV so this method should not report another
  # problem. There is no way to tell the difference between cases 1b and 2
  # so case 2 is ignored because making the feedvalidator pretty is more
  # important than perfect validation when an API users makes a mistake.
  self.ValidateStartDate(problems)
  self.ValidateEndDate(problems)
 
  self.ValidateEndDateAfterStartDate(problems)
  self.ValidateDaysOfWeek(problems)
  self.ValidateHasServiceAtLeastOnceAWeek(problems)
  self.ValidateDates(problems)
 
  #!/usr/bin/python2.5
 
  # Copyright (C) 2007 Google Inc.
  #
  # Licensed under the Apache License, Version 2.0 (the "License");
  # you may not use this file except in compliance with the License.
  # You may obtain a copy of the License at
  #
  # http://www.apache.org/licenses/LICENSE-2.0
  #
  # Unless required by applicable law or agreed to in writing, software
  # distributed under the License is distributed on an "AS IS" BASIS,
  # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  # See the License for the specific language governing permissions and
  # limitations under the License.
 
  import bisect
 
  from gtfsfactoryuser import GtfsFactoryUser
  import problems as problems_module
  import util
 
  class Shape(GtfsFactoryUser):
  """This class represents a geographic shape that corresponds to the route
  taken by one or more Trips."""
  _REQUIRED_FIELD_NAMES = ['shape_id', 'shape_pt_lat', 'shape_pt_lon',
  'shape_pt_sequence']
  _FIELD_NAMES = _REQUIRED_FIELD_NAMES + ['shape_dist_traveled']
  def __init__(self, shape_id):
  # List of shape point tuple (lat, lng, shape_dist_traveled), where lat and
  # lon is the location of the shape point, and shape_dist_traveled is an
  # increasing metric representing the distance traveled along the shape.
  self.points = []
  # An ID that uniquely identifies a shape in the dataset.
  self.shape_id = shape_id
  # The max shape_dist_traveled of shape points in this shape.
  self.max_distance = 0
  # List of shape_dist_traveled of each shape point.
  self.distance = []
  # List of shape_pt_sequence of each shape point.
  self.sequence = []
 
  def AddPoint(self, lat, lon, distance=None,
  problems=problems_module.default_problem_reporter):
  shapepoint_class = self.GetGtfsFactory().ShapePoint
  shapepoint = shapepoint_class(
  self.shape_id, lat, lon, len(self.sequence), distance)
  if shapepoint.ParseAttributes(problems):
  self.AddShapePointObjectUnsorted(shapepoint, problems)
 
  def AddShapePointObjectUnsorted(self, shapepoint, problems):
  """Insert a point into a correct position by sequence. """
  if (len(self.sequence) == 0 or
  shapepoint.shape_pt_sequence >= self.sequence[-1]):
  index = len(self.sequence)
  elif shapepoint.shape_pt_sequence <= self.sequence[0]:
  index = 0
  else:
  index = bisect.bisect(self.sequence, shapepoint.shape_pt_sequence)
 
  if shapepoint.shape_pt_sequence in self.sequence:
  problems.InvalidValue('shape_pt_sequence', shapepoint.shape_pt_sequence,
  'The sequence number %d occurs more than once in '
  'shape %s.' %
  (shapepoint.shape_pt_sequence, self.shape_id))
 
  if shapepoint.shape_dist_traveled is not None and len(self.sequence) > 0:
  if (index != len(self.sequence) and
  shapepoint.shape_dist_traveled > self.distance[index]):
  problems.InvalidValue('shape_dist_traveled',
  shapepoint.shape_dist_traveled,
  'Each subsequent point in a shape should have '
  'a distance value that shouldn\'t be larger '
  'than the next ones. In this case, the next '
  'distance was %f.' % self.distance[index])
 
  if (index > 0 and
  shapepoint.shape_dist_traveled < self.distance[index - 1]):
  problems.InvalidValue('shape_dist_traveled',
  shapepoint.shape_dist_traveled,
  'Each subsequent point in a shape should have '
  'a distance value that\'s at least as large as '
  'the previous ones. In this case, the previous '
  'distance was %f.' % self.distance[index - 1])
 
  if shapepoint.shape_dist_traveled > self.max_distance:
  self.max_distance = shapepoint.shape_dist_traveled
 
  self.sequence.insert(index, shapepoint.shape_pt_sequence)
  self.distance.insert(index, shapepoint.shape_dist_traveled)
  self.points.insert(index, (shapepoint.shape_pt_lat,
  shapepoint.shape_pt_lon,
  shapepoint.shape_dist_traveled))
 
  def ClearPoints(self):
  self.points = []
 
  def __eq__(self, other):
  if not other:
  return False
 
  if id(self) == id(other):
  return True
 
  return self.points == other.points
 
  def __ne__(self, other):
  return not self.__eq__(other)
 
  def __repr__(self):
  return "<Shape %s>" % self.__dict__
 
  def ValidateShapeId(self, problems):
  if util.IsEmpty(self.shape_id):
  problems.MissingValue('shape_id')
 
  def ValidateShapePoints(self, problems):
  if not self.points:
  problems.OtherProblem('The shape with shape_id "%s" contains no points.' %
  self.shape_id, type=problems_module.TYPE_WARNING)
 
  def Validate(self, problems=problems_module.default_problem_reporter):
  self.ValidateShapeId(problems)
  self.ValidateShapePoints(problems)
 
  def GetPointWithDistanceTraveled(self, shape_dist_traveled):
  """Returns a point on the shape polyline with the input shape_dist_traveled.
 
  Args:
  shape_dist_traveled: The input shape_dist_traveled.
 
  Returns:
  The shape point as a tuple (lat, lng, shape_dist_traveled), where lat and
  lng is the location of the shape point, and shape_dist_traveled is an
  increasing metric representing the distance traveled along the shape.
  Returns None if there is data error in shape.
  """
  if not self.distance:
  return None
  if shape_dist_traveled <= self.distance[0]:
  return self.points[0]
  if shape_dist_traveled >= self.distance[-1]:
  return self.points[-1]
 
  index = bisect.bisect(self.distance, shape_dist_traveled)
  (lat0, lng0, dist0) = self.points[index - 1]
  (lat1, lng1, dist1) = self.points[index]
 
  # Interpolate if shape_dist_traveled does not equal to any of the point
  # in shape segment.
  # (lat0, lng0) (lat, lng) (lat1, lng1)
  # -----|--------------------|---------------------|------
  # dist0 shape_dist_traveled dist1
  # \------- ca --------/ \-------- bc -------/
  # \----------------- ba ------------------/
  ca = shape_dist_traveled - dist0
  bc = dist1 - shape_dist_traveled
  ba = bc + ca
  if ba == 0:
  # This only happens when there's data error in shapes and should have been
  # catched before. Check to avoid crash.
  return None
  # This won't work crossing longitude 180 and is only an approximation which
  # works well for short distance.
  lat = (lat1 * ca + lat0 * bc) / ba
  lng = (lng1 * ca + lng0 * bc) / ba
  return (lat, lng, shape_dist_traveled)
 
  #!/usr/bin/python2.4
  #
  # Copyright 2007 Google Inc. All Rights Reserved.
  #
  # Licensed under the Apache License, Version 2.0 (the "License");
  # you may not use this file except in compliance with the License.
  # You may obtain a copy of the License at
  #
  # http://www.apache.org/licenses/LICENSE-2.0
  #
  # Unless required by applicable law or agreed to in writing, software
  # distributed under the License is distributed on an "AS IS" BASIS,
  # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  # See the License for the specific language governing permissions and
  # limitations under the License.
 
  """A library for manipulating points and polylines.
 
  This is a library for creating and manipulating points on the unit
  sphere, as an approximate model of Earth. The primary use of this
  library is to make manipulation and matching of polylines easy in the
  transitfeed library.
 
  NOTE: in this library, Earth is modelled as a sphere, whereas
  GTFS specifies that latitudes and longitudes are in WGS84. For the
  purpose of comparing and matching latitudes and longitudes that
  are relatively close together on the surface of the earth, this
  is adequate; for other purposes, this library may not be accurate
  enough.
  """
 
  __author__ = 'chris.harrelson.code@gmail.com (Chris Harrelson)'
 
  import copy
  import decimal
  import heapq
  import math
 
  class ShapeError(Exception):
  """Thrown whenever there is a shape parsing error."""
  pass
 
 
  EARTH_RADIUS_METERS = 6371010.0
 
 
  class Point(object):
  """
  A class representing a point on the unit sphere in three dimensions.
  """
  def __init__(self, x, y, z):
  self.x = x
  self.y = y
  self.z = z
 
  def __hash__(self):
  return hash((self.x, self.y, self.z))
 
  def __cmp__(self, other):
  if not isinstance(other, Point):
  raise TypeError('Point.__cmp__(x,y) requires y to be a "Point", '
  'not a "%s"' % type(other).__name__)
  return cmp((self.x, self.y, self.z), (other.x, other.y, other.z))
 
  def __str__(self):
  return "(%.15f, %.15f, %.15f) " % (self.x, self.y, self.z)
 
  def Norm2(self):
  """
  Returns the L_2 (Euclidean) norm of self.
  """
  sum = self.x * self.x + self.y * self.y + self.z * self.z
  return math.sqrt(float(sum))
 
  def IsUnitLength(self):
  return abs(self.Norm2() - 1.0) < 1e-14
 
  def Plus(self, other):
  """
  Returns a new point which is the pointwise sum of self and other.
  """
  return Point(self.x + other.x,
  self.y + other.y,
  self.z + other.z)
 
  def Minus(self, other):
  """
  Returns a new point which is the pointwise subtraction of other from
  self.
  """
  return Point(self.x - other.x,
  self.y - other.y,
  self.z - other.z)
 
  def DotProd(self, other):
  """
  Returns the (scalar) dot product of self with other.
  """
  return self.x * other.x + self.y * other.y + self.z * other.z
 
  def Times(self, val):
  """
  Returns a new point which is pointwise multiplied by val.
  """
  return Point(self.x * val, self.y * val, self.z * val)
 
  def Normalize(self):
  """
  Returns a unit point in the same direction as self.
  """
  return self.Times(1 / self.Norm2())
 
  def RobustCrossProd(self, other):
  """
  A robust version of cross product. If self and other
  are not nearly the same point, returns the same value
  as CrossProd() modulo normalization. Otherwise returns
  an arbitrary unit point orthogonal to self.
  """
  assert(self.IsUnitLength() and other.IsUnitLength())
  x = self.Plus(other).CrossProd(other.Minus(self))
  if abs(x.x) > 1e-15 or abs(x.y) > 1e-15 or abs(x.z) > 1e-15:
  return x.Normalize()
  else:
  return self.Ortho()
 
  def LargestComponent(self):
  """
  Returns (i, val) where i is the component index (0 - 2)
  which has largest absolute value and val is the value
  of the component.
  """
  if abs(self.x) > abs(self.y):
  if abs(self.x) > abs(self.z):
  return (0, self.x)
  else:
  return (2, self.z)
  else:
  if abs(self.y) > abs(self.z):
  return (1, self.y)
  else:
  return (2, self.z)
 
  def Ortho(self):
  """Returns a unit-length point orthogonal to this point"""
  (index, val) = self.LargestComponent()
  index = index - 1
  if index < 0:
  index = 2
  temp = Point(0.012, 0.053, 0.00457)
  if index == 0:
  temp.x = 1
  elif index == 1:
  temp.y = 1
  elif index == 2:
  temp.z = 1
  return self.CrossProd(temp).Normalize()
 
  def CrossProd(self, other):
  """
  Returns the cross product of self and other.
  """
  return Point(
  self.y * other.z - self.z * other.y,
  self.z * other.x - self.x * other.z,
  self.x * other.y - self.y * other.x)
 
  @staticmethod
  def _approxEq(a, b):
  return abs(a - b) < 1e-11
 
  def Equals(self, other):
  """
  Returns true of self and other are approximately equal.
  """
  return (self._approxEq(self.x, other.x)
  and self._approxEq(self.y, other.y)
  and self._approxEq(self.z, other.z))
 
  def Angle(self, other):
  """
  Returns the angle in radians between self and other.
  """
  return math.atan2(self.CrossProd(other).Norm2(),
  self.DotProd(other))
 
  def ToLatLng(self):
  """
  Returns that latitude and longitude that this point represents
  under a spherical Earth model.
  """
  rad_lat = math.atan2(self.z, math.sqrt(self.x * self.x + self.y * self.y))
  rad_lng = math.atan2(self.y, self.x)
  return (rad_lat * 180.0 / math.pi, rad_lng * 180.0 / math.pi)
 
  @staticmethod
  def FromLatLng(lat, lng):
  """
  Returns a new point representing this latitude and longitude under
  a spherical Earth model.
  """
  phi = lat * (math.pi / 180.0)
  theta = lng * (math.pi / 180.0)
  cosphi = math.cos(phi)
  return Point(math.cos(theta) * cosphi,
  math.sin(theta) * cosphi,
  math.sin(phi))
 
  def GetDistanceMeters(self, other):
  assert(self.IsUnitLength() and other.IsUnitLength())
  return self.Angle(other) * EARTH_RADIUS_METERS
 
 
  def SimpleCCW(a, b, c):
  """
  Returns true if the triangle abc is oriented counterclockwise.
  """
  return c.CrossProd(a).DotProd(b) > 0
 
  def GetClosestPoint(x, a, b):
  """
  Returns the point on the great circle segment ab closest to x.
  """
  assert(x.IsUnitLength())
  assert(a.IsUnitLength())
  assert(b.IsUnitLength())
 
  a_cross_b = a.RobustCrossProd(b)
  # project to the great circle going through a and b
  p = x.Minus(
  a_cross_b.Times(
  x.DotProd(a_cross_b) / a_cross_b.Norm2()))
 
  # if p lies between a and b, return it
  if SimpleCCW(a_cross_b, a, p) and SimpleCCW(p, b, a_cross_b):
  return p.Normalize()
 
  # otherwise return the closer of a or b
  if x.Minus(a).Norm2() <= x.Minus(b).Norm2():
  return a
  else:
  return b
 
 
  class Poly(object):
  """
  A class representing a polyline.
  """
  def __init__(self, points = [], name=None):
  self._points = list(points)
  self._name = name
 
  def AddPoint(self, p):
  """
  Adds a new point to the end of the polyline.
  """
  assert(p.IsUnitLength())
  self._points.append(p)
 
  def GetName(self):
  return self._name
 
  def GetPoint(self, i):
  return self._points[i]
 
  def GetPoints(self):
  return self._points
 
  def GetNumPoints(self):
  return len(self._points)
 
  def _GetPointSafe(self, i):
  try:
  return self.GetPoint(i)
  except IndexError:
  return None
 
  def GetClosestPoint(self, p):
  """
  Returns (closest_p, closest_i), where closest_p is the closest point
  to p on the piecewise linear curve represented by the polyline,
  and closest_i is the index of the point on the polyline just before
  the polyline segment that contains closest_p.
  """
  assert(len(self._points) > 0)
  closest_point = self._points[0]
  closest_i = 0
 
  for i in range(0, len(self._points) - 1):
  (a, b) = (self._points[i], self._points[i+1])
  cur_closest_point = GetClosestPoint(p, a, b)
  if p.Angle(cur_closest_point) < p.Angle(closest_point):
  closest_point = cur_closest_point.Normalize()
  closest_i = i
 
  return (closest_point, closest_i)
 
  def LengthMeters(self):
  """Return length of this polyline in meters."""
  assert(len(self._points) > 0)
  length = 0
  for i in range(0, len(self._points) - 1):
  length += self._points[i].GetDistanceMeters(self._points[i+1])
  return length
 
  def Reversed(self):
  """Return a polyline that is the reverse of this polyline."""
  return Poly(reversed(self.GetPoints()), self.GetName())
 
  def CutAtClosestPoint(self, p):
  """
  Let x be the point on the polyline closest to p. Then
  CutAtClosestPoint returns two new polylines, one representing
  the polyline from the beginning up to x, and one representing
  x onwards to the end of the polyline. x is the first point
  returned in the second polyline.
  """
  (closest, i) = self.GetClosestPoint(p)
 
  tmp = [closest]
  tmp.extend(self._points[i+1:])
  return (Poly(self._points[0:i+1]),
  Poly(tmp))
 
  def GreedyPolyMatchDist(self, shape):
  """
  Tries a greedy matching algorithm to match self to the
  given shape. Returns the maximum distance in meters of
  any point in self to its matched point in shape under the
  algorithm.
 
  Args: shape, a Poly object.
  """
  tmp_shape = Poly(shape.GetPoints())
  max_radius = 0
  for (i, point) in enumerate(self._points):
  tmp_shape = tmp_shape.CutAtClosestPoint(point)[1]
  dist = tmp_shape.GetPoint(0).GetDistanceMeters(point)
  max_radius = max(max_radius, dist)
  return max_radius
 
  @staticmethod
  def MergePolys(polys, merge_point_threshold=10):
  """
  Merge multiple polylines, in the order that they were passed in.
  Merged polyline will have the names of their component parts joined by ';'.
  Example: merging [a,b], [c,d] and [e,f] will result in [a,b,c,d,e,f].
  However if the endpoints of two adjacent polylines are less than
  merge_point_threshold meters apart, we will only use the first endpoint in
  the merged polyline.
  """
  name = ";".join((p.GetName(), '')[p.GetName() is None] for p in polys)
  merged = Poly([], name)
  if polys:
  first_poly = polys[0]
  for p in first_poly.GetPoints():
  merged.AddPoint(p)
  last_point = merged._GetPointSafe(-1)
  for poly in polys[1:]:
  first_point = poly._GetPointSafe(0)
  if (last_point and first_point and
  last_point.GetDistanceMeters(first_point) <= merge_point_threshold):
  points = poly.GetPoints()[1:]
  else:
  points = poly.GetPoints()
  for p in points:
  merged.AddPoint(p)
  last_point = merged._GetPointSafe(-1)
  return merged
 
 
  def __str__(self):
  return self._ToString(str)
 
  def ToLatLngString(self):
  return self._ToString(lambda p: str(p.ToLatLng()))
 
  def _ToString(self, pointToStringFn):
  return "%s: %s" % (self.GetName() or "",
  ", ".join([pointToStringFn(p) for p in self._points]))
 
 
  class PolyCollection(object):
  """
  A class representing a collection of polylines.
  """
  def __init__(self):
  self._name_to_shape = {}
  pass
 
  def AddPoly(self, poly, smart_duplicate_handling=True):
  """
  Adds a new polyline to the collection.
  """
  inserted_name = poly.GetName()
  if poly.GetName() in self._name_to_shape:
  if not smart_duplicate_handling:
  raise ShapeError("Duplicate shape found: " + poly.GetName())
 
  print ("Warning: duplicate shape id being added to collection: " +
  poly.GetName())
  if poly.GreedyPolyMatchDist(self._name_to_shape[poly.GetName()]) < 10:
  print " (Skipping as it apears to be an exact duplicate)"
  else:
  print " (Adding new shape variant with uniquified name)"
  inserted_name = "%s-%d" % (inserted_name, len(self._name_to_shape))
  self._name_to_shape[inserted_name] = poly
 
  def NumPolys(self):
  return len(self._name_to_shape)
 
  def FindMatchingPolys(self, start_point, end_point, max_radius=150):
  """
  Returns a list of polylines in the collection that have endpoints
  within max_radius of the given start and end points.
  """
  matches = []
  for shape in self._name_to_shape.itervalues():
  if start_point.GetDistanceMeters(shape.GetPoint(0)) < max_radius and \
  end_point.GetDistanceMeters(shape.GetPoint(-1)) < max_radius:
  matches.append(shape)
  return matches
 
  class PolyGraph(PolyCollection):
  """
  A class representing a graph where the edges are polylines.
  """
  def __init__(self):
  PolyCollection.__init__(self)
  self._nodes = {}
 
  def AddPoly(self, poly, smart_duplicate_handling=True):
  PolyCollection.AddPoly(self, poly, smart_duplicate_handling)
  start_point = poly.GetPoint(0)
  end_point = poly.GetPoint(-1)
  self._AddNodeWithEdge(start_point, poly)
  self._AddNodeWithEdge(end_point, poly)
 
  def _AddNodeWithEdge(self, point, edge):
  if point in self._nodes:
  self._nodes[point].add(edge)
  else:
  self._nodes[point] = set([edge])
 
  def ShortestPath(self, start, goal):
  """Uses the A* algorithm to find a shortest path between start and goal.
 
  For more background see http://en.wikipedia.org/wiki/A-star_algorithm
 
  Some definitions:
  g(x): The actual shortest distance traveled from initial node to current
  node.
  h(x): The estimated (or "heuristic") distance from current node to goal.
  We use the distance on Earth from node to goal as the heuristic.
  This heuristic is both admissible and monotonic (see wikipedia for
  more details).
  f(x): The sum of g(x) and h(x), used to prioritize elements to look at.
 
  Arguments:
  start: Point that is in the graph, start point of the search.
  goal: Point that is in the graph, end point for the search.
 
  Returns:
  A Poly object representing the shortest polyline through the graph from
  start to goal, or None if no path found.
  """
 
  assert start in self._nodes
  assert goal in self._nodes
  closed_set = set() # Set of nodes already evaluated.
  open_heap = [(0, start)] # Nodes to visit, heapified by f(x).
  open_set = set([start]) # Same as open_heap, but a set instead of a heap.
  g_scores = { start: 0 } # Distance from start along optimal path
  came_from = {} # Map to reconstruct optimal path once we're done.
  while open_set:
  (f_x, x) = heapq.heappop(open_heap)
  open_set.remove(x)
  if x == goal:
  return self._ReconstructPath(came_from, goal)
  closed_set.add(x)
  edges = self._nodes[x]
  for edge in edges:
  if edge.GetPoint(0) == x:
  y = edge.GetPoint(-1)
  else:
  y = edge.GetPoint(0)
  if y in closed_set:
  continue
  tentative_g_score = g_scores[x] + edge.LengthMeters()
  tentative_is_better = False
  if y not in open_set:
  h_y = y.GetDistanceMeters(goal)
  f_y = tentative_g_score + h_y
  open_set.add(y)
  heapq.heappush(open_heap, (f_y, y))
  tentative_is_better = True
  elif tentative_g_score < g_scores[y]:
  tentative_is_better = True
  if tentative_is_better:
  came_from[y] = (x, edge)
  g_scores[y] = tentative_g_score
  return None
 
  def _ReconstructPath(self, came_from, current_node):
  """
  Helper method for ShortestPath, to reconstruct path.
 
  Arguments:
  came_from: a dictionary mapping Point to (Point, Poly) tuples.
  This dictionary keeps track of the previous neighbor to a node, and
  the edge used to get from the previous neighbor to the node.
  current_node: the current Point in the path.
 
  Returns:
  A Poly that represents the path through the graph from the start of the
  search to current_node.
  """
  if current_node in came_from:
  (previous_node, previous_edge) = came_from[current_node]
  if previous_edge.GetPoint(0) == current_node:
  previous_edge = previous_edge.Reversed()
  p = self._ReconstructPath(came_from, previous_node)
  return Poly.MergePolys([p, previous_edge], merge_point_threshold=0)
  else:
  return Poly([], '')
 
  def FindShortestMultiPointPath(self, points, max_radius=150, keep_best_n=10,
  verbosity=0):
  """
  Return a polyline, representing the shortest path through this graph that
  has edge endpoints on each of a given list of points in sequence. We allow
  fuzziness in matching of input points to points in this graph.
 
  We limit ourselves to a view of the best keep_best_n paths at any time, as a
  greedy optimization.
  """
  assert len(points) > 1
  nearby_points = []
  paths_found = [] # A heap sorted by inverse path length.
 
  for i, point in enumerate(points):
  nearby = [p for p in self._nodes.iterkeys()
  if p.GetDistanceMeters(point) < max_radius]
  if verbosity >= 2:
  print ("Nearby points for point %d %s: %s"
  % (i + 1,
  str(point.ToLatLng()),
  ", ".join([str(n.ToLatLng()) for n in nearby])))
  if nearby:
  nearby_points.append(nearby)
  else:
  print "No nearby points found for point %s" % str(point.ToLatLng())
  return None
 
  pathToStr = lambda start, end, path: (" Best path %s -> %s: %s"
  % (str(start.ToLatLng()),
  str(end.ToLatLng()),
  path and path.GetName() or
  "None"))
  if verbosity >= 3:
  print "Step 1"
  step = 2
 
  start_points = nearby_points[0]
  end_points = nearby_points[1]
 
  for start in start_points:
  for end in end_points:
  path = self.ShortestPath(start, end)
  if verbosity >= 3:
  print pathToStr(start, end, path)
  PolyGraph._AddPathToHeap(paths_found, path, keep_best_n)
 
  for possible_points in nearby_points[2:]:
  if verbosity >= 3:
  print "\nStep %d" % step
  step += 1
  new_paths_found = []
 
  start_end_paths = {} # cache of shortest paths between (start, end) pairs
  for score, path in paths_found:
  start = path.GetPoint(-1)
  for end in possible_points:
  if (start, end) in start_end_paths:
  new_segment = start_end_paths[(start, end)]
  else:
  new_segment = self.ShortestPath(start, end)
  if verbosity >= 3:
  print pathToStr(start, end, new_segment)
  start_end_paths[(start, end)] = new_segment
 
  if new_segment:
  new_path = Poly.MergePolys([path, new_segment],
  merge_point_threshold=0)
  PolyGraph._AddPathToHeap(new_paths_found, new_path, keep_best_n)
  paths_found = new_paths_found
 
  if paths_found:
  best_score, best_path = max(paths_found)
  return best_path
  else:
  return None
 
  @staticmethod
  def _AddPathToHeap(heap, path, keep_best_n):
  if path and path.GetNumPoints():
  new_item = (-path.LengthMeters(), path)
  if new_item not in heap:
  if len(heap) < keep_best_n:
  heapq.heappush(heap, new_item)
  else:
  heapq.heapreplace(heap, new_item)
 
  #!/usr/bin/python2.5
 
  # Copyright (C) 2007 Google Inc.
  #
  # Licensed under the Apache License, Version 2.0 (the "License");
  # you may not use this file except in compliance with the License.
  # You may obtain a copy of the License at
  #
  # http://www.apache.org/licenses/LICENSE-2.0
  #
  # Unless required by applicable law or agreed to in writing, software
  # distributed under the License is distributed on an "AS IS" BASIS,
  # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  # See the License for the specific language governing permissions and
  # limitations under the License.
 
  from loader import Loader
 
  class ShapeLoader(Loader):
  """A subclass of Loader that only loads the shapes from a GTFS file."""
 
  def __init__(self, *args, **kwargs):
  """Initialize a new ShapeLoader object.
 
  See Loader.__init__ for argument documentation.
  """
  Loader.__init__(self, *args, **kwargs)
 
  def Load(self):
  self._LoadShapes()
  return self._schedule
  #!/usr/bin/python2.5
 
 
  # Copyright (C) 2007 Google Inc.
  #
  # Licensed under the Apache License, Version 2.0 (the "License");
  # you may not use this file except in compliance with the License.
  # You may obtain a copy of the License at
  #
  # http://www.apache.org/licenses/LICENSE-2.0
  #
  # Unless required by applicable law or agreed to in writing, software
  # distributed under the License is distributed on an "AS IS" BASIS,
  # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  # See the License for the specific language governing permissions and
  # limitations under the License.
 
  import bisect
  from gtfsobjectbase import GtfsObjectBase
  import problems as problems_module
  import util
  import sys
 
  class ShapePoint(GtfsObjectBase):
  """This class represents a single shape point.
 
  Attributes:
  shape_id: represents the shape_id of the point
  shape_pt_lat: represents the latitude of the point
  shape_pt_lon: represents the longitude of the point
  shape_pt_sequence: represents the sequence of the point
  shape_dist_traveled: represents the distance of the point
  """
  _REQUIRED_FIELD_NAMES = ['shape_id', 'shape_pt_lat', 'shape_pt_lon',
  'shape_pt_sequence']
  _FIELD_NAMES = _REQUIRED_FIELD_NAMES + ['shape_dist_traveled']
  def __init__(self, shape_id=None, lat=None, lon=None,seq=None, dist=None,
  field_dict=None):
  """Initialize a new ShapePoint object.
 
  Args:
  field_dict: A dictionary mapping attribute name to unicode string
  """
  self._schedule = None
  if field_dict:
  if isinstance(field_dict, self.__class__):
  for k, v in field_dict.iteritems():
  self.__dict__[k] = v
  else:
  self.__dict__.update(field_dict)
  else:
  self.shape_id = shape_id
  self.shape_pt_lat = lat
  self.shape_pt_lon = lon
  self.shape_pt_sequence = seq
  self.shape_dist_traveled = dist
 
  def ParseAttributes(self, problems):
  """Parse all attributes, calling problems as needed.
 
  Return True if all of the values are valid.
  """
  if util.IsEmpty(self.shape_id):
  problems.MissingValue('shape_id')
  return
 
  try:
  if not isinstance(self.shape_pt_sequence, int):
  self.shape_pt_sequence = \
  util.NonNegIntStringToInt(self.shape_pt_sequence, problems)
  elif self.shape_pt_sequence < 0:
  problems.InvalidValue('shape_pt_sequence', self.shape_pt_sequence,
  'Value should be a number (0 or higher)')
  except (TypeError, ValueError):
  problems.InvalidValue('shape_pt_sequence', self.shape_pt_sequence,
  'Value should be a number (0 or higher)')
  return
 
  try:
  if not isinstance(self.shape_pt_lat, (int, float)):
  self.shape_pt_lat = util.FloatStringToFloat(self.shape_pt_lat, problems)
  if abs(self.shape_pt_lat) > 90.0:
  problems.InvalidValue('shape_pt_lat', self.shape_pt_lat)
  return
  except (TypeError, ValueError):
  problems.InvalidValue('shape_pt_lat', self.shape_pt_lat)
  return
 
  try:
  if not isinstance(self.shape_pt_lon, (int, float)):
  self.shape_pt_lon = util.FloatStringToFloat(self.shape_pt_lon, problems)
  if abs(self.shape_pt_lon) > 180.0:
  problems.InvalidValue('shape_pt_lon', self.shape_pt_lon)
  return
  except (TypeError, ValueError):
  problems.InvalidValue('shape_pt_lon', self.shape_pt_lon)
  return
 
  if abs(self.shape_pt_lat) < 1.0 and abs(self.shape_pt_lon) < 1.0:
  problems.InvalidValue('shape_pt_lat', self.shape_pt_lat,
  'Point location too close to 0, 0, which means '
  'that it\'s probably an incorrect location.',
  type=problems_module.TYPE_WARNING)
  return
 
  if self.shape_dist_traveled == '':
  self.shape_dist_traveled = None
 
  if (self.shape_dist_traveled is not None and
  not isinstance(self.shape_dist_traveled, (int, float))):
  try:
  self.shape_dist_traveled = \
  util.FloatStringToFloat(self.shape_dist_traveled, problems)
  except (TypeError, ValueError):
  problems.InvalidValue('shape_dist_traveled', self.shape_dist_traveled,
  'This value should be a positive number.')
  return
 
  if self.shape_dist_traveled is not None and self.shape_dist_traveled < 0:
  problems.InvalidValue('shape_dist_traveled', self.shape_dist_traveled,
  'This value should be a positive number.')
  return
 
  return True
 
  #!/usr/bin/python2.5
 
  # Copyright (C) 2007 Google Inc.
  #
  # Licensed under the Apache License, Version 2.0 (the "License");
  # you may not use this file except in compliance with the License.
  # You may obtain a copy of the License at
  #
  # http://www.apache.org/licenses/LICENSE-2.0
  #
  # Unless required by applicable law or agreed to in writing, software
  # distributed under the License is distributed on an "AS IS" BASIS,
  # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  # See the License for the specific language governing permissions and
  # limitations under the License.
 
  import warnings
 
  from gtfsobjectbase import GtfsObjectBase
  import problems as problems_module
  import util
 
  class Stop(GtfsObjectBase):
  """Represents a single stop. A stop must have a latitude, longitude and name.
 
  Callers may assign arbitrary values to instance attributes.
  Stop.ParseAttributes validates attributes according to GTFS and converts some
  into native types. ParseAttributes may delete invalid attributes.
  Accessing an attribute that is a column in GTFS will return None if this
  object does not have a value or it is ''.
  A Stop object acts like a dict with string values.
 
  Attributes:
  stop_lat: a float representing the latitude of the stop
  stop_lon: a float representing the longitude of the stop
  All other attributes are strings.
  """
  _REQUIRED_FIELD_NAMES = ['stop_id', 'stop_name', 'stop_lat', 'stop_lon']
  _FIELD_NAMES = _REQUIRED_FIELD_NAMES + \
  ['stop_desc', 'zone_id', 'stop_url', 'stop_code',
  'location_type', 'parent_station']
  _TABLE_NAME = 'stops'
 
  def __init__(self, lat=None, lng=None, name=None, stop_id=None,
  field_dict=None, stop_code=None):
  """Initialize a new Stop object.
 
  Args:
  field_dict: A dictionary mapping attribute name to unicode string
  lat: a float, ignored when field_dict is present
  lng: a float, ignored when field_dict is present
  name: a string, ignored when field_dict is present
  stop_id: a string, ignored when field_dict is present
  stop_code: a string, ignored when field_dict is present
  """
  self._schedule = None
  if field_dict:
  if isinstance(field_dict, self.__class__):
  # Special case so that we don't need to re-parse the attributes to
  # native types iteritems returns all attributes that don't start with _
  for k, v in field_dict.iteritems():
  self.__dict__[k] = v
  else:
  self.__dict__.update(field_dict)
  else:
  if lat is not None:
  self.stop_lat = lat
  if lng is not None:
  self.stop_lon = lng
  if name is not None:
  self.stop_name = name
  if stop_id is not None:
  self.stop_id = stop_id
  if stop_code is not None:
  self.stop_code = stop_code
 
  def GetTrips(self, schedule=None):
  """Return iterable containing trips that visit this stop."""
  return [trip for trip, ss in self._GetTripSequence(schedule)]
 
  def _GetTripSequence(self, schedule=None):
  """Return a list of (trip, stop_sequence) for all trips visiting this stop.
 
  A trip may be in the list multiple times with different index.
  stop_sequence is an integer.
 
  Args:
  schedule: Deprecated, do not use.
  """
  if schedule is None:
  schedule = getattr(self, "_schedule", None)
  if schedule is None:
  warnings.warn("No longer supported. _schedule attribute is used to get "
  "stop_times table", DeprecationWarning)
  cursor = schedule._connection.cursor()
  cursor.execute("SELECT trip_id,stop_sequence FROM stop_times "
  "WHERE stop_id=?",
  (self.stop_id, ))
  return [(schedule.GetTrip(row[0]), row[1]) for row in cursor]
 
  def _GetTripIndex(self, schedule=None):
  """Return a list of (trip, index).
 
  trip: a Trip object
  index: an offset in trip.GetStopTimes()
  """
  trip_index = []
  for trip, sequence in self._GetTripSequence(schedule):
  for index, st in enumerate(trip.GetStopTimes()):
  if st.stop_sequence == sequence:
  trip_index.append((trip, index))
  break
  else:
  raise RuntimeError("stop_sequence %d not found in trip_id %s" %
  sequence, trip.trip_id)
  return trip_index
 
  def GetStopTimeTrips(self, schedule=None):
  """Return a list of (time, (trip, index), is_timepoint).
 
  time: an integer. It might be interpolated.
  trip: a Trip object.
  index: the offset of this stop in trip.GetStopTimes(), which may be
  different from the stop_sequence.
  is_timepoint: a bool
  """
  time_trips = []
  for trip, index in self._GetTripIndex(schedule):
  secs, stoptime, is_timepoint = trip.GetTimeInterpolatedStops()[index]
  time_trips.append((secs, (trip, index), is_timepoint))
  return time_trips
 
  def __getattr__(self, name):
  """Return None or the default value if name is a known attribute.
 
  This method is only called when name is not found in __dict__.
  """
  if name == "location_type":
  return 0
  elif name == "trip_index":
  return self._GetTripIndex()
  elif name in self._FIELD_NAMES:
  return None
  else:
  raise AttributeError(name)
 
  def ValidateStopLatitude(self, problems):
  if self.stop_lat:
  value = self.stop_lat
  try:
  if not isinstance(value, (float, int)):
  self.stop_lat = util.FloatStringToFloat(value, problems)
  except (ValueError, TypeError):
  problems.InvalidValue('stop_lat', value)
  del self.stop_lat
  else:
  if self.stop_lat > 90 or self.stop_lat < -90:
  problems.InvalidValue('stop_lat', value)
 
  def ValidateStopLongitude(self, problems):
  if self.stop_lon:
  value = self.stop_lon
  try:
  if not isinstance(value, (float, int)):
  self.stop_lon = util.FloatStringToFloat(value, problems)
  except (ValueError, TypeError):
  problems.InvalidValue('stop_lon', value)
  del self.stop_lon
  else:
  if self.stop_lon > 180 or self.stop_lon < -180:
  problems.InvalidValue('stop_lon', value)
 
  def ValidateStopUrl(self, problems):
  value = self.stop_url
  if value and not util.IsValidURL(value):
  problems.InvalidValue('stop_url', value)
  del self.stop_url
 
  def ValidateStopLocationType(self, problems):
  value = self.location_type
  if value == '':
  self.location_type = 0
  else:
  try:
  self.location_type = int(value)
  except (ValueError, TypeError):
  problems.InvalidValue('location_type', value)
  del self.location_type
  else:
  if self.location_type not in (0, 1):
  problems.InvalidValue('location_type', value,
  type=problems_module.TYPE_WARNING)
 
  def ValidateStopRequiredFields(self, problems):
  for required in self._REQUIRED_FIELD_NAMES:
  if util.IsEmpty(getattr(self, required, None)):
  # TODO: For now I'm keeping the API stable but it would be cleaner to
  # treat whitespace stop_id as invalid, instead of missing
  problems.MissingValue(required)
 
  def ValidateStopNotTooCloseToOrigin(self, problems):
  if (self.stop_lat is not None and self.stop_lon is not None and
  abs(self.stop_lat) < 1.0) and (abs(self.stop_lon) < 1.0):
  problems.InvalidValue('stop_lat', self.stop_lat,
  'Stop location too close to 0, 0',
  type=problems_module.TYPE_WARNING)
 
  def ValidateStopDescriptionAndNameAreDifferent(self, problems):
  if (self.stop_desc is not None and self.stop_name is not None and
  self.stop_desc and self.stop_name and
  not util.IsEmpty(self.stop_desc) and
  self.stop_name.strip().lower() == self.stop_desc.strip().lower()):
  problems.InvalidValue('stop_desc', self.stop_desc,
  'stop_desc should not be the same as stop_name')
 
  def ValidateStopIsNotStationWithParent(self, problems):
  if self.parent_station and self.location_type == 1:
  problems.InvalidValue('parent_station', self.parent_station,
  'Stop row with location_type=1 (a station) must '
  'not have a parent_station')
 
  def ValidateBeforeAdd(self, problems):
  # First check that all required fields are present because ParseAttributes
  # may remove invalid attributes.
  self.ValidateStopRequiredFields(problems)
 
  #If value is valid for attribute name store it.
  #If value is not valid call problems. Return a new value of the correct type
  #or None if value couldn't be converted.
  self.ValidateStopLatitude(problems)
  self.ValidateStopLongitude(problems)
  self.ValidateStopUrl(problems)
  self.ValidateStopLocationType(problems)
 
  # Check that this object is consistent with itself
  self.ValidateStopNotTooCloseToOrigin(problems)
  self.ValidateStopDescriptionAndNameAreDifferent(problems)
  self.ValidateStopIsNotStationWithParent(problems)
 
  # None of these checks are blocking
  return True
 
  def ValidateAfterAdd(self, problems):
  return
 
  def Validate(self, problems=problems_module.default_problem_reporter):
  self.ValidateBeforeAdd(problems)
  self.ValidateAfterAdd(problems)
 
  def AddToSchedule(self, schedule, problems):
  schedule.AddStopObject(self, problems)
 
  #!/usr/bin/python2.5
 
  # Copyright (C) 2007 Google Inc.
  #
  # Licensed under the Apache License, Version 2.0 (the "License");
  # you may not use this file except in compliance with the License.
  # You may obtain a copy of the License at
  #
  # http://www.apache.org/licenses/LICENSE-2.0
  #
  # Unless required by applicable law or agreed to in writing, software
  # distributed under the License is distributed on an "AS IS" BASIS,
  # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  # See the License for the specific language governing permissions and
  # limitations under the License.
 
  import problems as problems_module
  from stop import Stop
  import util
 
  class StopTime(object):
  """
  Represents a single stop of a trip. StopTime contains most of the columns
  from the stop_times.txt file. It does not contain trip_id, which is implied
  by the Trip used to access it.
 
  See the Google Transit Feed Specification for the semantic details.
 
  stop: A Stop object
  arrival_time: str in the form HH:MM:SS; readonly after __init__
  departure_time: str in the form HH:MM:SS; readonly after __init__
  arrival_secs: int number of seconds since midnight
  departure_secs: int number of seconds since midnight
  stop_headsign: str
  pickup_type: int
  drop_off_type: int
  shape_dist_traveled: float
  stop_id: str; readonly
  stop_time: The only time given for this stop. If present, it is used
  for both arrival and departure time.
  stop_sequence: int
  """
  _REQUIRED_FIELD_NAMES = ['trip_id', 'arrival_time', 'departure_time',
  'stop_id', 'stop_sequence']
  _OPTIONAL_FIELD_NAMES = ['stop_headsign', 'pickup_type',
  'drop_off_type', 'shape_dist_traveled']
  _FIELD_NAMES = _REQUIRED_FIELD_NAMES + _OPTIONAL_FIELD_NAMES
  _SQL_FIELD_NAMES = ['trip_id', 'arrival_secs', 'departure_secs',
  'stop_id', 'stop_sequence', 'stop_headsign',
  'pickup_type', 'drop_off_type', 'shape_dist_traveled']
  _STOP_CLASS = Stop
 
  __slots__ = ('arrival_secs', 'departure_secs', 'stop_headsign', 'stop',
  'stop_headsign', 'pickup_type', 'drop_off_type',
  'shape_dist_traveled', 'stop_sequence')
  def __init__(self, problems, stop,
  arrival_time=None, departure_time=None,
  stop_headsign=None, pickup_type=None, drop_off_type=None,
  shape_dist_traveled=None, arrival_secs=None,
  departure_secs=None, stop_time=None, stop_sequence=None):
  # Implementation note from Andre, July 22, 2010:
  # The checks performed here should be in their own Validate* methods to
  # keep consistency. Unfortunately the performance degradation is too great,
  # so the validation was left in __init__.
  # Performance is also the reason why we don't use the GtfsFactory, but
  # have StopTime._STOP_CLASS instead. If a Stop class that does not inherit
  # from transitfeed.Stop is used, the extension should also provide a
  # StopTime class that updates _STOP_CLASS accordingly.
  #
  # For more details see the discussion at
  # http://codereview.appspot.com/1713041
  if stop_time != None:
  arrival_time = departure_time = stop_time
 
  if arrival_secs != None:
  self.arrival_secs = arrival_secs
  elif arrival_time in (None, ""):
  self.arrival_secs = None # Untimed
  arrival_time = None
  else:
  try:
  self.arrival_secs = util.TimeToSecondsSinceMidnight(arrival_time)
  except problems_module.Error:
  problems.InvalidValue('arrival_time', arrival_time)
  self.arrival_secs = None
 
  if departure_secs != None:
  self.departure_secs = departure_secs
  elif departure_time in (None, ""):
  self.departure_secs = None
  departure_time = None
  else:
  try:
  self.departure_secs = util.TimeToSecondsSinceMidnight(departure_time)
  except problems_module.Error:
  problems.InvalidValue('departure_time', departure_time)
  self.departure_secs = None
 
  if not isinstance(stop, self._STOP_CLASS):
  # Not quite correct, but better than letting the problem propagate
  problems.InvalidValue('stop', stop)
  self.stop = stop
  self.stop_headsign = stop_headsign
 
  if pickup_type in (None, ""):
  self.pickup_type = None
  else:
  try:
  pickup_type = int(pickup_type)
  except ValueError:
  problems.InvalidValue('pickup_type', pickup_type)
  else:
  if pickup_type < 0 or pickup_type > 3:
  problems.InvalidValue('pickup_type', pickup_type)
  self.pickup_type = pickup_type
 
  if drop_off_type in (None, ""):
  self.drop_off_type = None
  else:
  try:
  drop_off_type = int(drop_off_type)
  except ValueError:
  problems.InvalidValue('drop_off_type', drop_off_type)
  else:
  if drop_off_type < 0 or drop_off_type > 3:
  problems.InvalidValue('drop_off_type', drop_off_type)
  self.drop_off_type = drop_off_type
 
  if (self.pickup_type == 1 and self.drop_off_type == 1 and
  self.arrival_secs == None and self.departure_secs == None):
  problems.OtherProblem('This stop time has a pickup_type and '
  'drop_off_type of 1, indicating that riders '
  'can\'t get on or off here. Since it doesn\'t '
  'define a timepoint either, this entry serves no '
  'purpose and should be excluded from the trip.',
  type=problems_module.TYPE_WARNING)
 
  if ((self.arrival_secs != None) and (self.departure_secs != None) and
  (self.departure_secs < self.arrival_secs)):
  problems.InvalidValue('departure_time', departure_time,
  'The departure time at this stop (%s) is before '
  'the arrival time (%s). This is often caused by '
  'problems in the feed exporter\'s time conversion')
 
  # If the caller passed a valid arrival time but didn't attempt to pass a
  # departure time complain
  if (self.arrival_secs != None and
  self.departure_secs == None and departure_time == None):
  # self.departure_secs might be None because departure_time was invalid,
  # so we need to check both
  problems.MissingValue('departure_time',
  'arrival_time and departure_time should either '
  'both be provided or both be left blank. '
  'It\'s OK to set them both to the same value.')
  # If the caller passed a valid departure time but didn't attempt to pass a
  # arrival time complain
  if (self.departure_secs != None and
  self.arrival_secs == None and arrival_time == None):
  problems.MissingValue('arrival_time',
  'arrival_time and departure_time should either '
  'both be provided or both be left blank. '
  'It\'s OK to set them both to the same value.')
 
  if shape_dist_traveled in (None, ""):
  self.shape_dist_traveled = None
  else:
  try:
  self.shape_dist_traveled = float(shape_dist_traveled)
  except ValueError:
  problems.InvalidValue('shape_dist_traveled', shape_dist_traveled)
 
  if stop_sequence is not None:
  self.stop_sequence = stop_sequence
 
  def GetFieldValuesTuple(self, trip_id):
  """Return a tuple that outputs a row of _FIELD_NAMES to be written to a
  GTFS file.
 
  Arguments:
  trip_id: The trip_id of the trip to which this StopTime corresponds.
  It must be provided, as it is not stored in StopTime.
  """
  result = []
  for fn in self._FIELD_NAMES:
  if fn == 'trip_id':
  result.append(trip_id)
  else:
  # Since we'll be writting to an output file, we want empty values to be
  # outputted as an empty string
  result.append(getattr(self, fn) or '' )
  return tuple(result)
 
  def GetSqlValuesTuple(self, trip_id):
  """Return a tuple that outputs a row of _FIELD_NAMES to be written to a
  SQLite database.
 
  Arguments:
  trip_id: The trip_id of the trip to which this StopTime corresponds.
  It must be provided, as it is not stored in StopTime.
  """
 
  result = []
  for fn in self._SQL_FIELD_NAMES:
  if fn == 'trip_id':
  result.append(trip_id)
  else:
  # Since we'll be writting to SQLite, we want empty values to be
  # outputted as NULL string (contrary to what happens in
  # GetFieldValuesTuple)
  result.append(getattr(self, fn))
  return tuple(result)
 
  def GetTimeSecs(self):
  """Return the first of arrival_secs and departure_secs that is not None.
  If both are None return None."""
  if self.arrival_secs != None:
  return self.arrival_secs
  elif self.departure_secs != None:
  return self.departure_secs
  else:
  return None
 
  def __getattr__(self, name):
  if name == 'stop_id':
  return self.stop.stop_id
  elif name == 'arrival_time':
  return (self.arrival_secs != None and
  util.FormatSecondsSinceMidnight(self.arrival_secs) or '')
  elif name == 'departure_time':
  return (self.departure_secs != None and
  util.FormatSecondsSinceMidnight(self.departure_secs) or '')
  elif name == 'shape_dist_traveled':
  return ''
  raise AttributeError(name)
 
  #!/usr/bin/python2.5
 
  # Copyright (C) 2007 Google Inc.
  #
  # Licensed under the Apache License, Version 2.0 (the "License");
  # you may not use this file except in compliance with the License.
  # You may obtain a copy of the License at
  #
  # http://www.apache.org/licenses/LICENSE-2.0
  #
  # Unless required by applicable law or agreed to in writing, software
  # distributed under the License is distributed on an "AS IS" BASIS,
  # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  # See the License for the specific language governing permissions and
  # limitations under the License.
 
  from gtfsobjectbase import GtfsObjectBase
  import problems as problems_module
  import util
 
  class Transfer(GtfsObjectBase):
  """Represents a transfer in a schedule"""
  _REQUIRED_FIELD_NAMES = ['from_stop_id', 'to_stop_id', 'transfer_type']
  _FIELD_NAMES = _REQUIRED_FIELD_NAMES + ['min_transfer_time']
  _TABLE_NAME = 'transfers'
  _ID_COLUMNS = ['from_stop_id', 'to_stop_id']
 
  def __init__(self, schedule=None, from_stop_id=None, to_stop_id=None, transfer_type=None,
  min_transfer_time=None, field_dict=None):
  self._schedule = None
  if field_dict:
  self.__dict__.update(field_dict)
  else:
  self.from_stop_id = from_stop_id
  self.to_stop_id = to_stop_id
  self.transfer_type = transfer_type
  self.min_transfer_time = min_transfer_time
 
  if getattr(self, 'transfer_type', None) in ("", None):
  # Use the default, recommended transfer, if attribute is not set or blank
  self.transfer_type = 0
  else:
  try:
  self.transfer_type = util.NonNegIntStringToInt(self.transfer_type)
  except (TypeError, ValueError):
  pass
 
  if hasattr(self, 'min_transfer_time'):
  try:
  self.min_transfer_time = util.NonNegIntStringToInt(self.min_transfer_time)
  except (TypeError, ValueError):
  pass
  else:
  self.min_transfer_time = None
  if schedule is not None:
  # Note from Tom, Nov 25, 2009: Maybe calling __init__ with a schedule
  # should output a DeprecationWarning. A schedule factory probably won't
  # use it and other GenericGTFSObject subclasses don't support it.
  schedule.AddTransferObject(self)
 
  def ValidateFromStopIdIsPresent(self, problems):
  if util.IsEmpty(self.from_stop_id):
  problems.MissingValue('from_stop_id')
  return False
  return True
 
  def ValidateToStopIdIsPresent(self, problems):
  if util.IsEmpty(self.to_stop_id):
  problems.MissingValue('to_stop_id')
  return False
  return True
 
  def ValidateTransferType(self, problems):
  if not util.IsEmpty(self.transfer_type):
  if (not isinstance(self.transfer_type, int)) or \
  (self.transfer_type not in range(0, 4)):
  problems.InvalidValue('transfer_type', self.transfer_type)
  return False
  return True
 
  def ValidateMinimumTransferTime(self, problems):
  if not util.IsEmpty(self.min_transfer_time):
  if self.transfer_type != 2:
  problems.MinimumTransferTimeSetWithInvalidTransferType(
  self.transfer_type)
 
  # If min_transfer_time is negative, equal to or bigger than 24h, issue
  # an error. If smaller than 24h but bigger than 3h issue a warning.
  # These errors are not blocking, and should not prevent the transfer
  # from being added to the schedule.
  if (isinstance(self.min_transfer_time, int)):
  if self.min_transfer_time < 0:
  problems.InvalidValue('min_transfer_time', self.min_transfer_time,
  reason="This field cannot contain a negative " \
  "value.")
  elif self.min_transfer_time >= 24*3600:
  problems.InvalidValue('min_transfer_time', self.min_transfer_time,
  reason="The value is very large for a " \
  "transfer time and most likely " \
  "indicates an error.")
  elif self.min_transfer_time >= 3*3600:
  problems.InvalidValue('min_transfer_time', self.min_transfer_time,
  type=problems_module.TYPE_WARNING,
  reason="The value is large for a transfer " \
  "time and most likely indicates " \
  "an error.")
  else:
  # It has a value, but it is not an integer
  problems.InvalidValue('min_transfer_time', self.min_transfer_time,
  reason="If present, this field should contain " \
  "an integer value.")
  return False
  return True
 
  def GetTransferDistance(self):
  from_stop = self._schedule.stops[self.from_stop_id]
  to_stop = self._schedule.stops[self.to_stop_id]
  distance = util.ApproximateDistanceBetweenStops(from_stop, to_stop)
  return distance
 
  def ValidateFromStopIdIsValid(self, problems):
  if self.from_stop_id not in self._schedule.stops.keys():
  problems.InvalidValue('from_stop_id', self.from_stop_id)
  return False
  return True
 
  def ValidateToStopIdIsValid(self, problems):
  if self.to_stop_id not in self._schedule.stops.keys():
  problems.InvalidValue('to_stop_id', self.to_stop_id)
  return False
  return True
 
  def ValidateTransferDistance(self, problems):
  distance = self.GetTransferDistance()
 
  if distance > 10000:
  problems.TransferDistanceTooBig(self.from_stop_id,
  self.to_stop_id,
  distance)
  elif distance > 2000:
  problems.TransferDistanceTooBig(self.from_stop_id,
  self.to_stop_id,
  distance,
  type=problems_module.TYPE_WARNING)
 
  def ValidateTransferWalkingTime(self, problems):
  if util.IsEmpty(self.min_transfer_time):
  return
 
  if self.min_transfer_time < 0:
  # Error has already been reported, and it does not make sense
  # to calculate walking speed with negative times.
  return
 
  distance = self.GetTransferDistance()
  # If min_transfer_time + 120s isn't enough for someone walking very fast
  # (2m/s) then issue a warning.
  #
  # Stops that are close together (less than 240m appart) never trigger this
  # warning, regardless of min_transfer_time.
  FAST_WALKING_SPEED= 2 # 2m/s
  if self.min_transfer_time + 120 < distance / FAST_WALKING_SPEED:
  problems.TransferWalkingSpeedTooFast(from_stop_id=self.from_stop_id,
  to_stop_id=self.to_stop_id,
  transfer_time=self.min_transfer_time,
  distance=distance)
 
  def ValidateBeforeAdd(self, problems):
  result = True
  result = self.ValidateFromStopIdIsPresent(problems) and result
  result = self.ValidateToStopIdIsPresent(problems) and result
  result = self.ValidateTransferType(problems) and result
  result = self.ValidateMinimumTransferTime(problems) and result
  return result
 
  def ValidateAfterAdd(self, problems):
  valid_stop_ids = True
  valid_stop_ids = self.ValidateFromStopIdIsValid(problems) and valid_stop_ids
  valid_stop_ids = self.ValidateToStopIdIsValid(problems) and valid_stop_ids
  # We need both stop IDs to be valid to able to validate their distance and
  # the walking time between them
  if valid_stop_ids:
  self.ValidateTransferDistance(problems)
  self.ValidateTransferWalkingTime(problems)
 
  def Validate(self,
  problems=problems_module.default_problem_reporter):
  if self.ValidateBeforeAdd(problems) and self._schedule:
  self.ValidateAfterAdd(problems)
 
  def _ID(self):
  return tuple(self[i] for i in self._ID_COLUMNS)
 
  def AddToSchedule(self, schedule, problems):
  schedule.AddTransferObject(self, problems)
 
  #!/usr/bin/python2.5
 
  # Copyright (C) 2007 Google Inc.
  #
  # Licensed under the Apache License, Version 2.0 (the "License");
  # you may not use this file except in compliance with the License.
  # You may obtain a copy of the License at
  #
  # http://www.apache.org/licenses/LICENSE-2.0
  #
  # Unless required by applicable law or agreed to in writing, software
  # distributed under the License is distributed on an "AS IS" BASIS,
  # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  # See the License for the specific language governing permissions and
  # limitations under the License.
 
  import warnings
 
  from gtfsobjectbase import GtfsObjectBase
  import problems as problems_module
  import util
 
  class Trip(GtfsObjectBase):
  _REQUIRED_FIELD_NAMES = ['route_id', 'service_id', 'trip_id']
  _FIELD_NAMES = _REQUIRED_FIELD_NAMES + [
  'trip_headsign', 'direction_id', 'block_id', 'shape_id'
  ]
  _TABLE_NAME= "trips"
 
  def __init__(self, headsign=None, service_period=None,
  route=None, trip_id=None, field_dict=None):
  self._schedule = None
  self._headways = [] # [(start_time, end_time, headway_secs)]
  if not field_dict:
  field_dict = {}
  if headsign is not None:
  field_dict['trip_headsign'] = headsign
  if route:
  field_dict['route_id'] = route.route_id
  if trip_id is not None:
  field_dict['trip_id'] = trip_id
  if service_period is not None:
  field_dict['service_id'] = service_period.service_id
  # Earlier versions of transitfeed.py assigned self.service_period here
  # and allowed the caller to set self.service_id. Schedule.Validate
  # checked the service_id attribute if it was assigned and changed it to a
  # service_period attribute. Now only the service_id attribute is used and
  # it is validated by Trip.Validate.
  if service_period is not None:
  # For backwards compatibility
  self.service_id = service_period.service_id
  self.__dict__.update(field_dict)
 
  def GetFieldValuesTuple(self):
  return [getattr(self, fn) or '' for fn in self._FIELD_NAMES]
 
  def AddStopTime(self, stop, problems=None, schedule=None, **kwargs):
  """Add a stop to this trip. Stops must be added in the order visited.
 
  Args:
  stop: A Stop object
  kwargs: remaining keyword args passed to StopTime.__init__
 
  Returns:
  None
  """
  if problems is None:
  # TODO: delete this branch when StopTime.__init__ doesn't need a
  # ProblemReporter
  problems = problems_module.default_problem_reporter
  stoptime = self.GetGtfsFactory().StopTime(
  problems=problems, stop=stop, **kwargs)
  self.AddStopTimeObject(stoptime, schedule)
 
  def _AddStopTimeObjectUnordered(self, stoptime, schedule):
  """Add StopTime object to this trip.
 
  The trip isn't checked for duplicate sequence numbers so it must be
  validated later."""
  stop_time_class = self.GetGtfsFactory().StopTime
  cursor = schedule._connection.cursor()
  insert_query = "INSERT INTO stop_times (%s) VALUES (%s);" % (
  ','.join(stop_time_class._SQL_FIELD_NAMES),
  ','.join(['?'] * len(stop_time_class._SQL_FIELD_NAMES)))
  cursor = schedule._connection.cursor()
  cursor.execute(
  insert_query, stoptime.GetSqlValuesTuple(self.trip_id))
 
  def ReplaceStopTimeObject(self, stoptime, schedule=None):
  """Replace a StopTime object from this trip with the given one.
 
  Keys the StopTime object to be replaced by trip_id, stop_sequence
  and stop_id as 'stoptime', with the object 'stoptime'.
  """
 
  if schedule is None:
  schedule = self._schedule
 
  new_secs = stoptime.GetTimeSecs()
  cursor = schedule._connection.cursor()
  cursor.execute("DELETE FROM stop_times WHERE trip_id=? and "
  "stop_sequence=? and stop_id=?",
  (self.trip_id, stoptime.stop_sequence, stoptime.stop_id))
  if cursor.rowcount == 0:
  raise problems_module.Error, 'Attempted replacement of StopTime object which does not exist'
  self._AddStopTimeObjectUnordered(stoptime, schedule)
 
  def AddStopTimeObject(self, stoptime, schedule=None, problems=None):
  """Add a StopTime object to the end of this trip.
 
  Args:
  stoptime: A StopTime object. Should not be reused in multiple trips.
  schedule: Schedule object containing this trip which must be
  passed to Trip.__init__ or here
  problems: ProblemReporter object for validating the StopTime in its new
  home
 
  Returns:
  None
  """
  if schedule is None:
  schedule = self._schedule
  if schedule is None:
  warnings.warn("No longer supported. _schedule attribute is used to get "
  "stop_times table", DeprecationWarning)
  if problems is None:
  problems = schedule.problem_reporter
 
  new_secs = stoptime.GetTimeSecs()
  cursor = schedule._connection.cursor()
  cursor.execute("SELECT max(stop_sequence), max(arrival_secs), "
  "max(departure_secs) FROM stop_times WHERE trip_id=?",
  (self.trip_id,))
  row = cursor.fetchone()
  if row[0] is None:
  # This is the first stop_time of the trip
  stoptime.stop_sequence = 1
  if new_secs == None:
  problems.OtherProblem(
  'No time for first StopTime of trip_id "%s"' % (self.trip_id,))
  else:
  stoptime.stop_sequence = row[0] + 1
  prev_secs = max(row[1], row[2])
  if new_secs != None and new_secs < prev_secs:
  problems.OtherProblem(
  'out of order stop time for stop_id=%s trip_id=%s %s < %s' %
  (util.EncodeUnicode(stoptime.stop_id),
  util.EncodeUnicode(self.trip_id),
  util.FormatSecondsSinceMidnight(new_secs),
  util.FormatSecondsSinceMidnight(prev_secs)))
  self._AddStopTimeObjectUnordered(stoptime, schedule)
 
  def GetTimeStops(self):
  """Return a list of (arrival_secs, departure_secs, stop) tuples.
 
  Caution: arrival_secs and departure_secs may be 0, a false value meaning a
  stop at midnight or None, a false value meaning the stop is untimed."""
  return [(st.arrival_secs, st.departure_secs, st.stop) for st in
  self.GetStopTimes()]
 
  def GetCountStopTimes(self):
  """Return the number of stops made by this trip."""
  cursor = self._schedule._connection.cursor()
  cursor.execute(
  'SELECT count(*) FROM stop_times WHERE trip_id=?', (self.trip_id,))
  return cursor.fetchone()[0]
 
  def GetTimeInterpolatedStops(self):
  """Return a list of (secs, stoptime, is_timepoint) tuples.
 
  secs will always be an int. If the StopTime object does not have explict
  times this method guesses using distance. stoptime is a StopTime object and
  is_timepoint is a bool.
 
  Raises:
  ValueError if this trip does not have the times needed to interpolate
  """
  rv = []
 
  stoptimes = self.GetStopTimes()
  # If there are no stoptimes [] is the correct return value but if the start
  # or end are missing times there is no correct return value.
  if not stoptimes:
  return []
  if (stoptimes[0].GetTimeSecs() is None or
  stoptimes[-1].GetTimeSecs() is None):
  raise ValueError("%s must have time at first and last stop" % (self))
 
  cur_timepoint = None
  next_timepoint = None
  distance_between_timepoints = 0
  distance_traveled_between_timepoints = 0
 
  for i, st in enumerate(stoptimes):
  if st.GetTimeSecs() != None:
  cur_timepoint = st
  distance_between_timepoints = 0
  distance_traveled_between_timepoints = 0
  if i + 1 < len(stoptimes):
  k = i + 1
  distance_between_timepoints += util.ApproximateDistanceBetweenStops(stoptimes[k-1].stop, stoptimes[k].stop)
  while stoptimes[k].GetTimeSecs() == None:
  k += 1
  distance_between_timepoints += util.ApproximateDistanceBetweenStops(stoptimes[k-1].stop, stoptimes[k].stop)
  next_timepoint = stoptimes[k]
  rv.append( (st.GetTimeSecs(), st, True) )
  else:
  distance_traveled_between_timepoints += util.ApproximateDistanceBetweenStops(stoptimes[i-1].stop, st.stop)
  distance_percent = distance_traveled_between_timepoints / distance_between_timepoints
  total_time = next_timepoint.GetTimeSecs() - cur_timepoint.GetTimeSecs()
  time_estimate = distance_percent * total_time + cur_timepoint.GetTimeSecs()
  rv.append( (int(round(time_estimate)), st, False) )
 
  return rv
 
  def ClearStopTimes(self):
  """Remove all stop times from this trip.
 
  StopTime objects previously returned by GetStopTimes are unchanged but are
  no longer associated with this trip.
  """
  cursor = self._schedule._connection.cursor()
  cursor.execute('DELETE FROM stop_times WHERE trip_id=?', (self.trip_id,))
 
  def GetStopTimes(self, problems=None):
  """Return a sorted list of StopTime objects for this trip."""
  # In theory problems=None should be safe because data from database has been
  # validated. See comment in _LoadStopTimes for why this isn't always true.
  cursor = self._schedule._connection.cursor()
  cursor.execute(
  'SELECT arrival_secs,departure_secs,stop_headsign,pickup_type,'
  'drop_off_type,shape_dist_traveled,stop_id,stop_sequence FROM '
  'stop_times WHERE '
  'trip_id=? ORDER BY stop_sequence', (self.trip_id,))
  stop_times = []
  stoptime_class = self.GetGtfsFactory().StopTime
  for row in cursor.fetchall():
  stop = self._schedule.GetStop(row[6])
  stop_times.append(stoptime_class(problems=problems,
  stop=stop,
  arrival_secs=row[0],
  departure_secs=row[1],
  stop_headsign=row[2],
  pickup_type=row[3],
  drop_off_type=row[4],
  shape_dist_traveled=row[5],
  stop_sequence=row[7]))
  return stop_times
 
  def GetHeadwayStopTimes(self, problems=None):
  """Deprecated. Please use GetFrequencyStopTimes instead."""
  warnings.warn("No longer supported. The HeadwayPeriod class was renamed to "
  "Frequency, and all related functions were renamed "
  "accordingly.", DeprecationWarning)
  return self.GetFrequencyStopTimes(problems)
 
  def GetFrequencyStopTimes(self, problems=None):
  """Return a list of StopTime objects for each headway-based run.
 
  Returns:
  a list of list of StopTime objects. Each list of StopTime objects
  represents one run. If this trip doesn't have headways returns an empty
  list.
  """
  stoptimes_list = [] # list of stoptime lists to be returned
  stoptime_pattern = self.GetStopTimes()
  first_secs = stoptime_pattern[0].arrival_secs # first time of the trip
  stoptime_class = self.GetGtfsFactory().StopTime
  # for each start time of a headway run
  for run_secs in self.GetFrequencyStartTimes():
  # stop time list for a headway run
  stoptimes = []
  # go through the pattern and generate stoptimes
  for st in stoptime_pattern:
  arrival_secs, departure_secs = None, None # default value if the stoptime is not timepoint
  if st.arrival_secs != None:
  arrival_secs = st.arrival_secs - first_secs + run_secs
  if st.departure_secs != None:
  departure_secs = st.departure_secs - first_secs + run_secs
  # append stoptime
  stoptimes.append(stoptime_class(problems=problems, stop=st.stop,
  arrival_secs=arrival_secs,
  departure_secs=departure_secs,
  stop_headsign=st.stop_headsign,
  pickup_type=st.pickup_type,
  drop_off_type=st.drop_off_type,
  shape_dist_traveled= \
  st.shape_dist_traveled,
  stop_sequence=st.stop_sequence))
  # add stoptimes to the stoptimes_list
  stoptimes_list.append ( stoptimes )
  return stoptimes_list
 
  def GetStartTime(self, problems=problems_module.default_problem_reporter):
  """Return the first time of the trip. TODO: For trips defined by frequency
  return the first time of the first trip."""
  cursor = self._schedule._connection.cursor()
  cursor.execute(
  'SELECT arrival_secs,departure_secs FROM stop_times WHERE '
  'trip_id=? ORDER BY stop_sequence LIMIT 1', (self.trip_id,))
  (arrival_secs, departure_secs) = cursor.fetchone()
  if arrival_secs != None:
  return arrival_secs
  elif departure_secs != None:
  return departure_secs
  else:
  problems.InvalidValue('departure_time', '',
  'The first stop_time in trip %s is missing '
  'times.' % self.trip_id)
 
  def GetHeadwayStartTimes(self):
  """Deprecated. Please use GetFrequencyStartTimes instead."""
  warnings.warn("No longer supported. The HeadwayPeriod class was renamed to "
  "Frequency, and all related functions were renamed "
  "accordingly.", DeprecationWarning)
  return self.GetFrequencyStartTimes()
 
  def GetFrequencyStartTimes(self):
  """Return a list of start time for each headway-based run.
 
  Returns:
  a sorted list of seconds since midnight, the start time of each run. If
  this trip doesn't have headways returns an empty list."""
  start_times = []
  # for each headway period of the trip
  for start_secs, end_secs, headway_secs in self.GetFrequencyTuples():
  # reset run secs to the start of the timeframe
  run_secs = start_secs
  while run_secs < end_secs:
  start_times.append(run_secs)
  # increment current run secs by headway secs
  run_secs += headway_secs
  return start_times
 
  def GetEndTime(self, problems=problems_module.default_problem_reporter):
  """Return the last time of the trip. TODO: For trips defined by frequency
  return the last time of the last trip."""
  cursor = self._schedule._connection.cursor()
  cursor.execute(
  'SELECT arrival_secs,departure_secs FROM stop_times WHERE '
  'trip_id=? ORDER BY stop_sequence DESC LIMIT 1', (self.trip_id,))
  (arrival_secs, departure_secs) = cursor.fetchone()
  if departure_secs != None:
  return departure_secs
  elif arrival_secs != None:
  return arrival_secs
  else:
  problems.InvalidValue('arrival_time', '',
  'The last stop_time in trip %s is missing '
  'times.' % self.trip_id)
 
  def _GenerateStopTimesTuples(self):
  """Generator for rows of the stop_times file"""
  stoptimes = self.GetStopTimes()
  for i, st in enumerate(stoptimes):
  yield st.GetFieldValuesTuple(self.trip_id)
 
  def GetStopTimesTuples(self):
  results = []
  for time_tuple in self._GenerateStopTimesTuples():
  results.append(time_tuple)
  return results
 
  def GetPattern(self):
  """Return a tuple of Stop objects, in the order visited"""
  stoptimes = self.GetStopTimes()
  return tuple(st.stop for st in stoptimes)
 
  def AddHeadwayPeriodObject(self, headway_period, problem_reporter):
  """Deprecated. Please use AddFrequencyObject instead."""
  warnings.warn("No longer supported. The HeadwayPeriod class was renamed to "
  "Frequency, and all related functions were renamed "
  "accordingly.", DeprecationWarning)
  self.AddFrequencyObject(frequency, problem_reporter)
 
  def AddFrequencyObject(self, frequency, problem_reporter):
  """Add a Frequency object to this trip's list of Frequencies."""
  if frequency is not None:
  self.AddFrequency(frequency.StartTime(),
  frequency.EndTime(),
  frequency.HeadwaySecs(),
  problem_reporter)
 
  def AddHeadwayPeriod(self, start_time, end_time, headway_secs,
  problem_reporter=problems_module.default_problem_reporter):
  """Deprecated. Please use AddFrequency instead."""
  warnings.warn("No longer supported. The HeadwayPeriod class was renamed to "
  "Frequency, and all related functions were renamed "
  "accordingly.", DeprecationWarning)
  self.AddFrequency(start_time, end_time, headway_secs, problem_reporter)
 
  def AddFrequency(self, start_time, end_time, headway_secs,
  problem_reporter=problems_module.default_problem_reporter):
  """Adds a period to this trip during which the vehicle travels
  at regular intervals (rather than specifying exact times for each stop).
 
  Args:
  start_time: The time at which this headway period starts, either in
  numerical seconds since midnight or as "HH:MM:SS" since midnight.
  end_time: The time at which this headway period ends, either in
  numerical seconds since midnight or as "HH:MM:SS" since midnight.
  This value should be larger than start_time.
  headway_secs: The amount of time, in seconds, between occurences of
  this trip.
  problem_reporter: Optional parameter that can be used to select
  how any errors in the other input parameters will be reported.
  Returns:
  None
  """
  if start_time == None or start_time == '': # 0 is OK
  problem_reporter.MissingValue('start_time')
  return
  if isinstance(start_time, basestring):
  try:
  start_time = util.TimeToSecondsSinceMidnight(start_time)
  except problems_module.Error:
  problem_reporter.InvalidValue('start_time', start_time)
  return
  elif start_time < 0:
  problem_reporter.InvalidValue('start_time', start_time)
 
  if end_time == None or end_time == '':
  problem_reporter.MissingValue('end_time')
  return
  if isinstance(end_time, basestring):
  try:
  end_time = util.TimeToSecondsSinceMidnight(end_time)
  except problems_module.Error:
  problem_reporter.InvalidValue('end_time', end_time)
  return
  elif end_time < 0:
  problem_reporter.InvalidValue('end_time', end_time)
  return
 
  if not headway_secs:
  problem_reporter.MissingValue('headway_secs')
  return
  try:
  headway_secs = int(headway_secs)
  except ValueError:
  problem_reporter.InvalidValue('headway_secs', headway_secs)
  return
 
  if headway_secs <= 0:
  problem_reporter.InvalidValue('headway_secs', headway_secs)
  return
 
  if end_time <= start_time:
  problem_reporter.InvalidValue('end_time', end_time,
  'should be greater than start_time')
 
  self._headways.append((start_time, end_time, headway_secs))
 
  def ClearFrequencies(self):
  self._headways = []
 
  def _HeadwayOutputTuple(self, headway):
  return (self.trip_id,
  util.FormatSecondsSinceMidnight(headway[0]),
  util.FormatSecondsSinceMidnight(headway[1]),
  unicode(headway[2]))
 
  def GetFrequencyOutputTuples(self):
  tuples = []
  for headway in self._headways:
  tuples.append(self._HeadwayOutputTuple(headway))
  return tuples
 
  def GetFrequencyTuples(self):
  return self._headways
 
  def __getattr__(self, name):
  if name == 'service_period':
  assert self._schedule, "Must be in a schedule to get service_period"
  return self._schedule.GetServicePeriod(self.service_id)
  elif name == 'pattern_id':
  if '_pattern_id' not in self.__dict__:
  self.__dict__['_pattern_id'] = hash(self.GetPattern())
  return self.__dict__['_pattern_id']
  else:
  return GtfsObjectBase.__getattr__(self, name)
 
  def ValidateRouteId(self, problems):
  if util.IsEmpty(self.route_id):
  problems.MissingValue('route_id')
 
  def ValidateServicePeriod(self, problems):
  if 'service_period' in self.__dict__:
  # Some tests assign to the service_period attribute. Patch up self before
  # proceeding with validation. See also comment in Trip.__init__.
  self.service_id = self.__dict__['service_period'].service_id
  del self.service_period
  if util.IsEmpty(self.service_id):
  problems.MissingValue('service_id')
 
  def ValidateTripId(self, problems):
  if util.IsEmpty(self.trip_id):
  problems.MissingValue('trip_id')
 
  def ValidateDirectionId(self, problems):
  if hasattr(self, 'direction_id') and (not util.IsEmpty(self.direction_id)) \
  and (self.direction_id != '0') and (self.direction_id != '1'):
  problems.InvalidValue('direction_id', self.direction_id,
  'direction_id must be "0" or "1"')
 
  def ValidateShapeIdsExistInShapeList(self, problems):
  if self._schedule:
  if self.shape_id and self.shape_id not in self._schedule._shapes:
  problems.InvalidValue('shape_id', self.shape_id)
 
  def ValidateRouteIdExistsInRouteList(self, problems):
  if self._schedule:
  if self.route_id and self.route_id not in self._schedule.routes:
  problems.InvalidValue('route_id', self.route_id)
 
  def ValidateServiceIdExistsInServiceList(self, problems):
  if self._schedule:
  if (self.service_id and
  self.service_id not in self._schedule.service_periods):
  problems.InvalidValue('service_id', self.service_id)
 
  def Validate(self, problems, validate_children=True):
  """Validate attributes of this object.
 
  Check that this object has all required values set to a valid value without
  reference to the rest of the schedule. If the _schedule attribute is set
  then check that references such as route_id and service_id are correct.
 
  Args:
  problems: A ProblemReporter object
  validate_children: if True and the _schedule attribute is set than call
  ValidateChildren
  """
  self.ValidateRouteId(problems)
  self.ValidateServicePeriod(problems)
  self.ValidateDirectionId(problems)
  self.ValidateTripId(problems)
  self.ValidateShapeIdsExistInShapeList(problems)
  self.ValidateRouteIdExistsInRouteList(problems)
  self.ValidateServiceIdExistsInServiceList(problems)
  if self._schedule and validate_children:
  self.ValidateChildren(problems)
 
  def ValidateNoDuplicateStopSequences(self, problems):
  cursor = self._schedule._connection.cursor()
  cursor.execute("SELECT COUNT(stop_sequence) AS a FROM stop_times "
  "WHERE trip_id=? GROUP BY stop_sequence HAVING a > 1",
  (self.trip_id,))
  for row in cursor:
  problems.InvalidValue('stop_sequence', row[0],
  'Duplicate stop_sequence in trip_id %s' %
  self.trip_id)
 
  def ValidateTripStartAndEndTimes(self, problems, stoptimes):
  if stoptimes:
  if stoptimes[0].arrival_time is None and stoptimes[0].departure_time is None:
  problems.OtherProblem(
  'No time for start of trip_id "%s""' % (self.trip_id))
  if stoptimes[-1].arrival_time is None and stoptimes[-1].departure_time is None:
  problems.OtherProblem(
  'No time for end of trip_id "%s""' % (self.trip_id))
 
  def ValidateStopTimesSequenceHasIncreasingTimeAndDistance(self,
  problems,
  stoptimes):
  if stoptimes:
  route_class = self.GetGtfsFactory().Route
  # Checks that the arrival time for each time point is after the departure
  # time of the previous. Assumes a stoptimes sorted by sequence
  prev_departure = 0
  prev_stop = None
  prev_distance = None
  try:
  route_type = self._schedule.GetRoute(self.route_id).route_type
  max_speed = route_class._ROUTE_TYPES[route_type]['max_speed']
  except KeyError, e:
  # If route_type cannot be found, assume it is 0 (Tram) for checking
  # speeds between stops.
  max_speed = route_class._ROUTE_TYPES[0]['max_speed']
  for timepoint in stoptimes:
  # Distance should be a nonnegative float number, so it should be
  # always larger than None.
  distance = timepoint.shape_dist_traveled
  if distance is not None:
  if distance > prev_distance and distance >= 0:
  prev_distance = distance
  else:
  if distance == prev_distance:
  type = problems_module.TYPE_WARNING
  else:
  type = problems_module.TYPE_ERROR
  problems.InvalidValue('stoptimes.shape_dist_traveled', distance,
  'For the trip %s the stop %s has shape_dist_traveled=%s, '
  'which should be larger than the previous ones. In this '
  'case, the previous distance was %s.' %
  (self.trip_id, timepoint.stop_id, distance, prev_distance),
  type=type)
 
  if timepoint.arrival_secs is not None:
  self._CheckSpeed(prev_stop, timepoint.stop, prev_departure,
  timepoint.arrival_secs, max_speed, problems)
 
  if timepoint.arrival_secs >= prev_departure:
  prev_departure = timepoint.departure_secs
  prev_stop = timepoint.stop
  else:
  problems.OtherProblem('Timetravel detected! Arrival time '
  'is before previous departure '
  'at sequence number %s in trip %s' %
  (timepoint.stop_sequence, self.trip_id))
 
  def ValidateShapeDistTraveledSmallerThanMaxShapeDistance(self,
  problems,
  stoptimes):
  if stoptimes:
  if self.shape_id and self.shape_id in self._schedule._shapes:
  shape = self._schedule.GetShape(self.shape_id)
  max_shape_dist = shape.max_distance
  st = stoptimes[-1]
  if (st.shape_dist_traveled and
  st.shape_dist_traveled > max_shape_dist):
  problems.OtherProblem(
  'In stop_times.txt, the stop with trip_id=%s and '
  'stop_sequence=%d has shape_dist_traveled=%f, which is larger '
  'than the max shape_dist_traveled=%f of the corresponding '
  'shape (shape_id=%s)' %
  (self.trip_id, st.stop_sequence, st.shape_dist_traveled,
  max_shape_dist, self.shape_id),
  type=problems_module.TYPE_WARNING)
 
  def ValidateDistanceFromStopToShape(self, problems, stoptimes):
  if stoptimes:
  if self.shape_id and self.shape_id in self._schedule._shapes:
  shape = self._schedule.GetShape(self.shape_id)
  max_shape_dist = shape.max_distance
  st = stoptimes[-1]
  # shape_dist_traveled is valid in shape if max_shape_dist larger than
  # 0.
  if max_shape_dist > 0:
  for st in stoptimes:
  if st.shape_dist_traveled is None:
  continue
  pt = shape.GetPointWithDistanceTraveled(st.shape_dist_traveled)
  if pt:
  stop = self._schedule.GetStop(st.stop_id)
  distance = util.ApproximateDistance(stop.stop_lat, stop.stop_lon,
  pt[0], pt[1])
  if distance > problems_module.MAX_DISTANCE_FROM_STOP_TO_SHAPE:
  problems.StopTooFarFromShapeWithDistTraveled(
  self.trip_id, stop.stop_name, stop.stop_id, pt[2],
  self.shape_id, distance,
  problems_module.MAX_DISTANCE_FROM_STOP_TO_SHAPE)
 
  def ValidateFrequencies(self, problems):
  # O(n^2), but we don't anticipate many headway periods per trip
  for headway_index, headway in enumerate(self._headways[0:-1]):
  for other in self._headways[headway_index + 1:]:
  if (other[0] < headway[1]) and (other[1] > headway[0]):
  problems.OtherProblem('Trip contains overlapping headway periods '
  '%s and %s' %
  (self._HeadwayOutputTuple(headway),
  self._HeadwayOutputTuple(other)))
 
  def ValidateChildren(self, problems):
  """Validate StopTimes and headways of this trip."""
  assert self._schedule, "Trip must be in a schedule to ValidateChildren"
  # TODO: validate distance values in stop times (if applicable)
 
  self.ValidateNoDuplicateStopSequences(problems)
  stoptimes = self.GetStopTimes(problems)
  stoptimes.sort(key=lambda x: x.stop_sequence)
  self.ValidateTripStartAndEndTimes(problems, stoptimes)
  self.ValidateStopTimesSequenceHasIncreasingTimeAndDistance(problems,
  stoptimes)
  self.ValidateShapeDistTraveledSmallerThanMaxShapeDistance(problems,
  stoptimes)
  self.ValidateDistanceFromStopToShape(problems, stoptimes)
  self.ValidateFrequencies(problems)
 
  def ValidateBeforeAdd(self, problems):
  return True
 
  def ValidateAfterAdd(self, problems):
  self.Validate(problems)
 
  def _CheckSpeed(self, prev_stop, next_stop, depart_time,
  arrive_time, max_speed, problems):
  # Checks that the speed between two stops is not faster than max_speed
  if prev_stop != None:
  try:
  time_between_stops = arrive_time - depart_time
  except TypeError:
  return
 
  try:
  dist_between_stops = \
  util.ApproximateDistanceBetweenStops(next_stop, prev_stop)
  except TypeError, e:
  return
 
  if time_between_stops == 0:
  # HASTUS makes it hard to output GTFS with times to the nearest second;
  # it rounds times to the nearest minute. Therefore stop_times at the
  # same time ending in :00 are fairly common. These times off by no more
  # than 30 have not caused a problem. See
  # http://code.google.com/p/googletransitdatafeed/issues/detail?id=193
  # Show a warning if times are not rounded to the nearest minute or
  # distance is more than max_speed for one minute.
  if depart_time % 60 != 0 or dist_between_stops / 1000 * 60 > max_speed:
  problems.TooFastTravel(self.trip_id,
  prev_stop.stop_name,
  next_stop.stop_name,
  dist_between_stops,
  time_between_stops,
  speed=None,
  type=problems_module.TYPE_WARNING)
  return
  # This needs floating point division for precision.
  speed_between_stops = ((float(dist_between_stops) / 1000) /
  (float(time_between_stops) / 3600))
  if speed_between_stops > max_speed:
  problems.TooFastTravel(self.trip_id,
  prev_stop.stop_name,
  next_stop.stop_name,
  dist_between_stops,
  time_between_stops,
  speed_between_stops,
  type=problems_module.TYPE_WARNING)
 
  def AddToSchedule(self, schedule, problems):
  schedule.AddTripObject(self, problems)
 
  #!/usr/bin/python2.5
 
  # Copyright (C) 2009 Google Inc.
  #
  # Licensed under the Apache License, Version 2.0 (the "License");
  # you may not use this file except in compliance with the License.
  # You may obtain a copy of the License at
  #
  # http://www.apache.org/licenses/LICENSE-2.0
  #
  # Unless required by applicable law or agreed to in writing, software
  # distributed under the License is distributed on an "AS IS" BASIS,
  # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  # See the License for the specific language governing permissions and
  # limitations under the License.
 
  import codecs
  import csv
  import datetime
  import math
  import optparse
  import random
  import re
  import sys
 
  import problems
  from trip import Trip
 
  class OptionParserLongError(optparse.OptionParser):
  """OptionParser subclass that includes list of options above error message."""
  def error(self, msg):
  print >>sys.stderr, self.format_help()
  print >>sys.stderr, '\n\n%s: error: %s\n\n' % (self.get_prog_name(), msg)
  sys.exit(2)
 
 
  def RunWithCrashHandler(f):
  try:
  exit_code = f()
  sys.exit(exit_code)
  except (SystemExit, KeyboardInterrupt):
  raise
  except:
  import inspect
  import traceback
 
  # Save trace and exception now. These calls look at the most recently
  # raised exception. The code that makes the report might trigger other
  # exceptions.
  original_trace = inspect.trace(3)[1:]
  formatted_exception = traceback.format_exception_only(*(sys.exc_info()[:2]))
 
  apology = """Yikes, the program threw an unexpected exception!
 
  Hopefully a complete report has been saved to transitfeedcrash.txt,
  though if you are seeing this message we've already disappointed you once
  today. Please include the report in a new issue at
  http://code.google.com/p/googletransitdatafeed/issues/entry
  or an email to the public group googletransitdatafeed@googlegroups.com. Sorry!
 
  """
  dashes = '%s\n' % ('-' * 60)
  dump = []
  dump.append(apology)
  dump.append(dashes)
  try:
  import transitfeed
  dump.append("transitfeed version %s\n\n" % transitfeed.__version__)
  except NameError:
  # Oh well, guess we won't put the version in the report
  pass
 
  for (frame_obj, filename, line_num, fun_name, context_lines,
  context_index) in original_trace:
  dump.append('File "%s", line %d, in %s\n' % (filename, line_num,
  fun_name))
  if context_lines:
  for (i, line) in enumerate(context_lines):
  if i == context_index:
  dump.append(' --> %s' % line)
  else:
  dump.append(' %s' % line)
  for local_name, local_val in frame_obj.f_locals.items():
  try:
  truncated_val = str(local_val)[0:500]
  except Exception, e:
  dump.append(' Exception in str(%s): %s' % (local_name, e))
  else:
  if len(truncated_val) >= 500:
  truncated_val = '%s...' % truncated_val[0:499]
  dump.append(' %s = %s\n' % (local_name, truncated_val))
  dump.append('\n')
 
  dump.append(''.join(formatted_exception))
 
  open('transitfeedcrash.txt', 'w').write(''.join(dump))
 
  print ''.join(dump)
  print
  print dashes
  print apology
 
  try:
  raw_input('Press enter to continue...')
  except EOFError:
  # Ignore stdin being closed. This happens during some tests.
  pass
  sys.exit(127)
 
 
  # Pick one of two defaultdict implementations. A native version was added to
  # the collections library in python 2.5. If that is not available use Jason's
  # pure python recipe. He gave us permission to distribute it.
 
  # On Mon, Nov 30, 2009 at 07:27, jason kirtland <jek at discorporate.us> wrote:
  # >
  # > Hi Tom, sure thing! It's not easy to find on the cookbook site, but the
  # > recipe is under the Python license.
  # >
  # > Cheers,
  # > Jason
  # >
  # > On Thu, Nov 26, 2009 at 3:03 PM, Tom Brown <tom.brown.code@gmail.com> wrote:
  # >
  # >> I would like to include http://code.activestate.com/recipes/523034/ in
  # >> http://code.google.com/p/googletransitdatafeed/wiki/TransitFeedDistribution
  # >> which is distributed under the Apache License, Version 2.0 with Copyright
  # >> Google. May we include your code with a comment in the source pointing at
  # >> the original URL? Thanks, Tom Brown
 
  try:
  # Try the native implementation first
  from collections import defaultdict
  except:
  # Fallback for python2.4, which didn't include collections.defaultdict
  class defaultdict(dict):
  def __init__(self, default_factory=None, *a, **kw):
  if (default_factory is not None and
  not hasattr(default_factory, '__call__')):
  raise TypeError('first argument must be callable')
  dict.__init__(self, *a, **kw)
  self.default_factory = default_factory
  def __getitem__(self, key):
  try:
  return dict.__getitem__(self, key)
  except KeyError:
  return self.__missing__(key)
  def __missing__(self, key):
  if self.default_factory is None:
  raise KeyError(key)
  self[key] = value = self.default_factory()
  return value
  def __reduce__(self):
  if self.default_factory is None:
  args = tuple()
  else:
  args = self.default_factory,
  return type(self), args, None, None, self.items()
  def copy(self):
  return self.__copy__()
  def __copy__(self):
  return type(self)(self.default_factory, self)
  def __deepcopy__(self, memo):
  import copy
  return type(self)(self.default_factory,
  copy.deepcopy(self.items()))
  def __repr__(self):
  return 'defaultdict(%s, %s)' % (self.default_factory,
  dict.__repr__(self))
 
 
 
  OUTPUT_ENCODING = 'utf-8'
 
  def EncodeUnicode(text):
  """
  Optionally encode text and return it. The result should be safe to print.
  """
  if type(text) == type(u''):
  return text.encode(OUTPUT_ENCODING)
  else:
  return text
 
  def IsValidURL(url):
  """Checks the validity of a URL value."""
  # TODO: Add more thorough checking of URL
  return url.startswith(u'http://') or url.startswith(u'https://')
 
 
  def IsValidColor(color):
  """Checks the validity of a hex color value."""
  return not re.match('^[0-9a-fA-F]{6}$', color) == None
 
 
  def ColorLuminance(color):
  """Compute the brightness of an sRGB color using the formula from
  http://www.w3.org/TR/2000/WD-AERT-20000426#color-contrast.
 
  Args:
  color: a string of six hex digits in the format verified by IsValidColor().
 
  Returns:
  A floating-point number between 0.0 (black) and 255.0 (white). """
  r = int(color[0:2], 16)
  g = int(color[2:4], 16)
  b = int(color[4:6], 16)
  return (299*r + 587*g + 114*b) / 1000.0
 
 
  def IsEmpty(value):
  return value is None or (isinstance(value, basestring) and not value.strip())
 
 
  def FindUniqueId(dic):
  """Return a string not used as a key in the dictionary dic"""
  name = str(len(dic))
  while name in dic:
  # Use bigger numbers so it is obvious when an id is picked randomly.
  name = str(random.randint(1000000, 999999999))
  return name
 
 
  def TimeToSecondsSinceMidnight(time_string):
  """Convert HHH:MM:SS into seconds since midnight.
 
  For example "01:02:03" returns 3723. The leading zero of the hours may be
  omitted. HH may be more than 23 if the time is on the following day."""
  m = re.match(r'(\d{1,3}):([0-5]\d):([0-5]\d)$', time_string)
  # ignored: matching for leap seconds
  if not m:
  raise problems.Error, 'Bad HH:MM:SS "%s"' % time_string
  return int(m.group(1)) * 3600 + int(m.group(2)) * 60 + int(m.group(3))
 
 
  def FormatSecondsSinceMidnight(s):
  """Formats an int number of seconds past midnight into a string
  as "HH:MM:SS"."""
  return "%02d:%02d:%02d" % (s / 3600, (s / 60) % 60, s % 60)
 
 
  def DateStringToDateObject(date_string):
  """Return a date object for a string "YYYYMMDD"."""
  # If this becomes a bottleneck date objects could be cached
  return datetime.date(int(date_string[0:4]), int(date_string[4:6]),
  int(date_string[6:8]))
 
 
  def FloatStringToFloat(float_string, problems=None):
  """Convert a float as a string to a float or raise an exception"""
  # Will raise TypeError unless a string
  match = re.match(r"^[+-]?\d+(\.\d+)?$", float_string)
  # Will raise TypeError if the string can't be parsed
  parsed_value = float(float_string)
 
  if "x" in float_string:
  # This is needed because Python 2.4 does not complain about float("0x20").
  # But it does complain about float("0b10"), so this should be enough.
  raise ValueError()
 
  if not match and problems is not None:
  # Does not match the regex, but it's a float according to Python
  problems.InvalidFloatValue(float_string)
  return parsed_value
 
  def NonNegIntStringToInt(int_string, problems=None):
  """Convert an non-negative integer string to an int or raise an exception"""
  # Will raise TypeError unless a string
  match = re.match(r"^(?:0|[1-9]\d*)$", int_string)
  # Will raise ValueError if the string can't be parsed
  parsed_value = int(int_string)
 
  if parsed_value < 0:
  raise ValueError()
  elif not match and problems is not None:
  # Does not match the regex, but it's an int according to Python
  problems.InvalidNonNegativeIntegerValue(int_string)
 
  return parsed_value
 
  EARTH_RADIUS = 6378135 # in meters
  def ApproximateDistance(degree_lat1, degree_lng1, degree_lat2, degree_lng2):
  """Compute approximate distance between two points in meters. Assumes the
  Earth is a sphere."""
  # TODO: change to ellipsoid approximation, such as
  # http://www.codeguru.com/Cpp/Cpp/algorithms/article.php/c5115/
  lat1 = math.radians(degree_lat1)
  lng1 = math.radians(degree_lng1)
  lat2 = math.radians(degree_lat2)
  lng2 = math.radians(degree_lng2)
  dlat = math.sin(0.5 * (lat2 - lat1))
  dlng = math.sin(0.5 * (lng2 - lng1))
  x = dlat * dlat + dlng * dlng * math.cos(lat1) * math.cos(lat2)
  return EARTH_RADIUS * (2 * math.atan2(math.sqrt(x),
  math.sqrt(max(0.0, 1.0 - x))))
 
 
  def ApproximateDistanceBetweenStops(stop1, stop2):
  """Compute approximate distance between two stops in meters. Assumes the
  Earth is a sphere."""
  return ApproximateDistance(stop1.stop_lat, stop1.stop_lon,
  stop2.stop_lat, stop2.stop_lon)
 
  class CsvUnicodeWriter:
  """
  Create a wrapper around a csv writer object which can safely write unicode
  values. Passes all arguments to csv.writer.
  """
  def __init__(self, *args, **kwargs):
  self.writer = csv.writer(*args, **kwargs)
 
  def writerow(self, row):
  """Write row to the csv file. Any unicode strings in row are encoded as
  utf-8."""
  encoded_row = []
  for s in row:
  if isinstance(s, unicode):
  encoded_row.append(s.encode("utf-8"))
  else:
  encoded_row.append(s)
  try:
  self.writer.writerow(encoded_row)
  except Exception, e:
  print 'error writing %s as %s' % (row, encoded_row)
  raise e
 
  def writerows(self, rows):
  """Write rows to the csv file. Any unicode strings in rows are encoded as
  utf-8."""
  for row in rows:
  self.writerow(row)
 
  def __getattr__(self, name):
  return getattr(self.writer, name)
 
  # Map from literal string that should never be found in the csv data to a human
  # readable description
  INVALID_LINE_SEPARATOR_UTF8 = {
  "\x0c": "ASCII Form Feed 0x0C",
  # May be part of end of line, but not found elsewhere
  "\x0d": "ASCII Carriage Return 0x0D, \\r",
  "\xe2\x80\xa8": "Unicode LINE SEPARATOR U+2028",
  "\xe2\x80\xa9": "Unicode PARAGRAPH SEPARATOR U+2029",
  "\xc2\x85": "Unicode NEXT LINE SEPARATOR U+0085",
  }
 
  class EndOfLineChecker:
  """Wrapper for a file-like object that checks for consistent line ends.
 
  The check for consistent end of lines (all CR LF or all LF) only happens if
  next() is called until it raises StopIteration.
  """
  def __init__(self, f, name, problems):
  """Create new object.
 
  Args:
  f: file-like object to wrap
  name: name to use for f. StringIO objects don't have a name attribute.
  problems: a ProblemReporterBase object
  """
  self._f = f
  self._name = name
  self._crlf = 0
  self._crlf_examples = []
  self._lf = 0
  self._lf_examples = []
  self._line_number = 0 # first line will be number 1
  self._problems = problems
 
  def __iter__(self):
  return self
 
  def next(self):
  """Return next line without end of line marker or raise StopIteration."""
  try:
  next_line = self._f.next()
  except StopIteration:
  self._FinalCheck()
  raise
 
  self._line_number += 1
  m_eol = re.search(r"[\x0a\x0d]*$", next_line)
  if m_eol.group() == "\x0d\x0a":
  self._crlf += 1
  if self._crlf <= 5:
  self._crlf_examples.append(self._line_number)
  elif m_eol.group() == "\x0a":
  self._lf += 1
  if self._lf <= 5:
  self._lf_examples.append(self._line_number)
  elif m_eol.group() == "":
  # Should only happen at the end of the file
  try:
  self._f.next()
  raise RuntimeError("Unexpected row without new line sequence")
  except StopIteration:
  # Will be raised again when EndOfLineChecker.next() is next called
  pass
  else:
  self._problems.InvalidLineEnd(
  codecs.getencoder('string_escape')(m_eol.group())[0],
  (self._name, self._line_number))
  next_line_contents = next_line[0:m_eol.start()]
  for seq, name in INVALID_LINE_SEPARATOR_UTF8.items():
  if next_line_contents.find(seq) != -1:
  self._problems.OtherProblem(
  "Line contains %s" % name,
  context=(self._name, self._line_number))
  return next_line_contents
 
  def _FinalCheck(self):
  if self._crlf > 0 and self._lf > 0:
  crlf_plural = self._crlf > 1 and "s" or ""
  crlf_lines = ", ".join(["%s" % e for e in self._crlf_examples])
  if self._crlf > len(self._crlf_examples):
  crlf_lines += ", ..."
  lf_plural = self._lf > 1 and "s" or ""
  lf_lines = ", ".join(["%s" % e for e in self._lf_examples])
  if self._lf > len(self._lf_examples):
  lf_lines += ", ..."
 
  self._problems.OtherProblem(
  "Found %d CR LF \"\\r\\n\" line end%s (line%s %s) and "
  "%d LF \"\\n\" line end%s (line%s %s). A file must use a "
  "consistent line end." % (self._crlf, crlf_plural, crlf_plural,
  crlf_lines, self._lf, lf_plural,
  lf_plural, lf_lines),
  (self._name,))
  # Prevent _FinalCheck() from reporting the problem twice, in the unlikely
  # case that it is run twice
  self._crlf = 0
  self._lf = 0
 
  def SortListOfTripByTime(trips):
  trips.sort(key=Trip.GetStartTime)
 
  #!/usr/bin/python2.5
 
  # Copyright (C) 2007 Google Inc.
  #
  # Licensed under the Apache License, Version 2.0 (the "License");
  # you may not use this file except in compliance with the License.
  # You may obtain a copy of the License at
  #
  # http://www.apache.org/licenses/LICENSE-2.0
  #
  # Unless required by applicable law or agreed to in writing, software
  # distributed under the License is distributed on an "AS IS" BASIS,
  # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  # See the License for the specific language governing permissions and
  # limitations under the License.
 
  """
  Filters out trips which are not on the defualt routes and
  set their trip_typeattribute accordingly.
 
  For usage information run unusual_trip_filter.py --help
  """
 
  __author__ = 'Jiri Semecky <jiri.semecky@gmail.com>'
 
  import codecs
  import os
  import os.path
  import sys
  import time
  import transitfeed
  from transitfeed import util
 
 
  class UnusualTripFilter(object):
  """Class filtering trips going on unusual paths.
 
  Those are usually trips going to/from depot or changing to another route
  in the middle. Sets the 'trip_type' attribute of the trips.txt dataset
  so that non-standard trips are marked as special (value 1)
  instead of regular (default value 0).
  """
 
  def __init__ (self, threshold=0.1, force=False, quiet=False, route_type=None):
  self._threshold = threshold
  self._quiet = quiet
  self._force = force
  if route_type in transitfeed.Route._ROUTE_TYPE_NAMES:
  self._route_type = transitfeed.Route._ROUTE_TYPE_NAMES[route_type]
  elif route_type is None:
  self._route_type = None
  else:
  self._route_type = int(route_type)
 
  def filter_line(self, route):
  """Mark unusual trips for the given route."""
  if self._route_type is not None and self._route_type != route.route_type:
  self.info('Skipping route %s due to different route_type value (%s)' %
  (route['route_id'], route['route_type']))
  return
  self.info('Filtering infrequent trips for route %s.' % route.route_id)
  trip_count = len(route.trips)
  for pattern_id, pattern in route.GetPatternIdTripDict().items():
  ratio = float(1.0 * len(pattern) / trip_count)
  if not self._force:
  if (ratio < self._threshold):
  self.info("\t%d trips on route %s with headsign '%s' recognized "
  "as unusual (ratio %f)" %
  (len(pattern),
  route['route_short_name'],
  pattern[0]['trip_headsign'],
  ratio))
  for trip in pattern:
  trip.trip_type = 1 # special
  self.info("\t\tsetting trip_type of trip %s as special" %
  trip.trip_id)
  else:
  self.info("\t%d trips on route %s with headsign '%s' recognized "
  "as %s (ratio %f)" %
  (len(pattern),
  route['route_short_name'],
  pattern[0]['trip_headsign'],
  ('regular', 'unusual')[ratio < self._threshold],
  ratio))
  for trip in pattern:
  trip.trip_type = ('0','1')[ratio < self._threshold]
  self.info("\t\tsetting trip_type of trip %s as %s" %
  (trip.trip_id,
  ('regular', 'unusual')[ratio < self._threshold]))
 
  def filter(self, dataset):
  """Mark unusual trips for all the routes in the dataset."""
  self.info('Going to filter infrequent routes in the dataset')
  for route in dataset.routes.values():
  self.filter_line(route)
 
  def info(self, text):
  if not self._quiet:
  print text.encode("utf-8")
 
 
  def main():
  usage = \
  '''%prog [options] <GTFS.zip>
 
  Sets the trip_type for trips that have an unusual pattern for a route.
  <GTFS.zip> is overwritten with the modifed GTFS file unless the --output
  option is used.
 
  For more information see
  http://code.google.com/p/googletransitdatafeed/wiki/UnusualTripFilter
  '''
  parser = util.OptionParserLongError(
  usage=usage, version='%prog '+transitfeed.__version__)
  parser.add_option('-o', '--output', dest='output', metavar='FILE',
  help='Name of the output GTFS file (writing to input feed if omitted).')
  parser.add_option('-m', '--memory_db', dest='memory_db', action='store_true',
  help='Force use of in-memory sqlite db.')
  parser.add_option('-t', '--threshold', default=0.1,
  dest='threshold', type='float',
  help='Frequency threshold for considering pattern as non-regular.')
  parser.add_option('-r', '--route_type', default=None,
  dest='route_type', type='string',
  help='Filter only selected route type (specified by number'
  'or one of the following names: ' + \
  ', '.join(transitfeed.Route._ROUTE_TYPE_NAMES) + ').')
  parser.add_option('-f', '--override_trip_type', default=False,
  dest='override_trip_type', action='store_true',
  help='Forces overwrite of current trip_type values.')
  parser.add_option('-q', '--quiet', dest='quiet',
  default=False, action='store_true',
  help='Suppress information output.')
 
  (options, args) = parser.parse_args()
  if len(args) != 1:
  parser.error('You must provide the path of a single feed.')
 
  filter = UnusualTripFilter(float(options.threshold),
  force=options.override_trip_type,
  quiet=options.quiet,
  route_type=options.route_type)
  feed_name = args[0]
  feed_name = feed_name.strip()
  filter.info('Loading %s' % feed_name)
  loader = transitfeed.Loader(feed_name, extra_validation=True,
  memory_db=options.memory_db)
  data = loader.Load()
  filter.filter(data)
  print 'Saving data'
 
  # Write the result
  if options.output is None:
  data.WriteGoogleTransitFeed(feed_name)
  else:
  data.WriteGoogleTransitFeed(options.output)
 
 
  if __name__ == '__main__':
  util.RunWithCrashHandler(main)