| LEFT | RIGHT |
| 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 Loading... |
| 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 Loading... |
| 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 Loading... |
| 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 Loading... |
| 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 Loading... |
| 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 Loading... |
| 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 Loading... |
| 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 Loading... |
| 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): |
| 2456 if date in self.date_exceptions and problems: |
| 2457 problems.DuplicateID(('service_id', 'date'), |
| 2458 (self.service_id, date), |
| 2459 type=TYPE_WARNING) |
| 2331 self.date_exceptions[date] = has_service and 1 or 2 | 2460 self.date_exceptions[date] = has_service and 1 or 2 |
| 2332 | 2461 |
| 2333 def ResetDateToNormalService(self, date): | 2462 def ResetDateToNormalService(self, date): |
| 2334 if date in self.date_exceptions: | 2463 if date in self.date_exceptions: |
| 2335 del self.date_exceptions[date] | 2464 del self.date_exceptions[date] |
| 2336 | 2465 |
| 2337 def SetStartDate(self, start_date): | 2466 def SetStartDate(self, start_date): |
| 2338 """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""" |
| 2339 self.start_date = start_date | 2468 self.start_date = start_date |
| 2340 | 2469 |
| (...skipping 32 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 2373 | 2502 |
| 2374 def IsActiveOn(self, date): | 2503 def IsActiveOn(self, date): |
| 2375 """Test if this service period is active on a date. | 2504 """Test if this service period is active on a date. |
| 2376 | 2505 |
| 2377 Args: | 2506 Args: |
| 2378 date: a string of form "YYYYMMDD" | 2507 date: a string of form "YYYYMMDD" |
| 2379 | 2508 |
| 2380 Returns: | 2509 Returns: |
| 2381 True iff this service is active on date. | 2510 True iff this service is active on date. |
| 2382 """ | 2511 """ |
| 2383 date_obj = DateStringToDateObject(date) | |
| 2384 if date in self.date_exceptions: | 2512 if date in self.date_exceptions: |
| 2385 if self.date_exceptions[date] == 1: | 2513 if self.date_exceptions[date] == 1: |
| 2386 return True | 2514 return True |
| 2387 else: | 2515 else: |
| 2388 return False | 2516 return False |
| 2389 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 |
| 2390 date <= self.end_date): | 2518 date <= self.end_date): |
| 2519 date_obj = DateStringToDateObject(date) |
| 2391 return self.day_of_week[date_obj.weekday()] | 2520 return self.day_of_week[date_obj.weekday()] |
| 2392 return False | 2521 return False |
| 2393 | 2522 |
| 2394 def ActiveDates(self): | 2523 def ActiveDates(self): |
| 2395 """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".""" |
| 2396 (earliest, latest) = self.GetDateRange() | 2525 (earliest, latest) = self.GetDateRange() |
| 2397 if earliest is None: | 2526 if earliest is None: |
| 2398 return [] | 2527 return [] |
| 2399 dates = [] | 2528 dates = [] |
| 2400 date_it = DateStringToDateObject(earliest) | 2529 date_it = DateStringToDateObject(earliest) |
| (...skipping 316 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 2717 | 2846 |
| 2718 ranges = [period.GetDateRange() for period in self.GetServicePeriodList()] | 2847 ranges = [period.GetDateRange() for period in self.GetServicePeriodList()] |
| 2719 starts = filter(lambda x: x, [item[0] for item in ranges]) | 2848 starts = filter(lambda x: x, [item[0] for item in ranges]) |
| 2720 ends = filter(lambda x: x, [item[1] for item in ranges]) | 2849 ends = filter(lambda x: x, [item[1] for item in ranges]) |
| 2721 | 2850 |
| 2722 if not starts or not ends: | 2851 if not starts or not ends: |
| 2723 return (None, None) | 2852 return (None, None) |
| 2724 | 2853 |
| 2725 return (min(starts), max(ends)) | 2854 return (min(starts), max(ends)) |
| 2726 | 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 |
| 2727 def AddStop(self, lat, lng, name): | 2883 def AddStop(self, lat, lng, name): |
| 2728 """Add a stop to this schedule. | 2884 """Add a stop to this schedule. |
| 2729 | 2885 |
| 2730 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 |
| 2731 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. |
| 2732 | 2888 |
| 2733 Args: | 2889 Args: |
| 2734 lat: Latitude of the stop as a float or string | 2890 lat: Latitude of the stop as a float or string |
| 2735 lng: Longitude of the stop as a float or string | 2891 lng: Longitude of the stop as a float or string |
| 2736 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 Loading... |
| 3094 | 3250 |
| 3095 archive.close() | 3251 archive.close() |
| 3096 | 3252 |
| 3097 def Validate(self, problems=None, validate_children=True): | 3253 def Validate(self, problems=None, validate_children=True): |
| 3098 """Validates various holistic aspects of the schedule | 3254 """Validates various holistic aspects of the schedule |
| 3099 (mostly interrelationships between the various data sets).""" | 3255 (mostly interrelationships between the various data sets).""" |
| 3100 if not problems: | 3256 if not problems: |
| 3101 problems = self.problem_reporter | 3257 problems = self.problem_reporter |
| 3102 | 3258 |
| 3103 (start_date, end_date) = self.GetDateRange() | 3259 (start_date, end_date) = self.GetDateRange() |
| 3104 if not end_date: | 3260 if not end_date or not start_date: |
| 3105 problems.OtherProblem('This feed has no effective service dates!', | 3261 problems.OtherProblem('This feed has no effective service dates!', |
| 3106 type=TYPE_WARNING) | 3262 type=TYPE_WARNING) |
| 3107 else: | 3263 else: |
| 3108 try: | 3264 try: |
| 3109 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")) |
| 3110 now = time.mktime(time.localtime()) | 3267 now = time.mktime(time.localtime()) |
| 3111 warning_cutoff = now + 60 * 60 * 24 * 30 # one month from expiration | 3268 warning_cutoff = now + 60 * 60 * 24 * 30 # one month from expiration |
| 3112 if expiration < warning_cutoff: | 3269 if expiration < warning_cutoff: |
| 3113 problems.ExpirationDate(expiration) | 3270 problems.ExpirationDate(expiration) |
| 3271 if start_date_time > now: |
| 3272 problems.FutureService(start_date_time) |
| 3114 except ValueError: | 3273 except ValueError: |
| 3115 problems.InvalidValue('end_date', end_date) | 3274 # Format of start_date and end_date checked in class ServicePeriod |
| 3275 pass |
| 3116 | 3276 |
| 3117 # TODO: Check Trip fields against valid values | 3277 # TODO: Check Trip fields against valid values |
| 3118 | 3278 |
| 3119 # 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 |
| 3120 # parent_station references. | 3280 # parent_station references. Also check that the parent station isn't too |
| 3281 # far from its child stops. |
| 3121 for stop in self.stops.values(): | 3282 for stop in self.stops.values(): |
| 3122 if validate_children: | 3283 if validate_children: |
| 3123 stop.Validate(problems) | 3284 stop.Validate(problems) |
| 3124 cursor = self._connection.cursor() | 3285 cursor = self._connection.cursor() |
| 3125 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", |
| 3126 (stop.stop_id,)) | 3287 (stop.stop_id,)) |
| 3127 count = cursor.fetchone()[0] | 3288 count = cursor.fetchone()[0] |
| 3128 if stop.location_type == 0 and count == 0: | 3289 if stop.location_type == 0 and count == 0: |
| 3129 problems.UnusedStop(stop.stop_id, stop.stop_name) | 3290 problems.UnusedStop(stop.stop_id, stop.stop_name) |
| 3130 elif stop.location_type == 1 and count != 0: | 3291 elif stop.location_type == 1 and count != 0: |
| 3131 problems.UsedStation(stop.stop_id, stop.stop_name) | 3292 problems.UsedStation(stop.stop_id, stop.stop_name) |
| 3132 | 3293 |
| 3133 if stop.location_type != 1 and stop.parent_station: | 3294 if stop.location_type != 1 and stop.parent_station: |
| 3134 if stop.parent_station not in self.stops: | 3295 if stop.parent_station not in self.stops: |
| 3135 problems.InvalidValue("parent_station", | 3296 problems.InvalidValue("parent_station", |
| 3136 EncodeUnicode(stop.parent_station), | 3297 EncodeUnicode(stop.parent_station), |
| 3137 "parent_station '%s' not found for stop_id " | 3298 "parent_station '%s' not found for stop_id " |
| 3138 "'%s' in stops.txt" % | 3299 "'%s' in stops.txt" % |
| 3139 (EncodeUnicode(stop.parent_station), | 3300 (EncodeUnicode(stop.parent_station), |
| 3140 EncodeUnicode(stop.stop_id))) | 3301 EncodeUnicode(stop.stop_id))) |
| 3141 elif self.stops[stop.parent_station].location_type != 1: | 3302 elif self.stops[stop.parent_station].location_type != 1: |
| 3142 problems.InvalidValue("parent_station", | 3303 problems.InvalidValue("parent_station", |
| 3143 EncodeUnicode(stop.parent_station), | 3304 EncodeUnicode(stop.parent_station), |
| 3144 "parent_station '%s' of stop_id '%s' must " | 3305 "parent_station '%s' of stop_id '%s' must " |
| 3145 "have location_type=1 in stops.txt" % | 3306 "have location_type=1 in stops.txt" % |
| 3146 (EncodeUnicode(stop.parent_station), | 3307 (EncodeUnicode(stop.parent_station), |
| 3147 EncodeUnicode(stop.stop_id))) | 3308 EncodeUnicode(stop.stop_id))) |
| 3148 | 3309 else: |
| 3149 #TODO: check that every station is used and within 1km of stops that are | 3310 parent_station = self.stops[stop.parent_station] |
| 3150 # 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. |
| 3151 | 3323 |
| 3152 # Check for stops that might represent the same location (specifically, | 3324 # Check for stops that might represent the same location (specifically, |
| 3153 # 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 |
| 3154 # 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 |
| 3155 # 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 |
| 3156 # 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 |
| 3157 # index. | 3329 # index. |
| 3158 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, |
| 3159 self.GetStopList()) | 3331 self.GetStopList()) |
| 3160 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 Loading... |
| 3190 elif stop.location_type == 1 and other_stop.location_type == 0: | 3362 elif stop.location_type == 1 and other_stop.location_type == 0: |
| 3191 this_stop = other_stop | 3363 this_stop = other_stop |
| 3192 this_station = stop | 3364 this_station = stop |
| 3193 if this_stop.parent_station != this_station.stop_id: | 3365 if this_stop.parent_station != this_station.stop_id: |
| 3194 problems.OtherProblem( | 3366 problems.OtherProblem( |
| 3195 'The parent_station of stop "%s" (ID "%s") is not ' | 3367 'The parent_station of stop "%s" (ID "%s") is not ' |
| 3196 'station "%s" (ID "%s") but they are only %0.2fm apart.' % | 3368 'station "%s" (ID "%s") but they are only %0.2fm apart.' % |
| 3197 (EncodeUnicode(this_stop.stop_name), | 3369 (EncodeUnicode(this_stop.stop_name), |
| 3198 EncodeUnicode(this_stop.stop_id), | 3370 EncodeUnicode(this_stop.stop_id), |
| 3199 EncodeUnicode(this_station.stop_name), | 3371 EncodeUnicode(this_station.stop_name), |
| 3200 EncodeUnicode(this_station.stop_id), distance)) | 3372 EncodeUnicode(this_station.stop_id), distance), |
| 3373 type=TYPE_WARNING) |
| 3201 index += 1 | 3374 index += 1 |
| 3202 | 3375 |
| 3203 # Check for multiple routes using same short + long name | 3376 # Check for multiple routes using same short + long name |
| 3204 route_names = {} | 3377 route_names = {} |
| 3205 for route in self.routes.values(): | 3378 for route in self.routes.values(): |
| 3206 if validate_children: | 3379 if validate_children: |
| 3207 route.Validate(problems) | 3380 route.Validate(problems) |
| 3208 short_name = '' | 3381 short_name = '' |
| 3209 if not IsEmpty(route.route_short_name): | 3382 if not IsEmpty(route.route_short_name): |
| 3210 short_name = route.route_short_name.lower().strip() | 3383 short_name = route.route_short_name.lower().strip() |
| 3211 long_name = '' | 3384 long_name = '' |
| 3212 if not IsEmpty(route.route_long_name): | 3385 if not IsEmpty(route.route_long_name): |
| 3213 long_name = route.route_long_name.lower().strip() | 3386 long_name = route.route_long_name.lower().strip() |
| 3214 name = (short_name, long_name) | 3387 name = (short_name, long_name) |
| 3215 if name in route_names: | 3388 if name in route_names: |
| 3216 problems.InvalidValue('route_long_name', | 3389 problems.InvalidValue('route_long_name', |
| 3217 long_name, | 3390 long_name, |
| 3218 'The same combination of ' | 3391 'The same combination of ' |
| 3219 'route_short_name and route_long_name ' | 3392 'route_short_name and route_long_name ' |
| 3220 'shouldn\'t be used for more than one ' | 3393 'shouldn\'t be used for more than one ' |
| 3221 'route, as it is for the for the two routes ' | 3394 'route, as it is for the for the two routes ' |
| 3222 'with IDs "%s" and "%s".' % | 3395 'with IDs "%s" and "%s".' % |
| 3223 (route.route_id, route_names[name].route_id), | 3396 (route.route_id, route_names[name].route_id), |
| 3224 type=TYPE_WARNING) | 3397 type=TYPE_WARNING) |
| 3225 else: | 3398 else: |
| 3226 route_names[name] = route | 3399 route_names[name] = route |
| 3227 | 3400 |
| 3228 # 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] |
| 3229 # service and start times. | 3402 trips = {} # a dict mapping tuple to (route_id, trip_id) |
| 3230 | 3403 for trip in sorted(self.trips.values()): |
| 3231 if self._check_duplicate_trips: | 3404 if trip.route_id not in self.routes: |
| 3232 trips = {} | 3405 continue |
| 3233 for trip in self.trips.values(): | 3406 route_type = self.GetRoute(trip.route_id).route_type |
| 3234 stop_ids = [] | 3407 arrival_times = [] |
| 3235 stop_times = [] | 3408 stop_ids = [] |
| 3236 for index, st in enumerate(trip.GetStopTimes(problems)): | 3409 for index, st in enumerate(trip.GetStopTimes(problems)): |
| 3237 stop_times.append(st.arrival_time) | 3410 stop_id = st.stop.stop_id |
| 3238 stop_ids.append(st.stop.stop_id) | 3411 arrival_times.append(st.arrival_time) |
| 3239 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: |
| 3240 continue | 3434 continue |
| 3241 key = (trip.service_id, min(stop_times), str(stop_ids)) | 3435 key = (trip.service_id, min(arrival_times), str(stop_ids)) |
| 3242 if key not in trips: | 3436 if key not in trips: |
| 3243 trips[key] = [trip.route_id, trip.trip_id] | 3437 trips[key] = (trip.route_id, trip.trip_id) |
| 3244 else: | 3438 else: |
| 3245 problems.OtherProblem('Trip %s of route %s might be duplicated ' | 3439 problems.DuplicateTrip(trips[key][1], trips[key][0], trip.trip_id, |
| 3246 'with trip %s of route %s. They go through ' | 3440 trip.route_id) |
| 3247 'the same stops with same service. ' | |
| 3248 % (trips[key][1], trips[key][0], | |
| 3249 trip.trip_id, trip.route_id), | |
| 3250 type=TYPE_WARNING) | |
| 3251 | 3441 |
| 3252 # Check that routes' agency IDs are valid, if set | 3442 # Check that routes' agency IDs are valid, if set |
| 3253 for route in self.routes.values(): | 3443 for route in self.routes.values(): |
| 3254 if (not IsEmpty(route.agency_id) and | 3444 if (not IsEmpty(route.agency_id) and |
| 3255 not route.agency_id in self._agencies): | 3445 not route.agency_id in self._agencies): |
| 3256 problems.InvalidValue('agency_id', | 3446 problems.InvalidValue('agency_id', |
| 3257 route.agency_id, | 3447 route.agency_id, |
| 3258 'The route with ID "%s" specifies agency_id ' | 3448 'The route with ID "%s" specifies agency_id ' |
| 3259 '"%s", which doesn\'t exist.' % | 3449 '"%s", which doesn\'t exist.' % |
| 3260 (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 Loading... |
| 3724 def _FileContents(self, file_name): | 3914 def _FileContents(self, file_name): |
| 3725 results = None | 3915 results = None |
| 3726 if self._zip: | 3916 if self._zip: |
| 3727 try: | 3917 try: |
| 3728 results = self._zip.read(file_name) | 3918 results = self._zip.read(file_name) |
| 3729 except KeyError: # file not found in archve | 3919 except KeyError: # file not found in archve |
| 3730 self._problems.MissingFile(file_name) | 3920 self._problems.MissingFile(file_name) |
| 3731 return None | 3921 return None |
| 3732 else: | 3922 else: |
| 3733 try: | 3923 try: |
| 3734 data_file = open(os.path.join(self._path, file_name), 'r') | 3924 data_file = open(os.path.join(self._path, file_name), 'rb') |
| 3735 results = data_file.read() | 3925 results = data_file.read() |
| 3736 except IOError: # file not found | 3926 except IOError: # file not found |
| 3737 self._problems.MissingFile(file_name) | 3927 self._problems.MissingFile(file_name) |
| 3738 return None | 3928 return None |
| 3739 | 3929 |
| 3740 if not results: | 3930 if not results: |
| 3741 self._problems.EmptyFile(file_name) | 3931 self._problems.EmptyFile(file_name) |
| 3742 return results | 3932 return results |
| 3743 | 3933 |
| 3744 def _LoadAgencies(self): | 3934 def _LoadAgencies(self): |
| (...skipping 41 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 3786 periods = {} | 3976 periods = {} |
| 3787 | 3977 |
| 3788 # process calendar.txt | 3978 # process calendar.txt |
| 3789 if self._HasFile(file_name): | 3979 if self._HasFile(file_name): |
| 3790 has_useful_contents = False | 3980 has_useful_contents = False |
| 3791 for (row, row_num, cols) in \ | 3981 for (row, row_num, cols) in \ |
| 3792 self._ReadCSV(file_name, | 3982 self._ReadCSV(file_name, |
| 3793 ServicePeriod._FIELD_NAMES, | 3983 ServicePeriod._FIELD_NAMES, |
| 3794 ServicePeriod._FIELD_NAMES_REQUIRED): | 3984 ServicePeriod._FIELD_NAMES_REQUIRED): |
| 3795 context = (file_name, row_num, row, cols) | 3985 context = (file_name, row_num, row, cols) |
| 3986 self._problems.SetFileContext(*context) |
| 3796 | 3987 |
| 3797 period = ServicePeriod(field_list=row) | 3988 period = ServicePeriod(field_list=row) |
| 3798 | 3989 |
| 3799 if period.service_id in periods: | 3990 if period.service_id in periods: |
| 3800 self._problems.DuplicateID('service_id', period.service_id) | 3991 self._problems.DuplicateID('service_id', period.service_id) |
| 3801 continue | 3992 else: |
| 3802 | 3993 periods[period.service_id] = (period, context) |
| 3803 periods[period.service_id] = (period, context) | 3994 self._problems.ClearContext() |
| 3804 | 3995 |
| 3805 # process calendar_dates.txt | 3996 # process calendar_dates.txt |
| 3806 if self._HasFile(file_name_dates): | 3997 if self._HasFile(file_name_dates): |
| 3807 service_id_date_entries = {} | |
| 3808 # ['service_id', 'date', 'exception_type'] | 3998 # ['service_id', 'date', 'exception_type'] |
| 3809 fields = ServicePeriod._FIELD_NAMES_CALENDAR_DATES | 3999 fields = ServicePeriod._FIELD_NAMES_CALENDAR_DATES |
| 3810 for (row, row_num, cols) in self._ReadCSV(file_name_dates, | 4000 for (row, row_num, cols) in self._ReadCSV(file_name_dates, |
| 3811 fields, fields): | 4001 fields, fields): |
| 3812 context = (file_name_dates, row_num, row, cols) | 4002 context = (file_name_dates, row_num, row, cols) |
| 4003 self._problems.SetFileContext(*context) |
| 3813 | 4004 |
| 3814 service_id = row[0] | 4005 service_id = row[0] |
| 3815 | 4006 |
| 3816 period = None | 4007 period = None |
| 3817 if service_id in periods: | 4008 if service_id in periods: |
| 3818 period = periods[service_id][0] | 4009 period = periods[service_id][0] |
| 3819 else: | 4010 else: |
| 3820 period = ServicePeriod(service_id) | 4011 period = ServicePeriod(service_id) |
| 3821 periods[period.service_id] = (period, context) | 4012 periods[period.service_id] = (period, context) |
| 3822 | |
| 3823 # Check uniqueness of service_id x date | |
| 3824 if (service_id, row[1]) in service_id_date_entries: | |
| 3825 self._problems.DuplicateID(('service_id', 'date'), | |
| 3826 (service_id, row[1]), | |
| 3827 type=TYPE_WARNING) | |
| 3828 service_id_date_entries[service_id, row[1]] = 1 | |
| 3829 | 4013 |
| 3830 exception_type = row[2] | 4014 exception_type = row[2] |
| 3831 if exception_type == u'1': | 4015 if exception_type == u'1': |
| 3832 period.SetDateHasService(row[1], True) | 4016 period.SetDateHasService(row[1], True, self._problems) |
| 3833 elif exception_type == u'2': | 4017 elif exception_type == u'2': |
| 3834 period.SetDateHasService(row[1], False) | 4018 period.SetDateHasService(row[1], False, self._problems) |
| 3835 else: | 4019 else: |
| 3836 self._problems.InvalidValue('exception_type', exception_type) | 4020 self._problems.InvalidValue('exception_type', exception_type) |
| 4021 self._problems.ClearContext() |
| 3837 | 4022 |
| 3838 # 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 |
| 3839 # validated with both calendar and calendar_dates info present | 4024 # validated with both calendar and calendar_dates info present |
| 3840 for period, context in periods.values(): | 4025 for period, context in periods.values(): |
| 3841 self._problems.SetFileContext(*context) | 4026 self._problems.SetFileContext(*context) |
| 3842 self._schedule.AddServicePeriodObject(period, self._problems) | 4027 self._schedule.AddServicePeriodObject(period, self._problems) |
| 3843 self._problems.ClearContext() | 4028 self._problems.ClearContext() |
| 3844 | 4029 |
| 3845 def _LoadShapes(self): | 4030 def _LoadShapes(self): |
| 3846 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 Loading... |
| 4048 def __init__(self, *args, **kwargs): | 4233 def __init__(self, *args, **kwargs): |
| 4049 """Initialize a new ShapeLoader object. | 4234 """Initialize a new ShapeLoader object. |
| 4050 | 4235 |
| 4051 See Loader.__init__ for argument documentation. | 4236 See Loader.__init__ for argument documentation. |
| 4052 """ | 4237 """ |
| 4053 Loader.__init__(self, *args, **kwargs) | 4238 Loader.__init__(self, *args, **kwargs) |
| 4054 | 4239 |
| 4055 def Load(self): | 4240 def Load(self): |
| 4056 self._LoadShapes() | 4241 self._LoadShapes() |
| 4057 return self._schedule | 4242 return self._schedule |
| LEFT | RIGHT |