Sat, 10 Jun 2017 16:36:36 +0300
Plöö
0 | 1 | #!/usr/bin/env python3 |
2 | 2 | import enum, json |
3 | from sys import stderr | |
0 | 4 | from datetime import date, time, datetime, timedelta |
5 | 5 | from copy import copy |
2 | 6 | from misc import * |
5 | 7 | from geometria import * |
0 | 8 | Suunta = enum.Enum('Suunta', [('Taaksepäin', 0), ('Eteenpäin', 1)]) |
9 | ||
10 | def muunna_ajovuoro_tunniste(tunniste): | |
4 | 11 | return tunniste |
0 | 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 | |
5 | 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 | |
0 | 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: | |
2 | 63 | def __init__(self, tunniste, nimi, sijainti): |
64 | self.tunniste, self.nimi, self.sijainti = tunniste, nimi, sijainti | |
0 | 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 | |
4 | 97 | if aika >= nyt(): |
0 | 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 = [] | |
4 | 104 | päivä = tänään() |
0 | 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ä] | |
5 | 117 | @property |
118 | def linkki_karttaan(self): | |
119 | return 'http://www.openstreetmap.org/#map=19/%f/%f' % (self.sijainti.leveys, self.sijainti.pituus) | |
0 | 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 | ||
2 | 135 | print('Ladataan linjat... ', file = stderr, end = '', flush = True) |
0 | 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 | |
2 | 142 | print('%d linjaa' % len(linjat), file = stderr) |
0 | 143 | |
2 | 144 | print('Ladataan ajot... ', file = stderr, end = '', flush = True) |
0 | 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 | |
2 | 157 | print('%d ajoa' % len(ajot), file = stderr) |
0 | 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 | ||
2 | 166 | print('Ladataan päiväykset... ', file = stderr, flush = True) |
0 | 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 | ||
2 | 186 | print('Ladataan pysäkit... ', file = stderr, end = '', flush = True) |
0 | 187 | with open('gtfs/stops.txt') as file: |
188 | for rivi in lue_csv(file): | |
5 | 189 | sijainti = Sijainti(float(rivi['stop_lat']), float(rivi['stop_lon'])) |
2 | 190 | pysäkki = Pysäkki(rivi['stop_id'], rivi['stop_name'], sijainti) |
0 | 191 | pysäkit[pysäkki.tunniste] = pysäkki |
2 | 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) | |
0 | 196 | |
2 | 197 | print('Ladataan aikataulut... ', end = '', flush = True, file = stderr) |
0 | 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: | |
2 | 210 | print('\rLadataan aikataulut... %.1f%%' % (laskettu * 100 / rivimäärä), end = ' ', file = stderr) |
4 | 211 | print('\rLadataan aikataulut... ladattu', file = stderr) |