#!/usr/bin/python2.5 |
#!/usr/bin/python2.5 |
|
|
# Copyright (C) 2007 Google Inc. |
# Copyright (C) 2007 Google Inc. |
# |
# |
# Licensed under the Apache License, Version 2.0 (the "License"); |
# Licensed under the Apache License, Version 2.0 (the "License"); |
# you may not use this file except in compliance with the License. |
# you may not use this file except in compliance with the License. |
# You may obtain a copy of the License at |
# You may obtain a copy of the License at |
# |
# |
# http://www.apache.org/licenses/LICENSE-2.0 |
# http://www.apache.org/licenses/LICENSE-2.0 |
# |
# |
# Unless required by applicable law or agreed to in writing, software |
# Unless required by applicable law or agreed to in writing, software |
# distributed under the License is distributed on an "AS IS" BASIS, |
# distributed under the License is distributed on an "AS IS" BASIS, |
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
# See the License for the specific language governing permissions and |
# See the License for the specific language governing permissions and |
# limitations under the License. |
# limitations under the License. |
|
|
""" |
""" |
An example application that uses the transitfeed module. |
An example application that uses the transitfeed module. |
|
|
You must provide a Google Maps API key. |
You must provide a Google Maps API key. |
""" |
""" |
|
|
|
|
import BaseHTTPServer, sys, urlparse |
import BaseHTTPServer, sys, urlparse |
import bisect |
import bisect |
from gtfsscheduleviewer.marey_graph import MareyGraph |
from gtfsscheduleviewer.marey_graph import MareyGraph |
import gtfsscheduleviewer |
import gtfsscheduleviewer |
import mimetypes |
import mimetypes |
import os.path |
import os.path |
import re |
import re |
import signal |
import signal |
import simplejson |
import simplejson |
import socket |
import socket |
import time |
import time |
import transitfeed |
import transitfeed |
from transitfeed import util |
from transitfeed import util |
import urllib |
import urllib |
|
|
|
|
# By default Windows kills Python with Ctrl+Break. Instead make Ctrl+Break |
# By default Windows kills Python with Ctrl+Break. Instead make Ctrl+Break |
# raise a KeyboardInterrupt. |
# raise a KeyboardInterrupt. |
if hasattr(signal, 'SIGBREAK'): |
if hasattr(signal, 'SIGBREAK'): |
signal.signal(signal.SIGBREAK, signal.default_int_handler) |
signal.signal(signal.SIGBREAK, signal.default_int_handler) |
|
|
|
|
mimetypes.add_type('text/plain', '.vbs') |
mimetypes.add_type('text/plain', '.vbs') |
|
|
|
|
class ResultEncoder(simplejson.JSONEncoder): |
class ResultEncoder(simplejson.JSONEncoder): |
def default(self, obj): |
def default(self, obj): |
try: |
try: |
iterable = iter(obj) |
iterable = iter(obj) |
except TypeError: |
except TypeError: |
pass |
pass |
else: |
else: |
return list(iterable) |
return list(iterable) |
return simplejson.JSONEncoder.default(self, obj) |
return simplejson.JSONEncoder.default(self, obj) |
|
|
# Code taken from |
# Code taken from |
# http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/425210/index_txt |
# http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/425210/index_txt |
# An alternate approach is shown at |
# An alternate approach is shown at |
# http://mail.python.org/pipermail/python-list/2003-July/212751.html |
# http://mail.python.org/pipermail/python-list/2003-July/212751.html |
# but it requires multiple threads. A sqlite object can only be used from one |
# but it requires multiple threads. A sqlite object can only be used from one |
# thread. |
# thread. |
class StoppableHTTPServer(BaseHTTPServer.HTTPServer): |
class StoppableHTTPServer(BaseHTTPServer.HTTPServer): |
def server_bind(self): |
def server_bind(self): |
BaseHTTPServer.HTTPServer.server_bind(self) |
BaseHTTPServer.HTTPServer.server_bind(self) |
self.socket.settimeout(1) |
self.socket.settimeout(1) |
self._run = True |
self._run = True |
|
|
def get_request(self): |
def get_request(self): |
while self._run: |
while self._run: |
try: |
try: |
sock, addr = self.socket.accept() |
sock, addr = self.socket.accept() |
sock.settimeout(None) |
sock.settimeout(None) |
return (sock, addr) |
return (sock, addr) |
except socket.timeout: |
except socket.timeout: |
pass |
pass |
|
|
def stop(self): |
def stop(self): |
self._run = False |
self._run = False |
|
|
def serve(self): |
def serve(self): |
while self._run: |
while self._run: |
self.handle_request() |
self.handle_request() |
|
|
|
|
def StopToTuple(stop): |
def StopToTuple(stop): |
"""Return tuple as expected by javascript function addStopMarkerFromList""" |
"""Return tuple as expected by javascript function addStopMarkerFromList""" |
return (stop.stop_id, stop.stop_name, float(stop.stop_lat), |
return (stop.stop_id, stop.stop_name, float(stop.stop_lat), |
float(stop.stop_lon), stop.location_type) |
float(stop.stop_lon), stop.location_type) |
|
|
|
|
class ScheduleRequestHandler(BaseHTTPServer.BaseHTTPRequestHandler): |
class ScheduleRequestHandler(BaseHTTPServer.BaseHTTPRequestHandler): |
def do_GET(self): |
def do_GET(self): |
scheme, host, path, x, params, fragment = urlparse.urlparse(self.path) |
scheme, host, path, x, params, fragment = urlparse.urlparse(self.path) |
parsed_params = {} |
parsed_params = {} |
for k in params.split('&'): |
for k in params.split('&'): |
k = urllib.unquote(k) |
k = urllib.unquote(k) |
if '=' in k: |
if '=' in k: |
k, v = k.split('=', 1) |
k, v = k.split('=', 1) |
parsed_params[k] = unicode(v, 'utf8') |
parsed_params[k] = unicode(v, 'utf8') |
else: |
else: |
parsed_params[k] = '' |
parsed_params[k] = '' |
|
|
if path == '/': |
if path == '/': |
return self.handle_GET_home() |
return self.handle_GET_home() |
|
|
m = re.match(r'/json/([a-z]{1,64})', path) |
m = re.match(r'/json/([a-z]{1,64})', path) |
if m: |
if m: |
handler_name = 'handle_json_GET_%s' % m.group(1) |
handler_name = 'handle_json_GET_%s' % m.group(1) |
handler = getattr(self, handler_name, None) |
handler = getattr(self, handler_name, None) |
if callable(handler): |
if callable(handler): |
return self.handle_json_wrapper_GET(handler, parsed_params) |
return self.handle_json_wrapper_GET(handler, parsed_params) |
|
|
# Restrict allowable file names to prevent relative path attacks etc |
# Restrict allowable file names to prevent relative path attacks etc |
m = re.match(r'/file/([a-z0-9_-]{1,64}\.?[a-z0-9_-]{1,64})$', path) |
m = re.match(r'/file/([a-z0-9_-]{1,64}\.?[a-z0-9_-]{1,64})$', path) |
if m and m.group(1): |
if m and m.group(1): |
try: |
try: |
f, mime_type = self.OpenFile(m.group(1)) |
f, mime_type = self.OpenFile(m.group(1)) |
return self.handle_static_file_GET(f, mime_type) |
return self.handle_static_file_GET(f, mime_type) |
except IOError, e: |
except IOError, e: |
print "Error: unable to open %s" % m.group(1) |
print "Error: unable to open %s" % m.group(1) |
# Ignore and treat as 404 |
# Ignore and treat as 404 |
|
|
m = re.match(r'/([a-z]{1,64})', path) |
m = re.match(r'/([a-z]{1,64})', path) |
if m: |
if m: |
handler_name = 'handle_GET_%s' % m.group(1) |
handler_name = 'handle_GET_%s' % m.group(1) |
handler = getattr(self, handler_name, None) |
handler = getattr(self, handler_name, None) |
if callable(handler): |
if callable(handler): |
return handler(parsed_params) |
return handler(parsed_params) |
|
|
return self.handle_GET_default(parsed_params, path) |
return self.handle_GET_default(parsed_params, path) |
|
|
def OpenFile(self, filename): |
def OpenFile(self, filename): |
"""Try to open filename in the static files directory of this server. |
"""Try to open filename in the static files directory of this server. |
Return a tuple (file object, string mime_type) or raise an exception.""" |
Return a tuple (file object, string mime_type) or raise an exception.""" |
(mime_type, encoding) = mimetypes.guess_type(filename) |
(mime_type, encoding) = mimetypes.guess_type(filename) |
assert mime_type |
assert mime_type |
# A crude guess of when we should use binary mode. Without it non-unix |
# A crude guess of when we should use binary mode. Without it non-unix |
# platforms may corrupt binary files. |
# platforms may corrupt binary files. |
if mime_type.startswith('text/'): |
if mime_type.startswith('text/'): |
mode = 'r' |
mode = 'r' |
else: |
else: |
mode = 'rb' |
mode = 'rb' |
return open(os.path.join(self.server.file_dir, filename), mode), mime_type |
return open(os.path.join(self.server.file_dir, filename), mode), mime_type |
|
|
def handle_GET_default(self, parsed_params, path): |
def handle_GET_default(self, parsed_params, path): |
self.send_error(404) |
self.send_error(404) |
|
|
def handle_static_file_GET(self, fh, mime_type): |
def handle_static_file_GET(self, fh, mime_type): |
content = fh.read() |
content = fh.read() |
self.send_response(200) |
self.send_response(200) |
self.send_header('Content-Type', mime_type) |
self.send_header('Content-Type', mime_type) |
self.send_header('Content-Length', str(len(content))) |
self.send_header('Content-Length', str(len(content))) |
self.end_headers() |
self.end_headers() |
self.wfile.write(content) |
self.wfile.write(content) |
|
|
def AllowEditMode(self): |
def AllowEditMode(self): |
return False |
return False |
|
|
def handle_GET_home(self): |
def handle_GET_home(self): |
schedule = self.server.schedule |
schedule = self.server.schedule |
(min_lat, min_lon, max_lat, max_lon) = schedule.GetStopBoundingBox() |
(min_lat, min_lon, max_lat, max_lon) = schedule.GetStopBoundingBox() |
forbid_editing = ('true', 'false')[self.AllowEditMode()] |
forbid_editing = ('true', 'false')[self.AllowEditMode()] |
|
|
agency = ', '.join(a.agency_name for a in schedule.GetAgencyList()).encode('utf-8') |
agency = ', '.join(a.agency_name for a in schedule.GetAgencyList()).encode('utf-8') |
|
|
key = self.server.key |
key = self.server.key |
host = self.server.host |
host = self.server.host |
|
|
# A very simple template system. For a fixed set of values replace [xxx] |
# A very simple template system. For a fixed set of values replace [xxx] |
# with the value of local variable xxx |
# with the value of local variable xxx |
f, _ = self.OpenFile('index.html') |
f, _ = self.OpenFile('index.html') |
content = f.read() |
content = f.read() |
for v in ('agency', 'min_lat', 'min_lon', 'max_lat', 'max_lon', 'key', |
for v in ('agency', 'min_lat', 'min_lon', 'max_lat', 'max_lon', 'key', |
'host', 'forbid_editing'): |
'host', 'forbid_editing'): |
content = content.replace('[%s]' % v, str(locals()[v])) |
content = content.replace('[%s]' % v, str(locals()[v])) |
|
|
self.send_response(200) |
self.send_response(200) |
self.send_header('Content-Type', 'text/html') |
self.send_header('Content-Type', 'text/html') |
self.send_header('Content-Length', str(len(content))) |
self.send_header('Content-Length', str(len(content))) |
self.end_headers() |
self.end_headers() |
self.wfile.write(content) |
self.wfile.write(content) |
|
|
def handle_json_GET_routepatterns(self, params): |
def handle_json_GET_routepatterns(self, params): |
"""Given a route_id generate a list of patterns of the route. For each |
"""Given a route_id generate a list of patterns of the route. For each |
pattern include some basic information and a few sample trips.""" |
pattern include some basic information and a few sample trips.""" |
schedule = self.server.schedule |
schedule = self.server.schedule |
route = schedule.GetRoute(params.get('route', None)) |
route = schedule.GetRoute(params.get('route', None)) |
if not route: |
if not route: |
self.send_error(404) |
self.send_error(404) |
return |
return |
time = int(params.get('time', 0)) |
time = int(params.get('time', 0)) |
sample_size = 10 # For each pattern return the start time for this many trips |
sample_size = 10 # For each pattern return the start time for this many trips |
|
|
pattern_id_trip_dict = route.GetPatternIdTripDict() |
pattern_id_trip_dict = route.GetPatternIdTripDict() |
patterns = [] |
patterns = [] |
|
|
for pattern_id, trips in pattern_id_trip_dict.items(): |
for pattern_id, trips in pattern_id_trip_dict.items(): |
time_stops = trips[0].GetTimeStops() |
time_stops = trips[0].GetTimeStops() |
if not time_stops: |
if not time_stops: |
continue |
continue |
has_non_zero_trip_type = False; |
has_non_zero_trip_type = False; |
for trip in trips: |
for trip in trips: |
if trip['trip_type'] and trip['trip_type'] != '0': |
if trip['trip_type'] and trip['trip_type'] != '0': |
has_non_zero_trip_type = True |
has_non_zero_trip_type = True |
name = u'%s to %s, %d stops' % (time_stops[0][2].stop_name, time_stops[-1][2].stop_name, len(time_stops)) |
name = u'%s to %s, %d stops' % (time_stops[0][2].stop_name, time_stops[-1][2].stop_name, len(time_stops)) |
transitfeed.SortListOfTripByTime(trips) |
transitfeed.SortListOfTripByTime(trips) |
|
|
num_trips = len(trips) |
num_trips = len(trips) |
if num_trips <= sample_size: |
if num_trips <= sample_size: |
start_sample_index = 0 |
start_sample_index = 0 |
num_after_sample = 0 |
num_after_sample = 0 |
else: |
else: |
# Will return sample_size trips that start after the 'time' param. |
# Will return sample_size trips that start after the 'time' param. |
|
|
# Linear search because I couldn't find a built-in way to do a binary |
# Linear search because I couldn't find a built-in way to do a binary |
# search with a custom key. |
# search with a custom key. |
start_sample_index = len(trips) |
start_sample_index = len(trips) |
for i, trip in enumerate(trips): |
for i, trip in enumerate(trips): |
if trip.GetStartTime() >= time: |
if trip.GetStartTime() >= time: |
start_sample_index = i |
start_sample_index = i |
break |
break |
|
|
num_after_sample = num_trips - (start_sample_index + sample_size) |
num_after_sample = num_trips - (start_sample_index + sample_size) |
if num_after_sample < 0: |
if num_after_sample < 0: |
# Less than sample_size trips start after 'time' so return all the |
# Less than sample_size trips start after 'time' so return all the |
# last sample_size trips. |
# last sample_size trips. |
num_after_sample = 0 |
num_after_sample = 0 |
start_sample_index = num_trips - sample_size |
start_sample_index = num_trips - sample_size |
|
|
sample = [] |
sample = [] |
for t in trips[start_sample_index:start_sample_index + sample_size]: |
for t in trips[start_sample_index:start_sample_index + sample_size]: |
sample.append( (t.GetStartTime(), t.trip_id) ) |
sample.append( (t.GetStartTime(), t.trip_id) ) |
|
|
patterns.append((name, pattern_id, start_sample_index, sample, |
patterns.append((name, pattern_id, start_sample_index, sample, |
num_after_sample, (0,1)[has_non_zero_trip_type])) |
num_after_sample, (0,1)[has_non_zero_trip_type])) |
|
|
patterns.sort() |
patterns.sort() |
return patterns |
return patterns |
|
|
def handle_json_wrapper_GET(self, handler, parsed_params): |
def handle_json_wrapper_GET(self, handler, parsed_params): |
"""Call handler and output the return value in JSON.""" |
"""Call handler and output the return value in JSON.""" |
schedule = self.server.schedule |
schedule = self.server.schedule |
result = handler(parsed_params) |
result = handler(parsed_params) |
content = ResultEncoder().encode(result) |
content = ResultEncoder().encode(result) |
self.send_response(200) |
self.send_response(200) |
self.send_header('Content-Type', 'text/plain') |
self.send_header('Content-Type', 'text/plain') |
self.send_header('Content-Length', str(len(content))) |
self.send_header('Content-Length', str(len(content))) |
self.end_headers() |
self.end_headers() |
self.wfile.write(content) |
self.wfile.write(content) |
|
|
def handle_json_GET_routes(self, params): |
def handle_json_GET_routes(self, params): |
"""Return a list of all routes.""" |
"""Return a list of all routes.""" |
schedule = self.server.schedule |
schedule = self.server.schedule |
result = [] |
result = [] |
for r in schedule.GetRouteList(): |
for r in schedule.GetRouteList(): |
result.append( (r.route_id, r.route_short_name, r.route_long_name) ) |
result.append( (r.route_id, r.route_short_name, r.route_long_name) ) |
result.sort(key = lambda x: x[1:3]) |
result.sort(key = lambda x: x[1:3]) |
return result |
return result |
|
|
def handle_json_GET_routerow(self, params): |
def handle_json_GET_routerow(self, params): |
schedule = self.server.schedule |
schedule = self.server.schedule |
route = schedule.GetRoute(params.get('route', None)) |
route = schedule.GetRoute(params.get('route', None)) |
return [transitfeed.Route._FIELD_NAMES, route.GetFieldValuesTuple()] |
return [transitfeed.Route._FIELD_NAMES, route.GetFieldValuesTuple()] |
|
|
|
def handle_json_GET_routetrips(self, params): |
|
""" 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): |
def handle_json_GET_triprows(self, params): |
"""Return a list of rows from the feed file that are related to this |
"""Return a list of rows from the feed file that are related to this |
trip.""" |
trip.""" |
schedule = self.server.schedule |
schedule = self.server.schedule |
try: |
try: |
trip = schedule.GetTrip(params.get('trip', None)) |
trip = schedule.GetTrip(params.get('trip', None)) |
except KeyError: |
except KeyError: |
# if a non-existent trip is searched for, the return nothing |
# if a non-existent trip is searched for, the return nothing |
return |
return |
route = schedule.GetRoute(trip.route_id) |
route = schedule.GetRoute(trip.route_id) |
trip_row = dict(trip.iteritems()) |
trip_row = dict(trip.iteritems()) |
route_row = dict(route.iteritems()) |
route_row = dict(route.iteritems()) |
return [['trips.txt', trip_row], ['routes.txt', route_row]] |
return [['trips.txt', trip_row], ['routes.txt', route_row]] |
|
|
def handle_json_GET_tripstoptimes(self, params): |
def handle_json_GET_tripstoptimes(self, params): |
schedule = self.server.schedule |
schedule = self.server.schedule |
try: |
try: |
trip = schedule.GetTrip(params.get('trip')) |
trip = schedule.GetTrip(params.get('trip')) |
except KeyError: |
except KeyError: |
# if a non-existent trip is searched for, the return nothing |
# if a non-existent trip is searched for, the return nothing |
return |
return |
time_stops = trip.GetTimeStops() |
time_stops = trip.GetTimeStops() |
stops = [] |
stops = [] |
times = [] |
times = [] |
for arr,dep,stop in time_stops: |
for arr,dep,stop in time_stops: |
stops.append(StopToTuple(stop)) |
stops.append(StopToTuple(stop)) |
times.append(arr) |
times.append(arr) |
return [stops, times] |
return [stops, times] |
|
|
def handle_json_GET_tripshape(self, params): |
def handle_json_GET_tripshape(self, params): |
schedule = self.server.schedule |
schedule = self.server.schedule |
try: |
try: |
trip = schedule.GetTrip(params.get('trip')) |
trip = schedule.GetTrip(params.get('trip')) |
except KeyError: |
except KeyError: |
# if a non-existent trip is searched for, the return nothing |
# if a non-existent trip is searched for, the return nothing |
return |
return |
points = [] |
points = [] |
if trip.shape_id: |
if trip.shape_id: |
shape = schedule.GetShape(trip.shape_id) |
shape = schedule.GetShape(trip.shape_id) |
for (lat, lon, dist) in shape.points: |
for (lat, lon, dist) in shape.points: |
points.append((lat, lon)) |
points.append((lat, lon)) |
else: |
else: |
time_stops = trip.GetTimeStops() |
time_stops = trip.GetTimeStops() |
for arr,dep,stop in time_stops: |
for arr,dep,stop in time_stops: |
points.append((stop.stop_lat, stop.stop_lon)) |
points.append((stop.stop_lat, stop.stop_lon)) |
return points |
return points |
|
|
def handle_json_GET_neareststops(self, params): |
def handle_json_GET_neareststops(self, params): |
"""Return a list of the nearest 'limit' stops to 'lat', 'lon'""" |
"""Return a list of the nearest 'limit' stops to 'lat', 'lon'""" |
schedule = self.server.schedule |
schedule = self.server.schedule |
lat = float(params.get('lat')) |
lat = float(params.get('lat')) |
lon = float(params.get('lon')) |
lon = float(params.get('lon')) |
limit = int(params.get('limit')) |
limit = int(params.get('limit')) |
stops = schedule.GetNearestStops(lat=lat, lon=lon, n=limit) |
stops = schedule.GetNearestStops(lat=lat, lon=lon, n=limit) |
return [StopToTuple(s) for s in stops] |
return [StopToTuple(s) for s in stops] |
|
|
def handle_json_GET_boundboxstops(self, params): |
def handle_json_GET_boundboxstops(self, params): |
"""Return a list of up to 'limit' stops within bounding box with 'n','e' |
"""Return a list of up to 'limit' stops within bounding box with 'n','e' |
and 's','w' in the NE and SW corners. Does not handle boxes crossing |
and 's','w' in the NE and SW corners. Does not handle boxes crossing |
longitude line 180.""" |
longitude line 180.""" |
schedule = self.server.schedule |
schedule = self.server.schedule |
n = float(params.get('n')) |
n = float(params.get('n')) |
e = float(params.get('e')) |
e = float(params.get('e')) |
s = float(params.get('s')) |
s = float(params.get('s')) |
w = float(params.get('w')) |
w = float(params.get('w')) |
limit = int(params.get('limit')) |
limit = int(params.get('limit')) |
stops = schedule.GetStopsInBoundingBox(north=n, east=e, south=s, west=w, n=limit) |
stops = schedule.GetStopsInBoundingBox(north=n, east=e, south=s, west=w, n=limit) |
return [StopToTuple(s) for s in stops] |
return [StopToTuple(s) for s in stops] |
|
|
def handle_json_GET_stops(self, params): |
def handle_json_GET_stops(self, params): |
schedule = self.server.schedule |
schedule = self.server.schedule |
return [StopToTuple(s) for s in schedule.GetStopList()] |
return [StopToTuple(s) for s in schedule.GetStopList()] |
|
|
def handle_json_GET_timingpoints(self, params): |
def handle_json_GET_timingpoints(self, params): |
schedule = self.server.schedule |
schedule = self.server.schedule |
matches = [] |
matches = [] |
for s in schedule.GetStopList(): |
for s in schedule.GetStopList(): |