Yhtenäistetty ohjelmakoodin kieli englanniksi

Tue, 20 Jun 2017 10:27:52 +0300

author
Teemu Piippo <teemu@hecknology.net>
date
Tue, 20 Jun 2017 10:27:52 +0300
changeset 24
e6bdb9c54096
parent 23
3a495bc4b7b5
child 25
cb423946cf33

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 %}
 	&#127769;
 	{% 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 %}

mercurial