3 from sys import stderr |
3 from sys import stderr |
4 from datetime import date, time, datetime, timedelta |
4 from datetime import date, time, datetime, timedelta |
5 from copy import copy |
5 from copy import copy |
6 from misc import * |
6 from misc import * |
7 from geometry import * |
7 from geometry import * |
8 Suunta = enum.Enum('Suunta', [('Taaksepäin', 0), ('Eteenpäin', 1)]) |
8 |
9 |
9 def transform_trip_reference(reference): |
10 def muunna_ajovuoro_tunniste(tunniste): |
10 return reference |
11 return tunniste |
11 |
12 |
12 class BusTrip: |
13 class Ajovuoro: |
13 def __init__(self, reference, route, service, length): |
14 def __init__(self, tunniste, linja, palvelu, kyltti, suunta, length): |
14 self.reference, self.route, self.service = reference, route, service |
15 self.tunniste, self.linja, self.palvelu, self.kyltti, self.suunta = tunniste, linja, \ |
|
16 palvelu, kyltti, suunta |
|
17 self.length = length |
15 self.length = length |
18 self.reitti = [] |
16 self.schedule = [] |
19 self.nimi = muunna_ajovuoro_tunniste(tunniste) |
17 self.name = transform_trip_reference(reference) |
20 def __repr__(self): |
18 def __repr__(self): |
21 return 'ajot[%r]' % self.nimi |
19 return 'all_trips[%r]' % self.name |
22 def pysäkkiReitillä(self, pysäkki): |
20 def contains_stop(self, stop): |
23 for pysähdys in self.reitti: |
21 for halt in self.schedule: |
24 if pysähdys.pysäkki is pysäkki: |
22 if halt.stop is stop: |
25 return pysähdys |
23 return halt |
26 else: |
24 else: |
27 return None |
25 return None |
28 def ajetaan_päivänä(self, päivä): |
26 def is_served_at(self, day): |
29 try: |
27 try: |
30 return self.palvelu in palvelut_per_päivä[päivä] |
28 return self.service in services_for_day[day] |
31 except KeyError: |
29 except KeyError: |
32 return False |
30 return False |
33 def suppea_reitti(self, pysäkistä = None): |
31 def concise_schedule(self, starting_stop = None): |
34 if pysäkistä and pysäkistä in self.reitti: |
32 if starting_stop and starting_stop in self.schedule: |
35 reitti = copy(self.reitti) |
33 schedule = copy(self.schedule) |
36 reitti = reitti[reitti.index(pysäkistä):] |
34 schedule = schedule[schedule.index(starting_stop):] |
37 else: |
35 else: |
38 reitti = self.reitti |
36 schedule = self.schedule |
39 käytetyt_alueet = set() |
37 used_areas = set() |
40 tulos = [] |
38 result = [] |
41 for pysähdys in reitti: |
39 for halt in schedule: |
42 pysäkki = pysähdys.pysäkki |
40 stop = halt.stop |
43 if pysäkki.alue and pysäkki.alue not in käytetyt_alueet: |
41 if stop.region and stop.region not in used_areas: |
44 käytetyt_alueet.add(pysäkki.alue) |
42 used_areas.add(stop.region) |
45 tulos.append(pysäkki.alue) |
43 result.append(stop.region) |
46 return tulos |
44 return result |
47 |
45 |
48 class Linja: |
46 class BusRoute: |
49 def __init__(self, tietue): |
47 def __init__(self, entry): |
50 self.tunniste = tietue['route_id'] |
48 self.id = entry['route_id'] |
51 self.viite = tietue['route_short_name'] |
49 self.reference = entry['route_short_name'] |
52 self.selite = tietue['route_long_name'] |
50 def __repr__(self): |
53 def __repr__(self): |
51 return 'routes[%r]' % self.reference |
54 return 'linjat[%r]' % self.viite |
52 |
55 |
53 class BusService: |
56 class Palvelu: |
54 def __init__(self, reference): |
57 def __init__(self, tunniste): |
55 self.reference = reference |
58 self.tunniste = tunniste |
56 self.dates = set() |
59 self.päivät = set() |
57 def __repr__(self): |
60 def __repr__(self): |
58 return 'services[%r]' % self.reference |
61 return 'palvelut[%r]' % self.tunniste |
59 |
62 |
60 class BusStop: |
63 class Pysäkki: |
61 def __init__(self, reference, name, location): |
64 def __init__(self, tunniste, nimi, sijainti): |
62 self.reference, self.name, self.location = reference, name, location |
65 self.tunniste, self.nimi, self.sijainti = tunniste, nimi, sijainti |
|
66 self.cluster = None |
63 self.cluster = None |
67 self.pairs = set() # samannimiset lähellä olevat pysäkit |
64 self.pairs = set() # samannimiset lähellä olevat pysäkit |
68 def __repr__(self): |
65 def __repr__(self): |
69 return 'pysäkit[%r]' % self.tunniste |
66 return 'bus_stops[%r]' % self.reference |
70 def schedule(self, max_amount = 50): |
67 def schedule(self, max_amount = 50): |
71 ''' |
68 ''' |
72 Hakee tämän pysäkin seuraavat `määrä` lähtöä. Päätepysäkille saapuvia busseja ei |
69 Hakee tämän pysäkin seuraavat `määrä` lähtöä. Päätepysäkille saapuvia busseja ei |
73 lasketa. Palauttaa pysähdykset listana jossa alkiot ovat muotoa (aika, pysähdys), |
70 lasketa. Palauttaa pysähdykset listana jossa alkiot ovat muotoa (aika, halt), |
74 jossa: |
71 jossa: |
75 - `aika` on saapumishetki muotoa datetime ja |
72 - `aika` on saapumishetki muotoa datetime ja |
76 - `pysähdys` on vastaava Pysähdys olio. |
73 - `halt` on vastaava BusHalt olio. |
77 |
74 |
78 Mikäli pysäkille ei ole määrätty riittävästi pysähdyksiä kalenterissa, tuloslista |
75 Mikäli pysäkille ei ole määrätty riittävästi pysähdyksiä kalenterissa, tuloslista |
79 jää alimittaiseksi, mahdollisesti jopa tyhjäksi. |
76 jää alimittaiseksi, mahdollisesti jopa tyhjäksi. |
80 ''' |
77 ''' |
81 result = [] |
78 result = [] |
82 # -1 päivää yövuoroja varten |
79 # -1 päivää yövuoroja varten |
83 date = tänään() - timedelta(days = 1) |
80 date = today() - timedelta(days = 1) |
84 # Niin kauan kuin aikatauluja ei ole vielä tarpeeksi, |
81 # Niin kauan kuin aikatauluja ei ole vielä tarpeeksi, |
85 while len(result) < max_amount: |
82 while len(result) < max_amount: |
86 try: |
83 try: |
87 # hae nykyisen päivän aikataulut ja lisää ne, |
84 # hae nykyisen päivän aikataulut ja lisää ne, |
88 result += self.schedule_for_day(date) |
85 result += self.schedule_for_day(date) |
96 def schedule_for_day(self, date): |
93 def schedule_for_day(self, date): |
97 ''' |
94 ''' |
98 Hakee pysäkin aikataulut tiettynä päivänä. |
95 Hakee pysäkin aikataulut tiettynä päivänä. |
99 ''' |
96 ''' |
100 # Jos päädyttiin aikataulukalenterin ulkopuolelle, niin tuotetaan virhe. Jos vain |
97 # Jos päädyttiin aikataulukalenterin ulkopuolelle, niin tuotetaan virhe. Jos vain |
101 # palautettaisiin tyhjä tulos, niin algoritmi jatkaisi etsintää loputtomiin. |
98 # palautettaisiin tyhjä result, niin algoritmi jatkaisi etsintää loputtomiin. |
102 if date > viimeinen_käyttöpäivä: |
99 if date > viimeinen_käyttöpäivä: |
103 raise ValueError('tried to retrieve schedule for date %s which is outside schedule data' % date) |
100 raise ValueError('tried to retrieve schedule for date %s which is outside schedule data' % date) |
104 result = [] |
101 result = [] |
105 # Jokaiselle ajovuorolle, |
102 # Jokaiselle ajovuorolle, |
106 for trip in ajot.values(): |
103 for trip in all_trips.values(): |
107 # jos tämä ajovuoro ajetaan tänä päivänä |
104 # jos tämä ajovuoro ajetaan tänä päivänä |
108 if trip.ajetaan_päivänä(date): |
105 if trip.is_served_at(date): |
109 # ja jos tämä ajo pysähtyy tällä pysäkillä, ei kuitenkaan saapuen |
106 # ja jos tämä trip pysähtyy tällä pysäkillä, ei kuitenkaan saapuen |
110 # päätepysäkille, |
107 # päätepysäkille, |
111 stop = trip.pysäkkiReitillä(self) |
108 stop = trip.contains_stop(self) |
112 if stop and not stop.isArrival: # stop is not trip.reitti[-1]: |
109 if stop and not stop.isArrival: # stop is not trip.schedule[-1]: |
113 # ja jos tämä pysähdys on tulevaisuudessa, |
110 # ja jos tämä halt on tulevaisuudessa, |
114 stop_time = datetime.combine(date, time()) + stop.saapumisaika |
111 stop_time = datetime.combine(date, time()) + stop.arrival_time |
115 if stop_time >= nyt(): |
112 if stop_time >= now(): |
116 # lisää pysähdys listaan. |
113 # lisää halt listaan. |
117 result.append({ |
114 result.append({ |
118 'time': stop_time, |
115 'time': stop_time, |
119 'trip': trip, |
116 'trip': trip, |
120 'stop': stop, |
117 'stop': stop, |
121 }) |
118 }) |
122 # Lajittele lopputulos saapumisajan mukaan. |
119 # Lajittele lopputulos saapumisajan mukaan. |
123 result.sort(key = lambda schedule_entry: schedule_entry['time']) |
120 result.sort(key = lambda schedule_entry: schedule_entry['time']) |
124 return result |
121 return result |
125 |
122 |
126 class Pysähdys: |
123 class BusHalt: |
127 def __init__(self, saapumisaika, lähtöaika, pysäkki, ajo, ajettu_matka): |
124 def __init__(self, arrival_time, departure_time, stop, trip, traveled_distance): |
128 self.saapumisaika, self.lähtöaika, self.pysäkki, self.ajo = saapumisaika, lähtöaika, \ |
125 self.arrival_time, self.departure_time, self.stop, self.trip = arrival_time, departure_time, \ |
129 pysäkki, ajo |
126 stop, trip |
130 self.ajettu_matka = ajettu_matka |
127 self.traveled_distance = traveled_distance |
131 self._isArrival = None |
|
132 @property |
128 @property |
133 def isArrival(self): |
129 def isArrival(self): |
134 if self._isArrival is None: |
130 if not hasattr(self, 'cachedIsArrival'): |
135 iterator = iter(self.ajo.reitti) |
131 iterator = iter(self.trip.schedule) |
136 stop = next(iterator) |
132 stop = next(iterator) |
137 while stop is not self: |
133 while stop is not self: |
138 stop = next(iterator) |
134 stop = next(iterator) |
139 for stop in iterator: |
135 for stop in iterator: |
140 if stop.pysäkki.alue != self.pysäkki.alue: |
136 if stop.stop.region != self.stop.region: |
141 self._isArrival = False |
137 self.cachedIsArrival = False |
142 break |
138 break |
143 else: |
139 else: |
144 self._isArrival = True |
140 self.cachedIsArrival = True |
145 return self._isArrival |
141 return self.cachedIsArrival |
146 def __repr__(self): |
142 def __repr__(self): |
147 return 'Pysähdys(%r, %r, %r, %r)' % (self.saapumisaika, self.lähtöaika, self.pysäkki, self.ajo) |
143 return 'BusHalt(%r, %r, %r, %r)' % (self.arrival_time, self.departure_time, self.stop, self.trip) |
148 |
144 |
149 linjat = {} |
145 routes = {} |
150 linjat_per_tunniste = {} |
146 routes_per_id = {} |
151 ajot = {} |
147 all_trips = {} |
152 ajot_per_numero = {} |
148 services = {} |
153 palvelut = {} |
149 bus_stops = {} |
154 pysäkit = {} |
|
155 all_clusters = set() |
150 all_clusters = set() |
156 |
151 |
157 print('Ladataan linjat... ', file = stderr, end = '', flush = True) |
152 print('Ladataan routes... ', file = stderr, end = '', flush = True) |
158 with open('gtfs/routes.txt') as tiedosto: |
153 with open('gtfs/routes.txt') as file: |
159 for rivi in lue_csv(tiedosto): |
154 for row in read_csv(file): |
160 linja = Linja(rivi) |
155 route = BusRoute(row) |
161 linja.tunniste = linja.tunniste |
156 routes[route.reference] = route |
162 linjat[linja.viite] = linja |
157 routes_per_id[route.id] = route |
163 linjat_per_tunniste[linja.tunniste] = linja |
158 print('%d linjaa' % len(routes), file = stderr) |
164 print('%d linjaa' % len(linjat), file = stderr) |
159 |
165 |
160 print('Ladataan ajovuorot... ', file = stderr, end = '', flush = True) |
166 print('Ladataan ajot... ', file = stderr, end = '', flush = True) |
|
167 |
161 |
168 shape_distances = {} |
162 shape_distances = {} |
169 with open('gtfs/shapes.txt') as file: |
163 with open('gtfs/shapes.txt') as file: |
170 for row in lue_csv(file): |
164 for row in read_csv(file): |
171 shape_distances[row['shape_id']] = max(shape_distances.get(row['shape_id'], 0), float(row['shape_dist_traveled'])) |
165 shape_distances[row['shape_id']] = max(shape_distances.get(row['shape_id'], 0), float(row['shape_dist_traveled'])) |
172 |
166 |
173 with open('gtfs/trips.txt') as tiedosto: |
167 with open('gtfs/trips.txt') as file: |
174 for rivi in lue_csv(tiedosto, muunnokset = {'direction_id': lambda k: Suunta(int(k))}): |
168 for row in read_csv(file): |
175 if rivi['service_id'] not in palvelut: |
169 if row['service_id'] not in services: |
176 palvelut[rivi['service_id']] = Palvelu(rivi['service_id']) |
170 services[row['service_id']] = BusService(row['service_id']) |
177 linja = linjat_per_tunniste[rivi['route_id']] |
171 route = routes_per_id[row['route_id']] |
178 ajo = Ajovuoro( |
172 trip = BusTrip( |
179 tunniste = rivi['trip_id'], |
173 reference = row['trip_id'], |
180 linja = linja, |
174 route = route, |
181 palvelu = palvelut[rivi['service_id']], |
175 service = services[row['service_id']], |
182 kyltti = rivi['trip_headsign'], |
176 length = shape_distances[row['shape_id']] |
183 suunta = rivi['direction_id'], |
|
184 length = shape_distances[rivi['shape_id']] |
|
185 ) |
177 ) |
186 assert ajo.nimi not in ajot |
178 assert trip.name not in all_trips |
187 ajot[ajo.nimi] = ajo |
179 all_trips[trip.name] = trip |
188 print('%d ajoa' % len(ajot), file = stderr) |
180 print('%d ajoa' % len(all_trips), file = stderr) |
189 |
181 |
190 def lue_päiväys(teksti): |
182 def lue_päiväys(teksti): |
191 return date(int(teksti[:4]), int(teksti[4:6]), int(teksti[6:])) |
183 return date(int(teksti[:4]), int(teksti[4:6]), int(teksti[6:])) |
192 |
184 |
193 def lue_aika(teksti): |
185 def read_time(teksti): |
194 tunti, minuutti, sekunti = map(int, teksti.split(':')) |
186 tunti, minuutti, sekunti = map(int, teksti.split(':')) |
195 return timedelta(hours = tunti, minutes = minuutti, seconds = sekunti) |
187 return timedelta(hours = tunti, minutes = minuutti, seconds = sekunti) |
196 |
188 |
197 print('Ladataan päiväykset... ', file = stderr, flush = True) |
189 print('Ladataan päiväykset... ', file = stderr, flush = True) |
198 |
190 |
199 viimeinen_käyttöpäivä = date.today() |
191 viimeinen_käyttöpäivä = date.today() |
200 palvelut_per_päivä = {} |
192 services_for_day = {} |
201 |
193 |
202 with open('gtfs/calendar_dates.txt') as tiedosto: |
194 with open('gtfs/calendar_dates.txt') as file: |
203 for rivi in lue_csv(tiedosto): |
195 for row in read_csv(file): |
204 palvelu = palvelut[rivi['service_id']] |
196 service = services[row['service_id']] |
205 päivä = lue_päiväys(rivi['date']) |
197 day = lue_päiväys(row['date']) |
206 palvelu.päivät.add(päivä) |
198 service.dates.add(day) |
207 if päivä not in palvelut_per_päivä: |
199 if day not in services_for_day: |
208 palvelut_per_päivä[päivä] = set() |
200 services_for_day[day] = set() |
209 palvelut_per_päivä[päivä].add(palvelu) |
201 services_for_day[day].add(service) |
210 viimeinen_käyttöpäivä = max(päivä, viimeinen_käyttöpäivä) |
202 viimeinen_käyttöpäivä = max(day, viimeinen_käyttöpäivä) |
211 |
203 |
212 def palvelut_käytössä(päivä): |
204 def services_available_at(day): |
213 for palvelu in palvelut.values(): |
205 for service in services.values(): |
214 if päivä in palvelu.päivät: |
206 if day in service.dates: |
215 yield palvelu |
207 yield service |
216 |
208 |
217 print('Ladataan pysäkit... ', file = stderr, end = '', flush = True) |
209 print('Ladataan pysäkit... ', file = stderr, end = '', flush = True) |
218 with open('gtfs/stops.txt') as file: |
210 with open('gtfs/stops.txt') as file: |
219 for rivi in lue_csv(file): |
211 for row in read_csv(file): |
220 sijainti = Sijainti(float(rivi['stop_lat']), float(rivi['stop_lon'])) |
212 location = Sijainti(float(row['stop_lat']), float(row['stop_lon'])) |
221 pysäkki = Pysäkki(rivi['stop_id'], rivi['stop_name'], sijainti) |
213 stop = BusStop(row['stop_id'], row['stop_name'], location) |
222 pysäkit[pysäkki.tunniste] = pysäkki |
214 bus_stops[stop.reference] = stop |
223 with open('regions-per-stop.json') as file: |
215 with open('regions-per-stop.json') as file: |
224 for pysäkkitunniste, alue in json.load(file).items(): |
216 for stop_reference, region in json.load(file).items(): |
225 pysäkit[pysäkkitunniste].alue = alue |
217 bus_stops[stop_reference].region = region |
226 print('%d pysäkkiä' % len(pysäkit), file = stderr) |
218 print('%d pysäkkiä' % len(bus_stops), file = stderr) |
227 |
219 |
228 |
220 |
229 class BusStopCluster: |
221 class BusStopCluster: |
230 def __init__(self): |
222 def __init__(self): |
231 self.stops = set() |
223 self.stops = set() |