service.py

changeset 127
2bc0529d44a5
parent 114
b736478416d4
child 134
4ac0f2e2ec4e
--- 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))

mercurial