diff --git a/core/osm_connector.py b/core/osm_connector.py index dc47f382..9d172172 100644 --- a/core/osm_connector.py +++ b/core/osm_connector.py @@ -290,7 +290,10 @@ def _build_route_master(self, route_master, members): return name = route_master.tags['name'] - rm = RouteMaster(route_master.id, ref, name, members) + 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 @@ -324,6 +327,11 @@ def _build_route_variant(self, route_variant, query_result_set, rm=None): else: name = None + if 'travel_time' in route_variant.tags: + travel_time = route_variant.tags['travel_time'] + else: + travel_time = None + stops = [] # Add ids for stops of this route variant @@ -342,7 +350,8 @@ def _build_route_variant(self, route_variant, query_result_set, rm=None): 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) + rv = Route(route_variant.id, fr, to, stops, + rm, ref, name, shape, travel_time) print(rv) return rv @@ -522,7 +531,6 @@ def _is_valid_stop_candidate(self, stop): return False def _get_names_for_unnamed_stops(self): - """Intelligently guess stop names for unnamed stops by sourrounding street names and amenities. diff --git a/core/osm_routes.py b/core/osm_routes.py index de891617..07621026 100644 --- a/core/osm_routes.py +++ b/core/osm_routes.py @@ -23,13 +23,14 @@ def __repr__(self): class Route(BaseRoute): - def __init__(self, osm, fr, to, stops, master, ref, name, shape): + 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): @@ -77,9 +78,10 @@ def print_shape_for_leaflet(self): class RouteMaster(BaseRoute): - def __init__(self, osm, ref, name, routes): + 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 diff --git a/core/osm_stops.py b/core/osm_stops.py index 5accfe7d..ce9828b1 100644 --- a/core/osm_stops.py +++ b/core/osm_stops.py @@ -21,8 +21,10 @@ def __repr__(self): 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) + ")" + 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 diff --git a/creators/accra/__init__.py b/creators/accra/__init__.py new file mode 100644 index 00000000..5c9136ad --- /dev/null +++ b/creators/accra/__init__.py @@ -0,0 +1,2 @@ +#!/usr/bin/env python +# coding=utf-8 diff --git a/creators/accra/accra.json b/creators/accra/accra.json new file mode 100644 index 00000000..c67d2893 --- /dev/null +++ b/creators/accra/accra.json @@ -0,0 +1,35 @@ +{ + "query": { + "bbox": { + "n": "5.7844", + "s": "5.4783", + "e": "0.0611", + "w": "-0.4779" + }, + "tags": { + "route": "bus" + } + }, + "stops": { + "name_without": "Add a name to the stop with JungleBus app", + "name_auto": "yes" + }, + "agency": { + "agency_id": "AM3", + "agency_name": "Accra Tro tro", + "agency_url": "https://ama.gov.gh/welcome/transport/", + "agency_timezone": "Africa/Accra", + "agency_lang": "EN", + "agency_phone": "", + "agency_fare_url": "" + }, + "feed_info": { + "publisher_name": "Jungle Bus", + "publisher_url": "http://junglebus.io", + "version": "0.1", + "start_date": "20170901", + "end_date": "20180730" + }, + "output_file": "data/accra.zip", + "selector": "accra" +} diff --git a/creators/accra/routes_creator_accra.py b/creators/accra/routes_creator_accra.py new file mode 100644 index 00000000..14d6e2b8 --- /dev/null +++ b/creators/accra/routes_creator_accra.py @@ -0,0 +1,13 @@ +# coding=utf-8 + +from creators.routes_creator import RoutesCreator + + +class RoutesCreatorAccra(RoutesCreator): + + def add_routes_to_schedule(self, schedule, data): + # Get routes information + data.get_routes() + + # GTFS routes are created in TripsCreator + return diff --git a/creators/accra/stops_creator_accra.py b/creators/accra/stops_creator_accra.py new file mode 100644 index 00000000..f24f1118 --- /dev/null +++ b/creators/accra/stops_creator_accra.py @@ -0,0 +1,82 @@ +# coding=utf-8 + + +from creators.stops_creator import StopsCreator +import math + + +def get_crow_fly_distance(from_tuple, to_tuple): + """ + Uses the Haversine formmula to compute distance (https://en.wikipedia.org/wiki/Haversine_formula#The_haversine_formula) + """ + lat1, lon1 = from_tuple + lat2, lon2 = to_tuple + + lat1 = float(lat1) + lat2 = float(lat2) + lon1 = float(lon1) + lon2 = float(lon2) + + radius = 6371 # km + + dlat = math.radians(lat2 - lat1) + dlon = math.radians(lon2 - lon1) + a = math.sin(dlat / 2) * math.sin(dlat / 2) + math.cos(math.radians(lat1)) * \ + math.cos(math.radians(lat2)) * math.sin(dlon / 2) * math.sin(dlon / 2) + c = 2 * math.atan2(math.sqrt(a), math.sqrt(1 - a)) + d = radius * c + + return d * 1000 # meters + + +def create_stop_area(stop_data, schedule): + gtfs_stop_area = schedule.AddStop( + lat=float(stop_data.lat), + lng=float(stop_data.lon), + name=stop_data.name, + stop_id="SA" + str(stop_data.id) + ) + gtfs_stop_area.location_type = 1 + return gtfs_stop_area + + +def create_stop_point(stop_data, schedule): + gtfs_stop_point = schedule.AddStop( + lat=float(stop_data.lat), + lng=float(stop_data.lon), + name=stop_data.name, + stop_id=str(stop_data.id) + ) + return gtfs_stop_point + +def get_stop_id(stop): + return stop.id + +class StopsCreatorAccra(StopsCreator): + + def add_stops_to_schedule(self, schedule, data): + stops = data.get_stops() + stops_by_name = {} + + for a_stop in stops.values(): + if a_stop.name not in stops_by_name: + stops_by_name[a_stop.name] = [] + stops_by_name[a_stop.name].append(a_stop) + + for a_stop_name in stops_by_name: + stop_areas = [] + + for a_stop_point in sorted(stops_by_name[a_stop_name], key=get_stop_id): + gtfs_stop_point = create_stop_point(a_stop_point, schedule) + stop_point_has_parent_location = False + for a_stop_area in stop_areas: + distance_to_parent_station = get_crow_fly_distance( + (a_stop_area.stop_lat, a_stop_area.stop_lon), (a_stop_point.lat, a_stop_point.lon)) + if distance_to_parent_station < 500: + gtfs_stop_point.parent_station = a_stop_area.stop_id + stop_point_has_parent_location = True + break + if not stop_point_has_parent_location: + new_sa = create_stop_area(a_stop_point, schedule) + gtfs_stop_point.parent_station = new_sa.stop_id + stop_areas.append(new_sa) diff --git a/creators/accra/trips_creator_accra.py b/creators/accra/trips_creator_accra.py new file mode 100644 index 00000000..96e2d51b --- /dev/null +++ b/creators/accra/trips_creator_accra.py @@ -0,0 +1,96 @@ +# coding=utf-8 + +import sys +import json +import re +from datetime import timedelta, datetime +from creators.trips_creator import TripsCreator +from core.osm_routes import Route, RouteMaster + + +class TripsCreatorAccra(TripsCreator): + + def add_trips_to_schedule(self, schedule, data): + self.service_weekday = schedule.GetDefaultServicePeriod() + self.service_weekday.SetStartDate( + self.config['feed_info']['start_date']) + self.service_weekday.SetEndDate(self.config['feed_info']['end_date']) + self.service_weekday.SetWeekdayService(True) + self.service_weekday.SetWeekendService(True) + + lines = data.routes + for route_ref, line in sorted(lines.iteritems()): + if type(line).__name__ != "RouteMaster": + continue + + line_gtfs = schedule.AddRoute( + short_name=line.ref, + long_name=line.name.decode('utf8'), + # 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) + 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(): + trip_gtfs = line_gtfs.AddTrip(schedule) + trip_gtfs.shape_id = TripsCreator.add_shape( + schedule, a_route_ref, a_route) + trip_gtfs.direction_id = route_index % 2 + route_index += 1 + + if a_route.fr and a_route.to: + trip_gtfs.trip_headsign = a_route.to + line_gtfs.route_long_name = a_route.fr.decode( + 'utf8') + " ↔ ".decode('utf8') + a_route.to.decode('utf8') + + DEFAULT_ROUTE_FREQUENCY = 30 + DEFAULT_TRAVEL_TIME = 120 + + try: + ROUTE_FREQUENCY = int(line.frequency) + if not ROUTE_FREQUENCY > 0: + print("frequency is invalid for route_master " + str(line.id)) + ROUTE_FREQUENCY = DEFAULT_ROUTE_FREQUENCY + except Exception as e: + print("frequency not a number for route_master " + str(line.id)) + ROUTE_FREQUENCY = DEFAULT_ROUTE_FREQUENCY + trip_gtfs.AddFrequency( + "05:00:00", "22:00:00", ROUTE_FREQUENCY * 60) + + try: + TRAVEL_TIME = int(a_route.travel_time) + if not TRAVEL_TIME > 0: + print("travel_time is invalid for route " + + str(lia_routene.id)) + TRAVEL_TIME = DEFAULT_TRAVEL_TIME + except Exception as e: + print("travel_time not a number for route " + str(a_route.id)) + TRAVEL_TIME = DEFAULT_TRAVEL_TIME + + for index_stop, a_stop in enumerate(a_route.stops): + stop_id = a_stop.split('/')[-1] + departure_time = datetime(2008, 11, 22, 6, 0, 0) + + if index_stop == 0: + trip_gtfs.AddStopTime(schedule.GetStop( + str(stop_id)), stop_time=departure_time.strftime("%H:%M:%S")) + elif index_stop == len(a_route.stops) - 1: + departure_time += timedelta(minutes=TRAVEL_TIME) + trip_gtfs.AddStopTime(schedule.GetStop( + str(stop_id)), stop_time=departure_time.strftime("%H:%M:%S")) + else: + trip_gtfs.AddStopTime(schedule.GetStop(str(stop_id))) + + for secs, stop_time, is_timepoint in trip_gtfs.GetTimeInterpolatedStops(): + if not is_timepoint: + stop_time.arrival_secs = secs + stop_time.departure_secs = secs + trip_gtfs.ReplaceStopTimeObject(stop_time) + + TripsCreator.interpolate_stop_times(trip_gtfs) diff --git a/creators/fenix/trips_creator_fenix.py b/creators/fenix/trips_creator_fenix.py index 0fa5a49e..6db84042 100644 --- a/creators/fenix/trips_creator_fenix.py +++ b/creators/fenix/trips_creator_fenix.py @@ -208,7 +208,7 @@ def add_trips_by_day(self, schedule, line, service, route, horarios, day): self.add_trip_stops(schedule, trip, route, start_time, end_time) # interpolate times, because Navitia can not handle this itself - self.interpolate_stop_times(trip) + TripsCreator.interpolate_stop_times(trip) def get_exception_service_period(self, schedule, date, day): date_string = date.strftime("%Y%m%d") @@ -292,11 +292,3 @@ def add_trip_stops(schedule, trip, route, start_time, end_time): trip.AddStopTime(schedule.GetStop(str(stop.id))) # print "INTER: " + str(stop) i += 1 - - @staticmethod - def interpolate_stop_times(trip): - for secs, stop_time, is_timepoint in trip.GetTimeInterpolatedStops(): - if not is_timepoint: - stop_time.arrival_secs = secs - stop_time.departure_secs = secs - trip.ReplaceStopTimeObject(stop_time) diff --git a/creators/incofer/trips_creator_incofer.py b/creators/incofer/trips_creator_incofer.py index dbc6d8a1..7e3c5ffe 100644 --- a/creators/incofer/trips_creator_incofer.py +++ b/creators/incofer/trips_creator_incofer.py @@ -25,7 +25,7 @@ def add_trips_to_schedule(self, schedule, data): # print("DEBUG. procesando el itinerario", itinerary.name) # shape for itinerary - shape_id = _add_shape(schedule, itinerary_id, itinerary) + shape_id = TripsCreator.add_shape(schedule, itinerary_id, itinerary) # service periods | días de opearación (c/u con sus horarios) operations = self._get_itinerary_operation(itinerary) @@ -110,20 +110,6 @@ def _create_service_period(self, schedule, operation): return schedule.GetServicePeriod(operation) -def _add_shape(schedule, route_id, osm_r): - # get shape id - shape_id = str(route_id) - try: - schedule.GetShape(shape_id) - except KeyError: - shape = transitfeed.Shape(shape_id) - for point in osm_r.shape: - shape.AddPoint(lat=float(point["lat"]), lon=float(point["lon"])) - schedule.AddShapeObject(shape) - - return shape_id - - def add_trips_for_route(schedule, gtfs_route, itinerary, service_period, shape_id, estaciones, horarios): # debug diff --git a/creators/trips_creator.py b/creators/trips_creator.py index 42172631..aa5a18fe 100644 --- a/creators/trips_creator.py +++ b/creators/trips_creator.py @@ -14,3 +14,31 @@ def __repr__(self): def add_trips_to_schedule(self, schedule, data): raise NotImplementedError("Should have implemented this") + + @staticmethod + def interpolate_stop_times(trip): + """ + interpolate stop_times, because Navitia does not handle this itself by now + """ + for secs, stop_time, is_timepoint in trip.GetTimeInterpolatedStops(): + if not is_timepoint: + stop_time.arrival_secs = secs + stop_time.departure_secs = secs + trip.ReplaceStopTimeObject(stop_time) + + @staticmethod + def add_shape(schedule, route_id, osm_r): + """ + create GTFS shape and return shape_id to add on GTFS trip + """ + import transitfeed + shape_id = str(route_id) + try: + schedule.GetShape(shape_id) + except KeyError: + shape = transitfeed.Shape(shape_id) + for point in osm_r.shape: + shape.AddPoint( + lat=float(point["lat"]), lon=float(point["lon"])) + schedule.AddShapeObject(shape) + return shape_id