bussit.py

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

mercurial