From: maxious Date: Tue, 02 Nov 2010 13:59:56 +0000 Subject: More network 10 updates X-Git-Url: https://maxious.lambdacomplex.org/git/?p=bus.git&a=commitdiff&h=cbd4cfb99deafe3b04112026dc22776df8727d9f --- More network 10 updates --- --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,2 @@ -maxious-canberra-transit-feed/output/ +origin-src --- a/betweenpoint.php +++ b/betweenpoint.php @@ -7,71 +7,65 @@ 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/Maps/OSM/${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()); } + + + + + + +
+ +
+

'.$pageTitle.'

+
+
'; +} + +function include_footer() +{ + echo '
'; +} function service_period() { @@ -13,5 +51,35 @@ 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; +} ?> - + --- a/busui/index.php +++ b/busui/index.php @@ -1,32 +1,20 @@ + - jQTouch β - - - - - + bus.lambdacomplex.org + + + + - --> -
-
-

jQTouch

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

    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,23 @@ +# 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 --- a/busui/routeList.php +++ b/busui/routeList.php @@ -1,1 +1,34 @@ +'; +echo "";*/ +echo '
      '; +$url = $APIurl."/json/routes"; +$contents = json_decode(getPage($url)); +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"; +/* +
    +
    + +
    +*/ +include_footer(); +?> + --- /dev/null +++ b/busui/schedule_viewer.py @@ -1,1 +1,544 @@ - +#!/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_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)) + 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] [] + +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,17 @@ +'; +$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 '
  • '.midnight_seconds_to_time($row[0]).' '.$row[1][1].'
  • '; +} +echo '
'; +include_footer(); +?> + --- a/busui/stopList.php +++ b/busui/stopList.php @@ -1,23 +1,35 @@ -
-
-

GET Example

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

Events test

- AJAX - Home -
-
- This is a test of live events. -
-
+'; +$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 ''; +/* +
    +
    + +
    +*/ +include_footer(); +?> + + --- /dev/null +++ b/busui/trip.php @@ -1,1 +1,25 @@ +route_short_name . ' '. $trips[1]->route_long_name); +echo '
      '; + + + + +$url = $APIurl."/json/tripstoptimes?trip=".$_REQUEST['tripid']; + +$json = json_decode(getPage($url)); +$stops = $json[0]; +$times = $json[1]; +foreach ($stops as $key => $row) +{ +echo '
    • '.midnight_seconds_to_time($times[$key]).' '.$row[1].'
    • '; +} +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,10 +14,16 @@ 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; + timing_point end end time_points.delete(nil) + time_points.delete("WheelchairAccessible") + time_points.delete("Wheelchair Accessible") timetable["time_points"] = time_points.to_a timetable["long_name"] = "To " + time_points.last periodtimes = [] @@ -27,8 +33,11 @@ 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("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 == "" then time = nil end + time end + times.delete(nil) if not times.empty? if not (route = times.shift) raise("TODO: account for shifting route numbers eg. intertown/redex 62/162") @@ -42,7 +51,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,7 +59,6 @@ timetable end -#TODO fix route 934 Dir.glob("source-html/Route*.htm*") { |file| puts "Opened " + file doc = Nokogiri::HTML(open(file)) @@ -58,7 +66,7 @@ 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(", ","/").gsub("ACTION Buses Timetable for ","").squeeze(" ").strip end if short_name == "" raise "Route number(s) not found in tag" @@ -67,7 +75,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") @@ -25,60 +25,31 @@ #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" - } + "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)", + "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", + "William Web / Ginninderra Drive"=>"William Webb / Ginninderra Drive", + "Procor / Mead"=>"Proctor / Mead", +"Fyshwick DirectFactory Outlet"=>"Fyshwick Direct Factory Outlet", +"Dickson ShopsAntill Street"=>"Dickson Shops/Antill St", +"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" +} + time_point_corrections.each do |wrong, right| $time_points_sources[wrong].each do |wrongfile| badtimetable = YAML::load_file(wrongfile) @@ -86,7 +57,7 @@ badtimetable["time_points"][badentrynumber] = right puts "Corrected '" + wrong + "' to '" + right + "' in " + wrongfile File.open(wrongfile, "w") do |f| - f.write badtimetable.to_yaml + f.write badtimetable.to_yaml end end end --- a/maxious-canberra-transit-feed/03-locatetimepoints.rb +++ b/maxious-canberra-transit-feed/03-locatetimepoints.rb @@ -1,10 +1,10 @@ #!/usr/bin/ruby -require 'postgres' require 'highline.rb' include HighLine require 'rubygems' +require 'postgres' require 'json' require 'net/http' def cbr_geocode(query) @@ -12,7 +12,7 @@ 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)) data = resp.body - +pp url # we convert the returned JSON data to native Ruby # data structure - a hash result = JSON.parse(data) @@ -51,13 +51,11 @@ connbus = PGconn.connect("localhost", 5432, '', '', "bus", "postgres", "snmc") -connosm = PGconn.connect("localhost", 5432, '', '', "openstreetmap", -"postgres", "snmc") if ask_if("Insert Timing Point names to database?") $time_points.each do |time_point| begin - time_point = time_point.gsub(/\\/, '\&\&').gsub(/'/, "''").gsub("St", "%") + time_point = time_point.gsub(/\\/, '\&\&').gsub(/'/, "''") # DON'T PUT MORE GSUB HERE res = connbus.exec("INSERT INTO timing_point (name) VALUES ('#{time_point}')") puts "Put '#{time_point}' into DB" rescue PGError => e @@ -68,164 +66,6 @@ end -if ask_if("Fill null Timing Points from OSM bus_stop database?") -# TODO Where there's a "Cnr" or a \/ or a &, Look for 2 ways or nodes and average the closest two! - begin - null_points = connbus.exec('SELECT name FROM timing_point WHERE lat IS null OR lng IS null;') - rescue PGError => e - puts "Error selecting null points from DB #{e}" - #conn.close() if conn - end - - null_points.each do |null_point_name| - begin - name = null_point_name.to_s.gsub(/\\/, '\&\&').gsub(/'/, "''") - pp name - search_name = ask("Hmm, if we're still looking, the name is probably wrong. What's the right name?", :string, :default => name) - matching_nodes = connosm.exec("Select * FROM (SELECT * from current_node_tags, - (Select id as ctagid FROM current_node_tags WHERE v LIKE '%#{search_name}%') as a - where a.ctagid = current_node_tags.id) as ctags INNER JOIN current_nodes ON - ctags.id=current_nodes.id") - rescue PGError => e - puts "Error selecting matching bus stops from DB #{e}" - #conn.close() if conn - end - suggested_nodes = Hash.new() - - matching_nodes.each do |matching_node_row| - #pp matching_node_row - # 0 = id - # 1 = k - # 2 = v - # 3,4 = redundant ids - # 5 = lat*100000 - # 6 = lng*100000 - suggested_node = suggested_nodes.fetch(matching_node_row[0], {'lat' => Float(matching_node_row[5])/10000000, - 'lng' => Float(matching_node_row[6])/10000000}) - if matching_node_row[1] == "ref" - matching_node_row[1] = "loc_ref" - end - suggested_node[matching_node_row[1]] = matching_node_row[2] - suggested_nodes[matching_node_row[0]] = suggested_node - end - pp suggested_nodes - nodeID = ask("Enter selected node ID:", :string) - if suggested_nodes.has_key?(nodeID) - node = suggested_nodes.fetch(nodeID) - guess = ask_if("Is this a guess?") - puts "Location #{node["lat"]},#{node["lng"]} for #{null_point_name}" - begin - res = connbus.exec("UPDATE timing_point SET lat = #{node["lat"]*10000000}, lng = -#{node["lng"]*10000000},osm_node = #{nodeID}" + (node.has_key?("loc_ref") ? ",loc_ref = #{node["loc_ref"]}" : "") + ",guess = #{guess} WHERE name -= '#{name}'") - puts "Put '#{null_point_name}' into DB" - rescue PGError => e - puts "Error inserting '#{null_point_name}' to DB #{e}" - ask_if("Continue?") - #conn.close() if conn - end - else - puts "Uhh, there was no suggestion ID like that. Try again next time!" - end - - puts "Hmm, so maybe this isn't a point? Maybe it's a way.... like a street or something? Is it a 'street' or a 'corner' or nothing?" - whatisit = ask("So what is it:", :string, :default => "corner") - if whatisit == "street" - begin - name = null_point_name.to_s.gsub(/\\/, '\&\&').gsub(/'/, "''") - pp "#{name} (ways)" - search_name = ask("Streets tend to have pretty bad quality data, What's the real name of the street?", :string, :default => name) - matching_ways = connosm.exec("Select avg(latitude), avg(longitude), name FROM ( - SELECT * from current_way_nodes,(Select id as ctagid, v as name FROM current_way_tags WHERE k = 'name' AND v LIKE - '%#{search_name}%') as a where a.ctagid = current_way_nodes.id) as ctags INNER JOIN current_nodes ON ctags.node_id=current_nodes.id - GROUP BY name") - rescue PGError => e - puts "Error selecting matching ways from DB #{e}" - #conn.close() if conn - end - suggested_ways = Hash.new() - - matching_ways.each do |matching_way_row| - #pp matching_way_row - # 0 = lat*100000 - # 1 = lng*100000 - # 2 = name - suggested_way = suggested_ways.fetch(matching_way_row[2], {'lat' => Float(matching_way_row[0])/10000000, - 'lng' => Float(matching_way_row[1])/10000000}) - suggested_way['name'] = suggested_way['name'] - suggested_ways[matching_way_row[2]] = suggested_way - end - pp suggested_ways - wayID = ask("Enter selected way ID:", :string) - if suggested_ways.has_key?(wayID) - way = suggested_ways.fetch(wayID) - guess = ask_if("Is this a guess?") - puts "Location #{way["lat"]},#{way["lng"]} for #{null_point_name}" - begin - res = connbus.exec("UPDATE timing_point SET lat = #{way["lat"]*10000000}, lng = - #{way["lng"]*10000000},guess = #{guess} WHERE name = '#{name}'") - puts "Put '#{null_point_name}' into DB" - rescue PGError => e - puts "Error inserting '#{null_point_name}' to DB #{e}" - ask_if("Continue?") - #conn.close() if conn - end - else - puts "Uhh, there was no suggestion ID like that. Try again next time!" - end - end - if whatisit == "corner" - # Where there's a "Cnr" or a \/ or a &, look for 2 ways and find the intersections - - name = null_point_name.to_s.gsub(/\\/, '\&\&').gsub(/'/, "''") - search_name = ask("I need this to look like STREETNAME1/STREETNAME2, okay? Can you do that for me?", :string, :default => name) - search_pieces = search_name.split("/") - pp search_pieces - if search_pieces.length == 2 - begin - matching_ways = connosm.exec("SELECT w.way_id, latitude, longitude, w.node_id from (Select current_way_nodes.id as way_id, * from current_nodes inner join current_way_nodes on current_nodes.id=current_way_nodes.node_id inner join current_ways on current_way_nodes.id=current_ways.id) as w, (select node_id, count(node_id) from - (Select * FROM (SELECT * from current_way_nodes, (Select id as ctagid, v as name FROM current_way_tags WHERE k = 'name' AND v LIKE '%#{search_pieces[0]}%') as a where a.ctagid = current_way_nodes.id ) as ctags INNER JOIN current_nodes ON ctags.node_id=current_nodes.id where sequence_id = 1 union Select * FROM ( SELECT * from current_way_nodes, (Select id as ctagid, v as name FROM current_way_tags WHERE k = 'name' AND v LIKE '%#{search_pieces[0]}%') as a where a.ctagid = current_way_nodes.id ) as ctags INNER JOIN current_nodes ON ctags.node_id=current_nodes.id where sequence_id = (select max(sequence_id) from current_way_nodes cnodes where cnodes.id = ctags.id) union Select * FROM ( SELECT * from current_way_nodes, (Select id as ctagid, v as name FROM current_way_tags WHERE k = 'name' AND v LIKE '%#{search_pieces[1]}%') as a where a.ctagid = current_way_nodes.id ) as ctags INNER JOIN current_nodes ON ctags.node_id=current_nodes.id where sequence_id = 1 union Select * FROM