--- a/service.py Tue Sep 25 22:22:10 2018 +0300 +++ b/service.py Thu Dec 06 19:35:38 2018 +0200 @@ -4,45 +4,58 @@ from os import path, listdir, environ from configparser import ConfigParser import locale - app = Flask(__name__) - from misc import * from busroute import reduce_schedule -from busroute import simplify_name import buses - regions = {} -suffix_regions = {'naantalin pikatie', 'helsingin valtatie', 'kansanpuisto'} -# Varmista ettei järjestelmän kieliasetukset sotke muotoiluja def reset_locale(): + ''' + Resets the locale to system so that the system language settings + do not mess with the formatting. + ''' locale.setlocale(locale.LC_ALL, locale.getdefaultlocale()) def activate_locale(language = None): + ''' + Activates either the locale for the provided language, or the default locale. + Returns such an object that resets the locale upon function exit. + ''' language = language or language_for_page() class result: def __enter__(self): - locale.setlocale(locale.LC_ALL, tr('locale', 'other', language = language)) + if language: + locale.setlocale(locale.LC_ALL, tr('locale', 'other', language = language)) def __exit__(self, *args): reset_locale() return result() +def simplify_name(region_name, replace = False): + region = regions.get(region_name) + if region: + if replace and 'replacement' in region: + return simplify_name(region['replacement']) + return tr(region.get('name', region_name), 'region_short_name', default = region.get('short_name', region_name)) + else: + return tr(region_name, 'region_short_name') + reset_locale() # Load translations class Translator: - def __init__(self): - self.languages = {} + def __init__(self, languages = None): + self.languages = languages or dict() def load_language(self, file_path): language_name = path.splitext(path.basename(file_path))[0] ini = ConfigParser() ini.read(path.join(file_path)) self.languages[language_name] = ini - def __call__(self, name, *sections, language = None): + def __call__(self, name, *sections, language = None, default = None): language = language or language_for_page() for section in sections: try: + print('Trying:', repr(language), repr(section), repr(name)) return self.languages[language][section][name] except KeyError: try: @@ -50,25 +63,31 @@ except KeyError: pass else: - return name[:1].upper() + name[1:] + return default or (name[:1].upper() + name[1:]) def load_region(self, region): for key, value in region.items(): if ':' in key: name_type, language = key.split(':', 1) - if (name_type.endswith('name') or name_type == 'genitive') and language: + if name_type.endswith('name') and language != '': section = 'region_' + name_type if section not in self.languages[language]: self.languages[language][section] = {} + print(repr(language), repr(section), repr(region['name']), '=', repr(value)) self.languages[language][section][region['name']] = value def load_regions(self, regions): for region in regions.values(): self.load_region(region) + def __repr__(self): + return 'Translator(languages = ' + repr(self.languages) + ')' tr = Translator() for file in listdir('tr'): tr.load_language(path.join('tr', file)) def language_for_page(): + ''' + Returns the code of which language to use for the page. + ''' from flask import request if request.args.get('untranslated') is not None: return None @@ -80,8 +99,11 @@ return request.accept_languages.best_match(tr.languages) def sign_elements(schedule_entry, format = 'medium'): + ''' + For an entry in a bus stop schedule, find out where the connection is leading to. + Returns a list of places, possibly empty. + ''' from math import ceil - from busroute import simplify_name trip_length = schedule_entry['trip'].length - schedule_entry['stop'].traveled_distance regions = schedule_entry['trip'].concise_schedule(schedule_entry['stop']) return [ @@ -93,32 +115,14 @@ ) ] -def genitive(name): - from busroute import regions - region = regions.get(name) - if region: - return region.get('genitive:fi', simplify_name(name) + 'n') - else: - return simplify_name(name) + 'n' - -def via_fi(via): - if len(via) > 1: - return ', '.join(via[:-1]) + ' ja ' + via[-1] - else: - return via[0] - def sign(schedule_entry, format = 'medium'): + ''' + For an entry in a bus stop schedule, find out where the connection is leading to. + Returns a string. + ''' sign = sign_elements(schedule_entry, format = format) if sign: - #if language_for_page() == 'fi': - # if len(sign) > 1: - # return simplify_name(sign[-1]) + ' ' + via_fi([genitive(place) for place in sign[:-1]]) + ' kautta' - # else: - # return simplify_name(sign[0]) - # sign_representation = ' - '.join(tr(place, 'region_short_name') for place in sign if place not in suffix_regions) - # sign_representation += ''.join(' ' + tr(place, 'suffix-places') for place in sign if place in suffix_regions) - # return sign_representation - return ' - '.join(tr(simplify_name(place), 'region_short_name') for place in sign) + return ' - '.join(tr(simplify_name(place), 'region_name', 'region_short_name') for place in sign) else: return schedule_entry['trip'].schedule[-1].stop.name @@ -196,16 +200,19 @@ bus_stop = bus_stops[reference] except KeyError: abort(404) - for schedule_entry in bus_stop.schedule(max_amount = 100, arrivals = True): + for schedule_entry in bus_stop.schedule(max_amount = 100, max_past = 4, arrivals = True): route_ref = schedule_entry['trip'].route.reference schedule.append({ 'time': time_representation(schedule_entry['time']), + 'timestamp': int(schedule_entry['time'].timestamp()), 'route': route_ref, 'route-splice': split_route_ref(route_ref), 'sign': sign(schedule_entry), 'trip': schedule_entry['stop'].trip.name, 'night': is_night_time(schedule_entry['time']), 'imminent': imminent(schedule_entry), + 'id': str(schedule_entry['stop'].uuid), + 'gone': schedule_entry['gone'], }) return render_template( 'stop.html', @@ -273,11 +280,8 @@ adjusted_time = time - timedelta(hours = 4, minutes = 30) return adjusted_time.weekday() in [4, 5] and is_night_time(time) -#encircled = '\u24b6\u24b7\u24b8\u24b9\u24ba\u24bb\u24bc\u24bd\u24be\u24bf' \ -# '\u24c0\u24c1\u24c2\u24c3\u24c4\u24c5\u24c6\u24c7\u24c8\u24c9\u24ca\u24cb' \ -# '\u24cc\u24cd\u24ce\u24cf' -#encircled = '⒜⒝⒞⒟⒠⒡⒢⒣⒤⒥⒦⒧⒨⒩⒪⒫⒬⒭⒮⒯⒰⒱⒲⒳⒴⒵' -encircled = 'ⓐⓑⓒⓓⓔⓕⓖⓗⓘⓙⓚⓛⓜⓝⓞⓟⓠⓡⓢⓣⓤⓥⓦⓧⓨⓩ' +encircled = '⒜⒝⒞⒟⒠⒡⒢⒣⒤⒥⒦⒧⒨⒩⒪⒫⒬⒭⒮⒯⒰⒱⒲⒳⒴⒵' +#encircled = 'ⓐⓑⓒⓓⓔⓕⓖⓗⓘⓙⓚⓛⓜⓝⓞⓟⓠⓡⓢⓣⓤⓥⓦⓧⓨⓩ' def encircle(char): from string import ascii_uppercase @@ -351,14 +355,12 @@ }) counts_per_variant[route_name] = count all_variants.sort(key = lambda k: k['count']) + def route_limit(route): + return (route in night_routes) and 6 or 20 + rare_variants = {variant['name'] for variant in all_variants if variant['count'] < route_limit(variant['name'])} route_variant_count = len(all_variants) - # Only consider variants so that they cover at least 99% of bus leaves - coverage = 0 - #while coverage / num_leaves < 0.99: - # variant = all_variants.pop() for variant in all_variants: routes_per_destination[variant['destination']].add(variant['name']) - coverage += variant['count'] for key in routes_per_destination: routes_per_destination[key] = sorted(routes_per_destination[key], key = route_key) def route_len(route): @@ -370,25 +372,28 @@ break return length or len(route) from math import inf - def route_limit(route): - return (route in night_routes) and 6 or 20 def route_key(route): from math import log return ( + counts_per_variant.get(route, 0) < route_limit(route), route in night_routes, - counts_per_variant.get(route, 0) < route_limit(route), route_len(route), str(route) ) def routes_key(routes): return min(route_key(route) for route in routes) + # Convert routes per destination to item pairs so that we can split it into rare and non-rare + route_destination_pairs = list() + for regions, routes in routes_per_destination.items(): + common_routes = set(route for route in routes if counts_per_variant[route] >= route_limit(route)) + rare_routes = set(routes) - common_routes + if common_routes: + route_destination_pairs.append((regions, common_routes)) + if rare_routes: + route_destination_pairs.append((regions, rare_routes)) result = [] - rare_variants = {variant['name'] for variant in all_variants if variant['count'] < route_limit(variant['name'])} rare_variant_groups = set() - for regions, routes in sorted( - routes_per_destination.items(), - key = lambda pair: routes_key(pair[1]) - ): + for regions, routes in sorted(route_destination_pairs, key = lambda pair: routes_key(pair[1])): routes_tuple = tuple(condense_route_list(sorted(routes, key = route_key))) result.append(( routes_tuple, @@ -401,7 +406,6 @@ 'all-night-routes': lambda entry, description: all(route in description['night-routes'] for route in entry[0]), 'simple': route_variant_count <= 1, 'description': result, - 'wtf': destinations_per_route, 'variant-map': {k:variant_names[v] for k, v in trip_mapping.items()}, 'rare-variants': rare_variants, 'rare-variant-groups': rare_variant_groups, @@ -445,6 +449,28 @@ 'abbreviation': trip_abbreviation(trip), }) + +def service_start_time(): + from datetime import date, datetime, timedelta + result = datetime.now().replace(hour = 0, minute = 0, second = 0, microsecond = 0) + if datetime.now().hour < 4: + result -= timedelta(1) + return result + +@app.route('/find_halt/<stop_reference>/<blockref>/<int:originalaimeddeparturetime>') +def find_halt(stop_reference, blockref, originalaimeddeparturetime): + from datetime import datetime + from flask import jsonify + info = (blockref, datetime.fromtimestamp(originalaimeddeparturetime) - service_start_time()) + trip = buses.trips_by_vehicle_info[info] + try: + return jsonify({ + 'id': [str(halt.uuid) for halt in buses.trips_by_vehicle_info[info].schedule if halt.stop.reference == stop_reference][0], + }) + except: + abort(404) + + def current_bus_day(): from datetime import date, datetime, timedelta day = date.today() @@ -464,6 +490,7 @@ schedule.append({ 'time_data': schedule_entry['time'], 'time': time_representation(schedule_entry['time']), + 'timestamp': int(schedule_entry['time'].timestamp()), 'route': schedule_entry['trip'].route.reference, 'sign': long_form_sign(schedule_entry, format = 'medium'), 'trip': schedule_entry['stop'].trip.name, @@ -501,73 +528,13 @@ tr = tr, ) -@app.route('/test') -def test(): - from buses import bus_stops - bus_stop = bus_stops['16'] - schedule = [{'imminent': True, - 'index': 0, - 'night': False, - 'route': '2A', - 'sign': {'destination': 'Kohmo', 'via': ['Nummenmäki', 'Kurala']}, - 'time': '1m', - 'trip': '00012501__3798generatedBlock'}, - {'imminent': True, - 'index': 1, - 'night': False, - 'route': '54', - 'sign': {'destination': 'Ylioppilaskylä', 'via': []}, - 'time': '2m', - 'trip': '00014359__5656generatedBlock'}, - {'imminent': True, - 'index': 2, - 'night': False, - 'route': '1', - 'sign': {'destination': 'Lentoasema ✈', 'via': ['Urusvuori']}, - 'time': '3m', - 'trip': '00010281__1281generatedBlock'}, - {'imminent': False, - 'index': 3, - 'night': False, - 'route': '56', - 'sign': {'destination': 'Räntämäki', 'via': ['Nummenmäki', 'Halinen']}, - 'time': '8m', - 'trip': '00014686__5983generatedBlock'}, - {'imminent': False, - 'index': 4, - 'night': False, - 'route': '42', - 'sign': {'destination': 'Varissuo', 'via': ['Kupittaa as', 'Itäharju']}, - 'time': '18:30', - 'trip': '00014010__5307generatedBlock'}, - {'imminent': False, - 'index': 5, - 'night': False, - 'route': '2B', - 'sign': {'destination': 'Littoinen', - 'via': ['Nummenmäki', 'Kurala', 'Kohmo']}, - 'time': '18:35', - 'trip': '00012629__3926generatedBlock'}] - return render_template( - 'stop_display.html', - schedule = schedule, - ref = bus_stop.code, - name = tr(bus_stop.name, 'bus-stops'), - link_to_map = bus_stop.location.link_to_map, - region = bus_stop.region, - location = bus_stop.location, - cluster = bus_stop.cluster.url_name if len(bus_stop.cluster.stops) > 1 else None, - num_imminent_leaves = max(1, sum(schedule_entry['imminent'] for schedule_entry in schedule)), - tr = tr, - ) - def time_representation(time, relative = True): time_difference = time - now() - if relative and timedelta(minutes = -1) < time_difference < timedelta(minutes = 1): - return tr('right-now', 'misc-text') - elif relative and time_difference > timedelta(0) and time_difference < timedelta(minutes = 10): - return '%dm' % round(time_difference.seconds / 60) - elif time.date() == today(): + #if relative and timedelta(minutes = -1) < time_difference < timedelta(minutes = 1): + # return tr('right-now', 'misc-text') + #elif relative and time_difference > timedelta(0) and time_difference < timedelta(minutes = 10): + # return '%dm' % round(time_difference.seconds / 60) + if time.date() == today(): return '%d:%02d' % (time.hour, time.minute) elif time_difference < timedelta(7): with activate_locale(): @@ -621,7 +588,7 @@ else: return make_cluster(cluster) -@app.route('/custom') +@app.route('/cluster') def custom_cluster(): from flask import request from buses import bus_stops, CustomBusStopCluster @@ -923,22 +890,6 @@ tr = tr, ) -@app.route('/') -def index(): - return redirect('stop_cluster/kauppatori') - -@app.route('/pysäkki/<reference>') -def redirect_pysäkki(reference): - return redirect('stop/' + str(reference)) - -@app.route('/pysäkkiryhmä/<reference>') -def redirect_pysäkkiryhmä(reference): - return redirect('stop_cluster/' + str(reference)) - -@app.route('/ajovuoro/<reference>') -def redirect_ajovuoro(reference): - return redirect('trip/' + str(reference)) - @app.route('/static/<path:path>') def static_file(path): return send_from_directory(path.join('static', path))