/
BusRouteAnalysis.py
254 lines (211 loc) · 8.85 KB
/
BusRouteAnalysis.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
'''
- Class to hold bus route info
- Read from KML
- Save to CSV
- Show route on a map
- Function to combine multiple routes into a dict
- Function to measure distance & travel time between addresses and/or
coordinate pairs
'''
# Imports
from bs4 import BeautifulSoup as BS
from Tkinter import Tk
from tkFileDialog import askopenfilename
from tkFileDialog import asksaveasfilename
import numpy as np
from matplotlib import pyplot as plt
from mpl_toolkits.basemap import Basemap
from matplotlib.patches import Polygon
import googlemaps
# Define class to hold data/info on a given bus route
class BusRoute(object):
'''
Class to hold and process information for a single bus route.
Kwargs:
route_number Int specifying the route number to import
dimensions
'''
def __init__(self, **kwargs):
self._set_options(**kwargs)
self._import_data()
def _set_options(self, route_number=10, dimensions=3, **kwargs):
# set options/inputs
self.filename = 'data\\route_locations\\route%d.kml'%(route_number)
self.dimensions = dimensions
def _import_data(self):
# open the source KML file, ask for a new file if invalid
if self.filename is None:
Tk().withdraw() # keep the root window from appearing
self.filename = askopenfilename()
try:
f = open(self.filename, 'r')
except IOError:
Tk().withdraw() # keep the root window from appearing
self.filename = askopenfilename()
f = open(self.filename, 'r')
self.KMLdata = BS(f)
f.close()
# extract coordinate data from KML and convert to array
coords_str = unicode(self.KMLdata.find(name='coordinates').contents[0])
self.coordinates = _unicode_to_array(coords_str)
def saveas_csv(self, filename=None, **kwargs):
'''
Save bus route coordinate data (lat, longitude, 0) as a csv file
'''
# Note: kwargs are just passed to numpy's built-in savetxt function
# get filename
if filename is None:
Tk().withdraw() # keep the root window from appearing
filename = asksaveasfilename()
# save using numpy's built-in function
np.savetxt(filename, self.coordinates, **kwargs)
def show_route(self):
'''
Display the route coordinates on a map of Tompkins County
'''
# plot basemap w/ state and county lines, etc
fig = plt.figure()
m = Basemap(llcrnrlon=-76.8, llcrnrlat=42.2, urcrnrlon=-76.2, \
urcrnrlat=42.7, rsphere=(6378137.00,6356752.3142), resolution='l', \
projection='merc')
m.shadedrelief()
m.drawcoastlines()
m.drawstates()
m.drawcountries()
m.drawcounties()
# plot ny state water features
m.readshapefile('data\\water\\NHD_M_36_New_York_ST\\NHDFlowline','water', color='LightSteelBlue', linewidth=2.)
m.readshapefile('data\\water\\NHD_M_36_New_York_ST\\NHDArea','water_area', drawbounds=False)
m.readshapefile('data\\water\\NHD_M_36_New_York_ST\\NHDWaterbody','lakes', drawbounds=False)
for lake in m.lakes + m.water_area:
poly = Polygon(lake, facecolor='LightSteelBlue', edgecolor='CornflowerBlue')
plt.gca().add_patch(poly)
# read and plot tompkins county shapefile
m.readshapefile('data\\parcels\\ParcelPublic2016_WGS84', 'parcels')
# plot route coordinates
m.plot(self.coordinates[:,0], self.coordinates[:,1], '.-',
latlon=True, c='FireBrick', lw=2.)
# finalize and show plot
fig.show()
# Other functions
def _unicode_to_array(string):
'''
Take a string with coordinates in the form "lat,lon,0 "*N
and return an N-by-3 numpy array containing the coordinates as floats
Inputs (required):
string String with coordinates in the form "lat,lon,0 "*N
Outputs:
array Numpy array of floats with shape (N,3) containing the
coordinates from string. Each row is a coordinate set
i.e. [[lat0, lon0, 0], [lat0, lon0, 0],...]
'''
# setup, process inputs
num_cols = 3
string = string.strip().split(' ')
num_rows = len(string)
# calculate how many rows and preallocate output
array = np.empty((num_rows, num_cols), dtype=float)
# convert from string to float
for rr in xrange(num_rows):
row = string[rr].split(',')
for cc in xrange(num_cols):
array[rr][cc] = float(row[cc])
return array
def get_routes(numbers):
'''
Create dictionary to hold all bus routes given numbers
Inputs (required):
numbers Tuple of ints listing the bus route(s) to be
containted in the output
Outputs:
routes Dict of bus route objects, identified by their int bus route
number
'''
all_routes = dict(zip(numbers, numbers))
for n in numbers:
all_routes[n] = BusRoute(route_number=n)
return all_routes
def geocode(address, key='default', other={}):
'''
Use google maps geocoding API to convert string with address to coordinates
Input (required):
address String specifying map address
Kwargs (optional):
key Key used for gaining API access. Default is [hidden]
other Dict containing other kwargs passes to the gmaps api
Output:
coordinates Numpy float arrary of shape (3L,) containing the
coordinates of the given address, i.e. [lat, lon, 0.]
'''
if key == 'default':
fkey = open('GoogleMapsAPIKey.txt', 'r')
key = fkey.readline()
fkey.close()
gmaps = googlemaps.Client(key=key)
params = {}
params.update(other)
res = gmaps.geocode(address=address, **params)
coordinates = np.array((res[0]['geometry']['location']['lat'],
res[0]['geometry']['location']['lng'],
0.), dtype=float)
return coordinates
def distance_time(pointA, pointB, units='imperial', mode='walking',
printout=False, key='default', other={}):
'''
Use googlemaps API to return the distance (in meters) and travel time (in
seconds) from the addresses or coordinate pairs in pointA to the addresses
or coordinate pairs in pointB. Additional optional kwargs are used to set
API parameters.
Inputs (required):
pointA String or tuple of strings or (lat,lon) pairs specifying
origin address(es)
pointB String or tuple of strings or (lat,lon) pairs specifying
destination address(es)
KWargs (optional):
units 'imperial' or 'metric', specifying the units for printed
quantities only. Default is 'imperial'
mode Mode of transportation, see googlemaps distance matrix API
documentation for a complete list of options. Default is
'walking'
key Key used for gaining API access. Default is [hidden]
printout Boolean specifying if the distance and time results should
be printed to the screen. Default is False
other Dict containing other kwargs passes to the gmaps api
Outputs:
distance Distance in meters between the (first) origin and
destination pair
duration Time in seconds to travel between the (first) origin and
destination pair
'''
# handle inputs
if key == 'default':
fkey = open('GoogleMapsAPIKey.txt', 'r')
key = fkey.readline()
fkey.close()
params = {'units': units, 'mode': mode}
params.update(other)
pointA, pointB = [list(c) if (type(c) is tuple) else c for c in (pointA, pointB)]
# make request
gmaps = googlemaps.Client(key=key)
res = gmaps.distance_matrix(pointA, pointB, **params)
# interpret response to return distance & duration measures
if res['status']=='OK':
# print each result to the screen
if printout:
o = 0
for A in res['origin_addresses']:
d = 0
for B in res['destination_addresses']:
print '%s to %s: %s (%s %s)'%(A, B, \
res['rows'][o]['elements'][d]['distance']['text'], \
res['rows'][o]['elements'][d]['duration']['text'], \
params['mode'])
d += 1
o += 1
# return the set of distances and durations
distances = [[a['distance']['value'] for a in b['elements']] for b in res['rows']]
durations = [[a['duration']['value'] for a in b['elements']] for b in res['rows']]
return (distances, durations)
else:
print 'Oops! hit a problem: ', res['status']
return (None, None)