buses.py

changeset 15
a22cdf28930f
parent 7
f3791dccfd03
child 17
fa3c822859b5
equal deleted inserted replaced
14:b4fbc92cd915 15:a22cdf28930f
60 return 'palvelut[%r]' % self.tunniste 60 return 'palvelut[%r]' % self.tunniste
61 61
62 class Pysäkki: 62 class Pysäkki:
63 def __init__(self, tunniste, nimi, sijainti): 63 def __init__(self, tunniste, nimi, sijainti):
64 self.tunniste, self.nimi, self.sijainti = tunniste, nimi, sijainti 64 self.tunniste, self.nimi, self.sijainti = tunniste, nimi, sijainti
65 self.cluster = None
66 self.pairs = set() # samannimiset lähellä olevat pysäkit
65 def __repr__(self): 67 def __repr__(self):
66 return 'pysäkit[%r]' % self.tunniste 68 return 'pysäkit[%r]' % self.tunniste
67 def aikataulu(self, määrä = 50): 69 def aikataulu(self, määrä = 50):
68 ''' 70 '''
69 Hakee tämän pysäkin seuraavat `määrä` lähtöä. Päätepysäkille saapuvia busseja ei 71 Hakee tämän pysäkin seuraavat `määrä` lähtöä. Päätepysäkille saapuvia busseja ei
129 linjat_per_tunniste = {} 131 linjat_per_tunniste = {}
130 ajot = {} 132 ajot = {}
131 ajot_per_numero = {} 133 ajot_per_numero = {}
132 palvelut = {} 134 palvelut = {}
133 pysäkit = {} 135 pysäkit = {}
136 all_clusters = set()
134 137
135 print('Ladataan linjat... ', file = stderr, end = '', flush = True) 138 print('Ladataan linjat... ', file = stderr, end = '', flush = True)
136 with open('gtfs/routes.txt') as tiedosto: 139 with open('gtfs/routes.txt') as tiedosto:
137 for rivi in lue_csv(tiedosto): 140 for rivi in lue_csv(tiedosto):
138 linja = Linja(rivi) 141 linja = Linja(rivi)
191 pysäkit[pysäkki.tunniste] = pysäkki 194 pysäkit[pysäkki.tunniste] = pysäkki
192 with open('regions-per-stop.json') as file: 195 with open('regions-per-stop.json') as file:
193 for pysäkkitunniste, alue in json.load(file).items(): 196 for pysäkkitunniste, alue in json.load(file).items():
194 pysäkit[pysäkkitunniste].alue = alue 197 pysäkit[pysäkkitunniste].alue = alue
195 print('%d pysäkkiä' % len(pysäkit), file = stderr) 198 print('%d pysäkkiä' % len(pysäkit), file = stderr)
199
200 class BusStopCluster:
201 def __init__(self):
202 self.stops = set()
203 self._center = None
204 self.name = None
205 def add_stop(self, stop):
206 assert not stop.cluster
207 stop.cluster = self
208 self.stops.add(stop)
209 self._center = None
210 @property
211 def center(self):
212 if not self._center:
213 if self.stops:
214 from statistics import median
215 pointtype = type(next(iter(self.stops)).sijainti)
216 self._center = pointtype(
217 median(stop.sijainti.x for stop in self.stops),
218 median(stop.sijainti.y for stop in self.stops),
219 )
220 else:
221 raise ValueError('an empty cluster has no center point')
222 return self._center
223 def merge(self, other):
224 for bus_stop in other.stops:
225 bus_stop.cluster = self
226 self.stops |= other.stops
227 other.stops = set()
228 other._center = None
229
230 from collections import defaultdict
231 bus_stops_by_name = defaultdict(set)
232 for bus_stop in pysäkit.values():
233 bus_stops_by_name[bus_stop.nimi].add(bus_stop)
234 bus_stops_by_name = dict(bus_stops_by_name)
235
236 # ryhmittele pysäkit nimen mukaan
237 all_clusters = []
238 def cluster_bus_stops():
239 sorted_bus_stops = sorted(pysäkit.values(), key = lambda bus_stop: bus_stop.nimi)
240 for bus_stop in sorted_bus_stops:
241 if not bus_stop.cluster:
242 stops_to_cluster = {bus_stop}
243 # etsi pysäkin samannimiset vastaparit
244 for pair_candidate in bus_stops_by_name[bus_stop.nimi]:
245 distance = pair_candidate.sijainti.etäisyys(bus_stop.sijainti)
246 if pair_candidate is not bus_stop and distance <= 0.3:
247 stops_to_cluster.add(pair_candidate)
248 for stop_to_cluster in stops_to_cluster:
249 if stop_to_cluster.cluster:
250 cluster = stop_to_cluster.cluster
251 break
252 else:
253 cluster = BusStopCluster()
254 all_clusters.append(cluster)
255 for stop_to_cluster in stops_to_cluster:
256 if not stop_to_cluster.cluster:
257 cluster.add_stop(stop_to_cluster)
258 # Merkitse muistiin pysäkkien vastaparit käyttäen hyväksi tämänhetkistä ryhmittelytietoa
259 for bus_stop in pysäkit.values():
260 if bus_stop.cluster:
261 bus_stop.pairs = bus_stop.cluster.stops - {bus_stop}
262 # Ryhmitä ne pysäkit, joilla ei ollut omaa vastaparia, muiden pysäkkien kanssa
263 for bus_stop in sorted_bus_stops:
264 if len(bus_stop.cluster.stops) == 1:
265 possibilities = set()
266 for cluster in all_clusters:
267 if cluster is not bus_stop.cluster:
268 distance = cluster.center.etäisyys(bus_stop.sijainti)
269 if distance <= 0.3:
270 possibilities.add((distance, cluster))
271 if possibilities:
272 best = min(possibilities)[1]
273 all_clusters.remove(bus_stop.cluster)
274 best.merge(bus_stop.cluster)
275
276 def shared_elements_in_n_sets(sets):
277 from itertools import combinations
278 result = set()
279 for pair in combinations(sets, 2):
280 result |= pair[0] & pair[1]
281 return result
282
283 def name_clusters():
284 from collections import defaultdict
285 from pprint import pprint
286 clusters_per_name = defaultdict(set)
287 for cluster in all_clusters:
288 name_representing_stop = min((len(pysäkki.tunniste), pysäkki.tunniste, pysäkki) for pysäkki in cluster.stops)[2]
289 name = name_representing_stop.nimi.lower().replace(' ', '-')
290 clusters_per_name[name].add(cluster)
291 for name, clusters in clusters_per_name.items():
292 if len(clusters) == 1:
293 # Ryhmä on ainoa jolla on varaus tälle nimelle. Sen kuin vaan.
294 next(iter(clusters)).name = name
295 else:
296 # Olisiko kaikki klusterit eri alueilla?
297 common_regions = shared_elements_in_n_sets({stop.alue for stop in cluster.stops} for cluster in clusters)
298 # Esitys: ryhmä -> ne alueet jotka ovat tälle ryhmälle ainutlaatuisia
299 proposal = {
300 cluster: {stop.alue for stop in cluster.stops} - common_regions - {None}
301 for cluster in clusters
302 }
303 # Jos enintään yksi klusteri tässä esityksessä on kokonaan ilman omaa aluetta, jolla se voisi eritellä,
304 # niin nimetään klusterit näiden alueiden mukaan.
305 # Se klusteri jolla ei ole omaa aluetta (jos on) jätetään ilman aluepäätettä.
306 if sum([1 for unique_areas in proposal.values() if not unique_areas]) <= 1:
307 for cluster, unique_areas in proposal.items():
308 individual_cluster_name = name
309 if unique_areas:
310 individual_cluster_name += '-' + min(unique_areas).lower().replace(' ', '-')
311 cluster.name = individual_cluster_name
312 else:
313 # Typerä reunatapaus. Indeksoidaan numeroin...
314 for n, (_, cluster) in enumerate(sorted(
315 min((stop.tunniste.lower(), cluster) for stop in cluster.stops)
316 for cluster in clusters
317 ), 1):
318 individual_cluster_name = name + '-' + str(n)
319 cluster.name = individual_cluster_name
320
321 print('Ryhmitellään pysäkit...')
322 cluster_bus_stops()
323 name_clusters()
196 324
197 print('Ladataan aikataulut... ', end = '', flush = True, file = stderr) 325 print('Ladataan aikataulut... ', end = '', flush = True, file = stderr)
198 with open('gtfs/stop_times.txt') as file: 326 with open('gtfs/stop_times.txt') as file:
199 rivimäärä = sum(line.count('\n') for line in file) 327 rivimäärä = sum(line.count('\n') for line in file)
200 laskettu = 0 328 laskettu = 0

mercurial