-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathweb_app.py
116 lines (103 loc) · 3.81 KB
/
web_app.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
"""
Scrapes realtime NCTX bus times and returns
a JSON object.
"""
from datetime import datetime, timedelta, tzinfo
import os
from flask import Flask, jsonify, session
from flask_cors import CORS
from gazpacho import get, Soup
import requests
APP = Flask(__name__, static_folder='./frontend', static_url_path='/')
APP.secret_key = os.environ.get('SECRET_KEY')
cors = CORS(APP)
STOP_URL = "https://www.nctx.co.uk/stops/%s"
WEATHER_KEY = os.environ.get('WEATHER_KEY')
WEATHER_URL = "https://api.openweathermap.org/data/2.5/onecall?lat=%s&lon=%s&appid=%s&units=metric"
TRANSPORT_APP = os.environ.get('TRANSPORT_APP')
TRANSPORT_KEY = os.environ.get('TRANSPORT_KEY')
TRANSPORT_URL = """http://transportapi.com/v3/uk/places.json?
query=%s&type=bus_stop&app_id=%s&app_key=%s"""
@APP.route('/')
def index():
"""
Built from the React UI (see in README)
takes a query string:
?stop='ATCOCODE'
"""
return APP.send_static_file('index.html')
@APP.route('/times/<stopid>')
def times(stopid=None):
"""
GET request returns the next 5
bus numbers, destinations, and times.
Stop IDS available:
https: // www.nctx.co.uk/open-data/network/current
"""
try:
html = get(STOP_URL % (stopid))
soup = Soup(html)
numbers = soup.find(
'div', {'class': 'single-visit__name'}, partial=False)
destinations = soup.find(
'div', {'class': 'single-visit__description'}, partial=False)
due = soup.find(
'div', {'class': 'single-visit__arrival-time__cell'}, partial=True)
buses = []
if len(numbers) == len(destinations) == len(due):
for i, bus in enumerate(numbers):
buses.append(
{'number': numbers[i].text,
'destination': destinations[i].text,
'due': due[i].text})
return jsonify(buses=buses[0:5])
except Exception:
return jsonify(buses=[])
@APP.route('/weather/<stopid>')
def weather(stopid=None):
"""
Uses transport API to get lat/lon
cooridnates for a bus based on its
ATCO code. Then gets the daily forecast
for that location and the current
temperature, wind speed and bearing.
"""
now = datetime.now()
cached = session.get(stopid, {'expiry': now - timedelta(hours=24)})
now = now.replace(tzinfo=None)
expiry = cached.get('expiry').replace(tzinfo=None)
if now > expiry:
new_expiry = now + timedelta(hours=24)
bus_stop_request = requests.get(
TRANSPORT_URL % (stopid, TRANSPORT_APP, TRANSPORT_KEY))
if bus_stop_request.status_code == 200:
stop = bus_stop_request.json().get('member')[0]
lat = stop.get('latitude')
lon = stop.get('longitude')
cached.update({'lat': lat, 'lon': lon, 'expiry': new_expiry})
session.update({stopid: cached})
else:
return "Unable to locate bus stop"
lat = cached.get('lat')
lon = cached.get('lon')
if lat and lon:
weather_request = requests.get(WEATHER_URL % (lat, lon, WEATHER_KEY))
if weather_request.status_code == 200:
forecast = weather_request.json().get(
'current').get('weather')[0].get(
'description'
)
temperature = weather_request.json().get(
'current').get('temp')
wind_speed = weather_request.json().get(
'current').get('wind_speed') * 2.23694
wind_bearing = weather_request.json().get(
'current').get('wind_deg')
return jsonify(forecast=forecast,
temperature=temperature,
windSpeed=f'{wind_speed:0.2f}',
windBearing=wind_bearing)
else:
return "Unable to find weather"
if __name__ == '__main__':
APP.run(host="0.0.0.0", debug=True)