--- a/origin-src/transitfeed-1.2.6/transitfeed/trip.py +++ b/origin-src/transitfeed-1.2.6/transitfeed/trip.py @@ -1,1 +1,732 @@ - +#!/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 Trip(GtfsObjectBase): + _REQUIRED_FIELD_NAMES = ['route_id', 'service_id', 'trip_id'] + _FIELD_NAMES = _REQUIRED_FIELD_NAMES + [ + 'trip_headsign', 'direction_id', 'block_id', 'shape_id' + ] + _TABLE_NAME= "trips" + + def __init__(self, headsign=None, service_period=None, + route=None, trip_id=None, field_dict=None): + self._schedule = None + self._headways = [] # [(start_time, end_time, headway_secs)] + if not field_dict: + field_dict = {} + if headsign is not None: + field_dict['trip_headsign'] = headsign + if route: + field_dict['route_id'] = route.route_id + if trip_id is not None: + field_dict['trip_id'] = trip_id + if service_period is not None: + field_dict['service_id'] = service_period.service_id + # Earlier versions of transitfeed.py assigned self.service_period here + # and allowed the caller to set self.service_id. Schedule.Validate + # checked the service_id attribute if it was assigned and changed it to a + # service_period attribute. Now only the service_id attribute is used and + # it is validated by Trip.Validate. + if service_period is not None: + # For backwards compatibility + self.service_id = service_period.service_id + self.__dict__.update(field_dict) + + def GetFieldValuesTuple(self): + return [getattr(self, fn) or '' for fn in self._FIELD_NAMES] + + def AddStopTime(self, stop, problems=None, schedule=None, **kwargs): + """Add a stop to this trip. Stops must be added in the order visited. + + Args: + stop: A Stop object + kwargs: remaining keyword args passed to StopTime.__init__ + + Returns: + None + """ + if problems is None: + # TODO: delete this branch when StopTime.__init__ doesn't need a + # ProblemReporter + problems = problems_module.default_problem_reporter + stoptime = self.GetGtfsFactory().StopTime( + problems=problems, stop=stop, **kwargs) + self.AddStopTimeObject(stoptime, schedule) + + def _AddStopTimeObjectUnordered(self, stoptime, schedule): + """Add StopTime object to this trip. + + The trip isn't checked for duplicate sequence numbers so it must be + validated later.""" + stop_time_class = self.GetGtfsFactory().StopTime + cursor = schedule._connection.cursor() + insert_query = "INSERT INTO stop_times (%s) VALUES (%s);" % ( + ','.join(stop_time_class._SQL_FIELD_NAMES), + ','.join(['?'] * len(stop_time_class._SQL_FIELD_NAMES))) + cursor = schedule._connection.cursor() + cursor.execute( + insert_query, stoptime.GetSqlValuesTuple(self.trip_id)) + + def ReplaceStopTimeObject(self, stoptime, schedule=None): + """Replace a StopTime object from this trip with the given one. + + Keys the StopTime object to be replaced by trip_id, stop_sequence + and stop_id as 'stoptime', with the object 'stoptime'. + """ + + if schedule is None: + schedule = self._schedule + + new_secs = stoptime.GetTimeSecs() + cursor = schedule._connection.cursor() + cursor.execute("DELETE FROM stop_times WHERE trip_id=? and " + "stop_sequence=? and stop_id=?", + (self.trip_id, stoptime.stop_sequence, stoptime.stop_id)) + if cursor.rowcount == 0: + raise problems_module.Error, 'Attempted replacement of StopTime object which does not exist' + self._AddStopTimeObjectUnordered(stoptime, schedule) + + def AddStopTimeObject(self, stoptime, schedule=None, problems=None): + """Add a StopTime object to the end of this trip. + + Args: + stoptime: A StopTime object. Should not be reused in multiple trips. + schedule: Schedule object containing this trip which must be + passed to Trip.__init__ or here + problems: ProblemReporter object for validating the StopTime in its new + home + + Returns: + None + """ + if schedule is None: + schedule = self._schedule + if schedule is None: + warnings.warn("No longer supported. _schedule attribute is used to get " + "stop_times table", DeprecationWarning) + if problems is None: + problems = schedule.problem_reporter + + new_secs = stoptime.GetTimeSecs() + cursor = schedule._connection.cursor() + cursor.execute("SELECT max(stop_sequence), max(arrival_secs), " + "max(departure_secs) FROM stop_times WHERE trip_id=?", + (self.trip_id,)) + row = cursor.fetchone() + if row[0] is None: + # This is the first stop_time of the trip + stoptime.stop_sequence = 1 + if new_secs == None: + problems.OtherProblem( + 'No time for first StopTime of trip_id "%s"' % (self.trip_id,)) + else: + stoptime.stop_sequence = row[0] + 1 + prev_secs = max(row[1], row[2]) + if new_secs != None and new_secs < prev_secs: + problems.OtherProblem( + 'out of order stop time for stop_id=%s trip_id=%s %s < %s' % + (util.EncodeUnicode(stoptime.stop_id), + util.EncodeUnicode(self.trip_id), + util.FormatSecondsSinceMidnight(new_secs), + util.FormatSecondsSinceMidnight(prev_secs))) + self._AddStopTimeObjectUnordered(stoptime, schedule) + + def GetTimeStops(self): + """Return a list of (arrival_secs, departure_secs, stop) tuples. + + Caution: arrival_secs and departure_secs may be 0, a false value meaning a + stop at midnight or None, a false value meaning the stop is untimed.""" + return [(st.arrival_secs, st.departure_secs, st.stop) for st in + self.GetStopTimes()] + + def GetCountStopTimes(self): + """Return the number of stops made by this trip.""" + cursor = self._schedule._connection.cursor() + cursor.execute( + 'SELECT count(*) FROM stop_times WHERE trip_id=?', (self.trip_id,)) + return cursor.fetchone()[0] + + def GetTimeInterpolatedStops(self): + """Return a list of (secs, stoptime, is_timepoint) tuples. + + secs will always be an int. If the StopTime object does not have explict + times this method guesses using distance. stoptime is a StopTime object and + is_timepoint is a bool. + + Raises: + ValueError if this trip does not have the times needed to interpolate + """ + rv = [] + + stoptimes = self.GetStopTimes() + # If there are no stoptimes [] is the correct return value but if the start + # or end are missing times there is no correct return value. + if not stoptimes: + return [] + if (stoptimes[0].GetTimeSecs() is None or + stoptimes[-1].GetTimeSecs() is None): + raise ValueError("%s must have time at first and last stop" % (self)) + + cur_timepoint = None + next_timepoint = None + distance_between_timepoints = 0 + distance_traveled_between_timepoints = 0 + + for i, st in enumerate(stoptimes): + if st.GetTimeSecs() != None: + cur_timepoint = st + distance_between_timepoints = 0 + distance_traveled_between_timepoints = 0 + if i + 1 < len(stoptimes): + k = i + 1 + distance_between_timepoints += util.ApproximateDistanceBetweenStops(stoptimes[k-1].stop, stoptimes[k].stop) + while stoptimes[k].GetTimeSecs() == None: + k += 1 + distance_between_timepoints += util.ApproximateDistanceBetweenStops(stoptimes[k-1].stop, stoptimes[k].stop) + next_timepoint = stoptimes[k] + rv.append( (st.GetTimeSecs(), st, True) ) + else: + distance_traveled_between_timepoints += util.ApproximateDistanceBetweenStops(stoptimes[i-1].stop, st.stop) + distance_percent = distance_traveled_between_timepoints / distance_between_timepoints + total_time = next_timepoint.GetTimeSecs() - cur_timepoint.GetTimeSecs() + time_estimate = distance_percent * total_time + cur_timepoint.GetTimeSecs() + rv.append( (int(round(time_estimate)), st, False) ) + + return rv + + def ClearStopTimes(self): + """Remove all stop times from this trip. + + StopTime objects previously returned by GetStopTimes are unchanged but are + no longer associated with this trip. + """ + cursor = self._schedule._connection.cursor() + cursor.execute('DELETE FROM stop_times WHERE trip_id=?', (self.trip_id,)) + + def GetStopTimes(self, problems=None): + """Return a sorted list of StopTime objects for this trip.""" + # In theory problems=None should be safe because data from database has been + # validated. See comment in _LoadStopTimes for why this isn't always true. + cursor = self._schedule._connection.cursor() + cursor.execute( + 'SELECT arrival_secs,departure_secs,stop_headsign,pickup_type,' + 'drop_off_type,shape_dist_traveled,stop_id,stop_sequence FROM ' + 'stop_times WHERE ' + 'trip_id=? ORDER BY stop_sequence', (self.trip_id,)) + stop_times = [] + stoptime_class = self.GetGtfsFactory().StopTime + for row in cursor.fetchall(): + stop = self._schedule.GetStop(row[6]) + stop_times.append(stoptime_class(problems=problems, + stop=stop, + arrival_secs=row[0], + departure_secs=row[1], + stop_headsign=row[2], + pickup_type=row[3], + drop_off_type=row[4], + shape_dist_traveled=row[5], + stop_sequence=row[7])) + return stop_times + + def GetHeadwayStopTimes(self, problems=None): + """Deprecated. Please use GetFrequencyStopTimes instead.""" + warnings.warn("No longer supported. The HeadwayPeriod class was renamed to " + "Frequency, and all related functions were renamed " + "accordingly.", DeprecationWarning) + return self.GetFrequencyStopTimes(problems) + + def GetFrequencyStopTimes(self, problems=None): + """Return a list of StopTime objects for each headway-based run. + + Returns: + a list of list of StopTime objects. Each list of StopTime objects + represents one run. If this trip doesn't have headways returns an empty + list. + """ + stoptimes_list = [] # list of stoptime lists to be returned + stoptime_pattern = self.GetStopTimes() + first_secs = stoptime_pattern[0].arrival_secs # first time of the trip + stoptime_class = self.GetGtfsFactory().StopTime + # for each start time of a headway run + for run_secs in self.GetFrequencyStartTimes(): + # stop time list for a headway run + stoptimes = [] + # go through the pattern and generate stoptimes + for st in stoptime_pattern: + arrival_secs, departure_secs = None, None # default value if the stoptime is not timepoint + if st.arrival_secs != None: + arrival_secs = st.arrival_secs - first_secs + run_secs + if st.departure_secs != None: + departure_secs = st.departure_secs - first_secs + run_secs + # append stoptime + stoptimes.append(stoptime_class(problems=problems, stop=st.stop, + arrival_secs=arrival_secs, + departure_secs=departure_secs, + stop_headsign=st.stop_headsign, + pickup_type=st.pickup_type, + drop_off_type=st.drop_off_type, + shape_dist_traveled= \ + st.shape_dist_traveled, + stop_sequence=st.stop_sequence)) + # add stoptimes to the stoptimes_list + stoptimes_list.append ( stoptimes ) + return stoptimes_list + + def GetStartTime(self, problems=problems_module.default_problem_reporter): + """Return the first time of the trip. TODO: For trips defined by frequency + return the first time of the first trip.""" + cursor = self._schedule._connection.cursor() + cursor.execute( + 'SELECT arrival_secs,departure_secs FROM stop_times WHERE ' + 'trip_id=? ORDER BY stop_sequence LIMIT 1', (self.trip_id,)) + (arrival_secs, departure_secs) = cursor.fetchone() + if arrival_secs != None: + return arrival_secs + elif departure_secs != None: + return departure_secs + else: + problems.InvalidValue('departure_time', '', + 'The first stop_time in trip %s is missing ' + 'times.' % self.trip_id) + + def GetHeadwayStartTimes(self): + """Deprecated. Please use GetFrequencyStartTimes instead.""" + warnings.warn("No longer supported. The HeadwayPeriod class was renamed to " + "Frequency, and all related functions were renamed " + "accordingly.", DeprecationWarning) + return self.GetFrequencyStartTimes() + + def GetFrequencyStartTimes(self): + """Return a list of start time for each headway-based run. + + Returns: + a sorted list of seconds since midnight, the start time of each run. If + this trip doesn't have headways returns an empty list.""" + start_times = [] + # for each headway period of the trip + for start_secs, end_secs, headway_secs in self.GetFrequencyTuples(): + # reset run secs to the start of the timeframe + run_secs = start_secs + while run_secs < end_secs: + start_times.append(run_secs) + # increment current run secs by headway secs + run_secs += headway_secs + return start_times + + def GetEndTime(self, problems=problems_module.default_problem_reporter): + """Return the last time of the trip. TODO: For trips defined by frequency + return the last time of the last trip.""" + cursor = self._schedule._connection.cursor() + cursor.execute( + 'SELECT arrival_secs,departure_secs FROM stop_times WHERE ' + 'trip_id=? ORDER BY stop_sequence DESC LIMIT 1', (self.trip_id,)) + (arrival_secs, departure_secs) = cursor.fetchone() + if departure_secs != None: + return departure_secs + elif arrival_secs != None: + return arrival_secs + else: + problems.InvalidValue('arrival_time', '', + 'The last stop_time in trip %s is missing ' + 'times.' % self.trip_id) + + def _GenerateStopTimesTuples(self): + """Generator for rows of the stop_times file""" + stoptimes = self.GetStopTimes() + for i, st in enumerate(stoptimes): + yield st.GetFieldValuesTuple(self.trip_id) + + def GetStopTimesTuples(self): + results = [] + for time_tuple in self._GenerateStopTimesTuples(): + results.append(time_tuple) + return results + + def GetPattern(self): + """Return a tuple of Stop objects, in the order visited""" + stoptimes = self.GetStopTimes() + return tuple(st.stop for st in stoptimes) + + def AddHeadwayPeriodObject(self, headway_period, problem_reporter): + """Deprecated. Please use AddFrequencyObject instead.""" + warnings.warn("No longer supported. The HeadwayPeriod class was renamed to " + "Frequency, and all related functions were renamed " + "accordingly.", DeprecationWarning) + self.AddFrequencyObject(frequency, problem_reporter) + + def AddFrequencyObject(self, frequency, problem_reporter): + """Add a Frequency object to this trip's list of Frequencies.""" + if frequency is not None: + self.AddFrequency(frequency.StartTime(), + frequency.EndTime(), + frequency.HeadwaySecs(), + problem_reporter) + + def AddHeadwayPeriod(self, start_time, end_time, headway_secs, + problem_reporter=problems_module.default_problem_reporter): + """Deprecated. Please use AddFrequency instead.""" + warnings.warn("No longer supported. The HeadwayPeriod class was renamed to " + "Frequency, and all related functions were renamed " + "accordingly.", DeprecationWarning) + self.AddFrequency(start_time, end_time, headway_secs, problem_reporter) + + def AddFrequency(self, start_time, end_time, headway_secs, + problem_reporter=problems_module.default_problem_reporter): + """Adds a period to this trip during which the vehicle travels + at regular intervals (rather than specifying exact times for each stop). + + Args: + start_time: The time at which this headway period starts, either in + numerical seconds since midnight or as "HH:MM:SS" since midnight. + end_time: The time at which this headway period ends, either in + numerical seconds since midnight or as "HH:MM:SS" since midnight. + This value should be larger than start_time. + headway_secs: The amount of time, in seconds, between occurences of + this trip. + problem_reporter: Optional parameter that can be used to select + how any errors in the other input parameters will be reported. + Returns: + None + """ + if start_time == None or start_time == '': # 0 is OK + problem_reporter.MissingValue('start_time') + return + if isinstance(start_time, basestring): + try: + start_time = util.TimeToSecondsSinceMidnight(start_time) + except problems_module.Error: + problem_reporter.InvalidValue('start_time', start_time) + return + elif start_time < 0: + problem_reporter.InvalidValue('start_time', start_time) + + if end_time == None or end_time == '': + problem_reporter.MissingValue('end_time') + return + if isinstance(end_time, basestring): + try: + end_time = util.TimeToSecondsSinceMidnight(end_time) + except problems_module.Error: + problem_reporter.InvalidValue('end_time', end_time) + return + elif end_time < 0: + problem_reporter.InvalidValue('end_time', end_time) + return + + if not headway_secs: + problem_reporter.MissingValue('headway_secs') + return + try: + headway_secs = int(headway_secs) + except ValueError: + problem_reporter.InvalidValue('headway_secs', headway_secs) + return + + if headway_secs <= 0: + problem_reporter.InvalidValue('headway_secs', headway_secs) + return + + if end_time <= start_time: + problem_reporter.InvalidValue('end_time', end_time, + 'should be greater than start_time') + + self._headways.append((start_time, end_time, headway_secs)) + + def ClearFrequencies(self): + self._headways = [] + + def _HeadwayOutputTuple(self, headway): + return (self.trip_id, + util.FormatSecondsSinceMidnight(headway[0]), + util.FormatSecondsSinceMidnight(headway[1]), + unicode(headway[2])) + + def GetFrequencyOutputTuples(self): + tuples = [] + for headway in self._headways: + tuples.append(self._HeadwayOutputTuple(headway)) + return tuples + + def GetFrequencyTuples(self): + return self._headways + + def __getattr__(self, name): + if name == 'service_period': + assert self._schedule, "Must be in a schedule to get service_period" + return self._schedule.GetServicePeriod(self.service_id) + elif name == 'pattern_id': + if '_pattern_id' not in self.__dict__: + self.__dict__['_pattern_id'] = hash(self.GetPattern()) + return self.__dict__['_pattern_id'] + else: + return GtfsObjectBase.__getattr__(self, name) + + def ValidateRouteId(self, problems): + if util.IsEmpty(self.route_id): + problems.MissingValue('route_id') + + def ValidateServicePeriod(self, problems): + if 'service_period' in self.__dict__: + # Some tests assign to the service_period attribute. Patch up self before + # proceeding with validation. See also comment in Trip.__init__. + self.service_id = self.__dict__['service_period'].service_id + del self.service_period + if util.IsEmpty(self.service_id): + problems.MissingValue('service_id') + + def ValidateTripId(self, problems): + if util.IsEmpty(self.trip_id): + problems.MissingValue('trip_id') + + def ValidateDirectionId(self, problems): + if hasattr(self, 'direction_id') and (not util.IsEmpty(self.direction_id)) \ + and (self.direction_id != '0') and (self.direction_id != '1'): + problems.InvalidValue('direction_id', self.direction_id, + 'direction_id must be "0" or "1"') + + def ValidateShapeIdsExistInShapeList(self, problems): + if self._schedule: + if self.shape_id and self.shape_id not in self._schedule._shapes: + problems.InvalidValue('shape_id', self.shape_id) + + def ValidateRouteIdExistsInRouteList(self, problems): + if self._schedule: + if self.route_id and self.route_id not in self._schedule.routes: + problems.InvalidValue('route_id', self.route_id) + + def ValidateServiceIdExistsInServiceList(self, problems): + if self._schedule: + if (self.service_id and + self.service_id not in self._schedule.service_periods): + problems.InvalidValue('service_id', self.service_id) + + def Validate(self, problems, validate_children=True): + """Validate attributes of this object. + + Check that this object has all required values set to a valid value without + reference to the rest of the schedule. If the _schedule attribute is set + then check that references such as route_id and service_id are correct. + + Args: + problems: A ProblemReporter object + validate_children: if True and the _schedule attribute is set than call + ValidateChildren + """ + self.ValidateRouteId(problems) + self.ValidateServicePeriod(problems) + self.ValidateDirectionId(problems) + self.ValidateTripId(problems) + self.ValidateShapeIdsExistInShapeList(problems) + self.ValidateRouteIdExistsInRouteList(problems) + self.ValidateServiceIdExistsInServiceList(problems) + if self._schedule and validate_children: + self.ValidateChildren(problems) + + def ValidateNoDuplicateStopSequences(self, problems): + cursor = self._schedule._connection.cursor() + cursor.execute("SELECT COUNT(stop_sequence) AS a FROM stop_times " + "WHERE trip_id=? GROUP BY stop_sequence HAVING a > 1", + (self.trip_id,)) + for row in cursor: + problems.InvalidValue('stop_sequence', row[0], + 'Duplicate stop_sequence in trip_id %s' % + self.trip_id) + + def ValidateTripStartAndEndTimes(self, problems, stoptimes): + if stoptimes: + if stoptimes[0].arrival_time is None and stoptimes[0].departure_time is None: + problems.OtherProblem( + 'No time for start of trip_id "%s""' % (self.trip_id)) + if stoptimes[-1].arrival_time is None and stoptimes[-1].departure_time is None: + problems.OtherProblem( + 'No time for end of trip_id "%s""' % (self.trip_id)) + + def ValidateStopTimesSequenceHasIncreasingTimeAndDistance(self, + problems, + stoptimes): + if stoptimes: + route_class = self.GetGtfsFactory().Route + # Checks that the arrival time for each time point is after the departure + # time of the previous. Assumes a stoptimes sorted by sequence + prev_departure = 0 + prev_stop = None + prev_distance = None + try: + route_type = self._schedule.GetRoute(self.route_id).route_type + max_speed = route_class._ROUTE_TYPES[route_type]['max_speed'] + except KeyError, e: + # If route_type cannot be found, assume it is 0 (Tram) for checking + # speeds between stops. + max_speed = route_class._ROUTE_TYPES[0]['max_speed'] + for timepoint in stoptimes: + # Distance should be a nonnegative float number, so it should be + # always larger than None. + distance = timepoint.shape_dist_traveled + if distance is not None: + if distance > prev_distance and distance >= 0: + prev_distance = distance + else: + if distance == prev_distance: + type = problems_module.TYPE_WARNING + else: + type = problems_module.TYPE_ERROR + problems.InvalidValue('stoptimes.shape_dist_traveled', distance, + 'For the trip %s the stop %s has shape_dist_traveled=%s, ' + 'which should be larger than the previous ones. In this ' + 'case, the previous distance was %s.' % + (self.trip_id, timepoint.stop_id, distance, prev_distance), + type=type) + + if timepoint.arrival_secs is not None: + self._CheckSpeed(prev_stop, timepoint.stop, prev_departure, + timepoint.arrival_secs, max_speed, problems) + + if timepoint.arrival_secs >= prev_departure: + prev_departure = timepoint.departure_secs + prev_stop = timepoint.stop + else: + problems.OtherProblem('Timetravel detected! Arrival time ' + 'is before previous departure ' + 'at sequence number %s in trip %s' % + (timepoint.stop_sequence, self.trip_id)) + + def ValidateShapeDistTraveledSmallerThanMaxShapeDistance(self, + problems, + stoptimes): + if stoptimes: + if self.shape_id and self.shape_id in self._schedule._shapes: + shape = self._schedule.GetShape(self.shape_id) + max_shape_dist = shape.max_distance + st = stoptimes[-1] + if (st.shape_dist_traveled and + st.shape_dist_traveled > max_shape_dist): + problems.OtherProblem( + 'In stop_times.txt, the stop with trip_id=%s and ' + 'stop_sequence=%d has shape_dist_traveled=%f, which is larger ' + 'than the max shape_dist_traveled=%f of the corresponding ' + 'shape (shape_id=%s)' % + (self.trip_id, st.stop_sequence, st.shape_dist_traveled, + max_shape_dist, self.shape_id), + type=problems_module.TYPE_WARNING) + + def ValidateDistanceFromStopToShape(self, problems, stoptimes): + if stoptimes: + if self.shape_id and self.shape_id in self._schedule._shapes: + shape = self._schedule.GetShape(self.shape_id) + max_shape_dist = shape.max_distance + st = stoptimes[-1] + # shape_dist_traveled is valid in shape if max_shape_dist larger than + # 0. + if max_shape_dist > 0: + for st in stoptimes: + if st.shape_dist_traveled is None: + continue + pt = shape.GetPointWithDistanceTraveled(st.shape_dist_traveled) + if pt: + stop = self._schedule.GetStop(st.stop_id) + distance = util.ApproximateDistance(stop.stop_lat, stop.stop_lon, + pt[0], pt[1]) + if distance > problems_module.MAX_DISTANCE_FROM_STOP_TO_SHAPE: + problems.StopTooFarFromShapeWithDistTraveled( + self.trip_id, stop.stop_name, stop.stop_id, pt[2], + self.shape_id, distance, + problems_module.MAX_DISTANCE_FROM_STOP_TO_SHAPE) + + def ValidateFrequencies(self, problems): + # O(n^2), but we don't anticipate many headway periods per trip + for headway_index, headway in enumerate(self._headways[0:-1]): + for other in self._headways[headway_index + 1:]: + if (other[0] < headway[1]) and (other[1] > headway[0]): + problems.OtherProblem('Trip contains overlapping headway periods ' + '%s and %s' % + (self._HeadwayOutputTuple(headway), + self._HeadwayOutputTuple(other))) + + def ValidateChildren(self, problems): + """Validate StopTimes and headways of this trip.""" + assert self._schedule, "Trip must be in a schedule to ValidateChildren" + # TODO: validate distance values in stop times (if applicable) + + self.ValidateNoDuplicateStopSequences(problems) + stoptimes = self.GetStopTimes(problems) + stoptimes.sort(key=lambda x: x.stop_sequence) + self.ValidateTripStartAndEndTimes(problems, stoptimes) + self.ValidateStopTimesSequenceHasIncreasingTimeAndDistance(problems, + stoptimes) + self.ValidateShapeDistTraveledSmallerThanMaxShapeDistance(problems, + stoptimes) + self.ValidateDistanceFromStopToShape(problems, stoptimes) + self.ValidateFrequencies(problems) + + def ValidateBeforeAdd(self, problems): + return True + + def ValidateAfterAdd(self, problems): + self.Validate(problems) + + def _CheckSpeed(self, prev_stop, next_stop, depart_time, + arrive_time, max_speed, problems): + # Checks that the speed between two stops is not faster than max_speed + if prev_stop != None: + try: + time_between_stops = arrive_time - depart_time + except TypeError: + return + + try: + dist_between_stops = \ + util.ApproximateDistanceBetweenStops(next_stop, prev_stop) + except TypeError, e: + return + + if time_between_stops == 0: + # HASTUS makes it hard to output GTFS with times to the nearest second; + # it rounds times to the nearest minute. Therefore stop_times at the + # same time ending in :00 are fairly common. These times off by no more + # than 30 have not caused a problem. See + # http://code.google.com/p/googletransitdatafeed/issues/detail?id=193 + # Show a warning if times are not rounded to the nearest minute or + # distance is more than max_speed for one minute. + if depart_time % 60 != 0 or dist_between_stops / 1000 * 60 > max_speed: + problems.TooFastTravel(self.trip_id, + prev_stop.stop_name, + next_stop.stop_name, + dist_between_stops, + time_between_stops, + speed=None, + type=problems_module.TYPE_WARNING) + return + # This needs floating point division for precision. + speed_between_stops = ((float(dist_between_stops) / 1000) / + (float(time_between_stops) / 3600)) + if speed_between_stops > max_speed: + problems.TooFastTravel(self.trip_id, + prev_stop.stop_name, + next_stop.stop_name, + dist_between_stops, + time_between_stops, + speed_between_stops, + type=problems_module.TYPE_WARNING) + + def AddToSchedule(self, schedule, problems): + schedule.AddTripObject(self, problems) +