-
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
Data structure, standard creators, Nicaragua networks #99
Changes from all commits
aec51de
2b6de6e
3c8079a
54358fa
8b5b4a1
95ddd96
cdb7b69
d42cc66
1e6995e
42dca8e
3f2470b
ec3f729
fbb8dd5
a306be0
1399c21
459a22e
756fcbd
057ca2f
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 |
---|---|---|
@@ -0,0 +1,186 @@ | ||
# coding=utf-8 | ||
|
||
import sys | ||
import attr | ||
|
||
|
||
@attr.s | ||
class Element(object): | ||
"""The basic data element. | ||
Contains the common attributes all other data classes share. | ||
|
||
""" | ||
osm_id = attr.ib() | ||
osm_type = attr.ib() | ||
osm_url = attr.ib() | ||
|
||
tags = attr.ib() | ||
name = attr.ib() | ||
|
||
|
||
@attr.s | ||
class Line(Element): | ||
"""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". | ||
|
||
""" | ||
route_id = attr.ib() | ||
|
||
route_type = attr.ib(default=None) | ||
route_desc = attr.ib(default=None) | ||
route_color = attr.ib(default="#FFFFFF") | ||
route_text_color = attr.ib(default="#000000") | ||
|
||
# Related route variants | ||
_itineraries = attr.ib(default=attr.Factory(list)) | ||
|
||
def __attrs_post_init__(self): | ||
''' | ||
Populates the object with information obtained from the tags | ||
''' | ||
# pylint: disable=unsupported-membership-test,unsubscriptable-object | ||
if 'colour' in self.tags: | ||
self.route_color = self.tags['colour'] | ||
|
||
# pylint: disable=unsupported-membership-test,unsubscriptable-object | ||
if 'route_master' in self.tags: | ||
self.route_type = self.tags['route_master'].capitalize() | ||
else: | ||
sys.stderr.write( | ||
"Warning: Route master relation without a route_master tag:\n") | ||
sys.stderr.write(" " + self.osm_url + "\n") | ||
|
||
# Try to guess the type differently | ||
if 'route' in self.tags: | ||
self.route_type = self.tags['route'].capitalize() | ||
else: | ||
self.route_type = "Bus" | ||
|
||
def add_itinerary(self, itinerary): | ||
|
||
if self.route_id != itinerary.route_id: | ||
raise ValueError('Itinerary route ID (' + | ||
itinerary.route_id + | ||
') does not match Line route ID (' + | ||
self.route_id + ')') | ||
# pylint: disable=no-member | ||
self._itineraries.append(itinerary) | ||
|
||
def get_itineraries(self): | ||
return self._itineraries | ||
|
||
|
||
@attr.s | ||
class Itinerary(Element): | ||
"""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 base to create "trips" | ||
|
||
""" | ||
route_id = attr.ib() | ||
shape = attr.ib() | ||
|
||
line = attr.ib(default=None) | ||
fr = attr.ib(default=None) | ||
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. Why are you using 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. 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. arg, sorry that I've missed that ! let's keep it that way for now ;) |
||
to = attr.ib(default=None) | ||
duration = attr.ib(default=None) | ||
|
||
# All stop objects of itinerary | ||
stops = attr.ib(default=attr.Factory(list)) | ||
|
||
def __attrs_post_init__(self): | ||
''' | ||
Populates the object with information obtained from the tags | ||
''' | ||
# pylint: disable=unsupported-membership-test,unsubscriptable-object | ||
if 'from' in self.tags: | ||
self.fr = self.tags['from'] | ||
|
||
# pylint: disable=unsupported-membership-test,unsubscriptable-object | ||
if 'to' in self.tags: | ||
self.to = self.tags['to'] | ||
|
||
def get_stops(self): | ||
return self.stops | ||
|
||
|
||
@attr.s | ||
class Station(Element): | ||
"""A public transport stop of the type station. | ||
|
||
It's a representation of a possible group of stops. | ||
|
||
In OpenStreetMap this is usually represented as "stop_area" relation. | ||
In GTFS it is handled as a stop with a location_type=1. Regular Stops with | ||
location_type=0 might specify a station as parent_station. | ||
|
||
""" | ||
lat = attr.ib() | ||
lon = attr.ib() | ||
|
||
stop_id = attr.ib(default="") | ||
location_type = attr.ib(default=1) | ||
|
||
# Stops forming part of this Station | ||
_members = attr.ib(default=attr.Factory(list)) | ||
|
||
def set_members(self, members): | ||
self._members = members | ||
|
||
def get_members(self): | ||
return self._members | ||
|
||
def get_stop_id(self): | ||
return self.stop_id | ||
|
||
def set_stop_id(self, stop_id): | ||
self.stop_id = stop_id | ||
|
||
|
||
@attr.s | ||
class Stop(Element): | ||
"""A public transport stop. | ||
|
||
In OpenStreetMap this is usually represented as an object of the role | ||
"plattform" in the route. | ||
|
||
""" | ||
lat = attr.ib() | ||
lon = attr.ib() | ||
|
||
stop_id = attr.ib("") | ||
location_type = attr.ib(default=0) | ||
|
||
# The id of the Station this Stop might be part of. | ||
_parent_station = attr.ib(default=None) | ||
|
||
def set_parent_station(self, identifier, override=False): | ||
""" | ||
Set the parent_station_id on the first time; | ||
Second attempts throw a warning | ||
""" | ||
if self._parent_station is None or override is True: | ||
self._parent_station = identifier | ||
else: | ||
sys.stderr.write("Warning: Stop is part of two stop areas:\n") | ||
sys.stderr.write( | ||
"https://osm.org/" + self.osm_type + "/" + str( | ||
self.osm_id) + "\n") | ||
|
||
def get_parent_station(self): | ||
return self._parent_station | ||
|
||
def get_stop_id(self): | ||
return self.stop_id | ||
|
||
def set_stop_id(self, stop_id): | ||
self.stop_id = stop_id | ||
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 personally liked how before, the classes were logically grouped and split up in several files instead of one huge file holding all classes like it is 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. Oh, yeah, big question on having 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. Couldn't you just have three files: elements (or osm_base), osm_routes and osm_stops? The reason why the modules should not be just names routes and stops, is because then variable names in the code shadow module names. 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 guess this is because of individual taste... I preferred to put them all in one file. But I'm not that strong about this. So, renamed them to By the way, it doesn't make sense to use the prefix 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. Did you see this:
So in Python it is then ambiguous 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. So, why not staying with one |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,68 @@ | ||
# coding=utf-8 | ||
|
||
import sys | ||
from math import cos, sin, atan2, sqrt, radians, degrees | ||
|
||
|
||
class Helper(object): | ||
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. Great idea with the Helper class! |
||
"""The Helper class contains useful static functions | ||
|
||
""" | ||
|
||
@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 | ||
|
||
@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 interpolate_stop_times(trip): | ||
""" | ||
Interpolate stop_times, because Navitia does not handle this itself | ||
""" | ||
try: | ||
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) | ||
except ValueError as e: | ||
print(e) |
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.
@jamescr looks like you are doing the same thing in #120. Once is probably enough.
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.
Yes, we just have to coordinate which one to merge in first. I would be happy and propose to give the preference to #120, as this one is smaller and easier to review first and then rebase here.
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 agree doing this once is enough but in configuration.py there is a difference between both Pull Requests (in line 86 of this PR the
read()
method should be deleted).The reason why I created the "PR" on master instead to directly in mapanica-master is because this issue is a bug in currently master branch code.
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.
@jamescr but doesn't your PR add the
read()
method there as well? I don't see the difference!?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.
My PR added the
read()
in line 84 and do nothing in line 86 (unifying the schedule_source type to str), This PR do nothing on line 84 and adds theread()
in line 86.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.
Applied the minor change of the
read
call in the same line as the PR in #120. This way I tested the code and approved the PR there. Either one, #99 or #120 which gets in first will fix the issue in the same way.