diff -r 659ab465152e -r f9788970fa46 busroute.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/busroute.py Wed Jul 29 23:45:53 2020 +0300 @@ -0,0 +1,130 @@ +#!/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