More between points work + mobile UI work:
More between points work + mobile UI work:

  <?php
  /*
  * GeoPo Encode in PHP
  * @author : Shintaro Inagaki
  * @param $location (Array)
  * @return $geopo (String)
  */
  function geopoEncode($lat, $lng) {
  // 64characters (number + big and small letter + hyphen + underscore)
  $chars = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ-_";
 
  $geopo = "";
  $scale = 7;
 
  // Change a degree measure to a decimal number
  $lat = ($lat + 90) / 180 * pow(8, 10);
  $lng = ($lng + 180) / 360 * pow(8, 10);
  // Compute a GeoPo code from head and concatenate
  for($i = 0; $i < $scale; $i++) {
  $geopo .= substr($chars, floor($lat / pow(8, 9 - $i) % 8) + floor($lng / pow(8, 9 - $i) % 8) * 8, 1);
  }
  return $geopo;
  }
 
  /*
  * GeoPo Decode in PHP
  * @author : Shintaro Inagaki
  * @param $geopo (String)
  * @return $location (Array)
  */
  function geopoDecode($geopo) {
  // 64characters (number + big and small letter + hyphen + underscore)
  $chars = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ-_";
  // Array for geolocation
  $location = array ();
 
  for ($i = 0; $i < strlen($geopo); $i++) {
  // What number of character that equal to a GeoPo code (0-63)
  $order = strpos($chars, substr($geopo, $i, 1));
  // Lat/Lng plus geolocation value of scale
  $location['lat'] = $location['lat'] + floor($order % 8) * pow(8, 9 - $i);
  $location['lng'] = $location['lng'] + floor($order / 8) * pow(8, 9 - $i);
  }
 
  // Change a decimal number to a degree measure, and plus revised value that shift center of area
  $location['lat'] = $location['lat'] * 180 / pow(8, 10) + 180 / pow(8, strlen($geopo)) / 2 - 90;
  $location['lng'] = $location['lng'] * 360 / pow(8, 10) + 360 / pow(8, strlen($geopo)) / 2 - 180;
  $location['scale'] = strlen($geopo);
 
  return $location;
  }
 
  $conn = pg_connect("dbname=bus user=postgres password=snmc");
  if (!$conn) {
  echo "An error occured.\n";
  exit;
  }
  $xml = simplexml_load_file("04-locatebetweenpoints.osm");
 
  /* Access the <rating> nodes of the first movie.
  * Output the rating scale, too. */
  foreach ($xml->node as $node) {
  $geoPo = geopoEncode((float)$node['lat'],(float)$node['lon']);
  $node['lat'] = (int) ((float)$node['lat']*10000000);
  $node['lon'] = (int) ((float)$node['lon']*10000000);
  echo($node['lat'].",".$node['lon']."=$geoPo<br>");
  $sql = "INSERT INTO stops (geohash, lat, lng) VALUES('$geoPo','{$node['lat']}','{$node['lon']}')";
  $result = pg_query($conn, $sql);
  if (!$result) {
  echo("Error in SQL query: " . pg_last_error() ."<br>\n");
  }
  flush();
 
  }
  ?>
 
<?php <?php
date_default_timezone_set('Australia/ACT'); date_default_timezone_set('Australia/ACT');
  $APIurl = "http://localhost:8765";
   
  function isDebug()
  {
  return true;
  }
   
  function isFastDevice() {
  return true;
  }
   
  function include_header($pageTitle) {
  // if (isDebug()) // set php error level high
  echo '
  <!DOCTYPE html>
  <html>
  <head>
  <title>bus.lambdacomplex.org - '.$pageTitle.'</title>
  <link rel="stylesheet" href="http://code.jquery.com/mobile/1.0a1/jquery.mobile-1.0a1.min.css" />
  <script src="http://code.jquery.com/jquery-1.4.3.min.js"></script><script type="text/javascript"
  src="http://code.jquery.com/mobile/1.0a1/jquery.mobile-1.0a1.min.js"></script>
  <script type="text/javascript" src="docs/docs.js"></script>
  </head>
  <body>
   
  <div data-role="page">
   
  <div data-role="header">
  <h1>'.$pageTitle.'</h1>
  </div><!-- /header -->
  <div data-role="content"> ';
  }
   
  function include_footer()
  {
  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()
  {
  // from http://www.perturb.org/display/Perlfunc__Seconds_Since_Midnight.html
  $secs = (date("G") * 3600) + (date("i") * 60) + date("s");
  return $secs;
  }
   
  function midnight_seconds_to_time($seconds)
  {
  $midnight = mktime (0, 0, 0, date("n"), date("j"), date("Y"));
  return date("h:ia",$midnight+$seconds);
  }
  function getPage($url)
  {
  $ch = curl_init($url);
  curl_setopt( $ch, CURLOPT_RETURNTRANSFER, 1 );
  curl_setopt( $ch, CURLOPT_HEADER, 0 );
  $page = curl_exec($ch);
  curl_close($ch);
  return $page;
  }
  function array_flatten($a,$f=array()){
  if(!$a||!is_array($a))return '';
  foreach($a as $k=>$v){
  if(is_array($v))$f=array_flatten($v,$f);
  else $f[$k]=$v;
  }
  return $f;
  }
?> ?>
   
  <?php
  include('common.inc.php');
  ?>
<!doctype html> <!doctype html>
<html> <html>
<head> <head>
<meta charset="UTF-8" /> <meta charset="UTF-8" />
<title>jQTouch &beta;</title> <title>bus.lambdacomplex.org</title>
<style type="text/css" media="screen">@import "jqtouch/jqtouch.min.css";</style> <link rel="stylesheet" href="http://code.jquery.com/mobile/1.0a1/jquery.mobile-1.0a1.min.css" />
<style type="text/css" media="screen">@import "themes/jqt/theme.min.css";</style> <script src="http://code.jquery.com/jquery-1.4.3.min.js"></script>
<script src="jqtouch/jquery.1.3.2.min.js" type="text/javascript" charset="utf-8"></script> <script src="http://code.jquery.com/mobile/1.0a1/jquery.mobile-1.0a1.min.js"></script>
<script src="jqtouch/jqtouch.min.js" type="application/x-javascript" charset="utf-8"></script> <!-- <script src="jqtouch/extensions/jqt.location.js" type="application/x-javascript" charset="utf-8"></script> -->
<script src="extensions/jqt.location.js" type="application/x-javascript" charset="utf-8"></script>  
<script type="text/javascript" charset="utf-8"> <!-- <script type="text/javascript" charset="utf-8">
var jQT = new $.jQTouch({  
icon: 'jqtouch.png',  
addGlossToIcon: false,  
startupScreen: 'jqt_startup.png',  
statusBar: 'black',  
preloadImages: [  
'themes/jqt/img/back_button.png',  
'themes/jqt/img/back_button_clicked.png',  
'themes/jqt/img/button_clicked.png',  
'themes/jqt/img/grayButton.png',  
'themes/jqt/img/whiteButton.png',  
'themes/jqt/img/loading.gif'  
]  
});  
$(function(){ $(function(){
function setDisplay(text) { function setDisplay(text) {
$('.info').empty().append(text) $('.latlng').empty().append(text)
} }
// We pass "updateLocation" a callback function, // We pass "updateLocation" a callback function,
// to run once we have the coordinates. // to run once we have the coordinates.
// We also set it to a variable, so we can know // We also set it to a variable, so we can know
// right away if it's working or not // right away if it's working or not
var lookup = jQT.updateLocation(function(coords){ var lookup = jQT.updateLocation(function(coords){
if (coords) { if (coords) {
setDisplay('Latitude: ' + coords.latitude + '<br />Longitude: ' + coords.longitude); setDisplay('Latitude: ' + coords.latitude + '<br />Longitude: ' + coords.longitude);
$('.nearby').empty().append('<a href="list.php?lat=' + coords.latitude + '&lng=' + coords.longitude + '">Nearby List</a>'); $('.nearby').empty().append('<a href="list.php?lat=' + coords.latitude + '&lng=' + coords.longitude + '">Nearby List</a>');
} else { } else {
setDisplay('Device not capable of geo-location.'); setDisplay('Device not capable of geo-location.');
} }
}); });
if (lookup) { if (lookup) {
setDisplay('Looking up location&hellip;'); setDisplay('Looking up location&hellip;');
} }
}); });
// Some sample Javascript functions: </script> -->
$(function(){  
$('a[target="_blank"]').click(function() {  
if (confirm('This link opens in a new window.')) {  
return true;  
} else {  
$(this).removeClass('active');  
return false;  
}  
});  
// Page animation callback events  
$('#pageevents').  
bind('pageAnimationStart', function(e, info){  
$(this).find('.info').append('Started animating ' + info.direction + '&hellip; ');  
}).  
bind('pageAnimationEnd', function(e, info){  
$(this).find('.info').append(' finished animating ' + info.direction + '.<br /><br />');  
});  
// Page animations end with AJAX callback event, example 1 (load remote HTML only first time)  
$('#callback').bind('pageAnimationEnd', function(e, info){  
if (!$(this).data('loaded')) { // Make sure the data hasn't already been loaded (we'll set 'loaded' to true a couple lines further down)  
$(this).append($('<div>Loading</div>'). // Append a placeholder in case the remote HTML takes its sweet time making it back  
load('ajax.html .info', function() { // Overwrite the "Loading" placeholder text with the remote HTML  
$(this).parent().data('loaded', true); // Set the 'loaded' var to true so we know not to re-load the HTML next time the #callback div animation ends  
}));  
}  
});  
// Orientation callback event  
$('body').bind('turn', function(e, data){  
$('#orient').html('Orientation: ' + data.orientation);  
});  
});  
</script>  
</head> </head>
<body> <body>
<div id="home" class="current"> <div data-role="page" data-theme="b" id="jqm-home">
<div class="toolbar"> <div id="jqm-homeheader">
<h1>jQTouch</h1> <p>bus.lambdacomplex.org</p>
<a class="button slideup" id="infoButton" href="#about">About</a> </div>
</div> <div data-role="content">
<ul class="rounded"> <ul data-role="listview" data-inset="true" data-theme="c" data-dividertheme="b">
<li class="arrow"><a href="#ui">User Interface</a> <small class="counter">4</small></li> <li data-role="list-divider">Stops</li>
<li class="arrow"><a href="list.php">All stops List</a></li> <li><a href="stopList.php">All stops List</a></li>
<li class="arrow nearby">Nearby List</li> <li class="nearby"><a href="">Nearby List</a></li>
<li class="arrow"><a href="list.php">Favourites List</a></li> <li><a href="stopList.php">Favourites List</a></li>
  </ul>
  <ul data-role="listview" data-inset="true" data-theme="c" data-dividertheme="b">
  <li data-role="list-divider">Routes</li>
  <li><a href="routeList.php">All Routes List</a></li>
  <li class="nearby"><a href="">Nearby List</a></li>
  <li><a href="routeList.php">Favourites List</a></li>
</ul> </ul>
<div class="info"> <div class="info">
<p>Add this page to your home screen to view the custom icon, startup screen, and full screen mode.</p> <p class="latlng"></p>
  Time: <?php echo date("H:m"); ?> <br>
  Service Period: <?php echo ucwords(service_period()); ?>
</div> </div>
</div> </div>
</body> </div>
  </body>
</html> </html>
   
  # input location (via GPS or favourites or search) and destination (via searchable list, optional)
  # http://10.0.1.153:8765/json/boundboxstops?n=-35.27568499917103&e=149.1346514225006&s=-35.279495003493516
  &w=149.12622928619385&limit=50
  # http://10.0.1.153:8765/json/stoptrips?stop=43&time=64440 # recursively call to show all services nearby, sort by distance, need to filter by service period
  # Hey, can pick destination again from a list filtered to places these stops go if you're curious!
  # http://10.0.1.153:8765/json/tripstoptimes?trip=2139 # Can recursively call and parse based on intended destination to show ETA
  # http://10.0.1.153:8765/json/triprows?trip=2139 # For pretty maps
   
  have to do
  /usr/sbin/setsebool -P httpd_can_network_connect=1
  on fedora
   
  might need http://forum.jquery.com/topic/google-maps-inside-jquery-mobile
   
  some extras
  /json/routes = all routes
  /json/neareststops?lat/lng/number
TODO TODO
  Destinations
  Favourites
  OOP stops/routes
  Stop sorting/search-filter
   
  <?php
  include('common.inc.php');
  include_header("Routes");
  /* data-filter="true">';
  echo "<script> $('#routeList').listnav({prefixes: ['to'] }); </script>";*/
  echo ' <ul data-role="listview">';
  $url = $APIurl."/json/routes";
   
  $contents = json_decode(getPage($url));
  foreach ($contents as $key => $row) {
  $routeDestinations[$row[2]][] = $row;
  }
  foreach ($routeDestinations as $destination => $routes)
  {
  echo '<li>'.$destination."... <ul>\n";
  foreach($routes as $row) {
  echo '<li>'.$row[1].' <a href="route.php?routeid='.$row[0].'">'.$row[2]."</a></li>\n";
  }
  echo "</ul></li>\n";
  }
  echo "</ul>\n";
  /*
  <div data-role="footer" data-id="foo1" data-position="fixed">
  <div data-role="navbar">
  <ul>
  <li><a href="footer-persist-a.html" class="ui-btn-active">Friends</a></li>
  <li><a href="footer-persist-b.html">Albums</a></li>
  <li><a href="footer-persist-c.html">Emails</a></li>
  </ul>
  </div><!-- /navbar -->
  */
  include_footer();
  ?>
   
  #!/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 = 10 # 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_stops(self, params):
  schedule = self.server.schedule
  return [StopToTuple(s) for s in schedule.GetStopList()]
 
  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_stop(self, params):
  schedule = self.server.schedule
  query = params.get('stop_id', None).lower()
  for s in schedule.GetStopList():
  if s.stop_id.lower() == query:
  return StopToTuple(s)
  return []
 
  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 15 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[:15]
  # 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()
 
  <?php
  include('common.inc.php');
  $url = $APIurl."/json/stop?stop_id=".$_REQUEST['stopid'];
  $stop = json_decode(getPage($url));
   
  include_header("Trips passing ".$stop[1]);
  echo ' <ul data-role="listview" >';
  $url = $APIurl."/json/stoptrips?stop=".$_REQUEST['stopid']."&time=".midnight_seconds();
  $page = getPage($url);
  foreach (json_decode($page) as $row)
  {
  if ($row[1][2] === service_period()) echo '<li>'.midnight_seconds_to_time($row[0]).' <a href="trip.php?stopid='.$_REQUEST['stopid'].'&tripid='.$row[1][0].'">'.$row[1][1].'</a></li>';
  }
  echo '</ul>';
  include_footer();
  ?>
   
<div id="get"> <?php
<div class="toolbar"> include('common.inc.php');
<h1>GET Example</h1> include_header("Stops");
<a class="back" href="#">AJAX</a> echo ' <ul data-role="listview" data-filter="true">';
</div> $url = $APIurl."/json/stops";
<div class="info"> if ($_REQUEST['lat'] && $_REQUEST['lon']) $url = $APIurl."/json/neareststops?lat={$_REQUEST['lat']}&lon={$_REQUEST['lon']}&limit=15";
This page was loaded via AJAX. $contents = json_decode(getPage($url));
</div> foreach ($contents as $key => $row) {
<ul class="rounded"> $stopName[$key] = $row[1];
<li><a href="#livetest">Test live events</a></li> }
</ul>  
</div>  
<div id="livetest">  
<div class="toolbar">  
<h1>Events test</h1>  
<a class="back" href="#">AJAX</a>  
<a class="button goback" href="#home">Home</a>  
</div>  
<div class="info">  
This is a test of live events.  
</div>  
</div>  
   
  // Sort the data with volume descending, edition ascending
  // Add $data as the last parameter, to sort by the common key
  array_multisort($stopName, SORT_ASC, $contents);
   
  foreach ($contents as $row)
  {
   
  echo '<li><a href="stop.php?stopid='.$row[0].'">'.$row[1].'</a></li>';
  }
  echo '</ul>';
  /*
  <div data-role="footer" data-id="foo1" data-position="fixed">
  <div data-role="navbar">
  <ul>
  <li><a href="footer-persist-a.html" class="ui-btn-active">Friends</a></li>
  <li><a href="footer-persist-b.html">Albums</a></li>
  <li><a href="footer-persist-c.html">Emails</a></li>
  </ul>
  </div><!-- /navbar -->
  */
  include_footer();
  ?>
   
   
file:b/busui/trip.php (new)
  <?php
  include('common.inc.php');
  $url = $APIurl."/json/triprows?trip=".$_REQUEST['tripid'];
  $trips = array_flatten(json_decode(getPage($url)));
  print_r($trips);
 
  include_header("Stops on ". $trips[1]->route_short_name . ' '. $trips[1]->route_long_name);
  echo ' <ul data-role="listview" >';
 
 
 
 
  $url = $APIurl."/json/tripstoptimes?trip=".$_REQUEST['tripid'];
 
  $json = json_decode(getPage($url));
  $stops = $json[0];
  $times = $json[1];
  foreach ($stops as $key => $row)
  {
  echo '<li>'.midnight_seconds_to_time($times[$key]).' <a href="stop.php?stopid='.$row[0].'">'.$row[1].'</a></li>';
  }
  echo '</ul>';
  include_footer();
  ?>
 
file:b/busui/view.sh (new)
  # input location (via GPS or favourites or search) and destination (via searchable list, optional)
  # http://10.0.1.153:8765/json/boundboxstops?n=-35.27568499917103&e=149.1346514225006&s=-35.279495003493516&w=149.12622928619385&limit=50
  # http://10.0.1.153:8765/json/stoptrips?stop=43&time=64440 # recursively call to show all services nearby, sort by distance, need to filter by service period
  # Hey, can pick destination again from a list filtered to places these stops go if you're curious!
  # http://10.0.1.153:8765/json/tripstoptimes?trip=2139 # Can recursively call and parse based on intended destination to show ETA
  # http://10.0.1.153:8765/json/triprows?trip=2139 # For pretty maps
  python schedule_viewer.py --feed=../maxious-canberra-transit-feed/cbrfeed.zip --key=ABQIAAAA95XYXN0cki3Yj_Sb71CFvBTPaLd08ONybQDjcH_VdYtHHLgZvRTw2INzI_m17_IoOUqH3RNNmlTk1Q