Fri, 05 Feb 2021 12:16:29 +0200
update
1 | 1 | #!/usr/bin/env python3 |
2 | def via_factor(region_name, *, regions): | |
3 | # how important is it that | |
4 | if region_name in regions: | |
5 | return float(regions[region_name]['via_factor']) | |
6 | else: | |
7 | return 0 | |
8 | ||
9 | def simplify_name(region_name, *, regions, replace = False): | |
10 | # take short_name to account | |
11 | region = regions.get(region_name) | |
12 | if region: | |
13 | return region.get('short_name', region_name) | |
14 | else: | |
15 | return region_name | |
16 | ||
17 | def destinations_list( | |
18 | itinerary, | |
19 | *, trip_length, | |
20 | regions, | |
21 | whole = False, | |
22 | format = 'medium', | |
23 | ): | |
24 | ''' | |
25 | Produces a sign of destinations for the given itinerary. | |
26 | `itinerary`: list of region names passed through | |
27 | `trip_length`: length of the itinerary in meters. | |
28 | `regions`: the regions table, used to decide what is important to show. | |
29 | `whole`: whether or not the starting place is also included. | |
30 | `format` controls what kind of sign to produce: | |
31 | - 'short': at most 2 destinations, with reducing | |
32 | - 'medium': at most 3 destinations, with reducing | |
33 | - 'long': at most 4 destinations, no reducing. | |
34 | Returns a list of region names. | |
35 | e.g. ['Turun keskusta', 'Ihala', 'Kauppakeskus Mylly'] | |
36 | for Föli bus route 220 at the student village. | |
37 | ''' | |
38 | # prefer longer destination signs on longer routes | |
39 | length = ((trip_length / 600) * 3 + len(itinerary) * 2) / 5 | |
40 | # collect regions along the itinerary | |
41 | have_already = set() | |
42 | i = 0 | |
43 | if not itinerary: | |
44 | # not going anywhere? | |
45 | return '' | |
46 | while i < len(itinerary): | |
47 | region = regions.get(itinerary[i]) | |
48 | if not itinerary[i] or itinerary[i] in have_already: | |
49 | del itinerary[i] | |
50 | else: | |
51 | have_already.add(itinerary[i]) | |
52 | i += 1 | |
53 | from_place = itinerary[0] | |
54 | destination = itinerary[-1] | |
55 | route_weights = {} | |
56 | # create weights for all places along the way. Transforming by x^-0.3 | |
57 | # lessens weights for places further out in the itinerary. | |
58 | f = lambda i: i**-0.3 | |
59 | # this factor scales the weights so that they become comparable against | |
60 | # constant values | |
61 | factor = 1 / max(f(i + 1) for i in range(len(itinerary))) | |
62 | while via_factor(itinerary[-1], regions = regions) < 0: | |
63 | del itinerary[-1] | |
64 | if not itinerary: | |
65 | return '' | |
66 | destination = itinerary[-1] | |
67 | for i, stop in enumerate(itinerary): | |
68 | # transform index by: | |
69 | # - our gradually decreasing x^-0.3 curve, | |
70 | # - our normalising factor, | |
71 | # - and the via_factor of the stop | |
72 | route_weights[stop] = f(i + 1) * factor * via_factor(stop, regions = regions) | |
73 | # ensure that the starting region does not make it into the destinations | |
74 | # sign by setting its weight to 0 | |
75 | if from_place in route_weights: | |
76 | route_weights[from_place] = 0 | |
77 | # ensure that the destination does make it to the signpost | |
78 | route_weights[destination] = 1.0e+10 | |
79 | # sort destinations by importance | |
80 | weights = sorted( | |
81 | [ | |
82 | (stop, route_weights[stop], i) | |
83 | for i, stop in enumerate(itinerary) | |
84 | if route_weights[stop] >= 1 | |
85 | ], key = lambda stop: -stop[1]) | |
86 | # now consider what do we want to display: | |
87 | if format == 'long': | |
88 | # long format, just take at most four destinations | |
89 | weights = weights[:4] | |
90 | elif format == 'short': | |
91 | # short format, take at most two destinations | |
92 | weights = weights[:2] | |
93 | # possibly drop the via-region as well | |
94 | try: | |
95 | if weights[1][0] != destination and weights[1][1] < (500 / length ** 1.15): | |
96 | del weights[1] | |
97 | except IndexError: | |
98 | pass | |
99 | elif format == 'medium': | |
100 | # regular format, at most three destinations | |
101 | weights = weights[:3] | |
102 | # if the third sign value is not significant enough, drop it | |
103 | try: | |
104 | if weights[2][0] != destination and weights[2][1] < (725 / length ** 0.8): | |
105 | del weights[2] | |
106 | except IndexError: | |
107 | pass | |
108 | # and repeat for the second sign value | |
109 | try: | |
110 | if weights[1][0] != destination and weights[1][1] < (500 / length ** 1.15): | |
111 | del weights[1] | |
112 | except IndexError: | |
113 | pass | |
114 | else: | |
115 | raise ValueError(str.format('Unknown format {format}', format = repr(format))) | |
116 | # restore the signpost back to itinerary order | |
117 | weights.sort(key = lambda weight_data: weight_data[2]) | |
118 | # form the sign | |
119 | sign = [paino[0] for paino in weights] | |
120 | old_sign = sign.copy() | |
121 | sign = [] | |
122 | for place in old_sign: | |
123 | if place not in sign: | |
124 | sign.append(place) | |
125 | if whole: | |
126 | # whole format, also include the starting point | |
127 | sign = [from_place] + sign | |
128 | if not sign: | |
129 | sign = [destination] | |
130 | return sign |