Lisää päivityksiä

Sat, 24 Jun 2017 19:38:05 +0300

author
Teemu Piippo <teemu@hecknology.net>
date
Sat, 24 Jun 2017 19:38:05 +0300
changeset 30
a5bfd99bc2a3
parent 29
2c78e68d7363
child 31
60045b362d71

Lisää päivityksiä

.hgignore file | annotate | diff | comparison | revisions
buses.py file | annotate | diff | comparison | revisions
profiles/föli.cfg file | annotate | diff | comparison | revisions
profiles/föli.ini file | annotate | diff | comparison | revisions
profiles/hsl.cfg file | annotate | diff | comparison | revisions
profiles/hsl.ini file | annotate | diff | comparison | revisions
region-representatives.json file | annotate | diff | comparison | revisions
regions.gmp file | annotate | diff | comparison | revisions
service.py file | annotate | diff | comparison | revisions
static/style.css file | annotate | diff | comparison | revisions
templates/cluster.html file | annotate | diff | comparison | revisions
tr/en.ini file | annotate | diff | comparison | revisions
tr/fi.ini file | annotate | diff | comparison | revisions
tr/sv.ini file | annotate | diff | comparison | revisions
--- a/.hgignore	Thu Jun 22 19:01:31 2017 +0300
+++ b/.hgignore	Sat Jun 24 19:38:05 2017 +0300
@@ -1,5 +1,5 @@
 syntax:glob
 __pycache__
 gtfs
-gtfs.zip
+gtfs*.zip
 regions-per-stop.json
--- a/buses.py	Thu Jun 22 19:01:31 2017 +0300
+++ b/buses.py	Sat Jun 24 19:38:05 2017 +0300
@@ -154,294 +154,314 @@
 services = {}
 bus_stops = {}
 all_clusters = set()
-
-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 ajovuorot... ', file = stderr, end = '', flush = True)
-
-shape_distances = {}
-with open('gtfs/shapes.txt') as 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 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']]
-		)
-		route.trips.add(trip)
-		assert trip.name not in all_trips
-		all_trips[trip.name] = trip
-print('%d ajoa' % len(all_trips), file = stderr)
-
-def read_date(teksti):
-	return date(int(teksti[:4]), int(teksti[4:6]), int(teksti[6:]))
-
-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()
+viimeinen_käyttöpäivä = None
+clusters_by_name = {}
 services_for_day = {}
 
-def date_range(start_date, end_date, *, include_end = False):
-	''' Generates date from start_date to end_date. If include_end is True, then end_date will be yielded. '''
-	current_date = start_date
-	while current_date < end_date:
-		yield current_date
-		current_date += timedelta(1)
-	if include_end:
-		yield end_date
+def load_buses(gtfs_zip_path, profile):
+	global viimeinen_käyttöpäivä
+	from zipfile import ZipFile
+	with ZipFile(gtfs_zip_path) as gtfs_zip:
+		print('Ladataan linjat... ', file = stderr, end = '', flush = True)
+		with gtfs_zip.open('routes.txt') as file:
+			for row in read_csv(map(bytes.decode, file)):
+				route = BusRoute(row)
+				routes[route.reference] = route
+				routes_per_id[route.id] = route
+		print('%d linjaa' % len(routes), file = stderr)
+
+		print('Ladataan ajovuorot... ', file = stderr, end = '', flush = True)
+
+		shape_distances = {}
+		with gtfs_zip.open('shapes.txt') as file:
+			for row in read_csv(map(bytes.decode, file)):
+				shape_distances[row['shape_id']] = max(shape_distances.get(row['shape_id'], 0), float(row['shape_dist_traveled']))
 
-def add_day_to_service(service_name, day):
-	try:
-		service = services[service_name]
-	except KeyError:
-		return
-	else:
-		service.dates.add(day)
-		if day not in services_for_day:
-			services_for_day[day] = set()
-		services_for_day[day].add(service)
-		global viimeinen_käyttöpäivä
-		viimeinen_käyttöpäivä = max(day, viimeinen_käyttöpäivä)
+		with gtfs_zip.open('trips.txt') as file:
+			for row in read_csv(map(bytes.decode, 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']] * float(profile['metrics']['shape-modifier'])
+				)
+				route.trips.add(trip)
+				assert trip.name not in all_trips
+				all_trips[trip.name] = trip
+		print('%d ajoa' % len(all_trips), file = stderr)
 
-def filter_day(row, day):
-	day_names = ['monday', 'tuesday', 'wednesday', 'thursday', 'friday', 'saturday', 'sunday']
-	return int(row[day_names[day.isoweekday() - 1]])
+		def read_date(teksti):
+			return date(int(teksti[:4]), int(teksti[4:6]), int(teksti[6:]))
+
+		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()
 
-with open('gtfs/calendar.txt') as file:
-	for row in read_csv(file):
-		for day in date_range(read_date(row['start_date']), read_date(row['end_date']), include_end = True):
-			if filter_day(row, day):
-				add_day_to_service(service_name = row['service_id'], day = day)
+		def date_range(start_date, end_date, *, include_end = False):
+			''' Generates date from start_date to end_date. If include_end is True, then end_date will be yielded. '''
+			current_date = start_date
+			while current_date < end_date:
+				yield current_date
+				current_date += timedelta(1)
+			if include_end:
+				yield end_date
 
-with open('gtfs/calendar_dates.txt') as file:
-	for row in read_csv(file):
-		add_day_to_service(service_name = row['service_id'], day = read_date(row['date']))
+		def add_day_to_service(service_name, day):
+			try:
+				service = services[service_name]
+			except KeyError:
+				return
+			else:
+				service.dates.add(day)
+				if day not in services_for_day:
+					services_for_day[day] = set()
+				services_for_day[day].add(service)
+				global viimeinen_käyttöpäivä
+				viimeinen_käyttöpäivä = max(day, viimeinen_käyttöpäivä)
 
-def services_available_at(day):
-	for service in services.values():
-		if day in service.dates:
-			yield service
+		def filter_day(row, day):
+			day_names = ['monday', 'tuesday', 'wednesday', 'thursday', 'friday', 'saturday', 'sunday']
+			return int(row[day_names[day.isoweekday() - 1]])
 
-print('Ladataan pysäkit... ', file = stderr, end = '', flush = True)
-with open('gtfs/stops.txt') as file:
-	for row in read_csv(file):
-		location = Sijainti(float(row['stop_lat']), float(row['stop_lon']))
-		stop = BusStop(
-			reference = row['stop_id'],
-			name = row['stop_name'],
-			location = location, 
-			code = row['stop_code'],
-		)
-		bus_stops[stop.reference] = stop
-with open('regions-per-stop.json') as file:
-	for stop_reference, region in json.load(file).items():
-		bus_stops[stop_reference].region = region
-print('%d pysäkkiä' % len(bus_stops), file = stderr)
+		with gtfs_zip.open('calendar.txt') as file:
+			for row in read_csv(map(bytes.decode, file)):
+				for day in date_range(read_date(row['start_date']), read_date(row['end_date']), include_end = True):
+					if filter_day(row, day):
+						add_day_to_service(service_name = row['service_id'], day = day)
+
+		with gtfs_zip.open('calendar_dates.txt') as file:
+			for row in read_csv(map(bytes.decode, file)):
+				add_day_to_service(service_name = row['service_id'], day = read_date(row['date']))
+
+		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 gtfs_zip.open('stops.txt') as file:
+			for row in read_csv(map(bytes.decode, file)):
+				location = Sijainti(float(row['stop_lat']), float(row['stop_lon']))
+				stop = BusStop(
+					reference = row['stop_id'],
+					name = row['stop_name'],
+					location = location, 
+					code = row['stop_code'],
+				)
+				bus_stops[stop.reference] = stop
+		with open('regions-per-stop.json') as file:
+			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:
-	def __init__(self):
-		self.stops = set()
-		self._center = None
-		self.name = None
-	@property
-	def url_name(self):
-		return self.name.lower().replace('(', '').replace(')', '').replace(' ', '-')
-	def add_stop(self, stop):
-		assert not stop.cluster
-		stop.cluster = self
-		self.stops.add(stop)
-		self._center = None
-	@property
-	def center(self):
-		if not self._center:
-			if self.stops:
-				from statistics import median
-				pointtype = type(next(iter(self.stops)).location)
-				self._center = pointtype(
-					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')
-		return self._center
-	def merge(self, other):
-		for bus_stop in other.stops:
-			bus_stop.cluster = self
-		self.stops |= other.stops
-		other.stops = set()
-		other._center = None
-	def schedule(self, max_amount = 50):
-		result = []
-		for stop in self.stops:
-			result += stop.schedule(max_amount)
-		result.sort(key = lambda schedule_entry: schedule_entry['time'])
-		return result[:max_amount]
+		class BusStopCluster:
+			def __init__(self):
+				self.stops = set()
+				self._center = None
+				self.name = None
+			@property
+			def url_name(self):
+				return self.name.lower().replace('(', '').replace(')', '').replace(' ', '-')
+			def add_stop(self, stop):
+				assert not stop.cluster
+				stop.cluster = self
+				self.stops.add(stop)
+				self._center = None
+			@property
+			def center(self):
+				if not self._center:
+					if self.stops:
+						from statistics import median
+						pointtype = type(next(iter(self.stops)).location)
+						self._center = pointtype(
+							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')
+				return self._center
+			def merge(self, other):
+				for bus_stop in other.stops:
+					bus_stop.cluster = self
+				self.stops |= other.stops
+				other.stops = set()
+				other._center = None
+			def schedule(self, max_amount = 50):
+				result = []
+				for stop in self.stops:
+					result += stop.schedule(max_amount)
+				result.sort(key = lambda schedule_entry: schedule_entry['time'])
+				return result[:max_amount]
 
-from collections import defaultdict
-bus_stops_by_name = defaultdict(set)
-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)
+		from collections import defaultdict
+		bus_stops_by_name = defaultdict(set)
+		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 bus_stops nimen mukaan
-all_clusters = []
-def cluster_bus_stops():
-	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.name]:
-				distance = pair_candidate.location.etäisyys(bus_stop.location)
-				if pair_candidate is not bus_stop and distance <= 0.4:
-					stops_to_cluster.add(pair_candidate)
-			for stop_to_cluster in stops_to_cluster:
-				if stop_to_cluster.cluster:
-					cluster = stop_to_cluster.cluster
-					break
-			else:
-				cluster = BusStopCluster()
-				all_clusters.append(cluster)
-			for stop_to_cluster in stops_to_cluster:
-				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 bus_stops.values():
-		if bus_stop.cluster:
-			bus_stop.pairs = bus_stop.cluster.stops - {bus_stop}
-	# 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.location)
-					if distance <= 0.4:
-						possibilities.add((distance, cluster))
-			if possibilities:
-				best = min(possibilities)[1]
-				all_clusters.remove(bus_stop.cluster)
-				best.merge(bus_stop.cluster)
+		# ryhmittele bus_stops nimen mukaan
+		all_clusters = []
+		def cluster_bus_stops():
+			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.name]:
+						distance = pair_candidate.location.etäisyys(bus_stop.location)
+						if pair_candidate is not bus_stop and distance <= 0.4:
+							stops_to_cluster.add(pair_candidate)
+					for stop_to_cluster in stops_to_cluster:
+						if stop_to_cluster.cluster:
+							cluster = stop_to_cluster.cluster
+							break
+					else:
+						cluster = BusStopCluster()
+						all_clusters.append(cluster)
+					for stop_to_cluster in stops_to_cluster:
+						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 bus_stops.values():
+				if bus_stop.cluster:
+					bus_stop.pairs = bus_stop.cluster.stops - {bus_stop}
+			# 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.location)
+							if distance <= 0.4:
+								possibilities.add((distance, cluster))
+					if possibilities:
+						best = min(possibilities)[1]
+						all_clusters.remove(bus_stop.cluster)
+						best.merge(bus_stop.cluster)
 
-def shared_elements_in_n_sets(sets):
-	from itertools import combinations
-	result = set()
-	for pair in combinations(sets, 2):
-		result |= pair[0] & pair[1]
-	return result
+		def shared_elements_in_n_sets(sets):
+			from itertools import combinations
+			result = set()
+			for pair in combinations(sets, 2):
+				result |= pair[0] & pair[1]
+			return result
 
-def name_clusters():
-	from collections import defaultdict
-	from pprint import pprint
-	clusters_per_name = defaultdict(set)
-	for cluster in all_clusters:
-		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.region for stop in cluster.stops} for cluster in clusters)
-			# Esitys: ryhmä -> ne alueet jotka ovat tälle ryhmälle ainutlaatuisia
-			proposal = {
-				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ä,
-			# niin nimetään klusterit näiden alueiden mukaan.
-			# Se klusteri jolla ei ole omaa aluetta (jos on) jätetään ilman aluepäätettä.
-			if sum([1 for unique_areas in proposal.values() if not unique_areas]) <= 1:
-				for cluster, unique_areas in proposal.items():
-					individual_cluster_name = name
-					if unique_areas:
-						individual_cluster_name += ' (' + min(unique_areas) + ')'
-					cluster.name = individual_cluster_name
+		def name_clusters():
+			from collections import defaultdict
+			from pprint import pprint
+			clusters_per_name = defaultdict(set)
+			for cluster in all_clusters:
+				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.region for stop in cluster.stops} for cluster in clusters)
+					# Esitys: ryhmä -> ne alueet jotka ovat tälle ryhmälle ainutlaatuisia
+					proposal = {
+						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ä,
+					# niin nimetään klusterit näiden alueiden mukaan.
+					# Se klusteri jolla ei ole omaa aluetta (jos on) jätetään ilman aluepäätettä.
+					if sum([1 for unique_areas in proposal.values() if not unique_areas]) <= 1:
+						for cluster, unique_areas in proposal.items():
+							individual_cluster_name = name
+							if unique_areas:
+								individual_cluster_name += ' (' + min(unique_areas) + ')'
+							cluster.name = individual_cluster_name
+					else:
+						# Typerä reunatapaus. Indeksoidaan numeroin...
+						for n, (_, cluster) in enumerate(sorted(
+							min((stop.reference.lower(), cluster) for stop in cluster.stops)
+							for cluster in clusters
+						), 1):
+							individual_cluster_name = name + '-' + str(n)
+							cluster.name = individual_cluster_name
+
+		print('Ryhmitellään pysäkit...')
+		cluster_bus_stops()
+		name_clusters()
+
+		for cluster in all_clusters:
+			if cluster.url_name in clusters_by_name:
+				print('Warning: Clusters %r and %r share the same URL name: %r' % (cluster.name, clusters_by_name[cluster.url_name].name, cluster.url_name))
 			else:
-				# Typerä reunatapaus. Indeksoidaan numeroin...
-				for n, (_, cluster) in enumerate(sorted(
-					min((stop.reference.lower(), cluster) for stop in cluster.stops)
-					for cluster in clusters
-				), 1):
-					individual_cluster_name = name + '-' + str(n)
-					cluster.name = individual_cluster_name
-
-print('Ryhmitellään pysäkit...')
-cluster_bus_stops()
-name_clusters()
-
-clusters_by_name = {}
-for cluster in all_clusters:
-	if cluster.url_name in clusters_by_name:
-		print('Warning: Clusters %r and %r share the same URL name: %r' % (cluster.name, clusters_by_name[cluster.url_name].name, cluster.url_name))
-	else:
-		clusters_by_name[cluster.url_name] = cluster
+				clusters_by_name[cluster.url_name] = cluster
 
-print('Ladataan aikataulut... ', end = '', flush = True, file = stderr)
-with open('gtfs/stop_times.txt') as file:
-	row_count = sum(line.count('\n') for line in file)
-	progress = 0
-	file.seek(0)
-	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))
-		stop.involved_trips.add(trip)
-		progress += 1
-		if progress % 1000 == 0:
-			print('\rLadataan aikataulut... %.1f%%' % (progress * 100 / row_count), end = ' ', file = stderr)
-print('\rLadataan aikataulut... ladattu', file = stderr)
+		print('Ladataan aikataulut... ', end = '', flush = True, file = stderr)
+		with gtfs_zip.open('stop_times.txt') as file:
+			row_count = sum(line.count(b'\n') for line in file)
+		with gtfs_zip.open('stop_times.txt') as file:
+			progress = 0
+			for row in read_csv(map(bytes.decode, 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']) * float(profile['metrics']['shape-modifier'])
+				trip.schedule.append(BusHalt(arrival_time, departure_time, stop, trip, traveled_distance))
+				stop.involved_trips.add(trip)
+				progress += 1
+				if progress % 1000 == 0:
+					print('\rLadataan aikataulut... %.1f%%' % (progress * 100 / row_count), end = ' ', file = stderr)
+		print('\rLadataan aikataulut... ladattu', file = stderr)
+
+		for trip in all_trips.values():
+			from busroute import simplify_name
+			schedule = trip.concise_schedule()
+			try:
+				trip.from_place = simplify_name(schedule[0])
+				trip.to_place = simplify_name(schedule[-1])
+			except IndexError:
+				trip.from_place = ''
+				trip.to_place = ''
 
-for trip in all_trips.values():
-	from busroute import simplify_name
-	schedule = trip.concise_schedule()
-	try:
-		trip.from_place = simplify_name(schedule[0])
-		trip.to_place = simplify_name(schedule[-1])
-	except IndexError:
-		trip.from_place = ''
-		trip.to_place = ''
+		for route in routes.values():
+			from collections import Counter
+			from busroute import simplify_name
+			tally = Counter()
+			for trip in route.trips:
+				schedule = trip.concise_schedule()
+				places = set(schedule)
+				do_add = True
+				assert type(schedule) is list
+				for candidate in tally:
+					if places.issubset(set(candidate)):
+						do_add = False
+						tally.update({tuple(candidate)})
+				if do_add:
+					tally.update({tuple(schedule)})
+			try:
+				most_common_route = tally.most_common(1)[0][0]
+				route.description = simplify_name(most_common_route[0]) + ' - ' + simplify_name(most_common_route[-1])
+			except:
+				route.description = ''
+			route.trips = sorted(route.trips, key = lambda trip: trip.schedule[0].departure_time)
 
-for route in routes.values():
-	from collections import Counter
-	from busroute import simplify_name
-	tally = Counter()
-	for trip in route.trips:
-		schedule = trip.concise_schedule()
-		places = set(schedule)
-		do_add = True
-		assert type(schedule) is list
-		for candidate in tally:
-			if places.issubset(set(candidate)):
-				do_add = False
-				tally.update({tuple(candidate)})
-		if do_add:
-			tally.update({tuple(schedule)})
-	try:
-		most_common_route = tally.most_common(1)[0][0]
-		route.description = simplify_name(most_common_route[0]) + ' - ' + simplify_name(most_common_route[-1])
-	except:
-		route.description = ''
-	route.trips = sorted(route.trips, key = lambda trip: trip.schedule[0].departure_time)
+		# Fölin datassa on jotain tosi kummaa. Ilmeisesti ajovuoron viimeisen pysähdyksen saapumisaika on ihan täysin
+		# väärin. Arvaan että se on seuraavan lähdön aika, mutta joka tapauksessa se on väärin.
+		# Arvataan mikä se todellinen saapumisaika on. Se ei voi mennä kauhean paljon pahemmin vikaan kuin alkuperäinen
+		# väärin oleva data.
+		for trip in all_trips.values():
+			bus_speed_coefficient = 750 # metriä minuutissa
+			last_leg_distance = trip.schedule[-1].traveled_distance - trip.schedule[-2].traveled_distance
+			trip.schedule[-1].arrival_time = trip.schedule[-2].departure_time + timedelta(minutes = last_leg_distance / bus_speed_coefficient)
+
+if __name__ == '__main__':
+	from configparser import ConfigParser
+	profile = ConfigParser()
+	profile.read('profiles/föli.ini')
+	load_buses('gtfs.zip', profile)
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/profiles/föli.ini	Sat Jun 24 19:38:05 2017 +0300
@@ -0,0 +1,2 @@
+[metrics]
+shape-modifier = 1
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/profiles/hsl.ini	Sat Jun 24 19:38:05 2017 +0300
@@ -0,0 +1,6 @@
+[metrics]
+shape-modifier = 1000
+
+[service-patterns]
+light-rail = [A-Z]
+metro = M\d+
--- a/region-representatives.json	Thu Jun 22 19:01:31 2017 +0300
+++ b/region-representatives.json	Sat Jun 24 19:38:05 2017 +0300
@@ -89,6 +89,7 @@
 	"Kaivola": "3229",
 	"Kakskerta": "1764",
 	"Kastu": "627",
+	"Kasarmialue": "1797",
 	"Katariina": "516",
 	"Kauppatori": "T1",
 	"Kerttuli": "867",
@@ -111,6 +112,7 @@
 	"Lauste": "708",
 	"Lauttaranta": "128",
 	"Lentoasema": "1586",
+	"Lehmusvalkama": "1802",
 	"Liljalaakso": "592",
 	"Linja-autoasema": "41",
 	"Linnavuori": "3122",
@@ -201,7 +203,7 @@
 	"Velusmaa": "3287",
 	"Vähä-Heikkilä": "944",
 	"Yli-Maaria": "396",
-	"Yliopistonmäki": "1797",
+	"Yliopisto": "65",
 	"Ylioppilaskylä-Länsi": "445",
 	"Ylioppilaskylä-Itä": "448",
 	"Meripirtti": "3179",
--- a/regions.gmp	Thu Jun 22 19:01:31 2017 +0300
+++ b/regions.gmp	Sat Jun 24 19:38:05 2017 +0300
@@ -1,9 +1,8 @@
-roadmap^60.407187649046506, 22.331562262097012^16@undefined^#FF0000,5,1,#ff8800,0.4^^60.39482,22.2577~60.40037,22.27521~60.39147,22.28456~60.38851,22.27179
-polygon^#FF0000,5,1,#ff8800,0.4^^60.3977,22.24465~60.40772,22.24046~60.40792,22.26057~60.40076,22.27409~60.3952,22.25642
+roadmap^60.435782981983806, 22.304026704350125^15@polygon^#FF0000,5,1,#ff8800,0.4^^60.3977,22.24465~60.40772,22.24046~60.40792,22.26057~60.40076,22.27409~60.3952,22.25642
 polygon^#FF0000,5,1,#ff8800,0.4^^60.3977,22.22706~60.38719,22.23676~60.38341,22.22466~60.38518,22.21205~60.39359,22.20706~60.39816,22.21341
-undefined^#FF0000,5,1,#ff8800,0.4^^60.39868,22.21599~60.39804,22.22852~60.38876,22.23916~60.38952,22.24843~60.39329,22.25135~60.40217,22.23474~60.4019,22.21689
-undefined^#FF0000,5,1,#ff8800,0.4^^60.38871,22.16432~60.39236,22.18483~60.39972,22.188~60.40171,22.1823~60.39919,22.15413~60.39151,22.15633
-undefined^#FF0000,5,1,#ff8800,0.4^^60.42123,22.17264~60.41472,22.15422~60.40622,22.14277~60.40003,22.15599~60.40577,22.17143~60.41357,22.18174
+polygon^#FF0000,5,1,#ff8800,0.4^^60.39868,22.21599~60.39804,22.22852~60.38876,22.23916~60.38952,22.24843~60.39329,22.25135~60.40217,22.23474~60.4019,22.21689
+polygon^#FF0000,5,1,#ff8800,0.4^^60.38871,22.16432~60.39236,22.18483~60.39972,22.188~60.40171,22.1823~60.39919,22.15413~60.39151,22.15633
+polygon^#FF0000,5,1,#ff8800,0.4^^60.42123,22.17264~60.41472,22.15422~60.40622,22.14277~60.40003,22.15599~60.40577,22.17143~60.41357,22.18174
 undefined^#FF0000,5,1,#ff8800,0.4^^60.43275,22.20869~60.4239,22.21632~60.41889,22.19831~60.42495,22.19216~60.43033,22.19648
 undefined^#FF0000,5,1,#ff8800,0.4^^60.42148,22.17333~60.42584,22.18964~60.41902,22.1971~60.41534,22.18056
 undefined^#FF0000,5,1,#ff8800,0.4^^60.49702,22.21496~60.49936,22.22028~60.50048,22.23744~60.49676,22.2392~60.49373,22.23435~60.49306,22.23554~60.49178,22.23402~60.48872,22.22903~60.48729,22.2221~60.49085,22.21505
@@ -65,7 +64,6 @@
 undefined^#FF0000,5,1,#ff8800,0.4^^60.48122,22.18547~60.47853,22.19719~60.47645,22.20365~60.47299,22.19482
 undefined^#FF0000,5,1,#ff8800,0.4^^60.49404,22.26856~60.49248,22.28712~60.48973,22.30216~60.48271,22.29808~60.47627,22.28925~60.47537,22.27866~60.47564,22.26596~60.48309,22.27138~60.48695,22.27301
 undefined^#FF0000,5,1,#ff8800,0.4^^60.43241,22.32263~60.4233,22.33585~60.42025,22.32107~60.42818,22.31196~60.43215,22.31439~60.43258,22.31667
-undefined^#FF0000,5,1,#ff8800,0.4^^60.4422,22.29864~60.43473,22.30327~60.43438,22.32316~60.43887,22.3172~60.44454,22.30841
 undefined^#FF0000,5,1,#ff8800,0.4^^60.42961,22.26392~60.4343,22.28456~60.43499,22.29225~60.43245,22.29564~60.42894,22.29349~60.42506,22.27929~60.42531,22.26699
 undefined^#FF0000,5,1,#ff8800,0.4^^60.42521,22.26577~60.42195,22.26877~60.41579,22.26733~60.41625,22.27877~60.42052,22.2783~60.42469,22.2762
 undefined^#FF0000,5,1,#ff8800,0.4^^60.42288,22.24603~60.42536,22.26349~60.4219,22.26787~60.4157,22.26641~60.41536,22.2713~60.40943,22.26612~60.41382,22.25849
@@ -220,7 +218,6 @@
 undefined^#FF0000,5,1,#ff8800,0.4^^60.57752,22.31255~60.56217,22.37125~60.59623,22.42342~60.63292,22.45499~60.65058,22.46426~60.65584,22.36526~60.63174,22.35328~60.59817,22.33307~60.58865,22.32559
 undefined^#FF0000,5,1,#ff8800,0.4^^60.44566,22.32894~60.44287,22.33761~60.44321,22.34782~60.44984,22.34589~60.45284,22.34048~60.45192,22.3356~60.44968,22.33216
 undefined^#FF0000,5,1,#ff8800,0.4^^60.44342,22.35014~60.44365,22.36593~60.45433,22.36259~60.4541,22.34633
-undefined^#FF0000,5,1,#ff8800,0.4^^60.45325,22.27598~60.45286,22.27984~60.45163,22.28199~60.45289,22.28662~60.45341,22.28622~60.45409,22.28811~60.4547,22.29174~60.4581,22.28897~60.45934,22.28666~60.45933,22.28296~60.45869,22.27967~60.4577,22.27773
 undefined^#FF0000,5,1,#ff8800,0.4^^59.98508,22.40559~59.96861,22.46199~59.90874,22.41863~59.91541,22.38107
 undefined^#FF0000,5,1,#ff8800,0.4^^60.18318,22.75921~60.16474,22.7853~60.14331,22.70053~60.1664,22.67258~60.18195,22.7181
 undefined^#FF0000,5,1,#ff8800,0.4^^60.36619,22.69037~60.35583,22.75114~60.30695,22.70246~60.32315,22.59175~60.359,22.64749
@@ -228,4 +225,9 @@
 undefined^#FF0000,5,1,#ff8800,0.4^^60.47333,22.01433~60.47077,22.02031~60.46786,22.01456~60.47116,22.00777
 undefined^#FF0000,5,1,#ff8800,0.4^^60.41684,22.28808~60.41429,22.28833~60.41165,22.288~60.40885,22.28525~60.40811,22.2759~60.40868,22.27027~60.41208,22.2701~60.4155,22.27276
 undefined^#FF0000,5,1,#ff8800,0.4^^60.40938,22.31293~60.40543,22.32123~60.4055,22.33379~60.40597,22.34177~60.40995,22.3419~60.41107,22.32617
+undefined^#FF0000,5,1,#ff8800,0.4^^60.39509,22.25658~60.40071,22.27487~60.39686,22.28173~60.38912,22.27937~60.39126,22.2647
+undefined^#FF0000,5,1,#ff8800,0.4^^60.45149,22.28207~60.45296,22.28687~60.45331,22.2863~60.45396,22.28784~60.4547,22.2914~60.45718,22.28964~60.45899,22.28822~60.45989,22.28723~60.46017,22.28231~60.45966,22.28067~60.45865,22.28021~60.45842,22.28321~60.45696,22.28289~60.4546,22.28215~60.45266,22.28019
+undefined^#FF0000,5,1,#ff8800,0.4^^60.45324,22.27609~60.45293,22.27966~60.45275,22.28014~60.45463,22.28203~60.45742,22.28287~60.45835,22.28304~60.45865,22.27854
+undefined^#FF0000,5,1,#ff8800,0.4^^60.44228,22.29847~60.44443,22.30868~60.43893,22.31718~60.43753,22.31481~60.43776,22.31424~60.43729,22.31155~60.43557,22.31207~60.43555,22.31654~60.43732,22.31509~60.4388,22.31754~60.43444,22.32246~60.43402,22.30368
+undefined^#FF0000,5,1,#ff8800,0.4^^60.43727,22.31164~60.4377,22.3142~60.43561,22.31633~60.43565,22.31219
 @@@@@@
--- a/service.py	Thu Jun 22 19:01:31 2017 +0300
+++ b/service.py	Sat Jun 24 19:38:05 2017 +0300
@@ -7,6 +7,7 @@
 
 from misc import *
 from busroute import reduce_schedule
+import buses
 
 app = Flask(__name__)
 
@@ -220,10 +221,19 @@
 def static_file(path):
 	return send_from_directory(path.join('static', path))
 
+from argparse import ArgumentParser
+parser = ArgumentParser()
+parser.add_argument('gtfs_zip_path')
+parser.add_argument('profile_path')
+
 if __name__ == '__main__':
-	from argparse import ArgumentParser
-	parser = ArgumentParser()
 	parser.add_argument('-p', '--port', type = int, default = 5000)
 	parser.add_argument('-d', '--debug', action = 'store_true')
-	args = parser.parse_args()
+
+args = parser.parse_args()
+profile = ConfigParser()
+profile.read(args.profile_path)
+buses.load_buses(args.gtfs_zip_path, profile = profile)
+
+if __name__ == '__main__':
 	app.run(debug = args.debug, port = args.port)
--- a/static/style.css	Thu Jun 22 19:01:31 2017 +0300
+++ b/static/style.css	Sat Jun 24 19:38:05 2017 +0300
@@ -1,6 +1,7 @@
 *
 {
 	font-family: FreeSans, helvetica, Arial, sans-serif;
+	font-weight: light;
 }
 body
 {
@@ -44,7 +45,8 @@
 {
 	margin: auto;
 	margin-bottom: 30pt;
-	min-width: 50%;
+	/*min-width: 50%;*/
+	min-width: 85%;
 	border: 1px solid gray;
 }
 
--- a/templates/cluster.html	Thu Jun 22 19:01:31 2017 +0300
+++ b/templates/cluster.html	Sat Jun 24 19:38:05 2017 +0300
@@ -22,6 +22,10 @@
 	{
 		text-align: center;
 	}
+	.sarake-määränpää
+	{
+		text-align: left;
+	}
 	/*
 	body
 	{
--- a/tr/en.ini	Thu Jun 22 19:01:31 2017 +0300
+++ b/tr/en.ini	Sat Jun 24 19:38:05 2017 +0300
@@ -1,14 +1,16 @@
 [paikat]
-satama = ⚓ Harbor
-kauppatori = City Centre
-lentoasema = ✈ Airport
-ylioppilaskylä = Student Village
+satama = Harbor ⚓
+kauppatori = City centre
+lentoasema = Airport ✈
+ylioppilaskylä = Student village
 kauppakeskus mylly = Mylly
-raision keskusta = Raisio Centrum
-naantalin keskusta = Naantali Centrum
-linja-autoasema = Bus Station
-rautatieasema = Railway Station
+raision keskusta = Raisio
+naantalin keskusta = Naantali
+linja-autoasema = Bus station
+rautatieasema = Railway station
 kaarinan keskusta = Kaarina
+pernon telakka = Perno docks
+kasarmialue = Barracks area
 
 [misc-text]
 right-now = now
--- a/tr/fi.ini	Thu Jun 22 19:01:31 2017 +0300
+++ b/tr/fi.ini	Sat Jun 24 19:38:05 2017 +0300
@@ -1,8 +1,8 @@
 [paikat]
 Korpo = Korppoo
 Pargas = Parainen
-lentoasema = ✈ Lentoasema
-satama = ⚓ Satama
+lentoasema = Lentoasema ✈
+satama = Satama ⚓
 kauppakeskus mylly = Mylly
 kaarinan keskusta = Kaarina
 naantalin keskusta = Naantali
--- a/tr/sv.ini	Thu Jun 22 19:01:31 2017 +0300
+++ b/tr/sv.ini	Sat Jun 24 19:38:05 2017 +0300
@@ -1651,6 +1651,7 @@
 kaistarniemi = Kaistarudden
 katariina = Katarina
 kauppatori = Salutorget
+kasarmialue = Kasernen
 kerttuli = Gertrudsbacken
 koivula = Björkas
 konserttitalo = Konserthuset
@@ -1665,7 +1666,7 @@
 isokylä = Storby
 lauste = Laustis
 lauttaranta = Färjstranden
-lentoasema = ✈ Flygplatsen
+lentoasema = Flygplatsen ✈
 liljalaakso = Liljedalen
 linja-autoasema = Linjebilstationen
 littoinen = Littois
@@ -1696,7 +1697,7 @@
 ruskon keskusta = Rusko centrum
 rymättylä = Rimito
 saramäki = Starrbacka
-satama = ⚓ Hamnen
+satama = Hamnen ⚓
 skanssi = Skansen
 toijainen = Toijais
 tuomiokirkkotori = Domkyrkotorget
@@ -1709,7 +1710,7 @@
 vii kaupunginosa = VII stadsdelen
 vähä-heikkilä = Lillheikkilä
 yli-maaria = Övre S:t Marie
-yliopistonmäki = Universitetsbacken
+yliopisto = Universitet
 ylioppilaskylä = Studentbyn
 ylioppilaskylä-länsi = Västra Studentbyn
 ylioppilaskylä-itä = Östra Studentbyn

mercurial