|
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 |