Rietveld Code Review Tool
Help | Bug tracker | Discussion group | Source code | Sign in
(129)

Delta Between Two Patch Sets: python/transitfeed.py

Issue 75063: Adding validation for uniqueness of service_id X date in calendar_dates SVN Base: http://googletransitdatafeed.googlecode.com/svn/trunk/
Left Patch Set: Moving error handling into SetDateHasService. Created 5 months ago
Right Patch Set: Removing unnecessary linebreak Created 1 month, 1 week ago
Use n/p to move between diff chunks; N/P to move between comments. Please Sign in to add in-line comments.
Jump to:
Left: Side by side diff | Download
Right: Side by side diff | Download
LEFTRIGHT
1 #!/usr/bin/python2.5 1 #!/usr/bin/python2.5
2 2
3 # Copyright (C) 2007 Google Inc. 3 # Copyright (C) 2007 Google Inc.
4 # 4 #
5 # Licensed under the Apache License, Version 2.0 (the "License"); 5 # Licensed under the Apache License, Version 2.0 (the "License");
6 # you may not use this file except in compliance with the License. 6 # you may not use this file except in compliance with the License.
7 # You may obtain a copy of the License at 7 # You may obtain a copy of the License at
8 # 8 #
9 # http://www.apache.org/licenses/LICENSE-2.0 9 # http://www.apache.org/licenses/LICENSE-2.0
10 # 10 #
(...skipping 51 matching lines...) Expand 10 before | Expand all | Expand 10 after
62 import warnings 62 import warnings
63 # Objects in a schedule (Route, Trip, etc) should not keep a strong reference 63 # Objects in a schedule (Route, Trip, etc) should not keep a strong reference
64 # to the Schedule object to avoid a reference cycle. Schedule needs to use 64 # to the Schedule object to avoid a reference cycle. Schedule needs to use
65 # __del__ to cleanup its temporary file. The garbage collector can't handle 65 # __del__ to cleanup its temporary file. The garbage collector can't handle
66 # reference cycles containing objects with custom cleanup code. 66 # reference cycles containing objects with custom cleanup code.
67 import weakref 67 import weakref
68 import zipfile 68 import zipfile
69 69
70 OUTPUT_ENCODING = 'utf-8' 70 OUTPUT_ENCODING = 'utf-8'
71 MAX_DISTANCE_FROM_STOP_TO_SHAPE = 1000 71 MAX_DISTANCE_FROM_STOP_TO_SHAPE = 1000
72 72 MAX_DISTANCE_BETWEEN_STOP_AND_PARENT_STATION_WARNING = 100.0
73 73 MAX_DISTANCE_BETWEEN_STOP_AND_PARENT_STATION_ERROR = 1000.0
74 __version__ = '1.2.1' 74
75 __version__ = '1.2.2'
75 76
76 77
77 def EncodeUnicode(text): 78 def EncodeUnicode(text):
78 """ 79 """
79 Optionally encode text and return it. The result should be safe to print. 80 Optionally encode text and return it. The result should be safe to print.
80 """ 81 """
81 if type(text) == type(u''): 82 if type(text) == type(u''):
82 return text.encode(OUTPUT_ENCODING) 83 return text.encode(OUTPUT_ENCODING)
83 else: 84 else:
84 return text 85 return text
(...skipping 79 matching lines...) Expand 10 before | Expand all | Expand 10 after
164 context2=self._context) 165 context2=self._context)
165 self._Report(e) 166 self._Report(e)
166 167
167 def InvalidValue(self, column_name, value, reason=None, context=None, 168 def InvalidValue(self, column_name, value, reason=None, context=None,
168 type=TYPE_ERROR): 169 type=TYPE_ERROR):
169 e = InvalidValue(column_name=column_name, value=value, reason=reason, 170 e = InvalidValue(column_name=column_name, value=value, reason=reason,
170 context=context, context2=self._context, type=type) 171 context=context, context2=self._context, type=type)
171 self._Report(e) 172 self._Report(e)
172 173
173 def DuplicateID(self, column_names, values, context=None, type=TYPE_ERROR): 174 def DuplicateID(self, column_names, values, context=None, type=TYPE_ERROR):
174 if not isinstance(column_names, tuple): 175 if isinstance(column_names, tuple):
175 column_names = (column_names,) 176 column_names = '(' + ', '.join(column_names) + ')'
176 if not isinstance(values, tuple): 177 if isinstance(values, tuple):
177 values = (values,) 178 values = '(' + ', '.join(values) + ')'
178 e = DuplicateID(column_names=column_names, values=values, 179 e = DuplicateID(column_name=column_names, value=values,
179 context=context, context2=self._context, type=type) 180 context=context, context2=self._context, type=type)
180 self._Report(e) 181 self._Report(e)
181 182
182 def UnusedStop(self, stop_id, stop_name, context=None): 183 def UnusedStop(self, stop_id, stop_name, context=None):
183 e = UnusedStop(stop_id=stop_id, stop_name=stop_name, 184 e = UnusedStop(stop_id=stop_id, stop_name=stop_name,
184 context=context, context2=self._context, type=TYPE_WARNING) 185 context=context, context2=self._context, type=TYPE_WARNING)
185 self._Report(e) 186 self._Report(e)
186 187
187 def UsedStation(self, stop_id, stop_name, context=None): 188 def UsedStation(self, stop_id, stop_name, context=None):
188 e = UsedStation(stop_id=stop_id, stop_name=stop_name, 189 e = UsedStation(stop_id=stop_id, stop_name=stop_name,
189 context=context, context2=self._context, type=TYPE_ERROR) 190 context=context, context2=self._context, type=TYPE_ERROR)
190 self._Report(e) 191 self._Report(e)
191 192
193 def StopTooFarFromParentStation(self, stop_id, stop_name, parent_stop_id,
194 parent_stop_name, distance,
195 type=TYPE_WARNING, context=None):
196 e = StopTooFarFromParentStation(
197 stop_id=stop_id, stop_name=stop_name,
198 parent_stop_id=parent_stop_id,
199 parent_stop_name=parent_stop_name, distance=distance,
200 context=context, context2=self._context, type=type)
201 self._Report(e)
202
192 def ExpirationDate(self, expiration, context=None): 203 def ExpirationDate(self, expiration, context=None):
193 e = ExpirationDate(expiration=expiration, context=context, 204 e = ExpirationDate(expiration=expiration, context=context,
194 context2=self._context, type=TYPE_WARNING) 205 context2=self._context, type=TYPE_WARNING)
206 self._Report(e)
207
208 def FutureService(self, start_date, context=None):
209 e = FutureService(start_date=start_date, context=context,
210 context2=self._context, type=TYPE_WARNING)
195 self._Report(e) 211 self._Report(e)
196 212
197 def InvalidLineEnd(self, bad_line_end, context=None): 213 def InvalidLineEnd(self, bad_line_end, context=None):
198 """bad_line_end is a human readable string.""" 214 """bad_line_end is a human readable string."""
199 e = InvalidLineEnd(bad_line_end=bad_line_end, context=context, 215 e = InvalidLineEnd(bad_line_end=bad_line_end, context=context,
200 context2=self._context, type=TYPE_WARNING) 216 context2=self._context, type=TYPE_WARNING)
201 self._Report(e) 217 self._Report(e)
202 218
219 def TooFastTravel(self, trip_id, prev_stop, next_stop, dist, time, speed,
220 type=TYPE_ERROR):
221 e = TooFastTravel(trip_id=trip_id, prev_stop=prev_stop,
222 next_stop=next_stop, time=time, dist=dist, speed=speed,
223 context=None, context2=self._context, type=type)
224 self._Report(e)
225
226 def StopWithMultipleRouteTypes(self, stop_name, stop_id, route_id1, route_id2,
227 context=None):
228 e = StopWithMultipleRouteTypes(stop_name=stop_name, stop_id=stop_id,
229 route_id1=route_id1, route_id2=route_id2,
230 context=context, context2=self._context,
231 type=TYPE_WARNING)
232 self._Report(e)
233
234 def DuplicateTrip(self, trip_id1, route_id1, trip_id2, route_id2,
235 context=None):
236 e = DuplicateTrip(trip_id1=trip_id1, route_id1=route_id1, trip_id2=trip_id2,
237 route_id2=route_id2, context=context,
238 context2=self._context, type=TYPE_WARNING)
239 self._Report(e)
240
203 def OtherProblem(self, description, context=None, type=TYPE_ERROR): 241 def OtherProblem(self, description, context=None, type=TYPE_ERROR):
204 e = OtherProblem(description=description, 242 e = OtherProblem(description=description,
205 context=context, context2=self._context, type=type) 243 context=context, context2=self._context, type=type)
206 self._Report(e) 244 self._Report(e)
207 245
208 class ProblemReporter(ProblemReporterBase): 246 class ProblemReporter(ProblemReporterBase):
209 """This is a basic problem reporter that just prints to console.""" 247 """This is a basic problem reporter that just prints to console."""
210 def _Report(self, e): 248 def _Report(self, e):
211 print EncodeUnicode(self._LineWrap(e.FormatProblem(), 78))
212 context = e.FormatContext() 249 context = e.FormatContext()
213 if context: 250 if context:
214 print context 251 print context
252 print EncodeUnicode(self._LineWrap(e.FormatProblem(), 78))
215 253
216 @staticmethod 254 @staticmethod
217 def _LineWrap(text, width): 255 def _LineWrap(text, width):
218 """ 256 """
219 A word-wrap function that preserves existing line breaks 257 A word-wrap function that preserves existing line breaks
220 and most spaces in the text. Expects that existing line 258 and most spaces in the text. Expects that existing line
221 breaks are posix newlines (\n). 259 breaks are posix newlines (\n).
222 260
223 Taken from: 261 Taken from:
224 http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/148061 262 http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/148061
(...skipping 131 matching lines...) Expand 10 before | Expand all | Expand 10 after
356 class CsvSyntax(ExceptionWithContext): 394 class CsvSyntax(ExceptionWithContext):
357 ERROR_TEXT = '%(description)s' 395 ERROR_TEXT = '%(description)s'
358 396
359 class MissingValue(ExceptionWithContext): 397 class MissingValue(ExceptionWithContext):
360 ERROR_TEXT = 'Missing value for column %(column_name)s' 398 ERROR_TEXT = 'Missing value for column %(column_name)s'
361 399
362 class InvalidValue(ExceptionWithContext): 400 class InvalidValue(ExceptionWithContext):
363 ERROR_TEXT = 'Invalid value %(value)s in field %(column_name)s' 401 ERROR_TEXT = 'Invalid value %(value)s in field %(column_name)s'
364 402
365 class DuplicateID(ExceptionWithContext): 403 class DuplicateID(ExceptionWithContext):
366 ERROR_TEXT = 'Duplicate ID %(values)s in column %(column_names)s' 404 ERROR_TEXT = 'Duplicate ID %(value)s in column %(column_name)s'
367 405
368 class UnusedStop(ExceptionWithContext): 406 class UnusedStop(ExceptionWithContext):
369 ERROR_TEXT = "%(stop_name)s (ID %(stop_id)s) isn't used in any trips" 407 ERROR_TEXT = "%(stop_name)s (ID %(stop_id)s) isn't used in any trips"
370 408
371 class UsedStation(ExceptionWithContext): 409 class UsedStation(ExceptionWithContext):
372 ERROR_TEXT = "%(stop_name)s (ID %(stop_id)s) has location_type=1 " \ 410 ERROR_TEXT = "%(stop_name)s (ID %(stop_id)s) has location_type=1 " \
373 "(station) so it should not appear in stop_times" 411 "(station) so it should not appear in stop_times"
412
413 class StopTooFarFromParentStation(ExceptionWithContext):
414 ERROR_TEXT = "%(stop_name)s (ID %(stop_id)s) is too far from its" \
415 " parent station %(parent_stop_name)s (ID %(parent_stop_id)s)" \
416 " : %(distance)s meters."
374 417
375 class ExpirationDate(ExceptionWithContext): 418 class ExpirationDate(ExceptionWithContext):
376 def FormatProblem(self, d=None): 419 def FormatProblem(self, d=None):
377 if not d: 420 if not d:
378 d = self.GetDictToFormat() 421 d = self.GetDictToFormat()
379 expiration = d['expiration'] 422 expiration = d['expiration']
380 formatted_date = time.strftime("%B %d, %Y", 423 formatted_date = time.strftime("%B %d, %Y",
381 time.localtime(expiration)) 424 time.localtime(expiration))
382 if (expiration < time.mktime(time.localtime())): 425 if (expiration < time.mktime(time.localtime())):
383 return "This feed expired on %s" % formatted_date 426 return "This feed expired on %s" % formatted_date
384 else: 427 else:
385 return "This feed will soon expire, on %s" % formatted_date 428 return "This feed will soon expire, on %s" % formatted_date
386 429
430 class FutureService(ExceptionWithContext):
431 def FormatProblem(self, d=None):
432 if not d:
433 d = self.GetDictToFormat()
434 formatted_date = time.strftime("%B %d, %Y", time.localtime(d['start_date']))
435 return ("The earliest service date in this feed is in the future, on %s. "
436 "Published feeds must always include the current date." %
437 formatted_date)
438
439
387 class InvalidLineEnd(ExceptionWithContext): 440 class InvalidLineEnd(ExceptionWithContext):
388 ERROR_TEXT = "Each line must end with CR LF or LF except for the last line " \ 441 ERROR_TEXT = "Each line must end with CR LF or LF except for the last line " \
389 "of the file. This line ends with \"%(bad_line_end)s\"." 442 "of the file. This line ends with \"%(bad_line_end)s\"."
443
444 class StopWithMultipleRouteTypes(ExceptionWithContext):
445 ERROR_TEXT = "Stop %(stop_name)s (ID=%(stop_id)s) belongs to both " \
446 "subway (ID=%(route_id1)s) and bus line (ID=%(route_id2)s)."
447
448 class TooFastTravel(ExceptionWithContext):
449 def FormatProblem(self, d=None):
450 if not d:
451 d = self.GetDictToFormat()
452 if not d['speed']:
453 return "High speed travel detected in trip %(trip_id)s: %(prev_stop)s" \
454 " to %(next_stop)s. %(dist)d meters in %(time)d seconds." % d
455 else:
456 return "High speed travel detected in trip %(trip_id)s: %(prev_stop)s" \
457 " to %(next_stop)s. %(dist)d meters in %(time)d seconds." \
458 " (%(speed)f km/h)." % d
459
460 class DuplicateTrip(ExceptionWithContext):
461 ERROR_TEXT = "Trip %(trip_id1)s of route %(route_id1)s might be duplicated " \
462 "with trip %(trip_id2)s of route %(route_id2)s. They go " \
463 "through the same stops with same service."
390 464
391 class OtherProblem(ExceptionWithContext): 465 class OtherProblem(ExceptionWithContext):
392 ERROR_TEXT = '%(description)s' 466 ERROR_TEXT = '%(description)s'
393 467
394 468
395 class ExceptionProblemReporter(ProblemReporter): 469 class ExceptionProblemReporter(ProblemReporter):
396 def __init__(self, raise_warnings=False): 470 def __init__(self, raise_warnings=False):
397 ProblemReporterBase.__init__(self) 471 ProblemReporterBase.__init__(self)
398 self.raise_warnings = raise_warnings 472 self.raise_warnings = raise_warnings
399 473
(...skipping 46 matching lines...) Expand 10 before | Expand all | Expand 10 after
446 520
447 def FindUniqueId(dic): 521 def FindUniqueId(dic):
448 """Return a string not used as a key in the dictionary dic""" 522 """Return a string not used as a key in the dictionary dic"""
449 name = str(len(dic)) 523 name = str(len(dic))
450 while name in dic: 524 while name in dic:
451 name = str(random.randint(1, 999999999)) 525 name = str(random.randint(1, 999999999))
452 return name 526 return name
453 527
454 528
455 def TimeToSecondsSinceMidnight(time_string): 529 def TimeToSecondsSinceMidnight(time_string):
456 """Convert HH:MM:SS into seconds since midnight. 530 """Convert HHH:MM:SS into seconds since midnight.
457 531
458 For example "01:02:03" returns 3723. The leading zero of the hours may be 532 For example "01:02:03" returns 3723. The leading zero of the hours may be
459 omitted. HH may be more than 23 if the time is on the following day.""" 533 omitted. HH may be more than 23 if the time is on the following day."""
460 m = re.match(r'(\d{1,2}):([0-5]\d):([0-5]\d)$', time_string) 534 m = re.match(r'(\d{1,3}):([0-5]\d):([0-5]\d)$', time_string)
461 # ignored: matching for leap seconds 535 # ignored: matching for leap seconds
462 if not m: 536 if not m:
463 raise Error, 'Bad HH:MM:SS "%s"' % time_string 537 raise Error, 'Bad HH:MM:SS "%s"' % time_string
464 return int(m.group(1)) * 3600 + int(m.group(2)) * 60 + int(m.group(3)) 538 return int(m.group(1)) * 3600 + int(m.group(2)) * 60 + int(m.group(3))
465 539
466 540
467 def FormatSecondsSinceMidnight(s): 541 def FormatSecondsSinceMidnight(s):
468 """Formats an int number of seconds past midnight into a string 542 """Formats an int number of seconds past midnight into a string
469 as "HH:MM:SS".""" 543 as "HH:MM:SS"."""
470 return "%02d:%02d:%02d" % (s / 3600, (s / 60) % 60, s % 60) 544 return "%02d:%02d:%02d" % (s / 3600, (s / 60) % 60, s % 60)
(...skipping 335 matching lines...) Expand 10 before | Expand all | Expand 10 after
806 880
807 class Route(GenericGTFSObject): 881 class Route(GenericGTFSObject):
808 """Represents a single route.""" 882 """Represents a single route."""
809 883
810 _REQUIRED_FIELD_NAMES = [ 884 _REQUIRED_FIELD_NAMES = [
811 'route_id', 'route_short_name', 'route_long_name', 'route_type' 885 'route_id', 'route_short_name', 'route_long_name', 'route_type'
812 ] 886 ]
813 _FIELD_NAMES = _REQUIRED_FIELD_NAMES + [ 887 _FIELD_NAMES = _REQUIRED_FIELD_NAMES + [
814 'agency_id', 'route_desc', 'route_url', 'route_color', 'route_text_color' 888 'agency_id', 'route_desc', 'route_url', 'route_color', 'route_text_color'
815 ] 889 ]
816 _ROUTE_TYPE_NAMES = { 890 _ROUTE_TYPES = {
817 'Tram': 0, 891 0: {'name':'Tram', 'max_speed':50},
818 'Subway': 1, 892 1: {'name':'Subway', 'max_speed':150},
819 'Rail': 2, 893 2: {'name':'Rail', 'max_speed':300},
820 'Bus': 3, 894 3: {'name':'Bus', 'max_speed':100},
821 'Ferry': 4, 895 4: {'name':'Ferry', 'max_speed':80},
822 'Cable Car': 5, 896 5: {'name':'Cable Car', 'max_speed':50},
823 'Gondola': 6, 897 6: {'name':'Gondola', 'max_speed':50},
824 'Funicular': 7 898 7: {'name':'Funicular', 'max_speed':50},
825 } 899 }
826 _ROUTE_TYPE_IDS = set(_ROUTE_TYPE_NAMES.values()) 900 # Create a reverse lookup dict of route type names to route types.
901 _ROUTE_TYPE_IDS = set(_ROUTE_TYPES.keys())
902 _ROUTE_TYPE_NAMES = dict((v['name'], k) for k, v in _ROUTE_TYPES.items())
827 _TABLE_NAME = 'routes' 903 _TABLE_NAME = 'routes'
828 904
829 def __init__(self, short_name=None, long_name=None, route_type=None, 905 def __init__(self, short_name=None, long_name=None, route_type=None,
830 route_id=None, agency_id=None, field_dict=None): 906 route_id=None, agency_id=None, field_dict=None):
831 self._schedule = None 907 self._schedule = None
832 self._trips = [] 908 self._trips = []
833 909
834 if not field_dict: 910 if not field_dict:
835 field_dict = {} 911 field_dict = {}
836 if short_name is not None: 912 if short_name is not None:
(...skipping 818 matching lines...) Expand 10 before | Expand all | Expand 10 after
1655 problems.OtherProblem( 1731 problems.OtherProblem(
1656 'No time for start of trip_id "%s""' % (self.trip_id)) 1732 'No time for start of trip_id "%s""' % (self.trip_id))
1657 if stoptimes[-1].arrival_time is None and stoptimes[-1].departure_time is None: 1733 if stoptimes[-1].arrival_time is None and stoptimes[-1].departure_time is None:
1658 problems.OtherProblem( 1734 problems.OtherProblem(
1659 'No time for end of trip_id "%s""' % (self.trip_id)) 1735 'No time for end of trip_id "%s""' % (self.trip_id))
1660 1736
1661 # Sorts the stoptimes by sequence and then checks that the arrival time 1737 # Sorts the stoptimes by sequence and then checks that the arrival time
1662 # for each time point is after the departure time of the previous. 1738 # for each time point is after the departure time of the previous.
1663 stoptimes.sort(key=lambda x: x.stop_sequence) 1739 stoptimes.sort(key=lambda x: x.stop_sequence)
1664 prev_departure = 0 1740 prev_departure = 0
1741 prev_stop = None
1742 try:
1743 route_type = self._schedule.GetRoute(self.route_id).route_type
1744 max_speed = Route._ROUTE_TYPES[route_type]['max_speed']
1745 except KeyError, e:
1746 # If route_type cannot be found, assume it is 0 (Tram) for checking
1747 # speeds between stops.
1748 route_type = 0
1749 max_speed = 30
1665 for timepoint in stoptimes: 1750 for timepoint in stoptimes:
1666 if timepoint.arrival_secs is not None: 1751 if timepoint.arrival_secs is not None:
1752 self._CheckSpeed(prev_stop, timepoint.stop, prev_departure,
1753 timepoint.arrival_secs, max_speed, problems)
1754
1667 if timepoint.arrival_secs >= prev_departure: 1755 if timepoint.arrival_secs >= prev_departure:
1668 prev_departure = timepoint.departure_secs 1756 prev_departure = timepoint.departure_secs
1757 prev_stop = timepoint.stop
1669 else: 1758 else:
1670 problems.OtherProblem('Timetravel detected! Arrival time ' 1759 problems.OtherProblem('Timetravel detected! Arrival time '
1671 'is before previous departure ' 1760 'is before previous departure '
1672 'at sequence number %s in trip %s' % 1761 'at sequence number %s in trip %s' %
1673 (timepoint.stop_sequence, self.trip_id)) 1762 (timepoint.stop_sequence, self.trip_id))
1674 1763
1675 if self.shape_id and self.shape_id in self._schedule._shapes: 1764 if self.shape_id and self.shape_id in self._schedule._shapes:
1676 shape = self._schedule.GetShape(self.shape_id) 1765 shape = self._schedule.GetShape(self.shape_id)
1677 max_shape_dist = shape.max_distance 1766 max_shape_dist = shape.max_distance
1678 st = stoptimes[-1] 1767 st = stoptimes[-1]
(...skipping 28 matching lines...) Expand all
1707 type=TYPE_WARNING) 1796 type=TYPE_WARNING)
1708 1797
1709 # O(n^2), but we don't anticipate many headway periods per trip 1798 # O(n^2), but we don't anticipate many headway periods per trip
1710 for headway_index, headway in enumerate(self._headways[0:-1]): 1799 for headway_index, headway in enumerate(self._headways[0:-1]):
1711 for other in self._headways[headway_index + 1:]: 1800 for other in self._headways[headway_index + 1:]:
1712 if (other[0] < headway[1]) and (other[1] > headway[0]): 1801 if (other[0] < headway[1]) and (other[1] > headway[0]):
1713 problems.OtherProblem('Trip contains overlapping headway periods ' 1802 problems.OtherProblem('Trip contains overlapping headway periods '
1714 '%s and %s' % 1803 '%s and %s' %
1715 (self._HeadwayOutputTuple(headway), 1804 (self._HeadwayOutputTuple(headway),
1716 self._HeadwayOutputTuple(other))) 1805 self._HeadwayOutputTuple(other)))
1806
1807 def _CheckSpeed(self, prev_stop, next_stop, depart_time,
1808 arrive_time, max_speed, problems):
1809 # Checks that the speed between two stops is not faster than max_speed
1810 if prev_stop != None:
1811 try:
1812 time_between_stops = arrive_time - depart_time
1813 except TypeError:
1814 return
1815
1816 try:
1817 dist_between_stops = \
1818 ApproximateDistanceBetweenStops(next_stop, prev_stop)
1819 except TypeError, e:
1820 return
1821
1822 if time_between_stops == 0:
1823 problems.TooFastTravel(self.trip_id,
1824 prev_stop.stop_name,
1825 next_stop.stop_name,
1826 dist_between_stops,
1827 time_between_stops,
1828 speed=None,
1829 type=TYPE_WARNING)
1830 return
1831 # This needs floating point division for precision.
1832 speed_between_stops = ((float(dist_between_stops) / 1000) /
1833 (float(time_between_stops) / 3600))
1834 if speed_between_stops > max_speed:
1835 problems.TooFastTravel(self.trip_id,
1836 prev_stop.stop_name,
1837 next_stop.stop_name,
1838 dist_between_stops,
1839 time_between_stops,
1840 speed_between_stops,
1841 type=TYPE_WARNING)
1717 1842
1718 # TODO: move these into a separate file 1843 # TODO: move these into a separate file
1719 class ISO4217(object): 1844 class ISO4217(object):
1720 """Represents the set of currencies recognized by the ISO-4217 spec.""" 1845 """Represents the set of currencies recognized by the ISO-4217 spec."""
1721 codes = { # map of alpha code to numerical code 1846 codes = { # map of alpha code to numerical code
1722 'AED': 784, 'AFN': 971, 'ALL': 8, 'AMD': 51, 'ANG': 532, 'AOA': 973, 1847 'AED': 784, 'AFN': 971, 'ALL': 8, 'AMD': 51, 'ANG': 532, 'AOA': 973,
1723 'ARS': 32, 'AUD': 36, 'AWG': 533, 'AZN': 944, 'BAM': 977, 'BBD': 52, 1848 'ARS': 32, 'AUD': 36, 'AWG': 533, 'AZN': 944, 'BAM': 977, 'BBD': 52,
1724 'BDT': 50, 'BGN': 975, 'BHD': 48, 'BIF': 108, 'BMD': 60, 'BND': 96, 1849 'BDT': 50, 'BGN': 975, 'BHD': 48, 'BIF': 108, 'BMD': 60, 'BND': 96,
1725 'BOB': 68, 'BOV': 984, 'BRL': 986, 'BSD': 44, 'BTN': 64, 'BWP': 72, 1850 'BOB': 68, 'BOV': 984, 'BRL': 986, 'BSD': 44, 'BTN': 64, 'BWP': 72,
1726 'BYR': 974, 'BZD': 84, 'CAD': 124, 'CDF': 976, 'CHE': 947, 'CHF': 756, 1851 'BYR': 974, 'BZD': 84, 'CAD': 124, 'CDF': 976, 'CHE': 947, 'CHF': 756,
(...skipping 593 matching lines...) Expand 10 before | Expand all | Expand 10 after
2320 yield (self.service_id, date, unicode(exception_type)) 2445 yield (self.service_id, date, unicode(exception_type))
2321 2446
2322 def GetCalendarDatesFieldValuesTuples(self): 2447 def GetCalendarDatesFieldValuesTuples(self):
2323 """Return a list of date execeptions""" 2448 """Return a list of date execeptions"""
2324 result = [] 2449 result = []
2325 for date_tuple in self.GenerateCalendarDatesFieldValuesTuples(): 2450 for date_tuple in self.GenerateCalendarDatesFieldValuesTuples():
2326 result.append(date_tuple) 2451 result.append(date_tuple)
2327 result.sort() # helps with __eq__ 2452 result.sort() # helps with __eq__
2328 return result 2453 return result
2329 2454
2330 def SetDateHasService(self, date, has_service=True, 2455 def SetDateHasService(self, date, has_service=True, problems=None):
2331 problems=None, service_id=None):
2332 if date in self.date_exceptions and problems: 2456 if date in self.date_exceptions and problems:
2333 problems.DuplicateID(('service_id', 'date'), 2457 problems.DuplicateID(('service_id', 'date'),
2334 (service_id, date), 2458 (self.service_id, date),
2335 type=TYPE_WARNING) 2459 type=TYPE_WARNING)
2336 self.date_exceptions[date] = has_service and 1 or 2 2460 self.date_exceptions[date] = has_service and 1 or 2
2337
2338 2461
2339 def ResetDateToNormalService(self, date): 2462 def ResetDateToNormalService(self, date):
2340 if date in self.date_exceptions: 2463 if date in self.date_exceptions:
2341 del self.date_exceptions[date] 2464 del self.date_exceptions[date]
2342 2465
2343 def SetStartDate(self, start_date): 2466 def SetStartDate(self, start_date):
2344 """Set the first day of service as a string in YYYYMMDD format""" 2467 """Set the first day of service as a string in YYYYMMDD format"""
2345 self.start_date = start_date 2468 self.start_date = start_date
2346 2469
2347 def SetEndDate(self, end_date): 2470 def SetEndDate(self, end_date):
(...skipping 31 matching lines...) Expand 10 before | Expand all | Expand 10 after
2379 2502
2380 def IsActiveOn(self, date): 2503 def IsActiveOn(self, date):
2381 """Test if this service period is active on a date. 2504 """Test if this service period is active on a date.
2382 2505
2383 Args: 2506 Args:
2384 date: a string of form "YYYYMMDD" 2507 date: a string of form "YYYYMMDD"
2385 2508
2386 Returns: 2509 Returns:
2387 True iff this service is active on date. 2510 True iff this service is active on date.
2388 """ 2511 """
2389 date_obj = DateStringToDateObject(date)
2390 if date in self.date_exceptions: 2512 if date in self.date_exceptions:
2391 if self.date_exceptions[date] == 1: 2513 if self.date_exceptions[date] == 1:
2392 return True 2514 return True
2393 else: 2515 else:
2394 return False 2516 return False
2395 if (self.start_date and self.end_date and self.start_date <= date and 2517 if (self.start_date and self.end_date and self.start_date <= date and
2396 date <= self.end_date): 2518 date <= self.end_date):
2519 date_obj = DateStringToDateObject(date)
2397 return self.day_of_week[date_obj.weekday()] 2520 return self.day_of_week[date_obj.weekday()]
2398 return False 2521 return False
2399 2522
2400 def ActiveDates(self): 2523 def ActiveDates(self):
2401 """Return dates this service period is active as a list of "YYYYMMDD".""" 2524 """Return dates this service period is active as a list of "YYYYMMDD"."""
2402 (earliest, latest) = self.GetDateRange() 2525 (earliest, latest) = self.GetDateRange()
2403 if earliest is None: 2526 if earliest is None:
2404 return [] 2527 return []
2405 dates = [] 2528 dates = []
2406 date_it = DateStringToDateObject(earliest) 2529 date_it = DateStringToDateObject(earliest)
(...skipping 316 matching lines...) Expand 10 before | Expand all | Expand 10 after
2723 2846
2724 ranges = [period.GetDateRange() for period in self.GetServicePeriodList()] 2847 ranges = [period.GetDateRange() for period in self.GetServicePeriodList()]
2725 starts = filter(lambda x: x, [item[0] for item in ranges]) 2848 starts = filter(lambda x: x, [item[0] for item in ranges])
2726 ends = filter(lambda x: x, [item[1] for item in ranges]) 2849 ends = filter(lambda x: x, [item[1] for item in ranges])
2727 2850
2728 if not starts or not ends: 2851 if not starts or not ends:
2729 return (None, None) 2852 return (None, None)
2730 2853
2731 return (min(starts), max(ends)) 2854 return (min(starts), max(ends))
2732 2855
2856 def GetServicePeriodsActiveEachDate(self, date_start, date_end):
2857 """Return a list of tuples (date, [period1, period2, ...]).
2858
2859 For each date in the range [date_start, date_end) make list of each
2860 ServicePeriod object which is active.
2861
2862 Args:
2863 date_start: The first date in the list, a date object
2864 date_end: The first date after the list, a date object
2865
2866 Returns:
2867 A list of tuples. Each tuple contains a date object and a list of zero or
2868 more ServicePeriod objects.
2869 """
2870 date_it = date_start
2871 one_day = datetime.timedelta(days=1)
2872 date_service_period_list = []
2873 while date_it < date_end:
2874 periods_today = []
2875 for service in self.GetServicePeriodList():
2876 if service.IsActiveOn(date_it.strftime("%Y%m%d")):
2877 periods_today.append(service)
2878 date_service_period_list.append((date_it, periods_today))
2879 date_it += one_day
2880 return date_service_period_list
2881
2882
2733 def AddStop(self, lat, lng, name): 2883 def AddStop(self, lat, lng, name):
2734 """Add a stop to this schedule. 2884 """Add a stop to this schedule.
2735 2885
2736 A new stop_id is created for this stop. Do not use this method unless all 2886 A new stop_id is created for this stop. Do not use this method unless all
2737 stops in this Schedule are created with it. See source for details. 2887 stops in this Schedule are created with it. See source for details.
2738 2888
2739 Args: 2889 Args:
2740 lat: Latitude of the stop as a float or string 2890 lat: Latitude of the stop as a float or string
2741 lng: Longitude of the stop as a float or string 2891 lng: Longitude of the stop as a float or string
2742 name: Name of the stop, which will appear in the feed 2892 name: Name of the stop, which will appear in the feed
(...skipping 357 matching lines...) Expand 10 before | Expand all | Expand 10 after
3100 3250
3101 archive.close() 3251 archive.close()
3102 3252
3103 def Validate(self, problems=None, validate_children=True): 3253 def Validate(self, problems=None, validate_children=True):
3104 """Validates various holistic aspects of the schedule 3254 """Validates various holistic aspects of the schedule
3105 (mostly interrelationships between the various data sets).""" 3255 (mostly interrelationships between the various data sets)."""
3106 if not problems: 3256 if not problems:
3107 problems = self.problem_reporter 3257 problems = self.problem_reporter
3108 3258
3109 (start_date, end_date) = self.GetDateRange() 3259 (start_date, end_date) = self.GetDateRange()
3110 if not end_date: 3260 if not end_date or not start_date:
3111 problems.OtherProblem('This feed has no effective service dates!', 3261 problems.OtherProblem('This feed has no effective service dates!',
3112 type=TYPE_WARNING) 3262 type=TYPE_WARNING)
3113 else: 3263 else:
3114 try: 3264 try:
3115 expiration = time.mktime(time.strptime(end_date, "%Y%m%d")) 3265 expiration = time.mktime(time.strptime(end_date, "%Y%m%d"))
3266 start_date_time = time.mktime(time.strptime(start_date, "%Y%m%d"))
3116 now = time.mktime(time.localtime()) 3267 now = time.mktime(time.localtime())
3117 warning_cutoff = now + 60 * 60 * 24 * 30 # one month from expiration 3268 warning_cutoff = now + 60 * 60 * 24 * 30 # one month from expiration
3118 if expiration < warning_cutoff: 3269 if expiration < warning_cutoff:
3119 problems.ExpirationDate(expiration) 3270 problems.ExpirationDate(expiration)
3271 if start_date_time > now:
3272 problems.FutureService(start_date_time)
3120 except ValueError: 3273 except ValueError:
3121 problems.InvalidValue('end_date', end_date) 3274 # Format of start_date and end_date checked in class ServicePeriod
3275 pass
3122 3276
3123 # TODO: Check Trip fields against valid values 3277 # TODO: Check Trip fields against valid values
3124 3278
3125 # Check for stops that aren't referenced by any trips and broken 3279 # Check for stops that aren't referenced by any trips and broken
3126 # parent_station references. 3280 # parent_station references. Also check that the parent station isn't too
3281 # far from its child stops.
3127 for stop in self.stops.values(): 3282 for stop in self.stops.values():
3128 if validate_children: 3283 if validate_children:
3129 stop.Validate(problems) 3284 stop.Validate(problems)
3130 cursor = self._connection.cursor() 3285 cursor = self._connection.cursor()
3131 cursor.execute("SELECT count(*) FROM stop_times WHERE stop_id=? LIMIT 1", 3286 cursor.execute("SELECT count(*) FROM stop_times WHERE stop_id=? LIMIT 1",
3132 (stop.stop_id,)) 3287 (stop.stop_id,))
3133 count = cursor.fetchone()[0] 3288 count = cursor.fetchone()[0]
3134 if stop.location_type == 0 and count == 0: 3289 if stop.location_type == 0 and count == 0:
3135 problems.UnusedStop(stop.stop_id, stop.stop_name) 3290 problems.UnusedStop(stop.stop_id, stop.stop_name)
3136 elif stop.location_type == 1 and count != 0: 3291 elif stop.location_type == 1 and count != 0:
3137 problems.UsedStation(stop.stop_id, stop.stop_name) 3292 problems.UsedStation(stop.stop_id, stop.stop_name)
3138 3293
3139 if stop.location_type != 1 and stop.parent_station: 3294 if stop.location_type != 1 and stop.parent_station:
3140 if stop.parent_station not in self.stops: 3295 if stop.parent_station not in self.stops:
3141 problems.InvalidValue("parent_station", 3296 problems.InvalidValue("parent_station",
3142 EncodeUnicode(stop.parent_station), 3297 EncodeUnicode(stop.parent_station),
3143 "parent_station '%s' not found for stop_id " 3298 "parent_station '%s' not found for stop_id "
3144 "'%s' in stops.txt" % 3299 "'%s' in stops.txt" %
3145 (EncodeUnicode(stop.parent_station), 3300 (EncodeUnicode(stop.parent_station),
3146 EncodeUnicode(stop.stop_id))) 3301 EncodeUnicode(stop.stop_id)))
3147 elif self.stops[stop.parent_station].location_type != 1: 3302 elif self.stops[stop.parent_station].location_type != 1:
3148 problems.InvalidValue("parent_station", 3303 problems.InvalidValue("parent_station",
3149 EncodeUnicode(stop.parent_station), 3304 EncodeUnicode(stop.parent_station),
3150 "parent_station '%s' of stop_id '%s' must " 3305 "parent_station '%s' of stop_id '%s' must "
3151 "have location_type=1 in stops.txt" % 3306 "have location_type=1 in stops.txt" %
3152 (EncodeUnicode(stop.parent_station), 3307 (EncodeUnicode(stop.parent_station),
3153 EncodeUnicode(stop.stop_id))) 3308 EncodeUnicode(stop.stop_id)))
3154 3309 else:
3155 #TODO: check that every station is used and within 1km of stops that are 3310 parent_station = self.stops[stop.parent_station]
3156 # part of it. Then uncomment testStationWithoutReference. 3311 distance = ApproximateDistanceBetweenStops(stop, parent_station)
3312 if distance > MAX_DISTANCE_BETWEEN_STOP_AND_PARENT_STATION_ERROR:
3313 problems.StopTooFarFromParentStation(
3314 stop.stop_id, stop.stop_name, parent_station.stop_id,
3315 parent_station.stop_name, distance, TYPE_ERROR)
3316 elif distance > MAX_DISTANCE_BETWEEN_STOP_AND_PARENT_STATION_WARNING:
3317 problems.StopTooFarFromParentStation(
3318 stop.stop_id, stop.stop_name, parent_station.stop_id,
3319 parent_station.stop_name, distance, TYPE_WARNING)
3320
3321 #TODO: check that every station is used.
3322 # Then uncomment testStationWithoutReference.
3157 3323
3158 # Check for stops that might represent the same location (specifically, 3324 # Check for stops that might represent the same location (specifically,
3159 # stops that are less that 2 meters apart) First filter out stops without a 3325 # stops that are less that 2 meters apart) First filter out stops without a
3160 # valid lat and lon. Then sort by latitude, then find the distance between 3326 # valid lat and lon. Then sort by latitude, then find the distance between
3161 # each pair of stations within 2 meters latitude of each other. This avoids 3327 # each pair of stations within 2 meters latitude of each other. This avoids
3162 # doing n^2 comparisons in the average case and doesn't need a spatial 3328 # doing n^2 comparisons in the average case and doesn't need a spatial
3163 # index. 3329 # index.
3164 sorted_stops = filter(lambda s: s.stop_lat and s.stop_lon, 3330 sorted_stops = filter(lambda s: s.stop_lat and s.stop_lon,
3165 self.GetStopList()) 3331 self.GetStopList())
3166 sorted_stops.sort(key=(lambda x: x.stop_lat)) 3332 sorted_stops.sort(key=(lambda x: x.stop_lat))
(...skipping 29 matching lines...) Expand all
3196 elif stop.location_type == 1 and other_stop.location_type == 0: 3362 elif stop.location_type == 1 and other_stop.location_type == 0:
3197 this_stop = other_stop 3363 this_stop = other_stop
3198 this_station = stop 3364 this_station = stop
3199 if this_stop.parent_station != this_station.stop_id: 3365 if this_stop.parent_station != this_station.stop_id:
3200 problems.OtherProblem( 3366 problems.OtherProblem(
3201 'The parent_station of stop "%s" (ID "%s") is not ' 3367 'The parent_station of stop "%s" (ID "%s") is not '
3202 'station "%s" (ID "%s") but they are only %0.2fm apart.' % 3368 'station "%s" (ID "%s") but they are only %0.2fm apart.' %
3203 (EncodeUnicode(this_stop.stop_name), 3369 (EncodeUnicode(this_stop.stop_name),
3204 EncodeUnicode(this_stop.stop_id), 3370 EncodeUnicode(this_stop.stop_id),
3205 EncodeUnicode(this_station.stop_name), 3371 EncodeUnicode(this_station.stop_name),
3206 EncodeUnicode(this_station.stop_id), distance)) 3372 EncodeUnicode(this_station.stop_id), distance),
3373 type=TYPE_WARNING)
3207 index += 1 3374 index += 1
3208 3375
3209 # Check for multiple routes using same short + long name 3376 # Check for multiple routes using same short + long name
3210 route_names = {} 3377 route_names = {}
3211 for route in self.routes.values(): 3378 for route in self.routes.values():
3212 if validate_children: 3379 if validate_children:
3213 route.Validate(problems) 3380 route.Validate(problems)
3214 short_name = '' 3381 short_name = ''
3215 if not IsEmpty(route.route_short_name): 3382 if not IsEmpty(route.route_short_name):
3216 short_name = route.route_short_name.lower().strip() 3383 short_name = route.route_short_name.lower().strip()
3217 long_name = '' 3384 long_name = ''
3218 if not IsEmpty(route.route_long_name): 3385 if not IsEmpty(route.route_long_name):
3219 long_name = route.route_long_name.lower().strip() 3386 long_name = route.route_long_name.lower().strip()
3220 name = (short_name, long_name) 3387 name = (short_name, long_name)
3221 if name in route_names: 3388 if name in route_names:
3222 problems.InvalidValue('route_long_name', 3389 problems.InvalidValue('route_long_name',
3223 long_name, 3390 long_name,
3224 'The same combination of ' 3391 'The same combination of '
3225 'route_short_name and route_long_name ' 3392 'route_short_name and route_long_name '
3226 'shouldn\'t be used for more than one ' 3393 'shouldn\'t be used for more than one '
3227 'route, as it is for the for the two routes ' 3394 'route, as it is for the for the two routes '
3228 'with IDs "%s" and "%s".' % 3395 'with IDs "%s" and "%s".' %
3229 (route.route_id, route_names[name].route_id), 3396 (route.route_id, route_names[name].route_id),
3230 type=TYPE_WARNING) 3397 type=TYPE_WARNING)
3231 else: 3398 else:
3232 route_names[name] = route 3399 route_names[name] = route
3233 3400
3234 # Check duplicate trips which go through the same stops with same 3401 stop_types = {} # a dict mapping stop_id to [route_id, route_type, is_match]
3235 # service and start times. 3402 trips = {} # a dict mapping tuple to (route_id, trip_id)
3236 3403 for trip in sorted(self.trips.values()):
3237 if self._check_duplicate_trips: 3404 if trip.route_id not in self.routes:
3238 trips = {} 3405 continue
3239 for trip in self.trips.values(): 3406 route_type = self.GetRoute(trip.route_id).route_type
3240 stop_ids = [] 3407 arrival_times = []
3241 stop_times = [] 3408 stop_ids = []
3242 for index, st in enumerate(trip.GetStopTimes(problems)): 3409 for index, st in enumerate(trip.GetStopTimes(problems)):
3243 stop_times.append(st.arrival_time) 3410 stop_id = st.stop.stop_id
3244 stop_ids.append(st.stop.stop_id) 3411 arrival_times.append(st.arrival_time)
3245 if not stop_ids or not stop_times: 3412 stop_ids.append(stop_id)
3413 # Check a stop if which belongs to both subway and bus.
3414 if (route_type == Route._ROUTE_TYPE_NAMES['Subway'] or
3415 route_type == Route._ROUTE_TYPE_NAMES['Bus']):
3416 if stop_id not in stop_types:
3417 stop_types[stop_id] = [trip.route_id, route_type, 0]
3418 elif (stop_types[stop_id][1] != route_type and
3419 stop_types[stop_id][2] == 0):
3420 stop_types[stop_id][2] = 1
3421 if stop_types[stop_id][1] == Route._ROUTE_TYPE_NAMES['Subway']:
3422 subway_route_id = stop_types[stop_id][0]
3423 bus_route_id = trip.route_id
3424 else:
3425 subway_route_id = trip.route_id
3426 bus_route_id = stop_types[stop_id][0]
3427 problems.StopWithMultipleRouteTypes(st.stop.stop_name, stop_id,
3428 subway_route_id, bus_route_id)
3429
3430 # Check duplicate trips which go through the same stops with same
3431 # service and start times.
3432 if self._check_duplicate_trips:
3433 if not stop_ids or not arrival_times:
3246 continue 3434 continue
3247 key = (trip.service_id, min(stop_times), str(stop_ids)) 3435 key = (trip.service_id, min(arrival_times), str(stop_ids))
3248 if key not in trips: 3436 if key not in trips:
3249 trips[key] = [trip.route_id, trip.trip_id] 3437 trips[key] = (trip.route_id, trip.trip_id)
3250 else: 3438 else:
3251 problems.OtherProblem('Trip %s of route %s might be duplicated ' 3439 problems.DuplicateTrip(trips[key][1], trips[key][0], trip.trip_id,
3252 'with trip %s of route %s. They go through ' 3440 trip.route_id)
3253 'the same stops with same service. '
3254 % (trips[key][1], trips[key][0],
3255 trip.trip_id, trip.route_id),
3256 type=TYPE_WARNING)
3257 3441
3258 # Check that routes' agency IDs are valid, if set 3442 # Check that routes' agency IDs are valid, if set
3259 for route in self.routes.values(): 3443 for route in self.routes.values():
3260 if (not IsEmpty(route.agency_id) and 3444 if (not IsEmpty(route.agency_id) and
3261 not route.agency_id in self._agencies): 3445 not route.agency_id in self._agencies):
3262 problems.InvalidValue('agency_id', 3446 problems.InvalidValue('agency_id',
3263 route.agency_id, 3447 route.agency_id,
3264 'The route with ID "%s" specifies agency_id ' 3448 'The route with ID "%s" specifies agency_id '
3265 '"%s", which doesn\'t exist.' % 3449 '"%s", which doesn\'t exist.' %
3266 (route.route_id, route.agency_id)) 3450 (route.route_id, route.agency_id))
(...skipping 463 matching lines...) Expand 10 before | Expand all | Expand 10 after
3730 def _FileContents(self, file_name): 3914 def _FileContents(self, file_name):
3731 results = None 3915 results = None
3732 if self._zip: 3916 if self._zip:
3733 try: 3917 try:
3734 results = self._zip.read(file_name) 3918 results = self._zip.read(file_name)
3735 except KeyError: # file not found in archve 3919 except KeyError: # file not found in archve
3736 self._problems.MissingFile(file_name) 3920 self._problems.MissingFile(file_name)
3737 return None 3921 return None
3738 else: 3922 else:
3739 try: 3923 try:
3740 data_file = open(os.path.join(self._path, file_name), 'r') 3924 data_file = open(os.path.join(self._path, file_name), 'rb')
3741 results = data_file.read() 3925 results = data_file.read()
3742 except IOError: # file not found 3926 except IOError: # file not found
3743 self._problems.MissingFile(file_name) 3927 self._problems.MissingFile(file_name)
3744 return None 3928 return None
3745 3929
3746 if not results: 3930 if not results:
3747 self._problems.EmptyFile(file_name) 3931 self._problems.EmptyFile(file_name)
3748 return results 3932 return results
3749 3933
3750 def _LoadAgencies(self): 3934 def _LoadAgencies(self):
(...skipping 41 matching lines...) Expand 10 before | Expand all | Expand 10 after
3792 periods = {} 3976 periods = {}
3793 3977
3794 # process calendar.txt 3978 # process calendar.txt
3795 if self._HasFile(file_name): 3979 if self._HasFile(file_name):
3796 has_useful_contents = False 3980 has_useful_contents = False
3797 for (row, row_num, cols) in \ 3981 for (row, row_num, cols) in \
3798 self._ReadCSV(file_name, 3982 self._ReadCSV(file_name,
3799 ServicePeriod._FIELD_NAMES, 3983 ServicePeriod._FIELD_NAMES,
3800 ServicePeriod._FIELD_NAMES_REQUIRED): 3984 ServicePeriod._FIELD_NAMES_REQUIRED):
3801 context = (file_name, row_num, row, cols) 3985 context = (file_name, row_num, row, cols)
3986 self._problems.SetFileContext(*context)
3802 3987
3803 period = ServicePeriod(field_list=row) 3988 period = ServicePeriod(field_list=row)
3804 3989
3805 if period.service_id in periods: 3990 if period.service_id in periods:
3806 self._problems.DuplicateID('service_id', period.service_id) 3991 self._problems.DuplicateID('service_id', period.service_id)
3807 continue 3992 else:
3808 3993 periods[period.service_id] = (period, context)
3809 periods[period.service_id] = (period, context) 3994 self._problems.ClearContext()
3810 3995
3811 # process calendar_dates.txt 3996 # process calendar_dates.txt
3812 if self._HasFile(file_name_dates): 3997 if self._HasFile(file_name_dates):
3813 # ['service_id', 'date', 'exception_type'] 3998 # ['service_id', 'date', 'exception_type']
3814 fields = ServicePeriod._FIELD_NAMES_CALENDAR_DATES 3999 fields = ServicePeriod._FIELD_NAMES_CALENDAR_DATES
3815 for (row, row_num, cols) in self._ReadCSV(file_name_dates, 4000 for (row, row_num, cols) in self._ReadCSV(file_name_dates,
3816 fields, fields): 4001 fields, fields):
3817 context = (file_name_dates, row_num, row, cols) 4002 context = (file_name_dates, row_num, row, cols)
4003 self._problems.SetFileContext(*context)
3818 4004
3819 service_id = row[0] 4005 service_id = row[0]
3820 4006
3821 period = None 4007 period = None
3822 if service_id in periods: 4008 if service_id in periods:
3823 period = periods[service_id][0] 4009 period = periods[service_id][0]
3824 else: 4010 else:
3825 period = ServicePeriod(service_id) 4011 period = ServicePeriod(service_id)
3826 periods[period.service_id] = (period, context) 4012 periods[period.service_id] = (period, context)
3827 4013
3828 exception_type = row[2] 4014 exception_type = row[2]
3829 if exception_type == u'1': 4015 if exception_type == u'1':
3830 period.SetDateHasService(row[1], True, self._problems, service_id) 4016 period.SetDateHasService(row[1], True, self._problems)
3831 elif exception_type == u'2': 4017 elif exception_type == u'2':
3832 period.SetDateHasService(row[1], False, self._problems, service_id) 4018 period.SetDateHasService(row[1], False, self._problems)
3833 else: 4019 else:
3834 self._problems.InvalidValue('exception_type', exception_type) 4020 self._problems.InvalidValue('exception_type', exception_type)
4021 self._problems.ClearContext()
3835 4022
3836 # Now insert the periods into the schedule object, so that they're 4023 # Now insert the periods into the schedule object, so that they're
3837 # validated with both calendar and calendar_dates info present 4024 # validated with both calendar and calendar_dates info present
3838 for period, context in periods.values(): 4025 for period, context in periods.values():
3839 self._problems.SetFileContext(*context) 4026 self._problems.SetFileContext(*context)
3840 self._schedule.AddServicePeriodObject(period, self._problems) 4027 self._schedule.AddServicePeriodObject(period, self._problems)
3841 self._problems.ClearContext() 4028 self._problems.ClearContext()
3842 4029
3843 def _LoadShapes(self): 4030 def _LoadShapes(self):
3844 if not self._HasFile('shapes.txt'): 4031 if not self._HasFile('shapes.txt'):
(...skipping 201 matching lines...) Expand 10 before | Expand all | Expand 10 after
4046 def __init__(self, *args, **kwargs): 4233 def __init__(self, *args, **kwargs):
4047 """Initialize a new ShapeLoader object. 4234 """Initialize a new ShapeLoader object.
4048 4235
4049 See Loader.__init__ for argument documentation. 4236 See Loader.__init__ for argument documentation.
4050 """ 4237 """
4051 Loader.__init__(self, *args, **kwargs) 4238 Loader.__init__(self, *args, **kwargs)
4052 4239
4053 def Load(self): 4240 def Load(self):
4054 self._LoadShapes() 4241 self._LoadShapes()
4055 return self._schedule 4242 return self._schedule
LEFTRIGHT

Powered by Google App Engine
RSS Feeds Recent Issues | This issue
This is Rietveld r497