service.py

changeset 87
9139a94e540c
parent 85
62e753b7d3ff
child 88
3b86597c5a88
equal deleted inserted replaced
86:302f840d6a65 87:9139a94e540c
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 }
122 starting_halt = None 127 starting_halt = None
123 while True: 128 while True:
124 remaining_length = trip.length 129 remaining_length = trip.length
125 if starting_halt: 130 if starting_halt:
126 remaining_length -= starting_halt.traveled_distance 131 remaining_length -= starting_halt.traveled_distance
127 places = reduce_schedule(trip.concise_schedule(starting_stop = starting_halt), trip_length = remaining_length, long = False) 132 places = reduce_schedule(trip.concise_schedule(starting_stop = starting_halt), trip_length = remaining_length, format = 'medium')
128 new_places = set(places) - set(entries) 133 new_places = set(places) - set(entries)
129 if not new_places or places == old_places: 134 if not new_places or places == old_places:
130 break 135 break
131 for place in places: 136 for place in places:
132 if place in new_places: 137 if place in new_places:
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
292 for i, schedule_entry in enumerate(bus_stop.schedule_for_day(current_bus_day(), arrivals = False)): 351 for i, schedule_entry in enumerate(bus_stop.schedule_for_day(current_bus_day(), arrivals = False)):
293 schedule.append({ 352 schedule.append({
294 'time_data': schedule_entry['time'], 353 'time_data': schedule_entry['time'],
295 'time': time_representation(schedule_entry['time']), 354 'time': time_representation(schedule_entry['time']),
296 'route': schedule_entry['trip'].route.reference, 355 'route': schedule_entry['trip'].route.reference,
297 'sign': long_form_sign(schedule_entry, long = False), 356 'sign': long_form_sign(schedule_entry, format = 'medium'),
298 'trip': schedule_entry['stop'].trip.name, 357 'trip': schedule_entry['stop'].trip.name,
299 'night': is_night_time(schedule_entry['time']), 358 'night': is_night_time(schedule_entry['time']),
300 'imminent': imminent(schedule_entry), 359 'imminent': imminent(schedule_entry),
301 'index': i, 360 'index': i,
302 }) 361 })
532 'stop_week.html', 591 'stop_week.html',
533 ref = bus_stop.code, 592 ref = bus_stop.code,
534 name = tr(bus_stop.name, 'bus-stops'), 593 name = tr(bus_stop.name, 'bus-stops'),
535 tr = tr, 594 tr = tr,
536 week = week_model, 595 week = week_model,
596 description = describe(bus_stop),
537 ) 597 )
538 598
539 @app.route('/trip/<trip_reference>') 599 @app.route('/trip/<trip_reference>')
540 def trip(trip_reference): 600 def trip(trip_reference):
541 from flask import request 601 from flask import request

mercurial