service.py

changeset 91
209f5930d038
parent 90
36efdea68d03
child 92
16a5c37e4e67
equal deleted inserted replaced
90:36efdea68d03 91:209f5930d038
266 def is_weekend_night(time): 266 def is_weekend_night(time):
267 from datetime import timedelta 267 from datetime import timedelta
268 adjusted_time = time - timedelta(hours = 4, minutes = 30) 268 adjusted_time = time - timedelta(hours = 4, minutes = 30)
269 return adjusted_time.weekday() in [4, 5] and is_night_time(time) 269 return adjusted_time.weekday() in [4, 5] and is_night_time(time)
270 270
271 def describe(bus_stop): 271 encircled = '\u24b6\u24b7\u24b8\u24b9\u24ba\u24bb\u24bc\u24bd\u24be\u24bf' \
272 '\u24c0\u24c1\u24c2\u24c3\u24c4\u24c5\u24c6\u24c7\u24c8\u24c9\u24ca\u24cb' \
273 '\u24cc\u24cd\u24ce\u24cf'
274
275 def encircle(char):
276 from string import ascii_uppercase
277 try:
278 return encircled[ascii_uppercase.index(char.upper())]
279 except IndexError:
280 return char
281
282 def variant_abbreviations(variants):
283 '''Makes a mapping of route destination variants to letter abbreviations'''
284 suggestion = [variant[-1][0].upper() for variant in variants]
285 if len(set(suggestion)) != len(suggestion):
286 from string import ascii_uppercase
287 suggestion = ascii_uppercase
288 return dict(zip(variants, suggestion))
289
290 def schedule_entry_hash(schedule_entry):
291 return schedule_entry['trip'].name, schedule_entry['time']
292
293 def describe(bus_stop, week_schedule):
272 schedule = [] 294 schedule = []
273 from collections import defaultdict, Counter 295 from collections import defaultdict, Counter
274 from busroute import simplify_name 296 from busroute import simplify_name
275 destinations_per_route = defaultdict(Counter) 297 destinations_per_route = defaultdict(Counter)
276 def route_key(route_ref): 298 def route_key(route_ref):
286 data = [] 308 data = []
287 names = [] 309 names = []
288 from collections import defaultdict 310 from collections import defaultdict
289 night_routes = defaultdict(lambda: True) 311 night_routes = defaultdict(lambda: True)
290 num_leaves = 0 312 num_leaves = 0
291 all_routes = Counter() 313 trip_mapping = {}
292 for schedule_entry in week_schedule(bus_stop, arrivals = True): #bus_stop.schedule(max_amount = 500, arrivals = True): 314 for schedule_entry in week_schedule:
315 from busroute import greatly_simplify_name
293 sign_tuple = tuple(sign_elements(schedule_entry, format = 'short')) 316 sign_tuple = tuple(sign_elements(schedule_entry, format = 'short'))
317 sign_tuple = tuple(greatly_simplify_name(k) for k in sign_tuple)
294 route = schedule_entry['trip'].route.reference 318 route = schedule_entry['trip'].route.reference
295 night_routes[route] &= is_weekend_night(schedule_entry['time']) 319 night_routes[route] &= is_weekend_night(schedule_entry['time'])
296 destinations_per_route[route][sign_tuple] += 1 320 destinations_per_route[route][sign_tuple] += 1
297 all_routes[route] += 1 321 trip_mapping[schedule_entry_hash(schedule_entry)] = (route, sign_tuple)
298 num_leaves += 1 322 num_leaves += 1
299 night_routes = {key for key, value in night_routes.items() if value} 323 night_routes = {key for key, value in night_routes.items() if value}
300 routes_per_destination = defaultdict(set) 324 routes_per_destination = defaultdict(set)
301 for route in destinations_per_route: 325 all_variants = []
302 winner, count = destinations_per_route[route].most_common()[0] 326 variant_names = {}
303 if all_routes[route] >= 10 or all_routes[route] / num_leaves >= 0.01: 327 # Collect all variants
304 winner = filter_names(winner) 328 for route, tally in destinations_per_route.items():
305 #destinations_per_route[route] = winner and ' - '.join(winner) or '' 329 variants = variant_abbreviations(tally.keys())
306 routes_per_destination[winner].add(route) 330 winner, count = tally.most_common()[0]
331 for destination, count in tally.items():
332 route_name = route
333 if destination != winner:
334 route_name += encircle(variants[destination])
335 if route in night_routes:
336 night_routes.add(route_name)
337 variant_names[route, destination] = route_name
338 all_variants.append({
339 'name': route_name,
340 'destination': filter_names(destination),
341 'count': count
342 })
343 all_variants.sort(key = lambda k: k['count'])
344 route_variant_count = len(all_variants)
345 # Only consider variants so that they cover at least 99% of bus leaves
346 coverage = 0
347 while all_variants: #coverage / num_leaves < 0.99:
348 variant = all_variants.pop()
349 routes_per_destination[variant['destination']].add(variant['name'])
350 coverage += variant['count']
307 for key in routes_per_destination: 351 for key in routes_per_destination:
308 routes_per_destination[key] = sorted(routes_per_destination[key], key = route_key) 352 routes_per_destination[key] = sorted(routes_per_destination[key], key = route_key)
309 def route_len(route): 353 def route_len(route):
310 length = 0 354 length = 0
311 for char in route: 355 for char in route:
314 else: 358 else:
315 break 359 break
316 return length or len(route) 360 return length or len(route)
317 from math import inf 361 from math import inf
318 def route_key(route): 362 def route_key(route):
319 return (route in night_routes, route_len(route), str(route)) 363 return (
364 route in night_routes,
365 route_len(route),
366 str(route)
367 )
320 def routes_key(routes): 368 def routes_key(routes):
321 return min(route_key(route) for route in routes) 369 return min(route_key(route) for route in routes)
322 result = [] 370 result = []
323 for regions, routes in sorted( 371 for regions, routes in sorted(
324 routes_per_destination.items(), 372 routes_per_destination.items(),
329 ' - '.join(tr(region, 'region_short_name') for region in regions) 377 ' - '.join(tr(region, 'region_short_name') for region in regions)
330 )) 378 ))
331 return { 379 return {
332 'night-routes': night_routes, 380 'night-routes': night_routes,
333 'all-night-routes': lambda entry, description: all(route in description['night-routes'] for route in entry[0]), 381 'all-night-routes': lambda entry, description: all(route in description['night-routes'] for route in entry[0]),
334 'simple': len(all_routes) <= 1, 382 'simple': route_variant_count <= 1,
335 'description': result, 383 'description': result,
336 'wtf': destinations_per_route, 384 'wtf': destinations_per_route,
385 'variant-map': {k:variant_names[v] for k, v in trip_mapping.items()},
337 } 386 }
338 387
339 @app.route('/stop_description/<reference>') 388 @app.route('/stop_description/<reference>')
340 def bus_stop_description(reference): 389 def bus_stop_description(reference):
341 from buses import bus_stops 390 from buses import bus_stops
342 from pprint import pformat 391 from pprint import pformat
343 try: 392 try:
344 bus_stop = bus_stops[reference] 393 bus_stop = bus_stops[reference]
345 except KeyError: 394 except KeyError:
346 abort(404) 395 abort(404)
347 return '<pre>' + pformat(describe(bus_stop)) + '</pre>' 396 return '<pre>' + pformat(describe(bus_stop, week_schedule(bus_stop, arrivals = True))) + '</pre>'
348 397
349 @app.route('/api/describe_destination/<trip_reference>/<stop_reference>/<int:index>') 398 @app.route('/api/describe_destination/<trip_reference>/<stop_reference>/<int:index>')
350 def describe_destination(trip_reference, stop_reference, index): 399 def describe_destination(trip_reference, stop_reference, index):
351 from buses import bus_stops, all_trips 400 from buses import bus_stops, all_trips
352 from busroute import simplify_name 401 from busroute import simplify_name
574 try: 623 try:
575 bus_stop = bus_stops[stop_reference] 624 bus_stop = bus_stops[stop_reference]
576 except KeyError: 625 except KeyError:
577 abort(404) 626 abort(404)
578 week_model = {} 627 week_model = {}
579 for schedule_entry in week_schedule(bus_stop, arrivals = True): 628 bus_stop_schedule = list(week_schedule(bus_stop, arrivals = True))
629 description = describe(bus_stop, bus_stop_schedule)
630 for schedule_entry in bus_stop_schedule:
580 route_ref = schedule_entry['trip'].route.reference 631 route_ref = schedule_entry['trip'].route.reference
581 if route_filter(route_ref) and dest_filter(schedule_entry['trip']): 632 if route_filter(route_ref) and dest_filter(schedule_entry['trip']):
582 time = schedule_entry['time'] 633 time = schedule_entry['time']
583 date = schedule_entry['date'] 634 date = schedule_entry['date']
584 if date not in week_model: 635 if date not in week_model:
586 day_model = week_model[date] 637 day_model = week_model[date]
587 if time.hour not in day_model: 638 if time.hour not in day_model:
588 day_model[time.hour] = [] 639 day_model[time.hour] = []
589 hour_model = day_model[time.hour] 640 hour_model = day_model[time.hour]
590 hour_model.append({ 641 hour_model.append({
591 'route': route_ref, 642 'route': description['variant-map'][schedule_entry_hash(schedule_entry)],
592 'route-splice': split_route_ref(route_ref), 643 'route-splice': split_route_ref(route_ref),
593 'trip': schedule_entry['stop'].trip.name, 644 'trip': schedule_entry['stop'].trip.name,
594 'night': is_night_time(schedule_entry['time']), 645 'night': is_night_time(schedule_entry['time']),
595 'minute': time.minute, 646 'minute': time.minute,
596 }) 647 })
633 'stop_week.html', 684 'stop_week.html',
634 ref = bus_stop.code, 685 ref = bus_stop.code,
635 name = tr(bus_stop.name, 'bus-stops'), 686 name = tr(bus_stop.name, 'bus-stops'),
636 tr = tr, 687 tr = tr,
637 week = week_model, 688 week = week_model,
638 description = describe(bus_stop), 689 description = description,
639 typename = bus_stop.typename, 690 typename = bus_stop.typename,
640 ) 691 )
641 692
642 @app.route('/trip/<trip_reference>') 693 @app.route('/trip/<trip_reference>')
643 def trip(trip_reference): 694 def trip(trip_reference):

mercurial