-
Notifications
You must be signed in to change notification settings - Fork 1
/
Copy pathbuber.py
344 lines (262 loc) · 13.4 KB
/
buber.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
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
"""
Filename: buber.py
### Description: ###
An application to provide information on specific bus numbers
through Colchester, Essex, UK
This will be done with the help of the transportapi
https://developer.transportapi.com/
Map plotting is via folium and the following tutorial
https://www.geeksforgeeks.org/python-plotting-google-map-using-folium-package/?ref=rp
### Basic Decomposition: - "Eat the elephant one bite at a time" ###
1. Hide API key and Program ID so we dont need to keep entering it or URLS - Done
2. Retreive data for the Number 65 bus heading outbound - Done
3. Take user input for bus number. Only allow then to enter the buses we have supplied - Done
3.1 If user enter an incorrect bus number twice offer them the option to find the bus number by final destination
- NEEDS IMPLEMENTING
4. User enters a bus number. We are only using a subset of buses for Colchester. "64", "65", "67", "70", "74", "88",
"104".
We have atcocodes for these stops and can define start and end points
5. User enters a destination - Program tells user what bus goes there and when the next departure is (from Town Center)
6. Display data to user on a map. - Milestone 1 - Done
### Questions the app should be able to answer ###
1. When is the next bus - enter bus number return next bus time.
2. Given a location display what buses go there. if you select a bus then display the time of the next bus.
### Future work if we have time ###
1. Make a windows graphical app
2. Make an android graphical app
3. Deploy it to a virtual android phone and run it
TRANSPORT API - app_id & app_key are not a valid keys in the examples below. Yoou need to reg as a developer at
transportapi.com to generate your own API key
1. This URL identifies One particular bus service, identified by line and operator - Function bus_service(bus_number)
It can be used to get the BUS_START and BUS_END of the journey
RETURNS: bus_number, outbound, inbound
'https://transportapi.com/v3/uk/bus/services/FESX:65.json?app_id=8e1c41c&app_key=<API KEY>'
2. Bus departures at a given bus stop (live) - Function next_bus_live()
By passing in the atcocode for the bus stop you can get live departure information for that stop
##PARAMETERS TO PROVIDE
atcocode
##INTERESTING VALUES IN RETURENED DATA
line_name, direction, status, best_departure_estimate
This returns any bus operating company utilising that stop
Data is returned in a dictionary of x lists where x is the number of buses that use that stop
First part of data recieved from url pull
{'65': [{'mode': 'bus', 'line': '65', 'line_name': '65', 'direction': 'Highwoods Tesco', 'operator': 'FESX',
'date': '2020-05-19', 'expected_departure_date': '2020-05-19', 'aimed_departure_time': '09:32',
'expected_departure_time': '09:30', 'best_departure_estimate': '09:30',
'status': {'cancellation': {'value': False, 'reason': None}}, 'source': 'FirstTicketerNationwide', 'dir': 'outbound',
'operator_name': 'First Essex',
'id': <URL>>
'https://transportapi.com/v3/uk/bus/stop/1500AA20/live.json?app_id=8e1c41c& \
app_key=<APP KEY>a&group=route&nextbuses=yes'
3. Bus departures at a given bus stop (timetabled) - Function next_bus_timetabled()
This URL gives you the timetabled information for that particulatr stop
##PARAMETERS TO PROVIDE
date, time, atcocode
https://transportapi.com/v3/uk/bus/stop/1500AA20/2020-05-15/07:10/timetable.json?app_id=8e1c41c& \
app_key=<APP KEY>a&group=route
4. The route of one specific bus - Function bus_route()
This gives every stop the bus will make between its atcocode bust stop and the final destination
##PARAMETERS TO PROVIDE
date, time, atcocode
https://transportapi.com/v3/uk/bus/route/FESX/65/outbound/1500AA20/2020-05-15/07:10/timetable.json?app_id=8e1c41c& \
app_key=<APP KEY>a&edge_geometry=false&stops=ALL
"""
# Import Libraries we need
import urllib3
import json
import pandas as pd
import requests
import folium
import sys
import os
import webbrowser
import logging
import time
from datetime import date
# Import our buberconfig
import buberconfig
# Set up logging - Default id to ,log info and above
# change 'level=logging.INFO' to 'level=logging.DEBUG' to get debug messages
logging.basicConfig(filename='log/buber.log', level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s')
# Uncomment the logging.disable line to disable logging.
# Setting it to logging.CRITICAL disabled all logging
# CRITICAL and below Logging levels in order lowest to
# highest are as follows
# DEBUG (Lowest, INFO, WARNING, ERROR, CRITICAL (Highest)
#logging.disable(logging.CRITICAL)
# Start logging Program
logging.info('Start of Program')
# Constants
BASE_URL = 'https://transportapi.com/v3/uk/bus'
TODAY = date.today()
# define day of week for Sunday (0 - Monday, 1 - Tuesday, 2 - Wednesday, 3 - Thursday, 4 - Friday, 5 - Satarday,
# 6 - Sunday) - Get this from datetime - datetime.date.today().weekday() - Gives numeric for day of week
SUNDAY = 6
starttime = time.time()
def main():
# Ask the user to input a bus number
what_bus = input("Which First Essex bus do you require?: ")
# Validate if it is a service we support
bus = validate_bus(what_bus)
# Get the bus Route
bus_route(bus)
# only work on valid bus routes supported by our application
if bus != "Unsupported service":
# Get some information on the bus_service
bus_serv, outbound, inbound = bus_service(bus)
print("Bus Number: " + bus_serv + " , Travels between " + outbound + ", and " + inbound)
print("Preparing route map for the number " + bus_serv + " bus." )
print("This will only take a short while...")
else:
logging.debug('DEBUG 0: Unsupported bus %s. Program exits via sys.exit' % what_bus)
logging.info('End of Program')
sys.exit("Unsupported service at this time. Goodbye")
def validate_bus(what_bus):
"""
Validate if the bus service number is supported by our application.
Args:
what_bus (str): The bus service number to validate.
Returns:
str: The validated bus service number if it is supported by our application.
Otherwise, a message indicating that the service is unsupported.
"""
try:
# These are only First Essex buses
buses = {"S4", "65", "67", "70", "74B", "88", "104"}
if what_bus in buses:
return what_bus
else:
raise ValueError(f"We do not support bus service number {what_bus} at this time. Supported buses are: {', '.join(buses)}")
except ValueError as e:
return str(e)
def bus_service(bus_number):
"""
Retrieve the endpoints of a bus service given its number.
Args:
bus_number (str): The number of the bus service to retrieve.
Returns:
tuple: A tuple containing the bus service number and its endpoints.
Raises:
ValueError: If the bus service number is invalid or cannot be retrieved.
"""
try:
bus_num = bus_number
# Retrieve a URL via requests
# Use f-strings to pass in the Constants and Variables to make up the URL
url = f"{BASE_URL}/services/FESX:{bus_num}.json?app_id={buberconfig.APP_ID}&app_key={buberconfig.API_KEY}"
response = requests.get(url)
response.raise_for_status()
bus_service_dict = response.json()
outbound = bus_service_dict['directions'][0]['destination']['description']
inbound = bus_service_dict['directions'][1]['destination']['description']
# Return the bus number and its endpoints
return bus_num, outbound, inbound
except (requests.exceptions.RequestException, ValueError) as e:
raise ValueError(f"Failed to retrieve bus service {bus_number}: {e}")
def next_bus_live():
"""
Retrieve live data for the next buses at a specific stop.
Returns:
dict: A dictionary containing the live data for the next buses at the specified stop.
Raises:
ValueError: If the live data cannot be retrieved.
"""
try:
# Retrieve a URL via requests
# Use f-strings to pass in the Constants and Variables to make up the URL
url = f"{BASE_URL}/stop/1500AA20/live.json?app_id={buberconfig.APP_ID}&app_key={buberconfig.API_KEY}&group=route&nextbuses=yes"
response = requests.get(url)
response.raise_for_status()
next_bus_live_dict = response.json()
print(next_bus_live_dict)
return next_bus_live_dict
except (requests.exceptions.RequestException, ValueError) as e:
raise ValueError(f"Failed to retrieve live bus data: {e}")
def next_bus_timetabled():
"""
Retrieve timetabled data for the next buses at a specific stop.
Returns:
dict: A dictionary containing the timetabled data for the next buses at the specified stop.
Raises:
ValueError: If the timetabled data cannot be retrieved.
"""
try:
# Retrieve a URL via requests
# Use f-strings to pass in the Constants and Variables to make up the URL
url = f"{BASE_URL}/stop/1500AA20/{TODAY}/07:10/timetable.json?app_id={buberconfig.APP_ID}&app_key={buberconfig.API_KEY}"
response = requests.get(url)
response.raise_for_status()
next_bus_timetabled_dict = response.json()
#print(next_bus_timetabled_dict)
return next_bus_timetabled_dict
except (requests.exceptions.RequestException, ValueError) as e:
raise ValueError(f"Failed to retrieve timetabled bus data: {e}")
def bus_route(bus_number):
"""
Retrieve route data for a specific bus service number.
Args:
bus_number (str): The number of the bus service to retrieve.
Returns:
list: A list containing the bus stand, latitude, and longitude of each stop along the bus route.
Raises:
ValueError: If the route data cannot be retrieved.
"""
try:
bus = bus_number
# Retrieve a URL via requests
# Use f-strings to pass in the Constants and Variables to make up the URL
if bus == "64":
url = f"{BASE_URL}/route/FESX/{bus}/inbound/1500IM140/{TODAY}/07:05/timetable.json?app_id={buberconfig.APP_ID}&app_key={buberconfig.API_KEY}&edge_geometry=false&stops=ALL"
elif bus == "65":
url = f"{BASE_URL}/route/FESX/{bus}/inbound/1500IM2456B/{TODAY}/19:27/timetable.json?app_id={buberconfig.APP_ID}&app_key={buberconfig.API_KEY}&edge_geometry=false&stops=ALL"
elif bus == "67":
url = f"{BASE_URL}/route/FESX/{bus}/inbound/150033038003/{TODAY}/06:55/timetable.json?app_id={buberconfig.APP_ID}&app_key={buberconfig.API_KEY}&edge_geometry=false&stops=ALL"
elif bus == "70":
url = f"{BASE_URL}/route/FESX/{bus}/inbound/1500IM77A/{TODAY}/06:51/timetable.json?app_id={buberconfig.APP_ID}&app_key={buberconfig.API_KEY}&edge_geometry=false&stops=ALL"
elif bus == "74B":
url = f"{BASE_URL}/route/FESX/{bus}/inbound/15003303800B/{TODAY}/20:10/timetable.json?app_id={buberconfig.APP_ID}&app_key={buberconfig.API_KEY}&edge_geometry=false&stops=ALL"
elif bus == "88":
url = f"{BASE_URL}/route/FESX/{bus}/inbound/1500IM77A/{TODAY}/05:50/timetable.json?app_id={buberconfig.APP_ID}&app_key={buberconfig.API_KEY}&edge_geometry=false&stops=ALL"
else: # The 104
url = f"{BASE_URL}/route/FESX/{bus}/inbound/1500IM52/{TODAY}/06:00/timetable.json?app_id={buberconfig.APP_ID}&app_key={buberconfig.API_KEY}&edge_geometry=false&stops=ALL"
response = requests.get(url)
response.raise_for_status()
bus_route_dict = response.json()
# Create a list of bus stands, latitudes, and longitudes
bus_route_list = [[stop['stop_name'], stop['latitude'], stop['longitude']] for stop in bus_route_dict['stops']]
logging.debug('DEBUG 5: Bus Route List ' + str(bus_route_list))
return bus_route_list
except (requests.exceptions.RequestException, ValueError) as e:
raise ValueError(f"Failed to retrieve bus route data for bus service {bus_number}: {e}")
def map_it(bus_route_list):
"""
Map the bus route on a Folium map.
Args:
bus_route_list (list): A list containing the bus stand, latitude, and longitude of each stop along the bus route.
Raises:
ValueError: If the map cannot be created.
"""
try:
# Convert the list to a pandas DataFrame
map_it_df = pd.DataFrame(bus_route_list)
# Rename the DataFrame columns
map_it_df.columns = ['stop', 'lat', 'long']
logging.debug('DEBUG 6: map_it_df.head()' + str(map_it_df.head()))
# Prepare the data for the map
locations = map_it_df[['lat', 'long']]
locationlist = locations.values.tolist()
# Build the map centered on Colchester, Essex
route_map = folium.Map(location=[51.8959, 0.8919], zoom_start=14)
# Plot the stops on the map and add their names as popups
for point in range(0, len(locationlist)):
folium.Marker((locationlist[point]), popup=map_it_df['stop'][point]).add_to(route_map)
# Save the map to a file and open it in the browser
route_map.save("c:\\Data\\PythonProjects\\buber\\route_maps\\route_map.html ")
webbrowser.open('file://' + os.path.realpath('c:\\Data\\PythonProjects\\buber\\route_maps\\route_map.html'))
except Exception as e:
raise ValueError(f"Failed to create bus route map: {e}")
if __name__ == '__main__':
main()
# Record how long the program took to run and then stop logging program
logging.info('Program Run Time (s): ' + str(time.time() - starttime))
logging.info('End of Program')