bussit.py

changeset 7
f3791dccfd03
parent 6
88cfb916c852
child 8
d12e8a560faa
equal deleted inserted replaced
6:88cfb916c852 7:f3791dccfd03
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)

mercurial