#!/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 |
|
from BaseHTTPServer import HTTPServer, BaseHTTPRequestHandler |
|
from SocketServer import ThreadingMixIn |
|
import threading |
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 datetime |
import datetime |
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 |
class ThreadedHTTPServer(ThreadingMixIn, HTTPServer): |
# http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/425210/index_txt |
"""Handle requests in a separate thread.""" |
# 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): |
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, stop.stop_code) |
float(stop.stop_lon), stop.location_type, stop.stop_code) |
def StopZoneToTuple(stop): |
def StopZoneToTuple(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, stop.stop_code, stop.zone_id) |
float(stop.stop_lon), stop.location_type, stop.stop_code, stop.zone_id) |
|
|
class ScheduleRequestHandler(BaseHTTPServer.BaseHTTPRequestHandler): |
class ScheduleRequestHandler(BaseHTTPServer.BaseHTTPRequestHandler): |
cache = {} |
cache = {} |
|
|
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, handler_name) |
return self.handle_json_wrapper_GET(handler, parsed_params, handler_name) |
|
|
# 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, handler_name): |
def handle_json_wrapper_GET(self, handler, parsed_params, handler_name): |
"""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 |
# round times to nearest 100 seconds |
# round times to nearest 100 seconds |
if "time" in parsed_params: |
if "time" in parsed_params: |
parsed_params['time'] = int(round(float(parsed_params['time']),-2)) |
parsed_params['time'] = int(round(float(parsed_params['time']),-2)) |
paramkey = tuple(sorted(parsed_params.items())) |
paramkey = tuple(sorted(parsed_params.items())) |
if handler_name in self.cache and paramkey in self.cache[handler_name] : |
if handler_name in self.cache and paramkey in self.cache[handler_name] : |
print ("Cache hit for ",handler_name," params ",parsed_params) |
print ("Cache hit for ",handler_name," params ",parsed_params, |
|
" thread ", threading.currentThread().getName()) |
else: |
else: |
print ("Cache miss for ",handler_name," params ",parsed_params) |
print ("Cache miss for ",handler_name," params ",parsed_params, |
|
" thread ", threading.currentThread().getName()) |
result = handler(parsed_params) |
result = handler(parsed_params) |
if not handler_name in self.cache: |
if not handler_name in self.cache: |
self.cache[handler_name] = {} |
self.cache[handler_name] = {} |
self.cache[handler_name][paramkey] = ResultEncoder().encode(result) |
self.cache[handler_name][paramkey] = ResultEncoder().encode(result) |
content = self.cache[handler_name][paramkey] |
content = self.cache[handler_name][paramkey] |
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(): |
servicep = None |
servicep = None |
for t in schedule.GetTripList(): |
for t in schedule.GetTripList(): |
if t.route_id == r.route_id: |
if t.route_id == r.route_id: |
servicep = t.service_period |
servicep = t.service_period |
break |
break |
result.append( (r.route_id, r.route_short_name, r.route_long_name, servicep.service_id) ) |
result.append( (r.route_id, r.route_short_name, r.route_long_name, servicep.service_id) ) |
result.sort(key = lambda x: x[1:3]) |
result.sort(key = lambda x: x[1:3]) |
return result |
return result |
|
|
|
def handle_json_GET_routesearch(self, params): |
|
"""Return a list of routes with matching short name.""" |
|
schedule = self.server.schedule |
|
routeshortname = params.get('routeshortname', None) |
|
result = [] |
|
for r in schedule.GetRouteList(): |
|
if r.route_short_name == routeshortname: |
|
servicep = None |
|
for t in schedule.GetTripList(): |
|
if t.route_id == r.route_id: |
|
servicep = t.service_period |
|
break |
|
result.append( (r.route_id, r.route_short_name, r.route_long_name, servicep.service_id) ) |
|
result.sort(key = lambda x: x[1:3]) |
|
return result |
|
|
|
|
def handle_json_GET_routerow(self, params): |
def handle_json_GET_routerow(self, params): |
schedule = self.server.schedule |
schedule = self.server.schedule |
route = schedule.GetRoute(params.get('route', None)) |
route = schedule.GetRoute(params.get('route', None)) |
return [transitfeed.Route._FIELD_NAMES, route.GetFieldValuesTuple()] |
return [transitfeed.Route._FIELD_NAMES, route.GetFieldValuesTuple()] |
|
|
def handle_json_GET_routetrips(self, params): |
def handle_json_GET_routetrips(self, params): |
""" Get a trip for a route_id (preferablly the next one) """ |
""" Get a trip for a route_id (preferablly the next one) """ |
schedule = self.server.schedule |
schedule = self.server.schedule |
query = params.get('route_id', None).lower() |
query = params.get('route_id', None).lower() |
result = [] |
result = [] |
for t in schedule.GetTripList(): |
for t in schedule.GetTripList(): |
if t.route_id == query: |
if t.route_id == query: |
result.append ( (t.GetStartTime(), t.trip_id) ) |
try: |
return sorted(result, key=lambda trip: trip[0]) |
starttime = t.GetStartTime() |
|
except: |
|
print "Error for GetStartTime of trip #" + t.trip_id + sys.exc_info()[0] |
|
else: |
|
cursor = t._schedule._connection.cursor() |
|
cursor.execute( |
|
'SELECT arrival_secs,departure_secs FROM stop_times WHERE ' |
|
'trip_id=? ORDER BY stop_sequence DESC LIMIT 1', (t.trip_id,)) |
|
(arrival_secs, departure_secs) = cursor.fetchone() |
|
if arrival_secs != None: |
|
endtime = arrival_secs |
|
elif departure_secs != None: |
|
endtime = departure_secs |
|
else: |
|
endtime =0 |
|
result.append ( (starttime, t.trip_id, endtime) ) |
|
return sorted(result, key=lambda trip: trip[2]) |
|
|
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 |
tr |