Upgrade origin-src to google transit feed 1.2.6
[bus.git] / origin-src / transitfeed-1.2.6 / transitfeed / route.py
blob:a/origin-src/transitfeed-1.2.6/transitfeed/route.py -> blob:b/origin-src/transitfeed-1.2.6/transitfeed/route.py
--- a/origin-src/transitfeed-1.2.6/transitfeed/route.py
+++ b/origin-src/transitfeed-1.2.6/transitfeed/route.py
@@ -1,1 +1,273 @@
-
+#!/usr/bin/python2.5
+
+# Copyright (C) 2007 Google Inc.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+from gtfsobjectbase import GtfsObjectBase
+import problems as problems_module
+import util
+
+class Route(GtfsObjectBase):
+  """Represents a single route."""
+
+  _REQUIRED_FIELD_NAMES = [
+    'route_id', 'route_short_name', 'route_long_name', 'route_type'
+    ]
+  _FIELD_NAMES = _REQUIRED_FIELD_NAMES + [
+    'agency_id', 'route_desc', 'route_url', 'route_color', 'route_text_color'
+    ]
+  _ROUTE_TYPES = {
+    0: {'name':'Tram', 'max_speed':100},
+    1: {'name':'Subway', 'max_speed':150},
+    2: {'name':'Rail', 'max_speed':300},
+    3: {'name':'Bus', 'max_speed':100},
+    4: {'name':'Ferry', 'max_speed':80},
+    5: {'name':'Cable Car', 'max_speed':50},
+    6: {'name':'Gondola', 'max_speed':50},
+    7: {'name':'Funicular', 'max_speed':50},
+    }
+  # Create a reverse lookup dict of route type names to route types.
+  _ROUTE_TYPE_IDS = set(_ROUTE_TYPES.keys())
+  _ROUTE_TYPE_NAMES = dict((v['name'], k) for k, v in _ROUTE_TYPES.items())
+  _TABLE_NAME = 'routes'
+
+  def __init__(self, short_name=None, long_name=None, route_type=None,
+               route_id=None, agency_id=None, field_dict=None):
+    self._schedule = None
+    self._trips = []
+
+    if not field_dict:
+      field_dict = {}
+      if short_name is not None:
+        field_dict['route_short_name'] = short_name
+      if long_name is not None:
+        field_dict['route_long_name'] = long_name
+      if route_type is not None:
+        if route_type in self._ROUTE_TYPE_NAMES:
+          self.route_type = self._ROUTE_TYPE_NAMES[route_type]
+        else:
+          field_dict['route_type'] = route_type
+      if route_id is not None:
+        field_dict['route_id'] = route_id
+      if agency_id is not None:
+        field_dict['agency_id'] = agency_id
+    self.__dict__.update(field_dict)
+
+  def AddTrip(self, schedule=None, headsign=None, service_period=None,
+              trip_id=None):
+    """Add a trip to this route.
+
+    Args:
+      schedule: a Schedule object which will hold the new trip or None to use
+        the schedule of this route.
+      headsign: headsign of the trip as a string
+      service_period: a ServicePeriod object or None to use
+        schedule.GetDefaultServicePeriod()
+      trip_id: optional trip_id for the new trip
+
+    Returns:
+      a new Trip object
+    """
+    if schedule is None:
+      assert self._schedule is not None
+      schedule = self._schedule
+    if trip_id is None:
+      trip_id = util.FindUniqueId(schedule.trips)
+    if service_period is None:
+      service_period = schedule.GetDefaultServicePeriod()
+    trip_class = self.GetGtfsFactory().Trip
+    trip_obj = trip_class(route=self, headsign=headsign,
+                service_period=service_period, trip_id=trip_id)
+    schedule.AddTripObject(trip_obj)
+    return trip_obj
+
+  def _AddTripObject(self, trip):
+    # Only class Schedule may call this. Users of the API should call
+    # Route.AddTrip or schedule.AddTripObject.
+    self._trips.append(trip)
+
+  def __getattr__(self, name):
+    """Return None or the default value if name is a known attribute.
+
+    This method overrides GtfsObjectBase.__getattr__ to provide backwards
+    compatible access to trips.
+    """
+    if name == 'trips':
+      return self._trips
+    else:
+      return GtfsObjectBase.__getattr__(self, name)
+
+  def GetPatternIdTripDict(self):
+    """Return a dictionary that maps pattern_id to a list of Trip objects."""
+    d = {}
+    for t in self._trips:
+      d.setdefault(t.pattern_id, []).append(t)
+    return d
+
+  def ValidateRouteIdIsPresent(self, problems):
+    if util.IsEmpty(self.route_id):
+      problems.MissingValue('route_id')
+
+  def ValidateRouteTypeIsPresent(self, problems):
+    if util.IsEmpty(self.route_type):
+      problems.MissingValue('route_type')
+
+  def ValidateRouteShortAndLongNamesAreNotBlank(self, problems):
+    if util.IsEmpty(self.route_short_name) and \
+        util.IsEmpty(self.route_long_name):
+      problems.InvalidValue('route_short_name',
+                            self.route_short_name,
+                            'Both route_short_name and '
+                            'route_long name are blank.')
+
+  def ValidateRouteShortNameIsNotTooLong(self, problems):
+    if self.route_short_name and len(self.route_short_name) > 6:
+      problems.InvalidValue('route_short_name',
+                            self.route_short_name,
+                            'This route_short_name is relatively long, which '
+                            'probably means that it contains a place name.  '
+                            'You should only use this field to hold a short '
+                            'code that riders use to identify a route.  '
+                            'If this route doesn\'t have such a code, it\'s '
+                            'OK to leave this field empty.',
+                            type=problems_module.TYPE_WARNING)
+
+  def ValidateRouteLongNameDoesNotContainShortName(self, problems):
+    if self.route_short_name and self.route_long_name:
+      short_name = self.route_short_name.strip().lower()
+      long_name = self.route_long_name.strip().lower()
+      if (long_name.startswith(short_name + ' ') or
+          long_name.startswith(short_name + '(') or
+          long_name.startswith(short_name + '-')):
+        problems.InvalidValue('route_long_name',
+                              self.route_long_name,
+                              'route_long_name shouldn\'t contain '
+                              'the route_short_name value, as both '
+                              'fields are often displayed '
+                              'side-by-side.',
+                              type=problems_module.TYPE_WARNING)
+
+  def ValidateRouteShortAndLongNamesAreNotEqual(self, problems):
+    if self.route_short_name and self.route_long_name:
+      short_name = self.route_short_name.strip().lower()
+      long_name = self.route_long_name.strip().lower()
+      if long_name == short_name:
+        problems.InvalidValue('route_long_name',
+                              self.route_long_name,
+                              'route_long_name shouldn\'t be the same '
+                              'the route_short_name value, as both '
+                              'fields are often displayed '
+                              'side-by-side.  It\'s OK to omit either the '
+                              'short or long name (but not both).',
+                              type=problems_module.TYPE_WARNING)
+
+  def ValidateRouteDescriptionNotTheSameAsRouteName(self, problems):
+    if (self.route_desc and
+        ((self.route_desc == self.route_short_name) or
+         (self.route_desc == self.route_long_name))):
+      problems.InvalidValue('route_desc',
+                            self.route_desc,
+                            'route_desc shouldn\'t be the same as '
+                            'route_short_name or route_long_name')
+  def ValidateRouteTypeHasValidValue(self, problems):
+    if self.route_type is not None:
+      try:
+        if not isinstance(self.route_type, int):
+          self.route_type = util.NonNegIntStringToInt(self.route_type, problems)
+      except (TypeError, ValueError):
+        problems.InvalidValue('route_type', self.route_type)
+      else:
+        if self.route_type not in self._ROUTE_TYPE_IDS:
+          problems.InvalidValue('route_type',
+                                self.route_type,
+                                type=problems_module.TYPE_WARNING)
+
+  def ValidateRouteUrl(self, problems):
+    if self.route_url and not util.IsValidURL(self.route_url):
+      problems.InvalidValue('route_url', self.route_url)
+
+  def ValidateRouteColor(self, problems):
+    if self.route_color:
+      if not util.IsValidColor(self.route_color):
+        problems.InvalidValue('route_color', self.route_color,
+                              'route_color should be a valid color description '
+                              'which consists of 6 hexadecimal characters '
+                              'representing the RGB values. Example: 44AA06')
+        self.route_color = None
+
+  def ValidateRouteTextColor(self, problems):
+    if self.route_text_color:
+      if not util.IsValidColor(self.route_text_color):
+        problems.InvalidValue('route_text_color', self.route_text_color,
+                              'route_text_color should be a valid color '
+                              'description, which consists of 6 hexadecimal '
+                              'characters representing the RGB values. '
+                              'Example: 44AA06')
+        self.route_text_color = None
+
+  def ValidateRouteAndTextColors(self, problems):
+    if self.route_color:
+      bg_lum  = util.ColorLuminance(self.route_color)
+    else:
+      bg_lum = util.ColorLuminance('ffffff')   # white (default)
+    if self.route_text_color:
+      txt_lum = util.ColorLuminance(self.route_text_color)
+    else:
+      txt_lum = util.ColorLuminance('000000')  # black (default)
+    if abs(txt_lum - bg_lum) < 510/7.:
+      # http://www.w3.org/TR/2000/WD-AERT-20000426#color-contrast recommends
+      # a threshold of 125, but that is for normal text and too harsh for
+      # big colored logos like line names, so we keep the original threshold
+      # from r541 (but note that weight has shifted between RGB components).
+      problems.InvalidValue('route_color', self.route_color,
+                            'The route_text_color and route_color should '
+                            'be set to contrasting colors, as they are used '
+                            'as the text and background color (respectively) '
+                            'for displaying route names.  When left blank, '
+                            'route_text_color defaults to 000000 (black) and '
+                            'route_color defaults to FFFFFF (white).  A common '
+                            'source of issues here is setting route_color to '
+                            'a dark color, while leaving route_text_color set '
+                            'to black.  In this case, route_text_color should '
+                            'be set to a lighter color like FFFFFF to ensure '
+                            'a legible contrast between the two.',
+                            type=problems_module.TYPE_WARNING)  
+
+  def ValidateBeforeAdd(self, problems):
+    self.ValidateRouteIdIsPresent(problems)
+    self.ValidateRouteTypeIsPresent(problems)
+    self.ValidateRouteShortAndLongNamesAreNotBlank(problems)
+    self.ValidateRouteShortNameIsNotTooLong(problems)
+    self.ValidateRouteLongNameDoesNotContainShortName(problems)
+    self.ValidateRouteShortAndLongNamesAreNotEqual(problems)
+    self.ValidateRouteDescriptionNotTheSameAsRouteName(problems)
+    self.ValidateRouteTypeHasValidValue(problems)
+    self.ValidateRouteUrl(problems)
+    self.ValidateRouteColor(problems)
+    self.ValidateRouteTextColor(problems)
+    self.ValidateRouteAndTextColors(problems)
+
+    # None of these checks are blocking
+    return True
+
+  def ValidateAfterAdd(self, problems):
+    return
+
+  def AddToSchedule(self, schedule, problems):
+    schedule.AddRouteObject(self, problems)
+
+  def Validate(self, problems=problems_module.default_problem_reporter):
+    self.ValidateBeforeAdd(problems)
+    self.ValidateAfterAdd(problems)
+