#!/usr/bin/python print 'Content-Type: text/html' print # can't put meta tag here.. # bus_tracker_v8.py # grabs InstaMapper data about Duke buses; estimates ETA of the buses based on their locations on the route # meant to be called as a CGI script, displays data with this in mind # Matt Ball, December 2009; more info here: http://www.instructables.com/id/Bus-tracking-on-the-cheap import time; import urllib2; import csv; from math import sqrt import cgitb; cgitb.enable() def config(): keys = ['', ''] # IM api keys route_file = 'C1_route_v2.csv' data_file = 'bus_data.txt' # server-side storage of bus info log_file = 'bt_log_v2.txt' return keys, route_file, data_file, log_file def main(): access_time = time.time() keys, route_file, data_file, log_file = config() # get the keys, route info, data file name and log file name timestamp = check_stamp(data_file) # extract the data_file's time-of-last-calculation call_time = time.time() # get the current time if (call_time - timestamp > 10): # if it's been ten seconds since last fetch, we can go back to IM for new data (10s by their TOS) route_positions, route_data, dest, ETA, mindist_ind, vec = get_new_data(keys, route_file, data_file) write_data(call_time, route_positions, route_data, dest, ETA, data_file, mindist_ind, vec) # write the new data file else: call_time = timestamp # if we're not in the if-loop, we used the timestamped data display(data_file) # send the info to the browser log_data(data_file, log_file, call_time, access_time) # log the results of the calculations def display(data_file): # function for the CGI, kick this info out to the browser #print 'Content-Type: text/html' print '' # 11 second refresh to guarantee IM fetch; meta tag can't go up top in this script for some reason print '

bus_tracker_v8:

\n' print '

Matt Ball, December 2009

\n' response = open(data_file, 'r') a = len(response.readlines()) - 3 # how many lines (how many buses)? (ignore timestamp, mindist_ind, and dir-vector) response.seek(0) # go back to start of the file response.readline() # skip the first line, the timestamp; [should use response.seek() here] response.readline() # skip the second line, mindist_ind (the bus old positions encoded numerically) response.readline() # skip the third line, the bus traveling-direction data for i in range(a): data = [i for i in response.readline() if i != '\n'] # remove linebreaks line = '' # init a string for item in range(len(data)): line += data[item] # concatenate print '

' + line + '

' response.close() print '\n\n

..done

\n\n' print '

GPS tracking provided by InstaMapper

' def check_stamp(data_file): response = open(data_file, 'r') a = response.readline() response.close() a = [i for i in a if i != '\n'] # remove the line break timestamp = '' # init a string for item in range(len(a)): timestamp += a[item] # extract the timestamp as string timestamp = float(timestamp) return timestamp def get_new_data(keys, route_file, data_file): full_data = fetch(keys) # fetch raw data from IM; could also check to see if data's changed here.. route_data = [ x for x in csv.reader(open(route_file,'r')) ] # extract route info route_positions, mindist_ind, vec = calc_pos(full_data, route_data, data_file) # calc position based on raw data and route info dest, ETA = calc_time(route_positions, route_data, mindist_ind) return route_positions, route_data, dest, ETA, mindist_ind, vec def fetch(keys): # pull data from IM site bus_num = len(keys) full_data = [] for i in range(bus_num): response = urllib2.urlopen('http://www.instamapper.com/api?action=getPositions&key='+keys[i]) html = response.read() html = html.replace('\n', '') html = html.replace('InstaMapper API v1.00', '') data = html.split(',') data = [i for i in data if i != 'NULL'] #del data[len(data) - 1]; del data[len(data) - 1]; # could drop elevation data and heading data full_data.append(data) # a list of lists.. return full_data def calc_pos(full_data, route_data, data_file): # assumes records == 1 for now.. though why would you want more? route_positions = [] mindist_ind = [] vec = [] response = open(data_file, 'r') # import the old positions and travel-directions of the buses response.readline() # skip first line (the timestamp) old_pos = eval(response.readline()) # next line is the bus position vector which we want a = [i for i in response.readline() if i != '\n'] # annnd this line has the 'last direction' information line = '' for item in range(len(a)): line += a[item] # concatenate line.split(',') old_vec = eval(line) response.close() for i in range(len(full_data)): xpos = float(full_data[i][3]) # latitude..er should be ypos, whatever ypos = float(full_data[i][4]) # longitude dist = [] for k in range(len(route_data)): dist.append(sqrt((xpos - float(route_data[k][1]))**2 + (ypos - float(route_data[k][2]))**2)) # pythagoras' idea mindist_ind.append(dist.index(min(dist))) if((mindist_ind[i] - old_pos[i]) > 0): vec.append('West-bound') elif((mindist_ind[i] - old_pos[i]) < 0): vec.append('East-bound') else: vec.append(old_vec[i]) # if positions haven't changed, use the last value businfo = [full_data[i][1], full_data[i][2], route_data[mindist_ind[i]][0], vec[i]] # busID, time, pos on route, dir on route route_positions.append(businfo) # add this ith bus to the array of all bus data return route_positions, mindist_ind, vec def calc_time(route_positions, route_data, mindist_ind): dest = [] ETA = [] for i in range(len(route_positions)): direction = route_positions[i][3] if (direction == 'West-bound'): dest.append('West Campus stop') col = 4 # grab the E --> W ETA column in the routes csv file else: dest.append('East Campus stop') col = 3 ETA.append( route_data[mindist_ind[i]][col] ) return dest, ETA def write_data(call_time, route_positions, route_data, dest, ETA, data_file, mindist_ind, vec): busdata = open(data_file, 'w') busdata.write(str(call_time) + '\n') # record the time that IM was accessed busdata.write(str(mindist_ind) + '\n') # record current positions of the buses numerically busdata.write(str(vec) + '\n') # record the directions the buses are traveling for i in range(len(route_positions)): busID = str(route_positions[i][0]) # r_p is the full array of all buses and their locations direction = str(route_positions[i][3]) when = str(time.strftime('%a %b %d, %I:%M:%S %p', time.localtime(float(route_positions[i][1])))) where = str(route_positions[i][2]) if ETA[i] == '1': grammarnazi = ' minute.' # because '1 minutes' is unacceptable.. else: grammarnazi = ' minutes.' line_update = busID+' was '+direction+' near '+where+' on '+when+', arriving at the '+dest[i]+' in '+ETA[i]+ grammarnazi busdata.write(line_update+'\n') busdata.close() def log_data(data_file, log_file, call_time, access_time): datafile = open(data_file, 'r') data = datafile.readlines() datafile.close() try: logfile = open(log_file, 'r+') # storing it all.. a = logfile.readlines() hits = eval(str(a[len(a) - 3])) # grab the previous number of hits hits += 1 # annnd add one except IOError: logfile = open(log_file, 'a') # if there's an IOError, the logfile just needs to be created hits = 1 # and if it's a new logfile, this is the first hit access_time = time.strftime('%a, %d %b %Y %I:%M:%S %p', time.localtime(access_time)) logfile.write('\nweakorbit page accessed on: ' + access_time + '\n') call_time = time.strftime('%a, %d %b %Y %I:%M:%S %p', time.localtime(call_time)) # convert call_time to local time logfile.write('IM data grabbed on: ' + call_time + '\n') logfile.write('the contents of ' + data_file + ' were as follows:\n') for i in range(len(data)): logfile.write(data[i]) # write in all the lines in the current data_file logfile.write(str(hits) + '\n') # update the hit counter logfile.write('\n-------\n') logfile.close if __name__ == '__main__' : main()