1 #!/usr/bin/env python3 |
|
2 import enum, json |
|
3 from sys import stderr |
|
4 from datetime import date, time, datetime, timedelta |
|
5 from copy import copy |
|
6 from misc import * |
|
7 from geometria import * |
|
8 Suunta = enum.Enum('Suunta', [('Taaksepäin', 0), ('Eteenpäin', 1)]) |
|
9 |
|
10 def muunna_ajovuoro_tunniste(tunniste): |
|
11 return tunniste |
|
12 |
|
13 class Ajovuoro: |
|
14 def __init__(self, tunniste, linja, palvelu, kyltti, suunta): |
|
15 self.tunniste, self.linja, self.palvelu, self.kyltti, self.suunta = tunniste, linja, \ |
|
16 palvelu, kyltti, suunta |
|
17 self.reitti = [] |
|
18 self.nimi = muunna_ajovuoro_tunniste(tunniste) |
|
19 def __repr__(self): |
|
20 return 'ajot[%r]' % self.nimi |
|
21 def pysäkkiReitillä(self, pysäkki): |
|
22 for pysähdys in self.reitti: |
|
23 if pysähdys.pysäkki is pysäkki: |
|
24 return pysähdys |
|
25 else: |
|
26 return None |
|
27 def ajetaan_päivänä(self, päivä): |
|
28 try: |
|
29 return self.palvelu in palvelut_per_päivä[päivä] |
|
30 except KeyError: |
|
31 return False |
|
32 def suppea_reitti(self, pysäkistä = None): |
|
33 if pysäkistä and pysäkistä in self.reitti: |
|
34 reitti = copy(self.reitti) |
|
35 reitti = reitti[reitti.index(pysäkistä):] |
|
36 else: |
|
37 reitti = self.reitti |
|
38 käytetyt_alueet = set() |
|
39 tulos = [] |
|
40 for pysähdys in reitti: |
|
41 pysäkki = pysähdys.pysäkki |
|
42 if pysäkki.alue and pysäkki.alue not in käytetyt_alueet: |
|
43 käytetyt_alueet.add(pysäkki.alue) |
|
44 tulos.append(pysäkki.alue) |
|
45 return tulos |
|
46 |
|
47 class Linja: |
|
48 def __init__(self, tietue): |
|
49 self.tunniste = tietue['route_id'] |
|
50 self.viite = tietue['route_short_name'] |
|
51 self.selite = tietue['route_long_name'] |
|
52 def __repr__(self): |
|
53 return 'linjat[%r]' % self.viite |
|
54 |
|
55 class Palvelu: |
|
56 def __init__(self, tunniste): |
|
57 self.tunniste = tunniste |
|
58 self.päivät = set() |
|
59 def __repr__(self): |
|
60 return 'palvelut[%r]' % self.tunniste |
|
61 |
|
62 class Pysäkki: |
|
63 def __init__(self, tunniste, nimi, sijainti): |
|
64 self.tunniste, self.nimi, self.sijainti = tunniste, nimi, sijainti |
|
65 def __repr__(self): |
|
66 return 'pysäkit[%r]' % self.tunniste |
|
67 def aikataulu(self, määrä = 50): |
|
68 ''' |
|
69 Hakee tämän pysäkin seuraavat `määrä` lähtöä. Päätepysäkille saapuvia busseja ei |
|
70 lasketa. Palauttaa pysähdykset listana jossa alkiot ovat muotoa (aika, pysähdys), |
|
71 jossa: |
|
72 - `aika` on saapumishetki muotoa datetime ja |
|
73 - `pysähdys` on vastaava Pysähdys olio. |
|
74 |
|
75 Mikäli pysäkille ei ole määrätty riittävästi pysähdyksiä kalenterissa, tuloslista |
|
76 jää alimittaiseksi, mahdollisesti jopa tyhjäksi. |
|
77 ''' |
|
78 class PäivätLoppuError(Exception): |
|
79 pass |
|
80 # Hakee pysäkin aikataulut tiettynä päivänä. |
|
81 def aikataulu_päivänä(päivä): |
|
82 # Jos päädyttiin aikataulukalenterin ulkopuolelle, niin tuotetaan virhe. Jos vain |
|
83 # palautettaisiin tyhjä tulos, niin algoritmi jatkaisi etsintää loputtomiin. |
|
84 if päivä > viimeinen_käyttöpäivä: |
|
85 raise PäivätLoppuError() |
|
86 taulu = [] |
|
87 # Jokaiselle ajovuorolle, |
|
88 for ajo in ajot.values(): |
|
89 # jos tämä ajovuoro ajetaan tänä päivänä |
|
90 if ajo.ajetaan_päivänä(päivä): |
|
91 # ja jos tämä ajo pysähtyy tällä pysäkillä, ei kuitenkaan saapuen |
|
92 # päätepysäkille, |
|
93 pysähdys = ajo.pysäkkiReitillä(self) |
|
94 if pysähdys and pysähdys is not ajo.reitti[-1]: |
|
95 # ja jos tämä pysähdys on tulevaisuudessa, |
|
96 aika = datetime.combine(päivä, time()) + pysähdys.saapumisaika |
|
97 if aika >= nyt(): |
|
98 # lisää pysähdys listaan. |
|
99 taulu.append((aika, pysähdys)) |
|
100 # Lajittele lopputulos saapumisajan mukaan. |
|
101 taulu.sort(key = lambda tietue: tietue[0]) |
|
102 return taulu |
|
103 taulu = [] |
|
104 päivä = tänään() |
|
105 # Niin kauan kuin aikatauluja ei ole vielä tarpeeksi, |
|
106 while len(taulu) < määrä: |
|
107 try: |
|
108 # hae nykyisen päivän aikataulut ja lisää ne, |
|
109 taulu += aikataulu_päivänä(päivä) |
|
110 except PäivätLoppuError: |
|
111 # paitsi jos mentiin kalenterin ulkopuolelle, jolloin lopetetaan, |
|
112 break |
|
113 # ja siirry seuraavaan päivään. |
|
114 päivä += timedelta(1) |
|
115 # Typistä lopputulos haluttuun tulosmäärään. |
|
116 return taulu[:määrä] |
|
117 @property |
|
118 def linkki_karttaan(self): |
|
119 return 'http://www.openstreetmap.org/#map=19/%f/%f' % (self.sijainti.leveys, self.sijainti.pituus) |
|
120 |
|
121 class Pysähdys: |
|
122 def __init__(self, saapumisaika, lähtöaika, pysäkki, ajo): |
|
123 self.saapumisaika, self.lähtöaika, self.pysäkki, self.ajo = saapumisaika, lähtöaika, \ |
|
124 pysäkki, ajo |
|
125 def __repr__(self): |
|
126 return 'Pysähdys(%r, %r, %r, %r)' % (self.saapumisaika, self.lähtöaika, self.pysäkki, self.ajo) |
|
127 |
|
128 linjat = {} |
|
129 linjat_per_tunniste = {} |
|
130 ajot = {} |
|
131 ajot_per_numero = {} |
|
132 palvelut = {} |
|
133 pysäkit = {} |
|
134 |
|
135 print('Ladataan linjat... ', file = stderr, end = '', flush = True) |
|
136 with open('gtfs/routes.txt') as tiedosto: |
|
137 for rivi in lue_csv(tiedosto): |
|
138 linja = Linja(rivi) |
|
139 linja.tunniste = linja.tunniste |
|
140 linjat[linja.viite] = linja |
|
141 linjat_per_tunniste[linja.tunniste] = linja |
|
142 print('%d linjaa' % len(linjat), file = stderr) |
|
143 |
|
144 print('Ladataan ajot... ', file = stderr, end = '', flush = True) |
|
145 with open('gtfs/trips.txt') as tiedosto: |
|
146 for rivi in lue_csv(tiedosto, muunnokset = {'direction_id': lambda k: Suunta(int(k))}): |
|
147 if rivi['service_id'] not in palvelut: |
|
148 palvelut[rivi['service_id']] = Palvelu(rivi['service_id']) |
|
149 linja = linjat_per_tunniste[rivi['route_id']] |
|
150 ajo = Ajovuoro(tunniste = rivi['trip_id'], |
|
151 linja = linja, |
|
152 palvelu = palvelut[rivi['service_id']], |
|
153 kyltti = rivi['trip_headsign'], |
|
154 suunta = rivi['direction_id']) |
|
155 assert ajo.nimi not in ajot |
|
156 ajot[ajo.nimi] = ajo |
|
157 print('%d ajoa' % len(ajot), file = stderr) |
|
158 |
|
159 def lue_päiväys(teksti): |
|
160 return date(int(teksti[:4]), int(teksti[4:6]), int(teksti[6:])) |
|
161 |
|
162 def lue_aika(teksti): |
|
163 tunti, minuutti, sekunti = map(int, teksti.split(':')) |
|
164 return timedelta(hours = tunti, minutes = minuutti, seconds = sekunti) |
|
165 |
|
166 print('Ladataan päiväykset... ', file = stderr, flush = True) |
|
167 |
|
168 viimeinen_käyttöpäivä = date.today() |
|
169 palvelut_per_päivä = {} |
|
170 |
|
171 with open('gtfs/calendar_dates.txt') as tiedosto: |
|
172 for rivi in lue_csv(tiedosto): |
|
173 palvelu = palvelut[rivi['service_id']] |
|
174 päivä = lue_päiväys(rivi['date']) |
|
175 palvelu.päivät.add(päivä) |
|
176 if päivä not in palvelut_per_päivä: |
|
177 palvelut_per_päivä[päivä] = set() |
|
178 palvelut_per_päivä[päivä].add(palvelu) |
|
179 viimeinen_käyttöpäivä = max(päivä, viimeinen_käyttöpäivä) |
|
180 |
|
181 def palvelut_käytössä(päivä): |
|
182 for palvelu in palvelut.values(): |
|
183 if päivä in palvelu.päivät: |
|
184 yield palvelu |
|
185 |
|
186 print('Ladataan pysäkit... ', file = stderr, end = '', flush = True) |
|
187 with open('gtfs/stops.txt') as file: |
|
188 for rivi in lue_csv(file): |
|
189 sijainti = Sijainti(float(rivi['stop_lat']), float(rivi['stop_lon'])) |
|
190 pysäkki = Pysäkki(rivi['stop_id'], rivi['stop_name'], sijainti) |
|
191 pysäkit[pysäkki.tunniste] = pysäkki |
|
192 with open('pysäkkialueet.json') as file: |
|
193 for pysäkkitunniste, alue in json.load(file).items(): |
|
194 pysäkit[pysäkkitunniste].alue = alue |
|
195 print('%d pysäkkiä' % len(pysäkit), file = stderr) |
|
196 |
|
197 print('Ladataan aikataulut... ', end = '', flush = True, file = stderr) |
|
198 with open('gtfs/stop_times.txt') as file: |
|
199 rivimäärä = sum(line.count('\n') for line in file) |
|
200 laskettu = 0 |
|
201 file.seek(0) |
|
202 for rivi in lue_csv(file): |
|
203 ajo = ajot[muunna_ajovuoro_tunniste(rivi['trip_id'])] |
|
204 saapumisaika = lue_aika(rivi['arrival_time']) |
|
205 lähtöaika = lue_aika(rivi['departure_time']) |
|
206 pysäkki = pysäkit[rivi['stop_id']] |
|
207 ajo.reitti.append(Pysähdys(saapumisaika, lähtöaika, pysäkki, ajo)) |
|
208 laskettu += 1 |
|
209 if laskettu % 1000 == 0: |
|
210 print('\rLadataan aikataulut... %.1f%%' % (laskettu * 100 / rivimäärä), end = ' ', file = stderr) |
|
211 print('\rLadataan aikataulut... ladattu', file = stderr) |
|