Upgrade origin-src to google transit feed 1.2.6
[bus.git] / origin-src / transitfeed-1.2.6 / transitfeed / stop.py
blob:a/origin-src/transitfeed-1.2.6/transitfeed/stop.py -> blob:b/origin-src/transitfeed-1.2.6/transitfeed/stop.py
--- a/origin-src/transitfeed-1.2.6/transitfeed/stop.py
+++ b/origin-src/transitfeed-1.2.6/transitfeed/stop.py
@@ -1,1 +1,252 @@
-
+#!/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.
+
+import warnings
+
+from gtfsobjectbase import GtfsObjectBase
+import problems as problems_module
+import util
+
+class Stop(GtfsObjectBase):
+  """Represents a single stop. A stop must have a latitude, longitude and name.
+
+  Callers may assign arbitrary values to instance attributes.
+  Stop.ParseAttributes validates attributes according to GTFS and converts some
+  into native types. ParseAttributes may delete invalid attributes.
+  Accessing an attribute that is a column in GTFS will return None if this
+  object does not have a value or it is ''.
+  A Stop object acts like a dict with string values.
+
+  Attributes:
+    stop_lat: a float representing the latitude of the stop
+    stop_lon: a float representing the longitude of the stop
+    All other attributes are strings.
+  """
+  _REQUIRED_FIELD_NAMES = ['stop_id', 'stop_name', 'stop_lat', 'stop_lon']
+  _FIELD_NAMES = _REQUIRED_FIELD_NAMES + \
+                 ['stop_desc', 'zone_id', 'stop_url', 'stop_code',
+                  'location_type', 'parent_station']
+  _TABLE_NAME = 'stops'
+
+  def __init__(self, lat=None, lng=None, name=None, stop_id=None,
+               field_dict=None, stop_code=None):
+    """Initialize a new Stop object.
+
+    Args:
+      field_dict: A dictionary mapping attribute name to unicode string
+      lat: a float, ignored when field_dict is present
+      lng: a float, ignored when field_dict is present
+      name: a string, ignored when field_dict is present
+      stop_id: a string, ignored when field_dict is present
+      stop_code: a string, ignored when field_dict is present
+    """
+    self._schedule = None
+    if field_dict:
+      if isinstance(field_dict, self.__class__):
+        # Special case so that we don't need to re-parse the attributes to
+        # native types iteritems returns all attributes that don't start with _
+        for k, v in field_dict.iteritems():
+          self.__dict__[k] = v
+      else:
+        self.__dict__.update(field_dict)
+    else:
+      if lat is not None:
+        self.stop_lat = lat
+      if lng is not None:
+        self.stop_lon = lng
+      if name is not None:
+        self.stop_name = name
+      if stop_id is not None:
+        self.stop_id = stop_id
+      if stop_code is not None:
+        self.stop_code = stop_code
+
+  def GetTrips(self, schedule=None):
+    """Return iterable containing trips that visit this stop."""
+    return [trip for trip, ss in self._GetTripSequence(schedule)]
+
+  def _GetTripSequence(self, schedule=None):
+    """Return a list of (trip, stop_sequence) for all trips visiting this stop.
+
+    A trip may be in the list multiple times with different index.
+    stop_sequence is an integer.
+
+    Args:
+      schedule: Deprecated, do not use.
+    """
+    if schedule is None:
+      schedule = getattr(self, "_schedule", None)
+    if schedule is None:
+      warnings.warn("No longer supported. _schedule attribute is  used to get "
+                    "stop_times table", DeprecationWarning)
+    cursor = schedule._connection.cursor()
+    cursor.execute("SELECT trip_id,stop_sequence FROM stop_times "
+                   "WHERE stop_id=?",
+                   (self.stop_id, ))
+    return [(schedule.GetTrip(row[0]), row[1]) for row in cursor]
+
+  def _GetTripIndex(self, schedule=None):
+    """Return a list of (trip, index).
+
+    trip: a Trip object
+    index: an offset in trip.GetStopTimes()
+    """
+    trip_index = []
+    for trip, sequence in self._GetTripSequence(schedule):
+      for index, st in enumerate(trip.GetStopTimes()):
+        if st.stop_sequence == sequence:
+          trip_index.append((trip, index))
+          break
+      else:
+        raise RuntimeError("stop_sequence %d not found in trip_id %s" %
+                           sequence, trip.trip_id)
+    return trip_index
+
+  def GetStopTimeTrips(self, schedule=None):
+    """Return a list of (time, (trip, index), is_timepoint).
+
+    time: an integer. It might be interpolated.
+    trip: a Trip object.
+    index: the offset of this stop in trip.GetStopTimes(), which may be
+      different from the stop_sequence.
+    is_timepoint: a bool
+    """
+    time_trips = []
+    for trip, index in self._GetTripIndex(schedule):
+      secs, stoptime, is_timepoint = trip.GetTimeInterpolatedStops()[index]
+      time_trips.append((secs, (trip, index), is_timepoint))
+    return time_trips
+
+  def __getattr__(self, name):
+    """Return None or the default value if name is a known attribute.
+
+    This method is only called when name is not found in __dict__.
+    """
+    if name == "location_type":
+      return 0
+    elif name == "trip_index":
+      return self._GetTripIndex()
+    elif name in self._FIELD_NAMES:
+      return None
+    else:
+      raise AttributeError(name)
+
+  def ValidateStopLatitude(self, problems):
+    if self.stop_lat:
+      value = self.stop_lat
+      try:
+        if not isinstance(value, (float, int)):
+          self.stop_lat = util.FloatStringToFloat(value, problems)
+      except (ValueError, TypeError):
+        problems.InvalidValue('stop_lat', value)
+        del self.stop_lat
+      else:
+        if self.stop_lat > 90 or self.stop_lat < -90:
+          problems.InvalidValue('stop_lat', value)
+
+  def ValidateStopLongitude(self, problems):
+    if self.stop_lon:
+      value = self.stop_lon
+      try:
+        if not isinstance(value, (float, int)):
+          self.stop_lon = util.FloatStringToFloat(value, problems)
+      except (ValueError, TypeError):
+        problems.InvalidValue('stop_lon', value)
+        del self.stop_lon
+      else:
+        if self.stop_lon > 180 or self.stop_lon < -180:
+          problems.InvalidValue('stop_lon', value)
+
+  def ValidateStopUrl(self, problems):
+      value = self.stop_url
+      if value and not util.IsValidURL(value):
+        problems.InvalidValue('stop_url', value)
+        del self.stop_url
+
+  def ValidateStopLocationType(self, problems):
+      value = self.location_type
+      if value == '':
+        self.location_type = 0
+      else:
+        try:
+          self.location_type = int(value)
+        except (ValueError, TypeError):
+          problems.InvalidValue('location_type', value)
+          del self.location_type
+        else:
+          if self.location_type not in (0, 1):
+            problems.InvalidValue('location_type', value, 
+                                  type=problems_module.TYPE_WARNING)
+
+  def ValidateStopRequiredFields(self, problems):
+    for required in self._REQUIRED_FIELD_NAMES:
+      if util.IsEmpty(getattr(self, required, None)):
+        # TODO: For now I'm keeping the API stable but it would be cleaner to
+        # treat whitespace stop_id as invalid, instead of missing
+        problems.MissingValue(required)
+
+  def ValidateStopNotTooCloseToOrigin(self, problems):
+    if (self.stop_lat is not None and self.stop_lon is not None and
+        abs(self.stop_lat) < 1.0) and (abs(self.stop_lon) < 1.0):
+      problems.InvalidValue('stop_lat', self.stop_lat,
+                            'Stop location too close to 0, 0',
+                            type=problems_module.TYPE_WARNING)
+
+  def ValidateStopDescriptionAndNameAreDifferent(self, problems):
+    if (self.stop_desc is not None and self.stop_name is not None and
+        self.stop_desc and self.stop_name and
+        not util.IsEmpty(self.stop_desc) and
+        self.stop_name.strip().lower() == self.stop_desc.strip().lower()):
+      problems.InvalidValue('stop_desc', self.stop_desc,
+                            'stop_desc should not be the same as stop_name')
+
+  def ValidateStopIsNotStationWithParent(self, problems):
+    if self.parent_station and self.location_type == 1:
+      problems.InvalidValue('parent_station', self.parent_station,
+                            'Stop row with location_type=1 (a station) must '
+                            'not have a parent_station')
+
+  def ValidateBeforeAdd(self, problems):
+    # First check that all required fields are present because ParseAttributes
+    # may remove invalid attributes.
+    self.ValidateStopRequiredFields(problems)
+
+    #If value is valid for attribute name store it.
+    #If value is not valid call problems. Return a new value of the correct type
+    #or None if value couldn't be converted.
+    self.ValidateStopLatitude(problems)
+    self.ValidateStopLongitude(problems)
+    self.ValidateStopUrl(problems)
+    self.ValidateStopLocationType(problems)
+
+    # Check that this object is consistent with itself
+    self.ValidateStopNotTooCloseToOrigin(problems)
+    self.ValidateStopDescriptionAndNameAreDifferent(problems)
+    self.ValidateStopIsNotStationWithParent(problems)
+
+    # None of these checks are blocking
+    return True
+
+  def ValidateAfterAdd(self, problems):
+    return
+
+  def Validate(self, problems=problems_module.default_problem_reporter):
+    self.ValidateBeforeAdd(problems)
+    self.ValidateAfterAdd(problems)
+
+  def AddToSchedule(self, schedule, problems):
+    schedule.AddStopObject(self, problems)
+