Tue, 20 Jun 2017 10:27:52 +0300
Yhtenäistetty ohjelmakoodin kieli englanniksi
buses.py | file | annotate | diff | comparison | revisions | |
busroute.py | file | annotate | diff | comparison | revisions | |
misc.py | file | annotate | diff | comparison | revisions | |
service.py | file | annotate | diff | comparison | revisions | |
templates/ajovuoro.html | file | annotate | diff | comparison | revisions | |
templates/cluster.html | file | annotate | diff | comparison | revisions | |
templates/pysäkki.html | file | annotate | diff | comparison | revisions |
--- a/buses.py Tue Jun 20 09:39:42 2017 +0300 +++ b/buses.py Tue Jun 20 10:27:52 2017 +0300 @@ -5,82 +5,79 @@ from copy import copy from misc import * from geometry import * -Suunta = enum.Enum('Suunta', [('Taaksepäin', 0), ('Eteenpäin', 1)]) -def muunna_ajovuoro_tunniste(tunniste): - return tunniste +def transform_trip_reference(reference): + return reference -class Ajovuoro: - def __init__(self, tunniste, linja, palvelu, kyltti, suunta, length): - self.tunniste, self.linja, self.palvelu, self.kyltti, self.suunta = tunniste, linja, \ - palvelu, kyltti, suunta +class BusTrip: + def __init__(self, reference, route, service, length): + self.reference, self.route, self.service = reference, route, service self.length = length - self.reitti = [] - self.nimi = muunna_ajovuoro_tunniste(tunniste) + self.schedule = [] + self.name = transform_trip_reference(reference) def __repr__(self): - return 'ajot[%r]' % self.nimi - def pysäkkiReitillä(self, pysäkki): - for pysähdys in self.reitti: - if pysähdys.pysäkki is pysäkki: - return pysähdys + return 'all_trips[%r]' % self.name + def contains_stop(self, stop): + for halt in self.schedule: + if halt.stop is stop: + return halt else: return None - def ajetaan_päivänä(self, päivä): + def is_served_at(self, day): try: - return self.palvelu in palvelut_per_päivä[päivä] + return self.service in services_for_day[day] except KeyError: return False - def suppea_reitti(self, pysäkistä = None): - if pysäkistä and pysäkistä in self.reitti: - reitti = copy(self.reitti) - reitti = reitti[reitti.index(pysäkistä):] + def concise_schedule(self, starting_stop = None): + if starting_stop and starting_stop in self.schedule: + schedule = copy(self.schedule) + schedule = schedule[schedule.index(starting_stop):] else: - reitti = self.reitti - käytetyt_alueet = set() - tulos = [] - for pysähdys in reitti: - pysäkki = pysähdys.pysäkki - if pysäkki.alue and pysäkki.alue not in käytetyt_alueet: - käytetyt_alueet.add(pysäkki.alue) - tulos.append(pysäkki.alue) - return tulos + schedule = self.schedule + used_areas = set() + result = [] + for halt in schedule: + stop = halt.stop + if stop.region and stop.region not in used_areas: + used_areas.add(stop.region) + result.append(stop.region) + return result -class Linja: - def __init__(self, tietue): - self.tunniste = tietue['route_id'] - self.viite = tietue['route_short_name'] - self.selite = tietue['route_long_name'] +class BusRoute: + def __init__(self, entry): + self.id = entry['route_id'] + self.reference = entry['route_short_name'] def __repr__(self): - return 'linjat[%r]' % self.viite + return 'routes[%r]' % self.reference -class Palvelu: - def __init__(self, tunniste): - self.tunniste = tunniste - self.päivät = set() +class BusService: + def __init__(self, reference): + self.reference = reference + self.dates = set() def __repr__(self): - return 'palvelut[%r]' % self.tunniste + return 'services[%r]' % self.reference -class Pysäkki: - def __init__(self, tunniste, nimi, sijainti): - self.tunniste, self.nimi, self.sijainti = tunniste, nimi, sijainti +class BusStop: + def __init__(self, reference, name, location): + self.reference, self.name, self.location = reference, name, location self.cluster = None self.pairs = set() # samannimiset lähellä olevat pysäkit def __repr__(self): - return 'pysäkit[%r]' % self.tunniste + return 'bus_stops[%r]' % self.reference def schedule(self, max_amount = 50): ''' Hakee tämän pysäkin seuraavat `määrä` lähtöä. Päätepysäkille saapuvia busseja ei - lasketa. Palauttaa pysähdykset listana jossa alkiot ovat muotoa (aika, pysähdys), + lasketa. Palauttaa pysähdykset listana jossa alkiot ovat muotoa (aika, halt), jossa: - `aika` on saapumishetki muotoa datetime ja - - `pysähdys` on vastaava Pysähdys olio. + - `halt` on vastaava BusHalt olio. Mikäli pysäkille ei ole määrätty riittävästi pysähdyksiä kalenterissa, tuloslista jää alimittaiseksi, mahdollisesti jopa tyhjäksi. ''' result = [] # -1 päivää yövuoroja varten - date = tänään() - timedelta(days = 1) + date = today() - timedelta(days = 1) # Niin kauan kuin aikatauluja ei ole vielä tarpeeksi, while len(result) < max_amount: try: @@ -98,22 +95,22 @@ Hakee pysäkin aikataulut tiettynä päivänä. ''' # Jos päädyttiin aikataulukalenterin ulkopuolelle, niin tuotetaan virhe. Jos vain - # palautettaisiin tyhjä tulos, niin algoritmi jatkaisi etsintää loputtomiin. + # palautettaisiin tyhjä result, niin algoritmi jatkaisi etsintää loputtomiin. if date > viimeinen_käyttöpäivä: raise ValueError('tried to retrieve schedule for date %s which is outside schedule data' % date) result = [] # Jokaiselle ajovuorolle, - for trip in ajot.values(): + for trip in all_trips.values(): # jos tämä ajovuoro ajetaan tänä päivänä - if trip.ajetaan_päivänä(date): - # ja jos tämä ajo pysähtyy tällä pysäkillä, ei kuitenkaan saapuen + if trip.is_served_at(date): + # ja jos tämä trip pysähtyy tällä pysäkillä, ei kuitenkaan saapuen # päätepysäkille, - stop = trip.pysäkkiReitillä(self) - if stop and not stop.isArrival: # stop is not trip.reitti[-1]: - # ja jos tämä pysähdys on tulevaisuudessa, - stop_time = datetime.combine(date, time()) + stop.saapumisaika - if stop_time >= nyt(): - # lisää pysähdys listaan. + stop = trip.contains_stop(self) + if stop and not stop.isArrival: # stop is not trip.schedule[-1]: + # ja jos tämä halt on tulevaisuudessa, + stop_time = datetime.combine(date, time()) + stop.arrival_time + if stop_time >= now(): + # lisää halt listaan. result.append({ 'time': stop_time, 'trip': trip, @@ -123,107 +120,102 @@ result.sort(key = lambda schedule_entry: schedule_entry['time']) return result -class Pysähdys: - def __init__(self, saapumisaika, lähtöaika, pysäkki, ajo, ajettu_matka): - self.saapumisaika, self.lähtöaika, self.pysäkki, self.ajo = saapumisaika, lähtöaika, \ - pysäkki, ajo - self.ajettu_matka = ajettu_matka - self._isArrival = None +class BusHalt: + def __init__(self, arrival_time, departure_time, stop, trip, traveled_distance): + self.arrival_time, self.departure_time, self.stop, self.trip = arrival_time, departure_time, \ + stop, trip + self.traveled_distance = traveled_distance @property def isArrival(self): - if self._isArrival is None: - iterator = iter(self.ajo.reitti) + if not hasattr(self, 'cachedIsArrival'): + iterator = iter(self.trip.schedule) stop = next(iterator) while stop is not self: stop = next(iterator) for stop in iterator: - if stop.pysäkki.alue != self.pysäkki.alue: - self._isArrival = False + if stop.stop.region != self.stop.region: + self.cachedIsArrival = False break else: - self._isArrival = True - return self._isArrival + self.cachedIsArrival = True + return self.cachedIsArrival def __repr__(self): - return 'Pysähdys(%r, %r, %r, %r)' % (self.saapumisaika, self.lähtöaika, self.pysäkki, self.ajo) + return 'BusHalt(%r, %r, %r, %r)' % (self.arrival_time, self.departure_time, self.stop, self.trip) -linjat = {} -linjat_per_tunniste = {} -ajot = {} -ajot_per_numero = {} -palvelut = {} -pysäkit = {} +routes = {} +routes_per_id = {} +all_trips = {} +services = {} +bus_stops = {} all_clusters = set() -print('Ladataan linjat... ', file = stderr, end = '', flush = True) -with open('gtfs/routes.txt') as tiedosto: - for rivi in lue_csv(tiedosto): - linja = Linja(rivi) - linja.tunniste = linja.tunniste - linjat[linja.viite] = linja - linjat_per_tunniste[linja.tunniste] = linja -print('%d linjaa' % len(linjat), file = stderr) +print('Ladataan routes... ', file = stderr, end = '', flush = True) +with open('gtfs/routes.txt') as file: + for row in read_csv(file): + route = BusRoute(row) + routes[route.reference] = route + routes_per_id[route.id] = route +print('%d linjaa' % len(routes), file = stderr) -print('Ladataan ajot... ', file = stderr, end = '', flush = True) +print('Ladataan ajovuorot... ', file = stderr, end = '', flush = True) shape_distances = {} with open('gtfs/shapes.txt') as file: - for row in lue_csv(file): + for row in read_csv(file): shape_distances[row['shape_id']] = max(shape_distances.get(row['shape_id'], 0), float(row['shape_dist_traveled'])) -with open('gtfs/trips.txt') as tiedosto: - for rivi in lue_csv(tiedosto, muunnokset = {'direction_id': lambda k: Suunta(int(k))}): - if rivi['service_id'] not in palvelut: - palvelut[rivi['service_id']] = Palvelu(rivi['service_id']) - linja = linjat_per_tunniste[rivi['route_id']] - ajo = Ajovuoro( - tunniste = rivi['trip_id'], - linja = linja, - palvelu = palvelut[rivi['service_id']], - kyltti = rivi['trip_headsign'], - suunta = rivi['direction_id'], - length = shape_distances[rivi['shape_id']] +with open('gtfs/trips.txt') as file: + for row in read_csv(file): + if row['service_id'] not in services: + services[row['service_id']] = BusService(row['service_id']) + route = routes_per_id[row['route_id']] + trip = BusTrip( + reference = row['trip_id'], + route = route, + service = services[row['service_id']], + length = shape_distances[row['shape_id']] ) - assert ajo.nimi not in ajot - ajot[ajo.nimi] = ajo -print('%d ajoa' % len(ajot), file = stderr) + assert trip.name not in all_trips + all_trips[trip.name] = trip +print('%d ajoa' % len(all_trips), file = stderr) def lue_päiväys(teksti): return date(int(teksti[:4]), int(teksti[4:6]), int(teksti[6:])) -def lue_aika(teksti): +def read_time(teksti): tunti, minuutti, sekunti = map(int, teksti.split(':')) return timedelta(hours = tunti, minutes = minuutti, seconds = sekunti) print('Ladataan päiväykset... ', file = stderr, flush = True) viimeinen_käyttöpäivä = date.today() -palvelut_per_päivä = {} +services_for_day = {} -with open('gtfs/calendar_dates.txt') as tiedosto: - for rivi in lue_csv(tiedosto): - palvelu = palvelut[rivi['service_id']] - päivä = lue_päiväys(rivi['date']) - palvelu.päivät.add(päivä) - if päivä not in palvelut_per_päivä: - palvelut_per_päivä[päivä] = set() - palvelut_per_päivä[päivä].add(palvelu) - viimeinen_käyttöpäivä = max(päivä, viimeinen_käyttöpäivä) +with open('gtfs/calendar_dates.txt') as file: + for row in read_csv(file): + service = services[row['service_id']] + day = lue_päiväys(row['date']) + service.dates.add(day) + if day not in services_for_day: + services_for_day[day] = set() + services_for_day[day].add(service) + viimeinen_käyttöpäivä = max(day, viimeinen_käyttöpäivä) -def palvelut_käytössä(päivä): - for palvelu in palvelut.values(): - if päivä in palvelu.päivät: - yield palvelu +def services_available_at(day): + for service in services.values(): + if day in service.dates: + yield service print('Ladataan pysäkit... ', file = stderr, end = '', flush = True) with open('gtfs/stops.txt') as file: - for rivi in lue_csv(file): - sijainti = Sijainti(float(rivi['stop_lat']), float(rivi['stop_lon'])) - pysäkki = Pysäkki(rivi['stop_id'], rivi['stop_name'], sijainti) - pysäkit[pysäkki.tunniste] = pysäkki + for row in read_csv(file): + location = Sijainti(float(row['stop_lat']), float(row['stop_lon'])) + stop = BusStop(row['stop_id'], row['stop_name'], location) + bus_stops[stop.reference] = stop with open('regions-per-stop.json') as file: - for pysäkkitunniste, alue in json.load(file).items(): - pysäkit[pysäkkitunniste].alue = alue -print('%d pysäkkiä' % len(pysäkit), file = stderr) + for stop_reference, region in json.load(file).items(): + bus_stops[stop_reference].region = region +print('%d pysäkkiä' % len(bus_stops), file = stderr) class BusStopCluster: @@ -244,10 +236,10 @@ if not self._center: if self.stops: from statistics import median - pointtype = type(next(iter(self.stops)).sijainti) + pointtype = type(next(iter(self.stops)).location) self._center = pointtype( - median(stop.sijainti.x for stop in self.stops), - median(stop.sijainti.y for stop in self.stops), + median(stop.location.x for stop in self.stops), + median(stop.location.y for stop in self.stops), ) else: raise ValueError('an empty cluster has no center point') @@ -267,20 +259,20 @@ from collections import defaultdict bus_stops_by_name = defaultdict(set) -for bus_stop in pysäkit.values(): - bus_stops_by_name[bus_stop.nimi].add(bus_stop) +for bus_stop in bus_stops.values(): + bus_stops_by_name[bus_stop.name].add(bus_stop) bus_stops_by_name = dict(bus_stops_by_name) -# ryhmittele pysäkit nimen mukaan +# ryhmittele bus_stops nimen mukaan all_clusters = [] def cluster_bus_stops(): - sorted_bus_stops = sorted(pysäkit.values(), key = lambda bus_stop: bus_stop.nimi) + sorted_bus_stops = sorted(bus_stops.values(), key = lambda bus_stop: bus_stop.name) for bus_stop in sorted_bus_stops: if not bus_stop.cluster: stops_to_cluster = {bus_stop} # etsi pysäkin samannimiset vastaparit - for pair_candidate in bus_stops_by_name[bus_stop.nimi]: - distance = pair_candidate.sijainti.etäisyys(bus_stop.sijainti) + for pair_candidate in bus_stops_by_name[bus_stop.name]: + distance = pair_candidate.location.etäisyys(bus_stop.location) if pair_candidate is not bus_stop and distance <= 0.3: stops_to_cluster.add(pair_candidate) for stop_to_cluster in stops_to_cluster: @@ -294,16 +286,16 @@ if not stop_to_cluster.cluster: cluster.add_stop(stop_to_cluster) # Merkitse muistiin pysäkkien vastaparit käyttäen hyväksi tämänhetkistä ryhmittelytietoa - for bus_stop in pysäkit.values(): + for bus_stop in bus_stops.values(): if bus_stop.cluster: bus_stop.pairs = bus_stop.cluster.stops - {bus_stop} - # Ryhmitä ne pysäkit, joilla ei ollut omaa vastaparia, muiden pysäkkien kanssa + # Ryhmitä ne bus_stops, joilla ei ollut omaa vastaparia, muiden pysäkkien kanssa for bus_stop in sorted_bus_stops: if len(bus_stop.cluster.stops) == 1: possibilities = set() for cluster in all_clusters: if cluster is not bus_stop.cluster: - distance = cluster.center.etäisyys(bus_stop.sijainti) + distance = cluster.center.etäisyys(bus_stop.location) if distance <= 0.3: possibilities.add((distance, cluster)) if possibilities: @@ -323,18 +315,18 @@ from pprint import pprint clusters_per_name = defaultdict(set) for cluster in all_clusters: - name_representing_stop = min((len(pysäkki.tunniste), pysäkki.tunniste, pysäkki) for pysäkki in cluster.stops)[2] - clusters_per_name[name_representing_stop.nimi].add(cluster) + name_representing_stop = min((len(stop.reference), stop.reference, stop) for stop in cluster.stops)[2] + clusters_per_name[name_representing_stop.name].add(cluster) for name, clusters in clusters_per_name.items(): if len(clusters) == 1: # Ryhmä on ainoa jolla on varaus tälle nimelle. Sen kuin vaan. next(iter(clusters)).name = name else: # Olisiko kaikki klusterit eri alueilla? - common_regions = shared_elements_in_n_sets({stop.alue for stop in cluster.stops} for cluster in clusters) + common_regions = shared_elements_in_n_sets({stop.region for stop in cluster.stops} for cluster in clusters) # Esitys: ryhmä -> ne alueet jotka ovat tälle ryhmälle ainutlaatuisia proposal = { - cluster: {stop.alue for stop in cluster.stops} - common_regions - {None} + cluster: {stop.region for stop in cluster.stops} - common_regions - {None} for cluster in clusters } # Jos enintään yksi klusteri tässä esityksessä on kokonaan ilman omaa aluetta, jolla se voisi eritellä, @@ -349,7 +341,7 @@ else: # Typerä reunatapaus. Indeksoidaan numeroin... for n, (_, cluster) in enumerate(sorted( - min((stop.tunniste.lower(), cluster) for stop in cluster.stops) + min((stop.reference.lower(), cluster) for stop in cluster.stops) for cluster in clusters ), 1): individual_cluster_name = name + '-' + str(n) @@ -367,16 +359,16 @@ print('Ladataan aikataulut... ', end = '', flush = True, file = stderr) with open('gtfs/stop_times.txt') as file: rivimäärä = sum(line.count('\n') for line in file) - laskettu = 0 + progress = 0 file.seek(0) - for rivi in lue_csv(file): - ajo = ajot[muunna_ajovuoro_tunniste(rivi['trip_id'])] - saapumisaika = lue_aika(rivi['arrival_time']) - lähtöaika = lue_aika(rivi['departure_time']) - pysäkki = pysäkit[rivi['stop_id']] - ajettu_matka = float(rivi['shape_dist_traveled']) - ajo.reitti.append(Pysähdys(saapumisaika, lähtöaika, pysäkki, ajo, ajettu_matka)) - laskettu += 1 - if laskettu % 1000 == 0: - print('\rLadataan aikataulut... %.1f%%' % (laskettu * 100 / rivimäärä), end = ' ', file = stderr) + for row in read_csv(file): + trip = all_trips[transform_trip_reference(row['trip_id'])] + arrival_time = read_time(row['arrival_time']) + departure_time = read_time(row['departure_time']) + stop = bus_stops[row['stop_id']] + traveled_distance = float(row['shape_dist_traveled']) + trip.schedule.append(BusHalt(arrival_time, departure_time, stop, trip, traveled_distance)) + progress += 1 + if progress % 1000 == 0: + print('\rLadataan aikataulut... %.1f%%' % (progress * 100 / rivimäärä), end = ' ', file = stderr) print('\rLadataan aikataulut... ladattu', file = stderr)
--- a/busroute.py Tue Jun 20 09:39:42 2017 +0300 +++ b/busroute.py Tue Jun 20 10:27:52 2017 +0300 @@ -1,6 +1,6 @@ #!/usr/bin/env python3 -def supista_reitti(reitti, ajomatka, kokonainen = False): - length = ((ajomatka / 600) + len(reitti)) / 2 +def reduce_schedule(reitti, trip_length, whole = False): + length = ((trip_length / 600) + len(reitti)) / 2 def abstract_sign(sign): abstractions = { 'Moikoinen': 'Hirvensalo', @@ -58,7 +58,7 @@ else: have_already.add(reitti[i]) i += 1 - prioriteetit = { + priorities = { 'Ihala': 150, 'Kauppakeskus Mylly': 200, 'Kuninkoja': 80, @@ -122,10 +122,10 @@ } if 'Kauppatori' not in reitti: - prioriteetit['Länsikeskus'] = prioriteetit['Skanssi'] = prioriteetit['Kauppatori'] + priorities['Länsikeskus'] = priorities['Skanssi'] = priorities['Kauppatori'] # nimiä joista voidaan joustaa tarvittaessa - helpot_supistukset = { + abbreviations = { 'Raision keskusta': 'Raisio', 'Ruskon keskusta': 'Rusko', 'Naantalin keskusta': 'Naantali', @@ -133,70 +133,64 @@ 'Kauppakeskus Mylly': 'Mylly', } - # jos ei nyt millään vaan mahdu muuten... - vakavat_supistukset = { - 'Kauppatori': 'Tori', - 'Ylioppilaskylä': 'Yo-kylä', - } - - lähtö = reitti[0] - määränpää = reitti[-1] + from_place = reitti[0] + destination = reitti[-1] reitti_arvot = {} f = lambda i: i**-0.3 - jakaja = max(f(i + 1) for i in range(len(reitti))) - for i, pysäkki in enumerate(reitti): + factor = 1 / max(f(i + 1) for i in range(len(reitti))) + for i, stop in enumerate(reitti): # muunna indeksi siten että myöhemmät alueet korostuvat - i = f(i + 1) / jakaja + i = f(i + 1) * factor # ota prioriteetti huomioon, jotkin alueet ovat tärkeämpiä kyltissä kuin toiset - i *= prioriteetit.get(pysäkki, 1) - reitti_arvot[pysäkki] = i + i *= priorities.get(stop, 1) + reitti_arvot[stop] = i # nollaa lähtöpaikan arvo ettei se mitenkään tule kylttiin - if lähtö in reitti_arvot: - reitti_arvot[lähtö] = 0 - # varmista että määränpää tulee kylttiin - reitti_arvot[määränpää] = 1e10 - # muodosta kyltti-tiedot järjestettynä reittiarvon mukaan - painot = sorted([ - (pysäkki, reitti_arvot[pysäkki], i) \ - for i, pysäkki in enumerate(reitti) \ - if reitti_arvot[pysäkki] >= 1 - ], key = lambda pysäkki: -pysäkki[1]) + if from_place in reitti_arvot: + reitti_arvot[from_place] = 0 + # varmista että destination tulee kylttiin + reitti_arvot[destination] = 1e10 + # muodosta sign-tiedot järjestettynä reittiarvon mukaan + weights = sorted([ + (stop, reitti_arvot[stop], i) \ + for i, stop in enumerate(reitti) \ + if reitti_arvot[stop] >= 1 + ], key = lambda stop: -stop[1]) # enintään neljä tulee kylttiin - painot = painot[:3] + weights = weights[:3] # jos kolmas kylttiarvo ei ole tarpeeksi merkittävä suhteessa reitin pituuteen niin otetaan se pois try: - if painot[2][0] != määränpää and painot[2][1] < (1000 / length ** 1.15): - del painot[2] + if weights[2][0] != destination and weights[2][1] < (1000 / length ** 1.15): + del weights[2] except IndexError: pass try: - if painot[1][0] != määränpää and painot[1][1] < (500 / length ** 1.15): - del painot[1] + if weights[1][0] != destination and weights[1][1] < (500 / length ** 1.15): + del weights[1] except IndexError: pass - # lajitellaan painoarvot uudestaan reittijärjestykseen jotta kyltti tulee oikeinpäin - painot = sorted(painot, key = lambda paino: paino[2]) - # muodostetaan kyltti.. - kyltti = [paino[0] for paino in painot] - #kyltti = abstract_sign(kyltti) + # lajitellaan painoarvot uudestaan reittijärjestykseen jotta sign tulee oikeinpäin + weights = sorted(weights, key = lambda paino: paino[2]) + # muodostetaan sign.. + sign = [paino[0] for paino in weights] + #sign = abstract_sign(sign) # supista nimet jos mahdollista - def viimeistele(kyltti, supistus_taso = 0): - if supistus_taso > 0: - kyltti = [helpot_supistukset.get(paikka, paikka) for paikka in kyltti] - if supistus_taso > 1: - kyltti = [vakavat_supistukset.get(paikka, paikka) for paikka in kyltti] - return kyltti - tulos = viimeistele(kyltti) - for i in range(len(kyltti) - 1): - if kyltti[i + 1].startswith(kyltti[i]): - del kyltti[i] - if len(' - '.join(kyltti)) > 20: - tulos = viimeistele(kyltti, supistus_taso = 1) - if len(' - '.join(kyltti)) > 70: - tulos = viimeistele(kyltti, supistus_taso = 2) - if kokonainen: - tulos = [lähtö] + tulos - lyhyt_lähtö = replacements.get(lähtö, lähtö) - if lyhyt_lähtö != tulos[-1] and helpot_supistukset.get(lyhyt_lähtö, lyhyt_lähtö) in eksoalueet | {'Kauppatori'} and tulos[-1] in eksoalueet | {'Kauppatori'}: - tulos = ['Turku' if k == 'Kauppatori' else k for k in tulos] - return tulos + def finalise(sign, abbreviation_level = 0): + if abbreviation_level > 0: + sign = [abbreviations.get(paikka, paikka) for paikka in sign] + return sign + result = finalise(sign) + for i in range(len(sign) - 1): + if sign[i + 1].startswith(sign[i]): + del sign[i] + if len(' - '.join(sign)) > 20: + result = finalise(sign, abbreviation_level = 1) + if whole: + result = [from_place] + result + short_from_place = replacements.get(from_place, from_place) + if ( + short_from_place != result[-1] + and abbreviations.get(short_from_place, short_from_place) in eksoalueet | {'Kauppatori'} + and result[-1] in eksoalueet | {'Kauppatori'} + ): + result = ['Turku' if k == 'Kauppatori' else k for k in result] + return result
--- a/misc.py Tue Jun 20 09:39:42 2017 +0300 +++ b/misc.py Tue Jun 20 10:27:52 2017 +0300 @@ -1,25 +1,25 @@ from datetime import datetime, date, time, timedelta -def tänään(): +def today(): return date.today() #return date(2017, 1, 10) -def nyt(): +def now(): return datetime.now() - #return datetime.combine(tänään(), datetime.now().time()) + #return datetime.combine(today(), datetime.now().time()) -def lue_csv(tiedosto, muunnokset = None): +def read_csv(file, transformations = None): import csv - lukija = csv.reader(tiedosto) - otsakkeet = next(lukija) - for i in range(len(otsakkeet)): - otsakkeet[i] = otsakkeet[i].replace('\ufeff', '').strip() + lukija = csv.reader(file) + keys = next(lukija) + for i in range(len(keys)): + keys[i] = keys[i].replace('\ufeff', '').strip() for rivi in lukija: - tietue = dict(zip(otsakkeet, rivi)) - if muunnokset: - for avain, muunnos in muunnokset.items(): - tietue[avain] = muunnos(tietue[avain]) - yield tietue + entry = dict(zip(keys, rivi)) + if transformations: + for key, transformation in transformations.items(): + entry[key] = transformation(entry[key]) + yield entry def is_night_time(time): return time.hour >= 23 or time.hour < 5
--- a/service.py Tue Jun 20 09:39:42 2017 +0300 +++ b/service.py Tue Jun 20 10:27:52 2017 +0300 @@ -6,7 +6,7 @@ import locale from misc import * -from busroute import supista_reitti +from busroute import reduce_schedule app = Flask(__name__) @@ -58,138 +58,133 @@ def sign(schedule_entry): from math import ceil - ajomatka = schedule_entry['trip'].length - schedule_entry['stop'].ajettu_matka - sign = supista_reitti(schedule_entry['trip'].suppea_reitti(schedule_entry['stop']), ajomatka = ajomatka) - sign = [tr(paikka, 'paikat') for paikka in sign] + trip_length = schedule_entry['trip'].length - schedule_entry['stop'].traveled_distance + sign = reduce_schedule(schedule_entry['trip'].concise_schedule(schedule_entry['stop']), trip_length = trip_length) + sign = [tr(place, 'paikat') for place in sign] sign_representation = ' - '.join(sign) #if len(sign_representation) > 25: # k = ceil(len(sign) / 2) # sign_representation = ' - '.join(sign[:k]) + '\n' + ' - '.join(sign[k:]) return sign_representation -@app.route('/pysäkki/<tunniste>') -def pysäkkiaikataulu(tunniste): - from buses import pysäkit - aikataulu = [] +@app.route('/pysäkki/<reference>') +def bus_stop_schedule(reference): + from buses import bus_stops + schedule = [] try: - pysäkki = pysäkit[tunniste] + bus_stop = bus_stops[reference] except KeyError: abort(404) - for schedule_entry in pysäkki.schedule(100): - aikataulu.append({ - 'aika': time_representation(schedule_entry['time']), - 'linja': schedule_entry['trip'].linja.viite, - 'kyltti': sign(schedule_entry), - 'ajovuoro': schedule_entry['stop'].ajo.nimi, - 'yö': is_night_time(schedule_entry['time']), + for schedule_entry in bus_stop.schedule(100): + schedule.append({ + 'time': time_representation(schedule_entry['time']), + 'route': schedule_entry['trip'].route.reference, + 'sign': sign(schedule_entry), + 'trip': schedule_entry['stop'].trip.name, + 'night': is_night_time(schedule_entry['time']), }) - from os import path - tausta = path.join('static', 'tausta-' + (pysäkki.alue or 'pysäkki').lower().replace(' ', '-') + '.png') - if not path.isfile(tausta): - tausta = None return render_template( 'pysäkki.html', - aikataulu = aikataulu, - nimi = tunniste + ' ' + tr(pysäkki.nimi, 'pysäkit'), - linkki_karttaan = pysäkki.sijainti.link_to_map, - alue = pysäkki.alue, - sijainti = pysäkki.sijainti, - cluster = pysäkki.cluster.url_name if len(pysäkki.cluster.stops) > 1 else None, + schedule = schedule, + name = reference + ' ' + tr(bus_stop.name, 'bus_stops'), + link_to_map = bus_stop.location.link_to_map, + region = bus_stop.region, + location = bus_stop.location, + cluster = bus_stop.cluster.url_name if len(bus_stop.cluster.stops) > 1 else None, tr = tr, - tausta = tausta, ) -def time_representation(aika, suhteellinen = True): - erotus = aika - nyt() - if suhteellinen and erotus > timedelta(0) and erotus < timedelta(minutes = 1): +def time_representation(time, relative = True): + time_difference = time - now() + if relative and time_difference > timedelta(0) and time_difference < timedelta(minutes = 1): return tr('right-now', 'misc-text') - elif suhteellinen and erotus > timedelta(0) and erotus < timedelta(minutes = 10): - return '%dm' % round(erotus.seconds / 60) - elif aika.date() == tänään(): - return '%d:%02d' % (aika.hour, aika.minute) - elif erotus < timedelta(7): + elif relative and time_difference > timedelta(0) and time_difference < timedelta(minutes = 10): + return '%dm' % round(time_difference.seconds / 60) + elif time.date() == today(): + return '%d:%02d' % (time.hour, time.minute) + elif time_difference < timedelta(7): with activate_locale(): - return aika.strftime('%-a %H:%M') + return time.strftime('%-a %H:%M') else: with activate_locale(): - return aika.strftime('%-d.%-m. %H:%M') + return time.strftime('%-d.%-m. %H:%M') @app.route('/pysäkkiryhmä/<cluster_name>') def cluster_schedule(cluster_name): - from buses import pysäkit, clusters_by_name - aikataulu = [] + from buses import bus_stops, clusters_by_name + schedule = [] try: cluster = clusters_by_name[cluster_name] except KeyError: abort(404) for schedule_entry in cluster.schedule(100): - aikataulu.append({ - 'aika': time_representation(schedule_entry['time']), - 'linja': schedule_entry['trip'].linja.viite, - 'kyltti': sign(schedule_entry), - 'ajovuoro': schedule_entry['stop'].ajo.nimi, - 'yö': is_night_time(schedule_entry['time']), - 'stop_id': schedule_entry['stop'].pysäkki.tunniste, - 'stop_name': tr(schedule_entry['stop'].pysäkki.nimi, 'pysäkit'), + schedule.append({ + 'time': time_representation(schedule_entry['time']), + 'route': schedule_entry['trip'].route.reference, + 'sign': sign(schedule_entry), + 'trip': schedule_entry['stop'].trip.name, + 'night': is_night_time(schedule_entry['time']), + 'stop_id': schedule_entry['stop'].stop.reference, + 'stop_name': tr(schedule_entry['stop'].stop.name, 'bus_stops'), }) return render_template( 'cluster.html', - aikataulu = aikataulu, - nimi = cluster.name, - linkki_karttaan = cluster.center.link_to_map, - sijainti = cluster.center, + schedule = schedule, + name = cluster.name, + link_to_map = cluster.center.link_to_map, + location = cluster.center, stops_in_cluster = sorted( ({ - 'id': stop.tunniste, - 'name': tr(stop.nimi, 'pysäkit'), + 'id': stop.reference, + 'name': tr(stop.name, 'bus_stops'), } for stop in cluster.stops), key = lambda stop: (len(stop['id']), stop['id']) ), tr = tr, ) -@app.route('/ajovuoro/<numero>') -def ajoreitti(numero): +@app.route('/ajovuoro/<trip_reference>') +def trip(trip_reference): from flask import request - from buses import ajot + from buses import all_trips try: - ajovuoro = ajot[numero] + trip = all_trips[trip_reference] except KeyError: abort(404) - reitti = [] - suppea_reitti = [] - käydyt_alueet = set() - for pysähdys in ajovuoro.reitti: - aika = datetime.combine(tänään(), time()) + pysähdys.saapumisaika - muotoiltu_aika = time_representation(aika) - reitti.append({ - 'aika': muotoiltu_aika, - 'tunniste': pysähdys.pysäkki.tunniste, - 'alue': tr(pysähdys.pysäkki.alue or '', 'paikat'), - 'nimi': tr(pysähdys.pysäkki.nimi, 'pysäkit'), + schedule = [] + concise_schedule = [] + used_areas = set() + for halt in trip.schedule: + stop_time = datetime.combine(today(), time()) + halt.arrival_time + formatted_time = time_representation(stop_time) + schedule.append({ + 'time': formatted_time, + 'reference': halt.stop.reference, + 'region': tr(halt.stop.region or '', 'paikat'), + 'name': tr(halt.stop.name, 'bus_stops'), }) - alue = pysähdys.pysäkki.alue - if alue: - if alue not in käydyt_alueet: - suppea_reitti.append({ - 'aika': muotoiltu_aika, - 'alue': alue or '', + region = halt.stop.region + if region: + if region not in used_areas: + concise_schedule.append({ + 'time': formatted_time, + 'region': region or '', }) - käydyt_alueet.add(alue) - kyltti = supista_reitti([k['alue'] for k in suppea_reitti], kokonainen = True, ajomatka = ajovuoro.length) - kyltti = [kyltti[0], kyltti[-1]] - for entry in suppea_reitti: - entry['alue'] = tr(entry['alue'], 'paikat') + used_areas.add(region) + sign = reduce_schedule([k['region'] for k in concise_schedule], whole = True, trip_length = trip.length) + sign = [sign[0], sign[-1]] + for entry in concise_schedule: + entry['region'] = tr(entry['region'], 'paikat') return render_template('ajovuoro.html', - reitti = reitti, - suppea_reitti = suppea_reitti, - numero = numero, - linja = ajovuoro.linja.viite, - selite = ' - '.join(tr(paikka, 'paikat') for paikka in kyltti), - yö = is_night_time(datetime.combine(tänään(), time()) + ajovuoro.reitti[-1].saapumisaika), + schedule = schedule, + concise_schedule = concise_schedule, + trip_reference = trip_reference, + route = trip.route.reference, + description = ' - '.join(tr(place, 'paikat') for place in sign), + night = is_night_time(datetime.combine(today(), time()) + trip.schedule[-1].arrival_time), tr = tr, - length = ajovuoro.length / 1000 - ) + length = trip.length / 1000 + ) @app.route('/static/<path:path>') def static_file(path):
--- a/templates/ajovuoro.html Tue Jun 20 09:39:42 2017 +0300 +++ b/templates/ajovuoro.html Tue Jun 20 10:27:52 2017 +0300 @@ -34,14 +34,14 @@ window.scrollTo(0, 0); } </script> - <title>{{linja}} {{selite}}</title> + <title>{{route}} {{description}}</title> </head> <body> <h1> - {% if yö %} + {% if night %} 🌙 {% endif %} - {{linja}} {{selite}}</h1> + {{route}} {{description}}</h1> <nav> <div class="tab-bar"> <span></span> @@ -57,10 +57,10 @@ <th class='sarake-aika'>Aika</th> <th class='sarake-alue'>Alue</th> </tr> - {% for rivi in suppea_reitti %} + {% for entry in concise_schedule %} <tr> - <td class='sarake-aika'>{{rivi['aika']}}</td> - <td class='sarake-alue'>{{rivi['alue']}}</td> + <td class='sarake-aika'>{{entry['time']}}</td> + <td class='sarake-alue'>{{entry['region']}}</td> </tr> {% endfor %} </table> @@ -72,14 +72,14 @@ <th class='sarake-pysäkkiviite'>Pysäkki</th> <th class='sarake-pysäkki'>Nimi</th> </tr> - {% for rivi in reitti %} + {% for halt in schedule %} <tr> - <td class='sarake-aika'>{{rivi['aika']}}</td> + <td class='sarake-aika'>{{halt['time']}}</td> <td class='sarake-pysäkkiviite'> - <a href="/pysäkki/{{rivi['tunniste']}}"><img src='/static/pysäkki.png' height='24' /> {{rivi['tunniste']}}</a> + <a href="/pysäkki/{{halt['reference']}}"><img src='/static/pysäkki.png' height='24' /> {{halt['reference']}}</a> </td> <td class='sarake-pysäkki'> - <a href="/pysäkki/{{rivi['tunniste']}}">{{rivi['nimi']}}</a> + <a href="/pysäkki/{{halt['reference']}}">{{halt['name']}}</a> </td> </tr> {% endfor %}
--- a/templates/cluster.html Tue Jun 20 09:39:42 2017 +0300 +++ b/templates/cluster.html Tue Jun 20 10:27:52 2017 +0300 @@ -26,9 +26,9 @@ </style> </head> <body> - <h1><img src="/static/iso-pysäkki.png" height="96" /> <span>{{nimi}}</span></h1> + <h1><img src="/static/iso-pysäkki.png" height="96" /> <span>{{name}}</span></h1> <p id="pysäkki-info"> - <a class="pysäkki-sijainti" href="{{linkki_karttaan}}" target="_blank">📌 ({{sijainti}})</a> + <a class="pysäkki-sijainti" href="{{linkki_karttaan}}" target="_blank">📌 ({{location}})</a> </p> <p>Pysäkit ryhmässä:</p> <div class="stops-in-cluster"> @@ -45,17 +45,17 @@ <th class='sarake-määränpää'>{{tr('destination', 'headings')}}</th> <th class='sarake-pysäkki'>{{tr('bus-stop', 'headings')}}</th> </tr> - {% for rivi in aikataulu %} - <tr class="{% if rivi['yö'] %} yö {% endif %}"> - <td class='sarake-aika'>{{rivi['aika']}}</td> + {% for halt in schedule %} + <tr class="{% if halt['night'] %} yö {% endif %}"> + <td class='sarake-aika'>{{halt['time']}}</td> <td class='sarake-linja linja'> - <a href="/ajovuoro/{{rivi['ajovuoro']}}">{{rivi['linja']}}</a> + <a href="/ajovuoro/{{halt['trip']}}">{{halt['route']}}</a> </td> <td class='sarake-määränpää'> - <a href="/ajovuoro/{{rivi['ajovuoro']}}">{{rivi['kyltti']}}</a> + <a href="/ajovuoro/{{halt['trip']}}">{{halt['sign']}}</a> </td> <td class='sarake-pysäkki'> - <a href="/pysäkki/{{rivi['stop_id']}}"><img src="/static/pysäkki.png" height="24" /> {{rivi['stop_id']}}</a> + <a href="/pysäkki/{{halt['stop_id']}}"><img src="/static/pysäkki.png" height="24" /> {{halt['stop_id']}}</a> </td> </tr> {% endfor %}
--- a/templates/pysäkki.html Tue Jun 20 09:39:42 2017 +0300 +++ b/templates/pysäkki.html Tue Jun 20 10:27:52 2017 +0300 @@ -21,11 +21,11 @@ {% endif %} </head> <body> - <h1><img src="/static/iso-pysäkki.png" height="96" /> <span>{{nimi}}</span></h1> + <h1><img src="/static/iso-pysäkki.png" height="96" /> <span>{{name}}</span></h1> <p id="pysäkki-info"> - {{alue or ""}} + {{region or ""}} — - <a class="pysäkki-sijainti" href="{{linkki_karttaan}}" target="_blank">📌 ({{sijainti}})</a> + <a class="pysäkki-sijainti" href="{{linkki_karttaan}}" target="_blank">📌 ({{location}})</a> </p> {% if cluster %} <p> @@ -38,14 +38,14 @@ <th class='sarake-linja'>{{tr('route', 'headings')}}</th> <th class='sarake-määränpää'>{{tr('destination', 'headings')}}</th> </tr> - {% for rivi in aikataulu %} - <tr class="{% if rivi['yö'] %} yö {% endif %}"> - <td class='sarake-aika'>{{rivi['aika']}}</td> + {% for halt in schedule %} + <tr class="{% if halt['night'] %} yö {% endif %}"> + <td class='sarake-aika'>{{halt['time']}}</td> <td class='sarake-linja linja'> - <a href="/ajovuoro/{{rivi['ajovuoro']}}">{{rivi['linja']}}</a> + <a href="/ajovuoro/{{halt['trip']}}">{{halt['route']}}</a> </td> <td class='sarake-määränpää'> - <a href="/ajovuoro/{{rivi['ajovuoro']}}">{{rivi['kyltti']}}</a> + <a href="/ajovuoro/{{halt['trip']}}">{{halt['sign']}}</a> </td> </tr> {% endfor %}