From: maxious Date: Sat, 20 Nov 2010 11:56:52 +0000 Subject: More work on mobile UI, changed to network 10 start date X-Git-Url: http://maxious.lambdacomplex.org/git/?p=bus.git&a=commitdiff&h=c1cb228ddd2713d5c1a9e768b11170f62ca72d89 --- More work on mobile UI, changed to network 10 start date --- --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,2 @@ -maxious-canberra-transit-feed/output/ +origin-src --- a/betweenpoint.php +++ b/betweenpoint.php @@ -7,71 +7,66 @@ function init() { - // create the ol map object - var map = new OpenLayers.Map('map'); + // create the ol map object + var map = new OpenLayers.Map('map'); - var osmtiles = new OpenLayers.Layer.OSM("local", "http://127.0.0.1/Maps/OSM/${z}/${x}/${y}.png") + var osmtiles = new OpenLayers.Layer.OSM("local", "http://127.0.0.1/tiles/${z}/${x}/${y}.png") // use http://open.atlas.free.fr/GMapsTransparenciesImgOver.php and http://code.google.com/p/googletilecutter/ to make tiles - markers = new OpenLayers.Layer.Markers("Between Stop Markers"); + markers = new OpenLayers.Layer.Markers("Between Stop Markers"); var timeicon = new OpenLayers.Icon("http://maps.google.com/mapfiles/kml/pushpin/grn-pushpin.png",new OpenLayers.Size(32,32)); var timepoints = new OpenLayers.Layer.GeoRSS("Timing Points", "displaytimepoints.georss.php", { icon: timeicon }); map.addLayers([osmtiles, markers,timepoints]); map.addControl(new OpenLayers.Control.LayerSwitcher()); - map.zoomToExtent(markers.getDataExtent()); + map.zoomToExtent(markers.getDataExtent()); } + + + + + + + + + + '; +if ($opendiv) echo '
+ +
+

'.$pageTitle.'

+ Home +
+
'; +} + +function include_footer() +{ + echo '
'; +} function service_period() { @@ -13,5 +56,54 @@ 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; +} + +function staticmap($mapPoints) +{ +$width = 300; +$height = 300; + if (sizeof($mapPoints) < 1) return ""; + if (sizeof($mapPoints) === 1) $center = "{$mapPoints[0][0]},{$mapPoints[0][1]}"; + if (sizeof($mapPoints) > 1) $center = "{$mapPoints[0][0]},{$mapPoints[0][1]}"; //TODO average points + $markers = ""; + foreach ($mapPoints as $index => $mapPoint) { + if (sizeof($mapPoints) === 1) { + $markers .= $mapPoint[0].",".$mapPoint[1].",ol-marker"; + } else { + $markers .= $mapPoint[0].",".$mapPoint[1].",lightblue".($index+1); + } + } + return ''; +} ?> + --- a/busui/index.php +++ b/busui/index.php @@ -1,101 +1,32 @@ - - - - - jQTouch β - - - - - - - - - -
-
-

jQTouch

- About -
-
    -
  • User Interface 4
  • -
  • All stops List
  • -
  • Nearby List
  • -
  • Favourites List
  • + +
    +
    +

    jQuery Mobile Framework + bus.lambdacomplex.org

    +
    +
    + -
    -

    Add this page to your home screen to view the custom icon, startup screen, and full screen mode.

    + +
    +

    + Time:
    + Service Period:
    - +
    + --- a/busui/list.php +++ /dev/null @@ -1,22 +1,1 @@ -
    -
    -

    GET Example

    - AJAX -
    -
    - This page was loaded via AJAX. -
    - -
    -
    -
    -

    Events test

    - AJAX - Home -
    -
    - This is a test of live events. -
    -
    + --- a/busui/readme.txt +++ b/busui/readme.txt @@ -1,2 +1,30 @@ +# 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 +Destinations +Favourites +OOP stops/routes +Stop sorting/search-filter +static maps +https://code.google.com/apis/maps/documentation/staticmaps/ +http://www.multimap.com/openapidocs/1.2/web_service/staticmaps.htm +http://dev.openstreetmap.de/staticmap/ (os @ http://sourceforge.net/projects/staticmaplite/) +(php and open source @ http://trac.openstreetmap.org/browser/sites/other/StaticMap?rev=16348) +http://pafciu17.dev.openstreetmap.org/ + --- a/busui/route.php +++ /dev/null --- a/busui/routeList.php +++ b/busui/routeList.php @@ -1,1 +1,55 @@ +'; +echo "";*/ +echo '
      '; +$url = $APIurl."/json/routes"; +$contents = json_decode(getPage($url)); +if ($_REQUEST['bynumber']) { + $routeSeries = Array(); + foreach ($contents as $key => $row) { + foreach (explode(" ",$row[1]) as $routeNumber ) { + $seriesNum = substr($routeNumber, 0, -1)."0"; + if ($seriesNum == "0") $seriesNum = $routeNumber; + $routeSeries[$seriesNum][$seriesNum."-".$row[1]."-".$row[0]] = $row; + + + } + } + ksort($routeSeries); + foreach ($routeSeries as $series => $routes) + { + echo '
    • '.$series."...
        \n"; + foreach($routes as $row) { + echo '
      • '.$row[1].' '.$row[2]."
      • \n"; + } + echo "
    • \n"; + } +} else { + foreach ($contents as $key => $row) { + $routeDestinations[$row[2]][] = $row; + } + foreach ($routeDestinations as $destination => $routes) + { + echo '
    • '.$destination."...
        \n"; + foreach($routes as $row) { + echo '
      • '.$row[1].' '.$row[2]."
      • \n"; + } + echo "
    • \n"; + } +} +echo "
    \n"; +echo' +
    +
    + + '; +include_footer(); +?> + --- /dev/null +++ b/busui/schedule_viewer.py @@ -1,1 +1,557 @@ - +#!/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_routetrips(self, params): + """ Get a trip for a route_id (preferablly the next one) """ + schedule = self.server.schedule + query = params.get('route_id', None).lower() + result = [] + for t in schedule.GetTripList(): + if t.route_id == query: + result.append ( (t.GetStartTime(), t.trip_id) ) + """ UGH fails for 300s """ + """return result""" + return sorted(result, key=lambda trip: trip[0]) + + 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_timingpoints(self, params): + schedule = self.server.schedule + matches = [] + for s in schedule.GetStopList(): + if s.stop_code.find("Wj") == -1: + matches.append(StopToTuple(s)) + return matches + + 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)) + service_period = params.get('service_period', None) + 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 + if service_period == None or trip.service_id == service_period: + 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] [] + +Runs a webserver that lets you explore a in your browser. + +If 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() + --- a/busui/stop.php +++ b/busui/stop.php @@ -1,1 +1,22 @@ +

    '.staticmap(Array(0 => Array($stop[2],$stop[3]))).'

    '; +echo '
      '; +$url = $APIurl."/json/stoptrips?stop=".$_REQUEST['stopid']."&time=".midnight_seconds()."&service_period=".service_period(); +$trips = json_decode(getPage($url)); +foreach ($trips as $row) +{ +echo '
    • '; +echo '

      '.$row[1][1].'

      '; +echo '

      '.midnight_seconds_to_time($row[0]).'

      '; +echo '
    • '; +} +if (sizeof($trips) == 0) echo "
    • No trips in the near future.
    • "; +echo '
    '; +include_footer(); +?> + --- a/busui/stopList.php +++ b/busui/stopList.php @@ -1,23 +1,34 @@ -
    -
    -

    GET Example

    - AJAX -
    -
    - This page was loaded via AJAX. -
    - -
    -
    -
    -

    Events test

    - AJAX - Home -
    -
    - This is a test of live events. -
    -
    +'; +$url = $APIurl."/json/timingpoints"; +if ($_REQUEST['allstops']) $url = $APIurl."/json/stops"; +if ($_REQUEST['lat'] && $_REQUEST['lon']) $url = $APIurl."/json/neareststops?lat={$_REQUEST['lat']}&lon={$_REQUEST['lon']}&limit=15"; +$contents = json_decode(getPage($url)); +foreach ($contents as $key => $row) { + $stopName[$key] = $row[1]; +} +// 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 '
  • '.$row[1].'
  • '; + } +echo '
'; +echo' +
+
+ + '; +include_footer(); +?> + + --- /dev/null +++ b/busui/trip.php @@ -1,1 +1,38 @@ + 0)) $tripid = $trips[0][1]; +} +$url = $APIurl."/json/triprows?trip=".$tripid; +$trips = array_flatten(json_decode(getPage($url))); +include_header("Stops on ". $trips[1]->route_short_name . ' '. $trips[1]->route_long_name); +echo '
    '; + + +$url = $APIurl."/json/tripstoptimes?trip=".$tripid; + +$json = json_decode(getPage($url)); +$stops = $json[0]; +$times = $json[1]; +foreach ($stops as $key => $row) +{ + echo '
  • '; +echo '

    '.$row[1].'

    '; +echo '

    '.midnight_seconds_to_time($times[$key]).'

    '; +echo '
  • '; +} +echo '
'; +include_footer(); +?> + --- /dev/null +++ b/busui/view.sh @@ -1,1 +1,8 @@ +# 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 --- a/maxious-canberra-transit-feed/01-extracttimes.rb +++ b/maxious-canberra-transit-feed/01-extracttimes.rb @@ -14,7 +14,14 @@ timetable = {"between_stops" => [], "short_name" => short_name} time_points = table.xpath('tr[1]//th').map do |tp| if tp.content != "\302\240" && tp.content != "" && tp.content != "
" - timing_point = tp.content.squeeze(" ").gsub("\r\n Platform"," - Platform").gsub(" - "," - ").gsub("\n","").gsub("\r","").gsub("\\"," / ").strip + 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 + if (tp.content.match('Platform')) + timing_point.concat(")") + end; + if tp.to_s.match(/[0-9][0-9][0-9]/) or tp.to_s.include? "Wheelchair" + timing_point = nil + end + timing_point end end time_points.delete(nil) @@ -24,12 +31,17 @@ table.css('tr').each do |row| times = row.css('td').map do |cell| time = cell.content.squeeze(" ").strip - time = time.gsub(/ *A\S?M/,"a").gsub(/ ?P\S?M/,"p").gsub("12:08 AM","1208x").gsub(":","").gsub("1.","1").gsub("2.","2") - time = time.gsub("3.","3").gsub("4.","4") + 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("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(/\.+/,"-") + 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" + time = nil # This hacky way is faster than using position()>1 xpath on s! + end + time end - if not times.empty? + times.delete(nil) + if not times.empty? if not (route = times.shift) raise("TODO: account for shifting route numbers eg. intertown/redex 62/162") end @@ -42,7 +54,7 @@ timetable[period] = periodtimes.to_a # pp timetable filename = timetable["short_name"] + "-" + timetable["long_name"]+ "." + period + ".yml" - filename = filename.downcase.gsub(" ","-").gsub("/","-") + filename = filename.downcase.gsub(" ","-").gsub("/","-").gsub("(","").gsub(")","") puts "Saving " + filename File.open("#{File.dirname(__FILE__)}/output/"+filename, "w") do |f| f.write timetable.to_yaml @@ -50,15 +62,14 @@ timetable end -#TODO fix route 934 -Dir.glob("source-html/Route*.htm*") { |file| +Dir.glob("source-html/*oute*.htm*") { |file| puts "Opened " + file doc = Nokogiri::HTML(open(file)) # Search for nodes by css timetables = [] short_name = ""; doc.xpath('//title').each do |title| - short_name = title.content.gsub("Route_","").gsub("Route ","").gsub(", ","/").squeeze(" ").strip + short_name = title.content.gsub("Route_","").gsub("Route ","").gsub("route ","").gsub(", ","/").gsub("ACTION Buses Timetable for ","").squeeze(" ").strip end if short_name == "" raise "Route number(s) not found in tag" @@ -67,7 +78,15 @@ doc.xpath('//table[preceding::text()="Weekdays"]').each do |table| timetables << makeTimetable(table, "stop_times", short_name) end - + doc.xpath('//table[preceding::text()="This timetable is effective from Monday 15th November 2010."]').each do |table| + timetables << makeTimetable(table, "stop_times", short_name) + end + #all tables are weekdays on some really malformatted timetables + if short_name == "170" + doc.xpath('//table').each do |table| + timetables << makeTimetable(table, "stop_times", short_name) + end + end #weekends doc.xpath('//table[preceding::text()="Saturdays" and following::a]').each do |table| timetables << makeTimetable(table, "stop_times_saturday", short_name) --- a/maxious-canberra-transit-feed/02-tidytimepoints.rb +++ b/maxious-canberra-transit-feed/02-tidytimepoints.rb @@ -2,9 +2,9 @@ require 'pp' require 'yaml' class Array - def to_yaml_style - :inline - end + def to_yaml_style + :inline + end end Dir.chdir("output") @@ -19,78 +19,96 @@ end } end +def correctTimePoints() +time_point_corrections = {"North Lynehamham" => "North Lyneham", + "Woden Bus Station Platform 10)" => "Woden Bus Station (Platform 10)", + "Saint AndrewsVillage Hughes"=>"Saint Andrews Village Hughes", + "Flemmington Road / Sandford St"=>"Flemington Road / Sandford St", + "City Interchange"=>"City Bus Station", + "City Interchange (Platform 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)", + "Bridbabella Gardens Nursing Home"=>"Brindabella Gardens Nursing Home", + "Bridbabella GardensNursing Home"=> "Brindabella Gardens Nursing Home", + "BrindabellaBusiness Park"=> "Brindabella Business Park", + "NarrabundahTerminus"=>"Narrabundah Terminus", + "Railway StationKingston"=>"Railway Station Kingston", + "Saint AndrewsVillage Hughes"=>"Saint Andrews Village Hughes", + "Dickson ShopsAntill Street"=>"Dickson Shops", + "Cohen St Bus Station (Platform 3)" => "Cohen Street Bus Station (Platform 3)", + "Cohen St Bus Station (Platform 6)" => "Cohen Street Bus Station (Platform 6)", + "Newcastle Streetafter Isa Street" => "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", + "Northbourne Ave / Antill St" => "Northbourne Avenue / Antill St", + "Macarthur / Northbourne" => "Macarthur / Northbourne Ave", + "Macarthur Ave / Northbourne" => "Macarthur / Northbourne Ave", + "Kings Ave / National Cct"=> "Kings Ave / National Circuit", + "Kosciuszco Ave / Everard Street"=>"Kosciuszko / Everard", + "Lithgow St Terminus" => "Lithgow St Terminus Fyshwick", + "Hospice Menindee Dr" => "Hospice / Menindee Dr", + "Gungahlin Market Place"=> "Gungahlin Marketplace", + "Gwyder Square Kaleen"=> "Gwydir Square Kaleen", + "Flemington Road / Nullabor Ave"=>"Flemington Rd / Nullabor Ave", + "Flemington Road / Sandford St"=>"Flemington Rd / Sandford St", + "Heagney Cres Clift Cres Richardson"=> "Heagney / Clift Richardson", + "Charnwood Shops (Tillyard Drive)"=> "Charnwood Shops", + "charnwood Shops"=> "Charnwood Shops", + "Black Moutain- Telstra Tower"=>"Black Mountain Telstra Tower", + "Bonython Primary"=> "Bonython Primary School", + "Athllon Drive / Sulwood Dr Kambah"=>"Athllon / Sulwood Kambah", + "Alexander Machonochie Centre Hume"=>"Alexander Maconochie Centre", + "Alexander Maconochie Centre Hume"=>"Alexander Maconochie Centre", + "Anthony Rolfe Ave / Moonight Ave" =>"Anthony Rolfe Av / Moonlight Av", + "Australian National Botanic Gardens"=>"Botanic Gardens", + "Calwell shops"=> "Calwell Shops", + "Chuculba / William Slim Drive"=>"Chuculba / William Slim Dr", + "Fyshwick direct Factory Outlet"=>"Fyshwick Direct Factory Outlet", + "Kaleen Village / Maibrynong"=>"Kaleen Village / Marybrynong", + "Kaleen Village / Marybrynong Ave"=>"Kaleen Village / Marybrynong", + "National Aquarium"=>"National Zoo and Aquarium", + "chisholm Shops"=>"Chisholm Shops", + "O'connor Shops"=>"O'Connor Shops", + "Mckellar Shops"=>"McKellar Shops", + "Melba shops"=> "Melba Shops", + "William Web / Ginninderra Drive"=>"William Webb / Ginninderra Drive", + "Procor / Mead"=>"Proctor / Mead", + "Fyshwick DirectFactory Outlet"=>"Fyshwick Direct Factory Outlet", + "Yarrulumla Shops"=>"Yarralumla Shops", + "Tharwa Dr / Pocket Ave"=>"Tharwa Dr / Pockett Ave", + "Paul Coe / Mirrebei Dr"=>"Paul Coe / Mirrabei Dr", + "Mirrebei Drive / Dam Wall"=>"Mirrabei Drive / Dam Wall", + "Tharwa / Knoke" => "Tharwa Drive / Knoke Ave", + "Tharwa / Pocket" => "Tharwa Dr / Pockett Ave", + "Outrim / Duggan" => "Outtrim / Duggan", + "ANU Burton and Garran Hall Daley Rd" => "Burton and Garran Hall Daley Road", + "Farrer Primary"=>"Farrer Primary School", + "St Thomas More Campbell"=>"St Thomas More's Campbell", + "Lyneham Shops"=>"Lyneham Shops Wattle Street", + + +} + time_point_corrections.each do |wrong, right| + $time_points_sources[wrong].each do |wrongfile| + badtimetable = YAML::load_file(wrongfile) + badentrynumber = badtimetable["time_points"].index wrong + badtimetable["time_points"][badentrynumber] = right + puts "Corrected '" + wrong + "' to '" + right + "' in " + wrongfile + File.open(wrongfile, "w") do |f| + f.write badtimetable.to_yaml + end + end + end +end getTimePoints() #pp $time_points.sort! #pp $time_points_sources.sort -time_point_corrections = {"North Lynehamham" => "North Lyneham", - "Lathlain St Platform 2" => "Lathlain St Bus Station - Platform 2", - "Lathlain St Sation - Platform 5" => "Lathlain St Bus Station - Platform 5", - "Lathlain Steet Station" => "Lathlain St Bus Station", - "Lathlain St - Platform 3" => "Lathlain St Bus Station - Platform 3", - "Lathlain Steet Station - Platform 3" => "Lathlain St Bus Station - Platform 3", - "Lathlain St Station" => "Lathlain St Bus Station", - "Lathlain St Station - Platform 1" => "Lathlain St Bus Station - Platform 1", - "Lathlain St Station - Platform 2" => "Lathlain St Bus Station - Platform 2", - "Lathlain St Station - Platform 3" => "Lathlain St Bus Station - Platform 3", - "Lathlain St Station - Platform 4" => "Lathlain St Bus Station - Platform 4", - "Lathlain St Station - Platform 5" => "Lathlain St Bus Station - Platform 5", - "Lathlain St Station - Platform 6" => "Lathlain St Bus Station - Platform 6", - "Flemington Rd, Sandford St" => "Flemington Rd/Sandford St", - "Erindale Centre / - Sternberg Crescent" => "Erindale Drive/Sternberg", - "Canberra Hospita" => "Canberra Hospital", - "Cohen Str Station - Platform 1" => "Cohen St Bus Station - Platform 1", - "Cohen Street Station" => "Cohen St Bus Station", - "Cohen Street Station - Platform 2" => "Cohen St Bus Station - Platform 2", - "Cohn St Station - Platform 3" => "Cohen St Bus Station - Platform 3", - "Cohen St Station" => "Cohen St Bus Station", - "Cohen St Station - Platform 1" => "Cohen St Bus Station - Platform 1", - "Cohen St Station - Platform 2" => "Cohen St Bus Station - Platform 2", - "Cohen St Station - Platform 3" => "Cohen St Bus Station - Platform 3", - "Cohen St Station - Platform 4" => "Cohen St Bus Station - Platform 4", - "Cohen St Station - Platform 5" => "Cohen St Bus Station - Platform 5", - "Cohen St Station - Platform 6" => "Cohen St Bus Station - Platform 6", - "City - Platform 7" => "City Interchange - Platform 7", - "Cameron Avenue Station" => "Cameron Ave Bus Station", - "Cameron Avenue Station - Platform 1" => "Cameron Ave Bus Station - Platform 1", - "Cameron Avenue Station - Platform 2" => "Cameron Ave Bus Station - Platform 2", - "Cameron Avenue Station - Platform 3" => "Cameron Ave Bus Station - Platform 3", - "Cameron Avenue Station - Platform 4" => "Cameron Ave Bus Station - Platform 4", - "Cameron Avenue Station - Platform 5" => "Cameron Ave Bus Station - Platform 5", - "Cameron Ave Station" => "Cameron Ave Bus Station", - "Cameron Ave Station - Platform 1" => "Cameron Ave Bus Station - Platform 1", - "Cameron Ave Station - Platform 2" => "Cameron Ave Bus Station - Platform 2", - "Cameron Ave Station - Platform 3" => "Cameron Ave Bus Station - Platform 3", - "Cameron Ave Station - Platform 4" => "Cameron Ave Bus Station - Platform 4", - "Cameron Ave Station - Platform 5" => "Cameron Ave Bus Station - Platform 5", - "Burton & Garranan Hall, Daley Road ANU" => "Burton & Garran Hall/Daley Road ANU", - "Burton & Garranan Hall,Daley Road ANU" => "Burton & Garran Hall/Daley Road ANU", - "Garran/Daley Rd" => "Burton & Garran Hall/Daley Road ANU", - "Kingstons Ave/National Crt" => "Kings Ave/National Crt", - "Newcastle Street after Isa St" => "Newcastle / Isa Street Fyshwick", - "National Circ/Canberra Ave" => "National Circuit / Canberra Ave", - "St Clare of Conder" => "St Clare of Assisi Primary", - "McKillop College Isabella Campus" => "MacKillop College Isabella Campus", - "Outrim / Duggan" => "Outtrim / Duggan", - "Manuka Captain Cook" => "Manuka/Captain Cook", - "Manuka, Captain Cook" => "Manuka/Captain Cook", - "Hospice, Menindee Drive" => "Hospice", - "Tharwa/Pocket" => "Tharwa/Pockett", - "Paul-Co / Mirrabei" => "Paul Coe / Mirrabei" - } -time_point_corrections.each do |wrong, right| - $time_points_sources[wrong].each do |wrongfile| - badtimetable = YAML::load_file(wrongfile) - badentrynumber = badtimetable["time_points"].index wrong - badtimetable["time_points"][badentrynumber] = right - puts "Corrected '" + wrong + "' to '" + right + "' in " + wrongfile - File.open(wrongfile, "w") do |f| - f.write badtimetable.to_yaml - end - end -end +correctTimePoints() +getTimePoints() +correctTimePoints() getTimePoints() pp $time_po