-
Notifications
You must be signed in to change notification settings - Fork 31
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Add Addis Ababa and Add Partial GTFSFlex Implementation (#156) #159
base: master
Are you sure you want to change the base?
Changes from 17 commits
3d2fb3f
0220d8d
68164d4
8ad2de6
223fb80
7caa135
e923038
f92bfc9
090e03d
5909a8a
1dd9d17
54b5806
3eefdcd
8f86d82
8281870
b4bf0b9
8dfb768
b1f1f0c
56a071c
390fa50
c092176
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -23,4 +23,4 @@ __pycache__ | |
env | ||
|
||
# Visual Studio Code workspace settings folder | ||
.vscode | ||
.vscode |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,16 @@ | ||
{ | ||
// Use IntelliSense to learn about possible attributes. | ||
// Hover to view descriptions of existing attributes. | ||
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 | ||
"version": "0.2.0", | ||
"configurations": [ | ||
{ | ||
"name": "Python: OSM2GTFS", | ||
"type": "python", | ||
"request": "launch", | ||
"program": "/usr/local/bin/osm2gtfs", | ||
"console": "integratedTerminal", | ||
"args": ["-c", "osm2gtfs/creators/et_addisababa/config.json"] | ||
} | ||
] | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,2 @@ | ||
#!/usr/bin/env python | ||
# coding=utf-8 |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,35 @@ | ||
{ | ||
"query": { | ||
"bbox": { | ||
"n": "9.13", | ||
"s": "8.8", | ||
"e": "38.96", | ||
"w": "38.61" | ||
}, | ||
"tags": { | ||
"route": ["bus", "light_rail", "share_taxi"] | ||
} | ||
}, | ||
"stops": { | ||
"name_without": "no name", | ||
"name_auto": "yes" | ||
}, | ||
"agency": { | ||
"agency_id": "AA", | ||
"agency_name": "Addis Ababa Transport (all)", | ||
"agency_url": "https://example.com", | ||
"agency_timezone": "Africa/Addis_Ababa", | ||
"agency_lang": "en", | ||
"agency_phone": "", | ||
"agency_fare_url": "" | ||
}, | ||
"feed_info": { | ||
"publisher_name": "AddisMap", | ||
"publisher_url": "http://addismap.com", | ||
"version": "0.1", | ||
"start_date": "20191201", | ||
"end_date": "20201101" | ||
}, | ||
"output_file": "data/et-addisababa.zip", | ||
"selector": "et_addisababa" | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,36 @@ | ||
{ | ||
"query": { | ||
"bbox": { | ||
"n": "9.13", | ||
"s": "8.8", | ||
"e": "38.96", | ||
"w": "38.61" | ||
}, | ||
"tags": { | ||
"route": "bus", | ||
"operator:short": "AB" | ||
} | ||
}, | ||
"stops": { | ||
"name_without": "no name", | ||
"name_auto": "yes" | ||
}, | ||
"agency": { | ||
"agency_id": "AB", | ||
"agency_name": "Anbessa City Bus", | ||
"agency_url": "https://example.com", | ||
"agency_timezone": "Africa/Addis_Ababa", | ||
"agency_lang": "en", | ||
"agency_phone": "", | ||
"agency_fare_url": "" | ||
}, | ||
"feed_info": { | ||
"publisher_name": "AddisMap", | ||
"publisher_url": "http://addismap.com", | ||
"version": "0.1", | ||
"start_date": "20190101", | ||
"end_date": "20991231" | ||
}, | ||
"output_file": "data/et-addisababa-ab.zip", | ||
"selector": "et_addisababa" | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,36 @@ | ||
{ | ||
"query": { | ||
"bbox": { | ||
"n": "9.13", | ||
"s": "8.8", | ||
"e": "38.96", | ||
"w": "38.61" | ||
}, | ||
"tags": { | ||
"route": "bus", | ||
"operator:short": "SH" | ||
} | ||
}, | ||
"stops": { | ||
"name_without": "no name", | ||
"name_auto": "yes" | ||
}, | ||
"agency": { | ||
"agency_id": "SH", | ||
"agency_name": "Sheger City Bus", | ||
"agency_url": "https://example.com", | ||
"agency_timezone": "Africa/Addis_Ababa", | ||
"agency_lang": "en", | ||
"agency_phone": "", | ||
"agency_fare_url": "" | ||
}, | ||
"feed_info": { | ||
"publisher_name": "AddisMap", | ||
"publisher_url": "http://addismap.com", | ||
"version": "0.1", | ||
"start_date": "20190101", | ||
"end_date": "20991231" | ||
}, | ||
"output_file": "data/et-addisababa-sh.zip", | ||
"selector": "et_addisababa" | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,13 @@ | ||
# coding=utf-8 | ||
|
||
from osm2gtfs.creators.routes_creator import RoutesCreator | ||
|
||
|
||
class RoutesCreatorEtAddisababa(RoutesCreator): | ||
|
||
def add_routes_to_feed(self, feed, data): | ||
# Get routes information | ||
data.get_routes() | ||
|
||
# GTFS routes are created in TripsCreator | ||
return |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,10 @@ | ||
# coding=utf-8 | ||
|
||
from osm2gtfs.creators.schedule_creator import ScheduleCreator | ||
|
||
|
||
class ScheduleCreatorEtAddisababa(ScheduleCreator): | ||
|
||
def add_schedule_to_data(self, data): | ||
# Don't use any schedule source | ||
data.schedule = None |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,8 @@ | ||
# coding=utf-8 | ||
|
||
from osm2gtfs.creators.stops_creator import StopsCreator | ||
|
||
class StopsCreatorEtAddisababa(StopsCreator): | ||
""" | ||
nothing overwritten | ||
""" |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,133 @@ | ||
# coding=utf-8 | ||
|
||
from datetime import timedelta, datetime | ||
|
||
from osm2gtfs.creators.trips_creator import TripsCreator | ||
from osm2gtfs.core.helper import Helper | ||
from osm2gtfs.core.elements import Line | ||
|
||
from transitfeed.trip import Trip | ||
|
||
|
||
def time_string_to_minutes(time_string): | ||
(hours, minutes, seconds) = time_string.split(':') | ||
return int(hours) * 60 + int(minutes) | ||
|
||
class TripsCreatorEtAddisababa(TripsCreator): | ||
service_weekday = None | ||
|
||
def add_trips_to_feed(self, feed, data): | ||
self.service_weekday = feed.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 not isinstance(line, Line): | ||
continue | ||
print("Generating schedule for line: " + route_ref) | ||
|
||
flex_flag = None | ||
if 'route_master' in line.tags and line.tags['route_master'] == "light_rail": | ||
route_type = "Tram" | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I don't understand why you need to override the route_type here 🤔 There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. In OSM we tagged it as light_rail, and I was thinking it needs to be mapped to Tram in GTFS; but I am not sure |
||
route_suffix = " (Light Rail)" | ||
elif 'route_master' in line.tags and line.tags['route_master'] == "share_taxi": | ||
route_type = "Bus" | ||
route_suffix = " (Minibus)" | ||
flex_flag = 0 | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. if There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Probably yes :-) There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I don't like the idea of it being hard-coded. Can we rather have that information as an attribute on the route / route_master in OSM ? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Possible, need to discuss with the OSM community how it is mapped best on OSM side. |
||
else: | ||
route_type = "Bus" | ||
route_suffix = "" | ||
|
||
line_gtfs = feed.AddRoute( | ||
short_name=str(line.route_id), | ||
long_name=line.name + route_suffix, | ||
# 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=route_type, | ||
route_id=line.osm_id) | ||
line_gtfs.agency_id = feed.GetDefaultAgency().agency_id | ||
line_gtfs.route_desc = "" | ||
line_gtfs.route_color = "1779c2" | ||
line_gtfs.route_text_color = "ffffff" | ||
|
||
route_index = 0 | ||
itineraries = line.get_itineraries() | ||
for a_route in itineraries: | ||
trip_gtfs = line_gtfs.AddTrip(feed) | ||
trip_gtfs.shape_id = self._add_shape_to_feed( | ||
feed, a_route.osm_id, 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') + route_suffix | ||
|
||
DEFAULT_ROUTE_FREQUENCY = 30 | ||
DEFAULT_TRAVEL_TIME = 120 | ||
|
||
frequency = None | ||
|
||
ROUTE_FREQUENCY = DEFAULT_ROUTE_FREQUENCY | ||
|
||
if "interval" in a_route.tags: | ||
frequency = a_route.tags['interval'] | ||
try: | ||
ROUTE_FREQUENCY = time_string_to_minutes(frequency) | ||
if not ROUTE_FREQUENCY > 0: | ||
print("frequency is invalid for route_master " + str( | ||
line.osm_id)) | ||
except (ValueError, TypeError) as e: | ||
print("frequency not a number for route_master " + str( | ||
line.osm_id)) | ||
|
||
trip_gtfs.AddFrequency( | ||
"05:00:00", "22:00:00", ROUTE_FREQUENCY * 60) | ||
|
||
if 'duration' in a_route.tags: | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. the There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Interesting, how could we do this? |
||
try: | ||
TRAVEL_TIME = time_string_to_minutes(a_route.tags['duration']); | ||
if not TRAVEL_TIME > 0: | ||
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 / exception thrown for route with OSM ID " + str( | ||
a_route.osm_id)) | ||
TRAVEL_TIME = DEFAULT_TRAVEL_TIME | ||
else: | ||
TRAVEL_TIME = DEFAULT_TRAVEL_TIME | ||
print("WARNING: No duration set --- Using default travel time for route with OSM ID " +str(a_route.osm_id)); | ||
|
||
for index_stop, a_stop in enumerate(a_route.stops): | ||
stop_id = a_stop | ||
departure_time = datetime(2008, 11, 22, 6, 0, 0) | ||
|
||
if index_stop == 0: | ||
trip_gtfs.AddStopTime(feed.GetStop( | ||
str(stop_id)), stop_time=departure_time.strftime( | ||
"%H:%M:%S"), continuous_pickup = flex_flag, continuous_drop_off = flex_flag) | ||
elif index_stop == len(a_route.stops) - 1: | ||
departure_time += timedelta(minutes=TRAVEL_TIME) | ||
trip_gtfs.AddStopTime(feed.GetStop( | ||
str(stop_id)), stop_time=departure_time.strftime( | ||
"%H:%M:%S"), continuous_pickup = flex_flag, continuous_drop_off = flex_flag) | ||
else: | ||
trip_gtfs.AddStopTime(feed.GetStop(str(stop_id)), continuous_pickup = flex_flag, continuous_drop_off = flex_flag) | ||
|
||
for secs, stop_time, is_timepoint in trip_gtfs.GetTimeInterpolatedStops(): | ||
stop_time.continuous_pickup_flag = flex_flag | ||
stop_time.continuous_drop_off_flag = flex_flag | ||
if not is_timepoint: | ||
stop_time.arrival_secs = secs | ||
stop_time.departure_secs = secs | ||
trip_gtfs.ReplaceStopTimeObject(stop_time) | ||
|
||
Helper.interpolate_stop_times(trip_gtfs) |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
Adds Flexible Pick Off / Drop Off (Part of the GTFSFlex specification) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 👍 That's really cool to add this feature! Can you please add a link to the documentation of the format ? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. long time no feedback, sorry :-) |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,6 @@ | ||
#!/usr/bin/python2.5 | ||
|
||
from __future__ import absolute_import | ||
from .flexstoptime import * | ||
from .flexschedule import * | ||
from .setup_extension import * |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,11 @@ | ||
from transitfeed.schedule import Schedule | ||
|
||
class FlexSchedule(Schedule): | ||
|
||
def ConnectDb(self, memory_db): | ||
super(FlexSchedule, self).ConnectDb(memory_db) | ||
cursor = self._connection.cursor() | ||
cursor.execute("""ALTER TABLE stop_times ADD continuous_pickup INTEGER;""") | ||
cursor.execute("""ALTER TABLE stop_times ADD continuous_drop_off INTEGER;""") | ||
|
||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
All config should come with some regression tests, so we can make sure our future changes in osm2gtfs do not break your own implementation.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I will probably not be able to add those from my current limited python skills. Help is appreciated.