1 #!/usr/bin/env python3 |
1 #!/usr/bin/env python3 |
2 from flask import Flask, render_template, abort, send_from_directory, redirect |
2 from flask import Flask, render_template, abort, send_from_directory, redirect |
3 from datetime import datetime, date, time, timedelta |
3 from datetime import datetime, date, time, timedelta |
4 from os import path, listdir |
4 from os import path, listdir, environ |
5 from configparser import ConfigParser |
5 from configparser import ConfigParser |
6 import locale |
6 import locale |
|
7 |
|
8 app = Flask(__name__) |
7 |
9 |
8 from misc import * |
10 from misc import * |
9 from busroute import reduce_schedule |
11 from busroute import reduce_schedule |
10 import buses |
12 import buses |
11 |
13 |
12 app = Flask(__name__) |
|
13 suffix_regions = {'naantalin pikatie', 'helsingin valtatie', 'kansanpuisto'} |
14 suffix_regions = {'naantalin pikatie', 'helsingin valtatie', 'kansanpuisto'} |
14 |
15 |
15 # Varmista ettei järjestelmän kieliasetukset sotke muotoiluja |
16 # Varmista ettei järjestelmän kieliasetukset sotke muotoiluja |
16 def reset_locale(): |
17 def reset_locale(): |
17 locale.setlocale(locale.LC_ALL, locale.getdefaultlocale()) |
18 locale.setlocale(locale.LC_ALL, locale.getdefaultlocale()) |
62 if request.args.get(language_name) is not None: |
63 if request.args.get(language_name) is not None: |
63 return language_name |
64 return language_name |
64 else: |
65 else: |
65 return request.accept_languages.best_match(tr.languages) |
66 return request.accept_languages.best_match(tr.languages) |
66 |
67 |
67 def sign_elements(schedule_entry, long = False): |
68 def sign_elements(schedule_entry, format = 'medium'): |
68 from math import ceil |
69 from math import ceil |
|
70 from busroute import greatly_simplify_name |
69 trip_length = schedule_entry['trip'].length - schedule_entry['stop'].traveled_distance |
71 trip_length = schedule_entry['trip'].length - schedule_entry['stop'].traveled_distance |
|
72 regions = schedule_entry['trip'].concise_schedule(schedule_entry['stop']) |
|
73 if format == 'short': |
|
74 regions = [greatly_simplify_name(region) for region in regions] |
70 return reduce_schedule( |
75 return reduce_schedule( |
71 schedule_entry['trip'].concise_schedule(schedule_entry['stop']), |
76 regions, |
72 trip_length = trip_length, |
77 trip_length = trip_length, |
73 long = long) |
78 format = format) |
74 |
79 |
75 def sign(schedule_entry, long = False): |
80 def sign(schedule_entry, format = 'medium'): |
76 sign = sign_elements(schedule_entry, long = long) |
81 sign = sign_elements(schedule_entry, format = format) |
77 if sign: |
82 if sign: |
78 sign_representation = ' - '.join(tr(place, 'places') for place in sign if place not in suffix_regions) |
83 sign_representation = ' - '.join(tr(place, 'places') for place in sign if place not in suffix_regions) |
79 sign_representation += ''.join(' ' + tr(place, 'suffix-places') for place in sign if place in suffix_regions) |
84 sign_representation += ''.join(' ' + tr(place, 'suffix-places') for place in sign if place in suffix_regions) |
80 return sign_representation |
85 return sign_representation |
81 else: |
86 else: |
82 return schedule_entry['trip'].schedule[-1].stop.name |
87 return schedule_entry['trip'].schedule[-1].stop.name |
83 |
88 |
84 def long_form_sign(schedule_entry, long = True): |
89 def long_form_sign(schedule_entry, format = 'long'): |
85 from math import ceil |
90 from math import ceil |
86 trip_length = schedule_entry['trip'].length - schedule_entry['stop'].traveled_distance |
91 trip_length = schedule_entry['trip'].length - schedule_entry['stop'].traveled_distance |
87 sign = reduce_schedule(schedule_entry['trip'].concise_schedule(schedule_entry['stop']), trip_length = trip_length, long = long) |
92 sign = reduce_schedule(schedule_entry['trip'].concise_schedule(schedule_entry['stop']), trip_length = trip_length, format = format) |
88 if sign: |
93 if sign: |
89 return { |
94 return { |
90 'destination': tr(sign[-1], 'places'), |
95 'destination': tr(sign[-1], 'places'), |
91 'via': [tr(place, 'places') for place in sign[:-1]], |
96 'via': [tr(place, 'places') for place in sign[:-1]], |
92 } |
97 } |
192 try: |
197 try: |
193 return match.group(1), int(match.group(2)), match.group(3) |
198 return match.group(1), int(match.group(2)), match.group(3) |
194 except AttributeError: |
199 except AttributeError: |
195 raise ValueError(route_ref) |
200 raise ValueError(route_ref) |
196 |
201 |
197 @app.route('/stop_description/<reference>') |
202 def condense_route_list(route_list): |
198 def bus_stop_description(reference): |
203 def prepare_range_pool(range_pool): |
199 from buses import bus_stops |
204 if len(range_pool) < 3: |
|
205 yield from map(str, range_pool) |
|
206 else: |
|
207 yield str(min(range_pool)) + '-' + str(max(range_pool)) |
|
208 range_pool = [] |
|
209 for route in route_list: |
|
210 try: |
|
211 route_int = int(route) |
|
212 except ValueError: |
|
213 yield from prepare_range_pool(range_pool) |
|
214 range_pool = [] |
|
215 yield route |
|
216 else: |
|
217 if not range_pool or route_int - 1 in range_pool: |
|
218 range_pool.append(route_int) |
|
219 else: |
|
220 yield from prepare_range_pool(range_pool) |
|
221 range_pool = [route_int] |
|
222 if range_pool: |
|
223 yield from prepare_range_pool(range_pool) |
|
224 |
|
225 def is_weekend_night(time): |
|
226 from datetime import timedelta |
|
227 adjusted_time = time - timedelta(hours = 4, minutes = 30) |
|
228 return adjusted_time.weekday() in [4, 5] and is_night_time(time) |
|
229 |
|
230 def describe(bus_stop): |
200 schedule = [] |
231 schedule = [] |
201 try: |
|
202 bus_stop = bus_stops[reference] |
|
203 except KeyError: |
|
204 abort(404) |
|
205 from collections import defaultdict, Counter |
232 from collections import defaultdict, Counter |
206 from busroute import simplify_name |
233 from busroute import greatly_simplify_name |
207 destinations_per_route = defaultdict(Counter) |
234 destinations_per_route = defaultdict(Counter) |
208 def route_key(route_ref): |
235 def route_key(route_ref): |
209 try: |
236 try: |
210 return parse_route_ref(route_ref) |
237 return parse_route_ref(route_ref) |
211 except ValueError: |
238 except ValueError: |
212 return () |
239 return () |
213 def filter_names(names): |
240 def filter_names(names): |
214 if len(names) == 1 and names[0] == (bus_stop.region and simplify_name(bus_stop.region)): |
241 if len(names) == 1 and names[0] == (bus_stop.region and greatly_simplify_name(bus_stop.region)): |
215 return type(names)() |
242 return type(names)() |
216 else: |
243 else: |
217 return names |
244 return names |
218 data = [] |
245 data = [] |
219 names = [] |
246 names = [] |
220 from collections import defaultdict |
247 from collections import defaultdict |
221 night_routes = defaultdict(lambda: True) |
248 night_routes = defaultdict(lambda: True) |
222 num_leaves = 0 |
249 num_leaves = 0 |
|
250 all_routes = Counter() |
223 for schedule_entry in week_schedule(bus_stop, arrivals = True): #bus_stop.schedule(max_amount = 500, arrivals = True): |
251 for schedule_entry in week_schedule(bus_stop, arrivals = True): #bus_stop.schedule(max_amount = 500, arrivals = True): |
224 sign_tuple = tuple(sign_elements(schedule_entry)) |
252 sign_tuple = tuple(sign_elements(schedule_entry, format = 'short')) |
225 night_routes[schedule_entry['trip'].route.reference] &= is_night_time(schedule_entry['time']) |
253 route = schedule_entry['trip'].route.reference |
226 #for entry in sign_tuple: |
254 night_routes[route] &= is_weekend_night(schedule_entry['time']) |
227 # if entry not in names: |
255 destinations_per_route[route][sign_tuple] += 1 |
228 # names.append(entry) |
256 all_routes[route] += 1 |
229 #sign_tuple = tuple(names.index(place) for place in sign_tuple) |
|
230 destinations_per_route[schedule_entry['trip'].route.reference][sign_tuple] += 1 |
|
231 num_leaves += 1 |
257 num_leaves += 1 |
232 night_routes = {key for key, value in night_routes.items() if value} |
258 night_routes = {key for key, value in night_routes.items() if value} |
233 routes_per_destination = defaultdict(set) |
259 routes_per_destination = defaultdict(set) |
234 for route in destinations_per_route: |
260 for route in destinations_per_route: |
235 winner, count = destinations_per_route[route].most_common()[0] |
261 winner, count = destinations_per_route[route].most_common()[0] |
236 if count >= 10 or count / num_leaves >= 0.01: |
262 if all_routes[route] >= 10 or all_routes[route] / num_leaves >= 0.01: |
237 winner = filter_names(winner) |
263 winner = filter_names(winner) |
238 #destinations_per_route[route] = winner and ' - '.join(winner) or '' |
264 #destinations_per_route[route] = winner and ' - '.join(winner) or '' |
239 routes_per_destination[winner].add(route) |
265 routes_per_destination[winner].add(route) |
240 for key in routes_per_destination: |
266 for key in routes_per_destination: |
241 routes_per_destination[key] = sorted(routes_per_destination[key], key = route_key) |
267 routes_per_destination[key] = sorted(routes_per_destination[key], key = route_key) |
|
268 def route_len(route): |
|
269 length = 0 |
|
270 for char in route: |
|
271 if char.isdigit(): |
|
272 length += 1 |
|
273 else: |
|
274 break |
|
275 return length or len(route) |
|
276 from math import inf |
|
277 def route_key(route): |
|
278 return (route in night_routes, route_len(route), str(route)) |
|
279 def routes_key(routes): |
|
280 return min(route_key(route) for route in routes) |
|
281 result = [] |
|
282 for regions, routes in sorted( |
|
283 routes_per_destination.items(), |
|
284 key = lambda pair: routes_key(pair[1]) |
|
285 ): |
|
286 result.append(( |
|
287 list(condense_route_list(sorted(routes, key = route_key))), |
|
288 ' - '.join(tr(region, 'regions') for region in regions) |
|
289 )) |
|
290 return { |
|
291 'night-routes': night_routes, |
|
292 'all-night-routes': lambda entry, description: all(route in description['night-routes'] for route in entry[0]), |
|
293 'simple': len(all_routes) <= 1, |
|
294 'description': result, |
|
295 'wtf': destinations_per_route, |
|
296 } |
|
297 |
|
298 @app.route('/stop_description/<reference>') |
|
299 def bus_stop_description(reference): |
|
300 from buses import bus_stops |
242 from pprint import pformat |
301 from pprint import pformat |
243 return '<pre>' + \ |
302 try: |
244 'names: ' + str(names) + '\n '+ \ |
303 bus_stop = bus_stops[reference] |
245 'night_routes: ' + str(night_routes) + '\n '+ \ |
304 except KeyError: |
246 'destinations_per_route: ' + pformat(dict(destinations_per_route)) + '\n' + \ |
305 abort(404) |
247 'routes_per_destination: ' + pformat(dict(routes_per_destination)) + '</pre>' |
306 return '<pre>' + pformat(describe(bus_stop)) + '</pre>' |
248 |
307 |
249 @app.route('/api/describe_destination/<trip_reference>/<stop_reference>/<int:index>') |
308 @app.route('/api/describe_destination/<trip_reference>/<stop_reference>/<int:index>') |
250 def describe_destination(trip_reference, stop_reference, index): |
309 def describe_destination(trip_reference, stop_reference, index): |
251 from buses import bus_stops, all_trips |
310 from buses import bus_stops, all_trips |
252 from busroute import simplify_name |
311 from busroute import simplify_name |