diff --git a/osm2gtfs/core/debug.py b/osm2gtfs/core/debug.py new file mode 100644 index 00000000..d00ec9f4 --- /dev/null +++ b/osm2gtfs/core/debug.py @@ -0,0 +1,23 @@ +# coding=utf-8 + + +class Debug(object): + """The Debug class contains special functions used for debugging + + """ + + @staticmethod + def print_shape_for_leaflet(shape): + print "var shape = [", + i = 0 + for node in shape: + print "new L.LatLng(" + str(node["lat"]) + ", " + str(node["lon"]) + ")", + if i != len(shape) - 1: + print ",", + i += 1 + print "];" + i = 0 + for node in shape: + print "L.marker([" + str(node["lat"]) + ", " + str(node["lon"]) + "]).addTo(map)" + print " .bindPopup(\"" + str(i) + "\").openPopup();" + i += 1 diff --git a/osm2gtfs/core/osm_connector.py b/osm2gtfs/core/osm_connector.py index 1fe81d75..5e62d352 100644 --- a/osm2gtfs/core/osm_connector.py +++ b/osm2gtfs/core/osm_connector.py @@ -3,10 +3,11 @@ import sys import overpy from collections import OrderedDict +from math import cos, sin, atan2, sqrt, radians, degrees from transitfeed import util from osm2gtfs.core.cache import Cache -from osm2gtfs.core.osm_routes import Route, RouteMaster -from osm2gtfs.core.osm_stops import Stop, StopArea +from osm2gtfs.core.routes import Itinerary, Line +from osm2gtfs.core.stops import Stop, StopArea class OsmConnector(object): @@ -41,12 +42,12 @@ def __init__(self, config): # tags from config file for querying self.tags = '' for key, value in config["query"].get("tags", {}).iteritems(): - self.tags += str('["' + key + '" = "' + value + '"]') + self.tags += unicode('["' + key + '" = "' + value + '"]') if not self.tags: # fallback self.tags = '["public_transport:version" = "2"]' print("No tags found for querying from OpenStreetMap.") - print("Using tag 'public_transport:version=2") + print("Using tag 'public_transport:version=2'") # Define name for stops without one self.stop_no_name = 'No name' @@ -84,8 +85,8 @@ def get_routes(self, refresh=False): Data about routes is getting obtained from OpenStreetMap through the Overpass API, based on the configuration from the config file. - Then this data gets prepared by building up objects of RouteMaster and - RouteVariant objects that are related to each other. + Then this data gets prepared by building up objects of Line and + Itinerary objects that are related to each other. It uses caching to leverage fast performance and spare the Overpass API. Special commands are used to refresh cached data. @@ -94,8 +95,8 @@ def get_routes(self, refresh=False): :param refresh: A simple boolean indicating a data refresh or use of caching if possible. - :return routes: A dictionary of RouteMaster objects with related - RouteVariant objects constituting the tree of data. + :return routes: A dictionary of Line objects with related + Itinerary objects constituting the tree of data. """ # Preferably return cached data about routes @@ -125,53 +126,62 @@ def get_routes(self, refresh=False): # Build routes from master relations for rmid, route_master in route_masters.iteritems(): - members = OrderedDict() + itineraries = OrderedDict() # Build route variant members for member in route_master.members: + # Create Itinerary objects from member route variants if member.ref in route_variants: rv = route_variants.pop(member.ref) - members[rv.id] = self._build_route_variant(rv, result) + itineraries[rv.id] = self._build_itinerary(rv, result) - # Route master member was already used before or is not valid + # Route variant was already used or is not valid else: rv = result.get_relations(member.ref) if bool(rv): rv = rv.pop() - sys.stderr.write("Route variant was assigned again:\n") + sys.stderr.write("Itinerary was assigned again:\n") sys.stderr.write( "http://osm.org/relation/" + str(rv.id) + "\n") - members[rv.id] = self._build_route_variant(rv, result) + itineraries[rv.id] = self._build_itinerary(rv, result) else: sys.stderr.write( - "Member relation is not a valid route variant:\n") - sys.stderr.write("http://osm.org/relation/" + - str(member.ref) + "\n") + "Member relation is not a valid itinerary:\n") + sys.stderr.write("http://osm.org/relation/" + str(member.ref) + "\n") - rm = self._build_route_master(route_master, members) + # Create Line object from route master + line = self._build_line(route_master, itineraries) - # Make sure ref number is not already taken - if rm.ref in self.routes: + # Make sure route_id (ref) number is not already taken + if line.route_id in self.routes: sys.stderr.write("'Ref' of route_master already taken\n") sys.stderr.write( "http://osm.org/relation/" + str(route_master.id) + "\n") sys.stderr.write("Skipped. Please fix in OpenStreetMap\n") else: - self.routes[rm.ref] = rm + self.routes[line.route_id] = line # Build routes from variants (missing master relation) for rvid, route_variant in route_variants.iteritems(): - sys.stderr.write("Route (variant) without masters\n") - rv = self._build_route_variant(route_variant, result) - # Make sure ref number is not already taken - if rv.ref in self.routes: - sys.stderr.write("Route (variant) with existing 'Ref'\n") + sys.stderr.write("Itinerary without master\n") + sys.stderr.write( + "http://osm.org/relation/" + str(route_variant.id) + "\n") + sys.stderr.write("Please fix in OpenStreetMap\n") + itinerary = self._build_itinerary(route_variant, result) + + # Make sure route_id (ref) number is not already taken + if itinerary.route_id in self.routes: + sys.stderr.write("Itinerary with existing route id (ref)\n") sys.stderr.write( "http://osm.org/relation/" + str(route_variant.id) + "\n") sys.stderr.write("Skipped. Please fix in OpenStreetMap\n") else: - self.routes[rv.ref] = rv + # Create Line from route variant + itineraries = OrderedDict() + itineraries[itinerary.osm_id] = itinerary + line = self._build_line(route_variant, itineraries) + self.routes[line.route_id] = line # Cache data Cache.write_data('routes-' + self.selector, self.routes) @@ -209,7 +219,6 @@ def get_stops(self, refresh=False): self.stops = Cache.read_data('stops-' + self.selector) if bool(self.stops): - # Maybe check for unnamed stop names if self.auto_stop_names: self._get_names_for_unnamed_stops() @@ -240,8 +249,11 @@ def get_stops(self, refresh=False): # valid stop_area candidade? if 'public_transport' in relation.tags: if relation.tags["public_transport"] == "stop_area": - self.stops["relation/" + str(relation.id) - ] = self._build_stop_area(relation) + try: + self.stops["relation/" + str(relation.id) + ] = self._build_stop_area(relation) + except (ValueError, TypeError) as e: + sys.stderr.write('Cannot add stop area: ' + str(relation.id)) # Cache data Cache.write_data('stops-' + self.selector, self.stops) @@ -259,48 +271,77 @@ def get_stops(self, refresh=False): return self.stops - def _build_route_master(self, route_master, members): - """Helper function to build a RouteMaster object + def _build_line(self, route_master, itineraries): + """Helper function to build a Line object - Returns a initiated RouteMaster object from raw data + Returns a initiated Line object from raw data """ if 'ref' in route_master.tags: ref = route_master.tags['ref'] else: sys.stderr.write( - "RouteMaster without 'ref'. Please fix in OpenStreetMap\n") + "Relation without 'ref'. Please fix in OpenStreetMap\n") sys.stderr.write( "http://osm.org/relation/" + str(route_master.id) + "\n") - # Check if a ref can be taken from one of the members + # Check if a ref can be taken from one of the itineraries ref = False - for member in list(members.values()): - if not ref and member.ref: - ref = member.ref + for itinerary in list(itineraries.values()): + if not ref and itinerary.route_id: + ref = itinerary.route_id sys.stderr.write( "Using 'ref' from member variant instead\n") - sys.stderr.write( - "http://osm.org/relation/" + str(member.id) + "\n") + sys.stderr.write(itinerary.osm_url + "\n") # Ignore whole Line if no reference number could be obtained if not ref: sys.stderr.write( - "No 'ref' could be obtained from members. Skipping.\n") + "No 'ref' could be obtained. Skipping whole route.\n") return name = route_master.tags['name'] frequency = None if "frequency" in route_master.tags: frequency = route_master.tags['frequency'] - rm = RouteMaster(route_master.id, ref, name, members, frequency) - print(rm) - return rm - def _build_route_variant(self, route_variant, query_result_set, rm=None): - """Helper function to build a RouteVariant object + color = "FFFFFF" + if "colour" in route_master.tags: + color = OsmConnector.get_hex_code_for_color(route_master.tags['colour']) + + text_color = OsmConnector.get_complementary_color(color) + if "text_colour" in route_master.tags: + text_color = OsmConnector.get_hex_code_for_color(route_master.tags['text_colour']) + + if 'route_master' in route_master.tags: + route_type = route_master.tags['route_master'].capitalize() + + # If there was no route_master present we have a route relation here + elif 'route' in route_master.tags: + route_type = route_master.tags['route'].capitalize() + + # Create Line (route master) object + line = Line(osm_id=route_master.id, route_id=ref, + name=name, route_type=route_type, frequency=frequency, + route_color=color, route_text_color=text_color) + + # Add Itinerary objects (route variants) to Line (route master) + for itinerary in list(itineraries.values()): + try: + line.add_itinerary(itinerary) + except ValueError: + sys.stderr.write( + "Itinerary ID does not match line ID. Please fix in OSM.\n") + sys.stderr.write(line.osm_url) + itinerary.route_id = line.route_id + line.add_itinerary(itinerary) + + return line + + def _build_itinerary(self, route_variant, query_result_set): + """Helper function to build a Itinerary object - Returns a initiated RouteVariant object from raw data + Returns a initiated Itinerary object from raw data """ if 'ref' in route_variant.tags: @@ -310,6 +351,8 @@ def _build_route_variant(self, route_variant, query_result_set, rm=None): "RouteVariant without 'ref': " + str(route_variant.id) + "\n") sys.stderr.write( "http://osm.org/relation/" + str(route_variant.id) + "\n") + sys.stderr.write( + "Whole Itinerary skipped. Please fix in OpenStreetMap\n") return if 'from' in route_variant.tags: @@ -345,14 +388,15 @@ def _build_route_variant(self, route_variant, query_result_set, rm=None): otype = "way" else: - raise RuntimeError("Unknown type: " + str(stop_candidate)) + sys.stderr.write("Unknown type of itinerary member: " + + str(stop_candidate) + "\n") stops.append(otype + "/" + str(stop_candidate.ref)) shape = self._generate_shape(route_variant, query_result_set) - rv = Route(route_variant.id, fr, to, stops, - rm, ref, name, shape, travel_time) - print(rv) + rv = Itinerary(osm_id=route_variant.id, fr=fr, + to=to, stops=stops, shape=shape, route_id=ref, + name=name, travel_time=travel_time) return rv def _build_stop(self, stop, stop_type): @@ -368,9 +412,9 @@ def _build_stop(self, stop, stop_type): # Ways don't have coordinates and they have to be calculated if stop_type == "way": - (stop.lat, stop.lon) = Stop.get_center_of_nodes(stop.get_nodes()) + (stop.lat, stop.lon) = self.get_center_of_nodes(stop.get_nodes()) - s = Stop(stop.id, "node", stop.tags['name'], stop.lat, stop.lon) + s = Stop(stop.id, "node", unicode(stop.tags['name']), stop.lat, stop.lon) return s def _build_stop_area(self, relation): @@ -382,8 +426,15 @@ def _build_stop_area(self, relation): for member in relation.members: if (isinstance(member, overpy.RelationNode) and member.role == "platform"): - stop = self.stops.pop("node/" + str(member.ref)) - stop_members["node/" + str(member.ref)] = stop + if "node/" + str(member.ref) in self.stops: + stop = self.stops.pop("node/" + str(member.ref)) + stop_members["node/" + str(member.ref)] = stop + else: + sys.stderr.write("Stop not found in stops: ") + sys.stderr.write("http://osm.org/node/" + + str(member.ref) + "\n") + if len(stop_members) < 1: + sys.stderr.write("Cannot build stop area with no members\n") if 'name' not in relation.tags: sys.stderr.write("Stop area without name." + @@ -543,7 +594,7 @@ def _get_names_for_unnamed_stops(self): # If there is no name, query one intelligently from OSM if stop.name == "[" + self.stop_no_name + "]": self._find_best_name_for_unnamed_stop(stop) - print stop + print(stop) # Cache stops with newly created stop names Cache.write_data('stops-' + self.selector, self.stops) @@ -598,7 +649,7 @@ def _find_best_name_for_unnamed_stop(self, stop): winner_distance = sys.maxint for candidate in candidates: if isinstance(candidate, overpy.Way): - lat, lon = Stop.get_center_of_nodes( + lat, lon = self.get_center_of_nodes( candidate.get_nodes(resolve_missing=True)) distance = util.ApproximateDistance( lat, @@ -619,3 +670,67 @@ def _find_best_name_for_unnamed_stop(self, stop): # take name from winner stop.name = winner.tags["name"].encode('utf-8') + + @staticmethod + def get_center_of_nodes(nodes): + """Helper function to get center coordinates of a group of nodes + + """ + x = 0 + y = 0 + z = 0 + + if len(nodes) < 1: + sys.stderr.write("Cannot find the center of zero nodes\n") + for node in nodes: + lat = radians(float(node.lat)) + lon = radians(float(node.lon)) + + x += cos(lat) * cos(lon) + y += cos(lat) * sin(lon) + z += sin(lat) + + x = float(x / len(nodes)) + y = float(y / len(nodes)) + z = float(z / len(nodes)) + + center_lat = degrees(atan2(z, sqrt(x * x + y * y))) + center_lon = degrees(atan2(y, x)) + + return center_lat, center_lon + + @staticmethod + def get_hex_code_for_color(color): + color = color.lower() + if color == u'black': + return '000000' + if color == u'blue': + return '0000FF' + if color == u'gray': + return '808080' + if color == u'green': + return '008000' + if color == u'purple': + return '800080' + if color == u'red': + return 'FF0000' + if color == u'silver': + return 'C0C0C0' + if color == u'white': + return 'FFFFFF' + if color == u'yellow': + return 'FFFF00' + print('Color not known: ' + color) + return 'FA8072' + + @staticmethod + def get_complementary_color(color): + """ + Returns complementary RGB color + Source: https://stackoverflow.com/a/38478744 + """ + if color[0] == '#': + color = color[1:] + rgb = (color[0:2], color[2:4], color[4:6]) + comp = ['%02X' % (255 - int(a, 16)) for a in rgb] + return ''.join(comp) diff --git a/osm2gtfs/core/osm_routes.py b/osm2gtfs/core/osm_routes.py deleted file mode 100644 index 07621026..00000000 --- a/osm2gtfs/core/osm_routes.py +++ /dev/null @@ -1,109 +0,0 @@ -# coding=utf-8 - - -class BaseRoute(object): - - def __init__(self, osm, ref, name): - self.id = osm - self.ref = ref - if name is not None: - self.name = name.encode('utf-8') - else: - self.name = name - self.last_update = None - - def __repr__(self): - rep = "" - if self.ref is not None: - rep += str(self.ref) + " | " - if self.name is not None: - rep += self.name - return rep - - -class Route(BaseRoute): - - def __init__(self, osm, fr, to, stops, master, ref, name, shape, travel_time=None): - BaseRoute.__init__(self, osm, ref, name) - self.fr = fr - self.to = to - self.stops = stops - self.master = master - self.shape = shape - self.travel_time = travel_time - self.duration = None - - def __repr__(self): - rep = BaseRoute.__repr__(self) - if self.stops is not None: - rep += " | Stops: " + str(len(self.stops)) + " | " - rep += "https://www.openstreetmap.org/relation/" + str(self.id) + " " - rep += "http://www.consorciofenix.com.br/horarios?q=" + str(self.ref) - return rep - - def set_duration(self, duration): - self.duration = duration - - def get_first_stop(self): - if len(self.stops) > 0: - return self.stops[0] - else: - return None - - def get_first_alt_stop(self): - if self.fr is not None: - return self.fr - else: - return "???" - - def has_proper_master(self): - return self.master is not None and len(self.master.routes) > 1 - - # TODO move to debug class? - def print_shape_for_leaflet(self): - print "var shape = [", - i = 0 - for node in self.shape: - print "new L.LatLng(" + str(node["lat"]) + ", " + str(node["lon"]) + ")", - if i != len(self.shape) - 1: - print ",", - i += 1 - print "];" - i = 0 - for node in self.shape: - print "L.marker([" + str(node["lat"]) + ", " + str(node["lon"]) + "]).addTo(map)" - print " .bindPopup(\"" + str(i) + "\").openPopup();" - i += 1 - - -class RouteMaster(BaseRoute): - - def __init__(self, osm, ref, name, routes, frequency=None): - BaseRoute.__init__(self, osm, ref, name) - self.routes = routes - self.frequency = frequency - for route in self.routes.values(): - route.master = self - - def __repr__(self): - rep = BaseRoute.__repr__(self) - rep += " | https://www.openstreetmap.org/relation/" + \ - str(self.id) + "\n" - - i = 1 - for route in self.routes: - rep += " Route %d: " % i - rep += str(self.routes[route]) + "\n" - i += 1 - - return rep - - def set_duration(self, duration): - for route in self.routes.values(): - route.set_duration(duration) - - def get_first_stop(self): - return self.routes.itervalues().next().get_first_stop() - - def get_first_alt_stop(self): - return self.routes.itervalues().next().get_first_alt_stop() diff --git a/osm2gtfs/core/osm_stops.py b/osm2gtfs/core/osm_stops.py deleted file mode 100644 index ce9828b1..00000000 --- a/osm2gtfs/core/osm_stops.py +++ /dev/null @@ -1,76 +0,0 @@ -# coding=utf-8 - -from math import cos, sin, atan2, sqrt, radians, degrees - - -class Stop(object): - - def __init__(self, osm, stop_type, name=None, lat=None, lon=None): - self.id = osm - if name is not None: - self.name = name.encode('utf-8') - else: - self.name = name - self.lat = lat - self.lon = lon - self.type = stop_type - self.added = False - - def __repr__(self): - rep = "" - if self.name is not None: - rep += self.name - if self.lat is not None and self.lon is not None: - rep += " http://www.openstreetmap.org/?mlat=" + \ - str(self.lat) + "&mlon=" + str(self.lon) - rep += " (https://www.openstreetmap.org/" + \ - self.type + "/" + str(self.id) + ")" - return rep - - @staticmethod - def get_center_of_nodes(nodes): - """Helper function to get center coordinates of a group of nodes - - """ - x = 0 - y = 0 - z = 0 - - for node in nodes: - lat = radians(float(node.lat)) - lon = radians(float(node.lon)) - - x += cos(lat) * cos(lon) - y += cos(lat) * sin(lon) - z += sin(lat) - - x = float(x / len(nodes)) - y = float(y / len(nodes)) - z = float(z / len(nodes)) - - center_lat = degrees(atan2(z, sqrt(x * x + y * y))) - center_lon = degrees(atan2(y, x)) - - return center_lat, center_lon - - -class StopArea(object): - - def __init__(self, osm, stop_members, name=None): - self.id = osm - if name is not None: - self.name = name.encode('utf-8') - else: - self.name = name - self.stop_members = stop_members - self.lat, self.lon = Stop.get_center_of_nodes(stop_members.values()) - - def __repr__(self): - rep = "" - if self.name is not None: - rep += self.name - if self.lat is not None and self.lon is not None: - rep += " lat: " + str(self.lat) + " lon: " + str(self.lon) + "\n\t" - for ref, stop in self.stop_members.iteritems(): - rep += " | Stop member: " + ref + " - " + stop.name - return rep diff --git a/osm2gtfs/core/routes.py b/osm2gtfs/core/routes.py new file mode 100644 index 00000000..a7a8ec89 --- /dev/null +++ b/osm2gtfs/core/routes.py @@ -0,0 +1,82 @@ +# coding=utf-8 + +import attr + + +@attr.s +class Line(object): + """A general public transport service Line. + + It's a container of meta information and different Itinerary objects for + variants of the same service line. + + In OpenStreetMap this is usually represented as "route_master" relation. + In GTFS this is usually represented as "route" + + """ + osm_id = attr.ib() + route_id = attr.ib() + name = attr.ib() + route_type = attr.ib() # Required (Tram, Subway, Rail, Bus, ...) + + route_desc = attr.ib(default=None) + route_url = attr.ib(default=None) + route_color = attr.ib(default="FFFFFF") + route_text_color = attr.ib(default="000000") + osm_url = attr.ib(default="http://osm.org/relation/" + str(osm_id)) + frequency = attr.ib(default=None) + + # Related route variants + _itineraries = attr.ib(default=attr.Factory(list)) + + def add_itinerary(self, itinerary): + if self.route_id.encode('utf-8') != itinerary.route_id.encode('utf-8'): + raise ValueError('Itinerary route ID (' + + itinerary.route_id + + ') does not match Line route ID (' + + self.route_id + ')') + self._itineraries.append(itinerary) + + def get_itineraries(self): + return self._itineraries + + +@attr.s +class Itinerary(object): + """A public transport service itinerary. + + It's a representation of a possible variant of a line, grouped together by + a Line object. + + In OpenStreetMap this is usually represented as "route" relation. + In GTFS this is not exlicitly presented but used as based to create "trips" + + """ + osm_id = attr.ib() + route_id = attr.ib() + name = attr.ib() + fr = attr.ib() + to = attr.ib() + shape = attr.ib() + stops = attr.ib() + travel_time = attr.ib() + + route_url = attr.ib(default=None) + wheelchair_accessible = attr.ib(default=0) + bikes_allowed = attr.ib(default=0) + osm_url = attr.ib(default="http://osm.org/relation/" + str(osm_id)) + + # Useful information for further calculation + duration = attr.ib(default=None) + + # All stop objects of itinerary + _stop_objects = attr.ib(default=attr.Factory(list)) + + def add_stop(self, stop): + self._stop_objects.append(stop) + + def get_stop_by_position(self, pos): + raise NotImplementedError("Should have implemented this") + + def get_stops(self): + return self._stop_objects diff --git a/osm2gtfs/core/stops.py b/osm2gtfs/core/stops.py new file mode 100644 index 00000000..7d2e3cac --- /dev/null +++ b/osm2gtfs/core/stops.py @@ -0,0 +1,38 @@ +# coding=utf-8 + +import attr + + +@attr.s +class Stop(object): + + osm_id = attr.ib() + osm_type = attr.ib() + name = attr.ib() + lat = attr.ib() + lon = attr.ib() + gtfs_id = attr.ib(default=osm_id) + osm_url = attr.ib(default="http://osm.org/" + + str(osm_type) + "/" + str(osm_id)) + + +@attr.s +class StopArea(object): + + osm_id = attr.ib() + name = attr.ib() + lat = attr.ib() + lon = attr.ib() + + stop_members = attr.ib(default=attr.Factory(list)) + + def __init__(self, osm_id, stop_members, name=None): + self.osm_id = osm_id + if name is not None: + self.name = name.encode('utf-8') + else: + self.name = name + self.stop_members = stop_members + + from osm2gtfs.core.osm_connector import OsmConnector + self.lat, self.lon = OsmConnector.get_center_of_nodes(self.stop_members.values()) diff --git a/osm2gtfs/creators/accra/stops_creator_accra.py b/osm2gtfs/creators/accra/stops_creator_accra.py index 6a7509f2..292ea180 100644 --- a/osm2gtfs/creators/accra/stops_creator_accra.py +++ b/osm2gtfs/creators/accra/stops_creator_accra.py @@ -35,7 +35,7 @@ def create_stop_area(stop_data, schedule): lat=float(stop_data.lat), lng=float(stop_data.lon), name=stop_data.name, - stop_id="SA" + str(stop_data.id) + stop_id="SA" + str(stop_data.osm_id) ) gtfs_stop_area.location_type = 1 return gtfs_stop_area @@ -46,13 +46,13 @@ def create_stop_point(stop_data, schedule): lat=float(stop_data.lat), lng=float(stop_data.lon), name=stop_data.name, - stop_id=str(stop_data.id) + stop_id=str(stop_data.osm_id) ) return gtfs_stop_point def get_stop_id(stop): - return stop.id + return stop.osm_id class StopsCreatorAccra(StopsCreator): @@ -61,7 +61,8 @@ def add_stops_to_schedule(self, schedule, data): stops = data.get_stops() stops_by_name = {} - for a_stop in stops.values(): + for a_stop_id, a_stop in stops.items(): + a_stop.osm_id = a_stop_id if a_stop.name not in stops_by_name: stops_by_name[a_stop.name] = [] stops_by_name[a_stop.name].append(a_stop) diff --git a/osm2gtfs/creators/accra/trips_creator_accra.py b/osm2gtfs/creators/accra/trips_creator_accra.py index 99a5b254..d6c5e6a2 100644 --- a/osm2gtfs/creators/accra/trips_creator_accra.py +++ b/osm2gtfs/creators/accra/trips_creator_accra.py @@ -3,6 +3,7 @@ from datetime import timedelta, datetime from osm2gtfs.creators.trips_creator import TripsCreator +from osm2gtfs.core.routes import Line class TripsCreatorAccra(TripsCreator): @@ -18,24 +19,25 @@ def add_trips_to_schedule(self, schedule, data): lines = data.routes for route_ref, line in sorted(lines.iteritems()): - if type(line).__name__ != "RouteMaster": + if not isinstance(line, Line): continue line_gtfs = schedule.AddRoute( - short_name=line.ref, - long_name=line.name.decode('utf8'), + short_name=line.route_id, + long_name=line.name, # we change the route_long_name with the 'from' and 'to' tags # of the last route as the route_master name tag contains # the line code (route_short_name) route_type="Bus", - route_id=line.id) + route_id=line.osm_id) line_gtfs.agency_id = schedule.GetDefaultAgency().agency_id line_gtfs.route_desc = "" line_gtfs.route_color = "1779c2" line_gtfs.route_text_color = "ffffff" route_index = 0 - for a_route_ref, a_route in line.routes.iteritems(): + itineraries = line.get_itineraries() + for a_route_ref, a_route in itineraries: trip_gtfs = line_gtfs.AddTrip(schedule) trip_gtfs.shape_id = TripsCreator.add_shape( schedule, a_route_ref, a_route) @@ -53,10 +55,10 @@ def add_trips_to_schedule(self, schedule, data): try: ROUTE_FREQUENCY = int(line.frequency) if not ROUTE_FREQUENCY > 0: - print("frequency is invalid for route_master " + str(line.id)) + print("frequency is invalid for route_master " + str(line.osm_id)) ROUTE_FREQUENCY = DEFAULT_ROUTE_FREQUENCY except (ValueError, TypeError) as e: - print("frequency not a number for route_master " + str(line.id)) + print("frequency not a number for route_master " + str(line.osm_id)) ROUTE_FREQUENCY = DEFAULT_ROUTE_FREQUENCY trip_gtfs.AddFrequency( "05:00:00", "22:00:00", ROUTE_FREQUENCY * 60) @@ -64,10 +66,10 @@ def add_trips_to_schedule(self, schedule, data): try: TRAVEL_TIME = int(a_route.travel_time) if not TRAVEL_TIME > 0: - print("travel_time is invalid for route " + str(a_route.id)) + print("travel_time is invalid for route " + str(a_route.osm_id)) TRAVEL_TIME = DEFAULT_TRAVEL_TIME except (ValueError, TypeError) as e: - print("travel_time not a number for route " + str(a_route.id)) + print("travel_time not a number for route " + str(a_route.osm_id)) TRAVEL_TIME = DEFAULT_TRAVEL_TIME for index_stop, a_stop in enumerate(a_route.stops): diff --git a/osm2gtfs/creators/fenix/trips_creator_fenix.py b/osm2gtfs/creators/fenix/trips_creator_fenix.py index 14ee0684..d2091ebd 100644 --- a/osm2gtfs/creators/fenix/trips_creator_fenix.py +++ b/osm2gtfs/creators/fenix/trips_creator_fenix.py @@ -6,7 +6,7 @@ import transitfeed from datetime import timedelta, datetime from osm2gtfs.creators.trips_creator import TripsCreator -from osm2gtfs.core.osm_routes import Route, RouteMaster +from osm2gtfs.core.routes import Line, Itinerary DEBUG_ROUTE = "" BLACKLIST = [ @@ -87,7 +87,7 @@ def add_trips_to_schedule(self, schedule, data): def add_route(self, schedule, route, horarios, operacoes): line = schedule.AddRoute( - short_name=route.ref, + short_name=route.route_id, long_name=route.name.decode('utf8'), route_type="Bus") line.agency_id = schedule.GetDefaultAgency().agency_id @@ -144,7 +144,7 @@ def add_trips_by_day(self, schedule, line, service, route, horarios, day): if horarios is None or len(horarios) == 0: return - if isinstance(route, RouteMaster): + if isinstance(route, Line): # recurse into "Ida" and "Volta" routes for sub_route in route.routes.values(): self.add_trips_by_day(schedule, line, service, sub_route, horarios, day) @@ -275,7 +275,7 @@ def normalize_stop_name(old_name): @staticmethod def add_trip_stops(schedule, trip, route, start_time, end_time): - if isinstance(route, Route): + if isinstance(route, Itinerary): i = 1 for stop in route.stops: if i == 1: diff --git a/osm2gtfs/creators/incofer/routes_creator_incofer.py b/osm2gtfs/creators/incofer/routes_creator_incofer.py index 08590fb6..c32782af 100644 --- a/osm2gtfs/creators/incofer/routes_creator_incofer.py +++ b/osm2gtfs/creators/incofer/routes_creator_incofer.py @@ -5,35 +5,14 @@ class RoutesCreatorIncofer(RoutesCreator): - def add_routes_to_schedule(self, schedule, data): + def _get_route_type(self, line): + return "Tram" - # Get routes information - lines = data.get_routes() - # debug - # print("DEBUG: creando itinerarios a partir de", str(len(lines)), - # "lineas") + def _get_route_description(self, line): + return "Esta línea está a prueba" - # Loop through all lines (master_routes) - for line_ref, line in sorted(lines.iteritems()): - route = schedule.AddRoute( - short_name=line.ref.encode('utf-8'), - long_name=line.name, - # TODO: infer transitfeed "route type" from OSM data - route_type="Tram", - route_id=line_ref) + def _get_route_color(self, line): + return "ff0000" - # AddRoute method add defaut agency as default - route.agency_id = schedule.GetDefaultAgency().agency_id - - route.route_desc = "Esta línea está a prueba" - - # TODO: get route_url from OSM or other source. - # url = "http://www.incofer.go.cr/tren-urbano-alajuela-rio-segundo" - - # line.route_url = url - route.route_color = "ff0000" - route.route_text_color = "ffffff" - - # debug - # print("información de la linea:", line.name, "agregada.") - return + def _get_route_text_color(self, line): + return "ffffff" diff --git a/osm2gtfs/creators/incofer/trips_creator_incofer.py b/osm2gtfs/creators/incofer/trips_creator_incofer.py index acd011a8..baeeb77e 100644 --- a/osm2gtfs/creators/incofer/trips_creator_incofer.py +++ b/osm2gtfs/creators/incofer/trips_creator_incofer.py @@ -20,7 +20,8 @@ def add_trips_to_schedule(self, schedule, data): # print("DEBUG. procesando la línea:", line.name) # itinerary (osm route | non existent gtfs element) - for itinerary_id, itinerary in line.routes.iteritems(): + itineraries = line.get_itineraries() + for itinerary_id, itinerary in itineraries: # debug # print("DEBUG. procesando el itinerario", itinerary.name) @@ -171,7 +172,7 @@ def load_times(route, operation, filename='data/input_incofer.json'): # route_directions = input_data["itinerario"][route.ref]["horarios"] times = None - for direction in input_data["itinerario"][route.ref]: + for direction in input_data["itinerario"][route.route_id]: fr = direction["from"].encode('utf-8') to = direction["to"].encode('utf-8') diff --git a/osm2gtfs/creators/routes_creator.py b/osm2gtfs/creators/routes_creator.py index a65a110b..2241d45b 100644 --- a/osm2gtfs/creators/routes_creator.py +++ b/osm2gtfs/creators/routes_creator.py @@ -13,4 +13,5 @@ def __repr__(self): return rep def add_routes_to_schedule(self, schedule, data): + raise NotImplementedError("Should have implemented this") diff --git a/osm2gtfs/creators/stops_creator.py b/osm2gtfs/creators/stops_creator.py index b113b6ae..d31983d3 100644 --- a/osm2gtfs/creators/stops_creator.py +++ b/osm2gtfs/creators/stops_creator.py @@ -1,9 +1,9 @@ # coding=utf-8 +import sys import transitfeed - -from osm2gtfs.core.osm_routes import Route, RouteMaster -from osm2gtfs.core.osm_stops import Stop, StopArea +from osm2gtfs.core.routes import Itinerary, Line +from osm2gtfs.core.stops import Stop, StopArea class StopsCreator(object): @@ -45,10 +45,10 @@ def add_stop(self, schedule, stop, parent_station=None, is_station=False): "stop_name": stop.name} if is_station: - stop_dict["stop_id"] = "SA" + str(stop.id) + stop_dict["stop_id"] = "SA" + str(stop.osm_id) stop_dict["location_type"] = "1" else: - stop_dict["stop_id"] = str(stop.id) + stop_dict["stop_id"] = str(stop.osm_id) stop_dict["location_type"] = "" if parent_station is None: @@ -78,7 +78,7 @@ def _fill_stops(self, stops, route): """Fill a route object with stop objects for of linked stop ids """ - if isinstance(route, Route): + if isinstance(route, Itinerary): i = 0 for stop in route.stops: # Replace stop id with Stop objects @@ -86,12 +86,13 @@ def _fill_stops(self, stops, route): route.stops[i] = self._get_stop(stop, stops) i += 1 - elif isinstance(route, RouteMaster): - for route_variant_ref, route_variant in route.routes.iteritems(): - self._fill_stops(stops, route_variant) + elif isinstance(route, Line): + itineraries = route.get_itineraries() + for itinerary_ref, itinerary in itineraries: + self._fill_stops(stops, itinerary) else: - raise RuntimeError("Unknown Route: " + str(route)) + sys.stderr.write("Unknown route: " + str(route) + "\n") def _get_stop(self, stop_id, stops): for ref, elem in stops.iteritems(): @@ -102,4 +103,4 @@ def _get_stop(self, stop_id, stops): if stop_id in elem.stop_members: return elem.stop_members[stop_id] else: - raise RuntimeError("Unknown stop: " + str(stop_id)) + sys.stderr.write("Unknown stop: " + str(stop_id) + "\n") diff --git a/osm2gtfs/creators/trips_creator.py b/osm2gtfs/creators/trips_creator.py index aa5a18fe..c4ea278f 100644 --- a/osm2gtfs/creators/trips_creator.py +++ b/osm2gtfs/creators/trips_creator.py @@ -13,6 +13,7 @@ def __repr__(self): return rep def add_trips_to_schedule(self, schedule, data): + raise NotImplementedError("Should have implemented this") @staticmethod diff --git a/setup.py b/setup.py index 2c68069c..8c56ded2 100644 --- a/setup.py +++ b/setup.py @@ -10,11 +10,12 @@ keywords='openstreetmap gtfs schedule public-transportation python', author='Various collaborators: https://github.com/grote/osm2gtfs/graphs/contributors', - install_requires=['overpy>=0.4', 'transitfeed'], + install_requires=['attrs', 'overpy>=0.4', 'transitfeed'], packages=find_packages(), include_package_data=True, - entry_points=''' - [console_scripts] - osm2gtfs = osm2gtfs.osm2gtfs:main - ''' + entry_points={ + 'console_scripts': [ + 'osm2gtfs = osm2gtfs.osm2gtfs:main' + ] + }, )