busroute.py

Thu, 30 Jul 2020 21:52:31 +0300

author
Teemu Piippo <teemu@hecknology.net>
date
Thu, 30 Jul 2020 21:52:31 +0300
changeset 2
7378b802ddf8
parent 1
f9788970fa46
permissions
-rw-r--r--

destination processing

#!/usr/bin/env python3
def via_factor(region_name, *, regions):
	# how important is it that 
	if region_name in regions:
		return float(regions[region_name]['via_factor'])
	else:
		return 0

def simplify_name(region_name, *, regions, replace = False):
	# take short_name to account
	region = regions.get(region_name)
	if region:
		return region.get('short_name', region_name)
	else:
		return region_name

def destinations_list(
	itinerary,
	*, trip_length,
	regions,
	whole = False,
	format = 'medium',
):
	'''
		Produces a sign of destinations for the given itinerary.
		`itinerary`: list of region names passed through
		`trip_length`: length of the itinerary in meters.
		`regions`: the regions table, used to decide what is important to show.
		`whole`: whether or not the starting place is also included.
		`format` controls what kind of sign to produce:
			- 'short': at most 2 destinations, with reducing
			- 'medium': at most 3 destinations, with reducing
			- 'long': at most 4 destinations, no reducing.
		Returns a list of region names.
		e.g. ['Turun keskusta', 'Ihala', 'Kauppakeskus Mylly']
		for Föli bus route 220 at the student village.
	'''
	# prefer longer destination signs on longer routes
	length = ((trip_length / 600) * 3 + len(itinerary) * 2) / 5
	# collect regions along the itinerary
	have_already = set()
	i = 0
	if not itinerary:
		# not going anywhere?
		return ''
	while i < len(itinerary):
		region = regions.get(itinerary[i])
		if not itinerary[i] or itinerary[i] in have_already:
			del itinerary[i]
		else:
			have_already.add(itinerary[i])
			i += 1
	from_place = itinerary[0]
	destination = itinerary[-1]
	route_weights = {}
	# create weights for all places along the way. Transforming by x^-0.3
	# lessens weights for places further out in the itinerary.
	f = lambda i: i**-0.3
	# this factor scales the weights so that they become comparable against
	# constant values
	factor = 1 / max(f(i + 1) for i in range(len(itinerary)))
	while via_factor(itinerary[-1], regions = regions) < 0:
		del itinerary[-1]
		if not itinerary:
			return ''
		destination = itinerary[-1]
	for i, stop in enumerate(itinerary):
		# transform index by:
		# - our gradually decreasing x^-0.3 curve,
		# - our normalising factor,
		# - and the via_factor of the stop
		route_weights[stop] = f(i + 1) * factor * via_factor(stop, regions = regions)
	# ensure that the starting region does not make it into the destinations
	# sign by setting its weight to 0
	if from_place in route_weights:
		route_weights[from_place] = 0
	# ensure that the destination does make it to the signpost
	route_weights[destination] = 1.0e+10
	# sort destinations by importance
	weights = sorted(
		[
			(stop, route_weights[stop], i)
			for i, stop in enumerate(itinerary)
			if route_weights[stop] >= 1
		], key = lambda stop: -stop[1])
	# now consider what do we want to display:
	if format == 'long':
		# long format, just take at most four destinations
		weights = weights[:4]
	elif format == 'short':
		# short format, take at most two destinations
		weights = weights[:2]
		# possibly drop the via-region as well
		try:
			if weights[1][0] != destination and weights[1][1] < (500 / length ** 1.15):
				del weights[1]
		except IndexError:
			pass
	elif format == 'medium':
		# regular format, at most three destinations
		weights = weights[:3]
		# if the third sign value is not significant enough, drop it
		try:
			if weights[2][0] != destination and weights[2][1] < (725 / length ** 0.8):
				del weights[2]
		except IndexError:
			pass
		# and repeat for the second sign value
		try:
			if weights[1][0] != destination and weights[1][1] < (500 / length ** 1.15):
				del weights[1]
		except IndexError:
			pass
	else:
		raise ValueError(str.format('Unknown format {format}', format = repr(format)))
	# restore the signpost back to itinerary order
	weights.sort(key = lambda weight_data: weight_data[2])
	# form the sign
	sign = [paino[0] for paino in weights]
	old_sign = sign.copy()
	sign = []
	for place in old_sign:
		if place not in sign:
			sign.append(place)
	if whole:
		# whole format, also include the starting point
		sign = [from_place] + sign
	if not sign:
		sign = [destination]
	return sign

mercurial