152 routes_per_id = {} |
152 routes_per_id = {} |
153 all_trips = {} |
153 all_trips = {} |
154 services = {} |
154 services = {} |
155 bus_stops = {} |
155 bus_stops = {} |
156 all_clusters = set() |
156 all_clusters = set() |
157 |
157 viimeinen_käyttöpäivä = None |
158 print('Ladataan routes... ', file = stderr, end = '', flush = True) |
158 clusters_by_name = {} |
159 with open('gtfs/routes.txt') as file: |
|
160 for row in read_csv(file): |
|
161 route = BusRoute(row) |
|
162 routes[route.reference] = route |
|
163 routes_per_id[route.id] = route |
|
164 print('%d linjaa' % len(routes), file = stderr) |
|
165 |
|
166 print('Ladataan ajovuorot... ', file = stderr, end = '', flush = True) |
|
167 |
|
168 shape_distances = {} |
|
169 with open('gtfs/shapes.txt') as file: |
|
170 for row in read_csv(file): |
|
171 shape_distances[row['shape_id']] = max(shape_distances.get(row['shape_id'], 0), float(row['shape_dist_traveled'])) |
|
172 |
|
173 with open('gtfs/trips.txt') as file: |
|
174 for row in read_csv(file): |
|
175 if row['service_id'] not in services: |
|
176 services[row['service_id']] = BusService(row['service_id']) |
|
177 route = routes_per_id[row['route_id']] |
|
178 trip = BusTrip( |
|
179 reference = row['trip_id'], |
|
180 route = route, |
|
181 service = services[row['service_id']], |
|
182 length = shape_distances[row['shape_id']] |
|
183 ) |
|
184 route.trips.add(trip) |
|
185 assert trip.name not in all_trips |
|
186 all_trips[trip.name] = trip |
|
187 print('%d ajoa' % len(all_trips), file = stderr) |
|
188 |
|
189 def read_date(teksti): |
|
190 return date(int(teksti[:4]), int(teksti[4:6]), int(teksti[6:])) |
|
191 |
|
192 def read_time(teksti): |
|
193 tunti, minuutti, sekunti = map(int, teksti.split(':')) |
|
194 return timedelta(hours = tunti, minutes = minuutti, seconds = sekunti) |
|
195 |
|
196 print('Ladataan päiväykset... ', file = stderr, flush = True) |
|
197 |
|
198 viimeinen_käyttöpäivä = date.today() |
|
199 services_for_day = {} |
159 services_for_day = {} |
200 |
160 |
201 def date_range(start_date, end_date, *, include_end = False): |
161 def load_buses(gtfs_zip_path, profile): |
202 ''' Generates date from start_date to end_date. If include_end is True, then end_date will be yielded. ''' |
162 global viimeinen_käyttöpäivä |
203 current_date = start_date |
163 from zipfile import ZipFile |
204 while current_date < end_date: |
164 with ZipFile(gtfs_zip_path) as gtfs_zip: |
205 yield current_date |
165 print('Ladataan linjat... ', file = stderr, end = '', flush = True) |
206 current_date += timedelta(1) |
166 with gtfs_zip.open('routes.txt') as file: |
207 if include_end: |
167 for row in read_csv(map(bytes.decode, file)): |
208 yield end_date |
168 route = BusRoute(row) |
209 |
169 routes[route.reference] = route |
210 def add_day_to_service(service_name, day): |
170 routes_per_id[route.id] = route |
211 try: |
171 print('%d linjaa' % len(routes), file = stderr) |
212 service = services[service_name] |
172 |
213 except KeyError: |
173 print('Ladataan ajovuorot... ', file = stderr, end = '', flush = True) |
214 return |
174 |
215 else: |
175 shape_distances = {} |
216 service.dates.add(day) |
176 with gtfs_zip.open('shapes.txt') as file: |
217 if day not in services_for_day: |
177 for row in read_csv(map(bytes.decode, file)): |
218 services_for_day[day] = set() |
178 shape_distances[row['shape_id']] = max(shape_distances.get(row['shape_id'], 0), float(row['shape_dist_traveled'])) |
219 services_for_day[day].add(service) |
179 |
220 global viimeinen_käyttöpäivä |
180 with gtfs_zip.open('trips.txt') as file: |
221 viimeinen_käyttöpäivä = max(day, viimeinen_käyttöpäivä) |
181 for row in read_csv(map(bytes.decode, file)): |
222 |
182 if row['service_id'] not in services: |
223 def filter_day(row, day): |
183 services[row['service_id']] = BusService(row['service_id']) |
224 day_names = ['monday', 'tuesday', 'wednesday', 'thursday', 'friday', 'saturday', 'sunday'] |
184 route = routes_per_id[row['route_id']] |
225 return int(row[day_names[day.isoweekday() - 1]]) |
185 trip = BusTrip( |
226 |
186 reference = row['trip_id'], |
227 with open('gtfs/calendar.txt') as file: |
187 route = route, |
228 for row in read_csv(file): |
188 service = services[row['service_id']], |
229 for day in date_range(read_date(row['start_date']), read_date(row['end_date']), include_end = True): |
189 length = shape_distances[row['shape_id']] * float(profile['metrics']['shape-modifier']) |
230 if filter_day(row, day): |
|
231 add_day_to_service(service_name = row['service_id'], day = day) |
|
232 |
|
233 with open('gtfs/calendar_dates.txt') as file: |
|
234 for row in read_csv(file): |
|
235 add_day_to_service(service_name = row['service_id'], day = read_date(row['date'])) |
|
236 |
|
237 def services_available_at(day): |
|
238 for service in services.values(): |
|
239 if day in service.dates: |
|
240 yield service |
|
241 |
|
242 print('Ladataan pysäkit... ', file = stderr, end = '', flush = True) |
|
243 with open('gtfs/stops.txt') as file: |
|
244 for row in read_csv(file): |
|
245 location = Sijainti(float(row['stop_lat']), float(row['stop_lon'])) |
|
246 stop = BusStop( |
|
247 reference = row['stop_id'], |
|
248 name = row['stop_name'], |
|
249 location = location, |
|
250 code = row['stop_code'], |
|
251 ) |
|
252 bus_stops[stop.reference] = stop |
|
253 with open('regions-per-stop.json') as file: |
|
254 for stop_reference, region in json.load(file).items(): |
|
255 bus_stops[stop_reference].region = region |
|
256 print('%d pysäkkiä' % len(bus_stops), file = stderr) |
|
257 |
|
258 |
|
259 class BusStopCluster: |
|
260 def __init__(self): |
|
261 self.stops = set() |
|
262 self._center = None |
|
263 self.name = None |
|
264 @property |
|
265 def url_name(self): |
|
266 return self.name.lower().replace('(', '').replace(')', '').replace(' ', '-') |
|
267 def add_stop(self, stop): |
|
268 assert not stop.cluster |
|
269 stop.cluster = self |
|
270 self.stops.add(stop) |
|
271 self._center = None |
|
272 @property |
|
273 def center(self): |
|
274 if not self._center: |
|
275 if self.stops: |
|
276 from statistics import median |
|
277 pointtype = type(next(iter(self.stops)).location) |
|
278 self._center = pointtype( |
|
279 median(stop.location.x for stop in self.stops), |
|
280 median(stop.location.y for stop in self.stops), |
|
281 ) |
190 ) |
|
191 route.trips.add(trip) |
|
192 assert trip.name not in all_trips |
|
193 all_trips[trip.name] = trip |
|
194 print('%d ajoa' % len(all_trips), file = stderr) |
|
195 |
|
196 def read_date(teksti): |
|
197 return date(int(teksti[:4]), int(teksti[4:6]), int(teksti[6:])) |
|
198 |
|
199 def read_time(teksti): |
|
200 tunti, minuutti, sekunti = map(int, teksti.split(':')) |
|
201 return timedelta(hours = tunti, minutes = minuutti, seconds = sekunti) |
|
202 |
|
203 print('Ladataan päiväykset... ', file = stderr, flush = True) |
|
204 |
|
205 viimeinen_käyttöpäivä = date.today() |
|
206 |
|
207 def date_range(start_date, end_date, *, include_end = False): |
|
208 ''' Generates date from start_date to end_date. If include_end is True, then end_date will be yielded. ''' |
|
209 current_date = start_date |
|
210 while current_date < end_date: |
|
211 yield current_date |
|
212 current_date += timedelta(1) |
|
213 if include_end: |
|
214 yield end_date |
|
215 |
|
216 def add_day_to_service(service_name, day): |
|
217 try: |
|
218 service = services[service_name] |
|
219 except KeyError: |
|
220 return |
282 else: |
221 else: |
283 raise ValueError('an empty cluster has no center point') |
222 service.dates.add(day) |
284 return self._center |
223 if day not in services_for_day: |
285 def merge(self, other): |
224 services_for_day[day] = set() |
286 for bus_stop in other.stops: |
225 services_for_day[day].add(service) |
287 bus_stop.cluster = self |
226 global viimeinen_käyttöpäivä |
288 self.stops |= other.stops |
227 viimeinen_käyttöpäivä = max(day, viimeinen_käyttöpäivä) |
289 other.stops = set() |
228 |
290 other._center = None |
229 def filter_day(row, day): |
291 def schedule(self, max_amount = 50): |
230 day_names = ['monday', 'tuesday', 'wednesday', 'thursday', 'friday', 'saturday', 'sunday'] |
292 result = [] |
231 return int(row[day_names[day.isoweekday() - 1]]) |
293 for stop in self.stops: |
232 |
294 result += stop.schedule(max_amount) |
233 with gtfs_zip.open('calendar.txt') as file: |
295 result.sort(key = lambda schedule_entry: schedule_entry['time']) |
234 for row in read_csv(map(bytes.decode, file)): |
296 return result[:max_amount] |
235 for day in date_range(read_date(row['start_date']), read_date(row['end_date']), include_end = True): |
297 |
236 if filter_day(row, day): |
298 from collections import defaultdict |
237 add_day_to_service(service_name = row['service_id'], day = day) |
299 bus_stops_by_name = defaultdict(set) |
238 |
300 for bus_stop in bus_stops.values(): |
239 with gtfs_zip.open('calendar_dates.txt') as file: |
301 bus_stops_by_name[bus_stop.name].add(bus_stop) |
240 for row in read_csv(map(bytes.decode, file)): |
302 bus_stops_by_name = dict(bus_stops_by_name) |
241 add_day_to_service(service_name = row['service_id'], day = read_date(row['date'])) |
303 |
242 |
304 # ryhmittele bus_stops nimen mukaan |
243 def services_available_at(day): |
305 all_clusters = [] |
244 for service in services.values(): |
306 def cluster_bus_stops(): |
245 if day in service.dates: |
307 sorted_bus_stops = sorted(bus_stops.values(), key = lambda bus_stop: bus_stop.name) |
246 yield service |
308 for bus_stop in sorted_bus_stops: |
247 |
309 if not bus_stop.cluster: |
248 print('Ladataan pysäkit... ', file = stderr, end = '', flush = True) |
310 stops_to_cluster = {bus_stop} |
249 with gtfs_zip.open('stops.txt') as file: |
311 # etsi pysäkin samannimiset vastaparit |
250 for row in read_csv(map(bytes.decode, file)): |
312 for pair_candidate in bus_stops_by_name[bus_stop.name]: |
251 location = Sijainti(float(row['stop_lat']), float(row['stop_lon'])) |
313 distance = pair_candidate.location.etäisyys(bus_stop.location) |
252 stop = BusStop( |
314 if pair_candidate is not bus_stop and distance <= 0.4: |
253 reference = row['stop_id'], |
315 stops_to_cluster.add(pair_candidate) |
254 name = row['stop_name'], |
316 for stop_to_cluster in stops_to_cluster: |
255 location = location, |
317 if stop_to_cluster.cluster: |
256 code = row['stop_code'], |
318 cluster = stop_to_cluster.cluster |
257 ) |
319 break |
258 bus_stops[stop.reference] = stop |
|
259 with open('regions-per-stop.json') as file: |
|
260 for stop_reference, region in json.load(file).items(): |
|
261 bus_stops[stop_reference].region = region |
|
262 print('%d pysäkkiä' % len(bus_stops), file = stderr) |
|
263 |
|
264 |
|
265 class BusStopCluster: |
|
266 def __init__(self): |
|
267 self.stops = set() |
|
268 self._center = None |
|
269 self.name = None |
|
270 @property |
|
271 def url_name(self): |
|
272 return self.name.lower().replace('(', '').replace(')', '').replace(' ', '-') |
|
273 def add_stop(self, stop): |
|
274 assert not stop.cluster |
|
275 stop.cluster = self |
|
276 self.stops.add(stop) |
|
277 self._center = None |
|
278 @property |
|
279 def center(self): |
|
280 if not self._center: |
|
281 if self.stops: |
|
282 from statistics import median |
|
283 pointtype = type(next(iter(self.stops)).location) |
|
284 self._center = pointtype( |
|
285 median(stop.location.x for stop in self.stops), |
|
286 median(stop.location.y for stop in self.stops), |
|
287 ) |
|
288 else: |
|
289 raise ValueError('an empty cluster has no center point') |
|
290 return self._center |
|
291 def merge(self, other): |
|
292 for bus_stop in other.stops: |
|
293 bus_stop.cluster = self |
|
294 self.stops |= other.stops |
|
295 other.stops = set() |
|
296 other._center = None |
|
297 def schedule(self, max_amount = 50): |
|
298 result = [] |
|
299 for stop in self.stops: |
|
300 result += stop.schedule(max_amount) |
|
301 result.sort(key = lambda schedule_entry: schedule_entry['time']) |
|
302 return result[:max_amount] |
|
303 |
|
304 from collections import defaultdict |
|
305 bus_stops_by_name = defaultdict(set) |
|
306 for bus_stop in bus_stops.values(): |
|
307 bus_stops_by_name[bus_stop.name].add(bus_stop) |
|
308 bus_stops_by_name = dict(bus_stops_by_name) |
|
309 |
|
310 # ryhmittele bus_stops nimen mukaan |
|
311 all_clusters = [] |
|
312 def cluster_bus_stops(): |
|
313 sorted_bus_stops = sorted(bus_stops.values(), key = lambda bus_stop: bus_stop.name) |
|
314 for bus_stop in sorted_bus_stops: |
|
315 if not bus_stop.cluster: |
|
316 stops_to_cluster = {bus_stop} |
|
317 # etsi pysäkin samannimiset vastaparit |
|
318 for pair_candidate in bus_stops_by_name[bus_stop.name]: |
|
319 distance = pair_candidate.location.etäisyys(bus_stop.location) |
|
320 if pair_candidate is not bus_stop and distance <= 0.4: |
|
321 stops_to_cluster.add(pair_candidate) |
|
322 for stop_to_cluster in stops_to_cluster: |
|
323 if stop_to_cluster.cluster: |
|
324 cluster = stop_to_cluster.cluster |
|
325 break |
|
326 else: |
|
327 cluster = BusStopCluster() |
|
328 all_clusters.append(cluster) |
|
329 for stop_to_cluster in stops_to_cluster: |
|
330 if not stop_to_cluster.cluster: |
|
331 cluster.add_stop(stop_to_cluster) |
|
332 # Merkitse muistiin pysäkkien vastaparit käyttäen hyväksi tämänhetkistä ryhmittelytietoa |
|
333 for bus_stop in bus_stops.values(): |
|
334 if bus_stop.cluster: |
|
335 bus_stop.pairs = bus_stop.cluster.stops - {bus_stop} |
|
336 # Ryhmitä ne bus_stops, joilla ei ollut omaa vastaparia, muiden pysäkkien kanssa |
|
337 for bus_stop in sorted_bus_stops: |
|
338 if len(bus_stop.cluster.stops) == 1: |
|
339 possibilities = set() |
|
340 for cluster in all_clusters: |
|
341 if cluster is not bus_stop.cluster: |
|
342 distance = cluster.center.etäisyys(bus_stop.location) |
|
343 if distance <= 0.4: |
|
344 possibilities.add((distance, cluster)) |
|
345 if possibilities: |
|
346 best = min(possibilities)[1] |
|
347 all_clusters.remove(bus_stop.cluster) |
|
348 best.merge(bus_stop.cluster) |
|
349 |
|
350 def shared_elements_in_n_sets(sets): |
|
351 from itertools import combinations |
|
352 result = set() |
|
353 for pair in combinations(sets, 2): |
|
354 result |= pair[0] & pair[1] |
|
355 return result |
|
356 |
|
357 def name_clusters(): |
|
358 from collections import defaultdict |
|
359 from pprint import pprint |
|
360 clusters_per_name = defaultdict(set) |
|
361 for cluster in all_clusters: |
|
362 name_representing_stop = min((len(stop.reference), stop.reference, stop) for stop in cluster.stops)[2] |
|
363 clusters_per_name[name_representing_stop.name].add(cluster) |
|
364 for name, clusters in clusters_per_name.items(): |
|
365 if len(clusters) == 1: |
|
366 # Ryhmä on ainoa jolla on varaus tälle nimelle. Sen kuin vaan. |
|
367 next(iter(clusters)).name = name |
|
368 else: |
|
369 # Olisiko kaikki klusterit eri alueilla? |
|
370 common_regions = shared_elements_in_n_sets({stop.region for stop in cluster.stops} for cluster in clusters) |
|
371 # Esitys: ryhmä -> ne alueet jotka ovat tälle ryhmälle ainutlaatuisia |
|
372 proposal = { |
|
373 cluster: {stop.region for stop in cluster.stops} - common_regions - {None} |
|
374 for cluster in clusters |
|
375 } |
|
376 # Jos enintään yksi klusteri tässä esityksessä on kokonaan ilman omaa aluetta, jolla se voisi eritellä, |
|
377 # niin nimetään klusterit näiden alueiden mukaan. |
|
378 # Se klusteri jolla ei ole omaa aluetta (jos on) jätetään ilman aluepäätettä. |
|
379 if sum([1 for unique_areas in proposal.values() if not unique_areas]) <= 1: |
|
380 for cluster, unique_areas in proposal.items(): |
|
381 individual_cluster_name = name |
|
382 if unique_areas: |
|
383 individual_cluster_name += ' (' + min(unique_areas) + ')' |
|
384 cluster.name = individual_cluster_name |
|
385 else: |
|
386 # Typerä reunatapaus. Indeksoidaan numeroin... |
|
387 for n, (_, cluster) in enumerate(sorted( |
|
388 min((stop.reference.lower(), cluster) for stop in cluster.stops) |
|
389 for cluster in clusters |
|
390 ), 1): |
|
391 individual_cluster_name = name + '-' + str(n) |
|
392 cluster.name = individual_cluster_name |
|
393 |
|
394 print('Ryhmitellään pysäkit...') |
|
395 cluster_bus_stops() |
|
396 name_clusters() |
|
397 |
|
398 for cluster in all_clusters: |
|
399 if cluster.url_name in clusters_by_name: |
|
400 print('Warning: Clusters %r and %r share the same URL name: %r' % (cluster.name, clusters_by_name[cluster.url_name].name, cluster.url_name)) |
320 else: |
401 else: |
321 cluster = BusStopCluster() |
402 clusters_by_name[cluster.url_name] = cluster |
322 all_clusters.append(cluster) |
403 |
323 for stop_to_cluster in stops_to_cluster: |
404 print('Ladataan aikataulut... ', end = '', flush = True, file = stderr) |
324 if not stop_to_cluster.cluster: |
405 with gtfs_zip.open('stop_times.txt') as file: |
325 cluster.add_stop(stop_to_cluster) |
406 row_count = sum(line.count(b'\n') for line in file) |
326 # Merkitse muistiin pysäkkien vastaparit käyttäen hyväksi tämänhetkistä ryhmittelytietoa |
407 with gtfs_zip.open('stop_times.txt') as file: |
327 for bus_stop in bus_stops.values(): |
408 progress = 0 |
328 if bus_stop.cluster: |
409 for row in read_csv(map(bytes.decode, file)): |
329 bus_stop.pairs = bus_stop.cluster.stops - {bus_stop} |
410 trip = all_trips[transform_trip_reference(row['trip_id'])] |
330 # Ryhmitä ne bus_stops, joilla ei ollut omaa vastaparia, muiden pysäkkien kanssa |
411 arrival_time = read_time(row['arrival_time']) |
331 for bus_stop in sorted_bus_stops: |
412 departure_time = read_time(row['departure_time']) |
332 if len(bus_stop.cluster.stops) == 1: |
413 stop = bus_stops[row['stop_id']] |
333 possibilities = set() |
414 traveled_distance = float(row['shape_dist_traveled']) * float(profile['metrics']['shape-modifier']) |
334 for cluster in all_clusters: |
415 trip.schedule.append(BusHalt(arrival_time, departure_time, stop, trip, traveled_distance)) |
335 if cluster is not bus_stop.cluster: |
416 stop.involved_trips.add(trip) |
336 distance = cluster.center.etäisyys(bus_stop.location) |
417 progress += 1 |
337 if distance <= 0.4: |
418 if progress % 1000 == 0: |
338 possibilities.add((distance, cluster)) |
419 print('\rLadataan aikataulut... %.1f%%' % (progress * 100 / row_count), end = ' ', file = stderr) |
339 if possibilities: |
420 print('\rLadataan aikataulut... ladattu', file = stderr) |
340 best = min(possibilities)[1] |
421 |
341 all_clusters.remove(bus_stop.cluster) |
422 for trip in all_trips.values(): |
342 best.merge(bus_stop.cluster) |
423 from busroute import simplify_name |
343 |
424 schedule = trip.concise_schedule() |
344 def shared_elements_in_n_sets(sets): |
425 try: |
345 from itertools import combinations |
426 trip.from_place = simplify_name(schedule[0]) |
346 result = set() |
427 trip.to_place = simplify_name(schedule[-1]) |
347 for pair in combinations(sets, 2): |
428 except IndexError: |
348 result |= pair[0] & pair[1] |
429 trip.from_place = '' |
349 return result |
430 trip.to_place = '' |
350 |
431 |
351 def name_clusters(): |
432 for route in routes.values(): |
352 from collections import defaultdict |
433 from collections import Counter |
353 from pprint import pprint |
434 from busroute import simplify_name |
354 clusters_per_name = defaultdict(set) |
435 tally = Counter() |
355 for cluster in all_clusters: |
436 for trip in route.trips: |
356 name_representing_stop = min((len(stop.reference), stop.reference, stop) for stop in cluster.stops)[2] |
437 schedule = trip.concise_schedule() |
357 clusters_per_name[name_representing_stop.name].add(cluster) |
438 places = set(schedule) |
358 for name, clusters in clusters_per_name.items(): |
439 do_add = True |
359 if len(clusters) == 1: |
440 assert type(schedule) is list |
360 # Ryhmä on ainoa jolla on varaus tälle nimelle. Sen kuin vaan. |
441 for candidate in tally: |
361 next(iter(clusters)).name = name |
442 if places.issubset(set(candidate)): |
362 else: |
443 do_add = False |
363 # Olisiko kaikki klusterit eri alueilla? |
444 tally.update({tuple(candidate)}) |
364 common_regions = shared_elements_in_n_sets({stop.region for stop in cluster.stops} for cluster in clusters) |
445 if do_add: |
365 # Esitys: ryhmä -> ne alueet jotka ovat tälle ryhmälle ainutlaatuisia |
446 tally.update({tuple(schedule)}) |
366 proposal = { |
447 try: |
367 cluster: {stop.region for stop in cluster.stops} - common_regions - {None} |
448 most_common_route = tally.most_common(1)[0][0] |
368 for cluster in clusters |
449 route.description = simplify_name(most_common_route[0]) + ' - ' + simplify_name(most_common_route[-1]) |
369 } |
450 except: |
370 # Jos enintään yksi klusteri tässä esityksessä on kokonaan ilman omaa aluetta, jolla se voisi eritellä, |
451 route.description = '' |
371 # niin nimetään klusterit näiden alueiden mukaan. |
452 route.trips = sorted(route.trips, key = lambda trip: trip.schedule[0].departure_time) |
372 # Se klusteri jolla ei ole omaa aluetta (jos on) jätetään ilman aluepäätettä. |
453 |
373 if sum([1 for unique_areas in proposal.values() if not unique_areas]) <= 1: |
454 # Fölin datassa on jotain tosi kummaa. Ilmeisesti ajovuoron viimeisen pysähdyksen saapumisaika on ihan täysin |
374 for cluster, unique_areas in proposal.items(): |
455 # väärin. Arvaan että se on seuraavan lähdön aika, mutta joka tapauksessa se on väärin. |
375 individual_cluster_name = name |
456 # Arvataan mikä se todellinen saapumisaika on. Se ei voi mennä kauhean paljon pahemmin vikaan kuin alkuperäinen |
376 if unique_areas: |
457 # väärin oleva data. |
377 individual_cluster_name += ' (' + min(unique_areas) + ')' |
458 for trip in all_trips.values(): |
378 cluster.name = individual_cluster_name |
459 bus_speed_coefficient = 750 # metriä minuutissa |
379 else: |
460 last_leg_distance = trip.schedule[-1].traveled_distance - trip.schedule[-2].traveled_distance |
380 # Typerä reunatapaus. Indeksoidaan numeroin... |
461 trip.schedule[-1].arrival_time = trip.schedule[-2].departure_time + timedelta(minutes = last_leg_distance / bus_speed_coefficient) |
381 for n, (_, cluster) in enumerate(sorted( |
462 |
382 min((stop.reference.lower(), cluster) for stop in cluster.stops) |
463 if __name__ == '__main__': |
383 for cluster in clusters |
464 from configparser import ConfigParser |
384 ), 1): |
465 profile = ConfigParser() |
385 individual_cluster_name = name + '-' + str(n) |
466 profile.read('profiles/föli.ini') |
386 cluster.name = individual_cluster_name |
467 load_buses('gtfs.zip', profile) |
387 |
|
388 print('Ryhmitellään pysäkit...') |
|
389 cluster_bus_stops() |
|
390 name_clusters() |
|
391 |
|
392 clusters_by_name = {} |
|
393 for cluster in all_clusters: |
|
394 if cluster.url_name in clusters_by_name: |
|
395 print('Warning: Clusters %r and %r share the same URL name: %r' % (cluster.name, clusters_by_name[cluster.url_name].name, cluster.url_name)) |
|
396 else: |
|
397 clusters_by_name[cluster.url_name] = cluster |
|
398 |
|
399 print('Ladataan aikataulut... ', end = '', flush = True, file = stderr) |
|
400 with open('gtfs/stop_times.txt') as file: |
|
401 row_count = sum(line.count('\n') for line in file) |
|
402 progress = 0 |
|
403 file.seek(0) |
|
404 for row in read_csv(file): |
|
405 trip = all_trips[transform_trip_reference(row['trip_id'])] |
|
406 arrival_time = read_time(row['arrival_time']) |
|
407 departure_time = read_time(row['departure_time']) |
|
408 stop = bus_stops[row['stop_id']] |
|
409 traveled_distance = float(row['shape_dist_traveled']) |
|
410 trip.schedule.append(BusHalt(arrival_time, departure_time, stop, trip, traveled_distance)) |
|
411 stop.involved_trips.add(trip) |
|
412 progress += 1 |
|
413 if progress % 1000 == 0: |
|
414 print('\rLadataan aikataulut... %.1f%%' % (progress * 100 / row_count), end = ' ', file = stderr) |
|
415 print('\rLadataan aikataulut... ladattu', file = stderr) |
|
416 |
|
417 for trip in all_trips.values(): |
|
418 from busroute import simplify_name |
|
419 schedule = trip.concise_schedule() |
|
420 try: |
|
421 trip.from_place = simplify_name(schedule[0]) |
|
422 trip.to_place = simplify_name(schedule[-1]) |
|
423 except IndexError: |
|
424 trip.from_place = '' |
|
425 trip.to_place = '' |
|
426 |
|
427 for route in routes.values(): |
|
428 from collections import Counter |
|
429 from busroute import simplify_name |
|
430 tally = Counter() |
|
431 for trip in route.trips: |
|
432 schedule = trip.concise_schedule() |
|
433 places = set(schedule) |
|
434 do_add = True |
|
435 assert type(schedule) is list |
|
436 for candidate in tally: |
|
437 if places.issubset(set(candidate)): |
|
438 do_add = False |
|
439 tally.update({tuple(candidate)}) |
|
440 if do_add: |
|
441 tally.update({tuple(schedule)}) |
|
442 try: |
|
443 most_common_route = tally.most_common(1)[0][0] |
|
444 route.description = simplify_name(most_common_route[0]) + ' - ' + simplify_name(most_common_route[-1]) |
|
445 except: |
|
446 route.description = '' |
|
447 route.trips = sorted(route.trips, key = lambda trip: trip.schedule[0].departure_time) |
|