--- a/schedule_viewer.py +++ b/schedule_viewer.py @@ -22,6 +22,8 @@ import BaseHTTPServer, sys, urlparse +from BaseHTTPServer import HTTPServer, BaseHTTPRequestHandler +from SocketServer import ForkingMixIn import bisect from gtfsscheduleviewer.marey_graph import MareyGraph import gtfsscheduleviewer @@ -57,34 +59,8 @@ 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() - +class ForkedHTTPServer(ForkingMixIn, HTTPServer): + """Handle requests in a separate forked process.""" def StopToTuple(stop): """Return tuple as expected by javascript function addStopMarkerFromList""" @@ -249,14 +225,14 @@ def handle_json_wrapper_GET(self, handler, parsed_params, handler_name): """Call handler and output the return value in JSON.""" schedule = self.server.schedule - # round times to nearest 100 seconds + # round times to nearest 1000 seconds - up to 17 minutes out of date if "time" in parsed_params: - parsed_params['time'] = int(round(float(parsed_params['time']),-2)) + parsed_params['time'] = int(round(float(parsed_params['time']),-3)) paramkey = tuple(sorted(parsed_params.items())) if handler_name in self.cache and paramkey in self.cache[handler_name] : print ("Cache hit for ",handler_name," params ",parsed_params) else: - print ("Cache miss for ",handler_name," params ",parsed_params) + print ("Cache miss for ",handler_name," params ",parsed_params) result = handler(parsed_params) if not handler_name in self.cache: self.cache[handler_name] = {} @@ -282,11 +258,28 @@ result.sort(key = lambda x: x[1:3]) 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): 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 @@ -294,8 +287,24 @@ result = [] for t in schedule.GetTripList(): if t.route_id == query: - result.append ( (t.GetStartTime(), t.trip_id) ) - return sorted(result, key=lambda trip: trip[0]) + try: + 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): """Return a list of rows from the feed file that are related to this @@ -344,14 +353,56 @@ points.append((stop.stop_lat, stop.stop_lon)) return points +# +# GeoPo Encode in Python +# @author : Shintaro Inagaki +# @param location (Dictionary) [lat (Float), lng (Float), scale(Int)] +# @return geopo (String) +# + 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] + limit = int(params.get('limit',5)) + scale = int(params.get('scale',5)) # 5 = neighbourhood ~ 1km, 4= town 5 by 7km + stops = [] + + # 64characters (number + big and small letter + hyphen + underscore) + chars = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ-_" + + geopo = "" + + # Change a degree measure to a decimal number + lat = (lat + 90.0) / 180 * 8 ** 10 # 90.0 is forced FLOAT type when lat is INT + lon = (lon + 180.0) / 360 * 8 ** 10 # 180.0 is same + + # Compute a GeoPo code from head and concatenate + for i in range(scale): + order = int(lat / (8 ** (9 - i)) % 8) + int(lon / (8 ** (9 - i)) % 8) * 8 + geopo = geopo + chars[order] + + + for s in schedule.GetStopList(): + if s.stop_code.find(geopo) != -1: + stops.append(s) + + if scale == 5: + print stops + return [StopToTuple(s) for s in stops] + else: + dist_stop_list = [] + for s in stops: + # TODO: Use util.ApproximateDistanceBetweenStops? + dist = (s.stop_lat - lat)**2 + (s.stop_lon - lon)**2 + if len(dist_stop_list) < limit: + bisect.insort(dist_stop_list, (dist, s)) + elif dist < dist_stop_list[-1][0]: + bisect.insort(dist_stop_list, (dist, s)) + dist_stop_list.pop() # Remove stop with greatest distance + print dist_stop_list + return [StopToTuple(s) for dist, s in dist_stop_list] def handle_json_GET_boundboxstops(self, params): """Return a list of up to 'limit' stops within bounding box with 'n','e' @@ -421,34 +472,42 @@ 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.""" + + def handle_json_GET_stoproutes(self, params): + """Given a stop_id return all routes to visit the stop.""" schedule = self.server.schedule stop = schedule.GetStop(params.get('stop', None)) - time = int(params.get('time', 0)) - limit = int(params.get('limit', 15)) 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] + trips = stop.GetTrips(schedule) + result = {} + for trip in trips: + route = schedule.GetRoute(trip.route_id) + if service_period == None or trip.service_id == service_period: + if not route.route_short_name+route.route_long_name+trip.service_id in result: + result[route.route_short_name+route.route_long_name+trip.service_id] = (route.route_id, route.route_short_name, route.route_long_name, trip.trip_id, trip.service_id) + return result + + def handle_json_GET_stopalltrips(self, params): + """Given a stop_id return all trips to visit the stop (without times).""" + schedule = self.server.schedule + stop = schedule.GetStop(params.get('stop', None)) + service_period = params.get('service_period', None) + trips = stop.GetTrips(schedule) + result = [] + for trip in trips: + if service_period == None or trip.service_id == service_period: + result.append((trip.trip_id, trip.service_id)) + return result + + def handle_json_GET_stopalltriptimes(self, params): + """Given a stop_id return all trips to visit the stop (with times). + DEPRECIATED?""" + schedule = self.server.schedule + stop = schedule.GetStop(params.get('stop', None)) + service_period = params.get('service_period', None) + time_trips = stop.GetStopTrips(schedule) result = [] for time, (trip, index), tp in time_trips: - if len(result) > limit: - break - 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: @@ -457,9 +516,41 @@ if len(trip_name): trip_name += " - " trip_name += route.route_long_name - # comment out directions because we already have them in the 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_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)) + requested_time = int(params.get('time', 0)) + limit = int(params.get('limit', 15)) + service_period = params.get('service_period', None) + time_range = int(params.get('time_range', 24*60*60)) + + filtered_time_trips = [] + for trip, index in stop._GetTripIndex(schedule): + tripstarttime = trip.GetStartTime() + if tripstarttime > requested_time and tripstarttime < (requested_time + time_range): + time, stoptime, tp = trip.GetTimeInterpolatedStops()[index] + if time > requested_time and time < (requested_time + time_range): + bisect.insort(filtered_time_trips, (time, (trip, index), tp)) + result = [] + for time, (trip, index), tp in filtered_time_trips: + if len(result) > limit: + break + 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 service_period == None or trip.service_id == service_period: result.append((time, (trip.trip_id, trip_name, trip.service_id), tp)) return result @@ -590,14 +681,13 @@ t0 = datetime.datetime.now() schedule.Load(options.feed_filename) print ("Loaded in", (datetime.datetime.now() - t0).seconds , "seconds") - server = StoppableHTTPServer(server_address=('', options.port), + server = ForkedHTTPServer(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 - + server.feed_path = options.feed_filename print ("To view, point your browser at http://localhost:%d/" %