From: maxious Date: Mon, 19 Apr 2010 12:45:16 +0000 Subject: Initial Commit X-Git-Url: http://maxious.lambdacomplex.org/git/?p=bus.git&a=commitdiff&h=10df80f8fd69cd08e79e437fdcfc426696701be7 --- Initial Commit --- --- /dev/null +++ b/display.kml.php @@ -1,1 +1,65 @@ + +'; +echo ' + '; +$conn = pg_connect("dbname=openstreetmap user=postgres password=snmc"); +if (!$conn) { + echo "An error occured.\n"; + exit; +} + +$result_route = pg_query($conn, "SELECT * from current_relation_tags, (Select id FROM current_relation_tags WHERE k = 'route' AND v = 'bus') as a +where a.id = current_relation_tags.id and k = 'ref';"); +if (!$result_route) { + echo "An route retirieve error occured.\n"; + exit; +} + +while ($route = pg_fetch_assoc($result_route)) { + echo "\n\n"; + echo "".$route['v']." position at ".$route['id'].""; + echo "".$route['v']." position at ".$route['id'].""; +echo "#yellowLineGreenPoly"; +echo " + 1 + "; +$result_way = pg_query($conn, 'SELECT member_id, sequence_id FROM "current_relation_members" WHERE "id" = '.$route['id'].' order by "sequence_id" +ASC'); +if (!$result_way) { + echo "An way retirieve error occured.\n"; + exit; +} + $count = 0; + +while ($way = pg_fetch_assoc($result_way)) { + $result_node = pg_query($conn, 'SELECT * FROM current_nodes INNER JOIN current_way_nodes ON current_way_nodes.node_id=current_nodes.id WHERE +current_way_nodes.id = '.$way['member_id'].' order by "sequence_id" ASC'); + if (!$result_node) { + echo "An node retirieve error occured.\n"; + exit; + } + + while ($node = pg_fetch_assoc($result_node)) { + $count++; + echo ($node['longitude']/10000000).",".($node['latitude']/10000000).",600 \n"; + } +} + if ($count == 0) echo (0).",".(0).",600 \n"; +echo " + "; +echo ''; +} + +echo "\n\n"; +?> --- /dev/null +++ b/display.php @@ -1,1 +1,58 @@ + + + + + + + + +
+ + + + --- /dev/null +++ b/index.php @@ -1,1 +1,3 @@ +$query = "SELECT * from current_relation_tags, (Select "id" FROM "public"."current_relation_tags" WHERE "k" = 'route' AND "v" = 'bus') as a where +a.id = current_relation_tags.id;" --- /dev/null +++ b/maxious-canberra-transit-feed/.gitignore @@ -1,1 +1,3 @@ - +hfxfeed.zip +hfxtable.yml +*~ --- /dev/null +++ b/maxious-canberra-transit-feed/900-intertown.yml @@ -1,1 +1,80 @@ +short_name: 900 +long_name: Intertown +time_points: [ civic_platform_6, 3042, 4531, 4929, civic_platform_1, civic_platform_5 ] +between_stops: [ ] +stop_times: [ + [ 632a, 642a, 657a, 708a, 715a, 727a], + [ 702a, 712a, 727a, 738a, 745a, 757a], + [ -, -, 755a, 806a, 813a, 825a], + [ 732a, 742a, 757a, 808a, 815a, 827a], + [ 802a, 812a, 827a, 838a, 845a, 857a], + [ 832a, 842a, 857a, 908a, 915a, 927a], + [ 902a, 912a, 927a, 938a, 945a, 957a], + [ 932a, 942a, 957a, 1008a, 1015a, 1027a], + [ 1002a, 1012a, 1027a, 1038a, 1045a, 1057a], + [ 1032a, 1042a, 1057a, 1108a, 1115a, 1127a], + [ 1102a, 1112a, 1127a, 1138a, 1145a, 1157a], + [ 1132a, 1142a, 1157a, 1208p, 1215p, 1227p], + [ 1202p, 1212p, 1227p, 1238p, 1245p, 1257p], + [ 1232p, 1242p, 1257p, 108p, 115p, 127p], + [ 102p, 112p, 127p, 138p, 145p, 157p], + [ 132p, 142p, 157p, 208p, 215p, 227p], + [ 202p, 212p, 227p, 238p, 245p, 257p], + [ 232p, 242p, 257p, 308p, 315p, 327p], + [ 302p, 312p, 327p, 338p, 345p, 357p], + [ -, -, 340p, 351p, 358p, 410p], + [ -, -, -, -, 407p, 419p], + [ 332p, 342p, 357p, 408p, 415p, 427p], + [ -, -, -, -, 428p, 440p], + [ 359p, 409p, 424p, 435p, 442p, 454p], + [ 432p, 442p, 457p, 508p, 515p, 527p], + [ 502p, 512p, 527p, 538p, 545p, 557p], + [ 532p, 542p, 557p, 608p, 615p, 627p], + [ 602p, 612p, 627p, 638p, 645p, 657p], + [ 632p, 642p, 657p, 708p, 715p, 727p], + [ 702p, 712p, 727p, 738p, 745p, 757p], + [ 732p, 742p, 757p, 808p, 815p, 827p], + [ 832p, 842p, 857p, 908p, 915p, 927p], + [ 932p, 942p, 957p, 1008p, 1015p, 1027p], + [ 1032p, 1042p, 1057p, -, -, -], + [ 1132p, 1142p, 1157p, -, -, -], + [ 1235x, 1245x, 100x, -, -, -] +] +stop_times_saturday: [ + [ 646a, 655a, 710a, 721a, 728a, 740a], + [ 746a, 755a, 810a, 821a, 828a, 840a], + [ 846a, 855a, 910a, 921a, 928a, 940a], + [ 946a, 955a, 1010a, 1021a, 1028a, 1040a], + [ 1046a, 1055a, 1110a, 1121a, 1128a, 1140a], + [ 1146a, 1155a, 1210p, 1221p, 1228p, 1240p], + [ 1246p, 1255p, 110p, 121p, 128p, 140p], + [ 146p, 155p, 210p, 221p, 228p, 240p], + [ 246p, 255p, 310p, 321p, 328p, 340p], + [ 346p, 355p, 410p, 421p, 428p, 440p], + [ 446p, 455p, 510p, 521p, 528p, 540p], + [ 546p, 555p, 610p, 621p, 628p, 640p], + [ 646p, 655p, 710p, 721p, 728p, 740p], + [ 746p, 755p, 810p, 821p, 828p, 840p], + [ 846p, 855p, 910p, 921p, 928p, 940p], + [ 946p, 955p, 1010p, 1021p, 1028p, 1040p], + [ 1046p, 1055p, 1107p, -, -, -] +] +stop_times_sunday: [ + [ 728a, 736a, 750a, 801a, 808a, 817a], + [ 828a, 836a, 850a, 901a, 908a, 917a], + [ 928a, 936a, 950a, 1001a, 1008a, 1017a], + [ 1028a, 1036a, 1050a, 1101a, 1108a, 1117a], + [ 1128a, 1136a, 1150a, 1201p, 1208p, 1217p], + [ 1228p, 1236p, 1250p, 101p, 108p, 117p], + [ 128p, 136p, 150p, 201p, 208p, 217p], + [ 228p, 236p, 250p, 301p, 308p, 317p], + [ 328p, 336p, 350p, 401p, 408p, 417p], + [ 428p, 436p, 450p, 501p, 508p, 517p], + [ 528p, 536p, 550p, 601p, 608p, 617p], + [ 628p, 636p, 650p, 701p, 708p, 717p], + [ 728p, 736p, 750p, 801p, 808p, 817p], + [ 828p, 836p, 850p, 901p, 908p, 917p], + [ 928p, 936p, 950p, 1001p, 1008p, 1017p], + [ 1028p, 1036p, 1050p, 1101p, 1108p, 1117p] +] --- /dev/null +++ b/maxious-canberra-transit-feed/Makefile @@ -1,1 +1,23 @@ +default: cbrfeed.zip +cbrfeed.zip: cbrtable.yml createfeed.py + ./createfeed.py --input=cbrtable.yml --output=cbrfeed.zip + +ROUTE_FILES=900-intertown.yml + +cbrtable.yml: cbrtable.yml.in $(ROUTE_FILES) indent-route.pl + cp cbrtable.yml.in cbrtable.yml + @$(foreach ROUTE_FILE, $(ROUTE_FILES), \ + echo "Parsing $(ROUTE_FILE)"; \ + echo "TODO: replace friendly timing spot names with OSM node IDs in $(ROUTE_FILE)"; \ + echo "TODO: add inbetween stops in $(ROUTE_FILE)"; \ + ./indent-route.pl < $(ROUTE_FILE) >> cbrtable.yml;) + +cbrtable.yml.in: cbrtable.yml.in.in + @echo "TODO: autogenerate stops via OSM" + cp cbrtable.yml.in.in cbrtable.yml.in + + +clean: + rm -f cbrtable.yml cbrtable.yml.in cbrfeed.zip *~ + --- /dev/null +++ b/maxious-canberra-transit-feed/README @@ -1,1 +1,35 @@ +=== Introduction === +This distribution contains everything required to build a basic google transit +feed for Halifax Metro Transit, Nova Scotia, Canada. Note that it is woefully +incomplete at the moment. + +Requirements: GNU Make, Perl, Python 2.5. + +=== Usage === + +First, grab a copy of google transit feed tools: + +cd $HOME/src +wget http://googletransitdatafeed.googlecode.com/files/transitfeed-1.1.7.tar.gz +tar zxvf transitfeed-1.1.7.tar.gz + +Set PYTHONPATH to the python directory in the above checkout: + +export PYTHONPATH=$HOME/src/transitfeed-1.1.7/python + +Then just type "make" to build the feed. The output at the end is "feed.zip". +For fun, you can view this feed using the snazzy transit feed view application: + +$HOME/src/transitfeed-1.1.7/python/schedule_viewer.py --feed=hfxfeed.zip + +=== Copyright === + +With the exception of createfeed.py, which is licensed under the Apache Public +License, please consider all software tools in distribution to be in the public +domain. Use them for what you will. + +I believe the Metro Transit route data is considered factual information +which can not be copyrighted. Note, however, that Metro Transit and/or +the city of Halifax may have claim over its own name and other trademarks. + --- /dev/null +++ b/maxious-canberra-transit-feed/add-between-times.pl @@ -1,1 +1,104 @@ +#!/usr/bin/perl +use strict; + +sub parse_time { + my ($time) = @_; + + my ($hour, $minute); + + if ($time =~ /a\Z/) { + $time =~ m/([0-9]+)([0-9][0-9])a/; + ($hour, $minute) = ($1, $2); + } elsif ($time =~ /p\Z/) { + $time =~ m/([0-9]+)([0-9][0-9])p/; + ($hour, $minute) = ($1, $2); + if ($hour < 12) { + $hour += 12; + } + } elsif ($time =~ /x\Z/) { + $time =~ m/([0-9]+)([0-9][0-9])x/; + ($hour, $minute) = ($1, $2); + if ($hour == 12) { + $hour += 12; + } else { + $hour += 24; + } + } elsif ($time =~ /^\ *-\Z/) { + ($hour, $minute) = (0, 0); + # no stop at this time + } else { + print "Should not happen! Time ('$time') misformed.\n"; + exit; + } + + return ($hour, $minute); +} + +my $num_intervals = $ARGV[0] or die "No num intervals given!"; +my $interval = $ARGV[1] or die "No interval given!"; + +my @times; + +$_ = ; +print $_; + +if ($_ !~ /^\#/) { + my @timestrs; + if ($_ =~ m/\[(.*)\]/) { + my $inner = $1; + @timestrs = split (/\,/, $inner); + + } else { + @timestrs = split /\ /; + } + + foreach (@timestrs) { + my ($hour, $minute) = parse_time($_); + push @times, [ $hour, $minute ]; + } +} + +for (my $i=1; $i<($num_intervals+1); $i++) { + my $first = 1; + foreach (@times) { + my $mytime = $_; + my ($hour, $minute) = (@$mytime[0], @$mytime[1]); + if ($hour > 0 || $minute > 0) { + $minute += $interval * $i; + if ($minute > 59) { + $hour += int($minute / 60); + $minute = $minute % 60; + if ($minute < 10) { + $minute = "0" . $minute; + } + } + } + + sub print_time { + my ($hour, $minute) = @_; + if ($hour == 0 && $minute == 0) { + print "-"; + } else { + if ($hour < 12) { + print "$hour$minute" . "a"; + } else { + if ($hour > 12) { + $hour -= 12; + } + print "$hour$minute" . "p"; + } + } + } + + if (!$first) { + print " "; + print_time($hour, $minute); + } else { + $first = 0; + print_time($hour, $minute); + } + } +print "\n"; +} + --- /dev/null +++ b/maxious-canberra-transit-feed/cbrtable.yml @@ -1,1 +1,99 @@ +options: + start_date: 20090525 + end_date: 20100601 + remove_date: 2010601 + agency_name: ACT Internal Omnibus Network (ACTION) + agency_url: http://www.action.act.gov.au/ + agency_timezone: Australia/Canberra +stops: + - { name: Civic Interchange Platform 1,stop_code: civic_platform_1, lat: -35.2794347, lng: 149.130588} + - { name: Civic Interchange Platform 5,stop_code: civic_platform_5, lat: -35.2786, lng: 149.13033} + - { name: Civic Interchange Platform 6,stop_code: civic_platform_6, lat: -35.27851, lng: 149.12979 } + - { name: Canberra House Northbound, stop_code: 3042, lat: -35.27833, +lng: 149.12712 } + - { name: Canberra House Southbound, stop_code: 4531, +lat: -35.2786, lng: 149.13033 } + - { name: Marcus Clarke Street - Unilodge ANU, stop_code: 4929, lat: -35.2764151, lng: 149.1267199 } + +routes: + - short_name: 900 + long_name: Intertown + time_points: [ civic_platform_6, 3042, 4531, 4929, civic_platform_1, civic_platform_5 ] + between_stops: [ ] + stop_times: [ + [ 632a, 642a, 657a, 708a, 715a, 727a], + [ 702a, 712a, 727a, 738a, 745a, 757a], + [ -, -, 755a, 806a, 813a, 825a], + [ 732a, 742a, 757a, 808a, 815a, 827a], + [ 802a, 812a, 827a, 838a, 845a, 857a], + [ 832a, 842a, 857a, 908a, 915a, 927a], + [ 902a, 912a, 927a, 938a, 945a, 957a], + [ 932a, 942a, 957a, 1008a, 1015a, 1027a], + [ 1002a, 1012a, 1027a, 1038a, 1045a, 1057a], + [ 1032a, 1042a, 1057a, 1108a, 1115a, 1127a], + [ 1102a, 1112a, 1127a, 1138a, 1145a, 1157a], + [ 1132a, 1142a, 1157a, 1208p, 1215p, 1227p], + [ 1202p, 1212p, 1227p, 1238p, 1245p, 1257p], + [ 1232p, 1242p, 1257p, 108p, 115p, 127p], + [ 102p, 112p, 127p, 138p, 145p, 157p], + [ 132p, 142p, 157p, 208p, 215p, 227p], + [ 202p, 212p, 227p, 238p, 245p, 257p], + [ 232p, 242p, 257p, 308p, 315p, 327p], + [ 302p, 312p, 327p, 338p, 345p, 357p], + [ -, -, 340p, 351p, 358p, 410p], + [ -, -, -, -, 407p, 419p], + [ 332p, 342p, 357p, 408p, 415p, 427p], + [ -, -, -, -, 428p, 440p], + [ 359p, 409p, 424p, 435p, 442p, 454p], + [ 432p, 442p, 457p, 508p, 515p, 527p], + [ 502p, 512p, 527p, 538p, 545p, 557p], + [ 532p, 542p, 557p, 608p, 615p, 627p], + [ 602p, 612p, 627p, 638p, 645p, 657p], + [ 632p, 642p, 657p, 708p, 715p, 727p], + [ 702p, 712p, 727p, 738p, 745p, 757p], + [ 732p, 742p, 757p, 808p, 815p, 827p], + [ 832p, 842p, 857p, 908p, 915p, 927p], + [ 932p, 942p, 957p, 1008p, 1015p, 1027p], + [ 1032p, 1042p, 1057p, -, -, -], + [ 1132p, 1142p, 1157p, -, -, -], + [ 1235x, 1245x, 100x, -, -, -] + ] + stop_times_saturday: [ + [ 646a, 655a, 710a, 721a, 728a, 740a], + [ 746a, 755a, 810a, 821a, 828a, 840a], + [ 846a, 855a, 910a, 921a, 928a, 940a], + [ 946a, 955a, 1010a, 1021a, 1028a, 1040a], + [ 1046a, 1055a, 1110a, 1121a, 1128a, 1140a], + [ 1146a, 1155a, 1210p, 1221p, 1228p, 1240p], + [ 1246p, 1255p, 110p, 121p, 128p, 140p], + [ 146p, 155p, 210p, 221p, 228p, 240p], + [ 246p, 255p, 310p, 321p, 328p, 340p], + [ 346p, 355p, 410p, 421p, 428p, 440p], + [ 446p, 455p, 510p, 521p, 528p, 540p], + [ 546p, 555p, 610p, 621p, 628p, 640p], + [ 646p, 655p, 710p, 721p, 728p, 740p], + [ 746p, 755p, 810p, 821p, 828p, 840p], + [ 846p, 855p, 910p, 921p, 928p, 940p], + [ 946p, 955p, 1010p, 1021p, 1028p, 1040p], + [ 1046p, 1055p, 1107p, -, -, -] + ] + stop_times_sunday: [ + [ 728a, 736a, 750a, 801a, 808a, 817a], + [ 828a, 836a, 850a, 901a, 908a, 917a], + [ 928a, 936a, 950a, 1001a, 1008a, 1017a], + [ 1028a, 1036a, 1050a, 1101a, 1108a, 1117a], + [ 1128a, 1136a, 1150a, 1201p, 1208p, 1217p], + [ 1228p, 1236p, 1250p, 101p, 108p, 117p], + [ 128p, 136p, 150p, 201p, 208p, 217p], + [ 228p, 236p, 250p, 301p, 308p, 317p], + [ 328p, 336p, 350p, 401p, 408p, 417p], + [ 428p, 436p, 450p, 501p, 508p, 517p], + [ 528p, 536p, 550p, 601p, 608p, 617p], + [ 628p, 636p, 650p, 701p, 708p, 717p], + [ 728p, 736p, 750p, 801p, 808p, 817p], + [ 828p, 836p, 850p, 901p, 908p, 917p], + [ 928p, 936p, 950p, 1001p, 1008p, 1017p], + [ 1028p, 1036p, 1050p, 1101p, 1108p, 1117p] + ] + --- /dev/null +++ b/maxious-canberra-transit-feed/cbrtable.yml.in @@ -1,1 +1,20 @@ +options: + start_date: 20090525 + end_date: 20100601 + remove_date: 2010601 + agency_name: ACT Internal Omnibus Network (ACTION) + agency_url: http://www.action.act.gov.au/ + agency_timezone: Australia/Canberra +stops: + - { name: Civic Interchange Platform 1,stop_code: civic_platform_1, lat: -35.2794347, lng: 149.130588} + - { name: Civic Interchange Platform 5,stop_code: civic_platform_5, lat: -35.2786, lng: 149.13033} + - { name: Civic Interchange Platform 6,stop_code: civic_platform_6, lat: -35.27851, lng: 149.12979 } + - { name: Canberra House Northbound, stop_code: 3042, lat: -35.27833, +lng: 149.12712 } + - { name: Canberra House Southbound, stop_code: 4531, +lat: -35.2786, lng: 149.13033 } + - { name: Marcus Clarke Street - Unilodge ANU, stop_code: 4929, lat: -35.2764151, lng: 149.1267199 } + +routes: + --- /dev/null +++ b/maxious-canberra-transit-feed/cbrtable.yml.in.in @@ -1,1 +1,20 @@ +options: + start_date: 20090525 + end_date: 20100601 + remove_date: 2010601 + agency_name: ACT Internal Omnibus Network (ACTION) + agency_url: http://www.action.act.gov.au/ + agency_timezone: Australia/Canberra +stops: + - { name: Civic Interchange Platform 1,stop_code: civic_platform_1, lat: -35.2794347, lng: 149.130588} + - { name: Civic Interchange Platform 5,stop_code: civic_platform_5, lat: -35.2786, lng: 149.13033} + - { name: Civic Interchange Platform 6,stop_code: civic_platform_6, lat: -35.27851, lng: 149.12979 } + - { name: Canberra House Northbound, stop_code: 3042, lat: -35.27833, +lng: 149.12712 } + - { name: Canberra House Southbound, stop_code: 4531, +lat: -35.2786, lng: 149.13033 } + - { name: Marcus Clarke Street - Unilodge ANU, stop_code: 4929, lat: -35.2764151, lng: 149.1267199 } + +routes: + --- /dev/null +++ b/maxious-canberra-transit-feed/createfeed.py @@ -1,1 +1,194 @@ +#!/usr/bin/python +# Copyright (C) 2007 Google Inc. +# Copyright (C) 2008-2009 William Lachance +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import transitfeed +from transitfeed import ServicePeriod +from optparse import OptionParser +import yaml, sys, os.path +import re + +stops = {} + +def ProcessOptions(schedule, options): + + # the follow features are REQUIRED + agency_name = options.get('agency_name') + agency_url = options.get('agency_url') + agency_timezone = options.get('agency_timezone') + + service_periods = [] + + service_periods.append(ServicePeriod(id="weekday")) + service_periods[0].SetWeekdayService() + service_periods.append(ServicePeriod(id="saturday")) + service_periods[1].SetDayOfWeekHasService(5) + service_periods.append(ServicePeriod(id="sunday")) + service_periods[2].SetDayOfWeekHasService(6) + + # the service period options are, well, optional + for service_period in service_periods: + if options.get('start_date'): + service_period.SetStartDate(options['start_date']) + if options.get('end_date'): + service_period.SetEndDate(options['end_date']) + if options.get('add_date'): + service_period.SetDateHasService(options['add_date']) + if options.get('remove_date'): + service_period.SetDateHasService(options['remove_date'], + has_service=False) + + # Add all service period objects to the schedule + schedule.SetDefaultServicePeriod(service_periods[0], validate=False) + schedule.AddServicePeriodObject(service_periods[1], validate=False) + schedule.AddServicePeriodObject(service_periods[2], validate=False) + + if not (agency_name and agency_url and agency_timezone): + print "You must provide agency information" + + schedule.NewDefaultAgency(agency_name=agency_name, agency_url=agency_url, + agency_timezone=agency_timezone) + + +# Remove any stops from stopsdata that aren't serviced by any routes in +# routedata. +def PruneStops(stopsdata, routedata): + stopset = set() + for route in routedata: + stopset.update(route['time_points']) + for between_list in route['between_stops']: + stopset.update(route['between_stops'][between_list]) + + toprune = list() + for i, stop in enumerate(stopsdata): + if stop['stop_code'] not in stopset: + print "Pruning unused stop %s " % stop['stop_code'] + toprune.append(i) + + # Prune the list in reverse order, as the indices will change otherwise. + toprune.sort() + toprune.reverse() + for prunee in toprune: + del stopsdata[prunee] + +def AddStops(schedule, stopsdata): + for stopdata in stopsdata: + stop_code = stopdata['stop_code'] + # we have to manually add the stop instead of using AddStop, cause + # we want the stop_code + stop_id = unicode(len(schedule.stops)) + stop = transitfeed.Stop(stop_id=stop_id, lat=stopdata['lat'], + lng=stopdata['lng'], name=stopdata['name'], + stop_code=stop_code) + schedule.AddStopObject(stop) + stops[stop_code] = stop + + +def AddTripsToSchedule(schedule, route, routedata, service_id, stop_times): + + service_period = schedule.GetServicePeriod(service_id) + timerex = re.compile('^(\d+)(\d\d)([a-z])$') + + for trip in stop_times: + t = route.AddTrip(schedule, headsign=routedata['long_name'], service_period=service_period) + + if len(trip) > len(routedata['time_points']): + print "Length of trip (%s) exceeds number of time points (%s)!" % (len(trip), len(routedata['time_points'])) + class StopTimesError(Exception): pass + raise StopTimesError() + else: + trip_stops = [] # Build a list of (time, stop_code) tuples + i = 0 + for stop_time in trip: + matches = timerex.match(str(stop_time)) + if matches and len(matches.groups()) == 3: + hour, minute, shift = (int(matches.group(1)), + str(matches.group(2)), + matches.group(3)) + if shift == 'p' and hour < 12: + hour += 12 + elif shift == 'x': + if hour == 12: + hour += 12 + else: + hour += 24 + + # munge hours and minutes if they're < 10 + if hour < 10: + hour = "0" + str(hour) + + clock_time = str(hour) + ":" + minute + ":00" + seconds = transitfeed.TimeToSecondsSinceMidnight(clock_time) + trip_stops.append((seconds, routedata['time_points'][i]) ) + elif re.search(r'^\-$', str(stop_time)): + pass + else: + class InvalidStopTimeError(Exception): pass + raise InvalidStopTimeError, 'Bad stoptime "%s"' % stop_time + i = i + 1 + + trip_stops.sort() # Sort by time + prev_stop_code = None + between_stops = routedata.get('between_stops') + + for (time, stop_code) in trip_stops: + if prev_stop_code and between_stops: + between_stop_list = between_stops.get('%s-%s' % (prev_stop_code, stop_code)) + if between_stop_list: + for between_stop_code in between_stop_list: + t.AddStopTime(stop=stops[between_stop_code]) + + t.AddStopTime(stop=stops[stop_code], arrival_secs=time, + departure_secs=time) + prev_stop_code = stop_code + + + +def AddRouteToSchedule(schedule, routedata): + r = schedule.AddRoute(short_name=str(routedata['short_name']), + long_name=routedata['long_name'], + route_type='Bus') + AddTripsToSchedule(schedule, r, routedata, "weekday", routedata['stop_times']) + if routedata.get('stop_times_saturday'): + AddTripsToSchedule(schedule, r, routedata, "saturday", routedata['stop_times_saturday']) + if routedata.get('stop_times_sunday'): + AddTripsToSchedule(schedule, r, routedata, "sunday", routedata['stop_times_sunday']) + +def main(): + parser = OptionParser() + parser.add_option('--input', dest='input', + help='Path of input file') + parser.add_option('--output', dest='output', + help='Path of output file, should end in .zip') + parser.set_defaults(output='feed.zip') + (options, args) = parser.parse_args() + + schedule = transitfeed.Schedule() + stream = open(options.input, 'r') + data = yaml.load(stream) + ProcessOptions(schedule, data['options']) + PruneStops(data['stops'], data['routes']) + AddStops(schedule, data['stops']) + + for route in data['routes']: + AddRouteToSchedule(schedule, route) + + schedule.WriteGoogleTransitFeed(options.output) + + +if __name__ == '__main__': + main() + --- /dev/null +++ b/maxious-canberra-transit-feed/extracttimes.rb @@ -1,1 +1,95 @@ +require 'rubygems' +require 'nokogiri' +require 'open-uri' +require 'pp' +def makeTimetable(table, period, short_name) + timetable = {"stop_times" => [], "between_stops" => [], "short_name" => short_name} + time_points = table.xpath('tr[1]//th').map do |tp| + if tp.content != "\302\240" && tp.content != "" && tp.content != "
" + timing_point = tp.content.squeeze(" ").gsub("\r\n Platform"," - Platform").strip + end + end + time_points.delete(nil) + timetable["time_points"] = time_points + timetable["long_name"] = "To " + time_points.last + periodtimes = [] + table.css('tr').each do |row| + times = row.css('td').map do |cell| + #TODO convert to GTFS time + time = cell.content.squeeze(" ").strip + end + if not times.empty? + if not (route = times.shift) + raise("TODO: account for shifting route numbers eg. intertown/redex 62/162") + end + periodtimes << times + end + end + if periodtimes.size < 1 + raise "No times for route " + short_name + " in period " + period + end + timetable["stop_times"] = { period => periodtimes } + # pp timetable + filename = timetable["short_name"] + "-" + timetable["long_name"].downcase.gsub(" ","-").gsub("/","") + "." + period + ".yml" + puts "Saving " + filename + File.open("#{File.dirname(__FILE__)}/output/"+filename, "w") do |f| + f.write timetable.to_yaml + end + timetable +end + +#TODO fix route 934 +Dir.glob("source-html/Route*.htm*") { |file| + puts "Opened " + file + doc = Nokogiri::HTML(open(file)) + # Search for nodes by css + timetables = [] + short_name = ""; + doc.xpath('//title').each do |title| + short_name = title.content.gsub("Route_","").gsub("Route ","").squeeze(" ").strip + end + if short_name == "" + raise "Route number(s) not found in tag" + end + + doc.xpath('//table[preceding::text()="Weekdays"]').each do |table| + timetables << makeTimetable(table, "weekday", short_name) + end + + #weekends + doc.xpath('//table[preceding::text()="Saturdays" and following::a]').each do |table| + timetables << makeTimetable(table, "saturday", short_name) + end + doc.xpath('//table[preceding::text()="Sundays"]').each do |table| + timetables << makeTimetable(table, "sunday", short_name) + end + #930/934 special cases + doc.xpath('//table[preceding::text()="Saturday" and following::h2]').each do |table| + timetables << makeTimetable(table, "saturday", short_name) + end + doc.xpath('//table[preceding::text()="Sunday"]').each do |table| + timetables << makeTimetable(table, "sunday", short_name) + end + #route 81 = Weekdays - School Holidays Only + doc.xpath('//table[preceding::text()="Weekdays - School Holidays Only "]').each do |table| + timetable = makeTimetable(table, "weekday", short_name) + #TODO set active date range to only be holidays + timetables << timetable; + end + + + if timetables.size > 2 + puts "WARNING: " + file + " more than 2 timetables (weekend split?):" + timetables.size.to_s + end + if timetables.size < 2 + puts "WARNING: " + file + " less than 2 timetables (weekday loop service?):" + timetables.size.to_s + elsif not (timetables[0]["time_points"] - timetables[1]["time_points"].reverse).empty? + puts "WARNING: first pair of timetable timing points are not complementary for "+ file + pp(timetables[0]["time_points"] - timetables[1]["time_points"].reverse) + end + if timetables.size < 1 + raise "No timetables extracted from " + file + end +} + --- /dev/null +++ b/maxious-canberra-transit-feed/indent-route.pl @@ -1,1 +1,14 @@ +#!/usr/bin/perl +use strict; + +my $first = 1; +while (<STDIN>) { + if ($first) { + $first = 0; + print " - $_"; + } else { + print " $_"; + } +} + --- /dev/null +++ b/maxious-canberra-transit-feed/parse-times.pl @@ -1,1 +1,35 @@ +#!/usr/bin/perl +use strict; + +my $first = 1; +my $prev_comment = 0; +while (<STDIN>) { + if ($_ !~ /^\#/) { + if (!$first && !$prev_comment) { + print ",\n"; + } else { + $first = 0; + $prev_comment = 0; + } + chomp; + my @times = split /\ +/; + print " [ "; + my $first = 1; + foreach (@times) { + if (!$first) { + print ", "; + } else { + $first = 0; + } + print $_; + } + print "]"; + } else { + # yes, this conditional is loaded with assumptions... + print ",\n" . $_; + $prev_comment = 1; + } +} + + --- /dev/null +++ b/maxious-canberra-transit-feed/source-html/Intertown_300.html @@ -1,1 +1,3473 @@ +<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> +<html xmlns="http://www.w3.org/1999/xhtml"><!-- InstanceBegin template="/Templates/timetable08.dwt" codeOutsideHTMLIsLocked="false" --> +<head> +<meta http-equiv="Content-Type" content="text/html; charset=iso-8859-1" /> +<link rel="stylesheet" type="text/css" href="/New_ACTION/CSS/print_timetable.css" media="print" /> +<link rel="stylesheet" type="text/css" href="/New_ACTION/CSS/timetable.css" media="screen" /> + +<!-- InstanceBeginEditable name="doctitle" --> +<title>Intertown 300 + + + + + + +
+

Chosen services: 300, 312, 313, 314, 315, 318, 319

+ +

View timetable and map

+

This timetable is effective from Monday 25 May 2009.

+ + +

Weekdays

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
 Tuggeranong Interchange
+ Platform 8
Woden Interchange
+ Platform 9
City Interchange
+ Platform 5
Cameron Ave Bus StationLathlain St Bus Station Cohen St Bus Station
300 wheelchair access5:30 AM5:48 AM6:04 AM6:21 AM6:23 AM6:27 AM
318 wheelchair access6:08 AM6:26 AM6:42 AM6:59 AM7:01 AM7:05 AM
319 wheelchair access6:25 AM6:43 AM6:59 AM7:16 AM7:18 AM7:22 AM
3186:38 AM6:56 AM7:12 AM7:29 AM7:31 AM7:35 AM
300 wheelchair access6:42 AM7:00 AM7:16 AM7:33 AM7:35 AM7:39 AM
111.....7:13 AM7:29 AM...............
319 wheelchair access6:55 AM7:13 AM7:29 AM7:47 AM7:49 AM7:53 AM
318 wheelchair access6:58 AM7:16 AM7:32 AM7:50 AM7:52 AM7:56 AM
314 wheelchair access7:06 AM7:24 AM7:41 AM7:59 AM8:01 AM8:05 AM
313 wheelchair access7:11 AM7:29 AM7:47 AM8:05 AM8:07 AM8:11 AM
111.....7:33 AM7:51 AM...............
319 wheelchair access7:15 AM7:33 AM7:51 AM8:09 AM8:11 AM8:15 AM
160.....7:34 AM7:49 AM...............
318 wheelchair access7:20 AM7:38 AM7:56 AM8:14 AM8:16 AM8:20 AM
162.....7:40 AM7:55 AM...............
312 wheelchair access7:26 AM7:45 AM8:03 AM8:21 AM8:23 AM8:27 AM
170.....7:49 AM8:04 AM...............
315 wheelchair access7:31 AM7:50 AM8:08 AM8:26 AM8:28 AM8:32 AM
111.....7:54 AM8:12 AM...............
319 wheelchair access7:35 AM7:54 AM8:12 AM8:30 AM8:32 AM8:36 AM
318 wheelchair access7:40 AM7:59 AM8:17 AM8:35 AM8:37 AM8:41 AM
161.....8:05 AM8:19 AM...............
314 wheelchair access7:46 AM8:05 AM8:23 AM8:41 AM8:43 AM8:47 AM
162.....8:07 AM8:22 AM...............
170.....8:07 AM8:22 AM...............
300 wheelchair access.....8:07 AM8:25 AM8:43 AM8:45 AM8:49 AM
313 wheelchair access7:51 AM8:10 AM8:28 AM8:46 AM8:48 AM8:52 AM
160.....8:11 AM8:26 AM...............
319 wheelchair access7:55 AM8:14 AM8:32 AM8:50 AM8:52 AM8:56 AM
111.....8:15 AM8:33 AM...............
318 wheelchair access8:00 AM8:19 AM8:37 AM8:55 AM8:57 AM9:01 AM
314 wheelchair access8:06 AM8:25 AM8:43 AM9:01 AM9:03 AM9:07 AM
300.....8:27 AM8:45 AM9:03 AM9:05 AM9:09 AM
313 wheelchair access8:11 AM8:30 AM8:48 AM9:06 AM9:08 AM9:12 AM
319 wheelchair access8:15 AM8:34 AM8:52 AM9:10 AM9:12 AM9:16 AM
111.....8:36 AM8:54 AM...............
318 wheelchair access8:20 AM8:39 AM8:57 AM9:15 AM9:17 AM9:21 AM
162.....8:40 AM8:55 AM...............
160.....8:41 AM8:56 AM...............
3128:26 AM8:45 AM9:03 AM9:21 AM9:23 AM9:27 AM
315 wheelchair access8:31 AM8:50 AM9:08 AM9:26 AM9:28 AM9:32 AM
319 wheelchair access8:34 AM8:53 AM9:11 AM9:29 AM9:31 AM9:35 AM
111.....8:56 AM9:14 AM...............
318 wheelchair access8:40 AM8:59 AM9:17 AM9:35 AM9:37 AM9:41 AM
314 wheelchair access8:43 AM9:02 AM9:20 AM9:38 AM9:40 AM9:44 AM
313 wheelchair access8:51 AM9:10 AM9:28 AM9:45 AM9:47 AM9:51 AM
319 wheelchair access8:55 AM9:14 AM9:32 AM9:49 AM9:51 AM9:55 AM
312 wheelchair access9:01 AM9:20 AM9:37 AM9:54 AM9:56 AM10:00 AM
318 wheelchair access9:05 AM9:24 AM9:41 AM9:58 AM10:00 AM10:04 AM
3159:11 AM9:30 AM9:46 AM10:03 AM10:05 AM10:09 AM
314 wheelchair access9:16 AM9:35 AM9:51 AM10:08 AM10:10 AM10:14 AM
3139:21 AM9:40 AM9:56 AM10:13 AM10:15 AM10:19 AM
319 wheelchair access9:25 AM9:43 AM9:59 AM10:16 AM10:18 AM10:22 AM
3129:31 AM9:49 AM10:05 AM10:22 AM10:24 AM10:28 AM
318 wheelchair access9:35 AM9:53 AM10:09 AM10:26 AM10:28 AM10:32 AM
315 wheelchair access9:41 AM9:59 AM10:15 AM10:32 AM10:34 AM10:38 AM
314 wheelchair access9:46 AM10:04 AM10:20 AM10:37 AM10:39 AM10:43 AM
3139:51 AM10:09 AM10:25 AM10:42 AM10:44 AM10:48 AM
319 wheelchair access9:55 AM10:13 AM10:29 AM10:46 AM10:48 AM10:52 AM
31210:01 AM10:19 AM10:35 AM10:52 AM10:54 AM10:58 AM
318 wheelchair access10:05 AM10:23 AM10:39 AM10:56 AM10:58 AM11:02 AM
31510:11 AM10:29 AM10:45 AM11:02 AM11:04 AM11:08 AM
314 wheelchair access10:16 AM10:34 AM10:50 AM11:07 AM11:09 AM11:13 AM
31310:21 AM10:39 AM10:55 AM11:12 AM11:14 AM11:18 AM
319 wheelchair access10:25 AM10:43 AM10:59 AM11:16 AM11:18 AM11:22 AM
312 wheelchair access10:31 AM10:49 AM11:05 AM11:22 AM11:24 AM11:28 AM
318 wheelchair access10:35 AM10:53 AM11:09 AM11:26 AM11:28 AM11:32 AM
315 wheelchair access10:41 AM10:59 AM11:15 AM11:32 AM11:34 AM11:38 AM
314 wheelchair access10:46 AM11:04 AM11:20 AM11:37 AM11:39 AM11:43 AM
313 wheelchair access10:51 AM11:09 AM11:25 AM11:42 AM11:44 AM11:48 AM
319 wheelchair access10:55 AM11:13 AM11:29 AM11:46 AM11:48 AM11:52 AM
31211:01 AM11:19 AM11:35 AM11:52 AM11:54 AM11:58 AM
318 wheelchair access11:05 AM11:23 AM11:39 AM11:56 AM11:58 AM12:02 PM
315 wheelchair access11:11 AM11:29 AM11:45 AM12:02 PM12:04 PM12:08 PM
31411:16 AM11:34 AM11:50 AM12:07 PM12:09 PM12:13 PM
313 wheelchair access11:21 AM11:39 AM11:55 AM12:12 PM12:14 PM12:18 PM
319 wheelchair access11:25 AM11:43 AM11:59 AM12:16 PM12:18 PM12:22 PM
31211:31 AM11:49 AM12:05 PM12:22 PM12:24 PM12:28 PM
318 wheelchair access11:35 AM11:53 AM12:09 PM12:26 PM12:28 PM12:32 PM
31511:41 AM11:59 AM12:15 PM12:32 PM12:34 PM12:38 PM
314 wheelchair access11:46 AM12:04 PM12:20 PM12:37 PM12:39 PM12:43 PM
313 wheelchair access11:51 AM12:09 PM12:25 PM12:42 PM12:44 PM12:48 PM
319 wheelchair access11:55 AM12:13 PM12:29 PM12:46 PM12:48 PM12:52 PM
312 wheelchair access12:01 PM12:19 PM12:35 PM12:52 PM12:54 PM12:58 PM
318 wheelchair access12:05 PM12:23 PM12:39 PM12:56 PM12:58 PM1:02 PM
315 wheelchair access12:11 PM12:29 PM12:45 PM1:02 PM1:04 PM1:08 PM
314 wheelchair access12:16 PM12:34 PM12:50 PM1:07 PM1:09 PM1:13 PM
31312:21 PM12:39 PM12:55 PM1:12 PM1:14 PM1:18 PM
319 wheelchair access12:25 PM12:43 PM12:59 PM1:16 PM1:18 PM1:22 PM
31212:31 PM12:49 PM1:05 PM1:22 PM1:24 PM1:28 PM
318 wheelchair access12:35 PM12:53 PM1:09 PM1:26 PM1:28 PM1:32 PM
31512:41 PM12:59 PM1:15 PM1:32 PM1:34 PM1:38 PM
314 wheelchair access12:46 PM1:04 PM1:20 PM1:37 PM1:39 PM1:43 PM
313 wheelchair access12:51 PM1:09 PM1:25 PM1:42 PM1:44 PM1:48 PM
319 wheelchair access12:55 PM1:13 PM1:29 PM1:46 PM1:48 PM1:52 PM
312 wheelchair access1:01 PM1:19 PM1:35 PM1:52 PM1:54 PM1:58 PM
318 wheelchair access1:05 PM1:23 PM1:39 PM1:56 PM1:58 PM2:02 PM
315 wheelchair access1:11 PM1:29 PM1:45 PM2:02 PM2:04 PM2:08 PM
314 wheelchair access1:16 PM1:34 PM1:50 PM2:07 PM2:09 PM2:13 PM
313 wheelchair access1:21 PM1:39 PM1:55 PM2:12 PM2:14 PM2:18 PM
319 wheelchair access1:25 PM1:43 PM1:59 PM2:16 PM2:18 PM2:22 PM
3121:31 PM1:49 PM2:05 PM2:22 PM2:24 PM2:28 PM
318 wheelchair access1:35 PM1:53 PM2:09 PM2:26 PM2:28 PM2:32 PM
3151:41 PM1:59 PM2:15 PM2:32 PM2:34 PM2:38 PM
314 wheelchair access1:46 PM2:04 PM2:20 PM2:37 PM2:39 PM2:43 PM
3131:51 PM2:09 PM2:25 PM2:42 PM2:44 PM2:48 PM
319 wheelchair access1:55 PM2:13 PM2:29 PM2:46 PM2:48 PM2:52 PM
312 wheelchair access2:01 PM2:19 PM2:35 PM2:52 PM2:54 PM2:58 PM
3182:05 PM2:23 PM2:39 PM2:56 PM2:58 PM3:02 PM
315 wheelchair access2:11 PM2:29 PM2:45 PM3:02 PM3:04 PM3:08 PM
3142:16 PM2:34 PM2:50 PM3:07 PM3:09 PM3:13 PM
3132:21 PM2:39 PM2:55 PM3:13 PM3:15 PM3:19 PM
319 wheelchair access2:25 PM2:43 PM2:59 PM3:17 PM3:19 PM3:23 PM
3122:31 PM2:49 PM3:05 PM3:23 PM3:25 PM3:29 PM
318 wheelchair access2:35 PM2:53 PM3:10 PM3:28 PM3:30 PM3:34 PM
315 wheelchair access2:41 PM2:59 PM3:17 PM3:35 PM3:37 PM3:41 PM
314 wheelchair access2:45 PM3:03 PM3:21 PM3:39 PM3:41 PM3:45 PM
313 wheelchair access2:50 PM3:08 PM3:26 PM3:44 PM3:46 PM3:50 PM
3192:54 PM3:13 PM3:31 PM3:49 PM3:51 PM3:55 PM
312 wheelchair access3:01 PM3:20 PM3:38 PM3:56 PM3:58 PM4:02 PM
318 wheelchair access3:04 PM3:23 PM3:41 PM3:59 PM4:01 PM4:05 PM
315 wheelchair access3:11 PM3:30 PM3:48 PM4:06 PM4:08 PM4:12 PM
313 wheelchair access3:16 PM3:35 PM3:53 PM4:11 PM4:13 PM4:17 PM
314 wheelchair access3:21 PM3:40 PM3:58 PM4:16 PM4:18 PM4:22 PM
319 wheelchair access3:24 PM3:43 PM4:01 PM4:19 PM4:21 PM4:25 PM
312 wheelchair access3:31 PM3:50 PM4:08 PM4:26 PM4:28 PM4:32 PM
3183:35 PM3:54 PM4:12 PM4:30 PM4:32 PM4:36 PM
300 wheelchair access..........4:15 PM4:33 PM4:35 PM4:39 PM
315 wheelchair access3:41 PM4:00 PM4:18 PM4:36 PM4:38 PM4:42 PM
3133:46 PM4:05 PM4:23 PM4:41 PM4:43 PM4:47 PM
314 wheelchair access3:51 PM4:10 PM4:28 PM4:46 PM4:48 PM4:52 PM
3123:56 PM4:15 PM4:33 PM4:51 PM4:53 PM4:57 PM
318 wheelchair access4:00 PM4:19 PM4:37 PM4:55 PM4:57 PM5:01 PM
313 wheelchair access4:06 PM4:25 PM4:43 PM5:01 PM5:03 PM5:07 PM
315 wheelchair access4:11 PM4:30 PM4:48 PM5:06 PM5:08 PM5:12 PM
312 wheelchair access4:16 PM4:35 PM4:53 PM5:11 PM5:13 PM5:17 PM
314 wheelchair access4:21 PM4:40 PM4:58 PM5:16 PM5:18 PM5:22 PM
313 wheelchair access4:26 PM4:45 PM5:03 PM5:21 PM5:23 PM5:27 PM
3184:29 PM4:48 PM5:06 PM5:24 PM5:26 PM5:30 PM
300 wheelchair access..........5:10 PM5:28 PM5:30 PM5:34 PM
3124:36 PM4:55 PM5:13 PM5:31 PM5:33 PM5:37 PM
315 wheelchair access4:41 PM5:00 PM5:18 PM5:36 PM5:38 PM5:42 PM
3134:46 PM5:05 PM5:23 PM5:41 PM5:43 PM5:47 PM
314 wheelchair access4:51 PM5:10 PM5:28 PM5:46 PM5:48 PM5:52 PM
300 wheelchair access..........5:30 PM5:48 PM5:50 PM5:54 PM
312 wheelchair access4:56 PM5:15 PM5:33 PM5:51 PM5:53 PM5:57 PM
315 wheelchair access5:01 PM5:20 PM5:38 PM5:56 PM5:58 PM6:02 PM
318 wheelchair access5:04 PM5:23 PM5:41 PM5:59 PM6:01 PM6:05 PM
314 wheelchair access5:11 PM5:30 PM5:48 PM6:06 PM6:08 PM6:12 PM
3125:16 PM5:35 PM5:53 PM6:11 PM6:13 PM6:17 PM
315 wheelchair access5:21 PM5:40 PM5:58 PM6:16 PM6:18 PM6:22 PM
313 wheelchair access5:26 PM5:45 PM6:03 PM6:21 PM6:23 PM6:27 PM
3145:31 PM5:50 PM6:08 PM6:26 PM6:28 PM6:32 PM
312 wheelchair access5:36 PM5:55 PM6:13 PM6:31 PM6:33 PM6:36 PM
300 wheelchair access5:40 PM5:59 PM6:17 PM6:35 PM6:37 PM6:40 PM
3005:45 PM6:04 PM6:22 PM6:39 PM6:41 PM6:44 PM
314 wheelchair access5:51 PM6:10 PM6:28 PM6:45 PM6:47 PM6:50 PM
3135:56 PM6:15 PM6:32 PM6:49 PM6:51 PM6:54 PM
315 wheelchair access6:01 PM6:20 PM6:36 PM6:53 PM6:55 PM6:58 PM
300wheelchair access6:06 PM6:25 PM6:41 PM6:58 PM7:00 PM7:03 PM
300 wheelchair access6:11 PM6:30 PM6:45 PM7:02 PM7:04 PM7:07 PM
300 wheelchair access6:16 PM6:34 PM6:49 PM7:06 PM7:08 PM7:11 PM
314 wheelchair access6:21 PM6:39 PM6:54 PM7:11 PM7:13 PM7:16 PM
300 wheelchair access6:26 PM6:43 PM6:58 PM7:15 PM7:17 PM7:20 PM
300wheelchair access6:31 PM6:48 PM7:03 PM7:20 PM7:22 PM7:25 PM
312 wheelchair access6:36 PM6:53 PM7:08 PM7:25 PM7:27 PM7:30 PM
318 wheelchair access6:42 PM6:59 PM7:14 PM7:31 PM7:33 PM7:36 PM
300wheelchair access6:45 PM7:02 PM7:17 PM7:34 PM7:36 PM7:39 PM
300wheelchair access6:50 PM7:07 PM7:22 PM7:39 PM7:41 PM7:44 PM
313 wheelchair access6:56 PM7:13 PM7:28 PM7:45 PM7:47 PM7:50 PM
300 wheelchair access7:07 PM7:24 PM7:39 PM7:56 PM7:58 PM8:01 PM
300 wheelchair access7:22 PM7:39 PM7:54 PM8:11 PM8:13 PM8:16 PM
300 wheelchair access7:37 PM7:54 PM8:09 PM8:26 PM8:28 PM8:31 PM
300 wheelchair access7:52 PM8:09 PM8:24 PM8:41 PM8:43 PM8:46 PM
300 wheelchair access8:07 PM8:24 PM8:39 PM8:56 PM8:58 PM9:01 PM
300 wheelchair access8:22 PM8:39 PM8:54 PM9:11 PM9:13 PM9:16 PM
300 wheelchair access8:37 PM8:54 PM9:09 PM9:26 PM9:28 PM9:31 PM
300wheelchair access8:52 PM9:09 PM9:24 PM9:41 PM9:43 PM9:46 PM
300 wheelchair access9:07 PM9:24 PM9:39 PM9:56 PM9:58 PM10:01 PM
300 wheelchair access9:22 PM9:39 PM9:54 PM10:11 PM10:13 PM10:16 PM
300 wheelchair access9:37 PM9:54 PM10:09 PM10:26 PM10:28 PM10:31 PM
300 wheelchair access9:52 PM10:09 PM10:24 PM10:41 PM10:43 PM10:46 PM
300 wheelchair access10:07 PM10:24 PM10:39 PM10:56 PM10:58 PM11:01 PM
300 wheelchair access10:22 PM10:39 PM10:54 PM11:11 PM11:13 PM11:16 PM
300 wheelchair access10:37 PM10:54 PM11:09 PM11:26 PM11:28 PM11:31 PM
300 wheelchair access10:52 PM11:09 PM11:24 PM11:41 PM11:43 PM11:46 PM
300 wheelchair access11:07 PM11:24 PM11:39 PM11:56 PM11:58 PM12:01 AM
300 wheelchair access11:22 PM11:39 PM11:54 PM12:11 AM12:13 AM12:16 AM
+

 

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
 Cohen St Bus Station
+ Platform 3
Lathlain St Bus Station
+ Platform 1
Cameron Ave Bus Station
+ Platform 1
City Interchange
+ Platform 1
Woden Interchange
+ Platform 6
Tuggeranong Interchange
300wheelchair access6:03 AM6:05 AM6:09 AM6:26 AM6:43 AM6:56 AM
313 wheelchair access6:13 AM6:15 AM6:19 AM6:36 AM6:53 AM7:06 AM
3006:23 AM6:25 AM6:29 AM6:46 AM7:03 AM7:16 AM
300 wheelchair access6:33 AM6:35 AM6:39 AM6:56 AM7:13 AM7:26 AM
313 wheelchair access6:43 AM6:45 AM6:49 AM7:06 AM7:23 AM7:38 AM
3006:53 AM6:55 AM6:59 AM7:16 AM7:33 AM7:50 AM
315 wheelchair access6:58 AM7:00 AM7:04 AM7:21 AM7:38 AM7:55 AM
319 wheelchair access7:03 AM7:05 AM7:09 AM7:26 AM7:43 AM8:01 AM
314 wheelchair access7:08 AM7:10 AM7:14 AM7:31 AM7:48 AM8:05 AM
3137:13 AM7:15 AM7:19 AM7:37 AM7:54 AM8:11 AM
312 wheelchair access7:18 AM7:20 AM7:24 AM7:42 AM7:59 AM8:16 AM
3187:23 AM7:25 AM7:29 AM7:48 AM8:05 AM8:23 AM
3157:28 AM7:30 AM7:34 AM7:53 AM8:10 AM8:27 AM
319 wheelchair access7:33 AM7:35 AM7:39 AM7:58 AM8:15 AM.....
314 wheelchair access7:38 AM7:40 AM7:44 AM8:03 AM8:20 AM8:37 AM
3137:43 AM7:45 AM7:49 AM8:08 AM8:25 AM8:42 AM
3007:45 AM7:47 AM7:51 AM8:10 AM8:25 AM.....
312 wheelchair access7:48 AM7:50 AM7:54 AM8:13 AM8:30 AM8:47 AM
3187:53 AM7:55 AM7:59 AM8:18 AM8:35 AM.....
315 wheelchair access7:58 AM8:00 AM8:04 AM8:23 AM8:40 AM8:57 AM
3127:59 AM8:01 AM8:05 AM8:24 AM8:41 AM8:58 AM
314 wheelchair access8:03 AM8:05 AM8:09 AM8:28 AM8:45 AM9:02 AM
313 wheelchair access8:08 AM8:10 AM8:14 AM8:33 AM8:50 AM9:07 AM
3008:10 AM8:12 AM8:16 AM8:35 AM8:50 AM.....
312 wheelchair access8:13 AM8:15 AM8:19 AM8:38 AM8:55 AM9:12 AM
3158:18 AM8:20 AM8:24 AM8:43 AM9:00 AM9:17 AM
314 wheelchair access8:23 AM8:25 AM8:29 AM8:48 AM9:05 AM9:22 AM
3138:28 AM8:30 AM8:34 AM8:53 AM9:10 AM9:27 AM
3128:33 AM8:35 AM8:39 AM8:58 AM9:15 AM9:32 AM
3158:37 AM8:39 AM8:43 AM9:00 AM..........
315 wheelchair access8:38 AM8:40 AM8:44 AM9:03 AM9:20 AM9:36 AM
314 wheelchair access8:43 AM8:45 AM8:49 AM9:08 AM9:25 AM9:41 AM
313 wheelchair access8:48 AM8:50 AM8:54 AM9:13 AM9:30 AM9:45 AM
3128:53 AM8:55 AM8:59 AM9:18 AM9:35 AM9:50 AM
3158:58 AM9:00 AM9:04 AM9:23 AM9:40 AM9:55 AM
3199:03 AM9:05 AM9:09 AM9:28 AM9:45 AM.....
314 wheelchair access9:08 AM9:10 AM9:14 AM9:33 AM9:50 AM10:05 AM
313 wheelchair access9:13 AM9:15 AM9:19 AM9:37 AM9:54 AM10:09 AM
312 wheelchair access9:18 AM9:20 AM9:24 AM9:42 AM9:59 AM10:14 AM
3189:23 AM9:25 AM9:29 AM9:46 AM10:03 AM.....
315 wheelchair access9:28 AM9:30 AM9:34 AM9:51 AM10:08 AM10:23 AM
3199:33 AM9:35 AM9:39 AM9:56 AM10:13 AM.....
314 wheelchair access9:38 AM9:40 AM9:44 AM10:01 AM10:18 AM10:33 AM
313 wheelchair access9:43 AM9:45 AM9:49 AM10:06 AM10:23 AM10:38 AM
312 wheelchair access9:48 AM9:50 AM9:54 AM10:11 AM10:28 AM10:43 AM
3189:53 AM9:55 AM9:59 AM10:16 AM10:33 AM.....
315 wheelchair access9:58 AM10:00 AM10:04 AM10:21 AM10:38 AM10:53 AM
31910:03 AM10:05 AM10:09 AM10:26 AM10:43 AM.....
314 wheelchair access10:08 AM10:10 AM10:14 AM10:31 AM10:48 AM11:03 AM
313 wheelchair access10:13 AM10:15 AM10:19 AM10:36 AM10:53 AM11:08 AM
312 wheelchair access10:18 AM10:20 AM10:24 AM10:41 AM10:58 AM11:13 AM
31810:23 AM10:25 AM10:29 AM10:46 AM11:03 AM.....
31510:28 AM10:30 AM10:34 AM10:51 AM11:08 AM11:23 AM
319 wheelchair access10:33 AM10:35 AM10:39 AM10:56 AM11:13 AM.....
314 wheelchair access10:38 AM10:40 AM10:44 AM11:01 AM11:18 AM11:33 AM
313 wheelchair access10:43 AM10:45 AM10:49 AM11:06 AM11:23 AM11:38 AM
312 wheelchair access10:48 AM10:50 AM10:54 AM11:11 AM11:28 AM11:43 AM
31810:53 AM10:55 AM10:59 AM11:16 AM11:33 AM.....
315 wheelchair access10:58 AM11:00 AM11:04 AM11:21 AM11:38 AM11:53 AM
319 wheelchair access11:03 AM11:05 AM11:09 AM11:26 AM11:43 AM.....
314 wheelchair access11:08 AM11:10 AM11:14 AM11:31 AM11:48 AM12:03 PM
31311:13 AM11:15 AM11:19 AM11:36 AM11:53 AM12:08 PM
31211:18 AM11:20 AM11:24 AM11:41 AM11:58 AM12:13 PM
31811:23 AM11:25 AM11:29 AM11:46 AM12:03 PM.....
31511:28 AM11:30 AM11:34 AM11:51 AM12:08 PM12:23 PM
319 wheelchair access11:33 AM11:35 AM11:39 AM11:56 AM12:13 PM12:29 PM
314 wheelchair access11:38 AM11:40 AM11:44 AM12:01 PM12:18 PM12:33 PM
313 wheelchair access11:43 AM11:45 AM11:49 AM12:06 PM12:23 PM12:38 PM
312 wheelchair access11:48 AM11:50 AM11:54 AM12:11 PM12:28 PM12:43 PM
31811:53 AM11:55 AM11:59 AM12:16 PM12:33 PM12:49 PM
315 wheelchair access11:58 AM12:00 PM12:04 PM12:21 PM12:38 PM12:53 PM
31912:03 PM12:05 PM12:09 PM12:26 PM12:43 PM12:59 PM
314 wheelchair access12:08 PM12:10 PM12:14 PM12:31 PM12:48 PM1:03 PM
313 wheelchair access12:13 PM12:15 PM12:19 PM12:36 PM12:53 PM1:08 PM
31212:18 PM12:20 PM12:24 PM12:41 PM12:58 PM1:13 PM
31812:23 PM12:25 PM12:29 PM12:46 PM1:03 PM1:19 PM
31512:28 PM12:30 PM12:34 PM12:51 PM1:08 PM1:23 PM
319 wheelchair access12:33 PM12:35 PM12:39 PM12:56 PM1:13 PM1:29 PM
314 wheelchair access12:38 PM12:40 PM12:44 PM1:01 PM1:18 PM1:33 PM
31312:43 PM12:45 PM12:49 PM1:06 PM1:23 PM1:38 PM
312 wheelchair access12:48 PM12:50 PM12:54 PM1:11 PM1:28 PM1:43 PM
31812:53 PM12:55 PM12:59 PM1:16 PM1:33 PM1:49 PM
315 wheelchair access12:58 PM1:00 PM1:04 PM1:21 PM1:38 PM1:53 PM
319 wheelchair access1:03 PM1:05 PM1:09 PM1:26 PM1:43 PM1:59 PM
314 wheelchair access1:08 PM1:10 PM1:14 PM1:31 PM1:48 PM2:03 PM
313 wheelchair access1:13 PM1:15 PM1:19 PM1:36 PM1:53 PM2:08 PM
312 wheelchair access1:18 PM1:20 PM1:24 PM1:41 PM1:58 PM2:13 PM
3181:23 PM1:25 PM1:29 PM1:46 PM2:03 PM2:19 PM
3151:28 PM1:30 PM1:34 PM1:51 PM2:08 PM2:23 PM
3191:33 PM1:35 PM1:39 PM1:56 PM2:13 PM2:29 PM
314 wheelchair access1:38 PM1:40 PM1:44 PM2:01 PM2:18 PM2:33 PM
313 wheelchair access1:43 PM1:45 PM1:49 PM2:06 PM2:23 PM2:38 PM
312 wheelchair access1:48 PM1:50 PM1:54 PM2:11 PM2:28 PM2:43 PM
3181:53 PM1:55 PM1:59 PM2:16 PM2:33 PM2:49 PM
315 wheelchair access1:58 PM2:00 PM2:04 PM2:21 PM2:38 PM2:53 PM
319 wheelchair access2:03 PM2:05 PM2:09 PM2:26 PM2:43 PM2:59 PM
314 wheelchair access2:08 PM2:10 PM2:14 PM2:31 PM2:48 PM3:04 PM
313 wheelchair access2:13 PM2:15 PM2:19 PM2:36 PM2:53 PM3:10 PM
312 wheelchair access2:18 PM2:20 PM2:24 PM2:41 PM2:58 PM3:16 PM
3182:23 PM2:25 PM2:29 PM2:46 PM3:03 PM3:23 PM
315 wheelchair access2:28 PM2:30 PM2:34 PM2:51 PM3:08 PM3:27 PM
3192:33 PM2:35 PM2:39 PM2:56 PM3:13 PM3:33 PM
3142:38 PM2:40 PM2:44 PM3:01 PM3:18 PM3:37 PM
3132:43 PM2:45 PM2:49 PM3:07 PM3:24 PM3:43 PM
312 wheelchair access2:48 PM2:50 PM2:54 PM3:12 PM3:29 PM3:48 PM
319....................3:30 PM3:50 PM
3182:53 PM2:55 PM2:59 PM3:18 PM3:35 PM3:55 PM
315 wheelchair access2:58 PM3:00 PM3:04 PM3:23 PM3:40 PM3:59 PM
319 wheelchair access3:03 PM3:05 PM3:09 PM3:28 PM3:45 PM4:05 PM
314 wheelchair access3:08 PM3:10 PM3:14 PM3:33 PM3:50 PM4:09 PM
313 wheelchair access3:13 PM3:15 PM3:19 PM3:38 PM3:55 PM4:14 PM
3123:18 PM3:20 PM3:24 PM3:43 PM4:00 PM4:19 PM
3183:23 PM3:25 PM3:29 PM3:48 PM4:05 PM4:25 PM
315 wheelchair access3:28 PM3:30 PM3:34 PM3:53 PM4:10 PM4:29 PM
319 wheelchair access3:33 PM3:35 PM3:39 PM3:58 PM4:15 PM4:35 PM
314 wheelchair access3:38 PM3:40 PM3:44 PM4:03 PM4:20 PM4:39 PM
313 wheelchair access3:43 PM3:45 PM3:49 PM4:08 PM4:25 PM4:44 PM
312 wheelchair access3:48 PM3:50 PM3:54 PM4:13 PM4:30 PM4:49 PM
3183:53 PM3:55 PM3:59 PM4:18 PM4:35 PM4:55 PM
315 wheelchair access3:58 PM4:00 PM4:04 PM4:23 PM4:40 PM4:59 PM
319 wheelchair access4:03 PM4:05 PM4:09 PM4:28 PM4:45 PM5:05 PM
312 wheelchair access4:08 PM4:10 PM4:14 PM4:33 PM4:50 PM5:09 PM
313 wheelchair access4:13 PM4:15 PM4:19 PM4:38 PM4:55 PM5:14 PM
3144:18 PM4:20 PM4:24 PM4:43 PM5:00 PM5:19 PM
300 wheelchair access...............4:45 PM5:02 PM5:21 PM
3184:23 PM4:25 PM4:29 PM4:48 PM5:05 PM5:25 PM
3154:28 PM4:30 PM4:34 PM4:53 PM5:10 PM5:29 PM
300 wheelchair access...............4:55 PM5:12 PM5:31 PM
111...............4:56 PM5:13 PM.....
319 wheelchair access4:33 PM4:35 PM4:39 PM4:58 PM5:15 PM5:35 PM
160...............5:01 PM5:17 PM.....
312 wheelchair access4:38 PM4:40 PM4:44 PM5:03 PM5:20 PM5:39 PM
170...............5:05 PM5:21 PM.....
300wheelchair access...............5:05 PM5:22 PM5:41 PM
3184:43 PM4:45 PM4:49 PM5:08 PM5:25 PM5:45 PM
314 wheelchair access4:48 PM4:50 PM4:54 PM5:13 PM5:30 PM5:49 PM
300 wheelchair access...............5:15 PM5:32 PM5:51 PM
162...............5:16 PM5:32 PM.....
111...............5:16 PM5:33 PM.....
3194:53 PM4:55 PM4:59 PM5:18 PM5:35 PM5:55 PM
312 wheelchair access4:58 PM5:00 PM5:04 PM5:23 PM5:40 PM5:59 PM
161...............5:26 PM5:42 PM.....
3185:03 PM5:05 PM5:09 PM5:28 PM5:45 PM6:05 PM
315 wheelchair access5:08 PM5:10 PM5:14 PM5:33 PM5:50 PM6:09 PM
111...............5:34 PM5:51 PM.....
160...............5:37 PM5:53 PM.....
319 wheelchair access5:13 PM5:15 PM5:19 PM5:38 PM5:55 PM6:15 PM
3185:18 PM5:20 PM5:24 PM5:43 PM6:00 PM6:20 PM
162...............5:46 PM6:02 PM.....
312 wheelchair access5:23 PM5:25 PM5:29 PM5:48 PM6:05 PM6:24 PM
300 wheelchair access5:28 PM5:30 PM5:34 PM5:53 PM6:10 PM6:29 PM
111...............5:56 PM6:13 PM.....
319 wheelchair access5:33 PM5:35 PM5:39 PM5:58 PM6:15 PM6:34 PM
160...............6:01 PM6:17 PM.....
3005:38 PM5:40 PM5:44 PM6:03 PM6:20 PM6:37 PM
313 wheelchair access5:43 PM5:45 PM5:49 PM6:08 PM6:25 PM6:41 PM
312 wheelchair access5:48 PM5:50 PM5:54 PM6:13 PM6:30 PM6:45 PM
162...............6:16 PM6:32 PM.....
111...............6:16 PM6:33 PM.....
3185:53 PM5:55 PM5:59 PM6:18 PM6:34 PM6:50 PM
315 wheelchair access5:58 PM6:00 PM6:04 PM6:23 PM6:38 PM6:53 PM
319 wheelchair access6:03 PM6:05 PM6:09 PM6:28 PM6:42 PM6:58 PM
314 wheelchair access6:08 PM6:10 PM6:14 PM6:32 PM6:46 PM7:01 PM
313 wheelchair access6:13 PM6:15 PM6:19 PM6:36 PM6:50 PM7:05 PM
312 wheelchair access6:18 PM6:20 PM6:24 PM6:41 PM6:55 PM7:10 PM
3186:23 PM6:25 PM6:29 PM6:45 PM6:59 PM7:15 PM
315 wheelchair access6:29 PM6:31 PM6:34 PM6:50 PM7:04 PM7:19 PM
319 wheelchair access6:34 PM6:36 PM6:39 PM6:55 PM7:09 PM7:25 PM
314 wheelchair access6:39 PM6:41 PM6:44 PM7:00 PM7:14 PM7:29 PM
313 wheelchair access6:44 PM6:46 PM6:49 PM7:05 PM7:19 PM7:34 PM
312 wheelchair access6:49 PM6:51 PM6:54 PM7:10 PM7:24 PM7:39 PM
3186:54 PM6:56 PM6:59 PM7:15 PM7:29 PM7:45 PM
300 wheelchair access6:56 PM6:58 PM7:01 PM7:17 PM7:31 PM7:46 PM
300 wheelchair access7:11 PM7:13 PM7:16 PM7:32 PM7:46 PM8:01 PM
300 wheelchair access7:26 PM7:28 PM7:31 PM7:47 PM8:01 PM8:16 PM
300 wheelchair access7:41 PM7:43 PM7:46 PM8:02 PM8:16 PM8:31 PM
300wheelchair access 7:56 PM7:58 PM8:01 PM8:17 PM8:31 PM8:46 PM
300 wheelchair access8:11 PM8:13 PM8:16 PM8:32 PM8:46 PM9:01 PM
300 wheelchair access8:26 PM8:28 PM8:31 PM8:47 PM9:01 PM9:16 PM
300 wheelchair access8:41 PM8:43 PM8:46 PM9:02 PM9:16 PM9:31 PM
300 wheelchair access8:56 PM8:58 PM9:01 PM9:17 PM9:31 PM9:46 PM
300 wheelchair access9:11 PM9:13 PM9:16 PM9:32 PM9:46 PM10:01 PM
300 wheelchair access9:26 PM9:28 PM9:31 PM9:47 PM10:01 PM10:16 PM
300 wheelchair access9:41 PM9:43 PM9:46 PM10:02 PM10:16 PM10:31 PM
300 wheelchair access9:56 PM9:58 PM10:01 PM10:17 PM10:31 PM10:46 PM
300 wheelchair access10:11 PM10:13 PM10:16 PM10:32 PM10:46 PM11:01 PM
300 wheelchair access10:26 PM10:28 PM10:31 PM10:47 PM11:01 PM11:16 PM
300 wheelchair access10:41 PM10:43 PM10:46 PM11:02 PM11:16 PM11:31 PM
300 wheelchair access10:56 PM10:58 PM11:01 PM11:17 PM11:31 PM11:46 PM
300 wheelchair access11:11 PM11:13 PM11:16 PM11:32 PM11:46 PM12:01 AM
+

wheelchair_icon- Wheelchair Accessible Bus

+
+ + + + --- /dev/null +++ b/maxious-canberra-transit-feed/source-html/Route_10.html @@ -1,1 +1,1638 @@ + + + + + + + + +Route 10 + + + + + + + + +
+

Chosen services: 10

+

View timetable and map

+ +This timetable is effective from Monday 25 May 2009. +

Weekdays

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
 Cohen St Station
+ Platform 2
Lathlain St Station
+ Platform 3
Cameron Ave Station
+ Platform 3
Jamison Centre CookArandaCaswell Drive City Interchange
+ Platform 7
War Memorial ADFACampbell Park OfficesMajura Business Park Brindabella Business ParkFairbairn Park
105:53 AM5:55 AM5:59 AM6:06 AM6:16 AM6:21 AM6:24 AM6:34 AM..............................
106:24 AM6:26 AM6:30 AM6:37 AM6:47 AM6:52 AM6:55 AM7:05 AM..............................
106:54 AM6:56 AM7:00 AM7:07 AM7:17 AM7:22 AM7:25 AM7:36 AM7:46 AM7:52 AM7:56 AM8:03 AM..........
10....................7:24 AM7:29 AM7:32 AM7:42 AM..............................
107:09 AM7:11 AM7:15 AM7:22 AM7:32 AM7:37 AM7:40 AM7:50 AM..............................
107:24 AM7:26 AM7:30 AM7:37 AM7:47 AM7:52 AM7:55 AM8:06 AM8:16 AM8:22 AM8:26 AM8:35 AM..........
10....................7:54 AM7:59 AM8:02 AM8:12 AM..............................
10wheelchair access7:39 AM7:41 AM7:45 AM7:52 AM8:02 AM8:07 AM8:10 AM8:20 AM..............................
107:54 AM7:56 AM8:00 AM8:07 AM8:17 AM8:22 AM8:25 AM8:36 AM8:46 AM8:52 AM8:56 AM...............
10...............8:12 AM8:22 AM8:27 AM8:30 AM8:40 AM..............................
108:09 AM8:11 AM8:15 AM8:22 AM8:32 AM8:37 AM8:40 AM8:51 AM9:01 AM9:07 AM9:11 AM9:18 AM9:27 AM9:35 AM
108:24 AM8:26 AM8:30 AM8:37 AM8:47 AM8:52 AM8:55 AM9:05 AM..............................
108:39 AM8:41 AM8:45 AM8:52 AM9:02 AM9:07 AM9:10 AM9:21 AM9:31 AM9:37 AM9:40 AM9:47 AM9:56 AM10:04 AM
10wheelchair access8:54 AM8:56 AM9:00 AM9:07 AM9:17 AM9:22 AM9:25 AM9:35 AM..............................
10wheelchair access...................................9:55 AM10:05 AM10:11 AM10:14 AM10:21 AM10:30 AM10:38 AM
109:25 AM9:27 AM9:31 AM9:38 AM9:48 AM9:53 AM9:55 AM10:05 AM..............................
10wheelchair access...................................10:25 AM10:35 AM10:41 AM10:44 AM10:51 AM11:00 AM11:08 AM
109:57 AM9:59 AM10:03 AM10:10 AM10:20 AM10:25 AM10:27 AM10:37 AM..............................
10...................................10:55 AM11:05 AM11:11 AM11:14 AM11:21 AM11:30 AM11:38 AM
1010:26 AM10:28 AM10:32 AM10:39 AM10:49 AM10:54 AM10:56 AM11:06 AM..............................
10...................................11:25 AM11:35 AM11:41 AM11:44 AM11:51 AM12:00 PM12:08 PM
1010:56 AM10:58 AM11:02 AM11:09 AM11:19 AM11:24 AM11:26 AM11:36 AM..............................
10...................................11:55 AM12:05 PM12:11 PM12:14 PM12:21 PM12:30 PM12:38 PM
1011:26 AM11:28 AM11:32 AM11:39 AM11:49 AM11:54 AM11:56 AM12:06 PM..............................
10...................................12:25 PM12:35 PM12:41 PM12:44 PM12:51 PM1:00 PM1:08 PM
1011:56 AM11:58 AM12:02 PM12:09 PM12:19 PM12:24 PM12:26 PM12:36 PM..............................
10wheelchair access...................................12:55 PM1:05 PM1:11 PM1:14 PM1:21 PM1:30 PM1:38 PM
1012:26 PM12:28 PM12:32 PM12:39 PM12:49 PM12:54 PM12:56 PM1:06 PM..............................
10wheelchair access...................................1:25 PM1:35 PM1:41 PM1:44 PM1:51 PM2:00 PM2:08 PM
1012:56 PM12:58 PM1:02 PM1:09 PM1:19 PM1:24 PM1:26 PM1:36 PM..............................
10...................................1:55 PM2:05 PM2:11 PM2:14 PM2:21 PM2:30 PM2:38 PM
101:26 PM1:28 PM1:32 PM1:39 PM1:49 PM1:54 PM1:56 PM2:06 PM..............................
10...................................2:25 PM2:35 PM2:41 PM2:44 PM2:51 PM3:00 PM3:08 PM
101:56 PM1:58 PM2:02 PM2:09 PM2:19 PM2:24 PM2:26 PM2:36 PM..............................
10...................................2:55 PM3:05 PM3:11 PM3:15 PM3:22 PM3:31 PM3:39 PM
102:26 PM2:28 PM2:32 PM2:39 PM2:49 PM2:54 PM2:56 PM3:06 PM..............................
10...................................3:29 PM3:39 PM3:45 PM3:49 PM3:56 PM4:05 PM4:13 PM
102:56 PM2:58 PM3:02 PM3:09 PM3:19 PM3:24 PM3:26 PM3:36 PM..............................
10...................................3:59 PM4:09 PM4:15 PM4:19 PM4:26 PM4:35 PM4:43 PM
103:26 PM3:28 PM3:32 PM3:39 PM3:49 PM3:54 PM3:56 PM4:06 PM..............................
103:41 PM3:43 PM3:47 PM3:54 PM4:04 PM4:09 PM4:11 PM4:21 PM..............................
10wheelchair access3:56 PM3:58 PM4:02 PM4:09 PM4:19 PM4:24 PM4:26 PM4:36 PM..............................
10...................................4:47 PM4:57 PM5:03 PM5:07 PM...............
104:11 PM4:13 PM4:17 PM4:24 PM4:34 PM4:39 PM4:41 PM4:51 PM..............................
10wheelchair access4:26 PM4:28 PM4:32 PM4:39 PM4:49 PM4:54 PM4:56 PM5:06 PM..............................
104:41 PM4:43 PM4:47 PM4:54 PM5:04 PM5:09 PM5:11 PM5:21 PM..............................
104:56 PM4:58 PM5:02 PM5:09 PM5:19 PM5:24 PM5:26 PM5:36 PM..............................
105:11 PM5:13 PM5:17 PM5:24 PM5:34 PM5:39 PM5:41 PM5:51 PM..............................
105:26 PM5:28 PM5:32 PM5:39 PM5:49 PM5:54 PM5:56 PM6:06 PM..............................
105:41 PM5:43 PM5:47 PM5:54 PM6:04 PM6:09 PM6:11 PM6:21 PM..............................
106:20 PM6:22 PM6:26 PM6:33 PM6:43 PM6:48 PM6:50 PM7:00 PM..............................
107:20 PM7:22 PM7:25 PM7:32 PM7:42 PM7:47 PM7:49 PM7:59 PM..............................
108:20 PM8:22 PM8:25 PM8:32 PM8:42 PM8:47 PM8:49 PM8:59 PM..............................
109:20 PM9:22 PM9:25 PM9:32 PM9:42 PM9:47 PM9:49 PM9:59 PM..............................
1010:20 PM10:22 PM10:25 PM10:32 PM10:42 PM10:47 PM10:49 PM10:59 PM..............................
1011:20 PM11:22 PM11:25 PM11:32 PM11:42 PM11:47 PM11:49 PM...................................
+

 

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
 Fairbairn ParkBrindabella Business ParkMajura Business Park Campbell Park OfficesADFAWar Memorial City Interchange
+ Platform 4
Caswell Drive ArandaCookJamison Centre Cameron Ave StationLathlain St StationCohen St Station
10..............................6:32 AM6:42 AM6:44 AM6:49 AM6:59 AM7:06 AM7:08 AM7:12 AM
10wheelchair access..............................7:02 AM7:12 AM7:14 AM7:19 AM7:29 AM7:36 AM7:38 AM7:42 AM
10..............................7:32 AM7:42 AM7:44 AM7:49 AM7:59 AM8:06 AM8:08 AM8:12 AM
10..............................8:02 AM8:12 AM8:14 AM8:19 AM8:29 AM8:36 AM8:38 AM8:42 AM
10...............8:00 AM8:03 AM8:08 AM8:20 AM8:30 AM8:32 AM8:37 AM8:47 AM8:54 AM8:56 AM9:00 AM
10...............8:30 AM8:33 AM8:38 AM8:50 AM9:00 AM9:02 AM9:07 AM9:17 AM9:24 AM9:26 AM9:30 AM
10...............9:00 AM9:03 AM9:08 AM9:20 AM9:30 AM9:32 AM9:37 AM9:47 AM9:54 AM9:56 AM10:00 AM
109:18 AM9:29 AM9:34 AM9:41 AM9:44 AM9:49 AM10:01 AM10:11 AM10:13 AM10:18 AM10:28 AM10:35 AM10:37 AM10:41 AM
109:48 AM9:59 AM10:04 AM10:11 AM10:14 AM10:19 AM10:31 AM10:41 AM10:43 AM10:48 AM10:58 AM11:05 AM11:07 AM11:11 AM
1010:18 AM10:29 AM10:34 AM10:41 AM10:44 AM10:49 AM11:01 AM11:11 AM11:13 AM11:18 AM11:28 AM11:35 AM11:37 AM11:41 AM
10wheelchair access10:48 AM10:59 AM11:04 AM11:11 AM11:14 AM11:19 AM11:31 AM11:41 AM11:43 AM11:48 AM11:58 AM12:05 PM12:07 PM12:11 PM
10wheelchair access11:18 AM11:29 AM11:34 AM11:41 AM11:44 AM11:49 AM12:01 PM12:11 PM12:13 PM12:18 PM12:28 PM12:35 PM12:37 PM12:41 PM
1011:48 AM11:59 AM12:04 PM12:11 PM12:14 PM12:19 PM12:31 PM12:41 PM12:43 PM12:48 PM12:58 PM1:05 PM1:07 PM1:11 PM
1012:18 PM12:29 PM12:34 PM12:41 PM12:44 PM12:49 PM1:01 PM1:11 PM1:13 PM1:18 PM1:28 PM1:35 PM1:37 PM1:41 PM
1012:48 PM12:59 PM1:04 PM1:11 PM1:14 PM1:19 PM1:31 PM1:41 PM1:43 PM1:48 PM1:58 PM2:05 PM2:07 PM2:11 PM
101:18 PM1:29 PM1:34 PM1:41 PM1:44 PM1:49 PM2:01 PM2:11 PM2:13 PM2:18 PM2:28 PM2:35 PM2:37 PM2:41 PM
101:48 PM1:59 PM2:04 PM2:11 PM2:14 PM2:19 PM2:31 PM2:41 PM2:43 PM2:48 PM2:58 PM3:05 PM3:07 PM3:11 PM
102:18 PM2:29 PM2:34 PM2:41 PM2:44 PM2:49 PM3:01 PM3:11 PM3:14 PM3:19 PM3:29 PM3:36 PM3:38 PM3:42 PM
102:48 PM2:59 PM3:04 PM3:11 PM3:15 PM3:20 PM3:32 PM3:42 PM3:45 PM3:50 PM4:00 PM4:07 PM4:09 PM4:13 PM
103:18 PM3:29 PM3:34 PM3:41 PM3:45 PM3:50 PM4:02 PM4:12 PM4:15 PM4:20 PM4:30 PM4:37 PM4:39 PM4:43 PM
10..............................4:16 PM4:26 PM4:29 PM4:34 PM4:44 PM4:51 PM4:53 PM4:57 PM
103:48 PM3:59 PM4:04 PM4:11 PM4:15 PM4:20 PM4:32 PM4:42 PM4:45 PM4:50 PM5:00 PM5:07 PM5:09 PM5:13 PM
10..............................4:46 PM4:56 PM4:59 PM5:04 PM5:14 PM5:21 PM5:23 PM5:27 PM
10..........4:31 PM4:41 PM4:45 PM4:50 PM5:02 PM5:12 PM5:15 PM5:20 PM5:30 PM5:37 PM5:39 PM5:43 PM
10..............................5:16 PM5:26 PM5:29 PM5:34 PM5:44 PM5:51 PM5:53 PM5:57 PM
10..........4:58 PM5:11 PM5:15 PM5:20 PM5:32 PM5:42 PM5:45 PM5:50 PM6:00 PM6:07 PM6:09 PM6:13 PM
10..............................5:46 PM5:56 PM5:59 PM6:04 PM6:14 PM6:21 PM6:23 PM6:27 PM
10...............5:40 PM5:44 PM5:49 PM6:01 PM6:11 PM6:14 PM6:19 PM6:29 PM6:36 PM6:38 PM6:41 PM
10..............................6:16 PM6:26 PM6:29 PM6:34 PM6:44 PM6:51 PM6:53 PM6:56 PM
10...............6:11 PM6:15 PM6:20 PM6:32 PM6:42 PM6:44 PM6:49 PM6:59 PM7:06 PM7:08 PM7:11 PM
10..............................7:36 PM7:46 PM7:48 PM7:53 PM8:03 PM8:10 PM8:12 PM8:15 PM
10..............................8:36 PM8:46 PM8:48 PM8:53 PM9:03 PM9:10 PM9:12 PM9:15 PM
10..............................9:36 PM9:46 PM9:48 PM9:53 PM10:03 PM10:10 PM10:12 PM10:15 PM
10..............................10:36 PM10:46 PM10:48 PM10:53 PM11:03 PM11:10 PM11:12 PM11:15 PM
10..............................11:36 PM11:46 PM11:48 PM11:53 PM12:03 AM12:10 AM12:12 AM12:15 AM
+

wheelchair_icon- Wheelchair Accessible Bus

+
+ + + + --- /dev/null +++ b/maxious-canberra-transit-feed/source-html/Route_11_111.html @@ -1,1 +1,804 @@ + + + + + + + +Route 11, 111 + + + + + + + +
+

Chosen services: 11, 111

+ +

View timetable and map

+ This timetable is effective from Monday 25 May 2009. +

Weekdays

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
 Tuggeranong Interchange
+ Platform 3
McKillop College Isabella CampusTheodoreCalwell ShopsErindale CentreWoden Interchange
+ Platform 9
City Interchange
1116:21 AM6:27 AM6:41 AM6:51 AM6:57 AM7:13 AM7:29 AM
1116:41 AM6:47 AM7:01 AM7:11 AM7:17 AM7:33 AM7:51 AM
1117:01 AM7:07 AM7:21 AM7:31 AM7:37 AM7:54 AM8:12 AM
1117:21 AM7:27 AM7:42 AM7:52 AM7:58 AM8:15 AM8:33 AM
1117:41 AM7:48 AM8:03 AM8:13 AM8:19 AM8:36 AM8:54 AM
1118:01 AM8:08 AM8:23 AM8:33 AM8:39 AM8:56 AM9:14 AM
11wheelchair access8:21 AM8:28 AM8:43 AM8:53 AM8:59 AM9:14 AM.....
118:41 AM8:48 AM9:03 AM9:13 AM9:19 AM9:33 AM.....
119:21 AM9:27 AM9:40 AM9:49 AM9:55 AM10:07 AM.....
119:51 AM9:57 AM10:10 AM10:19 AM10:25 AM10:37 AM.....
1110:21 AM10:27 AM10:40 AM10:49 AM10:55 AM11:07 AM.....
1110:51 AM10:57 AM11:10 AM11:19 AM11:25 AM11:37 AM.....
11wheelchair access11:21 AM11:27 AM11:40 AM11:49 AM11:55 AM12:07 PM.....
1111:51 AM11:57 AM12:10 PM12:19 PM12:25 PM12:37 PM.....
1112:21 PM12:27 PM12:40 PM12:49 PM12:55 PM1:07 PM.....
11wheelchair access12:51 PM12:57 PM1:10 PM1:19 PM1:25 PM1:37 PM.....
11wheelchair access1:21 PM1:27 PM1:40 PM1:49 PM1:55 PM2:07 PM.....
111:51 PM1:57 PM2:10 PM2:19 PM2:25 PM2:37 PM.....
11wheelchair access2:21 PM2:27 PM2:40 PM2:49 PM2:55 PM3:07 PM.....
112:51 PM2:57 PM3:10 PM3:19 PM3:25 PM3:39 PM.....
113:23 PM3:30 PM3:45 PM3:55 PM4:01 PM4:16 PM.....
11wheelchair access3:40 PM3:47 PM4:02 PM4:12 PM4:18 PM4:33 PM.....
11wheelchair access4:00 PM4:07 PM4:22 PM4:32 PM4:38 PM4:53 PM.....
114:18 PM4:25 PM4:40 PM4:50 PM4:56 PM5:11 PM.....
114:41 PM4:48 PM5:03 PM5:13 PM5:19 PM..........
115:01 PM5:08 PM5:23 PM5:33 PM5:39 PM..........
11wheelchair access5:21 PM5:28 PM5:43 PM5:53 PM5:59 PM6:14 PM.....
11wheelchair access5:41 PM5:48 PM6:03 PM6:13 PM6:19 PM..........
11wheelchair access6:01 PM6:08 PM6:23 PM6:33 PM6:39 PM..........
116:25 PM6:32 PM6:45 PM6:54 PM7:00 PM7:12 PM.....
11wheelchair access7:25 PM7:31 PM7:44 PM7:53 PM7:59 PM8:11 PM.....
118:25 PM8:31 PM8:44 PM8:53 PM8:59 PM9:11 PM.....
11wheelchair access9:25 PM9:31 PM9:44 PM9:53 PM9:59 PM10:11 PM.....
1110:25 PM10:31 PM10:44 PM10:53 PM10:59 PM11:11 PM.....
1111:25 PM11:31 PM11:44 PM11:53 PM11:59 PM..........
+

 

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
 City Interchange
+ Platform 1
Woden Interchange
+ Platform 11
Erindale CentreCalwell ShopsTheodoreMcKillop College Isabella CampusTuggeranong Interchange
11...............5:46 AM5:56 AM6:09 AM6:16 AM
11...............6:06 AM6:16 AM6:29 AM6:36 AM
11...............6:26 AM6:36 AM6:49 AM6:56 AM
11...............6:46 AM6:56 AM7:09 AM7:16 AM
11wheelchair access...............7:06 AM7:16 AM7:29 AM7:36 AM
11...............7:25 AM7:35 AM7:49 AM7:56 AM
11...............7:45 AM7:55 AM8:09 AM8:16 AM
11...............8:05 AM8:15 AM8:29 AM8:36 AM
11wheelchair access...............8:25 AM8:35 AM8:49 AM8:56 AM
11wheelchair access...............8:45 AM8:55 AM9:09 AM9:16 AM
11...............9:17 AM9:27 AM9:40 AM9:46 AM
11wheelchair access.....9:30 AM9:42 AM9:48 AM9:57 AM10:10 AM10:16 AM
11.....10:00 AM10:12 AM10:18 AM10:27 AM10:40 AM10:46 AM
11wheelchair access.....10:30 AM10:42 AM10:48 AM10:57 AM11:10 AM11:16 AM
11.....11:00 AM11:12 AM11:18 AM11:27 AM11:40 AM11:46 AM
11wheelchair access.....11:30 AM11:42 AM11:48 AM11:57 AM12:10 PM12:16 PM
11wheelchair access.....12:00 PM12:12 PM12:18 PM12:27 PM12:40 PM12:46 PM
11wheelchair access.....12:30 PM12:42 PM12:48 PM12:57 PM1:10 PM1:16 PM
11wheelchair access.....1:00 PM1:12 PM1:18 PM1:27 PM1:40 PM1:46 PM
11wheelchair access.....1:30 PM1:42 PM1:48 PM1:57 PM2:10 PM2:16 PM
11wheelchair access.....2:00 PM2:12 PM2:18 PM2:27 PM2:40 PM2:46 PM
11.....2:30 PM2:42 PM2:48 PM2:57 PM3:11 PM3:18 PM
11.....3:00 PM3:14 PM3:21 PM3:31 PM3:45 PM3:52 PM
11.....3:20 PM3:34 PM3:41 PM3:51 PM4:05 PM4:12 PM
11.....3:40 PM3:54 PM4:01 PM4:11 PM4:25 PM4:32 PM
11.....4:00 PM4:14 PM4:21 PM4:31 PM4:45 PM4:52 PM
11wheelchair access.....4:25 PM4:39 PM4:46 PM4:56 PM5:10 PM5:17 PM
11.....4:40 PM4:54 PM5:01 PM5:11 PM5:25 PM5:32 PM
11wheelchair access.....5:00 PM5:14 PM5:21 PM5:31 PM5:45 PM5:52 PM
111wheelchair access4:56 PM5:13 PM5:27 PM5:34 PM5:44 PM5:58 PM6:05 PM
111wheelchair access5:16 PM5:33 PM5:47 PM5:54 PM6:04 PM6:18 PM6:25 PM
1115:34 PM5:51 PM6:05 PM6:12 PM6:22 PM6:36 PM6:41 PM
1115:56 PM6:13 PM6:27 PM6:33 PM6:42 PM6:55 PM7:01 PM
1116:16 PM6:33 PM6:45 PM6:51 PM7:00 PM7:13 PM7:19 PM
11wheelchair access.....7:33 PM7:45 PM7:51 PM8:00 PM8:13 PM8:19 PM
11.....8:33 PM8:45 PM8:51 PM9:00 PM9:13 PM9:19 PM
11.....9:33 PM9:45 PM9:51 PM10:00 PM10:13 PM10:19 PM
11wheelchair access.....10:33 PM10:45 PM10:51 PM11:00 PM11:13 PM11:19 PM
+

wheelchair_icon- Wheelchair Accessible Bus

+
+ + + + + --- /dev/null +++ b/maxious-canberra-transit-feed/source-html/Route_12_312.html @@ -1,1 +1,881 @@ + + + + + + + + +Route 12, 312 + + + + + + + +
+

Chosen services: 12, 312

+ +

View timetable and map

+ This timetable is effective from Monday 25 May 2009. +

Weekdays

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
 Spence TerminusEvattCopland CollegeMcKellarCohen St Station
+ Platform 3
Lathlain St Station
+ Platform 1
Cameron Ave Station
+ Platform 1
City Interchange
+ Platform 1
Woden Interchange
+ Platform 6
Tuggeranong Interchange
126:28 AM6:33 AM6:36 AM6:40 AM6:50 AM6:52 AM6:56 AM...............
312 wheelchair access6:56 AM7:01 AM7:04 AM7:08 AM7:18 AM7:20 AM7:24 AM7:42 AM7:59 AM8:16 AM
312 wheelchair access7:25 AM7:30 AM7:34 AM7:38 AM7:48 AM7:50 AM7:54 AM8:13 AM8:30 AM8:47 AM
312wheelchair access7:36 AM7:41 AM7:45 AM7:49 AM7:59 AM8:01 AM8:05 AM8:24 AM8:41 AM8:58 AM
312 wheelchair access7:50 AM7:55 AM7:59 AM8:03 AM8:13 AM8:15 AM8:19 AM8:38 AM8:55 AM9:12 AM
3128:10 AM8:15 AM8:19 AM8:23 AM8:33 AM8:35 AM8:39 AM8:58 AM9:15 AM9:32 AM
312wheelchair access8:30 AM8:35 AM8:39 AM8:43 AM8:53 AM8:55 AM8:59 AM9:18 AM9:35 AM9:50 AM
312 wheelchair access8:55 AM9:00 AM9:04 AM9:08 AM9:18 AM9:20 AM9:24 AM9:42 AM9:59 AM10:14 AM
312 wheelchair access9:26 AM9:31 AM9:34 AM9:38 AM9:48 AM9:50 AM9:54 AM10:11 AM10:28 AM10:43 AM
312 wheelchair access9:56 AM10:01 AM10:04 AM10:08 AM10:18 AM10:20 AM10:24 AM10:41 AM10:58 AM11:13 AM
312wheelchair access10:26 AM10:31 AM10:34 AM10:38 AM10:48 AM10:50 AM10:54 AM11:11 AM11:28 AM11:43 AM
312wheelchair access10:56 AM11:01 AM11:04 AM11:08 AM11:18 AM11:20 AM11:24 AM11:41 AM11:58 AM12:13 PM
312 wheelchair access11:26 AM11:31 AM11:34 AM11:38 AM11:48 AM11:50 AM11:54 AM12:11 PM12:28 PM12:43 PM
312wheelchair access11:56 AM12:01 PM12:04 PM12:08 PM12:18 PM12:20 PM12:24 PM12:41 PM12:58 PM1:13 PM
312 wheelchair access12:26 PM12:31 PM12:34 PM12:38 PM12:48 PM12:50 PM12:54 PM1:11 PM1:28 PM1:43 PM
312 wheelchair access12:56 PM1:01 PM1:04 PM1:08 PM1:18 PM1:20 PM1:24 PM1:41 PM1:58 PM2:13 PM
312 wheelchair access1:26 PM1:31 PM1:34 PM1:38 PM1:48 PM1:50 PM1:54 PM2:11 PM2:28 PM2:43 PM
312 wheelchair access1:56 PM2:01 PM2:04 PM2:08 PM2:18 PM2:20 PM2:24 PM2:41 PM2:58 PM3:16 PM
312 wheelchair access2:26 PM2:31 PM2:34 PM2:38 PM2:48 PM2:50 PM2:54 PM3:12 PM3:29 PM3:48 PM
312wheelchair access2:55 PM3:00 PM3:04 PM3:08 PM3:18 PM3:20 PM3:24 PM3:43 PM4:00 PM4:19 PM
312 wheelchair access3:25 PM3:30 PM3:34 PM3:38 PM3:48 PM3:50 PM3:54 PM4:13 PM4:30 PM4:49 PM
312 wheelchair access3:45 PM3:50 PM3:54 PM3:58 PM4:08 PM4:10 PM4:14 PM4:33 PM4:50 PM5:09 PM
312 wheelchair access4:15 PM4:20 PM4:24 PM4:28 PM4:38 PM4:40 PM4:44 PM5:03 PM5:20 PM5:39 PM
312 wheelchair access4:35 PM4:40 PM4:44 PM4:48 PM4:58 PM5:00 PM5:04 PM5:23 PM5:40 PM5:59 PM
312 wheelchair access5:00 PM5:05 PM5:09 PM5:13 PM5:23 PM5:25 PM5:29 PM5:48 PM6:05 PM6:24 PM
312 wheelchair access5:25 PM5:30 PM5:34 PM5:38 PM5:48 PM5:50 PM5:54 PM6:13 PM6:30 PM6:45 PM
312 wheelchair access5:55 PM6:00 PM6:04 PM6:08 PM6:18 PM6:20 PM6:24 PM6:41 PM6:55 PM7:10 PM
312 wheelchair access6:27 PM6:32 PM6:35 PM6:39 PM6:49 PM6:51 PM6:54 PM7:10 PM7:24 PM7:39 PM
12wheelchair access7:07 PM7:12 PM7:15 PM7:19 PM7:28 AM7:30 AM7:34 PM...............
128:07 PM8:12 PM8:15 PM8:19 PM8:28 AM8:30 AM8:34 PM...............
129:07 PM9:12 PM9:15 PM9:19 PM9:28 AM9:30 AM9:34 PM...............
1210:07 PM10:12 PM10:15 PM10:19 PM10:28 AM10:30 AM10:34 PM...............
+

 

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
 Tuggeranong Interchange
+ Platform 8
Woden Interchange
+ Platform 9
City Interchange
+ Platform 5
Cameron Ave Station
+ Platform 4
Lathlain St Station
+ Platform 4
Cohen St Station
+ Platform 6
McKellarCopland CollegeEvattSpence Terminus
12...............7:23 AM7:25 AM7:29 AM7:37 AM7:41 AM7:46 AM7:52 AM
12wheelchair access...............8:02 AM8:04 AM8:08 AM8:16 AM8:20 AM8:25 AM8:31 AM
312 wheelchair access7:26 AM7:45 AM8:03 AM8:21 AM8:23 AM8:27 AM8:35 AM8:39 AM8:44 AM8:50 AM
312wheelchair access8:26 AM8:45 AM9:03 AM9:21 AM9:23 AM9:27 AM9:34 AM9:38 AM9:42 AM9:48 AM
312 wheelchair access9:01 AM9:20 AM9:37 AM9:54 AM9:56 AM10:00 AM10:07 AM10:11 AM10:15 AM10:21 AM
312wheelchair access9:31 AM9:49 AM10:05 AM10:22 AM10:24 AM10:28 AM10:35 AM10:39 AM10:43 AM10:49 AM
312wheelchair access10:01 AM10:19 AM10:35 AM10:52 AM10:54 AM10:58 AM11:05 AM11:09 AM11:13 AM11:19 AM
312 wheelchair access10:31 AM10:49 AM11:05 AM11:22 AM11:24 AM11:28 AM11:35 AM11:39 AM11:43 AM11:49 AM
312wheelchair access11:01 AM11:19 AM11:35 AM11:52 AM11:54 AM11:58 AM12:05 PM12:09 PM12:13 PM12:19 PM
312wheelchair access11:31 AM11:49 AM12:05 PM12:22 PM12:24 PM12:28 PM12:35 PM12:39 PM12:43 PM12:49 PM
312 wheelchair access12:01 PM12:19 PM12:35 PM12:52 PM12:54 PM12:58 PM1:05 PM1:09 PM1:13 PM1:19 PM
312wheelchair access12:31 PM12:49 PM1:05 PM1:22 PM1:24 PM1:28 PM1:35 PM1:39 PM1:43 PM1:49 PM
312 wheelchair access1:01 PM1:19 PM1:35 PM1:52 PM1:54 PM1:58 PM2:05 PM2:09 PM2:13 PM2:19 PM
312wheelchair access1:31 PM1:49 PM2:05 PM2:22 PM2:24 PM2:28 PM2:35 PM2:39 PM2:43 PM2:49 PM
312 wheelchair access2:01 PM2:19 PM2:35 PM2:52 PM2:54 PM2:58 PM3:06 PM3:10 PM3:15 PM3:21 PM
312 wheelchair access2:31 PM2:49 PM3:05 PM3:23 PM3:25 PM3:29 PM3:37 PM3:41 PM3:46 PM3:52 PM
312 wheelchair access3:01 PM3:20 PM3:38 PM3:56 PM3:58 PM4:02 PM4:10 PM4:14 PM4:19 PM4:25 PM
312wheelchair access3:31 PM3:50 PM4:08 PM4:26 PM4:28 PM4:32 PM4:40 PM4:44 PM4:49 PM4:55 PM
312wheelchair access3:56 PM4:15 PM4:33 PM4:51 PM4:53 PM4:57 PM5:05 PM5:09 PM5:14 PM5:20 PM
312 wheelchair access4:16 PM4:35 PM4:53 PM5:11 PM5:13 PM5:17 PM5:25 PM5:29 PM5:34 PM5:40 PM
312wheelchair access4:36 PM4:55 PM5:13 PM5:31 PM5:33 PM5:37 PM5:45 PM5:49 PM5:54 PM6:00 PM
312 wheelchair access4:56 PM5:15 PM5:33 PM5:51 PM5:53 PM5:57 PM6:05 PM6:09 PM6:14 PM6:20 PM
312wheelchair access5:16 PM5:35 PM5:53 PM6:11 PM6:13 PM6:17 PM6:25 PM6:29 PM6:33 PM6:39 PM
312 wheelchair access5:36 PM5:55 PM6:13 PM6:31 PM6:33 PM6:36 PM6:43 PM6:47 PM6:51 PM6:57 PM
312 wheelchair access6:36 PM6:53 PM7:08 PM7:25 PM7:27 PM7:30 PM7:37 PM7:41 PM7:45 PM7:51 PM
12...............8:35 PM8:37 PM8:40 PM8:47 PM8:51 PM8:55 PM9:01 PM
12...............9:35 PM9:37 PM9:40 PM9:47 PM9:51 PM9:55 PM10:01 PM
12...............10:35 PM10:37 PM10:40 PM10:47 PM10:51 PM10:55 PM11:01 PM
12...............11:35 PM11:37 PM11:40 PM11:47 PM11:51 PM11:55 PM12:01 AM
+

wheelchair_icon- Wheelchair Accessible Bus

+
+ + + + + --- /dev/null +++ b/maxious-canberra-transit-feed/source-html/Route_13_313.html @@ -1,1 +1,855 @@ + + + + + + + + +Route 13, 313 + + + + + + + +
+

Chosen services: 13, 313

+ +

View timetable and map

+ This timetable is effective from Monday 25 May 2009. +

Weekdays

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
 Fraser West TerminusCharnwoodScullin ShopsPage ShopsCohen St Station
+ Platform 3
Lathlain St Station
+ Platform 1
Cameron Ave Station
+ Platform 1
City Interchange
+ Platform 1
Woden Interchange
+ Platform 6
Tuggeranong Interchange
313 wheelchair access5:49 AM5:53 AM6:02 AM6:06 AM6:13 AM6:15 AM6:19 AM6:36 AM6:53 AM7:06 AM
313 wheelchair access6:19 AM6:23 AM6:32 AM6:36 AM6:43 AM6:45 AM6:49 AM7:06 AM7:23 AM7:38 AM
313wheelchair access6:49 AM6:53 AM7:02 AM7:06 AM7:13 AM7:15 AM7:19 AM7:37 AM7:54 AM8:11 AM
313wheelchair access7:17 AM7:21 AM7:30 AM7:35 AM7:43 AM7:45 AM7:49 AM8:08 AM8:25 AM8:42 AM
313 wheelchair access7:40 AM7:45 AM7:55 AM8:00 AM8:08 AM8:10 AM8:14 AM8:33 AM8:50 AM9:07 AM
313wheelchair access8:00 AM8:05 AM8:15 AM8:20 AM8:28 AM8:30 AM8:34 AM8:53 AM9:10 AM9:27 AM
313 wheelchair access8:20 AM8:25 AM8:35 AM8:40 AM8:48 AM8:50 AM8:54 AM9:13 AM9:30 AM9:45 AM
313 wheelchair access8:45 AM8:50 AM9:00 AM9:05 AM9:13 AM9:15 AM9:19 AM9:37 AM9:54 AM10:09 AM
313 wheelchair access9:17 AM9:22 AM9:32 AM9:36 AM9:43 AM9:45 AM9:49 AM10:06 AM10:23 AM10:38 AM
313 wheelchair access9:49 AM9:53 AM10:02 AM10:06 AM10:13 AM10:15 AM10:19 AM10:36 AM10:53 AM11:08 AM
313 wheelchair access10:19 AM10:23 AM10:32 AM10:36 AM10:43 AM10:45 AM10:49 AM11:06 AM11:23 AM11:38 AM
313wheelchair access10:49 AM10:53 AM11:02 AM11:06 AM11:13 AM11:15 AM11:19 AM11:36 AM11:53 AM12:08 PM
313 wheelchair access11:19 AM11:23 AM11:32 AM11:36 AM11:43 AM11:45 AM11:49 AM12:06 PM12:23 PM12:38 PM
313 wheelchair access11:49 AM11:53 AM12:02 PM12:06 PM12:13 PM12:15 PM12:19 PM12:36 PM12:53 PM1:08 PM
313wheelchair access12:19 PM12:23 PM12:32 PM12:36 PM12:43 PM12:45 PM12:49 PM1:06 PM1:23 PM1:38 PM
313 wheelchair access12:49 PM12:53 PM1:02 PM1:06 PM1:13 PM1:15 PM1:19 PM1:36 PM1:53 PM2:08 PM
313 wheelchair access1:19 PM1:23 PM1:32 PM1:36 PM1:43 PM1:45 PM1:49 PM2:06 PM2:23 PM2:38 PM
313 wheelchair access1:49 PM1:53 PM2:02 PM2:06 PM2:13 PM2:15 PM2:19 PM2:36 PM2:53 PM3:10 PM
313wheelchair access2:19 PM2:23 PM2:32 PM2:36 PM2:43 PM2:45 PM2:49 PM3:07 PM3:24 PM3:43 PM
313 wheelchair access2:48 PM2:52 PM3:01 PM3:05 PM3:13 PM3:15 PM3:19 PM3:38 PM3:55 PM4:14 PM
313 wheelchair access3:16 PM3:21 PM3:31 PM3:35 PM3:43 PM3:45 PM3:49 PM4:08 PM4:25 PM4:44 PM
313 wheelchair access3:46 PM3:51 PM4:01 PM4:05 PM4:13 PM4:15 PM4:19 PM4:38 PM4:55 PM5:14 PM
134:21 PM4:26 PM4:36 PM4:40 PM4:48 PM4:50 PM4:54 PM...............
134:50 PM4:55 PM5:05 PM5:09 PM5:17 PM5:19 PM5:23 PM...............
313 wheelchair access5:16 PM5:21 PM5:31 PM5:35 PM5:43 PM5:45 PM5:49 PM6:08 PM6:25 PM6:41 PM
313 wheelchair access5:46 PM5:51 PM6:01 PM6:05 PM6:13 PM6:15 PM6:19 PM6:36 PM6:50 PM7:05 PM
313 wheelchair access6:19 PM6:24 PM6:34 PM6:38 PM6:44 PM6:46 PM6:49 PM7:05 PM7:19 PM7:34 PM
137:12 PM7:16 PM7:25 PM7:29 PM7:35 PM7:37 PM7:40 PM...............
138:12 PM8:16 PM8:25 PM8:29 PM8:35 PM8:37 PM8:40 PM...............
139:12 PM9:16 PM9:25 PM9:29 PM9:35 PM9:37 PM9:40 PM...............
1310:12 PM10:16 PM10:25 PM10:29 PM10:35 PM10:37 PM10:40 PM...............
+

 

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
 Tuggeranong Interchange
+ Platform 8
Woden Interchange
+ Platform 9
City Interchange
+ Platform 5
Cameron Ave Station
+ Platform 4
Lathlain St Station
+ Platform 4
Cohen St Station
+ Platform 6
Page ShopsScullin ShopsCharnwoodFraser West Terminus
13...............7:28 AM7:30 AM7:34 AM7:37 AM7:41 AM7:50 AM7:57 AM
313 wheelchair access7:11 AM7:29 AM7:47 AM8:05 AM8:07 AM8:11 AM8:14 AM8:18 AM8:27 AM8:34 AM
313 wheelchair access7:51 AM8:10 AM8:28 AM8:46 AM8:48 AM8:52 AM8:55 AM8:59 AM9:08 AM9:15 AM
313 wheelchair access8:11 AM8:30 AM8:48 AM9:06 AM9:08 AM9:12 AM9:15 AM9:19 AM9:28 AM9:34 AM
313 wheelchair access8:51 AM9:10 AM9:28 AM9:45 AM9:47 AM9:51 AM9:53 AM9:57 AM10:05 AM10:11 AM
313wheelchair access9:21 AM9:40 AM9:56 AM10:13 AM10:15 AM10:19 AM10:21 AM10:25 AM10:33 AM10:39 AM
313wheelchair access9:51 AM10:09 AM10:25 AM10:42 AM10:44 AM10:48 AM10:50 AM10:54 AM11:02 AM11:08 AM
313wheelchair access10:21 AM10:39 AM10:55 AM11:12 AM11:14 AM11:18 AM11:20 AM11:24 AM11:32 AM11:38 AM
313 wheelchair access10:51 AM11:09 AM11:25 AM11:42 AM11:44 AM11:48 AM11:50 AM11:54 AM12:02 PM12:08 PM
313 wheelchair access11:21 AM11:39 AM11:55 AM12:12 PM12:14 PM12:18 PM12:20 PM12:24 PM12:32 PM12:38 PM
313 wheelchair access11:51 AM12:09 PM12:25 PM12:42 PM12:44 PM12:48 PM12:50 PM12:54 PM1:02 PM1:08 PM
313wheelchair access12:21 PM12:39 PM12:55 PM1:12 PM1:14 PM1:18 PM1:20 PM1:24 PM1:32 PM1:38 PM
313 wheelchair access12:51 PM1:09 PM1:25 PM1:42 PM1:44 PM1:48 PM1:50 PM1:54 PM2:02 PM2:08 PM
313 wheelchair access1:21 PM1:39 PM1:55 PM2:12 PM2:14 PM2:18 PM2:20 PM2:24 PM2:32 PM2:38 PM
313wheelchair access1:51 PM2:09 PM2:25 PM2:42 PM2:44 PM2:48 PM2:50 PM2:54 PM3:03 PM3:09 PM
313wheelchair access2:21 PM2:39 PM2:55 PM3:13 PM3:15 PM3:19 PM3:22 PM3:27 PM3:37 PM3:43 PM
313 wheelchair access2:50 PM3:08 PM3:26 PM3:44 PM3:46 PM3:50 PM3:53 PM3:58 PM4:08 PM4:14 PM
313 wheelchair access3:16 PM3:35 PM3:53 PM4:11 PM4:13 PM4:17 PM4:20 PM4:25 PM4:35 PM4:41 PM
313wheelchair access3:46 PM4:05 PM4:23 PM4:41 PM4:43 PM4:47 PM4:50 PM4:55 PM5:05 PM5:11 PM
313 wheelchair access4:06 PM4:25 PM4:43 PM5:01 PM5:03 PM5:07 PM5:10 PM5:15 PM5:25 PM5:31 PM
313 wheelchair access4:26 PM4:45 PM5:03 PM5:21 PM5:23 PM5:27 PM5:30 PM5:35 PM5:45 PM5:51 PM
313wheelchair access4:46 PM5:05 PM5:23 PM5:41 PM5:43 PM5:47 PM5:50 PM5:55 PM6:05 PM6:11 PM
313 wheelchair access5:26 PM5:45 PM6:03 PM6:21 PM6:23 PM6:27 PM6:30 PM6:34 PM6:42 PM6:48 PM
313wheelchair access5:56 PM6:15 PM6:32 PM6:49 PM6:51 PM6:54 PM6:56 PM7:00 PM7:08 PM7:14 PM
313 wheelchair access6:56 PM7:13 PM7:28 PM7:45 PM7:47 PM7:50 PM7:52 PM7:56 PM8:04 PM8:10 PM
13...............8:40 PM8:42 PM8:45 PM8:47 PM8:51 PM8:59 PM9:05 PM
13...............9:40 PM9:42 PM9:45 PM9:47 PM9:51 PM9:59 PM10:05 PM
13...............10:40 PM10:42 PM10:45 PM10:47 PM10:51 PM10:59 PM11:05 PM
+

wheelchair_icon- Wheelchair Accessible Bus

+
+ + + + + --- /dev/null +++ b/maxious-canberra-transit-feed/source-html/Route_14_314.html @@ -1,1 +1,884 @@ + + + + + + + + +Route 14, 314 + + + + + + + + +
+

Chosen services: 14, 314

+

View timetable and map

+ +This timetable is effective from Monday 25 May 2009. +

Weekdays

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
 Tuggeranong Interchange
+ Platform 8
Woden Interchange
+ Platform 9
City Interchange
+ Platform 5
Cameron Ave Station
+ Platform 4
Lathlain St Station
+ Platform 4
Cohen St Station
+ Platform 6
St Francis Xavier FloreyCharnwoodFraserFraser West Terminus
14...............7:05 AM7:07 AM7:11 AM7:16 AM7:21 AM7:25 AM7:33 AM
14...............7:23 AM7:25 AM7:29 AM7:35 AM7:40 AM7:45 AM7:53 AM
314 wheelchair access7:06 AM7:24 AM7:41 AM7:59 AM8:01 AM8:05 AM8:11 AM8:16 AM8:21 AM8:29 AM
314 wheelchair access7:46 AM8:05 AM8:23 AM8:41 AM8:43 AM8:47 AM8:53 AM8:58 AM9:03 AM9:11 AM
314 wheelchair access8:06 AM8:25 AM8:43 AM9:01 AM9:03 AM9:07 AM9:13 AM9:18 AM9:23 AM9:31 AM
314 wheelchair access8:43 AM9:02 AM9:20 AM9:38 AM9:40 AM9:44 AM9:49 AM9:54 AM9:58 AM10:06 AM
314 wheelchair access9:16 AM9:35 AM9:51 AM10:08 AM10:10 AM10:14 AM10:19 AM10:24 AM10:28 AM10:36 AM
314 wheelchair access9:46 AM10:04 AM10:20 AM10:37 AM10:39 AM10:43 AM10:48 AM10:53 AM10:57 AM11:05 AM
314 wheelchair access10:16 AM10:34 AM10:50 AM11:07 AM11:09 AM11:13 AM11:18 AM11:23 AM11:27 AM11:35 AM
314 wheelchair access10:46 AM11:04 AM11:20 AM11:37 AM11:39 AM11:43 AM11:48 AM11:53 AM11:57 AM12:05 PM
314wheelchair access11:16 AM11:34 AM11:50 AM12:07 PM12:09 PM12:13 PM12:18 PM12:23 PM12:27 PM12:35 PM
314 wheelchair access11:46 AM12:04 PM12:20 PM12:37 PM12:39 PM12:43 PM12:48 PM12:53 PM12:57 PM1:05 PM
314 wheelchair access12:16 PM12:34 PM12:50 PM1:07 PM1:09 PM1:13 PM1:18 PM1:23 PM1:27 PM1:35 PM
314 wheelchair access12:46 PM1:04 PM1:20 PM1:37 PM1:39 PM1:43 PM1:48 PM1:53 PM1:57 PM2:05 PM
314 wheelchair access1:16 PM1:34 PM1:50 PM2:07 PM2:09 PM2:13 PM2:18 PM2:23 PM2:27 PM2:35 PM
314 wheelchair access1:46 PM2:04 PM2:20 PM2:37 PM2:39 PM2:43 PM2:48 PM2:53 PM2:57 PM3:06 PM
314wheelchair access2:16 PM2:34 PM2:50 PM3:07 PM3:09 PM3:13 PM3:19 PM3:24 PM3:29 PM3:38 PM
314 wheelchair access2:45 PM3:03 PM3:21 PM3:39 PM3:41 PM3:45 PM3:51 PM3:56 PM4:01 PM4:10 PM
14s...............3:42 PM3:44 PM3:48 PM3:54 PM3:59 PM4:04 PM4:13 PM
314 wheelchair access3:21 PM3:40 PM3:58 PM4:16 PM4:18 PM4:22 PM4:28 PM4:33 PM4:38 PM4:47 PM
314 wheelchair access3:51 PM4:10 PM4:28 PM4:46 PM4:48 PM4:52 PM4:58 PM5:03 PM5:08 PM5:17 PM
314 wheelchair access4:21 PM4:40 PM4:58 PM5:16 PM5:18 PM5:22 PM5:28 PM5:33 PM5:38 PM5:47 PM
314 wheelchair access4:51 PM5:10 PM5:28 PM5:46 PM5:48 PM5:52 PM5:58 PM6:03 PM6:08 PM6:17 PM
314 wheelchair access5:11 PM5:30 PM5:48 PM6:06 PM6:08 PM6:12 PM6:18 PM6:23 PM6:28 PM6:36 PM
314wheelchair access5:31 PM5:50 PM6:08 PM6:26 PM6:28 PM6:32 PM6:37 PM6:42 PM6:46 PM6:54 PM
314 wheelchair access5:51 PM6:10 PM6:28 PM6:45 PM6:47 PM6:50 PM6:55 PM7:00 PM7:04 PM7:12 PM
314 wheelchair access6:21 PM6:39 PM6:54 PM7:11 PM7:13 PM7:16 PM7:21 PM7:26 PM7:30 PM7:38 PM
14 wheelchair access...............7:46 PM7:48 PM7:51 PM7:56 PM8:01 PM8:05 PM8:13 PM
14...............8:46 PM8:48 PM8:51 PM8:56 PM9:01 PM9:05 PM9:13 PM
14...............9:46 PM9:48 PM9:51 PM9:56 PM10:01 PM10:05 PM10:13 PM
14...............10:46 PM10:48 PM10:51 PM10:56 PM11:01 PM11:05 PM11:13 PM
+

s - Operates school days only and commences at Radford College at 3.40pm

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
 Fraser West TerminusFraserCharnwoodSt Francis Xavier FloreyCohn St Station
+ Platform 3
Lathlain St Station
+ Platform 1
Cameron Ave Station
+ Platform 1
City Interchange
+ Platform 1
Woden Interchange
+ Platform 6
Tuggeranong Interchange
146:13 AM6:20 AM6:24 AM6:29 AM6:38AM6:40 AM6:44 AM...............
314 wheelchair access6:43 AM6:50 AM6:54 AM6:59 AM7:08 AM7:10 AM7:14 AM7:31 AM7:48 AM8:05 AM
314 wheelchair access7:12 AM7:19 AM7:23 AM7:28 AM7:38 AM7:40 AM7:44 AM8:03 AM8:20 AM8:37 AM
314 wheelchair access7:35 AM7:43 AM7:48 AM7:53 AM8:03 AM8:05 AM8:09 AM8:28 AM8:45 AM9:02 AM
314 wheelchair access7:55 AM8:03 AM8:08 AM8:13 AM8:23 AM8:25 AM8:29 AM8:48 AM9:05 AM9:22 AM
314 wheelchair access8:15 AM8:23 AM8:28 AM8:33 AM8:43 AM8:45 AM8:49 AM9:08 AM9:25 AM9:41 AM
314 wheelchair access8:40 AM8:48 AM8:53 AM8:58 AM9:08 AM9:10 AM9:14 AM9:33 AM9:50 AM10:05 AM
314 wheelchair access9:11 AM9:19 AM9:24 AM9:29 AM9:38 AM9:40 AM9:44 AM10:01 AM10:18 AM10:33 AM
314 wheelchair access9:43 AM9:50 AM9:54 AM9:59 AM10:08 AM10:10 AM10:14 AM10:31 AM10:48 AM11:03 AM
314 wheelchair access10:13 AM10:20 AM10:24 AM10:29 AM10:38 AM10:40 AM10:44 AM11:01 AM11:18 AM11:33 AM
314 wheelchair access10:43 AM10:50 AM10:54 AM10:59 AM11:08 AM11:10 AM11:14 AM11:31 AM11:48 AM12:03 PM
314 wheelchair access11:13 AM11:20 AM11:24 AM11:29 AM11:38 AM11:40 AM11:44 AM12:01 PM12:18 PM12:33 PM
314 wheelchair access11:43 AM11:50 AM11:54 AM11:59 AM12:08 PM12:10 PM12:14 PM12:31 PM12:48 PM1:03 PM
314 wheelchair access12:13 PM12:20 PM12:24 PM12:29 PM12:38 PM12:40 PM12:44 PM1:01 PM1:18 PM1:33 PM
314 wheelchair access12:43 PM12:50 PM12:54 PM12:59 PM1:08 PM1:10 PM1:14 PM1:31 PM1:48 PM2:03 PM
314 wheelchair access1:13 PM1:20 PM1:24 PM1:29 PM1:38 PM1:40 PM1:44 PM2:01 PM2:18 PM2:33 PM
314 wheelchair access1:43 PM1:50 PM1:54 PM1:59 PM2:08 PM2:10 PM2:14 PM2:31 PM2:48 PM3:04 PM
314wheelchair access2:13 PM2:20 PM2:24 PM2:29 PM2:38 PM2:40 PM2:44 PM3:01 PM3:18 PM3:37 PM
314 wheelchair access2:42 PM2:49 PM2:53 PM2:58 PM3:08 PM3:10 PM3:14 PM3:33 PM3:50 PM4:09 PM
314 wheelchair access3:11 PM3:18 PM3:23 PM3:28 PM3:38 PM3:40 PM3:44 PM4:03 PM4:20 PM4:39 PM
314wheelchair access3:51 PM3:58 PM4:03 PM4:08 PM4:18 PM4:20 PM4:24 PM4:43 PM5:00 PM5:19 PM
314 wheelchair access4:21 PM4:28 PM4:33 PM4:38 PM4:48 PM4:50 PM4:54 PM5:13 PM5:30 PM5:49 PM
144:51 PM4:58 PM5:03 PM5:08 PM5:18 AM5:20 AM5:24 PM...............
314 wheelchair access5:41 PM5:48 PM5:53 PM5:58 PM6:08 PM6:10 PM6:14 PM6:32 PM6:46 PM7:01 PM
314 wheelchair access6:14 PM6:21 PM6:26 PM6:31 PM6:39 PM6:41 PM6:44 PM7:00 PM7:14 PM7:29 PM
146:39 PM6:46 PM6:50 PM6:55 PM7:02 AM7:04 AM7:08 PM...............
147:17 PM7:24 PM7:28 PM7:33 PM7:40 AM7:42 AM7:46 PM...............
148:17 PM8:24 PM8:28 PM8:33 PM8:40 AM8:42 AM8:46 PM...............
149:17 PM9:24 PM9:28 PM9:33 PM9:40 AM9:42 AM9:46 PM...............
1410:17 PM10:24 PM10:28 PM10:33 PM10:40 AM10:42 AM10:46 PM...............
+

wheelchair_icon- Wheelchair Accessible Bus

+
+ + + + --- /dev/null +++ b/maxious-canberra-transit-feed/source-html/Route_15_315.html @@ -1,1 +1,902 @@ + + + + + + + + +Route 15, 315 + + + + + + + +
+

Chosen services: 15, 315

+ +

View timetable and map

+ This timetable is effective from Monday 25 May 2009. +

Weekdays

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
 Spence TerminusSpenceAlpen & Clifford StMelbaCopland CollegeCohen St Station
+ Platform 3
Lathlain St Station
+ Platform 1
Cameron Ave Station
+ Platform 1
City Interchange
+ Platform 1
Woden Interchange
+ Platform 6
Tuggeranong Interchange
155:35 AM5:40 AM5:43 AM5:45 AM5:48 AM5:58 AM6:00 AM6:04 AM...............
156:05 AM6:10 AM6:13 AM6:15 AM6:18 AM6:28 AM6:30 AM6:34 AM...............
315 wheelchair access6:35 AM6:40 AM6:43 AM6:45 AM6:48 AM6:58 AM7:00 AM7:04 AM7:21 AM7:38 AM7:55 AM
315wheelchair access7:05 AM7:10 AM7:13 AM7:15 AM7:18 AM7:28 AM7:30 AM7:34 AM7:53 AM8:10 AM8:27 AM
315 wheelchair access7:33 AM7:38 AM7:41 AM7:44 AM7:48 AM7:58 AM8:00 AM8:04 AM8:23 AM8:40 AM8:57 AM
315wheelchair access7:53 AM7:58 AM8:01 AM8:04 AM8:08 AM8:18 AM8:20 AM8:24 AM8:43 AM9:00 AM9:17 AM
315wheelchair access..........8:21 AM8:23 AM8:27 AM8:37 AM8:39 AM8:43 AM9:00 AM..........
315 wheelchair access8:13 AM8:18 AM8:21 AM8:24 AM8:28 AM8:38 AM8:40 AM8:44 AM9:03 AM9:20 AM9:36 AM
315wheelchair access8:33 AM8:38 AM8:41 AM8:44 AM8:48 AM8:58 AM9:00 AM9:04 AM9:23 AM9:40 AM9:55 AM
315 wheelchair access9:03 AM9:08 AM9:11 AM9:14 AM9:18 AM9:28 AM9:30 AM9:34 AM9:51 AM10:08 AM10:23 AM
315 wheelchair access9:35 AM9:40 AM9:43 AM9:45 AM9:48 AM9:58 AM10:00 AM10:04 AM10:21 AM10:38 AM10:53 AM
315wheelchair access10:05 AM10:10 AM10:13 AM10:15 AM10:18 AM10:28 AM10:30 AM10:34 AM10:51 AM11:08 AM11:23 AM
315 wheelchair access10:35 AM10:40 AM10:43 AM10:45 AM10:48 AM10:58 AM11:00 AM11:04 AM11:21 AM11:38 AM11:53 AM
315wheelchair access11:05 AM11:10 AM11:13 AM11:15 AM11:18 AM11:28 AM11:30 AM11:34 AM11:51 AM12:08 PM12:23 PM
315 wheelchair access11:35 AM11:40 AM11:43 AM11:45 AM11:48 AM11:58 AM12:00 PM12:04 PM12:21 PM12:38 PM12:53 PM
315wheelchair access12:05 PM12:10 PM12:13 PM12:15 PM12:18 PM12:28 PM12:30 PM12:34 PM12:51 PM1:08 PM1:23 PM
315 wheelchair access12:35 PM12:40 PM12:43 PM12:45 PM12:48 PM12:58 PM1:00 PM1:04 PM1:21 PM1:38 PM1:53 PM
315wheelchair access1:05 PM1:10 PM1:13 PM1:15 PM1:18 PM1:28 PM1:30 PM1:34 PM1:51 PM2:08 PM2:23 PM
315 wheelchair access1:35 PM1:40 PM1:43 PM1:45 PM1:48 PM1:58 PM2:00 PM2:04 PM2:21 PM2:38 PM2:53 PM
315 wheelchair access2:05 PM2:10 PM2:13 PM2:15 PM2:18 PM2:28 PM2:30 PM2:34 PM2:51 PM3:08 PM3:27 PM
315 wheelchair access2:35 PM2:40 PM2:43 PM2:45 PM2:48 PM2:58 PM3:00 PM3:04 PM3:23 PM3:40 PM3:59 PM
315 wheelchair access3:03 PM3:08 PM3:11 PM3:14 PM3:18 PM3:28 PM3:30 PM3:34 PM3:53 PM4:10 PM4:29 PM
315 wheelchair access3:33 PM3:38 PM3:41 PM3:44 PM3:48 PM3:58 PM4:00 PM4:04 PM4:23 PM4:40 PM4:59 PM
315 wheelchair access4:03 PM4:08 PM4:11 PM4:14 PM4:18 PM4:28 PM4:30 PM4:34 PM4:53 PM5:10 PM5:29 PM
315 wheelchair access4:43 PM4:48 PM4:51 PM4:54 PM4:58 PM5:08 PM5:10 PM5:14 PM5:33 PM5:50 PM6:09 PM
315 wheelchair access5:33 PM5:38 PM5:41 PM5:44 PM5:48 PM5:58 PM6:00 PM6:04 PM6:23 PM6:38 PM6:53 PM
315 wheelchair access6:04 PM6:09 PM6:12 PM6:15 PM6:19 PM6:29 PM6:31 PM6:34 PM6:50 PM7:04 PM7:19 PM
156:23 PM6:28 PM6:31 PM6:33 PM6:36 PM6:45 PM6:47 PM6:50 PM...............
157:23 PM7:28 PM7:31 PM7:33 PM7:36 PM7:45 PM7:47 PM7:50 PM...............
158:23 PM8:28 PM8:31 PM8:33 PM8:36 PM8:45 PM8:47 PM8:50 PM...............
15wheelchair access9:23 PM9:28 PM9:31 PM9:33 PM9:36 PM9:45 PM9:47 PM9:50 PM...............
1510:23 PM10:28 PM10:31 PM10:33 PM10:36 PM10:45 PM10:47 PM10:50 PM...............
+

 

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
 Tuggeranong Interchange
+ Platform 8
Woden Interchange
+ Platform 9
City Interchange
+ Platform 5
Cameron Ave Station
+ Platform 4
Lathlain St Station
+ Platform 4
Cohen St Station
+ Platform 6
Copland CollegeMelbaSpenceSpence Terminus
15...............7:23 AM7:25 AM7:29 AM7:37 AM7:41 AM7:49 AM7:54 AM
15wheelchair access...............8:03 AM8:05 AM8:09 AM8:17 AM8:21 AM8:29 AM8:34 AM
315 wheelchair access7:31 AM7:50 AM8:08 AM8:26 AM8:28 AM8:32 AM8:40 AM8:44 AM8:52 AM8:57 AM
315 wheelchair access8:31 AM8:50 AM9:08 AM9:26 AM9:28 AM9:32 AM9:39 AM9:42 AM9:48 AM9:53 AM
315wheelchair access9:11 AM9:30 AM9:46 AM10:03 AM10:05 AM10:09 AM10:16 AM10:19 AM10:25 AM10:30 AM
315 wheelchair access9:41 AM9:59 AM10:15 AM10:32 AM10:34 AM10:38 AM10:45 AM10:48 AM10:54 AM10:59 AM
315wheelchair access10:11 AM10:29 AM10:45 AM11:02 AM11:04 AM11:08 AM11:15 AM11:18 AM11:24 AM11:29 AM
315 wheelchair access10:41 AM10:59 AM11:15 AM11:32 AM11:34 AM11:38 AM11:45 AM11:48 AM11:54 AM11:59 AM
315 wheelchair access11:11 AM11:29 AM11:45 AM12:02 PM12:04 PM12:08 PM12:15 PM12:18 PM12:24 PM12:29 PM
315wheelchair access11:41 AM11:59 AM12:15 PM12:32 PM12:34 PM12:38 PM12:45 PM12:48 PM12:54 PM12:59 PM
315 wheelchair access12:11 PM12:29 PM12:45 PM1:02 PM1:04 PM1:08 PM1:15 PM1:18 PM1:24 PM1:29 PM
315wheelchair access12:41 PM12:59 PM1:15 PM1:32 PM1:34 PM1:38 PM1:45 PM1:48 PM1:54 PM1:59 PM
315 wheelchair access1:11 PM1:29 PM1:45 PM2:02 PM2:04 PM2:08 PM2:15 PM2:18 PM2:24 PM2:29 PM
315wheelchair access1:41 PM1:59 PM2:15 PM2:32 PM2:34 PM2:38 PM2:45 PM2:48 PM2:54 PM2:59 PM
315 wheelchair access2:11 PM2:29 PM2:45 PM3:02 PM3:04 PM3:08 PM3:16 PM3:20 PM3:28 PM3:33 PM
315 wheelchair access2:41 PM2:59 PM3:17 PM3:35 PM3:37 PM3:41 PM3:49 PM3:53 PM4:01 PM4:06 PM
15a...............3:54 PM3:56 PM4:00 PM4:08 PM4:12 PM4:20 PM4:25 PM
315 wheelchair access3:11 PM3:30 PM3:48 PM4:06 PM4:08 PM4:12 PM4:20 PM4:24 PM4:32 PM4:37 PM
315 wheelchair access3:41 PM4:00 PM4:18 PM4:36 PM4:38 PM4:42 PM4:50 PM4:54 PM5:02 PM5:07 PM
315 wheelchair access4:11 PM4:30 PM4:48 PM5:06 PM5:08 PM5:12 PM5:20 PM5:24 PM5:32 PM5:37 PM
315 wheelchair access4:41 PM5:00 PM5:18 PM5:36 PM5:38 PM5:42 PM5:50 PM5:54 PM6:02 PM6:07 PM
315 wheelchair access5:01 PM5:20 PM5:38 PM5:56 PM5:58 PM6:02 PM6:10 PM6:14 PM6:22 PM6:27 PM
315 wheelchair access5:21 PM5:40 PM5:58 PM6:16 PM6:18 PM6:22 PM6:30 PM6:33 PM6:39 PM6:44 PM
315 wheelchair access6:01 PM6:20 PM6:36 PM6:53 PM6:55 PM6:58 PM7:05 PM7:08 PM7:14 PM7:19 PM
15...............7:53 PM7:55 PM7:58 PM8:05 PM8:08 PM8:14 PM8:19 PM
15...............8:53 PM8:55 PM8:58 PM9:05 PM9:08 PM9:14 PM9:19 PM
15...............9:53 PM9:55 PM9:58 PM10:05 PM10:08 PM10:14 PM10:19 PM
15...............10:53 PM10:55 PM10:58 PM11:05 PM11:08 PM11:14 PM11:19 PM
+

a - Departs Radford College at 3:52pm. Operates School Days Only

+

wheelchair_icon- Wheelchair Accessible Bus

+
+ + + + + --- /dev/null +++ b/maxious-canberra-transit-feed/source-html/Route_16.html @@ -1,1 +1,611 @@ + + + + + + + +Route 16 + + + + + + + +
+

Chosen services: 16

+ +

View timetable and map

+ This timetable is effective from Monday 25 May 2009. +

Weekdays

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
 Cameron Ave Station
+ Platform 5
Lathlain St Station
+ Platform 6
Cohen St Station
+ Platform 5
Florey ShopsLatham ShopsKippax
167:01 AM7:03 AM7:07 AM7:12 AM7:18 AM7:27 AM
168:01 AM8:03 AM8:07 AM8:13 AM8:19 AM8:30 AM
168:57 AM8:59 AM9:03 AM9:09 AM9:15 AM9:26 AM
169:27 AM9:29 AM9:33 AM9:38 AM9:44 AM9:53 AM
169:57 AM9:59 AM10:03 AM10:08 AM10:14 AM10:23 AM
1610:27 AM10:29 AM10:33 AM10:38 AM10:44 AM10:53 AM
1610:57 AM10:59 AM11:03 AM11:08 AM11:14 AM11:23 AM
16wheelchair access11:27 AM11:29 AM11:33 AM11:38 AM11:44 AM11:53 AM
1611:57 AM11:59 AM12:03 PM12:08 PM12:14 PM12:23 PM
1612:27 PM12:29 PM12:33 PM12:38 PM12:44 PM12:53 PM
1612:57 PM12:59 PM1:03 PM1:08 PM1:14 PM1:23 PM
161:27 PM1:29 PM1:33 PM1:38 PM1:44 PM1:53 PM
161:57 PM1:59 PM2:03 PM2:08 PM2:14 PM2:23 PM
162:27 PM2:29 PM2:33 PM2:38 PM2:44 PM2:53 PM
162:57 PM2:59 PM3:03 PM3:09 PM3:15 PM3:26 PM
163:27 PM3:29 PM3:33 PM3:39 PM3:45 PM3:56 PM
163:57 PM3:59 PM4:03 PM4:09 PM4:15 PM4:26 PM
164:27 PM4:29 PM4:33 PM4:39 PM4:45 PM4:56 PM
164:47 PM4:49 PM4:53 PM4:59 PM5:05 PM5:16 PM
165:07 PM5:09 PM5:13 PM5:19 PM5:25 PM5:36 PM
165:27 PM5:29 PM5:33 PM5:39 PM5:45 PM5:56 PM
165:47 PM5:49 PM5:53 PM5:59 PM6:05 PM6:16 PM
166:02 PM6:04 PM6:08 PM6:14 PM6:20 PM6:31 PM
166:18 PM6:20 PM6:24 PM6:30 PM6:36 PM6:45 PM
167:18 PM7:20 PM7:23 PM7:27 PM7:33 PM7:42 PM
168:18 PM8:20 PM8:23 PM8:27 PM8:33 PM8:42 PM
169:18 PM9:20 PM9:23 PM9:27 PM9:33 PM9:42 PM
1610:18 PM10:20 PM10:23 PM10:27 PM10:33 PM10:42 PM
1611:18 PM11:20 PM11:23 PM11:27 PM11:33 PM11:42 PM
+

 

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
 KippaxLatham ShopsFlorey ShopsCohen St StationLathlain St StationCameron Ave Station
16wheelchair access6:10 AM6:19 AM6:25 AM6:30 AM6:32 AM6:36 AM
166:40 AM6:49 AM6:55 AM7:00 AM7:02 AM7:06 AM
16wheelchair access7:11 AM7:20 AM7:26 AM7:31 AM7:33 AM7:37 AM
167:30 AM7:41 AM7:47 AM7:53 AM7:55 AM7:59 AM
16wheelchair access7:50 AM8:01 AM8:07 AM8:13 AM8:15 AM8:19 AM
168:10 AM8:21 AM8:27 AM8:33 AM8:35 AM8:39 AM
168:30 AM8:41 AM8:47 AM8:53 AM8:55 AM8:59 AM
168:51 AM9:02 AM9:08 AM9:12 AM9:14 AM9:18 AM
169:25 AM9:35 AM9:40 AM9:44 AM9:46 AM9:50 AM
169:54 AM10:03 AM10:09 AM10:14 AM10:16 AM10:20 AM
1610:24 AM10:33 AM10:39 AM10:44 AM10:46 AM10:50 AM
1610:54 AM11:03 AM11:09 AM11:14 AM11:16 AM11:20 AM
1611:24 AM11:33 AM11:39 AM11:44 AM11:46 AM11:50 AM
1611:54 AM12:03 PM12:09 PM12:14 PM12:16 PM12:20 PM
16wheelchair access12:24 PM12:33 PM12:39 PM12:44 PM12:46 PM12:50 PM
1612:54 PM1:03 PM1:09 PM1:14 PM1:16 PM1:20 PM
161:24 PM1:33 PM1:39 PM1:44 PM1:46 PM1:50 PM
161:54 PM2:03 PM2:09 PM2:14 PM2:16 PM2:20 PM
162:24 PM2:33 PM2:39 PM2:44 PM2:46 PM2:50 PM
162:55 PM3:04 PM3:10 PM3:14 PM3:16 PM3:20 PM
163:24 PM3:35 PM3:41 PM3:47 PM3:49 PM3:53 PM
163:54 PM4:05 PM4:11 PM4:17 PM4:19 PM4:23 PM
164:27 PM4:38 PM4:44 PM4:50 PM4:52 PM4:56 PM
164:56 PM5:07 PM5:13 PM5:19 PM5:21 PM5:25 PM
165:26 PM5:37 PM5:43 PM5:49 PM5:51 PM5:55 PM
165:49 PM6:00 PM6:06 PM6:12 PM6:14 PM6:18 PM
166:49 PM6:58 PM7:04 PM7:08 PM7:10 PM7:13 PM
167:49 PM7:58 PM8:04 PM8:08 PM8:10 PM8:13 PM
168:49 PM8:58 PM9:04 PM9:08 PM9:10 PM9:13 PM
169:49 PM9:58 PM10:04 PM10:08 PM10:10 PM10:13 PM
1610:49 PM10:58 PM11:04 PM11:08 PM11:10 PM11:13 PM
+

wheelchair_icon- Wheelchair Accessible Bus

+
+ + + + + --- /dev/null +++ b/maxious-canberra-transit-feed/source-html/Route_17.html @@ -1,1 +1,781 @@ + + + + + + + + +Route 17 + + + + + + + + +
+

Chosen services: 17

+

View timetable and map

+ +This timetable is effective from Monday 25 May 2009. +

Weekdays

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
 KippaxHigginsHawker CollegeHawkerWeetangera ShopsCohen St StationLathlain St StationCameron Ave Station
176:01 AM6:06 AM6:12 AM6:17 AM6:20 AM6:25 AM6:27 AM6:31 AM
176:31 AM6:36 AM6:42 AM6:47 AM6:50 AM6:55 AM6:57 AM7:01 AM
177:01 AM7:06 AM7:12 AM7:17 AM7:20 AM7:25 AM7:27 AM7:31 AM
177:21 AM7:26 AM7:32 AM7:37 AM7:40 AM7:46 AM7:48 AM7:52 AM
177:41 AM7:47 AM7:53 AM7:58 AM8:01 AM8:07 AM8:09 AM8:13 AM
178:01 AM8:07 AM8:13 AM8:18 AM8:21 AM8:27 AM8:29 AM8:33 AM
178:21 AM8:27 AM8:33 AM8:38 AM8:41 AM8:47 AM8:49 AM8:53 AM
17wheelchair access8:41 AM8:47 AM8:53 AM8:58 AM9:01 AM9:07 AM9:09 AM9:13 AM
179:25 AM9:31 AM9:37 AM9:42 AM9:45 AM9:50 AM9:52 AM9:56 AM
179:56 AM10:01 AM10:07 AM10:12 AM10:15 AM10:20 AM10:22 AM10:26 AM
1710:26 AM10:31 AM10:37 AM10:42 AM10:45 AM10:50 AM10:52 AM10:56 AM
1710:56 AM11:01 AM11:07 AM11:12 AM11:15 AM11:20 AM11:22 AM11:26 AM
1711:26 AM11:31 AM11:37 AM11:42 AM11:45 AM11:50 AM11:52 AM11:56 AM
1711:56 AM12:01 PM12:07 PM12:12 PM12:15 PM12:20 PM12:22 PM12:26 PM
17wheelchair access12:26 PM12:31 PM12:37 PM12:42 PM12:45 PM12:50 PM12:52 PM12:56 PM
1712:56 PM1:01 PM1:07 PM1:12 PM1:15 PM1:20 PM1:22 PM1:26 PM
171:26 PM1:31 PM1:37 PM1:42 PM1:45 PM1:50 PM1:52 PM1:56 PM
171:56 PM2:01 PM2:07 PM2:12 PM2:15 PM2:20 PM2:22 PM2:26 PM
172:26 PM2:31 PM2:37 PM2:42 PM2:45 PM2:50 PM2:52 PM2:56 PM
172:55 PM3:00 PM3:06 PM3:11 PM3:14 PM3:20 PM3:22 PM3:26 PM
17..........3:25 PM3:30 PM3:33 PM3:39 PM3:41 PM3:45 PM
173:26 PM3:32 PM3:38 PM3:43 PM3:46 PM3:52 PM3:54 PM3:58 PM
173:47 PM3:53 PM3:59 PM4:04 PM4:07 PM4:13 PM4:15 PM4:19 PM
17..........4:03 PM4:08 PM4:11 PM4:17 PM4:19 PM4:23 PM
174:17 PM4:23 PM4:29 PM4:34 PM4:37 PM4:43 PM4:45 PM4:49 PM
174:47 PM4:53 PM4:59 PM5:04 PM5:07 PM5:13 PM5:15 PM5:19 PM
175:17 PM5:23 PM5:29 PM5:34 PM5:37 PM5:43 PM5:45 PM5:49 PM
175:47 PM5:53 PM5:59 PM6:04 PM6:07 PM6:13 PM6:15 PM6:19 PM
176:17 PM6:23 PM6:29 PM6:34 PM6:37 PM6:41 PM6:43 PM6:46 PM
176:57 PM7:02 PM7:08 PM7:13 PM7:16 PM7:20 PM7:22 PM7:25 PM
177:57 PM8:02 PM8:08 PM8:13 PM8:16 PM8:20 PM8:22 PM8:25 PM
178:57 PM9:02 PM9:08 PM9:13 PM9:16 PM9:20 PM9:22 PM9:25 PM
179:57 PM10:02 PM10:08 PM10:13 PM10:16 PM10:20 PM10:22 PM10:25 PM
1710:57 PM11:02 PM11:08 PM11:13 PM11:16 PM11:20 PM11:22 PM11:25 PM
+

 

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
 Cameron Ave Station
+ Platform 5
Lathlain St Station
+ Platform 6
Cohen St Station
+ Platform 5
Weetangera ShopsHawkerHawker CollegeHigginsKippax
177:07 AM7:09 AM7:13 AM7:17 AM7:20 AM7:25 AM7:30 AM7:37 AM
178:07 AM8:09 AM8:13 AM8:18 AM8:21 AM8:26 AM8:31 AM8:38 AM
178:37 AM8:39 AM8:43 AM8:48 AM8:51 AM8:56 AM9:01 AM9:08 AM
178:55 AM8:57 AM9:01 AM9:06 AM9:09 AM9:14 AM9:19 AM9:26 AM
17wheelchair access9:25 AM9:27 AM9:31 AM9:35 AM9:38 AM9:43 AM9:48 AM9:53 AM
179:55 AM9:57 AM10:01 AM10:05 AM10:08 AM10:13 AM10:18 AM10:23 AM
1710:25 AM10:27 AM10:31 AM10:35 AM10:38 AM10:43 AM10:48 AM10:53 AM
1710:55 AM10:57 AM11:01 AM11:05 AM11:08 AM11:13 AM11:18 AM11:23 AM
17wheelchair access11:25 AM11:27 AM11:31 AM11:35 AM11:38 AM11:43 AM11:48 AM11:53 AM
1711:55 AM11:57 AM12:01 PM12:05 PM12:08 PM12:13 PM12:18 PM12:23 PM
1712:25 PM12:27 PM12:31 PM12:35 PM12:38 PM12:43 PM12:48 PM12:53 PM
1712:55 PM12:57 PM1:01 PM1:05 PM1:08 PM1:13 PM1:18 PM1:23 PM
171:25 PM1:27 PM1:31 PM1:35 PM1:38 PM1:43 PM1:48 PM1:53 PM
171:55 PM1:57 PM2:01 PM2:05 PM2:08 PM2:13 PM2:18 PM2:23 PM
172:25 PM2:27 PM2:31 PM2:35 PM2:38 PM2:43 PM2:48 PM2:53 PM
172:55 PM2:57 PM3:01 PM3:06 PM3:09 PM3:14 PM3:20 PM3:27 PM
173:25 PM3:27 PM3:31 PM3:36 PM3:39 PM3:44 PM3:50 PM3:57 PM
173:54 PM3:56 PM4:00 PM4:05 PM4:08 PM4:13 PM4:19 PM4:26 PM
174:13 PM4:15 PM4:19 PM4:24 PM4:27 PM4:32 PM4:38 PM4:45 PM
174:33 PM4:35 PM4:39 PM4:44 PM4:47 PM4:52 PM4:58 PM5:05 PM
174:53 PM4:55 PM4:59 PM5:04 PM5:07 PM5:12 PM5:18 PM5:25 PM
175:13 PM5:15 PM5:19 PM5:24 PM5:27 PM5:32 PM5:38 PM5:45 PM
175:33 PM5:35 PM5:39 PM5:44 PM5:47 PM5:52 PM5:58 PM6:05 PM
175:53 PM5:55 PM5:59 PM6:04 PM6:07 PM6:12 PM6:18 PM6:25 PM
176:24 PM6:26 PM6:30 PM6:34 PM6:37 PM6:42 PM6:47 PM6:52 PM
177:24 PM7:26 PM7:29 PM7:33 PM7:36 PM7:41 PM7:46 PM7:51 PM
178:24 PM8:26 PM8:29 PM8:33 PM8:36 PM8:41 PM8:46 PM8:51 PM
179:24 PM9:26 PM9:29 PM9:33 PM9:36 PM9:41 PM9:46 PM9:51 PM
1710:24 PM10:26 PM10:29 PM10:33 PM10:36 PM10:41 PM10:46 PM10:51 PM
1711:24 PM11:26 PM11:29 PM11:33 PM11:36 PM11:41 PM11:46 PM11:51 PM
+

wheelchair_icon- Wheelchair Accessible Bus

+
+ + + + --- /dev/null +++ b/maxious-canberra-transit-feed/source-html/Route_170.html @@ -1,1 +1,91 @@ + + + + + + + + +Route_170 + + + + + +
+

Chosen services: 170

+ +

View timetable and map

+

This timetable is effective from Monday 25 May 2009.

+ +

Weekdays

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + +
 Erindale Dr/ Charleston St MonashGowrieErindale Drive/SternbergWoden Interchange
+ Platform 9
City Interchange
+ Platform 3
Citywest
1707:10 AM7:20 AM7:32 AM7:49 AM8:04 AM8:06 AM
1707:28 AM7:38 AM7:50 AM8:07 AM8:22 AM8:24 AM
+

 

+ + + + + + + + + + + + + + + + + + + +
 CitywestCity Interchange
+ Platform 1
Woden Interchange
+ Platform 12
Erindale Drive/SternbergGowrieErindale Dr/ Charleston St Monash
1705:00 PM5:05 PM5:21 PM5:36 PM5:46 PM5:56 PM
+
+ + + + + --- /dev/null +++ b/maxious-canberra-transit-feed/source-html/Route_18_318.html @@ -1,1 +1,887 @@ + + + + + + + + +Route 18, 318 + + + + + + + +
+

Chosen services: 18, 318

+ +

View timetable and map

+ This timetable is effective from Monday 25 May 2009. +

Weekdays

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
 Lanyon Market PlaceGordon PrimaryLewis Luxton/Woodcock DrWoodcock/Clare DennisTuggeranong Interchange
+ Platform 8
Woden Interchange
+ Platform 9
City Interchange
+ Platform 5
Cameron Ave StationLathlain St StationCohen St Station
318wheelchair access5:43 AM5:52 AM5:53 AM5:56 AM6:08 AM6:26 AM6:42 AM6:59 AM7:01 AM7:05 AM
318 wheelchair access6:13 AM6:22 AM6:23 AM6:26 AM6:38 AM6:56 AM7:12 AM7:29 AM7:31 AM7:35 AM
318 wheelchair access6:33 AM6:42 AM6:43 AM6:46 AM6:58 AM7:16 AM7:32 AM7:50 AM7:52 AM7:56 AM
318 wheelchair access6:55 AM7:04 AM7:05 AM7:08 AM7:20 AM7:38 AM7:56 AM8:14 AM8:16 AM8:20 AM
318 wheelchair access7:14 AM7:23 AM7:24 AM7:27 AM7:40 AM7:59 AM8:17 AM8:35 AM8:37 AM8:41 AM
318 wheelchair access7:31 AM7:41 AM7:42 AM7:46 AM8:00 AM8:19 AM8:37 AM8:55 AM8:57 AM9:01 AM
18a..........7:46 AM7:50 AM7:58 AM.........................
318 wheelchair access7:51 AM8:01 AM8:02 AM8:06 AM8:20 AM8:39 AM8:57 AM9:15 AM9:17 AM9:21 AM
318 wheelchair access8:11 AM8:21 AM8:22 AM8:26 AM8:40 AM8:59 AM9:17 AM9:35 AM9:37 AM9:41 AM
318 wheelchair access8:36 AM8:46 AM8:47 AM8:51 AM9:05 AM9:24 AM9:41 AM9:58 AM10:00 AM10:04 AM
318 wheelchair access9:06 AM9:16 AM9:17 AM9:21 AM9:35 AM9:53 AM10:09 AM10:26 AM10:28 AM10:32 AM
318 wheelchair access9:40 AM9:49 AM9:50 AM9:53 AM10:05 AM10:23 AM10:39 AM10:56 AM10:58 AM11:02 AM
318 wheelchair access10:10 AM10:19 AM10:20 AM10:23 AM10:35 AM10:53 AM11:09 AM11:26 AM11:28 AM11:32 AM
318 wheelchair access10:40 AM10:49 AM10:50 AM10:53 AM11:05 AM11:23 AM11:39 AM11:56 AM11:58 AM12:02 PM
318 wheelchair access11:10 AM11:19 AM11:20 AM11:23 AM11:35 AM11:53 AM12:09 PM12:26 PM12:28 PM12:32 PM
318 wheelchair access11:40 AM11:49 AM11:50 AM11:53 AM12:05 PM12:23 PM12:39 PM12:56 PM12:58 PM1:02 PM
318 wheelchair access12:10 PM12:19 PM12:20 PM12:23 PM12:35 PM12:53 PM1:09 PM1:26 PM1:28 PM1:32 PM
318 wheelchair access12:40 PM12:49 PM12:50 PM12:53 PM1:05 PM1:23 PM1:39 PM1:56 PM1:58 PM2:02 PM
318 wheelchair access1:10 PM1:19 PM1:20 PM1:23 PM1:35 PM1:53 PM2:09 PM2:26 PM2:28 PM2:32 PM
318wheelchair access1:40 PM1:49 PM1:50 PM1:53 PM2:05 PM2:23 PM2:39 PM2:56 PM2:58 PM3:02 PM
318 wheelchair access2:10 PM2:19 PM2:20 PM2:23 PM2:35 PM2:53 PM3:10 PM3:28 PM3:30 PM3:34 PM
318 wheelchair access2:39 PM2:48 PM2:49 PM2:52 PM3:04 PM3:23 PM3:41 PM3:59 PM4:01 PM4:05 PM
318wheelchair access3:06 PM3:16 PM3:17 PM3:21 PM3:35 PM3:54 PM4:12 PM4:30 PM4:32 PM4:36 PM
318 wheelchair access3:31 PM3:41 PM3:42 PM3:46 PM4:00 PM4:19 PM4:37 PM4:55 PM4:57 PM5:01 PM
318wheelchair access4:00 PM4:10 PM4:11 PM4:15 PM4:29 PM4:48 PM5:06 PM5:24 PM5:26 PM5:30 PM
318 wheelchair access4:35 PM4:45 PM4:46 PM4:50 PM5:04 PM5:23 PM5:41 PM5:59 PM6:01 PM6:05 PM
18 wheelchair access5:15 PM5:25 PM5:26 PM5:30 PM5:40 PM.........................
18 wheelchair access5:45 PM5:55 PM5:56 PM6:00 PM6:10 PM.........................
318 wheelchair access6:15 PM6:25 PM6:26 PM6:30 PM6:42 PM6:59 PM7:14 PM7:31 PM7:33 PM7:36 PM
187:13 PM7:22 PM7:23 PM7:26 PM7:34 PM.........................
188:14 PM8:23 PM8:24 PM8:27 PM8:35 PM.........................
189:14 PM9:23 PM9:24 PM9:27 PM9:35 PM.........................
1810:14 PM10:23 PM10:24 PM10:27 PM10:35 PM.........................
18 wheelchair access11:14 PM11:23 PM11:24 PM11:27 PM11:35 PM.........................
+

a - Operates School Days Only

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
 Cohen St Station
+ Platform 2
Lathlain St Station
+ Platform 1
Cameron Ave Station
+ Platform 1
City Interchange
+ Platform 1
Woden Interchange
+ Platform 6
Tuggeranong Interchange
+ Platform 7
Woodcock/Clare DennisGordon PrimaryLanyon Market Place
18.........................7:14 AM7:22 AM7:26 AM7:36 AM
18 wheelchair access.........................7:40 AM7:50 AM7:55 AM8:05 AM
318 wheelchair access7:23 AM7:25 AM7:29 AM7:48 AM8:05 AM8:23 AM8:33 AM8:38 AM8:48 AM
318 wheelchair access7:53 AM7:55 AM7:59 AM8:18 AM8:35 AM8:53 AM9:03 AM9:08 AM9:18 AM
18 wheelchair access.........................9:16 AM9:26 AM9:31 AM9:40 AM
18.........................9:49 AM9:57 AM10:01 AM10:10 AM
318 wheelchair access9:23 AM9:25 AM9:29 AM9:46 AM10:03 AM10:19 AM10:27 AM10:31 AM10:40 AM
318 wheelchair access9:53 AM9:55 AM9:59 AM10:16 AM10:33 AM10:49 AM10:57 AM11:01 AM11:10 AM
318 wheelchair access10:23 AM10:25 AM10:29 AM10:46 AM11:03 AM11:19 AM11:27 AM11:31 AM11:40 AM
318 wheelchair access10:53 AM10:55 AM10:59 AM11:16 AM11:33 AM11:49 AM11:57 AM12:01 PM12:10 PM
318 wheelchair access11:23 AM11:25 AM11:29 AM11:46 AM12:03 PM12:19 PM12:27 PM12:31 PM12:40 PM
318 wheelchair access11:53 AM11:55 AM11:59 AM12:16 PM12:33 PM12:49 PM12:57 PM1:01 PM1:10 PM
318 wheelchair access12:23 PM12:25 PM12:29 PM12:46 PM1:03 PM1:19 PM1:27 PM1:31 PM1:40 PM
318 wheelchair access12:53 PM12:55 PM12:59 PM1:16 PM1:33 PM1:49 PM1:57 PM2:01 PM2:10 PM
318 wheelchair access1:23 PM1:25 PM1:29 PM1:46 PM2:03 PM2:19 PM2:27 PM2:31 PM2:40 PM
318 wheelchair access1:53 PM1:55 PM1:59 PM2:16 PM2:33 PM2:49 PM2:57 PM3:01 PM3:10 PM
318 wheelchair access2:23 PM2:25 PM2:29 PM2:46 PM3:03 PM3:23 PM3:31 PM3:35 PM3:44 PM
318wheelchair access 2:53 PM2:55 PM2:59 PM3:18 PM3:35 PM3:55 PM4:03 PM4:07 PM4:16 PM
318wheelchair access 3:23 PM3:25 PM3:29 PM3:48 PM4:05 PM4:25 PM4:33 PM4:37 PM4:46 PM
318 wheelchair access3:53 PM3:55 PM3:59 PM4:18 PM4:35 PM4:55 PM5:03 PM5:07 PM5:16 PM
318 wheelchair access4:23 PM4:25 PM4:29 PM4:48 PM5:05 PM5:25 PM5:33 PM5:37 PM5:46 PM
318 wheelchair access4:43 PM4:45 PM4:49 PM5:08 PM5:25 PM5:45 PM5:53 PM5:57 PM6:06 PM
318 wheelchair access5:03 PM5:05 PM5:09 PM5:28 PM5:45 PM6:05 PM6:13 PM6:17 PM6:26 PM
318 wheelchair access5:18 PM5:20 PM5:24 PM5:43 PM6:00 PM6:20 PM6:28 PM6:32 PM6:41 PM
318 wheelchair access5:53 PM5:55 PM5:59 PM6:18 PM6:34 PM6:50 PM6:58 PM7:02 PM7:11 PM
318 wheelchair access6:23 PM6:25 PM6:29 PM6:45 PM6:59 PM7:15 PM7:23 PM7:27 PM7:36 PM
318 wheelchair access6:54 PM6:56 PM6:59 PM7:15 PM7:29 PM7:45 PM7:53 PM7:57 PM8:06 PM
18.........................8:48 PM8:56 PM9:00 PM9:09 PM
18.........................9:48 PM9:56 PM10:00 PM10:09 PM
18 wheelchair access.........................10:48 PM10:56 PM11:00 PM11:09 PM
+

wheelchair_icon- Wheelchair Accessible Bus

+
+ + + + + --- /dev/null +++ b/maxious-canberra-transit-feed/source-html/Route_19_319.html @@ -1,1 +1,932 @@ + + + + + + + + +Route 19, 319 + + + + + + + +
+

Chosen services: 19, 319

+ +

View timetable and map

+ This timetable is effective from Monday 25 May 2009. +

Weekdays

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
 Cohen St Station
+ Platform 2
Lathlain St Station
+ Platform 1
Cameron Ave Station
+ Platform 1
City Interchange
+ Platform 1
Woden Interchange
+ Platform 6
Tuggeranong Interchange
+ Platform 4
Bonython PrimarySt Clare of Conder Conder PrimaryLanyon Market Place
19 wheelchair access.........................7:05 AM7:11 AM7:16 AM7:25 AM7:31 AM
19 wheelchair access.........................7:40 AM7:47 AM7:54 AM8:03 AM8:10 AM
319 wheelchair access7:03 AM7:05 AM7:09 AM7:26 AM7:43 AM8:01 AM8:08 AM8:15 AM8:24 AM8:31 AM
319 wheelchair access7:33 AM7:35 AM7:39 AM7:58 AM8:15 AM8:33 AM8:40 AM8:47 AM8:56 AM9:03 AM
19 wheelchair access.........................9:04 AM9:11 AM9:18 AM9:27 AM9:33 AM
19wheelchair access.........................9:33 AM9:39 AM9:44 AM9:53 AM9:59 AM
319 wheelchair access9:03 AM9:05 AM9:09 AM9:28 AM9:45 AM10:01 AM10:07 AM10:12 AM10:21 AM10:27 AM
319 wheelchair access9:33 AM9:35 AM9:39 AM9:56 AM10:13 AM10:29 AM10:35 AM10:40 AM10:49 AM10:55 AM
319wheelchair access10:03 AM10:05 AM10:09 AM10:26 AM10:43 AM10:59 AM11:05 AM11:10 AM11:19 AM11:25 AM
319 wheelchair access10:33 AM10:35 AM10:39 AM10:56 AM11:13 AM11:29 AM11:35 AM11:40 AM11:49 AM11:55 AM
319 wheelchair access11:03 AM11:05 AM11:09 AM11:26 AM11:43 AM11:59 AM12:05 PM12:10 PM12:19 PM12:25 PM
319 wheelchair access11:33 AM11:35 AM11:39 AM11:56 AM12:13 PM12:29 PM12:35 PM12:40 PM12:49 PM12:55 PM
319wheelchair access12:03 PM12:05 PM12:09 PM12:26 PM12:43 PM12:59 PM1:05 PM1:10 PM1:19 PM1:25 PM
319 wheelchair access12:33 PM12:35 PM12:39 PM12:56 PM1:13 PM1:29 PM1:35 PM1:40 PM1:49 PM1:55 PM
319 wheelchair access1:03 PM1:05 PM1:09 PM1:26 PM1:43 PM1:59 PM2:05 PM2:10 PM2:19 PM2:25 PM
319wheelchair access1:33 PM1:35 PM1:39 PM1:56 PM2:13 PM2:29 PM2:35 PM2:40 PM2:49 PM2:55 PM
319 wheelchair access2:03 PM2:05 PM2:09 PM2:26 PM2:43 PM2:59 PM3:06 PM3:13 PM3:22 PM3:29 PM
319wheelchair access2:33 PM2:35 PM2:39 PM2:56 PM3:13 PM3:33 PM3:40 PM3:47 PM3:56 PM4:03 PM
319 wheelchair access....................3:30 PM3:50 PM3:57 PM4:04 PM4:13 PM4:20 PM
319 wheelchair access3:03 PM3:05 PM3:09 PM3:28 PM3:45 PM4:05 PM4:12 PM4:19 PM4:28 PM4:35 PM
319 wheelchair access3:33 PM3:35 PM3:39 PM3:58 PM4:15 PM4:35 PM4:42 PM4:49 PM4:58 PM5:05 PM
319 wheelchair access4:03 PM4:05 PM4:09 PM4:28 PM4:45 PM5:05 PM5:12 PM5:19 PM5:28 PM5:35 PM
319 wheelchair access4:33 PM4:35 PM4:39 PM4:58 PM5:15 PM5:35 PM5:42 PM5:49 PM5:58 PM6:05 PM
319 wheelchair access4:53 PM4:55 PM4:59 PM5:18 PM5:35 PM5:55 PM6:02 PM6:09 PM6:18 PM6:25 PM
319 wheelchair access5:13 PM5:15 PM5:19 PM5:38 PM5:55 PM6:15 PM6:22 PM6:29 PM6:38 PM6:44 PM
319 wheelchair access5:33 PM5:35 PM5:39 PM5:58 PM6:15 PM6:34 PM6:40 PM6:45 PM6:54 PM7:00 PM
319 wheelchair access6:03 PM6:05 PM6:09 PM6:28 PM6:42 PM6:58 PM7:04 PM7:09 PM7:18 PM7:24 PM
319 wheelchair access6:34 PM6:36 PM6:39 PM6:55 PM7:09 PM7:25 PM7:31 PM7:36 PM7:45 PM7:51 PM
19.........................8:18 PM8:24 PM8:29 PM8:38 PM8:44 PM
19.........................9:18 PM9:24 PM9:29 PM9:38 PM9:44 PM
19.........................10:18 PM10:24 PM10:29 PM10:38 PM10:44 PM
19wheelchair access.........................11:18 PM11:24 PM11:29 PM11:38 PM11:44 PM
+

 

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
 Lanyon Market PlaceConder PrimarySt Clare of Conder Bonython PrimaryTuggeranong Interchange
+ Platform 8
Woden Interchange
+ Platform 9
City Interchange
+ Platform 5
Cameron Ave StationLathlain St StationCohen St Station
319 wheelchair access5:50 AM5:56 AM6:06 AM6:12 AM6:25 AM6:43 AM6:59 AM7:16 AM7:18 AM7:22 AM
319 wheelchair access6:20 AM6:26 AM6:36 AM6:42 AM6:55 AM7:13 AM7:29 AM7:47 AM7:49 AM7:53 AM
319 wheelchair access6:40 AM6:46 AM6:56 AM7:02 AM7:15 AM7:33 AM7:51 AM8:09 AM8:11 AM8:15 AM
319 wheelchair access7:00 AM7:06 AM7:16 AM7:22 AM7:35 AM7:54 AM8:12 AM8:30 AM8:32 AM8:36 AM
319 wheelchair access7:17 AM7:23 AM7:33 AM7:41 AM7:55 AM8:14 AM8:32 AM8:50 AM8:52 AM8:56 AM
19a wheelchair access7:24 AM7:30 AM7:40 AM7:48 AM7:58 AM.........................
319 wheelchair access7:36 AM7:43 AM7:53 AM8:01 AM8:15 AM8:34 AM8:52 AM9:10 AM9:12 AM9:16 AM
319 wheelchair access7:55 AM8:02 AM8:12 AM8:20 AM8:34 AM8:53 AM9:11 AM9:29 AM9:31 AM9:35 AM
319 wheelchair access8:16 AM8:23 AM8:33 AM8:41 AM8:55 AM9:14 AM9:32 AM9:49 AM9:51 AM9:55 AM
319 wheelchair access8:46 AM8:53 AM9:03 AM9:11 AM9:25 AM9:43 AM9:59 AM10:16 AM10:18 AM10:22 AM
319 wheelchair access9:19 AM9:26 AM9:36 AM9:42 AM9:55 AM10:13 AM10:29 AM10:46 AM10:48 AM10:52 AM
319 wheelchair access9:50 AM9:56 AM10:06 AM10:12 AM10:25 AM10:43 AM10:59 AM11:16 AM11:18 AM11:22 AM
319 wheelchair access10:20 AM10:26 AM10:36 AM10:42 AM10:55 AM11:13 AM11:29 AM11:46 AM11:48 AM11:52 AM
319 wheelchair access10:50 AM10:56 AM11:06 AM11:12 AM11:25 AM11:43 AM11:59 AM12:16 PM12:18 PM12:22 PM
319 wheelchair access11:20 AM11:26 AM11:36 AM11:42 AM11:55 AM12:13 PM12:29 PM12:46 PM12:48 PM12:52 PM
319 wheelchair access11:50 AM11:56 AM12:06 PM12:12 PM12:25 PM12:43 PM12:59 PM1:16 PM1:18 PM1:22 PM
319 wheelchair access12:20 PM12:26 PM12:36 PM12:42 PM12:55 PM1:13 PM1:29 PM1:46 PM1:48 PM1:52 PM
319 wheelchair access12:50 PM12:56 PM1:06 PM1:12 PM1:25 PM1:43 PM1:59 PM2:16 PM2:18 PM2:22 PM
319 wheelchair access1:20 PM1:26 PM1:36 PM1:42 PM1:55 PM2:13 PM2:29 PM2:46 PM2:48 PM2:52 PM
319 wheelchair access1:50 PM1:56 PM2:06 PM2:12 PM2:25 PM2:43 PM2:59 PM3:17 PM3:19 PM3:23 PM
319wheelchair access2:19 PM2:25 PM2:35 PM2:41 PM2:54 PM3:13 PM3:31 PM3:49 PM3:51 PM3:55 PM
319 wheelchair access2:46 PM2:52 PM3:02 PM3:10 PM3:24 PM3:43 PM4:01 PM4:19 PM4:21 PM4:25 PM
19wheelchair access3:20 PM3:27 PM3:37 PM3:45 PM3:55 PM.........................
19wheelchair access3:52 PM3:59 PM4:09 PM4:17 PM4:27 PM.........................
194:24 PM4:31 PM4:41 PM4:49 PM4:59 PM.........................
194:54 PM5:01 PM5:11 PM5:19 PM5:29 PM.........................
195:24 PM5:31 PM5:41 PM5:49 PM5:59 PM.........................
195:56 PM6:03 PM6:13 PM6:21 PM6:31 PM.........................
196:54 PM7:00 PM7:10 PM7:16 PM7:25 PM.........................
197:54 PM8:00 PM8:10 PM8:16 PM8:25 PM.........................
19wheelchair access8:49 PM8:55 PM9:05 PM9:11 PM9:20 PM.........................
199:49 PM9:55 PM10:05 PM10:11 PM10:20 PM.........................
1910:49 PM10:55 PM11:05 PM11:11 PM11:20 PM.........................
+

a - Operates School Days Only

+

wheelchair_icon- Wheelchair Accessible Bus

+
+ + + + + --- /dev/null +++ b/maxious-canberra-transit-feed/source-html/Route_2.html @@ -1,1 +1,1341 @@ + + + + + + + + +Route 2 + + + + + + + +
+

Chosen services: 2

+ +

View timetable and map

+ +

Weekdays

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
 Woden Interchange
+ Platform 4
CurtinJohn James HospitalYarralumla ShopsDeakinParliament HouseKings Ave/National CrtCity Interchange
+ Platform 10
Olims HotelAinslieHackettDickson
2...................................7:03 AM7:10 AM7:15 AM7:23 AM7:28 AM
2wheelchair access6:53 AM7:04 AM7:08 AM7:11 AM7:15 AM7:19 AM7:23 AM7:33 AM7:40 AM7:46 AM7:54 AM8:01 AM
2wheelchair access7:08 AM7:19 AM7:23 AM7:26 AM7:30 AM7:34 AM7:38 AM7:49 AM7:56 AM8:02 AM8:10 AM8:17 AM
27:19 AM7:30 AM7:34 AM7:37 AM7:41 AM7:45 AM7:49 AM8:00 AM8:07 AM8:13 AM8:21 AM8:28 AM
2wheelchair access7:38 AM7:49 AM7:54 AM7:58 AM8:03 AM8:08 AM8:14 AM8:30 AM8:38 AM8:45 AM8:53 AM8:59 AM
27:53 AM8:04 AM8:08 AM8:12 AM8:17 AM8:23 AM8:26 AM8:43 AM8:49 AM8:54 AM9:02AM9:10 AM
28:08 AM8:19 AM8:23 AM8:26 AM8:30 AM8:34 AM8:38 AM8:49 AM8:56 AM9:02 AM9:10 AM9:17 AM
28:23 AM8:34 AM8:38 AM8:41 AM8:45 AM8:49 AM8:53 AM9:04 AM9:11 AM9:17 AM9:25 AM9:31 AM
28:38 AM8:51 AM8:55 AM8:58 AM9:03 AM9:08 AM9:14 AM9:26 AM....................
28:53 AM9:04 AM9:08 AM9:11 AM9:15 AM9:19 AM9:23 AM9:34 AM9:41 AM9:46 AM9:54 AM9:59 AM
29:23 AM9:34 AM9:38 AM9:41 AM9:45 AM9:49 AM9:53 AM10:04 AM10:11 AM10:16 AM10:24 AM10:29 AM
29:53 AM10:04 AM10:08 AM10:11 AM10:15 AM10:19 AM10:23 AM10:34 AM10:41 AM10:46 AM10:54 AM10:59 AM
210:23 AM10:34 AM10:38 AM10:41 AM10:45 AM10:49 AM10:53 AM11:04 AM11:11 AM11:16 AM11:24 AM11:29 AM
210:53 AM11:04 AM11:08 AM11:11 AM11:15 AM11:19 AM11:23 AM11:34 AM11:41 AM11:46 AM11:54 AM11:59 AM
211:23 AM11:34 AM11:38 AM11:41 AM11:45 AM11:49 AM11:53 AM12:04 PM12:11 PM12:16 PM12:24 PM12:29 PM
211:53 AM12:04 PM12:08 PM12:11 PM12:15 PM12:19 PM12:23 PM12:34 PM12:41 PM12:46 PM12:54 PM12:59 PM
212:23 PM12:34 PM12:38 PM12:41 PM12:45 PM12:49 PM12:53 PM1:04 PM1:11 PM1:16 PM1:24 PM1:29 PM
212:53 PM1:04 PM1:08 PM1:11 PM1:15 PM1:19 PM1:23 PM1:34 PM1:41 PM1:46 PM1:54 PM1:59 PM
21:23 PM1:34 PM1:38 PM1:41 PM1:45 PM1:49 PM1:53 PM2:04 PM2:11 PM2:16 PM2:24 PM2:29 PM
21:53 PM2:04 PM2:08 PM2:11 PM2:15 PM2:19 PM2:23 PM2:34 PM2:41 PM2:46 PM2:54 PM2:59 PM
22:23 PM2:34 PM2:38 PM2:41 PM2:45 PM2:49 PM2:53 PM3:04 PM3:11 PM3:16 PM3:24 PM3:29 PM
2wheelchair access2:38 PM2:49 PM2:53 PM2:56 PM3:00 PM3:04 PM3:08 PM3:19 PM3:26 PM3:32 PM3:40 PM3:47 PM
22:53 PM3:04 PM3:08 PM3:11 PM3:15 PM3:19 PM3:23 PM3:34 PM3:41 PM3:46 PM3:54 PM3:59 PM
23:08 PM3:19 PM3:23 PM3:26 PM3:30 PM3:34 PM3:38 PM3:49 PM3:56 PM4:02 PM4:10 PM4:17 PM
23:23 PM3:34 PM3:38 PM3:41 PM3:45 PM3:49 PM3:53 PM4:04 PM4:11 PM4:17 PM4:25 PM4:32 PM
23:38 PM3:49 PM3:53 PM3:56 PM4:00 PM4:04 PM4:08 PM4:19 PM4:26 PM4:32 PM4:40 PM4:47 PM
23:53 PM4:04 PM4:08 PM4:11 PM4:15 PM4:19 PM4:23 PM4:34 PM4:41 PM4:47 PM4:55 PM5:02 PM
24:08 PM4:19 PM4:23 PM4:26 PM4:30 PM4:34 PM4:38 PM4:49 PM4:56 PM5:02 PM5:10 PM5:17 PM
24:23 PM4:34 PM4:38 PM4:41 PM4:45 PM4:49 PM4:53 PM5:04 PM5:11 PM5:17 PM5:25 PM5:32 PM
24:38 PM4:49 PM4:53 PM4:56 PM5:00 PM5:04 PM5:08 PM5:19 PM5:26 PM5:32 PM5:40 PM5:47 PM
24:53 PM5:04 PM5:08 PM5:11 PM5:15 PM5:19 PM5:23 PM5:34 PM5:41 PM5:47 PM5:55 PM6:02 PM
25:08 PM5:19 PM5:23 PM5:26 PM5:30 PM5:34 PM5:38 PM5:49 PM5:56 PM6:02 PM6:10 PM6:17 PM
25:23 PM5:34 PM5:38 PM5:41 PM5:45 PM5:49 PM5:53 PM6:04 PM6:11 PM6:17 PM6:25 PM6:31 PM
25:38 PM5:49 PM5:53 PM5:56 PM6:00 PM6:04 PM6:08 PM6:19 PM6:26 PM6:32 PM6:38 PM6:42 PM
25:53 PM6:04 PM6:08 PM6:11 PM6:15 PM6:19 PM6:23 PM6:34 PM6:39 PM6:44 PM6:50 PM6:54 PM
26:40 PM6:50 PM6:53 PM6:56 PM7:00 PM7:03 PM7:07 PM7:17 PM7:22 PM7:27 PM7:33 PM7:37 PM
27:40 PM7:50 PM7:53 PM7:56 PM8:00 PM8:03 PM8:07 PM8:17 PM8:22 PM8:27 PM8:33 PM8:37 PM
28:40 PM8:50 PM8:53 PM8:56 PM9:00 PM9:03 PM9:07 PM9:17 PM9:22 PM9:27 PM9:33 PM9:37 PM
29:40 PM9:50 PM9:53 PM9:56 PM10:00 PM10:03 PM10:07 PM10:17 PM10:22 PM10:27 PM10:33 PM10:37 PM
210:40 PM10:50 PM10:53 PM10:56 PM11:00 PM11:03 PM11:07 PM11:17 PM11:22 PM11:27 PM11:33 PM11:37 PM
+

 

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
 Lyneham HighDicksonHackettAinslieOlims HotelCity Interchange
+ Platform 3
Kings Ave/National CrtParliament HouseDeakinYarralumla ShopsJohn James HospitalCurtinWoden Interchange
2.....6:34 AM6:39 AM6:47 AM6:53 AM7:02 AM7:11 AM7:15 AM7:20 AM7:24 AM7:27 AM7:31 AM7:42 AM
2.....7:04 AM7:09 AM7:17 AM7:23 AM7:32 AM7:42 AM7:47 AM7:52 AM7:56 AM7:59 AM8:03 AM8:14 AM
2.....7:19 AM7:24 AM7:32 AM7:38 AM7:47 AM7:57 AM8:02 AM8:07 AM8:11 AM8:14 AM8:18 AM8:29 AM
2.....7:32 AM7:39 AM7:47 AM7:53 AM8:02 AM8:12 AM8:17 AM8:22 AM8:26 AM8:29 AM8:33 AM8:44 AM
2wheelchair access.....7:49 AM7:56 AM8:04 AM8:10 AM8:19 AM8:29 AM8:34 AM8:39 AM8:43 AM8:46 AM8:50 AM9:01 AM
2.....8:02 AM8:09 AM8:17 AM8:25 AM8:38 AM8:47 AM8:53 AM8:57 AM9:01 AM9:05 AM9:08 AM9:21 AM
2.....8:19 AM8:26 AM8:34 AM8:40 AM8:48 AM...................................
2.....8:32 AM8:39 AM8:47 AM8:53 AM9:02 AM9:12 AM9:17 AM9:22 AM9:26 AM9:29 AM9:33 AM9:44 AM
2.....8:49 AM8:56 AM9:04 AM9:10 AM9:18 AM...................................
2.....9:04 AM9:09 AM9:17 AM9:23 AM9:32 AM9:42 AM9:46 AM9:51 AM9:55 AM9:58 AM10:02 AM10:13 AM
2.....9:34 AM9:39 AM9:47 AM9:53 AM10:02 AM10:12 AM10:16 AM10:21 AM10:25 AM10:28 AM10:32 AM10:43 AM
2wheelchair access.....10:04 AM10:09 AM10:17 AM10:23 AM10:32 AM10:42 AM10:46 AM10:51 AM10:55 AM10:58 AM11:02 AM11:13 AM
2.....10:34 AM10:39 AM10:47 AM10:53 AM11:02 AM11:12 AM11:16 AM11:21 AM11:25 AM11:28 AM11:32 AM11:43 AM
2.....11:04 AM11:09 AM11:17 AM11:23 AM11:32 AM11:42 AM11:46 AM11:51 AM11:55 AM11:58 AM12:02 PM12:13 PM
2.....11:34 AM11:39 AM11:47 AM11:53 AM12:02 PM12:12 PM12:16 PM12:21 PM12:25 PM12:28 PM12:32 PM12:43 PM
2.....12:04 PM12:09 PM12:17 PM12:23 PM12:32 PM12:42 PM12:46 PM12:51 PM12:55 PM12:58 PM1:02 PM1:13 PM
2.....12:34 PM12:39 PM12:47 PM12:53 PM1:02 PM1:12 PM1:16 PM1:21 PM1:25 PM1:28 PM1:32 PM1:43 PM
2.....1:04 PM1:09 PM1:17 PM1:23 PM1:32 PM1:42 PM1:46 PM1:51 PM1:55 PM1:58 PM2:02 PM2:13 PM
2.....1:34 PM1:39 PM1:47 PM1:53 PM2:02 PM2:12 PM2:16 PM2:21 PM2:25 PM2:28 PM2:32 PM2:43 PM
2.....2:04 PM2:09 PM2:17 PM2:23 PM2:32 PM2:42 PM2:46 PM2:51 PM2:55 PM2:58 PM3:02 PM3:13 PM
2.....2:34 PM2:39 PM2:47 PM2:53 PM3:02 PM3:12 PM3:16 PM3:21 PM3:25 PM3:28 PM3:32 PM3:43 PM
2.....2:49 PM2:54 PM3:02 PM3:08 PM3:17 PM3:27 PM3:32 PM3:37 PM3:41 PM3:44 PM3:48 PM3:59 PM
2.....3:04 PM3:09 PM3:17 PM3:23 PM3:32 PM3:42 PM3:46 PM3:51 PM3:55 PM3:58 PM4:02 PM4:13 PM
23:13 PM3:19 PM3:26 PM3:34 PM3:40 PM3:49 PM3:59 PM4:04 PM4:09 PM4:13 PM4:16 PM4:20 PM4:31 PM
2.....3:32 PM3:39 PM3:47 PM3:53 PM4:02 PM4:12 PM4:17 PM4:22 PM4:26 PM4:29 PM4:33 PM4:44 PM
2wheelchair access.....3:49 PM3:56 PM4:04 PM4:10 PM4:19 PM4:29 PM4:34 PM4:39 PM4:43 PM4:46 PM4:50 PM5:01 PM
2.....4:02 PM4:09 PM4:17 PM4:23 PM4:32 PM4:42 PM4:47 PM4:52 PM4:56 PM4:59 PM5:03 PM5:14 PM
2.....4:19 PM4:26 PM4:34 PM4:40 PM4:49 PM4:59 PM5:04 PM5:09 PM5:13 PM5:16 PM5:20 PM5:31 PM
2.....4:32 PM4:39 PM4:47 PM4:53 PM5:02 PM5:12 PM5:17 PM5:22 PM5:26 PM5:29 PM5:33 PM5:44 PM
2.....4:49 PM4:56 PM5:04 PM5:10 PM5:19 PM5:29 PM5:34 PM5:39 PM5:43 PM5:46 PM5:50 PM6:01 PM
2.....5:02 PM5:09 PM5:17 PM5:23 PM5:32 PM5:42 PM5:47 PM5:52 PM5:56 PM5:59 PM6:03 PM6:14 PM
2.....5:19 PM5:26 PM5:34 PM5:40 PM5:49 PM5:59 PM6:04 PM6:09 PM6:13 PM6:16 PM6:20 PM6:31 PM
2.....5:32 PM5:39 PM5:47 PM5:53 PM6:02 PM6:12 PM6:17 PM6:22 PM6:26 PM6:29 PM6:33 PM6:43 PM
2.....5:49 PM5:56 PM6:04 PM6:10 PM6:19 PM6:29 PM6:34 PM6:38 PM6:42 PM6:45 PM6:49 PM6:59 PM
2.....6:03 PM6:10 PM6:18 PM6:24 PM6:32 PM6:41 PM6:46 PM6:50 PM6:54 PM6:57 PM7:01 PM7:11 PM
2wheelchair access.....6:26 PM6:32 PM6:38 PM6:43 PM6:49 PM6:58 PM7:03 PM7:07 PM7:11 PM7:14 PM7:18 PM7:28 PM
2.....7:26 PM7:31 PM7:37 PM7:42 PM7:48 PM7:57 PM8:02 PM8:06 PM8:10 PM8:13 PM8:17 PM8:27 PM
2wheelchair access.....8:26 PM8:31 PM8:37 PM8:42 PM8:48 PM8:57 PM9:02 PM9:06 PM9:10 PM9:13 PM9:17 PM9:27 PM
2.....9:26 PM9:31 PM9:37 PM9:42 PM9:48 PM9:57 PM10:02 PM10:06 PM10:10 PM10:13 PM10:17 PM10:27 PM
2.....10:26 PM10:31 PM10:37 PM10:42 PM10:48 PM10:57 PM11:02 PM11:06 PM11:10 PM11:13 PM11:17 PM11:27 PM
2.....11:26 PM11:31 PM11:37 PM11:42 PM11:47 PM...................................
+

wheelchair_icon- Wheelchair Accessible Bus

+
+ + + + + --- /dev/null +++ b/maxious-canberra-transit-feed/source-html/Route_21.html @@ -1,1 +1,241 @@ + + + + + + + + +Route 21 + + + + + + + +
+

Chosen services: 21

+ +

View timetable and map

+ This timetable is effective from Monday 25 May 2009. +

Weekdays

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
 Woden Interchange
+ Platform 15
PearceTorrens ShopsSouthlands MawsonWoden Interchange
216:57 AM7:03 AM7:06 AM7:12 AM7:24 AM
217:27 AM7:34 AM7:37 AM7:44 AM7:57 AM
21wheelchair access7:57 AM8:04 AM8:07 AM8:14 AM8:27 AM
218:27 AM8:34 AM8:37 AM8:44 AM8:57 AM
21wheelchair access9:04 AM9:11 AM9:14 AM9:21 AM9:34 AM
2110:04 AM10:10 AM10:13 AM10:19 AM10:31 AM
2111:04 AM11:10 AM11:13 AM11:19 AM11:31 AM
21wheelchair access12:04 PM12:10 PM12:13 PM12:19 PM12:31 PM
211:04 PM1:10 PM1:13 PM1:19 PM1:31 PM
21wheelchair access2:04 PM2:10 PM2:13 PM2:19 PM2:31 PM
21wheelchair access3:04 PM3:11 PM3:14 PM3:21 PM3:34 PM
213:27 PM3:34 PM3:37 PM3:44 PM3:57 PM
213:57 PM4:04 PM4:07 PM4:14 PM4:27 PM
214:27 PM4:34 PM4:37 PM4:44 PM4:57 PM
21wheelchair access4:57 PM5:04 PM5:07 PM5:14 PM5:27 PM
215:27 PM5:34 PM5:37 PM5:44 PM5:57 PM
215:57 PM6:04 PM6:07 PM6:14 PM6:27 PM
216:27 PM6:33 PM6:36 PM6:42 PM6:54 PM
21wheelchair access7:20 PM7:26 PM7:29 PM7:35 PM7:47 PM
218:20 PM8:26 PM8:29 PM8:35 PM8:47 PM
219:20 PM9:26 PM9:29 PM9:35 PM9:47 PM
21wheelchair access10:20 PM10:26 PM10:29 PM10:35 PM10:47 PM
21wheelchair access11:20 PM11:26 PM11:29 PM11:35 PM.....
+

wheelchair_icon- Wheelchair Accessible Bus

+
+ + + + + --- /dev/null +++ b/maxious-canberra-transit-feed/source-html/Route_22.html @@ -1,1 +1,241 @@ + + + + + + + + +Route 22 + + + + + + + +
+

Chosen services: 22

+ +

View timetable and map

+ This timetable is effective from Monday 25 May 2009. +

Weekdays

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
 Woden Interchange
+ Platform 15
Southlands MawsonTorrens ShopsPearceWoden Interchange
22wheelchair access6:35 AM6:48 AM6:56 AM6:59 AM7:07 AM
22wheelchair access7:05 AM7:18 AM7:26 AM7:29 AM7:38 AM
227:35 AM7:49 AM7:58 AM8:01 AM8:10 AM
228:05 AM8:19 AM8:28 AM8:31 AM8:40 AM
228:43 AM8:57 AM9:06 AM9:09 AM9:18 AM
229:43 AM9:56 AM10:04 AM10:07 AM10:15 AM
2210:43 AM10:56 AM11:04 AM11:07 AM11:15 AM
2211:43 AM11:56 AM12:04 PM12:07 PM12:15 PM
2212:43 PM12:56 PM1:04 PM1:07 PM1:15 PM
221:43 PM1:56 PM2:04 PM2:07 PM2:15 PM
222:43 PM2:56 PM3:05 PM3:08 PM3:17 PM
223:13 PM3:27 PM3:36 PM3:39 PM3:48 PM
223:35 PM3:49 PM3:58 PM4:01 PM4:10 PM
224:05 PM4:19 PM4:28 PM4:31 PM4:40 PM
224:35 PM4:49 PM4:58 PM5:01 PM5:10 PM
225:05 PM5:19 PM5:28 PM5:31 PM5:40 PM
22wheelchair access5:35 PM5:49 PM5:58 PM6:01 PM6:10 PM
226:05 PM6:19 PM6:28 PM6:31 PM6:39 PM
22wheelchair access6:38 PM6:51 PM6:59 PM7:02 PM7:10 PM
227:38 PM7:51 PM7:59 PM8:02 PM8:10 PM
228:38 PM8:51 PM8:59 PM9:02 PM9:10 PM
22wheelchair access9:38 PM9:51 PM9:59 PM10:02 PM10:10 PM
22wheelchair access10:38 PM10:51 PM10:59 PM11:02 PM11:10 PM
+

wheelchair_icon- Wheelchair Accessible Bus

+
+ + + + + --- /dev/null +++ b/maxious-canberra-transit-feed/source-html/Route_23.html @@ -1,1 +1,324 @@ + + + + + + + + +Route 23 + + + + + + + +
+

Chosen services: 23

+ +

View timetable and map

+ This timetable is effective from Monday 25 May 2009. +

Weekdays

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
 Woden Interchange
+ Platform 15
LyonsChifleySouthlands MawsonFarrer TerminusIsaacsCanberra HospitalWoden Interchange
236:07 AM6:09 AM6:13 AM6:22 AM6:28 AM6:34 AM6:42 AM6:47 AM
23wheelchair access6:44 AM6:46 AM6:50 AM6:59 AM7:05 AM7:11 AM7:19 AM7:24 AM
237:14 AM7:16 AM7:20 AM7:29 AM7:36 AM7:42 AM7:52 AM7:57 AM
23wheelchair access7:44 AM7:48 AM7:53 AM8:01 AM8:08 AM8:14 AM8:24 AM8:29 AM
23wheelchair access8:14 AM8:18 AM8:23 AM8:31 AM8:38 AM8:44 AM8:54 AM8:59 AM
238:44 AM8:48 AM8:53 AM9:01 AM9:08 AM9:14 AM9:24 AM9:29 AM
239:26 AM9:30 AM9:34 AM9:43 AM9:49 AM9:55 AM10:03 AM10:08 AM
23wheelchair access10:26 AM10:28 AM10:32 AM10:41 AM10:47 AM10:53 AM11:01 AM11:06 AM
2311:26 AM11:28 AM11:32 AM11:41 AM11:47 AM11:53 AM12:01 PM12:06 PM
2312:26 PM12:28 PM12:32 PM12:41 PM12:47 PM12:53 PM1:01 PM1:06 PM
231:26 PM1:28 PM1:32 PM1:41 PM1:47 PM1:53 PM2:01 PM2:06 PM
232:26 PM2:28 PM2:32 PM2:41 PM2:47 PM2:53 PM3:01 PM3:06 PM
233:14 PM3:18 PM3:23 PM3:31 PM3:38 PM3:44 PM3:54 PM3:59 PM
233:44 PM3:48 PM3:53 PM4:01 PM4:08 PM4:14 PM4:24 PM4:29 PM
234:14 PM4:18 PM4:23 PM4:31 PM4:38 PM4:44 PM4:54 PM4:59 PM
234:44 PM4:48 PM4:53 PM5:01 PM5:08 PM5:14 PM5:24 PM5:29 PM
235:14 PM5:18 PM5:23 PM5:31 PM5:38 PM5:44 PM5:54 PM5:59 PM
235:44 PM5:48 PM5:53 PM6:01 PM6:08 PM6:14 PM6:24 PM6:29 PM
23wheelchair access6:26 PM6:30 PM6:34 PM6:43 PM6:49 PM6:55 PM7:03 PM7:08 PM
23wheelchair access7:26 PM7:28 PM7:32 PM7:41 PM7:47 PM7:53 PM8:01 PM8:06 PM
23wheelchair access8:26 PM8:28 PM8:32 PM8:41 PM8:47 PM8:53 PM9:01 PM9:06 PM
239:26 PM9:28 PM9:32 PM9:41 PM9:47 PM9:53 PM10:01 PM10:06 PM
23wheelchair access10:26 PM10:28 PM10:32 PM10:41 PM10:47 PM10:53 PM11:01 PM11:06 PM
2311:26 PM11:28 PM11:32 PM11:41 PM....................
+

wheelchair_icon- Wheelchair Accessible Bus

+
+ + + + + --- /dev/null +++ b/maxious-canberra-transit-feed/source-html/Route_24.html @@ -1,1 +1,302 @@ + + + + + + + + +Route 24 + + + + + + + +
+

Chosen services: 24

+ +

View timetable and map

+ This timetable is effective from Monday 25 May 2009. +

Weekdays

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
 Woden Interchange
+ Platform 15
Canberra HospitalIsaacsFarrer TerminusSouthlands MawsonChifleyLyonsWoden Interchange
24...............7:03 AM7:09 AM7:15 AM7:20 AM7:24 AM
24wheelchair access7:02 AM7:08 AM7:15 AM7:20 AM7:26 AM7:32 AM7:37 AM7:42 AM
247:39 AM7:46 AM7:54 AM8:00 AM8:06 AM8:13 AM8:18 AM8:23 AM
248:09 AM8:16 AM8:24 AM8:30 AM8:36 AM8:43 AM8:48 AM8:53 AM
24wheelchair access8:39 AM8:46 AM8:54 AM9:00 AM9:06 AM9:13 AM9:18 AM9:23 AM
249:56 AM10:02 AM10:09 AM10:14 AM10:20 AM10:26 AM10:31 AM10:35 AM
2410:56 AM11:02 AM11:09 AM11:14 AM11:20 AM11:26 AM11:31 AM11:35 AM
2411:56 AM12:02 PM12:09 PM12:14 PM12:20 PM12:26 PM12:31 PM12:35 PM
24wheelchair access12:56 PM1:02 PM1:09 PM1:14 PM1:20 PM1:26 PM1:31 PM1:35 PM
24wheelchair access1:56 PM2:02 PM2:09 PM2:14 PM2:20 PM2:26 PM2:31 PM2:35 PM
242:56 PM3:02 PM3:10 PM3:16 PM3:22 PM3:29 PM3:34 PM3:39 PM
243:39 PM3:46 PM3:54 PM4:00 PM4:06 PM4:13 PM4:18 PM4:23 PM
24wheelchair access4:09 PM4:16 PM4:24 PM4:30 PM4:36 PM4:43 PM4:48 PM4:53 PM
244:39 PM4:46 PM4:54 PM5:00 PM5:06 PM5:13 PM5:18 PM5:23 PM
245:09 PM5:16 PM5:24 PM5:30 PM5:36 PM5:43 PM5:48 PM5:53 PM
245:38 PM5:45 PM5:53 PM5:59 PM6:05 PM6:12 PM6:17 PM6:22 PM
246:08 PM6:15 PM6:23 PM6:29 PM6:35 PM6:41 PM6:46 PM6:50 PM
247:00 PM7:06 PM7:13 PM7:18 PM7:24 PM7:30 PM7:35 PM7:39 PM
24wheelchair access8:00 PM8:06 PM8:13 PM8:18 PM8:24 PM8:30 PM8:35 PM8:39 PM
249:00 PM9:06 PM9:13 PM9:18 PM9:24 PM9:30 PM9:35 PM9:39 PM
24wheelchair access10:00 PM10:06 PM10:13 PM10:18 PM10:24 PM10:30 PM10:35 PM10:39 PM
24wheelchair access11:00 PM11:06 PM11:13 PM11:18 PM11:24 PM11:30 PM11:35 PM11:39 PM
+

wheelchair_icon- Wheelchair Accessible Bus

+
+ + + + + --- /dev/null +++ b/maxious-canberra-transit-feed/source-html/Route_25_225.html @@ -1,1 +1,526 @@ + + + + + + + + +Route 25, 225 + + + + + + + +
+

Chosen services: 25, 225

+ +

View timetable and map

+ This timetable is effective from Monday 20 July 2009. +

Weekdays

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
 Campbell Park OfficesADFARussell OfficesKings Ave/National CrtWoden Interchange
+ Platform 16
Weston PrimaryHolderCooleman Court
25wheelchair access....................7:12 AM7:20 AM7:23 AM7:34 AM
25....................8:07 AM8:19 AM8:23 AM8:35 AM
25....................8:42 AM8:54 AM8:58 AM9:10 AM
25wheelchair access....................9:40 AM9:49 AM9:52 AM10:02 AM
25....................10:40 AM10:49 AM10:52 AM11:02 AM
25....................11:40 AM11:49 AM11:52 AM12:02 PM
25wheelchair access....................12:40 PM12:49 PM12:52 PM1:02 PM
25....................1:40 PM1:49 PM1:52 PM2:02 PM
25....................2:40 PM2:49 PM2:52 PM3:06 PM
25....................3:42 PM3:52 PM3:56 PM4:08 PM
25....................4:12 PM4:22 PM4:26 PM4:38 PM
2254:17 PM4:21 PM4:25 PM4:28 PM4:43 PM4:53 PM4:57 PM5:09 PM
2254:47 PM4:51 PM4:55 PM4:58 PM5:13 PM5:23 PM5:27 PM5:39 PM
2255:17 PM5:21 PM5:25 PM5:28 PM5:43 PM5:53 PM5:57 PM6:09 PM
25....................6:12 PM6:22 PM6:26 PM6:37 PM
25....................6:56 PM7:04 PM7:07 PM7:17 PM
25....................7:56 PM8:04 PM8:07 PM8:17 PM
25....................8:56 PM9:04 PM9:07 PM9:17 PM
25....................9:56 PM10:04 PM10:07 PM10:17 PM
25wheelchair access....................10:56 PM11:04 PM11:07 PM11:17 PM
+

 

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
 Cooleman CourtHolderWeston Primary Woden Interchange
+ Platform 10
Kings Ave/National CrtRussell Offices ADFACampbell Park Offices
256:12 AM6:22 AM6:25 AM6:34 AM....................
225wheelchair access6:42 AM6:52 AM6:55 AM7:05 AM7:19 AM7:22 AM7:26 AM7:30 AM
2257:02 AM7:12 AM7:15 AM7:25 AM7:39 AM7:43 AM7:47 AM7:51 AM
2257:34 AM7:49 AM7:52 AM8:05 AM8:19 AM8:23 AM8:27 AM8:31 AM
258:08 AM8:23 AM8:26 AM8:38 AM....................
258:38 AM8:53 AM8:56 AM9:08 AM....................
259:10 AM9:25 AM9:28 AM9:38 AM....................
25wheelchair access10:12 AM10:22 AM10:25 AM10:35 AM....................
2511:12 AM11:22 AM11:25 AM11:35 AM....................
2512:12 PM12:22 PM12:25 PM12:35 PM....................
25wheelchair access1:12 PM1:22 PM1:25 PM1:35 PM....................
252:12 PM2:22 PM2:25 PM2:35 PM....................
253:12 PM3:24 PM3:27 PM3:36 PM....................
253:42 PM3:54 PM3:57 PM4:06 PM....................
254:12 PM4:24 PM4:27 PM4:36 PM....................
255:12 PM5:24 PM5:27 PM5:36 PM....................
256:22 PM6:33 PM6:36 PM6:45 PM....................
257:22 PM7:32 PM7:35 PM7:44 PM....................
258:22 PM8:32 PM8:35 PM8:44 PM....................
259:22 PM9:32 PM9:35 PM9:44 PM....................
2510:22 PM10:32 PM10:35 PM10:44 PM....................
+

wheelchair_icon- Wheelchair Accessible Bus

+
+ + + + + --- /dev/null +++ b/maxious-canberra-transit-feed/source-html/Route_26_226.html @@ -1,1 +1,593 @@ + + + + + + + + +Route 26, 226 + + + + + + + +
+

Chosen services: 26, 226

+ +

View timetable and map

+ This timetable is effective from Monday 25 May 2009. +

Weekdays

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
 Weston Creek TerminusChapmanCanberra College WestonCooleman CourtWoden Interchange
+ Platform 10
Kings Ave/National CrtRussell OfficesADFACampbell Park Offices
266:15 AM6:19 AM6:23 AM6:25 AM6:32 AM....................
226wheelchair access6:57 AM7:01 AM7:05 AM7:07 AM7:15 AM7:29 AM7:33 AM7:37 AM7:41 AM
2267:16 AM7:20 AM7:24 AM7:26 AM7:36 AM7:50 AM7:54 AM7:58 AM8:02 AM
2267:47 AM7:52 AM7:58 AM8:02 AM8:15 AM8:29 AM8:33 AM8:37 AM8:41 AM
268:00 AM8:05 AM8:11 AM8:15 AM8:27 AM....................
268:20 AM8:25 AM8:31 AM8:35 AM8:47 AM....................
268:50 AM8:55 AM9:01 AM9:05 AM9:17 AM....................
269:25 AM9:30 AM9:35 AM9:38 AM9:48 AM....................
2610:25 AM10:29 AM10:34 AM10:37 AM10:47 AM....................
2611:25 AM11:29 AM11:34 AM11:37 AM11:47 AM....................
2612:25 PM12:29 PM12:34 PM12:37 PM12:47 PM....................
261:25 PM1:29 PM1:34 PM1:37 PM1:47 PM....................
262:25 PM2:29 PM2:34 PM2:37 PM2:47 PM....................
26wheelchair access2:55 PM2:59 PM3:05 PM3:08 PM3:17 PM....................
263:20 PM3:24 PM3:30 PM3:33 PM3:42 PM....................
26wheelchair access4:20 PM4:24 PM4:30 PM4:33 PM4:42 PM....................
265:20 PM5:24 PM5:30 PM5:33 PM5:42 PM....................
266:20 PM6:24 PM6:30 PM6:32 PM6:39 PM....................
267:14 PM7:18 PM7:22 PM7:24 PM7:31 PM....................
268:14 PM8:18 PM8:22 PM8:24 PM8:31 PM....................
269:14 PM9:18 PM9:22 PM9:24 PM9:31 PM....................
26wheelchair access10:14 PM10:18 PM10:22 PM10:24 PM10:31 PM....................
+

 

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
 Campbell Park OfficesADFARussell Offices Kings Ave/National CrtWoden Interchange
+ Platform 3
Cooleman CourtCanberra College WestonChapmanWeston Creek Terminus
26....................7:18 AM7:25 AM7:27 AM7:31 AM7:35 AM
26....................8:18 AM8:28 AM8:32 AM8:37 AM8:41 AM
26....................8:58 AM9:08 AM9:12 AM9:17 AM9:21 AM
26....................9:58 AM10:07 AM10:10 AM10:15 AM10:19 AM
26....................10:58 AM11:07 AM11:10 AM11:15 AM11:19 AM
26....................11:58 AM12:07 PM12:10 PM12:15 PM12:19 PM
26....................12:58 PM1:07 PM1:10 PM1:15 PM1:19 PM
26....................1:58 PM2:07 PM2:10 PM2:15 PM2:19 PM
26....................2:58 PM3:09 PM3:13 PM3:19 PM3:24 PM
26....................3:28 PM3:40 PM3:44 PM3:50 PM3:55 PM
26....................3:54 PM4:06 PM4:10 PM4:16 PM4:21 PM
26....................4:18 PM4:30 PM4:34 PM4:40 PM4:45 PM
26....................4:48 PM5:00 PM5:04 PM5:10 PM5:15 PM
2264:52 PM4:56 PM5:00 PM5:03 PM5:18 PM5:30 PM5:34 PM5:40 PM5:45 PM
2265:22 PM5:26 PM5:30 PM5:33 PM5:48 PM6:00 PM6:04 PM6:10 PM6:15 PM
26....................6:18 PM6:30 PM6:32 PM6:36 PM6:40 PM
26....................6:50 PM6:57 PM6:59 PM7:03 PM7:07 PM
26....................7:50 PM7:57 PM7:59 PM8:03 PM8:07 PM
26....................8:50 PM8:57 PM8:59 PM9:03 PM9:07 PM
26wheelchair access....................9:50 PM9:57 PM9:59 PM10:03 PM10:07 PM
26....................10:50 PM10:57 PM10:59 PM11:03 PM11:07 PM
+

wheelchair_icon- Wheelchair Accessible Bus

+
+ + + + + --- /dev/null +++ b/maxious-canberra-transit-feed/source-html/Route_27_227.html @@ -1,1 +1,581 @@ + + + + + + + + +Route 27, 227 + + + + + + + +
+

Chosen services: 27, 227

+ +

View timetable and map

+ This timetable is effective from Monday 25 May 2009. +

Weekdays

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
 Cooleman CourtRivettFisherWaramangaWoden Interchange
+ Platform 10
Kings Ave/National CrtRussell OfficesADFACampbell Park Offices
2276:29 AM6:35 AM6:43 AM6:47 AM6:55 AM7:09 AM7:12 AM7:16 AM7:20 AM
2276:54 AM7:00 AM7:08 AM7:12 AM7:20 AM7:34 AM7:38 AM7:42 AM7:46 AM
27..........7:28 AM7:35 AM7:46 AM....................
2277:22 AM7:28 AM7:36 AM7:43 AM7:55 AM8:09 AM8:13 AM8:17 AM8:21 AM
27wheelchair access7:40 AM7:46 AM7:54 AM8:01 AM8:12 AM....................
277:48 AM7:54 AM8:04 AM8:10 AM8:20 AM....................
278:23 AM8:29 AM8:37 AM8:44 AM8:55 AM....................
278:53 AM8:59 AM9:07 AM9:14 AM9:25 AM....................
279:25 AM9:31 AM9:38 AM9:42 AM9:49 AM....................
2710:25 AM10:31 AM10:38 AM10:42 AM10:49 AM....................
2711:25 AM11:31 AM11:38 AM11:42 AM11:49 AM....................
2712:25 PM12:31 PM12:38 PM12:42 PM12:49 PM....................
271:25 PM1:31 PM1:38 PM1:42 PM1:49 PM....................
272:25 PM2:31 PM2:38 PM2:42 PM2:49 PM....................
273:25 PM3:30 PM3:37 PM3:41 PM3:49 PM....................
273:55 PM4:00 PM4:07 PM4:11 PM4:19 PM....................
27wheelchair access4:25 PM4:30 PM4:37 PM4:41 PM4:49 PM....................
275:25 PM5:30 PM5:37 PM5:41 PM5:49 PM....................
276:25 PM6:30 PM6:37 PM6:40 PM6:47 PM....................
277:00 PM7:05 PM7:12 PM7:15 PM7:22 PM....................
27wheelchair access8:00 PM8:05 PM8:12 PM8:15 PM8:22 PM....................
279:00 PM9:05 PM9:12 PM9:15 PM9:22 PM....................
2710:00 PM10:05 PM10:12 PM10:15 PM10:22 PM....................
+

 

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
 Campbell Park OfficesADFARussell OfficesKings Ave/National CrtWoden Interchange
+ Platform 3
WaramangaFisherRivettCooleman Court
27....................8:21 AM8:29 AM8:33 AM8:40 AM8:45 AM
27....................8:54 AM9:02 AM9:06 AM9:13 AM9:18 AM
27....................9:54 AM10:01 AM10:05 AM10:13 AM10:19 AM
27....................10:54 AM11:01 AM11:05 AM11:13 AM11:19 AM
27wheelchair access....................11:54 AM12:01 PM12:05 PM12:13 PM12:19 PM
27....................12:54 PM1:01 PM1:05 PM1:13 PM1:19 PM
27....................1:54 PM2:01 PM2:05 PM2:13 PM2:19 PM
27....................2:54 PM3:02 PM3:07 PM3:14 PM3:22 PM
27wheelchair access....................3:21 PM3:33 PM3:38 PM3:45 PM3:53 PM
27....................3:51 PM4:03 PM4:08 PM4:15 PM4:23 PM
27....................4:21 PM4:33 PM4:38 PM4:45 PM4:53 PM
2274:27 PM4:31 PM4:35 PM4:38 PM4:53 PM5:05 PM5:10 PM5:17 PM5:25 PM
27....................5:21 PM5:33 PM5:38 PM5:45 PM5:53 PM
2275:27 PM5:31 PM5:35 PM5:38 PM5:53 PM6:05 PM6:10 PM6:17 PM6:25 PM
27....................6:35 PM6:41 PM6:44 PM6:50 PM6:55 PM
27wheelchair access....................7:35 PM7:41 PM7:44 PM7:50 PM7:55 PM
27....................8:35 PM8:41 PM8:44 PM8:50 PM8:55 PM
27wheelchair access....................9:35 PM9:41 PM9:44 PM9:50 PM9:55 PM
27....................10:35 PM10:41 PM10:44 PM10:50 PM10:55 PM
+

wheelchair_icon- Wheelchair Accessible Bus

+
+ + + + + --- /dev/null +++ b/maxious-canberra-transit-feed/source-html/Route_28.html @@ -1,1 +1,568 @@ + + + + + + + +Route 28 + + + + + + + +
+

Chosen services: 28

+ +

View timetable and map

+ This timetable is effective from Monday 25 May 2009. +

Weekdays

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
 Cooleman CourtDuffy Primary SchoolPolice College WestonLyonsWoden Interchange
+ Platform 10
Kings Ave/National CrtRussell OfficesBrindabella Business ParkFairbairn Park
286:15 AM6:24 AM6:30 AM6:34 AM6:38 AM6:52 AM6:55 AM7:11 AM7:25 AM
28wheelchair access6:37 AM6:46 AM6:52 AM6:56 AM7:00 AM7:14 AM7:17 AM7:33 AM7:47 AM
287:05 AM7:14 AM7:20 AM7:24 AM7:28 AM7:42 AM7:46 AM8:02 AM8:16 AM
287:45 AM7:57 AM8:05 AM8:10 AM8:15 AM8:29 AM8:33 AM8:49 AM9:03 AM
288:15 AM8:27 AM8:35 AM8:40 AM8:44 AM....................
288:44 AM8:56 AM9:04 AM9:09 AM9:13 AM....................
289:26 AM9:38 AM9:45 AM9:49 AM9:53 AM....................
2810:26 AM10:38 AM10:45 AM10:49 AM10:53 AM....................
2811:26 AM11:38 AM11:45 AM11:49 AM11:53 AM....................
28wheelchair access12:26 PM12:38 PM12:45 PM12:49 PM12:53 PM....................
281:26 PM1:38 PM1:45 PM1:49 PM1:53 PM....................
282:26 PM2:38 PM2:45 PM2:49 PM2:53 PM....................
283:26 PM3:38 PM3:46 PM3:51 PM3:54 PM....................
283:56 PM4:08 PM4:16 PM4:21 PM4:25 PM....................
284:15 PM4:27 PM4:35 PM4:40 PM4:44 PM....................
285:15 PM5:27 PM5:35 PM5:40 PM5:44 PM....................
286:15 PM6:27 PM6:34 PM6:38 PM6:41 PM....................
287:00 PM7:09 PM7:15 PM7:19 PM7:22 PM....................
288:00 PM8:09 PM8:15 PM8:19 PM8:22 PM....................
289:00 PM9:09 PM9:15 PM9:19 PM9:22 PM....................
28wheelchair access10:00 PM10:09 PM10:15 PM10:19 PM10:22 PM....................
+

 

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
 Fairbairn ParkBrindabella Business ParkRussell OfficesKings Ave/National CrtWoden Interchange
+ Platform 16
LyonsPolice College WestonDuffy Primary SchoolCooleman Court
28....................7:42 AM7:46 AM7:51 AM7:59 AM8:11 AM
28....................8:45 AM8:49 AM8:54 AM9:02 AM9:14 AM
28....................9:52 AM9:56 AM10:00 AM10:07 AM10:19 AM
28....................10:52 AM10:56 AM11:00 AM11:07 AM11:19 AM
28....................11:52 AM11:56 AM12:00 PM12:07 PM12:19 PM
28....................12:52 PM12:56 PM1:00 PM1:07 PM1:19 PM
28....................1:52 PM1:56 PM2:00 PM2:07 PM2:19 PM
28....................2:52 PM2:56 PM3:00 PM3:08 PM3:20 PM
28....................3:12 PM3:16 PM3:21 PM3:29 PM3:41 PM
28....................3:42 PM3:46 PM3:51 PM3:59 PM4:11 PM
28....................4:12 PM4:16 PM4:21 PM4:29 PM4:41 PM
28....................4:42 PM4:46 PM4:51 PM4:59 PM5:11 PM
284:29 PM4:38 PM4:54 PM4:57 PM5:12 PM5:16 PM5:21 PM5:29 PM5:41 PM
284:49 PM5:08 PM5:24 PM5:27 PM5:42 PM5:46 PM5:51 PM5:59 PM6:11 PM
285:19 PM5:38 PM5:54 PM5:57 PM6:12 PM6:16 PM6:21 PM6:29 PM6:38 PM
285:49 PM6:08 PM6:24 PM6:27 PM6:42 PM6:45 PM6:49 PM6:56 PM7:05 PM
28....................7:32 PM7:35 PM7:39 PM7:46 PM7:55 PM
28....................8:32 PM8:35 PM8:39 PM8:46 PM8:55 PM
28....................9:32 PM9:35 PM9:39 PM9:46 PM9:55 PM
28....................10:32 PM10:35 PM10:39 PM10:46 PM10:55 PM
+

wheelchair_icon- Wheelchair Accessible Bus

+
+ + + + + --- /dev/null +++ b/maxious-canberra-transit-feed/source-html/Route_3.html @@ -1,1 +1,1145 @@ + + + + + + + + +Route 3 + + + + + + + +
+

Chosen services: 3

+ +

View timetable and map

+ +

Weekdays

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
 Cohen St Station
+ Platform 2
Lathlain St Station
+ Platform 3
Cameron Ave Station
+ Platform 3
Calvary HospitalO'ConnorGarran/Daley RdNational MuseumCity Interchange
+ Platform 3
Kings Ave/National CrtParliament HouseDeakinHughesGarranWoden Interchange
3...................................6:18 AM6:27 AM6:31 AM6:36 AM6:40 AM6:44 AM6:53 AM
3...................................6:48 AM6:57 AM7:01 AM7:06 AM7:10 AM7:14 AM7:23 AM
36:31 AM6:33 AM6:37 AM6:52 AM6:58 AM7:02 AM7:06 AM7:18 AM7:27 AM7:31 AM7:36 AM7:42 AM7:46 AM7:58 AM
3wheelchair access6:59 AM7:01 AM7:05 AM7:20 AM7:26 AM7:30 AM7:35 AM7:48 AM7:58 AM8:03 AM8:08 AM8:14 AM8:18 AM8:30 AM
3wheelchair access7:24 AM7:26 AM7:30 AM7:47 AM7:55 AM8:00 AM8:05 AM8:18 AM8:28 AM8:33 AM8:38 AM8:44 AM8:48 AM9:00 AM
37:48 AM7:50 AM7:54 AM8:10 AM8:19 AM8:27 AM8:32 AM8:48 AM8:53 AM9:01 AM9:06 AM9:15 AM9:19 AM9:31 AM
38:24 AM8:26 AM8:30 AM8:47 AM8:55 AM9:00 AM9:05 AM9:18 AM9:28 AM9:32 AM9:37 AM9:42 AM9:46 AM9:55 AM
3wheelchair access8:54 AM8:56 AM9:00 AM9:17 AM9:25 AM9:30 AM9:35 AM9:48 AM9:58 AM10:02 AM10:07 AM10:12 AM10:16 AM10:25 AM
3wheelchair access9:27 AM9:29 AM9:33 AM9:48 AM9:55 AM10:00 AM10:05 AM10:18 AM10:28 AM10:32 AM10:37 AM10:42 AM10:46 AM10:55 AM
3wheelchair access9:57 AM9:59 AM10:03 AM10:18 AM10:25 AM10:30 AM10:35 AM10:48 AM10:58 AM11:02 AM11:07 AM11:12 AM11:16 AM11:25 AM
310:27 AM10:29 AM10:33 AM10:48 AM10:55 AM11:00 AM11:05 AM11:18 AM11:28 AM11:32 AM11:37 AM11:42 AM11:46 AM11:55 AM
3wheelchair access10:57 AM10:59 AM11:03 AM11:18 AM11:25 AM11:30 AM11:35 AM11:48 AM11:58 AM12:02 PM12:07 PM12:12 PM12:16 PM12:25 PM
311:27 AM11:29 AM11:33 AM11:48 AM11:55 AM12:00 PM12:05 PM12:18 PM12:28 PM12:32 PM12:37 PM12:42 PM12:46 PM12:55 PM
3wheelchair access11:57 AM11:59 AM12:03 PM12:18 PM12:25 PM12:30 PM12:35 PM12:48 PM12:58 PM1:02 PM1:07 PM1:12 PM1:16 PM1:25 PM
3wheelchair access12:27 PM12:29 PM12:33 PM12:48 PM12:55 PM1:00 PM1:05 PM1:18 PM1:28 PM1:32 PM1:37 PM1:42 PM1:46 PM1:55 PM
3wheelchair access12:57 PM12:59 PM1:03 PM1:18 PM1:25 PM1:30 PM1:35 PM1:48 PM1:58 PM2:02 PM2:07 PM2:12 PM2:16 PM2:25 PM
3wheelchair access1:27 PM1:29 PM1:33 PM1:48 PM1:55 PM2:00 PM2:05 PM2:18 PM2:28 PM2:32 PM2:37 PM2:42 PM2:46 PM2:55 PM
31:57 PM1:59 PM2:03 PM2:18 PM2:25 PM2:30 PM2:35 PM2:48 PM2:58 PM3:03 PM3:08 PM3:14 PM3:18 PM3:29 PM
3wheelchair access2:32 PM2:34 PM2:38 PM2:48 PM2:58 PM3:03 PM3:10 PM3:24 PM3:34 PM3:39 PM3:44 PM3:50 PM3:54 PM4:05 PM
32:53 PM2:55 PM2:59 PM3:16 PM3:24 PM3:29 PM3:35 PM3:48 PM3:58 PM4:03 PM4:08 PM4:14 PM4:18 PM4:29 PM
33:20 PM3:22 PM3:26 PM3:43 PM3:51 PM3:56 PM4:02 PM4:15 PM4:25 PM4:30 PM4:35 PM4:41 PM4:45 PM4:56 PM
33:49 PM3:51 PM3:55 PM4:12 PM4:20 PM4:25 PM4:31 PM4:44 PM4:54 PM4:59 PM5:04 PM5:10 PM5:14 PM5:25 PM
3wheelchair access4:21 PM4:23 PM4:27 PM4:44 PM4:52 PM4:57 PM5:03 PM5:16 PM5:26 PM5:31 PM5:36 PM5:42 PM5:46 PM5:57 PM
3wheelchair access4:48 PM4:50 PM4:54 PM5:11 PM5:19 PM5:24 PM5:30 PM5:43 PM5:53 PM5:58 PM6:03 PM6:09 PM6:13 PM6:24 PM
35:18 PM5:20 PM5:24 PM5:41 PM5:49 PM5:54 PM6:00 PM6:13 PM6:23 PM6:28 PM6:32 PM6:37 PM6:41 PM6:50 PM
3wheelchair access5:50 PM5:52 PM5:56 PM6:13 PM6:21 PM6:26 PM6:32 PM6:44 PM6:53 PM6:58 PM7:02 PM7:07 PM7:11 PM7:20 PM
3wheelchair access6:23 PM6:25 PM6:29 PM6:44 PM6:51 PM6:56 PM7:01 PM7:13 PM7:22 PM7:27 PM7:31 PM7:36 PM7:40 PM7:49 PM
3wheelchair access7:27 PM7:29 PM7:32 PM7:47 PM7:54 PM7:59 PM8:04 PM8:16 PM8:25 PM8:30 PM8:34 PM8:39 PM8:43 PM8:52 PM
3wheelchair access8:29 PM8:31 PM8:34 PM8:49 PM8:56 PM9:01 PM9:06 PM9:18 PM9:27 PM9:32 PM9:36 PM9:41 PM9:45 PM9:54 PM
3wheelchair access9:29 PM9:31 PM9:34 PM9:49 PM9:56 PM10:01 PM10:06 PM10:18 PM10:27 PM10:32 PM10:36 PM10:41 PM10:45 PM10:54 PM
3wheelchair access10:29 PM10:31 PM10:34 PM10:49 PM10:56 PM11:01 PM11:06 PM11:16 PM..............................
+

 

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
 Woden Interchange
+ Platform 14
GarranHughesDeakinParliament HouseKings Ave/National CrtCity Interchange
+ Platform 4
National MuseumGarran/Daley RdO'ConnorCalvary HospitalCameron Ave StationLathlain St Station Cohen St Station
3wheelchair access6:12 AM6:21 AM6:25 AM6:30 AM6:34 AM6:38 AM6:50 AM6:56 AM7:01 AM7:06 AM7:13 AM7:28 AM7:30 AM7:34 AM
3wheelchair access6:42 AM6:51 AM6:55 AM7:00 AM7:04 AM7:08 AM7:20 AM7:26 AM7:31 AM7:36 AM7:45 AM8:02 AM8:04 AM8:08 AM
3wheelchair access7:12 AM7:21 AM7:25 AM7:30 AM7:34 AM7:38 AM7:50 AM7:56 AM8:01 AM8:06 AM8:15 AM8:32 AM8:34 AM8:38 AM
37:38 AM7:48 AM7:53 AM8:00 AM8:04 AM8:08 AM8:20 AM8:26 AM8:31 AM8:36 AM8:45 AM9:02 AM9:04 AM9:08 AM
38:08 AM8:18 AM8:23 AM8:30 AM8:34 AM8:38 AM8:50 AM8:56 AM9:01 AM9:06 AM9:15 AM9:32 AM9:34 AM9:38 AM
3wheelchair access8:38 AM8:48 AM8:53 AM9:00 AM9:04 AM9:08 AM9:20 AM9:26 AM9:31 AM9:36 AM9:43 AM9:58 AM10:00 AM10:04 AM
3wheelchair access9:12 AM9:21 AM9:25 AM9:30 AM9:34 AM9:38 AM9:50 AM9:56 AM10:01 AM10:06 AM10:13 AM10:31 AM10:33 AM10:34 AM
39:42 AM9:51 AM9:55 AM10:00 AM10:04 AM10:08 AM10:20 AM10:26 AM10:31 AM10:36 AM10:43 AM10:58 AM11:00 AM11:04 AM
310:12 AM10:21 AM10:25 AM10:30 AM10:34 AM10:38 AM10:50 AM10:56 AM11:01 AM11:06 AM11:13 AM11:28 AM11:30 AM11:34 AM
3wheelchair access10:42 AM10:51 AM10:55 AM11:00 AM11:04 AM11:08 AM11:20 AM11:26 AM11:31 AM11:36 AM11:43 AM11:58 AM12:00 PM12:04 PM
311:12 AM11:21 AM11:25 AM11:30 AM11:34 AM11:38 AM11:50 AM11:56 AM12:01 PM12:06 PM12:13 PM12:28 PM12:30 PM12:34 PM
311:42 AM11:51 AM11:55 AM12:00 PM12:04 PM12:08 PM12:20 PM12:26 PM12:31 PM12:36 PM12:43 PM12:58 PM1:00 PM1:04 PM
312:12 PM12:21 PM12:25 PM12:30 PM12:34 PM12:38 PM12:50 PM12:56 PM1:01 PM1:06 PM1:13 PM1:28 PM1:30 PM1:34 PM
312:42 PM12:51 PM12:55 PM1:00 PM1:04 PM1:08 PM1:20 PM1:26 PM1:31 PM1:36 PM1:43 PM1:58 PM2:00 PM2:04 PM
31:12 PM1:21 PM1:25 PM1:30 PM1:34 PM1:38 PM1:50 PM1:56 PM2:01 PM2:06 PM2:13 PM2:28 PM2:30 PM2:34 PM
3wheelchair access1:42 PM1:51 PM1:55 PM2:00 PM2:04 PM2:08 PM2:20 PM2:26 PM2:31 PM2:36 PM2:43 PM2:58 PM3:00 PM3:04 PM
32:12 PM2:21 PM2:25 PM2:30 PM2:34 PM2:38 PM2:50 PM2:56 PM3:01 PM3:07 PM3:15 PM3:32 PM3:34 PM3:38 PM
3wheelchair access2:42 PM2:51 PM2:55 PM3:00 PM3:04 PM3:08 PM3:20 PM3:26 PM3:32 PM3:38 PM3:46 PM4:03 PM4:05 PM4:09 PM
33:09 PM3:19 PM3:24 PM3:30 PM3:34 PM3:38 PM3:50 PM3:56 PM4:02 PM4:08 PM4:16 PM4:33 PM4:35 PM4:39 PM
3wheelchair access3:39 PM3:49 PM3:54 PM4:00 PM4:04 PM4:08 PM4:20 PM4:26 PM4:32 PM4:38 PM4:46 PM5:03 PM5:05 PM5:09 PM
34:09 PM4:19 PM4:24 PM4:30 PM4:34 PM4:38 PM4:50 PM4:56 PM5:02 PM5:08 PM5:16 PM5:33 PM5:35 PM5:39 PM
3wheelchair access4:39 PM4:49 PM4:54 PM5:00 PM5:04 PM5:08 PM5:20 PM5:26 PM5:32 PM5:38 PM5:46 PM6:03 PM6:05 PM6:09 PM
35:11 PM5:21 PM5:26 PM5:32 PM5:36 PM5:40 PM5:52 PM5:58 PM6:04 PM6:10 PM6:18 PM6:34 PM6:36 PM6:39 PM
35:39 PM5:49 PM5:54 PM6:00 PM6:04 PM6:08 PM6:20 PM6:26 PM6:32 PM6:37 PM6:44 PM6:59 PM7:01 PM7:04 PM
36:08 PM6:18 PM6:23 PM6:29 PM6:32 PM6:36 PM6:48 PM6:54 PM6:59 PM7:04 PM7:11 PM7:26 PM7:28 PM7:31 PM
3wheelchair access6:43 PM6:51 PM6:55 PM7:00 PM7:03 PM7:07 PM7:19 PM7:25 PM7:30 PM7:35 PM7:42 PM7:57 PM7:59 PM8:02 PM
3wheelchair access7:13 PM7:21 PM7:25 PM7:30 PM7:33 PM7:37 PM7:49 PM7:55 PM8:00 PM8:05 PM8:12 PM8:27 PM8:29 PM8:32 PM
3wheelchair access8:13 PM8:21 PM8:25 PM8:30 PM8:33 PM8:37 PM8:49 PM8:55 PM9:00 PM9:05 PM9:12 PM9:27 PM9:29 PM9:32 PM
39:13 PM9:21 PM9:25 PM9:30 PM9:33 PM9:37 PM9:49 PM9:55 PM10:00 PM10:05 PM10:12 PM10:27 PM10:29 PM10:32 PM
310:13 PM10:21 PM10:25 PM10:30 PM10:33 PM10:37 PM10:49 PM10:55 PM11:00 PM11:05 PM11:12 PM11:27 PM11:29 PM11:32 PM
3wheelchair access11:13 PM11:21 PM11:25 PM11:30 PM11:33 PM11:37 PM11:47 PM...................................
+

wheelchair_icon- Wheelchair Accessible Bus

+
+ + + + + --- /dev/null +++ b/maxious-canberra-transit-feed/source-html/Route_30.html @@ -1,1 +1,763 @@ + + + + + + + + +Route 30 + + + + + + + +
+

Chosen services: 30

+ +

View timetable and map

+ This timetable is effective from Monday 25 May 2009. +

Weekdays

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
 Cohen St Station
+ Platform 1
Lathlain St Station
+ Platform 3
Cameron Ave Station
+ Platform 3
University of CanberraGiralangKaleen Village/MaribyrnongNorth LynehamMacarthur\NorthbourneCity Interchange
305:49 AM5:51 AM5:55 AM5:58 AM6:06 AM6:13 AM6:19 AM6:24 AM6:30 AM
306:18 AM6:20 AM6:24 AM6:27 AM6:35 AM6:42 AM6:48 AM6:53 AM6:59 AM
306:34 AM6:36 AM6:40 AM6:43 AM6:51 AM6:58 AM7:04 AM7:09 AM7:15 AM
306:59 AM7:01 AM7:05 AM7:08 AM7:16 AM7:23 AM7:29 AM7:38 AM7:53 AM
30....................7:29 AM7:38 AM7:46 AM7:55 AM8:10 AM
307:27 AM7:29 AM7:33 AM7:36 AM7:44 AM7:53 AM8:01 AM8:10 AM8:25 AM
30....................8:03 AM8:12 AM8:24 AM8:33 AM8:48 AM
307:58 AM8:00 AM8:04 AM8:07 AM8:15 AM8:24 AM8:34 AM8:43 AM8:58 AM
30....................8:29 AM8:38 AM8:46 AM8:55 AM9:10 AM
30wheelchair access8:27 AM8:29 AM8:33 AM8:36 AM8:44 AM8:53 AM9:01 AM9:10 AM9:25 AM
308:56 AM8:58 AM9:02 AM9:05 AM9:13 AM9:22 AM9:30 AM9:35 AM9:41 AM
309:56 AM9:58 AM10:02 AM10:05 AM10:12 AM10:20 AM10:28 AM10:33 AM10:39 AM
3010:56 AM10:58 AM11:02 AM11:05 AM11:12 AM11:20 AM11:28 AM11:33 AM11:39 AM
3011:56 AM11:58 AM12:02 PM12:05 PM12:12 PM12:20 PM12:28 PM12:33 PM12:39 PM
30wheelchair access12:56 PM12:58 PM1:02 PM1:05 PM1:12 PM1:20 PM1:28 PM1:33 PM1:39 PM
301:56 PM1:58 PM2:02 PM2:05 PM2:12 PM2:20 PM2:28 PM2:33 PM2:39 PM
302:45 PM2:47 PM2:51 PM2:54 PM3:01 PM3:09 PM3:17 PM3:23 PM3:30 PM
30wheelchair access3:10 PM3:12 PM3:16 PM3:19 PM3:28 PM3:36 PM3:44 PM3:50 PM3:57 PM
303:34 PM3:36 PM3:40 PM3:43 PM3:52 PM4:00 PM4:08 PM4:14 PM4:21 PM
304:04 PM4:06 PM4:10 PM4:13 PM4:22 PM4:30 PM4:38 PM4:44 PM4:51 PM
304:34 PM4:36 PM4:40 PM4:43 PM4:52 PM5:00 PM5:08 PM5:14 PM5:21 PM
305:04 PM5:06 PM5:10 PM5:13 PM5:22 PM5:30 PM5:38 PM5:44 PM5:51 PM
305:34 PM5:36 PM5:40 PM5:43 PM5:52 PM6:00 PM6:08 PM6:14 PM6:21 PM
305:55 PM5:57 PM6:01 PM6:04 PM6:13 PM6:21 PM6:29 PM6:34 PM6:39 PM
306:56 PM6:58 PM7:01 PM7:04 PM7:12 PM7:19 PM7:25 PM7:30 PM7:35 PM
307:56 PM7:58 PM8:01 PM8:04 PM8:12 PM8:19 PM8:25 PM8:30 PM8:35 PM
308:56 PM8:58 PM9:01 PM9:04 PM9:12 PM9:19 PM9:25 PM9:30 PM9:35 PM
30wheelchair access9:56 PM9:58 PM10:01 PM10:04 PM10:12 PM10:19 PM10:25 PM10:30 PM10:35 PM
3010:56 PM10:58 PM11:01 PM11:04 PM11:12 PM11:19 PM11:25 PM11:30 PM11:35 PM
+

 

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
 City Interchange
+ Platform 8
Macarthur\NorthbourneNorth LynehamKaleen Village/MaribyrnongGiralangUniversity of CanberraCameron Ave StationLathlain St StationCohen St Station
306:03 AM6:09 AM6:14 AM6:21 AM6:28 AM6:35 AM6:38 AM6:40 AM6:44 AM
306:33 AM6:39 AM6:44 AM6:51 AM6:58 AM7:05 AM7:08 AM7:10 AM7:14 AM
307:01 AM7:07 AM7:12 AM7:19 AM7:26 AM7:35 AM7:38 AM7:40 AM7:44 AM
307:26 AM7:32 AM7:37 AM7:45 AM7:53 AM8:05 AM8:08 AM8:10 AM8:14 AM
307:59 AM8:06 AM8:11 AM8:19 AM8:27 AM8:39 AM8:42 AM8:44 AM8:48 AM
308:29 AM8:36 AM8:41 AM8:49 AM8:57 AM9:09 AM9:12 AM9:14 AM9:18 AM
308:59 AM9:06 AM9:11 AM9:19 AM9:27 AM9:35 AM9:38 AM9:40 AM9:44 AM
309:33 AM9:39 AM9:44 AM9:51 AM9:58 AM10:05 AM10:08 AM10:10 AM10:14 AM
3010:02 AM10:08 AM10:13 AM10:20 AM10:27 AM10:34 AM10:37 AM10:39 AM10:43 AM
3011:02 AM11:08 AM11:13 AM11:20 AM11:27 AM11:34 AM11:37 AM11:39 AM11:43 AM
3012:02 PM12:08 PM12:13 PM12:20 PM12:27 PM12:34 PM12:37 PM12:39 PM12:43 PM
301:02 PM1:08 PM1:13 PM1:20 PM1:27 PM1:34 PM1:37 PM1:39 PM1:43 PM
302:02 PM2:08 PM2:13 PM2:20 PM2:27 PM2:34 PM2:37 PM2:39 PM2:43 PM
303:02 PM3:09 PM3:16 PM3:24 PM3:32 PM3:44 PM3:47 PM3:49 PM3:53 PM
303:34 PM3:41 PM3:48 PM3:56 PM4:04 PM4:16 PM4:19 PM4:21 PM4:25 PM
303:59 PM4:06 PM4:13 PM4:21 PM4:29 PM4:41 PM4:44 PM4:46 PM4:50 PM
304:29 PM4:36 PM4:43 PM4:51 PM4:59 PM5:11 PM5:14 PM5:16 PM5:20 PM
304:59 PM5:06 PM5:13 PM5:21 PM5:29 PM5:41 PM5:44 PM5:46 PM5:50 PM
305:14 PM5:21 PM5:28 PM5:36 PM5:44 PM5:56 PM5:59 PM6:01 PM6:05 PM
305:29 PM5:36 PM5:43 PM5:51 PM5:59 PM6:11 PM6:14 PM6:16 PM6:20 PM
305:44 PM5:51 PM5:58 PM6:06 PM6:14 PM6:26 PM6:29 PM6:31 PM6:34 PM
305:59 PM6:06 PM6:13 PM6:21 PM6:29 PM6:36 PM6:39 PM6:41 PM6:44 PM
30wheelchair access6:33 PM6:39 PM6:44 PM6:51 PM6:58 PM7:05 PM7:08 PM7:10 PM7:13 PM
307:02 PM7:08 PM7:13 PM7:20 PM7:27 PM7:34 PM7:37 PM7:39 PM7:42 PM
308:02 PM8:08 PM8:13 PM8:20 PM8:27 PM8:34 PM8:37 PM8:39 PM8:42 PM
309:02 PM9:08 PM9:13 PM9:20 PM9:27 PM9:34 PM9:37 PM9:39 PM9:42 PM
3010:02 PM10:08 PM10:13 PM10:20 PM10:27 PM10:34 PM10:37 PM10:39 PM10:42 PM
3011:02 PM11:08 PM11:13 PM11:20 PM11:27 PM11:34 PM11:37 PM11:39 PM11:42 PM
+

wheelchair_icon- Wheelchair Accessible Bus

+
+ + + + + --- /dev/null +++ b/maxious-canberra-transit-feed/source-html/Route_31.html @@ -1,1 +1,594 @@ + + + + + + + + +Route 31 + + + + + + + +
+

Chosen services: 31

+ +

View timetable and map

+ This timetable is effective from Monday 25 May 2009. +

Weekdays

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
 City Interchange
+ Platform 8
Macarthur\NorthbourneNorth LynehamGwydir Square KaleenUniversity of CanberraCameron Ave StationLathlain St StationCohen St Station
31..........6:37 AM6:43 AM6:48 AM6:51 AM6:53 AM6:57 AM
31..........7:07 AM7:13 AM7:18 AM7:21 AM7:23 AM7:27 AM
317:33 AM7:40 AM7:45 AM7:53 AM8:00 AM8:03 AM8:05 AM8:09 AM
318:03 AM8:10 AM8:15 AM8:23 AM8:30 AM8:33 AM8:35 AM8:39 AM
318:29 AM8:36 AM8:41 AM8:49 AM8:56 AM8:59 AM9:01 AM9:05 AM
31wheelchair access9:10 AM9:17 AM9:22 AM9:30 AM9:36 AM9:39 AM9:41 AM9:45 AM
319:48 AM9:54 AM9:59 AM10:05 AM10:11 AM10:14 AM10:16 AM10:20 AM
3110:48 AM10:54 AM10:59 AM11:05 AM11:11 AM11:14 AM11:16 AM11:20 AM
3111:48 AM11:54 AM11:59 AM12:05 PM12:11 PM12:14 PM12:16 PM12:20 PM
3112:48 PM12:54 PM12:59 PM1:05 PM1:11 PM1:14 PM1:16 PM1:20 PM
311:48 PM1:54 PM1:59 PM2:05 PM2:11 PM2:14 PM2:16 PM2:20 PM
312:48 PM2:54 PM2:59 PM3:07 PM3:15 PM3:18 PM3:20 PM3:24 PM
313:03 PM3:10 PM3:15 PM3:23 PM3:31 PM3:34 PM3:36 PM3:40 PM
313:33 PM3:40 PM3:45 PM3:53 PM4:01 PM4:04 PM4:06 PM4:10 PM
31wheelchair access4:03 PM4:10 PM4:15 PM4:23 PM4:31 PM4:34 PM4:36 PM4:40 PM
314:33 PM4:40 PM4:45 PM4:53 PM5:01 PM5:04 PM5:06 PM5:10 PM
315:03 PM5:10 PM5:15 PM5:23 PM5:31 PM5:34 PM5:36 PM5:40 PM
315:33 PM5:40 PM5:45 PM5:53 PM6:01 PM6:04 PM6:06 PM6:10 PM
316:03 PM6:10 PM6:15 PM6:23 PM6:31 PM6:34 PM6:36 PM6:39 PM
316:48 PM6:54 PM6:59 PM7:05 PM7:10 PM7:13 PM7:15 PM7:18 PM
31wheelchair access7:48 PM7:54 PM7:59 PM8:05 PM8:10 PM8:13 PM8:15 PM8:18 PM
318:48 PM8:54 PM8:59 PM9:05 PM9:10 PM9:13 PM9:15 PM9:18 PM
319:48 PM9:54 PM9:59 PM10:05 PM10:10 PM10:13 PM10:15 PM10:18 PM
3110:48 PM10:54 PM10:59 PM11:05 PM11:10 PM11:13 PM11:15 PM11:18 PM
+

 

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
 Cohen St Station
+ Platform 1
Lathlain St Station
+ Platform 3
Cameron Ave Station
+ Platform 3
University of CanberraGwydir Square KaleenNorth LynehamMacarthur\NorthbourneCity Interchange
316:15 AM6:17 AM6:21 AM6:24 AM6:29 AM6:36 AM6:41 AM6:47 AM
316:45 AM6:47 AM6:51 AM6:54 AM6:59 AM7:06 AM7:11 AM7:17 AM
317:12 AM7:14 AM7:18 AM7:21 AM7:26 AM7:34 AM7:42 AM7:57 AM
317:41 AM7:43 AM7:47 AM7:50 AM7:57 AM8:06 AM8:14 AM8:29 AM
318:11 AM8:13 AM8:17 AM8:20 AM8:27 AM8:36 AM8:44 AM8:59 AM
318:41 AM8:43 AM8:47 AM8:50 AM8:57 AM9:06 AM9:14 AM9:29 AM
319:30 AM9:32 AM9:36 AM9:39 AM9:45 AM9:53 AM9:58 AM10:04 AM
3110:30 AM10:32 AM10:36 AM10:39 AM10:45 AM10:53 AM10:58 AM11:04 AM
3111:30 AM11:32 AM11:36 AM11:39 AM11:45 AM11:53 AM11:58 AM12:04 PM
3112:30 PM12:32 PM12:36 PM12:39 PM12:45 PM12:53 PM12:58 PM1:04 PM
311:30 PM1:32 PM1:36 PM1:39 PM1:45 PM1:53 PM1:58 PM2:04 PM
312:30 PM2:32 PM2:36 PM2:39 PM2:45 PM2:53 PM2:58 PM3:05 PM
313:15 PM3:17 PM3:21 PM3:24 PM3:30 PM3:38 PM3:43 PM3:50 PM
313:45 PM3:47 PM3:51 PM3:54 PM4:00 PM4:08 PM4:13 PM4:20 PM
31wheelchair access4:15 PM4:17 PM4:21 PM4:24 PM4:30 PM4:38 PM4:43 PM4:50 PM
314:45 PM4:47 PM4:51 PM4:54 PM5:00 PM5:08 PM5:13 PM5:20 PM
315:14 PM5:16 PM5:20 PM5:23 PM5:29 PM5:37 PM5:42 PM5:49 PM
315:45 PM5:47 PM5:51 PM5:54 PM6:00 PM6:08 PM6:13 PM6:20 PM
316:30 PM6:32 PM6:35 PM6:38 PM6:43 PM6:50 PM6:55 PM7:00 PM
31wheelchair access7:30 PM7:32 PM7:35 PM7:38 PM7:43 PM7:50 PM7:55 PM8:00 PM
31wheelchair access8:30 PM8:32 PM8:35 PM8:38 PM8:43 PM8:50 PM8:55 PM9:00 PM
319:30 PM9:32 PM9:35 PM9:38 PM9:43 PM9:50 PM9:55 PM10:00 PM
3110:30 PM10:32 PM10:35 PM10:38 PM10:43 PM10:50 PM10:55 PM11:00 PM
+

wheelchair_icon- Wheelchair Accessible Bus

+
+ + + + + --- /dev/null +++ b/maxious-canberra-transit-feed/source-html/Route_39.html @@ -1,1 +1,601 @@ + + + + + + + + +Route 39 + + + + + + + +
+

Chosen services: 39

+ +

View timetable and map

+ This timetable is effective from Monday 25 May 2009. +

Weekdays

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
 City Interchange
+ Platform 8
Macarthur\NorthbourneDickson Shops/Antill StWatsonWatson TerminusWatsonDickson Shops/Antill StMacarthur\NorthbourneCity Interchange
39...............5:49 AM5:55 AM6:01 AM6:06 AM6:10 AM6:17 AM
39wheelchair access6:09 AM6:15 AM6:18 AM6:24 AM6:30 AM6:36 AM6:41 AM6:45 AM6:52 AM
396:39 AM6:45 AM6:48 AM6:54 AM7:00 AM7:06 AM7:11 AM7:15 AM7:22 AM
39...............7:07 AM7:13 AM7:19 AM7:24 AM7:28 AM7:41 AM
397:03 AM7:09 AM7:12 AM7:18 AM7:24 AM7:30 AM7:36 AM7:42 AM7:57 AM
39...............7:26 AM7:32 AM7:38 AM7:44 AM7:50 AM8:05 AM
397:18 AM7:24 AM7:27 AM7:34 AM7:40 AM7:46 AM7:52 AM7:58 AM8:13 AM
39...............7:42 AM7:48 AM7:54 AM8:00 AM8:06 AM8:21 AM
397:33 AM7:39 AM7:42 AM7:49 AM7:55 AM8:01 AM8:07 AM8:13 AM8:28 AM
39...............7:56 AM8:02 AM8:08 AM8:14 AM8:20 AM8:35 AM
397:48 AM7:54 AM7:57 AM8:04 AM8:10 AM8:16 AM8:22 AM8:28 AM8:43 AM
397:58 AM8:04 AM8:07 AM8:14 AM8:20 AM8:26 AM8:32 AM8:38 AM8:53 AM
39...............8:24 AM8:30 AM8:36 AM8:42 AM8:48 AM9:03 AM
398:18 AM8:24 AM8:27 AM8:34 AM8:40 AM8:46 AM8:52 AM8:58 AM9:13 AM
398:33 AM8:39 AM8:42 AM8:49 AM8:55 AM9:01 AM9:07 AM9:13 AM9:28 AM
399:10 AM9:16 AM9:19 AM9:24 AM9:30 AM9:35 AM9:40 AM9:43 AM9:49 AM
399:40 AM9:46 AM9:49 AM9:54 AM10:00 AM10:05 AM10:10 AM10:13 AM10:19 AM
3910:10 AM10:16 AM10:19 AM10:24 AM10:30 AM10:35 AM10:40 AM10:43 AM10:49 AM
3910:40 AM10:46 AM10:49 AM10:54 AM11:00 AM11:05 AM11:10 AM11:13 AM11:19 AM
3911:10 AM11:16 AM11:19 AM11:24 AM11:30 AM11:35 AM11:40 AM11:43 AM11:49 AM
3911:40 AM11:46 AM11:49 AM11:54 AM12:00 PM12:05 PM12:10 PM12:13 PM12:19 PM
3912:10 PM12:16 PM12:19 PM12:24 PM12:30 PM12:35 PM12:40 PM12:43 PM12:49 PM
3912:40 PM12:46 PM12:49 PM12:54 PM1:00 PM1:05 PM1:10 PM1:13 PM1:19 PM
391:10 PM1:16 PM1:19 PM1:24 PM1:30 PM1:35 PM1:40 PM1:43 PM1:49 PM
391:40 PM1:46 PM1:49 PM1:54 PM2:00 PM2:05 PM2:10 PM2:13 PM2:19 PM
392:10 PM2:16 PM2:19 PM2:24 PM2:30 PM2:35 PM2:40 PM2:43 PM2:49 PM
392:40 PM2:46 PM2:49 PM2:54 PM3:00 PM3:07 PM3:13 PM3:17 PM3:24 PM
393:09 PM3:15 PM3:18 PM3:24 PM3:30 PM3:37 PM3:43 PM3:47 PM3:54 PM
393:28 PM3:34 PM3:37 PM3:43 PM3:49 PM3:56 PM4:02 PM4:06 PM4:13 PM
393:58 PM4:04 PM4:07 PM4:13 PM4:19 PM4:26 PM4:32 PM4:36 PM4:43 PM
394:17 PM4:23 PM4:26 PM4:32 PM4:38 PM4:45 PM4:51 PM4:55 PM5:02 PM
394:32 PM4:38 PM4:41 PM4:47 PM4:53 PM5:00 PM5:06 PM5:10 PM5:17 PM
394:47 PM4:53 PM4:56 PM5:02 PM5:08 PM5:15 PM5:21 PM5:25 PM5:32 PM
395:06 PM5:12 PM5:15 PM5:21 PM5:27 PM5:34 PM5:40 PM5:44 PM5:51 PM
395:12 PM5:18 PM5:21 PM5:27 PM5:33 PM5:40 PM...............
395:21 PM5:27 PM5:30 PM5:36 PM5:42 PM5:49 PM5:55 PM5:59 PM6:06 PM
395:36 PM5:42 PM5:45 PM5:51 PM5:57 PM6:04 PM6:10 PM6:14 PM6:21 PM
395:46 PM5:52 PM5:55 PM6:01 PM6:07 PM6:14 PM...............
395:55 PM6:01 PM6:04 PM6:10 PM6:16 PM6:23 PM6:29 PM6:32 PM6:38 PM
396:10 PM6:16 PM6:19 PM6:25 PM6:31 PM6:36 PM6:41 PM6:44 PM6:50 PM
397:10 PM7:16 PM7:19 PM7:24 PM7:30 PM7:35 PM7:40 PM7:43 PM7:49 PM
398:10 PM8:16 PM8:19 PM8:24 PM8:30 PM8:35 PM8:40 PM8:43 PM8:49 PM
399:10 PM9:16 PM9:19 PM9:24 PM9:30 PM9:35 PM9:40 PM9:43 PM9:49 PM
3910:10 PM10:16 PM10:19 PM10:24 PM10:30 PM10:35 PM10:40 PM10:43 PM10:49 PM
3911:10 PM11:16 PM11:19 PM11:24 PM11:30 PM11:35 PM...............
+

wheelchair_icon- Wheelchair Accessible Bus

+
+ + + + + --- /dev/null +++ b/maxious-canberra-transit-feed/source-html/Route_4.html @@ -1,1 +1,705 @@ + + + + + + + + +Route 4 + + + + + +
+

Chosen services: 4

+ +

View timetable and map

+ +

Route 4 diversion: 22 February to 5 March

+

Due to roadwork at the Intersection of Captain Cook Crescent and Jerrabomberra Avenue, Route 4 from the City to Geoscience Australia will be diverted around the intersection and travel along Boolimba and Sturt Avenue before rejoining at Jerrabomberra Avenue.

+

Weekdays

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
 City Interchange
+ Platform 9
Russell OfficesKings Ave/National CrtKingstonManuka Captain CookNarrabundah CollegeNarrabundah TerminusGeoscience Australia
46:37 AM6:45 AM6:49 AM6:53 AM6:56 AM7:04 AM.....7:07 AM
4wheelchair access7:07 AM7:15 AM7:19 AM7:23 AM7:26 AM7:35 AM.....7:38 AM
4wheelchair access7:37 AM7:46 AM7:51 AM7:56 AM7:59 AM8:09 AM.....8:12 AM
48:07 AM8:16 AM8:21 AM8:26 AM8:29 AM8:39 AM.....8:42 AM
48:21 AM8:30 AM8:35 AM8:40 AM8:43 AM8:53 AM.....8:56 AM
48:37 AM8:46 AM8:51 AM8:56 AM8:59 AM9:09 AM.....9:12 AM
49:07 AM9:16 AM9:21 AM9:26 AM9:29 AM9:38 AM.....9:41 AM
49:37 AM9:45 AM9:49 AM9:53 AM9:56 AM10:05 AM.....10:08 AM
410:07 AM10:15 AM10:19 AM10:23 AM10:26 AM10:35 AM.....10:38 AM
410:37 AM10:45 AM10:49 AM10:53 AM10:56 AM11:05 AM.....11:08 AM
411:07 AM11:15 AM11:19 AM11:23 AM11:26 AM11:35 AM.....11:38 AM
411:37 AM11:45 AM11:49 AM11:53 AM11:56 AM12:05 PM.....12:08 PM
412:07 PM12:15 PM12:19 PM12:23 PM12:26 PM12:35 PM.....12:38 PM
412:37 PM12:45 PM12:49 PM12:53 PM12:56 PM1:05 PM.....1:08 PM
4wheelchair access1:07 PM1:15 PM1:19 PM1:23 PM1:26 PM1:35 PM.....1:38 PM
41:37 PM1:45 PM1:49 PM1:53 PM1:56 PM2:05 PM.....2:08 PM
42:07 PM2:15 PM2:19 PM2:23 PM2:26 PM2:35 PM.....2:38 PM
42:37 PM2:45 PM2:49 PM2:53 PM2:56 PM3:05 PM.....3:08 PM
43:07 PM3:16 PM3:21 PM3:26 PM3:29 PM3:38 PM.....3:41 PM
4wheelchair access3:37 PM3:46 PM3:51 PM3:56 PM3:59 PM4:08 PM.....4:11 PM
44:07 PM4:16 PM4:21 PM4:26 PM4:29 PM4:38 PM.....4:41 PM
44:37 PM4:46 PM4:51 PM4:56 PM4:59 PM5:08 PM.....5:11 PM
4wheelchair access5:07 PM5:16 PM5:21 PM5:26 PM5:29 PM5:38 PM.....5:41 PM
45:37 PM5:46 PM5:51 PM5:56 PM5:59 PM6:08 PM6:11 PM.....
4wheelchair access6:37 PM6:45 PM6:49 PM6:53 PM6:56 PM7:01 PM7:04 PM.....
47:37 PM7:45 PM7:49 PM7:53 PM7:56 PM8:01 PM8:04 PM.....
48:37 PM8:45 PM8:49 PM8:53 PM8:56 PM9:01 PM9:04 PM.....
49:37 PM9:45 PM9:49 PM9:53 PM9:56 PM10:01 PM10:04 PM.....
410:37 PM10:45 PM10:49 PM10:53 PM10:56 PM11:01 PM11:04 PM.....
4wheelchair access11:37 PM11:45 PM11:49 PM11:53 PM11:56 PM12:01 AM12:04 AM.....
+

 

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
 Geoscience AustraliaNarrabundah TerminusNarrabundah CollegeManuka, Captain CookKingstonKings Ave/National CrtRussell OfficesCity Interchange
47:12 AM.....7:15 AM7:22 AM7:25 AM7:29 AM7:34 AM7:43 AM
4wheelchair access7:43 AM.....7:46 AM7:55 AM7:59 AM8:04 AM8:09 AM8:18 AM
4wheelchair access8:17 AM.....8:20 AM8:29 AM8:33 AM8:38 AM8:43 AM8:52 AM
48:47 AM.....8:50 AM8:59 AM9:03 AM9:08 AM9:13 AM9:22 AM
49:17 AM.....9:20 AM9:29 AM9:32 AM9:36 AM9:40 AM9:48 AM
4wheelchair access9:46 AM.....9:49 AM9:56 AM9:59 AM10:03 AM10:07 AM10:15 AM
410:13 AM.....10:16 AM10:23 AM10:26 AM10:30 AM10:34 AM10:42 AM
410:43 AM.....10:46 AM10:53 AM10:56 AM11:00 AM11:04 AM11:12 AM
411:13 AM.....11:16 AM11:23 AM11:26 AM11:30 AM11:34 AM11:42 AM
411:43 AM.....11:46 AM11:53 AM11:56 AM12:00 PM12:04 PM12:12 PM
412:13 PM.....12:16 PM12:23 PM12:26 PM12:30 PM12:34 PM12:42 PM
412:43 PM.....12:46 PM12:53 PM12:56 PM1:00 PM1:04 PM1:12 PM
41:13 PM.....1:16 PM1:23 PM1:26 PM1:30 PM1:34 PM1:42 PM
4wheelchair access1:43 PM.....1:46 PM1:53 PM1:56 PM2:00 PM2:04 PM2:12 PM
42:13 PM.....2:16 PM2:23 PM2:26 PM2:30 PM2:34 PM2:42 PM
4wheelchair access2:43 PM.....2:46 PM2:53 PM2:56 PM3:00 PM3:05 PM3:14 PM
43:13 PM.....3:16 PM3:25 PM3:29 PM3:34 PM3:39 PM3:48 PM
43:46 PM.....3:49 PM3:58 PM4:02 PM4:07 PM4:12 PM4:21 PM
44:16 PM.....4:19 PM4:28 PM4:32 PM4:37 PM4:42 PM4:51 PM
4wheelchair access4:46 PM.....4:49 PM4:56 PM5:00 PM5:05 PM5:10 PM5:21 PM
45:16 PM.....5:19 PM5:28 PM5:32 PM5:37 PM5:42 PM5:51 PM
4wheelchair access5:46 PM.....5:49 PM5:58 PM6:02 PM6:07 PM6:12 PM6:21 PM
4.....6:16 PM6:19 PM6:28 PM6:32 PM6:36 PM6:40 PM6:48 PM
4wheelchair access.....7:09 PM7:12 PM7:17 PM7:20 PM7:24 PM7:28 PM7:36 PM
4.....8:09 PM8:12 PM8:17 PM8:20 PM8:24 PM8:28 PM8:36 PM
4.....9:09 PM9:12 PM9:17 PM9:20 PM9:24 PM9:28 PM9:36 PM
4.....10:09 PM10:12 PM10:17 PM10:20 PM10:24 PM10:28 PM10:36 PM
4.....11:09 PM11:12 PM11:17 PM11:20 PM11:24 PM11:28 PM11:36 PM
+

wheelchair_icon- Wheelchair Accessible Bus

+
+ + + + + --- /dev/null +++ b/maxious-canberra-transit-feed/source-html/Route_43.html @@ -1,1 +1,471 @@ + + + + + + + + +Route 43 + + + + + + + +
+

Chosen services: 43

+ +

View timetable and map

+ This timetable is effective from Monday 25 May 2009. +

Weekdays

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
 Cameron Ave Station
+ Platform 5
Lathlain St Station
+ Platform 5
Cohen St Station
+ Platform 5
KippaxMacgregor ShopsCharnwoodMacgregor ShopsKippaxCohen St StationLathlain St StationCameron Ave Station
43....................6:22 AM6:29 AM6:38 AM6:43 AM6:48 AM6:50 AM6:54 AM
43wheelchair access....................6:41 AM6:48 AM6:57 AM7:02 AM7:07 AM7:09 AM7:13 AM
43wheelchair access6:45 AM6:47 AM6:51 AM6:56 AM7:01 AM7:08 AM7:17 AM7:22 AM7:27 AM7:29 AM7:33 AM
43....................7:21 AM7:28 AM7:39 AM7:44 AM7:52 AM7:54 AM7:58 AM
43....................7:42 AM7:49 AM8:00 AM8:05 AM8:13 AM8:15 AM8:19 AM
43....................8:03 AM8:10 AM8:21 AM8:26 AM8:34 AM8:36 AM8:40 AM
43....................8:25 AM8:32 AM8:43 AM8:48 AM8:56 AM8:58 AM9:02 AM
438:24 AM8:26 AM8:30 AM8:38 AM8:43 AM8:50 AM9:01 AM9:06 AM9:14 AM9:16 AM9:20 AM
438:44 AM8:46 AM8:50 AM8:58 AM9:03 AM9:10 AM9:21 AM9:26 AM9:33 AM9:35 AM9:39 AM
439:04 AM9:06 AM9:10 AM9:18 AM9:23 AM9:30 AM9:39 AM9:44 AM9:50 AM9:52 AM9:56 AM
4310:04 AM10:06 AM10:10 AM10:16 AM10:21 AM10:28 AM10:37 AM10:42 AM10:48 AM10:50 AM10:54 AM
4311:04 AM11:06 AM11:10 AM11:16 AM11:21 AM11:28 AM11:37 AM11:42 AM11:48 AM11:50 AM11:54 AM
4312:04 PM12:06 PM12:10 PM12:16 PM12:21 PM12:28 PM12:37 PM12:42 PM12:48 PM12:50 PM12:54 PM
43wheelchair access1:04 PM1:06 PM1:10 PM1:16 PM1:21 PM1:28 PM1:37 PM1:42 PM1:48 PM1:50 PM1:54 PM
432:04 PM2:06 PM2:10 PM2:16 PM2:21 PM2:28 PM2:37 PM2:42 PM2:48 PM2:50 PM2:54 PM
432:55 PM2:57 PM3:01 PM3:09 PM3:14 PM3:21 PM3:32 PM3:37 PM3:45 PM3:47 PM3:51 PM
433:24 PM3:26 PM3:30 PM3:38 PM3:43 PM3:50 PM4:01 PM4:06 PM4:14 PM4:16 PM4:20 PM
433:44 PM3:46 PM3:50 PM3:58 PM4:03 PM4:10 PM4:21 PM4:26 PM4:34 PM4:36 PM4:40 PM
434:04 PM4:06 PM4:10 PM4:18 PM4:23 PM4:30 PM4:41 PM4:46 PM4:54 PM4:56 PM5:00 PM
434:24 PM4:26 PM4:30 PM4:38 PM4:43 PM4:50 PM5:01 PM5:06 PM5:14 PM5:16 PM5:20 PM
434:44 PM4:46 PM4:50 PM4:58 PM5:03 PM5:10 PM5:21 PM5:26 PM5:34 PM5:36 PM5:40 PM
435:04 PM5:06 PM5:10 PM5:18 PM5:23 PM5:30 PM5:41 PM5:46 PM5:54 PM5:56 PM6:00 PM
435:24 PM5:26 PM5:30 PM5:38 PM5:43 PM5:50 PM6:01 PM6:06 PM6:14 PM6:16 PM6:20 PM
436:04 PM6:06 PM6:10 PM6:18 PM6:23 PM6:29 PM6:39 PM6:44 PM6:49 PM6:51 PM6:54 PM
437:04 PM7:06 PM7:09 PM7:14 PM7:19 PM7:26 PM7:35 PM7:40 PM7:45 PM7:47 PM7:50 PM
438:04 PM8:06 PM8:09 PM8:14 PM8:19 PM8:26 PM8:35 PM8:40 PM8:45 PM8:47 PM8:50 PM
439:04 PM9:06 PM9:09 PM9:14 PM9:19 PM9:26 PM9:35 PM9:40 PM9:45 PM9:47 PM9:50 PM
43wheelchair access10:04 PM10:06 PM10:09 PM10:14 PM10:19 PM10:26 PM10:35 PM10:40 PM10:45 PM10:47 PM10:50 PM
43wheelchair access11:04 PM11:06 PM11:09 PM11:14 PM11:19 PM11:26 PM11:35 PM....................
+

wheelchair_icon- Wheelchair Accessible Bus

+
+ + + + + --- /dev/null +++ b/maxious-canberra-transit-feed/source-html/Route_44.html @@ -1,1 +1,526 @@ + + + + + + + + +Route_44 + + + + + +
+

Chosen services: 44

+ +

View timetable and map

+

This timetable is effective from Monday 25 May 2009.

+ +

Weekdays

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

Cameron Ave Station
+ Platform 5
Lathlain St Sation
+ Platform 5
Cohen St Station
+ Platform 5
Belconnen Way HigginsHoltKippax Centre
447:34 AM7:36 AM7:40 AM7:46 AM7:53 AM8:02 AM8:04 AM
44wheelchair access8:03 AM8:05 AM8:09 AM8:15 AM8:22 AM8:31 AM8:33 AM
449:03 AM9:05 AM9:09 AM9:15 AM9:22 AM9:31 AM9:33 AM
4410:03 AM10:05 AM10:09 AM10:14 AM10:20 AM10:30 AM10:32 AM
4411:03 AM11:05 AM11:09 AM11:14 AM11:20 AM11:30 AM11:32 AM
4412:03 PM12:05 PM12:09 PM12:14 PM12:20 PM12:30 PM12:32 PM
441:03 PM1:05 PM1:09 PM1:14 PM1:20 PM1:30 PM1:32 PM
44wheelchair access2:03 PM2:05 PM2:09 PM2:14 PM2:20 PM2:30 PM2:32 PM
443:13 PM3:15 PM3:19 PM3:25 PM3:32 PM3:41 PM3:44 PM
443:45 PM3:47 PM3:51 PM3:57 PM4:04 PM4:13 PM4:16 PM
444:19 PM4:21 PM4:25 PM4:31 PM4:38 PM4:47 PM4:50 PM
444:50 PM4:52 PM4:56 PM5:02 PM5:09 PM5:18 PM5:21 PM
445:24 PM5:26 PM5:30 PM5:36 PM5:43 PM5:52 PM5:55 PM
445:55 PM5:57 PM6:01 PM6:07 PM6:14 PM6:23 PM6:26 PM
446:29 PM6:31 PM6:34 PM6:39 PM6:45 PM6:55 PM6:57 PM
447:03 PM7:05 PM7:08 PM7:13 PM7:19 PM7:29 PM7:31 PM
448:03 PM8:05 PM8:08 PM8:13 PM8:19 PM8:29 PM8:31 PM
449:03 PM9:05 PM9:08 PM9:13 PM9:19 PM9:29 PM9:31 PM
4410:03 PM10:05 PM10:08 PM10:13 PM10:19 PM10:29 PM10:31 PM
4411:03 PM11:05 PM11:08 PM11:13 PM11:19 PM11:29 PM11:31 PM
+

 

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
 Kippax Centre HoltHigginsBelconnen Way Cohen St Station Lathlain St Station Cameron Ave Station
44wheelchair access6:05 AM6:07 AM6:15 AM6:20 AM6:25 AM6:27 AM6:31 AM
446:38 AM6:40 AM6:48 AM6:53 AM6:58 AM7:00 AM7:04 AM
447:05 AM7:07 AM7:15 AM7:20 AM7:25 AM7:27 AM7:31 AM
44wheelchair access..........7:32 AM7:39 AM7:45 AM7:47 AM7:51 AM
447:38 AM7:41 AM7:50 AM7:57 AM8:03 AM8:05 AM8:09 AM
448:07 AM8:10 AM8:19 AM8:26 AM8:32 AM8:34 AM8:38 AM
448:42 AM8:45 AM8:54 AM9:01 AM9:07 AM9:09 AM9:13 AM
449:12 AM9:15 AM9:24 AM9:31 AM9:37 AM9:39 AM9:43 AM
449:37 AM9:39 AM9:48 AM9:54 AM10:00 AM10:02 AM10:06 AM
4410:37 AM10:39 AM10:48 AM10:54 AM11:00 AM11:02 AM11:06 AM
4411:37 AM11:39 AM11:48 AM11:54 AM12:00 PM12:02 PM12:06 PM
4412:37 PM12:39 PM12:48 PM12:54 PM1:00 PM1:02 PM1:06 PM
441:37 PM1:39 PM1:48 PM1:54 PM2:00 PM2:02 PM2:06 PM
442:37 PM2:39 PM2:48 PM2:54 PM3:00 PM3:02 PM3:06 PM
443:13 PM3:15 PM3:24 PM3:31 PM3:37 PM3:39 PM3:43 PM
443:47 PM3:49 PM3:58 PM4:05 PM4:11 PM4:13 PM4:17 PM
444:18 PM4:20 PM4:29 PM4:36 PM4:42 PM4:44 PM4:48 PM
444:52 PM4:54 PM5:03 PM5:10 PM5:16 PM5:18 PM5:22 PM
445:23 PM5:25 PM5:34 PM5:41 PM5:47 PM5:49 PM5:53 PM
445:57 PM5:59 PM6:08 PM6:15 PM6:21 PM6:23 PM6:27 PM
446:28 PM6:30 PM6:39 PM6:45 PM6:50 PM6:52 PM6:55 PM
446:42 PM6:44 PM6:53 PM6:59 PM7:04 PM7:06 PM7:09 PM
447:37 PM7:39 PM7:48 PM7:54 PM7:59 PM8:01 PM8:04 PM
448:37 PM8:39 PM8:48 PM8:54 PM8:59 PM9:01 PM9:04 PM
449:37 PM9:39 PM9:48 PM9:54 PM9:59 PM10:01 PM10:04 PM
4410:37 PM10:39 PM10:48 PM10:54 PM10:59 PM11:01 PM11:04 PM
+

wheelchair_icon- Wheelchair Accessible Bus

+
+ + + + + --- /dev/null +++ b/maxious-canberra-transit-feed/source-html/Route_45.html @@ -1,1 +1,500 @@ + + + + + + + + +Route 45 + + + + + + + +
+

Chosen services: 45

+ +

View timetable and map

+ This timetable is effective from Monday 25 May 2009. +

Weekdays

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
 Cohen St Station
+ Platform 1
Lathlain St Station
+ Platform 3
Cameron Ave Station
+ Platform 2
Copland CollegeCnr Tillyard Dr & Spalding StCharnwoodCnr Kerrigan/LhotskyCharnwoodCnr Tillyard Dr & Spalding StCopland CollegeCameron Ave StationLathlain St StationCohen St Station
45.........................6:27 AM6:31 AM6:37 AM6:39 AM6:47 AM6:54 AM6:56 AM7:00 AM
45.........................6:57 AM7:01 AM7:07 AM7:09 AM7:17 AM7:24 AM7:26 AM7:30 AM
45.........................7:29 AM7:33 AM7:39 AM7:41 AM7:49 AM7:56 AM7:58 AM8:02 AM
45.........................7:59 AM8:03 AM8:09 AM8:11 AM8:19 AM8:26 AM8:28 AM8:32 AM
45S.........................8:22 AM8:26 AM8:32 AM8:34 AM8:42 AM8:49 AM8:51 AM8:55 AM
45S.........................8:44 AM8:48 AM8:54 AM8:56 AM9:04 AM9:11 AM9:13 AM9:17 AM
458:32 AM8:34 AM8:38 AM8:46 AM8:54 AM8:56 AM9:00 AM9:06 AM9:08 AM9:16 AM9:23 AM9:25 AM9:29 AM
459:02 AM9:04 AM9:08 AM9:16 AM9:24 AM9:26 AM9:30 AM9:36 AM9:38 AM9:46 AM9:53 AM9:55 AM9:59 AM
459:25 AM9:27 AM9:31 AM9:39 AM9:47 AM9:49 AM9:53 AM9:59 AM10:01 AM10:09 AM10:16 AM10:18 AM10:22 AM
4510:25 AM10:27 AM10:31 AM10:39 AM10:47 AM10:49 AM10:53 AM10:59 AM11:01 AM11:09 AM11:16 AM11:18 AM11:22 AM
45wheelchair access11:25 AM11:27 AM11:31 AM11:39 AM11:47 AM11:49 AM11:53 AM11:59 AM12:01 PM12:09 PM12:16 PM12:18 PM12:22 PM
45wheelchair access12:25 PM12:27 PM12:31 PM12:39 PM12:47 PM12:49 PM12:53 PM12:59 PM1:01 PM1:09 PM1:16 PM1:18 PM1:22 PM
45wheelchair access1:25 PM1:27 PM1:31 PM1:39 PM1:47 PM1:49 PM1:53 PM1:59 PM2:01 PM2:09 PM2:16 PM2:18 PM2:22 PM
452:25 PM2:27 PM2:31 PM2:39 PM2:47 PM2:49 PM2:53 PM2:59 PM3:01 PM3:09 PM3:16 PM3:18 PM3:22 PM
45s3:02 PM3:04 PM3:08 PM3:16 PM3:24 PM3:26 PM3:30 PM3:36 PM3:38 PM3:46 PM3:53 PM3:55 PM3:59 PM
453:32 PM3:34 PM3:38 PM3:46 PM3:54 PM3:56 PM4:00 PM4:06 PM4:08 PM4:16 PM4:23 PM4:25 PM4:29 PM
454:02 PM4:04 PM4:08 PM4:16 PM4:24 PM4:26 PM4:30 PM4:36 PM4:38 PM4:46 PM4:53 PM4:55 PM4:59 PM
454:32 PM4:34 PM4:38 PM4:46 PM4:54 PM4:56 PM5:00 PM5:06 PM5:08 PM5:16 PM5:23 PM5:25 PM5:29 PM
455:02 PM5:04 PM5:08 PM5:16 PM5:24 PM5:26 PM5:30 PM5:36 PM5:38 PM5:46 PM5:53 PM5:55 PM5:59 PM
455:32 PM5:34 PM5:38 PM5:46 PM5:54 PM5:56 PM6:00 PM6:06 PM6:08 PM6:16 PM6:23 PM6:25 PM6:29 PM
456:02 PM6:04 PM6:08 PM6:16 PM6:24 PM6:26 PM6:30 PM6:36 PM6:38 PM6:46 PM6:52 PM6:54 PM6:57 PM
456:25 PM6:27 PM6:31 PM6:38 PM6:46 PM6:48 PM6:52 PM6:58 PM7:00 PM7:08 PM7:14 PM7:16 PM7:19 PM
457:25 PM7:27 PM7:30 PM7:37 PM7:45 PM7:47 PM7:51 PM7:57 PM7:59 PM8:07 PM8:13 PM8:15 PM8:18 PM
458:25 PM8:27 PM8:30 PM8:37 PM8:45 PM8:47 PM8:51 PM8:57 PM8:59 PM9:07 PM9:13 PM9:15 PM9:18 PM
459:25 PM9:27 PM9:30 PM9:37 PM9:45 PM9:47 PM9:51 PM9:57 PM9:59 PM10:07 PM10:13 PM10:15 PM10:18 PM
4510:25 PM10:27 PM10:30 PM10:37 PM10:45 PM10:47 PM10:51 PM10:57 PM10:59 PM11:07 PM11:13 PM11:15 PM11:18 PM
4511:25 PM11:27 PM11:30 PM11:37 PM11:45 PM11:47 PM11:51 PM11:57 PM.........................
+

S - This service travels via Alfred Hill Drive, dropping off students for Mount Rogers Primary - School Days Only

+

wheelchair_icon- Wheelchair Accessible Bus

+
+ + + + + --- /dev/null +++ b/maxious-canberra-transit-feed/source-html/Route_5.html @@ -1,1 +1,1196 @@ + + + + + + + + +Route 5 + + + + + + + +
+

Chosen services: 5

+ +

View timetable and map

+ +

Due to construction at The Canberra Hospital, The bus stops in Bateson Road will be out of service from Saturday 28 November until further notice. Passengers can still use the main stops along Hospital Road. There is no change to the direction of the route.

+

Please note: an additional timing point has been added to include Manuka.

+

Weekdays

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
 Woden Interchange
+ Platform 14
Canberra HospitalNarrabundah CollegeManuka, Captain CookKingstonKings Ave/National CrtRussell OfficesCity Interchange
+ Platform 8
Macarthur\NorthbourneNorthbourne Ave/Antill StFlemington Rd/Sandford StGungahlin Market Place
5wheelchair access6:17 AM6:23 AM6:32 AM6:38 AM6:42 AM6:46 AM6:50 AM7:07 AM7:10 AM7:12 AM7:17 AM7:23 AM
5wheelchair access6:46 AM6:54 AM7:04 AM7:16 AM7:19 AM7:23 AM7:27 AM7:36 AM7:42 AM7:44 AM7:52 AM8:00 AM
57:13 AM7:21 AM7:31 AM7:43 AM7:46 AM7:52 AM7:57 AM8:08 AM8:15 AM8:17 AM8:25 AM8:33 AM
57:28 AM7:36 AM7:46 AM7:59 AM8:02 AM8:08 AM8:13 AM8:24 AM8:31 AM8:33 AM8:41 AM8:49 AM
5wheelchair access7:42 AM7:50 AM8:00 AM8:13 AM8:16 AM8:22 AM8:27 AM8:38 AM8:45 AM8:47 AM8:55 AM9:02 AM
57:57 AM8:05 AM8:15 AM8:28 AM8:31 AM8:37 AM8:42 AM8:53 AM9:00 AM9:02 AM9:10 AM9:16 AM
58:13 AM8:21 AM8:31 AM8:44 AM8:47 AM8:53 AM8:58 AM9:07 AM9:13 AM9:15 AM9:23 AM9:29 AM
58:30 AM8:38 AM8:48 AM9:01 AM9:04 AM9:08 AM9:12 AM9:21 AM9:27 AM9:29 AM9:37 AM9:43 AM
58:47 AM8:55 AM9:05 AM9:17 AM9:20 AM9:24 AM9:28 AM9:37 AM9:43 AM9:45 AM9:53 AM9:59 AM
59:19 AM9:27 AM9:37 AM9:49 AM9:52 AM9:56 AM10:00 AM10:09 AM10:15 AM10:17 AM10:25 AM10:31 AM
5wheelchair access9:49 AM9:57 AM10:07 AM10:19 AM10:22 AM10:26 AM10:30 AM10:39 AM10:45 AM10:47 AM10:55 AM11:01 AM
510:19 AM10:27 AM10:37 AM10:49 AM10:52 AM10:56 AM11:00 AM11:09 AM11:15 AM11:17 AM11:25 AM11:31 AM
510:49 AM10:57 AM11:07 AM11:19 AM11:22 AM11:26 AM11:30 AM11:39 AM11:45 AM11:47 AM11:55 AM12:01 PM
511:19 AM11:27 AM11:37 AM11:49 AM11:52 AM11:56 AM12:00 PM12:09 PM12:15 PM12:17 PM12:25 PM12:31 PM
511:49 AM11:57 AM12:07 PM12:19 PM12:22 PM12:26 PM12:30 PM12:39 PM12:45 PM12:47 PM12:55 PM1:01 PM
5wheelchair access12:19 PM12:27 PM12:37 PM12:49 PM12:52 PM12:56 PM1:00 PM1:09 PM1:15 PM1:17 PM1:25 PM1:31 PM
512:49 PM12:57 PM1:07 PM1:19 PM1:22 PM1:26 PM1:30 PM1:39 PM1:45 PM1:47 PM1:55 PM2:01 PM
51:19 PM1:27 PM1:37 PM1:49 PM1:52 PM1:56 PM2:00 PM2:09 PM2:15 PM2:17 PM2:25 PM2:31 PM
5wheelchair access1:49 PM1:54 PM2:04 PM2:16 PM2:19 PM2:23 PM2:27 PM2:39 PM2:45 PM2:47 PM2:55 PM3:01 PM
52:19 PM2:27 PM2:37 PM2:49 PM2:52 PM2:56 PM3:00 PM3:08 PM....................
52:49 PM2:57 PM3:07 PM3:19 PM3:22 PM3:26 PM3:30 PM3:38 PM....................
53:15 PM3:23 PM3:33 PM3:45 PM3:48 PM3:52 PM3:56 PM4:05 PM....................
53:41 PM3:49 PM3:59 PM4:12 PM4:15 PM4:21 PM4:26 PM4:35 PM....................
5wheelchair access4:11 PM4:19 PM4:29 PM4:42 PM4:45 PM4:51 PM4:56 PM5:05 PM....................
54:42 PM4:50 PM5:00 PM5:13 PM5:16 PM5:22 PM5:27 PM5:36 PM....................
55:15 PM5:23 PM5:33 PM5:46 PM5:49 PM5:55 PM6:00 PM6:08 PM....................
55:50 PM5:58 PM6:07 PM6:19 PM6:22 PM6:26 PM6:30 PM6:39 PM6:45 PM6:47 PM6:54 PM7:00 PM
5...................................7:09 PM7:15 PM7:17 PM7:24 PM7:30 PM
5wheelchair access6:51 PM6:58 PM7:07 PM7:19 PM7:22 PM7:26 PM7:30 PM7:39 PM7:45 PM7:47 PM7:54 PM8:00 PM
5...................................8:09 PM8:15 PM8:17 PM8:24 PM8:30 PM
57:51 PM7:58 PM8:07 PM8:19 PM8:22 PM8:26 PM8:30 PM8:39 PM8:45 PM8:47 PM8:54 PM9:00 PM
5...................................9:09 PM9:15 PM9:17 PM9:24 PM9:30 PM
58:51 PM8:58 PM9:07 PM9:19 PM9:22 PM9:26 PM9:30 PM9:39 PM9:45 PM9:47 PM9:54 PM10:00 PM
5...................................10:09 PM10:15 PM10:17 PM10:24 PM10:30 PM
59:51 PM9:58 PM10:07 PM10:19 PM10:22 PM10:26 PM10:30 PM10:39 PM10:45 PM10:47 PM10:54 PM11:00 PM
510:51 PM10:58 PM11:07 PM11:19 PM11:22 PM11:26 PM11:30 PM11:38 PM....................
5...................................11:09 PM11:15 PM11:17 PM11:24 PM11:30 PM
+

 

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
 Gungahlin Market PlaceFlemington Rd/Sandford StNorthbourne Ave/Antill StMacarthur\NorthbourneCity Interchange
+ Platform 9
Russell OfficesKings Ave/National CrtKingstonManuka, Captain CookNarrabundah CollegeCanberra HospitalWoden Interchange
5....................6:30 AM6:38 AM6:42 AM6:46 AM6:49 AM7:01 AM7:11 AM7:19 AM
5....................7:10 AM7:18 AM7:22 AM7:26 AM7:29 AM7:41 AM7:52 AM8:00 AM
5....................7:30 AM7:38 AM7:42 AM7:46 AM7:49 AM8:02 AM8:14 AM8:22 AM
5....................7:40 AM7:48 AM7:53 AM7:58 AM8:01 AM8:14 AM8:26 AM8:34 AM
5....................7:55 AM8:04 AM8:09 AM8:14 AM8:17 AM8:30 AM8:42 AM8:50 AM
5....................8:10 AM8:19 AM8:24 AM8:29 AM8:32 AM8:45 AM8:57 AM9:05 AM
5....................8:30 AM8:39 AM8:44 AM8:49 AM8:52 AM9:05 AM9:15 AM9:23 AM
5....................8:40 AM8:49 AM8:54 AM8:59 AM9:02 AM9:14 AM9:24 AM9:32 AM
5wheelchair access9:09 AM9:15 AM9:22 AM9:24 AM9:32 AM9:40 AM9:44 AM9:48 AM9:51 AM10:03 AM10:13 AM10:21 AM
5wheelchair access9:39 AM9:45 AM9:52 AM9:54 AM10:02 AM10:10 AM10:14 AM10:18 AM10:21 AM10:33 AM10:43 AM10:51 AM
510:09 AM10:15 AM10:22 AM10:24 AM10:32 AM10:40 AM10:44 AM10:48 AM10:51 AM11:03 AM11:13 AM11:21 AM
510:39 AM10:45 AM10:52 AM10:54 AM11:02 AM11:10 AM11:14 AM11:18 AM11:21 AM11:33 AM11:43 AM11:51 AM
511:09 AM11:15 AM11:22 AM11:24 AM11:32 AM11:40 AM11:44 AM11:48 AM11:51 AM12:03 PM12:13 PM12:21 PM
511:39 AM11:45 AM11:52 AM11:54 AM12:02 PM12:10 PM12:14 PM12:18 PM12:21 PM12:33 PM12:43 PM12:51 PM
512:09 PM12:15 PM12:22 PM12:24 PM12:32 PM12:40 PM12:44 PM12:48 PM12:51 PM1:03 PM1:13 PM1:21 PM
512:39 PM12:45 PM12:52 PM12:54 PM1:02 PM1:10 PM1:14 PM1:18 PM1:21 PM1:33 PM1:43 PM1:51 PM
51:09 PM1:15 PM1:22 PM1:24 PM1:32 PM1:40 PM1:44 PM1:48 PM1:51 PM2:03 PM2:13 PM2:21 PM
5wheelchair access1:39 PM1:45 PM1:52 PM1:54 PM2:02 PM2:10 PM2:14 PM2:18 PM2:21 PM2:33 PM2:43 PM2:51 PM
52:09 PM2:15 PM2:22 PM2:24 PM2:32 PM2:40 PM2:44 PM2:48 PM2:51 PM3:03 PM3:13 PM3:21 PM
52:39 PM2:45 PM2:52 PM2:54 PM3:02 PM3:10 PM3:14 PM3:18 PM3:21 PM3:33 PM3:43 PM3:51 PM
5wheelchair access3:09 PM3:15 PM3:22 PM3:24 PM3:32 PM3:40 PM3:44 PM3:48 PM3:51 PM4:03 PM4:15 PM4:23 PM
53:39 PM3:45 PM3:52 PM3:54 PM4:03 PM4:12 PM4:17 PM4:22 PM4:25 PM4:38 PM4:50 PM4:58 PM
54:07 PM4:15 PM4:22 PM4:24 PM4:33 PM4:42 PM4:47 PM4:52 PM4:55 PM5:08 PM5:20 PM5:28 PM
54:37 PM4:45 PM4:52 PM4:54 PM5:03 PM5:12 PM5:17 PM5:22 PM5:25 PM5:38 PM5:50 PM5:58 PM
55:10 PM5:18 PM5:25 PM5:27 PM5:36 PM5:45 PM5:50 PM5:54 PM5:56 PM6:08 PM6:17 PM6:25 PM
55:37 PM5:45 PM5:52 PM5:54 PM6:02 PM6:10 PM6:14 PM6:18 PM6:21 PM6:33 PM6:42 PM6:50 PM
56:09 PM6:15 PM6:22 PM6:24 PM6:32 PM6:40 PM6:44 PM6:48 PM6:51 PM7:03 PM7:12 PM7:20 PM
56:39 PM6:45 PM6:52 PM6:54 PM7:02 PM7:10 PM7:14 PM7:18 PM7:21 PM7:33 PM7:42 PM7:50 PM
57:09 PM7:15 PM7:22 PM7:24 PM7:32 PM7:40 PM7:44 PM7:48 PM7:51 PM8:03 PM8:12 PM8:20 PM
57:39 PM7:45 PM7:52 PM7:54 PM8:01 PM...................................
5wheelchair access8:09 PM8:15 PM8:22 PM8:24 PM8:32 PM8:40 PM8:44 PM8:48 PM8:51 PM9:03 PM9:12 PM9:20 PM
58:39 PM8:45 PM8:52 PM8:54 PM9:01 PM...................................
59:09 PM9:15 PM9:22 PM9:24 PM9:32 PM9:40 PM9:44 PM9:48 PM9:51 PM10:03 PM10:12 PM10:20 PM
59:39 PM9:45 PM9:52 PM9:54 PM10:01 PM...................................
510:09 PM10:15 PM10:22 PM10:24 PM10:32 PM10:40 PM10:44 PM10:48 PM10:51 PM11:03 PM11:12 PM11:20 PM
510:39 PM10:45 PM10:52 PM10:54 PM11:01 PM...................................
511:09 PM11:15 PM11:22 PM11:24 PM11:31 PM...................................
+

wheelchair_icon- Wheelchair Accessible Bus

+
+ + + + + --- /dev/null +++ b/maxious-canberra-transit-feed/source-html/Route_51.html @@ -1,1 +1,967 @@ + + + + + + + + +Route 51 + + + + + + + +
+

Chosen services: 51

+ +

View timetable and map

+ This timetable is effective from Monday 25 May 2009. +

Weekdays

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
 Cohen St Station
+ Platform 1
Lathlain St Station
+ Platform 2
Cameron Ave Station
+ Platform 2
Chuculba/William SlimFederation SquareNicholls PrimaryNgunnawal PrimaryGungahlin Market PlaceHibberson/Kate CraceFlemington Rd/Sandford StNorthbourne Ave/Antill StMacarthur\NorthbourneCity Interchange
51....................5:32 AM5:41 AM5:50 AM5:59 AM6:02 AM....................
51....................6:16 AM6:25 AM6:34 AM6:43 AM6:46 AM....................
516:18 AM6:20 AM6:24 AM6:31 AM6:36 AM6:45 AM6:54 AM7:03 AM7:06 AM7:09 AM7:16 AM7:18 AM7:25 AM
51....................6:56 AM7:05 AM7:14 AM7:23 AM7:26 AM7:29 AM7:36 AM7:38 AM7:45 AM
516:54 AM6:56 AM7:00 AM7:07 AM7:12 AM7:21 AM7:30 AM7:39 AM7:42 AM7:45 AM7:56 AM8:01 AM8:15 AM
51...............7:21 AM7:26 AM7:35 AM7:44 AM7:53 AM7:56 AM8:01 AM8:12 AM8:17 AM8:32 AM
517:32 AM7:34 AM7:38 AM7:45 AM7:50 AM8:00 AM8:10 AM8:19 AM8:22 AM8:27 AM8:38 AM8:43 AM8:58 AM
51wheelchair access7:51 AM7:53 AM7:57 AM8:05 AM8:10 AM8:20 AM8:30 AM8:39 AM8:42 AM8:47 AM8:56 AM9:01 AM9:12 AM
51....................8:30 AM8:40 AM8:50 AM8:59 AM9:02 AM9:05 AM9:12 AM9:17 AM9:28 AM
518:38 AM8:40 AM8:44 AM8:52 AM8:57 AM9:06 AM9:15 AM9:24 AM9:27 AM9:30 AM9:37 AM9:39 AM9:46 AM
51wheelchair access9:13 AM9:15 AM9:19 AM9:26 AM9:31 AM9:40 AM9:49 AM9:58 AM10:01 AM....................
519:43 AM9:45 AM9:49 AM9:56 AM10:01 AM10:10 AM10:19 AM10:28 AM10:31 AM....................
51wheelchair access10:43 AM10:45 AM10:49 AM10:56 AM11:01 AM11:10 AM11:19 AM11:28 AM11:31 AM....................
51wheelchair access11:43 AM11:45 AM11:49 AM11:56 AM12:01 PM12:10 PM12:19 PM12:28 PM12:31 PM....................
5112:43 PM12:45 PM12:49 PM12:56 PM1:01 PM1:10 PM1:19 PM1:28 PM1:31 PM....................
511:43 PM1:45 PM1:49 PM1:56 PM2:01 PM2:10 PM2:19 PM2:28 PM2:31 PM....................
512:43 PM2:45 PM2:49 PM2:56 PM3:01 PM3:10 PM3:19 PM3:28 PM3:31 PM....................
513:38 PM3:40 PM3:44 PM3:51 PM3:56 PM4:06 PM4:16 PM4:25 PM4:28 PM....................
514:18 PM4:20 PM4:24 PM4:32 PM4:37 PM4:48 PM4:58 PM5:07 PM5:10 PM....................
514:38 PM4:40 PM4:44 PM4:52 PM4:57 PM5:08 PM5:18 PM5:27 PM5:30 PM....................
514:58 PM5:00 PM5:04 PM5:12 PM5:17 PM5:28 PM5:38 PM5:47 PM5:50 PM....................
515:17 PM5:19 PM5:23 PM5:31 PM5:36 PM5:47 PM5:57 PM6:06 PM6:09 PM....................
515:39 PM5:41 PM5:45 PM5:53 PM5:58 PM6:07 PM6:16 PM6:25 PM6:28 PM....................
516:43 PM6:45 PM6:48 PM6:55 PM7:00 PM7:09 PM7:18 PM7:27 PM7:30 PM....................
517:43 PM7:45 PM7:48 PM7:55 PM8:00 PM8:09 PM8:18 PM8:27 PM8:30 PM....................
51wheelchair access8:43 PM8:45 PM8:48 PM8:55 PM9:00 PM9:09 PM9:18 PM9:27 PM9:30 PM....................
519:43 PM9:45 PM9:48 PM9:55 PM10:00 PM10:09 PM10:18 PM10:27 PM10:30 PM....................

51

10:43 PM10:45 PM10:48 PM10:55 PM11:00 PM11:09 PM11:18 PM11:27 PM11:30 PM....................
+

 

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
 City Interchange
+ Platform 8
Macarthur\NorthbourneNorthbourne Ave/Antill StFlemington Rd/Sandford StHibberson/Kate CraceGungahlin Market PlaceNgunnawal PrimaryNicholls PrimaryFederation SquareChuculba/William SlimCameron Ave StationLathlain St StationCohen St Station
51....................7:01 AM7:04 AM7:13 AM7:23 AM7:33 AM7:38 AM7:48 AM7:50 AM7:54 AM
51....................7:21 AM7:24 AM7:33 AM7:43 AM7:53 AM7:58 AM8:09 AM8:11 AM8:15 AM
51wheelchair access....................7:41 AM7:44 AM7:53 AM8:03 AM8:13 AM8:18 AM8:29 AM8:31 AM8:35 AM
51....................8:00 AM8:03 AM8:12 AM8:22 AM8:32 AM8:37 AM8:48 AM8:50 AM8:54 AM
51wheelchair access....................8:21 AM8:24 AM8:33 AM8:43 AM8:53 AM8:58 AM9:06 AM9:08 AM9:12 AM
51....................8:40 AM8:43 AM8:52 AM9:02 AM9:11 AM9:16 AM9:23 AM9:25 AM9:29 AM
51....................9:40 AM9:43 AM9:52 AM10:01 AM10:10 AM10:15 AM10:22 AM10:24 AM10:28 AM
51....................10:40 AM10:43 AM10:52 AM11:01 AM11:10 AM11:15 AM11:22 AM11:24 AM11:28 AM
51wheelchair access....................11:40 AM11:43 AM11:52 AM12:01 PM12:10 PM12:15 PM12:22 PM12:24 PM12:28 PM
51....................12:40 PM12:43 PM12:52 PM1:01 PM1:10 PM1:15 PM1:22 PM1:24 PM1:28 PM
51....................1:40 PM1:43 PM1:52 PM2:01 PM2:10 PM2:15 PM2:22 PM2:24 PM2:28 PM
51....................2:40 PM2:43 PM2:52 PM3:01 PM3:10 PM3:15 PM3:22 PM3:24 PM3:28 PM
51....................3:07 PM3:10 PM3:19 PM3:28 PM3:37 PM3:42 PM3:49 PM3:51 PM3:55 PM
513:28 PM3:34 PM3:36 PM3:44 PM3:47 PM3:50 PM3:59 PM4:09 PM4:19 PM4:24 PM4:32 PM4:34 PM4:38 PM
51wheelchair access4:04 PM4:11 PM4:13 PM4:22 PM4:27 PM4:30 PM4:39 PM4:49 PM4:59 PM5:04 PM5:12 PM5:14 PM5:18 PM
514:24 PM4:31 PM4:33 PM4:42 PM4:47 PM4:50 PM4:59 PM5:09 PM5:19 PM5:24 PM5:32 PM5:34 PM5:38 PM
514:44 PM4:51 PM4:53 PM5:02 PM5:07 PM5:10 PM5:19 PM5:29 PM5:39 PM5:44 PM5:52 PM5:54 PM5:58 PM
515:08 PM5:15 PM5:17 PM5:26 PM5:31 PM5:34 PM5:43 PM5:53 PM6:03 PM6:08 PM6:15 PM6:17 PM6:20 PM
515:24 PM5:31 PM5:33 PM5:42 PM5:47 PM5:50 PM5:59 PM6:08 PM6:17 PM6:22 PM6:29 PM6:31 PM6:34 PM
515:35 PM5:42 PM5:44 PM5:53 PM5:58 PM6:01 PM6:10 PM6:19 PM6:28 PM6:33 PM6:40 PM6:42 PM6:45 PM
515:47 PM5:54 PM5:56 PM6:04 PM6:07 PM6:10 PM6:19 PM6:28 PM6:37 PM6:42 PM6:49 PM6:51 PM6:54 PM
516:13 PM6:19 PM6:21 PM6:28 PM6:31 PM6:34 PM6:43 PM6:52 PM7:01 PM7:06 PM7:13 PM7:15 PM7:18 PM
51....................7:40 PM7:43 PM7:52 PM8:01 PM8:10 PM8:15 PM8:22 PM8:24 PM8:27 PM
51....................8:40 PM8:43 PM8:52 PM9:01 PM9:10 PM9:15 PM9:22 PM9:24 PM9:27 PM
51wheelchair access....................9:40 PM9:43 PM9:52 PM10:01 PM10:10 PM10:15 PM10:22 PM10:24 PM10:27 PM
51....................10:40 PM10:43 PM10:52 PM11:01 PM11:10 PM11:15 PM11:22 PM11:24 PM11:27 PM
51....................11:40 PM11:43 PM11:52 PM12:01 AM12:10 AM12:15 AM12:22 AM12:24 AM12:27 AM
+

wheelchair_icon- Wheelchair Accessible Bus

+
+ + + + + --- /dev/null +++ b/maxious-canberra-transit-feed/source-html/Route_52.html @@ -1,1 +1,983 @@ + + + + + + + + +Route 52 + + + + + + + +
+

Chosen services: 52

+ +

View timetable and map

+ This timetable is effective from Monday 25 May 2009. +

Weekdays

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
 Cohen St Station
+ Platform 1
Lathlain St Station
+ Platform 2
Cameron Ave Station
+ Platform 2
Chuculba/William SlimFederation SquareNicholls PrimaryNgunnawal PrimaryGungahlin Market PlaceHibberson/Kate CraceFlemington Rd/Sandford StNorthbourne Ave/Antill StMacarthur\NorthbourneCity Interchange
52wheelchair access....................5:39 AM5:47 AM5:55 AM6:01 AM6:04 AM....................
52wheelchair access....................6:18 AM6:26 AM6:34 AM6:40 AM6:43 AM....................
526:30 AM6:32 AM6:36 AM6:46 AM6:51 AM6:59 AM7:07 AM7:13 AM7:16 AM7:19 AM7:26 AM7:28 AM7:35 AM
52....................7:08 AM7:16 AM7:24 AM7:30 AM7:33 AM7:36 AM7:43 AM7:45 AM8:00 AM
52....................7:25 AM7:33 AM7:41 AM7:47 AM7:50 AM7:55 AM8:06 AM8:11 AM8:26 AM
527:23 AM7:25 AM7:29 AM7:39 AM7:44 AM7:52 AM8:00 AM8:07 AM8:10 AM8:15 AM8:26 AM8:31 AM8:46 AM
52....................8:06 AM8:14 AM8:22 AM8:29 AM8:32 AM8:37 AM8:48 AM8:53 AM9:05 AM
528:01 AM8:03 AM8:07 AM8:18 AM8:23 AM8:31 AM8:39 AM8:46 AM8:49 AM8:54 AM9:02 AM9:07 AM9:18 AM
52wheelchair access8:30 AM8:32 AM8:36 AM8:47 AM8:52 AM9:00 AM9:08 AM9:14 AM9:17 AM9:20 AM9:27 AM9:29 AM9:36 AM
529:16 AM9:18 AM9:22 AM9:32 AM9:37 AM9:45 AM9:53 AM9:59 AM10:02 AM....................
5210:16 AM10:18 AM10:22 AM10:32 AM10:37 AM10:45 AM10:53 AM10:59 AM11:02 AM....................
52wheelchair access11:16 AM11:18 AM11:22 AM11:32 AM11:37 AM11:45 AM11:53 AM11:59 AM12:02 PM....................
5212:16 PM12:18 PM12:22 PM12:32 PM12:37 PM12:45 PM12:53 PM12:59 PM1:02 PM....................
521:16 PM1:18 PM1:22 PM1:32 PM1:37 PM1:45 PM1:53 PM1:59 PM2:02 PM....................
52wheelchair access2:16 PM2:18 PM2:22 PM2:32 PM2:37 PM2:45 PM2:53 PM2:59 PM3:02 PM....................
522:33 PM2:35 PM2:39 PM2:49 PM2:54 PM3:02 PM3:10 PM3:16 PM3:19 PM....................
523:16 PM3:18 PM3:22 PM3:32 PM3:37 PM3:45 PM3:53 PM3:59 PM4:02 PM....................
523:56 PM3:58 PM4:02 PM4:13 PM4:18 PM4:27 PM4:35 PM4:42 PM4:45 PM....................
524:16 PM4:18 PM4:22 PM4:33 PM4:38 PM4:47 PM4:55 PM5:02 PM5:05 PM....................
524:36 PM4:38 PM4:42 PM4:53 PM4:58 PM5:07 PM5:15 PM5:22 PM5:25 PM....................
524:56 PM4:58 PM5:02 PM5:13 PM5:18 PM5:27 PM5:35 PM5:42 PM5:45 PM....................
525:16 PM5:18 PM5:22 PM5:33 PM5:38 PM5:47 PM5:55 PM6:02 PM6:05 PM....................
525:36 PM5:38 PM5:42 PM5:53 PM5:58 PM6:06 PM6:14 PM6:20 PM6:23 PM....................
526:16 PM6:18 PM6:21 PM6:31 PM6:36 PM6:44 PM6:52 PM6:58 PM7:01 PM....................
527:16 PM7:18 PM7:21 PM7:31 PM7:36 PM7:44 PM7:52 PM7:58 PM8:01 PM....................
528:16 PM8:18 PM8:21 PM8:31 PM8:36 PM8:44 PM8:52 PM8:58 PM9:01 PM....................
52wheelchair access9:16 PM9:18 PM9:21 PM9:31 PM9:36 PM9:44 PM9:52 PM9:58 PM10:01 PM....................
5210:16 PM10:18 PM10:21 PM10:31 PM10:36 PM10:44 PM10:52 PM10:58 PM11:01 PM....................
+

 

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
 City Interchange
+ Platform 8
Macarthur\NorthbourneNorthbourne Ave/Antill StFlemington Rd/Sandford StHibberson/Kate CraceGungahlin Market PlaceNgunnawal PrimaryNicholls PrimaryFederation SquareChuculba/William SlimCameron Ave StationLathlain St StationCohen St Station
52....................7:15 AM7:18 AM7:24 AM7:32 AM7:40 AM7:45 AM7:56 AM7:58 AM8:02 AM
52....................7:35 AM7:38 AM7:44 AM7:53 AM8:01 AM8:06 AM8:17 AM8:19 AM8:23 AM
52....................7:55 AM7:58 AM8:04 AM8:13 AM8:21 AM8:26 AM8:37 AM8:39 AM8:43 AM
52wheelchair access....................8:15 AM8:18 AM8:24 AM8:33 AM8:41 AM8:46 AM8:57 AM8:59 AM9:03 AM
52....................8:35 AM8:38 AM8:44 AM8:53 AM9:01 AM9:06 AM9:16 AM9:18 AM9:22 AM
52....................8:55 AM8:58 AM9:04 AM9:12 AM9:20 AM9:25 AM9:35 AM9:37 AM9:41 AM
52wheelchair access....................9:15 AM9:18 AM9:24 AM9:32 AM9:40 AM9:45 AM9:55 AM9:57 AM10:01 AM
52wheelchair access....................9:42 AM9:45 AM9:51 AM9:59 AM10:07 AM10:12 AM10:22 AM10:24 AM10:28 AM
52....................10:05 AM10:08 AM10:14 AM10:22 AM10:30 AM10:35 AM10:45 AM10:47 AM10:51 AM
52....................11:05 AM11:08 AM11:14 AM11:22 AM11:30 AM11:35 AM11:45 AM11:47 AM11:51 AM
52wheelchair access....................12:05 PM12:08 PM12:14 PM12:22 PM12:30 PM12:35 PM12:45 PM12:47 PM12:51 PM
52wheelchair access....................1:05 PM1:08 PM1:14 PM1:22 PM1:30 PM1:35 PM1:45 PM1:47 PM1:51 PM
52....................2:05 PM2:08 PM2:14 PM2:22 PM2:30 PM2:35 PM2:45 PM2:47 PM2:51 PM
52wheelchair access....................3:01 PM3:04 PM3:10 PM3:18 PM3:26 PM3:31 PM3:41 PM3:43 PM3:47 PM
52....................3:40 PM3:43 PM3:49 PM3:57 PM4:05 PM4:10 PM4:21 PM4:23 PM4:27 PM
523:41 PM3:47 PM3:49 PM3:57 PM4:00 PM4:03 PM4:09 PM4:18 PM4:26 PM4:31 PM4:42 PM4:44 PM4:48 PM
523:57 PM4:04 PM4:06 PM4:15 PM4:20 PM4:23 PM4:29 PM4:38 PM4:46 PM4:51 PM5:02 PM5:04 PM5:08 PM
524:17 PM4:24 PM4:26 PM4:35 PM4:40 PM4:43 PM4:49 PM4:58 PM5:06 PM5:11 PM5:22 PM5:24 PM5:28 PM
524:37 PM4:44 PM4:46 PM4:55 PM5:00 PM5:03 PM5:09 PM5:18 PM5:26 PM5:31 PM5:42 PM5:44 PM5:48 PM
524:57 PM5:04 PM5:06 PM5:15 PM5:20 PM5:23 PM5:29 PM5:38 PM5:46 PM5:51 PM6:02 PM6:04 PM6:07 PM
525:17 PM5:24 PM5:26 PM5:35 PM5:40 PM5:43 PM5:49 PM5:58 PM6:06 PM6:11 PM6:21 PM6:23 PM6:26 PM
525:34 PM5:41 PM5:43 PM5:52 PM5:57 PM6:00 PM6:06 PM6:14 PM6:22 PM6:27 PM6:37 PM6:39 PM6:42 PM
525:57 PM6:03 PM6:05 PM6:12 PM6:15 PM6:18 PM6:24 PM6:32 PM6:40 PM6:45 PM6:55 PM6:57 PM7:00 PM
52....................7:05 PM7:08 PM7:14 PM7:22 PM7:30 PM7:35 PM7:45 PM7:47 PM7:50 PM
52....................8:05 PM8:08 PM8:14 PM8:22 PM8:30 PM8:35 PM8:45 PM8:47 PM8:50 PM
52wheelchair access....................9:05 PM9:08 PM9:14 PM9:22 PM9:30 PM9:35 PM9:45 PM9:47 PM9:50 PM
52....................10:05 PM10:08 PM10:14 PM10:22 PM10:30 PM10:35 PM10:45 PM10:47 PM10:50 PM
52....................11:05 PM11:08 PM11:14 PM11:22 PM11:30 PM....................
+

wheelchair_icon- Wheelchair Accessible Bus

+
+ + + + + --- /dev/null +++ b/maxious-canberra-transit-feed/source-html/Route_56.html @@ -1,1 +1,823 @@ + + + + + + + + +Route 56 + + + + + + + +
+

Chosen services: 56

+ +

View timetable and map

+ This timetable is effective from Monday 25 May 2009. +

Weekdays

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
 Cohen Str Station
+ Platform 1
Lathlain St Station
+ Platform 2
Cameron Ave Station
+ Platform 2
Chuculba/William SlimGungahlin Market PlaceKosciuszko/EverardFlemington Rd/Sandford StNorthbourne Ave/Antill StMacarthur\NorthbourneCity Interchange
565:37 AM5:39 AM5:43 AM5:57 AM6:08 AM6:18 AM6:25 AM6:31 AM6:33 AM6:39 AM
566:17 AM6:19 AM6:23 AM6:37 AM6:48 AM6:58 AM7:05 AM7:11 AM7:13 AM7:19 AM
566:37 AM6:39 AM6:43 AM6:57 AM7:08 AM7:18 AM7:25 AM7:31 AM7:33 AM7:39 AM
56....................7:22 AM7:31 AM7:38 AM7:44 AM7:49 AM8:04 AM
567:01 AM7:03 AM7:07 AM7:21 AM7:32 AM7:41 AM7:49 AM8:00 AM8:05 AM8:20 AM
567:20 AM7:22 AM7:26 AM7:40 AM7:51 AM8:05 AM8:13 AM8:24 AM8:29 AM8:44 AM
567:41 AM7:43 AM7:47 AM8:02 AM8:13 AM8:27 AM8:35 AM8:46 AM8:51 AM9:02 AM
568:05 AM8:07 AM8:11 AM8:26 AM8:37 AM8:51 AM8:58 AM9:04 AM9:09 AM9:19 AM
56wheelchair access8:50 AM8:52 AM8:56 AM9:10 AM9:20 AM9:30 AM9:37 AM9:43 AM9:45 AM9:51 AM
569:33 AM9:35 AM9:39 AM9:53 AM10:03 AM10:13 AM10:20 AM10:26 AM10:28 AM10:34 AM
5610:33 AM10:35 AM10:39 AM10:53 AM11:03 AM11:13 AM11:20 AM11:26 AM11:28 AM11:34 AM
5611:33 AM11:35 AM11:39 AM11:53 AM12:03 PM12:13 PM12:20 PM12:26 PM12:28 PM12:34 PM
5612:33 PM12:35 PM12:39 PM12:53 PM1:03 PM1:13 PM1:20 PM1:26 PM1:28 PM1:34 PM
561:33 PM1:35 PM1:39 PM1:53 PM2:03 PM2:13 PM2:20 PM2:26 PM2:28 PM2:34 PM
562:38 PM2:40 PM2:44 PM2:58 PM3:08 PM3:18 PM3:25 PM3:31 PM3:33 PM3:39 PM
563:15 PM3:17 PM3:21 PM3:35 PM3:45 PM3:55 PM4:02 PM4:09 PM4:11 PM4:18 PM
563:43 PM3:45 PM3:49 PM4:03 PM4:14 PM4:26 PM4:34 PM4:41 PM4:43 PM4:50 PM
564:23 PM4:25 PM4:29 PM4:44 PM4:55 PM5:07 PM5:15 PM5:22 PM5:24 PM5:31 PM
564:43 PM4:45 PM4:49 PM5:04 PM5:15 PM5:25 PM5:33 PM5:40 PM5:42 PM5:49 PM
564:59 PM5:01 PM5:05 PM5:20 PM5:31 PM5:43 PM5:51 PM5:58 PM6:00 PM6:05 PM
565:19 PM5:21 PM5:25 PM5:40 PM5:51 PM6:03 PM6:10 PM6:16 PM6:18 PM6:23 PM
565:39 PM5:41 PM5:45 PM6:00 PM6:10 PM6:20 PM6:27 PM6:33 PM6:35 PM6:40 PM
56wheelchair access6:00 PM6:02 PM6:05 PM6:19 PM6:29 PM6:39 PM6:46 PM6:52 PM6:54 PM6:59 PM
566:33 PM6:35 PM6:38 PM6:52 PM7:02 PM7:12 PM7:19 PM7:25 PM7:27 PM7:32 PM
567:33 PM7:35 PM7:38 PM7:52 PM8:02 PM8:12 PM8:19 PM8:25 PM8:27 PM8:32 PM
568:33 PM8:35 PM8:38 PM8:52 PM9:02 PM9:12 PM9:19 PM9:25 PM9:27 PM9:32 PM
569:33 PM9:35 PM9:38 PM9:52 PM10:02 PM10:12 PM10:19 PM10:25 PM10:27 PM10:32 PM
56wheelchair access10:33 PM10:35 PM10:38 PM10:52 PM11:02 PM11:12 PM11:19 PM11:25 PM11:27 PM11:32 PM
+

 

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
 City Interchange
+ Platform 8
Macarthur\NorthbourneNorthbourne Ave/Antill StFlemington Rd/Sandford StKosciuszko/EverardGungahlin Market PlaceChuculba/William SlimCameron Ave StationLathlain St StationCohen St Station
56c....................6:02 AM6:12 AM6:23 AM6:37 AM6:39 AM6:43 AM
56cwheelchair access....................6:36 AM6:46 AM6:57 AM7:11 AM7:13 AM7:17 AM
56c....................7:06 AM7:16 AM7:27 AM7:41 AM7:43 AM7:47 AM
566:51 AM6:57 AM6:59 AM7:05 AM7:12 AM7:22 AM7:33 AM7:47 AM7:49 AM7:53 AM
56c....................7:26 AM7:36 AM7:47 AM8:02 AM8:04 AM8:08 AM
56c....................7:44 AM7:56 AM8:07 AM8:22 AM8:24 AM8:28 AM
56wheelchair access7:41 AM7:47 AM7:49 AM7:55 AM8:03 AM8:15 AM8:26 AM8:41 AM8:43 AM8:47 AM
568:01 AM8:08 AM8:10 AM8:16 AM8:24 AM8:36 AM8:47 AM9:02 AM9:04 AM9:08 AM
568:21 AM8:28 AM8:30 AM8:36 AM8:44 AM8:56 AM9:06 AM9:20 AM9:22 AM9:26 AM
568:51 AM8:58 AM9:00 AM9:06 AM9:13 AM9:25 AM9:35 AM9:49 AM9:51 AM9:55 AM
5610:04 AM10:10 AM10:12 AM10:18 AM10:25 AM10:37 AM10:47 AM11:01 AM11:03 AM11:07 AM
5611:04 AM11:10 AM11:12 AM11:18 AM11:25 AM11:37 AM11:47 AM12:01 PM12:03 PM12:07 PM
56wheelchair access12:04 PM12:10 PM12:12 PM12:18 PM12:25 PM12:37 PM12:47 PM1:01 PM1:03 PM1:07 PM
561:04 PM1:10 PM1:12 PM1:18 PM1:25 PM1:37 PM1:47 PM2:01 PM2:03 PM2:07 PM
562:04 PM2:10 PM2:12 PM2:18 PM2:25 PM2:37 PM2:47 PM3:01 PM3:03 PM3:07 PM
563:04 PM3:11 PM3:13 PM3:20 PM3:28 PM3:40 PM3:51 PM4:06 PM4:08 PM4:12 PM
563:58 PM4:05 PM4:07 PM4:14 PM4:22 PM4:34 PM4:45 PM5:01 PM5:02 PM5:09 PM
564:09 PM4:16 PM4:18 PM4:25 PM4:33 PM4:45 PM4:56 PM5:11 PM5:13 PM5:17 PM
564:29 PM4:36 PM4:38 PM4:45 PM4:53 PM5:05 PM5:16 PM5:31 PM5:33 PM5:37 PM
56wheelchair access4:49 PM4:56 PM4:58 PM5:05 PM5:13 PM5:25 PM5:36 PM5:51 PM5:53 PM5:57 PM
56wheelchair access5:10 PM5:17 PM5:19 PM5:26 PM5:34 PM5:46 PM5:57 PM6:11 PM6:13 PM6:16 PM
565:30 PM5:37 PM5:39 PM5:46 PM5:54 PM6:05 PM6:15 PM6:29 PM6:31 PM6:34 PM
565:50 PM5:57 PM5:59 PM6:04 PM6:11 PM6:21 PM6:31 PM6:45 PM6:47 PM6:50 PM
566:10 PM6:16 PM6:18 PM6:23 PM6:30 PM6:40 PM6:50 PM7:04 PM7:06 PM7:09 PM
56wheelchair access7:04 PM7:10 PM7:12 PM7:17 PM7:24 PM7:34 PM7:44 PM7:58 PM8:00 PM8:03 PM
568:04 PM8:10 PM8:12 PM8:17 PM8:24 PM8:34 PM8:44 PM8:58 PM9:00 PM9:03 PM
569:04 PM9:10 PM9:12 PM9:17 PM9:24 PM9:34 PM9:44 PM9:58 PM10:00 PM10:03 PM
5610:04 PM10:10 PM10:12 PM10:17 PM10:24 PM10:34 PM10:44 PM10:58 PM11:00 PM11:03 PM
5611:04 PM11:10 PM11:12 PM11:17 PM11:24 PM11:34 PM11:44 PM11:58 PM12:00 AM12:03 AM
+

c - commences at the first stop near the dam wall on Mirrabei Drive 4 minutes earlier

+

wheelchair_icon- Wheelchair Accessible Bus

+
+ + + + + --- /dev/null +++ b/maxious-canberra-transit-feed/source-html/Route_58.html @@ -1,1 +1,825 @@ + + + + + + + + +Route 58 + + + + + + + +
+

Chosen services: 58

+ +

View timetable and map

+ This timetable is effective from Monday 25 May 2009. +

Weekdays

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
 Cohen St Station
+ Platform 1
Lathlain St Station
+ Platform 2
Cameron Ave Station
+ Platform 2
Chuculba/William SlimGungahlin Market PlaceAnthony Rolfe/MoonlightFlemington/NullaborFlemington Rd/Sandford StNorthbourne Ave/Antill StMacarthur\NorthbourneCity Interchange
58...............5:43 AM5:54 AM6:02 AM6:09 AM6:15 AM6:21 AM6:23 AM6:29 AM
58...............6:23 AM6:34 AM6:42 AM6:49 AM6:55 AM7:01 AM7:03 AM7:09 AM
58wheelchair access....................6:54 AM7:02 AM7:09 AM7:15 AM7:21 AM7:23 AM7:29 AM
58....................7:13 AM7:21 AM7:28 AM7:34 AM7:40 AM7:42 AM7:52 AM
58....................7:23 AM7:31 AM7:38 AM7:44 AM7:54 AM7:59 AM8:14 AM
58....................7:40 AM7:48 AM7:55 AM8:03 AM8:14 AM8:19 AM8:34 AM
587:26 AM7:28 AM7:32 AM7:46 AM7:57 AM8:06 AM8:13 AM8:21 AM8:33 AM8:39 AM8:54 AM
587:47 AM7:49 AM7:53 AM8:08 AM8:19 AM8:28 AM8:35 AM8:43 AM8:54 AM8:59 AM9:09 AM
588:29 AM8:31 AM8:35 AM8:50 AM9:01 AM9:09 AM9:16 AM9:23 AM9:29 AM9:31 AM9:37 AM
589:09 AM9:11 AM9:15 AM9:29 AM9:39 AM9:47 AM9:54 AM10:01 AM10:07 AM10:09 AM10:15 AM
5810:09 AM10:11 AM10:15 AM10:29 AM10:39 AM10:47 AM10:54 AM11:01 AM11:07 AM11:09 AM11:15 AM
5811:09 AM11:11 AM11:15 AM11:29 AM11:39 AM11:47 AM11:54 AM12:01 PM12:07 PM12:09 PM12:15 PM
58wheelchair access12:09 PM12:11 PM12:15 PM12:29 PM12:39 PM12:47 PM12:54 PM1:01 PM1:07 PM1:09 PM1:15 PM
581:09 PM1:11 PM1:15 PM1:29 PM1:39 PM1:47 PM1:54 PM2:01 PM2:07 PM2:09 PM2:15 PM
582:09 PM2:11 PM2:15 PM2:29 PM2:39 PM2:47 PM2:54 PM3:01 PM3:07 PM3:09 PM3:15 PM
583:09 PM3:11 PM3:15 PM3:29 PM3:39 PM3:47 PM3:54 PM4:01 PM4:08 PM4:10 PM4:17 PM
584:09 PM4:11 PM4:15 PM4:30 PM4:41 PM4:50 PM4:57 PM5:05 PM5:12 PM5:14 PM5:21 PM
584:29 PM4:31 PM4:35 PM4:50 PM5:01 PM5:10 PM5:17 PM5:25 PM5:32 PM5:34 PM5:41 PM
584:49 PM4:51 PM4:55 PM5:10 PM5:21 PM5:30 PM5:37 PM5:45 PM5:52 PM5:54 PM6:01 PM
585:09 PM5:11 PM5:15 PM5:30 PM5:41 PM5:50 PM5:57 PM6:04 PM6:10 PM6:12 PM6:17 PM
585:29 PM5:31 PM5:35 PM5:50 PM6:01 PM6:09 PM6:16 PM6:22 PM6:28 PM6:30 PM6:35 PM
585:49 PM5:51 PM5:55 PM6:09 PM6:19 PM6:27 PM6:33 PM6:39 PM6:45 PM6:47 PM6:52 PM
586:09 PM6:11 PM6:14 PM6:28 PM6:38 PM6:46 PM6:53 PM6:59 PM7:05 PM7:07 PM7:12 PM
587:09 PM7:11 PM7:14 PM7:28 PM7:38 PM7:46 PM7:53 PM7:59 PM8:05 PM8:07 PM8:12 PM
58wheelchair access8:09 PM8:11 PM8:14 PM8:28 PM8:38 PM8:46 PM8:53 PM8:59 PM9:05 PM9:07 PM9:12 PM
589:09 PM9:11 PM9:14 PM9:28 PM9:38 PM9:46 PM9:53 PM9:59 PM10:05 PM10:07 PM10:12 PM
5810:09 PM10:11 PM10:14 PM10:28 PM10:38 PM10:46 PM10:53 PM10:59 PM11:05 PM11:07 PM11:12 PM
+

 

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
 City Interchange
+ Platform 8
Macarthur\NorthbourneNorthbourne Ave/Antill StFlemington Rd/Sandford StFlemington/NullaborAnthony Rolfe/MoonlightGungahlin Market PlaceChuculba/William SlimCameron Ave StationLathlain St StationCohen St Station
58....................5:51 AM5:58 AM6:06 AM....................
58wheelchair access....................6:24 AM6:31 AM6:39 AM....................
586:31 AM6:37 AM6:39 AM6:45 AM6:51 AM6:58 AM7:06 AM7:17 AM7:31 AM7:33 AM7:37 AM
587:11 AM7:17 AM7:19 AM7:25 AM7:31 AM7:38 AM7:46 AM7:57 AM8:12 AM8:14 AM8:18 AM
587:27 AM7:33 AM7:35 AM7:41 AM7:48 AM7:57 AM8:06 AM8:17 AM8:32 AM8:34 AM8:38 AM
587:45 AM7:52 AM7:54 AM8:00 AM8:08 AM8:17 AM8:26 AM8:37 AM8:52 AM8:54 AM8:58 AM
588:05 AM8:12 AM8:14 AM8:20 AM8:28 AM8:37 AM8:46 AM8:57 AM9:11 AM9:13 AM9:17 AM
589:17 AM9:23 AM9:25 AM9:31 AM9:38 AM9:45 AM9:53 AM10:03 AM10:17 AM10:19 AM10:23 AM
5810:17 AM10:23 AM10:25 AM10:31 AM10:38 AM10:45 AM10:53 AM11:03 AM11:17 AM11:19 AM11:23 AM
5811:17 AM11:23 AM11:25 AM11:31 AM11:38 AM11:45 AM11:53 AM12:03 PM12:17 PM12:19 PM12:23 PM
5812:17 PM12:23 PM12:25 PM12:31 PM12:38 PM12:45 PM12:53 PM1:03 PM1:17 PM1:19 PM1:23 PM
581:17 PM1:23 PM1:25 PM1:31 PM1:38 PM1:45 PM1:53 PM2:03 PM2:17 PM2:19 PM2:23 PM
58wheelchair access2:17 PM2:23 PM2:25 PM2:31 PM2:38 PM2:45 PM2:53 PM3:03 PM3:18 PM3:20 PM3:24 PM
58wheelchair access3:28 PM3:35 PM3:37 PM3:44 PM3:52 PM4:01 PM4:10 PM4:21 PM4:36 PM4:38 PM4:42 PM
584:19 PM4:26 PM4:28 PM4:35 PM4:43 PM4:52 PM5:01 PM5:12 PM5:27 PM5:29 PM5:33 PM
584:39 PM4:46 PM4:48 PM4:55 PM5:03 PM5:12 PM5:21 PM5:32 PM5:47 PM5:49 PM5:53 PM
58wheelchair access5:00 PM5:07 PM5:09 PM5:16 PM5:24 PM5:33 PM5:42 PM5:53 PM6:07 PM6:09 PM6:12 PM
585:20 PM5:27 PM5:29 PM5:36 PM5:44 PM5:53 PM6:02 PM6:12 PM6:26 PM6:28 PM6:31 PM
585:40 PM5:47 PM5:49 PM5:56 PM6:03 PM6:10 PM6:18 PM6:28 PM6:42 PM6:44 PM6:47 PM
586:00 PM6:06 PM6:08 PM6:13 PM6:19 PM6:26 PM6:34 PM6:44 PM6:58 PM7:00 PM7:03 PM
586:31 PM6:37 PM6:39 PM6:44 PM6:50 PM6:57 PM7:05 PM7:15 PM7:29 PM7:31 PM7:34 PM
587:17 PM7:23 PM7:25 PM7:30 PM7:36 PM7:43 PM7:51 PM8:01 PM8:15 PM8:17 PM8:20 PM
588:17 PM8:23 PM8:25 PM8:30 PM8:36 PM8:43 PM8:51 PM9:01 PM9:15 PM9:17 PM9:20 PM
58wheelchair access9:17 PM9:23 PM9:25 PM9:30 PM9:36 PM9:43 PM9:51 PM10:01 PM10:15 PM10:17 PM10:20 PM
5810:17 PM10:23 PM10:25 PM10:30 PM10:36 PM10:43 PM10:51 PM11:01 PM11:15 PM11:17 PM11:20 PM
5811:17 PM11:23 PM11:25 PM11:30 PM11:36 PM11:43 PM11:51 PM12:01 AM12:15 AM12:17 AM12:20 AM
+

wheelchair_icon- Wheelchair Accessible Bus

+
+ + + + + --- /dev/null +++ b/maxious-canberra-transit-feed/source-html/Route_59.html @@ -1,1 +1,1000 @@ + + + + + + + + +Route 59 + + + + + + + +
+

Chosen services: 59

+ +

View timetable and map

+ This timetable is effective from Monday 25 May 2009. +

Weekdays

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
 Cohen St Station
+ Platform 1
Lathlain St Station
+ Platform 2
Cameron Avenue Station
+ Platform 2
Chuculba/William SlimPaul-Co / MirrabeiKatherine / Horse ParkGungahlin Market PlaceHibberson/Kate CraceFlemington Rd/Sandford StNorthbourne Ave/Antill StMacarthur\NorthbourneCity Interchange
59cwheelchair access....................5:42 AM5:48 AM6:03 AM6:06 AM....................
59c....................6:17 AM6:23 AM6:38 AM6:41 AM....................
59cwheelchair access....................6:50 AM6:56 AM7:11 AM7:14 AM7:17 AM7:24 AM7:26 AM7:33 AM
59c....................7:06 AM7:12 AM7:27 AM7:30 AM7:33 AM7:40 AM7:42 AM7:54 AM
59c....................7:18 AM7:24 AM7:39 AM7:42 AM7:45 AM7:56 AM8:01 AM8:15 AM
59c....................7:41 AM7:47 AM8:02 AM8:05 AM8:10 AM8:21 AM8:26 AM8:41 AM
59cwheelchair access....................8:13 AM8:19 AM8:34 AM8:37 AM8:42 AM8:53 AM8:58 AM9:06 AM
59c....................8:24 AM8:30 AM8:45 AM8:48 AM8:53 AM9:04 AM9:09 AM9:20 AM
59c....................8:57 AM9:03 AM9:18 AM9:21 AM9:24 AM9:31 AM9:33 AM9:40 AM
599:07 AM9:09 AM9:13 AM9:27 AM9:37 AM9:43 AM9:58 AM10:01 AM....................
5910:07 AM10:09 AM10:13 AM10:27 AM10:37 AM10:43 AM10:58 AM11:01 AM....................
5911:07 AM11:09 AM11:13 AM11:27 AM11:37 AM11:43 AM11:58 AM12:01 PM....................
5912:07 PM12:09 PM12:13 PM12:27 PM12:37 PM12:43 PM12:58 PM1:01 PM....................
591:07 PM1:09 PM1:13 PM1:27 PM1:37 PM1:43 PM1:58 PM2:01 PM....................
592:07 PM2:09 PM2:13 PM2:27 PM2:37 PM2:43 PM2:58 PM3:01 PM....................
592:44 PM2:46 PM2:50 PM3:04 PM3:14 PM3:20 PM3:35 PM3:38 PM....................
593:22 PM3:24 PM3:28 PM3:43 PM3:53 PM3:59 PM4:14 PM4:17 PM....................
593:37 PM3:39 PM3:43 PM3:58 PM4:08 PM4:14 PM4:29 PM4:32 PM....................
593:52 PM3:54 PM3:58 PM4:13 PM4:23 PM4:29 PM4:44 PM4:47 PM....................
59 wheelchair access4:07 PM4:09 PM4:13 PM4:28 PM4:38 PM4:44 PM4:59 PM5:02 PM....................
594:22 PM4:24 PM4:28 PM4:43 PM4:53 PM4:59 PM5:14 PM5:17 PM....................
594:37 PM4:39 PM4:43 PM4:58 PM5:08 PM5:14 PM5:29 PM5:32 PM....................
594:52 PM4:54 PM4:58 PM5:13 PM5:23 PM5:29 PM5:44 PM5:47 PM....................
595:07 PM5:09 PM5:13 PM5:28 PM5:38 PM5:44 PM5:59 PM6:02 PM....................
595:22 PM5:24 PM5:28 PM5:43 PM5:53 PM5:59 PM6:14 PM6:17 PM....................
595:35 PM5:37 PM5:41 PM5:56 PM6:05 PM6:11 PM6:26 PM6:29 PM....................
595:52 PM5:54 PM5:58 PM6:12 PM6:21 PM6:27 PM6:42 PM6:45 PM....................
596:08 PM6:10 PM6:13 PM6:27 PM6:36 PM6:42 PM6:57 PM7:00 PM....................
597:08 PM7:10 PM7:13 PM7:27 PM7:36 PM7:42 PM7:57 PM8:00 PM....................
59wheelchair access8:08 PM8:10 PM8:13 PM8:27 PM8:36 PM8:42 PM8:57 PM9:00 PM....................
599:08 PM9:10 PM9:13 PM9:27 PM9:36 PM9:42 PM9:57 PM10:00 PM....................
5910:08 PM10:10 PM10:13 PM10:27 PM10:36 PM10:42 PM10:57 PM11:00 PM....................
5911:08 PM11:10 PM11:13 PM11:27 PM11:36 PM11:42 PM11:57 PM12:00 AM....................
+

c - commences at the first stop near the dam wall on Mirrabei Drive 4 minutes earlier

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
 City Interchange
+ Platform 8
Macarthur\NorthbourneNorthbourne Ave/Antill StFlemington Rd/Sandford StHibberson/Kate CraceGungahlin Market PlaceKatherine / Horse ParkPaul-Co / MirrabeiChuculba/William SlimCameron Avenue StationLathlain St StationCohen Street Station
59....................6:45 AM6:48 AM7:03 AM7:09 AM7:18 AM7:32 AM7:34 AM7:38 AM
59....................7:10 AM7:13 AM7:28 AM7:34 AM7:43 AM7:58 AM8:00 AM8:04 AM
59wheelchair access....................7:30 AM7:33 AM7:48 AM7:54 AM8:03 AM8:18 AM8:20 AM8:24 AM
59....................7:55 AM7:58 AM8:13 AM8:19 AM8:28 AM8:43 AM8:45 AM8:49 AM
59....................8:15 AM8:18 AM8:33 AM8:39 AM8:48 AM9:03 AM9:05 AM9:09 AM
59....................9:07 AM9:10 AM9:25 AM9:31 AM9:40 AM9:54 AM9:56 AM10:00 AM
59wheelchair access....................10:07 AM10:10 AM10:25 AM10:31 AM10:40 AM10:54 AM10:56 AM11:00 AM
59....................11:07 AM11:10 AM11:25 AM11:31 AM11:40 AM11:54 AM11:56 AM12:00 PM
59....................12:07 PM12:10 PM12:25 PM12:31 PM12:40 PM12:54 PM12:56 PM1:00 PM
59....................1:07 PM1:10 PM1:25 PM1:31 PM1:40 PM1:54 PM1:56 PM2:00 PM
59....................2:07 PM2:10 PM2:25 PM2:31 PM2:40 PM2:54 PM2:56 PM3:00 PM
59wheelchair access....................3:07 PM3:10 PM3:25 PM3:31 PM3:40 PM3:54 PM3:56 PM4:00 PM
593:26 PM3:32 PM3:34 PM3:42 PM3:45 PM3:48 PM4:03 PM4:09 PM4:19 PM4:34 PM4:36 PM4:40 PM
593:41 PM3:47 PM3:49 PM3:57 PM4:00 PM4:03 PM4:18 PM4:24 PM4:34 PM4:49 PM4:51 PM4:55 PM
593:53 PM3:59 PM4:01 PM4:10 PM4:15 PM4:18 PM4:33 PM4:39 PM4:49 PM5:04 PM5:06 PM5:10 PM
594:07 PM4:14 PM4:16 PM4:25 PM4:30 PM4:33 PM4:48 PM4:54 PM5:04 PM5:19 PM5:21 PM5:25 PM
594:22 PM4:29 PM4:31 PM4:40 PM4:45 PM4:48 PM5:03 PM5:09 PM5:19 PM5:34 PM5:36 PM5:40 PM
594:37 PM4:44 PM4:46 PM4:55 PM5:00 PM5:03 PM5:18 PM5:24 PM5:34 PM5:49 PM5:51 PM5:55 PM
594:52 PM4:59 PM5:01 PM5:10 PM5:15 PM5:18 PM5:33 PM5:39 PM5:49 PM6:04 PM6:06 PM6:09 PM
595:07 PM5:14 PM5:16 PM5:25 PM5:30 PM5:33 PM5:48 PM5:54 PM6:04 PM6:18 PM6:20 PM6:23 PM
595:22 PM5:29 PM5:31 PM5:40 PM5:45 PM5:48 PM6:03 PM6:09 PM6:18 PM6:32 PM6:34 PM6:37 PM
595:36 PM5:43 PM5:45 PM5:54 PM5:59 PM6:02 PM6:17 PM6:23 PM6:32 PM6:46 PM6:48 PM6:51 PM
595:52 PM5:59 PM6:01 PM6:08 PM6:11 PM6:14 PM6:29 PM6:35 PM6:44 PM6:58 PM7:00 PM7:03 PM
59....................7:07 PM7:10 PM7:25 PM7:31 PM7:40 PM7:54 PM7:56 PM7:59 PM
59....................8:07 PM8:10 PM8:25 PM8:31 PM8:40 PM8:54 PM8:56 PM8:59 PM
59....................9:07 PM9:10 PM9:25 PM9:31 PM9:40 PM9:54 PM9:56 PM9:59 PM
59wheelchair access....................10:07 PM10:10 PM10:25 PM10:31 PM10:40 PM10:54 PM10:56 PM10:59 PM
59....................11:07 PM11:10 PM11:25 PM11:31 PM11:40 PM11:54 PM11:56 PM11:59 PM
+

wheelchair_icon- Wheelchair Accessible Bus

+
+ + + + + --- /dev/null +++ b/maxious-canberra-transit-feed/source-html/Route_6.html @@ -1,1 +1,652 @@ + + + + + + + + +Route 6 + + + + + + + +
+

Chosen services: 6

+ +

View timetable and map

+ +

Due to construction at The Canberra Hospital, The bus stops in Bateson Road will be out of service from Saturday 28 November until further notice. Passengers can still use the main stops along Hospital Road. There is no change to the direction of the route.

+

Weekdays

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
 DicksonNorth LynehamLynehamCity InterchangeKings Ave/National CrtManuka, Captain CookRed HillCanberra HospitalWoden Interchange
6...............6:50 AM6:58 AM7:03 AM7:10 AM7:20 AM7:28 AM
66:48 AM6:55 AM7:01 AM7:15 AM7:23 AM7:28 AM7:36 AM7:50 AM7:58 AM
67:18 AM7:25 AM7:31 AM7:47 AM7:59 AM8:04 AM8:12 AM8:26 AM8:34 AM
67:48 AM7:56 AM8:04 AM8:20 AM8:28 AM8:38 AM8:46 AM9:03 AM9:11 AM
68:18 AM8:26 AM8:33 AM8:49 AM9:01 AM9:06 AM9:14 AM9:28 AM9:36 AM
68:48 AM8:56 AM9:03 AM9:19 AM9:31 AM9:36 AM9:43 AM9:55 AM10:03 AM
69:18 AM9:26 AM9:33 AM9:47 AM9:57 AM10:02 AM10:09 AM10:21 AM10:29 AM
69:48 AM9:56 AM10:03 AM10:17 AM10:27 AM10:32 AM10:39 AM10:51 AM10:59 AM
6wheelchair access10:48 AM10:56 AM11:03 AM11:17 AM11:27 AM11:32 AM11:39 AM11:51 AM11:59 AM
611:48 AM11:56 AM12:03 PM12:17 PM12:27 PM12:32 PM12:39 PM12:51 PM12:59 PM
6wheelchair access12:48 PM12:56 PM1:03 PM1:17 PM1:27 PM1:32 PM1:39 PM1:51 PM1:59 PM
6wheelchair access1:48 PM1:56 PM2:03 PM2:17 PM2:27 PM2:32 PM2:39 PM2:51 PM2:59 PM
62:48 PM2:56 PM3:03 PM3:19 PM3:31 PM3:36 PM3:44 PM3:58 PM4:06 PM
63:18 PM3:26 PM3:33 PM3:49 PM4:01 PM4:06 PM4:14 PM4:28 PM4:36 PM
63:48 PM3:56 PM4:03 PM4:19 PM4:31 PM4:36 PM4:44 PM4:58 PM5:06 PM
64:18 PM4:26 PM4:33 PM4:49 PM5:01 PM5:06 PM5:14 PM5:28 PM5:36 PM
64:48 PM4:56 PM5:03 PM5:19 PM5:31 PM5:36 PM5:44 PM5:58 PM6:06 PM
65:18 PM5:26 PM5:33 PM5:49 PM6:01 PM6:06 PM6:14 PM6:28 PM6:36 PM
65:48 PM5:56 PM6:03 PM6:19 PM6:31 PM6:36 PM6:43 PM6:53 PM7:01 PM
66:40 PM6:47 PM6:53 PM7:05 PM7:13 PM7:18 PM7:25 PM7:35 PM7:43 PM
67:40 PM7:47 PM7:53 PM8:05 PM8:13 PM8:18 PM8:25 PM8:35 PM8:43 PM
6wheelchair access8:40 PM8:47 PM8:53 PM9:05 PM9:13 PM9:18 PM9:25 PM9:35 PM9:43 PM
69:40 PM9:47 PM9:53 PM10:05 PM10:13 PM10:18 PM10:25 PM10:35 PM10:43 PM
610:40 PM10:47 PM10:53 PM11:05 PM11:13 PM11:18 PM11:25 PM11:35 PM11:43 PM
+

 

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
 Woden InterchangeCanberra HospitalRed HillManuka, Captain CookKings Ave/National CrtCity InterchangeLynehamNorth LynehamDickson
66:18 AM6:26 AM6:38 AM6:45 AM6:50 AM7:01 AM7:13 AM7:19 AM7:25 AM
66:53 AM7:01 AM7:13 AM7:20 AM7:25 AM7:37 AM7:51 AM7:59 AM8:06 AM
67:23 AM7:31 AM7:45 AM7:53 AM7:58 AM8:12 AM8:26 AM8:34 AM8:41 AM
67:53 AM8:03 AM8:17 AM8:25 AM8:30 AM8:44 AM8:58 AM9:06 AM9:13 AM
68:23 AM8:33 AM8:47 AM8:55 AM9:00 AM9:14 AM9:28 AM9:36 AM9:43 AM
68:53 AM9:03 AM9:17 AM9:25 AM9:30 AM9:44 AM9:56 AM10:04 AM10:11 AM
69:23 AM9:33 AM9:45 AM9:52 AM9:57 AM10:11 AM10:23 AM10:31 AM10:38 AM
610:23 AM10:33 AM10:45 AM10:52 AM10:57 AM11:11 AM11:23 AM11:31 AM11:38 AM
6wheelchair access11:23 AM11:33 AM11:45 AM11:52 AM11:57 AM12:11 PM12:23 PM12:31 PM12:38 PM
612:23 PM12:33 PM12:45 PM12:52 PM12:57 PM1:11 PM1:23 PM1:31 PM1:38 PM
61:23 PM1:33 PM1:45 PM1:52 PM1:57 PM2:11 PM2:23 PM2:31 PM2:38 PM
6wheelchair access2:23 PM2:33 PM2:45 PM2:52 PM2:57 PM3:11 PM3:25 PM3:33 PM3:40 PM
6.........................3:44 PM3:58 PM4:06 PM4:13 PM
63:23 PM3:33 PM3:47 PM3:55 PM4:00 PM4:14 PM4:28 PM4:36 PM4:43 PM
63:53 PM4:03 PM4:17 PM4:25 PM4:30 PM4:44 PM4:58 PM5:06 PM5:13 PM
64:23 PM4:33 PM4:47 PM4:55 PM5:00 PM5:14 PM5:28 PM5:36 PM5:43 PM
64:53 PM5:03 PM5:17 PM5:25 PM5:30 PM5:44 PM5:58 PM6:06 PM6:13 PM
65:16 PM5:26 PM5:40 PM5:48 PM5:53 PM6:07 PM6:21 PM6:29 PM6:35 PM
65:53 PM6:03 PM6:17 PM6:25 PM6:30 PM6:40 PM6:50 PM6:56 PM7:02 PM
66:30 PM6:38 PM6:48 PM6:55 PM7:00 PM7:10 PM7:20 PM7:26 PM7:32 PM
6wheelchair access7:30 PM7:38 PM7:48 PM7:55 PM8:00 PM8:10 PM8:20 PM8:26 PM8:32 PM
68:30 PM8:38 PM8:48 PM8:55 PM9:00 PM9:10 PM9:20 PM9:26 PM9:32 PM
69:30 PM9:38 PM9:48 PM9:55 PM10:00 PM10:10 PM10:20 PM10:26 PM10:32 PM
610:30 PM10:38 PM10:48 PM10:55 PM11:00 PM11:10 PM11:20 PM11:26 PM11:32 PM
+

wheelchair_icon- Wheelchair Accessible Bus

+
+ + + + + --- /dev/null +++ b/maxious-canberra-transit-feed/source-html/Route_60_160.html @@ -1,1 +1,516 @@ + + + + + + + + +Route 60, 160 + + + + + + + +
+

Chosen services: 60, 160

+ +

View timetable and map

+ This timetable is effective from Monday 25 May 2009. +

Weekdays

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
 Tuggeranong Interchange
+ Platform 3
Kambah HighMount Neighbour SchoolWoden Interchange
+ Platform 9
City InterchangeCitywest
60wheelchair access6:06 AM6:15 AM6:21 AM6:32 AM..........
160wheelchair access7:06 AM7:15 AM7:21 AM7:34 AM7:49 AM7:52 AM
60wheelchair access7:30 AM7:40 AM7:48 AM8:02 AM..........
1607:38 AM7:48 AM7:56 AM8:11 AM8:26 AM8:29 AM
607:52 AM8:02 AM8:10 AM8:24 AM..........
1608:08 AM8:18 AM8:26 AM8:41 AM8:56 AM8:59 AM
60wheelchair access8:36 AM8:46 AM8:54 AM9:08 AM..........
609:06 AM9:16 AM9:24 AM9:37 AM..........
6010:06 AM10:16 AM10:24 AM10:36 AM..........
6011:06 AM11:16 AM11:24 AM11:36 AM..........
6012:06 PM12:16 PM12:24 PM12:36 PM..........
601:06 PM1:16 PM1:24 PM1:36 PM..........
602:06 PM2:16 PM2:24 PM2:36 PM..........
602:36 PM2:46 PM2:54 PM3:07 PM..........
60wheelchair access3:06 PM3:16 PM3:24 PM3:38 PM..........
603:36 PM3:46 PM3:54 PM4:08 PM..........
604:06 PM4:16 PM4:24 PM4:38 PM..........
604:36 PM4:46 PM4:54 PM5:08 PM..........
605:06 PM5:16 PM5:24 PM5:38 PM..........
605:36 PM5:46 PM5:54 PM6:08 PM..........
606:06 PM6:16 PM6:24 PM6:37 PM..........
607:06 PM7:16 PM7:22 PM7:34 PM..........
608:06 PM8:16 PM8:22 PM8:34 PM..........
609:06 PM9:16 PM9:22 PM9:34 PM..........
6010:06 PM10:16 PM10:22 PM10:34 PM..........
60wheelchair access11:06 PM11:16 PM11:22 PM11:34 PM..........
+

 

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
 CitywestCity Interchange
+ Platform 1
Woden Interchange
+ Platform 5
Mount Neighbour SchoolKambah HighTuggeranong Interchange
+ Platform 3
60..........6:47 AM7:01 AM7:08 AM7:18 AM
60..........7:17 AM7:31 AM7:39 AM7:50 AM
60wheelchair access..........7:47 AM8:01 AM8:09 AM8:20 AM
60C..........8:17 AM8:31 AM8:39 AM8:50 AM
60wheelchair access..........8:47 AM9:01 AM9:09 AM9:20 AM
60..........9:47 AM10:01 AM10:09 AM10:19 AM
60wheelchair access..........10:47 AM11:01 AM11:09 AM11:19 AM
60..........11:47 AM12:01 PM12:09 PM12:19 PM
60wheelchair access..........12:47 PM1:01 PM1:09 PM1:19 PM
60wheelchair access..........1:47 PM2:01 PM2:09 PM2:19 PM
60wheelchair access..........2:47 PM3:01 PM3:09 PM3:20 PM
60wheelchair access..........3:17 PM3:31 PM3:39 PM3:50 PM
60..........3:47 PM4:01 PM4:09 PM4:20 PM
60C..........4:17 PM4:31 PM4:39 PM4:50 PM
60..........4:47 PM5:01 PM5:09 PM5:20 PM
1604:55 PM5:01 PM5:17 PM5:31 PM5:39 PM5:50 PM
1605:31 PM5:37 PM5:53 PM6:07 PM6:15 PM6:26 PM
160wheelchair access5:55 PM6:01 PM6:17 PM6:31 PM6:38 PM6:47 PM
60wheelchair access..........6:47 PM7:01 PM7:08 PM7:17 PM
60..........7:47 PM8:01 PM8:08 PM8:17 PM
60wheelchair access..........8:47 PM9:01 PM9:08 PM9:17 PM
60..........9:47 PM10:01 PM10:08 PM10:17 PM
60wheelchair access..........10:47 PM11:01 PM11:08 PM11:17 PM
+

C - On School Days, this service diverts into Gleneagles. Travels to roundabout in Mount Vernon Drive and return.

+

wheelchair_icon- Wheelchair Accessible Bus

+
+ + + + + --- /dev/null +++ b/maxious-canberra-transit-feed/source-html/Route_61_161.html @@ -1,1 +1,555 @@ + + + + + + + + +Route 61, 161 + + + + + + + +
+

Chosen services: 61, 161

+ +

View timetable and map

+ This timetable is effective from Monday 20 July 2009. +

Weekdays

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
 Tuggeranong Interchange
+ Platform 3
Taverner/ErindaleLivingston Shops KambahAthllon/Sulwood KambahWoden Interchange
+ Platform 9
City InterchangeCitywest
61wheelchair access6:30 AM6:41 AM6:46 AM6:51 AM6:58 AM..........
617:00 AM7:12 AM7:17 AM7:22 AM7:33 AM..........
1617:26 AM7:39 AM7:46 AM7:51 AM8:05 AM8:19 AM8:22 AM
61wheelchair access7:40 AM7:54 AM7:59 AM8:04 AM8:13 AM..........
618:00 AM8:14 AM8:19 AM8:25 AM8:39 AM..........
618:37 AM8:51 AM8:56 AM9:01 AM9:10 AM..........
619:00 AM9:14 AM9:19 AM9:24 AM9:33 AM..........
619:30 AM9:43 AM9:48 AM9:53 AM10:01 AM..........
61wheelchair access10:30 AM10:43 AM10:48 AM10:53 AM11:01 AM..........
6111:30 AM11:43 AM11:48 AM11:53 AM12:01 PM..........
61wheelchair access12:30 PM12:43 PM12:48 PM12:53 PM1:01 PM..........
61wheelchair access1:30 PM1:43 PM1:48 PM1:53 PM2:01 PM..........
61wheelchair access2:30 PM2:43 PM2:48 PM2:53 PM3:01 PM..........
61wheelchair access3:30 PM3:44 PM3:49 PM3:54 PM4:03 PM..........
61wheelchair access4:00 PM4:14 PM4:19 PM4:24 PM4:33 PM..........
614:30 PM4:44 PM4:49 PM4:54 PM5:03 PM..........
615:00 PM5:14 PM5:19 PM5:24 PM5:33 PM..........
615:30 PM5:44 PM5:49 PM5:54 PM6:03 PM..........
61wheelchair access6:00 PM6:14 PM6:19 PM6:24 PM6:33 PM..........
616:30 PM6:43 PM6:48 PM6:53 PM7:01 PM..........
617:30 PM7:43 PM7:48 PM7:53 PM8:01 PM..........
61wheelchair access8:30 PM8:43 PM8:48 PM8:53 PM9:01 PM..........
61wheelchair access9:30 PM9:43 PM9:48 PM9:53 PM10:01 PM..........
6110:30 PM10:43 PM10:48 PM10:53 PM11:01 PM..........
61wheelchair access11:30 PM11:43 PM11:48 PM11:53 PM...............
+

 

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
 CitywestCity Interchange
+ Platform 1
Woden Interchange
+ Platform 11
Athllon/Sulwood KambahLivingston Shops KambahTaverner/ErindaleTuggeranong Interchange
61..........6:42 AM6:49 AM6:54 AM6:59 AM7:10 AM
61wheelchair access..........7:12 AM7:19 AM7:24 AM7:29 AM7:43 AM
61..........7:42 AM7:51 AM7:56 AM8:01 AM8:15 AM
61..........8:12 AM8:21 AM8:26 AM8:31 AM8:45 AM
61..........8:42 AM8:59 AM9:05 AM9:09 AM9:20 AM
61..........9:12 AM9:21 AM9:26 AM9:31 AM9:44 AM
61..........10:12 AM10:20 AM10:25 AM10:30 AM10:43 AM
61..........11:12 AM11:20 AM11:25 AM11:30 AM11:43 AM
61wheelchair access..........12:12 PM12:20 PM12:25 PM12:30 PM12:43 PM
61..........1:12 PM1:20 PM1:25 PM1:30 PM1:43 PM
61wheelchair access..........2:12 PM2:20 PM2:25 PM2:30 PM2:43 PM
61..........3:20 PM3:29 PM3:34 PM3:39 PM3:53 PM
61wheelchair access..........3:42 PM3:51 PM3:56 PM4:01 PM4:15 PM
61..........4:12 PM4:21 PM4:26 PM4:31 PM4:45 PM
61..........4:42 PM4:51 PM4:56 PM5:01 PM5:15 PM
61..........5:12 PM5:21 PM5:26 PM5:31 PM5:45 PM
1615:20 PM5:26 PM5:42 PM5:51 PM5:56 PM6:01 PM6:15 PM
61..........6:12 PM6:21 PM6:26 PM6:31 PM6:44 PM
61..........7:12 PM7:20 PM7:25 PM7:30 PM7:43 PM
61..........8:10 PM8:18 PM8:23 PM8:28 PM8:41 PM
61..........9:10 PM9:18 PM9:23 PM9:28 PM9:41 PM
61wheelchair access..........10:10 PM10:18 PM10:23 PM10:28 PM10:41 PM
61..........11:12 PM11:20 PM11:25 PM11:30 PM11:43 PM
+

wheelchair_icon- Wheelchair Accessible Bus

+
+ + + + + --- /dev/null +++ b/maxious-canberra-transit-feed/source-html/Route_62_162.html @@ -1,1 +1,505 @@ + + + + + + + + +Route 62, 162 + + + + + + + +
+

Chosen services: 62, 162

+ +

View timetable and map

+ This timetable is effective from Monday 25 May 2009. +

Weekdays

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
 Tuggeranong Interchange
+ Platform 4
Kambah HighKambah VillageWoden Interchange
+ Platform 9
City InterchangeCitywest
62wheelchair access6:09 AM6:16 AM6:24 AM6:37 AM..........
62wheelchair access6:39 AM6:46 AM6:54 AM7:07 AM..........
162wheelchair access7:09 AM7:16 AM7:25 AM7:40 AM7:55 AM7:58 AM
1627:36 AM7:43 AM7:52 AM8:07 AM8:22 AM8:25 AM
627:54 AM8:01 AM8:10 AM8:24 AM..........
1628:09 AM8:16 AM8:25 AM8:40 AM8:55 AM8:58 AM
628:39 AM8:46 AM8:55 AM9:09 AM..........
629:39 AM9:46 AM9:54 AM10:07 AM..........
6210:39 AM10:46 AM10:54 AM11:07 AM..........
62wheelchair access11:39 AM11:46 AM11:54 AM12:07 PM..........
6212:39 PM12:46 PM12:54 PM1:07 PM..........
621:39 PM1:46 PM1:54 PM2:07 PM..........
622:39 PM2:46 PM2:54 PM3:08 PM..........
623:09 PM3:16 PM3:25 PM3:39 PM..........
623:39 PM3:46 PM3:55 PM4:09 PM..........
624:09 PM4:16 PM4:25 PM4:39 PM..........
624:39 PM4:46 PM4:55 PM5:09 PM..........
625:09 PM5:16 PM5:25 PM5:39 PM..........
625:39 PM5:46 PM5:55 PM6:09 PM..........
62wheelchair access6:09 PM6:16 PM6:25 PM6:37 PM..........
626:39 PM6:45 PM6:52 PM7:03 PM..........
627:39 PM7:45 PM7:52 PM8:03 PM..........
628:39 PM8:45 PM8:52 PM9:03 PM..........
62wheelchair access9:40 PM9:46 PM9:53 PM10:04 PM..........
6210:40 PM10:46 PM10:53 PM11:04 PM..........
+

 

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
 CitywestCity Interchange
+ Platform 1
Woden Interchange
+ Platform 5
Kambah VillageKambah HighTuggeranong Interchange
62...............7:09 AM7:16 AM7:23 AM
62..........7:32 AM7:44 AM7:53 AM8:00 AM
62..........8:02 AM8:14 AM8:23 AM8:30 AM
62wheelchair access..........8:32 AM8:44 AM8:53 AM9:00 AM
62..........9:02 AM9:14 AM9:23 AM9:30 AM
62..........9:32 AM9:43 AM9:51 AM9:58 AM
62wheelchair access..........10:32 AM10:43 AM10:51 AM10:58 AM
62wheelchair access..........11:32 AM11:43 AM11:51 AM11:58 AM
62..........12:32 PM12:43 PM12:51 PM12:58 PM
62..........1:32 PM1:43 PM1:51 PM1:58 PM
62..........2:32 PM2:43 PM2:51 PM2:58 PM
62wheelchair access..........3:32 PM3:44 PM3:53 PM4:00 PM
62..........4:02 PM4:14 PM4:23 PM4:30 PM
62..........4:32 PM4:44 PM4:53 PM5:00 PM
62..........5:02 PM5:14 PM5:23 PM5:30 PM
162wheelchair access5:10 PM5:16 PM5:32 PM5:44 PM5:53 PM6:00 PM
1625:40 PM5:46 PM6:02 PM6:14 PM6:23 PM6:30 PM
1626:10 PM6:16 PM6:32 PM6:43 PM6:51 PM6:58 PM
62..........7:32 PM7:43 PM7:51 PM7:58 PM
62..........8:32 PM8:43 PM8:51 PM8:58 PM
62..........9:32 PM9:43 PM9:51 PM9:58 PM
62..........10:32 PM10:43 PM10:51 PM10:58 PM
62..........11:32 PM11:43 PM11:51 PM11:58 PM
+

wheelchair_icon- Wheelchair Accessible Bus

+
+ + + + + --- /dev/null +++ b/maxious-canberra-transit-feed/source-html/Route_63.html @@ -1,1 +1,731 @@ + + + + + + + + +Route 63 + + + + + + + +
+

Chosen services: 63

+ +

View timetable and map

+ This timetable is effective from Monday 25 May 2009. +

Weekdays

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
 Tuggeranong Interchange
+ Platform 5
MonashErindale CentreWanniassa HighAthllon/Sulwood KambahWoden Interchange
+ Platform 10
Kings Ave/National CrtRussell OfficesADFACampbell Park Offices
63wheelchair access6:11 AM6:19 AM6:23 AM6:28 AM6:33 AM6:40 AM....................
636:40 AM6:48 AM6:52 AM6:57 AM7:02 AM7:10 AM7:24 AM7:27 AM7:31 AM7:35 AM
637:12 AM7:20 AM7:24 AM7:29 AM7:35 AM7:45 AM7:59 AM8:03 AM8:07 AM8:11 AM
637:44 AM7:54 AM7:59 AM8:04 AM8:10 AM8:20 AM8:34 AM8:38 AM8:42 AM8:46 AM
638:10 AM8:20 AM8:25 AM8:30 AM8:36 AM8:45 AM....................
638:45 AM8:55 AM9:00 AM9:05 AM9:11 AM9:20 AM....................
639:45 AM9:54 AM9:58 AM10:03 AM10:09 AM10:17 AM....................
63wheelchair access10:45 AM10:54 AM10:58 AM11:03 AM11:09 AM11:17 AM....................
6311:45 AM11:54 AM11:58 AM12:03 PM12:09 PM12:17 PM....................
6312:45 PM12:54 PM12:58 PM1:03 PM1:09 PM1:17 PM....................
631:45 PM1:54 PM1:58 PM2:03 PM2:09 PM2:17 PM....................
63wheelchair access2:45 PM2:54 PM2:58 PM3:03 PM3:09 PM3:18 PM....................
633:14 PM3:24 PM3:29 PM3:34 PM3:40 PM3:49 PM....................
63wheelchair access3:45 PM3:55 PM4:00 PM4:05 PM4:11 PM4:20 PM....................
634:15 PM4:25 PM4:30 PM4:35 PM4:41 PM4:50 PM....................
634:45 PM4:55 PM5:00 PM5:05 PM5:11 PM5:20 PM....................
635:15 PM5:25 PM5:30 PM5:35 PM5:41 PM5:50 PM....................
635:45 PM5:55 PM6:00 PM6:05 PM6:11 PM6:20 PM....................
636:45 PM6:54 PM6:58 PM7:03 PM7:09 PM7:17 PM....................
637:45 PM7:54 PM7:58 PM8:03 PM8:09 PM8:17 PM....................
638:45 PM8:54 PM8:58 PM9:03 PM9:09 PM9:17 PM....................
63wheelchair access9:45 PM9:54 PM9:58 PM10:03 PM10:09 PM10:17 PM....................
6310:45 PM10:54 PM10:58 PM11:03 PM11:09 PM.........................
+

 

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
 Campbell Park OfficesADFARussell OfficesKings Ave/National CrtWoden Interchange
+ Platform 12
Athllon/Sulwood KambahSainsbury Street Wanniassa HighErindale CentreMonashTuggeranong Interchange
63C..............................6:10 AM6:15 AM6:19 AM6:23 AM6:31 AM
63Cwheelchair access..............................6:40 AM6:45 AM6:49 AM6:53 AM7:01 AM
63....................7:03 AM7:10 AM7:11 AM7:15 AM7:19 AM7:23 AM7:31 AM
63....................7:23 AM7:30 AM7:32 AM7:36 AM7:41 AM7:46 AM7:56 AM
63....................8:03 AM8:12 AM8:14 AM8:18 AM8:23 AM8:28 AM8:38 AM
63wheelchair access....................8:23 AM8:32 AM8:34 AM8:38 AM8:43 AM8:48 AM8:58 AM
63wheelchair access....................9:03 AM9:12 AM9:14 AM9:18 AM9:23 AM9:28 AM9:37 AM
63wheelchair access....................10:03 AM10:11 AM10:13 AM10:17 AM10:22 AM10:26 AM10:35 AM
63....................11:03 AM11:11 AM11:13 AM11:17 AM11:22 AM11:26 AM11:35 AM
63....................12:03 PM12:11 PM12:13 PM12:17 PM12:22 PM12:26 PM12:35 PM
63....................1:03 PM1:11 PM1:13 PM1:17 PM1:22 PM1:26 PM1:35 PM
63wheelchair access....................2:03 PM2:11 PM2:13 PM2:17 PM2:22 PM2:26 PM2:35 PM
63....................3:03 PM3:12 PM3:14 PM3:18 PM3:23 PM3:28 PM3:38 PM
63wheelchair access....................3:23 PM3:32 PM3:34 PM3:38 PM3:43 PM3:48 PM3:58 PM
63....................4:03 PM4:12 PM4:14 PM4:18 PM4:23 PM4:28 PM4:38 PM
63....................4:23 PM4:32 PM4:34 PM4:38 PM4:43 PM4:48 PM4:58 PM
634:37 PM4:41 PM4:45 PM4:48 PM5:03 PM5:12 PM5:14 PM5:18 PM5:23 PM5:28 PM5:38 PM
634:57 PM5:01 PM5:05 PM5:08 PM5:23 PM5:32 PM5:34 PM5:38 PM5:43 PM5:48 PM5:58 PM
63wheelchair access5:37 PM5:41 PM5:45 PM5:48 PM6:03 PM6:12 PM6:14 PM6:18 PM6:23 PM6:28 PM6:37 PM
635:57 PM6:01 PM6:05 PM6:08 PM6:23 PM6:32 PM6:34 PM6:38 PM6:43 PM6:47 PM6:56 PM
63....................7:03 PM7:11 PM7:13 PM7:17 PM7:22 PM7:26 PM7:35 PM
63....................8:03 PM8:11 PM8:13 PM8:17 PM8:22 PM8:26 PM8:35 PM
63wheelchair access....................9:03 PM9:11 PM9:13 PM9:17 PM9:22 PM9:26 PM9:35 PM
63....................10:03 PM10:11 PM10:13 PM10:17 PM10:22 PM10:26 PM10:35 PM
63wheelchair access....................11:03 PM11:11 PM11:13 PM11:17 PM11:22 PM11:26 PM11:35 PM
+

C - Commences at Sainsbury Street Wanniassa 5 minutes earlier

+

wheelchair_icon- Wheelchair Accessible Bus

+
+ + + + + --- /dev/null +++ b/maxious-canberra-transit-feed/source-html/Route_64.html @@ -1,1 +1,438 @@ + + + + + + + + +Route 64 + + + + + + + +
+

Chosen services: 64

+ +

View timetable and map

+

On School days only this bus arrives at Woden Interchange at 8:41am.

+

Weekdays

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
 Tuggeranong Interchange
+ Platform 5
Monash PrimaryMacKillop College Wanniassa CampusAthllon/Sulwood KambahWoden Interchange
64wheelchair access6:05 AM6:12 AM6:16 AM6:23 AM6:31 AM
64wheelchair access6:35 AM6:42 AM6:46 AM6:53 AM7:01 AM
647:05 AM7:12 AM7:16 AM7:23 AM7:31 AM
647:35 AM7:44 AM7:49 AM7:56 AM8:06 AM
648:05 AM8:14 AM8:19 AM8:26 AM8:36 AM
648:25 AM8:34 AM8:39 AM8:46 AM8:56 AM
64wheelchair access9:05 AM9:14 AM9:19 AM9:26 AM9:35 AM
64wheelchair access9:35 AM9:43 AM9:47 AM9:54 AM10:03 AM
6410:35 AM10:43 AM10:47 AM10:54 AM11:03 AM
64wheelchair access11:35 AM11:43 AM11:47 AM11:54 AM12:03 PM
6412:35 PM12:43 PM12:47 PM12:54 PM1:03 PM
64wheelchair access1:35 PM1:43 PM1:47 PM1:54 PM2:03 PM
642:35 PM2:43 PM2:47 PM2:54 PM3:03 PM
643:05 PM3:14 PM3:19 PM3:26 PM3:36 PM
643:35 PM3:44 PM3:49 PM3:56 PM4:06 PM
644:35 PM4:44 PM4:49 PM4:56 PM5:06 PM
645:05 PM5:14 PM5:19 PM5:26 PM5:36 PM
645:35 PM5:44 PM5:49 PM5:56 PM6:06 PM
646:36 PM6:44 PM6:48 PM6:55 PM7:04 PM
647:35 PM7:43 PM7:47 PM7:54 PM8:03 PM
648:35 PM8:43 PM8:47 PM8:54 PM9:03 PM
64wheelchair access9:35 PM9:43 PM9:47 PM9:54 PM10:03 PM
6410:35 PM10:43 PM10:47 PM10:54 PM.....
+

 

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
 Woden Interchange
+ Platform 11
Athllon/Sulwood KambahMacKillop College Wanniassa CampusMonash PrimaryTuggeranong Interchange
64Cwheelchair access..........6:51 AM6:55 AM7:02 AM
647:06 AM7:14 AM7:21 AM7:25 AM7:33 AM
64C..........7:51 AM7:56 AM8:05 AM
64wheelchair access8:06 AM8:16 AM8:23 AM8:28 AM8:37 AM
648:36 AM8:46 AM8:53 AM8:58 AM9:07 AM
64wheelchair access9:06 AM9:16 AM9:23 AM9:28 AM9:36 AM
6410:06 AM10:15 AM10:22 AM10:26 AM10:34 AM
64wheelchair access11:06 AM11:15 AM11:22 AM11:26 AM11:34 AM
6412:06 PM12:15 PM12:22 PM12:26 PM12:34 PM
64wheelchair access1:06 PM1:15 PM1:22 PM1:26 PM1:34 PM
64wheelchair access2:06 PM2:15 PM2:22 PM2:26 PM2:34 PM
64wheelchair access3:06 PM3:16 PM3:23 PM3:28 PM3:37 PM
643:36 PM3:46 PM3:53 PM3:58 PM4:07 PM
644:06 PM4:16 PM4:23 PM4:28 PM4:37 PM
644:36 PM4:46 PM4:53 PM4:58 PM5:07 PM
64wheelchair access5:06 PM5:16 PM5:23 PM5:28 PM5:37 PM
645:36 PM5:46 PM5:53 PM5:58 PM6:07 PM
64wheelchair access6:06 PM6:16 PM6:23 PM6:28 PM6:36 PM
647:06 PM7:15 PM7:22 PM7:26 PM7:34 PM
648:06 PM8:15 PM8:22 PM8:26 PM8:34 PM
64wheelchair access9:06 PM9:15 PM9:22 PM9:26 PM9:34 PM
64wheelchair access10:06 PM10:15 PM10:22 PM10:26 PM10:34 PM
6411:06 PM11:15 PM11:22 PM11:26 PM11:34 PM
+

C - Commences at Sainsbury Street Wanniassa 6 minutes earlier

+

wheelchair_icon- Wheelchair Accessible Bus

+
+ + + + + --- /dev/null +++ b/maxious-canberra-transit-feed/source-html/Route_65_265.html @@ -1,1 +1,694 @@ + + + + + + + + +Route 65 + + + + + + + + +
+

Chosen services: 65, 265_

+

View timetable and map

+ +This timetable is effective from Monday 20 July 2009. +

Weekdays

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
 Tuggeranong Interchange
+ Platform 5
McKillop College Isabella CampusGowrieErindale Centre /
+Sternberg Crescent
Woden Interchange
+ Platform 10
Kings Ave/National CrtRussell OfficesCity InterchangeCitywest
65wheelchair access5:35 AM5:41 AM5:52 AM5:57 AM6:11 AM....................
656:35 AM6:41 AM6:52 AM6:57 AM7:11 AM....................
2656:53 AM7:00 AM7:12 AM7:21 AM7:37 AM7:52 AM7:56 AM8:05 AM8:08 AM
2657:20 AM7:26 AM7:34 AM7:43 AM8:01 AM8:15 AM8:19 AM8:29 AM8:32 AM
657:30 AM7:39 AM7:56 AM8:05 AM8:22 AM....................
657:45 AM7:54 AM8:11 AM8:20 AM8:42 AM....................
658:15 AM8:24 AM8:41 AM8:50 AM9:07 AM....................
658:45 AM8:54 AM9:11 AM9:20 AM9:36 AM....................
65wheelchair access9:45 AM9:52 AM10:05 AM10:12 AM10:27 AM....................
65wheelchair access10:45 AM10:52 AM11:05 AM11:12 AM11:27 AM....................
6511:45 AM11:52 AM12:05 PM12:12 PM12:27 PM....................
6512:45 PM12:52 PM1:05 PM1:12 PM1:27 PM....................
651:45 PM1:52 PM2:05 PM2:12 PM2:27 PM....................
652:45 PM2:52 PM3:05 PM3:12 PM3:31 PM....................
653:15 PM3:24 PM3:37 PM3:44 PM4:03 PM....................
653:45 PM3:54 PM4:07 PM4:14 PM4:33 PM....................
654:20 PM4:29 PM4:42 PM4:49 PM5:08 PM....................
654:45 PM4:54 PM5:07 PM5:14 PM5:33 PM....................
65wheelchair access5:15 PM5:24 PM5:37 PM5:44 PM6:03 PM....................
65wheelchair access5:45 PM5:54 PM6:07 PM6:14 PM6:33 PM....................
656:15 PM6:24 PM6:36 PM6:41 PM6:57 PM....................
65wheelchair access6:41 PM6:47 PM6:59 PM7:04 PM7:20 PM....................
65wheelchair access7:41 PM7:47 PM7:59 PM8:04 PM8:20 PM....................
658:41 PM8:47 PM8:59 PM9:04 PM9:20 PM....................
659:41 PM9:47 PM9:59 PM10:04 PM10:20 PM....................
6510:41 PM10:47 PM10:59 PM11:04 PM11:20 PM....................
+

 

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
 CitywestCity Interchange
+ Platform 10
Russell OfficesKings Ave/National CrtWoden Interchange
+ Platform 12
Erindale CentreGowrieMcKillop College Isabella CampusTuggeranong Interchange
65Cwheelchair access..............................6:08 AM6:19 AM6:25 AM
65....................6:25 AM6:37 AM6:43 AM6:54 AM7:00 AM
65....................6:55 AM7:10 AM7:18 AM7:34 AM7:44 AM
65wheelchair access....................7:25 AM7:42 AM7:50 AM8:06 AM8:16 AM
65wheelchair access....................7:55 AM8:12 AM8:20 AM8:36 AM8:46 AM
65....................8:25 AM8:42 AM8:50 AM9:06 AM9:16 AM
65....................8:55 AM9:12 AM9:20 AM9:35 AM9:43 AM
65....................9:55 AM10:09 AM10:15 AM10:27 AM10:35 AM
65....................10:55 AM11:09 AM11:15 AM11:27 AM11:35 AM
65....................11:55 AM12:09 PM12:15 PM12:27 PM12:35 PM
65....................12:55 PM1:09 PM1:15 PM1:27 PM1:35 PM
65wheelchair access....................1:55 PM2:09 PM2:15 PM2:27 PM2:35 PM
65....................2:55 PM3:11 PM3:18 PM3:32 PM3:41 PM
65wheelchair access....................3:25 PM3:42 PM3:49 PM4:03 PM4:12 PM
65....................3:55 PM4:12 PM4:19 PM4:33 PM4:42 PM
65wheelchair access....................4:20 PM4:37 PM4:44 PM4:58 PM5:07 PM
65wheelchair access....................4:55 PM5:12 PM5:19 PM5:33 PM5:42 PM
2654:55 PM5:01 PM5:10 PM5:13 PM5:28 PM5:45 PM5:52 PM6:06 PM6:15 PM
2655:25 PM5:31 PM5:40 PM5:43 PM5:58 PM6:15 PM6:22 PM6:35 PM6:43 PM
2655:55 PM6:01 PM6:10 PM6:13 PM6:28 PM6:42 PM6:48 PM7:00 PM7:08 PM
65....................6:54 PM7:08 PM7:14 PM7:26 PM7:34 PM
65....................7:54 PM8:08 PM8:14 PM8:26 PM8:34 PM
65....................8:54 PM9:08 PM9:14 PM9:26 PM9:34 PM
65....................9:54 PM10:08 PM10:14 PM10:26 PM10:34 PM
65....................10:54 PM11:08 PM11:14 PM11:26 PM11:34 PM
+

C - Commences at the corner of Bugden Avenue and Sternberg Cresent at 6:04am

+

wheelchair_icon- Wheelchair Accessible Bus

+
+ + + + --- /dev/null +++ b/maxious-canberra-transit-feed/source-html/Route_66.html @@ -1,1 +1,503 @@ + + + + + + + + +Route 66 + + + + + + + +
+

Chosen services: 66

+ +

View timetable and map

+ This timetable is effective from Monday 25 May 2009. +

Weekdays

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
 Tuggeranong Interchange
+ Platform 7
Bonython PrimaryDeamer/Clift RichardsonProctor / MeadErindale CentreWoden Interchange
666:12 AM6:18 AM6:25 AM6:31 AM6:38 AM6:52 AM
666:41 AM6:47 AM6:54 AM7:00 AM7:12 AM7:27 AM
667:06 AM7:14 AM7:23 AM7:32 AM7:44 AM8:00 AM
667:36 AM7:44 AM7:53 AM8:02 AM8:14 AM8:30 AM
66 wheelchair access8:06 AM8:14 AM8:23 AM8:32 AM8:44 AM9:00 AM
668:36 AM8:44 AM8:53 AM9:02 AM9:14 AM9:30 AM
66 wheelchair access9:09 AM9:17 AM9:26 AM9:33 AM9:41 AM9:56 AM
6610:12 AM10:18 AM10:26 AM10:32 AM10:40 AM10:55 AM
66 wheelchair access11:12 AM11:18 AM11:26 AM11:32 AM11:40 AM11:55 AM
6612:12 PM12:18 PM12:26 PM12:32 PM12:40 PM12:55 PM
66 wheelchair access1:12 PM1:18 PM1:26 PM1:32 PM1:40 PM1:55 PM
662:12 PM2:18 PM2:26 PM2:32 PM2:40 PM2:55 PM
663:12 PM3:19 PM3:27 PM3:34 PM3:45 PM4:00 PM
664:12 PM4:19 PM4:27 PM4:34 PM4:45 PM5:00 PM
664:42 PM4:49 PM4:57 PM5:04 PM5:15 PM5:30 PM
665:12 PM5:19 PM5:27 PM5:34 PM5:45 PM6:00 PM
665:42 PM5:49 PM5:57 PM6:04 PM6:15 PM6:30 PM
666:13 PM6:20 PM6:28 PM6:34 PM6:42 PM6:57 PM
667:14 PM7:20 PM7:28 PM7:34 PM7:42 PM7:57 PM
668:14 PM8:20 PM8:28 PM8:34 PM8:42 PM8:57 PM
669:14 PM9:20 PM9:28 PM9:34 PM9:42 PM9:57 PM
66 wheelchair access10:14 PM10:20 PM10:28 PM10:34 PM10:42 PM10:57 PM
6611:14 PM11:20 PM11:28 PM11:34 PM11:42 PM.....
+

 

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
 Woden Interchange
+ Platform 11
Erindale CentreProctor / MeadDeamer/Clift RichardsonBonython PrimaryTuggeranong Interchange
66 wheelchair access.....6:02 AM6:10 AM6:17 AM6:22 AM6:31 AM
66 wheelchair access6:22 AM6:32 AM6:40 AM6:47 AM6:52 AM7:01 AM
666:52 AM7:02 AM7:10 AM7:17 AM7:22 AM7:31 AM
667:22 AM7:34 AM7:44 AM7:51 AM7:58 AM8:08 AM
667:52 AM8:13 AM8:23 AM8:30 AM8:37 AM8:47 AM
668:22 AM8:43 AM8:53 AM9:00 AM9:07 AM9:17 AM
669:16 AM9:34 AM9:42 AM9:49 AM9:55 AM10:04 AM
66 wheelchair access10:22 AM10:34 AM10:42 AM10:49 AM10:55 AM11:04 AM
66 wheelchair access11:22 AM11:34 AM11:42 AM11:49 AM11:55 AM12:04 PM
66 wheelchair access12:22 PM12:34 PM12:42 PM12:49 PM12:55 PM1:04 PM
661:22 PM1:34 PM1:42 PM1:49 PM1:55 PM2:04 PM
662:22 PM2:34 PM2:42 PM2:49 PM2:55 PM3:04 PM
662:52 PM3:04 PM3:15 PM3:22 PM3:29 PM3:39 PM
663:22 PM3:34 PM3:45 PM3:52 PM3:59 PM4:09 PM
663:52 PM4:04 PM4:15 PM4:22 PM4:29 PM4:39 PM
664:22 PM4:34 PM4:45 PM4:52 PM4:59 PM5:09 PM
664:52 PM5:04 PM5:15 PM5:22 PM5:29 PM5:39 PM
665:22 PM5:34 PM5:45 PM5:52 PM5:59 PM6:09 PM
665:52 PM6:04 PM6:15 PM6:22 PM6:29 PM6:38 PM
666:22 PM6:34 PM6:42 PM6:49 PM6:54 PM7:03 PM
667:22 PM7:34 PM7:42 PM7:49 PM7:54 PM8:03 PM
668:22 PM8:34 PM8:42 PM8:49 PM8:54 PM9:03 PM
669:22 PM9:34 PM9:42 PM9:49 PM9:54 PM10:03 PM
66 wheelchair access10:22 PM10:34 PM10:42 PM10:49 PM10:54 PM11:03 PM
6611:22 PM11:34 PM11:42 PM11:49 PM11:54 PM.....
+

wheelchair_icon- Wheelchair Accessible Bus

+
+ + + + + --- /dev/null +++ b/maxious-canberra-transit-feed/source-html/Route_67_267.html @@ -1,1 +1,720 @@ + + + + + + + + +Route 67, 267 + + + + + + + + +
+

Chosen services: 67, 267

+

View timetable and map

+ +This timetable is effective from Monday 25 May 2009. +

Weekdays

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
 Tuggeranong Interchange
+ Platform 7
Calwell ShopsChisholm ShopsErindale Drive/SternbergWoden Interchange
+ Platform 10
Kings Ave/National CrtRussell OfficesCity Interchange
+ Platform 11
Citywest
676:03 AM6:16 AM6:27 AM6:35 AM6:44 AM....................
676:33 AM6:46 AM6:57 AM7:05 AM7:14 AM....................
2677:02 AM7:15 AM7:26 AM7:35 AM7:50 AM8:04 AM8:08 AM8:18 AM8:21 AM
677:18 AM7:31 AM7:45 AM7:55 AM8:09 AM....................
267wheelchair access7:31 AM7:46 AM8:00 AM8:10 AM8:25 AM8:39 AM8:43 AM8:53 AM8:56 AM
678:03 AM8:18 AM8:32 AM8:42 AM8:56 AM....................
678:33 AM8:48 AM9:02 AM9:12 AM9:26 AM....................
679:03 AM9:18 AM9:32 AM9:40 AM9:53 AM....................
6710:03 AM10:17 AM10:28 AM10:36 AM10:49 AM....................
67 wheelchair access11:03 AM11:17 AM11:28 AM11:36 AM11:49 AM....................
6712:03 PM12:17 PM12:28 PM12:36 PM12:49 PM....................
671:03 PM1:17 PM1:28 PM1:36 PM1:49 PM....................
672:03 PM2:17 PM2:28 PM2:36 PM2:49 PM....................
673:03 PM3:18 PM3:32 PM3:42 PM3:56 PM....................
673:33 PM3:48 PM4:02 PM4:12 PM4:26 PM....................
674:03 PM4:18 PM4:32 PM4:42 PM4:56 PM....................
67 wheelchair access4:33 PM4:48 PM5:02 PM5:12 PM5:26 PM....................
675:03 PM5:18 PM5:32 PM5:42 PM5:56 PM....................
675:33 PM5:48 PM6:02 PM6:12 PM6:26 PM....................
676:03 PM6:18 PM6:32 PM6:40 PM6:53 PM....................
677:03 PM7:17 PM7:28 PM7:36 PM7:49 PM....................
678:03 PM8:17 PM8:28 PM8:36 PM8:49 PM....................
679:03 PM9:17 PM9:28 PM9:36 PM9:49 PM....................
6710:03 PM10:17 PM10:28 PM10:36 PM10:49 PM....................
6711:03 PM11:17 PM11:28 PM11:36 PM.........................
+

 

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
 CitywestCity Interchange
+ Platform 10
Russell OfficesKings Ave/National CrtWoden Interchange
+ Platform 5
Erindale Drive/SternbergBugden/SternbergChisholm ShopsCalwell ShopsTuggeranong Interchange
67 wheelchair access..............................6:01 AM6:08 AM6:18 AM6:32 AM
67....................6:17 AM 6:26 AM 6:26 AM6:33 AM6:43 AM6:57 AM
67 wheelchair access....................6:47 AM6:56 AM6:56 AM7:03 AM7:13 AM7:27 AM
67....................7:17 AM7:26 AM7:26 AM7:34 AM7:46 AM8:03 AM
67....................7:47 AM8:01 AM8:01 AM8:10 AM8:22 AM8:39 AM
67 wheelchair access....................8:17 AM8:31 AM8:31 AM8:40 AM8:52 AM9:09 AM
67....................8:47 AM9:01 AM9:01 AM9:10 AM9:22 AM9:38 AM
67....................9:17 AM9:31 AM9:31 AM9:38 AM9:47 AM10:02 AM
67....................10:17 AM10:30 AM10:30 AM10:37 AM10:46 AM11:01 AM
67....................11:17 AM11:30 AM11:30 AM11:37 AM11:46 AM12:01 PM
67....................12:17 PM12:30 PM12:30 PM12:37 PM12:46 PM1:01 PM
67....................1:17 PM1:30 PM1:30 PM1:37 PM1:46 PM2:01 PM
67 wheelchair access....................2:17 PM2:30 PM2:30 PM2:37 PM2:46 PM3:01 PM
67....................2:47 PM3:00 PM3:00 PM3:10 PM3:25 PM3:41 PM
67....................3:17 PM3:34 PM3:34 PM3:44 PM3:59 PM4:15 PM
67....................3:47 PM4:04 PM4:04 PM4:14 PM4:29 PM4:45 PM
67....................4:17 PM4:34 PM4:34 PM4:44 PM4:59 PM5:15 PM
67 wheelchair access....................4:47 PM5:04 PM5:04 PM5:14 PM5:29 PM5:45 PM
2674:30 PM 4:36 PM4:45 PM4:48 PM5:03 PM5:20 PM5:20 PM5:30 PM5:45 PM6:01 PM
2675:00 PM5:06 PM5:15 PM5:18 PM5:33 PM5:50 PM5:50 PM6:00 PM6:15 PM6:31 PM
2675:44 PM5:50 PM5:59 PM6:02 PM6:17 PM6:33 PM6:33 PM6:40 PM6:49 PM7:04 PM
67....................7:17 PM7:30 PM7:30 PM7:37 PM7:46 PM8:01 PM
67....................8:17 PM8:30 PM8:30 PM8:37 PM8:46 PM9:01 PM
67 wheelchair access....................9:17 PM9:30 PM9:30 PM9:37 PM9:46 PM10:01 PM
67 wheelchair access....................10:17 PM10:30 PM10:30 PM10:37 PM10:46 PM11:01 PM
67....................11:17 PM11:30 PM11:30 PM 11:37 PM11:46 PM12:01 AM
+

wheelchair_icon- Wheelchair Accessible Bus

+
+ + + + --- /dev/null +++ b/maxious-canberra-transit-feed/source-html/Route_7.html @@ -1,1 +1,655 @@ + + + + + + + + +Route 7 + + + + + + + +
+

Chosen services: 7

+ +

View timetable and map

+ This timetable is effective from Monday 25 May 2009. +

Weekdays

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
 Cohen Street Station
+ Platform 2
Lathlain Steet Station
+ Platform 3
Cameron Avenue Station
+ Platform 3
Australian Institute Sports BruceDicksonMerici BraddonCity Interchange
75:44 AM5:46 AM5:50 AM6:01 AM6:09 AM6:16 AM6:23 AM
76:14 AM6:16 AM6:20 AM6:31 AM6:39 AM6:46 AM6:53 AM
76:44 AM6:46 AM6:50 AM7:01 AM7:09 AM7:16 AM7:23 AM
77:14 AM7:16 AM7:20 AM7:31 AM7:40 AM7:47 AM7:55 AM
7wheelchair access7:44 AM7:46 AM7:50 AM8:03 AM8:12 AM8:19 AM8:27 AM
78:14 AM8:16 AM8:20 AM8:33 AM8:42 AM8:49 AM8:57 AM
7wheelchair access8:44 AM8:46 AM8:50 AM9:03 AM9:12 AM9:19 AM9:27 AM
79:18 AM9:20 AM9:24 AM9:36 AM9:44 AM9:51 AM9:58 AM
79:49 AM9:51 AM9:55 AM10:06 AM10:14 AM10:21 AM10:28 AM
710:19 AM10:21 AM10:25 AM10:36 AM10:44 AM10:51 AM10:58 AM
710:49 AM10:51 AM10:55 AM11:06 AM11:14 AM11:21 AM11:28 AM
711:19 AM11:21 AM11:25 AM11:36 AM11:44 AM11:51 AM11:58 AM
711:49 AM11:51 AM11:55 AM12:06 PM12:14 PM12:21 PM12:28 PM
712:19 PM12:21 PM12:25 PM12:36 PM12:44 PM12:51 PM12:58 PM
712:49 PM12:51 PM12:55 PM1:06 PM1:14 PM1:21 PM1:28 PM
7wheelchair access1:19 PM1:21 PM1:25 PM1:36 PM1:44 PM1:51 PM1:58 PM
71:49 PM1:51 PM1:55 PM2:06 PM2:14 PM2:21 PM2:28 PM
72:19 PM2:21 PM2:25 PM2:36 PM2:44 PM2:51 PM2:58 PM
72:49 PM2:51 PM2:55 PM3:07 PM3:15 PM3:22 PM3:30 PM
73:14 PM3:16 PM3:20 PM3:33 PM3:41 PM3:48 PM3:56 PM
73:44 PM3:46 PM3:50 PM4:03 PM4:11 PM4:18 PM4:26 PM
7wheelchair access4:14 PM4:16 PM4:20 PM4:33 PM4:41 PM4:48 PM4:56 PM
74:44 PM4:46 PM4:50 PM5:03 PM5:11 PM5:18 PM5:26 PM
75:14 PM5:16 PM5:20 PM5:33 PM5:41 PM5:48 PM5:56 PM
75:44 PM5:46 PM5:50 PM6:03 PM6:11 PM6:18 PM6:26 PM
76:50 PM6:52 PM6:55 PM7:06 PM7:14 PM7:20 PM7:27 PM
77:50 PM7:52 PM7:55 PM8:06 PM8:14 PM8:20 PM8:27 PM
78:50 PM8:52 PM8:55 PM9:06 PM9:14 PM9:20 PM9:27 PM
79:50 PM9:52 PM9:55 PM10:06 PM10:14 PM10:20 PM10:27 PM
710:50 PM10:52 PM10:55 PM11:06 PM11:14 PM11:20 PM11:27 PM
+

 

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
 City Interchange
+ Platform 10
Merici BraddonDicksonAustralian Institute Sports BruceCameron Avenue StationLathlain Steet StationCohen Street Station
76:32 AM6:39 AM6:46 AM6:54 AM7:05 AM7:07 AM7:11 AM
77:01 AM7:08 AM7:15 AM7:23 AM7:35 AM7:37 AM7:41 AM
77:31 AM7:39 AM7:46 AM7:54 AM8:07 AM8:09 AM8:13 AM
78:01 AM8:09 AM8:16 AM8:24 AM8:37 AM8:39 AM8:43 AM
78:29 AM8:37 AM8:44 AM8:52 AM9:05 AM9:07 AM9:11 AM
78:58 AM9:06 AM9:13 AM9:21 AM9:33 AM9:35 AM9:39 AM
79:30 AM9:37 AM9:44 AM9:52 AM10:03 AM10:05 AM10:09 AM
7wheelchair access10:00 AM10:07 AM10:14 AM10:22 AM10:33 AM10:35 AM10:39 AM
710:30 AM10:37 AM10:44 AM10:52 AM11:03 AM11:05 AM11:09 AM
711:00 AM11:07 AM11:14 AM11:22 AM11:33 AM11:35 AM11:39 AM
711:30 AM11:37 AM11:44 AM11:52 AM12:03 PM12:05 PM12:09 PM
712:00 PM12:07 PM12:14 PM12:22 PM12:33 PM12:35 PM12:39 PM
7wheelchair access12:30 PM12:37 PM12:44 PM12:52 PM1:03 PM1:05 PM1:09 PM
71:00 PM1:07 PM1:14 PM1:22 PM1:33 PM1:35 PM1:39 PM
7wheelchair access1:30 PM1:37 PM1:44 PM1:52 PM2:03 PM2:05 PM2:09 PM
72:00 PM2:07 PM2:14 PM2:22 PM2:33 PM2:35 PM2:39 PM
72:30 PM2:37 PM2:44 PM2:52 PM3:04 PM3:06 PM3:10 PM
72:59 PM3:07 PM3:14 PM3:23 PM3:36 PM3:38 PM3:42 PM
73:31 PM3:39 PM3:46 PM3:55 PM4:08 PM4:10 PM4:14 PM
74:01 PM4:09 PM4:16 PM4:25 PM4:38 PM4:40 PM4:44 PM
7wheelchair access4:31 PM4:39 PM4:46 PM4:55 PM5:08 PM5:10 PM5:14 PM
7wheelchair access5:01 PM5:09 PM5:16 PM5:25 PM5:38 PM5:40 PM5:44 PM
75:31 PM5:39 PM5:46 PM5:55 PM6:08 PM6:10 PM6:14 PM
76:31 PM6:37 PM6:44 PM6:52 PM7:03 PM7:05 PM7:08 PM
77:31 PM7:37 PM7:44 PM7:52 PM8:03 PM8:05 PM8:08 PM
7wheelchair access8:31 PM8:37 PM8:44 PM8:52 PM9:03 PM9:05 PM9:08 PM
79:31 PM9:37 PM9:44 PM9:52 PM10:03 PM10:05 PM10:08 PM
710:31 PM10:37 PM10:44 PM10:52 PM11:03 PM11:05 PM11:08 PM
+

wheelchair_icon- Wheelchair Accessible Bus

+
+ + + + + --- /dev/null +++ b/maxious-canberra-transit-feed/source-html/Route_701.html @@ -1,1 +1,143 @@ + + + + + + + + +Route_701 + + + + + +
+

Chosen services: 701

+ +

View timetable and map

+

This timetable is effective from Monday 17 August 2009.

+ +

Weekdays

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
 Spence TerminusSpenceCopland CollegeWilliam Webb / Ginninderra DriveMacarthur\NorthbourneCity Interchange
+ Platform 10
Russell OfficesNational Circ/Canberra Ave
7016:58 AM7:03 AM7:10 AM7:14 AM7:26 AM7:37 AM7:46 AM7:54 AM
7017:31 AM7:36 AM7:43 AM7:47 AM8:10 AM8:26 AM8:35 AM8:43 AM
7017:45 AM7:50 AM7:57 AM8:01 AM8:24 AM8:40 AM8:49 AM8:57 AM
+

 

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
 Sydney AvenueRussell OfficesCity Interchange
+ Platform 11
Macarthur\NorthbourneWilliam Webb / Ginninderra DriveCopland CollegeSpenceSpence Terminus
7014:42 PM4:50 PM5:02 PM5:09 PM5:22 PM5:27 PM5:34 PM5:40 PM
701..........5:20 PM5:27 PM5:39 PM5:43 PM5:50 PM5:54 PM
7015:25 PM5:33 PM5:43 PM5:50 PM6:02 PM6:06 PM6:13 PM6:17 PM
7015:42 PM5:50 PM6:00 PM6:07 PM6:19 PM6:23 PM6:30 PM6:34 PM
+
+ + + + + --- /dev/null +++ b/maxious-canberra-transit-feed/source-html/Route_702.html @@ -1,1 +1,140 @@ + + + + + + + + +Route_702 + + + + + +
+

Chosen services: 702

+ +

View timetable and map

+

This timetable is effective from Monday 25 May 2009.

+ +

Weekdays

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
 Sydney AvenueRussell OfficesCity Interchange
+ Platform 11
Macarthur\NorthbourneNorthbourne Ave/Antill StFlynnCharnwoodFraserFraser East Terminus
7024:50 PM4:58 PM5:08 PM5:13 PM5:15 PM5:27 PM5:32 PM5:38 PM5:42 PM
702..........5:30 PM5:35 PM5:37 PM5:49 PM5:54 PM6:00 PM6:04 PM
7025:35 PM5:43 PM5:53 PM5:58 PM6:00 PM6:12 PM6:17 PM6:23 PM6:27 PM
+

 

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
 Fraser East TerminusFraserCharnwoodFlynnNorthbourne Ave/Antill StMacarthur\NorthbourneCity Interchange
+ Platform 10
Russell OfficesNational Circ/Canberra Ave
7026:58 AM7:03 AM7:09 AM7:14 AM7:27 AM7:30 AM7:45 AM7:54 AM8:02 AM
7027:35 AM7:40 AM7:46 AM7:51 AM8:05 AM8:10 AM8:26 AM8:35 AM8:43 AM
7027:54 AM7:59 AM8:06 AM8:11 AM8:28 AM8:33 AM8:49 AM8:58 AM9:06 AM
+
+ + + + + --- /dev/null +++ b/maxious-canberra-transit-feed/source-html/Route_703.html @@ -1,1 +1,165 @@ + + + + + + + + +Route_703 + + + + + +
+

Chosen services: 703

+ +

View timetable and map

+

This timetable is effective from Monday 25 May 2009.

+ +

Weekdays

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
 Sydney AvenueRussell OfficesCity Interchange
+ Platform 11
Belconnen WayMacgregor ShopsDunlopFraser West Terminus
7034:40 PM4:48 PM4:58 PM5:16 PM5:27 PM5:34 PM5:41 PM
703..........5:15 PM5:33 PM5:44 PM5:51 PM5:58 PM
703 wheelchair access..........5:26 PM5:44 PM5:55 PM6:02 PM6:09 PM
7035:20 PM5:28 PM5:38 PM5:56 PM6:07 PM6:14 PM6:21 PM
7035:45 PM5:53 PM6:03 PM6:21 PM6:32 PM6:39 PM6:46 PM
+

 

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
 Fraser West TerminusDunlopMacgregor ShopsBelconnen WayCity Interchange
+ Platform 10
Russell OfficesNational Circ/Canberra Ave
7036:54 AM7:01 AM7:07 AM7:19 AM7:38 AM7:47 AM7:55 AM
7037:10 AM7:17 AM7:23 AM7:35 AM7:53 AM..........
7037:23 AM7:30 AM7:36 AM7:48 AM8:06 AM..........
7037:38 AM7:45 AM7:51 AM8:03 AM8:34 AM8:43 AM8:51 AM
7037:58 AM8:06 AM8:13 AM8:27 AM8:49 AM8:58 AM9:06 AM
+

wheelchair_icon- Wheelchair Accessible Bus

+
+ + + + + --- /dev/null +++ b/maxious-canberra-transit-feed/source-html/Route_704.html @@ -1,1 +1,105 @@ + + + + + + + + +Route_704 + + + + + +
+

Chosen services: 704

+ +

View timetable and map

+

This timetable is effective from Monday 25 May 2009.

+ +

Weekdays

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
 KippaxHigginsHawker CollegeHawkerMacquarieArandaCity Interchange
+ Platform 10
Russell OfficesNational Circ/Canberra Ave
7047:38 AM7:44 AM7:49 AM7:54 AM8:03 AM8:12 AM8:25 AM8:33 AM8:40 AM
7047:53 AM7:59 AM8:04 AM8:09 AM8:18 AM8:27 AM8:40 AM8:48 AM8:55 AM
+

 

+ + + + + + + + + + + + + + + + + + + + + + + + + +
 Sydney AvenueRussell OfficesCity Interchange
+ Platform 11
ArandaMacquarieHawkerHawker CollegeHigginsKippax
7045:06 PM5:14 PM5:24 PM5:33 PM5:42 PM5:50 PM5:55 PM6:00 PM6:06 PM
+

 

+
+ + + + + --- /dev/null +++ b/maxious-canberra-transit-feed/source-html/Route_705.html @@ -1,1 +1,160 @@ + + + + + + + + +Route_705 + + + + + +
+

Chosen services: 705

+ +

View timetable and map

+

This timetable is effective from Monday 25 May 2009.

+ +

Weekdays

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
 Cohen St Bus Station
+ Platform 2
Lathlain St Bus Station
+ Platform 2
Cameron Ave Bus Station
+ Platform 2
Tuggeranong Interchange
+ Platform 7
Centre Link Tuggeranong
705 wheelchair access7:21 AM7:23 AM7:27 AM8:03 AM8:05 AM
705 wheelchair access7:47 AM7:49 AM7:53 AM8:29 AM8:31 AM
705 wheelchair access8:13 AM8:15 AM8:19 AM8:55 AM8:57 AM
7054:45 PM4:47 PM4:51 PM5:26PM.....
7055:11 PM5:13 PM5:17 PM5:52PM.....
7055:38 PM5:40 PM5:44 PM6:19PM.....
+

 

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
 Centre Link TuggeranongTuggeranong Interchange
+ Platform 7
Cameron Ave Bus StationLathlain St Bus Station Cohen St Bus Station
705.....7:23 AM7:49 AM7:51 AM7:55 AM
705.....7:49 AM8:15 AM8:17 AM8:21 AM
705 wheelchair access.....8:14 AM8:40 AM8:42 AM8:46 AM
705 wheelchair access4:42 PM4:47 PM5:13 PM5:15 PM5:19 PM
7055:07 PM5:12 PM5:38 PM5:40 PM5:44 PM
7055:35 PM5:40 PM6:06 PM6:08 PM6:12 PM
+

wheelchair_icon- Wheelchair Accessible Bus

+
+ + + + + --- /dev/null +++ b/maxious-canberra-transit-feed/source-html/Route_71.html @@ -1,1 +1,126 @@ + + + + + + + + +Route_71 + + + + + +
+

Chosen services: 71

+ +

View timetable and map

+

This timetable is effective from Monday 25 May 2009.

+ +

Weekdays

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
 Cohen St Bus Station
+ Platform 1
Lathlain St Bus Station
+ Platform 3
Cameron Ave Bus Station
+ Platform 3
Gwydir Square KaleenKaleen Village/MaribyrnongGiralangKaleen Village/MaribyrnongGwydir Square KaleenCameron Ave Bus StationLathlain St Bus StationCohen St Bus Station
719:30 AM9:32 AM9:36 AM9:43 AM9:48 AM9:56 AM9:58 AM10:03 AM10:10 AM10:12 AM10:16 AM
7110:30 AM10:32 AM10:36 AM10:43 AM10:48 AM10:56 AM10:58 AM11:03 AM11:10 AM11:12 AM11:16 AM
7111:30 AM11:32 AM11:36 AM11:43 AM11:48 AM11:56 AM11:58 AM12:03 PM12:10 PM12:12 PM12:16 PM
7112:30 PM12:32 PM12:36 PM12:43 PM12:48 PM12:56 PM12:58 PM1:03 PM1:10 PM1:12 PM1:16 PM
711:30 PM1:32 PM1:36 PM1:43 PM1:48 PM1:56 PM1:58 PM2:03 PM2:10 PM2:12 PM2:16 PM
+
+ + + + + --- /dev/null +++ b/maxious-canberra-transit-feed/source-html/Route_710.html @@ -1,1 +1,156 @@ + + + + + + + + +Route_710 + + + + + +
+

Chosen services: 710

+ +

View timetable and map

+

This timetable is effective from Monday 25 May 2009.

+ +

Weekdays

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
 Sydney AvenueRussell OfficesCity Interchange
+ Platform 11
Cameron Ave Bus StationLathlain St Bus StationCohen St Bus Station
7104:07 PM4:15 PM4:25 PM4:42 PM4:44 PM4:48 PM
710 4:27 PM4:35 PM4:45 PM5:02 PM5:04 PM5:08 PM
7104:45 PM4:53 PM5:03 PM5:20 PM5:22 PM5:26 PM
7105:07 PM5:15 PM5:25 PM5:42 PM5:44 PM5:48 PM
7105:27 PM5:35 PM5:45 PM6:02 PM6:04 PM6:08 PM
+

 

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
 Cohen St Bus Station
+ Platform 2
Lathlain St Bus Station
+ Platform 2
Cameron Ave Bus Station
+ Platform 2
City Interchange
+ Platform 10
Russell OfficesNational Circ/Canberra Ave
7107:04 AM7:06 AM7:10 AM7:26 AM7:35 AM7:43 AM
7107:34 AM7:36 AM7:40 AM7:56 AM8:05 AM8:13 AM
7107:49 AM7:51 AM7:55 AM8:11 AM8:20 AM8:28 AM
7108:04 AM8:06 AM8:10 AM8:26 AM8:35 AM8:43 AM
7108:19 AM8:21 AM8:25 AM8:41 AM8:50 AM8:58 AM
+

wheelchair_icon- Wheelchair Accessible Bus

+
+ + + + + --- /dev/null +++ b/maxious-canberra-transit-feed/source-html/Route_720.html @@ -1,1 +1,129 @@ + + + + + + + + +Route_720 + + + + + +
+

Chosen services: 720

+ +

View timetable and map

+

This timetable is effective from Monday 25 May 2009.

+ +

Weekdays

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
 Farrer TerminusSouthlands MawsonGarranHughesCitywestCity InterchangeElectricity House
7207:10 AM7:16 AM7:28 AM7:34 AM7:52 AM7:56 AM7:57 AM
7207:40 AM7:46 AM7:58 AM8:04 AM8:22 AM8:26 AM8:27 AM
7208:16 AM8:22 AM8:34 AM8:40 AM8:58 AM9:02 AM9:03 AM
7208:40 AM8:46 AM8:58 AM9:04 AM9:22 AM9:26 AM9:27 AM
+

 

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
 CitywestCity Interchange
+ Platform 10
HughesGarranSouthlands MawsonFarrer Terminus
7204:40 PM4:46 PM5:04 PM5:10 PM5:23 PM5:29 PM
7205:10 PM5:16 PM5:34 PM5:40 PM5:53 PM5:59 PM
7205:40 PM5:46 PM6:04 PM6:10 PM6:23 PM6:29 PM
+
+ + + + + --- /dev/null +++ b/maxious-canberra-transit-feed/source-html/Route_727.html @@ -1,1 +1,1121 @@ + + + + + + + + +Route 727 + + + + + + + +
+

Chosen services: 727

+ +

View timetable and map

+

This timetable is effective from Monday 16 November 2009.

+ +

Weekdays

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
 Railway Station KingstonKings Ave/National CrtRussell OfficesCity Interchange
+ Platform 8
Macarthur\NorthbourneNorthbourne Ave/Antill StFlemington Rd, Sandford StGungahlin Market Place
727wheelchair access7:06 AM7:13 AM7:17 AM7:25 A0M7:32 AM7:34 AM7:41 AM7:48 AM
727wheelchair access7:21 AM7:28 AM7:32 AM7:40 AM7:47 AM7:49 AM7:56 AM8:04 AM
727wheelchair access7:36 AM7:43 AM7:47 AM7:55 AM8:02 AM8:04 AM8:11 AM8:21 AM
727wheelchair access7:51 AM7:58 AM8:03 AM8:12 AM8:18 AM8:20 AM8:27 AM8:37 AM
727wheelchair access8:06 AM8:14 AM8:20 AM8:29 AM8:35 AM8:37 AM8:44 AM8:54 AM
727wheelchair access8:21 AM8:29 AM8:35 AM8:44 AM8:50 AM8:52 AM8:59 AM9:06 AM
727wheelchair access8:36 AM8:44 AM8:50 AM8:59 AM9:06 AM9:08 AM9:15 AM9:22 AM
727wheelchair access8:51 AM8:59 AM9:03 AM9:11 AM9:18 AM9:20 AM9:27 AM9:34 AM
727wheelchair access9:06 AM9:13 AM9:17 AM9:25 AM9:32 AM9:34 AM9:41 AM9:48 AM
727wheelchair access9:21 AM9:28 AM9:32 AM9:40 AM9:47 AM9:49 AM9:56 AM10:03 AM
727wheelchair access9:36 AM9:43 AM9:47 AM9:55 AM10:02 AM10:04 AM10:11 AM10:18 AM
727wheelchair access9:51 AM9:58 AM10:02 AM10:10 AM10:17 AM10:19 AM10:26 AM10:33 AM
727wheelchair access10:06 AM10:13 AM10:17 AM10:25 AM10:32 AM10:34 AM10:41 AM10:48 AM
727wheelchair access10:21 AM10:28 AM10:32 AM10:40 AM10:47 AM10:49 AM10:56 AM11:03 AM
727wheelchair access10:36 AM10:43 AM10:47 AM10:55 AM11:02 AM11:04 AM11:11 AM11:18 AM
727wheelchair access10:51 AM10:58 AM11:02 AM11:10 AM11:17 AM11:19 AM11:26 AM11:33 AM
727wheelchair access11:06 AM11:13 AM11:17 AM11:25 AM11:32 AM11:34 AM11:41 AM11:48 AM
727wheelchair access11:21 AM11:28 AM11:32 AM11:40 AM11:47 AM11:49 AM11:56 AM12:03 PM
727wheelchair access11:36 AM11:43 AM11:47 AM11:55 AM12:02 PM12:04 PM12:11 PM12:18 PM
727wheelchair access11:51 AM11:58 AM12:02 PM12:10 PM12:17 PM12:19 PM12:26 PM12:33 PM
727wheelchair access12:06 PM12:13 PM12:17 PM12:25 PM12:32 PM12:34 PM12:41 PM12:48 PM
727wheelchair access12:21 PM12:28 PM12:32 PM12:40 PM12:47 PM12:49 PM12:56 PM1:03 PM
727wheelchair access12:36 PM12:43 PM12:47 PM12:55 PM1:02 PM1:04 PM1:11 PM1:18 PM
727wheelchair access12:51 PM12:58 PM1:02 PM1:10 PM1:17 PM1:19 PM1:26 PM1:33 PM
727wheelchair access1:06 PM1:13 PM1:17 PM1:25 PM1:32 PM1:34 PM1:41 PM1:48 PM
727wheelchair access1:21 PM1:28 PM1:32 PM1:40 PM1:47 PM1:49 PM1:56 PM2:03 PM
727wheelchair access1:36 PM1:43 PM1:47 PM1:55 PM2:02 PM2:04 PM2:11 PM2:18 PM
727wheelchair access1:51 PM1:58 PM2:02 PM2:10 PM2:17 PM2:19 PM2:26 PM2:33 PM
727wheelchair access2:06 PM2:13 PM2:17 PM2:25 PM2:32 PM2:34 PM2:41 PM2:48 PM
727wheelchair access2:21 PM2:28 PM2:32 PM2:40 PM2:47 PM2:49 PM2:56 PM3:03 PM
727wheelchair access2:36 PM2:43 PM2:47 PM2:55 PM3:02 PM3:04 PM3:11 PM3:18 PM
727wheelchair access2:51 PM2:58 PM3:02 PM3:10 PM3:17 PM3:19 PM3:26 PM3:33 PM
727wheelchair access3:06 PM3:13 PM3:17 PM3:25 PM3:32 PM3:34 PM3:41 PM3:48 PM
727wheelchair access3:21 PM3:28 PM3:32 PM3:40 PM3:47 PM3:49 PM3:56 PM4:04 PM
727wheelchair access3:36 PM3:43 PM3:47 PM3:55 PM4:01 PM4:04 PM4:11 PM4:21 PM
727wheelchair access3:51 PM3:58 PM4:03 PM4:15 PM4:20 PM4:23 PM4:30 PM4:40 PM
727wheelchair access4:06 PM4:14 PM4:20 PM4:32 PM4:37 PM4:40 PM4:47 PM4:57 PM
727wheelchair access4:21 PM4:29 PM4:35 PM4:47 PM4:52 PM4:55 PM5:02 PM5:12 PM
727wheelchair access4:36 PM4:44 PM4:50 PM5:02 PM5:07 PM5:10 PM5:17 PM5:27 PM
727wheelchair access4:51 PM4:59 PM5:05 PM5:17 PM5:22 PM5:25 PM5:32 PM5:42 PM
727wheelchair access5:06 PM5:14 PM5:20 PM5:32 PM5:37 PM5:40 PM5:47 PM5:57 PM
727wheelchair access5:21 PM5:29 PM5:35 PM5:47 PM5:52 PM5:55 PM6:02 PM6:10 PM
727wheelchair access5:36 PM5:44 PM5:50 PM6:02 PM6:06 PM6:09 PM6:15 PM6:23 PM
727wheelchair access5:51 PM5:59 PM6:03 PM6:12 PM6:16 PM6:19 PM6:25 PM6:33 PM
727wheelchair access6:06 PM6:12 PM6:16 PM6:25 PM6:29 PM6:32 PM6:38 PM6:46 PM
727wheelchair access6:21 PM6:27 PM6:31 PM6:40 PM6:44 PM6:47 PM6:53 PM7:01 PM
727wheelchair access6:36 PM6:42 PM6:46 PM6:55 PM6:59 PM7:02 PM7:08 PM7:16 PM
+

 

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
 Gungahlin Market PlaceFlemington Rd, Sandford StNorthbourne Ave/Antill StMacarthur\NorthbourneCity Interchange
+ Platform 9
Russell OfficesKings Ave/National CrtRailway Station Kingston
727wheelchair access7:01 AM7:09 AM7:15 AM7:18 AM7:23 AM7:31 AM7:35 AM7:41 AM
727wheelchair access7:16 AM7:24 AM7:30 AM7:36 AM7:46 AM7:56 AM8:00 AM8:06 AM
727wheelchair access7:31 AM7:40 AM7:49 AM7:55 AM8:05 AM8:15 AM8:19 AM8:25 AM
727wheelchair access7:46 AM7:55 AM8:04 AM8:10 AM8:20 AM8:30 AM8:34 AM8:40 AM
727wheelchair access8:01 AM8:10 AM8:19 AM8:25 AM8:35 AM8:45 AM8:49 AM8:55 AM
727wheelchair access8:16 AM8:25 AM8:34 AM8:40 AM8:50 AM9:00 AM9:04 AM9:10 AM
727wheelchair access8:31 AM8:40 AM8:49 AM8:55 AM9:03 AM9:11 AM9:15 AM9:21 AM
727wheelchair access8:46 AM8:55 AM9:03 AM9:06 AM9:11 AM9:19 AM9:23 AM9:29 AM
727wheelchair access9:01 AM9:09 AM9:15 AM9:18 AM9:23 AM9:31 AM9:35 AM9:41 AM
727wheelchair access9:16 AM9:24 AM9:30 AM9:33 AM9:38 AM9:46 AM9:50 AM9:56 AM
727wheelchair access9:31 AM9:39 AM9:45 AM9:48 AM9:53 AM10:01 AM10:05 AM10:11 AM
727wheelchair access9:46 AM9:54 AM10:00 AM10:03 AM10:08 AM10:16 AM10:20 AM10:26 AM
727wheelchair access10:01 AM10:09 AM10:15 AM10:18 AM10:23 AM10:31 AM10:35 AM10:41 AM
727wheelchair access10:16 AM10:24 AM10:30 AM10:33 AM10:38 AM10:46 AM10:50 AM10:56 AM
727wheelchair access10:31 AM10:39 AM10:45 AM10:48 AM10:53 AM11:01 AM11:05 AM11:11 AM
727wheelchair access10:46 AM10:54 AM11:00 AM11:03 AM11:08 AM11:16 AM11:20 AM11:26 AM
727wheelchair access11:01 AM11:09 AM11:15 AM11:18 AM11:23 AM11:31 AM11:35 AM11:41 AM
727wheelchair access11:16 AM11:24 AM11:30 AM11:33 AM11:38 AM11:46 AM11:50 AM11:56 AM
727wheelchair access11:31 AM11:39 AM11:45 AM11:48 AM11:53 AM12:01 PM12:05 PM12:11 PM
727wheelchair access11:46 AM11:54 AM12:00 PM12:03 PM12:08 PM12:16 PM12:20 PM12:26 PM
727wheelchair access12:01 PM12:09 PM12:15 PM12:18 PM12:23 PM12:31 PM12:35 PM12:41 PM
727wheelchair access12:16 PM12:24 PM12:30 PM12:33 PM12:38 PM12:46 PM12:50 PM12:56 PM
727wheelchair access12:31 PM12:39 PM12:45 PM12:48 PM12:53 PM1:01 PM1:05 PM1:11 PM
727wheelchair access12:46 PM12:54 PM1:00 PM1:03 PM1:08 PM1:16 PM1:20 PM1:26 PM
727wheelchair access1:01 PM1:09 PM1:15 PM1:18 PM1:23 PM1:31 PM1:35 PM1:41 PM
727wheelchair access1:16 PM1:24 PM1:30 PM1:33 PM1:38 PM1:46 PM1:50 PM1:56 PM
727wheelchair access1:31 PM1:39 PM1:45 PM1:48 PM1:53 PM2:01 PM2:05 PM2:11 PM
727wheelchair access1:46 PM1:54 PM2:00 PM2:03 PM2:08 PM2:16 PM2:20 PM2:26 PM
727wheelchair access2:01 PM2:09 PM2:15 PM2:18 PM2:23 PM2:31 PM2:35 PM2:41 PM
727wheelchair access2:16 PM2:24 PM2:30 PM2:33 PM2:38 PM2:46 PM2:50 PM2:56 PM
727wheelchair access2:31 PM2:39 PM2:45 PM2:48 PM2:53 PM3:01 PM3:05 PM3:11 PM
727wheelchair access2:46 PM2:54 PM3:00 PM3:03 PM3:08 PM3:16 PM3:20 PM3:26 PM
727wheelchair access3:01 PM3:09 PM3:15 PM3:18 PM3:23 PM3:31 PM3:35 PM3:41 PM
727wheelchair access3:16 PM3:24 PM3:30 PM3:33 PM3:38 PM3:46 PM3:50 PM3:56 PM
727wheelchair access3:31 PM3:39 PM3:45 PM3:48 PM3:53 PM4:01 PM4:06 PM4:14 PM
727wheelchair access3:46 PM3:54 PM4:00 PM4:03 PM4:12 PM4:22 PM4:27 PM4:35 PM
727wheelchair access4:01 PM4:10 PM4:17 PM4:20 PM4:29 PM4:39 PM4:44 PM4:52 PM
727wheelchair access4:16 PM4:25 PM4:32 PM4:35 PM4:44 PM4:54 PM4:59 PM5:07 PM
727wheelchair access4:31 PM4:40 PM4:47 PM4:50 PM4:59 PM5:09 PM5:14 PM5:22 PM
727wheelchair access4:46 PM4:55 PM5:02 PM5:05 PM5:14 PM5:24 PM5:29 PM5:37 PM
727wheelchair access5:01 PM5:10 PM5:17 PM5:20 PM5:29 PM5:39 PM5:44 PM5:52 PM
727wheelchair access5:16 PM5:25 PM5:32 PM5:35 PM5:44 PM5:54 PM5:59 PM6:05 PM
727wheelchair access5:31 PM5:40 PM5:47 PM5:50 PM5:59 PM6:07 PM6:11 PM6:17 PM
727wheelchair access5:46 PM5:55 PM6:02 PM6:05 PM6:10 PM6:18 PM6:22 PM6:28 PM
727wheelchair access6:01 PM6:09 PM6:15 PM6:18 PM6:23 PM6:31 PM6:35 PM6:41 PM
727wheelchair access6:16 PM6:24 PM6:30 PM6:33 PM6:38 PM6:46 PM6:50 PM6:56 PM
727wheelchair access6:31 PM6:39 PM6:45 PM6:48 PM6:53 PM7:01 PM7:05 PM7:11 PM
727wheelchair access6:46 PM6:54 PM7:00 PM7:03 PM7:08 PM7:16 PM7:20 PM7:26 PM
+

wheelchair_icon- Wheelchair Accessible Bus

+
+ + + + + --- /dev/null +++ b/maxious-canberra-transit-feed/source-html/Route_729.html @@ -1,1 +1,100 @@ + + + + + + + + +Route_729 + + + + + +
+

Chosen services: 729

+ +

View timetable and map

+

This timetable is effective from Monday 25 May 2009.

+ +

Weekdays

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
 Cooleman CourtRivettDuffyHolderCitywestCity InterchangeElectricity House
7297:09 AM7:15 AM7:24 AM7:28 AM7:49 AM7:53 AM7:55 AM
7297:39 AM7:45 AM7:54 AM7:58 AM8:19 AM8:23 AM8:25 AM
+

 

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + +
 CitywestCity Interchange
+ Platform 10
HolderDuffyRivettCooleman Court
7294:45 PM4:51 PM5:13 PM5:18 PM5:26 PM5:32 PM
7295:15 PM5:21 PM5:43 PM5:48 PM5:56 PM6:02 PM
+
+ + + + + --- /dev/null +++ b/maxious-canberra-transit-feed/source-html/Route_73.html @@ -1,1 +1,118 @@ + + + + + + + + +Route_73 + + + + + +
+

Chosen services: 73

+ +

View timetable and map

+

This timetable is effective from Monday 20 July 2009.

+ +

Weekdays

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
 Cameron Ave Bus Station
+ Platform 5
Lathlain St Station
+ Platform 5
Cohen St Bus Station
+ Platform 5
Florey ShopsPage ShopsHawkerCookJamison CentreCalvary HospitalCameron Ave Bus StationLathlain St StationCohen St Bus Station
739:17 AM9:19 AM9:23 AM9:28 AM9:34 AM9:38 AM9:44 AM9:47 AM9:54 AM10:02 AM10:04 AM10:08 AM
7310:47 AM10:49 AM10:53 AM10:58 AM11:04 AM11:08 AM11:14 AM11:17 AM11:24 AM11:32 AM11:34 AM11:38 AM
73 wheelchair access12:17 PM12:19 PM12:23 PM12:28 PM12:34 PM12:38 PM12:44 PM12:47 PM12:54 PM1:02 PM1:04 PM1:08 PM
731:47 PM1:49 PM1:53 PM1:58 PM2:04 PM2:08 PM2:14 PM2:17 PM2:24 PM2:32 PM2:34 PM2:38 PM
+

wheelchair_icon- Wheelchair Accessible Bus

+
+ + + + + --- /dev/null +++ b/maxious-canberra-transit-feed/source-html/Route_732.html @@ -1,1 +1,105 @@ + + + + + + + + +Route_732 + + + + + +
+

Chosen services: 732

+ +

View timetable and map

+

This timetable is effective from Monday 25 May 2009.

+ +

Weekdays

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
 Woden Interchange
+ Platform 4
CurtinCitywestCity InterchangeElectricity House
7327:15 AM7:24 AM7:38 AM7:42 AM7:44 AM
7327:48 AM8:03 AM8:14 AM8:19 AM8:21 AM
7328:18 AM8:27 AM8:41 AM8:45 AM8:47 AM
+

 

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
 CitywestCity Interchange
+ Platform 10
CurtinWoden Interchange
7324:35 PM4:41 PM4:53 PM5:03 PM
7325:05 PM5:11 PM5:23 PM5:33 PM
732 wheelchair access5:35 PM5:41 PM5:53 PM6:03 PM
+

wheelchair_icon- Wheelchair Accessible Bus

+
+ + + + + --- /dev/null +++ b/maxious-canberra-transit-feed/source-html/Route_737.html @@ -1,1 +1,135 @@ + + + + + + + + +Route_737 + + + + + +
+

Chosen services: 737

+ +

View timetable and map

+

This timetable is effective from Monday 20 July 2009.

+ +

Weekdays

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
 City Interchange
+ Platform 7
Russell OfficesBrindabella Business ParkFairbairn Park
7376:43 AM6:52 AM7:10 AM.....
7376:58 AM7:07 AM7:25 AM.....
7377:18 AM7:27 AM7:45 AM.....
7377:38 AM7:47 AM8:05 AM.....
7377:58 AM8:07 AM8:25 AM8:37 AM
7378:18 AM8:27 AM8:45 AM8:57 AM
+

 

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
 Fairbairn ParkBrindabella Business ParkRussell OfficesCity Interchange
7374:31 PM4:41 PM4:55 PM5:07 PM
7374:45 PM4:53 PM5:12 PM5:27 PM
7375:05 PM5:13 PM5:32 PM5:47 PM
7375:25 PM5:33 PM5:52 PM6:07 PM
737 wheelchair access5:45 PM5:53 PM6:12 PM6:27 PM
+

wheelchair_icon- Wheelchair Accessible Bus

+
+ + + + + --- /dev/null +++ b/maxious-canberra-transit-feed/source-html/Route_74.html @@ -1,1 +1,118 @@ + + + + + + + + +Route_74 + + + + + +
+

Chosen services: 74

+ +

View timetable and map

+

This timetable is effective from Monday 20 July 2009.

+ +

Weekdays

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
 Cohen St Bus Station
+ Platform 1
Lathlain St
+ Platform 3
Cameron Ave Bus Station
+ Platform 3
Calvary HospitalJamison CentreCookHawkerPage ShopsFlorey ShopsCohen St Bus StationLathlain St Bus Station Cameron Ave Bus Station
749:54 AM9:56 AM10:00 AM10:06 AM10:13 AM10:16 AM10:23 AM10:27 AM10:33 AM10:39 AM10:41 AM10:45 AM
7411:24 AM11:26 AM11:30 AM11:36 AM11:43 AM11:46 AM11:53 AM11:57 PM12:03 PM12:09 PM12:11 PM12:15 PM
74wheelchair access12:54 PM12:56 PM1:00 PM1:06 PM1:13 PM1:16 PM1:23 PM1:27 PM1:33 PM1:39 PM1:41 PM1:45 PM
74wheelchair access2:24 PM2:26 PM2:30 PM2:36 PM2:43 PM2:46 PM2:53 PM2:57 PM3:03 PM3:09 PM3:11 PM3:15 PM
+

wheelchair_icon- Wheelchair Accessible Bus

+
+ + + + + --- /dev/null +++ b/maxious-canberra-transit-feed/source-html/Route_749.html @@ -1,1 +1,116 @@ + + + + + + + + +Route_749 + + + + + +
+

Chosen services: 749

+ +

View timetable and map

+

This timetable is effective from Monday 25 May 2009.

+ +

Weekdays

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
 

Cohen St Bus Station
+ Platform 2

Lathlain St Platform 2 Cameron Ave Bus Station
+ Platform 2
Woden Interchange
7497:02 AM7:04 AM7:08 AM7:30AM
7497:37 AM7:39 AM7:43 AM8:10 AM
7498:07 AM8:09 AM8:13 AM8:40 AM
749 wheelchair access4:59 PM5:01 PM5:05 PM5:35 PM
+

 

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
 Woden Interchange
+ Platform 4
Cameron Ave Bus StationLathlain St Bus StationCohen St Bus Station
749 wheelchair access7:53 AM8:17 AM8:19 AM8:23 AM
7494:36 PM5:02 PM5:04 PM5:08 PM
7495:10 PM5:36 PM5:38 PM5:42 PM
7495:40 PM6:06 PM6:08 PM6:12 PM
+

wheelchair_icon- Wheelchair Accessible Bus

+
+ + + + + --- /dev/null +++ b/maxious-canberra-transit-feed/source-html/Route_75.html @@ -1,1 +1,86 @@ + + + + + + + + +Route_75 + + + + + +
+

Chosen services: 75

+ +

View timetable and map

+

This timetable is effective from Monday 25 May 2009.

+ +

Weekdays

+ + + + + + + + + + + + + + + + + + + +
 Woden Interchange
+ Platform 2
Stromlo High WaramangaCooleman Court
75wheelchair access 10:55 AM11:08 AM11:17 AM
7512:55 PM1:08 PM1:17 PM
+

 

+ + + + + + + + + + + + + + + + + + + + + + + + + +
 Cooleman CourtStromlo High WaramangaWoden Interchange
759:25 AM9:34 AM9:47 AM
7511:25 AM11:34 AM11:47 AM
751:25 PM1:34 PM1:47 PM
+

wheelchair_icon- Wheelchair Accessible Bus

+
+ + + + + --- /dev/null +++ b/maxious-canberra-transit-feed/source-html/Route_757.html @@ -1,1 +1,98 @@ + + + + + + + + +Route_757 + + + + + +
+

Chosen services: 757

+ +

View timetable and map

+

This timetable is effective from Monday 25 May 2009.

+ +

Weekdays

+ + + + + + + + + + + + + + + + + + + + + + + + + +
 Gungahlin Market PlaceDickson CollegeRussell OfficesBrindabella Business ParkFairbairn Park
7576:45 AM6:55 AM7:06 AM7:22 AM7:35 AM
7577:05 AM7:15 AM7:26 AM7:42 AM7:55 AM
+

 

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
 Fairbairn ParkBrindabella Business ParkRussell OfficesDickson CollegeGungahlin Market Place
7574:33 PM4:42 PM4:57 PM5:10 PM5:24 PM
7575:08 PM5:22 PM5:32 PM5:43 PM5:56 PM
7575:38 PM5:52 PM6:02 PM6:13 PM6:26 PM
+
+ + + + + --- /dev/null +++ b/maxious-canberra-transit-feed/source-html/Route_76.html @@ -1,1 +1,73 @@ + + + + + + + + +Route 76 + + + + + +
+

Chosen services: 76

+ +

View timetable and map

+

This timetable is effective from Monday 25 May 2009.

+ +

Weekdays

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
 Woden Interchange
+ Platform 2
Brindabella Gardens Nursing HomeSaint Andrews Village HughesCanberra HospitalWoden Interchange
76 wheelchair access10:00 AM10:07 AM10:15 AM10:20 AM10:28 AM
76 wheelchair access12:00 PM12:07 PM12:15 PM12:20 PM12:28 PM
76 wheelchair access2:00 PM2:07 PM2:15 PM2:20 PM2:28 PM
+

wheelchair_icon- Wheelchair Accessible Bus

+
+ + + + + --- /dev/null +++ b/maxious-canberra-transit-feed/source-html/Route_768.html @@ -1,1 +1,100 @@ + + + + + + + + +Route_768 + + + + + +
+

Chosen services: 768

+ +

View timetable and map

+

This timetable is effective from Monday 25 May 2009.

+ +

Weekdays

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + +
 Calwell ShopsIsabella ShopsChisholm ShopsRussell OfficesCity Interchange
+ Platform 11
Citywest
7687:07 AM7:15 AM7:26 AM7:51 AM8:00 AM8:04 AM
768 wheelchair access7:37 AM7:45 AM7:56 AM8:21 AM8:30 AM8:34 AM
+

 

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
 CitywestCity Interchange
+ Platform 10
Russell OfficesChisholm ShopsIsabella ShopsCalwell Shops
7684:47 PM4:53 PM5:02 PM5:26 PM5:37 PM5:45 PM
7685:19 PM5:25 PM5:34 PM5:58 PM6:09 PM6:17 PM
+

wheelchair_icon- Wheelchair Accessible Bus

+
+ + + + + --- /dev/null +++ b/maxious-canberra-transit-feed/source-html/Route_769.html @@ -1,1 +1,126 @@ + + + + + + + + +Route_769 + + + + + +
+

Chosen services: 769

+ +

View timetable and map

+

This timetable is effective from Monday 25 May 2009.

+ +

Weekdays

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
 Tharwa DriveTheodoreCalwell ShopsChisholm ShopsRussell OfficesCity Interchange
+ Platform 11
Citywest
769 wheelchair access6:41 AM6:46 AM6:56 AM7:06 AM7:33 AM7:43 AM7:47 AM
7697:21 AM7:26 AM7:36 AM7:46 AM8:13 AM8:23 AM8:27 AM
7697:41 AM7:46 AM7:56 AM8:06 AM8:33 AM8:43 AM8:47 AM
+

 

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
 CitywestCity Interchange
+ Platform 10
Russell OfficesChisholm ShopsCalwell ShopsTheodoreTharwa Drive
7694:27 PM4:33 PM4:42 PM5:07 PM5:17 PM5:27 PM5:32 PM
7695:00 PM5:06 PM5:15 PM5:40 PM5:50 PM6:00 PM6:05 PM
7695:37 PM5:43 PM5:52 PM6:17 PM6:27 PM6:37 PM6:42 PM
+

wheelchair_icon- Wheelchair Accessible Bus

+
+ + + + + --- /dev/null +++ b/maxious-canberra-transit-feed/source-html/Route_77.html @@ -1,1 +1,65 @@ + + + + + + + + +Route_77 + + + + + +
+

Chosen services: 77

+ +

View timetable and map

+

This timetable is effective from Monday 25 May 2009.

+ +

Weekdays

+ + + + + + + + + + + + + + + + + + + + + + + + + +
 Woden Interchange
+ Platform 2
Canberra HospitalSaint Andrews Village HughesBrindabella Gardens Nursing HomeWoden Interchange
77 wheelchair access11:00 AM11:08 AM11:13 AM11:21 AM11:28 AM
771:00 PM1:08 PM1:13 PM1:21 PM1:28 PM
+

wheelchair_icon- Wheelchair Accessible Bus

+
+ + + + + --- /dev/null +++ b/maxious-canberra-transit-feed/source-html/Route_780.html @@ -1,1 +1,80 @@ + + + + + + + + +Route_780 + + + + + +
+

Chosen services: 780

+ +

View timetable and map

+

This timetable is effective from Monday 25 May 2009.

+ +

Weekdays

+ + + + + + + + + + + + + + + + + + + +
 Fyshwick TerminusCanberra TimesCity Interchange
780 wheelchair access4:05 PM4:21 PM4:40 PM
7804:35 PM4:51 PM5:10 PM
+

 

+ + + + + + + + + + + + + + + + + + + +
 City
+ Platform 7
Newcastle / Isa Street FyshwickFyshwick Terminus
780 wheelchair access6:48 AM7:07 AM7:23 AM
7807:19 AM7:38 AM7:54 AM
+

wheelchair_icon- Wheelchair Accessible Bus

+
+ + + + + --- /dev/null +++ b/maxious-canberra-transit-feed/source-html/Route_785.html @@ -1,1 +1,99 @@ + + + + + + + + +Route_785 + + + + + +
+

Chosen services: 785

+ +

View timetable and map

+

This timetable is effective from Monday 25 May 2009.

+ +

Weekdays

+

Due to roadwork on Tharwa Drive near Mentone View in Conder, the bus stops located on Tharwa Drive adjacent to the roadwork will not be used by ACTION.

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + +
 Lanyon Market PlaceTharwa/PocketMentone View/Tharwa DriveCitywestCity Interchange
+ Platform 10
Electricity House
7856:52 AM6:55 AM7:13 AM7:43 AM7:47 AM7:49 AM
7857:25 AM7:28 AM7:46 AM8:16 AM8:20 AM8:22 AM
+

 

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + +
 CitywestCity Interchange
+ Platform 10
Electricity HouseMentone View/Tharwa DriveTharwa/PocketLanyon Market Place
7855:05 PM5:11 PM5:13 PM5:49 PM6:05 PM6:07 PM
7855:30 PM5:36 PM5:38 PM6:14 PM6:30 PM6:32 PM
+
+ + + + + --- /dev/null +++ b/maxious-canberra-transit-feed/source-html/Route_786.html @@ -1,1 +1,100 @@ + + + + + + + + +Route_786 + + + + + +
+

Chosen services: 786

+ +

View timetable and map

+

This timetable is effective from Monday 20 July 2009.

+ +

Weekdays

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
 Fairbairn ParkBrindabella Business ParkChisholm ShopsTuggeranong Interchange
7864:45 PM4:53 PM5:24 PM5:37 PM
7865:15 PM5:23 PM5:54 PM6:07 PM
7865:45 PM5:53 PM6:24 PM6:37 PM
+

 

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
 Tuggeranong Interchange
+ Platform 7
Chisholm ShopsBrindabella Business ParkFairbairn Park
786wheelchair access6:46 AM6:56 AM7:30 AM7:42 AM
7867:06 AM7:16 AM7:35 AM7:47 AM
7867:27 AM7:37 AM8:11 AM8:23 AM
+

wheelchair_icon- Wheelchair Accessible Bus

+
+ + + + + --- /dev/null +++ b/maxious-canberra-transit-feed/source-html/Route_787.html @@ -1,1 +1,99 @@ + + + + + + + + +Route_787 + + + + + +
+

Chosen services: 787

+ +

View timetable and map

+

This timetable is effective from Monday 25 May 2009.

+ +

Weekdays

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + +
 CitywestCity Interchange
+ Platform 10
Electricity HouseWoodcock/Clare DennisTharwa/KnokeLanyon Market Place
787 5:16 PM5:22 PM5:24 PM5:56 PM6:07 PM6:09 PM
787 wheelchair access5:35 PM5:41 PM5:43 PM6:15 PM6:26 PM6:28 PM
+

 

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + +
 Lanyon Market PlaceTharwa/KnokeWoodcock/Clare DennisCitywestCity Interchange
+ Platform 10
Electricity House
787 wheelchair access6:47 AM6:50 AM7:02 AM7:28 AM7:32 AM7:34 AM
7877:20 AM7:23 AM7:35 AM8:01 AM8:05 AM8:07 AM
+

wheelchair_icon- Wheelchair Accessible Bus

+
+ + + + + --- /dev/null +++ b/maxious-canberra-transit-feed/source-html/Route_788.html @@ -1,1 +1,108 @@ + + + + + + + + +Route_788 + + + + + +
+

Chosen services: 788

+ +

View timetable and map

+

This timetable is effective from Monday 25 May 2009.

+ +

Weekdays

+

Due to roadwork on Tharwa Drive near Mentone View in Conder, the bus stops located on Tharwa Drive adjacent to the roadwork will not be used by ACTION.

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + +
 Woodcock/Clare DennisTharwa/PocketMentone View/Tharwa DriveRussell OfficesCity Interchange
+ Platform 11
Citywest
7887:10 AM7:19 AM7:34 AM8:11 AM8:20 AM8:24 AM
7887:40 AM7:49 AM8:04 AM8:41 AM8:50 AM8:54 AM
+

 

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
 CitywestCity Interchange
+ Platform 10
Russell OfficesMentone View/Tharwa DriveTharwa/PocketWoodcock/Clare Dennis
7884:26 PM4:32 PM4:41 PM5:12 PM5:26 PM5:36 PM
7885:02 PM5:07 PM5:18 PM5:52 PM6:06 PM6:15 PM
7885:32 PM5:38 PM5:47 PM6:18 PM6:32 PM6:42 PM
+
+ + + + + --- /dev/null +++ b/maxious-canberra-transit-feed/source-html/Route_8.html @@ -1,1 +1,402 @@ + + + + + + + + +Route 8 + + + + + + + +
+

Chosen services: 8

+ +

View timetable and map

+ This timetable is effective from Monday 25 May 2009. +

Weekdays

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
 Dickson Cowper StLynehamMacarthur\Miller O'ConnorCity Interchange
86:26 AM6:32 AM6:37 AM6:44 AM
86:57 AM7:03 AM7:08 AM7:15 AM
87:24 AM7:30 AM7:37 AM7:46 AM
87:57 AM8:04 AM8:11 AM8:20 AM
88:31 AM8:38 AM8:45 AM8:54 AM
8wheelchair access8:59 AM9:06 AM9:13 AM9:22 AM
810:04 AM10:10 AM10:15 AM10:22 AM
811:04 AM11:10 AM11:15 AM11:22 AM
8wheelchair access12:04 PM12:10 PM12:15 PM12:22 PM
8wheelchair access1:04 PM1:10 PM1:15 PM1:22 PM
81:59 PM2:05 PM2:10 PM2:17 PM
83:02 PM3:09 PM3:16 PM3:25 PM
83:32 PM3:39 PM3:46 PM3:55 PM
84:07 PM4:14 PM4:21 PM4:30 PM
84:37 PM4:44 PM4:51 PM5:00 PM
85:07 PM5:14 PM5:21 PM5:30 PM
85:37 PM5:44 PM5:51 PM6:00 PM
86:42 PM6:48 PM6:53 PM6:58 PM
87:42 PM7:48 PM7:53 PM7:58 PM
88:42 PM8:48 PM8:53 PM8:58 PM
89:42 PM9:48 PM9:53 PM9:58 PM
810:42 PM10:48 PM10:53 PM10:58 PM
+

 

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
 City Interchange
+ Platform 4
Macarthur\Miller O'ConnorLynehamDickson Cowper St
86:55 AM7:02 AM7:07 AM7:13 AM
87:14 AM7:21 AM7:26 AM7:32 AM
87:41 AM7:50 AM7:57 AM8:04 AM
88:11 AM8:20 AM8:27 AM8:34 AM
88:41 AM8:50 AM8:57 AM9:04 AM
89:15 AM9:24 AM9:31 AM9:37 AM
89:47 AM9:54 AM9:59 AM10:05 AM
8wheelchair access10:18 AM10:25 AM10:30 AM10:36 AM
810:46 AM10:53 AM10:58 AM11:04 AM
811:46 AM11:53 AM11:58 AM12:04 PM
812:46 PM12:53 PM12:58 PM1:04 PM
81:46 PM1:53 PM1:58 PM2:04 PM
82:46 PM2:53 PM2:58 PM3:05 PM
83:11 PM3:20 PM3:27 PM3:34 PM
83:46 PM3:55 PM4:02 PM4:09 PM
84:11 PM4:20 PM4:27 PM4:34 PM
84:44 PM4:53 PM5:00 PM5:07 PM
85:23 PM5:32 PM5:39 PM5:46 PM
85:53 PM6:02 PM6:09 PM6:16 PM
86:23 PM6:31 PM6:36 PM6:42 PM
86:50 PM6:55 PM7:00 PM7:06 PM
87:05 PM7:10 PM7:15 PM7:21 PM
8wheelchair access8:05 PM8:10 PM8:15 PM8:21 PM
89:05 PM9:10 PM9:15 PM9:21 PM
810:05 PM10:10 PM10:15 PM10:21 PM
811:05 PM11:10 PM11:15 PM11:21 PM
+

wheelchair_icon- Wheelchair Accessible Bus

+
+ + + + + --- /dev/null +++ b/maxious-canberra-transit-feed/source-html/Route_80.html @@ -1,1 +1,708 @@ + + + + + + + + +Route_80 + + + + + +
+

Chosen services: 80

+ +

View timetable and map

+

This timetable is effective from Monday 25 May 2009.

+ +

Weekdays

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
 City Interchange
+ Platform 7
Russell OfficesKings Ave/National CrtCausewayRailway Station KingstonNewcastle Street after Isa StFyshwick Direct Factory OutletEye HospitalGeoscience AustraliaWoden Interchange
805:50 AM5:58 AM6:02 AM6:06 AM6:09 AM6:17 AM6:26 AM6:31 AM6:40 AM6:56 AM
806:17 AM6:25 AM6:29 AM6:33 AM6:36 AM6:44 AM6:53 AM6:58 AM7:07 AM7:23 AM
806:48 AM6:56 AM7:00 AM7:04 AM7:07 AM7:15 AM7:24 AM7:29 AM7:37 AM7:53 AM
807:19 AM7:27 AM7:31 AM7:38 AM7:41 AM7:50 AM8:04 AM8:10 AM8:18 AM8:34 AM
807:51 AM8:00 AM8:03 AM8:10 AM8:13 AM8:22 AM8:36 AM8:42 AM8:50 AM9:06 AM
80 wheelchair access8:28 AM8:37 AM8:40 AM8:47 AM8:50 AM8:59 AM9:13 AM9:19 AM9:27 AM9:44 AM
808:59 AM9:07 AM9:11 AM9:15 AM9:18 AM9:30 AM9:39 AM9:44 AM9:52 AM10:10 AM
809:27 AM9:35 AM9:39 AM9:43 AM9:46 AM9:54 AM10:03 AM10:08 AM10:16 AM10:34 AM
80 wheelchair access10:27 AM10:35 AM10:39 AM10:43 AM10:46 AM10:54 AM11:03 AM11:08 AM11:16 AM11:34 AM
8011:27 AM11:35 AM11:39 AM11:43 AM11:46 AM11:54 AM12:03 PM12:08 PM12:16 PM12:34 PM
8012:27 PM12:35 PM12:39 PM12:43 PM12:46 PM12:54 PM1:03 PM1:08 PM1:16 PM1:34 PM
801:27 PM1:35 PM1:39 PM1:43 PM1:46 PM1:54 PM2:03 PM2:08 PM2:16 PM2:34 PM
802:27 PM2:35 PM2:39 PM2:43 PM2:46 PM2:54 PM3:03 PM3:08 PM3:17 PM3:33 PM
803:30 PM3:39 PM3:44 PM3:49 PM3:52 PM4:00 PM4:10 PM4:16 PM4:26 PM4:44 PM
804:00 PM4:09 PM4:14 PM4:19 PM4:22 PM4:30 PM4:40 PM4:46 PM4:56 PM5:14 PM
804:34 PM4:43 PM4:48 PM4:53 PM4:56 PM5:04 PM5:14 PM5:20 PM5:30 PM5:48 PM
805:04 PM5:13 PM5:18 PM5:23 PM5:26 PM5:34 PM5:44 PM5:50 PM6:00 PM6:18 PM
80 wheelchair access5:34 PM5:43 PM5:48 PM5:53 PM5:56 PM6:04 PM6:14 PM6:20 PM6:30 PM6:45 PM
806:04 PM6:13 PM6:18 PM6:23 PM6:26 PM6:33 PM6:41 PM6:46 PM6:54 PM7:09 PM
807:04 PM7:12 PM7:16 PM7:20 PM7:22 PM.........................
808:04 PM8:12 PM8:16 PM8:20 PM8:22 PM.........................
809:04 PM9:12 PM9:16 PM9:20 PM9:22 PM.........................
8010:04 PM10:12 PM10:16 PM10:20 PM10:22 PM.........................
8011:04 PM11:12 PM11:16 PM11:20 PM11:22 PM.........................
+

 

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
 Woden Interchange
+ Platform 4
Geoscience AustraliaEye HospitalFyshwick Direct Factory OutletCanberra TimesRailway Station KingstonCausewayKings Ave/National CrtRussell OfficesCity Interchange
805:47 AM6:02 AM6:11 AM6:16 AM6:25 AM6:32 AM6:34 AM6:38 AM6:42 AM6:50 AM
806:06 AM6:21 AM6:30 AM6:35 AM6:44 AM6:51 AM6:53 AM6:57 AM7:01 AM7:09 AM
80 wheelchair access6:33 AM6:48 AM6:57 AM7:02 AM7:11 AM7:18 AM7:20 AM7:24 AM7:28 AM7:38 AM
807:00 AM7:15 AM7:24 AM7:29 AM7:40 AM7:48 AM7:52 AM7:59 AM8:03 AM8:14 AM
807:31 AM7:47 AM7:56 AM8:03 AM8:14 AM8:22 AM8:26 AM8:33 AM8:37 AM8:48 AM
808:01 AM8:17 AM8:26 AM8:33 AM8:44 AM8:52 AM8:56 AM9:03 AM9:07 AM9:18 AM
808:34 AM8:50 AM8:59 AM9:06 AM9:17 AM9:25 AM9:29 AM9:33 AM9:37 AM9:45 AM
80 wheelchair access9:08 AM9:23 AM9:33 AM9:38 AM9:47 AM9:54 AM9:56 AM10:00 AM10:04 AM10:12 AM
809:38 AM9:53 AM10:02 AM10:07 AM10:16 AM10:23 AM10:25 AM10:29 AM10:33 AM10:41 AM
8010:38 AM10:53 AM11:02 AM11:07 AM11:16 AM11:23 AM11:25 AM11:29 AM11:33 AM11:41 AM
80.........................11:31 AM11:33 AM11:37 AM11:41 AM11:49 AM
80 wheelchair access11:38 AM11:53 AM12:02 PM12:07 PM12:16 PM12:23 PM12:25 PM12:29 PM12:33 PM12:41 PM
80 wheelchair access12:38 PM12:53 PM1:02 PM1:07 PM1:16 PM1:23 PM1:25 PM1:29 PM1:33 PM1:41 PM
801:38 PM1:53 PM2:02 PM2:07 PM2:16 PM2:23 PM2:25 PM2:29 PM2:33 PM2:41 PM
802:38 PM2:53 PM3:02 PM3:07 PM3:16 PM3:23 PM3:25 PM3:29 PM3:33 PM3:41 PM
803:38 PM3:54 PM4:04 PM4:10 PM4:20 PM4:27 PM4:29 PM4:34 PM4:39 PM4:48 PM
804:08 PM4:24 PM4:34 PM4:40 PM4:50 PM4:57 PM4:59 PM5:04 PM5:09 PM5:18 PM
80 wheelchair access4:38 PM4:54 PM5:04 PM5:10 PM5:20 PM5:27 PM5:29 PM5:34 PM5:39 PM5:48 PM
805:08 PM5:24 PM5:34 PM5:40 PM5:50 PM5:57 PM5:59 PM6:04 PM6:09 PM6:18 PM
805:38 PM5:54 PM6:04 PM6:10 PM6:20 PM6:27 PM6:29 PM6:33 PM6:37 PM6:45 PM
805:56 PM6:12 PM6:22 PM6:28 PM6:36 PM6:42 PM6:44 PM6:48 PM6:52 PM7:00 PM
80.........................7:42 PM7:44 PM7:48 PM7:52 PM8:00 PM
80.........................8:42 PM8:44 PM8:48 PM8:52 PM9:00 PM
80.........................9:42 PM9:44 PM9:48 PM9:52 PM10:00 PM
80.........................10:42 PM10:44 PM10:48 PM10:52 PM11:00 PM
+

wheelchair_icon- Wheelchair Accessible Bus

+
+ + + + + --- /dev/null +++ b/maxious-canberra-transit-feed/source-html/Route_81.html @@ -1,1 +1,112 @@ + + + + + + + + +Route_81 + + + + + +
+

Chosen services: 81

+ +

View timetable and map

+ +

Weekdays - School Holidays Only

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
 City Interchange
+ Platform 9
National AquariumBlack Mountain Telstra TowerBotanic GardensCity Interchange
819:20 AM9:34 AM9:42 AM9:48 AM9:55 AM
8110:20 AM10:34 AM10:42 AM10:48 AM10:55 AM
8111:20 AM11:34 AM11:42 AM11:48 AM11:55 AM
8112:20 PM12:34 PM12:42 PM12:48 PM12:55 PM
811:20 PM1:34 PM1:42 PM1:48 PM1:55 PM
812:20 PM2:34 PM2:42 PM2:48 PM2:55 PM
813:20 PM3:34 PM3:42 PM3:48 PM3:55 PM
814:20 PM4:34 PM4:42 PM4:48 PM4:55 PM
+

 

+
+ + + + + --- /dev/null +++ b/maxious-canberra-transit-feed/source-html/Route_82.html @@ -1,1 +1,78 @@ + + + + + + + + +Route_82 + + + + + +
+

Chosen services: 82

+ +

View timetable and map

+

This timetable is effective from Monday 25 May 2009.

+ +

Weekdays

+ + + + + + + + + + + + + + + +
 Bimberi CentreNorthbourne Ave/Antill StMacarthur\NorthbourneCity Interchange
827:15 PM7:24 PM7:26 PM7:33 PM
+

 

+ + + + + + + + + + + + + + + + + + + + + + +
 City Interchange
+ Platform 8
Macarthur\NorthbourneNorthbourne Ave/Antill StBimberi Centre
826:32 AM6:38 AM6:40 AM6:50 AM
823:42 PM3:48 PM3:50 PM4:00 PM
+
+ + + + + --- /dev/null +++ b/maxious-canberra-transit-feed/source-html/Route_88.html @@ -1,1 +1,84 @@ + + + + + + + + +Route_88 + + + + + +
+

Chosen services: 88

+ +

View timetable and map

+

This timetable is effective from Monday 25 May 2009.

+ +

Weekdays

+ + + + + + + + + + + + + + + + + + + + + +
 Woden Interchange
+ Platform 4
Alexander Maconochie Centre Hume
888:26 AM8:46 AM
8812:40 PM1:00 PM
885:10 PM5:30 PM
+

 

+ + + + + + + + + + + + + + + + + + + + + +
 Alexander Maconochie Centre HumeWoden Interchange
88 wheelchair access11:50 AM12:10 PM
884:50 PM5:10 PM
888:05 PM8:25 PM
+

wheelchair_icon- Wheelchair Accessible Bus

+
+ + + + + --- /dev/null +++ b/maxious-canberra-transit-feed/source-html/Route_9.html @@ -1,1 +1,439 @@ + + + + + + + + +Route 9 + + + + + + + +
+

Chosen services: 9

+ +

View timetable and map

+ This timetable is effective from Monday 25 May 2009. +

Weekdays

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
 City Interchange
+ Platform 7
St Thomas More CampbellRussell OfficesHospice, Menindee Drive ADFACampbell Park Offices
97:14 AM7:26 AM7:31 AM7:33 AM7:41 AM7:45 AM
98:14 AM8:29 AM8:34 AM8:36 AM8:44 AM8:48 AM
98:57 AM9:11 AM9:16 AM9:18 AM9:26 AM9:31 AM
99:57 AM10:11 AM10:16 AM10:18 AM10:26 AM10:29 AM
910:57 AM11:11 AM11:16 AM11:18 AM11:26 AM11:29 AM
911:57 AM12:11 PM12:16 PM12:18 PM12:26 PM12:29 PM
912:57 PM1:11 PM1:16 PM1:18 PM1:26 PM1:29 PM
91:57 PM2:11 PM2:16 PM2:18 PM2:26 PM2:29 PM
92:57 PM3:12 PM3:17 PM3:19 PM3:27 PM3:31 PM
93:44 PM3:59 PM4:04 PM4:06 PM4:14 PM4:18 PM
94:14 PM4:29 PM4:34 PM4:36 PM4:44 PM4:48 PM
94:44 PM4:59 PM5:04 PM5:06 PM5:14 PM5:18 PM
95:14 PM5:29 PM5:34 PM5:36 PM5:44 PM5:48 PM
95:57 PM6:12 PM6:17 PM6:19 PM6:27 PM6:31 PM
96:57 PM7:08 PM7:12 PM7:14 PM7:20 PM7:23 PM
97:57 PM8:08 PM8:12 PM8:14 PM8:20 PM8:23 PM
98:57 PM9:08 PM9:12 PM9:14 PM9:20 PM9:23 PM
99:57 PM10:08 PM10:12 PM10:14 PM10:20 PM10:23 PM
910:57 PM11:08 PM11:12 PM11:14 PM11:20 PM11:23 PM
+

 

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
 Campbell Park OfficesADFAMenindee Drive - HospiceRussell OfficesSt Thomas More CampbellCity Interchange
9.....6:55 AM7:01 AM7:03 AM7:08 AM7:20 AM
97:20 AM7:23 AM7:29 AM7:31 AM7:36 AM7:51 AM
97:52 AM7:56 AM8:04 AM8:06 AM8:11 AM8:26 AM
98:22 AM8:26 AM8:34 AM8:36 AM8:41 AM8:56 AM
98:52 AM8:56 AM9:04 AM9:06 AM9:11 AM9:26 AM
99:34 AM9:37 AM9:45 AM9:47 AM9:52 AM10:06 AM
910:34 AM10:37 AM10:45 AM10:47 AM10:52 AM11:06 AM
911:34 AM11:37 AM11:45 AM11:47 AM11:52 AM12:06 PM
912:34 PM12:37 PM12:45 PM12:47 PM12:52 PM1:06 PM
91:34 PM1:37 PM1:45 PM1:47 PM1:52 PM2:06 PM
92:34 PM2:37 PM2:45 PM2:47 PM2:52 PM3:06 PM
93:35 PM3:39 PM3:47 PM3:49 PM3:54 PM4:09 PM
9wheelchair access3:52 PM3:56 PM4:04 PM4:06 PM4:11 PM4:26 PM
9wheelchair access4:22 PM4:26 PM4:34 PM4:36 PM4:41 PM4:56 PM
94:52 PM4:56 PM5:04 PM5:06 PM5:11 PM5:26 PM
95:22 PM5:26 PM5:34 PM5:36 PM5:41 PM5:56 PM
95:52 PM5:56 PM6:04 PM6:06 PM6:11 PM6:26 PM
96:28 PM6:32 PM6:38 PM6:40 PM6:45 PM6:56 PM
97:28 PM7:31 PM7:37 PM7:39 PM7:44 PM7:55 PM
98:28 PM8:31 PM8:37 PM8:39 PM8:44 PM8:55 PM
99:28 PM9:31 PM9:37 PM9:39 PM9:44 PM9:55 PM
910:28 PM10:31 PM10:37 PM10:39 PM10:44 PM10:55 PM
+

wheelchair_icon- Wheelchair Accessible Bus

+
+ + + + + --- /dev/null +++ b/maxious-canberra-transit-feed/source-html/Route_900wkend.html @@ -1,1 +1,2628 @@ + + + + + + + + +Route_900 + + + + + + + + +
+

Chosen services: 900

+ +

View timetable and map

+

This timetable is effective from Monday 25 May 2009.

+ + +

Saturdays

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
 Cohen St Bus Station
+ Platform 2
Lathlain St Bus Station
+ Platform 1
Cameron Ave Bus Station
+ Platform 1
City Interchange
+ Platform 1
Woden Interchange
+ Platform 6
Erindale CentreTuggeranong Interchange
900 wheelchair access6:34 AM6:36 AM6:40 AM6:57 AM7:14 AM7:29 AM7:35 AM
900 wheelchair access6:49 AM6:51 AM6:55 AM7:12 AM7:29 AM7:44 AM7:50 AM
900 wheelchair access7:04 AM7:06 AM7:10 AM7:27 AM7:44 AM7:59 AM8:05 AM
900 wheelchair access7:19 AM7:21 AM7:25 AM7:42 AM7:59 AM8:14 AM8:20 AM
900 wheelchair access7:34 AM7:36 AM7:40 AM7:57 AM8:14 AM8:29 AM8:35 AM
900 wheelchair access7:49 AM7:51 AM7:55 AM8:12 AM8:29 AM8:44 AM8:50 AM
900 wheelchair access8:04 AM8:06 AM8:10 AM8:27 AM8:44 AM8:59 AM9:05 AM
900 wheelchair access8:19 AM8:21 AM8:25 AM8:42 AM8:59 AM9:14 AM9:20 AM
900 wheelchair access8:34 AM8:36 AM8:40 AM8:57 AM9:14 AM9:29 AM9:35 AM
900 wheelchair access8:49 AM8:51 AM8:55 AM9:12 AM9:29 AM9:44 AM9:50 AM
900 wheelchair access9:04 AM9:06 AM9:10 AM9:27 AM9:44 AM9:59 AM10:05 AM
900 wheelchair access9:19 AM9:21 AM9:25 AM9:42 AM9:59 AM10:14 AM10:20 AM
900 wheelchair access9:34 AM9:36 AM9:40 AM9:57 AM10:14 AM10:29 AM10:35 AM
900 wheelchair access9:49 AM9:51 AM9:55 AM10:12 AM10:29 AM10:44 AM10:50 AM
900 wheelchair access10:04 AM10:06 AM10:10 AM10:27 AM10:44 AM10:59 AM11:05 AM
900 wheelchair access10:19 AM10:21 AM10:25 AM10:42 AM10:59 AM11:14 AM11:20 AM
900 wheelchair access10:34 AM10:36 AM10:40 AM10:57 AM11:14 AM11:29 AM11:35 AM
900 wheelchair access10:49 AM10:51 AM10:55 AM11:12 AM11:29 AM11:44 AM11:50 AM
900 wheelchair access10:56 AM10:58 AM11:02 AM11:19 AM...............
900 wheelchair access11:04 AM11:06 AM11:10 AM11:27 AM11:44 AM11:59 AM12:05 PM
900 wheelchair access11:19 AM11:21 AM11:25 AM11:42 AM11:59 AM12:14 PM12:20 PM
900 wheelchair access11:26 AM11:28 AM11:32 AM11:49 AM...............
900 wheelchair access11:34 AM11:36 AM11:40 AM11:57 AM12:14 PM12:29 PM12:35 PM
900 wheelchair access11:49 AM11:51 AM11:55 AM12:12 PM12:29 PM12:44 PM12:50 PM
900 wheelchair access11:56 AM11:58 AM12:02 PM12:19 PM...............
900 wheelchair access12:04 PM12:06 PM12:10 PM12:27 PM12:44 PM12:59 PM1:05 PM
900 wheelchair access12:19 PM12:21 PM12:25 PM12:42 PM12:59 PM1:14 PM1:20 PM
900 wheelchair access12:26 PM12:28 PM12:32 PM12:49 PM...............
900 wheelchair access12:34 PM12:36 PM12:40 PM12:57 PM1:14 PM1:29 PM1:35 PM
900 wheelchair access12:49 PM12:51 PM12:55 PM1:12 PM1:29 PM1:44 PM1:50 PM
900 wheelchair access12:56 PM12:58 PM1:02 PM1:19 PM...............
900 wheelchair access1:04 PM1:06 PM1:10 PM1:27 PM1:44 PM1:59 PM2:05 PM
900 wheelchair access1:19 PM1:21 PM1:25 PM1:42 PM1:59 PM2:14 PM2:20 PM
900 wheelchair access1:26 PM1:28 PM1:32 PM1:49 PM...............
900 wheelchair access1:34 PM1:36 PM1:40 PM1:57 PM2:14 PM2:29 PM2:35 PM
900 wheelchair access1:49 PM1:51 PM1:55 PM2:12 PM2:29 PM2:44 PM2:50 PM
900 wheelchair access1:56 PM1:58 PM2:02 PM2:19 PM...............
900 wheelchair access2:04 PM2:06 PM2:10 PM2:27 PM2:44 PM2:59 PM3:05 PM
900 wheelchair access2:19 PM2:21 PM2:25 PM2:42 PM2:59 PM3:14 PM3:20 PM
900 wheelchair access2:26 PM2:28 PM2:32 PM2:49 PM...............
900 wheelchair access2:34 PM2:36 PM2:40 PM2:57 PM3:14 PM3:29 PM3:35 PM
900 wheelchair access2:49 PM2:51 PM2:55 PM3:12 PM3:29 PM3:44 PM3:50 PM
900 wheelchair access2:56 PM2:58 PM3:02 PM3:19 PM...............
900 wheelchair access3:04 PM3:06 PM3:10 PM3:27 PM3:44 PM3:59 PM4:05 PM
900 wheelchair access3:19 PM3:21 PM3:25 PM3:42 PM3:59 PM4:14 PM4:20 PM
900 wheelchair access3:26 PM3:28 PM3:32 PM3:49 PM...............
900 wheelchair access3:34 PM3:36 PM3:40 PM3:57 PM4:14 PM4:29 PM4:35 PM
900 wheelchair access3:49 PM3:51 PM3:55 PM4:12 PM4:29 PM4:44 PM4:50 PM
900 wheelchair access3:56 PM3:58 PM4:02 PM4:19 PM...............
900 wheelchair access4:04 PM4:06 PM4:10 PM4:27 PM4:44 PM4:59 PM5:05 PM
900 wheelchair access4:19 PM4:21 PM4:25 PM4:42 PM4:59 PM5:14 PM5:20 PM
900 wheelchair access4:34 PM4:36 PM4:40 PM4:57 PM5:14 PM5:29 PM5:35 PM
900 wheelchair access4:49 PM4:51 PM4:55 PM5:12 PM5:29 PM5:44 PM5:50 PM
900 wheelchair access5:04 PM5:06 PM5:10 PM5:27 PM5:44 PM5:59 PM6:05 PM
900 wheelchair access5:19 PM5:21 PM5:25 PM5:42 PM5:59 PM6:14 PM6:20 PM
900 wheelchair access5:34 PM5:36 PM5:40 PM5:57 PM6:14 PM6:29 PM6:35 PM
900 wheelchair access5:49 PM5:51 PM5:55 PM6:12 PM6:29 PM6:43 PM6:49 PM
900 wheelchair access6:04 PM6:06 PM6:10 PM6:27 PM6:42 PM6:56 PM7:02 PM
900 wheelchair access6:19 PM6:21 PM6:25 PM6:40 PM6:55 PM7:09 PM7:15 PM
900 wheelchair access6:35 PM6:37 PM6:40 PM6:55 PM7:10 PM7:24 PM7:30 PM
900 wheelchair access6:50 PM6:52 PM6:55 PM7:10 PM7:25 PM7:39 PM7:45 PM
900 wheelchair access7:05 PM7:07 PM7:10 PM7:25 PM7:40 PM7:54 PM8:00 PM
900 wheelchair access7:20 PM7:22 PM7:25 PM7:40 PM7:55 PM8:09 PM8:15 PM
900 wheelchair access7:35 PM7:37 PM7:40 PM7:55 PM8:10 PM8:24 PM8:30 PM
900 wheelchair access7:50 PM7:52 PM7:55 PM8:10 PM8:25 PM8:39 PM8:45 PM
900 wheelchair access8:05 PM8:07 PM8:10 PM8:25 PM8:40 PM8:54 PM9:00 PM
900 wheelchair access8:20 PM8:22 PM8:25 PM8:40 PM8:55 PM9:09 PM9:15 PM
900 wheelchair access8:35 PM8:37 PM8:40 PM8:55 PM9:10 PM9:24 PM9:30 PM
900 wheelchair access8:50 PM8:52 PM8:55 PM9:10 PM9:25 PM9:39 PM9:45 PM
900 wheelchair access9:05 PM9:07 PM9:10 PM9:25 PM9:40 PM9:54 PM10:00 PM
900 wheelchair access9:20 PM9:22 PM9:25 PM9:40 PM9:55 PM10:09 PM10:15 PM
900 wheelchair access9:35 PM9:37 PM9:40 PM9:55 PM10:10 PM10:24 PM10:30 PM
900 wheelchair access9:50 PM9:52 PM9:55 PM10:10 PM10:25 PM10:39 PM10:45 PM
900 wheelchair access10:05 PM10:07 PM10:10 PM10:25 PM10:40 PM10:54 PM11:00 PM
900 wheelchair access10:20 PM10:22 PM10:25 PM10:40 PM10:55 PM11:09 PM11:15 PM
900 wheelchair access10:35 PM10:37 PM10:40 PM10:55 PM11:10 PM11:24 PM11:30 PM
900 wheelchair access10:50 PM10:52 PM10:55 PM11:10 PM11:25 PM11:39 PM11:45 PM
900 wheelchair access11:05 PM11:07 PM11:10 PM11:25 PM11:40 PM11:54 PM12:00 AM
+

 

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
 Tuggeranong Interchange
+ Platform 8
Erindale CentreWoden Interchange
+ Platform 9
City Interchange
+ Platform 5
Cameron Ave Bus StationLathlain St Bus StationCohen St Bus Station
900 wheelchair access6:30 AM6:41 AM6:57 AM7:14 AM7:31 AM7:33 AM7:37 AM
900 wheelchair access6:45 AM6:56 AM7:12 AM7:29 AM7:46 AM7:48 AM7:52 AM
900 wheelchair access7:00 AM7:11 AM7:27 AM7:44 AM8:01 AM8:03 AM8:07 AM
900 wheelchair access7:15 AM7:26 AM7:42 AM7:59 AM8:16 AM8:18 AM8:22 AM
900 wheelchair access7:30 AM7:41 AM7:57 AM8:14 AM8:31 AM8:33 AM8:37 AM
900 wheelchair access7:45 AM7:56 AM8:12 AM8:29 AM8:46 AM8:48 AM8:52 AM
900 wheelchair access8:00 AM8:11 AM8:27 AM8:44 AM9:01 AM9:03 AM9:07 AM
900 wheelchair access8:15 AM8:26 AM8:42 AM8:59 AM9:16 AM9:18 AM9:22 AM
900 wheelchair access8:30 AM8:41 AM8:57 AM9:14 AM9:31 AM9:33 AM9:37 AM
900 wheelchair access8:45 AM8:56 AM9:12 AM9:29 AM9:46 AM9:48 AM9:52 AM
900 wheelchair access9:00 AM9:11 AM9:27 AM9:44 AM10:01 AM10:03 AM10:07 AM
900 wheelchair access9:15 AM9:26 AM9:42 AM9:59 AM10:16 AM10:18 AM10:22 AM
900 wheelchair access9:30 AM9:41 AM9:57 AM10:14 AM10:31 AM10:33 AM10:37 AM
900 wheelchair access9:45 AM9:56 AM10:12 AM10:29 AM10:46 AM10:48 AM10:52 AM
900 wheelchair access10:00 AM10:11 AM10:27 AM10:44 AM11:01 AM11:03 AM11:07 AM
900 wheelchair access10:15 AM10:26 AM10:42 AM10:59 AM11:16 AM11:18 AM11:22 AM
900 wheelchair access10:30 AM10:41 AM10:57 AM11:14 AM11:31 AM11:33 AM11:37 AM
900 wheelchair access10:45 AM10:56 AM11:12 AM11:29 AM11:46 AM11:48 AM11:52 AM
900 wheelchair access11:00 AM11:11 AM11:27 AM11:44 AM12:01 PM12:03 PM12:07 PM
900 wheelchair access11:15 AM11:26 AM11:42 AM11:59 AM12:16 PM12:18 PM12:22 PM
900 wheelchair access..........11:49 AM12:06 PM12:23 PM12:25 PM12:29 PM
900 wheelchair access11:30 AM11:41 AM11:57 AM12:14 PM12:31 PM12:33 PM12:37 PM
900 wheelchair access11:45 AM11:56 AM12:12 PM12:29 PM12:46 PM12:48 PM12:52 PM
900 wheelchair access..........12:19 PM12:36 PM12:53 PM12:55 PM12:59 PM
900 wheelchair access12:00 PM12:11 PM12:27 PM12:44 PM1:01 PM1:03 PM1:07 PM
900 wheelchair access12:15 PM12:26 PM12:42 PM12:59 PM1:16 PM1:18 PM1:22 PM
900 wheelchair access..........12:49 PM1:06 PM1:23 PM1:25 PM1:29 PM
900 wheelchair access12:30 PM12:41 PM12:57 PM1:14 PM1:31 PM1:33 PM1:37 PM
900 wheelchair access12:45 PM12:56 PM1:12 PM1:29 PM1:46 PM1:48 PM1:52 PM
900 wheelchair access..........1:19 PM1:36 PM1:53 PM1:55 PM1:59 PM
900 wheelchair access1:00 PM1:11 PM1:27 PM1:44 PM2:01 PM2:03 PM2:07 PM
900 wheelchair access1:15 PM1:26 PM1:42 PM1:59 PM2:16 PM2:18 PM2:22 PM
900 wheelchair access..........1:49 PM2:06 PM2:23 PM2:25 PM2:29 PM
900 wheelchair access1:30 PM1:41 PM1:57 PM2:14 PM2:31 PM2:33 PM2:37 PM
900 wheelchair access1:45 PM1:56 PM2:12 PM2:29 PM2:46 PM2:48 PM2:52 PM
900 wheelchair access..........2:19 PM2:36 PM2:53 PM2:55 PM2:59 PM
900 wheelchair access2:00 PM2:11 PM2:27 PM2:44 PM3:01 PM3:03 PM3:07 PM
900 wheelchair access2:15 PM2:26 PM2:42 PM2:59 PM3:16 PM3:18 PM3:22 PM
900 wheelchair access..........2:49 PM3:06 PM3:23 PM3:25 PM3:29 PM
900 wheelchair access2:30 PM2:41 PM2:57 PM3:14 PM3:31 PM3:33 PM3:37 PM
900 wheelchair access2:45 PM2:56 PM3:12 PM3:29 PM3:46 PM3:48 PM3:52 PM
900 wheelchair access..........3:19 PM3:36 PM3:53 PM3:55 PM3:59 PM
900 wheelchair access3:00 PM3:11 PM3:27 PM3:44 PM4:01 PM4:03 PM4:07 PM
900 wheelchair access3:15 PM3:26 PM3:42 PM3:59 PM4:16 PM4:18 PM4:22 PM
900 wheelchair access..........3:49 PM4:06 PM4:23 PM4:25 PM4:29 PM
900 wheelchair access3:30 PM3:41 PM3:57 PM4:14 PM4:31 PM4:33 PM4:37 PM
900 wheelchair access3:45 PM3:56 PM4:12 PM4:29 PM4:46 PM4:48 PM4:52 PM
900 wheelchair access..........4:19 PM4:36 PM4:53 PM4:55 PM4:59 PM
900 wheelchair access4:00 PM4:11 PM4:27 PM4:44 PM5:01 PM5:03 PM5:07 PM
900 wheelchair access4:15 PM4:26 PM4:42 PM4:59 PM5:16 PM5:18 PM5:22 PM
900 wheelchair access..........4:49 PM5:06 PM5:23 PM5:25 PM5:29 PM
900 wheelchair access4:30 PM4:41 PM4:57 PM5:14 PM5:31 PM5:33 PM5:37 PM
900 wheelchair access4:45 PM4:56 PM5:12 PM5:29 PM5:46 PM5:48 PM5:52 PM
900 wheelchair access5:00 PM5:11 PM5:27 PM5:44 PM6:01 PM6:03 PM6:07 PM
900 wheelchair access5:15 PM5:26 PM5:42 PM5:59 PM6:16 PM6:18 PM6:22 PM
900 wheelchair access5:30 PM5:41 PM5:57 PM6:14 PM6:31 PM6:33 PM6:36 PM
900 wheelchair access5:45 PM5:56 PM6:12 PM6:29 PM6:46 PM6:48 PM6:51 PM
900 wheelchair access6:00 PM6:11 PM6:27 PM6:42 PM6:59 PM7:01 PM7:04 PM
900 wheelchair access6:15 PM6:26 PM6:41 PM6:56 PM7:13 PM7:15 PM7:18 PM
900 wheelchair access6:30 PM6:40 PM6:55 PM7:10 PM7:27 PM7:29 PM7:32 PM
900 wheelchair access6:45 PM6:55 PM7:10 PM7:25 PM7:42 PM7:44 PM7:47 PM
900 wheelchair access7:00 PM7:10 PM7:25 PM7:40 PM7:57 PM7:59 PM8:02 PM
900 wheelchair access7:15 PM7:25 PM7:40 PM7:55 PM8:12 PM8:14 PM8:17 PM
900 wheelchair access7:30 PM7:40 PM7:55 PM8:10 PM8:27 PM8:29 PM8:32 PM
900 wheelchair access7:45 PM7:55 PM8:10 PM8:25 PM8:42 PM8:44 PM8:47 PM
900 wheelchair access8:00 PM8:10 PM8:25 PM8:40 PM8:57 PM8:59 PM9:02 PM
900 wheelchair access8:15 PM8:25 PM8:40 PM8:55 PM9:12 PM9:14 PM9:17 PM
900 wheelchair access8:30 PM8:40 PM8:55 PM9:10 PM9:27 PM9:29 PM9:32 PM
900 wheelchair access8:45 PM8:55 PM9:10 PM9:25 PM9:42 PM9:44 PM9:47 PM
900 wheelchair access9:00 PM9:10 PM9:25 PM9:40 PM9:57 PM9:59 PM10:02 PM
900 wheelchair access9:15 PM9:25 PM9:40 PM9:55 PM10:12 PM10:14 PM10:17 PM
900 wheelchair access9:30 PM9:40 PM9:55 PM10:10 PM10:27 PM10:29 PM10:32 PM
900 wheelchair access9:45 PM9:55 PM10:10 PM10:25 PM10:42 PM10:44 PM10:47 PM
900 wheelchair access10:00 PM10:10 PM10:25 PM10:40 PM10:57 PM10:59 PM11:02 PM
900 wheelchair access10:15 PM10:25 PM10:40 PM10:55 PM11:12 PM11:14 PM11:17 PM
900 wheelchair access10:30 PM10:40 PM10:55 PM11:10 PM11:27 PM11:29 PM11:32 PM
900 wheelchair access10:45 PM10:55 PM11:10 PM11:25 PM11:42 PM11:44 PM11:47 PM
900 wheelchair access11:00 PM11:10 PM11:25 PM11:40 PM11:57 PM11:59 PM12:02 AM
900 wheelchair access11:15 PM11:25 PM11:40 PM11:55 PM12:12 AM12:14 AM12:17 AM
+ +

^top^

+

Sundays

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
 Cohen St Bus Station
+ Platform 2
Lathlain St Bus Station
+ Platform 1
Cameron Ave Bus Station
+ Platform 1
City Interchange
+ Platform 1
Woden Interchange
+ Platform 6
Erindale CentreTuggeranong Interchange
900 wheelchair access7:34 AM7:36 AM7:40 AM7:57 AM8:14 AM8:29 AM8:35 AM
900 wheelchair access7:49 AM7:51 AM7:55 AM8:12 AM8:29 AM8:44 AM8:50 AM
900 wheelchair access8:04 AM8:06 AM8:10 AM8:27 AM8:44 AM8:59 AM9:05 AM
900 wheelchair access8:19 AM8:21 AM8:25 AM8:42 AM8:59 AM9:14 AM9:20 AM
900 wheelchair access8:34 AM8:36 AM8:40 AM8:57 AM9:14 AM9:29 AM9:35 AM
900 wheelchair access8:49 AM8:51 AM8:55 AM9:12 AM9:29 AM9:44 AM9:50 AM
900 wheelchair access9:04 AM9:06 AM9:10 AM9:27 AM9:44 AM9:59 AM10:05 AM
900 wheelchair access9:19 AM9:21 AM9:25 AM9:42 AM9:59 AM10:14 AM10:20 AM
900 wheelchair access9:34 AM9:36 AM9:40 AM9:57 AM10:14 AM10:29 AM10:35 AM
900 wheelchair access9:49 AM9:51 AM9:55 AM10:12 AM10:29 AM10:44 AM10:50 AM
900 wheelchair access10:04 AM10:06 AM10:10 AM10:27 AM10:44 AM10:59 AM11:05 AM
900 wheelchair access10:19 AM10:21 AM10:25 AM10:42 AM10:59 AM11:14 AM11:20 AM
900 wheelchair access10:34 AM10:36 AM10:40 AM10:57 AM11:14 AM11:29 AM11:35 AM
900 wheelchair access10:49 AM10:51 AM10:55 AM11:12 AM11:29 AM11:44 AM11:50 AM
900 wheelchair access11:04 AM11:06 AM11:10 AM11:27 AM11:44 AM11:59 AM12:05 PM
900 wheelchair access11:19 AM11:21 AM11:25 AM11:42 AM11:59 AM12:14 PM12:20 PM
900 wheelchair access11:34 AM11:36 AM11:40 AM11:57 AM12:14 PM12:29 PM12:35 PM
900 wheelchair access11:49 AM11:51 AM11:55 AM12:12 PM12:29 PM12:44 PM12:50 PM
900 wheelchair access12:04 PM12:06 PM12:10 PM12:27 PM12:44 PM12:59 PM1:05 PM
900 wheelchair access12:19 PM12:21 PM12:25 PM12:42 PM12:59 PM1:14 PM1:20 PM
900 wheelchair access12:34 PM12:36 PM12:40 PM12:57 PM1:14 PM1:29 PM1:35 PM
900 wheelchair access12:49 PM12:51 PM12:55 PM1:12 PM1:29 PM1:44 PM1:50 PM
900 wheelchair access1:04 PM1:06 PM1:10 PM1:27 PM1:44 PM1:59 PM2:05 PM
900 wheelchair access1:19 PM1:21 PM1:25 PM1:42 PM1:59 PM2:14 PM2:20 PM
900 wheelchair access1:34 PM1:36 PM1:40 PM1:57 PM2:14 PM2:29 PM2:35 PM
900 wheelchair access1:49 PM1:51 PM1:55 PM2:12 PM2:29 PM2:44 PM2:50 PM
900 wheelchair access2:04 PM2:06 PM2:10 PM2:27 PM2:44 PM2:59 PM3:05 PM
900 wheelchair access2:19 PM2:21 PM2:25 PM2:42 PM2:59 PM3:14 PM3:20 PM
900 wheelchair access2:34 PM2:36 PM2:40 PM2:57 PM3:14 PM3:29 PM3:35 PM
900 wheelchair access2:49 PM2:51 PM2:55 PM3:12 PM3:29 PM3:44 PM3:50 PM
900 wheelchair access3:04 PM3:06 PM3:10 PM3:27 PM3:44 PM3:59 PM4:05 PM
900 wheelchair access3:19 PM3:21 PM3:25 PM3:42 PM3:59 PM4:14 PM4:20 PM
900 wheelchair access3:34 PM3:36 PM3:40 PM3:57 PM4:14 PM4:29 PM4:35 PM
900 wheelchair access3:49 PM3:51 PM3:55 PM4:12 PM4:29 PM4:44 PM4:50 PM
900 wheelchair access4:04 PM4:06 PM4:10 PM4:27 PM4:44 PM4:59 PM5:05 PM
900 wheelchair access4:19 PM4:21 PM4:25 PM4:42 PM4:59 PM5:14 PM5:20 PM
900 wheelchair access4:34 PM4:36 PM4:40 PM4:57 PM5:14 PM5:29 PM5:35 PM
900 wheelchair access4:49 PM4:51 PM4:55 PM5:12 PM5:29 PM5:44 PM5:50 PM
900 wheelchair access5:04 PM5:06 PM5:10 PM5:27 PM5:44 PM5:59 PM6:05 PM
900 wheelchair access5:19 PM5:21 PM5:25 PM5:42 PM5:59 PM6:14 PM6:20 PM
900 wheelchair access5:34 PM5:36 PM5:40 PM5:57 PM6:14 PM6:29 PM6:35 PM
900 wheelchair access5:49 PM5:51 PM5:55 PM6:12 PM6:29 PM6:43 PM6:49 PM
900 wheelchair access6:04 PM6:06 PM6:10 PM6:27 PM6:42 PM6:56 PM7:02 PM
900 wheelchair access6:19 PM6:21 PM6:25 PM6:40 PM6:55 PM7:09 PM7:15 PM
900 wheelchair access6:34 PM6:36 PM6:39 PM6:54 PM7:09 PM7:23 PM7:29 PM
900 wheelchair access6:49 PM6:51 PM6:54 PM7:09 PM7:24 PM7:38 PM7:44 PM
900 wheelchair access7:04 PM7:06 PM7:09 PM7:24 PM7:39 PM7:53 PM7:59 PM
+

 

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
 Tuggeranong Interchange
+ Platform 8
Erindale CentreWoden Interchange
+ Platform 9
City Interchange
+ Platform 5
Cameron Ave Bus StationLathlain St Bus StationCohen St Bus Station
900 wheelchair access7:30 AM7:41 AM7:57 AM8:14 AM8:31 AM8:35 AM8:37 AM
900 wheelchair access7:45 AM7:56 AM8:12 AM8:29 AM8:46 AM8:50 AM8:52 AM
900 wheelchair access8:00 AM8:11 AM8:27 AM8:44 AM9:01 AM9:05 AM9:07 AM
900 wheelchair access8:15 AM8:26 AM8:42 AM8:59 AM9:16 AM9:20 AM9:22 AM
900 wheelchair access8:30 AM8:41 AM8:57 AM9:14 AM9:31 AM9:35 AM9:37 AM
900 wheelchair access8:45 AM8:56 AM9:12 AM9:29 AM9:46 AM9:50 AM9:52 AM
900 wheelchair access9:00 AM9:11 AM9:27 AM9:44 AM10:01 AM10:05 AM10:07 AM
900 wheelchair access9:15 AM9:26 AM9:42 AM9:59 AM10:16 AM10:20 AM10:22 AM
900 wheelchair access9:30 AM9:41 AM9:57 AM10:14 AM10:31 AM10:35 AM10:37 AM
900 wheelchair access9:45 AM9:56 AM10:12 AM10:29 AM10:46 AM10:50 AM10:52 AM
900 wheelchair access10:00 AM10:11 AM10:27 AM10:44 AM11:01 AM11:05 AM11:07 AM
900 wheelchair access10:15 AM10:26 AM10:42 AM10:59 AM11:16 AM11:20 AM11:22 AM
900 wheelchair access10:30 AM10:41 AM10:57 AM11:14 AM11:31 AM11:35 AM11:37 AM
900 wheelchair access10:45 AM10:56 AM11:12 AM11:29 AM11:46 AM11:50 AM11:52 AM
900 wheelchair access11:00 AM11:11 AM11:27 AM11:44 AM12:01 PM12:05 PM12:07 PM
900 wheelchair access11:15 AM11:26 AM11:42 AM11:59 AM12:16 PM12:20 PM12:22 PM
900 wheelchair access11:30 AM11:41 AM11:57 AM12:14 PM12:31 PM12:35 PM12:37 PM
900 wheelchair access11:45 AM11:56 AM12:12 PM12:29 PM12:46 PM12:50 PM12:52 PM
900 wheelchair access12:00 PM12:11 PM12:27 PM12:44 PM1:01 PM1:05 PM1:07 PM
900 wheelchair access12:15 PM12:26 PM12:42 PM12:59 PM1:16 PM1:20 PM1:22 PM
900 wheelchair access12:30 PM12:41 PM12:57 PM1:14 PM1:31 PM1:35 PM1:37 PM
900 wheelchair access12:45 PM12:56 PM1:12 PM1:29 PM1:46 PM1:50 PM1:52 PM
900 wheelchair access1:00 PM1:11 PM1:27 PM1:44 PM2:01 PM2:05 PM2:07 PM
900 wheelchair access1:15 PM1:26 PM1:42 PM1:59 PM2:16 PM2:20 PM2:22 PM
900 wheelchair access1:30 PM1:41 PM1:57 PM2:14 PM2:31 PM2:35 PM2:37 PM
900 wheelchair access1:45 PM1:56 PM2:12 PM2:29 PM2:46 PM2:50 PM2:52 PM
900 wheelchair access2:00 PM2:11 PM2:27 PM2:44 PM3:01 PM3:05 PM3:07 PM
900 wheelchair access2:15 PM2:26 PM2:42 PM2:59 PM3:16 PM3:20 PM3:22 PM
900 wheelchair access2:30 PM2:41 PM2:57 PM3:14 PM3:31 PM3:35 PM3:37 PM
900 wheelchair access2:45 PM2:56 PM3:12 PM3:29 PM3:46 PM3:50 PM3:52 PM
900 wheelchair access3:00 PM3:11 PM3:27 PM3:44 PM4:01 PM4:05 PM4:07 PM
900 wheelchair access3:15 PM3:26 PM3:42 PM3:59 PM4:16 PM4:20 PM4:22 PM
900 wheelchair access3:30 PM3:41 PM3:57 PM4:14 PM4:31 PM4:35 PM4:37 PM
900 wheelchair access3:45 PM3:56 PM4:12 PM4:29 PM4:46 PM4:50 PM4:52 PM
900 wheelchair access4:00 PM4:11 PM4:27 PM4:44 PM5:01 PM5:05 PM5:07 PM
900 wheelchair access4:15 PM4:26 PM4:42 PM4:59 PM5:16 PM5:20 PM5:22 PM
900 wheelchair access4:30 PM4:41 PM4:57 PM5:14 PM5:31 PM5:35 PM5:37 PM
900 wheelchair access4:45 PM4:56 PM5:12 PM5:29 PM5:46 PM5:50 PM5:52 PM
900 wheelchair access5:00 PM5:11 PM5:27 PM5:44 PM6:01 PM6:05 PM6:07 PM
900 wheelchair access5:15 PM5:26 PM5:42 PM5:59 PM6:16 PM6:20 PM6:22 PM
900 wheelchair access5:30 PM5:41 PM5:57 PM6:14 PM6:31 PM6:34 PM6:36 PM
900 wheelchair access5:45 PM5:56 PM6:12 PM6:29 PM6:46 PM6:49 PM6:51 PM
900 wheelchair access6:00 PM6:11 PM6:27 PM6:42 PM6:59 PM7:02 PM7:04 PM
900 wheelchair access6:15 PM6:26 PM6:41 PM6:56 PM7:13 PM7:16 PM7:18 PM
900 wheelchair access6:30 PM6:40 PM6:55 PM7:10 PM7:27 PM7:30 PM7:32 PM
900 wheelchair access6:45 PM6:55 PM7:10 PM7:25 PM7:42 PM7:45 PM7:47 PM
900 wheelchair access7:06 PM7:16 PM7:31 PM7:46 PM8:03 PM8:06 PM8:08 PM
+

wheelchair_icon- Wheelchair Accessible Bus

+
+ + + + --- /dev/null +++ b/maxious-canberra-transit-feed/source-html/Route_902wkend.html @@ -1,1 +1,481 @@ + + + + + + + + +Route_902 + + + + + + + + +
+

Chosen services: 902

+

View timetable and map

+

This timetable is effective from Monday 25 May 2009.

+ + +

Saturdays

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
 Cameron Ave Bus Station
+ Platform 4
Lathlain St Bus Station
+ Platform 4
Cohen St Bus Station
+ Platform 6
McKellarEvattSpence TerminusEvattMcKellarCohen St Bus StationLathlain St Bus StationCameron Ave Bus Station
902.........................7:18 AM7:23 AM7:31 AM7:39 AM7:41 AM7:45 AM
902.........................8:18 AM8:23 AM8:31 AM8:39 AM8:41 AM8:45 AM
9028:52 AM8:56 AM8:58 AM9:05 AM9:13 AM9:18 AM9:23 AM9:31 AM9:39 AM9:41 AM9:45 AM
9029:52 AM9:56 AM9:58 AM10:05 AM10:13 AM10:18 AM10:23 AM10:31 AM10:39 AM10:41 AM10:45 AM
90210:52 AM10:56 AM10:58 AM11:05 AM11:13 AM11:18 AM11:23 AM11:31 AM11:39 AM11:41 AM11:45 AM
90211:52 AM11:56 AM11:58 AM12:05 PM12:13 PM12:18 PM12:23 PM12:31 PM12:39 PM12:41 PM12:45 PM
90212:52 PM12:56 PM12:58 PM1:05 PM1:13 PM1:18 PM1:23 PM1:31 PM1:39 PM1:41 PM1:45 PM
9021:52 PM1:56 PM1:58 PM2:05 PM2:13 PM2:18 PM2:23 PM2:31 PM2:39 PM2:41 PM2:45 PM
9022:52 PM2:56 PM2:58 PM3:05 PM3:13 PM3:18 PM3:23 PM3:31 PM3:39 PM3:41 PM3:45 PM
9023:52 PM3:56 PM3:58 PM4:05 PM4:13 PM4:18 PM4:23 PM4:31 PM4:39 PM4:41 PM4:45 PM
9024:52 PM4:56 PM4:58 PM5:05 PM5:13 PM5:18 PM5:23 PM5:31 PM5:39 PM5:41 PM5:45 PM
902 wheelchair access5:52 PM5:56 PM5:58 PM6:05 PM6:13 PM6:18 PM6:23 PM6:31 PM6:38 PM6:40 PM6:43 PM
902 wheelchair access6:52 PM6:55 PM6:57 PM7:03 PM7:10 PM7:15 PM7:20 PM7:28 PM7:35 PM7:37 PM7:40 PM
9027:52 PM7:55 PM7:57 PM8:03 PM8:10 PM8:15 PM8:20 PM8:28 PM8:35 PM8:37 PM8:40 PM
902 wheelchair access8:52 PM8:55 PM8:57 PM9:03 PM9:10 PM9:15 PM9:20 PM9:28 PM9:35 PM9:37 PM9:40 PM
9029:52 PM9:55 PM9:57 PM10:03 PM10:10 PM10:15 PM10:20 PM10:28 PM10:35 PM10:37 PM10:40 PM
902 wheelchair access10:52 PM10:55 PM10:57 PM11:03 PM11:10 PM11:15 PM11:20 PM11:28 PM11:35 PM11:37 PM11:40 PM
+ +

^top^

+

Sundays

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
 Cameron Ave Bus Station
+ Platform 4
Lathlain St Bus Station
+ Platform 4
Cohen St Bus Station
+ Platform 6
McKellarEvattSpence TerminusEvattMcKellarCohen St Bus StationLathlain St Bus StationCameron Ave Bus Station
9028:52 AM8:54 AM8:58 AM9:05 AM9:13 AM9:18 AM9:23 AM9:31 AM9:39 AM9:41 AM9:45 AM
9029:52 AM9:54 AM9:58 AM10:05 AM10:13 AM10:18 AM10:23 AM10:31 AM10:39 AM10:41 AM10:45 AM
90210:52 AM10:54 AM10:58 AM11:05 AM11:13 AM11:18 AM11:23 AM11:31 AM11:39 AM11:41 AM11:45 AM
90211:52 AM11:54 AM11:58 AM12:05 PM12:13 PM12:18 PM12:23 PM12:31 PM12:39 PM12:41 PM12:45 PM
90212:52 PM12:54 PM12:58 PM1:05 PM1:13 PM1:18 PM1:23 PM1:31 PM1:39 PM1:41 PM1:45 PM
9021:52 PM1:54 PM1:58 PM2:05 PM2:13 PM2:18 PM2:23 PM2:31 PM2:39 PM2:41 PM2:45 PM
9022:52 PM2:54 PM2:58 PM3:05 PM3:13 PM3:18 PM3:23 PM3:31 PM3:39 PM3:41 PM3:45 PM
9023:52 PM3:54 PM3:58 PM4:05 PM4:13 PM4:18 PM4:23 PM4:31 PM4:39 PM4:41 PM4:45 PM
9024:52 PM4:54 PM4:58 PM5:05 PM5:13 PM5:18 PM5:23 PM5:31 PM5:39 PM5:41 PM5:45 PM
9025:52 PM5:54 PM5:58 PM6:05 PM6:13 PM6:18 PM6:23 PM6:31 PM6:38 PM6:40 PM6:43 PM
9026:52 PM6:54 PM6:57 PM7:03 PM7:10 PM7:15 PM7:20 PM7:28 PM7:35 PM7:37 PM7:40 PM
+

wheelchair_icon- Wheelchair Accessible Bus

+
+ + + + --- /dev/null +++ b/maxious-canberra-transit-feed/source-html/Route_903wkend.html @@ -1,1 +1,410 @@ + + + + + + + + +Route_903 + + + + + + + + +
+

Chosen services: 903

+

View timetable and map

+

This timetable is effective from Monday 25 May 2009.

+ + +

Saturdays

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
 Cameron Ave Bus Station
+ Platform 4
Lathlain St Bus Station
+ Platform 4
Cohen St Bus Station
+ Platform 4
KippaxFraser West TerminusKippaxCohen St Bus StationLathlain St Bus StationCameron Ave Bus Station
903....................7:34 AM7:48 AM8:02 AM8:04 AM8:08 AM
9038:00 AM8:02 AM8:06 AM8:20 AM8:34 AM8:48 AM9:02 AM9:04 AM9:08 AM
9039:00 AM9:02 AM9:06 AM9:20 AM9:34 AM9:48 AM10:02 AM10:04 AM10:08 AM
90310:00 AM10:02 AM10:06 AM10:20 AM10:34 AM10:48 AM11:02 AM11:04 AM11:08 AM
90311:00 AM11:02 AM11:06 AM11:20 AM11:34 AM11:48 AM12:02 PM12:04 PM12:08 PM
90312:00 PM12:02 PM12:06 PM12:20 PM12:34 PM12:48 PM1:02 PM1:04 PM1:08 PM
9031:00 PM1:02 PM1:06 PM1:20 PM1:34 PM1:48 PM2:02 PM2:04 PM2:08 PM
9032:00 PM2:02 PM2:06 PM2:20 PM2:34 PM2:48 PM3:02 PM3:04 PM3:08 PM
9033:00 PM3:02 PM3:06 PM3:20 PM3:34 PM3:48 PM4:02 PM4:04 PM4:08 PM
9034:00 PM4:02 PM4:06 PM4:20 PM4:34 PM4:48 PM5:02 PM5:04 PM5:08 PM
903 wheelchair access5:00 PM5:02 PM5:06 PM5:20 PM5:34 PM5:48 PM6:02 PM6:04 PM6:08 PM
9036:00 PM6:02 PM6:06 PM6:20 PM6:34 PM6:48 PM7:01 PM7:03 PM7:06 PM
903 wheelchair access7:00 PM7:02 PM7:05 PM7:18 PM7:32 PM7:46 PM7:59 PM8:01 PM8:04 PM
903 wheelchair access8:00 PM8:02 PM8:05 PM8:18 PM8:32 PM8:46 PM8:59 PM9:01 PM9:04 PM
9039:00 PM9:02 PM9:05 PM9:18 PM9:32 PM9:46 PM9:59 PM10:01 PM10:04 PM
903 wheelchair access10:00 PM10:02 PM10:05 PM10:18 PM10:32 PM10:46 PM10:59 PM11:01 PM11:04 PM
90311:00 PM11:02 PM11:05 PM11:18 PM11:32 PM....................
+ +

^top^

+

Sundays

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
 Cameron Ave Bus Station
+ Platform 4
Lathlain St Bus Station
+ Platform 4
Cohen St Bus Station
+ Platform 6
KippaxFraser West TerminusKippaxCohen St Bus StationLathlain St Bus StationCameron Ave Bus Station
9039:00 AM9:02 AM9:06 AM9:20 AM9:34 AM9:48 AM10:02 AM10:04 AM10:08 AM
90310:00 AM10:02 AM10:06 AM10:20 AM10:34 AM10:48 AM11:02 AM11:04 AM11:08 AM
90311:00 AM11:02 AM11:06 AM11:20 AM11:34 AM11:48 AM12:02 PM12:04 PM12:08 PM
90312:00 PM12:02 PM12:06 PM12:20 PM12:34 PM12:48 PM1:02 PM1:04 PM1:08 PM
9031:00 PM1:02 PM1:06 PM1:20 PM1:34 PM1:48 PM2:02 PM2:04 PM2:08 PM
9032:00 PM2:02 PM2:06 PM2:20 PM2:34 PM2:48 PM3:02 PM3:04 PM3:08 PM
903 wheelchair access3:00 PM3:02 PM3:06 PM3:20 PM3:34 PM3:48 PM4:02 PM4:04 PM4:08 PM
9034:00 PM4:02 PM4:06 PM4:20 PM4:34 PM4:48 PM5:02 PM5:04 PM5:08 PM
9035:00 PM5:02 PM5:06 PM5:20 PM5:34 PM5:48 PM6:02 PM6:04 PM6:08 PM
9036:00 PM6:02 PM6:06 PM6:20 PM6:34 PM6:48 PM7:01 PM7:03 PM7:06 PM
+

wheelchair_icon- Wheelchair Accessible Bus

+
+ + + + --- /dev/null +++ b/maxious-canberra-transit-feed/source-html/Route_904wkend.html @@ -1,1 +1,421 @@ + + + + + + + + +Route_904 + + + + + + + + +
+

Chosen services: 904

+

View timetable and map

+

This timetable is effective from Monday 25 May 2009.

+ + +

Saturdays

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
 Cameron Ave Bus Station
+ Platform 5
Lathlain St Bus Station
+ Platform 6
Cohen St Bus Station
+ Platform 5
HigginsKippaxHigginsCohen St Bus StationLathlain St Bus StationCameron Ave Bus Station
904....................7:57 AM8:07 AM8:28 AM8:30 AM8:34 AM
9048:20 AM8:22 AM8:26 AM8:47 AM8:57 AM9:07 AM9:28 AM9:30 AM9:34 AM
9049:20 AM9:22 AM9:26 AM9:47 AM9:57 AM10:07 AM10:28 AM10:30 AM10:34 AM
90410:20 AM10:22 AM10:26 AM10:47 AM10:57 AM11:07 AM11:28 AM11:30 AM11:34 AM
90411:20 AM11:22 AM11:26 AM11:47 AM11:57 AM12:07 PM12:28 PM12:30 PM12:34 PM
90412:20 PM12:22 PM12:26 PM12:47 PM12:57 PM1:07 PM1:28 PM1:30 PM1:34 PM
9041:20 PM1:22 PM1:26 PM1:47 PM1:57 PM2:07 PM2:28 PM2:30 PM2:34 PM
9042:20 PM2:22 PM2:26 PM2:47 PM2:57 PM3:07 PM3:28 PM3:30 PM3:34 PM
9043:20 PM3:22 PM3:26 PM3:47 PM3:57 PM4:07 PM4:28 PM4:30 PM4:34 PM
904 wheelchair access4:20 PM4:22 PM4:26 PM4:47 PM4:57 PM5:07 PM5:28 PM5:30 PM5:34 PM
9045:20 PM5:22 PM5:26 PM5:47 PM5:57 PM6:07 PM6:28 PM6:30 PM6:33 PM
904 wheelchair access6:20 PM6:22 PM6:26 PM6:46 PM6:56 PM7:06 PM7:26 PM7:28 PM7:31 PM
9047:20 PM7:22 PM7:25 PM7:45 PM7:55 PM8:05 PM8:25 PM8:27 PM8:30 PM
9048:20 PM8:22 PM8:25 PM8:45 PM8:55 PM9:05 PM9:25 PM9:27 PM9:30 PM
904 wheelchair access9:20 PM9:22 PM9:25 PM9:45 PM9:55 PM10:05 PM10:25 PM10:27 PM10:30 PM
90410:20 PM10:22 PM10:25 PM10:45 PM10:55 PM11:05 PM11:25 PM11:27 PM11:30 PM
904 wheelchair access11:20 PM11:22 PM11:25 PM11:45 PM11:55 PM....................
+

^top^

+

Sundays

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
 Cameron Ave Bus Station
+ Platform 5
Lathlain St Bus Station
+ Platform 6
Cohen St Bus Station
+ Platform 5
HigginsKippaxHigginsCohen St Bus StationLathlain St Bus StationCameron Ave Bus Station
9048:20 AM8:22 AM8:26 AM8:47 AM8:57 AM9:07 AM9:28 AM9:30 AM9:34 AM
9049:20 AM9:22 AM9:26 AM9:47 AM9:57 AM10:07 AM10:28 AM10:30 AM10:34 AM
90410:20 AM10:22 AM10:26 AM10:47 AM10:57 AM11:07 AM11:28 AM11:30 AM11:34 AM
90411:20 AM11:22 AM11:26 AM11:47 AM11:57 AM12:07 PM12:28 PM12:30 PM12:34 PM
90412:20 PM12:22 PM12:26 PM12:47 PM12:57 PM1:07 PM1:28 PM1:30 PM1:34 PM
9041:20 PM1:22 PM1:26 PM1:47 PM1:57 PM2:07 PM2:28 PM2:30 PM2:34 PM
9042:20 PM2:22 PM2:26 PM2:47 PM2:57 PM3:07 PM3:28 PM3:30 PM3:34 PM
9043:20 PM3:22 PM3:26 PM3:47 PM3:57 PM4:07 PM4:28 PM4:30 PM4:34 PM
904 wheelchair access4:20 PM4:22 PM4:26 PM4:47 PM4:57 PM5:07 PM5:28 PM5:30 PM5:34 PM
904 5:20 PM5:22 PM5:26 PM5:47 PM5:57 PM6:07 PM6:28 PM6:30 PM6:33 PM
904 6:20 PM6:22 PM6:26 PM6:46 PM6:56 PM7:06 PM7:26 PM7:28 PM7:31 PM
+

wheelchair_icon- Wheelchair Accessible Bus

+
+ + + + --- /dev/null +++ b/maxious-canberra-transit-feed/source-html/Route_905wkend.html @@ -1,1 +1,542 @@ + + + + + + + + +Route_905 + + + + + + + + +
+

Chosen services: 905

+

View timetable and map

+

This timetable is effective from Monday 25 May 2009.

+ + +

Saturdays

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
 Cameron Ave Bus Station
+ Platform 4
Lathlain St Bus Station
+ Platform 4
Cohen St Bus Station
+ Platform 6
KippaxMacgregor ShopsCharnwoodFraser West TerminusCharnwoodMacgregor ShopsKippax Cohen St Bus StationLathlain St Bus StationCameron Ave Bus Station
905..............................7:57 AM8:09 AM8:16 AM8:23 AM8:36 AM8:38 AM8:42 AM
9058:15 AM8:17 AM8:21 AM8:34 AM8:40 AM8:47 AM8:57 AM9:09 AM9:16 AM9:23 AM9:36 AM9:38 AM9:42 AM
9059:15 AM9:17 AM9:21 AM9:34 AM9:40 AM9:47 AM9:57 AM10:09 AM10:16 AM10:23 AM10:36 AM10:38 AM10:42 AM
90510:15 AM10:17 AM10:21 AM10:34 AM10:40 AM10:47 AM10:57 AM11:09 AM11:16 AM11:23 AM11:36 AM11:38 AM11:42 AM
90511:15 AM11:17 AM11:21 AM11:34 AM11:40 AM11:47 AM11:57 AM12:09 PM12:16 PM12:23 PM12:36 PM12:38 PM12:42 PM
90512:15 PM12:17 PM12:21 PM12:34 PM12:40 PM12:47 PM12:57 PM1:09 PM1:16 PM1:23 PM1:36 PM1:38 PM1:42 PM
9051:15 PM1:17 PM1:21 PM1:34 PM1:40 PM1:47 PM1:57 PM2:09 PM2:16 PM2:23 PM2:36 PM2:38 PM2:42 PM
9052:15 PM2:17 PM2:21 PM2:34 PM2:40 PM2:47 PM2:57 PM3:09 PM3:16 PM3:23 PM3:36 PM3:38 PM3:42 PM
9053:15 PM3:17 PM3:21 PM3:34 PM3:40 PM3:47 PM3:57 PM4:09 PM4:16 PM4:23 PM4:36 PM4:38 PM4:42 PM
905 wheelchair access4:15 PM4:17 PM4:21 PM4:34 PM4:40 PM4:47 PM4:57 PM5:09 PM5:16 PM5:23 PM5:36 PM5:38 PM5:42 PM
9055:15 PM5:17 PM5:21 PM5:34 PM5:40 PM5:47 PM5:57 PM6:09 PM6:16 PM6:23 PM6:36 PM6:38 PM6:41 PM
9056:15 PM6:17 PM6:21 PM6:34 PM6:40 PM6:47 PM6:56 PM7:07 PM7:14 PM7:21 PM7:33 PM7:35 PM7:38 PM
9057:15 PM7:17 PM7:20 PM7:32 PM7:38 PM7:45 PM7:54 PM8:05 PM8:12 PM8:19 PM8:31 PM8:33 PM8:36 PM
9058:15 PM8:17 PM8:20 PM8:32 PM8:38 PM8:45 PM8:54 PM9:05 PM9:12 PM9:19 PM9:31 PM9:33 PM9:36 PM
905 wheelchair access9:15 PM9:17 PM9:20 PM9:32 PM9:38 PM9:45 PM9:54 PM10:05 PM10:12 PM10:19 PM10:31 PM10:33 PM10:36 PM
90510:15 PM10:17 PM10:20 PM10:32 PM10:38 PM10:45 PM10:54 PM..............................
905 wheelchair access11:15 PM11:17 PM11:20 PM11:32 PM11:38 PM11:45 PM11:54 PM..............................
+

^top^

+

Sundays

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
 Cameron Ave Bus Station
+ Platform 4
Lathlain St Bus Station
+ Platform 4
Cohen St Bus Station
+ Platform 6
KippaxMacgregor ShopsCharnwoodFraser West TerminusCharnwoodMacgregor ShopsKippax Cohen St Bus StationLathlain St Bus StationCameron Ave Bus Station
905..............................8:57 AM9:09 AM9:16 AM9:23 AM9:36 AM9:38 AM9:42 AM
9059:15 AM9:17 AM9:21 AM9:34 AM9:40 AM9:47 AM9:57 AM10:09 AM10:16 AM10:23 AM10:36 AM10:38 AM10:42 AM
90510:15 AM10:17 AM10:21 AM10:34 AM10:40 AM10:47 AM10:57 AM11:09 AM11:16 AM11:23 AM11:36 AM11:38 AM11:42 AM
90511:15 AM11:17 AM11:21 AM11:34 AM11:40 AM11:47 AM11:57 AM12:09 PM12:16 PM12:23 PM12:36 PM12:38 PM12:42 PM
90512:15 PM12:17 PM12:21 PM12:34 PM12:40 PM12:47 PM12:57 PM1:09 PM1:16 PM1:23 PM1:36 PM1:38 PM1:42 PM
9051:15 PM1:17 PM1:21 PM1:34 PM1:40 PM1:47 PM1:57 PM2:09 PM2:16 PM2:23 PM2:36 PM2:38 PM2:42 PM
9052:15 PM2:17 PM2:21 PM2:34 PM2:40 PM2:47 PM2:57 PM3:09 PM3:16 PM3:23 PM3:36 PM3:38 PM3:42 PM
9053:15 PM3:17 PM3:21 PM3:34 PM3:40 PM3:47 PM3:57 PM4:09 PM4:16 PM4:23 PM4:36 PM4:38 PM4:42 PM
9054:15 PM4:17 PM4:21 PM4:34 PM4:40 PM4:47 PM4:57 PM5:09 PM5:16 PM5:23 PM5:36 PM5:38 PM5:42 PM
9055:15 PM5:17 PM5:21 PM5:34 PM5:40 PM5:47 PM5:57 PM6:09 PM6:16 PM6:23 PM6:36 PM6:38 PM6:41 PM
9056:15 PM6:17 PM6:21 PM6:34 PM6:40 PM6:47 PM6:56 PM..............................
+

wheelchair_icon- Wheelchair Accessible Bus

+
+ + + + --- /dev/null +++ b/maxious-canberra-transit-feed/source-html/Route_906wkend.html @@ -1,1 +1,409 @@ + + + + + + + + +Route_906 + + + + + + + + +
+

Chosen services: 906

+

View timetable and map

+

This timetable is effective from Monday 25 May 2009.

+ + +

Saturdays

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
 Cameron Ave Bus Station
+ Platform 4
Lathlain St Bus Station
+ Platform 4
Cohen St Bus Station
+ Platform 6
MelbaSpence TerminusMelbaCohen St Bus StationLathlain St Bus StationCameron Ave Bus Station
906....................7:25 AM7:38 AM7:53 AM7:55 AM7:59 AM
9067:53 AM7:55 AM7:59 AM8:12 AM8:25 AM8:38 AM8:53 AM8:55 AM8:59 AM
9068:53 AM8:55 AM8:59 AM9:12 AM9:25 AM9:38 AM9:53 AM9:55 AM9:59 AM
9069:53 AM9:55 AM9:59 AM10:12 AM10:25 AM10:38 AM10:53 AM10:55 AM10:59 AM
90610:53 AM10:55 AM10:59 AM11:12 AM11:25 AM11:38 AM11:53 AM11:55 AM11:59 AM
90611:53 AM11:55 AM11:59 AM12:12 PM12:25 PM12:38 PM12:53 PM12:55 PM12:59 PM
90612:53 PM12:55 PM12:59 PM1:12 PM1:25 PM1:38 PM1:53 PM1:55 PM1:59 PM
9061:53 PM1:55 PM1:59 PM2:12 PM2:25 PM2:38 PM2:53 PM2:55 PM2:59 PM
9062:53 PM2:55 PM2:59 PM3:12 PM3:25 PM3:38 PM3:53 PM3:55 PM3:59 PM
9063:53 PM3:55 PM3:59 PM4:12 PM4:25 PM4:38 PM4:53 PM4:55 PM4:59 PM
9064:53 PM4:55 PM4:59 PM5:12 PM5:25 PM5:38 PM5:53 PM5:55 PM5:59 PM
9065:53 PM5:55 PM5:59 PM6:12 PM6:25 PM6:38 PM6:52 PM6:54 PM6:57 PM
9066:53 PM6:55 PM6:58 PM7:10 PM7:23 PM7:36 PM7:50 PM7:52 PM7:55 PM
9067:53 PM7:55 PM7:58 PM8:10 PM8:23 PM8:36 PM8:50 PM8:52 PM8:55 PM
9068:57 PM8:59 PM9:02 PM9:14 PM9:27 PM9:40 PM9:54 PM9:56 PM9:59 PM
906 wheelchair access9:57 PM9:59 PM10:02 PM10:14 PM10:27 PM10:40 PM10:54 PM10:56 PM10:59 PM
90610:57 PM10:59 PM11:02 PM11:14 PM11:27 PM11:40 PM11:54 PM11:56 PM11:59 PM
+

^top^

+

Sundays

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
 Cameron Ave Bus Station
+ Platform 4
Lathlain St Bus Station
+ Platform 4
Cohen St Bus Station
+ Platform 6
MelbaSpence TerminusMelbaCohen St Bus StationLathlain St Bus StationCameron Ave Bus Station
9068:53 AM8:55 AM8:59 AM9:12 AM9:25 AM9:38 AM9:53 AM9:55 AM9:59 AM
9069:53 AM9:55 AM9:59 AM10:12 AM10:25 AM10:38 AM10:53 AM10:55 AM10:59 AM
90610:53 AM10:55 AM10:59 AM11:12 AM11:25 AM11:38 AM11:53 AM11:55 AM11:59 AM
90611:53 AM11:55 AM11:59 AM12:12 PM12:25 PM12:38 PM12:53 PM12:55 PM12:59 PM
906 wheelchair access12:53 PM12:55 PM12:59 PM1:12 PM1:25 PM1:38 PM1:53 PM1:55 PM1:59 PM
9061:53 PM1:55 PM1:59 PM2:12 PM2:25 PM2:38 PM2:53 PM2:55 PM2:59 PM
9062:53 PM2:55 PM2:59 PM3:12 PM3:25 PM3:38 PM3:53 PM3:55 PM3:59 PM
9063:53 PM3:55 PM3:59 PM4:12 PM4:25 PM4:38 PM4:53 PM4:55 PM4:59 PM
9064:53 PM4:55 PM4:59 PM5:12 PM5:25 PM5:38 PM5:53 PM5:55 PM5:59 PM
9065:53 PM5:55 PM5:59 PM6:12 PM6:25 PM6:38 PM6:52 PM6:54 PM6:57 PM
+

wheelchair_icon- Wheelchair Accessible Bus

+
+ + + + --- /dev/null +++ b/maxious-canberra-transit-feed/source-html/Route_907wkend.html @@ -1,1 +1,421 @@ + + + + + + + + +Route_907 + + + + + + + + +
+

Chosen services: 907

+ +

View timetable and map

+

This timetable is effective from Monday 25 May 2009.

+ + +

Saturdays

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
 Cameron Ave Bus Station
+ Platform 5
Lathlain St Bus Station
+ Platform 6
Cohen St Bus Station
+ Platform 5
CharnwoodFraser East TerminusCharnwoodCohen St Bus StationLathlain St Bus StationCameron Ave Bus Station
907...............7:08 AM7:15 AM7:22 AM7:36 AM7:38 AM7:42 AM
907...............8:08 AM8:15 AM8:22 AM8:36 AM8:38 AM8:42 AM
9078:48 AM8:50 AM8:54 AM9:08 AM9:15 AM9:22 AM9:36 AM9:38 AM9:42 AM
9079:48 AM9:50 AM9:54 AM10:08 AM10:15 AM10:22 AM10:36 AM10:38 AM10:42 AM
90710:48 AM10:50 AM10:54 AM11:08 AM11:15 AM11:22 AM11:36 AM11:38 AM11:42 AM
90711:48 AM11:50 AM11:54 AM12:08 PM12:15 PM12:22 PM12:36 PM12:38 PM12:42 PM
90712:48 PM12:50 PM12:54 PM1:08 PM1:15 PM1:22 PM1:36 PM1:38 PM1:42 PM
9071:48 PM1:50 PM1:54 PM2:08 PM2:15 PM2:22 PM2:36 PM2:38 PM2:42 PM
9072:48 PM2:50 PM2:54 PM3:08 PM3:15 PM3:22 PM3:36 PM3:38 PM3:42 PM
9073:48 PM3:50 PM3:54 PM4:08 PM4:15 PM4:22 PM4:36 PM4:38 PM4:42 PM
9074:48 PM4:50 PM4:54 PM5:08 PM5:15 PM5:22 PM5:36 PM5:38 PM5:42 PM
907 wheelchair access5:48 PM5:50 PM5:54 PM6:08 PM6:15 PM6:22 PM6:36 PM6:38 PM6:41 PM
9076:48 PM6:50 PM6:53 PM7:06 PM7:13 PM7:20 PM7:33 PM7:35 PM7:38 PM
907 wheelchair access7:48 PM7:50 PM7:53 PM8:06 PM8:13 PM8:20 PM8:33 PM8:35 PM8:38 PM
9078:48 PM8:50 PM8:53 PM9:06 PM9:13 PM9:20 PM9:33 PM9:35 PM9:38 PM
9079:48 PM9:50 PM9:53 PM10:06 PM10:13 PM10:20 PM10:33 PM10:35 PM10:38 PM
907 wheelchair access10:48 PM10:50 PM10:53 PM11:06 PM11:13 PM11:20 PM11:33 PM11:35 PM11:38 PM
+

^top^

+

Sundays

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
 Cameron Ave Bus Station
+ Platform 5
Lathlain St Bus Station
+ Platform 6
Cohen St Bus Station
+ Platform 5
CharnwoodFraser East TerminusCharnwoodCohen St Bus StationLathlain St Bus StationCameron Ave Bus Station
9078:48 AM8:50 AM8:54 AM9:08 AM9:15 AM9:22 AM9:36 AM9:38 AM9:42 AM
9079:48 AM9:50 AM9:54 AM10:08 AM10:15 AM10:22 AM10:36 AM10:38 AM10:42 AM
90710:48 AM10:50 AM10:54 AM11:08 AM11:15 AM11:22 AM11:36 AM11:38 AM11:42 AM
90711:48 AM11:50 AM11:54 AM12:08 PM12:15 PM12:22 PM12:36 PM12:38 PM12:42 PM
90712:48 PM12:50 PM12:54 PM1:08 PM1:15 PM1:22 PM1:36 PM1:38 PM1:42 PM
9071:48 PM1:50 PM1:54 PM2:08 PM2:15 PM2:22 PM2:36 PM2:38 PM2:42 PM
9072:48 PM2:50 PM2:54 PM3:08 PM3:15 PM3:22 PM3:36 PM3:38 PM3:42 PM
9073:48 PM3:50 PM3:54 PM4:08 PM4:15 PM4:22 PM4:36 PM4:38 PM4:42 PM
9074:48 PM4:50 PM4:54 PM5:08 PM5:15 PM5:22 PM5:36 PM5:38 PM5:42 PM
907 wheelchair access5:48 PM5:50 PM5:54 PM6:08 PM6:15 PM6:22 PM6:36 PM6:38 PM6:41 PM
9076:48 PM6:50 PM6:53 PM7:06 PM7:13 PM7:20 PM7:33 PM7:35 PM7:38 PM
+

wheelchair_icon- Wheelchair Accessible Bus

+
+ + + + --- /dev/null +++ b/maxious-canberra-transit-feed/source-html/Route_912wkend.html @@ -1,1 +1,188 @@ + + + + + + + + +Route_912 + + + + + + + + +
+

Chosen services: 912

+ +

View timetable and map

+

This timetable is effective from Monday 25 May 2009.

+ + +

Saturdays

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
 Tuggeranong Interchange
+ Platform 4
Isabella Shops + Calwell ShopsTheodore + Outrim / DugganTuggeranong Interchange +
912 wheelchair access8:15 AM8:25 AM8:30 AM8:39 AM8:46 AM8:55 AM
912 wheelchair access10:15 AM10:25 AM10:30 AM10:39 AM10:46 AM10:55 AM
912 wheelchair access12:15 PM12:25 PM12:30 PM12:39 PM12:46 PM12:55 PM
912 wheelchair access2:15 PM2:25 PM2:30 PM2:39 PM2:46 PM2:55 PM
912 wheelchair access4:15 PM4:25 PM4:30 PM4:39 PM4:46 PM4:55 PM
912 wheelchair access6:15 PM6:25 PM6:30 PM6:39 PM6:46 PM6:55 PM
912 wheelchair access8:18 PM8:28 PM8:33 PM8:42 PM8:49 PM8:58 PM
912 wheelchair access10:18 PM10:28 PM10:33 PM10:42 PM10:49 PM10:58 PM
+

^top^

+

Sundays

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
 Tuggeranong Interchange
+ Platform 4
Isabella ShopsCalwell ShopsTheodoreOutrim / DugganTuggeranong Interchange
912 wheelchair access10:15 AM10:25 AM10:30 AM10:39 AM10:46 AM10:55 AM
912 wheelchair access12:15 PM12:25 PM12:30 PM12:39 PM12:46 PM12:55 PM
912 wheelchair access2:15 PM2:25 PM2:30 PM2:39 PM2:46 PM2:55 PM
912 wheelchair access4:15 PM4:25 PM4:30 PM4:39 PM4:46 PM4:55 PM
912 wheelchair access6:15 PM6:25 PM6:30 PM6:39 PM6:46 PM6:55 PM
+

wheelchair_icon- Wheelchair Accessible Bus

+
+ + + + --- /dev/null +++ b/maxious-canberra-transit-feed/source-html/Route_913wkend.html @@ -1,1 +1,254 @@ + + + + + + + + +Route_913 + + + + + + + + +
+

Chosen services: 913

+ +

View timetable and map

+

This timetable is effective from Monday 25 May 2009.

+ + +

Saturdays

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
 Tuggeranong Interchange
+ Platform 7
BonythonWoodcock/Clare DennisGordon PrimaryTharwa/KnokeConder PrimaryLanyon Market PlaceBonython PrimaryTuggeranong Interchange
913 wheelchair access7:25 AM7:34 AM7:38 AM7:42 AM7:45 AM7:48 AM7:54 AM7:58 AM8:04 AM
913 wheelchair access9:25 AM9:34 AM9:38 AM9:42 AM9:45 AM9:48 AM9:54 AM9:58 AM10:04 AM
913 wheelchair access11:25 AM11:34 AM11:38 AM11:42 AM11:45 AM11:48 AM11:54 AM11:58 AM12:04 PM
913 wheelchair access1:25 PM1:34 PM1:38 PM1:42 PM1:45 PM1:48 PM1:54 PM1:58 PM2:04 PM
913 wheelchair access3:25 PM3:34 PM3:38 PM3:42 PM3:45 PM3:48 PM3:54 PM3:58 PM4:04 PM
913 wheelchair access5:25 PM5:34 PM5:38 PM5:42 PM5:45 PM5:48 PM5:54 PM5:58 PM6:04 PM
913 wheelchair access7:25 PM7:34 PM7:38 PM7:42 PM7:45 PM7:48 PM7:54 PM7:58 PM8:04 PM
913 wheelchair access9:28 PM9:37 PM9:41 PM9:45 PM9:48 PM9:51 PM9:57 PM10:01 PM10:07 PM
913 wheelchair access11:28 PM11:37 PM11:41 PM11:45 PM11:48 PM11:51 PM11:57 PM..........
+

^top^

+

Sundays

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
 Tuggeranong Interchange
+ Platform 7
BonythonWoodcock/Clare DennisGordon PrimaryTharwa/KnokeConder PrimaryLanyon Market PlaceBonython PrimaryTuggeranong Interchange
913 wheelchair access9:25 AM9:34 AM9:38 AM9:42 AM9:45 AM9:48 AM9:54 AM9:58 AM10:04 AM
913 wheelchair access11:25 AM11:34 AM11:38 AM11:42 AM11:45 AM11:48 AM11:54 AM11:58 AM12:04 PM
913 wheelchair access1:25 PM1:34 PM1:38 PM1:42 PM1:45 PM1:48 PM1:54 PM1:58 PM2:04 PM
913 wheelchair access3:25 PM3:34 PM3:38 PM3:42 PM3:45 PM3:48 PM3:54 PM3:58 PM4:04 PM
913 wheelchair access5:25 PM5:34 PM5:38 PM5:42 PM5:45 PM5:48 PM5:54 PM5:58 PM6:04 PM
913 wheelchair access7:25 PM7:34 PM7:38 PM7:42 PM7:45 PM7:48 PM7:54 PM7:58 PM8:04 PM
+

wheelchair_icon- Wheelchair Accessible Bus

+
+ + + + --- /dev/null +++ b/maxious-canberra-transit-feed/source-html/Route_914wkend.html @@ -1,1 +1,242 @@ + + + + + + + + +Route_914 + + + + + + + + +
+

Chosen services: 914

+

View timetable and map

+

This timetable is effective from Monday 25 May 2009.

+ + +

Saturdays

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
 Tuggeranong Interchange
+ Platform 7
Bonython PrimaryLanyon Market PlaceConder PrimaryTharwa/PocketGordon PrimaryWoodcock/Clare DennisBonython PrimaryTuggeranong Interchange
914 wheelchair access6:25 AM6:34 AM6:38 AM6:44 AM6:47 AM6:50 AM6:54 AM6:58 AM7:04 AM
914 wheelchair access8:25 AM8:34 AM8:38 AM8:44 AM8:47 AM8:50 AM8:54 AM8:58 AM9:04 AM
914 wheelchair access10:25 AM10:34 AM10:38 AM10:44 AM10:47 AM10:50 AM10:54 AM10:58 AM11:04 AM
914 wheelchair access12:25 PM12:34 PM12:38 PM12:44 PM12:47 PM12:50 PM12:54 PM12:58 PM1:04 PM
914 wheelchair access2:25 PM2:34 PM2:38 PM2:44 PM2:47 PM2:50 PM2:54 PM2:58 PM3:04 PM
914 wheelchair access4:25 PM4:34 PM4:38 PM4:44 PM4:47 PM4:50 PM4:54 PM4:58 PM5:04 PM
914 wheelchair access6:25 PM6:34 PM6:38 PM6:44 PM6:47 PM6:50 PM6:54 PM6:58 PM7:04 PM
914 wheelchair access8:28 PM8:37 PM8:41 PM8:47 PM8:50 PM8:53 PM8:57 PM9:01 PM9:07 PM
914 wheelchair access10:28 PM10:37 PM10:41 PM10:47 PM10:50 PM10:53 PM10:57 PM11:01 PM11:07 PM
+

^top^

+

Sundays

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
 Tuggeranong Interchange
+ Platform 7
Bonython PrimaryLanyon Market PlaceConder PrimaryTharwa/PocketGordon PrimaryWoodcock/Clare DennisBonython PrimaryTuggeranong Interchange
914 wheelchair access10:25 AM10:34 AM10:38 AM10:44 AM10:47 AM10:50 AM10:54 AM10:58 AM11:04 AM
914 wheelchair access12:25 PM12:34 PM12:38 PM12:44 PM12:47 PM12:50 PM12:54 PM12:58 PM1:04 PM
914 wheelchair access2:25 PM2:34 PM2:38 PM2:44 PM2:47 PM2:50 PM2:54 PM2:58 PM3:04 PM
914 wheelchair access4:25 PM4:34 PM4:38 PM4:44 PM4:47 PM4:50 PM4:54 PM4:58 PM5:04 PM
914 wheelchair access6:25 PM6:34 PM6:38 PM6:44 PM6:47 PM6:50 PM6:54 PM6:58 PM7:04 PM
+

wheelchair_icon- Wheelchair Accessible Bus

+
+ + + + --- /dev/null +++ b/maxious-canberra-transit-feed/source-html/Route_915wkend.html @@ -1,1 +1,199 @@ + + + + + + + + +Route_915 + + + + + +
+

Chosen services: 915

+ +

View timetable and map

+

This timetable is effective from Monday 25 May 2009.

+ +

Saturdays

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
 Tuggeranong Interchange
+ Platform 4
Isabella ShopsTheodoreCalwell ShopsOutrim / DugganTuggeranong Interchange
915 wheelchair access7:15 AM7:25 AM7:34 AM7:43 AM7:46 AM7:55 AM
915 wheelchair access9:15 AM9:25 AM9:34 AM9:43 AM9:46 AM9:55 AM
915 wheelchair access11:15 AM11:25 AM11:34 AM11:43 AM11:46 AM11:55 AM
915 wheelchair access1:15 PM1:25 PM1:34 PM1:43 PM1:46 PM1:55 PM
915 wheelchair access3:15 PM3:25 PM3:34 PM3:43 PM3:46 PM3:55 PM
915 wheelchair access5:15 PM5:25 PM5:34 PM5:43 PM5:46 PM5:55 PM
915 wheelchair access7:15 PM7:25 PM7:34 PM7:43 PM7:46 PM7:55 PM
915 wheelchair access9:18 PM9:28 PM9:37 PM9:46 PM9:49 PM9:58 PM
915 wheelchair access11:18 PM11:28 PM11:37 PM11:46 PM11:49 PM.....
+

^top^

+

Sundays

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
 Tuggeranong Interchange
+ Platform 4
Isabella ShopsTheodoreCalwell ShopsOutrim / DugganTuggeranong Interchange
915 wheelchair access9:15 AM9:25 AM9:34 AM9:43 AM9:46 AM9:55 AM
915 wheelchair access 11:15 AM11:25 AM11:34 AM11:43 AM11:46 AM11:55 AM
915wheelchair access 1:15 PM1:25 PM1:34 PM1:43 PM1:46 PM1:55 PM
915 wheelchair access3:15 PM3:25 PM3:34 PM3:43 PM3:46 PM3:55 PM
915 wheelchair access5:15 PM5:25 PM5:34 PM5:43 PM5:46 PM5:55 PM
915 wheelchair access7:17 PM7:27 PM7:36 PM7:45 PM7:48 PM7:57 PM
+

wheelchair_icon- Wheelchair Accessible Bus

+
+ + + + + --- /dev/null +++ b/maxious-canberra-transit-feed/source-html/Route_921wkend.html @@ -1,1 +1,196 @@ + + + + + + + + +Route_921 + + + + + +
+

Chosen services: 921

+ +

View timetable and map

+

This timetable is effective from Monday 25 May 2009.

+ +

Saturdays

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
 Woden Interchange
+ Platform 15
LyonsChifleyTorrens ShopsSouthlands MawsonPearceWoden Interchange
921 wheelchair access9:33 AM9:36 AM9:40 AM9:45 AM9:51 AM9:55 AM10:01 AM
921 wheelchair access11:33 AM11:36 AM11:40 AM11:45 AM11:51 AM11:55 AM12:01 PM
921 wheelchair access1:33 PM1:36 PM1:40 PM1:45 PM1:51 PM1:55 PM2:01 PM
921 wheelchair access3:33 PM3:36 PM3:40 PM3:45 PM3:51 PM3:55 PM4:01 PM
921 wheelchair access5:33 PM5:36 PM5:40 PM5:45 PM5:51 PM5:55 PM6:01 PM
921 wheelchair access7:33 PM7:36 PM7:40 PM7:45 PM7:51 PM7:55 PM8:01 PM
921 wheelchair access9:33 PM9:36 PM9:40 PM9:45 PM9:51 PM9:55 PM10:01 PM
921 wheelchair access11:33 PM11:36 PM11:40 PM11:45 PM11:51 PM11:55 PM.....
+

^top^

+

Sundays

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
 Woden Interchange
+ Platform 15
LyonsChifleyTorrens ShopsSouthlands MawsonPearceWoden Interchange
921 wheelchair access9:33 AM9:36 AM9:40 AM9:45 AM9:51 AM9:55 AM10:01 AM
921 wheelchair access11:33 AM11:36 AM11:40 AM11:45 AM11:51 AM11:55 AM12:01 PM
921 wheelchair access1:33 PM1:36 PM1:40 PM1:45 PM1:51 PM1:55 PM2:01 PM
921 wheelchair access3:33 PM3:36 PM3:40 PM3:45 PM3:51 PM3:55 PM4:01 PM
921 wheelchair access5:33 PM5:36 PM5:40 PM5:45 PM5:51 PM5:55 PM6:01 PM
+

wheelchair_icon- Wheelchair Accessible Bus

+
+ + + + + --- /dev/null +++ b/maxious-canberra-transit-feed/source-html/Route_922wkend.html @@ -1,1 +1,198 @@ + + + + + + + + +Route_922 + + + + + + + +
+

Chosen services: 922

+

View timetable and map

+

This timetable is effective from Monday 25 May 2009.

+ + +

Saturdays

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
 Woden Interchange
+ Platform 15
PearceSouthlands MawsonTorrens ShopsChifleyLyonsWoden Interchange
922 wheelchair access8:33 AM8:39 AM8:43 AM8:49 AM8:54 AM8:58 AM9:01 AM
922 wheelchair access10:33 AM10:39 AM10:43 AM10:49 AM10:54 AM10:58 AM11:01 AM
922 wheelchair access12:33 PM12:39 PM12:43 PM12:49 PM12:54 PM12:58 PM1:01 PM
922 wheelchair access2:33 PM2:39 PM2:43 PM2:49 PM2:54 PM2:58 PM3:01 PM
922 wheelchair access4:33 PM4:39 PM4:43 PM4:49 PM4:54 PM4:58 PM5:01 PM
922 wheelchair access6:33 PM6:39 PM6:43 PM6:49 PM6:54 PM6:58 PM7:01 PM
922 wheelchair access8:33 PM8:39 PM8:43 PM8:49 PM8:54 PM8:58 PM9:01 PM
922 wheelchair access10:33 PM10:39 PM10:43 PM10:49 PM10:54 PM10:58 PM11:01 PM
+

^top^

+

Sundays

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
 Woden Interchange
+ Platform 15
PearceSouthlands MawsonTorrens ShopsChifleyLyonsWoden Interchange
922 wheelchair access10:33 AM10:39 AM10:43 AM10:49 AM10:54 AM10:58 AM11:01 AM
922 wheelchair access12:33 PM12:39 PM12:43 PM12:49 PM12:54 PM12:58 PM1:01 PM
922 2:33 PM2:39 PM2:43 PM2:49 PM2:54 PM2:58 PM3:01 PM
922 wheelchair access4:33 PM4:39 PM4:43 PM4:49 PM4:54 PM4:58 PM5:01 PM
922 wheelchair access6:33 PM6:39 PM6:43 PM6:49 PM6:54 PM6:58 PM7:01 PM
+

wheelchair_icon- Wheelchair Accessible Bus

+
+ + + + --- /dev/null +++ b/maxious-canberra-transit-feed/source-html/Route_923wkend.html @@ -1,1 +1,181 @@ + + + + + + + + +Route_923 + + + + + +
+

Chosen services: 923

+ +

View timetable and map

+

This timetable is effective from Monday 25 May 2009.

+ +

Saturdays

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
 Woden Interchange
+ Platform 15
Canberra HospitalIsaacsFarrer Primary SchoolSouthlands MawsonWoden Interchange
923 wheelchair access9:10 AM9:16 AM9:21 AM9:27 AM9:33 AM9:43 AM
923 wheelchair access11:10 AM11:16 AM11:21 AM11:27 AM11:33 AM11:43 AM
923 wheelchair access1:10 PM1:16 PM1:21 PM1:27 PM1:33 PM1:43 PM
923 wheelchair access3:10 PM3:16 PM3:21 PM3:27 PM3:33 PM3:43 PM
923 wheelchair access5:10 PM5:16 PM5:21 PM5:27 PM5:33 PM5:43 PM
923 wheelchair access7:13 PM7:18 PM7:23 PM7:28 PM7:34 PM7:43 PM
923 wheelchair access9:13 PM9:18 PM9:23 PM9:28 PM9:34 PM9:43 PM
923 wheelchair access11:13 PM11:18 PM11:23 PM11:28 PM11:34 PM11:43 PM
+

^top^

+

Sundays

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
 Woden Interchange
+ Platform 15
Canberra HospitalIsaacsFarrer Primary SchoolSouthlands MawsonWoden Interchange
923 wheelchair access9:10 AM9:16 AM9:21 AM9:27 AM9:33 AM9:43 AM
923 wheelchair access11:10 AM11:16 AM11:21 AM11:27 AM11:33 AM11:43 AM
923 wheelchair access1:10 PM1:16 PM1:21 PM1:27 PM1:33 PM1:43 PM
923 wheelchair access3:10 PM3:16 PM3:21 PM3:27 PM3:33 PM3:43 PM
923 wheelchair access5:10 PM5:16 PM5:21 PM5:27 PM5:33 PM5:43 PM
+

wheelchair_icon- Wheelchair Accessible Bus

+
+ + + + + --- /dev/null +++ b/maxious-canberra-transit-feed/source-html/Route_924wkend.html @@ -1,1 +1,184 @@ + + + + + + + + +Route_924 + + + + + + + +
+

Chosen services: 924

+

View timetable and map

+

This timetable is effective from Monday 25 May 2009.

+ + +

Saturdays

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
 Woden Interchange
+ Platform 15
Southlands MawsonFarrer Primary SchoolIsaacsCanberra HospitalWoden Interchange
924 wheelchair access8:10 AM8:19 AM8:24 AM8:29 AM8:33 AM8:41 AM
924 wheelchair access10:10 AM10:19 AM10:24 AM10:29 AM10:33 AM10:41 AM
924 wheelchair access12:10 PM12:19 PM12:24 PM12:29 PM12:33 PM12:41 PM
924 wheelchair access2:10 PM2:19 PM2:24 PM2:29 PM2:33 PM2:41 PM
924 wheelchair access4:10 PM4:19 PM4:24 PM4:29 PM4:33 PM4:41 PM
924 wheelchair access6:10 PM6:19 PM6:24 PM6:29 PM6:33 PM6:41 PM
924 wheelchair access8:13 PM8:21 PM8:26 PM8:30 PM8:34 PM8:41 PM
924 wheelchair access10:13 PM10:21 PM10:26 PM10:30 PM10:34 PM10:41 PM
+

^top^

+

Sundays

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
 Woden Interchange
+ Platform 15
Southlands MawsonFarrer Primary SchoolIsaacsCanberra HospitalWoden Interchange
924 wheelchair access10:10 AM10:19 AM10:24 AM10:29 AM10:33 AM10:41 AM
924 wheelchair access12:10 PM12:19 PM12:24 PM12:29 PM12:33 PM12:41 PM
924 wheelchair access2:10 PM2:19 PM2:24 PM2:29 PM2:33 PM2:41 PM
924 wheelchair access4:10 PM4:19 PM4:24 PM4:29 PM4:33 PM4:41 PM
924 wheelchair access6:10 PM6:19 PM6:24 PM6:29 PM6:33 PM6:41 PM
+

wheelchair_icon- Wheelchair Accessible Bus

+
+ + + + --- /dev/null +++ b/maxious-canberra-transit-feed/source-html/Route_925wkend.html @@ -1,1 +1,486 @@ + + + + + + + + +Route_925 + + + + + +
+

Chosen services: 925

+ +

View timetable and map

+

This timetable is effective from Monday 25 May 2009.

+ +

Saturdays

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
 Cooleman CourtDuffyHolderWeston PrimaryWoden Interchange
925 wheelchair access8:24 AM8:31 AM8:34 AM8:37 AM8:46 AM
925 wheelchair access9:24 AM9:31 AM9:34 AM9:37 AM9:46 AM
925 wheelchair access10:24 AM10:31 AM10:34 AM10:37 AM10:46 AM
925 wheelchair access11:24 AM11:31 AM11:34 AM11:37 AM11:46 AM
925 wheelchair access12:24 PM12:31 PM12:34 PM12:37 PM12:46 PM
925 wheelchair access1:24 PM1:31 PM1:34 PM1:37 PM1:46 PM
925 wheelchair access2:24 PM2:31 PM2:34 PM2:37 PM2:46 PM
925 wheelchair access3:24 PM3:31 PM3:34 PM3:37 PM3:46 PM
925 wheelchair access4:24 PM4:31 PM4:34 PM4:37 PM4:46 PM
925 wheelchair access5:24 PM5:31 PM5:34 PM5:37 PM5:46 PM
925 wheelchair access6:24 PM6:31 PM6:34 PM6:37 PM6:46 PM
925 wheelchair access7:24 PM7:31 PM7:34 PM7:37 PM7:46 PM
925 wheelchair access8:24 PM8:31 PM8:34 PM8:37 PM8:46 PM
925 wheelchair access9:24 PM9:31 PM9:34 PM9:37 PM9:46 PM
925 wheelchair access10:24 PM10:31 PM10:34 PM10:37 PM10:46 PM
+

 

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
 Woden Interchange
+ Platform 16
Weston PrimaryHolderDuffyCooleman Court
925 wheelchair access8:57 AM9:07 AM9:09 AM9:11 AM9:19 AM
925 wheelchair access9:57 AM10:07 AM10:09 AM10:11 AM10:19 AM
925 wheelchair access10:57 AM11:07 AM11:09 AM11:11 AM11:19 AM
925 wheelchair access11:57 AM12:07 PM12:09 PM12:11 PM12:19 PM
925 wheelchair access12:57 PM1:07 PM1:09 PM1:11 PM1:19 PM
925 wheelchair access1:57 PM2:07 PM2:09 PM2:11 PM2:19 PM
925 wheelchair access2:57 PM3:07 PM3:09 PM3:11 PM3:19 PM
925 wheelchair access3:57 PM4:07 PM4:09 PM4:11 PM4:19 PM
925 wheelchair access4:57 PM5:07 PM5:09 PM5:11 PM5:19 PM
925 wheelchair access5:57 PM6:07 PM6:09 PM6:11 PM6:19 PM
925 wheelchair access6:57 PM7:07 PM7:09 PM7:11 PM7:19 PM
925 wheelchair access7:57 PM8:07 PM8:09 PM8:11 PM8:19 PM
925 wheelchair access8:57 PM9:07 PM9:09 PM9:11 PM9:19 PM
925 wheelchair access9:57 PM10:07 PM10:09 PM10:11 PM10:19 PM
925 wheelchair access10:57 PM11:07 PM11:09 PM11:11 PM11:19 PM
+

^top^

+

Sundays

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
 Cooleman CourtDuffyHolderWeston PrimaryWoden Interchange
925 wheelchair access9:24 AM9:31 AM9:34 AM9:37 AM9:46 AM
925 wheelchair access10:24 AM10:31 AM10:34 AM10:37 AM10:46 AM
925 wheelchair access11:24 AM11:31 AM11:34 AM11:37 AM11:46 AM
925 wheelchair access12:24 PM12:31 PM12:34 PM12:37 PM12:46 PM
925 wheelchair access1:24 PM1:31 PM1:34 PM1:37 PM1:46 PM
925 wheelchair access2:24 PM2:31 PM2:34 PM2:37 PM2:46 PM
925 wheelchair access3:24 PM3:31 PM3:34 PM3:37 PM3:46 PM
925 wheelchair access4:24 PM4:31 PM4:34 PM4:37 PM4:46 PM
925 wheelchair access5:24 PM5:31 PM5:34 PM5:37 PM5:46 PM
925 wheelchair access6:24 PM6:31 PM6:34 PM6:37 PM6:46 PM
+

 

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
 Woden Interchange
+ Platform 16
Weston PrimaryHolderDuffyCooleman Court
925 wheelchair access9:57 AM10:07 AM10:09 AM10:11 AM10:19 AM
925 wheelchair access10:57 AM11:07 AM11:09 AM11:11 AM11:19 AM
925 wheelchair access11:57 AM12:07 PM12:09 PM12:11 PM12:19 PM
925 wheelchair access12:57 PM1:07 PM1:09 PM1:11 PM1:19 PM
925 wheelchair access1:57 PM2:07 PM2:09 PM2:11 PM2:19 PM
925 wheelchair access2:57 PM3:07 PM3:09 PM3:11 PM3:19 PM
925 wheelchair access3:57 PM4:07 PM4:09 PM4:11 PM4:19 PM
925 wheelchair access4:57 PM5:07 PM5:09 PM5:11 PM5:19 PM
925 wheelchair access5:57 PM6:07 PM6:09 PM6:11 PM6:19 PM
925 wheelchair access6:57 PM7:07 PM7:09 PM7:11 PM7:19 PM
+

wheelchair_icon- Wheelchair Accessible Bus

+
+ + + + + --- /dev/null +++ b/maxious-canberra-transit-feed/source-html/Route_927wkend.html @@ -1,1 +1,556 @@ + + + + + + + + +Route_927 + + + + + +
+

Chosen services: 927

+ +

View timetable and map

+

This timetable is effective from Monday 25 May 2009.

+ +

Saturdays

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
 Cooleman CourtRivettChapmanFisherWaramangaWoden Interchange
927 wheelchair access7:55 AM8:03 AM8:06 AM8:16 AM8:19 AM8:26 AM
927 wheelchair access8:55 AM9:03 AM9:06 AM9:16 AM9:19 AM9:26 AM
927 wheelchair access9:55 AM10:03 AM10:06 AM10:16 AM10:19 AM10:26 AM
927 wheelchair access10:55 AM11:03 AM11:06 AM11:16 AM11:19 AM11:26 AM
927 wheelchair access11:55 AM12:03 PM12:06 PM12:16 PM12:19 PM12:26 PM
927 12:55 PM1:03 PM1:06 PM1:16 PM1:19 PM1:26 PM
927 1:55 PM2:03 PM2:06 PM2:16 PM2:19 PM2:26 PM
927 wheelchair access2:55 PM3:03 PM3:06 PM3:16 PM3:19 PM3:26 PM
927 wheelchair access3:55 PM4:03 PM4:06 PM4:16 PM4:19 PM4:26 PM
927 wheelchair access4:55 PM5:03 PM5:06 PM5:16 PM5:19 PM5:26 PM
927 wheelchair access5:55 PM6:03 PM6:06 PM6:16 PM6:19 PM6:26 PM
927 wheelchair access6:55 PM7:03 PM7:06 PM7:16 PM7:19 PM7:26 PM
927 wheelchair access7:55 PM8:03 PM8:06 PM8:16 PM8:19 PM8:26 PM
927 wheelchair access8:55 PM9:03 PM9:06 PM9:16 PM9:19 PM9:26 PM
927 wheelchair access9:55 PM10:03 PM10:06 PM10:16 PM10:19 PM10:26 PM
927 wheelchair access10:55 PM11:03 PM11:06 PM11:16 PM11:19 PM11:26 PM
+

 

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
 Woden Interchange
+ Platform 3
WaramangaFisherChapmanRivettCooleman Court
927 wheelchair access9:20 AM9:29 AM9:32 AM9:42 AM9:45 AM9:50 AM
927 wheelchair access10:20 AM10:29 AM10:32 AM10:42 AM10:45 AM10:50 AM
927 wheelchair access11:20 AM11:29 AM11:32 AM11:42 AM11:45 AM11:50 AM
927 12:20 PM12:29 PM12:32 PM12:42 PM12:45 PM12:50 PM
927 1:20 PM1:29 PM1:32 PM1:42 PM1:45 PM1:50 PM
927 wheelchair access2:20 PM2:29 PM2:32 PM2:42 PM2:45 PM2:50 PM
927 wheelchair access3:20 PM3:29 PM3:32 PM3:42 PM3:45 PM3:50 PM
927 4:20 PM4:29 PM4:32 PM4:42 PM4:45 PM4:50 PM
927 wheelchair access5:20 PM5:29 PM5:32 PM5:42 PM5:45 PM5:50 PM
927 wheelchair access6:20 PM6:29 PM6:32 PM6:42 PM6:45 PM6:50 PM
927 wheelchair access7:20 PM7:29 PM7:32 PM7:42 PM7:45 PM7:50 PM
927 wheelchair access8:20 PM8:29 PM8:32 PM8:42 PM8:45 PM8:50 PM
927 wheelchair access9:20 PM9:29 PM9:32 PM9:42 PM9:45 PM9:50 PM
927 wheelchair access10:20 PM10:29 PM10:32 PM10:42 PM10:45 PM10:50 PM
927 wheelchair access11:20 PM11:29 PM11:32 PM11:42 PM11:45 PM11:50 PM
+

^top^

+

Sundays

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
 Cooleman CourtRivettChapmanFisherWaramangaWoden Interchange
927 wheelchair access8:55 AM9:03 AM9:06 AM9:16 AM9:19 AM9:26 AM
927 wheelchair access9:55 AM10:03 AM10:06 AM10:16 AM10:19 AM10:26 AM
927 wheelchair access10:55 AM11:03 AM11:06 AM11:16 AM11:19 AM11:26 AM
927 11:55 AM12:03 PM12:06 PM12:16 PM12:19 PM12:26 PM
927 wheelchair access12:55 PM1:03 PM1:06 PM1:16 PM1:19 PM1:26 PM
927 1:55 PM2:03 PM2:06 PM2:16 PM2:19 PM2:26 PM
927 2:55 PM3:03 PM3:06 PM3:16 PM3:19 PM3:26 PM
927 wheelchair access3:55 PM4:03 PM4:06 PM4:16 PM4:19 PM4:26 PM
927 4:55 PM5:03 PM5:06 PM5:16 PM5:19 PM5:26 PM
927 wheelchair access5:55 PM6:03 PM6:06 PM6:16 PM6:19 PM6:26 PM
927 wheelchair access6:55 PM7:03 PM7:06 PM7:16 PM7:19 PM7:26 PM
+

 

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
 Woden Interchange
+ Platform 3
WaramangaFisherChapmanRivettCooleman Court
927 wheelchair access9:20 AM9:29 AM9:32 AM9:42 AM9:45 AM9:50 AM
927 wheelchair access10:20 AM10:29 AM10:32 AM10:42 AM10:45 AM10:50 AM
927 11:20 AM11:29 AM11:32 AM11:42 AM11:45 AM11:50 AM
927 wheelchair access12:20 PM12:29 PM12:32 PM12:42 PM12:45 PM12:50 PM
927 1:20 PM1:29 PM1:32 PM1:42 PM1:45 PM1:50 PM
927 2:20 PM2:29 PM2:32 PM2:42 PM2:45 PM2:50 PM
927 wheelchair access3:20 PM3:29 PM3:32 PM3:42 PM3:45 PM3:50 PM
927 4:20 PM4:29 PM4:32 PM4:42 PM4:45 PM4:50 PM
927 wheelchair access5:20 PM5:29 PM5:32 PM5:42 PM5:45 PM5:50 PM
927 wheelchair access6:20 PM6:29 PM6:32 PM6:42 PM6:45 PM6:50 PM
+

wheelchair_icon- Wheelchair Accessible Bus

+
+ + + + + --- /dev/null +++ b/maxious-canberra-transit-feed/source-html/Route_930wkend.html @@ -1,1 +1,173 @@ + + + + + + + + +Route_930 + + + + + +
+

Chosen services: 930

+ +

View timetable and map

+

This timetable is effective from Monday 25 May 2009.

+ +

Saturday

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
 City Interchange
+ Platform 8
St Thomas More CampbellHospiceADFACity Interchange
930 wheelchair access10:01 AM10:13 AM10:20 AM10:27 AM10:41 AM
930 12:01 PM12:13 PM12:20 PM12:27 PM12:41 PM
930 wheelchair access2:01 PM2:13 PM2:20 PM2:27 PM2:41 PM
930 4:01 PM4:13 PM4:20 PM4:27 PM4:41 PM
930 6:01 PM6:13 PM6:20 PM6:27 PM6:41 PM
930 8:01 PM8:13 PM8:20 PM8:27 PM8:41 PM
930 wheelchair access9:01 PM9:13 PM9:20 PM9:27 PM9:41 PM
930 wheelchair access10:01 PM10:13 PM10:20 PM10:27 PM10:41 PM
930 11:01 PM11:13 PM11:20 PM11:27 PM11:41 PM
+

Sunday

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
 City Interchange
+ Platform 8
St Thomas More CampbellHospiceADFACity Interchange
930 10:01 AM10:13 AM10:20 AM10:27 AM10:41 AM
930 12:01 PM12:13 PM12:20 PM12:27 PM12:41 PM
930 2:01 PM2:13 PM2:20 PM2:27 PM2:41 PM
930 wheelchair access4:01 PM4:13 PM4:20 PM4:27 PM4:41 PM
930 wheelchair access6:01 PM6:13 PM6:20 PM6:27 PM6:41 PM
+

wheelchair_icon- Wheelchair Accessible Bus

+
+ + + + + --- /dev/null +++ b/maxious-canberra-transit-feed/source-html/Route_931wkend.html @@ -1,1 +1,166 @@ + + + + + + + + +Route_931 + + + + + +
+

Chosen services: 931

+ +

View timetable and map

+

This timetable is effective from Monday 25 May 2009.

+ +

Saturdays

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
 City Interchange
+ Platform 8
ADFAHospiceSt Thomas More CampbellCity Interchange
931 8:01 AM8:15 AM8:22 AM8:29 AM8:41 AM
931 9:01 AM9:15 AM9:22 AM9:29 AM9:41 AM
931 wheelchair access11:01 AM11:15 AM11:22 AM11:29 AM11:41 AM
931 wheelchair access1:01 PM1:15 PM1:22 PM1:29 PM1:41 PM
931 3:01 PM3:15 PM3:22 PM3:29 PM3:41 PM
931 5:01 PM5:15 PM5:22 PM5:29 PM5:41 PM
931 7:01 PM7:15 PM7:22 PM7:29 PM7:41 PM
+

^top^

+

Sundays

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
 City Interchange
+ Platform 8
ADFAHospiceSt Thomas More CampbellCity Interchange
931 9:01 AM9:15 AM9:22 AM9:29 AM9:41 AM
931 wheelchair access11:01 AM11:15 AM11:22 AM11:29 AM11:41 AM
931 wheelchair access1:01 PM1:15 PM1:22 PM1:29 PM1:41 PM
931 wheelchair access3:01 PM3:15 PM3:22 PM3:29 PM3:41 PM
931 5:01 PM5:15 PM5:22 PM5:29 PM5:41 PM
931 wheelchair access7:01 PM7:15 PM7:22 PM7:29 PM7:41 PM
+

wheelchair_icon- Wheelchair Accessible Bus

+
+ + + + + --- /dev/null +++ b/maxious-canberra-transit-feed/source-html/Route_932wkend.html @@ -1,1 +1,1067 @@ + + + + + + + + +Route_932 + + + + + +
+

Chosen services: 932

+ +

View timetable and map

+

This timetable is effective from Monday 25 May 2009.

+ +

Saturdays

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
 Woden Interchange
+ Platform 4
CurtinJohn James HospitalYarralumla ShopsCity Interchange
+ Platform 8
Macarthur\NorthbourneSouthwell ParkGiralangKaleen Village/MaribyrnongGwydir Square KaleenUniversity of CanberraCameron Ave Bus StationLathlain St Bus StationCohen St Bus Station
932 wheelchair access7:39 AM7:50 AM7:53 AM7:56 AM8:09 AM8:15 AM8:19 AM8:28 AM8:36 AM8:41 AM8:47 AM8:50 AM8:52 AM8:56 AM
932 wheelchair access8:39 AM8:50 AM8:53 AM8:56 AM9:09 AM9:15 AM9:19 AM9:28 AM9:36 AM9:41 AM9:47 AM9:50 AM9:52 AM9:56 AM
932 9:39 AM9:50 AM9:53 AM9:56 AM10:09 AM10:15 AM10:19 AM10:28 AM10:36 AM10:41 AM10:47 AM10:50 AM10:52 AM10:56 AM
932 10:39 AM10:50 AM10:53 AM10:56 AM11:09 AM11:15 AM11:19 AM11:28 AM11:36 AM11:41 AM11:47 AM11:50 AM11:52 AM11:56 AM
932 wheelchair access11:39 AM11:50 AM11:53 AM11:56 AM12:09 PM12:15 PM12:19 PM12:28 PM12:36 PM12:41 PM12:47 PM12:50 PM12:52 PM12:56 PM
932 wheelchair access12:39 PM12:50 PM12:53 PM12:56 PM1:09 PM1:15 PM1:19 PM1:28 PM1:36 PM1:41 PM1:47 PM1:50 PM1:52 PM1:56 PM
932 1:39 PM1:50 PM1:53 PM1:56 PM2:09 PM2:15 PM2:19 PM2:28 PM2:36 PM2:41 PM2:47 PM2:50 PM2:52 PM2:56 PM
932 2:39 PM2:50 PM2:53 PM2:56 PM3:09 PM3:15 PM3:19 PM3:28 PM3:36 PM3:41 PM3:47 PM3:50 PM3:52 PM3:56 PM
932 3:39 PM3:50 PM3:53 PM3:56 PM4:09 PM4:15 PM4:19 PM4:28 PM4:36 PM4:41 PM4:47 PM4:50 PM4:52 PM4:56 PM
932 wheelchair access4:39 PM4:50 PM4:53 PM4:56 PM5:09 PM5:15 PM5:19 PM5:28 PM5:36 PM5:41 PM5:47 PM5:50 PM5:52 PM5:56 PM
932 wheelchair access5:39 PM5:50 PM5:53 PM5:56 PM6:09 PM6:15 PM6:19 PM6:28 PM6:35 PM6:40 PM6:45 PM6:47 PM6:49 PM6:52 PM
932 wheelchair access6:39 PM6:48 PM6:51 PM6:54 PM7:07 PM7:12 PM7:16 PM7:25 PM7:32 PM7:37 PM7:42 PM7:44 PM7:46 PM7:49 PM
932 wheelchair access7:39 PM7:48 PM7:51 PM7:54 PM8:07 PM8:12 PM8:16 PM8:25 PM8:32 PM8:37 PM8:42 PM8:44 PM8:46 PM8:49 PM
932 8:39 PM8:48 PM8:51 PM8:54 PM9:07 PM9:12 PM9:16 PM9:25 PM9:32 PM9:37 PM9:42 PM9:44 PM9:46 PM9:49 PM
932 wheelchair access9:39 PM9:48 PM9:51 PM9:54 PM10:07 PM10:12 PM10:16 PM10:25 PM10:32 PM10:37 PM10:42 PM10:44 PM10:46 PM10:49 PM
932 wheelchair access10:39 PM10:48 PM10:51 PM10:54 PM11:07 PM11:12 PM11:16 PM11:25 PM11:32 PM11:37 PM11:42 PM11:44 PM11:46 PM11:49 PM
932 wheelchair access11:39 PM11:50 PM11:53 PM11:56 PM12.08 AM .............................................
+

 

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
 Cohen St Bus Station
+ Platform 2
Lathlain St Bus Station
+ Platform 3
Cameron Ave Bus Station
+ Platform 3
University of CanberraGwydir Square KaleenKaleen Village/MaribyrnongGiralangSouthwell ParkMacarthur\NorthbourneCity Interchange
+ Platform 9
Yarralumla ShopsJohn James HospitalCurtinWoden Interchange
932 wheelchair access7:49 AM7:51 AM7:55 AM7:58 AM8:04 AM8:09 AM8:11 AM8:26 AM8:31 AM8:38 AM8:50 AM8:53 AM8:57 AM9:08 AM
932 8:49 AM8:51 AM8:55 AM8:58 AM9:04 AM9:09 AM9:11 AM9:26 AM9:31 AM9:38 AM9:50 AM9:53 AM9:57 AM10:08 AM
932 9:49 AM9:51 AM9:55 AM9:58 AM10:04 AM10:09 AM10:11 AM10:26 AM10:31 AM10:38 AM10:50 AM10:53 AM10:57 AM11:08 AM
932 10:49 AM10:51 AM10:55 AM10:58 AM11:04 AM11:09 AM11:11 AM11:26 AM11:31 AM11:38 AM11:50 AM11:53 AM11:57 AM12:08 PM
932 11:49 AM11:51 AM11:55 AM11:58 AM12:04 PM12:09 PM12:11 PM12:26 PM12:31 PM12:38 PM12:50 PM12:53 PM12:57 PM1:08 PM
932 12:49 PM12:51 PM12:55 PM12:58 PM1:04 PM1:09 PM1:11 PM1:26 PM1:31 PM1:38 PM1:50 PM1:53 PM1:57 PM2:08 PM
932 wheelchair access1:49 PM1:51 PM1:55 PM1:58 PM2:04 PM2:09 PM2:11 PM2:26 PM2:31 PM2:38 PM2:50 PM2:53 PM2:57 PM3:08 PM
932 2:49 PM2:51 PM2:55 PM2:58 PM3:04 PM3:09 PM3:11 PM3:26 PM3:31 PM3:38 PM3:50 PM3:53 PM3:57 PM4:08 PM
932 wheelchair access3:49 PM3:51 PM3:55 PM3:58 PM4:04 PM4:09 PM4:11 PM4:26 PM4:31 PM4:38 PM4:50 PM4:53 PM4:57 PM5:08 PM
932 wheelchair access4:49 PM4:51 PM4:55 PM4:58 PM5:04 PM5:09 PM5:11 PM5:26 PM5:31 PM5:38 PM5:50 PM5:53 PM5:57 PM6:08 PM
932 wheelchair access5:49 PM5:51 PM5:55 PM5:58 PM6:04 PM6:09 PM6:11 PM6:26 PM6:31 PM6:37 PM6:49 PM6:52 PM6:55 PM7:05 PM
932 wheelchair access6:50 PM6:52 PM6:55 PM6:57 PM7:02 PM7:07 PM7:09 PM7:24 PM7:29 PM7:35 PM7:47 PM7:50 PM7:53 PM8:03 PM
932 wheelchair access7:50 PM7:52 PM7:55 PM7:57 PM8:02 PM8:07 PM8:09 PM8:24 PM8:29 PM8:35 PM8:47 PM8:50 PM8:53 PM9:03 PM
932 wheelchair access8:50 PM8:52 PM8:55 PM8:57 PM9:02 PM9:07 PM9:09 PM9:24 PM9:29 PM9:35 PM9:47 PM9:50 PM9:53 PM10:03 PM
932 wheelchair access9:50 PM9:52 PM9:55 PM9:57 PM10:02 PM10:07 PM10:09 PM10:24 PM10:29 PM10:35 PM10:47 PM10:50 PM10:53 PM11:03 PM
932 wheelchair access10:50 PM10:52 PM10:55 PM10:57 PM11:02 PM11:07 PM11:09 PM11:24 PM11:29 PM11.34 PM ....................
+

^top^

+

Sundays

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
 Woden Interchange
+ Platform 4
CurtinJohn James HospitalYarralumla ShopsCity Interchange
+ Platform 8
Macarthur\NorthbourneSouthwell ParkGiralangKaleen Village/MaribyrnongGwydir Square KaleenUniversity of CanberraCameron Ave Bus StationLathlain St Bus StationCohen St Bus Station
932 wheelchair access8:39 AM8:50 AM8:53 AM8:56 AM9:09 AM9:15 AM9:19 AM9:28 AM9:36 AM9:41 AM9:47 AM9:50 AM9:52 AM9:56 AM
932 9:39 AM9:50 AM9:53 AM9:56 AM10:09 AM10:15 AM10:19 AM10:28 AM10:36 AM10:41 AM10:47 AM10:50 AM10:52 AM10:56 AM
932 wheelchair access10:39 AM10:50 AM10:53 AM10:56 AM11:09 AM11:15 AM11:19 AM11:28 AM11:36 AM11:41 AM11:47 AM11:50 AM11:52 AM11:56 AM
932 11:39 AM11:50 AM11:53 AM11:56 AM12:09 PM12:15 PM12:19 PM12:28 PM12:36 PM12:41 PM12:47 PM12:50 PM12:52 PM12:56 PM
932 wheelchair access12:39 PM12:50 PM12:53 PM12:56 PM1:09 PM1:15 PM1:19 PM1:28 PM1:36 PM1:41 PM1:47 PM1:50 PM1:52 PM1:56 PM
932 1:39 PM1:50 PM1:53 PM1:56 PM2:09 PM2:15 PM2:19 PM2:28 PM2:36 PM2:41 PM2:47 PM2:50 PM2:52 PM2:56 PM
932 2:39 PM2:50 PM2:53 PM2:56 PM3:09 PM3:15 PM3:19 PM3:28 PM3:36 PM3:41 PM3:47 PM3:50 PM3:52 PM3:56 PM
932 3:39 PM3:50 PM3:53 PM3:56 PM4:09 PM4:15 PM4:19 PM4:28 PM4:36 PM4:41 PM4:47 PM4:50 PM4:52 PM4:56 PM
932 4:39 PM4:50 PM4:53 PM4:56 PM5:09 PM5:15 PM5:19 PM5:28 PM5:36 PM5:41 PM5:47 PM5:50 PM5:52 PM5:56 PM
932 5:39 PM5:50 PM5:53 PM5:56 PM6:09 PM6:15 PM6:19 PM6:28 PM6:35 PM6:40 PM6:45 PM6:47 PM6:49 PM6:52 PM
932 6:39 PM6:48 PM6:51 PM6:54 PM7:07 PM7:12 PM7:16 PM7:25 PM7:32 PM7:37 PM7:42 PM7:44 PM7:46 PM7:49 PM
+

 

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
 Cohen St Bus Station
+ Platform 2
Lathlain St Bus Station
+ Platform 3
Cameron Ave Bus Station
+ Platform 3
University of CanberraGwydir Square KaleenKaleen Village/MaribyrnongGiralangSouthwell ParkMacarthur\NorthbourneCity Interchange
+ Platform 9
Yarralumla ShopsJohn James HospitalCurtinWoden Interchange
932 wheelchair access7:49 AM7:51 AM7:55 AM7:58 AM8:04 AM8:09 AM8:11 AM8:26 AM8:31 AM8:38 AM8:50 AM8:53 AM8:57 AM9:08 AM
932 wheelchair access8:49 AM8:51 AM8:55 AM8:58 AM9:04 AM9:09 AM9:11 AM9:26 AM9:31 AM9:38 AM9:50 AM9:53 AM9:57 AM10:08 AM
932 9:49 AM9:51 AM9:55 AM9:58 AM10:04 AM10:09 AM10:11 AM10:26 AM10:31 AM10:38 AM10:50 AM10:53 AM10:57 AM11:08 AM
932 10:49 AM10:51 AM10:55 AM10:58 AM11:04 AM11:09 AM11:11 AM11:26 AM11:31 AM11:38 AM11:50 AM11:53 AM11:57 AM12:08 PM
932 11:49 AM11:51 AM11:55 AM11:58 AM12:04 PM12:09 PM12:11 PM12:26 PM12:31 PM12:38 PM12:50 PM12:53 PM12:57 PM1:08 PM
932 12:49 PM12:51 PM12:55 PM12:58 PM1:04 PM1:09 PM1:11 PM1:26 PM1:31 PM1:38 PM1:50 PM1:53 PM1:57 PM2:08 PM
932 wheelchair access1:49 PM1:51 PM1:55 PM1:58 PM2:04 PM2:09 PM2:11 PM2:26 PM2:31 PM2:38 PM2:50 PM2:53 PM2:57 PM3:08 PM
932 2:49 PM2:51 PM2:55 PM2:58 PM3:04 PM3:09 PM3:11 PM3:26 PM3:31 PM3:38 PM3:50 PM3:53 PM3:57 PM4:08 PM
932 wheelchair access3:49 PM3:51 PM3:55 PM3:58 PM4:04 PM4:09 PM4:11 PM4:26 PM4:31 PM4:38 PM4:50 PM4:53 PM4:57 PM5:08 PM
932 wheelchair access4:49 PM4:51 PM4:55 PM4:58 PM5:04 PM5:09 PM5:11 PM5:26 PM5:31 PM5:38 PM5:50 PM5:53 PM5:57 PM6:08 PM
932 wheelchair access5:49 PM5:51 PM5:55 PM5:58 PM6:04 PM6:09 PM6:11 PM6:26 PM6:31 PM6:37 PM6:49 PM6:52 PM6:55 PM7:05 PM
+

wheelchair_icon- Wheelchair Accessible Bus

+
+ + + + + --- /dev/null +++ b/maxious-canberra-transit-feed/source-html/Route_934wkend.html @@ -1,1 +1,1011 @@ + + + + + + + + +Route_934 + + + + + + + +
+

Chosen services: 934

+ +

View timetable and map

+

This timetable is effective from Monday 25 May 2009.

+ + +

Saturday

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
 Cohen St Bus Station
+ Platform 2
Lathlain St Bus Station
+ Platform 3
Cameron Ave Bus Station
+ Platform 3
Calvary HospitalO'ConnorBurton & Garranan Hall, Daley Road ANUNational MuseumCity Interchange
+ Platform 7
Kings Ave/National CrtDeakinHughesGarranWoden Interchange
934 wheelchair access7:31 AM7:33 AM7:37 AM7:53 AM8:00 AM8:05 AM8:10 AM8:19 AM8:28 AM8:37 AM8:42 AM8:46 AM8:55 AM
934 wheelchair access8:31 AM8:33 AM8:37 AM8:53 AM9:00 AM9:05 AM9:10 AM9:19 AM9:28 AM9:37 AM9:42 AM9:46 AM9:55 AM
934 wheelchair access9:31 AM9:33 AM9:37 AM9:53 AM10:00 AM10:05 AM10:10 AM10:19 AM10:28 AM10:37 AM10:42 AM10:46 AM10:55 AM
934 wheelchair access10:31 AM10:33 AM10:37 AM10:53 AM11:00 AM11:05 AM11:10 AM11:19 AM11:28 AM11:37 AM11:42 AM11:46 AM11:55 AM
934 wheelchair access11:31 AM11:33 AM11:37 AM11:53 AM12:00 PM12:05 PM12:10 PM12:19 PM12:28 PM12:37 PM12:42 PM12:46 PM12:55 PM
934 wheelchair access12:31 PM12:33 PM12:37 PM12:53 PM1:00 PM1:05 PM1:10 PM1:19 PM1:28 PM1:37 PM1:42 PM1:46 PM1:55 PM
934 wheelchair access1:31 PM1:33 PM1:37 PM1:53 PM2:00 PM2:05 PM2:10 PM2:19 PM2:28 PM2:37 PM2:42 PM2:46 PM2:55 PM
934 wheelchair access2:31 PM2:33 PM2:37 PM2:53 PM3:00 PM3:05 PM3:10 PM3:19 PM3:28 PM3:37 PM3:42 PM3:46 PM3:55 PM
934 wheelchair access3:31 PM3:33 PM3:37 PM3:53 PM4:00 PM4:05 PM4:10 PM4:19 PM4:28 PM4:37 PM4:42 PM4:46 PM4:55 PM
934 wheelchair access4:31 PM4:33 PM4:37 PM4:53 PM5:00 PM5:05 PM5:10 PM5:19 PM5:28 PM5:37 PM5:42 PM5:46 PM5:55 PM
934 wheelchair access5:31 PM5:33 PM5:37 PM5:53 PM6:00 PM6:05 PM6:10 PM6:19 PM6:28 PM6:37 PM6:42 PM6:46 PM6:55 PM
934 wheelchair access6:32 PM6:34 PM6:37 PM6:53 PM7:00 PM7:05 PM7:10 PM7:19 PM7:28 PM7:37 PM7:42 PM7:46 PM7:55 PM
934 wheelchair access7:32 PM7:34 PM7:37 PM7:53 PM8:00 PM8:05 PM8:10 PM8:19 PM8:28 PM8:37 PM8:42 PM8:46 PM8:55 PM
934 wheelchair access8:32 PM8:34 PM8:37 PM8:53 PM9:00 PM9:05 PM9:10 PM9:19 PM9:28 PM9:37 PM9:42 PM9:46 PM9:55 PM
934 wheelchair access9:32 PM9:34 PM9:37 PM9:53 PM10:00 PM10:05 PM10:10 PM10:19 PM10:28 PM10:37 PM10:42 PM10:46 PM10:55 PM
934 wheelchair access10:32 PM10:34 PM10:37 PM10:53 PM11:00 PM11:05 PM11:10 PM11.17 PM .........................
+

 

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
 Woden Interchange
+ Platform 14
GarranHughesDeakinKings Ave/National CrtCity Interchange
+ Platform 4
National MuseumBurton & Garranan Hall, Daley Road ANUO'ConnorCalvary HospitalCameron Ave Bus StationLathlain St Bus StationCohen St Bus Station
934 wheelchair access.........................7:52 AM7:59 AM8:04 AM8:09 AM8:16 AM8:32 AM8:34 AM8:38 AM
934 wheelchair access8:13 AM8:22 AM8:26 AM8:31 AM8:40 AM8:52 AM8:59 AM9:04 AM9:09 AM9:16 AM9:32 AM9:34 AM9:38 AM
934 wheelchair access9:13 AM9:22 AM9:26 AM9:31 AM9:40 AM9:52 AM9:59 AM10:04 AM10:09 AM10:16 AM10:32 AM10:34 AM10:38 AM
934 wheelchair access10:13 AM10:22 AM10:26 AM10:31 AM10:40 AM10:52 AM10:59 AM11:04 AM11:09 AM11:16 AM11:32 AM11:34 AM11:38 AM
934 wheelchair access11:13 AM11:22 AM11:26 AM11:31 AM11:40 AM11:52 AM11:59 AM12:04 PM12:09 PM12:16 PM12:32 PM12:34 PM12:38 PM
934 wheelchair access12:13 PM12:22 PM12:26 PM12:31 PM12:40 PM12:52 PM12:59 PM1:04 PM1:09 PM1:16 PM1:32 PM1:34 PM1:38 PM
934 wheelchair access1:13 PM1:22 PM1:26 PM1:31 PM1:40 PM1:52 PM1:59 PM2:04 PM2:09 PM2:16 PM2:32 PM2:34 PM2:38 PM
934 2:13 PM2:22 PM2:26 PM2:31 PM2:40 PM2:52 PM2:59 PM3:04 PM3:09 PM3:16 PM3:32 PM3:34 PM3:38 PM
934 wheelchair access3:13 PM3:22 PM3:26 PM3:31 PM3:40 PM3:52 PM3:59 PM4:04 PM4:09 PM4:16 PM4:32 PM4:34 PM4:38 PM
934 wheelchair access4:13 PM4:22 PM4:26 PM4:31 PM4:40 PM4:52 PM4:59 PM5:04 PM5:09 PM5:16 PM5:32 PM5:34 PM5:38 PM
934 wheelchair access5:13 PM5:22 PM5:26 PM5:31 PM5:40 PM5:52 PM5:59 PM6:04 PM6:09 PM6:16 PM6:32 PM6:34 PM6:37 PM
934 wheelchair access6:13 PM6:22 PM6:26 PM6:31 PM6:40 PM6:52 PM6:59 PM7:04 PM7:09 PM7:16 PM7:32 PM7:34 PM7:37 PM
934 wheelchair access7:13 PM7:22 PM7:26 PM7:31 PM7:40 PM7:52 PM7:59 PM8:04 PM8:09 PM8:16 PM8:32 PM8:34 PM8:37 PM
934 wheelchair access8:13 PM8:22 PM8:26 PM8:31 PM8:40 PM8:52 PM8:59 PM9:04 PM9:09 PM9:16 PM9:32 PM9:34 PM9:37 PM
934 wheelchair access9:13 PM9:22 PM9:26 PM9:31 PM9:40 PM9:52 PM9:59 PM10:04 PM10:09 PM10:16 PM10:32 PM10:34 PM10:37 PM
934 wheelchair access10:13 PM10:22 PM10:26 PM10:31 PM10:40 PM10:52 PM10:59 PM11:04 PM11:09 PM11:16 PM11:32 PM11:34 PM11:37 PM
934 wheelchair access11:13 PM11:22 PM11:26 PM11:31 PM11:40 PM11.50 PM ...................................
+

^top^

+

Sunday

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
 Cohen St Bus Station
+ Platform 2
Lathlain St Bus Station
+ Platform 3
Cameron Ave Bus Station
+ Platform 3
Calvary HospitalO'ConnorBurton & Garranan Hall, Daley Road ANUNational MuseumCity Interchange
+ Platform 7
Kings Ave/National CrtDeakinHughesGarranWoden Interchange
934 wheelchair access8:31 AM8:33 AM8:37 AM8:53 AM9:00 AM9:05 AM9:10 AM9:19 AM9:28 AM9:37 AM9:42 AM9:46 AM9:55 AM
934 9:31 AM9:33 AM9:37 AM9:53 AM10:00 AM10:05 AM10:10 AM10:19 AM10:28 AM10:37 AM10:42 AM10:46 AM10:55 AM
934 wheelchair access10:31 AM10:33 AM10:37 AM10:53 AM11:00 AM11:05 AM11:10 AM11:19 AM11:28 AM11:37 AM11:42 AM11:46 AM11:55 AM
934 wheelchair access11:31 AM11:33 AM11:37 AM11:53 AM12:00 PM12:05 PM12:10 PM12:19 PM12:28 PM12:37 PM12:42 PM12:46 PM12:55 PM
934 wheelchair access12:31 PM12:33 PM12:37 PM12:53 PM1:00 PM1:05 PM1:10 PM1:19 PM1:28 PM1:37 PM1:42 PM1:46 PM1:55 PM
934 wheelchair access1:31 PM1:33 PM1:37 PM1:53 PM2:00 PM2:05 PM2:10 PM2:19 PM2:28 PM2:37 PM2:42 PM2:46 PM2:55 PM
934 2:31 PM2:33 PM2:37 PM2:53 PM3:00 PM3:05 PM3:10 PM3:19 PM3:28 PM3:37 PM3:42 PM3:46 PM3:55 PM
934 wheelchair access3:31 PM3:33 PM3:37 PM3:53 PM4:00 PM4:05 PM4:10 PM4:19 PM4:28 PM4:37 PM4:42 PM4:46 PM4:55 PM
934 wheelchair access4:31 PM4:33 PM4:37 PM4:53 PM5:00 PM5:05 PM5:10 PM5:19 PM5:28 PM5:37 PM5:42 PM5:46 PM5:55 PM
934 5:31 PM5:33 PM5:37 PM5:53 PM6:00 PM6:05 PM6:10 PM6:19 PM6:28 PM6:37 PM6:42 PM6:46 PM6:55 PM
934 wheelchair access6:32 PM6:34 PM6:37 PM6:53 PM7:00 PM7:05 PM7:10 PM7:19 PM7:28 PM7:37 PM7:42 PM7:46 PM7:55 PM
+

 

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
 Woden Interchange
+ Platform 14
GarranHughesDeakinKings Ave/National CrtCity Interchange
+ Platform 4
National MuseumBurton & Garranan Hall,Daley Road ANUO'ConnorCalvary HospitalCameron Ave Bus StationLathlain St Bus StationCohen St Bus Station
934 wheelchair access8:13 AM8:22 AM8:26 AM8:31 AM8:40 AM8:52 AM8:59 AM9:04 AM9:09 AM9:16 AM9:32 AM9:34 AM9:38 AM
934 wheelchair access9:13 AM9:22 AM9:26 AM9:31 AM9:40 AM9:52 AM9:59 AM10:04 AM10:09 AM10:16 AM10:32 AM10:34 AM10:38 AM
934 wheelchair access10:13 AM10:22 AM10:26 AM10:31 AM10:40 AM10:52 AM10:59 AM11:04 AM11:09 AM11:16 AM11:32 AM11:34 AM11:38 AM
934 wheelchair access11:13 AM11:22 AM11:26 AM11:31 AM11:40 AM11:52 AM11:59 AM12:04 PM12:09 PM12:16 PM12:32 PM12:34 PM12:38 PM
934 wheelchair access12:13 PM12:22 PM12:26 PM12:31 PM12:40 PM12:52 PM12:59 PM1:04 PM1:09 PM1:16 PM1:32 PM1:34 PM1:38 PM
934 wheelchair access1:13 PM1:22 PM1:26 PM1:31 PM1:40 PM1:52 PM1:59 PM2:04 PM2:09 PM2:16 PM2:32 PM2:34 PM2:38 PM
934 wheelchair access2:13 PM2:22 PM2:26 PM2:31 PM2:40 PM2:52 PM2:59 PM3:04 PM3:09 PM3:16 PM3:32 PM3:34 PM3:38 PM
934 3:13 PM3:22 PM3:26 PM3:31 PM3:40 PM3:52 PM3:59 PM4:04 PM4:09 PM4:16 PM4:32 PM4:34 PM4:38 PM
934 wheelchair access4:13 PM4:22 PM4:26 PM4:31 PM4:40 PM4:52 PM4:59 PM5:04 PM5:09 PM5:16 PM5:32 PM5:34 PM5:38 PM
934 wheelchair access5:13 PM5:22 PM5:26 PM5:31 PM5:40 PM5:52 PM5:59 PM6:04 PM6:09 PM6:16 PM6:32 PM6:34 PM6:37 PM
934 wheelchair access6:13 PM6:22 PM6:26 PM6:31 PM6:40 PM6:52 PM6:59 PM7:04 PM7:09 PM7:16 PM7:32 PM7:34 PM7:37 PM
+

wheelchair_icon- Wheelchair Accessible Bus

+
+ + + + --- /dev/null +++ b/maxious-canberra-transit-feed/source-html/Route_935wkend.html @@ -1,1 +1,406 @@ + + + + + + + + +Route_935 + + + + + +
+

Chosen services: 935

+ +

View timetable and map

+

This timetable is effective from Monday 25 May 2009.

+ +

Saturdays

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
 City Interchange
+ Platform 7
Kings Ave/National CrtManukaRed HillNarrabundah TerminusRed HillManukaKings Ave/National CrtCity Interchange
935 7:56 AM8:03 AM8:07 AM8:14 AM8:24 AM8:33 AM8:39 AM8:43 AM8:52 AM
935 wheelchair access8:56 AM9:03 AM9:07 AM9:14 AM9:24 AM9:33 AM9:39 AM9:43 AM9:52 AM
935 wheelchair access9:56 AM10:03 AM10:07 AM10:14 AM10:24 AM10:33 AM10:39 AM10:43 AM10:51 AM
935 wheelchair access10:56 AM11:03 AM11:07 AM11:14 AM11:24 AM11:33 AM11:39 AM11:43 AM11:52 AM
935 wheelchair access11:56 AM12:03 PM12:07 PM12:14 PM12:24 PM12:33 PM12:39 PM12:43 PM12:51 PM
935 wheelchair access12:56 PM1:03 PM1:07 PM1:14 PM1:24 PM1:33 PM1:39 PM1:43 PM1:51 PM
935 wheelchair access1:56 PM2:03 PM2:07 PM2:14 PM2:24 PM2:33 PM2:39 PM2:43 PM2:52 PM
935 wheelchair access2:56 PM3:03 PM3:07 PM3:14 PM3:24 PM3:33 PM3:39 PM3:43 PM3:51 PM
935 wheelchair access3:56 PM4:03 PM4:07 PM4:14 PM4:24 PM4:33 PM4:39 PM4:43 PM4:51 PM
935 wheelchair access4:56 PM5:03 PM5:07 PM5:14 PM5:24 PM5:33 PM5:39 PM5:43 PM5:52 PM
935 wheelchair access5:56 PM6:03 PM6:07 PM6:14 PM6:24 PM6:33 PM6:39 PM6:43 PM6:52 PM
935 6:56 PM7:03 PM7:07 PM7:14 PM7:24 PM7:33 PM7:39 PM7:43 PM7:52 PM
935 wheelchair access7:56 PM8:03 PM8:07 PM8:14 PM8:24 PM8:33 PM8:39 PM8:43 PM8:52 PM
935 8:56 PM9:03 PM9:07 PM9:14 PM9:24 PM9:33 PM9:39 PM9:43 PM9:52 PM
935 wheelchair access9:56 PM10:03 PM10:07 PM10:14 PM10:24 PM10:33 PM10:39 PM10:43 PM10:52 PM
935 wheelchair access10:56 PM11:03 PM11:07 PM11:14 PM11:24 PM....................
+

^top^

+

Sundays

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
 City Interchange
+ Platform 7
Kings Ave/National CrtManukaRed HillNarrabundah TerminusRed HillManukaKings Ave/National CrtCity Interchange
935 wheelchair access....................8:24 AM8:33 AM8:39 AM8:43 AM8:52 AM
935 wheelchair access8:56 AM9:03 AM9:07 AM9:14 AM9:24 AM9:33 AM9:39 AM9:43 AM9:51 AM
935 wheelchair access9:56 AM10:03 AM10:07 AM10:14 AM10:24 AM10:33 AM10:39 AM10:43 AM10:51 AM
935 wheelchair access10:56 AM11:03 AM11:07 AM11:14 AM11:24 AM11:33 AM11:39 AM11:43 AM11:52 AM
935 wheelchair access11:56 AM12:03 PM12:07 PM12:14 PM12:24 PM12:33 PM12:39 PM12:43 PM12:52 PM
935 12:56 PM1:03 PM1:07 PM1:14 PM1:24 PM1:33 PM1:39 PM1:43 PM1:52 PM
935 wheelchair access1:56 PM2:03 PM2:07 PM2:14 PM2:24 PM2:33 PM2:39 PM2:43 PM2:52 PM
935 2:56 PM3:03 PM3:07 PM3:14 PM3:24 PM3:33 PM3:39 PM3:43 PM3:52 PM
935 3:56 PM4:03 PM4:07 PM4:14 PM4:24 PM4:33 PM4:39 PM4:43 PM4:52 PM
935 4:56 PM5:03 PM5:07 PM5:14 PM5:24 PM5:33 PM5:39 PM5:43 PM5:51 PM
935 wheelchair access5:56 PM6:03 PM6:07 PM6:14 PM6:24 PM6:33 PM6:39 PM6:43 PM6:52 PM
935 wheelchair access6:56 PM7:03 PM7:07 PM7:14 PM7:24 PM....................
+

wheelchair_icon- Wheelchair Accessible Bus

+
+ + + + + --- /dev/null +++ b/maxious-canberra-transit-feed/source-html/Route_936wkend.html @@ -1,1 +1,390 @@ + + + + + + + + +Route_936 + + + + + + + +
+

Chosen services: 936

+ +

View timetable and map

+

This timetable is effective from Monday 25 May 2009.

+ + +

Saturday

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
 City Interchange
+ Platform 4
Macarthur\Miller O'ConnorLynehamNorth LynehamDicksonHackettAinslieCity Interchange
936 7:18 AM7:27 AM7:30 AM7:35 AM7:44 AM7:49 AM7:57 AM8:09 AM
936 8:18 AM8:27 AM8:30 AM8:35 AM8:44 AM8:49 AM8:57 AM9:09 AM
936 wheelchair access9:18 AM9:27 AM9:30 AM9:35 AM9:44 AM9:49 AM9:57 AM10:09 AM
936 wheelchair access10:18 AM10:27 AM10:30 AM10:35 AM10:44 AM10:49 AM10:57 AM11:09 AM
936 wheelchair access11:18 AM11:27 AM11:30 AM11:35 AM11:44 AM11:49 AM11:57 AM12:09 PM
936 12:18 PM12:27 PM12:30 PM12:35 PM12:44 PM12:49 PM12:57 PM1:09 PM
936 wheelchair access1:18 PM1:27 PM1:30 PM1:35 PM1:44 PM1:49 PM1:57 PM2:09 PM
936 wheelchair access2:18 PM2:27 PM2:30 PM2:35 PM2:44 PM2:49 PM2:57 PM3:09 PM
936 3:18 PM3:27 PM3:30 PM3:35 PM3:44 PM3:49 PM3:57 PM4:09 PM
936 4:18 PM4:27 PM4:30 PM4:35 PM4:44 PM4:49 PM4:57 PM5:09 PM
936 5:18 PM5:27 PM5:30 PM5:35 PM5:44 PM5:49 PM5:57 PM6:09 PM
936 6:18 PM6:27 PM6:30 PM6:35 PM6:44 PM6:49 PM6:57 PM7:09 PM
936 wheelchair access7:18 PM7:27 PM7:30 PM7:35 PM7:44 PM7:49 PM7:57 PM8:09 PM
936 wheelchair access8:18 PM8:27 PM8:30 PM8:35 PM8:44 PM8:49 PM8:57 PM9:09 PM
936 9:18 PM9:27 PM9:30 PM9:35 PM9:44 PM9:49 PM9:57 PM10:09 PM
936 wheelchair access10:18 PM10:27 PM10:30 PM10:35 PM10:44 PM10:49 PM10:57 PM11:09 PM
936 11:18 PM11:27 PM11:30 PM11:35 PM11:44 PM...............
+

^top^

+

Sunday

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
 City Interchange
+ Platform 4
Macarthur\Miller O'ConnorLynehamNorth LynehamDicksonHackettAinslieCity Interchange
936 8:18 AM8:27 AM8:30 AM8:35 AM8:44 AM8:49 AM8:57 AM9:09 AM
936 wheelchair access9:18 AM9:27 AM9:30 AM9:35 AM9:44 AM9:49 AM9:57 AM10:09 AM
936 10:18 AM10:27 AM10:30 AM10:35 AM10:44 AM10:49 AM10:57 AM11:09 AM
936 wheelchair access11:18 AM11:27 AM11:30 AM11:35 AM11:44 AM11:49 AM11:57 AM12:09 PM
936 12:18 PM12:27 PM12:30 PM12:35 PM12:44 PM12:49 PM12:57 PM1:09 PM
936 1:18 PM1:27 PM1:30 PM1:35 PM1:44 PM1:49 PM1:57 PM2:09 PM
936 2:18 PM2:27 PM2:30 PM2:35 PM2:44 PM2:49 PM2:57 PM3:09 PM
936 3:18 PM3:27 PM3:30 PM3:35 PM3:44 PM3:49 PM3:57 PM4:09 PM
936 4:18 PM4:27 PM4:30 PM4:35 PM4:44 PM4:49 PM4:57 PM5:09 PM
936 5:18 PM5:27 PM5:30 PM5:35 PM5:44 PM5:49 PM5:57 PM6:09 PM
936 wheelchair access6:18 PM6:27 PM6:30 PM6:35 PM6:44 PM6:49 PM6:57 PM7:09 PM
936 wheelchair access7:18 PM7:27 PM7:30 PM7:35 PM7:44 PM7:49 PM7:57 PM8:09 PM
+

wheelchair_icon- Wheelchair Accessible Bus

+
+ + + + --- /dev/null +++ b/maxious-canberra-transit-feed/source-html/Route_937wkend.html @@ -1,1 +1,365 @@ + + + + + + + + +Route_937 + + + + + +
+

Chosen services: 937

+ +

View timetable and map

+

This timetable is effective from Monday 25 May 2009.

+ +

Saturdays

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
 City Interchange
+ Platform 8
AinslieHackettDicksonNorth LynehamhamLynehamMacarthur\Miller O'ConnorCity Interchange
937 7:59 AM8:11 AM8:19 AM8:25 AM8:34 AM8:39 AM8:42 AM8:51 AM
937 8:59 AM9:11 AM9:19 AM9:25 AM9:34 AM9:39 AM9:42 AM9:51 AM
937 wheelchair access9:59 AM10:11 AM10:19 AM10:25 AM10:34 AM10:39 AM10:42 AM10:51 AM
937 wheelchair access10:59 AM11:11 AM11:19 AM11:25 AM11:34 AM11:39 AM11:42 AM11:51 AM
937 wheelchair access11:59 AM12:11 PM12:19 PM12:25 PM12:34 PM12:39 PM12:42 PM12:51 PM
937 wheelchair access12:59 PM1:11 PM1:19 PM1:25 PM1:34 PM1:39 PM1:42 PM1:51 PM
937 wheelchair access1:59 PM2:11 PM2:19 PM2:25 PM2:34 PM2:39 PM2:42 PM2:51 PM
937 wheelchair access2:59 PM3:11 PM3:19 PM3:25 PM3:34 PM3:39 PM3:42 PM3:51 PM
937 wheelchair access3:59 PM4:11 PM4:19 PM4:25 PM4:34 PM4:39 PM4:42 PM4:51 PM
937 5:00 PM5:12 PM5:20 PM5:26 PM5:35 PM5:40 PM5:43 PM5:52 PM
937 wheelchair access5:59 PM6:11 PM6:19 PM6:25 PM6:34 PM6:39 PM6:42 PM6:51 PM
937 wheelchair access6:59 PM7:11 PM7:19 PM7:25 PM7:34 PM7:39 PM7:42 PM7:51 PM
937 wheelchair access7:49 PM8:01 PM8:09 PM8:15 PM8:24 PM8:29 PM8:32 PM8:41 PM
937 8:49 PM9:01 PM9:09 PM9:15 PM9:24 PM9:29 PM9:32 PM9:41 PM
937 wheelchair access9:49 PM10:01 PM10:09 PM10:15 PM10:24 PM10:29 PM10:32 PM10:41 PM
937 10:49 PM11:01 PM11:09 PM11:15 PM11:24 PM11:29 PM11:32 PM11:41 PM
+

^top^

+

Sundays

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
 City Interchange
+ Platform 8
AinslieHackettDicksonNorth LynehamhamLynehamMacarthur\Miller O'ConnorCity Interchange
937 wheelchair access8:59 AM9:11 AM9:19 AM9:25 AM9:34 AM9:39 AM9:42 AM9:51 AM
937 wheelchair access9:59 AM10:11 AM10:19 AM10:25 AM10:34 AM10:39 AM10:42 AM10:51 AM
937 wheelchair access10:59 AM11:11 AM11:19 AM11:25 AM11:34 AM11:39 AM11:42 AM11:51 AM
937 wheelchair access11:59 AM12:11 PM12:19 PM12:25 PM12:34 PM12:39 PM12:42 PM12:51 PM
937 wheelchair access12:59 PM1:11 PM1:19 PM1:25 PM1:34 PM1:39 PM1:42 PM1:51 PM
937 wheelchair access1:59 PM2:11 PM2:19 PM2:25 PM2:34 PM2:39 PM2:42 PM2:51 PM
937 2:59 PM3:11 PM3:19 PM3:25 PM3:34 PM3:39 PM3:42 PM3:51 PM
937 wheelchair access3:59 PM4:11 PM4:19 PM4:25 PM4:34 PM4:39 PM4:42 PM4:51 PM
937 wheelchair access4:59 PM5:11 PM5:19 PM5:25 PM5:34 PM5:39 PM5:42 PM5:51 PM
937 wheelchair access5:59 PM6:11 PM6:19 PM6:25 PM6:34 PM6:39 PM6:42 PM6:51 PM
937 6:59 PM7:11 PM7:19 PM7:25 PM7:34 PM7:39 PM7:42 PM7:51 PM
+

wheelchair_icon- Wheelchair Accessible Bus

+
+ + + + + --- /dev/null +++ b/maxious-canberra-transit-feed/source-html/Route_938wkend.html @@ -1,1 +1,646 @@ + + + + + + + + +Route_938 + + + + + + + +
+

Chosen services: 938

+

View timetable and map

+

This timetable is effective from Monday 25 May 2009.

+ + +

Saturdays

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
 City Interchange
+ Platform 9
Russell OfficesKingstons Ave/National CrtKingstonNarrabundah CollegeCanberra HospitalWoden Interchange
938 wheelchair access7:46 AM7:54 AM7:58 AM8:02 AM8:17 AM8:27 AM8:34 AM
938 8:46 AM8:54 AM8:58 AM9:02 AM9:17 AM9:27 AM9:34 AM
938 9:46 AM9:54 AM9:58 AM10:02 AM10:17 AM10:27 AM10:34 AM
938 wheelchair access10:46 AM10:54 AM10:58 AM11:02 AM11:17 AM11:27 AM11:34 AM
938 11:46 AM11:54 AM11:58 AM12:02 PM12:17 PM12:27 PM12:34 PM
938 wheelchair access12:46 PM12:54 PM12:58 PM1:02 PM1:17 PM1:27 PM1:34 PM
938 wheelchair access1:46 PM1:54 PM1:58 PM2:02 PM2:17 PM2:27 PM2:34 PM
938 wheelchair access2:46 PM2:54 PM2:58 PM3:02 PM3:17 PM3:27 PM3:34 PM
938 wheelchair access3:46 PM3:54 PM3:58 PM4:02 PM4:17 PM4:27 PM4:34 PM
938 wheelchair access4:46 PM4:54 PM4:58 PM5:02 PM5:17 PM5:27 PM5:34 PM
938 wheelchair access5:46 PM5:54 PM5:58 PM6:02 PM6:17 PM6:27 PM6:34 PM
938 wheelchair access6:46 PM6:54 PM6:58 PM7:02 PM7:15 PM7:24 PM7:31 PM
938 7:46 PM7:53 PM7:57 PM8:01 PM8:14 PM8:23 PM8:30 PM
938 wheelchair access8:46 PM8:53 PM8:57 PM9:01 PM9:14 PM9:23 PM9:30 PM
938 wheelchair access9:46 PM9:53 PM9:57 PM10:01 PM10:14 PM10:23 PM10:30 PM
938 wheelchair access10:46 PM10:53 PM10:57 PM11:01 PM11:14 PM11:23 PM11:30 PM
+

 

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
 Woden Interchange
+ Platform 14
Canberra HospitalNarrabundah CollegeKingstonKingstons Ave/National CrtRussell OfficesCity Interchange
938 wheelchair access8:00 AM8:08 AM8:18 AM8:33 AM8:37 AM8:41 AM8:49 AM
938 wheelchair access9:00 AM9:08 AM9:18 AM9:33 AM9:37 AM9:41 AM9:49 AM
938 wheelchair access10:00 AM10:08 AM10:18 AM10:33 AM10:37 AM10:41 AM10:49 AM
938 wheelchair access11:00 AM11:08 AM11:18 AM11:33 AM11:37 AM11:41 AM11:49 AM
938 wheelchair access12:00 PM12:08 PM12:18 PM12:33 PM12:37 PM12:41 PM12:49 PM
938 wheelchair access1:00 PM1:08 PM1:18 PM1:33 PM1:37 PM1:41 PM1:49 PM
938 2:00 PM2:08 PM2:18 PM2:33 PM2:37 PM2:41 PM2:49 PM
938 wheelchair access3:00 PM3:08 PM3:18 PM3:33 PM3:37 PM3:41 PM3:49 PM
938 wheelchair access4:00 PM4:08 PM4:18 PM4:33 PM4:37 PM4:41 PM4:49 PM
938 wheelchair access5:00 PM5:08 PM5:18 PM5:33 PM5:37 PM5:41 PM5:49 PM
938 wheelchair access6:00 PM6:08 PM6:18 PM6:33 PM6:37 PM6:41 PM6:49 PM
938 wheelchair access7:00 PM7:07 PM7:16 PM7:29 PM7:33 PM7:37 PM7:44 PM
938 8:00 PM8:07 PM8:16 PM8:29 PM8:33 PM8:37 PM8:44 PM
938 wheelchair access9:00 PM9:07 PM9:16 PM9:29 PM9:33 PM9:37 PM9:44 PM
938 wheelchair access10:00 PM10:07 PM10:16 PM10:29 PM10:33 PM10:37 PM10:44 PM
938 wheelchair access11:00 PM11:07 PM11:16 PM11:29 PM11:33 PM11:37 PM11:44 PM
+

^top^

+

Sundays

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
 City Interchange
+ Platform 9
Russell OfficesKingstons Ave/National CrtKingstonNarrabundah CollegeCanberra HospitalWoden Interchange
938 8:46 AM8:54 AM8:58 AM9:02 AM9:17 AM9:27 AM9:34 AM
938 9:46 AM9:54 AM9:58 AM10:02 AM10:17 AM10:27 AM10:34 AM
938 10:46 AM10:54 AM10:58 AM11:02 AM11:17 AM11:27 AM11:34 AM
938 wheelchair access11:46 AM11:54 AM11:58 AM12:02 PM12:17 PM12:27 PM12:34 PM
938 12:46 PM12:54 PM12:58 PM1:02 PM1:17 PM1:27 PM1:34 PM
938 wheelchair access1:46 PM1:54 PM1:58 PM2:02 PM2:17 PM2:27 PM2:34 PM
938 wheelchair access2:46 PM2:54 PM2:58 PM3:02 PM3:17 PM3:27 PM3:34 PM
938 3:46 PM3:54 PM3:58 PM4:02 PM4:17 PM4:27 PM4:34 PM
938 wheelchair access4:46 PM4:54 PM4:58 PM5:02 PM5:17 PM5:27 PM5:34 PM
938 5:46 PM5:54 PM5:58 PM6:02 PM6:17 PM6:27 PM6:34 PM
938 wheelchair access6:46 PM6:54 PM6:58 PM7:02 PM7:14 PM7:22 PM7:28 PM
+

 

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
 Woden Interchange
+ Platform 14
Canberra HospitalNarrabundah CollegeKingstonKingstons Ave/National CrtRussell OfficesCity Interchange
938 wheelchair access8:00 AM8:08 AM8:18 AM8:33 AM8:37 AM8:41 AM8:49 AM
938 wheelchair access9:00 AM9:08 AM9:18 AM9:33 AM9:37 AM9:41 AM9:49 AM
938 wheelchair access10:00 AM10:08 AM10:18 AM10:33 AM10:37 AM10:41 AM10:49 AM
938 wheelchair access11:00 AM11:08 AM11:18 AM11:33 AM11:37 AM11:41 AM11:49 AM
938 wheelchair access12:00 PM12:08 PM12:18 PM12:33 PM12:37 PM12:41 PM12:49 PM
938 wheelchair access1:00 PM1:08 PM1:18 PM1:33 PM1:37 PM1:41 PM1:49 PM
938 2:00 PM2:08 PM2:18 PM2:33 PM2:37 PM2:41 PM2:49 PM
938 wheelchair access3:00 PM3:08 PM3:18 PM3:33 PM3:37 PM3:41 PM3:49 PM
938 4:00 PM4:08 PM4:18 PM4:33 PM4:37 PM4:41 PM4:49 PM
938 wheelchair access5:00 PM5:08 PM5:18 PM5:33 PM5:37 PM5:41 PM5:49 PM
938 wheelchair access6:00 PM6:08 PM6:18 PM6:33 PM6:37 PM6:41 PM6:49 PM
938 7:00 PM7:07 PM7:14 PM7:27 PM7:31 PM7:35 PM7:41 PM
+

wheelchair_icon- Wheelchair Accessible Bus

+
+ + + + --- /dev/null +++ b/maxious-canberra-transit-feed/source-html/Route_939wkend.html @@ -1,1 +1,347 @@ + + + + + + + + +Route_939 + + + + + + + +
+

Chosen services: 939

+

View timetable and map

+

This timetable is effective from Saturday 10 April 2010.

+ + +

Saturdays

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
 City Interchange
+ Platform 8
DicksonWatsonWatson TerminusWatsonDicksonCity Interchange
939 ...............7:08 AM7:14 AM7:19 AM7:33 AM
939 ...............8:08 AM8:14 AM8:19 AM8:33 AM
939 8:46 AM8:59 AM9:04 AM9:08 AM9:14 AM9:19 AM9:33 AM
939 wheelchair access9:46 AM9:59 AM10:04 AM10:08 AM10:14 AM10:19 AM10:33 AM
939 wheelchair access10:46 AM11:00 AM11:05 AM11:10 AM11:18 AM11:25 AM11:39 AM
939 wheelchair access11:46 AM11:59 AM12:04 PM12:08 PM12:14 PM12:19 PM12:33 PM
939 12:46 PM12:59 PM1:04 PM1:08 PM1:14 PM1:19 PM1:33 PM
939 1:46 PM1:59 PM2:04 PM2:08 PM2:14 PM2:19 PM2:33 PM
939 wheelchair access2:46 PM2:59 PM3:04 PM3:08 PM3:14 PM3:19 PM3:33 PM
939 3:46 PM3:59 PM4:04 PM4:08 PM4:14 PM4:19 PM4:33 PM
939 wheelchair access4:46 PM4:59 PM5:04 PM5:08 PM5:14 PM5:19 PM5:33 PM
939 5:46 PM5:59 PM6:04 PM6:08 PM6:14 PM6:19 PM6:33 PM
939 6:46 PM6:59 PM7:04 PM7:08 PM7:14 PM7:19 PM7:33 PM
939 7:46 PM7:59 PM8:04 PM8:08 PM8:14 PM8:19 PM8:33 PM
939 wheelchair access8:46 PM8:59 PM9:04 PM9:08 PM9:14 PM9:19 PM9:33 PM
939 9:46 PM9:59 PM10:04 PM10:08 PM10:14 PM10:19 PM10:33 PM
939 wheelchair access10:46 PM10:59 PM11:04 PM11:08 PM11:14 PM11:19 PM11:33 PM
+

Sundays

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
 City Interchange
+ Platform 8
DicksonWatsonWatson TerminusWatsonDicksonCity Interchange
939 wheelchair access8:46 AM8:59 AM9:04 AM9:08 AM9:14 AM9:19 AM9:33 AM
939 9:46 AM9:59 AM10:04 AM10:08 AM10:14 AM10:19 AM10:33 AM
939 10:46 AM10:59 AM11:04 AM11:08 AM11:14 AM11:19 AM11:33 AM
939 wheelchair access11:46 AM11:59 AM12:04 PM12:08 PM12:14 PM12:19 PM12:33 PM
939 wheelchair access12:46 PM12:59 PM1:04 PM1:08 PM1:14 PM1:19 PM1:33 PM
939 wheelchair access1:46 PM1:59 PM2:04 PM2:08 PM2:14 PM2:19 PM2:33 PM
939 wheelchair access2:46 PM2:59 PM3:04 PM3:08 PM3:14 PM3:19 PM3:33 PM
939 3:46 PM3:59 PM4:04 PM4:08 PM4:14 PM4:19 PM4:33 PM
939 4:46 PM4:59 PM5:04 PM5:08 PM5:14 PM5:19 PM5:33 PM
939 5:46 PM5:59 PM6:04 PM6:08 PM6:14 PM6:19 PM6:33 PM
939 wheelchair access6:46 PM6:59 PM7:04 PM7:08 PM7:14 PM7:19 PM7:33 PM
+

wheelchair_icon- Wheelchair Accessible Bus

+
+ + + + --- /dev/null +++ b/maxious-canberra-transit-feed/source-html/Route_942wkend.html @@ -1,1 +1,674 @@ + + + + + + + + +Route_942 + + + + + +
+

Chosen services: 942

+ +

View timetable and map

+

This timetable is effective from Monday 25 May 2009.

+ +

Saturdays

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
 Cohen St Bus Station
+ Platform 2
Lathlain St Bus Station
+ Platform 3
Cameron Ave Bus Station
+ Platform 3
Jamison CentreCookArandaCaswell DriveCity Interchange
9428:19 AM8:21 AM8:25 AM8:31 AM8:40 AM8:44 AM8:45 AM8:55 AM
942wheelchair access9:19 AM9:21 AM9:25 AM9:31 AM9:40 AM9:44 AM9:45 AM9:55 AM
942wheelchair access10:19 AM10:21 AM10:25 AM10:31 AM10:40 AM10:44 AM10:45 AM10:55 AM
94211:19 AM11:21 AM11:25 AM11:31 AM11:40 AM11:44 AM11:45 AM11:55 AM
942wheelchair access12:19 PM12:21 PM12:25 PM12:31 PM12:40 PM12:44 PM12:45 PM12:55 PM
942wheelchair access1:19 PM1:21 PM1:25 PM1:31 PM1:40 PM1:44 PM1:45 PM1:55 PM
9422:19 PM2:21 PM2:25 PM2:31 PM2:40 PM2:44 PM2:45 PM2:55 PM
9423:19 PM3:21 PM3:25 PM3:31 PM3:40 PM3:44 PM3:45 PM3:55 PM
9424:19 PM4:21 PM4:25 PM4:31 PM4:40 PM4:44 PM4:45 PM4:55 PM
9425:19 PM5:21 PM5:25 PM5:31 PM5:40 PM5:44 PM5:45 PM5:55 PM
9426:19 PM6:21 PM6:25 PM6:31 PM6:40 PM6:44 PM6:45 PM6:55 PM
942wheelchair access7:20 PM7:22 PM7:25 PM7:31 PM7:40 PM7:44 PM7:45 PM7:55 PM
9428:20 PM8:22 PM8:25 PM8:31 PM8:40 PM8:44 PM8:45 PM8:55 PM
9429:20 PM9:22 PM9:25 PM9:31 PM9:40 PM9:44 PM9:45 PM9:55 PM
942wheelchair access10:20 PM10:22 PM10:25 PM10:31 PM10:40 PM10:44 PM10:45 PM10:55 PM
+

 

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
 City Interchange
+ Platform 4
Caswell DriveArandaCookJamison CentreCameron Ave Bus StationLathlain St Bus StationCohen St Bus Station
9428:14 AM8:23 AM8:24 AM8:27 AM8:36 AM8:43 AM8:45 AM8:49 AM
9429:14 AM9:23 AM9:24 AM9:27 AM9:36 AM9:43 AM9:45 AM9:49 AM
942wheelchair access10:14 AM10:23 AM10:24 AM10:27 AM10:36 AM10:43 AM10:45 AM10:49 AM
942wheelchair access11:14 AM11:23 AM11:24 AM11:27 AM11:36 AM11:43 AM11:45 AM11:49 AM
942wheelchair access12:14 PM12:23 PM12:24 PM12:27 PM12:36 PM12:43 PM12:45 PM12:49 PM
942wheelchair access1:14 PM1:23 PM1:24 PM1:27 PM1:36 PM1:43 PM1:45 PM1:49 PM
942wheelchair access2:14 PM2:23 PM2:24 PM2:27 PM2:36 PM2:43 PM2:45 PM2:49 PM
942wheelchair access3:14 PM3:23 PM3:24 PM3:27 PM3:36 PM3:43 PM3:45 PM3:49 PM
942wheelchair access4:14 PM4:23 PM4:24 PM4:27 PM4:36 PM4:43 PM4:45 PM4:49 PM
9425:14 PM5:23 PM5:24 PM5:27 PM5:36 PM5:43 PM5:45 PM5:49 PM
942wheelchair access6:14 PM6:23 PM6:24 PM6:27 PM6:36 PM6:43 PM6:45 PM6:48 PM
9427:14 PM7:23 PM7:24 PM7:27 PM7:36 PM7:43 PM7:45 PM7:48 PM
942wheelchair access8:14 PM8:23 PM8:24 PM8:27 PM8:36 PM8:43 PM8:45 PM8:48 PM
9429:14 PM9:23 PM9:24 PM9:27 PM9:36 PM9:43 PM9:45 PM9:48 PM
94210:14 PM10:23 PM10:24 PM10:27 PM10:36 PM10:43 PM10:45 PM10:48 PM
942wheelchair access11:14 PM11:23 PM11:24 PM11:27 PM11:36 PM11:43 PM11:45 PM11:48 PM
+

^top^

+

Sunday

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
 Cohen St Bus Station
+ Platform 2
Lathlain St Bus Station
+ Platform 3
Cameron Ave Bus Station
+ Platform 3
Jamison CentreCookArandaCaswell DriveCity Interchange
9428:19 AM8:21 AM8:25 AM8:31 AM8:40 AM8:44 AM8:45 AM8:55 AM
9429:19 AM9:21 AM9:25 AM9:31 AM9:40 AM9:44 AM9:45 AM9:55 AM
94210:19 AM10:21 AM10:25 AM10:31 AM10:40 AM10:44 AM10:45 AM10:55 AM
94211:19 AM11:21 AM11:25 AM11:31 AM11:40 AM11:44 AM11:45 AM11:55 AM
942wheelchair access12:19 PM12:21 PM12:25 PM12:31 PM12:40 PM12:44 PM12:45 PM12:55 PM
9421:19 PM1:21 PM1:25 PM1:31 PM1:40 PM1:44 PM1:45 PM1:55 PM
942wheelchair access2:19 PM2:21 PM2:25 PM2:31 PM2:40 PM2:44 PM2:45 PM2:55 PM
942wheelchair access3:19 PM3:21 PM3:25 PM3:31 PM3:40 PM3:44 PM3:45 PM3:55 PM
9424:19 PM4:21 PM4:25 PM4:31 PM4:40 PM4:44 PM4:45 PM4:55 PM
942wheelchair access5:19 PM5:21 PM5:25 PM5:31 PM5:40 PM5:44 PM5:45 PM5:55 PM
942wheelchair access6:19 PM6:21 PM6:25 PM6:31 PM6:40 PM6:44 PM6:45 PM6:55 PM
+

 

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
 City Interchange
+ Platform 4
Caswell DriveArandaCookJamison CentreCameron Ave Bus StationLathlain St Bus StationCohen St Bus Station
9429:14 AM9:23 AM9:24 AM9:27 AM9:36 AM9:43 AM9:45 AM9:49 AM
942wheelchair access10:14 AM10:23 AM10:24 AM10:27 AM10:36 AM10:43 AM10:45 AM10:49 AM
94211:14 AM11:23 AM11:24 AM11:27 AM11:36 AM11:43 AM11:45 AM11:49 AM
942wheelchair access12:14 PM12:23 PM12:24 PM12:27 PM12:36 PM12:43 PM12:45 PM12:49 PM
9421:14 PM1:23 PM1:24 PM1:27 PM1:36 PM1:43 PM1:45 PM1:49 PM
9422:14 PM2:23 PM2:24 PM2:27 PM2:36 PM2:43 PM2:45 PM2:49 PM
9423:14 PM3:23 PM3:24 PM3:27 PM3:36 PM3:43 PM3:45 PM3:49 PM
9424:14 PM4:23 PM4:24 PM4:27 PM4:36 PM4:43 PM4:45 PM4:49 PM
9425:14 PM5:23 PM5:24 PM5:27 PM5:36 PM5:43 PM5:45 PM5:49 PM
9426:14 PM6:23 PM6:24 PM6:27 PM6:36 PM6:43 PM6:45 PM6:48 PM
+

wheelchair_icon- Wheelchair Accessible Bus

+
+ + + + + --- /dev/null +++ b/maxious-canberra-transit-feed/source-html/Route_951wkend.html @@ -1,1 +1,661 @@ + + + + + + + + +Route_951 + + + + + +
+

Chosen services: 951

+ +

View timetable and map

+

This timetable is effective from Monday 25 May 2009.

+ +

Saturdays

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
 Cohen St Bus Station
+ Platform 1
Lathlain St Bus Station
+ Platform 2
Cameron Ave Bus Station
+ Platform 2
Chuculba/William SlimFederation SquareNicholls PrimaryNgunnawal PrimaryGungahlin Market Place
951wheelchair access8:24 AM8:26 AM8:30 AM8:37 AM8:42 AM8:47 AM8:57 AM9:06 AM
951wheelchair access9:24 AM9:26 AM9:30 AM9:37 AM9:42 AM9:47 AM9:57 AM10:06 AM
95110:24 AM10:26 AM10:30 AM10:37 AM10:42 AM10:47 AM10:57 AM11:06 AM
95111:24 AM11:26 AM11:30 AM11:37 AM11:42 AM11:47 AM11:57 AM12:06 PM
95112:24 PM12:26 PM12:30 PM12:37 PM12:42 PM12:47 PM12:57 PM1:06 PM
951wheelchair access1:24 PM1:26 PM1:30 PM1:37 PM1:42 PM1:47 PM1:57 PM2:06 PM
951wheelchair access2:24 PM2:26 PM2:30 PM2:37 PM2:42 PM2:47 PM2:57 PM3:06 PM
9513:24 PM3:26 PM3:30 PM3:37 PM3:42 PM3:47 PM3:57 PM4:06 PM
9514:24 PM4:26 PM4:30 PM4:37 PM4:42 PM4:47 PM4:57 PM5:06 PM
951wheelchair access5:24 PM5:26 PM5:30 PM5:37 PM5:42 PM5:47 PM5:57 PM6:06 PM
951wheelchair access6:24 PM6:26 PM6:30 PM6:37 PM6:42 PM6:47 PM6:57 PM7:06 PM
951wheelchair access7:25 PM7:27 PM7:30 PM7:37 PM7:42 PM7:47 PM7:57 PM8:06 PM
951wheelchair access8:25 PM8:27 PM8:30 PM8:37 PM8:42 PM8:47 PM8:57 PM9:06 PM
951wheelchair access9:25 PM9:27 PM9:30 PM9:37 PM9:42 PM9:47 PM9:57 PM10:06 PM
951wheelchair access10:25 PM10:27 PM10:30 PM10:37 PM10:42 PM10:47 PM10:57 PM11:06 PM
+

 

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
 Gungahlin Market PlaceNgunnawal PrimaryNicholls PrimaryFederation SquareChuculba/William SlimCameron Ave Bus StationLathlain St Bus StationCohen St Bus Station
951wheelchair access8:12 AM8:21 AM8:31 AM8:37 AM8:42 AM8:49 AM8:51 AM8:55 AM
951wheelchair access9:12 AM9:21 AM9:31 AM9:37 AM9:42 AM9:49 AM9:51 AM9:55 AM
951wheelchair access10:12 AM10:21 AM10:31 AM10:37 AM10:42 AM10:49 AM10:51 AM10:55 AM
95111:12 AM11:21 AM11:31 AM11:37 AM11:42 AM11:49 AM11:51 AM11:55 AM
95112:12 PM12:21 PM12:31 PM12:37 PM12:42 PM12:49 PM12:51 PM12:55 PM
9511:12 PM1:21 PM1:31 PM1:37 PM1:42 PM1:49 PM1:51 PM1:55 PM
951wheelchair access2:12 PM2:21 PM2:31 PM2:37 PM2:42 PM2:49 PM2:51 PM2:55 PM
951wheelchair access3:12 PM3:21 PM3:31 PM3:37 PM3:42 PM3:49 PM3:51 PM3:55 PM
9514:12 PM4:21 PM4:31 PM4:37 PM4:42 PM4:49 PM4:51 PM4:55 PM
9515:12 PM5:21 PM5:31 PM5:37 PM5:42 PM5:49 PM5:51 PM5:55 PM
951wheelchair access6:12 PM6:21 PM6:31 PM6:37 PM6:42 PM6:49 PM6:51 PM6:54 PM
951wheelchair access7:12 PM7:21 PM7:31 PM7:37 PM7:42 PM7:49 PM7:51 PM7:54 PM
951wheelchair access8:12 PM8:21 PM8:31 PM8:37 PM8:42 PM8:49 PM8:51 PM8:54 PM
951wheelchair access9:12 PM9:21 PM9:31 PM9:37 PM9:42 PM9:49 PM9:51 PM9:54 PM
951wheelchair access10:12 PM10:21 PM10:31 PM10:37 PM10:42 PM10:49 PM10:51 PM10:54 PM
951wheelchair access11:12 PM11:21 PM11:31 PM11:37 PM11:42 PM11:49 PM11:51 PM11:54 PM
+

^top^

+

Sundays

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
 Cohen St Bus Station
+ Platform 1
Lathlain St Bus Station
+ Platform 2
Cameron Ave Bus Station
+ Platform 2
Chuculba/William SlimFederation SquareNicholls PrimaryNgunnawal PrimaryGungahlin Market Place
9519:24 AM9:26 AM9:30 AM9:37 AM9:42 AM9:47 AM9:57 AM10:06 AM
951wheelchair access10:24 AM10:26 AM10:30 AM10:37 AM10:42 AM10:47 AM10:57 AM11:06 AM
951wheelchair access11:24 AM11:26 AM11:30 AM11:37 AM11:42 AM11:47 AM11:57 AM12:06 PM
951wheelchair access12:24 PM12:26 PM12:30 PM12:37 PM12:42 PM12:47 PM12:57 PM1:06 PM
951wheelchair access1:24 PM1:26 PM1:30 PM1:37 PM1:42 PM1:47 PM1:57 PM2:06 PM
951wheelchair access2:24 PM2:26 PM2:30 PM2:37 PM2:42 PM2:47 PM2:57 PM3:06 PM
9513:24 PM3:26 PM3:30 PM3:37 PM3:42 PM3:47 PM3:57 PM4:06 PM
9514:24 PM4:26 PM4:30 PM4:37 PM4:42 PM4:47 PM4:57 PM5:06 PM
951wheelchair access5:24 PM5:26 PM5:30 PM5:37 PM5:42 PM5:47 PM5:57 PM6:06 PM
9516:24 PM6:26 PM6:30 PM6:37 PM6:42 PM6:47 PM6:57 PM7:06 PM
+

 

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
 Gungahlin Market PlaceNgunnawal PrimaryNicholls PrimaryFederation SquareChuculba/William SlimCameron Ave Bus StationLathlain St Bus StationCohen St Bus Station
9519:12 AM9:21 AM9:31 AM9:37 AM9:42 AM9:49 AM9:51 AM9:55 AM
95110:12 AM10:21 AM10:31 AM10:37 AM10:42 AM10:49 AM10:51 AM10:55 AM
951wheelchair access11:12 AM11:21 AM11:31 AM11:37 AM11:42 AM11:49 AM11:51 AM11:55 AM
95112:12 PM12:21 PM12:31 PM12:37 PM12:42 PM12:49 PM12:51 PM12:55 PM
951wheelchair access1:12 PM1:21 PM1:31 PM1:37 PM1:42 PM1:49 PM1:51 PM1:55 PM
951wheelchair access2:12 PM2:21 PM2:31 PM2:37 PM2:42 PM2:49 PM2:51 PM2:55 PM
951wheelchair access3:12 PM3:21 PM3:31 PM3:37 PM3:42 PM3:49 PM3:51 PM3:55 PM
9514:12 PM4:21 PM4:31 PM4:37 PM4:42 PM4:49 PM4:51 PM4:55 PM
9515:12 PM5:21 PM5:31 PM5:37 PM5:42 PM5:49 PM5:51 PM5:55 PM
951wheelchair access6:12 PM6:21 PM6:31 PM6:37 PM6:42 PM6:49 PM6:51 PM6:54 PM
+

wheelchair_icon- Wheelchair Accessible Bus

+
+ + + + + --- /dev/null +++ b/maxious-canberra-transit-feed/source-html/Route_952wkend.html @@ -1,1 +1,626 @@ + + + + + + + + +Route_952 + + + + + +
+

Chosen services: 952

+ +

View timetable and map

+

This timetable is effective from Monday 25 May 2009.

+ +

Saturdays

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
 Cohen St Bus Station
+ Platform 1
Lathlain St Bus Station
+ Platform 2
Cameron Ave Bus Station
+ Platform 2
Chuculba/William SlimFederation SquareNicholls PrimaryGungahlin Market Place
952wheelchair access....................8:12 AM8:25 AM8:33 AM
952wheelchair access8:49 AM8:51 AM8:55 AM9:07 AM9:12 AM9:25 AM9:33 AM
952wheelchair access9:49 AM9:51 AM9:55 AM10:07 AM10:12 AM10:25 AM10:33 AM
952wheelchair access10:49 AM10:51 AM10:55 AM11:07 AM11:12 AM11:25 AM11:33 AM
952wheelchair access11:49 AM11:51 AM11:55 AM12:07 PM12:12 PM12:25 PM12:33 PM
95212:49 PM12:51 PM12:55 PM1:07 PM1:12 PM1:25 PM1:33 PM
952wheelchair access1:49 PM1:51 PM1:55 PM2:07 PM2:12 PM2:25 PM2:33 PM
952wheelchair access2:49 PM2:51 PM2:55 PM3:07 PM3:12 PM3:25 PM3:33 PM
952wheelchair access3:49 PM3:51 PM3:55 PM4:07 PM4:12 PM4:25 PM4:33 PM
952wheelchair access4:49 PM4:51 PM4:55 PM5:07 PM5:12 PM5:25 PM5:33 PM
9525:49 PM5:51 PM5:55 PM6:07 PM6:12 PM6:25 PM6:33 PM
952wheelchair access6:50 PM6:52 PM6:55 PM7:07 PM7:12 PM7:25 PM7:33 PM
952wheelchair access7:50 PM7:52 PM7:55 PM8:07 PM8:12 PM8:25 PM8:33 PM
952wheelchair access8:50 PM8:52 PM8:55 PM9:07 PM9:12 PM9:25 PM9:33 PM
952wheelchair access9:50 PM9:52 PM9:55 PM10:07 PM10:12 PM10:25 PM10:33 PM
952wheelchair access10:50 PM10:52 PM10:55 PM11:07 PM11:12 PM11:25 PM11:33 PM
+

 

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
 Gungahlin Market PlaceNicholls PrimaryFederation SquareChuculba/William SlimCameron Ave Bus StationLathlain St Bus StationCohen St Bus Station
952wheelchair access7:39 AM7:47 AM8:00 AM8:05 AM8:17 AM8:19 AM8:23 AM
952wheelchair access8:39 AM8:47 AM9:00 AM9:05 AM9:17 AM9:19 AM9:23 AM
952wheelchair access9:39 AM9:47 AM10:00 AM10:05 AM10:17 AM10:19 AM10:23 AM
952wheelchair access10:39 AM10:47 AM11:00 AM11:05 AM11:17 AM11:19 AM11:23 AM
952wheelchair access11:39 AM11:47 AM12:00 PM12:05 PM12:17 PM12:19 PM12:23 PM
952wheelchair access12:39 PM12:47 PM1:00 PM1:05 PM1:17 PM1:19 PM1:23 PM
9521:39 PM1:47 PM2:00 PM2:05 PM2:17 PM2:19 PM2:23 PM
952wheelchair access2:39 PM2:47 PM3:00 PM3:05 PM3:17 PM3:19 PM3:23 PM
952wheelchair access3:39 PM3:47 PM4:00 PM4:05 PM4:17 PM4:19 PM4:23 PM
952wheelchair access4:39 PM4:47 PM5:00 PM5:05 PM5:17 PM5:19 PM5:23 PM
952wheelchair access5:39 PM5:47 PM6:00 PM6:05 PM6:17 PM6:19 PM6:23 PM
9526:39 PM6:47 PM7:00 PM7:05 PM7:17 PM7:19 PM7:22 PM
952wheelchair access7:39 PM7:47 PM8:00 PM8:05 PM8:17 PM8:19 PM8:22 PM
952wheelchair access8:39 PM8:47 PM9:00 PM9:05 PM9:17 PM9:19 PM9:22 PM
952wheelchair access9:39 PM9:47 PM10:00 PM10:05 PM10:17 PM10:19 PM10:22 PM
952wheelchair access10:39 PM10:47 PM11:00 PM11:05 PM11:17 PM11:19 PM11:22 PM
+

^top^

+

Sundays

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
 Cohen St Bus Station
+ Platform 1
Lathlain St Bus Station
+ Platform 2
Cameron Ave Bus Station
+ Platform 2
Chuculba/William SlimFederation SquareNicholls PrimaryGungahlin Market Place
952wheelchair access9:49 AM9:51 AM9:55 AM10:07 AM10:12 AM10:25 AM10:33 AM
952wheelchair access10:49 AM10:51 AM10:55 AM11:07 AM11:12 AM11:25 AM11:33 AM
95211:49 AM11:51 AM11:55 AM12:07 PM12:12 PM12:25 PM12:33 PM
952wheelchair access12:49 PM12:51 PM12:55 PM1:07 PM1:12 PM1:25 PM1:33 PM
952wheelchair access1:49 PM1:51 PM1:55 PM2:07 PM2:12 PM2:25 PM2:33 PM
952wheelchair access2:49 PM2:51 PM2:55 PM3:07 PM3:12 PM3:25 PM3:33 PM
9523:49 PM3:51 PM3:55 PM4:07 PM4:12 PM4:25 PM4:33 PM
952wheelchair access4:49 PM4:51 PM4:55 PM5:07 PM5:12 PM5:25 PM5:33 PM
9525:49 PM5:51 PM5:55 PM6:07 PM6:12 PM6:25 PM6:33 PM
952wheelchair access6:50 PM6:52 PM6:55 PM7:07 PM7:12 PM7:25 PM7:33 PM
+

 

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
 Gungahlin Market PlaceNicholls PrimaryFederation SquareChuculba/William SlimCameron Ave Bus StationLathlain St Bus StationCohen St Bus Station
9528:39 AM8:47 AM9:00 AM9:05 AM9:17 AM9:19 AM9:23 AM
952wheelchair access9:39 AM9:47 AM10:00 AM10:05 AM10:17 AM10:19 AM10:23 AM
952wheelchair access10:39 AM10:47 AM11:00 AM11:05 AM11:17 AM11:19 AM11:23 AM
952wheelchair access11:39 AM11:47 AM12:00 PM12:05 PM12:17 PM12:19 PM12:23 PM
95212:39 PM12:47 PM1:00 PM1:05 PM1:17 PM1:19 PM1:23 PM
952wheelchair access1:39 PM1:47 PM2:00 PM2:05 PM2:17 PM2:19 PM2:23 PM
952wheelchair access2:39 PM2:47 PM3:00 PM3:05 PM3:17 PM3:19 PM3:23 PM
952wheelchair access3:39 PM3:47 PM4:00 PM4:05 PM4:17 PM4:19 PM4:23 PM
9524:39 PM4:47 PM5:00 PM5:05 PM5:17 PM5:19 PM5:23 PM
952wheelchair access5:39 PM5:47 PM6:00 PM6:05 PM6:17 PM6:19 PM6:23 PM
9526:39 PM6:47 PM7:00 PM7:05 PM7:17 PM7:19 PM7:22 PM
+

wheelchair_icon- Wheelchair Accessible Bus

+
+ + + + + --- /dev/null +++ b/maxious-canberra-transit-feed/source-html/Route_956wkend.html @@ -1,1 +1,812 @@ + + + + + + + + +Route_956 + + + + + +
+

Chosen services: 956

+ +

View timetable and map

+

This timetable is effective from Monday 25 May 2009.

+ +

Saturdays

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
 Cohen St Bus Station
+ Platform 2
Lathlain St Bus Station
+ Platform 2
Cameron Ave Bus Station
+ Platform 1
William Webb / Ginninderra DriveChuculba/William SlimGungahlin Market PlaceKosciuszko/EverardFlemington RdMacarthur\NorthbourneCity Interchange
9567:43 AM7:45 AM7:49 AM7:54 AM7:59 AM8:09 AM8:19 AM8:26 AM8:34 AM8:40 AM
956wheelchair access8:43 AM8:45 AM8:49 AM8:54 AM8:59 AM9:09 AM9:19 AM9:26 AM9:34 AM9:40 AM
956wheelchair access9:43 AM9:45 AM9:49 AM9:54 AM9:59 AM10:09 AM10:19 AM10:26 AM10:34 AM10:40 AM
95610:43 AM10:45 AM10:49 AM10:54 AM10:59 AM11:09 AM11:19 AM11:26 AM11:34 AM11:40 AM
956wheelchair access11:43 AM11:45 AM11:49 AM11:54 AM11:59 AM12:09 PM12:19 PM12:26 PM12:34 PM12:40 PM
95612:43 PM12:45 PM12:49 PM12:54 PM12:59 PM1:09 PM1:19 PM1:26 PM1:34 PM1:40 PM
956wheelchair access1:43 PM1:45 PM1:49 PM1:54 PM1:59 PM2:09 PM2:19 PM2:26 PM2:34 PM2:40 PM
9562:43 PM2:45 PM2:49 PM2:54 PM2:59 PM3:09 PM3:19 PM3:26 PM3:34 PM3:40 PM
956wheelchair access3:43 PM3:45 PM3:49 PM3:54 PM3:59 PM4:09 PM4:19 PM4:26 PM4:34 PM4:40 PM
956wheelchair access4:43 PM4:45 PM4:49 PM4:54 PM4:59 PM5:09 PM5:19 PM5:26 PM5:34 PM5:40 PM
956wheelchair access5:43 PM5:45 PM5:49 PM5:54 PM5:59 PM6:09 PM6:19 PM6:26 PM6:34 PM6:40 PM
956wheelchair access6:44 PM6:46 PM6:49 PM6:54 PM6:59 PM7:09 PM7:19 PM7:26 PM7:34 PM7:40 PM
956wheelchair access7:44 PM7:46 PM7:49 PM7:54 PM7:59 PM8:09 PM8:19 PM8:26 PM8:34 PM8:40 PM
956wheelchair access8:44 PM8:46 PM8:49 PM8:54 PM8:59 PM9:09 PM9:19 PM9:26 PM9:34 PM9:40 PM
956wheelchair access9:44 PM9:46 PM9:49 PM9:54 PM9:59 PM10:09 PM10:19 PM10:26 PM10:34 PM10:40 PM
956wheelchair access10:44 PM10:46 PM10:49 PM10:54 PM10:59 PM11:09 PM11:19 PM11:26 PM11:34 PM11:40 PM
+

 

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
 City Interchange
+ Platform 8
Macarthur\NorthbourneFlemington RdKosciuszko/EverardGungahlin Market PlaceChuculba/William SlimWilliam Webb / Ginninderra DriveCameron Ave Bus StationLathlain St Bus StationCohen St Bus Station
9567:38 AM7:44 AM7:52 AM7:59 AM8:09 AM8:19 AM8:24 AM8:29 AM8:31 AM8:35 AM
9568:38 AM8:44 AM8:52 AM8:59 AM9:09 AM9:19 AM9:24 AM9:29 AM9:31 AM9:35 AM
9569:38 AM9:44 AM9:52 AM9:59 AM10:09 AM10:19 AM10:24 AM10:29 AM10:31 AM10:35 AM
956wheelchair access10:38 AM10:44 AM10:52 AM10:59 AM11:09 AM11:19 AM11:24 AM11:29 AM11:31 AM11:35 AM
95611:38 AM11:44 AM11:52 AM11:59 AM12:09 PM12:19 PM12:24 PM12:29 PM12:31 PM12:35 PM
956wheelchair access12:38 PM12:44 PM12:52 PM12:59 PM1:09 PM1:19 PM1:24 PM1:29 PM1:31 PM1:35 PM
9561:38 PM1:44 PM1:52 PM1:59 PM2:09 PM2:19 PM2:24 PM2:29 PM2:31 PM2:35 PM
956wheelchair access2:38 PM2:44 PM2:52 PM2:59 PM3:09 PM3:19 PM3:24 PM3:29 PM3:31 PM3:35 PM
956wheelchair access3:38 PM3:44 PM3:52 PM3:59 PM4:09 PM4:19 PM4:24 PM4:29 PM4:31 PM4:35 PM
9564:38 PM4:44 PM4:52 PM4:59 PM5:09 PM5:19 PM5:24 PM5:29 PM5:31 PM5:35 PM
956wheelchair access5:38 PM5:44 PM5:52 PM5:59 PM6:09 PM6:19 PM6:24 PM6:29 PM6:31 PM6:34 PM
9566:38 PM6:44 PM6:52 PM6:59 PM7:09 PM7:19 PM7:24 PM7:29 PM7:31 PM7:34 PM
956wheelchair access7:38 PM7:44 PM7:52 PM7:59 PM8:09 PM8:19 PM8:24 PM8:29 PM8:31 PM8:34 PM
9568:38 PM8:44 PM8:52 PM8:59 PM9:09 PM9:19 PM9:24 PM9:29 PM9:31 PM9:34 PM
956wheelchair access9:38 PM9:44 PM9:52 PM9:59 PM10:09 PM10:19 PM10:24 PM10:29 PM10:31 PM10:34 PM
95610:38 PM10:44 PM10:52 PM10:59 PM11:09 PM11:19 PM11:24 PM11:29 PM11:31 PM11:34 PM
+

^top^

+

Sundays

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
 Cohen St Bus Station
+ Platform 2
Lathlain St Bus Station
+ Platform 2
Cameron Ave Bus Station
+ Platform 1
William Webb / Ginninderra DriveChuculba/William SlimGungahlin Market PlaceKosciuszko/EverardFlemington RdMacarthur\NorthbourneCity Interchange
956wheelchair access8:43 AM8:45 AM8:49 AM8:54 AM8:59 AM9:09 AM9:19 AM9:26 AM9:34 AM9:40 AM
9569:43 AM9:45 AM9:49 AM9:54 AM9:59 AM10:09 AM10:19 AM10:26 AM10:34 AM10:40 AM
95610:43 AM10:45 AM10:49 AM10:54 AM10:59 AM11:09 AM11:19 AM11:26 AM11:34 AM11:40 AM
95611:43 AM11:45 AM11:49 AM11:54 AM11:59 AM12:09 PM12:19 PM12:26 PM12:34 PM12:40 PM
95612:43 PM12:45 PM12:49 PM12:54 PM12:59 PM1:09 PM1:19 PM1:26 PM1:34 PM1:40 PM
956wheelchair access1:43 PM1:45 PM1:49 PM1:54 PM1:59 PM2:09 PM2:19 PM2:26 PM2:34 PM2:40 PM
9562:43 PM2:45 PM2:49 PM2:54 PM2:59 PM3:09 PM3:19 PM3:26 PM3:34 PM3:40 PM
9563:43 PM3:45 PM3:49 PM3:54 PM3:59 PM4:09 PM4:19 PM4:26 PM4:34 PM4:40 PM
9564:43 PM4:45 PM4:49 PM4:54 PM4:59 PM5:09 PM5:19 PM5:26 PM5:34 PM5:40 PM
956wheelchair access5:43 PM5:45 PM5:49 PM5:54 PM5:59 PM6:09 PM6:19 PM6:26 PM6:34 PM6:40 PM
9566:44 PM6:46 PM6:49 PM6:54 PM6:59 PM7:09 PM7:19 PM7:26 PM7:34 PM7:40 PM
+

 

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
 City Interchange
+ Platform 8
Macarthur\NorthbourneFlemington RdKosciuszko/EverardGungahlin Market PlaceChuculba/William SlimWilliam Webb / Ginninderra DriveCameron Ave Bus StationLathlain St Bus StationCohen St Bus Station
9568:38 AM8:44 AM8:52 AM8:59 AM9:09 AM9:19 AM9:24 AM9:29 AM9:31 AM9:35 AM
956wheelchair access9:38 AM9:44 AM9:52 AM9:59 AM10:09 AM10:19 AM10:24 AM10:29 AM10:31 AM10:35 AM
95610:38 AM10:44 AM10:52 AM10:59 AM11:09 AM11:19 AM11:24 AM11:29 AM11:31 AM11:35 AM
95611:38 AM11:44 AM11:52 AM11:59 AM12:09 PM12:19 PM12:24 PM12:29 PM12:31 PM12:35 PM
956wheelchair access12:38 PM12:44 PM12:52 PM12:59 PM1:09 PM1:19 PM1:24 PM1:29 PM1:31 PM1:35 PM
9561:38 PM1:44 PM1:52 PM1:59 PM2:09 PM2:19 PM2:24 PM2:29 PM2:31 PM2:35 PM
9562:38 PM2:44 PM2:52 PM2:59 PM3:09 PM3:19 PM3:24 PM3:29 PM3:31 PM3:35 PM
9563:38 PM3:44 PM3:52 PM3:59 PM4:09 PM4:19 PM4:24 PM4:29 PM4:31 PM4:35 PM
956wheelchair access4:38 PM4:44 PM4:52 PM4:59 PM5:09 PM5:19 PM5:24 PM5:29 PM5:31 PM5:35 PM
9565:38 PM5:44 PM5:52 PM5:59 PM6:09 PM6:19 PM6:24 PM6:29 PM6:31 PM6:34 PM
9566:38 PM6:44 PM6:52 PM6:59 PM7:09 PM7:19 PM7:24 PM7:29 PM7:31 PM7:34 PM
+

wheelchair_icon- Wheelchair Accessible Bus

+
+ + + + + --- /dev/null +++ b/maxious-canberra-transit-feed/source-html/Route_958wkend.html @@ -1,1 +1,958 @@ + + + + + + + + +Route_958 + + + + + +
+

Chosen services: 958

+ +

View timetable and map

+

This timetable is effective from Monday 25 May 2009.

+ +

Saturday

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
 Cohen St Bus Station
+ Platform 1
Lathlain St Bus Station
+ Platform 2
Cameron Ave Bus Station
+ Platform 2
Chuculba/William SlimNgunnawal PrimaryShoalhaven / Katherine AveGungahlin Market PlaceAnthony Rolfe/MoonlightFlemington/NullaborFlemington RdMacarthur\NorthbourneCity Interchange
958...............7:09 AM7:20 AM7:28 AM7:36 AM7:45 AM7:52 AM7:59 AM8:07 AM8:13 AM
958wheelchair access7:53 AM7:55 AM7:59 AM8:09 AM8:20 AM8:28 AM8:36 AM8:45 AM8:52 AM8:59 AM9:07 AM9:13 AM
958wheelchair access8:53 AM8:55 AM8:59 AM9:09 AM9:20 AM9:28 AM9:36 AM9:45 AM9:52 AM9:59 AM10:07 AM10:13 AM
9589:53 AM9:55 AM9:59 AM10:09 AM10:20 AM10:28 AM10:36 AM10:45 AM10:52 AM10:59 AM11:07 AM11:13 AM
95810:53 AM10:55 AM10:59 AM11:09 AM11:20 AM11:28 AM11:36 AM11:45 AM11:52 AM11:59 AM12:07 PM12:13 PM
958wheelchair access11:53 AM11:55 AM11:59 AM12:09 PM12:20 PM12:28 PM12:36 PM12:45 PM12:52 PM12:59 PM1:07 PM1:13 PM
958wheelchair access12:53 PM12:55 PM12:59 PM1:09 PM1:20 PM1:28 PM1:36 PM1:45 PM1:52 PM1:59 PM2:07 PM2:13 PM
958wheelchair access1:53 PM1:55 PM1:59 PM2:09 PM2:20 PM2:28 PM2:36 PM2:45 PM2:52 PM2:59 PM3:07 PM3:13 PM
9582:53 PM2:55 PM2:59 PM3:09 PM3:20 PM3:28 PM3:36 PM3:45 PM3:52 PM3:59 PM4:07 PM4:13 PM
958wheelchair access3:53 PM3:55 PM3:59 PM4:09 PM4:20 PM4:28 PM4:36 PM4:45 PM4:52 PM4:59 PM5:07 PM5:13 PM
9584:53 PM4:55 PM4:59 PM5:09 PM5:20 PM5:28 PM5:36 PM5:45 PM5:52 PM5:59 PM6:07 PM6:13 PM
958wheelchair access5:53 PM5:55 PM5:59 PM6:09 PM6:20 PM6:28 PM6:36 PM6:45 PM6:52 PM6:59 PM7:07 PM7:13 PM
958wheelchair access6:54 PM6:56 PM6:59 PM7:09 PM7:20 PM7:28 PM7:36 PM7:45 PM7:52 PM7:59 PM8:07 PM8:13 PM
9587:54 PM7:56 PM7:59 PM8:09 PM8:20 PM8:28 PM8:36 PM8:45 PM8:52 PM8:59 PM9:07 PM9:13 PM
958wheelchair access8:54 PM8:56 PM8:59 PM9:09 PM9:20 PM9:28 PM9:36 PM9:45 PM9:52 PM9:59 PM10:07 PM10:13 PM
9589:54 PM9:56 PM9:59 PM10:09 PM10:20 PM10:28 PM10:36 PM10:45 PM10:52 PM10:59 PM11:07 PM11:13 PM
958wheelchair access10:54 PM10:56 PM10:59 PM11:09 PM11:20 PM11:28 PM11:36 PM11:45 PM11:52 PM...............
+

 

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
 City Interchange
+ Platform 8
Macarthur\NorthbourneFlemington RdFlemington/NullaborAnthony Rolfe/MoonlightGungahlin Market PlaceShoalhaven / Katherine AveNgunnawal PrimaryChuculba/William SlimCameron Ave Bus StationLathlain St Bus StationCohen St Bus Station
958wheelchair access...............7:23 AM7:30 AM7:39 AM7:47 AM7:55 AM8:06 AM8:16 AM8:18 AM8:22 AM
958wheelchair access7:59 AM8:05 AM8:13 AM8:20 AM8:27 AM8:36 AM8:44 AM8:52 AM9:03 AM9:13 AM9:15 AM9:19 AM
9588:59 AM9:05 AM9:13 AM9:20 AM9:27 AM9:36 AM9:44 AM9:52 AM10:03 AM10:13 AM10:15 AM10:19 AM
9589:59 AM10:05 AM10:13 AM10:20 AM10:27 AM10:36 AM10:44 AM10:52 AM11:03 AM11:13 AM11:15 AM11:19 AM
958wheelchair access10:59 AM11:05 AM11:13 AM11:20 AM11:27 AM11:36 AM11:44 AM11:52 AM12:03 PM12:13 PM12:15 PM12:19 PM
958wheelchair access11:59 AM12:05 PM12:13 PM12:20 PM12:27 PM12:36 PM12:44 PM12:52 PM1:03 PM1:13 PM1:15 PM1:19 PM
958wheelchair access12:59 PM1:05 PM1:13 PM1:20 PM1:27 PM1:36 PM1:44 PM1:52 PM2:03 PM2:13 PM2:15 PM2:19 PM
958wheelchair access1:59 PM2:05 PM2:13 PM2:20 PM2:27 PM2:36 PM2:44 PM2:52 PM3:03 PM3:13 PM3:15 PM3:19 PM
9582:59 PM3:05 PM3:13 PM3:20 PM3:27 PM3:36 PM3:44 PM3:52 PM4:03 PM4:13 PM4:15 PM4:19 PM
958wheelchair access3:59 PM4:05 PM4:13 PM4:20 PM4:27 PM4:36 PM4:44 PM4:52 PM5:03 PM5:13 PM5:15 PM5:19 PM
958wheelchair access4:59 PM5:05 PM5:13 PM5:20 PM5:27 PM5:36 PM5:44 PM5:52 PM6:03 PM6:13 PM6:15 PM6:19 PM
958wheelchair access5:59 PM6:05 PM6:13 PM6:20 PM6:27 PM6:36 PM6:44 PM6:52 PM7:03 PM7:13 PM7:15 PM7:18 PM
958wheelchair access6:59 PM7:05 PM7:13 PM7:20 PM7:27 PM7:36 PM7:44 PM7:52 PM8:03 PM8:13 PM8:15 PM8:18 PM
958wheelchair access7:59 PM8:05 PM8:13 PM8:20 PM8:27 PM8:36 PM8:44 PM8:52 PM9:03 PM9:13 PM9:15 PM9:18 PM
958wheelchair access8:59 PM9:05 PM9:13 PM9:20 PM9:27 PM9:36 PM9:44 PM9:52 PM10:03 PM10:13 PM10:15 PM10:18 PM
9589:59 PM10:05 PM10:13 PM10:20 PM10:27 PM10:36 PM10:44 PM10:52 PM11:03 PM11:13 PM11:15 PM11:18 PM
958wheelchair access10:59 PM11:05 PM11:13 PM11:20 PM11:27 PM11:36 PM..............................
+

^top^

+

Sunday

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
 Cohen St Bus Station
+ Platform 1
Lathlain St Bus Station
+ Platform 2
Cameron Ave Bus Station
+ Platform 2
Chuculba/William SlimNgunnawal PrimaryShoalhaven / Katherine AveGungahlin Market PlaceAnthony Rolfe/MoonlightFlemington/NullaborFlemington RdMacarthur\NorthbourneCity Interchange
9588:53 AM8:55 AM8:59 AM9:09 AM9:20 AM9:28 AM9:36 AM9:45 AM9:52 AM9:59 AM10:07 AM10:13 AM
958wheelchair access9:53 AM9:55 AM9:59 AM10:09 AM10:20 AM10:28 AM10:36 AM10:45 AM10:52 AM10:59 AM11:07 AM11:13 AM
95810:53 AM10:55 AM10:59 AM11:09 AM11:20 AM11:28 AM11:36 AM11:45 AM11:52 AM11:59 AM12:07 PM12:13 PM
95811:53 AM11:55 AM11:59 AM12:09 PM12:20 PM12:28 PM12:36 PM12:45 PM12:52 PM12:59 PM1:07 PM1:13 PM
958wheelchair access12:53 PM12:55 PM12:59 PM1:09 PM1:20 PM1:28 PM1:36 PM1:45 PM1:52 PM1:59 PM2:07 PM2:13 PM
9581:53 PM1:55 PM1:59 PM2:09 PM2:20 PM2:28 PM2:36 PM2:45 PM2:52 PM2:59 PM3:07 PM3:13 PM
958wheelchair access2:53 PM2:55 PM2:59 PM3:09 PM3:20 PM3:28 PM3:36 PM3:45 PM3:52 PM3:59 PM4:07 PM4:13 PM
9583:53 PM3:55 PM3:59 PM4:09 PM4:20 PM4:28 PM4:36 PM4:45 PM4:52 PM4:59 PM5:07 PM5:13 PM
958wheelchair access4:53 PM4:55 PM4:59 PM5:09 PM5:20 PM5:28 PM5:36 PM5:45 PM5:52 PM5:59 PM6:07 PM6:13 PM
958wheelchair access5:53 PM5:55 PM5:59 PM6:09 PM6:20 PM6:28 PM6:36 PM6:45 PM6:52 PM6:59 PM7:07 PM7:13 PM
9586:54 PM6:56 PM6:59 PM7:09 PM7:20 PM7:28 PM7:36 PM7:45 PM7:52 PM7:59 PM8:07 PM8:13 PM
+

 

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
 City Interchange
+ Platform 8
Macarthur\NorthbourneFlemington RdFlemington/NullaborAnthony Rolfe/MoonlightGungahlin Market PlaceShoalhaven / Katherine AveNgunnawal PrimaryChuculba/William SlimCameron Ave Bus StationLathlain St Bus StationCohen St Bus Station
958wheelchair access8:59 AM9:05 AM9:13 AM9:20 AM9:27 AM9:36 AM9:44 AM9:52 AM10:03 AM10:13 AM10:15 AM10:19 AM
958wheelchair access9:59 AM10:05 AM10:13 AM10:20 AM10:27 AM10:36 AM10:44 AM10:52 AM11:03 AM11:13 AM11:15 AM11:19 AM
958wheelchair access10:59 AM11:05 AM11:13 AM11:20 AM11:27 AM11:36 AM11:44 AM11:52 AM12:03 PM12:13 PM12:15 PM12:19 PM
958wheelchair access11:59 AM12:05 PM12:13 PM12:20 PM12:27 PM12:36 PM12:44 PM12:52 PM1:03 PM1:13 PM1:15 PM1:19 PM
958wheelchair access12:59 PM1:05 PM1:13 PM1:20 PM1:27 PM1:36 PM1:44 PM1:52 PM2:03 PM2:13 PM2:15 PM2:19 PM
9581:59 PM2:05 PM2:13 PM2:20 PM2:27 PM2:36 PM2:44 PM2:52 PM3:03 PM3:13 PM3:15 PM3:19 PM
958wheelchair access2:59 PM3:05 PM3:13 PM3:20 PM3:27 PM3:36 PM3:44 PM3:52 PM4:03 PM4:13 PM4:15 PM4:19 PM
958wheelchair access3:59 PM4:05 PM4:13 PM4:20 PM4:27 PM4:36 PM4:44 PM4:52 PM5:03 PM5:13 PM5:15 PM5:19 PM
9584:59 PM5:05 PM5:13 PM5:20 PM5:27 PM5:36 PM5:44 PM5:52 PM6:03 PM6:13 PM6:15 PM6:19 PM
9585:59 PM6:05 PM6:13 PM6:20 PM6:27 PM6:36 PM6:44 PM6:52 PM7:03 PM7:13 PM7:15 PM7:18 PM
958wheelchair access6:59 PM7:05 PM7:13 PM7:20 PM7:27 PM7:36 PM7:44 PM7:52 PM8:03 PM8:13 PM8:15 PM8:18 PM
+

wheelchair_icon- Wheelchair Accessible Bus

+
+ + + + + --- /dev/null +++ b/maxious-canberra-transit-feed/source-html/Route_960wkend.html @@ -1,1 +1,460 @@ + + + + + + + + +Route_960 + + + + + +
+

Chosen services: 960

+ +

View timetable and map

+

This timetable is effective from Monday 25 May 2009.

+ +

Saturday

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
 Tuggeranong Interchange
+ Platform 3
Kambah HighMount Neighbour SchoolWoden Interchange
960wheelchair access7:55 AM8:05 AM8:11 AM8:23 AM
960wheelchair access8:55 AM9:05 AM9:11 AM9:23 AM
960wheelchair access9:55 AM10:05 AM10:11 AM10:23 AM
960wheelchair access10:55 AM11:05 AM11:11 AM11:23 AM
960wheelchair access11:55 AM12:05 PM12:11 PM12:23 PM
960wheelchair access12:55 PM1:05 PM1:11 PM1:23 PM
960wheelchair access1:55 PM2:05 PM2:11 PM2:23 PM
960wheelchair access2:55 PM3:05 PM3:11 PM3:23 PM
960wheelchair access3:55 PM4:05 PM4:11 PM4:23 PM
960wheelchair access4:55 PM5:05 PM5:11 PM5:23 PM
960wheelchair access5:55 PM6:05 PM6:11 PM6:23 PM
960wheelchair access6:55 PM7:05 PM7:11 PM7:21 PM
960wheelchair access7:55 PM8:04 PM8:10 PM8:20 PM
960wheelchair access8:55 PM9:04 PM9:10 PM9:20 PM
960wheelchair access9:55 PM10:04 PM10:10 PM10:20 PM
960wheelchair access10:55 PM11:04 PM11:10 PM11:20 PM
+

 

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
 Woden Interchange
+ Platform 5
Mount Neighbour SchoolKambah HighTuggeranong Interchange
960wheelchair access8:50 AM9:02 AM9:08 AM9:18 AM
960wheelchair access9:50 AM10:02 AM10:08 AM10:18 AM
960wheelchair access10:50 AM11:02 AM11:08 AM11:18 AM
960wheelchair access11:50 AM12:02 PM12:08 PM12:18 PM
96012:50 PM1:02 PM1:08 PM1:18 PM
960wheelchair access1:50 PM2:02 PM2:08 PM2:18 PM
960wheelchair access2:50 PM3:02 PM3:08 PM3:18 PM
960wheelchair access3:50 PM4:02 PM4:08 PM4:18 PM
960wheelchair access4:50 PM5:02 PM5:08 PM5:18 PM
960wheelchair access5:50 PM6:02 PM6:08 PM6:18 PM
960wheelchair access6:50 PM7:02 PM7:08 PM7:17 PM
960wheelchair access7:50 PM8:00 PM8:06 PM8:15 PM
960wheelchair access8:50 PM9:00 PM9:06 PM9:15 PM
960wheelchair access9:50 PM10:00 PM10:06 PM10:15 PM
960wheelchair access10:50 PM11:00 PM11:06 PM11:15 PM
+

^top^

+

Sunday

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
 Tuggeranong Interchange
+ Platform 3
Kambah HighMount Neighbour SchoolWoden Interchange
960wheelchair access7:55 AM8:05 AM8:11 AM8:23 AM
960wheelchair access8:55 AM9:05 AM9:11 AM9:23 AM
960wheelchair access9:55 AM10:05 AM10:11 AM10:23 AM
960wheelchair access10:55 AM11:05 AM11:11 AM11:23 AM
960wheelchair access11:55 AM12:05 PM12:11 PM12:23 PM
960wheelchair access12:55 PM1:05 PM1:11 PM1:23 PM
9601:55 PM2:05 PM2:11 PM2:23 PM
960wheelchair access2:55 PM3:05 PM3:11 PM3:23 PM
960wheelchair access3:55 PM4:05 PM4:11 PM4:23 PM
960wheelchair access4:55 PM5:05 PM5:11 PM5:23 PM
960wheelchair access5:55 PM6:05 PM6:11 PM6:23 PM
960wheelchair access6:55 PM7:05 PM7:11 PM7:23 PM
+

 

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
 Woden Interchange
+ Platform 5
Mount Neighbour SchoolKambah HighTuggeranong Interchange
960wheelchair access8:50 AM9:02 AM9:08 AM9:18 AM
960wheelchair access9:50 AM10:02 AM10:08 AM10:18 AM
96010:50 AM11:02 AM11:08 AM11:18 AM
960wheelchair access11:50 AM12:02 PM12:08 PM12:18 PM
96012:50 PM1:02 PM1:08 PM1:18 PM
960wheelchair access1:50 PM2:02 PM2:08 PM2:18 PM
960wheelchair access2:50 PM3:02 PM3:08 PM3:18 PM
960wheelchair access3:50 PM4:02 PM4:08 PM4:18 PM
960wheelchair access4:50 PM5:02 PM5:08 PM5:18 PM
960wheelchair access5:50 PM6:02 PM6:08 PM6:18 PM
960wheelchair access6:50 PM7:02 PM7:08 PM7:18 PM
+

wheelchair_icon- Wheelchair Accessible Bus

+
+ + + + + --- /dev/null +++ b/maxious-canberra-transit-feed/source-html/Route_961wkend.html @@ -1,1 +1,432 @@ + + + + + + + + +Route_961 + + + + + +
+

Chosen services: 961

+ +

View timetable and map

+

This timetable is effective from Monday 25 May 2009.

+ +

Saturdays

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
 Tuggeranong Interchange
+ Platform 3
Erindale CentreAthllon/Sulwood KambahWoden Interchange
961wheelchair access8:42 AM8:56 AM9:06 AM9:15 AM
961wheelchair access9:42 AM9:56 AM10:06 AM10:15 AM
961wheelchair access10:42 AM10:56 AM11:06 AM11:15 AM
961wheelchair access11:42 AM11:56 AM12:06 PM12:15 PM
961wheelchair access12:42 PM12:56 PM1:06 PM1:15 PM
961wheelchair access1:42 PM1:56 PM2:06 PM2:15 PM
961wheelchair access2:42 PM2:56 PM3:06 PM3:15 PM
961wheelchair access3:42 PM3:56 PM4:06 PM4:15 PM
961wheelchair access4:42 PM4:56 PM5:06 PM5:15 PM
961wheelchair access5:42 PM5:56 PM6:06 PM6:15 PM
961wheelchair access6:42 PM6:56 PM7:06 PM7:15 PM
961wheelchair access7:42 PM7:56 PM8:06 PM8:15 PM
961wheelchair access8:42 PM8:56 PM9:06 PM9:15 PM
961wheelchair access9:42 PM9:56 PM10:06 PM10:15 PM
961wheelchair access10:42 PM10:56 PM11:06 PM11:15 PM
+

 

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
 Woden Interchange
+ Platform 11
Athllon/Sulwood KambahErindale CentreTuggeranong Interchange
961wheelchair access8:31 AM8:40 AM8:50 AM9:03 AM
961wheelchair access9:31 AM9:40 AM9:50 AM10:03 AM
961wheelchair access10:31 AM10:40 AM10:50 AM11:03 AM
96111:31 AM11:40 AM11:50 AM12:03 PM
961wheelchair access12:31 PM12:40 PM12:50 PM1:03 PM
961wheelchair access1:31 PM1:40 PM1:50 PM2:03 PM
961wheelchair access2:31 PM2:40 PM2:50 PM3:03 PM
961wheelchair access3:31 PM3:40 PM3:50 PM4:03 PM
961wheelchair access4:31 PM4:40 PM4:50 PM5:03 PM
961wheelchair access5:31 PM5:40 PM5:50 PM6:03 PM
961wheelchair access6:26 PM6:35 PM6:45 PM6:58 PM
961wheelchair access7:26 PM7:35 PM7:45 PM7:58 PM
961wheelchair access8:26 PM8:35 PM8:45 PM8:58 PM
961wheelchair access9:26 PM9:35 PM9:45 PM9:58 PM
961wheelchair access10:26 PM10:35 PM10:45 PM10:58 PM
961wheelchair access11:26 PM11:35 PM11:45 PM11:58 PM
+

^top^

+

Sundays

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
 Tuggeranong Interchange
+ Platform 3
Erindale CentreAthllon/Sulwood KambahWoden Interchange
961wheelchair access9:42 AM9:56 AM10:06 AM10:15 AM
961wheelchair access10:42 AM10:56 AM11:06 AM11:15 AM
96111:42 AM11:56 AM12:06 PM12:15 PM
961wheelchair access12:42 PM12:56 PM1:06 PM1:15 PM
961wheelchair access1:42 PM1:56 PM2:06 PM2:15 PM
961wheelchair access2:42 PM2:56 PM3:06 PM3:15 PM
961wheelchair access3:42 PM3:56 PM4:06 PM4:15 PM
961wheelchair access4:42 PM4:56 PM5:06 PM5:15 PM
961wheelchair access5:42 PM5:56 PM6:06 PM6:15 PM
+

 

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
 Woden Interchange
+ Platform 11
Athllon/Sulwood KambahErindale CentreTuggeranong Interchange
961wheelchair access9:31 AM9:40 AM9:50 AM10:03 AM
961wheelchair access10:31 AM10:40 AM10:50 AM11:03 AM
961wheelchair access11:31 AM11:40 AM11:50 AM12:03 PM
961wheelchair access12:31 PM12:40 PM12:50 PM1:03 PM
961wheelchair access1:31 PM1:40 PM1:50 PM2:03 PM
961wheelchair access2:31 PM2:40 PM2:50 PM3:03 PM
961wheelchair access3:31 PM3:40 PM3:50 PM4:03 PM
961wheelchair access4:31 PM4:40 PM4:50 PM5:03 PM
961wheelchair access5:31 PM5:40 PM5:50 PM6:03 PM
961wheelchair access6:31 PM6:40 PM6:50 PM7:03 PM
+

wheelchair_icon- Wheelchair Accessible Bus

+
+ + + + + --- /dev/null +++ b/maxious-canberra-transit-feed/source-html/Route_962wkend.html @@ -1,1 +1,440 @@ + + + + + + + + +Route_962 + + + + + +
+

Chosen services: 962

+ +

View timetable and map

+

This timetable is effective from Monday 25 May 2009.

+ +

Saturday

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
 Tuggeranong Interchange
+ Platform 4
Kambah HighKambah Village Woden Interchange
962wheelchair access8:24 AM8:31 AM8:39 AM8:52 AM
962wheelchair access9:25 AM9:32 AM9:40 AM9:53 AM
962wheelchair access10:25 AM10:32 AM10:40 AM10:53 AM
962wheelchair access11:25 AM11:32 AM11:40 AM11:53 AM
962wheelchair access12:25 PM12:32 PM12:40 PM12:53 PM
9621:25 PM1:32 PM1:40 PM1:53 PM
962wheelchair access2:25 PM2:32 PM2:40 PM2:53 PM
962wheelchair access3:25 PM3:32 PM3:40 PM3:53 PM
962wheelchair access4:24 PM4:31 PM4:39 PM4:52 PM
962wheelchair access5:24 PM5:31 PM5:39 PM5:52 PM
962wheelchair access6:25 PM6:32 PM6:39 PM6:50 PM
962wheelchair access7:25 PM7:31 PM7:38 PM7:49 PM
962wheelchair access8:25 PM8:31 PM8:38 PM8:49 PM
962wheelchair access9:25 PM9:31 PM9:38 PM9:49 PM
962wheelchair access10:25 PM10:31 PM10:38 PM10:49 PM
962wheelchair access11:25 PM11:31 PM11:38 PM11:49 PM
+

 

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
 Woden Interchange
+ Platform 5
Kambah Village Kambah High Tuggeranong Interchange
962wheelchair access8:51 AM9:02 AM9:10 AM9:17 AM
962wheelchair access9:51 AM10:02 AM10:10 AM10:17 AM
962wheelchair access10:51 AM11:02 AM11:10 AM11:17 AM
962wheelchair access11:51 AM12:02 PM12:10 PM12:17 PM
962wheelchair access12:51 PM1:02 PM1:10 PM1:17 PM
962wheelchair access1:51 PM2:02 PM2:10 PM2:17 PM
962wheelchair access2:51 PM3:02 PM3:10 PM3:17 PM
962wheelchair access3:51 PM4:02 PM4:10 PM4:17 PM
962wheelchair access4:51 PM5:02 PM5:10 PM5:17 PM
962wheelchair access5:51 PM6:02 PM6:10 PM6:17 PM
962wheelchair access6:51 PM7:02 PM7:10 PM7:17 PM
962wheelchair access7:51 PM8:02 PM8:10 PM8:17 PM
962wheelchair access8:51 PM9:02 PM9:10 PM9:17 PM
962wheelchair access9:51 PM10:02 PM10:10 PM10:17 PM
962wheelchair access10:51 PM11:02 PM11:10 PM11:17 PM
+

 

+

^top^

+

Sunday

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
 Tuggeranong Interchange
+ Platform 4
Kambah HighKambah Village Woden Interchange
962wheelchair access9:25 AM9:32 AM9:40 AM9:53 AM
962wheelchair access10:25 AM10:32 AM10:40 AM10:53 AM
96211:25 AM11:32 AM11:40 AM11:53 AM
962wheelchair access12:25 PM12:32 PM12:40 PM12:53 PM
962wheelchair access1:25 PM1:32 PM1:40 PM1:53 PM
962wheelchair access2:25 PM2:32 PM2:40 PM2:53 PM
9623:25 PM3:32 PM3:40 PM3:53 PM
962wheelchair access4:25 PM4:32 PM4:40 PM4:53 PM
962wheelchair access5:25 PM5:32 PM5:40 PM5:53 PM
962wheelchair access6:25 PM6:32 PM6:39 PM6:50 PM
+

 

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
 Woden Interchange
+ Platform 5
Kambah VillageKambah HighTuggeranong Interchange
962wheelchair access9:51 AM10:02 AM10:10 AM10:17 AM
962wheelchair access10:51 AM11:02 AM11:10 AM11:17 AM
962wheelchair access11:51 AM12:02 PM12:10 PM12:17 PM
962wheelchair access12:51 PM1:02 PM1:10 PM1:17 PM
962wheelchair access1:51 PM2:02 PM2:10 PM2:17 PM
962wheelchair access2:51 PM3:02 PM3:10 PM3:17 PM
962wheelchair access3:51 PM4:02 PM4:10 PM4:17 PM
962wheelchair access4:51 PM5:02 PM5:10 PM5:17 PM
962wheelchair access5:51 PM6:02 PM6:10 PM6:17 PM
962wheelchair access6:51 PM7:02 PM7:10 PM7:17 PM
+

wheelchair_icon- Wheelchair Accessible Bus

+
+ + + + + --- /dev/null +++ b/maxious-canberra-transit-feed/source-html/Route_964wkend.html @@ -1,1 +1,446 @@ + + + + + + + + +Route_964 + + + + + +
+

Chosen services: 964

+ +

View timetable and map

+

This timetable is effective from Monday 25 May 2009.

+ +

Saturdays

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
 Tuggeranong Interchange
+ Platform 5
Erindale CentreAthllon/Sulwood KambahWoden Interchange
964wheelchair access8:25 AM8:37 AM8:49 AM8:58 AM
964wheelchair access9:25 AM9:37 AM9:49 AM9:58 AM
964wheelchair access10:25 AM10:37 AM10:49 AM10:58 AM
964wheelchair access11:25 AM11:37 AM11:49 AM11:58 AM
964wheelchair access12:25 PM12:37 PM12:49 PM12:58 PM
9641:25 PM1:37 PM1:49 PM1:58 PM
964wheelchair access2:25 PM2:37 PM2:49 PM2:58 PM
964wheelchair access3:25 PM3:37 PM3:49 PM3:58 PM
964wheelchair access4:25 PM4:37 PM4:49 PM4:58 PM
964wheelchair access5:25 PM5:37 PM5:49 PM5:58 PM
964wheelchair access6:25 PM6:37 PM6:49 PM6:58 PM
964wheelchair access7:25 PM7:37 PM7:49 PM7:58 PM
964wheelchair access8:25 PM8:37 PM8:49 PM8:58 PM
964wheelchair access9:25 PM9:37 PM9:49 PM9:58 PM
964wheelchair access10:25 PM10:37 PM10:49 PM10:58 PM
964wheelchair access11:25 PM11:37 PM11:49 PM.....
+

 

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
 Woden Interchange
+ Platform 11
Athllon/Sulwood KambahErindale CentreTuggeranong Interchange
964wheelchair access9:05 AM9:14 AM9:26 AM9:37 AM
964wheelchair access10:05 AM10:14 AM10:26 AM10:37 AM
964wheelchair access11:05 AM11:14 AM11:26 AM11:37 AM
964wheelchair access12:05 PM12:14 PM12:26 PM12:37 PM
964wheelchair access1:05 PM1:14 PM1:26 PM1:37 PM
964wheelchair access2:05 PM2:14 PM2:26 PM2:37 PM
964wheelchair access3:05 PM3:14 PM3:26 PM3:37 PM
964wheelchair access4:05 PM4:14 PM4:26 PM4:37 PM
964wheelchair access5:05 PM5:14 PM5:26 PM5:37 PM
964wheelchair access6:05 PM6:14 PM6:26 PM6:37 PM
964wheelchair access7:05 PM7:14 PM7:26 PM7:37 PM
964wheelchair access8:05 PM8:14 PM8:26 PM8:37 PM
964wheelchair access9:05 PM9:14 PM9:26 PM9:37 PM
964wheelchair access10:05 PM10:14 PM10:26 PM10:37 PM
964wheelchair access11:05 PM11:14 PM11:26 PM11:37 PM
+

^top^

+

Sundays

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
 Tuggeranong Interchange
+ Platform 5
Erindale CentreAthllon/Sulwood KambahWoden Interchange
964wheelchair access9:25 AM9:37 AM9:49 AM9:58 AM
964wheelchair access10:25 AM10:37 AM10:49 AM10:58 AM
964wheelchair access11:25 AM11:37 AM11:49 AM11:58 AM
964wheelchair access12:25 PM12:37 PM12:49 PM12:58 PM
964wheelchair access1:25 PM1:37 PM1:49 PM1:58 PM
964wheelchair access2:25 PM2:37 PM2:49 PM2:58 PM
964wheelchair access3:25 PM3:37 PM3:49 PM3:58 PM
964wheelchair access4:25 PM4:37 PM4:49 PM4:58 PM
964wheelchair access5:25 PM5:37 PM5:49 PM5:58 PM
964wheelchair access6:25 PM6:37 PM6:49 PM6:58 PM
+

 

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
 Woden Interchange
+ Platform 11
Athllon/Sulwood KambahErindale CentreTuggeranong Interchange
964wheelchair access9:05 AM9:14 AM9:26 AM9:37 AM
964wheelchair access10:05 AM10:14 AM10:26 AM10:37 AM
96411:05 AM11:14 AM11:26 AM11:37 AM
964wheelchair access12:05 PM12:14 PM12:26 PM12:37 PM
964wheelchair access1:05 PM1:14 PM1:26 PM1:37 PM
964wheelchair access2:05 PM2:14 PM2:26 PM2:37 PM
964wheelchair access3:05 PM3:14 PM3:26 PM3:37 PM
964wheelchair access4:05 PM4:14 PM4:26 PM4:37 PM
964wheelchair access5:05 PM5:14 PM5:26 PM5:37 PM
964wheelchair access6:05 PM6:14 PM6:26 PM6:37 PM
964wheelchair access7:05 PM7:14 PM7:26 PM7:37 PM
+

wheelchair_icon- Wheelchair Accessible Bus

+
+ + + + + --- /dev/null +++ b/maxious-canberra-transit-feed/source-html/Route_966wkend.html @@ -1,1 +1,346 @@ + + + + + + + + +Route_966 + + + + + +
+

Chosen services: 966

+ +

View timetable and map

+

This timetable is effective from Monday 25 May 2009.

+ +

Saturdays

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
 Tuggeranong Interchange
+ Platform 7
Erindale CentreGowrieChisholm ShopsGowrieErindale CentreTuggeranong Interchange
966wheelchair access...............7:42 AM7:55 AM8:04 AM8:16 AM
966wheelchair access8:08 AM8:21 AM8:30 AM8:42 AM8:55 AM9:04 AM9:16 AM
966wheelchair access9:08 AM9:21 AM9:30 AM9:42 AM9:55 AM10:04 AM10:16 AM
966wheelchair access10:08 AM10:21 AM10:30 AM10:42 AM10:55 AM11:04 AM11:16 AM
966wheelchair access11:08 AM11:21 AM11:30 AM11:42 AM11:55 AM12:04 PM12:16 PM
96612:08 PM12:21 PM12:30 PM12:42 PM12:55 PM1:04 PM1:16 PM
966wheelchair access1:08 PM1:21 PM1:30 PM1:42 PM1:55 PM2:04 PM2:16 PM
966wheelchair access2:08 PM2:21 PM2:30 PM2:42 PM2:55 PM3:04 PM3:16 PM
966wheelchair access3:08 PM3:21 PM3:30 PM3:42 PM3:55 PM4:04 PM4:16 PM
966wheelchair access4:08 PM4:21 PM4:30 PM4:42 PM4:55 PM5:04 PM5:16 PM
966wheelchair access5:08 PM5:21 PM5:30 PM5:42 PM5:55 PM6:04 PM6:16 PM
966wheelchair access6:08 PM6:21 PM6:30 PM6:42 PM6:55 PM7:04 PM7:16 PM
966wheelchair access7:03 PM7:16 PM7:25 PM7:37 PM7:50 PM7:59 PM8:11 PM
966wheelchair access8:03 PM8:16 PM8:25 PM8:37 PM8:50 PM8:59 PM9:11 PM
966wheelchair access9:03 PM9:16 PM9:25 PM9:37 PM9:50 PM9:59 PM10:11 PM
966wheelchair access10:03 PM10:16 PM10:25 PM10:37 PM10:50 PM10:59 PM11:11 PM
966wheelchair access11:03 PM11:16 PM11:25 PM11:37 PM...............
+

^top^

+

Sundays

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
 Tuggeranong Interchange
+ Platform 7
Erindale CentreGowrieChisholm ShopsGowrieErindale CentreTuggeranong Interchange
966wheelchair access9:08 AM9:21 AM9:30 AM9:42 AM9:55 AM10:04 AM10:16 AM
966wheelchair access10:08 AM10:21 AM10:30 AM10:42 AM10:55 AM11:04 AM11:16 AM
966wheelchair access11:08 AM11:21 AM11:30 AM11:42 AM11:55 AM12:04 PM12:16 PM
966wheelchair access12:08 PM12:21 PM12:30 PM12:42 PM12:55 PM1:04 PM1:16 PM
966wheelchair access1:08 PM1:21 PM1:30 PM1:42 PM1:55 PM2:04 PM2:16 PM
966wheelchair access2:08 PM2:21 PM2:30 PM2:42 PM2:55 PM3:04 PM3:16 PM
966wheelchair access3:08 PM3:21 PM3:30 PM3:42 PM3:55 PM4:04 PM4:16 PM
966wheelchair access4:08 PM4:21 PM4:30 PM4:42 PM4:55 PM5:04 PM5:16 PM
966wheelchair access5:08 PM5:21 PM5:30 PM5:42 PM5:55 PM6:04 PM6:16 PM
966wheelchair access6:08 PM6:21 PM6:30 PM6:42 PM6:55 PM7:04 PM7:16 PM
966wheelchair access7:08 PM7:21 PM7:30 PM7:42 PM7:55 PM8:04 PM8:16 PM
+

wheelchair_icon- Wheelchair Accessible Bus

+
+ + + + + --- /dev/null +++ b/maxious-canberra-transit-feed/source-html/Route_967wkend.html @@ -1,1 +1,184 @@ + + + + + + + + +Route_967 + + + + + + + +
+

Chosen services: 967

+ +

View timetable and map

+

This timetable is effective from Monday 25 May 2009.

+

 

+ +

Saturdays

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
 Tuggeranong Interchange
+ Platform 7
Erindale CentreChisholm ShopsHeagney/Clift RichardsonTuggeranong Interchange
967wheelchair access9:03 AM9:14 AM9:28 AM9:37 AM9:50 AM
967wheelchair access11:03 AM11:14 AM11:28 AM11:37 AM11:50 AM
967wheelchair access1:03 PM1:14 PM1:28 PM1:37 PM1:50 PM
967wheelchair access3:03 PM3:14 PM3:28 PM3:37 PM3:50 PM
967wheelchair access5:03 PM5:14 PM5:28 PM5:37 PM5:50 PM
967wheelchair access7:03 PM7:14 PM7:28 PM7:37 PM7:50 PM
967wheelchair access9:03 PM9:14 PM9:28 PM9:37 PM9:50 PM
967wheelchair access11:03 PM11:14 PM11:28 PM11:37 PM11:50 PM
+

^top^

+

Sundays

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
 Tuggeranong Interchange
+ Platform 7
Erindale CentreChisholm ShopsHeagney/Clift RichardsonTuggeranong Interchange
967wheelchair access9:03 AM9:14 AM9:28 AM9:37 AM9:50 AM
967wheelchair access11:03 AM11:14 AM11:28 AM11:37 AM11:50 AM
967wheelchair access1:03 PM1:14 PM1:28 PM1:37 PM1:50 PM
967wheelchair access3:03 PM3:14 PM3:28 PM3:37 PM3:50 PM
967wheelchair access5:03 PM5:14 PM5:28 PM5:37 PM5:50 PM
967wheelchair access7:03 PM7:14 PM7:28 PM7:37 PM7:50 PM
+

wheelchair_icon- Wheelchair Accessible Bus

+
+ + + + + --- /dev/null +++ b/maxious-canberra-transit-feed/source-html/Route_968wkend.html @@ -1,1 +1,166 @@ + + + + + + + + +Route_968 + + + + + +
+

Chosen services: 968

+ +

View timetable and map

+

This timetable is effective from Monday 25 May 2009.

+ +

Saturdays

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
 Tuggeranong Interchange
+ Platform 7
Heagney/Clift RichardsonChisholm ShopsErindale CentreTuggeranong Interchange
968wheelchair access8:03 AM8:16 AM8:24 AM8:38 AM8:48 AM
968wheelchair access10:03 AM10:16 AM10:24 AM10:38 AM10:48 AM
968wheelchair access12:03 PM12:16 PM12:24 PM12:38 PM12:48 PM
968wheelchair access2:03 PM2:16 PM2:24 PM2:38 PM2:48 PM
968wheelchair access4:03 PM4:16 PM4:24 PM4:38 PM4:48 PM
968wheelchair access6:03 PM6:16 PM6:24 PM6:38 PM6:48 PM
968wheelchair access8:03 PM8:16 PM8:24 PM8:38 PM8:48 PM
968wheelchair access10:03 PM10:16 PM10:24 PM10:38 PM10:48 PM
+

^top^

+

Sundays

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
 Tuggeranong Interchange
+ Platform 7
Heagney/Clift RichardsonChisholm ShopsErindale CentreTuggeranong Interchange
968wheelchair access10:03 AM10:16 AM10:24 AM10:38 AM10:48 AM
968wheelchair access12:03 PM12:16 PM12:24 PM12:38 PM12:48 PM
968wheelchair access2:03 PM2:16 PM2:24 PM2:38 PM2:48 PM
968wheelchair access4:03 PM4:16 PM4:24 PM4:38 PM4:48 PM
968wheelchair access6:03 PM6:16 PM6:24 PM6:38 PM6:48 PM
+

wheelchair_icon- Wheelchair Accessible Bus

+
+ + + + + --- /dev/null +++ b/maxious-canberra-transit-feed/source-html/Route_980wkend.html @@ -1,1 +1,990 @@ + + + + + + + + +Route_980 + + + + + +
+

Chosen services: 980

+ +

View timetable and map

+

This timetable is effective from Monday 25 May 2009.

+ +

Saturdays

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
 Cohen St Bus Station
+ Platform 1
Lathlain St Bus Station
+ Platform 3
Cameron Ave Bus Station
+ Platform 3
University of CanberraAustralian Institute Sports BruceNational Hockey Centre LynehamMacarthur\NorthbourneCity Interchange
+ Platform 9
Russell OfficesRailway Station KingstonNewcastle Street after Isa StLithgow St Terminus Fyshwick
9807:21 AM7:23 AM7:27 AM7:35 AM7:41 AM7:46 AM7:52 AM7:59 AM8:08 AM8:14 AM8:22 AM8:40 AM
9808:21 AM8:23 AM8:27 AM8:35 AM8:41 AM8:46 AM8:52 AM8:59 AM9:08 AM9:14 AM9:22 AM9:40 AM
9809:21 AM9:23 AM9:27 AM9:35 AM9:41 AM9:46 AM9:52 AM9:59 AM10:08 AM10:14 AM10:22 AM10:40 AM
980wheelchair access10:21 AM10:23 AM10:27 AM10:35 AM10:41 AM10:46 AM10:52 AM10:59 AM11:08 AM11:14 AM11:22 AM11:40 AM
98011:21 AM11:23 AM11:27 AM11:35 AM11:41 AM11:46 AM11:52 AM11:59 AM12:08 PM12:14 PM12:22 PM12:40 PM
98012:21 PM12:23 PM12:27 PM12:35 PM12:41 PM12:46 PM12:52 PM12:59 PM1:08 PM1:14 PM1:22 PM1:40 PM
9801:21 PM1:23 PM1:27 PM1:35 PM1:41 PM1:46 PM1:52 PM1:59 PM2:08 PM2:14 PM2:22 PM2:40 PM
9802:21 PM2:23 PM2:27 PM2:35 PM2:41 PM2:46 PM2:52 PM2:59 PM3:08 PM3:14 PM3:22 PM3:40 PM
9803:21 PM3:23 PM3:27 PM3:35 PM3:41 PM3:46 PM3:52 PM3:59 PM4:08 PM4:14 PM4:22 PM4:40 PM
980wheelchair access...................................4:15 PM4:24 PM4:30 PM..........
980wheelchair access4:21 PM4:23 PM4:27 PM4:35 PM4:41 PM4:46 PM4:52 PM4:59 PM5:08 PM5:14 PM5:22 PM5:40 PM
980wheelchair access5:21 PM5:23 PM5:27 PM5:35 PM5:41 PM5:46 PM5:52 PM5.58 PM ....................
980wheelchair access6:16 PM6:18 PM6:22 PM6:30 PM6:36 PM6:41 PM6:46 PM6.52 PM ....................
9807:27 PM7:29 PM7:32 PM7:39 PM7:45 PM7:50 PM7:55 PM8.01 PM ....................
980wheelchair access8:36 PM8:38 PM8:41 PM8:48 PM8:54 PM8:59 PM9:04 PM9.10 PM ....................
980wheelchair access9:47 PM9:49 PM9:52 PM9:59 PM10:05 PM10:10 PM10:15 PM10.21 PM ....................
980wheelchair access10:59 PM11:01 PM11:04 PM11:11 PM11:17 PM11:22 PM11:27 PM11.33 PM ....................
+

 

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
 Lithgow St Terminus FyshwickCanberra TimesRailway Station KingstonRussell OfficesCity Interchange
+ Platform 8
Macarthur\NorthbourneNational Hockey Centre LynehamAustralian Institute Sports BruceUniversity of CanberraCameron Ave Bus StationLathlain St Bus StationCohen St Bus Station
980wheelchair access....................8:09 AM8:15 AM8:20 AM8:24 AM8:30 AM8:37 AM8:39 AM8:43 AM
9808:45 AM9:04 AM9:11 AM9:17 AM9:28 AM9:34 AM9:39 AM9:43 AM9:49 AM9:56 AM9:58 AM10:02 AM
9809:45 AM10:04 AM10:11 AM10:17 AM10:28 AM10:34 AM10:39 AM10:43 AM10:49 AM10:56 AM10:58 AM11:02 AM
98010:45 AM11:04 AM11:11 AM11:17 AM11:28 AM11:34 AM11:39 AM11:43 AM11:49 AM11:56 AM11:58 AM12:02 PM
980..........11:30 AM11:36 AM11:46 PM...................................
980wheelchair access11:45 AM12:04 PM12:11 PM12:17 PM12:28 PM12:34 PM12:39 PM12:43 PM12:49 PM12:56 PM12:58 PM1:02 PM
98012:45 PM1:04 PM1:11 PM1:17 PM1:28 PM1:34 PM1:39 PM1:43 PM1:49 PM1:56 PM1:58 PM2:02 PM
9801:45 PM2:04 PM2:11 PM2:17 PM2:28 PM2:34 PM2:39 PM2:43 PM2:49 PM2:56 PM2:58 PM3:02 PM
9802:45 PM3:04 PM3:11 PM3:17 PM3:28 PM3:34 PM3:39 PM3:43 PM3:49 PM3:56 PM3:58 PM4:02 PM
9803:45 PM4:04 PM4:11 PM4:17 PM4:28 PM4:34 PM4:39 PM4:43 PM4:49 PM4:56 PM4:58 PM5:02 PM
980wheelchair access..........4:40 PM4:46 PM4:56 PM...................................
9804:45 PM5:04 PM5:11 PM5:17 PM5:28 PM5:34 PM5:39 PM5:43 PM5:49 PM5:56 PM5:58 PM6:02 PM
980wheelchair access5:45 PM6:04 PM6:11 PM6:17 PM6:28 PM6:34 PM6:39 PM6:43 PM6:49 PM6:56 PM6:58 PM7:01 PM
980wheelchair access....................6:55 PM7:01 PM7:06 PM7:10 PM7:16 PM7:23 PM7:25 PM7:28 PM
980wheelchair access....................8:05 PM8:11 PM8:16 PM8:20 PM8:26 PM8:33 PM8:35 PM8:38 PM
980wheelchair access....................9:16 PM9:22 PM9:27 PM9:31 PM9:37 PM9:44 PM9:46 PM9:49 PM
980wheelchair access....................10:27 PM10:33 PM10:38 PM10:42 PM10:48 PM10:55 PM10:57 PM11:00 PM
980wheelchair access....................11:39 PM11:45 PM11:50 PM11:54 PM12:00 AM12:07 AM12:09 AM12:12 AM
+

^top^

+

Sundays

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
 Cohen St Bus Station
+ Platform 1
Lathlain St Bus Station
+ Platform 3
Cameron Ave Bus Station
+ Platform 3
University of CanberraAustralian Institute Sports BruceNational Hockey Centre LynehamMacarthur\NorthbourneCity Interchange
+ Platform 9
Russell OfficesRailway Station KingstonNewcastle Street after Isa StLithgow St Terminus Fyshwick
980wheelchair access8:21 AM8:23 AM8:27 AM8:35 AM8:41 AM8:46 AM8:52 AM8:59 AM9:08 AM9:14 AM9:22 AM9:40 AM
980wheelchair access9:21 AM9:23 AM9:27 AM9:35 AM9:41 AM9:46 AM9:52 AM9:59 AM10:08 AM10:14 AM10:22 AM10:40 AM
98010:21 AM10:23 AM10:27 AM10:35 AM10:41 AM10:46 AM10:52 AM10:59 AM11:08 AM11:14 AM11:22 AM11:40 AM
980wheelchair access11:21 AM11:23 AM11:27 AM11:35 AM11:41 AM11:46 AM11:52 AM11:59 AM12:08 PM12:14 PM12:22 PM12:40 PM
980wheelchair access12:21 PM12:23 PM12:27 PM12:35 PM12:41 PM12:46 PM12:52 PM12:59 PM1:08 PM1:14 PM1:22 PM1:40 PM
980wheelchair access1:21 PM1:23 PM1:27 PM1:35 PM1:41 PM1:46 PM1:52 PM1:59 PM2:08 PM2:14 PM2:22 PM2:40 PM
980wheelchair access2:21 PM2:23 PM2:27 PM2:35 PM2:41 PM2:46 PM2:52 PM2:59 PM3:08 PM3:14 PM3:22 PM3:40 PM
980wheelchair access3:21 PM3:23 PM3:27 PM3:35 PM3:41 PM3:46 PM3:52 PM3:59 PM4:08 PM4:14 PM4:22 PM4:40 PM
980...................................4:15 PM4:24 PM4:30 PM..........
980wheelchair access4:21 PM4:23 PM4:27 PM4:35 PM4:41 PM4:46 PM4:52 PM4:59 PM5:08 PM5:14 PM5:22 PM5:40 PM
980wheelchair access5:21 PM5:23 PM5:27 PM5:35 PM5:41 PM5:46 PM5:52 PM5.58 PM ....................
9806:16 PM6:18 PM6:22 PM6:30 PM6:36 PM6:41 PM6:46 PM6.52 PM ....................
+

 

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
 Lithgow St Terminus FyshwickCanberra TimesRailway Station KingstonRussell OfficesCity Interchange
+ Platform 8
Macarthur\NorthbourneNational Hockey Centre LynehamAustralian Institute Sports BruceUniversity of CanberraCameron Ave Bus StationLathlain St Bus StationCohen St Bus Station
980wheelchair access8:45 AM9:04 AM9:11 AM9:17 AM9:28 AM9:34 AM9:39 AM9:43 AM9:49 AM9:56 AM9:58 AM10:02 AM
980wheelchair access9:45 AM10:04 AM10:11 AM10:17 AM10:28 AM10:34 AM10:39 AM10:43 AM10:49 AM10:56 AM10:58 AM11:02 AM
980wheelchair access10:45 AM11:04 AM11:11 AM11:17 AM11:28 AM11:34 AM11:39 AM11:43 AM11:49 AM11:56 AM11:58 AM12:02 PM
98011:45 AM12:04 PM12:11 PM12:17 PM12:28 PM12:34 PM12:39 PM12:43 PM12:49 PM12:56 PM12:58 PM1:02 PM
980wheelchair access12:45 PM1:04 PM1:11 PM1:17 PM1:28 PM1:34 PM1:39 PM1:43 PM1:49 PM1:56 PM1:58 PM2:02 PM
980wheelchair access1:45 PM2:04 PM2:11 PM2:17 PM2:28 PM2:34 PM2:39 PM2:43 PM2:49 PM2:56 PM2:58 PM3:02 PM
980wheelchair access2:45 PM3:04 PM3:11 PM3:17 PM3:28 PM3:34 PM3:39 PM3:43 PM3:49 PM3:56 PM3:58 PM4:02 PM
980wheelchair access3:45 PM4:04 PM4:11 PM4:17 PM4:28 PM4:34 PM4:39 PM4:43 PM4:49 PM4:56 PM4:58 PM5:02 PM
980..........4:40 PM4:46 PM4.56 PM ...................................
980wheelchair access4:45 PM5:04 PM5:11 PM5:17 PM5:28 PM5:34 PM5:39 PM5:43 PM5:49 PM5:56 PM5:58 PM6:02 PM
980wheelchair access5:45 PM6:04 PM6:11 PM6:17 PM6:28 PM6:34 PM6:39 PM6:43 PM6:49 PM6:56 PM6:58 PM7:01 PM
+

wheelchair_icon- Wheelchair Accessible Bus

+
+ + + + + --- /dev/null +++ b/maxious-canberra-transit-feed/source-html/Route_981wkend.html @@ -1,1 +1,142 @@ + + + + + + + + +Route_981 + + + + + +
+

Chosen services: 981

+ +

View timetable and map

+

This timetable is effective from Monday 25 May 2009.

+ +

Saturdays

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
 City Interchange
+ Platform 9
National AquariumBlack Mountain Telstra TowerBotanic GardensCity Interchange
981 wheelchair access10:20 AM10:34 AM10:42 AM10:48 AM10:55 AM
98111:50 AM12:04 PM12:12 PM12:18 PM12:25 PM
981 wheelchair access1:20 PM1:34 PM1:42 PM1:48 PM1:55 PM
981 wheelchair access2:50 PM3:04 PM3:12 PM3:18 PM3:25 PM
9814:20 PM4:34 PM4:42 PM4:48 PM4:55 PM
+

^top^

+

Sundays

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
 City Interchange
+ Platform 9
National AquariumBlack Mountain Telstra TowerBotanic GardensCity Interchange
981 wheelchair access10:20 AM10:34 AM10:42 AM10:48 AM10:55 AM
98111:50 AM12:04 PM12:12 PM12:18 PM12:25 PM
9811:20 PM1:34 PM1:42 PM1:48 PM1:55 PM
9812:50 PM3:04 PM3:12 PM3:18 PM3:25 PM
981 wheelchair access4:20 PM4:34 PM4:42 PM4:48 PM4:55 PM
+

wheelchair_icon- Wheelchair Accessible Bus

+
+ + + + + --- /dev/null +++ b/maxious-canberra-transit-feed/source-html/Route_982wkend.html @@ -1,1 +1,114 @@ + + + + + + + + +Route_982 + + + + + +
+

Chosen services: 982

+ +

View timetable and map

+

This timetable is effective from Monday 25 May 2009.

+ +

Saturdays

+ + + + + + + + + + + + + + + +
 Bimberi CentreNorthbourne Ave/Antill StMacarthur\NorthbourneCity Interchange
982 wheelchair access7:15 PM7:24 PM7:26 PM7:33 PM
+

 

+ + + + + + + + + + + + + + + + + + + + + + +
 City Interchange
+ Platform 8
Macarthur\NorthbourneNorthbourne Ave/Antill StBimberi Centre
9826:32 AM6:38 AM6:40 AM6:50 AM
982 wheelchair access3:42 PM3:48 PM3:50 PM4:00 PM
+

^top^

+

Sundays

+ + + + + + + + + + + + + + + +
 Bimberi CentreNorthbourne Ave/Antill StMacarthur\NorthbourneCity Interchange
982 wheelchair access7:15 PM7:24 PM7:26 PM7:33 PM
+

 

+ + + + + + + + + + + + + + + +
 City InterchangeMacarthur\NorthbourneNorthbourne Ave/Antill StBimberi Centre
9823:42 PM3:48 PM3:50 PM4:00 PM
+

wheelchair_icon- Wheelchair Accessible Bus

+
+ + + + + --- /dev/null +++ b/maxious-canberra-transit-feed/source-html/Route_988wkend.html @@ -1,1 +1,130 @@ + + + + + + + + +Route_988 + + + + + +
+

Chosen services: 988

+ +

View timetable and map

+

This timetable is effective from Monday 25 May 2009.

+ +

Saturdays

+ + + + + + + + + + + + + + + + + + + + + +
 Woden Interchange
+ Platform 4
Alexander Maconochie Centre Hume
988 wheelchair access8:40 AM9:00 AM
988 wheelchair access12:25 PM12:45 PM
988 wheelchair access5:10 PM5:30 PM
+

 

+ + + + + + + + + + + + + + + + + + + + + +
 Alexander Maconochie Centre HumeWoden Interchange
988 wheelchair access11:35 AM11:55 AM
988 wheelchair access4:35 PM4:55 PM
9887:35 PM7:55 PM
+

Sundays

+ + + + + + + + + + + + + + + + + + + + + +
 Woden InterchangeAlexander Maconochie Centre Hume
988 wheelchair access8:40 AM9:00 AM
98812:25 PM12:45 PM
988 wheelchair access5:10 PM5:30 PM
+

 

+ + + + + + + + + + + + + + + + + + + + + +
 Alexander Maconochie Centre HumeWoden Interchange
988 wheelchair access11:35 AM11:55 AM
988 wheelchair access4:35 PM4:55 PM
988 wheelchair access7:35 PM7:55 PM
+

wheelchair_icon- Wheelchair Accessible Bus

+
+ + + + + --- /dev/null +++ b/maxious-canberra-transit-feed/source-html/get.sh @@ -1,1 +1,4 @@ +#wget -nd -np -r -I /routes_by_number.html,*25_May* --random-wait -c http://www.action.act.gov.au/routes_by_number.html +grep "Page not found" * + --- /dev/null +++ b/maxious-canberra-transit-feed/source-html/getpdfs.sh @@ -1,1 +1,3 @@ +grep .pdf Route* redex.html | perl -0ne 'print "$1\n" while (/.*?<\/a>/igs)' | sed 's/" target="_blank//g' | sed 's/\.\.\///g' | xargs -Ifile wget -c http://www.action.act.gov.au/file + --- /dev/null +++ b/maxious-canberra-transit-feed/source-html/robots.txt @@ -1,1 +1,124 @@ + + + +ACTION Buses - Error On Page + + + + + + + + + + + + + +
+ + + + +
+
+
+

Home-> Page not Found

+
+ + + + + + + + + + + + +

Page not found

+

The action website has been rebuilt and the urls of all files has been changed. You will need to update your bookmarks accordingly.

+

Timetable information can be found at the following url: http://www.action.act.gov.au/routes_timetables.html

+

School Timetable information is available from: http://www.action.act.gov.au/school_services.html

+

Click here to return to your last location.

  +

Alternatively, if you encounter ongoing problems, please email the site administrator.

  + +
+
+ + + + + + + + --- /dev/null +++ b/maxious-canberra-transit-feed/source-html/routes_by_number.html @@ -1,1 +1,142 @@ + + + +ACTION Buses - Routes by Number + + + + + + + + + + + + + +
+ + + + +
+
+
+

Home-> Routes & Timetables-> Routes by Number

+
+ +

Routes by Number

+ + + + + + + + + + + + + +
On this Page: 
Weekday + Routes Sunday + Routes
Saturday + Routes 
+

 

+

Weekday Routes:

+

2,  345,  678, 9, 10, 11, 12, 13, 14, 15, 161718, 19, 21,  22232425262728,   30313943, 44, 45, 51, 52, 56, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 71, 73, 74, 75, 76, 77, 80, 81, 82, 88, 111, 160, 161, 162, 225, 226, 227, 265, 267, 312, 313, 314, 315, 318, 319
+
+
+ Xpresso Direct Travel
+
+ 170, 701, 702, 703, 704, 705, 710, 720, 729, 732, 737, 749, 757, 768, 769, 780, 785, 786, 787, 788
+
+ Intertown Route 300 Series (between interchanges)
+
+ REDEX - Rapid Express GUNGAHLIN - CITY - KINGSTON +
+
+ REDEX 727 +

+

Saturday Routes:

+ +

900, 902, 903, 904, 905, 906, 907, 912, 913, 914, 915, 921, 922, 923, 924, 925, 927, 930, 931, 932, 934, 935, 936, 937, 938, 939, 942, 951, 952, 956, 958, 960, 961, 962, 964, 966, 967, 968, 980, 981, 982, 988
+
+ Intertown Route 900 (between interchanges)

+

Sunday & Public Holiday Routes:

+ +

900, 902, 903, 904, 905, 906, 907, 912, 913, 914, 915, 921, 922, 923, 924, 925, 927, 930, 931, 932, 934, 935, 936, 937, 938, 939, 942, 951, 952, 956, 958, 960, 961, 962, 964, 966, 967, 968, 980, 981, 982, 988
+
+ Intertown Route 900 (between interchanges)

+
+
+ + + + + + --- /dev/null +++ b/maxious-canberra-transit-feed/substitutetimepoints.rb @@ -1,1 +1,60 @@ +require 'rubygems' +require 'pp' +require 'yaml' +time_points = [] +time_points_sources = Hash.new([]) +Dir.chdir("output") +Dir.glob("*.yml") { |file| + timetable = YAML::load_file(file) + time_points = time_points | timetable["time_points"] + timetable["time_points"].each do |timepoint| + time_points_sources[timepoint] = time_points_sources[timepoint] | [ file ] + end +} +pp time_points.sort! +pp time_points_sources.sort + +time_point_corrections = {"North Lynehamham" => "North Lyneham", + "Lathlain St Platform 2" => "Lathlain St Bus Station- Platform 2", + "Lathlain St Sation - Platform 5" => "Lathlain St Bus Station - Platform 5", + "Lathlain Steet Station" => "Lathlain St Bus Station", + "Lathlain St - Platform 3" => "Lathlain St Bus Station - Platform 3", + "Lathlain Steet Station - Platform 3" => "Lathlain St Bus Station - Platform 3", + "Lathlain Street Station" => "Lathlain St Station", + "Manuka Captain Cook" => "Manuka, Captain Cook", + "Flemington Rd, Sandford St" => "Flemington Rd/Sandford St", + "Erindale Centre / - Sternberg Crescent" => "Erindale Drive/Sternberg", +"Canberra Hospita" => "Canberra Hospital", +"Cohen Str Station - Platform 1" => "Cohen Street Bus Station - Platform 1", +"Cohen Street Station" => "Cohen Street Bus Station", +"Cohen Street Station - Platform 2" => "Cohen Street Bus Station - Platform 2", +"Cohn St Station - Platform 3" => "Cohen Street Bus Station - Platform 3", +"Cohen St Station" => "Cohen Street Bus Station", +"Cohen St Station - Platform 1" => "Cohen Street Bus Station - Platform 1", +"Cohen St Station - Platform 2" => "Cohen Street Bus Station - Platform 2", +"Cohen St Station - Platform 3" => "Cohen Street Bus Station - Platform 3", +"Cohen St Station - Platform 4" => "Cohen Street Bus Station - Platform 4", +"Cohen St Station - Platform 5" => "Cohen Street Bus Station - Platform 5", +"City - Platform 7" => "City Interchange - Platform 7", +"Cameron Avenue Station" => "Cameron Ave Bus Station", +"Cameron Avenue Station - Platform 2" => "Cameron Ave Bus Station - Platform 2", +"Cameron Avenue Station - Platform 3" => "Cameron Ave Bus Station - Platform 3", +"Cameron Ave Station" => "Cameron Ave Bus Station", +"Cameron Ave Station - Platform 1" => "Cameron Ave Bus Station - Platform 1", +"Cameron Ave Station - Platform 2" => "Cameron Ave Bus Station - Platform 2", +"Cameron Ave Station - Platform 3" => "Cameron Ave Bus Station - Platform 3", +"Cameron Ave Station - Platform 4" => "Cameron Ave Bus Station - Platform 4", +"Cameron Ave Station - Platform 5" => "Cameron Ave Bus Station - Platform 5", +"Burton & Garranan Hall, Daley Road ANU" => "Burton & Garran Hall, Daley Road ANU", +"Burton & Garranan Hall,Daley Road ANU" => "Burton & Garran Hall, Daley Road ANU" + } +time_point_corrections.each do |wrong, right| + time_points_sources[wrong].each do |wrongfile| + badtimetable = YAML::load_file(wrongfile) + badentrynumber = badtimetable["time_points"].index wrong + badtimetable["time_points"][badentrynumber] = right + puts "Corrected " + wrong + " to " + right + " in " + wrongfile + end +end + --- /dev/null +++ b/maxious-canberra-transit-feed/validate.sh @@ -1,1 +1,2 @@ +../transitfeed-1.2.5/feedvalidator.py cbrfeed.zip --- /dev/null +++ b/maxious-canberra-transit-feed/validation-results.html @@ -1,1 +1,85 @@ + + + +FeedValidator: cbrfeed.zip + + + +GTFS validation results for feed:
+cbrfeed.zip +

+ + + + + + + +
Agencies:ACT Internal Omnibus Network (ACTION)
Routes:1
Stops:6
Trips:69
Shapes:0
Effective:May 25, 2009 to June 01, 2010
+
+During the upcoming service dates Sun Apr 18 to Mon May 31: + + + + +
Average trips per date:30
Most trips on a date:36, on 31 service dates (Mon Apr 19, Tue Apr 20, Wed Apr 21, ...)
Least trips on a date:16, on 7 service dates (Sun Apr 18, Sun Apr 25, Sun May 02, ...)
+
+Found these problems: + + + + +
3 errors2 warnings
+ +
3Invalid Values
+
+ + +
1Expiration Date
1Stops Too Close
+
+

+

Errors:

Invalid Value

    +
  • Invalid value 2010601 in field date
    in line 2 of calendar.txt
    + +
    service_idstart_dateend_datemondaytuesdaywednesdaythursdayfridaysaturdaysunday
    sunday20090525201006010000001
    +
  • +
  • Invalid value 2010601 in field date
    in line 3 of calendar.txt
    + +
    service_idstart_dateend_datemondaytuesdaywednesdaythursdayfridaysaturdaysunday
    weekday20090525201006011111100
    +
  • +
  • Invalid value 2010601 in field date
    in line 4 of calendar.txt
    + +
    service_idstart_dateend_datemondaytuesdaywednesdaythursdayfridaysaturdaysunday
    saturday20090525201006010000010
    +
  • +
+

Warnings:

Expiration Date

    +
  • This feed will soon expire, on June 01, 2010

  • +
+

Stops Too Close

    +
  • The stops "Civic Interchange Platform 5" (ID 1) and "Canberra House Southbound" (ID 4) are 0.00m apart and probably represent the same location.

  • +
+ + + + --- /dev/null +++ b/maxious-canberra-transit-feed/view.sh @@ -1,1 +1,2 @@ +../transitfeed-1.2.5/schedule_viewer.py --feed=cbrfeed.zip --- /dev/null +++ b/openlayers/OpenLayers.js @@ -1,1 +1,2144 @@ +/* + OpenLayers.js -- OpenLayers Map Viewer Library + + Copyright 2005-2008 MetaCarta, Inc., released under the Clear BSD license. + Please see http://svn.openlayers.org/trunk/openlayers/license.txt + for the full text of the license. + + Includes compressed code under the following licenses: + + (For uncompressed versions of the code used please see the + OpenLayers SVN repository: ) + +*/ + +/* Contains portions of Prototype.js: + * + * Prototype JavaScript framework, version 1.4.0 + * (c) 2005 Sam Stephenson + * + * Prototype is freely distributable under the terms of an MIT-style license. + * For details, see the Prototype web site: http://prototype.conio.net/ + * + *--------------------------------------------------------------------------*/ + +/** +* +* Contains portions of Rico +* +* Copyright 2005 Sabre Airline Solutions +* +* Licensed under the Apache License, Version 2.0 (the "License"); you +* may not use this file except in compliance with the License. You +* may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or +* implied. See the License for the specific language governing +* permissions and limitations under the License. +* +**/ + +/** + * Contains XMLHttpRequest.js + * Copyright 2007 Sergey Ilinsky (http://www.ilinsky.com) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * http://www.apache.org/licenses/LICENSE-2.0 + */ + +/** + * Contains portions of Gears + * + * Copyright 2007, Google Inc. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * 3. Neither the name of Google Inc. nor the names of its contributors may be + * used to endorse or promote products derived from this software without + * specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO + * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; + * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR + * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * Sets up google.gears.*, which is *the only* supported way to access Gears. + * + * Circumvent this file at your own risk! + * + * In the future, Gears may automatically define google.gears.* without this + * file. Gears may use these objects to transparently fix bugs and compatibility + * issues. Applications that use the code below will continue to work seamlessly + * when that happens. + */ +var OpenLayers={singleFile:true};(function(){var singleFile=(typeof OpenLayers=="object"&&OpenLayers.singleFile);window.OpenLayers={_scriptName:(!singleFile)?"lib/OpenLayers.js":"OpenLayers.js",_getScriptLocation:function(){var scriptLocation="";var isOL=new RegExp("(^|(.*?\\/))("+OpenLayers._scriptName+")(\\?|$)");var scripts=document.getElementsByTagName('script');for(var i=0,len=scripts.length;i";}else{var s=document.createElement("script");s.src=host+jsfiles[i];var h=document.getElementsByTagName("head").length?document.getElementsByTagName("head")[0]:document.body;h.appendChild(s);}} +if(docWrite){document.write(allScriptTags.join(""));}}})();OpenLayers.VERSION_NUMBER="OpenLayers 2.8 -- $Revision: 9492 $";OpenLayers.String={startsWith:function(str,sub){return(str.indexOf(sub)==0);},contains:function(str,sub){return(str.indexOf(sub)!=-1);},trim:function(str){return str.replace(/^\s\s*/,'').replace(/\s\s*$/,'');},camelize:function(str){var oStringList=str.split('-');var camelizedString=oStringList[0];for(var i=1,len=oStringList.length;i0){fig=parseFloat(num.toPrecision(sig));} +return fig;},format:function(num,dec,tsep,dsep){dec=(typeof dec!="undefined")?dec:0;tsep=(typeof tsep!="undefined")?tsep:OpenLayers.Number.thousandsSeparator;dsep=(typeof dsep!="undefined")?dsep:OpenLayers.Number.decimalSeparator;if(dec!=null){num=parseFloat(num.toFixed(dec));} +var parts=num.toString().split(".");if(parts.length==1&&dec==null){dec=0;} +var integer=parts[0];if(tsep){var thousands=/(-?[0-9]+)([0-9]{3})/;while(thousands.test(integer)){integer=integer.replace(thousands,"$1"+tsep+"$2");}} +var str;if(dec==0){str=integer;}else{var rem=parts.length>1?parts[1]:"0";if(dec!=null){rem=rem+new Array(dec-rem.length+1).join("0");} +str=integer+dsep+rem;} +return str;}};if(!Number.prototype.limitSigDigs){Number.prototype.limitSigDigs=function(sig){OpenLayers.Console.warn(OpenLayers.i18n("methodDeprecated",{'newMethod':'OpenLayers.Number.limitSigDigs'}));return OpenLayers.Number.limitSigDigs(this,sig);};} +OpenLayers.Function={bind:function(func,object){var args=Array.prototype.slice.apply(arguments,[2]);return function(){var newArgs=args.concat(Array.prototype.slice.apply(arguments,[0]));return func.apply(object,newArgs);};},bindAsEventListener:function(func,object){return function(event){return func.call(object,event||window.event);};}};if(!Function.prototype.bind){Function.prototype.bind=function(){OpenLayers.Console.warn(OpenLayers.i18n("methodDeprecated",{'newMethod':'OpenLayers.Function.bind'}));Array.prototype.unshift.apply(arguments,[this]);return OpenLayers.Function.bind.apply(null,arguments);};} +if(!Function.prototype.bindAsEventListener){Function.prototype.bindAsEventListener=function(object){OpenLayers.Console.warn(OpenLayers.i18n("methodDeprecated",{'newMethod':'OpenLayers.Function.bindAsEventListener'}));return OpenLayers.Function.bindAsEventListener(this,object);};} +OpenLayers.Array={filter:function(array,callback,caller){var selected=[];if(Array.prototype.filter){selected=array.filter(callback,caller);}else{var len=array.length;if(typeof callback!="function"){throw new TypeError();} +for(var i=0;i1){initialize=arguments[i].prototype.initialize;arguments[i].prototype.initialize=function(){};extended=new arguments[i];if(initialize===undefined){delete arguments[i].prototype.initialize;}else{arguments[i].prototype.initialize=initialize;}} +parent=arguments[i].prototype;}else{parent=arguments[i];} +OpenLayers.Util.extend(extended,parent);} +Class.prototype=extended;return Class;};OpenLayers.Class.isPrototype=function(){};OpenLayers.Class.create=function(){return function(){if(arguments&&arguments[0]!=OpenLayers.Class.isPrototype){this.initialize.apply(this,arguments);}};};OpenLayers.Class.inherit=function(){var superClass=arguments[0];var proto=new superClass(OpenLayers.Class.isPrototype);for(var i=1,len=arguments.length;i=0;i--){if(array[i]==item){array.splice(i,1);}} +return array;};OpenLayers.Util.clearArray=function(array){OpenLayers.Console.warn(OpenLayers.i18n("methodDeprecated",{'newMethod':'array = []'}));array.length=0;};OpenLayers.Util.indexOf=function(array,obj){for(var i=0,len=array.length;i=0.0&&parseFloat(opacity)<1.0){element.style.filter='alpha(opacity='+(opacity*100)+')';element.style.opacity=opacity;}else if(parseFloat(opacity)==1.0){element.style.filter='';element.style.opacity='';}};OpenLayers.Util.createDiv=function(id,px,sz,imgURL,position,border,overflow,opacity){var dom=document.createElement('div');if(imgURL){dom.style.backgroundImage='url('+imgURL+')';} +if(!id){id=OpenLayers.Util.createUniqueID("OpenLayersDiv");} +if(!position){position="absolute";} +OpenLayers.Util.modifyDOMElement(dom,id,px,sz,position,border,overflow,opacity);return dom;};OpenLayers.Util.createImage=function(id,px,sz,imgURL,position,border,opacity,delayDisplay){var image=document.createElement("img");if(!id){id=OpenLayers.Util.createUniqueID("OpenLayersDiv");} +if(!position){position="relative";} +OpenLayers.Util.modifyDOMElement(image,id,px,sz,position,border,null,opacity);if(delayDisplay){image.style.display="none";OpenLayers.Event.observe(image,"load",OpenLayers.Function.bind(OpenLayers.Util.onImageLoad,image));OpenLayers.Event.observe(image,"error",OpenLayers.Function.bind(OpenLayers.Util.onImageLoadError,image));} +image.style.alt=id;image.galleryImg="no";if(imgURL){image.src=imgURL;} +return image;};OpenLayers.Util.setOpacity=function(element,opacity){OpenLayers.Util.modifyDOMElement(element,null,null,null,null,null,null,opacity);};OpenLayers.Util.onImageLoad=function(){if(!this.viewRequestID||(this.map&&this.viewRequestID==this.map.viewRequestID)){this.style.backgroundColor="transparent";this.style.display="";}};OpenLayers.Util.onImageLoadErrorColor="pink";OpenLayers.IMAGE_RELOAD_ATTEMPTS=0;OpenLayers.Util.onImageLoadError=function(){this._attempts=(this._attempts)?(this._attempts+1):1;if(this._attempts<=OpenLayers.IMAGE_RELOAD_ATTEMPTS){var urls=this.urls;if(urls&&urls instanceof Array&&urls.length>1){var src=this.src.toString();var current_url,k;for(k=0;current_url=urls[k];k++){if(src.indexOf(current_url)!=-1){break;}} +var guess=Math.floor(urls.length*Math.random());var new_url=urls[guess];k=0;while(new_url==current_url&&k++<4){guess=Math.floor(urls.length*Math.random());new_url=urls[guess];} +this.src=src.replace(current_url,new_url);}else{this.src=this.src;}}else{this.style.backgroundColor=OpenLayers.Util.onImageLoadErrorColor;} +this.style.display="";};OpenLayers.Util.alphaHackNeeded=null;OpenLayers.Util.alphaHack=function(){if(OpenLayers.Util.alphaHackNeeded==null){var arVersion=navigator.appVersion.split("MSIE");var version=parseFloat(arVersion[1]);var filter=false;try{filter=!!(document.body.filters);}catch(e){} +OpenLayers.Util.alphaHackNeeded=(filter&&(version>=5.5)&&(version<7));} +return OpenLayers.Util.alphaHackNeeded;};OpenLayers.Util.modifyAlphaImageDiv=function(div,id,px,sz,imgURL,position,border,sizing,opacity){OpenLayers.Util.modifyDOMElement(div,id,px,sz,position,null,null,opacity);var img=div.childNodes[0];if(imgURL){img.src=imgURL;} +OpenLayers.Util.modifyDOMElement(img,div.id+"_innerImage",null,sz,"relative",border);if(OpenLayers.Util.alphaHack()){if(div.style.display!="none"){div.style.display="inline-block";} +if(sizing==null){sizing="scale";} +div.style.filter="progid:DXImageTransform.Microsoft"+".AlphaImageLoader(src='"+img.src+"', "+"sizingMethod='"+sizing+"')";if(parseFloat(div.style.opacity)>=0.0&&parseFloat(div.style.opacity)<1.0){div.style.filter+=" alpha(opacity="+div.style.opacity*100+")";} +img.style.filter="alpha(opacity=0)";}};OpenLayers.Util.createAlphaImageDiv=function(id,px,sz,imgURL,position,border,sizing,opacity,delayDisplay){var div=OpenLayers.Util.createDiv();var img=OpenLayers.Util.createImage(null,null,null,null,null,null,null,false);div.appendChild(img);if(delayDisplay){img.style.display="none";OpenLayers.Event.observe(img,"load",OpenLayers.Function.bind(OpenLayers.Util.onImageLoad,div));OpenLayers.Event.observe(img,"error",OpenLayers.Function.bind(OpenLayers.Util.onImageLoadError,div));} +OpenLayers.Util.modifyAlphaImageDiv(div,id,px,sz,imgURL,position,border,sizing,opacity);return div;};OpenLayers.Util.upperCaseObject=function(object){var uObject={};for(var key in object){uObject[key.toUpperCase()]=object[key];} +return uObject;};OpenLayers.Util.applyDefaults=function(to,from){to=to||{};var fromIsEvt=typeof window.Event=="function"&&from instanceof window.Event;for(var key in from){if(to[key]===undefined||(!fromIsEvt&&from.hasOwnProperty&&from.hasOwnProperty(key)&&!to.hasOwnProperty(key))){to[key]=from[key];}} +if(!fromIsEvt&&from&&from.hasOwnProperty&&from.hasOwnProperty('toString')&&!to.hasOwnProperty('toString')){to.toString=from.toString;} +return to;};OpenLayers.Util.getParameterString=function(params){var paramsArray=[];for(var key in params){var value=params[key];if((value!=null)&&(typeof value!='function')){var encodedValue;if(typeof value=='object'&&value.constructor==Array){var encodedItemArray=[];for(var itemIndex=0,len=value.length;itemIndex0)) +{if(!index){index=0;} +if(result[index].childNodes.length>1){return result.childNodes[1].nodeValue;} +else if(result[index].childNodes.length==1){return result[index].firstChild.nodeValue;}}else{return"";}};OpenLayers.Util.getXmlNodeValue=function(node){var val=null;OpenLayers.Util.Try(function(){val=node.text;if(!val){val=node.textContent;} +if(!val){val=node.firstChild.nodeValue;}},function(){val=node.textContent;});return val;};OpenLayers.Util.mouseLeft=function(evt,div){var target=(evt.relatedTarget)?evt.relatedTarget:evt.toElement;while(target!=div&&target!=null){target=target.parentNode;} +return(target!=div);};OpenLayers.Util.DEFAULT_PRECISION=14;OpenLayers.Util.toFloat=function(number,precision){if(precision==null){precision=OpenLayers.Util.DEFAULT_PRECISION;} +var number;if(precision==0){number=parseFloat(number);}else{number=parseFloat(parseFloat(number).toPrecision(precision));} +return number;};OpenLayers.Util.rad=function(x){return x*Math.PI/180;};OpenLayers.Util.distVincenty=function(p1,p2){var a=6378137,b=6356752.3142,f=1/298.257223563;var L=OpenLayers.Util.rad(p2.lon-p1.lon);var U1=Math.atan((1-f)*Math.tan(OpenLayers.Util.rad(p1.lat)));var U2=Math.atan((1-f)*Math.tan(OpenLayers.Util.rad(p2.lat)));var sinU1=Math.sin(U1),cosU1=Math.cos(U1);var sinU2=Math.sin(U2),cosU2=Math.cos(U2);var lambda=L,lambdaP=2*Math.PI;var iterLimit=20;while(Math.abs(lambda-lambdaP)>1e-12&&--iterLimit>0){var sinLambda=Math.sin(lambda),cosLambda=Math.cos(lambda);var sinSigma=Math.sqrt((cosU2*sinLambda)*(cosU2*sinLambda)+ +(cosU1*sinU2-sinU1*cosU2*cosLambda)*(cosU1*sinU2-sinU1*cosU2*cosLambda));if(sinSigma==0){return 0;} +var cosSigma=sinU1*sinU2+cosU1*cosU2*cosLambda;var sigma=Math.atan2(sinSigma,cosSigma);var alpha=Math.asin(cosU1*cosU2*sinLambda/sinSigma);var cosSqAlpha=Math.cos(alpha)*Math.cos(alpha);var cos2SigmaM=cosSigma-2*sinU1*sinU2/cosSqAlpha;var C=f/16*cosSqAlpha*(4+f*(4-3*cosSqAlpha));lambdaP=lambda;lambda=L+(1-C)*f*Math.sin(alpha)*(sigma+C*sinSigma*(cos2SigmaM+C*cosSigma*(-1+2*cos2SigmaM*cos2SigmaM)));} +if(iterLimit==0){return NaN;} +var uSq=cosSqAlpha*(a*a-b*b)/(b*b);var A=1+uSq/16384*(4096+uSq*(-768+uSq*(320-175*uSq)));var B=uSq/1024*(256+uSq*(-128+uSq*(74-47*uSq)));var deltaSigma=B*sinSigma*(cos2SigmaM+B/4*(cosSigma*(-1+2*cos2SigmaM*cos2SigmaM)- +B/6*cos2SigmaM*(-3+4*sinSigma*sinSigma)*(-3+4*cos2SigmaM*cos2SigmaM)));var s=b*A*(sigma-deltaSigma);var d=s.toFixed(3)/1000;return d;};OpenLayers.Util.getParameters=function(url){url=url||window.location.href;var paramsString="";if(OpenLayers.String.contains(url,'?')){var start=url.indexOf('?')+1;var end=OpenLayers.String.contains(url,"#")?url.indexOf('#'):url.length;paramsString=url.substring(start,end);} +var parameters={};var pairs=paramsString.split(/[&;]/);for(var i=0,len=pairs.length;i1.0)?(1.0/scale):scale;return normScale;};OpenLayers.Util.getResolutionFromScale=function(scale,units){if(units==null){units="degrees";} +var normScale=OpenLayers.Util.normalizeScale(scale);var resolution=1/(normScale*OpenLayers.INCHES_PER_UNIT[units]*OpenLayers.DOTS_PER_INCH);return resolution;};OpenLayers.Util.getScaleFromResolution=function(resolution,units){if(units==null){units="degrees";} +var scale=resolution*OpenLayers.INCHES_PER_UNIT[units]*OpenLayers.DOTS_PER_INCH;return scale;};OpenLayers.Util.safeStopPropagation=function(evt){OpenLayers.Event.stop(evt,true);};OpenLayers.Util.pagePosition=function(forElement){var valueT=0,valueL=0;var element=forElement;var child=forElement;while(element){if(element==document.body){if(OpenLayers.Element.getStyle(child,'position')=='absolute'){break;}} +valueT+=element.offsetTop||0;valueL+=element.offsetLeft||0;child=element;try{element=element.offsetParent;}catch(e){OpenLayers.Console.error(OpenLayers.i18n("pagePositionFailed",{'elemId':element.id}));break;}} +element=forElement;while(element){valueT-=element.scrollTop||0;valueL-=element.scrollLeft||0;element=element.parentNode;} +return[valueL,valueT];};OpenLayers.Util.isEquivalentUrl=function(url1,url2,options){options=options||{};OpenLayers.Util.applyDefaults(options,{ignoreCase:true,ignorePort80:true,ignoreHash:true});var urlObj1=OpenLayers.Util.createUrlObject(url1,options);var urlObj2=OpenLayers.Util.createUrlObject(url2,options);for(var key in urlObj1){if(key!=="args"){if(urlObj1[key]!=urlObj2[key]){return false;}}} +for(var key in urlObj1.args){if(urlObj1.args[key]!=urlObj2.args[key]){return false;} +delete urlObj2.args[key];} +for(var key in urlObj2.args){return false;} +return true;};OpenLayers.Util.createUrlObject=function(url,options){options=options||{};if(!(/^\w+:\/\//).test(url)){var loc=window.location;var port=loc.port?":"+loc.port:"";var fullUrl=loc.protocol+"//"+loc.host.split(":").shift()+port;if(url.indexOf("/")===0){url=fullUrl+url;}else{var parts=loc.pathname.split("/");parts.pop();url=fullUrl+parts.join("/")+"/"+url;}} +if(options.ignoreCase){url=url.toLowerCase();} +var a=document.createElement('a');a.href=url;var urlObject={};urlObject.host=a.host.split(":").shift();urlObject.protocol=a.protocol;if(options.ignorePort80){urlObject.port=(a.port=="80"||a.port=="0")?"":a.port;}else{urlObject.port=(a.port==""||a.port=="0")?"80":a.port;} +urlObject.hash=(options.ignoreHash||a.hash==="#")?"":a.hash;var queryString=a.search;if(!queryString){var qMark=url.indexOf("?");queryString=(qMark!=-1)?url.substr(qMark):"";} +urlObject.args=OpenLayers.Util.getParameters(queryString);urlObject.pathname=(a.pathname.charAt(0)=="/")?a.pathname:"/"+a.pathname;return urlObject;};OpenLayers.Util.removeTail=function(url){var head=null;var qMark=url.indexOf("?");var hashMark=url.indexOf("#");if(qMark==-1){head=(hashMark!=-1)?url.substr(0,hashMark):url;}else{head=(hashMark!=-1)?url.substr(0,Math.min(qMark,hashMark)):url.substr(0,qMark);} +return head;};OpenLayers.Util.getBrowserName=function(){var browserName="";var ua=navigator.userAgent.toLowerCase();if(ua.indexOf("opera")!=-1){browserName="opera";}else if(ua.indexOf("msie")!=-1){browserName="msie";}else if(ua.indexOf("safari")!=-1){browserName="safari";}else if(ua.indexOf("mozilla")!=-1){if(ua.indexOf("firefox")!=-1){browserName="firefox";}else{browserName="mozilla";}} +return browserName;};OpenLayers.Util.getRenderedDimensions=function(contentHTML,size,options){var w,h;var container=document.createElement("div");container.style.visibility="hidden";var containerElement=(options&&options.containerElement)?options.containerElement:document.body;if(size){if(size.w){w=size.w;container.style.width=w+"px";}else if(size.h){h=size.h;container.style.height=h+"px";}} +if(options&&options.displayClass){container.className=options.displayClass;} +var content=document.createElement("div");content.innerHTML=contentHTML;content.style.overflow="visible";if(content.childNodes){for(var i=0,l=content.childNodes.length;i"+el.innerHTML+"
";},_roundTopCorners:function(el,color,bgColor){var corner=this._createCorner(bgColor);for(var i=0;i=0;i--){corner.appendChild(this._createCornerSlice(color,bgColor,i,"bottom"));} +el.style.paddingBottom=0;el.appendChild(corner);},_createCorner:function(bgColor){var corner=document.createElement("div");corner.style.backgroundColor=(this._isTransparent()?"transparent":bgColor);return corner;},_createCornerSlice:function(color,bgColor,n,position){var slice=document.createElement("span");var inStyle=slice.style;inStyle.backgroundColor=color;inStyle.display="block";inStyle.height="1px";inStyle.overflow="hidden";inStyle.fontSize="1px";var borderColor=this._borderColor(color,bgColor);if(this.options.border&&n==0){inStyle.borderTopStyle="solid";inStyle.borderTopWidth="1px";inStyle.borderLeftWidth="0px";inStyle.borderRightWidth="0px";inStyle.borderBottomWidth="0px";inStyle.height="0px";inStyle.borderColor=borderColor;} +else if(borderColor){inStyle.borderColor=borderColor;inStyle.borderStyle="solid";inStyle.borderWidth="0px 1px";} +if(!this.options.compact&&(n==(this.options.numSlices-1))){inStyle.height="2px";} +this._setMargin(slice,n,position);this._setBorder(slice,n,position);return slice;},_setOptions:function(options){this.options={corners:"all",color:"fromElement",bgColor:"fromParent",blend:true,border:false,compact:false};OpenLayers.Util.extend(this.options,options||{});this.options.numSlices=this.options.compact?2:4;if(this._isTransparent()){this.options.blend=false;}},_whichSideTop:function(){if(this._hasString(this.options.corners,"all","top")){return"";} +if(this.options.corners.indexOf("tl")>=0&&this.options.corners.indexOf("tr")>=0){return"";} +if(this.options.corners.indexOf("tl")>=0){return"left";}else if(this.options.corners.indexOf("tr")>=0){return"right";} +return"";},_whichSideBottom:function(){if(this._hasString(this.options.corners,"all","bottom")){return"";} +if(this.options.corners.indexOf("bl")>=0&&this.options.corners.indexOf("br")>=0){return"";} +if(this.options.corners.indexOf("bl")>=0){return"left";}else if(this.options.corners.indexOf("br")>=0){return"right";} +return"";},_borderColor:function(color,bgColor){if(color=="transparent"){return bgColor;}else if(this.options.border){return this.options.border;}else if(this.options.blend){return this._blend(bgColor,color);}else{return"";}},_setMargin:function(el,n,corners){var marginSize=this._marginSize(n);var whichSide=corners=="top"?this._whichSideTop():this._whichSideBottom();if(whichSide=="left"){el.style.marginLeft=marginSize+"px";el.style.marginRight="0px";} +else if(whichSide=="right"){el.style.marginRight=marginSize+"px";el.style.marginLeft="0px";} +else{el.style.marginLeft=marginSize+"px";el.style.marginRight=marginSize+"px";}},_setBorder:function(el,n,corners){var borderSize=this._borderSize(n);var whichSide=corners=="top"?this._whichSideTop():this._whichSideBottom();if(whichSide=="left"){el.style.borderLeftWidth=borderSize+"px";el.style.borderRightWidth="0px";} +else if(whichSide=="right"){el.style.borderRightWidth=borderSize+"px";el.style.borderLeftWidth="0px";} +else{el.style.borderLeftWidth=borderSize+"px";el.style.borderRightWidth=borderSize+"px";} +if(this.options.border!=false){el.style.borderLeftWidth=borderSize+"px";el.style.borderRightWidth=borderSize+"px";}},_marginSize:function(n){if(this._isTransparent()){return 0;} +var marginSizes=[5,3,2,1];var blendedMarginSizes=[3,2,1,0];var compactMarginSizes=[2,1];var smBlendedMarginSizes=[1,0];if(this.options.compact&&this.options.blend){return smBlendedMarginSizes[n];}else if(this.options.compact){return compactMarginSizes[n];}else if(this.options.blend){return blendedMarginSizes[n];}else{return marginSizes[n];}},_borderSize:function(n){var transparentBorderSizes=[5,3,2,1];var blendedBorderSizes=[2,1,1,1];var compactBorderSizes=[1,0];var actualBorderSizes=[0,2,0,0];if(this.options.compact&&(this.options.blend||this._isTransparent())){return 1;}else if(this.options.compact){return compactBorderSizes[n];}else if(this.options.blend){return blendedBorderSizes[n];}else if(this.options.border){return actualBorderSizes[n];}else if(this._isTransparent()){return transparentBorderSizes[n];} +return 0;},_hasString:function(str){for(var i=1;i=0){return true;}return false;},_blend:function(c1,c2){var cc1=OpenLayers.Rico.Color.createFromHex(c1);cc1.blend(OpenLayers.Rico.Color.createFromHex(c2));return cc1;},_background:function(el){try{return OpenLayers.Rico.Color.createColorFromBackground(el).asHex();}catch(err){return"#ffffff";}},_isTransparent:function(){return this.options.color=="transparent";},_isTopRounded:function(){return this._hasString(this.options.corners,"all","top","tl","tr");},_isBottomRounded:function(){return this._hasString(this.options.corners,"all","bottom","bl","br");},_hasSingleTextChild:function(el){return el.childNodes.length==1&&el.childNodes[0].nodeType==3;}};(function(){if(window.google&&google.gears){return;} +var factory=null;if(typeof GearsFactory!='undefined'){factory=new GearsFactory();}else{try{factory=new ActiveXObject('Gears.Factory');if(factory.getBuildInfo().indexOf('ie_mobile')!=-1){factory.privateSetGlobalObject(this);}}catch(e){if((typeof navigator.mimeTypes!='undefined')&&navigator.mimeTypes["application/x-googlegears"]){factory=document.createElement("object");factory.style.display="none";factory.width=0;factory.height=0;factory.type="application/x-googlegears";document.documentElement.appendChild(factory);}}} +if(!factory){return;} +if(!window.google){google={};} +if(!google.gears){google.gears={factory:factory};}})();OpenLayers.Element={visible:function(element){return OpenLayers.Util.getElement(element).style.display!='none';},toggle:function(){for(var i=0,len=arguments.length;i"+ +this.contentDiv.innerHTML+"
";var containerElement=(this.map)?this.map.layerContainerDiv:document.body;var realSize=OpenLayers.Util.getRenderedDimensions(preparedHTML,null,{displayClass:this.displayClass,containerElement:containerElement});var safeSize=this.getSafeContentSize(realSize);var newSize=null;if(safeSize.equals(realSize)){newSize=realSize;}else{var fixedSize=new OpenLayers.Size();fixedSize.w=(safeSize.w(mapSize.w-this.map.paddingForPopups.right)){newTL.x=mapSize.w-this.map.paddingForPopups.right-this.size.w;} +if(origTL.y(mapSize.h-this.map.paddingForPopups.bottom)){newTL.y=mapSize.h-this.map.paddingForPopups.bottom-this.size.h;} +var dx=origTL.x-newTL.x;var dy=origTL.y-newTL.y;this.map.pan(dx,dy);},registerEvents:function(){this.events=new OpenLayers.Events(this,this.div,null,true);this.events.on({"mousedown":this.onmousedown,"mousemove":this.onmousemove,"mouseup":this.onmouseup,"click":this.onclick,"mouseout":this.onmouseout,"dblclick":this.ondblclick,scope:this});},onmousedown:function(evt){this.mousedown=true;OpenLayers.Event.stop(evt,true);},onmousemove:function(evt){if(this.mousedown){OpenLayers.Event.stop(evt,true);}},onmouseup:function(evt){if(this.mousedown){this.mousedown=false;OpenLayers.Event.stop(evt,true);}},onclick:function(evt){OpenLayers.Event.stop(evt,true);},onmouseout:function(evt){this.mousedown=false;},ondblclick:function(evt){OpenLayers.Event.stop(evt,true);},CLASS_NAME:"OpenLayers.Popup"});OpenLayers.Popup.WIDTH=200;OpenLayers.Popup.HEIGHT=200;OpenLayers.Popup.COLOR="white";OpenLayers.Popup.OPACITY=1;OpenLayers.Popup.BORDER="0px";OpenLayers.Protocol=OpenLayers.Class({format:null,options:null,autoDestroy:true,initialize:function(options){options=options||{};OpenLayers.Util.extend(this,options);this.options=options;},destroy:function(){this.options=null;this.format=null;},read:function(){},create:function(){},update:function(){},"delete":function(){},commit:function(){},abort:function(response){},CLASS_NAME:"OpenLayers.Protocol"});OpenLayers.Protocol.Response=OpenLayers.Class({code:null,requestType:null,last:true,features:null,reqFeatures:null,priv:null,initialize:function(options){OpenLayers.Util.extend(this,options);},success:function(){return this.code>0;},CLASS_NAME:"OpenLayers.Protocol.Response"});OpenLayers.Protocol.Response.SUCCESS=1;OpenLayers.Protocol.Response.FAILURE=0;OpenLayers.Renderer=OpenLayers.Class({container:null,root:null,extent:null,locked:false,size:null,resolution:null,map:null,initialize:function(containerID,options){this.container=OpenLayers.Util.getElement(containerID);},destroy:function(){this.container=null;this.extent=null;this.size=null;this.resolution=null;this.map=null;},supported:function(){return false;},setExtent:function(extent,resolutionChanged){this.extent=extent.clone();if(resolutionChanged){this.resolution=null;}},setSize:function(size){this.size=size.clone();this.resolution=null;},getResolution:function(){this.resolution=this.resolution||this.map.getResolution();return this.resolution;},drawFeature:function(feature,style){if(style==null){style=feature.style;} +if(feature.geometry){var bounds=feature.geometry.getBounds();if(bounds){if(!bounds.intersectsBounds(this.extent)){style={display:"none"};} +var rendered=this.drawGeometry(feature.geometry,style,feature.id);if(style.display!="none"&&style.label&&rendered!==false){this.drawText(feature.id,style,feature.geometry.getCentroid());}else{this.removeText(feature.id);} +return rendered;}}},drawGeometry:function(geometry,style,featureId){},drawText:function(featureId,style,location){},removeText:function(featureId){},clear:function(){},getFeatureIdFromEvent:function(evt){},eraseFeatures:function(features){if(!(features instanceof Array)){features=[features];} +for(var i=0,len=features.length;i0.5;},isDark:function(){return!this.isBright();},asRGB:function(){return"rgb("+this.rgb.r+","+this.rgb.g+","+this.rgb.b+")";},asHex:function(){return"#"+this.rgb.r.toColorPart()+this.rgb.g.toColorPart()+this.rgb.b.toColorPart();},asHSB:function(){return OpenLayers.Rico.Color.RGBtoHSB(this.rgb.r,this.rgb.g,this.rgb.b);},toString:function(){return this.asHex();}});OpenLayers.Rico.Color.createFromHex=function(hexCode){if(hexCode.length==4){var shortHexCode=hexCode;var hexCode='#';for(var i=1;i<4;i++){hexCode+=(shortHexCode.charAt(i)+ +shortHexCode.charAt(i));}} +if(hexCode.indexOf('#')==0){hexCode=hexCode.substring(1);} +var red=hexCode.substring(0,2);var green=hexCode.substring(2,4);var blue=hexCode.substring(4,6);return new OpenLayers.Rico.Color(parseInt(red,16),parseInt(green,16),parseInt(blue,16));};OpenLayers.Rico.Color.createColorFromBackground=function(elem){var actualColor=RicoUtil.getElementsComputedStyle(OpenLayers.Util.getElement(elem),"backgroundColor","background-color");if(actualColor=="transparent"&&elem.parentNode){return OpenLayers.Rico.Color.createColorFromBackground(elem.parentNode);} +if(actualColor==null){return new OpenLayers.Rico.Color(255,255,255);} +if(actualColor.indexOf("rgb(")==0){var colors=actualColor.substring(4,actualColor.length-1);var colorArray=colors.split(",");return new OpenLayers.Rico.Color(parseInt(colorArray[0]),parseInt(colorArray[1]),parseInt(colorArray[2]));} +else if(actualColor.indexOf("#")==0){return OpenLayers.Rico.Color.createFromHex(actualColor);} +else{return new OpenLayers.Rico.Color(255,255,255);}};OpenLayers.Rico.Color.HSBtoRGB=function(hue,saturation,brightness){var red=0;var green=0;var blue=0;if(saturation==0){red=parseInt(brightness*255.0+0.5);green=red;blue=red;} +else{var h=(hue-Math.floor(hue))*6.0;var f=h-Math.floor(h);var p=brightness*(1.0-saturation);var q=brightness*(1.0-saturation*f);var t=brightness*(1.0-(saturation*(1.0-f)));switch(parseInt(h)){case 0:red=(brightness*255.0+0.5);green=(t*255.0+0.5);blue=(p*255.0+0.5);break;case 1:red=(q*255.0+0.5);green=(brightness*255.0+0.5);blue=(p*255.0+0.5);break;case 2:red=(p*255.0+0.5);green=(brightness*255.0+0.5);blue=(t*255.0+0.5);break;case 3:red=(p*255.0+0.5);green=(q*255.0+0.5);blue=(brightness*255.0+0.5);break;case 4:red=(t*255.0+0.5);green=(p*255.0+0.5);blue=(brightness*255.0+0.5);break;case 5:red=(brightness*255.0+0.5);green=(p*255.0+0.5);blue=(q*255.0+0.5);break;}} +return{r:parseInt(red),g:parseInt(green),b:parseInt(blue)};};OpenLayers.Rico.Color.RGBtoHSB=function(r,g,b){var hue;var saturation;var brightness;var cmax=(r>g)?r:g;if(b>cmax){cmax=b;} +var cmin=(rthis.right)){this.right=bounds.right;} +if((this.top==null)||(bounds.top>this.top)){this.top=bounds.top;}}}},containsLonLat:function(ll,inclusive){return this.contains(ll.lon,ll.lat,inclusive);},containsPixel:function(px,inclusive){return this.contains(px.x,px.y,inclusive);},contains:function(x,y,inclusive){if(inclusive==null){inclusive=true;} +if(x==null||y==null){return false;} +x=OpenLayers.Util.toFloat(x);y=OpenLayers.Util.toFloat(y);var contains=false;if(inclusive){contains=((x>=this.left)&&(x<=this.right)&&(y>=this.bottom)&&(y<=this.top));}else{contains=((x>this.left)&&(xthis.bottom)&&(y=this.bottom)&&(bounds.bottom<=this.top))||((this.bottom>=bounds.bottom)&&(this.bottom<=bounds.top)));var inTop=(((bounds.top>=this.bottom)&&(bounds.top<=this.top))||((this.top>bounds.bottom)&&(this.top=this.left)&&(bounds.left<=this.right))||((this.left>=bounds.left)&&(this.left<=bounds.right)));var inRight=(((bounds.right>=this.left)&&(bounds.right<=this.right))||((this.right>=bounds.left)&&(this.right<=bounds.right)));intersects=((inBottom||inTop)&&(inLeft||inRight));} +return intersects;},containsBounds:function(bounds,partial,inclusive){if(partial==null){partial=false;} +if(inclusive==null){inclusive=true;} +var bottomLeft=this.contains(bounds.left,bounds.bottom,inclusive);var bottomRight=this.contains(bounds.right,bounds.bottom,inclusive);var topLeft=this.contains(bounds.left,bounds.top,inclusive);var topRight=this.contains(bounds.right,bounds.top,inclusive);return(partial)?(bottomLeft||bottomRight||topLeft||topRight):(bottomLeft&&bottomRight&&topLeft&&topRight);},determineQuadrant:function(lonlat){var quadrant="";var center=this.getCenterLonLat();quadrant+=(lonlat.lat=maxExtent.right&&newBounds.right>maxExtent.right){newBounds=newBounds.add(-maxExtent.getWidth(),0);}} +return newBounds;},CLASS_NAME:"OpenLayers.Bounds"});OpenLayers.Bounds.fromString=function(str){var bounds=str.split(",");return OpenLayers.Bounds.fromArray(bounds);};OpenLayers.Bounds.fromArray=function(bbox){return new OpenLayers.Bounds(parseFloat(bbox[0]),parseFloat(bbox[1]),parseFloat(bbox[2]),parseFloat(bbox[3]));};OpenLayers.Bounds.fromSize=function(size){return new OpenLayers.Bounds(0,size.h,size.w,0);};OpenLayers.Bounds.oppositeQuadrant=function(quadrant){var opp="";opp+=(quadrant.charAt(0)=='t')?'b':'t';opp+=(quadrant.charAt(1)=='l')?'r':'l';return opp;};OpenLayers.LonLat=OpenLayers.Class({lon:0.0,lat:0.0,initialize:function(lon,lat){this.lon=OpenLayers.Util.toFloat(lon);this.lat=OpenLayers.Util.toFloat(lat);},toString:function(){return("lon="+this.lon+",lat="+this.lat);},toShortString:function(){return(this.lon+", "+this.lat);},clone:function(){return new OpenLayers.LonLat(this.lon,this.lat);},add:function(lon,lat){if((lon==null)||(lat==null)){var msg=OpenLayers.i18n("lonlatAddError");OpenLayers.Console.error(msg);return null;} +return new OpenLayers.LonLat(this.lon+lon,this.lat+lat);},equals:function(ll){var equals=false;if(ll!=null){equals=((this.lon==ll.lon&&this.lat==ll.lat)||(isNaN(this.lon)&&isNaN(this.lat)&&isNaN(ll.lon)&&isNaN(ll.lat)));} +return equals;},transform:function(source,dest){var point=OpenLayers.Projection.transform({'x':this.lon,'y':this.lat},source,dest);this.lon=point.x;this.lat=point.y;return this;},wrapDateLine:function(maxExtent){var newLonLat=this.clone();if(maxExtent){while(newLonLat.lonmaxExtent.right){newLonLat.lon-=maxExtent.getWidth();}} +return newLonLat;},CLASS_NAME:"OpenLayers.LonLat"});OpenLayers.LonLat.fromString=function(str){var pair=str.split(",");return new OpenLayers.LonLat(parseFloat(pair[0]),parseFloat(pair[1]));};OpenLayers.Pixel=OpenLayers.Class({x:0.0,y:0.0,initialize:function(x,y){this.x=parseFloat(x);this.y=parseFloat(y);},toString:function(){return("x="+this.x+",y="+this.y);},clone:function(){return new OpenLayers.Pixel(this.x,this.y);},equals:function(px){var equals=false;if(px!=null){equals=((this.x==px.x&&this.y==px.y)||(isNaN(this.x)&&isNaN(this.y)&&isNaN(px.x)&&isNaN(px.y)));} +return equals;},add:function(x,y){if((x==null)||(y==null)){var msg=OpenLayers.i18n("pixelAddError");OpenLayers.Console.error(msg);return null;} +return new OpenLayers.Pixel(this.x+x,this.y+y);},offset:function(px){var newPx=this.clone();if(px){newPx=this.add(px.x,px.y);} +return newPx;},CLASS_NAME:"OpenLayers.Pixel"});OpenLayers.Control=OpenLayers.Class({id:null,map:null,div:null,type:null,allowSelection:false,displayClass:"",title:"",active:null,handler:null,eventListeners:null,events:null,EVENT_TYPES:["activate","deactivate"],initialize:function(options){this.displayClass=this.CLASS_NAME.replace("OpenLayers.","ol").replace(/\./g,"");OpenLayers.Util.extend(this,options);this.events=new OpenLayers.Events(this,null,this.EVENT_TYPES);if(this.eventListeners instanceof Object){this.events.on(this.eventListeners);} +if(this.id==null){this.id=OpenLayers.Util.createUniqueID(this.CLASS_NAME+"_");}},destroy:function(){if(this.events){if(this.eventListeners){this.events.un(this.eventListeners);} +this.events.destroy();this.events=null;} +this.eventListeners=null;if(this.handler){this.handler.destroy();this.handler=null;} +if(this.handlers){for(var key in this.handlers){if(this.handlers.hasOwnProperty(key)&&typeof this.handlers[key].destroy=="function"){this.handlers[key].destroy();}} +this.handlers=null;} +if(this.map){this.map.removeControl(this);this.map=null;}},setMap:function(map){this.map=map;if(this.handler){this.handler.setMap(map);}},draw:function(px){if(this.div==null){this.div=OpenLayers.Util.createDiv(this.id);this.div.className=this.displayClass;if(!this.allowSelection){this.div.className+=" olControlNoSelect";this.div.setAttribute("unselectable","on",0);this.div.onselectstart=function(){return(false);};} +if(this.title!=""){this.div.title=this.title;}} +if(px!=null){this.position=px.clone();} +this.moveTo(this.position);return this.div;},moveTo:function(px){if((px!=null)&&(this.div!=null)){this.div.style.left=px.x+"px";this.div.style.top=px.y+"px";}},activate:function(){if(this.active){return false;} +if(this.handler){this.handler.activate();} +this.active=true;if(this.map){OpenLayers.Element.addClass(this.map.viewPortDiv,this.displayClass.replace(/ /g,"")+"Active");} +this.events.triggerEvent("activate");return true;},deactivate:function(){if(this.active){if(this.handler){this.handler.deactivate();} +this.active=false;if(this.map){OpenLayers.Element.removeClass(this.map.viewPortDiv,this.displayClass.replace(/ /g,"")+"Active");} +this.events.triggerEvent("deactivate");return true;} +return false;},CLASS_NAME:"OpenLayers.Control"});OpenLayers.Control.TYPE_BUTTON=1;OpenLayers.Control.TYPE_TOGGLE=2;OpenLayers.Control.TYPE_TOOL=3;OpenLayers.Lang={code:null,defaultCode:"en",getCode:function(){if(!OpenLayers.Lang.code){OpenLayers.Lang.setCode();} +return OpenLayers.Lang.code;},setCode:function(code){var lang;if(!code){code=(OpenLayers.Util.getBrowserName()=="msie")?navigator.userLanguage:navigator.language;} +var parts=code.split('-');parts[0]=parts[0].toLowerCase();if(typeof OpenLayers.Lang[parts[0]]=="object"){lang=parts[0];} +if(parts[1]){var testLang=parts[0]+'-'+parts[1].toUpperCase();if(typeof OpenLayers.Lang[testLang]=="object"){lang=testLang;}} +if(!lang){OpenLayers.Console.warn('Failed to find OpenLayers.Lang.'+parts.join("-")+' dictionary, falling back to default language');lang=OpenLayers.Lang.defaultCode;} +OpenLayers.Lang.code=lang;},translate:function(key,context){var dictionary=OpenLayers.Lang[OpenLayers.Lang.getCode()];var message=dictionary[key];if(!message){message=key;} +if(context){message=OpenLayers.String.format(message,context);} +return message;}};OpenLayers.i18n=OpenLayers.Lang.translate;OpenLayers.Popup.Anchored=OpenLayers.Class(OpenLayers.Popup,{relativePosition:null,keepInMap:true,anchor:null,initialize:function(id,lonlat,contentSize,contentHTML,anchor,closeBox,closeBoxCallback){var newArguments=[id,lonlat,contentSize,contentHTML,closeBox,closeBoxCallback];OpenLayers.Popup.prototype.initialize.apply(this,newArguments);this.anchor=(anchor!=null)?anchor:{size:new OpenLayers.Size(0,0),offset:new OpenLayers.Pixel(0,0)};},destroy:function(){this.anchor=null;this.relativePosition=null;OpenLayers.Popup.prototype.destroy.apply(this,arguments);},show:function(){this.updatePosition();OpenLayers.Popup.prototype.show.apply(this,arguments);},moveTo:function(px){var oldRelativePosition=this.relativePosition;this.relativePosition=this.calculateRelativePosition(px);var newPx=this.calculateNewPx(px);var newArguments=new Array(newPx);OpenLayers.Popup.prototype.moveTo.apply(this,newArguments);if(this.relativePosition!=oldRelativePosition){this.updateRelativePosition();}},setSize:function(contentSize){OpenLayers.Popup.prototype.setSize.apply(this,arguments);if((this.lonlat)&&(this.map)){var px=this.map.getLayerPxFromLonLat(this.lonlat);this.moveTo(px);}},calculateRelativePosition:function(px){var lonlat=this.map.getLonLatFromLayerPx(px);var extent=this.map.getExtent();var quadrant=extent.determineQuadrant(lonlat);return OpenLayers.Bounds.oppositeQuadrant(quadrant);},updateRelativePosition:function(){},calculateNewPx:function(px){var newPx=px.offset(this.anchor.offset);var size=this.size||this.contentSize;var top=(this.relativePosition.charAt(0)=='t');newPx.y+=(top)?-size.h:this.anchor.size.h;var left=(this.relativePosition.charAt(1)=='l');newPx.x+=(left)?-size.w:this.anchor.size.w;return newPx;},CLASS_NAME:"OpenLayers.Popup.Anchored"});OpenLayers.Protocol.SQL=OpenLayers.Class(OpenLayers.Protocol,{databaseName:'ol',tableName:"ol_vector_features",postReadFiltering:true,initialize:function(options){OpenLayers.Protocol.prototype.initialize.apply(this,[options]);},destroy:function(){OpenLayers.Protocol.prototype.destroy.apply(this);},supported:function(){return false;},evaluateFilter:function(feature,filter){return filter&&this.postReadFiltering?filter.evaluate(feature):true;},CLASS_NAME:"OpenLayers.Protocol.SQL"});OpenLayers.Protocol.WFS=function(options){options=OpenLayers.Util.applyDefaults(options,OpenLayers.Protocol.WFS.DEFAULTS);var cls=OpenLayers.Protocol.WFS["v"+options.version.replace(/\./g,"_")];if(!cls){throw"Unsupported WFS version: "+options.version;} +return new cls(options);};OpenLayers.Protocol.WFS.fromWMSLayer=function(layer,options){var typeName,featurePrefix;var param=layer.params["LAYERS"];var parts=(param instanceof Array?param[0]:param).split(":");if(parts.length>1){featurePrefix=parts[0];} +typeName=parts.pop();var protocolOptions={url:layer.url,featureType:typeName,featurePrefix:featurePrefix,srsName:layer.projection&&layer.projection.getCode()||layer.map&&layer.map.getProjectionObject().getCode(),version:"1.1.0"};return new OpenLayers.Protocol.WFS(OpenLayers.Util.applyDefaults(options,protocolOptions));};OpenLayers.Protocol.WFS.DEFAULTS={"version":"1.0.0"};OpenLayers.Renderer.Canvas=OpenLayers.Class(OpenLayers.Renderer,{canvas:null,features:null,geometryMap:null,initialize:function(containerID){OpenLayers.Renderer.prototype.initialize.apply(this,arguments);this.root=document.createElement("canvas");this.container.appendChild(this.root);this.canvas=this.root.getContext("2d");this.features={};this.geometryMap={};},eraseGeometry:function(geometry){this.eraseFeatures(this.features[this.geometryMap[geometry.id]][0]);},supported:function(){var canvas=document.createElement("canvas");return!!canvas.getContext;},setExtent:function(extent){this.extent=extent.clone();this.resolution=null;this.redraw();},setSize:function(size){this.size=size.clone();this.root.style.width=size.w+"px";this.root.style.height=size.h+"px";this.root.width=size.w;this.root.height=size.h;this.resolution=null;},drawFeature:function(feature,style){if(style==null){style=feature.style;} +style=OpenLayers.Util.extend({'fillColor':'#000000','strokeColor':'#000000','strokeWidth':2,'fillOpacity':1,'strokeOpacity':1},style);this.features[feature.id]=[feature,style];if(feature.geometry){this.geometryMap[feature.geometry.id]=feature.id;} +this.redraw();},drawGeometry:function(geometry,style){var className=geometry.CLASS_NAME;if((className=="OpenLayers.Geometry.Collection")||(className=="OpenLayers.Geometry.MultiPoint")||(className=="OpenLayers.Geometry.MultiLineString")||(className=="OpenLayers.Geometry.MultiPolygon")){for(var i=0;i1){middle=parseInt((leftIndex+rightIndex)/2);var placement=this.compare(this,newNode,OpenLayers.Util.getElement(this.order[middle]));if(placement>0){leftIndex=middle;}else{rightIndex=middle;}} +this.order.splice(rightIndex,0,nodeId);this.indices[nodeId]=this.getZIndex(newNode);return this.getNextElement(rightIndex);},remove:function(node){var nodeId=node.id;var arrayIndex=OpenLayers.Util.indexOf(this.order,nodeId);if(arrayIndex>=0){this.order.splice(arrayIndex,1);delete this.indices[nodeId];if(this.order.length>0){var lastId=this.order[this.order.length-1];this.maxZIndex=this.indices[lastId];}else{this.maxZIndex=0;}}},clear:function(){this.order=[];this.indices={};this.maxZIndex=0;},exists:function(node){return(this.indices[node.id]!=null);},getZIndex:function(node){return node._style.graphicZIndex;},determineZIndex:function(node){var zIndex=node._style.graphicZIndex;if(zIndex==null){zIndex=this.maxZIndex;node._style.graphicZIndex=zIndex;}else if(zIndex>this.maxZIndex){this.maxZIndex=zIndex;}},getNextElement:function(index){var nextIndex=index+1;if(nextIndex0){this.vectorRoot.removeChild(this.vectorRoot.firstChild);}} +if(this.textRoot){while(this.textRoot.childNodes.length>0){this.textRoot.removeChild(this.textRoot.firstChild);}} +if(this.indexer){this.indexer.clear();}},getNodeType:function(geometry,style){},drawGeometry:function(geometry,style,featureId){var className=geometry.CLASS_NAME;var rendered=true;if((className=="OpenLayers.Geometry.Collection")||(className=="OpenLayers.Geometry.MultiPoint")||(className=="OpenLayers.Geometry.MultiLineString")||(className=="OpenLayers.Geometry.MultiPolygon")){for(var i=0,len=geometry.components.length;i0){if(this.threshold>1){var clone=clusters.slice();clusters=[];var candidate;for(var i=0,len=clone.length;i0&&this.clusters.length==this.layer.features.length){exist=true;for(var i=0;i0){var remote=this.layer.projection;var local=this.layer.map.getProjectionObject();if(!local.equals(remote)){var geom;for(var i=0,len=features.length;i0){this.length=newLength;} +return this.length;},pageNext:function(event){var changed=false;if(this.features){if(this.num===null){this.num=-1;} +var start=(this.num+1)*this.length;changed=this.page(start,event);} +return changed;},pagePrevious:function(){var changed=false;if(this.features){if(this.num===null){this.num=this.pageCount();} +var start=(this.num-1)*this.length;changed=this.page(start);} +return changed;},page:function(start,event){var changed=false;if(this.features){if(start>=0&&start0){this.layer.destroyFeatures(destroys);}}},CLASS_NAME:"OpenLayers.Strategy.Save"});OpenLayers.Tween=OpenLayers.Class({INTERVAL:10,easing:null,begin:null,finish:null,duration:null,callbacks:null,time:null,interval:null,playing:false,initialize:function(easing){this.easing=(easing)?easing:OpenLayers.Easing.Expo.easeOut;},start:function(begin,finish,duration,options){this.playing=true;this.begin=begin;this.finish=finish;this.duration=duration;this.callbacks=options.callbacks;this.time=0;if(this.interval){window.clearInterval(this.interval);this.interval=null;} +if(this.callbacks&&this.callbacks.start){this.callbacks.start.call(this,this.begin);} +this.interval=window.setInterval(OpenLayers.Function.bind(this.play,this),this.INTERVAL);},stop:function(){if(!this.playing){return;} +if(this.callbacks&&this.callbacks.done){this.callbacks.done.call(this,this.finish);} +window.clearInterval(this.interval);this.interval=null;this.playing=false;},play:function(){var value={};for(var i in this.begin){var b=this.begin[i];var f=this.finish[i];if(b==null||f==null||isNaN(b)||isNaN(f)){OpenLayers.Console.error('invalid value for Tween');} +var c=f-b;value[i]=this.easing.apply(this,[this.time,b,c,this.duration]);} +this.time++;if(this.callbacks&&this.callbacks.eachStep){this.callbacks.eachStep.call(this,value);} +if(this.time>this.duration){if(this.callbacks&&this.callbacks.done){this.callbacks.done.call(this,this.finish);this.playing=false;} +window.clearInterval(this.interval);this.interval=null;}},CLASS_NAME:"OpenLayers.Tween"});OpenLayers.Easing={CLASS_NAME:"OpenLayers.Easing"};OpenLayers.Easing.Linear={easeIn:function(t,b,c,d){return c*t/d+b;},easeOut:function(t,b,c,d){return c*t/d+b;},easeInOut:function(t,b,c,d){return c*t/d+b;},CLASS_NAME:"OpenLayers.Easing.Linear"};OpenLayers.Easing.Expo={easeIn:function(t,b,c,d){return(t==0)?b:c*Math.pow(2,10*(t/d-1))+b;},easeOut:function(t,b,c,d){return(t==d)?b+c:c*(-Math.pow(2,-10*t/d)+1)+b;},easeInOut:function(t,b,c,d){if(t==0)return b;if(t==d)return b+c;if((t/=d/2)<1)return c/2*Math.pow(2,10*(t-1))+b;return c/2*(-Math.pow(2,-10*--t)+2)+b;},CLASS_NAME:"OpenLayers.Easing.Expo"};OpenLayers.Easing.Quad={easeIn:function(t,b,c,d){return c*(t/=d)*t+b;},easeOut:function(t,b,c,d){return-c*(t/=d)*(t-2)+b;},easeInOut:function(t,b,c,d){if((t/=d/2)<1)return c/2*t*t+b;return-c/2*((--t)*(t-2)-1)+b;},CLASS_NAME:"OpenLayers.Easing.Quad"};OpenLayers.Control.ArgParser=OpenLayers.Class(OpenLayers.Control,{center:null,zoom:null,layers:null,displayProjection:null,initialize:function(options){OpenLayers.Control.prototype.initialize.apply(this,arguments);},setMap:function(map){OpenLayers.Control.prototype.setMap.apply(this,arguments);for(var i=0,len=this.map.controls.length;i0){this.map.setCenter(this.map.getLonLatFromPixel(evt.xy),this.map.getZoom()-1);}},zoomBoxEnd:function(evt){if(this.mouseDragStart!=null){if(Math.abs(this.mouseDragStart.x-evt.xy.x)>5||Math.abs(this.mouseDragStart.y-evt.xy.y)>5){var start=this.map.getLonLatFromViewPortPx(this.mouseDragStart);var end=this.map.getLonLatFromViewPortPx(evt.xy);var top=Math.max(start.lat,end.lat);var bottom=Math.min(start.lat,end.lat);var left=Math.min(start.lon,end.lon);var right=Math.max(start.lon,end.lon);var bounds=new OpenLayers.Bounds(left,bottom,right,top);this.map.zoomToExtent(bounds);}else{var end=this.map.getLonLatFromViewPortPx(evt.xy);this.map.setCenter(new OpenLayers.LonLat((end.lon),(end.lat)),this.map.getZoom()+1);} +this.removeZoomBox();}},removeZoomBox:function(){this.map.viewPortDiv.removeChild(this.zoomBox);this.zoomBox=null;},onWheelEvent:function(e){var inMap=false;var elem=OpenLayers.Event.element(e);while(elem!=null){if(this.map&&elem==this.map.div){inMap=true;break;} +elem=elem.parentNode;} +if(inMap){var delta=0;if(!e){e=window.event;} +if(e.wheelDelta){delta=e.wheelDelta/120;if(window.opera&&window.opera.version()<9.2){delta=-delta;}}else if(e.detail){delta=-e.detail/3;} +if(delta){e.xy=this.mousePosition;if(delta<0){this.defaultWheelDown(e);}else{this.defaultWheelUp(e);}} +OpenLayers.Event.stop(e);}},CLASS_NAME:"OpenLayers.Control.MouseDefaults"});OpenLayers.Control.MousePosition=OpenLayers.Class(OpenLayers.Control,{element:null,prefix:'',separator:', ',suffix:'',numDigits:5,granularity:10,lastXy:null,displayProjection:null,initialize:function(options){OpenLayers.Control.prototype.initialize.apply(this,arguments);},destroy:function(){if(this.map){this.map.events.unregister('mousemove',this,this.redraw);} +OpenLayers.Control.prototype.destroy.apply(this,arguments);},draw:function(){OpenLayers.Control.prototype.draw.apply(this,arguments);if(!this.element){this.div.left="";this.div.top="";this.element=this.div;} +this.redraw();return this.div;},redraw:function(evt){var lonLat;if(evt==null){lonLat=new OpenLayers.LonLat(0,0);}else{if(this.lastXy==null||Math.abs(evt.xy.x-this.lastXy.x)>this.granularity||Math.abs(evt.xy.y-this.lastXy.y)>this.granularity) +{this.lastXy=evt.xy;return;} +lonLat=this.map.getLonLatFromPixel(evt.xy);if(!lonLat){return;} +if(this.displayProjection){lonLat.transform(this.map.getProjectionObject(),this.displayProjection);} +this.lastXy=evt.xy;} +var newHtml=this.formatOutput(lonLat);if(newHtml!=this.element.innerHTML){this.element.innerHTML=newHtml;}},formatOutput:function(lonLat){var digits=parseInt(this.numDigits);var newHtml=this.prefix+ +lonLat.lon.toFixed(digits)+ +this.separator+ +lonLat.lat.toFixed(digits)+ +this.suffix;return newHtml;},setMap:function(){OpenLayers.Control.prototype.setMap.apply(this,arguments);this.map.events.register('mousemove',this,this.redraw);},CLASS_NAME:"OpenLayers.Control.MousePosition"});OpenLayers.Control.Pan=OpenLayers.Class(OpenLayers.Control,{slideFactor:50,direction:null,type:OpenLayers.Control.TYPE_BUTTON,initialize:function(direction,options){this.direction=direction;this.CLASS_NAME+=this.direction;OpenLayers.Control.prototype.initialize.apply(this,[options]);},trigger:function(){switch(this.direction){case OpenLayers.Control.Pan.NORTH:this.map.pan(0,-this.slideFactor);break;case OpenLayers.Control.Pan.SOUTH:this.map.pan(0,this.slideFactor);break;case OpenLayers.Control.Pan.WEST:this.map.pan(-this.slideFactor,0);break;case OpenLayers.Control.Pan.EAST:this.map.pan(this.slideFactor,0);break;}},CLASS_NAME:"OpenLayers.Control.Pan"});OpenLayers.Control.Pan.NORTH="North";OpenLayers.Control.Pan.SOUTH="South";OpenLayers.Control.Pan.EAST="East";OpenLayers.Control.Pan.WEST="West";OpenLayers.Control.PanZoom=OpenLayers.Class(OpenLayers.Control,{slideFactor:50,slideRatio:null,buttons:null,position:null,initialize:function(options){this.position=new OpenLayers.Pixel(OpenLayers.Control.PanZoom.X,OpenLayers.Control.PanZoom.Y);OpenLayers.Control.prototype.initialize.apply(this,arguments);},destroy:function(){OpenLayers.Control.prototype.destroy.apply(this,arguments);this.removeButtons();this.buttons=null;this.position=null;},draw:function(px){OpenLayers.Control.prototype.draw.apply(this,arguments);px=this.position;this.buttons=[];var sz=new OpenLayers.Size(18,18);var centered=new OpenLayers.Pixel(px.x+sz.w/2,px.y);this._addButton("panup","north-mini.png",centered,sz);px.y=centered.y+sz.h;this._addButton("panleft","west-mini.png",px,sz);this._addButton("panright","east-mini.png",px.add(sz.w,0),sz);this._addButton("pandown","south-mini.png",centered.add(0,sz.h*2),sz);this._addButton("zoomin","zoom-plus-mini.png",centered.add(0,sz.h*3+5),sz);this._addButton("zoomworld","zoom-world-mini.png",centered.add(0,sz.h*4+5),sz);this._addButton("zoomout","zoom-minus-mini.png",centered.add(0,sz.h*5+5),sz);return this.div;},_addButton:function(id,img,xy,sz){var imgLocation=OpenLayers.Util.getImagesLocation()+img;var btn=OpenLayers.Util.createAlphaImageDiv(this.id+"_"+id,xy,sz,imgLocation,"absolute");this.div.appendChild(btn);OpenLayers.Event.observe(btn,"mousedown",OpenLayers.Function.bindAsEventListener(this.buttonDown,btn));OpenLayers.Event.observe(btn,"dblclick",OpenLayers.Function.bindAsEventListener(this.doubleClick,btn));OpenLayers.Event.observe(btn,"click",OpenLayers.Function.bindAsEventListener(this.doubleClick,btn));btn.action=id;btn.map=this.map;if(!this.slideRatio){var slideFactorPixels=this.slideFactor;var getSlideFactor=function(){return slideFactorPixels;};}else{var slideRatio=this.slideRatio;var getSlideFactor=function(dim){return this.map.getSize()[dim]*slideRatio;};} +btn.getSlideFactor=getSlideFactor;this.buttons.push(btn);return btn;},_removeButton:function(btn){OpenLayers.Event.stopObservingElement(btn);btn.map=null;this.div.removeChild(btn);OpenLayers.Util.removeItem(this.buttons,btn);},removeButtons:function(){for(var i=this.buttons.length-1;i>=0;--i){this._removeButton(this.buttons[i]);}},doubleClick:function(evt){OpenLayers.Event.stop(evt);return false;},buttonDown:function(evt){if(!OpenLayers.Event.isLeftClick(evt)){return;} +switch(this.action){case"panup":this.map.pan(0,-this.getSlideFactor("h"));break;case"pandown":this.map.pan(0,this.getSlideFactor("h"));break;case"panleft":this.map.pan(-this.getSlideFactor("w"),0);break;case"panright":this.map.pan(this.getSlideFactor("w"),0);break;case"zoomin":this.map.zoomIn();break;case"zoomout":this.map.zoomOut();break;case"zoomworld":this.map.zoomToMaxExtent();break;} +OpenLayers.Event.stop(evt);},CLASS_NAME:"OpenLayers.Control.PanZoom"});OpenLayers.Control.PanZoom.X=4;OpenLayers.Control.PanZoom.Y=4;OpenLayers.Control.Panel=OpenLayers.Class(OpenLayers.Control,{controls:null,defaultControl:null,initialize:function(options){OpenLayers.Control.prototype.initialize.apply(this,[options]);this.controls=[];},destroy:function(){OpenLayers.Control.prototype.destroy.apply(this,arguments);for(var i=this.controls.length-1;i>=0;i--){if(this.controls[i].events){this.controls[i].events.un({"activate":this.redraw,"deactivate":this.redraw,scope:this});} +OpenLayers.Event.stopObservingElement(this.controls[i].panel_div);this.controls[i].panel_div=null;}},activate:function(){if(OpenLayers.Control.prototype.activate.apply(this,arguments)){for(var i=0,len=this.controls.length;i=9500&&scale<=950000){scale=Math.round(scale/1000)+"K";}else if(scale>=950000){scale=Math.round(scale/1000000)+"M";}else{scale=Math.round(scale);} +this.element.innerHTML=OpenLayers.i18n("scale",{'scaleDenom':scale});},CLASS_NAME:"OpenLayers.Control.Scale"});OpenLayers.Control.ScaleLine=OpenLayers.Class(OpenLayers.Control,{maxWidth:100,topOutUnits:"km",topInUnits:"m",bottomOutUnits:"mi",bottomInUnits:"ft",eTop:null,eBottom:null,initialize:function(options){OpenLayers.Control.prototype.initialize.apply(this,[options]);},draw:function(){OpenLayers.Control.prototype.draw.apply(this,arguments);if(!this.eTop){this.div.style.display="block";this.div.style.position="absolute";this.eTop=document.createElement("div");this.eTop.className=this.displayClass+"Top";var theLen=this.topInUnits.length;this.div.appendChild(this.eTop);if((this.topOutUnits=="")||(this.topInUnits=="")){this.eTop.style.visibility="hidden";}else{this.eTop.style.visibility="visible";} +this.eBottom=document.createElement("div");this.eBottom.className=this.displayClass+"Bottom";this.div.appendChild(this.eBottom);if((this.bottomOutUnits=="")||(this.bottomInUnits=="")){this.eBottom.style.visibility="hidden";}else{this.eBottom.style.visibility="visible";}} +this.map.events.register('moveend',this,this.update);this.update();return this.div;},getBarLen:function(maxLen){var digits=parseInt(Math.log(maxLen)/Math.log(10));var pow10=Math.pow(10,digits);var firstChar=parseInt(maxLen/pow10);var barLen;if(firstChar>5){barLen=5;}else if(firstChar>2){barLen=2;}else{barLen=1;} +return barLen*pow10;},update:function(){var res=this.map.getResolution();if(!res){return;} +var curMapUnits=this.map.getUnits();var inches=OpenLayers.INCHES_PER_UNIT;var maxSizeData=this.maxWidth*res*inches[curMapUnits];var topUnits;var bottomUnits;if(maxSizeData>100000){topUnits=this.topOutUnits;bottomUnits=this.bottomOutUnits;}else{topUnits=this.topInUnits;bottomUnits=this.bottomInUnits;} +var topMax=maxSizeData/inches[topUnits];var bottomMax=maxSizeData/inches[bottomUnits];var topRounded=this.getBarLen(topMax);var bottomRounded=this.getBarLen(bottomMax);topMax=topRounded/inches[curMapUnits]*inches[topUnits];bottomMax=bottomRounded/inches[curMapUnits]*inches[bottomUnits];var topPx=topMax/res;var bottomPx=bottomMax/res;if(this.eBottom.style.visibility=="visible"){this.eBottom.style.width=Math.round(bottomPx)+"px";this.eBottom.innerHTML=bottomRounded+" "+bottomUnits;} +if(this.eTop.style.visibility=="visible"){this.eTop.style.width=Math.round(topPx)+"px";this.eTop.innerHTML=topRounded+" "+topUnits;}},CLASS_NAME:"OpenLayers.Control.ScaleLine"});OpenLayers.Control.ZoomIn=OpenLayers.Class(OpenLayers.Control,{type:OpenLayers.Control.TYPE_BUTTON,trigger:function(){this.map.zoomIn();},CLASS_NAME:"OpenLayers.Control.ZoomIn"});OpenLayers.Control.ZoomOut=OpenLayers.Class(OpenLayers.Control,{type:OpenLayers.Control.TYPE_BUTTON,trigger:function(){this.map.zoomOut();},CLASS_NAME:"OpenLayers.Control.ZoomOut"});OpenLayers.Control.ZoomToMaxExtent=OpenLayers.Class(OpenLayers.Control,{type:OpenLayers.Control.TYPE_BUTTON,trigger:function(){if(this.map){this.map.zoomToMaxExtent();}},CLASS_NAME:"OpenLayers.Control.ZoomToMaxExtent"});OpenLayers.Event={observers:false,KEY_BACKSPACE:8,KEY_TAB:9,KEY_RETURN:13,KEY_ESC:27,KEY_LEFT:37,KEY_UP:38,KEY_RIGHT:39,KEY_DOWN:40,KEY_DELETE:46,element:function(event){return event.target||event.srcElement;},isLeftClick:function(event){return(((event.which)&&(event.which==1))||((event.button)&&(event.button==1)));},isRightClick:function(event){return(((event.which)&&(event.which==3))||((event.button)&&(event.button==2)));},stop:function(event,allowDefault){if(!allowDefault){if(event.preventDefault){event.preventDefault();}else{event.returnValue=false;}} +if(event.stopPropagation){event.stopPropagation();}else{event.cancelBubble=true;}},findElement:function(event,tagName){var element=OpenLayers.Event.element(event);while(element.parentNode&&(!element.tagName||(element.tagName.toUpperCase()!=tagName.toUpperCase()))){element=element.parentNode;} +return element;},observe:function(elementParam,name,observer,useCapture){var element=OpenLayers.Util.getElement(elementParam);useCapture=useCapture||false;if(name=='keypress'&&(navigator.appVersion.match(/Konqueror|Safari|KHTML/)||element.attachEvent)){name='keydown';} +if(!this.observers){this.observers={};} +if(!element._eventCacheID){var idPrefix="eventCacheID_";if(element.id){idPrefix=element.id+"_"+idPrefix;} +element._eventCacheID=OpenLayers.Util.createUniqueID(idPrefix);} +var cacheID=element._eventCacheID;if(!this.observers[cacheID]){this.observers[cacheID]=[];} +this.observers[cacheID].push({'element':element,'name':name,'observer':observer,'useCapture':useCapture});if(element.addEventListener){element.addEventListener(name,observer,useCapture);}else if(element.attachEvent){element.attachEvent('on'+name,observer);}},stopObservingElement:function(elementParam){var element=OpenLayers.Util.getElement(elementParam);var cacheID=element._eventCacheID;this._removeElementObservers(OpenLayers.Event.observers[cacheID]);},_removeElementObservers:function(elementObservers){if(elementObservers){for(var i=elementObservers.length-1;i>=0;i--){var entry=elementObservers[i];var args=new Array(entry.element,entry.name,entry.observer,entry.useCapture);var removed=OpenLayers.Event.stopObserving.apply(this,args);}}},stopObserving:function(elementParam,name,observer,useCapture){useCapture=useCapture||false;var element=OpenLayers.Util.getElement(elementParam);var cacheID=element._eventCacheID;if(name=='keypress'){if(navigator.appVersion.match(/Konqueror|Safari|KHTML/)||element.detachEvent){name='keydown';}} +var foundEntry=false;var elementObservers=OpenLayers.Event.observers[cacheID];if(elementObservers){var i=0;while(!foundEntry&&i
"+"To get rid of this message, select a new BaseLayer "+"in the layer switcher in the upper-right corner.

"+"Most likely, this is because the Google Maps library "+"script was either not included, or does not contain the "+"correct API key for your site.

"+"Developers: For help getting this working correctly, "+"click here",'getLayerWarning':"The ${layerType} Layer was unable to load correctly.

"+"To get rid of this message, select a new BaseLayer "+"in the layer switcher in the upper-right corner.

"+"Most likely, this is because the ${layerLib} library "+"script was not correctly included.

"+"Developers: For help getting this working correctly, "+"click here",'scale':"Scale = 1 : ${scaleDenom}",'layerAlreadyAdded':"You tried to add the layer: ${layerName} to the map, but it has already been added",'reprojectDeprecated':"You are using the 'reproject' option "+"on the ${layerName} layer. This option is deprecated: "+"its use was designed to support displaying data over commercial "+"basemaps, but that functionality should now be achieved by using "+"Spherical Mercator support. More information is available from "+"http://trac.openlayers.org/wiki/SphericalMercator.",'methodDeprecated':"This method has been deprecated and will be removed in 3.0. "+"Please use ${newMethod} instead.",'boundsAddError':"You must pass both x and y values to the add function.",'lonlatAddError':"You must pass both lon and lat values to the add function.",'pixelAddError':"You must pass both x and y values to the add function.",'unsupportedGeometryType':"Unsupported geometry type: ${geomType}",'pagePositionFailed':"OpenLayers.Util.pagePosition failed: element with id ${elemId} may be misplaced.",'end':'','filterEvaluateNotImplemented':"evaluate is not implemented for this filter type."};OpenLayers.Popup.AnchoredBubble=OpenLayers.Class(OpenLayers.Popup.Anchored,{rounded:false,initialize:function(id,lonlat,contentSize,contentHTML,anchor,closeBox,closeBoxCallback){this.padding=new OpenLayers.Bounds(0,OpenLayers.Popup.AnchoredBubble.CORNER_SIZE,0,OpenLayers.Popup.AnchoredBubble.CORNER_SIZE);OpenLayers.Popup.Anchored.prototype.initialize.apply(this,arguments);},draw:function(px){OpenLayers.Popup.Anchored.prototype.draw.apply(this,arguments);this.setContentHTML();this.setBackgroundColor();this.setOpacity();return this.div;},updateRelativePosition:function(){this.setRicoCorners();},setSize:function(contentSize){OpenLayers.Popup.Anchored.prototype.setSize.apply(this,arguments);this.setRicoCorners();},setBackgroundColor:function(color){if(color!=undefined){this.backgroundColor=color;} +if(this.div!=null){if(this.contentDiv!=null){this.div.style.background="transparent";OpenLayers.Rico.Corner.changeColor(this.groupDiv,this.backgroundColor);}}},setOpacity:function(opacity){OpenLayers.Popup.Anchored.prototype.setOpacity.call(this,opacity);if(this.div!=null){if(this.groupDiv!=null){OpenLayers.Rico.Corner.changeOpacity(this.groupDiv,this.opacity);}}},setBorder:function(border){this.border=0;},setRicoCorners:function(){var corners=this.getCornersToRound(this.relativePosition);var options={corners:corners,color:this.backgroundColor,bgColor:"transparent",blend:false};if(!this.rounded){OpenLayers.Rico.Corner.round(this.div,options);this.rounded=true;}else{OpenLayers.Rico.Corner.reRound(this.groupDiv,options);this.setBackgroundColor();this.setOpacity();}},getCornersToRound:function(){var corners=['tl','tr','bl','br'];var corner=OpenLayers.Bounds.oppositeQuadrant(this.relativePosition);OpenLayers.Util.removeItem(corners,corner);return corners.join(" ");},CLASS_NAME:"OpenLayers.Popup.AnchoredBubble"});OpenLayers.Popup.AnchoredBubble.CORNER_SIZE=5;OpenLayers.Popup.Framed=OpenLayers.Class(OpenLayers.Popup.Anchored,{imageSrc:null,imageSize:null,isAlphaImage:false,positionBlocks:null,blocks:null,fixedRelativePosition:false,initialize:function(id,lonlat,contentSize,contentHTML,anchor,closeBox,closeBoxCallback){OpenLayers.Popup.Anchored.prototype.initialize.apply(this,arguments);if(this.fixedRelativePosition){this.updateRelativePosition();this.calculateRelativePosition=function(px){return this.relativePosition;};} +this.contentDiv.style.position="absolute";this.contentDiv.style.zIndex=1;if(closeBox){this.closeDiv.style.zIndex=1;} +this.groupDiv.style.position="absolute";this.groupDiv.style.top="0px";this.groupDiv.style.left="0px";this.groupDiv.style.height="100%";this.groupDiv.style.width="100%";},destroy:function(){this.imageSrc=null;this.imageSize=null;this.isAlphaImage=null;this.fixedRelativePosition=false;this.positionBlocks=null;for(var i=0;i=200&&request.status<300){response.features=this.parseFeatures(request);response.code=OpenLayers.Protocol.Response.SUCCESS;}else{response.code=OpenLayers.Protocol.Response.FAILURE;} +options.callback.call(options.scope,response);};},parseFeatures:function(request){var doc=request.responseXML;if(!doc||!doc.documentElement){doc=request.responseText;} +if(!doc||doc.length<=0){return null;} +return this.format.read(doc);},commit:function(features,options){options=OpenLayers.Util.extend({},options);OpenLayers.Util.applyDefaults(options,this.options);var response=new OpenLayers.Protocol.Response({requestType:"commit",reqFeatures:features});response.priv=OpenLayers.Request.POST({url:options.url,data:this.format.write(features,options),callback:this.createCallback(this.handleCommit,response,options)});return response;},handleCommit:function(response,options){if(options.callback){var request=response.priv;var data=request.responseXML;if(!data||!data.documentElement){data=request.responseText;} +var obj=this.format.read(data)||{};response.insertIds=obj.insertIds||[];response.code=(obj.success)?OpenLayers.Protocol.Response.SUCCESS:OpenLayers.Protocol.Response.FAILURE;options.callback.call(options.scope,response);}},filterDelete:function(filter,options){options=OpenLayers.Util.extend({},options);OpenLayers.Util.applyDefaults(options,this.options);var response=new OpenLayers.Protocol.Response({requestType:"commit"});var root=this.format.createElementNSPlus("wfs:Transaction",{attributes:{service:"WFS",version:this.version}});var deleteNode=this.format.createElementNSPlus("wfs:Delete",{attributes:{typeName:(options.featureNS?this.featurePrefix+":":"")+ +options.featureType}});if(options.featureNS){deleteNode.setAttribute("xmlns:"+this.featurePrefix,options.featureNS);} +var filterNode=this.format.writeNode("ogc:Filter",filter);deleteNode.appendChild(filterNode);root.appendChild(deleteNode);var data=OpenLayers.Format.XML.prototype.write.apply(this.format,[root]);return OpenLayers.Request.POST({url:this.url,callback:options.callback||function(){},data:data});},abort:function(response){if(response){response.priv.abort();}},CLASS_NAME:"OpenLayers.Protocol.WFS.v1"});OpenLayers.Renderer.SVG=OpenLayers.Class(OpenLayers.Renderer.Elements,{xmlns:"http://www.w3.org/2000/svg",xlinkns:"http://www.w3.org/1999/xlink",MAX_PIXEL:15000,translationParameters:null,symbolSize:{},isGecko:null,initialize:function(containerID){if(!this.supported()){return;} +OpenLayers.Renderer.Elements.prototype.initialize.apply(this,arguments);this.translationParameters={x:0,y:0};this.isGecko=(navigator.userAgent.toLowerCase().indexOf("gecko/")!=-1);},destroy:function(){OpenLayers.Renderer.Elements.prototype.destroy.apply(this,arguments);},supported:function(){var svgFeature="http://www.w3.org/TR/SVG11/feature#";return(document.implementation&&(document.implementation.hasFeature("org.w3c.svg","1.0")||document.implementation.hasFeature(svgFeature+"SVG","1.1")||document.implementation.hasFeature(svgFeature+"BasicStructure","1.1")));},inValidRange:function(x,y,xyOnly){var left=x+(xyOnly?0:this.translationParameters.x);var top=y+(xyOnly?0:this.translationParameters.y);return(left>=-this.MAX_PIXEL&&left<=this.MAX_PIXEL&&top>=-this.MAX_PIXEL&&top<=this.MAX_PIXEL);},setExtent:function(extent,resolutionChanged){OpenLayers.Renderer.Elements.prototype.setExtent.apply(this,arguments);var resolution=this.getResolution();var left=-extent.left/resolution;var top=extent.top/resolution;if(resolutionChanged){this.left=left;this.top=top;var extentString="0 0 "+this.size.w+" "+this.size.h;this.rendererRoot.setAttributeNS(null,"viewBox",extentString);this.translate(0,0);return true;}else{var inRange=this.translate(left-this.left,top-this.top);if(!inRange){this.setExtent(extent,true);} +return inRange;}},translate:function(x,y){if(!this.inValidRange(x,y,true)){return false;}else{var transformString="";if(x||y){transformString="translate("+x+","+y+")";} +this.root.setAttributeNS(null,"transform",transformString);this.translationParameters={x:x,y:y};return true;}},setSize:function(size){OpenLayers.Renderer.prototype.setSize.apply(this,arguments);this.rendererRoot.setAttributeNS(null,"width",this.size.w);this.rendererRoot.setAttributeNS(null,"height",this.size.h);},getNodeType:function(geometry,style){var nodeType=null;switch(geometry.CLASS_NAME){case"OpenLayers.Geometry.Point":if(style.externalGraphic){nodeType="image";}else if(this.isComplexSymbol(style.graphicName)){nodeType="use";}else{nodeType="circle";} +break;case"OpenLayers.Geometry.Rectangle":nodeType="rect";break;case"OpenLayers.Geometry.LineString":nodeType="polyline";break;case"OpenLayers.Geometry.LinearRing":nodeType="polygon";break;case"OpenLayers.Geometry.Polygon":case"OpenLayers.Geometry.Curve":case"OpenLayers.Geometry.Surface":nodeType="path";break;default:break;} +return nodeType;},setStyle:function(node,style,options){style=style||node._style;options=options||node._options;var r=parseFloat(node.getAttributeNS(null,"r"));var widthFactor=1;var pos;if(node._geometryClass=="OpenLayers.Geometry.Point"&&r){node.style.visibility="";if(style.graphic===false){node.style.visibility="hidden";}else if(style.externalGraphic){pos=this.getPosition(node);if(style.graphicTitle){node.setAttributeNS(null,"title",style.graphicTitle);} +if(style.graphicWidth&&style.graphicHeight){node.setAttributeNS(null,"preserveAspectRatio","none");} +var width=style.graphicWidth||style.graphicHeight;var height=style.graphicHeight||style.graphicWidth;width=width?width:style.pointRadius*2;height=height?height:style.pointRadius*2;var xOffset=(style.graphicXOffset!=undefined)?style.graphicXOffset:-(0.5*width);var yOffset=(style.graphicYOffset!=undefined)?style.graphicYOffset:-(0.5*height);var opacity=style.graphicOpacity||style.fillOpacity;node.setAttributeNS(null,"x",(pos.x+xOffset).toFixed());node.setAttributeNS(null,"y",(pos.y+yOffset).toFixed());node.setAttributeNS(null,"width",width);node.setAttributeNS(null,"height",height);node.setAttributeNS(this.xlinkns,"href",style.externalGraphic);node.setAttributeNS(null,"style","opacity: "+opacity);}else if(this.isComplexSymbol(style.graphicName)){var offset=style.pointRadius*3;var size=offset*2;var id=this.importSymbol(style.graphicName);var href="#"+id;pos=this.getPosition(node);widthFactor=this.symbolSize[id]/size;var parent=node.parentNode;var nextSibling=node.nextSibling;if(parent){parent.removeChild(node);} +node.setAttributeNS(this.xlinkns,"href",href);node.setAttributeNS(null,"width",size);node.setAttributeNS(null,"height",size);node.setAttributeNS(null,"x",pos.x-offset);node.setAttributeNS(null,"y",pos.y-offset);if(nextSibling){parent.insertBefore(node,nextSibling);}else if(parent){parent.appendChild(node);}}else{node.setAttributeNS(null,"r",style.pointRadius);} +if(typeof style.rotation!="undefined"&&pos){var rotation=OpenLayers.String.format("rotate(${0} ${1} ${2})",[style.rotation,pos.x,pos.y]);node.setAttributeNS(null,"transform",rotation);}} +if(options.isFilled){node.setAttributeNS(null,"fill",style.fillColor);node.setAttributeNS(null,"fill-opacity",style.fillOpacity);}else{node.setAttributeNS(null,"fill","none");} +if(options.isStroked){node.setAttributeNS(null,"stroke",style.strokeColor);node.setAttributeNS(null,"stroke-opacity",style.strokeOpacity);node.setAttributeNS(null,"stroke-width",style.strokeWidth*widthFactor);node.setAttributeNS(null,"stroke-linecap",style.strokeLinecap);node.setAttributeNS(null,"stroke-linejoin","round");node.setAttributeNS(null,"stroke-dasharray",this.dashStyle(style,widthFactor));}else{node.setAttributeNS(null,"stroke","none");} +if(style.pointerEvents){node.setAttributeNS(null,"pointer-events",style.pointerEvents);} +if(style.cursor!=null){node.setAttributeNS(null,"cursor",style.cursor);} +return node;},dashStyle:function(style,widthFactor){var w=style.strokeWidth*widthFactor;switch(style.strokeDashstyle){case'solid':return'none';case'dot':return[1,4*w].join();case'dash':return[4*w,4*w].join();case'dashdot':return[4*w,4*w,1,4*w].join();case'longdash':return[8*w,4*w].join();case'longdashdot':return[8*w,4*w,1,4*w].join();default:return style.strokeDashstyle.replace(/ /g,",");}},createNode:function(type,id){var node=document.createElementNS(this.xmlns,type);if(id){node.setAttributeNS(null,"id",id);} +return node;},nodeTypeCompare:function(node,type){return(type==node.nodeName);},createRenderRoot:function(){return this.nodeFactory(this.container.id+"_svgRoot","svg");},createRoot:function(suffix){return this.nodeFactory(this.container.id+suffix,"g");},createDefs:function(){var defs=this.nodeFactory(this.container.id+"_defs","defs");this.rendererRoot.appendChild(defs);return defs;},drawPoint:function(node,geometry){return this.drawCircle(node,geometry,1);},drawCircle:function(node,geometry,radius){var resolution=this.getResolution();var x=(geometry.x/resolution+this.left);var y=(this.top-geometry.y/resolution);if(this.inValidRange(x,y)){node.setAttributeNS(null,"cx",x);node.setAttributeNS(null,"cy",y);node.setAttributeNS(null,"r",radius);return node;}else{return false;}},drawLineString:function(node,geometry){var componentsResult=this.getComponentsString(geometry.components);if(componentsResult.path){node.setAttributeNS(null,"points",componentsResult.path);return(componentsResult.complete?node:null);}else{return false;}},drawLinearRing:function(node,geometry){var componentsResult=this.getComponentsString(geometry.components);if(componentsResult.path){node.setAttributeNS(null,"points",componentsResult.path);return(componentsResult.complete?node:null);}else{return false;}},drawPolygon:function(node,geometry){var d="";var draw=true;var complete=true;var linearRingResult,path;for(var j=0,len=geometry.components.length;j0){if(this.getShortString(components[i-1])){strings.push(this.clipLine(components[i],components[i-1]));}} +if(imaxX){k=(y2-y1)/(x2-x1);x2=x2<0?-maxX:maxX;y2=y1+(x2-x1)*k;} +if(y2<-maxY||y2>maxY){k=(x2-x1)/(y2-y1);y2=y2<0?-maxY:maxY;x2=x1+(y2-y1)*k;} +return x2+","+y2;},getShortString:function(point){var resolution=this.getResolution();var x=(point.x/resolution+this.left);var y=(this.top-point.y/resolution);if(this.inValidRange(x,y)){return x+","+y;}else{return false;}},getPosition:function(node){return({x:parseFloat(node.getAttributeNS(null,"cx")),y:parseFloat(node.getAttributeNS(null,"cy"))});},importSymbol:function(graphicName){if(!this.defs){this.defs=this.createDefs();} +var id=this.container.id+"-"+graphicName;if(document.getElementById(id)!=null){return id;} +var symbol=OpenLayers.Renderer.symbol[graphicName];if(!symbol){throw new Error(graphicName+' is not a valid symbol name');return;} +var symbolNode=this.nodeFactory(id,"symbol");var node=this.nodeFactory(null,"polygon");symbolNode.appendChild(node);var symbolExtent=new OpenLayers.Bounds(Number.MAX_VALUE,Number.MAX_VALUE,0,0);var points="";var x,y;for(var i=0;i=2*parts[1]){return"longdash";} +return(parts[0]==1||parts[1]==1)?"dot":"dash";}else if(parts.length==4){return(1*parts[0]>=2*parts[1])?"longdashdot":"dashdot";} +return"solid";}},createNode:function(type,id){var node=document.createElement(type);if(id){node.id=id;} +node.unselectable='on';node.onselectstart=function(){return(false);};return node;},nodeTypeCompare:function(node,type){var subType=type;var splitIndex=subType.indexOf(":");if(splitIndex!=-1){subType=subType.substr(splitIndex+1);} +var nodeName=node.nodeName;splitIndex=nodeName.indexOf(":");if(splitIndex!=-1){nodeName=nodeName.substr(splitIndex+1);} +return(subType==nodeName);},createRenderRoot:function(){return this.nodeFactory(this.container.id+"_vmlRoot","div");},createRoot:function(suffix){return this.nodeFactory(this.container.id+suffix,"olv:group");},drawPoint:function(node,geometry){return this.drawCircle(node,geometry,1);},drawCircle:function(node,geometry,radius){if(!isNaN(geometry.x)&&!isNaN(geometry.y)){var resolution=this.getResolution();node.style.left=((geometry.x/resolution-this.offset.x).toFixed()-radius)+"px";node.style.top=((geometry.y/resolution-this.offset.y).toFixed()-radius)+"px";var diameter=radius*2;node.style.width=diameter+"px";node.style.height=diameter+"px";return node;} +return false;},drawLineString:function(node,geometry){return this.drawLine(node,geometry,false);},drawLinearRing:function(node,geometry){return this.drawLine(node,geometry,true);},drawLine:function(node,geometry,closeLine){this.setNodeDimension(node,geometry);var resolution=this.getResolution();var numComponents=geometry.components.length;var parts=new Array(numComponents);var comp,x,y;for(var i=0;i0){symbolExtent.bottom=symbolExtent.bottom-diff;symbolExtent.top=symbolExtent.top+diff;}else{symbolExtent.left=symbolExtent.left-diff;symbolExtent.right=symbolExtent.right+diff;} +cache={path:path,size:symbolExtent.getWidth(),left:symbolExtent.left,bottom:symbolExtent.bottom};this.symbolCache[id]=cache;return cache;},CLASS_NAME:"OpenLayers.Renderer.VML"});OpenLayers.Renderer.VML.LABEL_SHIFT={"l":0,"c":.5,"r":1,"t":0,"m":.5,"b":1};OpenLayers.Tile=OpenLayers.Class({EVENT_TYPES:["loadstart","loadend","reload","unload"],events:null,id:null,layer:null,url:null,bounds:null,size:null,position:null,isLoading:false,initialize:function(layer,position,bounds,url,size){this.layer=layer;this.position=position.clone();this.bounds=bounds.clone();this.url=url;this.size=size.clone();this.id=OpenLayers.Util.createUniqueID("Tile_");this.events=new OpenLayers.Events(this,null,this.EVENT_TYPES);},unload:function(){if(this.isLoading){this.isLoading=false;this.events.triggerEvent("unload");}},destroy:function(){this.layer=null;this.bounds=null;this.size=null;this.position=null;this.events.destroy();this.events=null;},clone:function(obj){if(obj==null){obj=new OpenLayers.Tile(this.layer,this.position,this.bounds,this.url,this.size);} +OpenLayers.Util.applyDefaults(obj,this);return obj;},draw:function(){var maxExtent=this.layer.maxExtent;var withinMaxExtent=(maxExtent&&this.bounds.intersectsBounds(maxExtent,false));this.shouldDraw=(withinMaxExtent||this.layer.displayOutsideMaxExtent);this.clear();return this.shouldDraw;},moveTo:function(bounds,position,redraw){if(redraw==null){redraw=true;} +this.bounds=bounds.clone();this.position=position.clone();if(redraw){this.draw();}},clear:function(){},getBoundsFromBaseLayer:function(position){var msg=OpenLayers.i18n('reprojectDeprecated',{'layerName':this.layer.name});OpenLayers.Console.warn(msg);var topLeft=this.layer.map.getLonLatFromLayerPx(position);var bottomRightPx=position.clone();bottomRightPx.x+=this.size.w;bottomRightPx.y+=this.size.h;var bottomRight=this.layer.map.getLonLatFromLayerPx(bottomRightPx);if(topLeft.lon>bottomRight.lon){if(topLeft.lon<0){topLeft.lon=-180-(topLeft.lon+180);}else{bottomRight.lon=180+bottomRight.lon+180;}} +var bounds=new OpenLayers.Bounds(topLeft.lon,bottomRight.lat,bottomRight.lon,topLeft.lat);return bounds;},showTile:function(){if(this.shouldDraw){this.show();}},show:function(){},hide:function(){},CLASS_NAME:"OpenLayers.Tile"});OpenLayers.Control.MouseToolbar=OpenLayers.Class(OpenLayers.Control.MouseDefaults,{mode:null,buttons:null,direction:"vertical",buttonClicked:null,initialize:function(position,direction){OpenLayers.Control.prototype.initialize.apply(this,arguments);this.position=new OpenLayers.Pixel(OpenLayers.Control.MouseToolbar.X,OpenLayers.Control.MouseToolbar.Y);if(position){this.position=position;} +if(direction){this.direction=direction;} +this.measureDivs=[];},destroy:function(){for(var btnId in this.buttons){var btn=this.buttons[btnId];btn.map=null;btn.events.destroy();} +OpenLayers.Control.MouseDefaults.prototype.destroy.apply(this,arguments);},draw:function(){OpenLayers.Control.prototype.draw.apply(this,arguments);OpenLayers.Control.MouseDefaults.prototype.draw.apply(this,arguments);this.buttons={};var sz=new OpenLayers.Size(28,28);var centered=new OpenLayers.Pixel(OpenLayers.Control.MouseToolbar.X,0);this._addButton("zoombox","drag-rectangle-off.png","drag-rectangle-on.png",centered,sz,"Shift->Drag to zoom to area");centered=centered.add((this.direction=="vertical"?0:sz.w),(this.direction=="vertical"?sz.h:0));this._addButton("pan","panning-hand-off.png","panning-hand-on.png",centered,sz,"Drag the map to pan.");centered=centered.add((this.direction=="vertical"?0:sz.w),(this.direction=="vertical"?sz.h:0));this.switchModeTo("pan");return this.div;},_addButton:function(id,img,activeImg,xy,sz,title){var imgLocation=OpenLayers.Util.getImagesLocation()+img;var activeImgLocation=OpenLayers.Util.getImagesLocation()+activeImg;var btn=OpenLayers.Util.createAlphaImageDiv("OpenLayers_Control_MouseToolbar_"+id,xy,sz,imgLocation,"absolute");this.div.appendChild(btn);btn.imgLocation=imgLocation;btn.activeImgLocation=activeImgLocation;btn.events=new OpenLayers.Events(this,btn,null,true);btn.events.on({"mousedown":this.buttonDown,"mouseup":this.buttonUp,"dblclick":OpenLayers.Event.stop,scope:this});btn.action=id;btn.title=title;btn.alt=title;btn.map=this.map;this.buttons[id]=btn;return btn;},buttonDown:function(evt){if(!OpenLayers.Event.isLeftClick(evt)){return;} +this.buttonClicked=evt.element.action;OpenLayers.Event.stop(evt);},buttonUp:function(evt){if(!OpenLayers.Event.isLeftClick(evt)){return;} +if(this.buttonClicked!=null){if(this.buttonClicked==evt.element.action){this.switchModeTo(evt.element.action);} +OpenLayers.Event.stop(evt);this.buttonClicked=null;}},defaultDblClick:function(evt){this.switchModeTo("pan");this.performedDrag=false;var newCenter=this.map.getLonLatFromViewPortPx(evt.xy);this.map.setCenter(newCenter,this.map.zoom+1);OpenLayers.Event.stop(evt);return false;},defaultMouseDown:function(evt){if(!OpenLayers.Event.isLeftClick(evt)){return;} +this.mouseDragStart=evt.xy.clone();this.performedDrag=false;this.startViaKeyboard=false;if(evt.shiftKey&&this.mode!="zoombox"){this.switchModeTo("zoombox");this.startViaKeyboard=true;}else if(evt.altKey&&this.mode!="measure"){this.switchModeTo("measure");}else if(!this.mode){this.switchModeTo("pan");} +switch(this.mode){case"zoombox":this.map.div.style.cursor="crosshair";this.zoomBox=OpenLayers.Util.createDiv('zoomBox',this.mouseDragStart,null,null,"absolute","2px solid red");this.zoomBox.style.backgroundColor="white";this.zoomBox.style.filter="alpha(opacity=50)";this.zoomBox.style.opacity="0.50";this.zoomBox.style.fontSize="1px";this.zoomBox.style.zIndex=this.map.Z_INDEX_BASE["Popup"]-1;this.map.viewPortDiv.appendChild(this.zoomBox);this.performedDrag=true;break;case"measure":var distance="";if(this.measureStart){var measureEnd=this.map.getLonLatFromViewPortPx(this.mouseDragStart);distance=OpenLayers.Util.distVincenty(this.measureStart,measureEnd);distance=Math.round(distance*100)/100;distance=distance+"km";this.measureStartBox=this.measureBox;} +this.measureStart=this.map.getLonLatFromViewPortPx(this.mouseDragStart);;this.measureBox=OpenLayers.Util.createDiv(null,this.mouseDragStart.add(-2-parseInt(this.map.layerContainerDiv.style.left),-2-parseInt(this.map.layerContainerDiv.style.top)),null,null,"absolute");this.measureBox.style.width="4px";this.measureBox.style.height="4px";this.measureBox.style.fontSize="1px";this.measureBox.style.backgroundColor="red";this.measureBox.style.zIndex=this.map.Z_INDEX_BASE["Popup"]-1;this.map.layerContainerDiv.appendChild(this.measureBox);if(distance){this.measureBoxDistance=OpenLayers.Util.createDiv(null,this.mouseDragStart.add(-2-parseInt(this.map.layerContainerDiv.style.left),2-parseInt(this.map.layerContainerDiv.style.top)),null,null,"absolute");this.measureBoxDistance.innerHTML=distance;this.measureBoxDistance.style.zIndex=this.map.Z_INDEX_BASE["Popup"]-1;this.map.layerContainerDiv.appendChild(this.measureBoxDistance);this.measureDivs.push(this.measureBoxDistance);} +this.measureBox.style.zIndex=this.map.Z_INDEX_BASE["Popup"]-1;this.map.layerContainerDiv.appendChild(this.measureBox);this.measureDivs.push(this.measureBox);break;default:this.map.div.style.cursor="move";break;} +document.onselectstart=function(){return false;};OpenLayers.Event.stop(evt);},switchModeTo:function(mode){if(mode!=this.mode){if(this.mode&&this.buttons[this.mode]){OpenLayers.Util.modifyAlphaImageDiv(this.buttons[this.mode],null,null,null,this.buttons[this.mode].imgLocation);} +if(this.mode=="measure"&&mode!="measure"){for(var i=0,len=this.measureDivs.length;i1){this.onPreviousChange(this.previousStack[1],this.previousStack.length-1);} +if(this.previousStack.length>(this.limit+1)){this.previousStack.pop();} +if(this.nextStack.length>0){this.nextStack=[];this.onNextChange(null,0);}} +return true;},this);}},activate:function(){var activated=false;if(this.map){if(OpenLayers.Control.prototype.activate.apply(this)){if(this.listeners==null){this.setListeners();} +for(var type in this.listeners){this.map.events.register(type,this,this.listeners[type]);} +activated=true;if(this.previousStack.length==0){this.initStack();}}} +return activated;},initStack:function(){if(this.map.getCenter()){this.listeners.moveend();}},deactivate:function(){var deactivated=false;if(this.map){if(OpenLayers.Control.prototype.deactivate.apply(this)){for(var type in this.listeners){this.map.events.unregister(type,this,this.listeners[type]);} +if(this.clearOnDeactivate){this.clear();} +deactivated=true;}} +return deactivated;},CLASS_NAME:"OpenLayers.Control.NavigationHistory"});OpenLayers.Control.PanPanel=OpenLayers.Class(OpenLayers.Control.Panel,{initialize:function(options){OpenLayers.Control.Panel.prototype.initialize.apply(this,[options]);this.addControls([new OpenLayers.Control.Pan(OpenLayers.Control.Pan.NORTH),new OpenLayers.Control.Pan(OpenLayers.Control.Pan.SOUTH),new OpenLayers.Control.Pan(OpenLayers.Control.Pan.EAST),new OpenLayers.Control.Pan(OpenLayers.Control.Pan.WEST)]);},CLASS_NAME:"OpenLayers.Control.PanPanel"});OpenLayers.Control.PanZoomBar=OpenLayers.Class(OpenLayers.Control.PanZoom,{zoomStopWidth:18,zoomStopHeight:11,slider:null,sliderEvents:null,zoomBarDiv:null,divEvents:null,zoomWorldIcon:false,initialize:function(){OpenLayers.Control.PanZoom.prototype.initialize.apply(this,arguments);},destroy:function(){this._removeZoomBar();this.map.events.un({"changebaselayer":this.redraw,scope:this});OpenLayers.Control.PanZoom.prototype.destroy.apply(this,arguments);},setMap:function(map){OpenLayers.Control.PanZoom.prototype.setMap.apply(this,arguments);this.map.events.register("changebaselayer",this,this.redraw);},redraw:function(){if(this.div!=null){this.removeButtons();this._removeZoomBar();} +this.draw();},draw:function(px){OpenLayers.Control.prototype.draw.apply(this,arguments);px=this.position.clone();this.buttons=[];var sz=new OpenLayers.Size(18,18);var centered=new OpenLayers.Pixel(px.x+sz.w/2,px.y);var wposition=sz.w;if(this.zoomWorldIcon){centered=new OpenLayers.Pixel(px.x+sz.w,px.y);} +this._addButton("panup","north-mini.png",centered,sz);px.y=centered.y+sz.h;this._addButton("panleft","west-mini.png",px,sz);if(this.zoomWorldIcon){this._addButton("zoomworld","zoom-world-mini.png",px.add(sz.w,0),sz);wposition*=2;} +this._addButton("panright","east-mini.png",px.add(wposition,0),sz);this._addButton("pandown","south-mini.png",centered.add(0,sz.h*2),sz);this._addButton("zoomin","zoom-plus-mini.png",centered.add(0,sz.h*3+5),sz);centered=this._addZoomBar(centered.add(0,sz.h*4+5));this._addButton("zoomout","zoom-minus-mini.png",centered,sz);return this.div;},_addZoomBar:function(centered){var imgLocation=OpenLayers.Util.getImagesLocation();var id=this.id+"_"+this.map.id;var zoomsToEnd=this.map.getNumZoomLevels()-1-this.map.getZoom();var slider=OpenLayers.Util.createAlphaImageDiv(id,centered.add(-1,zoomsToEnd*this.zoomStopHeight),new OpenLayers.Size(20,9),imgLocation+"slider.png","absolute");this.slider=slider;this.sliderEvents=new OpenLayers.Events(this,slider,null,true,{includeXY:true});this.sliderEvents.on({"mousedown":this.zoomBarDown,"mousemove":this.zoomBarDrag,"mouseup":this.zoomBarUp,"dblclick":this.doubleClick,"click":this.doubleClick});var sz=new OpenLayers.Size();sz.h=this.zoomStopHeight*this.map.getNumZoomLevels();sz.w=this.zoomStopWidth;var div=null;if(OpenLayers.Util.alphaHack()){var id=this.id+"_"+this.map.id;div=OpenLayers.Util.createAlphaImageDiv(id,centered,new OpenLayers.Size(sz.w,this.zoomStopHeight),imgLocation+"zoombar.png","absolute",null,"crop");div.style.height=sz.h+"px";}else{div=OpenLayers.Util.createDiv('OpenLayers_Control_PanZoomBar_Zoombar'+this.map.id,centered,sz,imgLocation+"zoombar.png");} +this.zoombarDiv=div;this.divEvents=new OpenLayers.Events(this,div,null,true,{includeXY:true});this.divEvents.on({"mousedown":this.divClick,"mousemove":this.passEventToSlider,"dblclick":this.doubleClick,"click":this.doubleClick});this.div.appendChild(div);this.startTop=parseInt(div.style.top);this.div.appendChild(slider);this.map.events.register("zoomend",this,this.moveZoomBar);centered=centered.add(0,this.zoomStopHeight*this.map.getNumZoomLevels());return centered;},_removeZoomBar:function(){this.sliderEvents.un({"mousedown":this.zoomBarDown,"mousemove":this.zoomBarDrag,"mouseup":this.zoomBarUp,"dblclick":this.doubleClick,"click":this.doubleClick});this.sliderEvents.destroy();this.divEvents.un({"mousedown":this.divClick,"mousemove":this.passEventToSlider,"dblclick":this.doubleClick,"click":this.doubleClick});this.divEvents.destroy();this.div.removeChild(this.zoombarDiv);this.zoombarDiv=null;this.div.removeChild(this.slider);this.slider=null;this.map.events.unregister("zoomend",this,this.moveZoomBar);},passEventToSlider:function(evt){this.sliderEvents.handleBrowserEvent(evt);},divClick:function(evt){if(!OpenLayers.Event.isLeftClick(evt)){return;} +var y=evt.xy.y;var top=OpenLayers.Util.pagePosition(evt.object)[1];var levels=(y-top)/this.zoomStopHeight;if(!this.map.fractionalZoom){levels=Math.floor(levels);} +var zoom=(this.map.getNumZoomLevels()-1)-levels;zoom=Math.min(Math.max(zoom,0),this.map.getNumZoomLevels()-1);this.map.zoomTo(zoom);OpenLayers.Event.stop(evt);},zoomBarDown:function(evt){if(!OpenLayers.Event.isLeftClick(evt)){return;} +this.map.events.on({"mousemove":this.passEventToSlider,"mouseup":this.passEventToSlider,scope:this});this.mouseDragStart=evt.xy.clone();this.zoomStart=evt.xy.clone();this.div.style.cursor="move";this.zoombarDiv.offsets=null;OpenLayers.Event.stop(evt);},zoomBarDrag:function(evt){if(this.mouseDragStart!=null){var deltaY=this.mouseDragStart.y-evt.xy.y;var offsets=OpenLayers.Util.pagePosition(this.zoombarDiv);if((evt.clientY-offsets[1])>0&&(evt.clientY-offsets[1])0){pieces.push(',');} +pieces.push(this.writeNewline(),this.writeIndent(),json);}} +this.level-=1;pieces.push(this.writeNewline(),this.writeIndent(),']');return pieces.join('');},'string':function(string){var m={'\b':'\\b','\t':'\\t','\n':'\\n','\f':'\\f','\r':'\\r','"':'\\"','\\':'\\\\'};if(/["\\\x00-\x1f]/.test(string)){return'"'+string.replace(/([\x00-\x1f\\"])/g,function(a,b){var c=m[b];if(c){return c;} +c=b.charCodeAt();return'\\u00'+ +Math.floor(c/16).toString(16)+ +(c%16).toString(16);})+'"';} +return'"'+string+'"';},'number':function(number){return isFinite(number)?String(number):"null";},'boolean':function(bool){return String(bool);},'date':function(date){function format(number){return(number<10)?'0'+number:number;} +return'"'+date.getFullYear()+'-'+ +format(date.getMonth()+1)+'-'+ +format(date.getDate())+'T'+ +format(date.getHours())+':'+ +format(date.getMinutes())+':'+ +format(date.getSeconds())+'"';}},CLASS_NAME:"OpenLayers.Format.JSON"});OpenLayers.Format.WFST=function(options){options=OpenLayers.Util.applyDefaults(options,OpenLayers.Format.WFST.DEFAULTS);var cls=OpenLayers.Format.WFST["v"+options.version.replace(/\./g,"_")];if(!cls){throw"Unsupported WFST version: "+options.version;} +return new cls(options);};OpenLayers.Format.WFST.DEFAULTS={"version":"1.0.0"};OpenLayers.Format.WMSCapabilities=OpenLayers.Class(OpenLayers.Format.XML,{defaultVersion:"1.1.1",version:null,parser:null,initialize:function(options){OpenLayers.Format.prototype.initialize.apply(this,[options]);this.options=options;},read:function(data){if(typeof data=="string"){data=OpenLayers.Format.XML.prototype.read.apply(this,[data]);} +var root=data.documentElement;var version=this.version||root.getAttribute("version")||this.defaultVersion;if(!this.parser||this.parser.version!==version){var constr=OpenLayers.Format.WMSCapabilities["v"+version.replace(/\./g,"_")];if(!constr){throw"Can't find a WMS capabilities parser for version "+version;} +var parser=new constr(this.options);} +var capabilities=parser.read(data);capabilities.version=version;return capabilities;},CLASS_NAME:"OpenLayers.Format.WMSCapabilities"});OpenLayers.Format.XML=OpenLayers.Class(OpenLayers.Format,{namespaces:null,namespaceAlias:null,defaultPrefix:null,readers:{},writers:{},xmldom:null,initialize:function(options){if(window.ActiveXObject){this.xmldom=new ActiveXObject("Microsoft.XMLDOM");} +OpenLayers.Format.prototype.initialize.apply(this,[options]);this.namespaces=OpenLayers.Util.extend({},this.namespaces);this.namespaceAlias={};for(var alias in this.namespaces){this.namespaceAlias[this.namespaces[alias]]=alias;}},destroy:function(){this.xmldom=null;OpenLayers.Format.prototype.destroy.apply(this,arguments);},setNamespace:function(alias,uri){this.namespaces[alias]=uri;this.namespaceAlias[uri]=alias;},read:function(text){var index=text.indexOf('<');if(index>0){text=text.substring(index);} +var node=OpenLayers.Util.Try(OpenLayers.Function.bind((function(){var xmldom;if(window.ActiveXObject&&!this.xmldom){xmldom=new ActiveXObject("Microsoft.XMLDOM");}else{xmldom=this.xmldom;} +xmldom.loadXML(text);return xmldom;}),this),function(){return new DOMParser().parseFromString(text,'text/xml');},function(){var req=new XMLHttpRequest();req.open("GET","data:"+"text/xml"+";charset=utf-8,"+encodeURIComponent(text),false);if(req.overrideMimeType){req.overrideMimeType("text/xml");} +req.send(null);return req.responseXML;});if(this.keepData){this.data=node;} +return node;},write:function(node){var data;if(this.xmldom){data=node.xml;}else{var serializer=new XMLSerializer();if(node.nodeType==1){var doc=document.implementation.createDocument("","",null);if(doc.importNode){node=doc.importNode(node,true);} +doc.appendChild(node);data=serializer.serializeToString(doc);}else{data=serializer.serializeToString(node);}} +return data;},createElementNS:function(uri,name){var element;if(this.xmldom){if(typeof uri=="string"){element=this.xmldom.createNode(1,name,uri);}else{element=this.xmldom.createNode(1,name,"");}}else{element=document.createElementNS(uri,name);} +return element;},createTextNode:function(text){var node;if(this.xmldom){node=this.xmldom.createTextNode(text);}else{node=document.createTextNode(text);} +return node;},getElementsByTagNameNS:function(node,uri,name){var elements=[];if(node.getElementsByTagNameNS){elements=node.getElementsByTagNameNS(uri,name);}else{var allNodes=node.getElementsByTagName("*");var potentialNode,fullName;for(var i=0,len=allNodes.length;i0){prefix=name.substring(0,split);local=name.substring(split+1);}else{if(parent){prefix=this.namespaceAlias[parent.namespaceURI];}else{prefix=this.defaultPrefix;} +local=name;} +var child=this.writers[prefix][local].apply(this,[obj]);if(parent){parent.appendChild(child);} +return child;},getChildEl:function(node,name,uri){return node&&this.getThisOrNextEl(node.firstChild,name,uri);},getNextEl:function(node,name,uri){return node&&this.getThisOrNextEl(node.nextSibling,name,uri);},getThisOrNextEl:function(node,name,uri){outer:for(var sibling=node;sibling;sibling=sibling.nextSibling){switch(sibling.nodeType){case 1:if((!name||name===(sibling.localName||sibling.nodeName.split(":").pop()))&&(!uri||uri===sibling.namespaceURI)){break outer;} +sibling=null;break outer;case 3:if(/^\s*$/.test(sibling.nodeValue)){break;} +case 4:case 6:case 12:case 10:case 11:sibling=null;break outer;}} +return sibling||null;},lookupNamespaceURI:function(node,prefix){var uri=null;if(node){if(node.lookupNamespaceURI){uri=node.lookupNamespaceURI(prefix);}else{outer:switch(node.nodeType){case 1:if(node.namespaceURI!==null&&node.prefix===prefix){uri=node.namespaceURI;break outer;} +var len=node.attributes.length;if(len){var attr;for(var i=0;i=0;--i){this.controls[i].destroy();} +this.controls=null;} +if(this.layers!=null){for(var i=this.layers.length-1;i>=0;--i){this.layers[i].destroy(false);} +this.layers=null;} +if(this.viewPortDiv){this.div.removeChild(this.viewPortDiv);} +this.viewPortDiv=null;if(this.eventListeners){this.events.un(this.eventListeners);this.eventListeners=null;} +this.events.destroy();this.events=null;},setOptions:function(options){OpenLayers.Util.extend(this,options);},getTileSize:function(){return this.tileSize;},getBy:function(array,property,match){var test=(typeof match.test=="function");var found=OpenLayers.Array.filter(this[array],function(item){return item[property]==match||(test&&match.test(item[property]));});return found;},getLayersBy:function(property,match){return this.getBy("layers",property,match);},getLayersByName:function(match){return this.getLayersBy("name",match);},getLayersByClass:function(match){return this.getLayersBy("CLASS_NAME",match);},getControlsBy:function(property,match){return this.getBy("controls",property,match);},getControlsByClass:function(match){return this.getControlsBy("CLASS_NAME",match);},getLayer:function(id){var foundLayer=null;for(var i=0,len=this.layers.length;ithis.layers.length){idx=this.layers.length;} +if(base!=idx){this.layers.splice(base,1);this.layers.splice(idx,0,layer);for(var i=0,len=this.layers.length;i=0;--i){this.removePopup(this.popups[i]);}} +popup.map=this;this.popups.push(popup);var popupDiv=popup.draw();if(popupDiv){popupDiv.style.zIndex=this.Z_INDEX_BASE['Popup']+ +this.popups.length;this.layerContainerDiv.appendChild(popupDiv);}},removePopup:function(popup){OpenLayers.Util.removeItem(this.popups,popup);if(popup.div){try{this.layerContainerDiv.removeChild(popup.div);} +catch(e){}} +popup.map=null;},getSize:function(){var size=null;if(this.size!=null){size=this.size.clone();} +return size;},updateSize:function(){this.events.clearMouseCache();var newSize=this.getCurrentSize();var oldSize=this.getSize();if(oldSize==null){this.size=oldSize=newSize;} +if(!newSize.equals(oldSize)){this.size=newSize;for(var i=0,len=this.layers.length;ithis.restrictedExtent.getWidth()){lonlat=new OpenLayers.LonLat(maxCenter.lon,lonlat.lat);}else if(extent.leftthis.restrictedExtent.right){lonlat=lonlat.add(this.restrictedExtent.right- +extent.right,0);} +if(extent.getHeight()>this.restrictedExtent.getHeight()){lonlat=new OpenLayers.LonLat(lonlat.lon,maxCenter.lat);}else if(extent.bottomthis.restrictedExtent.top){lonlat=lonlat.add(0,this.restrictedExtent.top- +extent.top);}}} +var zoomChanged=forceZoomChange||((this.isValidZoomLevel(zoom))&&(zoom!=this.getZoom()));var centerChanged=(this.isValidLonLat(lonlat))&&(!lonlat.equals(this.center));if(zoomChanged||centerChanged||!dragging){if(!this.dragging&&!noEvent){this.events.triggerEvent("movestart");} +if(centerChanged){if((!zoomChanged)&&(this.center)){this.centerLayerContainer(lonlat);} +this.center=lonlat.clone();} +if((zoomChanged)||(this.layerContainerOrigin==null)){this.layerContainerOrigin=this.center.clone();this.layerContainerDiv.style.left="0px";this.layerContainerDiv.style.top="0px";} +if(zoomChanged){this.zoom=zoom;this.resolution=this.getResolutionForZoom(zoom);this.viewRequestID++;} +var bounds=this.getExtent();if(this.baseLayer.visibility){this.baseLayer.moveTo(bounds,zoomChanged,dragging);if(dragging){this.baseLayer.events.triggerEvent("move");}else{this.baseLayer.events.triggerEvent("moveend",{"zoomChanged":zoomChanged});}} +bounds=this.baseLayer.getExtent();for(var i=0,len=this.layers.length;i=0)&&(zoomLevel0){var separator=(url.indexOf('?')>-1)?'&':'?';url+=separator+paramString;}} +if(config.proxy&&(url.indexOf("http")==0)){url=config.proxy+encodeURIComponent(url);} +request.open(config.method,url,config.async,config.user,config.password);for(var header in config.headers){request.setRequestHeader(header,config.headers[header]);} +var complete=(config.scope)?OpenLayers.Function.bind(config.callback,config.scope):config.callback;var success;if(config.success){success=(config.scope)?OpenLayers.Function.bind(config.success,config.scope):config.success;} +var failure;if(config.failure){failure=(config.scope)?OpenLayers.Function.bind(config.failure,config.scope):config.failure;} +var events=this.events;request.onreadystatechange=function(){if(request.readyState==OpenLayers.Request.XMLHttpRequest.DONE){var proceed=events.triggerEvent("complete",{request:request,config:config,requestUrl:url});if(proceed!==false){complete(request);if(!request.status||(request.status>=200&&request.status<300)){events.triggerEvent("success",{request:request,config:config,requestUrl:url});if(success){success(request);}} +if(request.status&&(request.status<200||request.status>=300)){events.triggerEvent("failure",{request:request,config:config,requestUrl:url});if(failure){failure(request);}}}}};if(config.async===false){request.send(config.data);}else{window.setTimeout(function(){request.send(config.data);},0);} +return request;},GET:function(config){config=OpenLayers.Util.extend(config,{method:"GET"});return OpenLayers.Request.issue(config);},POST:function(config){config=OpenLayers.Util.extend(config,{method:"POST"});config.headers=config.headers?config.headers:{};if(!("CONTENT-TYPE"in OpenLayers.Util.upperCaseObject(config.headers))){config.headers["Content-Type"]="application/xml";} +return OpenLayers.Request.issue(config);},PUT:function(config){config=OpenLayers.Util.extend(config,{method:"PUT"});config.headers=config.headers?config.headers:{};if(!("CONTENT-TYPE"in OpenLayers.Util.upperCaseObject(config.headers))){config.headers["Content-Type"]="application/xml";} +return OpenLayers.Request.issue(config);},DELETE:function(config){config=OpenLayers.Util.extend(config,{method:"DELETE"});return OpenLayers.Request.issue(config);},HEAD:function(config){config=OpenLayers.Util.extend(config,{method:"HEAD"});return OpenLayers.Request.issue(config);},OPTIONS:function(config){config=OpenLayers.Util.extend(config,{method:"OPTIONS"});return OpenLayers.Request.issue(config);}};OpenLayers.Tile.Image=OpenLayers.Class(OpenLayers.Tile,{url:null,imgDiv:null,frame:null,layerAlphaHack:null,isBackBuffer:false,lastRatio:1,isFirstDraw:true,backBufferTile:null,initialize:function(layer,position,bounds,url,size){OpenLayers.Tile.prototype.initialize.apply(this,arguments);this.url=url;this.frame=document.createElement('div');this.frame.style.overflow='hidden';this.frame.style.position='absolute';this.layerAlphaHack=this.layer.alpha&&OpenLayers.Util.alphaHack();},destroy:function(){if(this.imgDiv!=null){if(this.layerAlphaHack){OpenLayers.Event.stopObservingElement(this.imgDiv.childNodes[0].id);} +OpenLayers.Event.stopObservingElement(this.imgDiv.id);if(this.imgDiv.parentNode==this.frame){this.frame.removeChild(this.imgDiv);this.imgDiv.map=null;} +this.imgDiv.urls=null;this.imgDiv.src=OpenLayers.Util.getImagesLocation()+"blank.gif";} +this.imgDiv=null;if((this.frame!=null)&&(this.frame.parentNode==this.layer.div)){this.layer.div.removeChild(this.frame);} +this.frame=null;if(this.backBufferTile){this.backBufferTile.destroy();this.backBufferTile=null;} +this.layer.events.unregister("loadend",this,this.resetBackBuffer);OpenLayers.Tile.prototype.destroy.apply(this,arguments);},clone:function(obj){if(obj==null){obj=new OpenLayers.Tile.Image(this.layer,this.position,this.bounds,this.url,this.size);} +obj=OpenLayers.Tile.prototype.clone.apply(this,[obj]);obj.imgDiv=null;return obj;},draw:function(){if(this.layer!=this.layer.map.baseLayer&&this.layer.reproject){this.bounds=this.getBoundsFromBaseLayer(this.position);} +var drawTile=OpenLayers.Tile.prototype.draw.apply(this,arguments);if(OpenLayers.Util.indexOf(this.layer.SUPPORTED_TRANSITIONS,this.layer.transitionEffect)!=-1){if(drawTile){if(!this.backBufferTile){this.backBufferTile=this.clone();this.backBufferTile.hide();this.backBufferTile.isBackBuffer=true;this.events.register('loadend',this,this.resetBackBuffer);this.layer.events.register("loadend",this,this.resetBackBuffer);} +this.startTransition();}else{if(this.backBufferTile){this.backBufferTile.clear();}}}else{if(drawTile&&this.isFirstDraw){this.events.register('loadend',this,this.showTile);this.isFirstDraw=false;}} +if(!drawTile){return false;} +if(this.isLoading){this.events.triggerEvent("reload");}else{this.isLoading=true;this.events.triggerEvent("loadstart");} +return this.renderTile();},resetBackBuffer:function(){this.showTile();if(this.backBufferTile&&(this.isFirstDraw||!this.layer.numLoadingTiles)){this.isFirstDraw=false;var maxExtent=this.layer.maxExtent;var withinMaxExtent=(maxExtent&&this.bounds.intersectsBounds(maxExtent,false));if(withinMaxExtent){this.backBufferTile.position=this.position;this.backBufferTile.bounds=this.bounds;this.backBufferTile.size=this.size;this.backBufferTile.imageSize=this.layer.imageSize||this.size;this.backBufferTile.imageOffset=this.layer.imageOffset;this.backBufferTile.resolution=this.layer.getResolution();this.backBufferTile.renderTile();} +this.backBufferTile.hide();}},renderTile:function(){if(this.imgDiv==null){this.initImgDiv();} +this.imgDiv.viewRequestID=this.layer.map.viewRequestID;if(this.layer.async){this.layer.getURLasync(this.bounds,this,"url",this.positionImage);}else{if(this.layer.url instanceof Array){this.imgDiv.urls=this.layer.url.slice();} +this.url=this.layer.getURL(this.bounds);this.positionImage();} +return true;},positionImage:function(){if(this.layer==null) +return;OpenLayers.Util.modifyDOMElement(this.frame,null,this.position,this.size);var imageSize=this.layer.getImageSize();if(this.layerAlphaHack){OpenLayers.Util.modifyAlphaImageDiv(this.imgDiv,null,null,imageSize,this.url);}else{OpenLayers.Util.modifyDOMElement(this.imgDiv,null,null,imageSize);this.imgDiv.src=this.url;}},clear:function(){if(this.imgDiv){this.hide();if(OpenLayers.Tile.Image.useBlankTile){this.imgDiv.src=OpenLayers.Util.getImagesLocation()+"blank.gif";}}},initImgDiv:function(){var offset=this.layer.imageOffset;var size=this.layer.getImageSize();if(this.layerAlphaHack){this.imgDiv=OpenLayers.Util.createAlphaImageDiv(null,offset,size,null,"relative",null,null,null,true);}else{this.imgDiv=OpenLayers.Util.createImage(null,offset,size,null,"relative",null,null,true);} +this.imgDiv.className='olTileImage';this.frame.style.zIndex=this.isBackBuffer?0:1;this.frame.appendChild(this.imgDiv);this.layer.div.appendChild(this.frame);if(this.layer.opacity!=null){OpenLayers.Util.modifyDOMElement(this.imgDiv,null,null,null,null,null,null,this.layer.opacity);} +this.imgDiv.map=this.layer.map;var onload=function(){if(this.isLoading){this.isLoading=false;this.events.triggerEvent("loadend");}};if(this.layerAlphaHack){OpenLayers.Event.observe(this.imgDiv.childNodes[0],'load',OpenLayers.Function.bind(onload,this));}else{OpenLayers.Event.observe(this.imgDiv,'load',OpenLayers.Function.bind(onload,this));} +var onerror=function(){if(this.imgDiv._attempts>OpenLayers.IMAGE_RELOAD_ATTEMPTS){onload.call(this);}};OpenLayers.Event.observe(this.imgDiv,"error",OpenLayers.Function.bind(onerror,this));},checkImgURL:function(){if(this.layer){var loaded=this.layerAlphaHack?this.imgDiv.firstChild.src:this.imgDiv.src;if(!OpenLayers.Util.isEquivalentUrl(loaded,this.url)){this.hide();}}},startTransition:function(){if(!this.backBufferTile||!this.backBufferTile.imgDiv){return;} +var ratio=1;if(this.backBufferTile.resolution){ratio=this.backBufferTile.resolution/this.layer.getResolution();} +if(ratio!=this.lastRatio){if(this.layer.transitionEffect=='resize'){var upperLeft=new OpenLayers.LonLat(this.backBufferTile.bounds.left,this.backBufferTile.bounds.top);var size=new OpenLayers.Size(this.backBufferTile.size.w*ratio,this.backBufferTile.size.h*ratio);var px=this.layer.map.getLayerPxFromLonLat(upperLeft);OpenLayers.Util.modifyDOMElement(this.backBufferTile.frame,null,px,size);var imageSize=this.backBufferTile.imageSize;imageSize=new OpenLayers.Size(imageSize.w*ratio,imageSize.h*ratio);var imageOffset=this.backBufferTile.imageOffset;if(imageOffset){imageOffset=new OpenLayers.Pixel(imageOffset.x*ratio,imageOffset.y*ratio);} +OpenLayers.Util.modifyDOMElement(this.backBufferTile.imgDiv,null,imageOffset,imageSize);this.backBufferTile.show();}}else{if(this.layer.singleTile){this.backBufferTile.show();}else{this.backBufferTile.hide();}} +this.lastRatio=ratio;},show:function(){this.frame.style.display='';if(OpenLayers.Util.indexOf(this.layer.SUPPORTED_TRANSITIONS,this.layer.transitionEffect)!=-1){if(navigator.userAgent.toLowerCase().indexOf("gecko")!=-1){this.frame.scrollLeft=this.frame.scrollLeft;}}},hide:function(){this.frame.style.display='none';},CLASS_NAME:"OpenLayers.Tile.Image"});OpenLayers.Tile.Image.useBlankTile=(OpenLayers.Util.getBrowserName()=="safari"||OpenLayers.Util.getBrowserName()=="opera");OpenLayers.Control.OverviewMap=OpenLayers.Class(OpenLayers.Control,{element:null,ovmap:null,size:new OpenLayers.Size(180,90),layers:null,minRectSize:15,minRectDisplayClass:"RectReplacement",minRatio:8,maxRatio:32,mapOptions:null,autoPan:false,handlers:null,resolutionFactor:1,initialize:function(options){this.layers=[];this.handlers={};OpenLayers.Control.prototype.initialize.apply(this,[options]);},destroy:function(){if(!this.mapDiv){return;} +this.handlers.click.destroy();this.mapDiv.removeChild(this.extentRectangle);this.extentRectangle=null;this.rectEvents.destroy();this.rectEvents=null;this.ovmap.destroy();this.ovmap=null;this.element.removeChild(this.mapDiv);this.mapDiv=null;this.div.removeChild(this.element);this.element=null;if(this.maximizeDiv){OpenLayers.Event.stopObservingElement(this.maximizeDiv);this.div.removeChild(this.maximizeDiv);this.maximizeDiv=null;} +if(this.minimizeDiv){OpenLayers.Event.stopObservingElement(this.minimizeDiv);this.div.removeChild(this.minimizeDiv);this.minimizeDiv=null;} +this.map.events.un({"moveend":this.update,"changebaselayer":this.baseLayerDraw,scope:this});OpenLayers.Control.prototype.destroy.apply(this,arguments);},draw:function(){OpenLayers.Control.prototype.draw.apply(this,arguments);if(!(this.layers.length>0)){if(this.map.baseLayer){var layer=this.map.baseLayer.clone();this.layers=[layer];}else{this.map.events.register("changebaselayer",this,this.baseLayerDraw);return this.div;}} +this.element=document.createElement('div');this.element.className=this.displayClass+'Element';this.element.style.display='none';this.mapDiv=document.createElement('div');this.mapDiv.style.width=this.size.w+'px';this.mapDiv.style.height=this.size.h+'px';this.mapDiv.style.position='relative';this.mapDiv.style.overflow='hidden';this.mapDiv.id=OpenLayers.Util.createUniqueID('overviewMap');this.extentRectangle=document.createElement('div');this.extentRectangle.style.position='absolute';this.extentRectangle.style.zIndex=1000;this.extentRectangle.className=this.displayClass+'ExtentRectangle';this.mapDiv.appendChild(this.extentRectangle);this.element.appendChild(this.mapDiv);this.div.appendChild(this.element);if(!this.outsideViewport){this.div.className+=" "+this.displayClass+'Container';var imgLocation=OpenLayers.Util.getImagesLocation();var img=imgLocation+'layer-switcher-maximize.png';this.maximizeDiv=OpenLayers.Util.createAlphaImageDiv(this.displayClass+'MaximizeButton',null,new OpenLayers.Size(18,18),img,'absolute');this.maximizeDiv.style.display='none';this.maximizeDiv.className=this.displayClass+'MaximizeButton';OpenLayers.Event.observe(this.maximizeDiv,'click',OpenLayers.Function.bindAsEventListener(this.maximizeControl,this));this.div.appendChild(this.maximizeDiv);var img=imgLocation+'layer-switcher-minimize.png';this.minimizeDiv=OpenLayers.Util.createAlphaImageDiv('OpenLayers_Control_minimizeDiv',null,new OpenLayers.Size(18,18),img,'absolute');this.minimizeDiv.style.display='none';this.minimizeDiv.className=this.displayClass+'MinimizeButton';OpenLayers.Event.observe(this.minimizeDiv,'click',OpenLayers.Function.bindAsEventListener(this.minimizeControl,this));this.div.appendChild(this.minimizeDiv);var eventsToStop=['dblclick','mousedown'];for(var i=0,len=eventsToStop.length;ithis.minRatio)&&(resRatio<=this.maxRatio)&&(this.ovmap.getExtent().containsBounds(testExtent)));},updateOverview:function(){var mapRes=this.map.getResolution();var targetRes=this.ovmap.getResolution();var resRatio=targetRes/mapRes;if(resRatio>this.maxRatio){targetRes=this.minRatio*mapRes;}else if(resRatio<=this.minRatio){targetRes=this.maxRatio*mapRes;} +var center;if(this.ovmap.getProjection()!=this.map.getProjection()){center=this.map.center.clone();center.transform(this.map.getProjectionObject(),this.ovmap.getProjectionObject());}else{center=this.map.center;} +this.ovmap.setCenter(center,this.ovmap.getZoomForResolution(targetRes*this.resolutionFactor));this.updateRectToMap();},createMap:function(){var options=OpenLayers.Util.extend({controls:[],maxResolution:'auto',fallThrough:false},this.mapOptions);this.ovmap=new OpenLayers.Map(this.mapDiv,options);OpenLayers.Event.stopObserving(window,'unload',this.ovmap.unloadDestroy);this.ovmap.addLayers(this.layers);this.ovmap.zoomToMaxExtent();this.wComp=parseInt(OpenLayers.Element.getStyle(this.extentRectangle,'border-left-width'))+ +parseInt(OpenLayers.Element.getStyle(this.extentRectangle,'border-right-width'));this.wComp=(this.wComp)?this.wComp:2;this.hComp=parseInt(OpenLayers.Element.getStyle(this.extentRectangle,'border-top-width'))+ +parseInt(OpenLayers.Element.getStyle(this.extentRectangle,'border-bottom-width'));this.hComp=(this.hComp)?this.hComp:2;this.handlers.drag=new OpenLayers.Handler.Drag(this,{move:this.rectDrag,done:this.updateMapToRect},{map:this.ovmap});this.handlers.click=new OpenLayers.Handler.Click(this,{"click":this.mapDivClick},{"single":true,"double":false,"stopSingle":true,"stopDouble":true,"pixelTolerance":1,map:this.ovmap});this.handlers.click.activate();this.rectEvents=new OpenLayers.Events(this,this.extentRectangle,null,true);this.rectEvents.register("mouseover",this,function(e){if(!this.handlers.drag.active&&!this.map.dragging){this.handlers.drag.activate();}});this.rectEvents.register("mouseout",this,function(e){if(!this.handlers.drag.dragging){this.handlers.drag.deactivate();}});if(this.ovmap.getProjection()!=this.map.getProjection()){var sourceUnits=this.map.getProjectionObject().getUnits()||this.map.units||this.map.baseLayer.units;var targetUnits=this.ovmap.getProjectionObject().getUnits()||this.ovmap.units||this.ovmap.baseLayer.units;this.resolutionFactor=sourceUnits&&targetUnits?OpenLayers.INCHES_PER_UNIT[sourceUnits]/OpenLayers.INCHES_PER_UNIT[targetUnits]:1;}},updateRectToMap:function(){var bounds;if(this.ovmap.getProjection()!=this.map.getProjection()){bounds=this.map.getExtent().transform(this.map.getProjectionObject(),this.ovmap.getProjectionObject());}else{bounds=this.map.getExtent();} +var pxBounds=this.getRectBoundsFromMapBounds(bounds);if(pxBounds){this.setRectPxBounds(pxBounds);}},updateMapToRect:function(){var lonLatBounds=this.getMapBoundsFromRectBounds(this.rectPxBounds);if(this.ovmap.getProjection()!=this.map.getProjection()){lonLatBounds=lonLatBounds.transform(this.ovmap.getProjectionObject(),this.map.getProjectionObject());} +this.map.panTo(lonLatBounds.getCenterLonLat());},setRectPxBounds:function(pxBounds){var top=Math.max(pxBounds.top,0);var left=Math.max(pxBounds.left,0);var bottom=Math.min(pxBounds.top+Math.abs(pxBounds.getHeight()),this.ovmap.size.h-this.hComp);var right=Math.min(pxBounds.left+pxBounds.getWidth(),this.ovmap.size.w-this.wComp);var width=Math.max(right-left,0);var height=Math.max(bottom-top,0);if(width0){this.read_cap_OnlineResource(obj,children[0]);}},read_cap_Service:function(capabilities,node){var service={};this.runChildNodes(service,node);capabilities.service=service;},read_cap_Layer:function(capability,node,parentLayer){var layer={formats:capability.request.getmap.formats||[],styles:[],queryable:(node.getAttribute("queryable")==="1"||node.getAttribute("queryable")==="true")};if(parentLayer){layer.styles=layer.styles.concat(parentLayer.styles);layer.llbbox=parentLayer.llbbox;layer.minScale=parentLayer.minScale;layer.maxScale=parentLayer.maxScale;} +var children=node.childNodes;var childNode,nodeName,processor;for(var i=0;i0){layer.prefix=layer.name.substring(0,index);} +capability.layers.push(layer);}},read_cap_ScaleHint:function(layer,node){var min=node.getAttribute("min");var max=node.getAttribute("max");var rad2=Math.pow(2,0.5);var ipm=OpenLayers.INCHES_PER_UNIT["m"];layer.maxScale=parseFloat(((rad2*min)*ipm*OpenLayers.DOTS_PER_INCH).toPrecision(13));layer.minScale=parseFloat(((rad2*max)*ipm*OpenLayers.DOTS_PER_INCH).toPrecision(13));},read_cap_Name:function(obj,node){var name=this.getChildValue(node);if(name){obj.name=name;}},read_cap_Title:function(obj,node){var title=this.getChildValue(node);if(title){obj.title=title;}},read_cap_Abstract:function(obj,node){var abst=this.getChildValue(node);if(abst){obj["abstract"]=abst;}},read_cap_LatLonBoundingBox:function(layer,node){layer.llbbox=[parseFloat(node.getAttribute("minx")),parseFloat(node.getAttribute("miny")),parseFloat(node.getAttribute("maxx")),parseFloat(node.getAttribute("maxy"))];},read_cap_Style:function(layer,node){var style={};this.runChildNodes(style,node);layer.styles.push(style);},read_cap_LegendURL:function(style,node){var legend={width:node.getAttribute('width'),height:node.getAttribute('height')};var links=node.getElementsByTagName("OnlineResource");if(links.length>0){this.read_cap_OnlineResource(legend,links[0]);} +style.legend=legend;},read_cap_OnlineResource:function(obj,node){obj.href=this.getAttributeNS(node,"http://www.w3.org/1999/xlink","href");},CLASS_NAME:"OpenLayers.Format.WMSCapabilities.v1_1"});OpenLayers.Format.WMSDescribeLayer=OpenLayers.Class(OpenLayers.Format.XML,{defaultVersion:"1.1.1",version:null,initialize:function(options){OpenLayers.Format.XML.prototype.initialize.apply(this,[options]);this.options=options;},read:function(data){if(typeof data=="string"){data=OpenLayers.Format.XML.prototype.read.apply(this,[data]);} +var root=data.documentElement;var version=this.version;if(!version){version=root.getAttribute("version");if(!version){version=this.defaultVersion;}} +if(version=="1.1.1"||version=="1.1.0"){version="1.1";} +var constructor=OpenLayers.Format.WMSDescribeLayer["v"+version.replace(/\./g,"_")];if(!constructor){throw"Can't find a WMS DescribeLayer parser for version "+ +version;} +var parser=new constructor(this.options);var describelayer=parser.read(data);describelayer.version=version;return describelayer;},CLASS_NAME:"OpenLayers.Format.WMSDescribeLayer"});OpenLayers.Format.WMSGetFeatureInfo=OpenLayers.Class(OpenLayers.Format.XML,{layerIdentifier:'_layer',featureIdentifier:'_feature',regExes:{trimSpace:(/^\s*|\s*$/g),removeSpace:(/\s*/g),splitSpace:(/\s+/),trimComma:(/\s*,\s*/g)},gmlFormat:null,initialize:function(options){OpenLayers.Format.XML.prototype.initialize.apply(this,arguments);OpenLayers.Util.extend(this,options);this.options=options;},read:function(data){var result;if(typeof data=="string"){data=OpenLayers.Format.XML.prototype.read.apply(this,[data]);} +var root=data.documentElement;if(root){var scope=this;var read=this["read_"+root.nodeName];if(read){result=read.call(this,root);}else{result=new OpenLayers.Format.GML((this.options?this.options:{})).read(data);}}else{result=data;} +return result;},read_msGMLOutput:function(data){var response=[];var layerNodes=this.getSiblingNodesByTagCriteria(data,this.layerIdentifier);if(layerNodes){for(var i=0,len=layerNodes.length;i0&&tagName.indexOf(criteria)>-1){nodes.push(child);}else{matchNodes=this.getSiblingNodesByTagCriteria(child,criteria);if(matchNodes.length>0){(nodes.length==0)?nodes=matchNodes:nodes.push(matchNodes);}}}} +return nodes;},parseAttributes:function(node){var attributes={};if(node.nodeType==1){var children=node.childNodes;n=children.length;for(var i=0;ithis.pixelTolerance){passes=false;}} +return passes;},clearTimer:function(){if(this.timerId!=null){window.clearTimeout(this.timerId);this.timerId=null;} +if(this.rightclickTimerId!=null){window.clearTimeout(this.rightclickTimerId);this.rightclickTimerId=null;}},delayedCall:function(evt){this.timerId=null;if(evt){this.callback('click',[evt]);}},deactivate:function(){var deactivated=false;if(OpenLayers.Handler.prototype.deactivate.apply(this,arguments)){this.clearTimer();this.down=null;deactivated=true;} +return deactivated;},CLASS_NAME:"OpenLayers.Handler.Click"});OpenLayers.Handler.Drag=OpenLayers.Class(OpenLayers.Handler,{started:false,stopDown:true,dragging:false,last:null,start:null,oldOnselectstart:null,interval:0,timeoutId:null,initialize:function(control,callbacks,options){OpenLayers.Handler.prototype.initialize.apply(this,arguments);},down:function(evt){},move:function(evt){},up:function(evt){},out:function(evt){},mousedown:function(evt){var propagate=true;this.dragging=false;if(this.checkModifiers(evt)&&OpenLayers.Event.isLeftClick(evt)){this.started=true;this.start=evt.xy;this.last=evt.xy;OpenLayers.Element.addClass(this.map.viewPortDiv,"olDragDown");this.down(evt);this.callback("down",[evt.xy]);OpenLayers.Event.stop(evt);if(!this.oldOnselectstart){this.oldOnselectstart=(document.onselectstart)?document.onselectstart:function(){return true;};document.onselectstart=function(){return false;};} +propagate=!this.stopDown;}else{this.started=false;this.start=null;this.last=null;} +return propagate;},mousemove:function(evt){if(this.started&&!this.timeoutId&&(evt.xy.x!=this.last.x||evt.xy.y!=this.last.y)){if(this.interval>0){this.timeoutId=setTimeout(OpenLayers.Function.bind(this.removeTimeout,this),this.interval);} +this.dragging=true;this.move(evt);this.callback("move",[evt.xy]);if(!this.oldOnselectstart){this.oldOnselectstart=document.onselectstart;document.onselectstart=function(){return false;};} +this.last=this.evt.xy;} +return true;},removeTimeout:function(){this.timeoutId=null;},mouseup:function(evt){if(this.started){var dragged=(this.start!=this.last);this.started=false;this.dragging=false;OpenLayers.Element.removeClass(this.map.viewPortDiv,"olDragDown");this.up(evt);this.callback("up",[evt.xy]);if(dragged){this.callback("done",[evt.xy]);} +document.onselectstart=this.oldOnselectstart;} +return true;},mouseout:function(evt){if(this.started&&OpenLayers.Util.mouseLeft(evt,this.map.div)){var dragged=(this.start!=this.last);this.started=false;this.dragging=false;OpenLayers.Element.removeClass(this.map.viewPortDiv,"olDragDown");this.out(evt);this.callback("out",[]);if(dragged){this.callback("done",[evt.xy]);} +if(document.onselectstart){document.onselectstart=this.oldOnselectstart;}} +return true;},click:function(evt){return(this.start==this.last);},activate:function(){var activated=false;if(OpenLayers.Handler.prototype.activate.apply(this,arguments)){this.dragging=false;activated=true;} +return activated;},deactivate:function(){var deactivated=false;if(OpenLayers.Handler.prototype.deactivate.apply(this,arguments)){this.started=false;this.dragging=false;this.start=null;this.last=null;deactivated=true;OpenLayers.Element.removeClass(this.map.viewPortDiv,"olDragDown");} +return deactivated;},CLASS_NAME:"OpenLayers.Handler.Drag"});OpenLayers.Handler.Feature=OpenLayers.Class(OpenLayers.Handler,{EVENTMAP:{'click':{'in':'click','out':'clickout'},'mousemove':{'in':'over','out':'out'},'dblclick':{'in':'dblclick','out':null},'mousedown':{'in':null,'out':null},'mouseup':{'in':null,'out':null}},feature:null,lastFeature:null,down:null,up:null,clickTolerance:4,geometryTypes:null,stopClick:true,stopDown:true,stopUp:false,initialize:function(control,layer,callbacks,options){OpenLayers.Handler.prototype.initialize.apply(this,[control,callbacks,options]);this.layer=layer;},mousedown:function(evt){this.down=evt.xy;return this.handle(evt)?!this.stopDown:true;},mouseup:function(evt){this.up=evt.xy;return this.handle(evt)?!this.stopUp:true;},click:function(evt){return this.handle(evt)?!this.stopClick:true;},mousemove:function(evt){if(!this.callbacks['over']&&!this.callbacks['out']){return true;} +this.handle(evt);return true;},dblclick:function(evt){return!this.handle(evt);},geometryTypeMatches:function(feature){return this.geometryTypes==null||OpenLayers.Util.indexOf(this.geometryTypes,feature.geometry.CLASS_NAME)>-1;},handle:function(evt){if(this.feature&&!this.feature.layer){this.feature=null;} +var type=evt.type;var handled=false;var previouslyIn=!!(this.feature);var click=(type=="click"||type=="dblclick");this.feature=this.layer.getFeatureFromEvent(evt);if(this.feature&&!this.feature.layer){this.feature=null;} +if(this.lastFeature&&!this.lastFeature.layer){this.lastFeature=null;} +if(this.feature){var inNew=(this.feature!=this.lastFeature);if(this.geometryTypeMatches(this.feature)){if(previouslyIn&&inNew){if(this.lastFeature){this.triggerCallback(type,'out',[this.lastFeature]);} +this.triggerCallback(type,'in',[this.feature]);}else if(!previouslyIn||click){this.triggerCallback(type,'in',[this.feature]);} +this.lastFeature=this.feature;handled=true;}else{if(this.lastFeature&&(previouslyIn&&inNew||click)){this.triggerCallback(type,'out',[this.lastFeature]);} +this.feature=null;}}else{if(this.lastFeature&&(previouslyIn||click)){this.triggerCallback(type,'out',[this.lastFeature]);}} +return handled;},triggerCallback:function(type,mode,args){var key=this.EVENTMAP[type][mode];if(key){if(type=='click'&&this.up&&this.down){var dpx=Math.sqrt(Math.pow(this.up.x-this.down.x,2)+ +Math.pow(this.up.y-this.down.y,2));if(dpx<=this.clickTolerance){this.callback(key,args);}}else{this.callback(key,args);}}},activate:function(){var activated=false;if(OpenLayers.Handler.prototype.activate.apply(this,arguments)){this.moveLayerToTop();this.map.events.on({"removelayer":this.handleMapEvents,"changelayer":this.handleMapEvents,scope:this});activated=true;} +return activated;},deactivate:function(){var deactivated=false;if(OpenLayers.Handler.prototype.deactivate.apply(this,arguments)){this.moveLayerBack();this.feature=null;this.lastFeature=null;this.down=null;this.up=null;this.map.events.un({"removelayer":this.handleMapEvents,"changelayer":this.handleMapEvents,scope:this});deactivated=true;} +return deactivated;},handleMapEvents:function(evt){if(!evt.property||evt.property=="order"){this.moveLayerToTop();}},moveLayerToTop:function(){var index=Math.max(this.map.Z_INDEX_BASE['Feature']-1,this.layer.getZIndex())+1;this.layer.setZIndex(index);},moveLayerBack:function(){var index=this.layer.getZIndex()-1;if(index>=this.map.Z_INDEX_BASE['Feature']){this.layer.setZIndex(index);}else{this.map.setLayerZIndex(this.layer,this.map.getLayerIndex(this.layer));}},CLASS_NAME:"OpenLayers.Handler.Feature"});OpenLayers.Handler.Hover=OpenLayers.Class(OpenLayers.Handler,{delay:500,pixelTolerance:null,stopMove:false,px:null,timerId:null,initialize:function(control,callbacks,options){OpenLayers.Handler.prototype.initialize.apply(this,arguments);},mousemove:function(evt){if(this.passesTolerance(evt.xy)){this.clearTimer();this.callback('move',[evt]);this.px=evt.xy;evt=OpenLayers.Util.extend({},evt);this.timerId=window.setTimeout(OpenLayers.Function.bind(this.delayedCall,this,evt),this.delay);} +return!this.stopMove;},mouseout:function(evt){if(OpenLayers.Util.mouseLeft(evt,this.map.div)){this.clearTimer();this.callback('move',[evt]);} +return true;},passesTolerance:function(px){var passes=true;if(this.pixelTolerance&&this.px){var dpx=Math.sqrt(Math.pow(this.px.x-px.x,2)+ +Math.pow(this.px.y-px.y,2));if(dpx=this.minResolution)&&(resolution<=this.maxResolution));}} +return inRange;},setIsBaseLayer:function(isBaseLayer){if(isBaseLayer!=this.isBaseLayer){this.isBaseLayer=isBaseLayer;if(this.map!=null){this.map.events.triggerEvent("changebaselayer",{layer:this});}}},initResolutions:function(){var props=new Array('projection','units','scales','resolutions','maxScale','minScale','maxResolution','minResolution','minExtent','maxExtent','numZoomLevels','maxZoomLevel');var notScaleProps=['projection','units'];var useInRange=false;var confProps={};for(var i=0,len=props.length;i1){base=Math.pow((confProps.maxResolution/confProps.minResolution),(1/(confProps.numZoomLevels-1)));} +for(var i=0;i=resolution){highRes=res;lowZoom=i;} +if(res<=resolution){lowRes=res;highZoom=i;break;}} +var dRes=highRes-lowRes;if(dRes>0){zoom=lowZoom+((highRes-resolution)/dRes);}else{zoom=lowZoom;}}else{var diff;var minDiff=Number.POSITIVE_INFINITY;for(var i=0,len=this.resolutions.length;iminDiff){break;} +minDiff=diff;}else{if(this.resolutions[i]cXMLHttpRequest.UNSENT) +this._aborted=true;this._object.abort();fCleanTransport(this);};cXMLHttpRequest.prototype.getAllResponseHeaders=function(){return this._object.getAllResponseHeaders();};cXMLHttpRequest.prototype.getResponseHeader=function(sName){return this._object.getResponseHeader(sName);};cXMLHttpRequest.prototype.setRequestHeader=function(sName,sValue){if(!this._headers) +this._headers={};this._headers[sName]=sValue;return this._object.setRequestHeader(sName,sValue);};cXMLHttpRequest.prototype.toString=function(){return'['+"object"+' '+"XMLHttpRequest"+']';};cXMLHttpRequest.toString=function(){return'['+"XMLHttpRequest"+']';};function fReadyStateChange(oRequest){if(oRequest.onreadystatechange) +oRequest.onreadystatechange.apply(oRequest);if(cXMLHttpRequest.onreadystatechange) +cXMLHttpRequest.onreadystatechange.apply(oRequest);};function fGetDocument(oRequest){var oDocument=oRequest.responseXML;if(bIE&&oDocument&&!oDocument.documentElement&&oRequest.getResponseHeader("Content-Type").match(/[^\/]+\/[^\+]+\+xml/)){oDocument=new ActiveXObject('Microsoft.XMLDOM');oDocument.loadXML(oRequest.responseText);} +if(oDocument) +if((bIE&&oDocument.parseError!=0)||(oDocument.documentElement&&oDocument.documentElement.tagName=="parsererror")) +return null;return oDocument;};function fSynchronizeValues(oRequest){try{oRequest.responseText=oRequest._object.responseText;}catch(e){} +try{oRequest.responseXML=fGetDocument(oRequest._object);}catch(e){} +try{oRequest.status=oRequest._object.status;}catch(e){} +try{oRequest.statusText=oRequest._object.statusText;}catch(e){}};function fCleanTransport(oRequest){oRequest._object.onreadystatechange=new window.Function;delete oRequest._headers;};if(!window.Function.prototype.apply){window.Function.prototype.apply=function(oRequest,oArguments){if(!oArguments) +oArguments=[];oRequest.__func=this;oRequest.__func(oArguments[0],oArguments[1],oArguments[2],oArguments[3],oArguments[4]);delete oRequest.__func;};};OpenLayers.Request.XMLHttpRequest=cXMLHttpRequest;})();OpenLayers.ProxyHost="";OpenLayers.nullHandler=function(request){OpenLayers.Console.userError(OpenLayers.i18n("unhandledRequest",{'statusText':request.statusText}));};OpenLayers.loadURL=function(uri,params,caller,onComplete,onFailure){if(typeof params=='string'){params=OpenLayers.Util.getParameters(params);} +var success=(onComplete)?onComplete:OpenLayers.nullHandler;var failure=(onFailure)?onFailure:OpenLayers.nullHandler;return OpenLayers.Request.GET({url:uri,params:params,success:success,failure:failure,scope:caller});};OpenLayers.parseXMLString=function(text){var index=text.indexOf('<');if(index>0){text=text.substring(index);} +var ajaxResponse=OpenLayers.Util.Try(function(){var xmldom=new ActiveXObject('Microsoft.XMLDOM');xmldom.loadXML(text);return xmldom;},function(){return new DOMParser().parseFromString(text,'text/xml');},function(){var req=new XMLHttpRequest();req.open("GET","data:"+"text/xml"+";charset=utf-8,"+encodeURIComponent(text),false);if(req.overrideMimeType){req.overrideMimeType("text/xml");} +req.send(null);return req.responseXML;});return ajaxResponse;};OpenLayers.Ajax={emptyFunction:function(){},getTransport:function(){return OpenLayers.Util.Try(function(){return new XMLHttpRequest();},function(){return new ActiveXObject('Msxml2.XMLHTTP');},function(){return new ActiveXObject('Microsoft.XMLHTTP');})||false;},activeRequestCount:0};OpenLayers.Ajax.Responders={responders:[],register:function(responderToAdd){for(var i=0;i-1)?'&':'?')+params;}else if(/Konqueror|Safari|KHTML/.test(navigator.userAgent)){params+='&_=';}} +try{var response=new OpenLayers.Ajax.Response(this);if(this.options.onCreate){this.options.onCreate(response);} +OpenLayers.Ajax.Responders.dispatch('onCreate',this,response);this.transport.open(this.method.toUpperCase(),this.url,this.options.asynchronous);if(this.options.asynchronous){window.setTimeout(OpenLayers.Function.bind(this.respondToReadyState,this,1),10);} +this.transport.onreadystatechange=OpenLayers.Function.bind(this.onStateChange,this);this.setRequestHeaders();this.body=this.method=='post'?(this.options.postBody||params):null;this.transport.send(this.body);if(!this.options.asynchronous&&this.transport.overrideMimeType){this.onStateChange();}}catch(e){this.dispatchException(e);}},onStateChange:function(){var readyState=this.transport.readyState;if(readyState>1&&!((readyState==4)&&this._complete)){this.respondToReadyState(this.transport.readyState);}},setRequestHeaders:function(){var headers={'X-Requested-With':'XMLHttpRequest','Accept':'text/javascript, text/html, application/xml, text/xml, */*','OpenLayers':true};if(this.method=='post'){headers['Content-type']=this.options.contentType+ +(this.options.encoding?'; charset='+this.options.encoding:'');if(this.transport.overrideMimeType&&(navigator.userAgent.match(/Gecko\/(\d{4})/)||[0,2005])[1]<2005){headers['Connection']='close';}} +if(typeof this.options.requestHeaders=='object'){var extras=this.options.requestHeaders;if(typeof extras.push=='function'){for(var i=0,length=extras.length;i=200&&status<300);},getStatus:function(){try{return this.transport.status||0;}catch(e){return 0;}},respondToReadyState:function(readyState){var state=OpenLayers.Ajax.Request.Events[readyState];var response=new OpenLayers.Ajax.Response(this);if(state=='Complete'){try{this._complete=true;(this.options['on'+response.status]||this.options['on'+(this.success()?'Success':'Failure')]||OpenLayers.Ajax.emptyFunction)(response);}catch(e){this.dispatchException(e);} +var contentType=response.getHeader('Content-type');} +try{(this.options['on'+state]||OpenLayers.Ajax.emptyFunction)(response);OpenLayers.Ajax.Responders.dispatch('on'+state,this,response);}catch(e){this.dispatchException(e);} +if(state=='Complete'){this.transport.onreadystatechange=OpenLayers.Ajax.emptyFunction;}},getHeader:function(name){try{return this.transport.getResponseHeader(name);}catch(e){return null;}},dispatchException:function(exception){var handler=this.options.onException;if(handler){handler(this,exception);OpenLayers.Ajax.Responders.dispatch('onException',this,exception);}else{var listener=false;var responders=OpenLayers.Ajax.Responders.responders;for(var i=0;i2&&!(!!(window.attachEvent&&!window.opera)))||readyState==4){this.status=this.getStatus();this.statusText=this.getStatusText();this.responseText=transport.responseText==null?'':String(transport.responseText);} +if(readyState==4){var xml=transport.responseXML;this.responseXML=xml===undefined?null:xml;}},getStatus:OpenLayers.Ajax.Request.prototype.getStatus,getStatusText:function(){try{return this.transport.statusText||'';}catch(e){return'';}},getHeader:OpenLayers.Ajax.Request.prototype.getHeader,getResponseHeader:function(name){return this.transport.getResponseHeader(name);}});OpenLayers.Ajax.getElementsByTagNameNS=function(parentnode,nsuri,nsprefix,tagname){var elem=null;if(parentnode.getElementsByTagNameNS){elem=parentnode.getElementsByTagNameNS(nsuri,tagname);}else{elem=parentnode.getElementsByTagName(nsprefix+':'+tagname);} +return elem;};OpenLayers.Ajax.serializeXMLToString=function(xmldom){var serializer=new XMLSerializer();var data=serializer.serializeToString(xmldom);return data;};OpenLayers.Control.DragFeature=OpenLayers.Class(OpenLayers.Control,{geometryTypes:null,onStart:function(feature,pixel){},onDrag:function(feature,pixel){},onComplete:function(feature,pixel){},layer:null,feature:null,dragCallbacks:{},featureCallbacks:{},lastPixel:null,initialize:function(layer,options){OpenLayers.Control.prototype.initialize.apply(this,[options]);this.layer=layer;this.handlers={drag:new OpenLayers.Handler.Drag(this,OpenLayers.Util.extend({down:this.downFeature,move:this.moveFeature,up:this.upFeature,out:this.cancel,done:this.doneDragging},this.dragCallbacks)),feature:new OpenLayers.Handler.Feature(this,this.layer,OpenLayers.Util.extend({over:this.overFeature,out:this.outFeature},this.featureCallbacks),{geometryTypes:this.geometryTypes})};},destroy:function(){this.layer=null;OpenLayers.Control.prototype.destroy.apply(this,[]);},activate:function(){return(this.handlers.feature.activate()&&OpenLayers.Control.prototype.activate.apply(this,arguments));},deactivate:function(){this.handlers.drag.deactivate();this.handlers.feature.deactivate();this.feature=null;this.dragging=false;this.lastPixel=null;OpenLayers.Element.removeClass(this.map.viewPortDiv,this.displayClass+"Over");return OpenLayers.Control.prototype.deactivate.apply(this,arguments);},overFeature:function(feature){if(!this.handlers.drag.dragging){this.feature=feature;this.handlers.drag.activate();this.over=true;OpenLayers.Element.addClass(this.map.viewPortDiv,this.displayClass+"Over");}else{if(this.feature.id==feature.id){this.over=true;}else{this.over=false;}}},downFeature:function(pixel){this.lastPixel=pixel;this.onStart(this.feature,pixel);},moveFeature:function(pixel){var res=this.map.getResolution();this.feature.geometry.move(res*(pixel.x-this.lastPixel.x),res*(this.lastPixel.y-pixel.y));this.layer.drawFeature(this.feature);this.lastPixel=pixel;this.onDrag(this.feature,pixel);},upFeature:function(pixel){if(!this.over){this.handlers.drag.deactivate();}},doneDragging:function(pixel){this.onComplete(this.feature,pixel);},outFeature:function(feature){if(!this.handlers.drag.dragging){this.over=false;this.handlers.drag.deactivate();OpenLayers.Element.removeClass(this.map.viewPortDiv,this.displayClass+"Over");this.feature=null;}else{if(this.feature.id==feature.id){this.over=false;}}},cancel:function(){this.handlers.drag.deactivate();this.over=false;},setMap:function(map){this.handlers.drag.setMap(map);this.handlers.feature.setMap(map);OpenLayers.Control.prototype.setMap.apply(this,arguments);},CLASS_NAME:"OpenLayers.Control.DragFeature"});OpenLayers.Control.DragPan=OpenLayers.Class(OpenLayers.Control,{type:OpenLayers.Control.TYPE_TOOL,panned:false,interval:25,draw:function(){this.handler=new OpenLayers.Handler.Drag(this,{"move":this.panMap,"done":this.panMapDone},{interval:this.interval});},panMap:function(xy){this.panned=true;this.map.pan(this.handler.last.x-xy.x,this.handler.last.y-xy.y,{dragging:this.handler.dragging,animate:false});},panMapDone:function(xy){if(this.panned){this.panMap(xy);this.panned=false;}},CLASS_NAME:"OpenLayers.Control.DragPan"});OpenLayers.Control.KeyboardDefaults=OpenLayers.Class(OpenLayers.Control,{slideFactor:75,initialize:function(){OpenLayers.Control.prototype.initialize.apply(this,arguments);},destroy:function(){if(this.handler){this.handler.destroy();} +this.handler=null;OpenLayers.Control.prototype.destroy.apply(this,arguments);},draw:function(){this.handler=new OpenLayers.Handler.Keyboard(this,{"keydown":this.defaultKeyPress});this.activate();},defaultKeyPress:function(evt){switch(evt.keyCode){case OpenLayers.Event.KEY_LEFT:this.map.pan(-this.slideFactor,0);break;case OpenLayers.Event.KEY_RIGHT:this.map.pan(this.slideFactor,0);break;case OpenLayers.Event.KEY_UP:this.map.pan(0,-this.slideFactor);break;case OpenLayers.Event.KEY_DOWN:this.map.pan(0,this.slideFactor);break;case 33:var size=this.map.getSize();this.map.pan(0,-0.75*size.h);break;case 34:var size=this.map.getSize();this.map.pan(0,0.75*size.h);break;case 35:var size=this.map.getSize();this.map.pan(0.75*size.w,0);break;case 36:var size=this.map.getSize();this.map.pan(-0.75*size.w,0);break;case 43:case 61:case 187:case 107:this.map.zoomIn();break;case 45:case 109:case 189:case 95:this.map.zoomOut();break;}},CLASS_NAME:"OpenLayers.Control.KeyboardDefaults"});OpenLayers.Control.WMSGetFeatureInfo=OpenLayers.Class(OpenLayers.Control,{hover:false,maxFeatures:10,layers:null,queryVisible:false,url:null,layerUrls:null,infoFormat:'text/html',vendorParams:{},format:null,formatOptions:null,handlerOptions:null,handler:null,hoverRequest:null,EVENT_TYPES:["getfeatureinfo"],initialize:function(options){this.EVENT_TYPES=OpenLayers.Control.WMSGetFeatureInfo.prototype.EVENT_TYPES.concat(OpenLayers.Control.prototype.EVENT_TYPES);options=options||{};options.handlerOptions=options.handlerOptions||{};OpenLayers.Control.prototype.initialize.apply(this,[options]);if(!this.format){this.format=new OpenLayers.Format.WMSGetFeatureInfo(options.formatOptions);} +if(this.hover){this.handler=new OpenLayers.Handler.Hover(this,{'move':this.cancelHover,'pause':this.getInfoForHover},OpenLayers.Util.extend(this.handlerOptions.hover||{},{'delay':250}));}else{this.handler=new OpenLayers.Handler.Click(this,{click:this.getInfoForClick},this.handlerOptions.click||{});}},activate:function(){if(!this.active){this.handler.activate();} +return OpenLayers.Control.prototype.activate.apply(this,arguments);},deactivate:function(){return OpenLayers.Control.prototype.deactivate.apply(this,arguments);},getInfoForClick:function(evt){OpenLayers.Element.addClass(this.map.viewPortDiv,"olCursorWait");this.request(evt.xy,{});},getInfoForHover:function(evt){this.request(evt.xy,{hover:true});},cancelHover:function(){if(this.hoverRequest){this.hoverRequest.abort();this.hoverRequest=null;}},findLayers:function(){var layers=[];var candidates=this.layers||this.map.layers;var layer,url;for(var i=0,len=candidates.length;i0){for(var i=0,len=layers.length;i0){this.read_wmc_OnlineResource(metadataURL,links[0]);} +layerInfo.options.metadataURL=metadataURL.href;},read_wmc_Abstract:function(obj,node){var abst=this.getChildValue(node);if(abst){obj["abstract"]=abst;}},read_wmc_LatLonBoundingBox:function(layer,node){layer.llbbox=[parseFloat(node.getAttribute("minx")),parseFloat(node.getAttribute("miny")),parseFloat(node.getAttribute("maxx")),parseFloat(node.getAttribute("maxy"))];},read_wmc_LegendURL:function(style,node){var legend={width:node.getAttribute('width'),height:node.getAttribute('height')};var links=node.getElementsByTagName("OnlineResource");if(links.length>0){this.read_wmc_OnlineResource(legend,links[0]);} +style.legend=legend;},write:function(context,options){var root=this.createElementDefaultNS("ViewContext");this.setAttributes(root,{version:this.VERSION,id:(options&&typeof options.id=="string")?options.id:OpenLayers.Util.createUniqueID("OpenLayers_Context_")});this.setAttributeNS(root,this.namespaces.xsi,"xsi:schemaLocation",this.schemaLocation);root.appendChild(this.write_wmc_General(context));root.appendChild(this.write_wmc_LayerList(context));return OpenLayers.Format.XML.prototype.write.apply(this,[root]);},createElementDefaultNS:function(name,childValue,attributes){var node=this.createElementNS(this.namespaces[this.defaultPrefix],name);if(childValue){node.appendChild(this.createTextNode(childValue));} +if(attributes){this.setAttributes(node,attributes);} +return node;},setAttributes:function(node,obj){var value;for(var name in obj){value=obj[name].toString();if(value.match(/[A-Z]/)){this.setAttributeNS(node,null,name,value);}else{node.setAttribute(name,value);}}},write_wmc_General:function(context){var node=this.createElementDefaultNS("General");if(context.size){node.appendChild(this.createElementDefaultNS("Window",null,{width:context.size.w,height:context.size.h}));} +var bounds=context.bounds;node.appendChild(this.createElementDefaultNS("BoundingBox",null,{minx:bounds.left.toPrecision(10),miny:bounds.bottom.toPrecision(10),maxx:bounds.right.toPrecision(10),maxy:bounds.top.toPrecision(10),SRS:context.projection}));node.appendChild(this.createElementDefaultNS("Title",context.title));node.appendChild(this.write_ol_MapExtension(context));return node;},write_ol_MapExtension:function(context){var node=this.createElementDefaultNS("Extension");var bounds=context.maxExtent;if(bounds){var maxExtent=this.createElementNS(this.namespaces.ol,"ol:maxExtent");this.setAttributes(maxExtent,{minx:bounds.left.toPrecision(10),miny:bounds.bottom.toPrecision(10),maxx:bounds.right.toPrecision(10),maxy:bounds.top.toPrecision(10)});node.appendChild(maxExtent);} +return node;},write_wmc_LayerList:function(context){var list=this.createElementDefaultNS("LayerList");var layer;for(var i=0,len=context.layers.length;i0){typeName=query[0].getAttribute('typeName');if(!typeName){typeName=query[0].getAttribute('typename');}} +describelayer.push({owsType:owsType,owsURL:owsURL,typeName:typeName});}} +return describelayer;},CLASS_NAME:"OpenLayers.Format.WMSDescribeLayer.v1_1"});OpenLayers.Handler.Box=OpenLayers.Class(OpenLayers.Handler,{dragHandler:null,boxDivClassName:'olHandlerBoxZoomBox',boxCharacteristics:null,initialize:function(control,callbacks,options){OpenLayers.Handler.prototype.initialize.apply(this,arguments);var callbacks={"down":this.startBox,"move":this.moveBox,"out":this.removeBox,"up":this.endBox};this.dragHandler=new OpenLayers.Handler.Drag(this,callbacks,{keyMask:this.keyMask});},setMap:function(map){OpenLayers.Handler.prototype.setMap.apply(this,arguments);if(this.dragHandler){this.dragHandler.setMap(map);}},startBox:function(xy){this.zoomBox=OpenLayers.Util.createDiv('zoomBox',this.dragHandler.start);this.zoomBox.className=this.boxDivClassName;this.zoomBox.style.zIndex=this.map.Z_INDEX_BASE["Popup"]-1;this.map.viewPortDiv.appendChild(this.zoomBox);OpenLayers.Element.addClass(this.map.viewPortDiv,"olDrawBox");},moveBox:function(xy){var startX=this.dragHandler.start.x;var startY=this.dragHandler.start.y;var deltaX=Math.abs(startX-xy.x);var deltaY=Math.abs(startY-xy.y);this.zoomBox.style.width=Math.max(1,deltaX)+"px";this.zoomBox.style.height=Math.max(1,deltaY)+"px";this.zoomBox.style.left=xy.xstartX){this.zoomBox.style.width=Math.max(1,deltaX-box.xOffset)+"px";} +if(xy.y>startY){this.zoomBox.style.height=Math.max(1,deltaY-box.yOffset)+"px";}}},endBox:function(end){var result;if(Math.abs(this.dragHandler.start.x-end.x)>5||Math.abs(this.dragHandler.start.y-end.y)>5){var start=this.dragHandler.start;var top=Math.min(start.y,end.y);var bottom=Math.max(start.y,end.y);var left=Math.min(start.x,end.x);var right=Math.max(start.x,end.x);result=new OpenLayers.Bounds(left,bottom,right,top);}else{result=this.dragHandler.start.clone();} +this.removeBox();this.callback("done",[result]);},removeBox:function(){this.map.viewPortDiv.removeChild(this.zoomBox);this.zoomBox=null;this.boxCharacteristics=null;OpenLayers.Element.removeClass(this.map.viewPortDiv,"olDrawBox");},activate:function(){if(OpenLayers.Handler.prototype.activate.apply(this,arguments)){this.dragHandler.activate();return true;}else{return false;}},deactivate:function(){if(OpenLayers.Handler.prototype.deactivate.apply(this,arguments)){this.dragHandler.deactivate();return true;}else{return false;}},getBoxCharacteristics:function(){if(!this.boxCharacteristics){var xOffset=parseInt(OpenLayers.Element.getStyle(this.zoomBox,"border-left-width"))+parseInt(OpenLayers.Element.getStyle(this.zoomBox,"border-right-width"))+1;var yOffset=parseInt(OpenLayers.Element.getStyle(this.zoomBox,"border-top-width"))+parseInt(OpenLayers.Element.getStyle(this.zoomBox,"border-bottom-width"))+1;var newBoxModel=OpenLayers.Util.getBrowserName()=="msie"?document.compatMode!="BackCompat":true;this.boxCharacteristics={xOffset:xOffset,yOffset:yOffset,newBoxModel:newBoxModel};} +return this.boxCharacteristics;},CLASS_NAME:"OpenLayers.Handler.Box"});OpenLayers.Handler.RegularPolygon=OpenLayers.Class(OpenLayers.Handler.Drag,{sides:4,radius:null,snapAngle:null,snapToggle:'shiftKey',persist:false,irregular:false,angle:null,fixedRadius:false,feature:null,layer:null,origin:null,initialize:function(control,callbacks,options){this.style=OpenLayers.Util.extend(OpenLayers.Feature.Vector.style['default'],{});OpenLayers.Handler.prototype.initialize.apply(this,[control,callbacks,options]);this.options=(options)?options:new Object();},setOptions:function(newOptions){OpenLayers.Util.extend(this.options,newOptions);OpenLayers.Util.extend(this,newOptions);},activate:function(){var activated=false;if(OpenLayers.Handler.prototype.activate.apply(this,arguments)){var options={displayInLayerSwitcher:false,calculateInRange:function(){return true;}};this.layer=new OpenLayers.Layer.Vector(this.CLASS_NAME,options);this.map.addLayer(this.layer);activated=true;} +return activated;},deactivate:function(){var deactivated=false;if(OpenLayers.Handler.Drag.prototype.deactivate.apply(this,arguments)){if(this.dragging){this.cancel();} +if(this.layer.map!=null){this.layer.destroy(false);if(this.feature){this.feature.destroy();}} +this.layer=null;this.feature=null;deactivated=true;} +return deactivated;},down:function(evt){this.fixedRadius=!!(this.radius);var maploc=this.map.getLonLatFromPixel(evt.xy);this.origin=new OpenLayers.Geometry.Point(maploc.lon,maploc.lat);if(!this.fixedRadius||this.irregular){this.radius=this.map.getResolution();} +if(this.persist){this.clear();} +this.feature=new OpenLayers.Feature.Vector();this.createGeometry();this.callback("create",[this.origin,this.feature]);this.layer.addFeatures([this.feature],{silent:true});this.layer.drawFeature(this.feature,this.style);},move:function(evt){var maploc=this.map.getLonLatFromPixel(evt.xy);var point=new OpenLayers.Geometry.Point(maploc.lon,maploc.lat);if(this.irregular){var ry=Math.sqrt(2)*Math.abs(point.y-this.origin.y)/2;this.radius=Math.max(this.map.getResolution()/2,ry);}else if(this.fixedRadius){this.origin=point;}else{this.calculateAngle(point,evt);this.radius=Math.max(this.map.getResolution()/2,point.distanceTo(this.origin));} +this.modifyGeometry();if(this.irregular){var dx=point.x-this.origin.x;var dy=point.y-this.origin.y;var ratio;if(dy==0){ratio=dx/(this.radius*Math.sqrt(2));}else{ratio=dx/dy;} +this.feature.geometry.resize(1,this.origin,ratio);this.feature.geometry.move(dx/2,dy/2);} +this.layer.drawFeature(this.feature,this.style);},up:function(evt){this.finalize();if(this.start==this.last){this.callback("done",[evt.xy]);}},out:function(evt){this.finalize();},createGeometry:function(){this.angle=Math.PI*((1/this.sides)-(1/2));if(this.snapAngle){this.angle+=this.snapAngle*(Math.PI/180);} +this.feature.geometry=OpenLayers.Geometry.Polygon.createRegularPolygon(this.origin,this.radius,this.sides,this.snapAngle);},modifyGeometry:function(){var angle,dx,dy,point;var ring=this.feature.geometry.components[0];if(ring.components.length!=(this.sides+1)){this.createGeometry();ring=this.feature.geometry.components[0];} +for(var i=0;i0){this.removeMarker(this.markers[0]);}}},drawMarker:function(marker){var px=this.map.getLayerPxFromLonLat(marker.lonlat);if(px==null){marker.display(false);}else{if(!marker.isDrawn()){var markerImg=marker.draw(px);this.div.appendChild(markerImg);}else if(marker.icon){marker.icon.moveTo(px);}}},getDataExtent:function(){var maxExtent=null;if(this.markers&&(this.markers.length>0)){var maxExtent=new OpenLayers.Bounds();for(var i=0,len=this.markers.length;i0){var feature=this.features.shift();feature.destroy();}},CLASS_NAME:"OpenLayers.Tile.WFS"});OpenLayers.Control.DrawFeature=OpenLayers.Class(OpenLayers.Control,{layer:null,callbacks:null,EVENT_TYPES:["featureadded"],featureAdded:function(){},handlerOptions:null,initialize:function(layer,handler,options){this.EVENT_TYPES=OpenLayers.Control.DrawFeature.prototype.EVENT_TYPES.concat(OpenLayers.Control.prototype.EVENT_TYPES);OpenLayers.Control.prototype.initialize.apply(this,[options]);this.callbacks=OpenLayers.Util.extend({done:this.drawFeature,modify:function(vertex,feature){this.layer.events.triggerEvent("sketchmodified",{vertex:vertex,feature:feature});},create:function(vertex,feature){this.layer.events.triggerEvent("sketchstarted",{vertex:vertex,feature:feature});}},this.callbacks);this.layer=layer;var sketchStyle=this.layer.styleMap&&this.layer.styleMap.styles.temporary;if(sketchStyle){this.handlerOptions=this.handlerOptions||{};this.handlerOptions.layerOptions=OpenLayers.Util.applyDefaults(this.handlerOptions.layerOptions,{styleMap:new OpenLayers.StyleMap({"default":sketchStyle})});} +this.handler=new handler(this,this.callbacks,this.handlerOptions);},drawFeature:function(geometry){var feature=new OpenLayers.Feature.Vector(geometry);var proceed=this.layer.events.triggerEvent("sketchcomplete",{feature:feature});if(proceed!==false){feature.state=OpenLayers.State.INSERT;this.layer.addFeatures([feature]);this.featureAdded(feature);this.events.triggerEvent("featureadded",{feature:feature});}},CLASS_NAME:"OpenLayers.Control.DrawFeature"});OpenLayers.Control.Measure=OpenLayers.Class(OpenLayers.Control,{EVENT_TYPES:['measure','measurepartial'],handlerOptions:null,callbacks:null,displaySystem:'metric',geodesic:false,displaySystemUnits:{geographic:['dd'],english:['mi','ft','in'],metric:['km','m']},partialDelay:300,delayedTrigger:null,persist:false,initialize:function(handler,options){this.EVENT_TYPES=OpenLayers.Control.Measure.prototype.EVENT_TYPES.concat(OpenLayers.Control.prototype.EVENT_TYPES);OpenLayers.Control.prototype.initialize.apply(this,[options]);this.callbacks=OpenLayers.Util.extend({done:this.measureComplete,point:this.measurePartial},this.callbacks);this.handlerOptions=OpenLayers.Util.extend({persist:this.persist},this.handlerOptions);this.handler=new handler(this,this.callbacks,this.handlerOptions);},cancel:function(){this.handler.cancel();},updateHandler:function(handler,options){var active=this.active;if(active){this.deactivate();} +this.handler=new handler(this,this.callbacks,options);if(active){this.activate();}},measureComplete:function(geometry){if(this.delayedTrigger){window.clearTimeout(this.delayedTrigger);} +this.measure(geometry,"measure");},measurePartial:function(point,geometry){this.delayedTrigger=window.setTimeout(OpenLayers.Function.bind(function(){this.measure(geometry,"measurepartial");},this),this.partialDelay);},measure:function(geometry,eventType){var stat,order;if(geometry.CLASS_NAME.indexOf('LineString')>-1){stat=this.getBestLength(geometry);order=1;}else{stat=this.getBestArea(geometry);order=2;} +this.events.triggerEvent(eventType,{measure:stat[0],units:stat[1],order:order,geometry:geometry});},getBestArea:function(geometry){var units=this.displaySystemUnits[this.displaySystem];var unit,area;for(var i=0,len=units.length;i1){break;}} +return[area,unit];},getArea:function(geometry,units){var area,geomUnits;if(this.geodesic){area=geometry.getGeodesicArea(this.map.getProjectionObject());geomUnits="m";}else{area=geometry.getArea();geomUnits=this.map.getUnits();} +var inPerDisplayUnit=OpenLayers.INCHES_PER_UNIT[units];if(inPerDisplayUnit){var inPerMapUnit=OpenLayers.INCHES_PER_UNIT[geomUnits];area*=Math.pow((inPerMapUnit/inPerDisplayUnit),2);} +return area;},getBestLength:function(geometry){var units=this.displaySystemUnits[this.displaySystem];var unit,length;for(var i=0,len=units.length;i1){break;}} +return[length,unit];},getLength:function(geometry,units){var length,geomUnits;if(this.geodesic){length=geometry.getGeodesicLength(this.map.getProjectionObject());geomUnits="m";}else{length=geometry.getLength();geomUnits=this.map.getUnits();} +var inPerDisplayUnit=OpenLayers.INCHES_PER_UNIT[units];if(inPerDisplayUnit){var inPerMapUnit=OpenLayers.INCHES_PER_UNIT[geomUnits];length*=(inPerMapUnit/inPerDisplayUnit);} +return length;},CLASS_NAME:"OpenLayers.Control.Measure"});OpenLayers.Control.ZoomBox=OpenLayers.Class(OpenLayers.Control,{type:OpenLayers.Control.TYPE_TOOL,out:false,alwaysZoom:false,draw:function(){this.handler=new OpenLayers.Handler.Box(this,{done:this.zoomBox},{keyMask:this.keyMask});},zoomBox:function(position){if(position instanceof OpenLayers.Bounds){if(!this.out){var minXY=this.map.getLonLatFromPixel(new OpenLayers.Pixel(position.left,position.bottom));var maxXY=this.map.getLonLatFromPixel(new OpenLayers.Pixel(position.right,position.top));var bounds=new OpenLayers.Bounds(minXY.lon,minXY.lat,maxXY.lon,maxXY.lat);}else{var pixWidth=Math.abs(position.right-position.left);var pixHeight=Math.abs(position.top-position.bottom);var zoomFactor=Math.min((this.map.size.h/pixHeight),(this.map.size.w/pixWidth));var extent=this.map.getExtent();var center=this.map.getLonLatFromPixel(position.getCenterPixel());var xmin=center.lon-(extent.getWidth()/2)*zoomFactor;var xmax=center.lon+(extent.getWidth()/2)*zoomFactor;var ymin=center.lat-(extent.getHeight()/2)*zoomFactor;var ymax=center.lat+(extent.getHeight()/2)*zoomFactor;var bounds=new OpenLayers.Bounds(xmin,ymin,xmax,ymax);} +var lastZoom=this.map.getZoom();this.map.zoomToExtent(bounds);if(lastZoom==this.map.getZoom()&&this.alwaysZoom==true){this.map.zoomTo(lastZoom+(this.out?-1:1));}}else{if(!this.out){this.map.setCenter(this.map.getLonLatFromPixel(position),this.map.getZoom()+1);}else{this.map.setCenter(this.map.getLonLatFromPixel(position),this.map.getZoom()-1);}}},CLASS_NAME:"OpenLayers.Control.ZoomBox"});OpenLayers.Format.WFSCapabilities.v1_0_0=OpenLayers.Class(OpenLayers.Format.WFSCapabilities.v1,{initialize:function(options){OpenLayers.Format.WFSCapabilities.v1.prototype.initialize.apply(this,[options]);},CLASS_NAME:"OpenLayers.Format.WFSCapabilities.v1_0_0"});OpenLayers.Format.WFSCapabilities.v1_1_0=OpenLayers.Class(OpenLayers.Format.WFSCapabilities.v1,{initialize:function(options){OpenLayers.Format.WFSCapabilities.v1.prototype.initialize.apply(this,[options]);},CLASS_NAME:"OpenLayers.Format.WFSCapabilities.v1_1_0"});OpenLayers.Format.WKT=OpenLayers.Class(OpenLayers.Format,{initialize:function(options){this.regExes={'typeStr':/^\s*(\w+)\s*\(\s*(.*)\s*\)\s*$/,'spaces':/\s+/,'parenComma':/\)\s*,\s*\(/,'doubleParenComma':/\)\s*\)\s*,\s*\(\s*\(/,'trimParens':/^\s*\(?(.*?)\)?\s*$/};OpenLayers.Format.prototype.initialize.apply(this,[options]);},read:function(wkt){var features,type,str;var matches=this.regExes.typeStr.exec(wkt);if(matches){type=matches[1].toLowerCase();str=matches[2];if(this.parse[type]){features=this.parse[type].apply(this,[str]);} +if(this.internalProjection&&this.externalProjection){if(features&&features.CLASS_NAME=="OpenLayers.Feature.Vector"){features.geometry.transform(this.externalProjection,this.internalProjection);}else if(features&&type!="geometrycollection"&&typeof features=="object"){for(var i=0,len=features.length;i0){pieces.push(',');} +geometry=collection[i].geometry;type=geometry.CLASS_NAME.split('.')[2].toLowerCase();if(!this.extract[type]){return null;} +if(this.internalProjection&&this.externalProjection){geometry=geometry.clone();geometry.transform(this.internalProjection,this.externalProjection);} +data=this.extract[type].apply(this,[geometry]);pieces.push(type.toUpperCase()+'('+data+')');} +if(isCollection){pieces.push(')');} +return pieces.join('');},extract:{'point':function(point){return point.x+' '+point.y;},'multipoint':function(multipoint){var array=[];for(var i=0,len=multipoint.components.length;i';} +contentHTML+=title;if(link){contentHTML+='';} +contentHTML+='';contentHTML+='
';contentHTML+=description;contentHTML+='
';data['popupContentHTML']=contentHTML;} +var feature=new OpenLayers.Feature(this,location,data);this.features.push(feature);var marker=feature.createMarker();marker.events.register('click',feature,this.markerClick);this.addMarker(marker);} +this.events.triggerEvent("loadend");},markerClick:function(evt){var sameMarkerClicked=(this==this.layer.selectedFeature);this.layer.selectedFeature=(!sameMarkerClicked)?this:null;for(var i=0,len=this.layer.map.popups.length;i0){var feature=this.features[0];OpenLayers.Util.removeItem(this.features,feature);feature.destroy();}}},CLASS_NAME:"OpenLayers.Layer.GeoRSS"});OpenLayers.Layer.Google=OpenLayers.Class(OpenLayers.Layer.EventPane,OpenLayers.Layer.FixedZoomLevels,{MIN_ZOOM_LEVEL:0,MAX_ZOOM_LEVEL:19,RESOLUTIONS:[1.40625,0.703125,0.3515625,0.17578125,0.087890625,0.0439453125,0.02197265625,0.010986328125,0.0054931640625,0.00274658203125,0.001373291015625,0.0006866455078125,0.00034332275390625,0.000171661376953125,0.0000858306884765625,0.00004291534423828125,0.00002145767211914062,0.00001072883605957031,0.00000536441802978515,0.00000268220901489257],type:null,sphericalMercator:false,dragObject:null,termsOfUse:null,poweredBy:null,initialize:function(name,options){OpenLayers.Layer.EventPane.prototype.initialize.apply(this,arguments);OpenLayers.Layer.FixedZoomLevels.prototype.initialize.apply(this,arguments);this.addContainerPxFunction();if(this.sphericalMercator){OpenLayers.Util.extend(this,OpenLayers.Layer.SphericalMercator);this.initMercatorParameters();}},loadMapObject:function(){try{this.mapObject=new GMap2(this.div);if(typeof this.mapObject.getDragObject=="function"){this.dragObject=this.mapObject.getDragObject();}else{this.dragPanMapObject=null;} +this.termsOfUse=this.div.lastChild;this.div.removeChild(this.termsOfUse);if(this.isFixed){this.map.viewPortDiv.appendChild(this.termsOfUse);}else{this.map.layerContainerDiv.appendChild(this.termsOfUse);} +this.termsOfUse.style.zIndex="1100";this.termsOfUse.style.display=this.div.style.display;this.termsOfUse.style.right="";this.termsOfUse.style.bottom="";this.termsOfUse.className="olLayerGoogleCopyright";this.poweredBy=this.div.lastChild;this.div.removeChild(this.poweredBy);if(this.isFixed){this.map.viewPortDiv.appendChild(this.poweredBy);}else{this.map.layerContainerDiv.appendChild(this.poweredBy);} +this.poweredBy.style.zIndex="1100";this.poweredBy.style.display=this.div.style.display;this.poweredBy.style.right="";this.poweredBy.style.bottom="";this.poweredBy.className="olLayerGooglePoweredBy gmnoprint";}catch(e){OpenLayers.Console.error(e);}},setMap:function(map){OpenLayers.Layer.EventPane.prototype.setMap.apply(this,arguments);if(this.type!=null){this.map.events.register("moveend",this,this.setMapType);}},setMapType:function(){if(this.mapObject.getCenter()!=null){if(OpenLayers.Util.indexOf(this.mapObject.getMapTypes(),this.type)==-1){this.mapObject.addMapType(this.type);} +this.mapObject.setMapType(this.type);this.map.events.unregister("moveend",this,this.setMapType);}},onMapResize:function(){if(this.visibility&&this.mapObject.isLoaded()){this.mapObject.checkResize();}else{if(!this._resized){var layer=this;var handle=GEvent.addListener(this.mapObject,"load",function(){GEvent.removeListener(handle);delete layer._resized;layer.mapObject.checkResize();layer.moveTo(layer.map.getCenter(),layer.map.getZoom());})} +this._resized=true;}},display:function(display){OpenLayers.Layer.EventPane.prototype.display.apply(this,arguments);this.termsOfUse.style.display=this.div.style.display;this.poweredBy.style.display=this.div.style.display;},removeMap:function(map){if(this.termsOfUse&&this.termsOfUse.parentNode){this.termsOfUse.parentNode.removeChild(this.termsOfUse);this.termsOfUse=null;} +if(this.poweredBy&&this.poweredBy.parentNode){this.poweredBy.parentNode.removeChild(this.poweredBy);this.poweredBy=null;} +OpenLayers.Layer.EventPane.prototype.removeMap.apply(this,arguments);},getOLBoundsFromMapObjectBounds:function(moBounds){var olBounds=null;if(moBounds!=null){var sw=moBounds.getSouthWest();var ne=moBounds.getNorthEast();if(this.sphericalMercator){sw=this.forwardMercator(sw.lng(),sw.lat());ne=this.forwardMercator(ne.lng(),ne.lat());}else{sw=new OpenLayers.LonLat(sw.lng(),sw.lat());ne=new OpenLayers.LonLat(ne.lng(),ne.lat());} +olBounds=new OpenLayers.Bounds(sw.lon,sw.lat,ne.lon,ne.lat);} +return olBounds;},getMapObjectBoundsFromOLBounds:function(olBounds){var moBounds=null;if(olBounds!=null){var sw=this.sphericalMercator?this.inverseMercator(olBounds.bottom,olBounds.left):new OpenLayers.LonLat(olBounds.bottom,olBounds.left);var ne=this.sphericalMercator?this.inverseMercator(olBounds.top,olBounds.right):new OpenLayers.LonLat(olBounds.top,olBounds.right);moBounds=new GLatLngBounds(new GLatLng(sw.lat,sw.lon),new GLatLng(ne.lat,ne.lon));} +return moBounds;},addContainerPxFunction:function(){if((typeof GMap2!="undefined")&&!GMap2.prototype.fromLatLngToContainerPixel){GMap2.prototype.fromLatLngToContainerPixel=function(gLatLng){var gPoint=this.fromLatLngToDivPixel(gLatLng);var div=this.getContainer().firstChild.firstChild;gPoint.x+=div.offsetLeft;gPoint.y+=div.offsetTop;return gPoint;};}},getWarningHTML:function(){return OpenLayers.i18n("googleWarning");},setMapObjectCenter:function(center,zoom){this.mapObject.setCenter(center,zoom);},dragPanMapObject:function(dX,dY){this.dragObject.moveBy(new GSize(-dX,dY));},getMapObjectCenter:function(){return this.mapObject.getCenter();},getMapObjectZoom:function(){return this.mapObject.getZoom();},getMapObjectLonLatFromMapObjectPixel:function(moPixel){return this.mapObject.fromContainerPixelToLatLng(moPixel);},getMapObjectPixelFromMapObjectLonLat:function(moLonLat){return this.mapObject.fromLatLngToContainerPixel(moLonLat);},getMapObjectZoomFromMapObjectBounds:function(moBounds){return this.mapObject.getBoundsZoomLevel(moBounds);},getLongitudeFromMapObjectLonLat:function(moLonLat){return this.sphericalMercator?this.forwardMercator(moLonLat.lng(),moLonLat.lat()).lon:moLonLat.lng();},getLatitudeFromMapObjectLonLat:function(moLonLat){var lat=this.sphericalMercator?this.forwardMercator(moLonLat.lng(),moLonLat.lat()).lat:moLonLat.lat();return lat;},getMapObjectLonLatFromLonLat:function(lon,lat){var gLatLng;if(this.sphericalMercator){var lonlat=this.inverseMercator(lon,lat);gLatLng=new GLatLng(lonlat.lat,lonlat.lon);}else{gLatLng=new GLatLng(lat,lon);} +return gLatLng;},getXFromMapObjectPixel:function(moPixel){return moPixel.x;},getYFromMapObjectPixel:function(moPixel){return moPixel.y;},getMapObjectPixelFromXY:function(x,y){return new GPoint(x,y);},CLASS_NAME:"OpenLayers.Layer.Google"});OpenLayers.Layer.Grid=OpenLayers.Class(OpenLayers.Layer.HTTPRequest,{tileSize:null,grid:null,singleTile:false,ratio:1.5,buffer:2,numLoadingTiles:0,initialize:function(name,url,params,options){OpenLayers.Layer.HTTPRequest.prototype.initialize.apply(this,arguments);this.events.addEventType("tileloaded");this.grid=[];},destroy:function(){this.clearGrid();this.grid=null;this.tileSize=null;OpenLayers.Layer.HTTPRequest.prototype.destroy.apply(this,arguments);},clearGrid:function(){if(this.grid){for(var iRow=0,len=this.grid.length;iRow=bounds.bottom-tilelat*this.buffer)||rowidx=0)&&(testCell=0)){tile=this.grid[testRow][testCell];} +if((tile!=null)&&(!tile.queued)){tileQueue.unshift(tile);tile.queued=true;directionsTried=0;iRow=testRow;iCell=testCell;}else{direction=(direction+1)%4;directionsTried++;}} +for(var i=0,len=tileQueue.length;i-this.tileSize.w*(buffer-1)){this.shiftColumn(true);}else if(tlViewPort.x<-this.tileSize.w*buffer){this.shiftColumn(false);}else if(tlViewPort.y>-this.tileSize.h*(buffer-1)){this.shiftRow(true);}else if(tlViewPort.y<-this.tileSize.h*buffer){this.shiftRow(false);}else{break;}};},shiftRow:function(prepend){var modelRowIndex=(prepend)?0:(this.grid.length-1);var grid=this.grid;var modelRow=grid[modelRowIndex];var resolution=this.map.getResolution();var deltaY=(prepend)?-this.tileSize.h:this.tileSize.h;var deltaLat=resolution*-deltaY;var row=(prepend)?grid.pop():grid.shift();for(var i=0,len=modelRow.length;irows){var row=this.grid.pop();for(var i=0,l=row.length;icolumns){for(var i=0,l=this.grid.length;i'+feature.attributes.title+''+'

'+feature.attributes.description+'

';} +data['overflow']=feature.attributes.overflow||"auto";var markerFeature=new OpenLayers.Feature(this,location,data);this.features.push(markerFeature);var marker=markerFeature.createMarker();if((feature.attributes.title!=null)&&(feature.attributes.description!=null)){marker.events.register('click',markerFeature,this.markerClick);} +this.addMarker(marker);} +this.events.triggerEvent("loadend");},markerClick:function(evt){var sameMarkerClicked=(this==this.layer.selectedFeature);this.layer.selectedFeature=(!sameMarkerClicked)?this:null;for(var i=0,len=this.layer.map.popups.length;i0){var feature=this.features[0];OpenLayers.Util.removeItem(this.features,feature);feature.destroy();}}},CLASS_NAME:"OpenLayers.Layer.Text"});OpenLayers.Layer.VirtualEarth=OpenLayers.Class(OpenLayers.Layer.EventPane,OpenLayers.Layer.FixedZoomLevels,{MIN_ZOOM_LEVEL:1,MAX_ZOOM_LEVEL:17,RESOLUTIONS:[1.40625,0.703125,0.3515625,0.17578125,0.087890625,0.0439453125,0.02197265625,0.010986328125,0.0054931640625,0.00274658203125,0.001373291015625,0.0006866455078125,0.00034332275390625,0.000171661376953125,0.0000858306884765625,0.00004291534423828125,0.00002145767211914062],type:null,sphericalMercator:false,initialize:function(name,options){OpenLayers.Layer.EventPane.prototype.initialize.apply(this,arguments);OpenLayers.Layer.FixedZoomLevels.prototype.initialize.apply(this,arguments);if(this.sphericalMercator){OpenLayers.Util.extend(this,OpenLayers.Layer.SphericalMercator);this.initMercatorParameters();}},loadMapObject:function(){var veDiv=OpenLayers.Util.createDiv(this.name);var sz=this.map.getSize();veDiv.style.width=sz.w+"px";veDiv.style.height=sz.h+"px";this.div.appendChild(veDiv);try{this.mapObject=new VEMap(this.name);}catch(e){} +if(this.mapObject!=null){try{this.mapObject.LoadMap(null,null,this.type,true);this.mapObject.AttachEvent("onmousedown",function(){return true;});}catch(e){} +this.mapObject.HideDashboard();} +if(!this.mapObject||!this.mapObject.vemapcontrol||!this.mapObject.vemapcontrol.PanMap||(typeof this.mapObject.vemapcontrol.PanMap!="function")){this.dragPanMapObject=null;}},getWarningHTML:function(){return OpenLayers.i18n("getLayerWarning",{'layerType':'VE','layerLib':'VirtualEarth'});},setMapObjectCenter:function(center,zoom){this.mapObject.SetCenterAndZoom(center,zoom);},getMapObjectCenter:function(){return this.mapObject.GetCenter();},dragPanMapObject:function(dX,dY){this.mapObject.vemapcontrol.PanMap(dX,-dY);},getMapObjectZoom:function(){return this.mapObject.GetZoomLevel();},getMapObjectLonLatFromMapObjectPixel:function(moPixel){return(typeof VEPixel!='undefined')?this.mapObject.PixelToLatLong(moPixel):this.mapObject.PixelToLatLong(moPixel.x,moPixel.y);},getMapObjectPixelFromMapObjectLonLat:function(moLonLat){return this.mapObject.LatLongToPixel(moLonLat);},getLongitudeFromMapObjectLonLat:function(moLonLat){return this.sphericalMercator?this.forwardMercator(moLonLat.Longitude,moLonLat.Latitude).lon:moLonLat.Longitude;},getLatitudeFromMapObjectLonLat:function(moLonLat){return this.sphericalMercator?this.forwardMercator(moLonLat.Longitude,moLonLat.Latitude).lat:moLonLat.Latitude;},getMapObjectLonLatFromLonLat:function(lon,lat){var veLatLong;if(this.sphericalMercator){var lonlat=this.inverseMercator(lon,lat);veLatLong=new VELatLong(lonlat.lat,lonlat.lon);}else{veLatLong=new VELatLong(lat,lon);} +return veLatLong;},getXFromMapObjectPixel:function(moPixel){return moPixel.x;},getYFromMapObjectPixel:function(moPixel){return moPixel.y;},getMapObjectPixelFromXY:function(x,y){return(typeof VEPixel!='undefined')?new VEPixel(x,y):new Msn.VE.Pixel(x,y);},CLASS_NAME:"OpenLayers.Layer.VirtualEarth"});OpenLayers.Layer.Yahoo=OpenLayers.Class(OpenLayers.Layer.EventPane,OpenLayers.Layer.FixedZoomLevels,{MIN_ZOOM_LEVEL:0,MAX_ZOOM_LEVEL:17,RESOLUTIONS:[1.40625,0.703125,0.3515625,0.17578125,0.087890625,0.0439453125,0.02197265625,0.010986328125,0.0054931640625,0.00274658203125,0.001373291015625,0.0006866455078125,0.00034332275390625,0.000171661376953125,0.0000858306884765625,0.00004291534423828125,0.00002145767211914062,0.00001072883605957031],type:null,sphericalMercator:false,initialize:function(name,options){OpenLayers.Layer.EventPane.prototype.initialize.apply(this,arguments);OpenLayers.Layer.FixedZoomLevels.prototype.initialize.apply(this,arguments);if(this.sphericalMercator){OpenLayers.Util.extend(this,OpenLayers.Layer.SphericalMercator);this.initMercatorParameters();}},loadMapObject:function(){try{var size=this.getMapObjectSizeFromOLSize(this.map.getSize());this.mapObject=new YMap(this.div,this.type,size);this.mapObject.disableKeyControls();this.mapObject.disableDragMap();if(!this.mapObject.moveByXY||(typeof this.mapObject.moveByXY!="function")){this.dragPanMapObject=null;}}catch(e){}},onMapResize:function(){try{var size=this.getMapObjectSizeFromOLSize(this.map.getSize());this.mapObject.resizeTo(size);}catch(e){}},setMap:function(map){OpenLayers.Layer.EventPane.prototype.setMap.apply(this,arguments);this.map.events.register("moveend",this,this.fixYahooEventPane);},fixYahooEventPane:function(){var yahooEventPane=OpenLayers.Util.getElement("ygddfdiv");if(yahooEventPane!=null){if(yahooEventPane.parentNode!=null){yahooEventPane.parentNode.removeChild(yahooEventPane);} +this.map.events.unregister("moveend",this,this.fixYahooEventPane);}},getWarningHTML:function(){return OpenLayers.i18n("getLayerWarning",{'layerType':'Yahoo','layerLib':'Yahoo'});},getOLZoomFromMapObjectZoom:function(moZoom){var zoom=null;if(moZoom!=null){zoom=OpenLayers.Layer.FixedZoomLevels.prototype.getOLZoomFromMapObjectZoom.apply(this,[moZoom]);zoom=18-zoom;} +return zoom;},getMapObjectZoomFromOLZoom:function(olZoom){var zoom=null;if(olZoom!=null){zoom=OpenLayers.Layer.FixedZoomLevels.prototype.getMapObjectZoomFromOLZoom.apply(this,[olZoom]);zoom=18-zoom;} +return zoom;},setMapObjectCenter:function(center,zoom){this.mapObject.drawZoomAndCenter(center,zoom);},getMapObjectCenter:function(){return this.mapObject.getCenterLatLon();},dragPanMapObject:function(dX,dY){this.mapObject.moveByXY({'x':-dX,'y':dY});},getMapObjectZoom:function(){return this.mapObject.getZoomLevel();},getMapObjectLonLatFromMapObjectPixel:function(moPixel){return this.mapObject.convertXYLatLon(moPixel);},getMapObjectPixelFromMapObjectLonLat:function(moLonLat){return this.mapObject.convertLatLonXY(moLonLat);},getLongitudeFromMapObjectLonLat:function(moLonLat){return this.sphericalMercator?this.forwardMercator(moLonLat.Lon,moLonLat.Lat).lon:moLonLat.Lon;},getLatitudeFromMapObjectLonLat:function(moLonLat){return this.sphericalMercator?this.forwardMercator(moLonLat.Lon,moLonLat.Lat).lat:moLonLat.Lat;},getMapObjectLonLatFromLonLat:function(lon,lat){var yLatLong;if(this.sphericalMercator){var lonlat=this.inverseMercator(lon,lat);yLatLong=new YGeoPoint(lonlat.lat,lonlat.lon);}else{yLatLong=new YGeoPoint(lat,lon);} +return yLatLong;},getXFromMapObjectPixel:function(moPixel){return moPixel.x;},getYFromMapObjectPixel:function(moPixel){return moPixel.y;},getMapObjectPixelFromXY:function(x,y){return new YCoordPoint(x,y);},getMapObjectSizeFromOLSize:function(olSize){return new YSize(olSize.w,olSize.h);},CLASS_NAME:"OpenLayers.Layer.Yahoo"});OpenLayers.Protocol.HTTP=OpenLayers.Class(OpenLayers.Protocol,{url:null,headers:null,params:null,callback:null,scope:null,readWithPOST:false,initialize:function(options){this.params={};this.headers={};OpenLayers.Protocol.prototype.initialize.apply(this,arguments);},destroy:function(){this.params=null;this.headers=null;OpenLayers.Protocol.prototype.destroy.apply(this);},createCallback:function(method,response,options){return OpenLayers.Function.bind(function(){method.apply(this,[response,options]);},this);},read:function(options){options=OpenLayers.Util.applyDefaults(options,this.options);var readWithPOST=(options.readWithPOST!==undefined)?options.readWithPOST:this.readWithPOST;var resp=new OpenLayers.Protocol.Response({requestType:"read"});if(options.filter&&options.filter instanceof OpenLayers.Filter.Spatial){if(options.filter.type==OpenLayers.Filter.Spatial.BBOX){options.params=OpenLayers.Util.extend(options.params,{bbox:options.filter.value.toArray()});}} +if(readWithPOST){resp.priv=OpenLayers.Request.POST({url:options.url,callback:this.createCallback(this.handleRead,resp,options),data:OpenLayers.Util.getParameterString(options.params),headers:{"Content-Type":"application/x-www-form-urlencoded"}});}else{resp.priv=OpenLayers.Request.GET({url:options.url,callback:this.createCallback(this.handleRead,resp,options),params:options.params,headers:options.headers});} +return resp;},handleRead:function(resp,options){this.handleResponse(resp,options);},create:function(features,options){options=OpenLayers.Util.applyDefaults(options,this.options);var resp=new OpenLayers.Protocol.Response({reqFeatures:features,requestType:"create"});resp.priv=OpenLayers.Request.POST({url:options.url,callback:this.createCallback(this.handleCreate,resp,options),headers:options.headers,data:this.format.write(features)});return resp;},handleCreate:function(resp,options){this.handleResponse(resp,options);},update:function(feature,options){var url=options.url||feature.url||this.options.url;options=OpenLayers.Util.applyDefaults(options,this.options);var resp=new OpenLayers.Protocol.Response({reqFeatures:feature,requestType:"update"});resp.priv=OpenLayers.Request.PUT({url:url,callback:this.createCallback(this.handleUpdate,resp,options),headers:options.headers,data:this.format.write(feature)});return resp;},handleUpdate:function(resp,options){this.handleResponse(resp,options);},"delete":function(feature,options){var url=options.url||feature.url||this.options.url;options=OpenLayers.Util.applyDefaults(options,this.options);var resp=new OpenLayers.Protocol.Response({reqFeatures:feature,requestType:"delete"});resp.priv=OpenLayers.Request.DELETE({url:url,callback:this.createCallback(this.handleDelete,resp,options),headers:options.headers});return resp;},handleDelete:function(resp,options){this.handleResponse(resp,options);},handleResponse:function(resp,options){var request=resp.priv;if(options.callback){if(request.status>=200&&request.status<300){if(resp.requestType!="delete"){resp.features=this.parseFeatures(request);} +resp.code=OpenLayers.Protocol.Response.SUCCESS;}else{resp.code=OpenLayers.Protocol.Response.FAILURE;} +options.callback.call(options.scope,resp);}},parseFeatures:function(request){var doc=request.responseXML;if(!doc||!doc.documentElement){doc=request.responseText;} +if(!doc||doc.length<=0){return null;} +return this.format.read(doc);},commit:function(features,options){options=OpenLayers.Util.applyDefaults(options,this.options);var resp=[],nResponses=0;var types={};types[OpenLayers.State.INSERT]=[];types[OpenLayers.State.UPDATE]=[];types[OpenLayers.State.DELETE]=[];var feature,list,requestFeatures=[];for(var i=0,len=features.length;i0?1:0)+ +types[OpenLayers.State.UPDATE].length+ +types[OpenLayers.State.DELETE].length;var success=true;var finalResponse=new OpenLayers.Protocol.Response({reqFeatures:requestFeatures});function insertCallback(response){var len=response.features?response.features.length:0;var fids=new Array(len);for(var i=0;i=nRequests){if(options.callback){finalResponse.code=success?OpenLayers.Protocol.Response.SUCCESS:OpenLayers.Protocol.Response.FAILURE;options.callback.apply(options.scope,[finalResponse]);}}} +var queue=types[OpenLayers.State.INSERT];if(queue.length>0){resp.push(this.create(queue,OpenLayers.Util.applyDefaults({callback:insertCallback,scope:this},options.create)));} +queue=types[OpenLayers.State.UPDATE];for(var i=queue.length-1;i>=0;--i){resp.push(this.update(queue[i],OpenLayers.Util.applyDefaults({callback:callback,scope:this},options.update)));} +queue=types[OpenLayers.State.DELETE];for(var i=queue.length-1;i>=0;--i){resp.push(this["delete"](queue[i],OpenLayers.Util.applyDefaults({callback:callback,scope:this},options["delete"])));} +return resp;},abort:function(response){if(response){response.priv.abort();}},callUserCallback:function(resp,options){var opt=options[resp.requestType];if(opt&&opt.callback){opt.callback.call(opt.scope,resp);}},CLASS_NAME:"OpenLayers.Protocol.HTTP"});OpenLayers.Style=OpenLayers.Class({name:null,title:null,description:null,layerName:null,isDefault:false,rules:null,context:null,defaultStyle:null,defaultsPerSymbolizer:false,propertyStyles:null,initialize:function(style,options){OpenLayers.Util.extend(this,options);this.rules=[];if(options&&options.rules){this.addRules(options.rules);} +this.setDefaultStyle(style||OpenLayers.Feature.Vector.style["default"]);},destroy:function(){for(var i=0,len=this.rules.length;i0){appliedRules=true;for(var i=0,len=elseRules.length;i0&&appliedRules==false){style.display="none";} +return style;},applySymbolizer:function(rule,style,feature){var symbolizerPrefix=feature.geometry?this.getSymbolizerPrefix(feature.geometry):OpenLayers.Style.SYMBOLIZER_PREFIXES[0];var symbolizer=rule.symbolizer[symbolizerPrefix]||rule.symbolizer;if(this.defaultsPerSymbolizer===true){var defaults=this.defaultStyle;OpenLayers.Util.applyDefaults(symbolizer,{pointRadius:defaults.pointRadius});if(symbolizer.stroke===true||symbolizer.graphic===true){OpenLayers.Util.applyDefaults(symbolizer,{strokeWidth:defaults.strokeWidth,strokeColor:defaults.strokeColor,strokeOpacity:defaults.strokeOpacity,strokeDashstyle:defaults.strokeDashstyle,strokeLinecap:defaults.strokeLinecap});} +if(symbolizer.fill===true||symbolizer.graphic===true){OpenLayers.Util.applyDefaults(symbolizer,{fillColor:defaults.fillColor,fillOpacity:defaults.fillOpacity});} +if(symbolizer.graphic===true){OpenLayers.Util.applyDefaults(symbolizer,{pointRadius:this.defaultStyle.pointRadius,externalGraphic:this.defaultStyle.externalGraphic,graphicName:this.defaultStyle.graphicName,graphicOpacity:this.defaultStyle.graphicOpacity,graphicWidth:this.defaultStyle.graphicWidth,graphicHeight:this.defaultStyle.graphicHeight,graphicXOffset:this.defaultStyle.graphicXOffset,graphicYOffset:this.defaultStyle.graphicYOffset});}} +return this.createLiterals(OpenLayers.Util.extend(style,symbolizer),feature);},createLiterals:function(style,feature){var context=this.context||feature.attributes||feature.data;for(var i in this.propertyStyles){style[i]=OpenLayers.Style.createLiteral(style[i],context,feature);} +return style;},findPropertyStyles:function(){var propertyStyles={};var style=this.defaultStyle;this.addPropertyStyles(propertyStyles,style);var rules=this.rules;var symbolizer,value;for(var i=0,len=rules.length;i=0&&along1<=1&&along2>=0&&along2<=1){if(!point){intersection=true;}else{var x=seg1.x1+(along1*x12_11);var y=seg1.y1+(along1*y12_11);intersection=new OpenLayers.Geometry.Point(x,y);}}} +if(tolerance){var dist;if(intersection){if(point){var segs=[seg1,seg2];var seg,x,y;outer:for(var i=0;i<2;++i){seg=segs[i];for(var j=1;j<3;++j){x=seg["x"+j];y=seg["y"+j];dist=Math.sqrt(Math.pow(x-intersection.x,2)+ +Math.pow(y-intersection.y,2));if(dist=1.0){x=x2;y=y2;}else{x=x1+along*dx;y=y1+along*dy;} +return{distance:Math.sqrt(Math.pow(x-x0,2)+Math.pow(y-y0,2)),x:x,y:y};};OpenLayers.Layer.ArcGIS93Rest=OpenLayers.Class(OpenLayers.Layer.Grid,{DEFAULT_PARAMS:{format:"png"},isBaseLayer:true,initialize:function(name,url,params,options){var newArguments=[];params=OpenLayers.Util.upperCaseObject(params);newArguments.push(name,url,params,options);OpenLayers.Layer.Grid.prototype.initialize.apply(this,newArguments);OpenLayers.Util.applyDefaults(this.params,OpenLayers.Util.upperCaseObject(this.DEFAULT_PARAMS));if(this.params.TRANSPARENT&&this.params.TRANSPARENT.toString().toLowerCase()=="true"){if((options==null)||(!options.isBaseLayer)){this.isBaseLayer=false;} +if(this.params.FORMAT=="jpg"){this.params.FORMAT=OpenLayers.Util.alphaHack()?"gif":"png";}}},destroy:function(){OpenLayers.Layer.Grid.prototype.destroy.apply(this,arguments);},clone:function(obj){if(obj==null){obj=new OpenLayers.Layer.ArcGIS93Rest(this.name,this.url,this.params,this.options);} +obj=OpenLayers.Layer.Grid.prototype.clone.apply(this,[obj]);return obj;},getURL:function(bounds){bounds=this.adjustBounds(bounds);var projWords=this.projection.getCode().split(":");var srid=projWords[projWords.length-1];var imageSize=this.getImageSize();var newParams={'BBOX':bounds.toBBOX(),'SIZE':imageSize.w+","+imageSize.h,'F':"image",'BBOXSR':srid,'IMAGESR':srid};if(this.layerDefs){var layerDefStrList=[];var layerID;for(layerID in this.layerDefs){if(this.layerDefs.hasOwnProperty(layerID)){if(this.layerDefs[layerID]){layerDefStrList.push(layerID);layerDefStrList.push(":");layerDefStrList.push(this.layerDefs[layerID]);layerDefStrList.push(";");}}} +if(layerDefStrList.length>0){newParams['LAYERDEFS']=layerDefStrList.join("");}} +var requestString=this.getFullRequestString(newParams);return requestString;},setLayerFilter:function(id,queryDef){if(!this.layerDefs){this.layerDefs={};} +if(queryDef){this.layerDefs[id]=queryDef;}else{delete this.layerDefs[id];}},clearLayerFilter:function(id){if(id){delete this.layerDefs[id];}else{delete this.layerDefs;}},mergeNewParams:function(newParams){var upperParams=OpenLayers.Util.upperCaseObject(newParams);var newArguments=[upperParams];return OpenLayers.Layer.Grid.prototype.mergeNewParams.apply(this,newArguments);},addTile:function(bounds,position){return new OpenLayers.Tile.Image(this,position,bounds,null,this.tileSize);},CLASS_NAME:"OpenLayers.Layer.ArcGIS93Rest"});OpenLayers.Layer.KaMap=OpenLayers.Class(OpenLayers.Layer.Grid,{isBaseLayer:true,units:null,resolution:OpenLayers.DOTS_PER_INCH,DEFAULT_PARAMS:{i:'jpeg',map:''},initialize:function(name,url,params,options){var newArguments=[];newArguments.push(name,url,params,options);OpenLayers.Layer.Grid.prototype.initialize.apply(this,newArguments);this.params=OpenLayers.Util.applyDefaults(this.params,this.DEFAULT_PARAMS);},getURL:function(bounds){bounds=this.adjustBounds(bounds);var mapRes=this.map.getResolution();var scale=Math.round((this.map.getScale()*10000))/10000;var pX=Math.round(bounds.left/mapRes);var pY=-Math.round(bounds.top/mapRes);return this.getFullRequestString({t:pY,l:pX,s:scale});},addTile:function(bounds,position){var url=this.getURL(bounds);return new OpenLayers.Tile.Image(this,position,bounds,url,this.tileSize);},calculateGridLayout:function(bounds,extent,resolution){var tilelon=resolution*this.tileSize.w;var tilelat=resolution*this.tileSize.h;var offsetlon=bounds.left;var tilecol=Math.floor(offsetlon/tilelon)-this.buffer;var tilecolremain=offsetlon/tilelon-tilecol;var tileoffsetx=-tilecolremain*this.tileSize.w;var tileoffsetlon=tilecol*tilelon;var offsetlat=bounds.top;var tilerow=Math.ceil(offsetlat/tilelat)+this.buffer;var tilerowremain=tilerow-offsetlat/tilelat;var tileoffsety=-(tilerowremain+1)*this.tileSize.h;var tileoffsetlat=tilerow*tilelat;return{tilelon:tilelon,tilelat:tilelat,tileoffsetlon:tileoffsetlon,tileoffsetlat:tileoffsetlat,tileoffsetx:tileoffsetx,tileoffsety:tileoffsety};},clone:function(obj){if(obj==null){obj=new OpenLayers.Layer.KaMap(this.name,this.url,this.params,this.options);} +obj=OpenLayers.Layer.Grid.prototype.clone.apply(this,[obj]);if(this.tileSize!=null){obj.tileSize=this.tileSize.clone();} +obj.grid=[];return obj;},getTileBounds:function(viewPortPx){var resolution=this.getResolution();var tileMapWidth=resolution*this.tileSize.w;var tileMapHeight=resolution*this.tileSize.h;var mapPoint=this.getLonLatFromViewPortPx(viewPortPx);var tileLeft=tileMapWidth*Math.floor(mapPoint.lon/tileMapWidth);var tileBottom=tileMapHeight*Math.floor(mapPoint.lat/tileMapHeight);return new OpenLayers.Bounds(tileLeft,tileBottom,tileLeft+tileMapWidth,tileBottom+tileMapHeight);},CLASS_NAME:"OpenLayers.Layer.KaMap"});OpenLayers.Layer.MapGuide=OpenLayers.Class(OpenLayers.Layer.Grid,{isBaseLayer:true,useHttpTile:false,singleTile:false,useOverlay:false,useAsyncOverlay:true,TILE_PARAMS:{operation:'GETTILEIMAGE',version:'1.2.0'},SINGLE_TILE_PARAMS:{operation:'GETMAPIMAGE',format:'PNG',locale:'en',clip:'1',version:'1.0.0'},OVERLAY_PARAMS:{operation:'GETDYNAMICMAPOVERLAYIMAGE',format:'PNG',locale:'en',clip:'1',version:'2.0.0'},FOLDER_PARAMS:{tileColumnsPerFolder:30,tileRowsPerFolder:30,format:'png',querystring:null},defaultSize:new OpenLayers.Size(300,300),initialize:function(name,url,params,options){OpenLayers.Layer.Grid.prototype.initialize.apply(this,arguments);if(options==null||options.isBaseLayer==null){this.isBaseLayer=((this.transparent!="true")&&(this.transparent!=true));} +if(options&&options.useOverlay!=null){this.useOverlay=options.useOverlay;} +if(this.singleTile){if(this.useOverlay){OpenLayers.Util.applyDefaults(this.params,this.OVERLAY_PARAMS);if(!this.useAsyncOverlay){this.params.version="1.0.0";}}else{OpenLayers.Util.applyDefaults(this.params,this.SINGLE_TILE_PARAMS);}}else{if(this.useHttpTile){OpenLayers.Util.applyDefaults(this.params,this.FOLDER_PARAMS);}else{OpenLayers.Util.applyDefaults(this.params,this.TILE_PARAMS);} +this.setTileSize(this.defaultSize);}},clone:function(obj){if(obj==null){obj=new OpenLayers.Layer.MapGuide(this.name,this.url,this.params,this.options);} +obj=OpenLayers.Layer.Grid.prototype.clone.apply(this,[obj]);return obj;},addTile:function(bounds,position){return new OpenLayers.Tile.Image(this,position,bounds,null,this.tileSize);},getURL:function(bounds){var url;var center=bounds.getCenterLonLat();var mapSize=this.map.getCurrentSize();if(this.singleTile){var params={setdisplaydpi:OpenLayers.DOTS_PER_INCH,setdisplayheight:mapSize.h*this.ratio,setdisplaywidth:mapSize.w*this.ratio,setviewcenterx:center.lon,setviewcentery:center.lat,setviewscale:this.map.getScale()};if(this.useOverlay&&!this.useAsyncOverlay){var getVisParams={};getVisParams=OpenLayers.Util.extend(getVisParams,params);getVisParams.operation="GETVISIBLEMAPEXTENT";getVisParams.version="1.0.0";getVisParams.session=this.params.session;getVisParams.mapName=this.params.mapName;getVisParams.format='text/xml';url=this.getFullRequestString(getVisParams);OpenLayers.Request.GET({url:url,async:false});} +url=this.getFullRequestString(params);}else{var currentRes=this.map.getResolution();var colidx=Math.floor((bounds.left-this.maxExtent.left)/currentRes);colidx=Math.round(colidx/this.tileSize.w);var rowidx=Math.floor((this.maxExtent.top-bounds.top)/currentRes);rowidx=Math.round(rowidx/this.tileSize.h);if(this.useHttpTile){url=this.getImageFilePath({tilecol:colidx,tilerow:rowidx,scaleindex:this.resolutions.length-this.map.zoom-1});}else{url=this.getFullRequestString({tilecol:colidx,tilerow:rowidx,scaleindex:this.resolutions.length-this.map.zoom-1});}} +return url;},getFullRequestString:function(newParams,altUrl){var url=(altUrl==null)?this.url:altUrl;if(typeof url=="object"){url=url[Math.floor(Math.random()*url.length)];} +var requestString=url;var allParams=OpenLayers.Util.extend({},this.params);allParams=OpenLayers.Util.extend(allParams,newParams);var urlParams=OpenLayers.Util.upperCaseObject(OpenLayers.Util.getArgs(url));for(var key in allParams){if(key.toUpperCase()in urlParams){delete allParams[key];}} +var paramsString=OpenLayers.Util.getParameterString(allParams);paramsString=paramsString.replace(/,/g,"+");if(paramsString!=""){var lastServerChar=url.charAt(url.length-1);if((lastServerChar=="&")||(lastServerChar=="?")){requestString+=paramsString;}else{if(url.indexOf('?')==-1){requestString+='?'+paramsString;}else{requestString+='&'+paramsString;}}} +return requestString;},getImageFilePath:function(newParams,altUrl){var url=(altUrl==null)?this.url:altUrl;if(typeof url=="object"){url=url[Math.floor(Math.random()*url.length)];} +var requestString=url;var tileRowGroup="";var tileColGroup="";if(newParams.tilerow<0){tileRowGroup='-';} +if(newParams.tilerow==0){tileRowGroup+='0';}else{tileRowGroup+=Math.floor(Math.abs(newParams.tilerow/this.params.tileRowsPerFolder))*this.params.tileRowsPerFolder;} +if(newParams.tilecol<0){tileColGroup='-';} +if(newParams.tilecol==0){tileColGroup+='0';}else{tileColGroup+=Math.floor(Math.abs(newParams.tilecol/this.params.tileColumnsPerFolder))*this.params.tileColumnsPerFolder;} +var tilePath='/S'+Math.floor(newParams.scaleindex) ++'/'+this.params.basemaplayergroupname ++'/R'+tileRowGroup ++'/C'+tileColGroup ++'/'+(newParams.tilerow%this.params.tileRowsPerFolder) ++'_'+(newParams.tilecol%this.params.tileColumnsPerFolder) ++'.'+this.params.format;if(this.params.querystring){tilePath+="?"+this.params.querystring;} +requestString+=tilePath;return requestString;},calculateGridLayout:function(bounds,extent,resolution){var tilelon=resolution*this.tileSize.w;var tilelat=resolution*this.tileSize.h;var offsetlon=bounds.left-extent.left;var tilecol=Math.floor(offsetlon/tilelon)-this.buffer;var tilecolremain=offsetlon/tilelon-tilecol;var tileoffsetx=-tilecolremain*this.tileSize.w;var tileoffsetlon=extent.left+tilecol*tilelon;var offsetlat=extent.top-bounds.top+tilelat;var tilerow=Math.floor(offsetlat/tilelat)-this.buffer;var tilerowremain=tilerow-offsetlat/tilelat;var tileoffsety=tilerowremain*this.tileSize.h;var tileoffsetlat=extent.top-tilelat*tilerow;return{tilelon:tilelon,tilelat:tilelat,tileoffsetlon:tileoffsetlon,tileoffsetlat:tileoffsetlat,tileoffsetx:tileoffsetx,tileoffsety:tileoffsety};},CLASS_NAME:"OpenLayers.Layer.MapGuide"});OpenLayers.Layer.MapServer=OpenLayers.Class(OpenLayers.Layer.Grid,{DEFAULT_PARAMS:{mode:"map",map_imagetype:"png"},initialize:function(name,url,params,options){var newArguments=[];newArguments.push(name,url,params,options);OpenLayers.Layer.Grid.prototype.initialize.apply(this,newArguments);this.params=OpenLayers.Util.applyDefaults(this.params,this.DEFAULT_PARAMS);if(options==null||options.isBaseLayer==null){this.isBaseLayer=((this.params.transparent!="true")&&(this.params.transparent!=true));}},clone:function(obj){if(obj==null){obj=new OpenLayers.Layer.MapServer(this.name,this.url,this.params,this.options);} +obj=OpenLayers.Layer.Grid.prototype.clone.apply(this,[obj]);return obj;},addTile:function(bounds,position){return new OpenLayers.Tile.Image(this,position,bounds,null,this.tileSize);},getURL:function(bounds){bounds=this.adjustBounds(bounds);var extent=[bounds.left,bounds.bottom,bounds.right,bounds.top];var imageSize=this.getImageSize();var url=this.getFullRequestString({mapext:extent,imgext:extent,map_size:[imageSize.w,imageSize.h],imgx:imageSize.w/2,imgy:imageSize.h/2,imgxy:[imageSize.w,imageSize.h]});return url;},getFullRequestString:function(newParams,altUrl){var url=(altUrl==null)?this.url:altUrl;var allParams=OpenLayers.Util.extend({},this.params);allParams=OpenLayers.Util.extend(allParams,newParams);var paramsString=OpenLayers.Util.getParameterString(allParams);if(url instanceof Array){url=this.selectUrl(paramsString,url);} +var urlParams=OpenLayers.Util.upperCaseObject(OpenLayers.Util.getParameters(url));for(var key in allParams){if(key.toUpperCase()in urlParams){delete allParams[key];}} +paramsString=OpenLayers.Util.getParameterString(allParams);var requestString=url;paramsString=paramsString.replace(/,/g,"+");if(paramsString!=""){var lastServerChar=url.charAt(url.length-1);if((lastServerChar=="&")||(lastServerChar=="?")){requestString+=paramsString;}else{if(url.indexOf('?')==-1){requestString+='?'+paramsString;}else{requestString+='&'+paramsString;}}} +return requestString;},CLASS_NAME:"OpenLayers.Layer.MapServer"});OpenLayers.Layer.TMS=OpenLayers.Class(OpenLayers.Layer.Grid,{serviceVersion:"1.0.0",isBaseLayer:true,tileOrigin:null,serverResolutions:null,initialize:function(name,url,options){var newArguments=[];newArguments.push(name,url,{},options);OpenLayers.Layer.Grid.prototype.initialize.apply(this,newArguments);},destroy:function(){OpenLayers.Layer.Grid.prototype.destroy.apply(this,arguments);},clone:function(obj){if(obj==null){obj=new OpenLayers.Layer.TMS(this.name,this.url,this.options);} +obj=OpenLayers.Layer.Grid.prototype.clone.apply(this,[obj]);return obj;},getURL:function(bounds){bounds=this.adjustBounds(bounds);var res=this.map.getResolution();var x=Math.round((bounds.left-this.tileOrigin.lon)/(res*this.tileSize.w));var y=Math.round((bounds.bottom-this.tileOrigin.lat)/(res*this.tileSize.h));var z=this.serverResolutions!=null?OpenLayers.Util.indexOf(this.serverResolutions,res):this.map.getZoom();var path=this.serviceVersion+"/"+this.layername+"/"+z+"/"+x+"/"+y+"."+this.type;var url=this.url;if(url instanceof Array){url=this.selectUrl(path,url);} +return url+path;},addTile:function(bounds,position){return new OpenLayers.Tile.Image(this,position,bounds,null,this.tileSize);},setMap:function(map){OpenLayers.Layer.Grid.prototype.setMap.apply(this,arguments);if(!this.tileOrigin){this.tileOrigin=new OpenLayers.LonLat(this.map.maxExtent.left,this.map.maxExtent.bottom);}},CLASS_NAME:"OpenLayers.Layer.TMS"});OpenLayers.Layer.TileCache=OpenLayers.Class(OpenLayers.Layer.Grid,{isBaseLayer:true,format:'image/png',serverResolutions:null,initialize:function(name,url,layername,options){this.layername=layername;OpenLayers.Layer.Grid.prototype.initialize.apply(this,[name,url,{},options]);this.extension=this.format.split('/')[1].toLowerCase();this.extension=(this.extension=='jpg')?'jpeg':this.extension;},clone:function(obj){if(obj==null){obj=new OpenLayers.Layer.TileCache(this.name,this.url,this.layername,this.options);} +obj=OpenLayers.Layer.Grid.prototype.clone.apply(this,[obj]);return obj;},getURL:function(bounds){var res=this.map.getResolution();var bbox=this.maxExtent;var size=this.tileSize;var tileX=Math.round((bounds.left-bbox.left)/(res*size.w));var tileY=Math.round((bounds.bottom-bbox.bottom)/(res*size.h));var tileZ=this.serverResolutions!=null?OpenLayers.Util.indexOf(this.serverResolutions,res):this.map.getZoom();function zeroPad(number,length){number=String(number);var zeros=[];for(var i=0;iOpenStreetMap",sphericalMercator:true,url:'http://tile.openstreetmap.org/${z}/${x}/${y}.png',CLASS_NAME:"OpenLayers.Layer.OSM"});OpenLayers.Protocol.SQL.Gears=OpenLayers.Class(OpenLayers.Protocol.SQL,{FID_PREFIX:'__gears_fid__',NULL_GEOMETRY:'__gears_null_geometry__',NULL_FEATURE_STATE:'__gears_null_feature_state__',jsonParser:null,wktParser:null,fidRegExp:null,saveFeatureState:true,typeOfFid:"string",db:null,initialize:function(options){if(!this.supported()){return;} +OpenLayers.Protocol.SQL.prototype.initialize.apply(this,[options]);this.jsonParser=new OpenLayers.Format.JSON();this.wktParser=new OpenLayers.Format.WKT();this.fidRegExp=new RegExp('^'+this.FID_PREFIX);this.initializeDatabase();},initializeDatabase:function(){this.db=google.gears.factory.create('beta.database');this.db.open(this.databaseName);this.db.execute("CREATE TABLE IF NOT EXISTS "+this.tableName+" (fid TEXT UNIQUE, geometry TEXT, properties TEXT,"+" state TEXT)");},destroy:function(){this.db.close();this.db=null;this.jsonParser=null;this.wktParser=null;OpenLayers.Protocol.SQL.prototype.destroy.apply(this);},supported:function(){return!!(window.google&&google.gears);},read:function(options){options=OpenLayers.Util.applyDefaults(options,this.options);var feature,features=[];var rs=this.db.execute("SELECT * FROM "+this.tableName);while(rs.isValidRow()){feature=this.unfreezeFeature(rs);if(this.evaluateFilter(feature,options.filter)){if(!options.noFeatureStateReset){feature.state=null;} +features.push(feature);} +rs.next();} +rs.close();var resp=new OpenLayers.Protocol.Response({code:OpenLayers.Protocol.Response.SUCCESS,requestType:"read",features:features});if(options&&options.callback){options.callback.call(options.scope,resp);} +return resp;},unfreezeFeature:function(row){var feature;var wkt=row.fieldByName('geometry');if(wkt==this.NULL_GEOMETRY){feature=new OpenLayers.Feature.Vector();}else{feature=this.wktParser.read(wkt);} +feature.attributes=this.jsonParser.read(row.fieldByName('properties'));feature.fid=this.extractFidFromField(row.fieldByName('fid'));var state=row.fieldByName('state');if(state==this.NULL_FEATURE_STATE){state=null;} +feature.state=state;return feature;},extractFidFromField:function(field){if(!field.match(this.fidRegExp)&&this.typeOfFid=="number"){field=parseFloat(field);} +return field;},create:function(features,options){options=OpenLayers.Util.applyDefaults(options,this.options);var resp=this.createOrUpdate(features);resp.requestType="create";if(options&&options.callback){options.callback.call(options.scope,resp);} +return resp;},update:function(features,options){options=OpenLayers.Util.applyDefaults(options,this.options);var resp=this.createOrUpdate(features);resp.requestType="update";if(options&&options.callback){options.callback.call(options.scope,resp);} +return resp;},createOrUpdate:function(features){if(!(features instanceof Array)){features=[features];} +var i,len=features.length,feature;var insertedFeatures=new Array(len);for(i=0;i=0;i--){feature=features[i];switch(feature.state){case OpenLayers.State.INSERT:toCreate.push(feature);break;case OpenLayers.State.UPDATE:toUpdate.push(feature);break;case OpenLayers.State.DELETE:toDelete.push(feature);break;}} +if(toCreate.length>0){nRequests++;opt=OpenLayers.Util.applyDefaults({"callback":callback,"scope":this},options.create);resp.push(this.create(toCreate,opt));} +if(toUpdate.length>0){nRequests++;opt=OpenLayers.Util.applyDefaults({"callback":callback,"scope":this},options.update);resp.push(this.update(toUpdate,opt));} +if(toDelete.length>0){nRequests++;opt=OpenLayers.Util.applyDefaults({"callback":callback,"scope":this},options["delete"]);resp.push(this["delete"](toDelete,opt));} +return resp;},clear:function(){this.db.execute("DELETE FROM "+this.tableName);},callUserCallback:function(options,resp){var opt=options[resp.requestType];if(opt&&opt.callback){opt.callback.call(opt.scope,resp);} +if(resp.last&&options.callback){options.callback.call(options.scope);}},CLASS_NAME:"OpenLayers.Protocol.SQL.Gears"});OpenLayers.Rule=OpenLayers.Class({id:null,name:'default',title:null,description:null,context:null,filter:null,elseFilter:false,symbolizer:null,minScaleDenominator:null,maxScaleDenominator:null,initialize:function(options){this.symbolizer={};OpenLayers.Util.extend(this,options);this.id=OpenLayers.Util.createUniqueID(this.CLASS_NAME+"_");},destroy:function(){for(var i in this.symbolizer){this.symbolizer[i]=null;} +this.symbolizer=null;},evaluate:function(feature){var context=this.getContext(feature);var applies=true;if(this.minScaleDenominator||this.maxScaleDenominator){var scale=feature.layer.map.getScale();} +if(this.minScaleDenominator){applies=scale>=OpenLayers.Style.createLiteral(this.minScaleDenominator,context);} +if(applies&&this.maxScaleDenominator){applies=scalethis.value;break;case OpenLayers.Filter.Comparison.LESS_THAN_OR_EQUAL_TO:result=context[this.property]<=this.value;break;case OpenLayers.Filter.Comparison.GREATER_THAN_OR_EQUAL_TO:result=context[this.property]>=this.value;break;case OpenLayers.Filter.Comparison.BETWEEN:result=(context[this.property]>=this.lowerBoundary)&&(context[this.property]<=this.upperBoundary);break;case OpenLayers.Filter.Comparison.LIKE:var regexp=new RegExp(this.value,"gi");result=regexp.test(context[this.property]);break;} +return result;},value2regex:function(wildCard,singleChar,escapeChar){if(wildCard=="."){var msg="'.' is an unsupported wildCard character for "+"OpenLayers.Filter.Comparison";OpenLayers.Console.error(msg);return null;} +wildCard=wildCard?wildCard:"*";singleChar=singleChar?singleChar:".";escapeChar=escapeChar?escapeChar:"!";this.value=this.value.replace(new RegExp("\\"+escapeChar+"(.|$)","g"),"\\$1");this.value=this.value.replace(new RegExp("\\"+singleChar,"g"),".");this.value=this.value.replace(new RegExp("\\"+wildCard,"g"),".*");this.value=this.value.replace(new RegExp("\\\\.\\*","g"),"\\"+wildCard);this.value=this.value.replace(new RegExp("\\\\\\.","g"),"\\"+singleChar);return this.value;},regex2value:function(){var value=this.value;value=value.replace(/!/g,"!!");value=value.replace(/(\\)?\\\./g,function($0,$1){return $1?$0:"!.";});value=value.replace(/(\\)?\\\*/g,function($0,$1){return $1?$0:"!*";});value=value.replace(/\\\\/g,"\\");value=value.replace(/\.\*/g,"*");return value;},clone:function(){return OpenLayers.Util.extend(new OpenLayers.Filter.Comparison(),this);},CLASS_NAME:"OpenLayers.Filter.Comparison"});OpenLayers.Filter.Comparison.EQUAL_TO="==";OpenLayers.Filter.Comparison.NOT_EQUAL_TO="!=";OpenLayers.Filter.Comparison.LESS_THAN="<";OpenLayers.Filter.Comparison.GREATER_THAN=">";OpenLayers.Filter.Comparison.LESS_THAN_OR_EQUAL_TO="<=";OpenLayers.Filter.Comparison.GREATER_THAN_OR_EQUAL_TO=">=";OpenLayers.Filter.Comparison.BETWEEN="..";OpenLayers.Filter.Comparison.LIKE="~";OpenLayers.Filter.FeatureId=OpenLayers.Class(OpenLayers.Filter,{fids:null,initialize:function(options){this.fids=[];OpenLayers.Filter.prototype.initialize.apply(this,[options]);},evaluate:function(feature){for(var i=0,len=this.fids.length;i0){this.setBounds(this.components[0].getBounds());for(var i=1,len=this.components.length;i-1)){if(index!=null&&(index=0;--i){this.removeComponent(components[i]);}},removeComponent:function(component){OpenLayers.Util.removeItem(this.components,component);this.clearBounds();},getLength:function(){var length=0.0;for(var i=0,len=this.components.length;i=0;i--){if(i!=0&&features[i-1].geometry){this.renderer.locked=true;}else{this.renderer.locked=false;} +var feature=features[i];delete this.unrenderedFeatures[feature.id];if(notify){this.events.triggerEvent("beforefeatureremoved",{feature:feature});} +this.features=OpenLayers.Util.removeItem(this.features,feature);feature.layer=null;if(feature.geometry){this.renderer.eraseFeatures(feature);} +if(OpenLayers.Util.indexOf(this.selectedFeatures,feature)!=-1){OpenLayers.Util.removeItem(this.selectedFeatures,feature);} +if(notify){this.events.triggerEvent("featureremoved",{feature:feature});}} +if(notify){this.events.triggerEvent("featuresremoved",{features:features});}},destroyFeatures:function(features,options){var all=(features==undefined);if(all){features=this.features;} +if(features){this.removeFeatures(features,options);for(var i=features.length-1;i>=0;i--){features[i].destroy();}}},drawFeature:function(feature,style){if(!this.drawn){return} +if(typeof style!="object"){if(!style&&feature.state===OpenLayers.State.DELETE){style="delete";} +var renderIntent=style||feature.renderIntent;style=feature.style||this.style;if(!style){style=this.styleMap.createSymbolizer(feature,renderIntent);}} +if(!this.renderer.drawFeature(feature,style)){this.unrenderedFeatures[feature.id]=feature;}else{delete this.unrenderedFeatures[feature.id];};},eraseFeatures:function(features){this.renderer.eraseFeatures(features);},getFeatureFromEvent:function(evt){if(!this.renderer){OpenLayers.Console.error(OpenLayers.i18n("getFeatureError"));return null;} +var featureId=this.renderer.getFeatureIdFromEvent(evt);return this.getFeatureById(featureId);},getFeatureById:function(featureId){var feature=null;for(var i=0,len=this.features.length;i0)){maxExtent=new OpenLayers.Bounds();for(var i=0,len=this.features.length;i=0;--i){feature=this.features[i];if(!options||options.except!=feature){this.unselect(feature);}}},selectSingle:function(evt){OpenLayers.Element.addClass(this.map.viewPortDiv,"olCursorWait");var bounds=this.pixelToBounds(evt.xy);this.setModifiers(evt);this.request(bounds,{single:true});},selectBox:function(position){if(position instanceof OpenLayers.Bounds){var minXY=this.map.getLonLatFromPixel(new OpenLayers.Pixel(position.left,position.bottom));var maxXY=this.map.getLonLatFromPixel(new OpenLayers.Pixel(position.right,position.top));var bounds=new OpenLayers.Bounds(minXY.lon,minXY.lat,maxXY.lon,maxXY.lat);this.setModifiers(this.handlers.box.dragHandler.evt);this.request(bounds);}},selectHover:function(evt){var bounds=this.pixelToBounds(evt.xy);this.request(bounds,{single:true,hover:true});},cancelHover:function(){if(this.hoverResponse){this.protocol.abort(this.hoverResponse);this.hoverResponse=null;}},request:function(bounds,options){options=options||{};var filter=new OpenLayers.Filter.Spatial({type:OpenLayers.Filter.Spatial.BBOX,value:bounds});var response=this.protocol.read({maxFeatures:options.single==true?this.maxFeatures:undefined,filter:filter,callback:function(result){if(result.code==1){if(result.features.length){if(options.single==true){this.selectBestFeature(result.features,bounds.getCenterLonLat(),options);}else{this.select(result.features);}}else if(options.hover){this.hoverSelect();}else{this.events.triggerEvent("clickout");if(this.clickout){this.unselectAll();}}} +OpenLayers.Element.removeClass(this.map.viewPortDiv,"olCursorWait");},scope:this});if(options.hover==true){this.hoverResponse=response;}},selectBestFeature:function(features,clickPosition,options){options=options||{};if(features.length){var point=new OpenLayers.Geometry.Point(clickPosition.lon,clickPosition.lat);var feature,resultFeature,dist;var minDist=Number.MAX_VALUE;for(var i=0;i=0;--i){target=this.targets[i];if(target.layer===layer){this.removeTarget(target);}}},removeTarget:function(target){return OpenLayers.Util.removeItem(this.targets,target);},activate:function(){var activated=OpenLayers.Control.prototype.activate.call(this);if(activated){if(this.layer&&this.layer.events){this.layer.events.on({sketchstarted:this.onSketchModified,sketchmodified:this.onSketchModified,vertexmodified:this.onVertexModified,scope:this});}} +return activated;},deactivate:function(){var deactivated=OpenLayers.Control.prototype.deactivate.call(this);if(deactivated){if(this.layer&&this.layer.events){this.layer.events.un({sketchstarted:this.onSketchModified,sketchmodified:this.onSketchModified,vertexmodified:this.onVertexModified,scope:this});}} +this.feature=null;this.point=null;return deactivated;},onSketchModified:function(event){this.feature=event.feature;this.considerSnapping(event.vertex,event.vertex);},onVertexModified:function(event){this.feature=event.feature;var loc=this.layer.map.getLonLatFromViewPortPx(event.pixel);this.considerSnapping(event.vertex,new OpenLayers.Geometry.Point(loc.lon,loc.lat));},considerSnapping:function(point,loc){var best={rank:Number.POSITIVE_INFINITY,dist:Number.POSITIVE_INFINITY,x:null,y:null};var snapped=false;var result,target;for(var i=0,len=this.targets.length;i0){var attributes=(this.dataFrom!=null)?(pointFeatures[i+this.dataFrom].data||pointFeatures[i+this.dataFrom].attributes):null;var line=new OpenLayers.Geometry.LineString([startPoint,endPoint]);lines[i-1]=new OpenLayers.Feature.Vector(line,attributes);} +startPoint=endPoint;} +this.addFeatures(lines);},CLASS_NAME:"OpenLayers.Layer.PointTrack"});OpenLayers.Layer.PointTrack.dataFrom={'SOURCE_NODE':-1,'TARGET_NODE':0};OpenLayers.Layer.Vector.RootContainer=OpenLayers.Class(OpenLayers.Layer.Vector,{displayInLayerSwitcher:false,layers:null,initialize:function(name,options){OpenLayers.Layer.Vector.prototype.initialize.apply(this,arguments);},display:function(){},getFeatureFromEvent:function(evt){var layers=this.layers;var feature;for(var i=0;i=this.resFactor||ratio<=(1/this.resFactor));} +return invalid;},calculateBounds:function(mapBounds){if(!mapBounds){mapBounds=this.getMapBounds();} +var center=mapBounds.getCenterLonLat();var dataWidth=mapBounds.getWidth()*this.ratio;var dataHeight=mapBounds.getHeight()*this.ratio;this.bounds=new OpenLayers.Bounds(center.lon-(dataWidth/2),center.lat-(dataHeight/2),center.lon+(dataWidth/2),center.lat+(dataHeight/2));},triggerRead:function(){this.layer.protocol.abort(this.response);this.layer.events.triggerEvent("loadstart");this.response=this.layer.protocol.read({filter:this.createFilter(),callback:this.merge,scope:this});},createFilter:function(){var filter=new OpenLayers.Filter.Spatial({type:OpenLayers.Filter.Spatial.BBOX,value:this.bounds,projection:this.layer.projection});if(this.layer.filter){filter=new OpenLayers.Filter.Logical({type:OpenLayers.Filter.Logical.AND,filters:[this.layer.filter,filter]});} +return filter;},merge:function(resp){this.layer.destroyFeatures();var features=resp.features;if(features&&features.length>0){var remote=this.layer.projection;var local=this.layer.map.getProjectionObject();if(!local.equals(remote)){var geom;for(var i=0,len=features.length;i=0;--i){feature=layer.selectedFeatures[i];if(!options||options.except!=feature){this.unselect(feature);}}}},clickFeature:function(feature){if(!this.hover){var selected=(OpenLayers.Util.indexOf(feature.layer.selectedFeatures,feature)>-1);if(selected){if(this.toggleSelect()){this.unselect(feature);}else if(!this.multipleSelect()){this.unselectAll({except:feature});}}else{if(!this.multipleSelect()){this.unselectAll({except:feature});} +this.select(feature);}}},multipleSelect:function(){return this.multiple||(this.handlers.feature.evt&&this.handlers.feature.evt[this.multipleKey]);},toggleSelect:function(){return this.toggle||(this.handlers.feature.evt&&this.handlers.feature.evt[this.toggleKey]);},clickoutFeature:function(feature){if(!this.hover&&this.clickout){this.unselectAll();}},overFeature:function(feature){var layer=feature.layer;if(this.hover){if(this.highlightOnly){this.highlight(feature);}else if(OpenLayers.Util.indexOf(layer.selectedFeatures,feature)==-1){this.select(feature);}}},outFeature:function(feature){if(this.hover){if(this.highlightOnly){if(feature._lastHighlighter==this.id){if(feature._prevHighlighter&&feature._prevHighlighter!=this.id){delete feature._lastHighlighter;var control=this.map.getControl(feature._prevHighlighter);if(control){control.highlight(feature);}}else{this.unhighlight(feature);}}}else{this.unselect(feature);}}},highlight:function(feature){var layer=feature.layer;var cont=this.events.triggerEvent("beforefeaturehighlighted",{feature:feature});if(cont!==false){feature._prevHighlighter=feature._lastHighlighter;feature._lastHighlighter=this.id;var style=this.selectStyle||this.renderIntent;layer.drawFeature(feature,style);this.events.triggerEvent("featurehighlighted",{feature:feature});}},unhighlight:function(feature){var layer=feature.layer;feature._lastHighlighter=feature._prevHighlighter;delete feature._prevHighlighter;layer.drawFeature(feature,feature.style||feature.layer.style||"default");this.events.triggerEvent("featureunhighlighted",{feature:feature});},select:function(feature){var cont=this.onBeforeSelect.call(this.scope,feature);var layer=feature.layer;if(cont!==false){cont=layer.events.triggerEvent("beforefeatureselected",{feature:feature});if(cont!==false){layer.selectedFeatures.push(feature);this.highlight(feature);layer.events.triggerEvent("featureselected",{feature:feature});this.onSelect.call(this.scope,feature);}}},unselect:function(feature){var layer=feature.layer;this.unhighlight(feature);OpenLayers.Util.removeItem(layer.selectedFeatures,feature);layer.events.triggerEvent("featureunselected",{feature:feature});this.onUnselect.call(this.scope,feature);},selectBox:function(position){if(position instanceof OpenLayers.Bounds){var minXY=this.map.getLonLatFromPixel(new OpenLayers.Pixel(position.left,position.bottom));var maxXY=this.map.getLonLatFromPixel(new OpenLayers.Pixel(position.right,position.top));var bounds=new OpenLayers.Bounds(minXY.lon,minXY.lat,maxXY.lon,maxXY.lat);if(!this.multipleSelect()){this.unselectAll();} +var prevMultiple=this.multiple;this.multiple=true;var layers=this.layers||[this.layer];var layer;for(var l=0;l-1){if(bounds.toGeometry().intersects(feature.geometry)){if(OpenLayers.Util.indexOf(layer.selectedFeatures,feature)==-1){this.select(feature);}}}}} +this.multiple=prevMultiple;}},setMap:function(map){this.handlers.feature.setMap(map);if(this.box){this.handlers.box.setMap(map);} +OpenLayers.Control.prototype.setMap.apply(this,arguments);},CLASS_NAME:"OpenLayers.Control.SelectFeature"});OpenLayers.Format.Filter.v1=OpenLayers.Class(OpenLayers.Format.XML,{namespaces:{ogc:"http://www.opengis.net/ogc",gml:"http://www.opengis.net/gml",xlink:"http://www.w3.org/1999/xlink",xsi:"http://www.w3.org/2001/XMLSchema-instance"},defaultPrefix:"ogc",schemaLocation:null,initialize:function(options){OpenLayers.Format.XML.prototype.initialize.apply(this,[options]);},read:function(data){var obj={};this.readers.ogc["Filter"].apply(this,[data,obj]);return obj.filter;},readers:{"ogc":{"Filter":function(node,parent){var obj={fids:[],filters:[]};this.readChildNodes(node,obj);if(obj.fids.length>0){parent.filter=new OpenLayers.Filter.FeatureId({fids:obj.fids});}else if(obj.filters.length>0){parent.filter=obj.filters[0];}},"FeatureId":function(node,obj){var fid=node.getAttribute("fid");if(fid){obj.fids.push(fid);}},"And":function(node,obj){var filter=new OpenLayers.Filter.Logical({type:OpenLayers.Filter.Logical.AND});this.readChildNodes(node,filter);obj.filters.push(filter);},"Or":function(node,obj){var filter=new OpenLayers.Filter.Logical({type:OpenLayers.Filter.Logical.OR});this.readChildNodes(node,filter);obj.filters.push(filter);},"Not":function(node,obj){var filter=new OpenLayers.Filter.Logical({type:OpenLayers.Filter.Logical.NOT});this.readChildNodes(node,filter);obj.filters.push(filter);},"PropertyIsLessThan":function(node,obj){var filter=new OpenLayers.Filter.Comparison({type:OpenLayers.Filter.Comparison.LESS_THAN});this.readChildNodes(node,filter);obj.filters.push(filter);},"PropertyIsGreaterThan":function(node,obj){var filter=new OpenLayers.Filter.Comparison({type:OpenLayers.Filter.Comparison.GREATER_THAN});this.readChildNodes(node,filter);obj.filters.push(filter);},"PropertyIsLessThanOrEqualTo":function(node,obj){var filter=new OpenLayers.Filter.Comparison({type:OpenLayers.Filter.Comparison.LESS_THAN_OR_EQUAL_TO});this.readChildNodes(node,filter);obj.filters.push(filter);},"PropertyIsGreaterThanOrEqualTo":function(node,obj){var filter=new OpenLayers.Filter.Comparison({type:OpenLayers.Filter.Comparison.GREATER_THAN_OR_EQUAL_TO});this.readChildNodes(node,filter);obj.filters.push(filter);},"PropertyIsBetween":function(node,obj){var filter=new OpenLayers.Filter.Comparison({type:OpenLayers.Filter.Comparison.BETWEEN});this.readChildNodes(node,filter);obj.filters.push(filter);},"PropertyIsLike":function(node,obj){var filter=new OpenLayers.Filter.Comparison({type:OpenLayers.Filter.Comparison.LIKE});this.readChildNodes(node,filter);var wildCard=node.getAttribute("wildCard");var singleChar=node.getAttribute("singleChar");var esc=node.getAttribute("escape");filter.value2regex(wildCard,singleChar,esc);obj.filters.push(filter);},"Literal":function(node,obj){obj.value=OpenLayers.String.numericIf(this.getChildValue(node));},"PropertyName":function(node,filter){filter.property=this.getChildValue(node);},"LowerBoundary":function(node,filter){filter.lowerBoundary=OpenLayers.String.numericIf(this.readOgcExpression(node));},"UpperBoundary":function(node,filter){filter.upperBoundary=OpenLayers.String.numericIf(this.readOgcExpression(node));},"Intersects":function(node,obj){this.readSpatial(node,obj,OpenLayers.Filter.Spatial.INTERSECTS);},"Within":function(node,obj){this.readSpatial(node,obj,OpenLayers.Filter.Spatial.WITHIN);},"Contains":function(node,obj){this.readSpatial(node,obj,OpenLayers.Filter.Spatial.CONTAINS);},"DWithin":function(node,obj){this.readSpatial(node,obj,OpenLayers.Filter.Spatial.DWITHIN);},"Distance":function(node,obj){obj.distance=parseInt(this.getChildValue(node));obj.distanceUnits=node.getAttribute("units");}}},readSpatial:function(node,obj,type){var filter=new OpenLayers.Filter.Spatial({type:type});this.readChildNodes(node,filter);filter.value=filter.components[0];delete filter.components;obj.filters.push(filter);},readOgcExpression:function(node){var obj={};this.readChildNodes(node,obj);var value=obj.value;if(!value){value=this.getChildValue(node);} +return value;},write:function(filter){return this.writers.ogc["Filter"].apply(this,[filter]);},writers:{"ogc":{"Filter":function(filter){var node=this.createElementNSPlus("ogc:Filter");var sub=filter.CLASS_NAME.split(".").pop();if(sub=="FeatureId"){for(var i=0;i":"PropertyIsGreaterThan","<=":"PropertyIsLessThanOrEqualTo",">=":"PropertyIsGreaterThanOrEqualTo","..":"PropertyIsBetween","~":"PropertyIsLike","BBOX":"BBOX","DWITHIN":"DWITHIN","WITHIN":"WITHIN","CONTAINS":"CONTAINS","INTERSECTS":"INTERSECTS"},CLASS_NAME:"OpenLayers.Format.Filter.v1"});OpenLayers.Geometry.Curve=OpenLayers.Class(OpenLayers.Geometry.MultiPoint,{componentTypes:["OpenLayers.Geometry.Point"],initialize:function(points){OpenLayers.Geometry.MultiPoint.prototype.initialize.apply(this,arguments);},getLength:function(){var length=0.0;if(this.components&&(this.components.length>1)){for(var i=1,len=this.components.length;i1)){var p1,p2;for(var i=1,len=geom.components.length;i0){this.layer.destroyFeatures(this.virtualVertices,{silent:true});this.virtualVertices=[];} +this.layer.drawFeature(this.feature,this.selectControl.renderIntent);} +this.layer.drawFeature(vertex);},dragComplete:function(vertex){this.resetVertices();this.setFeatureState();this.onModification(this.feature);this.layer.events.triggerEvent("featuremodified",{feature:this.feature});},setFeatureState:function(){if(this.feature.state!=OpenLayers.State.INSERT&&this.feature.state!=OpenLayers.State.DELETE){this.feature.state=OpenLayers.State.UPDATE;}},resetVertices:function(){if(this.dragControl.feature){this.dragControl.outFeature(this.dragControl.feature);} +if(this.vertices.length>0){this.layer.removeFeatures(this.vertices,{silent:true});this.vertices=[];} +if(this.virtualVertices.length>0){this.layer.removeFeatures(this.virtualVertices,{silent:true});this.virtualVertices=[];} +if(this.dragHandle){this.layer.destroyFeatures([this.dragHandle],{silent:true});this.dragHandle=null;} +if(this.radiusHandle){this.layer.destroyFeatures([this.radiusHandle],{silent:true});this.radiusHandle=null;} +if(this.feature&&this.feature.geometry.CLASS_NAME!="OpenLayers.Geometry.Point"){if((this.mode&OpenLayers.Control.ModifyFeature.DRAG)){this.collectDragHandle();} +if((this.mode&(OpenLayers.Control.ModifyFeature.ROTATE|OpenLayers.Control.ModifyFeature.RESIZE))){this.collectRadiusHandle();} +if(this.mode&OpenLayers.Control.ModifyFeature.RESHAPE){if(!(this.mode&OpenLayers.Control.ModifyFeature.RESIZE)){this.collectVertices();}}}},handleKeypress:function(evt){var code=evt.keyCode;if(this.feature&&OpenLayers.Util.indexOf(this.deleteCodes,code)!=-1){var vertex=this.dragControl.feature;if(vertex&&OpenLayers.Util.indexOf(this.vertices,vertex)!=-1&&!this.dragControl.handlers.drag.dragging&&vertex.geometry.parent){vertex.geometry.parent.removeComponent(vertex.geometry);this.layer.drawFeature(this.feature,this.selectControl.renderIntent);this.resetVertices();this.setFeatureState();this.onModification(this.feature);this.layer.events.triggerEvent("featuremodified",{feature:this.feature});}}},collectVertices:function(){this.vertices=[];this.virtualVertices=[];var control=this;function collectComponentVertices(geometry){var i,vertex,component,len;if(geometry.CLASS_NAME=="OpenLayers.Geometry.Point"){vertex=new OpenLayers.Feature.Vector(geometry);vertex._sketch=true;control.vertices.push(vertex);}else{var numVert=geometry.components.length;if(geometry.CLASS_NAME=="OpenLayers.Geometry.LinearRing"){numVert-=1;} +for(i=0;i2)){OpenLayers.Geometry.Collection.prototype.removeComponent.apply(this,arguments);}},intersects:function(geometry){var intersect=false;var type=geometry.CLASS_NAME;if(type=="OpenLayers.Geometry.LineString"||type=="OpenLayers.Geometry.LinearRing"||type=="OpenLayers.Geometry.Point"){var segs1=this.getSortedSegments();var segs2;if(type=="OpenLayers.Geometry.Point"){segs2=[{x1:geometry.x,y1:geometry.y,x2:geometry.x,y2:geometry.y}];}else{segs2=geometry.getSortedSegments();} +var seg1,seg1x1,seg1x2,seg1y1,seg1y2,seg2,seg2y1,seg2y2;outer:for(var i=0,len=segs1.length;iseg1x2){break;} +if(seg2.x2Math.max(seg1y1,seg1y2)){continue;} +if(Math.max(seg2y1,seg2y2)0){var xDir=seg.x10){lines.unshift(j,1);Array.prototype.splice.apply(targetParts,lines);j+=lines.length-2;} +if(mutual){for(var k=0,len=splits.points.length;k0&&points.length>0){points.push(vert2.clone());sourceParts.push(new OpenLayers.Geometry.LineString(points));}}else{results=target.splitWith(this,options);} +if(targetParts&&targetParts.length>1){targetSplit=true;}else{targetParts=[];} +if(sourceParts&&sourceParts.length>1){sourceSplit=true;}else{sourceParts=[];} +if(targetSplit||sourceSplit){if(mutual){results=[sourceParts,targetParts];}else{results=targetParts;}} +return results;},splitWith:function(geometry,options){return geometry.split(this,options);},getVertices:function(nodes){var vertices;if(nodes===true){vertices=[this.components[0],this.components[this.components.length-1]];}else if(nodes===false){vertices=this.components.slice(1,this.components.length-1);}else{vertices=this.components.slice();} +return vertices;},distanceTo:function(geometry,options){var edge=!(options&&options.edge===false);var details=edge&&options&&options.details;var result,best={};var min=Number.POSITIVE_INFINITY;if(geometry instanceof OpenLayers.Geometry.Point){var segs=this.getSortedSegments();var x=geometry.x;var y=geometry.y;var seg;for(var i=0,len=segs.length;ix&&((y>seg.y1&&yseg.y2))){break;}}} +if(details){best={distance:best.distance,x0:best.x,y0:best.y,x1:x,y1:y};}else{best=best.distance;}}else if(geometry instanceof OpenLayers.Geometry.LineString){var segs0=this.getSortedSegments();var segs1=geometry.getSortedSegments();var seg0,seg1,intersection,x0,y0;var len1=segs1.length;var interOptions={point:true};outer:for(var i=0,len=segs0.length;i4){this.components.pop();OpenLayers.Geometry.Collection.prototype.removeComponent.apply(this,arguments);var firstPoint=this.components[0];OpenLayers.Geometry.Collection.prototype.addComponent.apply(this,[firstPoint]);}},move:function(x,y){for(var i=0,len=this.components.length;i2)){var sumX=0.0;var sumY=0.0;for(var i=0;i2)){var sum=0.0;for(var i=0,len=this.components.length;i2){var p1,p2;for(var i=0;i=x1&&px<=x2)||x1>=x2&&(px<=x1&&px>=x2)){crosses=-1;break;}} +continue;} +cx=approx(getX(py,x1,y1,x2,y2),digs);if(cx==px){if(y1=y1&&py<=y2)||y1>y2&&(py<=y1&&py>=y2)){crosses=-1;break;}} +if(cx<=px){continue;} +if(x1!=x2&&(cxMath.max(x1,x2))){continue;} +if(y1=y1&&pyy2&&(py=y2)){++crosses;}} +var contained=(crosses==-1)?1:!!(crosses&1);return contained;},intersects:function(geometry){var intersect=false;if(geometry.CLASS_NAME=="OpenLayers.Geometry.Point"){intersect=this.containsPoint(geometry);}else if(geometry.CLASS_NAME=="OpenLayers.Geometry.LineString"){intersect=geometry.intersects(this);}else if(geometry.CLASS_NAME=="OpenLayers.Geometry.LinearRing"){intersect=OpenLayers.Geometry.LineString.prototype.intersects.apply(this,[geometry]);}else{for(var i=0,len=geometry.components.length;i1){sourceSplit=true;}else{sourceParts=[];} +if(targetParts&&targetParts.length>1){targetSplit=true;}else{targetParts=[];} +if(sourceSplit||targetSplit){if(mutual){results=[sourceParts,targetParts];}else{results=targetParts;}} +return results;},splitWith:function(geometry,options){var results=null;var mutual=options&&options.mutual;var splits,targetLine,sourceLines,sourceSplit,targetSplit,sourceParts,targetParts;if(geometry instanceof OpenLayers.Geometry.LineString){targetParts=[];sourceParts=[geometry];for(var i=0,len=this.components.length;i1){sourceSplit=true;}else{sourceParts=[];} +if(targetParts&&targetParts.length>1){targetSplit=true;}else{targetParts=[];} +if(sourceSplit||targetSplit){if(mutual){results=[sourceParts,targetParts];}else{results=targetParts;}} +return results;},CLASS_NAME:"OpenLayers.Geometry.MultiLineString"});OpenLayers.Handler.Path=OpenLayers.Class(OpenLayers.Handler.Point,{line:null,freehand:false,freehandToggle:'shiftKey',initialize:function(control,callbacks,options){OpenLayers.Handler.Point.prototype.initialize.apply(this,arguments);},createFeature:function(pixel){var lonlat=this.control.map.getLonLatFromPixel(pixel);this.point=new OpenLayers.Feature.Vector(new OpenLayers.Geometry.Point(lonlat.lon,lonlat.lat));this.line=new OpenLayers.Feature.Vector(new OpenLayers.Geometry.LineString([this.point.geometry]));this.callback("create",[this.point.geometry,this.getSketch()]);this.point.geometry.clearBounds();this.layer.addFeatures([this.line,this.point],{silent:true});},destroyFeature:function(){OpenLayers.Handler.Point.prototype.destroyFeature.apply(this);this.line=null;},removePoint:function(){if(this.point){this.layer.removeFeatures([this.point]);}},addPoint:function(pixel){this.layer.removeFeatures([this.point]);var lonlat=this.control.map.getLonLatFromPixel(pixel);this.point=new OpenLayers.Feature.Vector(new OpenLayers.Geometry.Point(lonlat.lon,lonlat.lat));this.line.geometry.addComponent(this.point.geometry,this.line.geometry.components.length);this.callback("point",[this.point.geometry,this.getGeometry()]);this.callback("modify",[this.point.geometry,this.getSketch()]);this.drawFeature();},freehandMode:function(evt){return(this.freehandToggle&&evt[this.freehandToggle])?!this.freehand:this.freehand;},modifyFeature:function(pixel){var lonlat=this.control.map.getLonLatFromPixel(pixel);this.point.geometry.x=lonlat.lon;this.point.geometry.y=lonlat.lat;this.callback("modify",[this.point.geometry,this.getSketch()]);this.point.geometry.clearBounds();this.drawFeature();},drawFeature:function(){this.layer.drawFeature(this.line,this.style);this.layer.drawFeature(this.point,this.style);},getSketch:function(){return this.line;},getGeometry:function(){var geometry=this.line&&this.line.geometry;if(geometry&&this.multi){geometry=new OpenLayers.Geometry.MultiLineString([geometry]);} +return geometry;},mousedown:function(evt){if(this.lastDown&&this.lastDown.equals(evt.xy)){return false;} +if(this.lastDown==null){if(this.persist){this.destroyFeature();} +this.createFeature(evt.xy);}else if((this.lastUp==null)||!this.lastUp.equals(evt.xy)){this.addPoint(evt.xy);} +this.mouseDown=true;this.lastDown=evt.xy;this.drawing=true;return false;},mousemove:function(evt){if(this.drawing){if(this.mouseDown&&this.freehandMode(evt)){this.addPoint(evt.xy);}else{this.modifyFeature(evt.xy);}} +return true;},mouseup:function(evt){this.mouseDown=false;if(this.drawing){if(this.freehandMode(evt)){this.removePoint();this.finalize();}else{if(this.lastUp==null){this.addPoint(evt.xy);} +this.lastUp=evt.xy;} +return false;} +return true;},dblclick:function(evt){if(!this.freehandMode(evt)){var index=this.line.geometry.components.length-1;this.line.geometry.removeComponent(this.line.geometry.components[index]);this.removePoint();this.finalize();} +return false;},CLASS_NAME:"OpenLayers.Handler.Path"});OpenLayers.Control.Split=OpenLayers.Class(OpenLayers.Control,{EVENT_TYPES:["beforesplit","split","aftersplit"],layer:null,source:null,sourceOptions:null,tolerance:null,edge:true,deferDelete:false,mutual:true,targetFilter:null,sourceFilter:null,handler:null,initialize:function(options){Array.prototype.push.apply(this.EVENT_TYPES,OpenLayers.Control.prototype.EVENT_TYPES);OpenLayers.Control.prototype.initialize.apply(this,[options]);this.options=options||{};if(this.options.source){this.setSource(this.options.source);}},setSource:function(layer){if(this.active){this.deactivate();if(this.handler){this.handler.destroy();delete this.handler;} +this.source=layer;this.activate();}else{this.source=layer;}},activate:function(){var activated=OpenLayers.Control.prototype.activate.call(this);if(activated){if(!this.source){if(!this.handler){this.handler=new OpenLayers.Handler.Path(this,{done:function(geometry){this.onSketchComplete({feature:new OpenLayers.Feature.Vector(geometry)});}},{layerOptions:this.sourceOptions});} +this.handler.activate();}else if(this.source.events){this.source.events.on({sketchcomplete:this.onSketchComplete,afterfeaturemodified:this.afterFeatureModified,scope:this});}} +return activated;},deactivate:function(){var deactivated=OpenLayers.Control.prototype.deactivate.call(this);if(deactivated){if(this.source&&this.source.events){this.layer.events.un({sketchcomplete:this.onSketchComplete,afterfeaturemodified:this.afterFeatureModified,scope:this});}} +return deactivated;},onSketchComplete:function(event){this.feature=null;return!this.considerSplit(event.feature);},afterFeatureModified:function(event){if(event.modified){var feature=event.feature;if(feature.geometry instanceof OpenLayers.Geometry.LineString||feature.geometry instanceof OpenLayers.Geometry.MultiLineString){this.feature=event.feature;this.considerSplit(event.feature);}}},removeByGeometry:function(features,geometry){for(var i=0,len=features.length;i1){parts.unshift(j,1);Array.prototype.splice.apply(sourceParts,parts);j+=parts.length-3;} +results=results[1];} +if(results.length>1){results.unshift(k,1);Array.prototype.splice.apply(targetParts,results);k+=results.length-3;}}}}}} +if(targetParts&&targetParts.length>1){this.geomsToFeatures(targetFeature,targetParts);this.events.triggerEvent("split",{original:targetFeature,features:targetParts});Array.prototype.push.apply(additions,targetParts);removals.push(targetFeature);targetSplit=true;}}} +if(sourceParts&&sourceParts.length>1){this.geomsToFeatures(feature,sourceParts);this.events.triggerEvent("split",{original:feature,features:sourceParts});Array.prototype.push.apply(additions,sourceParts);removals.push(feature);sourceSplit=true;} +if(sourceSplit||targetSplit){if(this.deferDelete){var feat,destroys=[];for(var i=0,len=removals.length;i0)){area+=Math.abs(this.components[0].getArea());for(var i=1,len=this.components.length;i0)){area+=Math.abs(this.components[0].getGeodesicArea(projection));for(var i=1,len=this.components.length;i0){contained=this.components[0].containsPoint(point);if(contained!==1){if(contained&&numRings>1){var hole;for(var i=1;i0||(lat.length>0&&lon.length>0)){var location;if(point.length>0){location=OpenLayers.String.trim(point[0].firstChild.nodeValue).split(/\s+/);if(location.length!=2){location=OpenLayers.String.trim(point[0].firstChild.nodeValue).split(/\s*,\s*/);}}else{location=[parseFloat(lat[0].firstChild.nodeValue),parseFloat(lon[0].firstChild.nodeValue)];} +var geometry=new OpenLayers.Geometry.Point(parseFloat(location[1]),parseFloat(location[0]));}else if(line.length>0){var coords=OpenLayers.String.trim(this.concatChildValues(line[0])).split(/\s+/);var components=[];var point;for(var i=0,len=coords.length;i0){var coords=OpenLayers.String.trim(this.concatChildValues(polygon[0])).split(/\s+/);var components=[];var point;for(var i=0,len=coords.length;i0){if(!this.gmlParser){this.gmlParser=new OpenLayers.Format.GML({'xy':this.xy});} +var feature=this.gmlParser.parseFeature(where[0]);geometry=feature.geometry;}else if(box.length>0){var coords=OpenLayers.String.trim(box[0].firstChild.nodeValue).split(/\s+/);var components=[];var point;if(coords.length>3){point=new OpenLayers.Geometry.Point(parseFloat(coords[1]),parseFloat(coords[0]));components.push(point);point=new OpenLayers.Geometry.Point(parseFloat(coords[1]),parseFloat(coords[2]));components.push(point);point=new OpenLayers.Geometry.Point(parseFloat(coords[3]),parseFloat(coords[2]));components.push(point);point=new OpenLayers.Geometry.Point(parseFloat(coords[3]),parseFloat(coords[0]));components.push(point);point=new OpenLayers.Geometry.Point(parseFloat(coords[1]),parseFloat(coords[0]));components.push(point);} +geometry=new OpenLayers.Geometry.Polygon([new OpenLayers.Geometry.LinearRing(components)]);} +if(geometry&&this.internalProjection&&this.externalProjection){geometry.transform(this.externalProjection,this.internalProjection);} +return geometry;},createFeatureFromItem:function(item){var geometry=this.createGeometryFromItem(item);var title=this.getChildValue(item,"*","title",this.featureTitle);var description=this.getChildValue(item,"*","description",this.getChildValue(item,"*","content",this.getChildValue(item,"*","summary",this.featureDescription)));var link=this.getChildValue(item,"*","link");if(!link){try{link=this.getElementsByTagNameNS(item,"*","link")[0].getAttribute("href");}catch(e){link=null;}} +var id=this.getChildValue(item,"*","id",null);var data={"title":title,"description":description,"link":link};var feature=new OpenLayers.Feature.Vector(geometry,data);feature.fid=id;return feature;},getChildValue:function(node,nsuri,name,def){var value;var eles=this.getElementsByTagNameNS(node,nsuri,name);if(eles&&eles[0]&&eles[0].firstChild&&eles[0].firstChild.nodeValue){value=eles[0].firstChild.nodeValue;}else{value=(def==undefined)?"":def;} +return value;},read:function(doc){if(typeof doc=="string"){doc=OpenLayers.Format.XML.prototype.read.apply(this,[doc]);} +var itemlist=null;itemlist=this.getElementsByTagNameNS(doc,'*','item');if(itemlist.length==0){itemlist=this.getElementsByTagNameNS(doc,'*','entry');} +var numItems=itemlist.length;var features=new Array(numItems);for(var i=0;i=this.maxDepth){return false;} +var newOptions=OpenLayers.Util.extend({},options);newOptions.depth++;for(var i=0,len=nodes.length;i0){var parser=this.parseGeometry[type.toLowerCase()];if(parser){geometry=parser.apply(this,[nodeList[0]]);if(this.internalProjection&&this.externalProjection){geometry.transform(this.externalProjection,this.internalProjection);}}else{OpenLayers.Console.error(OpenLayers.i18n("unsupportedGeometryType",{'geomType':type}));} +break;}} +var attributes;if(this.extractAttributes){attributes=this.parseAttributes(node);} +var feature=new OpenLayers.Feature.Vector(geometry,attributes);var fid=node.getAttribute("id")||node.getAttribute("name");if(fid!=null){feature.fid=fid;} +return feature;},getStyle:function(styleUrl,options){var styleBaseUrl=OpenLayers.Util.removeTail(styleUrl);var newOptions=OpenLayers.Util.extend({},options);newOptions.depth++;newOptions.styleBaseUrl=styleBaseUrl;if(!this.styles[styleUrl]&&!OpenLayers.String.startsWith(styleUrl,"#")&&newOptions.depth<=this.maxDepth&&!this.fetched[styleBaseUrl]){var data=this.fetchLink(styleBaseUrl);if(data){this.parseData(data,newOptions);}} +var style=OpenLayers.Util.extend({},this.styles[styleUrl]);return style;},parseGeometry:{point:function(node){var nodeList=this.getElementsByTagNameNS(node,this.internalns,"coordinates");var coords=[];if(nodeList.length>0){var coordString=nodeList[0].firstChild.nodeValue;coordString=coordString.replace(this.regExes.removeSpace,"");coords=coordString.split(",");} +var point=null;if(coords.length>1){if(coords.length==2){coords[2]=null;} +point=new OpenLayers.Geometry.Point(coords[0],coords[1],coords[2]);}else{throw"Bad coordinate string: "+coordString;} +return point;},linestring:function(node,ring){var nodeList=this.getElementsByTagNameNS(node,this.internalns,"coordinates");var line=null;if(nodeList.length>0){var coordString=this.getChildValue(nodeList[0]);coordString=coordString.replace(this.regExes.trimSpace,"");coordString=coordString.replace(this.regExes.trimComma,",");var pointList=coordString.split(this.regExes.splitSpace);var numPoints=pointList.length;var points=new Array(numPoints);var coords,numCoords;for(var i=0;i1){if(coords.length==2){coords[2]=null;} +points[i]=new OpenLayers.Geometry.Point(coords[0],coords[1],coords[2]);}else{throw"Bad LineString point coordinates: "+ +pointList[i];}} +if(numPoints){if(ring){line=new OpenLayers.Geometry.LinearRing(points);}else{line=new OpenLayers.Geometry.LineString(points);}}else{throw"Bad LineString coordinates: "+coordString;}} +return line;},polygon:function(node){var nodeList=this.getElementsByTagNameNS(node,this.internalns,"LinearRing");var numRings=nodeList.length;var components=new Array(numRings);if(numRings>0){var ring;for(var i=0,len=nodeList.length;i=0;i--){var nodes=this.createFeatureNodes(features[i]);for(var j=0;j0);} +return ret;},read:function(data){if(typeof data=="string"){data=OpenLayers.Format.XML.prototype.read.apply(this,[data]);} +var arcNode=null;if(data&&data.documentElement){if(data.documentElement.nodeName=="ARCXML"){arcNode=data.documentElement;}else{arcNode=data.documentElement.getElementsByTagName("ARCXML")[0];}} +if(!arcNode){var error,source;try{error=data.firstChild.nodeValue;source=data.firstChild.childNodes[1].firstChild.nodeValue;}catch(err){} +throw{message:"Error parsing the ArcXML request",error:error,source:source};} +var response=this.parseResponse(arcNode);return response;},write:function(request){if(!request){request=this.request;} +var root=this.createElementNS("","ARCXML");root.setAttribute("version","1.1");var reqElem=this.createElementNS("","REQUEST");if(request.get_image!=null){var getElem=this.createElementNS("","GET_IMAGE");reqElem.appendChild(getElem);var propElem=this.createElementNS("","PROPERTIES");getElem.appendChild(propElem);var props=request.get_image.properties;if(props.featurecoordsys!=null){var feat=this.createElementNS("","FEATURECOORDSYS");propElem.appendChild(feat);if(props.featurecoordsys.id===0){feat.setAttribute("string",props.featurecoordsys['string']);} +else{feat.setAttribute("id",props.featurecoordsys.id);}} +if(props.filtercoordsys!=null){var filt=this.createElementNS("","FILTERCOORDSYS");propElem.appendChild(filt);if(props.filtercoordsys.id===0){filt.setAttribute("string",props.filtercoordsys.string);} +else{filt.setAttribute("id",props.filtercoordsys.id);}} +if(props.envelope!=null){var env=this.createElementNS("","ENVELOPE");propElem.appendChild(env);env.setAttribute("minx",props.envelope.minx);env.setAttribute("miny",props.envelope.miny);env.setAttribute("maxx",props.envelope.maxx);env.setAttribute("maxy",props.envelope.maxy);} +var imagesz=this.createElementNS("","IMAGESIZE");propElem.appendChild(imagesz);imagesz.setAttribute("height",props.imagesize.height);imagesz.setAttribute("width",props.imagesize.width);if(props.imagesize.height!=props.imagesize.printheight||props.imagesize.width!=props.imagesize.printwidth){imagesz.setAttribute("printheight",props.imagesize.printheight);imagesz.setArrtibute("printwidth",props.imagesize.printwidth);} +if(props.background!=null){var backgrnd=this.createElementNS("","BACKGROUND");propElem.appendChild(backgrnd);backgrnd.setAttribute("color",props.background.color.r+","+ +props.background.color.g+","+ +props.background.color.b);if(props.background.transcolor!==null){backgrnd.setAttribute("transcolor",props.background.transcolor.r+","+ +props.background.transcolor.g+","+ +props.background.transcolor.b);}} +if(props.layerlist!=null&&props.layerlist.length>0){var layerlst=this.createElementNS("","LAYERLIST");propElem.appendChild(layerlst);for(var ld=0;ld0){queryElem.setAttribute("accuracy",query.accuracy);} +if(typeof query.featurelimit=="number"&&query.featurelimit<2000){queryElem.setAttribute("featurelimit",query.featurelimit);} +if(typeof query.subfields=="string"&&query.subfields!="#ALL#"){queryElem.setAttribute("subfields",query.subfields);} +if(typeof query.joinexpression=="string"&&query.joinexpression.length>0){queryElem.setAttribute("joinexpression",query.joinexpression);} +if(typeof query.jointables=="string"&&query.jointables.length>0){queryElem.setAttribute("jointables",query.jointables);} +ldef.appendChild(queryElem);} +if(typeof props.layerlist[ld].renderer=="object"){this.addRenderer(ldef,props.layerlist[ld].renderer);}}}}else if(request.get_feature!=null){var getElem=this.createElementNS("","GET_FEATURES");getElem.setAttribute("outputmode","newxml");getElem.setAttribute("checkesc","true");if(request.get_feature.geometry){getElem.setAttribute("geometry",request.get_feature.geometry);} +else{getElem.setAttribute("geometry","false");} +if(request.get_feature.compact){getElem.setAttribute("compact",request.get_feature.compact);} +if(request.get_feature.featurelimit=="number"){getElem.setAttribute("featurelimit",request.get_feature.featurelimit);} +getElem.setAttribute("globalenvelope","true");reqElem.appendChild(getElem);if(request.get_feature.layer!=null&&request.get_feature.layer.length>0){var lyrElem=this.createElementNS("","LAYER");lyrElem.setAttribute("id",request.get_feature.layer);getElem.appendChild(lyrElem);} +var fquery=request.get_feature.query;if(fquery!=null){var qElem=null;if(fquery.isspatial){qElem=this.createElementNS("","SPATIALQUERY");}else{qElem=this.createElementNS("","QUERY");} +getElem.appendChild(qElem);if(typeof fquery.accuracy=="number"){qElem.setAttribute("accuracy",fquery.accuracy);} +if(fquery.featurecoordsys!=null){var fcsElem1=this.createElementNS("","FEATURECOORDSYS");if(fquery.featurecoordsys.id==0){fcsElem1.setAttribute("string",fquery.featurecoordsys.string);}else{fcsElem1.setAttribute("id",fquery.featurecoordsys.id);} +qElem.appendChild(fcsElem1);} +if(fquery.filtercoordsys!=null){var fcsElem2=this.createElementNS("","FILTERCOORDSYS");if(fquery.filtercoordsys.id===0){fcsElem2.setAttribute("string",fquery.filtercoordsys.string);}else{fcsElem2.setAttribute("id",fquery.filtercoordsys.id);} +qElem.appendChild(fcsElem2);} +if(fquery.buffer>0){var bufElem=this.createElementNS("","BUFFER");bufElem.setAttribute("distance",fquery.buffer);qElem.appendChild(bufElem);} +if(fquery.isspatial){var spfElem=this.createElementNS("","SPATIALFILTER");spfElem.setAttribute("relation",fquery.spatialfilter.relation);qElem.appendChild(spfElem);if(fquery.spatialfilter.envelope){var envElem=this.createElementNS("","ENVELOPE");envElem.setAttribute("minx",fquery.spatialfilter.envelope.minx);envElem.setAttribute("miny",fquery.spatialfilter.envelope.miny);envElem.setAttribute("maxx",fquery.spatialfilter.envelope.maxx);envElem.setAttribute("maxy",fquery.spatialfilter.envelope.maxy);spfElem.appendChild(envElem);}else if(typeof fquery.spatialfilter.polygon=="object"){spfElem.appendChild(this.writePolygonGeometry(fquery.spatialfilter.polygon));}} +if(fquery.where!=null&&fquery.where.length>0){qElem.setAttribute("where",fquery.where);}}} +root.appendChild(reqElem);return OpenLayers.Format.XML.prototype.write.apply(this,[root]);},addGroupRenderer:function(ldef,toprenderer){var topRelem=this.createElementNS("","GROUPRENDERER");ldef.appendChild(topRelem);for(var rind=0;rind0){response.error=this.getChildValue(errorNode,"Unknown error.");}else{var responseNode=data.getElementsByTagName("RESPONSE");if(responseNode==null||responseNode.length==0){response.error="No RESPONSE tag found in ArcXML response.";return response;} +var rtype=responseNode[0].firstChild.nodeName;if(rtype=="#text"){rtype=responseNode[0].firstChild.nextSibling.nodeName;} +if(rtype=="IMAGE"){var envelopeNode=data.getElementsByTagName("ENVELOPE");var outputNode=data.getElementsByTagName("OUTPUT");if(envelopeNode==null||envelopeNode.length==0){response.error="No ENVELOPE tag found in ArcXML response.";}else if(outputNode==null||outputNode.length==0){response.error="No OUTPUT tag found in ArcXML response.";}else{var envAttr=this.parseAttributes(envelopeNode[0]);var outputAttr=this.parseAttributes(outputNode[0]);if(typeof outputAttr.type=="string"){response.image={envelope:envAttr,output:{type:outputAttr.type,data:this.getChildValue(outputNode[0])}};}else{response.image={envelope:envAttr,output:outputAttr};}}}else if(rtype=="FEATURES"){var features=responseNode[0].getElementsByTagName("FEATURES");var featureCount=features[0].getElementsByTagName("FEATURECOUNT");response.features.featurecount=featureCount[0].getAttribute("count");if(response.features.featurecount>0){var envelope=features[0].getElementsByTagName("ENVELOPE");response.features.envelope=this.parseAttributes(envelope[0],typeof(0));var featureList=features[0].getElementsByTagName("FEATURE");for(var fn=0;fn0){var ring=geom[0].getElementsByTagName("RING");var polys=[];for(var rn=0;rn0){var coordArr=this.getChildValue(coords[0]);coordArr=coordArr.split(/;/);for(var cn=0;cn0){for(var pn=0;pn0){var parser=this.parseGeometry[type.toLowerCase()];if(parser){geometry=parser.apply(this,[nodeList[0]]);if(this.internalProjection&&this.externalProjection){geometry.transform(this.externalProjection,this.internalProjection);}}else{OpenLayers.Console.error(OpenLayers.i18n("unsupportedGeometryType",{'geomType':type}));} +break;}} +var attributes;if(this.extractAttributes){attributes=this.parseAttributes(node);} +var feature=new OpenLayers.Feature.Vector(geometry,attributes);feature.gml={featureType:node.firstChild.nodeName.split(":")[1],featureNS:node.firstChild.namespaceURI,featureNSPrefix:node.firstChild.prefix};var childNode=node.firstChild;var fid;while(childNode){if(childNode.nodeType==1){fid=childNode.getAttribute("fid")||childNode.getAttribute("id");if(fid){break;}} +childNode=childNode.nextSibling;} +feature.fid=fid;return feature;},parseGeometry:{point:function(node){var nodeList,coordString;var coords=[];var nodeList=this.getElementsByTagNameNS(node,this.gmlns,"pos");if(nodeList.length>0){coordString=nodeList[0].firstChild.nodeValue;coordString=coordString.replace(this.regExes.trimSpace,"");coords=coordString.split(this.regExes.splitSpace);} +if(coords.length==0){nodeList=this.getElementsByTagNameNS(node,this.gmlns,"coordinates");if(nodeList.length>0){coordString=nodeList[0].firstChild.nodeValue;coordString=coordString.replace(this.regExes.removeSpace,"");coords=coordString.split(",");}} +if(coords.length==0){nodeList=this.getElementsByTagNameNS(node,this.gmlns,"coord");if(nodeList.length>0){var xList=this.getElementsByTagNameNS(nodeList[0],this.gmlns,"X");var yList=this.getElementsByTagNameNS(nodeList[0],this.gmlns,"Y");if(xList.length>0&&yList.length>0){coords=[xList[0].firstChild.nodeValue,yList[0].firstChild.nodeValue];}}} +if(coords.length==2){coords[2]=null;} +if(this.xy){return new OpenLayers.Geometry.Point(coords[0],coords[1],coords[2]);} +else{return new OpenLayers.Geometry.Point(coords[1],coords[0],coords[2]);}},multipoint:function(node){var nodeList=this.getElementsByTagNameNS(node,this.gmlns,"Point");var components=[];if(nodeList.length>0){var point;for(var i=0;i0){coordString=this.getChildValue(nodeList[0]);coordString=coordString.replace(this.regExes.trimSpace,"");coords=coordString.split(this.regExes.splitSpace);var dim=parseInt(nodeList[0].getAttribute("dimension"));var j,x,y,z;for(var i=0;i0){coordString=this.getChildValue(nodeList[0]);coordString=coordString.replace(this.regExes.trimSpace,"");coordString=coordString.replace(this.regExes.trimComma,",");var pointList=coordString.split(this.regExes.splitSpace);for(var i=0;i0){var line;for(var i=0;i0){var ring;for(var i=0;i0){var polygon;for(var i=0;i0){var coords=[];if(lpoint.length>0){coordString=lpoint[0].firstChild.nodeValue;coordString=coordString.replace(this.regExes.trimSpace,"");coords=coordString.split(this.regExes.splitSpace);} +if(coords.length==2){coords[2]=null;} +if(this.xy){var lowerPoint=new OpenLayers.Geometry.Point(coords[0],coords[1],coords[2]);}else{var lowerPoint=new OpenLayers.Geometry.Point(coords[1],coords[0],coords[2]);}} +var upoint=this.getElementsByTagNameNS(node,this.gmlns,"upperCorner");if(upoint.length>0){var coords=[];if(upoint.length>0){coordString=upoint[0].firstChild.nodeValue;coordString=coordString.replace(this.regExes.trimSpace,"");coords=coordString.split(this.regExes.splitSpace);} +if(coords.length==2){coords[2]=null;} +if(this.xy){var upperPoint=new OpenLayers.Geometry.Point(coords[0],coords[1],coords[2]);}else{var upperPoint=new OpenLayers.Geometry.Point(coords[1],coords[0],coords[2]);}} +if(lowerPoint&&upperPoint){components.push(new OpenLayers.Geometry.Point(lowerPoint.x,lowerPoint.y));components.push(new OpenLayers.Geometry.Point(upperPoint.x,lowerPoint.y));components.push(new OpenLayers.Geometry.Point(upperPoint.x,upperPoint.y));components.push(new OpenLayers.Geometry.Point(lowerPoint.x,upperPoint.y));components.push(new OpenLayers.Geometry.Point(lowerPoint.x,lowerPoint.y));var ring=new OpenLayers.Geometry.LinearRing(components);envelope=new OpenLayers.Geometry.Polygon([ring]);} +return envelope;}},parseAttributes:function(node){var attributes={};var childNode=node.firstChild;var children,i,child,grandchildren,grandchild,name,value;while(childNode){if(childNode.nodeType==1){children=childNode.childNodes;for(i=0;i0){obj.bounds=container.components[0];}},"Point":function(node,container){var obj={points:[]};this.readChildNodes(node,obj);if(!container.components){container.components=[];} +container.components.push(obj.points[0]);},"coordinates":function(node,obj){var str=this.getChildValue(node).replace(this.regExes.trimSpace,"");str=str.replace(this.regExes.trimComma,",");var pointList=str.split(this.regExes.splitSpace);var coords;var numPoints=pointList.length;var points=new Array(numPoints);for(var i=0;i0){container.components=[new OpenLayers.Geometry.MultiLineString(obj.components)];}},"curveMember":function(node,obj){this.readChildNodes(node,obj);},"MultiSurface":function(node,container){var obj={components:[]};this.readChildNodes(node,obj);if(obj.components.length>0){container.components=[new OpenLayers.Geometry.MultiPolygon(obj.components)];}},"surfaceMember":function(node,obj){this.readChildNodes(node,obj);},"surfaceMembers":function(node,obj){this.readChildNodes(node,obj);},"pointMembers":function(node,obj){this.readChildNodes(node,obj);},"lineStringMembers":function(node,obj){this.readChildNodes(node,obj);},"polygonMembers":function(node,obj){this.readChildNodes(node,obj);},"geometryMembers":function(node,obj){this.readChildNodes(node,obj);},"Envelope":function(node,container){var obj={points:new Array(2)};this.readChildNodes(node,obj);if(!container.components){container.components=[];} +var min=obj.points[0];var max=obj.points[1];container.components.push(new OpenLayers.Bounds(min.x,min.y,max.x,max.y));},"lowerCorner":function(node,container){var obj={};this.readers.gml.pos.apply(this,[node,obj]);container.points[0]=obj.points[0];},"upperCorner":function(node,container){var obj={};this.readers.gml.pos.apply(this,[node,obj]);container.points[1]=obj.points[0];}},OpenLayers.Format.GML.Base.prototype.readers["gml"]),"feature":OpenLayers.Format.GML.Base.prototype.readers["feature"],"wfs":OpenLayers.Format.GML.Base.prototype.readers["wfs"]},write:function(features){var name;if(features instanceof Array){name="featureMembers";}else{name="featureMember";} +var root=this.writeNode("gml:"+name,features);this.setAttributeNS(root,this.namespaces["xsi"],"xsi:schemaLocation",this.schemaLocation);return OpenLayers.Format.XML.prototype.write.apply(this,[root]);},writers:{"gml":OpenLayers.Util.applyDefaults({"featureMembers":function(features){var node=this.createElementNSPlus("gml:featureMembers");for(var i=0,len=features.length;i0){this.writeNode("ogc:PropertyName",{property:item.substring(0,last)},node);node.appendChild(this.createTextNode(item.substring(++last)));}else{node.appendChild(this.createTextNode("${"+item));}} +return node;},"Halo":function(symbolizer){var node=this.createElementNSPlus("Halo");if(symbolizer.haloRadius){this.writeNode("Radius",symbolizer.haloRadius,node);} +if(symbolizer.haloColor||symbolizer.haloOpacity){this.writeNode("Fill",{fillColor:symbolizer.haloColor,fillOpacity:symbolizer.haloOpacity},node);} +return node;},"Radius":function(value){return node=this.createElementNSPlus("Radius",{value:value});},"PolygonSymbolizer":function(symbolizer){var node=this.createElementNSPlus("PolygonSymbolizer");if(symbolizer.fillColor!=undefined||symbolizer.fillOpacity!=undefined){this.writeNode("Fill",symbolizer,node);} +if(symbolizer.strokeWidth!=undefined||symbolizer.strokeColor!=undefined||symbolizer.strokeOpacity!=undefined||symbolizer.strokeDashstyle!=undefined){this.writeNode("Stroke",symbolizer,node);} +return node;},"Fill":function(symbolizer){var node=this.createElementNSPlus("Fill");if(symbolizer.fillColor){this.writeNode("CssParameter",{symbolizer:symbolizer,key:"fillColor"},node);} +if(symbolizer.fillOpacity!=null){this.writeNode("CssParameter",{symbolizer:symbolizer,key:"fillOpacity"},node);} +return node;},"PointSymbolizer":function(symbolizer){var node=this.createElementNSPlus("PointSymbolizer");this.writeNode("Graphic",symbolizer,node);return node;},"Graphic":function(symbolizer){var node=this.createElementNSPlus("Graphic");if(symbolizer.externalGraphic!=undefined){this.writeNode("ExternalGraphic",symbolizer,node);}else{this.writeNode("Mark",symbolizer,node);} +if(symbolizer.graphicOpacity!=undefined){this.writeNode("Opacity",symbolizer.graphicOpacity,node);} +if(symbolizer.pointRadius!=undefined){this.writeNode("Size",symbolizer.pointRadius*2,node);} +if(symbolizer.rotation!=undefined){this.writeNode("Rotation",symbolizer.rotation,node);} +return node;},"ExternalGraphic":function(symbolizer){var node=this.createElementNSPlus("ExternalGraphic");this.writeNode("OnlineResource",symbolizer.externalGraphic,node);var format=symbolizer.graphicFormat||this.getGraphicFormat(symbolizer.externalGraphic);this.writeNode("Format",format,node);return node;},"Mark":function(symbolizer){var node=this.createElementNSPlus("Mark");if(symbolizer.graphicName){this.writeNode("WellKnownName",symbolizer.graphicName,node);} +this.writeNode("Fill",symbolizer,node);this.writeNode("Stroke",symbolizer,node);return node;},"WellKnownName":function(name){return this.createElementNSPlus("WellKnownName",{value:name});},"Opacity":function(value){return this.createElementNSPlus("Opacity",{value:value});},"Size":function(value){return this.createElementNSPlus("Size",{value:value});},"Rotation":function(value){return this.createElementNSPlus("Rotation",{value:value});},"OnlineResource":function(href){return this.createElementNSPlus("OnlineResource",{attributes:{"xlink:type":"simple","xlink:href":href}});},"Format":function(format){return this.createElementNSPlus("Format",{value:format});}}},OpenLayers.Format.Filter.v1_0_0.prototype.writers),CLASS_NAME:"OpenLayers.Format.SLD.v1"});OpenLayers.Format.WFST.v1_0_0=OpenLayers.Class(OpenLayers.Format.Filter.v1_0_0,OpenLayers.Format.WFST.v1,{version:"1.0.0",schemaLocations:{"wfs":"http://schemas.opengis.net/wfs/1.0.0/WFS-transaction.xsd"},initialize:function(options){OpenLayers.Format.Filter.v1_0_0.prototype.initialize.apply(this,[options]);OpenLayers.Format.WFST.v1.prototype.initialize.apply(this,[options]);},readers:{"wfs":OpenLayers.Util.applyDefaults({"WFS_TransactionResponse":function(node,obj){obj.insertIds=[];obj.success=false;this.readChildNodes(node,obj);},"InsertResult":function(node,container){var obj={fids:[]};this.readChildNodes(node,obj);container.insertIds.push(obj.fids[0]);},"TransactionResult":function(node,obj){this.readChildNodes(node,obj);},"Status":function(node,obj){this.readChildNodes(node,obj);},"SUCCESS":function(node,obj){obj.success=true;}},OpenLayers.Format.WFST.v1.prototype.readers["wfs"]),"gml":OpenLayers.Format.GML.v2.prototype.readers["gml"],"feature":OpenLayers.Format.GML.v2.prototype.readers["feature"],"ogc":OpenLayers.Format.Filter.v1_0_0.prototype.readers["ogc"]},writers:{"wfs":OpenLayers.Util.applyDefaults({"Query":function(options){options=OpenLayers.Util.extend({featureNS:this.featureNS,featurePrefix:this.featurePrefix,featureType:this.featureType,srsName:this.srsName},options);var node=this.createElementNSPlus("wfs:Query",{attributes:{typeName:(options.featureNS?options.featurePrefix+":":"")+ +options.featureType}});if(options.featureNS){node.setAttribute("xmlns:"+options.featurePrefix,options.featureNS);} +if(options.propertyNames){for(var i=0,len=options.propertyNames.length;i + + OpenLayers GML Parser + + + + + + +

GML Parser Example

+
+ +

+ Demonstrate the operation of the GML parser. +

+ +
+ +
+ This script reads data from a GML file and parses out the coordinates, appending them to a HTML string with markup tags. + This markup is dumped to an element in the page. +
+ + + --- /dev/null +++ b/openlayers/examples/Jugl.js @@ -1,1 +1,84 @@ +/** + * Jugl.js -- JavaScript Template Library + * + * Copyright 2007 Tim Schaub + * Released under the MIT license. Please see + * http://svn.tschaub.net/jugl/trunk/license.txt for the full license. + */ +(function(){var Jugl={prefix:"jugl",namespaceURI:"http://namespace.jugl.org/"};Jugl.Array={indexOf:function(array,obj){for(var i=0;i + + OpenLayers KML Parser Example + + + + + +

KML Parser Example

+
+ +

+ Demonstrate the operation of the KML parser. +

+ +
+ +
+ This script reads data from a KML file and parses out the coordinates, appending them to a HTML string with markup tags. + This markup is dumped to an element in the page. +
+ + + --- /dev/null +++ b/openlayers/examples/WMSDescribeLayerParser.html @@ -1,1 +1,42 @@ + + + OpenLayers WMSDescribeLayer Parser Example + + + + + +

WMSDescribeLayer Parser Example

+
+ +

+ Demonstrate the operation of the WMSDescribeLayer parser. +

+ +
+ +
+ This script reads data from a file and parses out the coordinates, appending them to a HTML string with markup tags. + This markup is dumped to an element in the page. +
+ + + --- /dev/null +++ b/openlayers/examples/accessible.html @@ -1,1 +1,140 @@ + + + OpenLayers Accessible Example + + + + + + + +

Accessible Example

+
+
+ +

+ Demonstrate how to use the KeyboardDefaults option parameter for layer types. +

+ + + + + + + + + + + + + + + + + + + +
+ + zoom out + + + + pan north + + + + zoom in + +
+ + pan west + + + + pan east + +
  + + pan south + +  
+ +
+

Navigate the map in one of three ways: +

    +
  • Click on the named links to zoom and pan
  • +
  • Use following keys to pan and zoom: +
      +
    • + (zoom in)
    • +
    • - (zoom out)
    • +
    • up-arrow (pan north)
    • +
    • down-arrow (pan south)
    • +
    • left-arrow (pan east)
    • +
    • right-arrow (pan west)
    • +
    +
  • +
  • If access keys work for links in your browser, use: +
      +
    • i (zoom in)
    • +
    • o (zoom out)
    • +
    • n (pan north)
    • +
    • s (pan south)
    • +
    • e (pan east)
    • +
    • w (pan west)
    • +
    +
  • +
+

+ + This is an example of using alternate methods to control panning and zooming. This approach uses map.pan() and map.zoom(). You'll note that to pan, additional math is necessary along with map.size() in order to set the distance to pan. +
+ + + --- /dev/null +++ b/openlayers/examples/all-overlays.html @@ -1,1 +1,70 @@ + + + All Overlays Example + + + + + + + +

OpenLayers Overlays Only Example

+

+ Demonstrates a map with overlays only. +

+
+
+ To create a map that allows any draw order with all layer types + and lets you set the visibility of any layer independently, set + the allOverlays property on the map to true. +
+ + + --- /dev/null +++ b/openlayers/examples/animated_panning.html @@ -1,1 +1,93 @@ + + + Animated Panning of the Map via map.panTo + + + + + + +

map.panTo Example

+
map.panTo
+
Show animated panning effects in the map
+
+
+

This is an example of transition effects. If the new random center is in the current extent, the map will pan smoothly.
+ The random selection will continue until you press it again. Additionally, you can single click in the map to pan smoothly + to that area, or use the pan control to pan smoothly. +

+
+ +
+
+

To turn off Animated Panning, create a map with an panMethod set to + null.

+
+ + + --- /dev/null +++ b/openlayers/examples/animator.js @@ -1,1 +1,671 @@ - +/* + Animator.js 1.1.9 + + This library is released under the BSD license: + + Copyright (c) 2006, Bernard Sumption. All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + + Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. Redistributions in binary + form must reproduce the above copyright notice, this list of conditions and + the following disclaimer in the documentation and/or other materials + provided with the distribution. Neither the name BernieCode nor + the names of its contributors may be used to endorse or promote products + derived from this software without specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE FOR + ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH + DAMAGE. + +*/ + + +// Applies a sequence of numbers between 0 and 1 to a number of subjects +// construct - see setOptions for parameters +function Animator(options) { + this.setOptions(options); + var _this = this; + this.timerDelegate = function(){_this.onTimerEvent()}; + this.subjects = []; + this.target = 0; + this.state = 0; + this.lastTime = null; +}; +Animator.prototype = { + // apply defaults + setOptions: function(options) { + this.options = Animator.applyDefaults({ + interval: 20, // time between animation frames + duration: 400, // length of animation + onComplete: function(){}, + onStep: function(){}, + transition: Animator.tx.easeInOut + }, options); + }, + // animate from the current state to provided value + seekTo: function(to) { + this.seekFromTo(this.state, to); + }, + // animate from the current state to provided value + seekFromTo: function(from, to) { + this.target = Math.max(0, Math.min(1, to)); + this.state = Math.max(0, Math.min(1, from)); + this.lastTime = new Date().getTime(); + if (!this.intervalId) { + this.intervalId = window.setInterval(this.timerDelegate, this.options.interval); + } + }, + // animate from the current state to provided value + jumpTo: function(to) { + this.target = this.state = Math.max(0, Math.min(1, to)); + this.propagate(); + }, + // seek to the opposite of the current target + toggle: function() { + this.seekTo(1 - this.target); + }, + // add a function or an object with a method setState(state) that will be called with a number + // between 0 and 1 on each frame of the animation + addSubject: function(subject) { + this.subjects[this.subjects.length] = subject; + return this; + }, + // remove all subjects + clearSubjects: function() { + this.subjects = []; + }, + // forward the current state to the animation subjects + propagate: function() { + var value = this.options.transition(this.state); + for (var i=0; i= Math.abs(this.state - this.target)) { + this.state = this.target; + } else { + this.state += movement; + } + + try { + this.propagate(); + } finally { + this.options.onStep.call(this); + if (this.target == this.state) { + window.clearInterval(this.intervalId); + this.intervalId = null; + this.options.onComplete.call(this); + } + } + }, + // shortcuts + play: function() {this.seekFromTo(0, 1)}, + reverse: function() {this.seekFromTo(1, 0)}, + // return a string describing this Animator, for debugging + inspect: function() { + var str = "# 20) return; + } + }, + getStyle: function(state) { + state = this.from + ((this.to - this.from) * state); + if (this.property == 'filter') return "alpha(opacity=" + Math.round(state*100) + ")"; + if (this.property == 'opacity') return state; + return Math.round(state) + this.units; + }, + inspect: function() { + return "\t" + this.property + "(" + this.from + this.units + " to " + this.to + this.units + ")\n"; + } +} + +// animates a colour based style property between two hex values +function ColorStyleSubject(els, property, from, to) { + this.els = Animator.makeArray(els); + this.property = Animator.camelize(property); + this.to = this.expandColor(to); + this.from = this.expandColor(from); + this.origFrom = from; + this.origTo = to; +} + +ColorStyleSubject.prototype = { + // parse "#FFFF00" to [256, 256, 0] + expandColor: function(color) { + var hexColor, red, green, blue; + hexColor = ColorStyleSubject.parseColor(color); + if (hexColor) { + red = parseInt(hexColor.slice(1, 3), 16); + green = parseInt(hexColor.slice(3, 5), 16); + blue = parseInt(hexColor.slice(5, 7), 16); + return [red,green,blue] + } + if (window.DEBUG) { + alert("Invalid colour: '" + color + "'"); + } + }, + getValueForState: function(color, state) { + return Math.round(this.from[color] + ((this.to[color] - this.from[color]) * state)); + }, + setState: function(state) { + var color = '#' + + ColorStyleSubject.toColorPart(this.getValueForState(0, state)) + + ColorStyleSubject.toColorPart(this.getValueForState(1, state)) + + ColorStyleSubject.toColorPart(this.getValueForState(2, state)); + for (var i=0; i 255) number = 255; + var digits = number.toString(16); + if (number < 16) return '0' + digits; + return digits; +} +ColorStyleSubject.parseColor.rgbRe = /^rgb\(\s*(\d+)\s*,\s*(\d+)\s*,\s*(\d+)\s*\)$/i; +ColorStyleSubject.parseColor.hexRe = /^\#([0-9a-fA-F]{3}|[0-9a-fA-F]{6})$/; + +// Animates discrete styles, i.e. ones that do not scale but have discrete values +// that can't be interpolated +function DiscreteStyleSubject(els, property, from, to, threshold) { + this.els = Animator.makeArray(els); + this.property = Animator.camelize(property); + this.from = from; + this.to = to; + this.threshold = threshold || 0.5; +} + +DiscreteStyleSubject.prototype = { + setState: function(state) { + var j=0; + for (var i=0; i section ? 1 : 0); + } + if (this.options.rememberance) { + document.location.hash = this.rememberanceTexts[section]; + } + } +} + --- /dev/null +++ b/openlayers/examples/arcgis93rest.html @@ -1,1 +1,65 @@ + + + + + + + + +

ArcGIS Server 9.3 Rest API Example

+ +
+
+

+ Shows the basic use of openlayers using an ArcGIS Server 9.3 Rest API layer +

+ +
+ +
+ This is an example of how to add an ArcGIS Server 9.3 Rest API layer to the OpenLayers window. +
+ + + +
+ (Filter is case sensitive.) + + + + + + + --- /dev/null +++ b/openlayers/examples/arcims-thematic.html @@ -1,1 +1,78 @@ + + + ArcIMS Thematic Example + + + + + + +

ArcIMS Thematic Example

+ +
+
+

+ Shows the advanced use of OpenLayers using a thematic ArcIMS layer +

+ +
+ +
+

This is an example of how to add an ArcIMS layer to an OpenLayers map.

+ +

Following the ArcXML convention to create a thematic (or chloropleth) map, + a layer definition is created with a query and a renderer to select portions + of the map data, and change their representation in the generated map tiles.

+
+ + + + --- /dev/null +++ b/openlayers/examples/arcims.html @@ -1,1 +1,53 @@ + + + ArcIMS Example + + + + + + +

ArcIMS Example

+ +
+
+

+ Shows the basic use of OpenLayers using an ArcIMS layer +

+ +
+ +
+ This is an example of how to add an ArcIMS layer to the OpenLayers window. +
+ + + + + + + + --- /dev/null +++ b/openlayers/examples/attribution.html @@ -1,1 +1,55 @@ + + + OpenLayers Attribution Example + + + + + + +

Attribution Example

+ +
+ copyright watermark logo attribution +
+ +

+ Shows the use of the attribution layer option on a number of layer types. +

+ +
+ +
+ This is an example of how to add an attribution block to the OpenLayers window. In order to use an + attribution block, an attribution parameter must be set in each layer that requires attribution. In + addition, an attribution control must be added to the map, though one is added to all OpenLayers Maps by default. + Be aware that this is a layer *option*: the options hash goes in + different places depending on the layer type you are using. +
+ + + --- /dev/null +++ b/openlayers/examples/baseLayers.html @@ -1,1 +1,97 @@ + + + OpenLayers Base Layers Example + + + + + + + + + + + + + + + +

Base Layers Example

+ +
+
+ +

+ This example shows the use base layers from multiple commercial map image providers. +

+ +
+
+ +
click to add a marker to the map
+
click to remove the marker from the map
+
+ +
+
+ + + --- /dev/null +++ b/openlayers/examples/behavior-fixed-http-gml.html @@ -1,1 +1,50 @@ + + + OpenLayers Vector Behavior Example + + + + + + +

Vector Behavior Example (Fixed/HTTP/GML)

+

+ Vector layer with a Fixed strategy, HTTP protocol, and GML format. +

+
+
+ The vector layer shown uses the Fixed strategy, the HTTP protocol, + and the GML format. + The Fixed strategy is a simple strategy that fetches features once + and never re-requests new data. + The HTTP protocol makes requests using HTTP verbs. It should be + constructed with a url that corresponds to a collection of features + (a resource on some server). + The GML format is used to serialize features. +
+ + + --- /dev/null +++ b/openlayers/examples/boxes-vector.html @@ -1,1 +1,55 @@ + + + OpenLayers Boxes Example + + + + + + +

Boxes Example

+ +
+
+ +

+ Demonstrate marker and box type annotations on a map. +

+ +
+ +
+ + + --- /dev/null +++ b/openlayers/examples/boxes.html @@ -1,1 +1,54 @@ + + + OpenLayers Boxes Example + + + + + + +

Boxes Example

+ +
+
+ +

+ Demonstrate marker and box type annotations on a map. +

+ +
+ +
+ + + --- /dev/null +++ b/openlayers/examples/browser-name.html @@ -1,1 +1,90 @@ + + + OpenLayers Example + + + + + + +

Example Showing Browser Name

+

+ +
+ +

+ Demonstrate a simple map that shows the browser name. +

+ +
+ +
+ + + --- /dev/null +++ b/openlayers/examples/buffer.html @@ -1,1 +1,50 @@ + + + OpenLayers Buffer Example + + + + + + +

Buffer Example

+ +
+
+ +

+ This example shows the use of the buffer layer option for any layer that inherits from OpenLayers.Layer.Grid. +

+ +
+ +
+ Use the buffer property to control how many tiles are included + outside the visible map area. Default is 2. +
+ + + --- /dev/null +++ b/openlayers/examples/click-handler.html @@ -1,1 +1,228 @@ - + + + OpenLayers Click Handler Example + + + + + + + + + +

Click Handler Example

+
+ +
+
+ +

+ This example shows the use of the click handler. +

+ +
+

+ The click handler can be used to gain more flexibility over handling + click events. The handler can be constructed with options to handle + only single click events, to handle single and double-click events, + to ignore clicks that include a drag, and to stop propagation of + single and/or double-click events. A single click is a click that + is not followed by another click for more than 300ms. This delay + is configured with the delay property. +

+

+ The options to stop single and double clicks have to do with + stopping event propagation on the map events listener queue + (not stopping events from cascading to other elements). The + ability to stop an event from propagating has to do with the + order in which listeners are registered. With stopSingle or + stopDouble true, a click handler will stop propagation to all + listeners that were registered (or all handlers that were + activated) before the click handler was activated. So, for + example, activating a click handler with stopDouble true after + the navigation control is active will stop double-clicks from + zooming in. +

+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Controls with click handlers (toggle on/off to clear output)
single only
double only
both
single with drag
single with stop
double with stop
+
+ + + --- /dev/null +++ b/openlayers/examples/click.html @@ -1,1 +1,82 @@ + + + OpenLayers Click Event Example + + + + + + + +

Click Event Example

+ +
+
+ +

+ This example shows the use of the click handler and getLonLatFromViewPortPx functions to trigger events on mouse click. + +

+ +
+ +
+ Using the Click handler allows you to (for example) catch clicks without catching double clicks, something that standard browser events don't do for you. (Try double clicking: you'll zoom in, whereas using the browser click event, you would just get two alerts.) This example click control shows you how to use it. +
+ + + --- /dev/null +++ b/openlayers/examples/controls.html @@ -1,1 +1,64 @@ + + + OpenLayers Map Controls Example + + + + + + +

Map Controls Example

+ +
+
+ +

+ Attach zooming, panning, layer switcher, overview map, and permalink map controls to an OpenLayers window. +

+ + +
+ +
+ + + --- /dev/null +++ b/openlayers/examples/custom-control-point.html @@ -1,1 +1,57 @@ + + + OpenLayers Custom Control Point Examle + + + + + + +

Custom Control Point Example

+ +
+
+ +

+ Demonstrate the addition of a point reporting control to the OpenLayers window. +

+ +
+
+ +
+ + + --- /dev/null +++ b/openlayers/examples/custom-control.html @@ -1,1 +1,60 @@ + + + Custom Control Example + + + + + + +

Custom Control Example

+ +
+
+ +

+ Demonstrate the addition of a draggable rectangle to the OpenLayers window. +

+ +
+ +
+ + + --- /dev/null +++ b/openlayers/examples/custom-style.html @@ -1,1 +1,62 @@ + + + Custom Style Example + + + + + + + + +

Custom Style Example

+ +
+
+ +

+ Demonstrate changing CSS styles on controls in the OpenLayers window. +

+ +
+ +
+

If you care to modify the style of any OpenLayers element, include + the default stylesheet as a link and declare any style modifications + below that link. These style declarations can be in other linked + stylesheets or in style tags. In addition, construct your map with + options that include {theme: null}. This will disable the default + method of loading the stylesheet and allow you to declare style rules + in your own linked stylesheets or style tags.

+

This example shows how to declare the font family, size, and color + for the mouse position. Note that only the style keys that you want to + modify (change from the default) need to be specified.

+
+ + + --- /dev/null +++ b/openlayers/examples/data/line.json @@ -1,1 +1,10 @@ - +{ + "type": "FeatureCollection", + "features": [ + {"type":"Feature", "id":"OpenLayers.Feature.Vector_458", "properties":{}, "geometry":{"type":"LineString", "coordinates":[[-121.640625, 24.2578125], [-78.046875, 27.7734375], [-45.703125, 24.9609375], [-13.359375, 16.5234375], [12.65625, 6.6796875], [39.375, 1.0546875], [76.640625, 1.0546875], [108.28125, 1.7578125], [156.09375, 15.8203125]]}, "crs":{"type":"OGC", "properties":{"urn":"urn:ogc:def:crs:OGC:1.3:CRS84"}}}, + {"type":"Feature", "id":"OpenLayers.Feature.Vector_1111", "properties":{}, "geometry":{"type":"LineString", "coordinates":[[-122.34375, -35.5078125], [-48.515625, -33.3984375], [-5.625, -37.6171875], [20.390625, -32.6953125], [69.609375, -34.1015625], [121.640625, -38.3203125], [150.46875, -33.3984375]]}, "crs":{"type":"OGC", "properties":{"urn":"urn:ogc:def:crs:OGC:1.3:CRS84"}}}, + {"type":"Feature", "id":"OpenLayers.Feature.Vector_634", "properties":{}, "geometry":{"type":"LineString", "coordinates":[[-54.84375, 69.9609375], [-56.953125, 31.9921875], [-56.953125, 5.2734375], [-65.390625, -34.8046875], [-66.09375, -61.5234375]]}, "crs":{"type":"OGC", "properties":{"urn":"urn:ogc:def:crs:OGC:1.3:CRS84"}}}, + {"type":"Feature", "id":"OpenLayers.Feature.Vector_820", "properties":{}, "geometry":{"type":"LineString", "coordinates":[[39.375, 58.0078125], [42.890625, 25.6640625], [42.1875, -1.0546875], [37.96875, -50.2734375], [37.265625, -64.3359375]]}, "crs":{"type":"OGC", "properties":{"urn":"urn:ogc:def:crs:OGC:1.3:CRS84"}}}, + {"type":"Feature", "id":"OpenLayers.Feature.Vector_1280", "properties":{}, "geometry":{"type":"LineString", "coordinates":[[101.25, 42.5390625], [106.875, 13.7109375], [106.171875, -17.9296875], [104.765625, -49.5703125], [102.65625, -67.1484375]]}, "crs":{"type":"OGC", "properties":{"urn":"urn:ogc:def:crs:OGC:1.3:CRS84"}}} + ] +} --- /dev/null +++ b/openlayers/examples/data/point.json @@ -1,1 +1,8 @@ - +{ + "type": "FeatureCollection", + "features": [ + {"type":"Feature", "id":"OpenLayers.Feature.Vector_1721", "properties":{}, "geometry":{"type":"Point", "coordinates":[-89.296875, -14.4140625]}, "crs":{"type":"OGC", "properties":{"urn":"urn:ogc:def:crs:OGC:1.3:CRS84"}}}, + {"type":"Feature", "id":"OpenLayers.Feature.Vector_1715", "properties":{}, "geometry":{"type":"Point", "coordinates":[-25.3125, -54.4921875]}, "crs":{"type":"OGC", "properties":{"urn":"urn:ogc:def:crs:OGC:1.3:CRS84"}}}, + {"type":"Feature", "id":"OpenLayers.Feature.Vector_1709", "properties":{}, "geometry":{"type":"Point", "coordinates":[73.828125, -23.5546875]}, "crs":{"type":"OGC", "properties":{"urn":"urn:ogc:def:crs:OGC:1.3:CRS84"}}} + ] +} --- /dev/null +++ b/openlayers/examples/data/poly.json @@ -1,1 +1,10 @@ +{ + "type": "FeatureCollection", + "features": [ + {"type":"Feature", "id":"OpenLayers.Feature.Vector_1489", "properties":{}, "geometry":{"type":"Polygon", "coordinates":[[[-109.6875, 63.6328125], [-112.5, 35.5078125], [-85.078125, 34.8046875], [-68.90625, 39.7265625], [-68.203125, 67.1484375], [-109.6875, 63.6328125]]]}, "crs":{"type":"OGC", "properties":{"urn":"urn:ogc:def:crs:OGC:1.3:CRS84"}}}, + {"type":"Feature", "id":"OpenLayers.Feature.Vector_1668", "properties":{}, "geometry":{"type":"Polygon", "coordinates":[[[-40.78125, 65.0390625], [-40.078125, 34.8046875], [-12.65625, 25.6640625], [21.09375, 17.2265625], [22.5, 58.0078125], [-40.78125, 65.0390625]]]}, "crs":{"type":"OGC", "properties":{"urn":"urn:ogc:def:crs:OGC:1.3:CRS84"}}} + ] +} + + --- /dev/null +++ b/openlayers/examples/data/roads.json @@ -1,1 +1,350 @@ +{ +"type": "FeatureCollection", +"features": [ +{ "type": "Feature", "properties": { "LINK_ID": 30760460.000000, "RP_TYPE": 12, "RP_FUNC": 0, "DIRECTION": 1, "LOGKOD": "", "CHANGED": "", "USERID": "", "ST_NAME": "KVARNGATAN", "L_REFADDR": "24", "L_NREFADDR": "22", "R_REFADDR": "27", "R_NREFADDR": "23", "SPEED_CAT": "6", "ZIPCODE": "59333", "SHAPE_LEN": 41.871700 }, "geometry": { "type": "LineString", "coordinates": [ [ 1549555.330250, 6403958.170400 ], [ 1549594.439950, 6403973.130400 ] ] } } +, +{ "type": "Feature", "properties": { "LINK_ID": 573730499.000000, "RP_TYPE": 14, "RP_FUNC": 0, "DIRECTION": 2, "LOGKOD": "", "CHANGED": "", "USERID": "", "ST_NAME": "", "L_REFADDR": "", "L_NREFADDR": "", "R_REFADDR": "", "R_NREFADDR": "", "SPEED_CAT": "7", "ZIPCODE": "59330", "SHAPE_LEN": 46.382600 }, "geometry": { "type": "LineString", "coordinates": [ [ 1549497.669850, 6403707.960000 ], [ 1549491.100000, 6403710.100000 ], [ 1549488.039950, 6403716.750400 ], [ 1549488.540100, 6403724.550400 ], [ 1549494.379850, 6403733.540000 ], [ 1549499.679900, 6403738.050400 ], [ 1549506.220000, 6403739.250400 ] ] } } +, +{ "type": "Feature", "properties": { "LINK_ID": 30760556.000000, "RP_TYPE": 12, "RP_FUNC": 1, "DIRECTION": 0, "LOGKOD": "", "CHANGED": "", "USERID": "", "ST_NAME": "BRUNNSGATAN", "L_REFADDR": "24", "L_NREFADDR": "16", "R_REFADDR": "", "R_NREFADDR": "", "SPEED_CAT": "7", "ZIPCODE": "59330", "SHAPE_LEN": 70.310600 }, "geometry": { "type": "LineString", "coordinates": [ [ 1549754.276900, 6403854.802400 ], [ 1549728.459850, 6403920.200000 ] ] } } +, +{ "type": "Feature", "properties": { "LINK_ID": 30760712.000000, "RP_TYPE": 10, "RP_FUNC": 1, "DIRECTION": 0, "LOGKOD": "", "CHANGED": "", "USERID": "", "ST_NAME": "SÖDRA JÄRNVÄGSGATAN", "L_REFADDR": "", "L_NREFADDR": "", "R_REFADDR": "9", "R_NREFADDR": "9", "SPEED_CAT": "6", "ZIPCODE": "59332", "SHAPE_LEN": 40.068900 }, "geometry": { "type": "LineString", "coordinates": [ [ 1549571.899950, 6403675.450400 ], [ 1549592.674200, 6403684.530400 ], [ 1549608.619850, 6403691.500000 ] ] } } +, +{ "type": "Feature", "properties": { "LINK_ID": 30837043.000000, "RP_TYPE": 12, "RP_FUNC": 0, "DIRECTION": 0, "LOGKOD": "", "CHANGED": "", "USERID": "", "ST_NAME": "BREDGATAN", "L_REFADDR": "", "L_NREFADDR": "", "R_REFADDR": "", "R_NREFADDR": "", "SPEED_CAT": "6", "ZIPCODE": "59330", "SHAPE_LEN": 78.203400 }, "geometry": { "type": "LineString", "coordinates": [ [ 1549741.089950, 6403765.520000 ], [ 1549730.790150, 6403779.880000 ], [ 1549703.919950, 6403834.130400 ] ] } } +, +{ "type": "Feature", "properties": { "LINK_ID": 80545558.000000, "RP_TYPE": 14, "RP_FUNC": 0, "DIRECTION": 0, "LOGKOD": "", "CHANGED": "", "USERID": "", "ST_NAME": "NORRA VARVSGATAN", "L_REFADDR": "", "L_NREFADDR": "", "R_REFADDR": "", "R_NREFADDR": "", "SPEED_CAT": "7", "ZIPCODE": "59330", "SHAPE_LEN": 20.687400 }, "geometry": { "type": "LineString", "coordinates": [ [ 1549909.400050, 6403973.670400 ], [ 1549900.829950, 6403992.491200 ] ] } } +, +{ "type": "Feature", "properties": { "LINK_ID": 30760549.000000, "RP_TYPE": 14, "RP_FUNC": 0, "DIRECTION": 0, "LOGKOD": "", "CHANGED": "", "USERID": "", "ST_NAME": "STRÖMSGATAN", "L_REFADDR": "38", "L_NREFADDR": "36", "R_REFADDR": "33", "R_NREFADDR": "31", "SPEED_CAT": "7", "ZIPCODE": "59330", "SHAPE_LEN": 32.788800 }, "geometry": { "type": "LineString", "coordinates": [ [ 1549878.029900, 6403861.890400 ], [ 1549867.520100, 6403892.960000 ] ] } } +, +{ "type": "Feature", "properties": { "LINK_ID": 80547479.000000, "RP_TYPE": 16, "RP_FUNC": 0, "DIRECTION": 0, "LOGKOD": "", "CHANGED": "", "USERID": "", "ST_NAME": "BREDGATAN", "L_REFADDR": "18", "L_NREFADDR": "14", "R_REFADDR": "15", "R_NREFADDR": "13", "SPEED_CAT": "8", "ZIPCODE": "59330", "SHAPE_LEN": 15.654700 }, "geometry": { "type": "LineString", "coordinates": [ [ 1549645.069900, 6403971.520000 ], [ 1549638.940000, 6403985.930400 ] ] } } +, +{ "type": "Feature", "properties": { "LINK_ID": 30760575.000000, "RP_TYPE": 12, "RP_FUNC": 0, "DIRECTION": 0, "LOGKOD": "", "CHANGED": "", "USERID": "", "ST_NAME": "BREDGATAN", "L_REFADDR": "24", "L_NREFADDR": "14", "R_REFADDR": "19", "R_NREFADDR": "13", "SPEED_CAT": "6", "ZIPCODE": "59330", "SHAPE_LEN": 118.385000 }, "geometry": { "type": "LineString", "coordinates": [ [ 1549703.919950, 6403834.130400 ], [ 1549656.739950, 6403942.710400 ] ] } } +, +{ "type": "Feature", "properties": { "LINK_ID": 30760608.000000, "RP_TYPE": 12, "RP_FUNC": 0, "DIRECTION": 0, "LOGKOD": "", "CHANGED": "", "USERID": "", "ST_NAME": "BÅTSMANSGATAN", "L_REFADDR": "32", "L_NREFADDR": "32", "R_REFADDR": "", "R_NREFADDR": "", "SPEED_CAT": "6", "ZIPCODE": "59330", "SHAPE_LEN": 74.462800 }, "geometry": { "type": "LineString", "coordinates": [ [ 1549566.450100, 6403780.090400 ], [ 1549635.170150, 6403808.780000 ] ] } } +, +{ "type": "Feature", "properties": { "LINK_ID": 80547481.000000, "RP_TYPE": 12, "RP_FUNC": 0, "DIRECTION": 0, "LOGKOD": "", "CHANGED": "", "USERID": "", "ST_NAME": "KVARNGATAN", "L_REFADDR": "", "L_NREFADDR": "", "R_REFADDR": "", "R_NREFADDR": "", "SPEED_CAT": "6", "ZIPCODE": "59330", "SHAPE_LEN": 13.834500 }, "geometry": { "type": "LineString", "coordinates": [ [ 1549625.900050, 6403981.310400 ], [ 1549638.940000, 6403985.930400 ] ] } } +, +{ "type": "Feature", "properties": { "LINK_ID": 573730495.000000, "RP_TYPE": 14, "RP_FUNC": 0, "DIRECTION": 1, "LOGKOD": "", "CHANGED": "", "USERID": "", "ST_NAME": "", "L_REFADDR": "", "L_NREFADDR": "", "R_REFADDR": "", "R_NREFADDR": "", "SPEED_CAT": "7", "ZIPCODE": "59330", "SHAPE_LEN": 63.537000 }, "geometry": { "type": "LineString", "coordinates": [ [ 1549535.370100, 6403692.830400 ], [ 1549549.530050, 6403703.030400 ], [ 1549570.300100, 6403708.850400 ], [ 1549570.600050, 6403733.360000 ] ] } } +, +{ "type": "Feature", "properties": { "LINK_ID": 80545560.000000, "RP_TYPE": 12, "RP_FUNC": 0, "DIRECTION": 0, "LOGKOD": "", "CHANGED": "", "USERID": "", "ST_NAME": "", "L_REFADDR": "", "L_NREFADDR": "", "R_REFADDR": "", "R_NREFADDR": "", "SPEED_CAT": "6", "ZIPCODE": "59330", "SHAPE_LEN": 20.545100 }, "geometry": { "type": "LineString", "coordinates": [ [ 1549927.119850, 6403985.020000 ], [ 1549944.182350, 6403996.455200 ] ] } } +, +{ "type": "Feature", "properties": { "LINK_ID": 30760664.000000, "RP_TYPE": 10, "RP_FUNC": 1, "DIRECTION": 0, "LOGKOD": "", "CHANGED": "", "USERID": "", "ST_NAME": "SÖDRA JÄRNVÄGSGATAN", "L_REFADDR": "", "L_NREFADDR": "", "R_REFADDR": "3", "R_NREFADDR": "1", "SPEED_CAT": "6", "ZIPCODE": "59331", "SHAPE_LEN": 59.030600 }, "geometry": { "type": "LineString", "coordinates": [ [ 1549679.130150, 6403720.210400 ], [ 1549717.099900, 6403730.700000 ], [ 1549726.590150, 6403734.160000 ], [ 1549734.260050, 6403739.820000 ] ] } } +, +{ "type": "Feature", "properties": { "LINK_ID": 80547480.000000, "RP_TYPE": 12, "RP_FUNC": 0, "DIRECTION": 0, "LOGKOD": "", "CHANGED": "", "USERID": "", "ST_NAME": "KVARNGATAN", "L_REFADDR": "20", "L_NREFADDR": "20", "R_REFADDR": "21", "R_NREFADDR": "21", "SPEED_CAT": "6", "ZIPCODE": "59333", "SHAPE_LEN": 12.375300 }, "geometry": { "type": "LineString", "coordinates": [ [ 1549614.030150, 6403977.820000 ], [ 1549621.149850, 6403980.140000 ], [ 1549625.900050, 6403981.310400 ] ] } } +, +{ "type": "Feature", "properties": { "LINK_ID": 30760739.000000, "RP_TYPE": 10, "RP_FUNC": 1, "DIRECTION": 0, "LOGKOD": "", "CHANGED": "", "USERID": "", "ST_NAME": "SÖDRA JÄRNVÄGSGATAN", "L_REFADDR": "", "L_NREFADDR": "", "R_REFADDR": "13", "R_NREFADDR": "11", "SPEED_CAT": "6", "ZIPCODE": "59332", "SHAPE_LEN": 57.793000 }, "geometry": { "type": "LineString", "coordinates": [ [ 1549522.250000, 6403645.880000 ], [ 1549571.899950, 6403675.450400 ] ] } } +, +{ "type": "Feature", "properties": { "LINK_ID": 80545557.000000, "RP_TYPE": 14, "RP_FUNC": 0, "DIRECTION": 0, "LOGKOD": "", "CHANGED": "", "USERID": "", "ST_NAME": "NORRA VARVSGATAN", "L_REFADDR": "26", "L_NREFADDR": "20", "R_REFADDR": "", "R_NREFADDR": "", "SPEED_CAT": "7", "ZIPCODE": "59330", "SHAPE_LEN": 62.216100 }, "geometry": { "type": "LineString", "coordinates": [ [ 1549929.770050, 6403914.890400 ], [ 1549909.400050, 6403973.670400 ] ] } } +, +{ "type": "Feature", "properties": { "LINK_ID": 30760610.000000, "RP_TYPE": 10, "RP_FUNC": 1, "DIRECTION": 0, "LOGKOD": "", "CHANGED": "", "USERID": "", "ST_NAME": "SLOTTSHOLMSVÄGEN", "L_REFADDR": "", "L_NREFADDR": "", "R_REFADDR": "", "R_NREFADDR": "", "SPEED_CAT": "6", "ZIPCODE": "59330", "SHAPE_LEN": 60.324700 }, "geometry": { "type": "LineString", "coordinates": [ [ 1549921.910100, 6403780.010400 ], [ 1549931.136800, 6403785.640000 ], [ 1549946.150050, 6403794.800000 ], [ 1549960.880150, 6403807.230400 ], [ 1549962.209450, 6403808.998400 ], [ 1549968.489850, 6403817.350400 ] ] } } +, +{ "type": "Feature", "properties": { "LINK_ID": 30760475.000000, "RP_TYPE": 16, "RP_FUNC": 0, "DIRECTION": 0, "LOGKOD": "", "CHANGED": "", "USERID": "", "ST_NAME": "SPÖTORGET", "L_REFADDR": "9", "L_NREFADDR": "1", "R_REFADDR": "", "R_NREFADDR": "", "SPEED_CAT": "8", "ZIPCODE": "59330", "SHAPE_LEN": 70.301600 }, "geometry": { "type": "LineString", "coordinates": [ [ 1549656.739950, 6403942.710400 ], [ 1549631.800000, 6403936.830400 ], [ 1549614.030150, 6403977.820000 ] ] } } +, +{ "type": "Feature", "properties": { "LINK_ID": 80547460.000000, "RP_TYPE": 12, "RP_FUNC": 0, "DIRECTION": 1, "LOGKOD": "", "CHANGED": "", "USERID": "", "ST_NAME": "KVARNGATAN", "L_REFADDR": "30", "L_NREFADDR": "26", "R_REFADDR": "31", "R_NREFADDR": "29", "SPEED_CAT": "6", "ZIPCODE": "59333", "SHAPE_LEN": 62.288000 }, "geometry": { "type": "LineString", "coordinates": [ [ 1549496.649950, 6403937.400000 ], [ 1549525.699950, 6403946.670400 ], [ 1549555.330250, 6403958.170400 ] ] } } +, +{ "type": "Feature", "properties": { "LINK_ID": 80547482.000000, "RP_TYPE": 12, "RP_FUNC": 0, "DIRECTION": 0, "LOGKOD": "", "CHANGED": "", "USERID": "", "ST_NAME": "", "L_REFADDR": "", "L_NREFADDR": "", "R_REFADDR": "", "R_NREFADDR": "", "SPEED_CAT": "6", "ZIPCODE": "59330", "SHAPE_LEN": 22.019100 }, "geometry": { "type": "LineString", "coordinates": [ [ 1549645.069900, 6403971.520000 ], [ 1549637.249850, 6403978.110400 ], [ 1549633.070150, 6403979.170400 ], [ 1549625.900050, 6403981.310400 ] ] } } +, +{ "type": "Feature", "properties": { "LINK_ID": 573730502.000000, "RP_TYPE": 14, "RP_FUNC": 0, "DIRECTION": 2, "LOGKOD": "", "CHANGED": "", "USERID": "", "ST_NAME": "", "L_REFADDR": "", "L_NREFADDR": "", "R_REFADDR": "", "R_NREFADDR": "", "SPEED_CAT": "7", "ZIPCODE": "59330", "SHAPE_LEN": 26.440100 }, "geometry": { "type": "LineString", "coordinates": [ [ 1549535.370100, 6403692.830400 ], [ 1549528.510100, 6403718.360000 ] ] } } +, +{ "type": "Feature", "properties": { "LINK_ID": 573730491.000000, "RP_TYPE": 12, "RP_FUNC": 1, "DIRECTION": 0, "LOGKOD": "", "CHANGED": "", "USERID": "", "ST_NAME": "ÖSTRA KYRKOGATAN", "L_REFADDR": "48", "L_NREFADDR": "48", "R_REFADDR": "", "R_NREFADDR": "", "SPEED_CAT": "7", "ZIPCODE": "59330", "SHAPE_LEN": 53.485400 }, "geometry": { "type": "LineString", "coordinates": [ [ 1549608.619850, 6403691.500000 ], [ 1549600.079850, 6403708.100000 ], [ 1549584.219950, 6403739.090400 ] ] } } +, +{ "type": "Feature", "properties": { "LINK_ID": 30760461.000000, "RP_TYPE": 12, "RP_FUNC": 0, "DIRECTION": 2, "LOGKOD": "", "CHANGED": "", "USERID": "", "ST_NAME": "RÅDHUSGATAN", "L_REFADDR": "52", "L_NREFADDR": "50", "R_REFADDR": "43", "R_NREFADDR": "41", "SPEED_CAT": "6", "ZIPCODE": "59333", "SHAPE_LEN": 62.397200 }, "geometry": { "type": "LineString", "coordinates": [ [ 1549555.330250, 6403958.170400 ], [ 1549531.400050, 6404015.800000 ] ] } } +, +{ "type": "Feature", "properties": { "LINK_ID": 30760674.000000, "RP_TYPE": 10, "RP_FUNC": 1, "DIRECTION": 0, "LOGKOD": "", "CHANGED": "", "USERID": "", "ST_NAME": "SÖDRA JÄRNVÄGSGATAN", "L_REFADDR": "", "L_NREFADDR": "", "R_REFADDR": "", "R_NREFADDR": "", "SPEED_CAT": "6", "ZIPCODE": "59331", "SHAPE_LEN": 13.834500 }, "geometry": { "type": "LineString", "coordinates": [ [ 1549666.080050, 6403715.590400 ], [ 1549679.130150, 6403720.210400 ] ] } } +, +{ "type": "Feature", "properties": { "LINK_ID": 80545555.000000, "RP_TYPE": 10, "RP_FUNC": 1, "DIRECTION": 0, "LOGKOD": "", "CHANGED": "", "USERID": "", "ST_NAME": "SLOTTSHOLMSVÄGEN", "L_REFADDR": "", "L_NREFADDR": "", "R_REFADDR": "", "R_NREFADDR": "", "SPEED_CAT": "6", "ZIPCODE": "59330", "SHAPE_LEN": 185.679000 }, "geometry": { "type": "LineString", "coordinates": [ [ 1549968.489850, 6403817.350400 ], [ 1549977.779900, 6403836.400000 ], [ 1549983.460050, 6403858.740000 ], [ 1549982.539900, 6403884.350400 ], [ 1549978.140050, 6403903.230400 ], [ 1549947.139850, 6403954.090400 ], [ 1549927.119850, 6403985.020000 ] ] } } +, +{ "type": "Feature", "properties": { "LINK_ID": 30760515.000000, "RP_TYPE": 14, "RP_FUNC": 0, "DIRECTION": 0, "LOGKOD": "", "CHANGED": "", "USERID": "", "ST_NAME": "NORRA VARVSGATAN", "L_REFADDR": "30", "L_NREFADDR": "28", "R_REFADDR": "", "R_NREFADDR": "", "SPEED_CAT": "7", "ZIPCODE": "59330", "SHAPE_LEN": 22.968600 }, "geometry": { "type": "LineString", "coordinates": [ [ 1549938.960000, 6403893.840000 ], [ 1549929.770050, 6403914.890400 ] ] } } +, +{ "type": "Feature", "properties": { "LINK_ID": 30760497.000000, "RP_TYPE": 12, "RP_FUNC": 0, "DIRECTION": 1, "LOGKOD": "", "CHANGED": "", "USERID": "", "ST_NAME": "KVARNGATAN", "L_REFADDR": "", "L_NREFADDR": "", "R_REFADDR": "", "R_NREFADDR": "", "SPEED_CAT": "6", "ZIPCODE": "59333", "SHAPE_LEN": 24.829800 }, "geometry": { "type": "LineString", "coordinates": [ [ 1549488.599950, 6403913.910400 ], [ 1549496.649950, 6403937.400000 ] ] } } +, +{ "type": "Feature", "properties": { "LINK_ID": 30837044.000000, "RP_TYPE": 10, "RP_FUNC": 1, "DIRECTION": 0, "LOGKOD": "", "CHANGED": "", "USERID": "", "ST_NAME": "STATIONSGATAN", "L_REFADDR": "", "L_NREFADDR": "", "R_REFADDR": "", "R_NREFADDR": "", "SPEED_CAT": "6", "ZIPCODE": "59330", "SHAPE_LEN": 146.769000 }, "geometry": { "type": "LineString", "coordinates": [ [ 1549776.080150, 6403777.100000 ], [ 1549785.590000, 6403778.330400 ], [ 1549886.280100, 6403772.890400 ], [ 1549908.484450, 6403777.327200 ], [ 1549921.910100, 6403780.010400 ] ] } } +, +{ "type": "Feature", "properties": { "LINK_ID": 30760477.000000, "RP_TYPE": 12, "RP_FUNC": 1, "DIRECTION": 0, "LOGKOD": "", "CHANGED": "", "USERID": "", "ST_NAME": "BRUNNSGATAN", "L_REFADDR": "14", "L_NREFADDR": "12", "R_REFADDR": "19", "R_NREFADDR": "11", "SPEED_CAT": "7", "ZIPCODE": "59330", "SHAPE_LEN": 78.700300 }, "geometry": { "type": "LineString", "coordinates": [ [ 1549723.519950, 6403934.620000 ], [ 1549697.600000, 6404008.930400 ] ] } } +, +{ "type": "Feature", "properties": { "LINK_ID": 30760542.000000, "RP_TYPE": 14, "RP_FUNC": 0, "DIRECTION": 0, "LOGKOD": "", "CHANGED": "", "USERID": "", "ST_NAME": "BÅTSMANSGATAN", "L_REFADDR": "22", "L_NREFADDR": "18", "R_REFADDR": "29", "R_NREFADDR": "21", "SPEED_CAT": "7", "ZIPCODE": "59330", "SHAPE_LEN": 34.587000 }, "geometry": { "type": "LineString", "coordinates": [ [ 1549798.179850, 6403867.590400 ], [ 1549830.790050, 6403879.130400 ] ] } } +, +{ "type": "Feature", "properties": { "LINK_ID": 30760457.000000, "RP_TYPE": 14, "RP_FUNC": 0, "DIRECTION": 0, "LOGKOD": "", "CHANGED": "", "USERID": "", "ST_NAME": "NYGATAN", "L_REFADDR": "8", "L_NREFADDR": "6", "R_REFADDR": "15", "R_NREFADDR": "7", "SPEED_CAT": "7", "ZIPCODE": "59330", "SHAPE_LEN": 45.468000 }, "geometry": { "type": "LineString", "coordinates": [ [ 1549796.459950, 6403958.910400 ], [ 1549839.739900, 6403972.810400 ] ] } } +, +{ "type": "Feature", "properties": { "LINK_ID": 573703846.000000, "RP_TYPE": 10, "RP_FUNC": 1, "DIRECTION": 0, "LOGKOD": "", "CHANGED": "", "USERID": "", "ST_NAME": "STATIONSGATAN", "L_REFADDR": "", "L_NREFADDR": "", "R_REFADDR": "", "R_NREFADDR": "", "SPEED_CAT": "6", "ZIPCODE": "59331", "SHAPE_LEN": 8.208130 }, "geometry": { "type": "LineString", "coordinates": [ [ 1549734.260050, 6403739.820000 ], [ 1549738.939900, 6403746.560000 ] ] } } +, +{ "type": "Feature", "properties": { "LINK_ID": 30760631.000000, "RP_TYPE": 10, "RP_FUNC": 1, "DIRECTION": 0, "LOGKOD": "", "CHANGED": "", "USERID": "", "ST_NAME": "STATIONSGATAN", "L_REFADDR": "", "L_NREFADDR": "", "R_REFADDR": "", "R_NREFADDR": "", "SPEED_CAT": "6", "ZIPCODE": "59330", "SHAPE_LEN": 46.824600 }, "geometry": { "type": "LineString", "coordinates": [ [ 1549741.290150, 6403748.820000 ], [ 1549753.539450, 6403766.201600 ], [ 1549754.750100, 6403767.920000 ], [ 1549761.249950, 6403772.460000 ], [ 1549776.080150, 6403777.100000 ] ] } } +, +{ "type": "Feature", "properties": { "LINK_ID": 30760491.000000, "RP_TYPE": 12, "RP_FUNC": 1, "DIRECTION": 0, "LOGKOD": "", "CHANGED": "", "USERID": "", "ST_NAME": "BRUNNSGATAN", "L_REFADDR": "", "L_NREFADDR": "", "R_REFADDR": "", "R_NREFADDR": "", "SPEED_CAT": "7", "ZIPCODE": "59330", "SHAPE_LEN": 15.240700 }, "geometry": { "type": "LineString", "coordinates": [ [ 1549728.459850, 6403920.200000 ], [ 1549723.519950, 6403934.620000 ] ] } } +, +{ "type": "Feature", "properties": { "LINK_ID": 30760566.000000, "RP_TYPE": 14, "RP_FUNC": 0, "DIRECTION": 0, "LOGKOD": "", "CHANGED": "", "USERID": "", "ST_NAME": "NORRA VARVSGATAN", "L_REFADDR": "", "L_NREFADDR": "", "R_REFADDR": "", "R_NREFADDR": "", "SPEED_CAT": "7", "ZIPCODE": "59330", "SHAPE_LEN": 54.648300 }, "geometry": { "type": "LineString", "coordinates": [ [ 1549960.410100, 6403843.980000 ], [ 1549959.139950, 6403850.640000 ], [ 1549952.470000, 6403860.580000 ], [ 1549938.960000, 6403893.840000 ] ] } } +, +{ "type": "Feature", "properties": { "LINK_ID": 80547447.000000, "RP_TYPE": 12, "RP_FUNC": 0, "DIRECTION": 0, "LOGKOD": "", "CHANGED": "", "USERID": "", "ST_NAME": "STATIONSGATAN", "L_REFADDR": "", "L_NREFADDR": "", "R_REFADDR": "", "R_NREFADDR": "", "SPEED_CAT": "6", "ZIPCODE": "59331", "SHAPE_LEN": 13.369300 }, "geometry": { "type": "LineString", "coordinates": [ [ 1549927.421200, 6403767.822400 ], [ 1549921.910100, 6403780.010400 ] ] } } +, +{ "type": "Feature", "properties": { "LINK_ID": 573730503.000000, "RP_TYPE": 14, "RP_FUNC": 0, "DIRECTION": 0, "LOGKOD": "", "CHANGED": "", "USERID": "", "ST_NAME": "", "L_REFADDR": "", "L_NREFADDR": "", "R_REFADDR": "", "R_NREFADDR": "", "SPEED_CAT": "7", "ZIPCODE": "59330", "SHAPE_LEN": 44.681900 }, "geometry": { "type": "LineString", "coordinates": [ [ 1549528.510100, 6403718.360000 ], [ 1549570.600050, 6403733.360000 ] ] } } +, +{ "type": "Feature", "properties": { "LINK_ID": 80545559.000000, "RP_TYPE": 14, "RP_FUNC": 0, "DIRECTION": 0, "LOGKOD": "", "CHANGED": "", "USERID": "", "ST_NAME": "", "L_REFADDR": "", "L_NREFADDR": "", "R_REFADDR": "", "R_NREFADDR": "", "SPEED_CAT": "7", "ZIPCODE": "59330", "SHAPE_LEN": 21.047100 }, "geometry": { "type": "LineString", "coordinates": [ [ 1549909.400050, 6403973.670400 ], [ 1549927.119850, 6403985.020000 ] ] } } +, +{ "type": "Feature", "properties": { "LINK_ID": 80547444.000000, "RP_TYPE": 12, "RP_FUNC": 0, "DIRECTION": 1, "LOGKOD": "", "CHANGED": "", "USERID": "", "ST_NAME": "STATIONSGATAN", "L_REFADDR": "", "L_NREFADDR": "", "R_REFADDR": "", "R_NREFADDR": "", "SPEED_CAT": "6", "ZIPCODE": "59331", "SHAPE_LEN": 46.504800 }, "geometry": { "type": "LineString", "coordinates": [ [ 1549967.599100, 6403744.932000 ], [ 1549943.650000, 6403755.770400 ], [ 1549927.421200, 6403767.822400 ] ] } } +, +{ "type": "Feature", "properties": { "LINK_ID": 573730492.000000, "RP_TYPE": 12, "RP_FUNC": 1, "DIRECTION": 0, "LOGKOD": "", "CHANGED": "", "USERID": "", "ST_NAME": "ÖSTRA KYRKOGATAN", "L_REFADDR": "", "L_NREFADDR": "", "R_REFADDR": "", "R_NREFADDR": "", "SPEED_CAT": "7", "ZIPCODE": "59330", "SHAPE_LEN": 44.681800 }, "geometry": { "type": "LineString", "coordinates": [ [ 1549584.219950, 6403739.090400 ], [ 1549566.450100, 6403780.090400 ] ] } } +, +{ "type": "Feature", "properties": { "LINK_ID": 30760700.000000, "RP_TYPE": 10, "RP_FUNC": 1, "DIRECTION": 0, "LOGKOD": "", "CHANGED": "", "USERID": "", "ST_NAME": "SÖDRA JÄRNVÄGSGATAN", "L_REFADDR": "", "L_NREFADDR": "", "R_REFADDR": "7", "R_NREFADDR": "5", "SPEED_CAT": "6", "ZIPCODE": "59332", "SHAPE_LEN": 62.310700 }, "geometry": { "type": "LineString", "coordinates": [ [ 1549608.619850, 6403691.500000 ], [ 1549666.080050, 6403715.590400 ] ] } } +, +{ "type": "Feature", "properties": { "LINK_ID": 30760611.000000, "RP_TYPE": 14, "RP_FUNC": 0, "DIRECTION": 0, "LOGKOD": "", "CHANGED": "", "USERID": "", "ST_NAME": "STRÖMSGATAN", "L_REFADDR": "", "L_NREFADDR": "", "R_REFADDR": "", "R_NREFADDR": "", "SPEED_CAT": "7", "ZIPCODE": "59330", "SHAPE_LEN": 51.110800 }, "geometry": { "type": "LineString", "coordinates": [ [ 1549921.910100, 6403780.010400 ], [ 1549913.480000, 6403787.710400 ], [ 1549891.640000, 6403820.850400 ] ] } } +, +{ "type": "Feature", "properties": { "LINK_ID": 80547478.000000, "RP_TYPE": 12, "RP_FUNC": 0, "DIRECTION": 0, "LOGKOD": "", "CHANGED": "", "USERID": "", "ST_NAME": "BREDGATAN", "L_REFADDR": "24", "L_NREFADDR": "20", "R_REFADDR": "19", "R_NREFADDR": "17", "SPEED_CAT": "6", "ZIPCODE": "59330", "SHAPE_LEN": 31.088600 }, "geometry": { "type": "LineString", "coordinates": [ [ 1549656.739950, 6403942.710400 ], [ 1549645.069900, 6403971.520000 ] ] } } +, +{ "type": "Feature", "properties": { "LINK_ID": 30760451.000000, "RP_TYPE": 12, "RP_FUNC": 0, "DIRECTION": 0, "LOGKOD": "", "CHANGED": "", "USERID": "", "ST_NAME": "KVARNGATAN", "L_REFADDR": "", "L_NREFADDR": "", "R_REFADDR": "", "R_NREFADDR": "", "SPEED_CAT": "6", "ZIPCODE": "59333", "SHAPE_LEN": 20.146600 }, "geometry": { "type": "LineString", "coordinates": [ [ 1549594.439950, 6403973.130400 ], [ 1549614.030150, 6403977.820000 ] ] } } +, +{ "type": "Feature", "properties": { "LINK_ID": 30760525.000000, "RP_TYPE": 14, "RP_FUNC": 0, "DIRECTION": 0, "LOGKOD": "", "CHANGED": "", "USERID": "", "ST_NAME": "BÅTSMANSGATAN", "L_REFADDR": "16", "L_NREFADDR": "14", "R_REFADDR": "19", "R_NREFADDR": "15", "SPEED_CAT": "7", "ZIPCODE": "59330", "SHAPE_LEN": 39.254300 }, "geometry": { "type": "LineString", "coordinates": [ [ 1549830.790050, 6403879.130400 ], [ 1549867.520100, 6403892.960000 ] ] } } +, +{ "type": "Feature", "properties": { "LINK_ID": 30760497.000000, "RP_TYPE": 12, "RP_FUNC": 0, "DIRECTION": 1, "LOGKOD": "", "CHANGED": "", "USERID": "", "ST_NAME": "ÖSTRA KYRKOGATAN", "L_REFADDR": "", "L_NREFADDR": "", "R_REFADDR": "", "R_NREFADDR": "", "SPEED_CAT": "6", "ZIPCODE": "59333", "SHAPE_LEN": 24.829800 }, "geometry": { "type": "LineString", "coordinates": [ [ 1549488.599950, 6403913.910400 ], [ 1549496.649950, 6403937.400000 ] ] } } +, +{ "type": "Feature", "properties": { "LINK_ID": 573703847.000000, "RP_TYPE": 10, "RP_FUNC": 1, "DIRECTION": 0, "LOGKOD": "", "CHANGED": "", "USERID": "", "ST_NAME": "STATIONSGATAN", "L_REFADDR": "", "L_NREFADDR": "", "R_REFADDR": "", "R_NREFADDR": "", "SPEED_CAT": "6", "ZIPCODE": "59331", "SHAPE_LEN": 3.259030 }, "geometry": { "type": "LineString", "coordinates": [ [ 1549738.939900, 6403746.560000 ], [ 1549741.290150, 6403748.820000 ] ] } } +, +{ "type": "Feature", "properties": { "LINK_ID": 573730500.000000, "RP_TYPE": 14, "RP_FUNC": 0, "DIRECTION": 1, "LOGKOD": "", "CHANGED": "", "USERID": "", "ST_NAME": "", "L_REFADDR": "", "L_NREFADDR": "", "R_REFADDR": "", "R_NREFADDR": "", "SPEED_CAT": "7", "ZIPCODE": "59330", "SHAPE_LEN": 31.544900 }, "geometry": { "type": "LineString", "coordinates": [ [ 1549528.510100, 6403718.360000 ], [ 1549511.590050, 6403738.200000 ], [ 1549506.220000, 6403739.250400 ] ] } } +, +{ "type": "Feature", "properties": { "LINK_ID": 573730504.000000, "RP_TYPE": 14, "RP_FUNC": 0, "DIRECTION": 1, "LOGKOD": "", "CHANGED": "", "USERID": "", "ST_NAME": "", "L_REFADDR": "", "L_NREFADDR": "", "R_REFADDR": "", "R_NREFADDR": "", "SPEED_CAT": "7", "ZIPCODE": "59330", "SHAPE_LEN": 32.542600 }, "geometry": { "type": "LineString", "coordinates": [ [ 1549497.669850, 6403707.960000 ], [ 1549528.510100, 6403718.360000 ] ] } } +, +{ "type": "Feature", "properties": { "LINK_ID": 30760589.000000, "RP_TYPE": 12, "RP_FUNC": 1, "DIRECTION": 0, "LOGKOD": "", "CHANGED": "", "USERID": "", "ST_NAME": "BRUNNSGATAN", "L_REFADDR": "", "L_NREFADDR": "", "R_REFADDR": "23", "R_NREFADDR": "21", "SPEED_CAT": "7", "ZIPCODE": "59330", "SHAPE_LEN": 47.569300 }, "geometry": { "type": "LineString", "coordinates": [ [ 1549771.489900, 6403810.460000 ], [ 1549754.276900, 6403854.802400 ] ] } } +, +{ "type": "Feature", "properties": { "LINK_ID": 0.000000, "RP_TYPE": 16, "RP_FUNC": 0, "DIRECTION": 0, "LOGKOD": "D", "CHANGED": "0808270836", "USERID": "LO-JKP", "ST_NAME": "STATIONSGATAN", "L_REFADDR": "", "L_NREFADDR": "", "R_REFADDR": "", "R_NREFADDR": "", "SPEED_CAT": "", "ZIPCODE": "59331", "SHAPE_LEN": 34.800000 }, "geometry": { "type": "LineString", "coordinates": [ [ 1549967.599100, 6403744.932000 ], [ 1549999.352500, 6403730.830400 ] ] } } +, +{ "type": "Feature", "properties": { "LINK_ID": 0.000000, "RP_TYPE": 19, "RP_FUNC": 0, "DIRECTION": 0, "LOGKOD": "D", "CHANGED": "0808270839", "USERID": "LO-JKP", "ST_NAME": "STATIONSGATAN", "L_REFADDR": "", "L_NREFADDR": "", "R_REFADDR": "", "R_NREFADDR": "", "SPEED_CAT": "", "ZIPCODE": "59331", "SHAPE_LEN": 9.900000 }, "geometry": { "type": "LineString", "coordinates": [ [ 1549967.599100, 6403744.932000 ], [ 1549975.575600, 6403750.824800 ] ] } } +, +{ "type": "Feature", "properties": { "LINK_ID": 0.000000, "RP_TYPE": 19, "RP_FUNC": 0, "DIRECTION": 0, "LOGKOD": "R", "CHANGED": "0808270840", "USERID": "LO-JKP", "ST_NAME": "STATIONSGATAN", "L_REFADDR": "", "L_NREFADDR": "", "R_REFADDR": "", "R_NREFADDR": "", "SPEED_CAT": "", "ZIPCODE": "59331", "SHAPE_LEN": 18.400000 }, "geometry": { "type": "LineString", "coordinates": [ [ 1549975.575600, 6403750.824800 ], [ 1549992.301750, 6403743.152800 ] ] } } +, +{ "type": "Feature", "properties": { "LINK_ID": 0.000000, "RP_TYPE": 22, "RP_FUNC": 0, "DIRECTION": 0, "LOGKOD": "D", "CHANGED": "0808270840", "USERID": "LO-JKP", "ST_NAME": "STATIONSGATAN", "L_REFADDR": "", "L_NREFADDR": "", "R_REFADDR": "", "R_NREFADDR": "", "SPEED_CAT": "", "ZIPCODE": "59331", "SHAPE_LEN": 16.100000 }, "geometry": { "type": "LineString", "coordinates": [ [ 1550001.325450, 6403756.464000 ], [ 1549992.301750, 6403743.152800 ] ] } } +, +{ "type": "Feature", "properties": { "LINK_ID": 0.000000, "RP_TYPE": 19, "RP_FUNC": 0, "DIRECTION": 0, "LOGKOD": "R", "CHANGED": "0808270842", "USERID": "LO-JKP", "ST_NAME": "STATIONSGATAN", "L_REFADDR": "", "L_NREFADDR": "", "R_REFADDR": "", "R_NREFADDR": "", "SPEED_CAT": "", "ZIPCODE": "59331", "SHAPE_LEN": 12.300000 }, "geometry": { "type": "LineString", "coordinates": [ [ 1549927.421200, 6403767.822400 ], [ 1549936.717550, 6403775.876000 ] ] } } +, +{ "type": "Feature", "properties": { "LINK_ID": 0.000000, "RP_TYPE": 19, "RP_FUNC": 0, "DIRECTION": 0, "LOGKOD": "R", "CHANGED": "0808270842", "USERID": "LO-JKP", "ST_NAME": "STATIONSGATAN", "L_REFADDR": "", "L_NREFADDR": "", "R_REFADDR": "", "R_NREFADDR": "", "SPEED_CAT": "", "ZIPCODE": "59331", "SHAPE_LEN": 46.500000 }, "geometry": { "type": "LineString", "coordinates": [ [ 1549936.717550, 6403775.876000 ], [ 1549958.789600, 6403758.524000 ], [ 1549975.575600, 6403750.824800 ] ] } } +, +{ "type": "Feature", "properties": { "LINK_ID": 80547691.000000, "RP_TYPE": 12, "RP_FUNC": 0, "DIRECTION": 2, "LOGKOD": "R", "CHANGED": "0808270844", "USERID": "LO-JKP", "ST_NAME": "NORRA BANGATAN", "L_REFADDR": "", "L_NREFADDR": "", "R_REFADDR": "", "R_NREFADDR": "", "SPEED_CAT": "6", "ZIPCODE": "59331", "SHAPE_LEN": 209.000000 }, "geometry": { "type": "LineString", "coordinates": [ [ 1549937.660100, 6403662.140000 ], [ 1549881.800100, 6403701.550400 ], [ 1549764.730000, 6403731.290400 ], [ 1549745.501350, 6403736.423200 ] ] } } +, +{ "type": "Feature", "properties": { "LINK_ID": 80547691.000000, "RP_TYPE": 12, "RP_FUNC": 0, "DIRECTION": 2, "LOGKOD": "R", "CHANGED": "0808270844", "USERID": "LO-JKP", "ST_NAME": "NORRA BANGATAN", "L_REFADDR": "", "L_NREFADDR": "", "R_REFADDR": "", "R_NREFADDR": "", "SPEED_CAT": "6", "ZIPCODE": "59331", "SHAPE_LEN": 11.800000 }, "geometry": { "type": "LineString", "coordinates": [ [ 1549745.501350, 6403736.423200 ], [ 1549734.260050, 6403739.820000 ] ] } } +, +{ "type": "Feature", "properties": { "LINK_ID": 0.000000, "RP_TYPE": 12, "RP_FUNC": 0, "DIRECTION": 0, "LOGKOD": "R", "CHANGED": "0808270847", "USERID": "LO-JKP", "ST_NAME": "STATIONSGATAN", "L_REFADDR": "", "L_NREFADDR": "", "R_REFADDR": "", "R_NREFADDR": "", "SPEED_CAT": "", "ZIPCODE": "59331", "SHAPE_LEN": 32.700000 }, "geometry": { "type": "LineString", "coordinates": [ [ 1549927.421200, 6403767.822400 ], [ 1549930.803600, 6403753.404000 ], [ 1549928.832400, 6403735.662400 ] ] } } +, +{ "type": "Feature", "properties": { "LINK_ID": 0.000000, "RP_TYPE": 12, "RP_FUNC": 0, "DIRECTION": 0, "LOGKOD": "R", "CHANGED": "0808270847", "USERID": "LO-JKP", "ST_NAME": "STATIONSGATAN", "L_REFADDR": "", "L_NREFADDR": "", "R_REFADDR": "", "R_NREFADDR": "", "SPEED_CAT": "", "ZIPCODE": "59331", "SHAPE_LEN": 53.100000 }, "geometry": { "type": "LineString", "coordinates": [ [ 1549928.832400, 6403735.662400 ], [ 1549962.732350, 6403727.381600 ], [ 1549967.599100, 6403744.932000 ] ] } } +, +{ "type": "Feature", "properties": { "LINK_ID": 0.000000, "RP_TYPE": 16, "RP_FUNC": 0, "DIRECTION": 0, "LOGKOD": "R", "CHANGED": "0808270848", "USERID": "LO-JKP", "ST_NAME": "STATIONSGATAN", "L_REFADDR": "", "L_NREFADDR": "", "R_REFADDR": "", "R_NREFADDR": "", "SPEED_CAT": "", "ZIPCODE": "59331", "SHAPE_LEN": 44.400000 }, "geometry": { "type": "LineString", "coordinates": [ [ 1549928.832400, 6403735.662400 ], [ 1549886.025300, 6403747.621600 ] ] } } +, +{ "type": "Feature", "properties": { "LINK_ID": 0.000000, "RP_TYPE": 16, "RP_FUNC": 0, "DIRECTION": 0, "LOGKOD": "R", "CHANGED": "0808270848", "USERID": "LO-JKP", "ST_NAME": "STATIONSGATAN", "L_REFADDR": "", "L_NREFADDR": "", "R_REFADDR": "", "R_NREFADDR": "", "SPEED_CAT": "", "ZIPCODE": "59331", "SHAPE_LEN": 11.200000 }, "geometry": { "type": "LineString", "coordinates": [ [ 1549886.025300, 6403747.621600 ], [ 1549875.211350, 6403750.643200 ] ] } } +, +{ "type": "Feature", "properties": { "LINK_ID": 0.000000, "RP_TYPE": 16, "RP_FUNC": 0, "DIRECTION": 0, "LOGKOD": "D", "CHANGED": "0808270848", "USERID": "LO-JKP", "ST_NAME": "STATIONSGATAN", "L_REFADDR": "", "L_NREFADDR": "", "R_REFADDR": "", "R_NREFADDR": "", "SPEED_CAT": "", "ZIPCODE": "59331", "SHAPE_LEN": 19.600000 }, "geometry": { "type": "LineString", "coordinates": [ [ 1549888.409150, 6403767.056000 ], [ 1549886.025300, 6403747.621600 ] ] } } +, +{ "type": "Feature", "properties": { "LINK_ID": 0.000000, "RP_TYPE": 19, "RP_FUNC": 0, "DIRECTION": 0, "LOGKOD": "D", "CHANGED": "0808270922", "USERID": "LO-JKP", "ST_NAME": "HALLSTRÖMSGATAN", "L_REFADDR": "", "L_NREFADDR": "", "R_REFADDR": "", "R_NREFADDR": "", "SPEED_CAT": "", "ZIPCODE": "59331", "SHAPE_LEN": 20.700000 }, "geometry": { "type": "LineString", "coordinates": [ [ 1549745.501350, 6403736.423200 ], [ 1549760.669300, 6403722.331200 ] ] } } +, +{ "type": "Feature", "properties": { "LINK_ID": 0.000000, "RP_TYPE": 19, "RP_FUNC": 0, "DIRECTION": 0, "LOGKOD": "D", "CHANGED": "0808270923", "USERID": "LO-JKP", "ST_NAME": "HALLSTRÖMSGATAN", "L_REFADDR": "", "L_NREFADDR": "", "R_REFADDR": "", "R_NREFADDR": "", "SPEED_CAT": "", "ZIPCODE": "59331", "SHAPE_LEN": 126.700000 }, "geometry": { "type": "LineString", "coordinates": [ [ 1549760.669300, 6403722.331200 ], [ 1549771.919700, 6403716.340800 ], [ 1549815.248650, 6403610.940000 ] ] } } +, +{ "type": "Feature", "properties": { "LINK_ID": 80547535.000000, "RP_TYPE": 10, "RP_FUNC": 1, "DIRECTION": 1, "LOGKOD": "R", "CHANGED": "0808270933", "USERID": "LO-JKP", "ST_NAME": "ESPLANADEN", "L_REFADDR": "", "L_NREFADDR": "", "R_REFADDR": "5", "R_NREFADDR": "1", "SPEED_CAT": "6", "ZIPCODE": "59331", "SHAPE_LEN": 5.000000 }, "geometry": { "type": "LineString", "coordinates": [ [ 1549681.045700, 6403715.598400 ], [ 1549679.130150, 6403720.210400 ] ] } } +, +{ "type": "Feature", "properties": { "LINK_ID": 0.000000, "RP_TYPE": 19, "RP_FUNC": 0, "DIRECTION": 0, "LOGKOD": "D", "CHANGED": "0808270933", "USERID": "LO-JKP", "ST_NAME": "SÖDRA JÄRNVÄGSGATAN", "L_REFADDR": "", "L_NREFADDR": "", "R_REFADDR": "", "R_NREFADDR": "", "SPEED_CAT": "", "ZIPCODE": "59331", "SHAPE_LEN": 68.600000 }, "geometry": { "type": "LineString", "coordinates": [ [ 1549745.501350, 6403736.423200 ], [ 1549740.387150, 6403731.321600 ], [ 1549681.045700, 6403715.598400 ] ] } } +, +{ "type": "Feature", "properties": { "LINK_ID": 30760732.000000, "RP_TYPE": 10, "RP_FUNC": 1, "DIRECTION": 2, "LOGKOD": "R", "CHANGED": "0808270934", "USERID": "LO-JKP", "ST_NAME": "ESPLANADEN", "L_REFADDR": "2", "L_NREFADDR": "2", "R_REFADDR": "", "R_NREFADDR": "", "SPEED_CAT": "6", "ZIPCODE": "59332", "SHAPE_LEN": 56.700000 }, "geometry": { "type": "LineString", "coordinates": [ [ 1549683.510050, 6403654.550400 ], [ 1549667.935400, 6403709.100000 ] ] } } +, +{ "type": "Feature", "properties": { "LINK_ID": 30760732.000000, "RP_TYPE": 10, "RP_FUNC": 1, "DIRECTION": 2, "LOGKOD": "R", "CHANGED": "0808270934", "USERID": "LO-JKP", "ST_NAME": "ESPLANADEN", "L_REFADDR": "2", "L_NREFADDR": "2", "R_REFADDR": "", "R_NREFADDR": "", "SPEED_CAT": "6", "ZIPCODE": "59332", "SHAPE_LEN": 6.800000 }, "geometry": { "type": "LineString", "coordinates": [ [ 1549667.935400, 6403709.100000 ], [ 1549666.080050, 6403715.590400 ] ] } } +, +{ "type": "Feature", "properties": { "LINK_ID": 0.000000, "RP_TYPE": 19, "RP_FUNC": 0, "DIRECTION": 0, "LOGKOD": "D", "CHANGED": "0808270934", "USERID": "LO-JKP", "ST_NAME": "SÖDRA JÄRNVÄGSGATAN", "L_REFADDR": "", "L_NREFADDR": "", "R_REFADDR": "", "R_NREFADDR": "", "SPEED_CAT": "", "ZIPCODE": "59331", "SHAPE_LEN": 14.600000 }, "geometry": { "type": "LineString", "coordinates": [ [ 1549681.045700, 6403715.598400 ], [ 1549667.935400, 6403709.100000 ] ] } } +, +{ "type": "Feature", "properties": { "LINK_ID": 0.000000, "RP_TYPE": 18, "RP_FUNC": 0, "DIRECTION": 0, "LOGKOD": "D", "CHANGED": "0808270935", "USERID": "LO-JKP", "ST_NAME": "HALLSTRÖMSGATAN", "L_REFADDR": "", "L_NREFADDR": "", "R_REFADDR": "", "R_NREFADDR": "", "SPEED_CAT": "", "ZIPCODE": "59331", "SHAPE_LEN": 40.200000 }, "geometry": { "type": "LineString", "coordinates": [ [ 1549763.755600, 6403714.004800 ], [ 1549738.019750, 6403704.509600 ], [ 1549731.660600, 6403715.640800 ] ] } } +, +{ "type": "Feature", "properties": { "LINK_ID": 80547428.000000, "RP_TYPE": 14, "RP_FUNC": 0, "DIRECTION": 0, "LOGKOD": "R", "CHANGED": "0808270936", "USERID": "LO-JKP", "ST_NAME": "HALLSTRÖMSGATAN", "L_REFADDR": "10", "L_NREFADDR": "2", "R_REFADDR": "1", "R_NREFADDR": "1", "SPEED_CAT": "7", "ZIPCODE": "59331", "SHAPE_LEN": 15.200000 }, "geometry": { "type": "LineString", "coordinates": [ [ 1549766.018350, 6403708.067200 ], [ 1549763.755600, 6403714.004800 ], [ 1549760.669300, 6403722.331200 ] ] } } +, +{ "type": "Feature", "properties": { "LINK_ID": 0.000000, "RP_TYPE": 18, "RP_FUNC": 0, "DIRECTION": 0, "LOGKOD": "D", "CHANGED": "0808270936", "USERID": "LO-JKP", "ST_NAME": "HALLSTRÖMSGATAN", "L_REFADDR": "", "L_NREFADDR": "", "R_REFADDR": "", "R_NREFADDR": "", "SPEED_CAT": "", "ZIPCODE": "59331", "SHAPE_LEN": 48.800000 }, "geometry": { "type": "LineString", "coordinates": [ [ 1549766.018350, 6403708.067200 ], [ 1549736.048550, 6403696.628800 ], [ 1549743.183300, 6403681.558400 ] ] } } +, +{ "type": "Feature", "properties": { "LINK_ID": 80547428.000000, "RP_TYPE": 14, "RP_FUNC": 0, "DIRECTION": 0, "LOGKOD": "R", "CHANGED": "0808270936", "USERID": "LO-JKP", "ST_NAME": "HALLSTRÖMSGATAN", "L_REFADDR": "10", "L_NREFADDR": "2", "R_REFADDR": "1", "R_NREFADDR": "1", "SPEED_CAT": "7", "ZIPCODE": "59331", "SHAPE_LEN": 22.800000 }, "geometry": { "type": "LineString", "coordinates": [ [ 1549774.118750, 6403686.709600 ], [ 1549766.018350, 6403708.067200 ] ] } } +, +{ "type": "Feature", "properties": { "LINK_ID": 0.000000, "RP_TYPE": 18, "RP_FUNC": 0, "DIRECTION": 0, "LOGKOD": "D", "CHANGED": "0808270936", "USERID": "LO-JKP", "ST_NAME": "HALLSTRÖMSGATAN", "L_REFADDR": "", "L_NREFADDR": "", "R_REFADDR": "", "R_NREFADDR": "", "SPEED_CAT": "", "ZIPCODE": "59331", "SHAPE_LEN": 36.200000 }, "geometry": { "type": "LineString", "coordinates": [ [ 1549774.118750, 6403686.709600 ], [ 1549747.876450, 6403676.916800 ], [ 1549751.868550, 6403670.136800 ] ] } } +, +{ "type": "Feature", "properties": { "LINK_ID": 80547428.000000, "RP_TYPE": 14, "RP_FUNC": 0, "DIRECTION": 0, "LOGKOD": "R", "CHANGED": "0808270937", "USERID": "LO-JKP", "ST_NAME": "HALLSTRÖMSGATAN", "L_REFADDR": "10", "L_NREFADDR": "2", "R_REFADDR": "1", "R_NREFADDR": "1", "SPEED_CAT": "7", "ZIPCODE": "59331", "SHAPE_LEN": 25.900000 }, "geometry": { "type": "LineString", "coordinates": [ [ 1549783.651700, 6403662.588800 ], [ 1549778.530150, 6403674.660000 ], [ 1549774.118750, 6403686.709600 ] ] } } +, +{ "type": "Feature", "properties": { "LINK_ID": 80547535.000000, "RP_TYPE": 10, "RP_FUNC": 1, "DIRECTION": 1, "LOGKOD": "R", "CHANGED": "0808270938", "USERID": "LO-JKP", "ST_NAME": "ESPLANADEN", "L_REFADDR": "", "L_NREFADDR": "", "R_REFADDR": "5", "R_NREFADDR": "1", "SPEED_CAT": "6", "ZIPCODE": "59331", "SHAPE_LEN": 18.800000 }, "geometry": { "type": "LineString", "coordinates": [ [ 1549703.060000, 6403662.590400 ], [ 1549695.854900, 6403679.940000 ] ] } } +, +{ "type": "Feature", "properties": { "LINK_ID": 80547535.000000, "RP_TYPE": 10, "RP_FUNC": 1, "DIRECTION": 1, "LOGKOD": "R", "CHANGED": "0808270938", "USERID": "LO-JKP", "ST_NAME": "ESPLANADEN", "L_REFADDR": "", "L_NREFADDR": "", "R_REFADDR": "5", "R_NREFADDR": "1", "SPEED_CAT": "6", "ZIPCODE": "59331", "SHAPE_LEN": 38.600000 }, "geometry": { "type": "LineString", "coordinates": [ [ 1549695.854900, 6403679.940000 ], [ 1549681.045700, 6403715.598400 ] ] } } +, +{ "type": "Feature", "properties": { "LINK_ID": 0.000000, "RP_TYPE": 16, "RP_FUNC": 0, "DIRECTION": 0, "LOGKOD": "D", "CHANGED": "0808270938", "USERID": "LO-JKP", "ST_NAME": "ESPLANADEN", "L_REFADDR": "", "L_NREFADDR": "", "R_REFADDR": "", "R_NREFADDR": "", "SPEED_CAT": "", "ZIPCODE": "59331", "SHAPE_LEN": 27.100000 }, "geometry": { "type": "LineString", "coordinates": [ [ 1549695.854900, 6403679.940000 ], [ 1549710.817400, 6403684.797600 ], [ 1549716.384850, 6403674.867200 ] ] } } +, +{ "type": "Feature", "properties": { "LINK_ID": 0.000000, "RP_TYPE": 16, "RP_FUNC": 0, "DIRECTION": 0, "LOGKOD": "D", "CHANGED": "0808270939", "USERID": "LO-JKP", "ST_NAME": "ESPLANADEN", "L_REFADDR": "", "L_NREFADDR": "", "R_REFADDR": "", "R_NREFADDR": "", "SPEED_CAT": "", "ZIPCODE": "59331", "SHAPE_LEN": 18.900000 }, "geometry": { "type": "LineString", "coordinates": [ [ 1549704.982200, 6403658.172000 ], [ 1549717.515000, 6403662.725600 ], [ 1549719.527500, 6403657.506400 ] ] } } +, +{ "type": "Feature", "properties": { "LINK_ID": 0.000000, "RP_TYPE": 19, "RP_FUNC": 0, "DIRECTION": 0, "LOGKOD": "D", "CHANGED": "0808271124", "USERID": "LO-JKP", "ST_NAME": "FÄNGELSETORGET", "L_REFADDR": "", "L_NREFADDR": "", "R_REFADDR": "", "R_NREFADDR": "", "SPEED_CAT": "", "ZIPCODE": "59331", "SHAPE_LEN": 192.800000 }, "geometry": { "type": "LineString", "coordinates": [ [ 1549908.200950, 6403637.271200 ], [ 1549910.100750, 6403648.924800 ], [ 1549887.237000, 6403693.868800 ], [ 1549878.956000, 6403699.779200 ], [ 1549807.205300, 6403714.370400 ], [ 1549760.669300, 6403722.331200 ] ] } } +, +{ "type": "Feature", "properties": { "LINK_ID": 1900112527.000000, "RP_TYPE": 12, "RP_FUNC": 0, "DIRECTION": 2, "LOGKOD": "R", "CHANGED": "0808271126", "USERID": "LO-JKP", "ST_NAME": "FÄNGELSETORGET", "L_REFADDR": "", "L_NREFADDR": "", "R_REFADDR": "", "R_NREFADDR": "", "SPEED_CAT": "", "ZIPCODE": "59331", "SHAPE_LEN": 100.800000 }, "geometry": { "type": "LineString", "coordinates": [ [ 1549809.770450, 6403695.048800 ], [ 1549792.424450, 6403687.958400 ], [ 1549824.218800, 6403612.351200 ] ] } } +, +{ "type": "Feature", "properties": { "LINK_ID": 0.000000, "RP_TYPE": 19, "RP_FUNC": 0, "DIRECTION": 0, "LOGKOD": "D", "CHANGED": "0808271126", "USERID": "LO-JKP", "ST_NAME": "FÄNGELSETOGET", "L_REFADDR": "", "L_NREFADDR": "", "R_REFADDR": "", "R_NREFADDR": "", "SPEED_CAT": "", "ZIPCODE": "59331", "SHAPE_LEN": 17.500000 }, "geometry": { "type": "LineString", "coordinates": [ [ 1549817.102950, 6403710.910400 ], [ 1549809.770450, 6403695.048800 ] ] } } +, +{ "type": "Feature", "properties": { "LINK_ID": 80547449.000000, "RP_TYPE": 10, "RP_FUNC": 1, "DIRECTION": 0, "LOGKOD": "R", "CHANGED": "0808271128", "USERID": "LO-JKP", "ST_NAME": "SÖDRA VARVSGATAN", "L_REFADDR": "40", "L_NREFADDR": "32", "R_REFADDR": "21", "R_NREFADDR": "15", "SPEED_CAT": "6", "ZIPCODE": "59350", "SHAPE_LEN": 23.500000 }, "geometry": { "type": "LineString", "coordinates": [ [ 1549989.554600, 6403806.848000 ], [ 1549976.880050, 6403812.990400 ], [ 1549968.489850, 6403817.350400 ] ] } } +, +{ "type": "Feature", "properties": { "LINK_ID": 573730501.000000, "RP_TYPE": 14, "RP_FUNC": 0, "DIRECTION": 0, "LOGKOD": "R", "CHANGED": "0808271500", "USERID": "LO-JKP", "ST_NAME": "", "L_REFADDR": "", "L_NREFADDR": "", "R_REFADDR": "", "R_NREFADDR": "", "SPEED_CAT": "7", "ZIPCODE": "59330", "SHAPE_LEN": 9.900000 }, "geometry": { "type": "LineString", "coordinates": [ [ 1549570.600050, 6403733.360000 ], [ 1549579.722100, 6403737.201600 ] ] } } +, +{ "type": "Feature", "properties": { "LINK_ID": 573730501.000000, "RP_TYPE": 14, "RP_FUNC": 0, "DIRECTION": 0, "LOGKOD": "R", "CHANGED": "0808271500", "USERID": "LO-JKP", "ST_NAME": "", "L_REFADDR": "", "L_NREFADDR": "", "R_REFADDR": "", "R_NREFADDR": "", "SPEED_CAT": "7", "ZIPCODE": "59330", "SHAPE_LEN": 4.900000 }, "geometry": { "type": "LineString", "coordinates": [ [ 1549579.722100, 6403737.201600 ], [ 1549584.219950, 6403739.090400 ] ] } } +, +{ "type": "Feature", "properties": { "LINK_ID": 573730505.000000, "RP_TYPE": 14, "RP_FUNC": 0, "DIRECTION": 0, "LOGKOD": "R", "CHANGED": "0808271504", "USERID": "LO-JKP", "ST_NAME": "LÄROVERKSGATAN", "L_REFADDR": "", "L_NREFADDR": "", "R_REFADDR": "", "R_NREFADDR": "", "SPEED_CAT": "7", "ZIPCODE": "59333", "SHAPE_LEN": 79.800000 }, "geometry": { "type": "LineString", "coordinates": [ [ 1549471.922100, 6403800.288000 ], [ 1549539.838900, 6403825.187200 ], [ 1549546.809850, 6403827.740000 ] ] } } +, +{ "type": "Feature", "properties": { "LINK_ID": 0.000000, "RP_TYPE": 19, "RP_FUNC": 0, "DIRECTION": 0, "LOGKOD": "R", "CHANGED": "0808271508", "USERID": "LO-JKP", "ST_NAME": "SÖDRA JÄRNVÄGSGATAN", "L_REFADDR": "", "L_NREFADDR": "", "R_REFADDR": "", "R_NREFADDR": "", "SPEED_CAT": "", "ZIPCODE": "59332", "SHAPE_LEN": 23.000000 }, "geometry": { "type": "LineString", "coordinates": [ [ 1549574.529850, 6403669.305600 ], [ 1549580.125650, 6403672.576800 ], [ 1549595.345750, 6403678.918400 ] ] } } +, +{ "type": "Feature", "properties": { "LINK_ID": 0.000000, "RP_TYPE": 19, "RP_FUNC": 0, "DIRECTION": 0, "LOGKOD": "R", "CHANGED": "0808271508", "USERID": "LO-JKP", "ST_NAME": "SÖDRA JÄRNVÄGSGATAN", "L_REFADDR": "", "L_NREFADDR": "", "R_REFADDR": "", "R_NREFADDR": "", "SPEED_CAT": "", "ZIPCODE": "59332", "SHAPE_LEN": 80.000000 }, "geometry": { "type": "LineString", "coordinates": [ [ 1549595.345750, 6403678.918400 ], [ 1549617.976400, 6403688.348000 ], [ 1549648.329450, 6403702.939200 ], [ 1549654.639250, 6403704.509600 ], [ 1549660.157350, 6403703.329600 ], [ 1549667.935400, 6403709.100000 ] ] } } +, +{ "type": "Feature", "properties": { "LINK_ID": 0.000000, "RP_TYPE": 19, "RP_FUNC": 0, "DIRECTION": 0, "LOGKOD": "D", "CHANGED": "0808271508", "USERID": "LO-JKP", "ST_NAME": "ÖSTRA KYRKOGATAN", "L_REFADDR": "", "L_NREFADDR": "", "R_REFADDR": "", "R_NREFADDR": "", "SPEED_CAT": "", "ZIPCODE": "59333", "SHAPE_LEN": 68.800000 }, "geometry": { "type": "LineString", "coordinates": [ [ 1549579.722100, 6403737.201600 ], [ 1549586.633550, 6403729.352000 ], [ 1549598.065250, 6403704.509600 ], [ 1549595.698200, 6403698.599200 ], [ 1549588.604750, 6403693.078400 ], [ 1549592.674200, 6403684.530400 ], [ 1549595.345750, 6403678.918400 ] ] } } +, +{ "type": "Feature", "properties": { "LINK_ID": 0.000000, "RP_TYPE": 19, "RP_FUNC": 0, "DIRECTION": 0, "LOGKOD": "R", "CHANGED": "0808271508", "USERID": "LO-JKP", "ST_NAME": "KVARNGATAN", "L_REFADDR": "", "L_NREFADDR": "", "R_REFADDR": "", "R_NREFADDR": "", "SPEED_CAT": "", "ZIPCODE": "59333", "SHAPE_LEN": 185.700000 }, "geometry": { "type": "LineString", "coordinates": [ [ 1549495.671350, 6403901.486400 ], [ 1549498.716350, 6403900.056000 ], [ 1549509.356700, 6403886.655200 ], [ 1549520.005100, 6403865.753600 ], [ 1549533.012150, 6403839.740800 ], [ 1549539.838900, 6403825.187200 ], [ 1549547.990750, 6403807.808000 ], [ 1549557.459650, 6403786.516800 ], [ 1549566.128450, 6403765.624800 ], [ 1549574.805650, 6403746.692800 ], [ 1549579.722100, 6403737.201600 ] ] } } +, +{ "type": "Feature", "properties": { "LINK_ID": 80547462.000000, "RP_TYPE": 12, "RP_FUNC": 1, "DIRECTION": 0, "LOGKOD": "R", "CHANGED": "0808271508", "USERID": "LO-JKP", "ST_NAME": "ÖSTRA KYRKOGATAN", "L_REFADDR": "44", "L_NREFADDR": "38", "R_REFADDR": "61", "R_NREFADDR": "53", "SPEED_CAT": "7", "ZIPCODE": "59333", "SHAPE_LEN": 14.200000 }, "geometry": { "type": "LineString", "coordinates": [ [ 1549501.325600, 6403908.552800 ], [ 1549497.550150, 6403911.790400 ], [ 1549488.599950, 6403913.910400 ] ] } } +, +{ "type": "Feature", "properties": { "LINK_ID": 0.000000, "RP_TYPE": 19, "RP_FUNC": 0, "DIRECTION": 0, "LOGKOD": "D", "CHANGED": "0808271508", "USERID": "LO-JKP", "ST_NAME": "ÖSTRA KYRKOGATAN", "L_REFADDR": "", "L_NREFADDR": "", "R_REFADDR": "", "R_NREFADDR": "", "SPEED_CAT": "", "ZIPCODE": "59333", "SHAPE_LEN": 9.100000 }, "geometry": { "type": "LineString", "coordinates": [ [ 1549495.671350, 6403901.486400 ], [ 1549501.325600, 6403908.552800 ] ] } } +, +{ "type": "Feature", "properties": { "LINK_ID": 0.000000, "RP_TYPE": 19, "RP_FUNC": 0, "DIRECTION": 0, "LOGKOD": "R", "CHANGED": "0808271509", "USERID": "LO-JKP", "ST_NAME": "SLOTTHOLMSLEDEN", "L_REFADDR": "", "L_NREFADDR": "", "R_REFADDR": "", "R_NREFADDR": "", "SPEED_CAT": "", "ZIPCODE": "59331", "SHAPE_LEN": 29.400000 }, "geometry": { "type": "LineString", "coordinates": [ [ 1549745.501350, 6403736.423200 ], [ 1549760.487650, 6403761.674400 ] ] } } +, +{ "type": "Feature", "properties": { "LINK_ID": 0.000000, "RP_TYPE": 19, "RP_FUNC": 0, "DIRECTION": 0, "LOGKOD": "R", "CHANGED": "0808271509", "USERID": "LO-JKP", "ST_NAME": "SLOTTHOLMSLEDEN", "L_REFADDR": "", "L_NREFADDR": "", "R_REFADDR": "", "R_NREFADDR": "", "SPEED_CAT": "", "ZIPCODE": "59331", "SHAPE_LEN": 130.200000 }, "geometry": { "type": "LineString", "coordinates": [ [ 1549760.487650, 6403761.674400 ], [ 1549773.107600, 6403768.775200 ], [ 1549794.000150, 6403771.925600 ], [ 1549806.223900, 6403771.925600 ], [ 1549847.621200, 6403768.775200 ], [ 1549874.427700, 6403767.595200 ], [ 1549888.409150, 6403767.056000 ] ] } } +, +{ "type": "Feature", "properties": { "LINK_ID": 30760574.000000, "RP_TYPE": 12, "RP_FUNC": 0, "DIRECTION": 0, "LOGKOD": "R", "CHANGED": "0808271509", "USERID": "LO-JKP", "ST_NAME": "BÅTSMANSGATAN", "L_REFADDR": "26", "L_NREFADDR": "24", "R_REFADDR": "", "R_NREFADDR": "", "SPEED_CAT": "6", "ZIPCODE": "59330", "SHAPE_LEN": 5.600000 }, "geometry": { "type": "LineString", "coordinates": [ [ 1549703.919950, 6403834.130400 ], [ 1549709.114300, 6403836.262400 ] ] } } +, +{ "type": "Feature", "properties": { "LINK_ID": 30760574.000000, "RP_TYPE": 12, "RP_FUNC": 0, "DIRECTION": 0, "LOGKOD": "R", "CHANGED": "0808271509", "USERID": "LO-JKP", "ST_NAME": "BÅTSMANSGATAN", "L_REFADDR": "26", "L_NREFADDR": "24", "R_REFADDR": "", "R_NREFADDR": "", "SPEED_CAT": "6", "ZIPCODE": "59330", "SHAPE_LEN": 48.800000 }, "geometry": { "type": "LineString", "coordinates": [ [ 1549709.114300, 6403836.262400 ], [ 1549754.276900, 6403854.802400 ] ] } } +, +{ "type": "Feature", "properties": { "LINK_ID": 30760590.000000, "RP_TYPE": 12, "RP_FUNC": 0, "DIRECTION": 0, "LOGKOD": "R", "CHANGED": "0808271510", "USERID": "LO-JKP", "ST_NAME": "BÅTSMANSGATAN", "L_REFADDR": "30", "L_NREFADDR": "28", "R_REFADDR": "", "R_NREFADDR": "", "SPEED_CAT": "6", "ZIPCODE": "59330", "SHAPE_LEN": 37.200000 }, "geometry": { "type": "LineString", "coordinates": [ [ 1549635.170150, 6403808.780000 ], [ 1549670.099800, 6403821.660000 ] ] } } +, +{ "type": "Feature", "properties": { "LINK_ID": 0.000000, "RP_TYPE": 16, "RP_FUNC": 0, "DIRECTION": 0, "LOGKOD": "D", "CHANGED": "0808271510", "USERID": "LO-JKP", "ST_NAME": "BÅTSMANSGATAN", "L_REFADDR": "", "L_NREFADDR": "", "R_REFADDR": "", "R_NREFADDR": "", "SPEED_CAT": "", "ZIPCODE": "59330", "SHAPE_LEN": 45.400000 }, "geometry": { "type": "LineString", "coordinates": [ [ 1549670.099800, 6403821.660000 ], [ 1549661.138750, 6403843.681600 ], [ 1549652.074100, 6403839.340800 ], [ 1549647.702600, 6403850.082400 ] ] } } +, +{ "type": "Feature", "properties": { "LINK_ID": 30760590.000000, "RP_TYPE": 12, "RP_FUNC": 0, "DIRECTION": 0, "LOGKOD": "R", "CHANGED": "0808271511", "USERID": "LO-JKP", "ST_NAME": "BÅTSMANSGATAN", "L_REFADDR": "30", "L_NREFADDR": "28", "R_REFADDR": "", "R_NREFADDR": "", "SPEED_CAT": "6", "ZIPCODE": "59330", "SHAPE_LEN": 4.700000 }, "geometry": { "type": "LineString", "coordinates": [ [ 1549670.099800, 6403821.660000 ], [ 1549674.526600, 6403823.292000 ] ] } } +, +{ "type": "Feature", "properties": { "LINK_ID": 30760590.000000, "RP_TYPE": 12, "RP_FUNC": 0, "DIRECTION": 0, "LOGKOD": "R", "CHANGED": "0808271511", "USERID": "LO-JKP", "ST_NAME": "BÅTSMANSGATAN", "L_REFADDR": "30", "L_NREFADDR": "28", "R_REFADDR": "", "R_NREFADDR": "", "SPEED_CAT": "6", "ZIPCODE": "59330", "SHAPE_LEN": 31.300000 }, "geometry": { "type": "LineString", "coordinates": [ [ 1549674.526600, 6403823.292000 ], [ 1549703.919950, 6403834.130400 ] ] } } +, +{ "type": "Feature", "properties": { "LINK_ID": 0.000000, "RP_TYPE": 16, "RP_FUNC": 0, "DIRECTION": 0, "LOGKOD": "D", "CHANGED": "0808271511", "USERID": "LO-JKP", "ST_NAME": "BÅTSMANSGATAN", "L_REFADDR": "", "L_NREFADDR": "", "R_REFADDR": "", "R_NREFADDR": "", "SPEED_CAT": "", "ZIPCODE": "59330", "SHAPE_LEN": 23.800000 }, "geometry": { "type": "LineString", "coordinates": [ [ 1549674.526600, 6403823.292000 ], [ 1549664.644350, 6403844.952000 ] ] } } +, +{ "type": "Feature", "properties": { "LINK_ID": 0.000000, "RP_TYPE": 19, "RP_FUNC": 0, "DIRECTION": 0, "LOGKOD": "R", "CHANGED": "0808271512", "USERID": "LO-JKP", "ST_NAME": "BREDGATAN", "L_REFADDR": "", "L_NREFADDR": "", "R_REFADDR": "", "R_NREFADDR": "", "SPEED_CAT": "", "ZIPCODE": "59330", "SHAPE_LEN": 47.100000 }, "geometry": { "type": "LineString", "coordinates": [ [ 1549760.487650, 6403761.674400 ], [ 1549753.539450, 6403766.201600 ], [ 1549734.473200, 6403778.625600 ], [ 1549728.022350, 6403793.287200 ] ] } } +, +{ "type": "Feature", "properties": { "LINK_ID": 0.000000, "RP_TYPE": 19, "RP_FUNC": 0, "DIRECTION": 0, "LOGKOD": "R", "CHANGED": "0808271513", "USERID": "LO-JKP", "ST_NAME": "BREDGATAN", "L_REFADDR": "", "L_NREFADDR": "", "R_REFADDR": "", "R_NREFADDR": "", "SPEED_CAT": "", "ZIPCODE": "59330", "SHAPE_LEN": 9.300000 }, "geometry": { "type": "LineString", "coordinates": [ [ 1549728.022350, 6403793.287200 ], [ 1549724.270700, 6403801.813600 ] ] } } +, +{ "type": "Feature", "properties": { "LINK_ID": 0.000000, "RP_TYPE": 19, "RP_FUNC": 0, "DIRECTION": 0, "LOGKOD": "R", "CHANGED": "0808271513", "USERID": "LO-JKP", "ST_NAME": "BREDGATAN", "L_REFADDR": "", "L_NREFADDR": "", "R_REFADDR": "", "R_NREFADDR": "", "SPEED_CAT": "", "ZIPCODE": "59330", "SHAPE_LEN": 37.600000 }, "geometry": { "type": "LineString", "coordinates": [ [ 1549724.270700, 6403801.813600 ], [ 1549709.114300, 6403836.262400 ] ] } } +, +{ "type": "Feature", "properties": { "LINK_ID": 0.000000, "RP_TYPE": 19, "RP_FUNC": 0, "DIRECTION": 0, "LOGKOD": "R", "CHANGED": "0808271514", "USERID": "LO-JKP", "ST_NAME": "BREDGATAN", "L_REFADDR": "", "L_NREFADDR": "", "R_REFADDR": "", "R_NREFADDR": "", "SPEED_CAT": "", "ZIPCODE": "59330", "SHAPE_LEN": 51.100000 }, "geometry": { "type": "LineString", "coordinates": [ [ 1549754.276900, 6403854.802400 ], [ 1549737.228050, 6403806.618400 ] ] } } +, +{ "type": "Feature", "properties": { "LINK_ID": 0.000000, "RP_TYPE": 19, "RP_FUNC": 0, "DIRECTION": 0, "LOGKOD": "R", "CHANGED": "0808271514", "USERID": "LO-JKP", "ST_NAME": "BREDGATAN", "L_REFADDR": "", "L_NREFADDR": "", "R_REFADDR": "", "R_NREFADDR": "", "SPEED_CAT": "", "ZIPCODE": "59330", "SHAPE_LEN": 13.800000 }, "geometry": { "type": "LineString", "coordinates": [ [ 1549737.228050, 6403806.618400 ], [ 1549724.270700, 6403801.813600 ] ] } } +, +{ "type": "Feature", "properties": { "LINK_ID": 80547503.000000, "RP_TYPE": 12, "RP_FUNC": 1, "DIRECTION": 0, "LOGKOD": "R", "CHANGED": "0808271514", "USERID": "LO-JKP", "ST_NAME": "BRUNNSGATAN", "L_REFADDR": "", "L_NREFADDR": "", "R_REFADDR": "25", "R_NREFADDR": "25", "SPEED_CAT": "7", "ZIPCODE": "59330", "SHAPE_LEN": 17.200000 }, "geometry": { "type": "LineString", "coordinates": [ [ 1549774.889100, 6403793.607200 ], [ 1549771.489900, 6403810.460000 ] ] } } +, +{ "type": "Feature", "properties": { "LINK_ID": 0.000000, "RP_TYPE": 19, "RP_FUNC": 0, "DIRECTION": 0, "LOGKOD": "D", "CHANGED": "0808271514", "USERID": "LO-JKP", "ST_NAME": "BRUNNSGATAN", "L_REFADDR": "", "L_NREFADDR": "", "R_REFADDR": "", "R_NREFADDR": "", "SPEED_CAT": "", "ZIPCODE": "59330", "SHAPE_LEN": 40.200000 }, "geometry": { "type": "LineString", "coordinates": [ [ 1549737.228050, 6403806.618400 ], [ 1549769.560700, 6403793.607200 ], [ 1549774.889100, 6403793.607200 ] ] } } +, +{ "type": "Feature", "properties": { "LINK_ID": 80547503.000000, "RP_TYPE": 12, "RP_FUNC": 1, "DIRECTION": 0, "LOGKOD": "R", "CHANGED": "0808271515", "USERID": "LO-JKP", "ST_NAME": "BRUNNSGATAN", "L_REFADDR": "", "L_NREFADDR": "", "R_REFADDR": "25", "R_NREFADDR": "25", "SPEED_CAT": "7", "ZIPCODE": "59330", "SHAPE_LEN": 14.500000 }, "geometry": { "type": "LineString", "coordinates": [ [ 1549776.080150, 6403777.100000 ], [ 1549775.301400, 6403791.567200 ] ] } } +, +{ "type": "Feature", "properties": { "LINK_ID": 80547503.000000, "RP_TYPE": 12, "RP_FUNC": 1, "DIRECTION": 0, "LOGKOD": "R", "CHANGED": "0808271515", "USERID": "LO-JKP", "ST_NAME": "BRUNNSGATAN", "L_REFADDR": "", "L_NREFADDR": "", "R_REFADDR": "25", "R_NREFADDR": "25", "SPEED_CAT": "7", "ZIPCODE": "59330", "SHAPE_LEN": 2.100000 }, "geometry": { "type": "LineString", "coordinates": [ [ 1549775.301400, 6403791.567200 ], [ 1549774.889100, 6403793.607200 ] ] } } +, +{ "type": "Feature", "properties": { "LINK_ID": 0.000000, "RP_TYPE": 19, "RP_FUNC": 0, "DIRECTION": 0, "LOGKOD": "R", "CHANGED": "0808271516", "USERID": "LO-JKP", "ST_NAME": "SLOTTHOLMSLEDEN", "L_REFADDR": "", "L_NREFADDR": "", "R_REFADDR": "", "R_NREFADDR": "", "SPEED_CAT": "", "ZIPCODE": "59331", "SHAPE_LEN": 23.500000 }, "geometry": { "type": "LineString", "coordinates": [ [ 1549888.409150, 6403767.056000 ], [ 1549894.924400, 6403766.804800 ], [ 1549911.882600, 6403767.984800 ] ] } } +, +{ "type": "Feature", "properties": { "LINK_ID": 0.000000, "RP_TYPE": 19, "RP_FUNC": 0, "DIRECTION": 0, "LOGKOD": "R", "CHANGED": "0808271516", "USERID": "LO-JKP", "ST_NAME": "SLOTTHOLMSLEDEN", "L_REFADDR": "", "L_NREFADDR": "", "R_REFADDR": "", "R_NREFADDR": "", "SPEED_CAT": "", "ZIPCODE": "59331", "SHAPE_LEN": 15.500000 }, "geometry": { "type": "LineString", "coordinates": [ [ 1549911.882600, 6403767.984800 ], [ 1549927.421200, 6403767.822400 ] ] } } +, +{ "type": "Feature", "properties": { "LINK_ID": 30760596.000000, "RP_TYPE": 14, "RP_FUNC": 0, "DIRECTION": 0, "LOGKOD": "R", "CHANGED": "0808271516", "USERID": "LO-JKP", "ST_NAME": "NORRA JÄRNVÄGSGATAN", "L_REFADDR": "12", "L_NREFADDR": "6", "R_REFADDR": "", "R_NREFADDR": "", "SPEED_CAT": "7", "ZIPCODE": "59330", "SHAPE_LEN": 68.600000 }, "geometry": { "type": "LineString", "coordinates": [ [ 1549818.068600, 6403799.888800 ], [ 1549884.091550, 6403818.700000 ] ] } } +, +{ "type": "Feature", "properties": { "LINK_ID": 30760596.000000, "RP_TYPE": 14, "RP_FUNC": 0, "DIRECTION": 0, "LOGKOD": "R", "CHANGED": "0808271516", "USERID": "LO-JKP", "ST_NAME": "NORRA JÄRNVÄGSGATAN", "L_REFADDR": "12", "L_NREFADDR": "6", "R_REFADDR": "", "R_NREFADDR": "", "SPEED_CAT": "7", "ZIPCODE": "59330", "SHAPE_LEN": 7.800000 }, "geometry": { "type": "LineString", "coordinates": [ [ 1549884.091550, 6403818.700000 ], [ 1549891.640000, 6403820.850400 ] ] } } +, +{ "type": "Feature", "properties": { "LINK_ID": 0.000000, "RP_TYPE": 19, "RP_FUNC": 0, "DIRECTION": 0, "LOGKOD": "D", "CHANGED": "0808271516", "USERID": "LO-JKP", "ST_NAME": "STRÖMSGATAN", "L_REFADDR": "", "L_NREFADDR": "", "R_REFADDR": "", "R_NREFADDR": "", "SPEED_CAT": "", "ZIPCODE": "59330", "SHAPE_LEN": 58.200000 }, "geometry": { "type": "LineString", "coordinates": [ [ 1549911.882600, 6403767.984800 ], [ 1549908.484450, 6403777.327200 ], [ 1549905.284050, 6403786.126400 ], [ 1549884.091550, 6403818.700000 ] ] } } +, +{ "type": "Feature", "properties": { "LINK_ID": 30760579.000000, "RP_TYPE": 14, "RP_FUNC": 0, "DIRECTION": 0, "LOGKOD": "R", "CHANGED": "0808271517", "USERID": "LO-JKP", "ST_NAME": "NORRA JÄRNVÄGSGATAN", "L_REFADDR": "4", "L_NREFADDR": "2", "R_REFADDR": "", "R_NREFADDR": "", "SPEED_CAT": "7", "ZIPCODE": "59330", "SHAPE_LEN": 6.400000 }, "geometry": { "type": "LineString", "coordinates": [ [ 1549891.640000, 6403820.850400 ], [ 1549897.839200, 6403822.604000 ] ] } } +, +{ "type": "Feature", "properties": { "LINK_ID": 0.000000, "RP_TYPE": 19, "RP_FUNC": 0, "DIRECTION": 0, "LOGKOD": "D", "CHANGED": "0808271517", "USERID": "LO-JKP", "ST_NAME": "STRÖMSGATAN", "L_REFADDR": "", "L_NREFADDR": "", "R_REFADDR": "", "R_NREFADDR": "", "SPEED_CAT": "", "ZIPCODE": "59330", "SHAPE_LEN": 61.200000 }, "geometry": { "type": "LineString", "coordinates": [ [ 1549936.717550, 6403775.876000 ], [ 1549931.136800, 6403785.640000 ], [ 1549927.257050, 6403792.427200 ], [ 1549897.839200, 6403822.604000 ] ] } } +, +{ "type": "Feature", "properties": { "LINK_ID": 0.000000, "RP_TYPE": 19, "RP_FUNC": 0, "DIRECTION": 0, "LOGKOD": "D", "CHANGED": "0808271517", "USERID": "LO-JKP", "ST_NAME": "SLOTTSHOLMSLEDEN", "L_REFADDR": "", "L_NREFADDR": "", "R_REFADDR": "", "R_NREFADDR": "", "SPEED_CAT": "", "ZIPCODE": "59331", "SHAPE_LEN": 7.000000 }, "geometry": { "type": "LineString", "coordinates": [ [ 1549982.841250, 6403805.048000 ], [ 1549989.554600, 6403806.848000 ] ] } } +, +{ "type": "Feature", "properties": { "LINK_ID": 0.000000, "RP_TYPE": 19, "RP_FUNC": 0, "DIRECTION": 0, "LOGKOD": "R", "CHANGED": "0808271518", "USERID": "LO-JKP", "ST_NAME": "SLOTTSHOLMSLEDEN", "L_REFADDR": "", "L_NREFADDR": "", "R_REFADDR": "", "R_NREFADDR": "", "SPEED_CAT": "", "ZIPCODE": "59330", "SHAPE_LEN": 14.000000 }, "geometry": { "type": "LineString", "coordinates": [ [ 1549775.301400, 6403791.567200 ], [ 1549789.273750, 6403792.036800 ] ] } } +, +{ "type": "Feature", "properties": { "LINK_ID": 0.000000, "RP_TYPE": 19, "RP_FUNC": 0, "DIRECTION": 0, "LOGKOD": "R", "CHANGED": "0808271518", "USERID": "LO-JKP", "ST_NAME": "SLOTTSHOLMSLEDEN", "L_REFADDR": "", "L_NREFADDR": "", "R_REFADDR": "", "R_NREFADDR": "", "SPEED_CAT": "", "ZIPCODE": "59330", "SHAPE_LEN": 116.600000 }, "geometry": { "type": "LineString", "coordinates": [ [ 1549789.273750, 6403792.036800 ], [ 1549820.418850, 6403786.116800 ], [ 1549862.599800, 6403784.936000 ], [ 1549905.284050, 6403786.126400 ] ] } } +, +{ "type": "Feature", "properties": { "LINK_ID": 0.000000, "RP_TYPE": 16, "RP_FUNC": 0, "DIRECTION": 0, "LOGKOD": "D", "CHANGED": "0808271518", "USERID": "LO-JKP", "ST_NAME": "NORRA JÄRNVÄGSGATAN", "L_REFADDR": "", "L_NREFADDR": "", "R_REFADDR": "", "R_NREFADDR": "", "SPEED_CAT": "", "ZIPCODE": "59330", "SHAPE_LEN": 29.800000 }, "geometry": { "type": "LineString", "coordinates": [ [ 1549818.068600, 6403799.888800 ], [ 1549789.273750, 6403792.036800 ] ] } } +, +{ "type": "Feature", "properties": { "LINK_ID": 80547461.000000, "RP_TYPE": 12, "RP_FUNC": 0, "DIRECTION": 0, "LOGKOD": "R", "CHANGED": "0808280759", "USERID": "LO-JKP", "ST_NAME": "VÅRDTRÄDSPLAN", "L_REFADDR": "", "L_NREFADDR": "", "R_REFADDR": "7", "R_NREFADDR": "3", "SPEED_CAT": "6", "ZIPCODE": "59330", "SHAPE_LEN": 93.100000 }, "geometry": { "type": "LineString", "coordinates": [ [ 1549635.170150, 6403808.780000 ], [ 1549633.900000, 6403814.330400 ], [ 1549618.609900, 6403847.560000 ], [ 1549614.980050, 6403851.970400 ], [ 1549605.460050, 6403851.850400 ], [ 1549590.180100, 6403883.960800 ] ] } } +, +{ "type": "Feature", "properties": { "LINK_ID": 80547461.000000, "RP_TYPE": 12, "RP_FUNC": 0, "DIRECTION": 0, "LOGKOD": "R", "CHANGED": "0808280759", "USERID": "LO-JKP", "ST_NAME": "VÅRDTRÄDSPLAN", "L_REFADDR": "", "L_NREFADDR": "", "R_REFADDR": "7", "R_NREFADDR": "3", "SPEED_CAT": "6", "ZIPCODE": "59330", "SHAPE_LEN": 51.200000 }, "geometry": { "type": "LineString", "coordinates": [ [ 1549590.180100, 6403883.960800 ], [ 1549568.259950, 6403874.780000 ], [ 1549542.790100, 6403864.450400 ] ] } } +, +{ "type": "Feature", "properties": { "LINK_ID": 0.000000, "RP_TYPE": 19, "RP_FUNC": 0, "DIRECTION": 0, "LOGKOD": "D", "CHANGED": "0808280759", "USERID": "LO-JKP", "ST_NAME": "VÅRDTRÄDSPLAN", "L_REFADDR": "", "L_NREFADDR": "", "R_REFADDR": "", "R_NREFADDR": "", "SPEED_CAT": "", "ZIPCODE": "59330", "SHAPE_LEN": 99.400000 }, "geometry": { "type": "LineString", "coordinates": [ [ 1549555.330250, 6403958.170400 ], [ 1549562.119900, 6403948.800800 ], [ 1549569.040100, 6403929.018400 ], [ 1549576.364550, 6403927.638400 ], [ 1549583.482500, 6403925.468800 ], [ 1549590.600800, 6403917.357600 ], [ 1549593.767950, 6403909.047200 ], [ 1549596.737350, 6403902.126400 ], [ 1549585.857950, 6403894.605600 ], [ 1549590.180100, 6403883.960800 ] ] } } +, +{ "type": "Feature", "properties": { "LINK_ID": 30760476.000000, "RP_TYPE": 14, "RP_FUNC": 0, "DIRECTION": 0, "LOGKOD": "R", "CHANGED": "0808280801", "USERID": "LO-JKP", "ST_NAME": "NYGATAN", "L_REFADDR": "12", "L_NREFADDR": "10", "R_REFADDR": "", "R_NREFADDR": "", "SPEED_CAT": "7", "ZIPCODE": "59330", "SHAPE_LEN": 62.700000 }, "geometry": { "type": "LineString", "coordinates": [ [ 1549723.519950, 6403934.620000 ], [ 1549782.972000, 6403954.418400 ] ] } } +, +{ "type": "Feature", "properties": { "LINK_ID": 30760476.000000, "RP_TYPE": 14, "RP_FUNC": 0, "DIRECTION": 0, "LOGKOD": "R", "CHANGED": "0808280801", "USERID": "LO-JKP", "ST_NAME": "NYGATAN", "L_REFADDR": "12", "L_NREFADDR": "10", "R_REFADDR": "", "R_NREFADDR": "", "SPEED_CAT": "7", "ZIPCODE": "59330", "SHAPE_LEN": 14.200000 }, "geometry": { "type": "LineString", "coordinates": [ [ 1549782.972000, 6403954.418400 ], [ 1549796.459950, 6403958.910400 ] ] } } +, +{ "type": "Feature", "properties": { "LINK_ID": 30760555.000000, "RP_TYPE": 14, "RP_FUNC": 0, "DIRECTION": 0, "LOGKOD": "R", "CHANGED": "0808280803", "USERID": "LO-JKP", "ST_NAME": "BÅTSMANSGATAN", "L_REFADDR": "", "L_NREFADDR": "", "R_REFADDR": "33", "R_NREFADDR": "31", "SPEED_CAT": "7", "ZIPCODE": "59330", "SHAPE_LEN": 37.100000 }, "geometry": { "type": "LineString", "coordinates": [ [ 1549754.276900, 6403854.802400 ], [ 1549789.877500, 6403865.172000 ] ] } } +, +{ "type": "Feature", "properties": { "LINK_ID": 30760555.000000, "RP_TYPE": 14, "RP_FUNC": 0, "DIRECTION": 0, "LOGKOD": "R", "CHANGED": "0808280803", "USERID": "LO-JKP", "ST_NAME": "BÅTSMANSGATAN", "L_REFADDR": "", "L_NREFADDR": "", "R_REFADDR": "33", "R_NREFADDR": "31", "SPEED_CAT": "7", "ZIPCODE": "59330", "SHAPE_LEN": 8.600000 }, "geometry": { "type": "LineString", "coordinates": [ [ 1549789.877500, 6403865.172000 ], [ 1549798.179850, 6403867.590400 ] ] } } +, +{ "type": "Feature", "properties": { "LINK_ID": 0.000000, "RP_TYPE": 14, "RP_FUNC": 0, "DIRECTION": 0, "LOGKOD": "D", "CHANGED": "0808280803", "USERID": "LO-JKP", "ST_NAME": "GÖSTA BERNARDS GATA", "L_REFADDR": "", "L_NREFADDR": "", "R_REFADDR": "", "R_NREFADDR": "", "SPEED_CAT": "", "ZIPCODE": "59330", "SHAPE_LEN": 44.000000 }, "geometry": { "type": "LineString", "coordinates": [ [ 1549763.127350, 6403899.236800 ], [ 1549769.602000, 6403885.904800 ], [ 1549772.571400, 6403880.964800 ], [ 1549789.877500, 6403865.172000 ] ] } } +, +{ "type": "Feature", "properties": { "LINK_ID": 0.000000, "RP_TYPE": 12, "RP_FUNC": 0, "DIRECTION": 0, "LOGKOD": "R", "CHANGED": "0808280805", "USERID": "LO-JKP", "ST_NAME": "GRÖNA GRÄND", "L_REFADDR": "", "L_NREFADDR": "", "R_REFADDR": "", "R_NREFADDR": "", "SPEED_CAT": "", "ZIPCODE": "59330", "SHAPE_LEN": 42.400000 }, "geometry": { "type": "LineString", "coordinates": [ [ 1549782.972000, 6403954.418400 ], [ 1549793.934000, 6403923.878400 ], [ 1549786.164350, 6403917.663200 ] ] } } +, +{ "type": "Feature", "properties": { "LINK_ID": 0.000000, "RP_TYPE": 12, "RP_FUNC": 0, "DIRECTION": 0, "LOGKOD": "R", "CHANGED": "0808280805", "USERID": "LO-JKP", "ST_NAME": "GRÖNA GRÄND", "L_REFADDR": "", "L_NREFADDR": "", "R_REFADDR": "", "R_NREFADDR": "", "SPEED_CAT": "", "ZIPCODE": "59330", "SHAPE_LEN": 29.500000 }, "geometry": { "type": "LineString", "coordinates": [ [ 1549786.164350, 6403917.663200 ], [ 1549763.127350, 6403899.236800 ] ] } } +, +{ "type": "Feature", "properties": { "LINK_ID": 30760512.000000, "RP_TYPE": 14, "RP_FUNC": 0, "DIRECTION": 2, "LOGKOD": "R", "CHANGED": "0808280806", "USERID": "LO-JKP", "ST_NAME": "TRÄDGÅRDSGATAN", "L_REFADDR": "", "L_NREFADDR": "", "R_REFADDR": "3", "R_NREFADDR": "1", "SPEED_CAT": "7", "ZIPCODE": "59330", "SHAPE_LEN": 16.600000 }, "geometry": { "type": "LineString", "coordinates": [ [ 1549763.127350, 6403899.236800 ], [ 1549754.840050, 6403906.050400 ], [ 1549749.691200, 6403908.812000 ] ] } } +, +{ "type": "Feature", "properties": { "LINK_ID": 30760512.000000, "RP_TYPE": 14, "RP_FUNC": 0, "DIRECTION": 2, "LOGKOD": "R", "CHANGED": "0808280806", "USERID": "LO-JKP", "ST_NAME": "TRÄDGÅRDSGATAN", "L_REFADDR": "", "L_NREFADDR": "", "R_REFADDR": "3", "R_NREFADDR": "1", "SPEED_CAT": "7", "ZIPCODE": "59330", "SHAPE_LEN": 24.100000 }, "geometry": { "type": "LineString", "coordinates": [ [ 1549749.691200, 6403908.812000 ], [ 1549728.459850, 6403920.200000 ] ] } } +, +{ "type": "Feature", "properties": { "LINK_ID": 0.000000, "RP_TYPE": 18, "RP_FUNC": 0, "DIRECTION": 0, "LOGKOD": "R", "CHANGED": "0808280806", "USERID": "LO-JKP", "ST_NAME": "GÖSTA BERNARDS GATA", "L_REFADDR": "", "L_NREFADDR": "", "R_REFADDR": "", "R_NREFADDR": "", "SPEED_CAT": "", "ZIPCODE": "59330", "SHAPE_LEN": 49.600000 }, "geometry": { "type": "LineString", "coordinates": [ [ 1549786.477600, 6403917.288000 ], [ 1549786.164350, 6403917.663200 ], [ 1549781.471200, 6403923.288000 ], [ 1549778.501800, 6403931.988800 ], [ 1549753.386150, 6403924.078400 ], [ 1549757.605750, 6403919.492000 ] ] } } +, +{ "type": "Feature", "properties": { "LINK_ID": 0.000000, "RP_TYPE": 18, "RP_FUNC": 0, "DIRECTION": 0, "LOGKOD": "R", "CHANGED": "0808280806", "USERID": "LO-JKP", "ST_NAME": "GÖSTA BERNARDS GATA", "L_REFADDR": "", "L_NREFADDR": "", "R_REFADDR": "", "R_NREFADDR": "", "SPEED_CAT": "", "ZIPCODE": "59330", "SHAPE_LEN": 7.300000 }, "geometry": { "type": "LineString", "coordinates": [ [ 1549757.605750, 6403919.492000 ], [ 1549762.549850, 6403914.117600 ] ] } } +, +{ "type": "Feature", "properties": { "LINK_ID": 0.000000, "RP_TYPE": 18, "RP_FUNC": 0, "DIRECTION": 0, "LOGKOD": "D", "CHANGED": "0808280806", "USERID": "LO-JKP", "ST_NAME": "GÖSTA BERNARDS GATA", "L_REFADDR": "", "L_NREFADDR": "", "R_REFADDR": "", "R_NREFADDR": "", "SPEED_CAT": "", "ZIPCODE": "59330", "SHAPE_LEN": 13.300000 }, "geometry": { "type": "LineString", "coordinates": [ [ 1549749.691200, 6403908.812000 ], [ 1549757.605750, 6403919.492000 ] ] } } +, +{ "type": "Feature", "properties": { "LINK_ID": 30760580.000000, "RP_TYPE": 14, "RP_FUNC": 0, "DIRECTION": 0, "LOGKOD": "R", "CHANGED": "0808280807", "USERID": "LO-JKP", "ST_NAME": "STRÖMSGATAN", "L_REFADDR": "42", "L_NREFADDR": "40", "R_REFADDR": "37", "R_NREFADDR": "35", "SPEED_CAT": "7", "ZIPCODE": "59330", "SHAPE_LEN": 25.400000 }, "geometry": { "type": "LineString", "coordinates": [ [ 1549891.640000, 6403820.850400 ], [ 1549883.651250, 6403844.940000 ] ] } } +, +{ "type": "Feature", "properties": { "LINK_ID": 30760580.000000, "RP_TYPE": 14, "RP_FUNC": 0, "DIRECTION": 0, "LOGKOD": "R", "CHANGED": "0808280807", "USERID": "LO-JKP", "ST_NAME": "STRÖMSGATAN", "L_REFADDR": "42", "L_NREFADDR": "40", "R_REFADDR": "37", "R_NREFADDR": "35", "SPEED_CAT": "7", "ZIPCODE": "59330", "SHAPE_LEN": 17.900000 }, "geometry": { "type": "LineString", "coordinates": [ [ 1549883.651250, 6403844.940000 ], [ 1549878.029900, 6403861.890400 ] ] } } +, +{ "type": "Feature", "properties": { "LINK_ID": 0.000000, "RP_TYPE": 18, "RP_FUNC": 0, "DIRECTION": 0, "LOGKOD": "D", "CHANGED": "0808280807", "USERID": "LO-JKP", "ST_NAME": "STRÖMSGATAN", "L_REFADDR": "", "L_NREFADDR": "", "R_REFADDR": "", "R_NREFADDR": "", "SPEED_CAT": "", "ZIPCODE": "59330", "SHAPE_LEN": 19.600000 }, "geometry": { "type": "LineString", "coordinates": [ [ 1549883.651250, 6403844.940000 ], [ 1549902.215600, 6403851.322400 ] ] } } +, +{ "type": "Feature", "properties": { "LINK_ID": 30760588.000000, "RP_TYPE": 14, "RP_FUNC": 0, "DIRECTION": 1, "LOGKOD": "R", "CHANGED": "0808280808", "USERID": "LO-JKP", "ST_NAME": "ÅTERVÄNDSGATAN", "L_REFADDR": "24", "L_NREFADDR": "14", "R_REFADDR": "23", "R_NREFADDR": "15", "SPEED_CAT": "7", "ZIPCODE": "59330", "SHAPE_LEN": 85.900000 }, "geometry": { "type": "LineString", "coordinates": [ [ 1549771.489900, 6403810.460000 ], [ 1549848.872800, 6403847.815200 ] ] } } +, +{ "type": "Feature", "properties": { "LINK_ID": 30760588.000000, "RP_TYPE": 14, "RP_FUNC": 0, "DIRECTION": 1, "LOGKOD": "R", "CHANGED": "0808280808", "USERID": "LO-JKP", "ST_NAME": "ÅTERVÄNDSGATAN", "L_REFADDR": "24", "L_NREFADDR": "14", "R_REFADDR": "23", "R_NREFADDR": "15", "SPEED_CAT": "7", "ZIPCODE": "59330", "SHAPE_LEN": 32.400000 }, "geometry": { "type": "LineString", "coordinates": [ [ 1549848.872800, 6403847.815200 ], [ 1549878.029900, 6403861.890400 ] ] } } +, +{ "type": "Feature", "properties": { "LINK_ID": 0.000000, "RP_TYPE": 18, "RP_FUNC": 0, "DIRECTION": 0, "LOGKOD": "R", "CHANGED": "0808280808", "USERID": "LO-JKP", "ST_NAME": "ÅTERVÄNDSGATAN", "L_REFADDR": "", "L_NREFADDR": "", "R_REFADDR": "", "R_NREFADDR": "", "SPEED_CAT": "", "ZIPCODE": "59330", "SHAPE_LEN": 16.900000 }, "geometry": { "type": "LineString", "coordinates": [ [ 1549848.872800, 6403847.815200 ], [ 1549858.013750, 6403836.851200 ], [ 1549859.956250, 6403835.057600 ] ] } } +, +{ "type": "Feature", "properties": { "LINK_ID": 0.000000, "RP_TYPE": 18, "RP_FUNC": 0, "DIRECTION": 0, "LOGKOD": "R", "CHANGED": "0808280808", "USERID": "LO-JKP", "ST_NAME": "ÅTERVÄNDSGATAN", "L_REFADDR": "", "L_NREFADDR": "", "R_REFADDR": "", "R_NREFADDR": "", "SPEED_CAT": "", "ZIPCODE": "59330", "SHAPE_LEN": 8.000000 }, "geometry": { "type": "LineString", "coordinates": [ [ 1549859.956250, 6403835.057600 ], [ 1549865.800200, 6403829.660800 ] ] } } +, +{ "type": "Feature", "properties": { "LINK_ID": 0.000000, "RP_TYPE": 18, "RP_FUNC": 0, "DIRECTION": 0, "LOGKOD": "D", "CHANGED": "0808280808", "USERID": "LO-JKP", "ST_NAME": "ÅTERVÄNDSGATAN", "L_REFADDR": "", "L_NREFADDR": "", "R_REFADDR": "", "R_NREFADDR": "", "SPEED_CAT": "", "ZIPCODE": "59330", "SHAPE_LEN": 7.900000 }, "geometry": { "type": "LineString", "coordinates": [ [ 1549859.956250, 6403835.057600 ], [ 1549866.996150, 6403838.471200 ] ] } } +, +{ "type": "Feature", "properties": { "LINK_ID": 30760516.000000, "RP_TYPE": 14, "RP_FUNC": 0, "DIRECTION": 0, "LOGKOD": "R", "CHANGED": "0808280809", "USERID": "LO-JKP", "ST_NAME": "BÅTSMANSGATAN", "L_REFADDR": "12", "L_NREFADDR": "2", "R_REFADDR": "13", "R_NREFADDR": "1", "SPEED_CAT": "7", "ZIPCODE": "59330", "SHAPE_LEN": 39.100000 }, "geometry": { "type": "LineString", "coordinates": [ [ 1549867.520100, 6403892.960000 ], [ 1549904.353050, 6403905.936000 ] ] } } +, +{ "type": "Feature", "properties": { "LINK_ID": 30760516.000000, "RP_TYPE": 14, "RP_FUNC": 0, "DIRECTION": 0, "LOGKOD": "R", "CHANGED": "0808280809", "USERID": "LO-JKP", "ST_NAME": "BÅTSMANSGATAN", "L_REFADDR": "12", "L_NREFADDR": "2", "R_REFADDR": "13", "R_NREFADDR": "1", "SPEED_CAT": "7", "ZIPCODE": "59330", "SHAPE_LEN": 26.900000 }, "geometry": { "type": "LineString", "coordinates": [ [ 1549904.353050, 6403905.936000 ], [ 1549929.770050, 6403914.890400 ] ] } } +, +{ "type": "Feature", "properties": { "LINK_ID": 30760548.000000, "RP_TYPE": 14, "RP_FUNC": 0, "DIRECTION": 0, "LOGKOD": "R", "CHANGED": "0808280809", "USERID": "LO-JKP", "ST_NAME": "ÅTERVÄNDSGATAN", "L_REFADDR": "12", "L_NREFADDR": "2", "R_REFADDR": "13", "R_NREFADDR": "13", "SPEED_CAT": "7", "ZIPCODE": "59330", "SHAPE_LEN": 43.000000 }, "geometry": { "type": "LineString", "coordinates": [ [ 1549878.029900, 6403861.890400 ], [ 1549916.095200, 6403881.850400 ] ] } } +, +{ "type": "Feature", "properties": { "LINK_ID": 30760548.000000, "RP_TYPE": 14, "RP_FUNC": 0, "DIRECTION": 0, "LOGKOD": "R", "CHANGED": "0808280809", "USERID": "LO-JKP", "ST_NAME": "ÅTERVÄNDSGATAN", "L_REFADDR": "12", "L_NREFADDR": "2", "R_REFADDR": "13", "R_NREFADDR": "13", "SPEED_CAT": "7", "ZIPCODE": "59330", "SHAPE_LEN": 25.800000 }, "geometry": { "type": "LineString", "coordinates": [ [ 1549916.095200, 6403881.850400 ], [ 1549938.960000, 6403893.840000 ] ] } } +, +{ "type": "Feature", "properties": { "LINK_ID": 0.000000, "RP_TYPE": 18, "RP_FUNC": 0, "DIRECTION": 0, "LOGKOD": "D", "CHANGED": "0808280809", "USERID": "LO-JKP", "ST_NAME": "BÅTSMANSGATAN", "L_REFADDR": "", "L_NREFADDR": "", "R_REFADDR": "", "R_NREFADDR": "", "SPEED_CAT": "", "ZIPCODE": "59330", "SHAPE_LEN": 26.800000 }, "geometry": { "type": "LineString", "coordinates": [ [ 1549904.353050, 6403905.936000 ], [ 1549912.608150, 6403888.475200 ], [ 1549916.095200, 6403881.850400 ] ] } } +, +{ "type": "Feature", "properties": { "LINK_ID": 30760517.000000, "RP_TYPE": 14, "RP_FUNC": 0, "DIRECTION": 0, "LOGKOD": "R", "CHANGED": "0808280811", "USERID": "LO-JKP", "ST_NAME": "STRÖMSGATAN", "L_REFADDR": "34", "L_NREFADDR": "24", "R_REFADDR": "29", "R_NREFADDR": "19", "SPEED_CAT": "7", "ZIPCODE": "59330", "SHAPE_LEN": 23.600000 }, "geometry": { "type": "LineString", "coordinates": [ [ 1549848.114000, 6403950.774400 ], [ 1549839.739900, 6403972.810400 ] ] } } +, +{ "type": "Feature", "properties": { "LINK_ID": 0.000000, "RP_TYPE": 18, "RP_FUNC": 0, "DIRECTION": 0, "LOGKOD": "D", "CHANGED": "0808280811", "USERID": "LO-JKP", "ST_NAME": "STRÖMSGATAN", "L_REFADDR": "", "L_NREFADDR": "", "R_REFADDR": "", "R_NREFADDR": "", "SPEED_CAT": "", "ZIPCODE": "59330", "SHAPE_LEN": 30.800000 }, "geometry": { "type": "LineString", "coordinates": [ [ 1549848.114000, 6403950.774400 ], [ 1549869.289000, 6403957.700800 ], [ 1549866.484800, 6403965.702400 ] ] } } +, +{ "type": "Feature", "properties": { "LINK_ID": 30760517.000000, "RP_TYPE": 14, "RP_FUNC": 0, "DIRECTION": 0, "LOGKOD": "R", "CHANGED": "0808280812", "USERID": "LO-JKP", "ST_NAME": "STRÖMSGATAN", "L_REFADDR": "34", "L_NREFADDR": "24", "R_REFADDR": "29", "R_NREFADDR": "19", "SPEED_CAT": "7", "ZIPCODE": "59330", "SHAPE_LEN": 36.600000 }, "geometry": { "type": "LineString", "coordinates": [ [ 1549867.520100, 6403892.960000 ], [ 1549856.605700, 6403927.937600 ] ] } } +, +{ "type": "Feature", "properties": { "LINK_ID": 30760517.000000, "RP_TYPE": 14, "RP_FUNC": 0, "DIRECTION": 0, "LOGKOD": "R", "CHANGED": "0808280812", "USERID": "LO-JKP", "ST_NAME": "STRÖMSGATAN", "L_REFADDR": "34", "L_NREFADDR": "24", "R_REFADDR": "29", "R_NREFADDR": "19", "SPEED_CAT": "7", "ZIPCODE": "59330", "SHAPE_LEN": 24.400000 }, "geometry": { "type": "LineString", "coordinates": [ [ 1549856.605700, 6403927.937600 ], [ 1549855.749950, 6403930.680000 ], [ 1549848.114000, 6403950.774400 ] ] } } +, +{ "type": "Feature", "properties": { "LINK_ID": 0.000000, "RP_TYPE": 18, "RP_FUNC": 0, "DIRECTION": 0, "LOGKOD": "D", "CHANGED": "0808280812", "USERID": "LO-JKP", "ST_NAME": "STRÖMSGATAN", "L_REFADDR": "", "L_NREFADDR": "", "R_REFADDR": "", "R_NREFADDR": "", "SPEED_CAT": "", "ZIPCODE": "59330", "SHAPE_LEN": 22.700000 }, "geometry": { "type": "LineString", "coordinates": [ [ 1549856.605700, 6403927.937600 ], [ 1549842.391850, 6403922.888000 ], [ 1549839.991550, 6403930.109600 ] ] } } +, +{ "type": "Feature", "properties": { "LINK_ID": 30760453.000000, "RP_TYPE": 14, "RP_FUNC": 0, "DIRECTION": 0, "LOGKOD": "R", "CHANGED": "0808280813", "USERID": "LO-JKP", "ST_NAME": "NYGATAN", "L_REFADDR": "4", "L_NREFADDR": "2", "R_REFADDR": "5", "R_NREFADDR": "1", "SPEED_CAT": "7", "ZIPCODE": "59330", "SHAPE_LEN": 44.500000 }, "geometry": { "type": "LineString", "coordinates": [ [ 1549839.739900, 6403972.810400 ], [ 1549882.122450, 6403986.464000 ] ] } } +, +{ "type": "Feature", "properties": { "LINK_ID": 30760453.000000, "RP_TYPE": 14, "RP_FUNC": 0, "DIRECTION": 0, "LOGKOD": "R", "CHANGED": "0808280814", "USERID": "LO-JKP", "ST_NAME": "NYGATAN", "L_REFADDR": "4", "L_NREFADDR": "2", "R_REFADDR": "5", "R_NREFADDR": "1", "SPEED_CAT": "7", "ZIPCODE": "59330", "SHAPE_LEN": 4.100000 }, "geometry": { "type": "LineString", "coordinates": [ [ 1549882.122450, 6403986.464000 ], [ 1549885.859750, 6403988.054400 ] ] } } +, +{ "type": "Feature", "properties": { "LINK_ID": 30760453.000000, "RP_TYPE": 14, "RP_FUNC": 0, "DIRECTION": 0, "LOGKOD": "R", "CHANGED": "0808280814", "USERID": "LO-JKP", "ST_NAME": "NYGATAN", "L_REFADDR": "4", "L_NREFADDR": "2", "R_REFADDR": "5", "R_NREFADDR": "1", "SPEED_CAT": "7", "ZIPCODE": "59330", "SHAPE_LEN": 15.600000 }, "geometry": { "type": "LineString", "coordinates": [ [ 1549885.859750, 6403988.054400 ], [ 1549900.829950, 6403992.491200 ] ] } } +, +{ "type": "Feature", "properties": { "LINK_ID": 0.000000, "RP_TYPE": 18, "RP_FUNC": 0, "DIRECTION": 0, "LOGKOD": "D", "CHANGED": "0808280814", "USERID": "LO-JKP", "ST_NAME": "NYGATAN", "L_REFADDR": "", "L_NREFADDR": "", "R_REFADDR": "", "R_NREFADDR": "", "SPEED_CAT": "", "ZIPCODE": "59330", "SHAPE_LEN": 30.000000 }, "geometry": { "type": "LineString", "coordinates": [ [ 1549885.859750, 6403988.054400 ], [ 1549892.433450, 6403972.732800 ], [ 1549900.598950, 6403962.172000 ] ] } } +, +{ "type": "Feature", "properties": { "LINK_ID": 0.000000, "RP_TYPE": 19, "RP_FUNC": 0, "DIRECTION": 0, "LOGKOD": "R", "CHANGED": "0808281227", "USERID": "LO-JKP", "ST_NAME": "SÖDRA VARVSGATAN", "L_REFADDR": "", "L_NREFADDR": "", "R_REFADDR": "", "R_NREFADDR": "", "SPEED_CAT": "", "ZIPCODE": "59331", "SHAPE_LEN": 43.100000 }, "geometry": { "type": "LineString", "coordinates": [ [ 1549936.717550, 6403775.876000 ], [ 1549969.438000, 6403803.858400 ] ] } } +, +{ "type": "Feature", "properties": { "LINK_ID": 0.000000, "RP_TYPE": 19, "RP_FUNC": 0, "DIRECTION": 0, "LOGKOD": "R", "CHANGED": "0808281227", "USERID": "LO-JKP", "ST_NAME": "SÖDRA VARVSGATAN", "L_REFADDR": "", "L_NREFADDR": "", "R_REFADDR": "", "R_NREFADDR": "", "SPEED_CAT": "", "ZIPCODE": "59331", "SHAPE_LEN": 13.500000 }, "geometry": { "type": "LineString", "coordinates": [ [ 1549969.438000, 6403803.858400 ], [ 1549982.841250, 6403805.048000 ] ] } } +, +{ "type": "Feature", "properties": { "LINK_ID": 30760579.000000, "RP_TYPE": 14, "RP_FUNC": 0, "DIRECTION": 0, "LOGKOD": "R", "CHANGED": "0808281227", "USERID": "LO-JKP", "ST_NAME": "NORRA JÄRNVÄGSGATAN", "L_REFADDR": "4", "L_NREFADDR": "2", "R_REFADDR": "", "R_NREFADDR": "", "SPEED_CAT": "7", "ZIPCODE": "59330", "SHAPE_LEN": 37.800000 }, "geometry": { "type": "LineString", "coordinates": [ [ 1549897.839200, 6403822.604000 ], [ 1549934.253900, 6403832.906400 ] ] } } +, +{ "type": "Feature", "properties": { "LINK_ID": 30760579.000000, "RP_TYPE": 14, "RP_FUNC": 0, "DIRECTION": 0, "LOGKOD": "R", "CHANGED": "0808281227", "USERID": "LO-JKP", "ST_NAME": "NORRA JÄRNVÄGSGATAN", "L_REFADDR": "4", "L_NREFADDR": "2", "R_REFADDR": "", "R_NREFADDR": "", "SPEED_CAT": "7", "ZIPCODE": "59330", "SHAPE_LEN": 29.500000 }, "geometry": { "type": "LineString", "coordinates": [ [ 1549934.253900, 6403832.906400 ], [ 1549957.490050, 6403839.480000 ], [ 1549960.410100, 6403843.980000 ] ] } } +, +{ "type": "Feature", "properties": { "LINK_ID": 0.000000, "RP_TYPE": 19, "RP_FUNC": 0, "DIRECTION": 0, "LOGKOD": "D", "CHANGED": "0808281227", "USERID": "LO-JKP", "ST_NAME": "SLOTTSHOLMSLEDEN", "L_REFADDR": "", "L_NREFADDR": "", "R_REFADDR": "", "R_NREFADDR": "", "SPEED_CAT": "", "ZIPCODE": "59330", "SHAPE_LEN": 45.700000 }, "geometry": { "type": "LineString", "coordinates": [ [ 1549969.438000, 6403803.858400 ], [ 1549962.209450, 6403808.998400 ], [ 1549954.459400, 6403814.508800 ], [ 1549934.253900, 6403832.906400 ] ] } } +, +{ "type": "Feature", "properties": { "LINK_ID": 80547462.000000, "RP_TYPE": 12, "RP_FUNC": 1, "DIRECTION": 0, "LOGKOD": "R", "CHANGED": "0808290805", "USERID": "LO-JKP", "ST_NAME": "ÖSTRA KYRKOGATAN", "L_REFADDR": "44", "L_NREFADDR": "38", "R_REFADDR": "61", "R_NREFADDR": "53", "SPEED_CAT": "7", "ZIPCODE": "59333", "SHAPE_LEN": 23.700000 }, "geometry": { "type": "LineString", "coordinates": [ [ 1549546.809850, 6403827.740000 ], [ 1549537.632500, 6403849.605600 ] ] } } +, +{ "type": "Feature", "properties": { "LINK_ID": 80547462.000000, "RP_TYPE": 12, "RP_FUNC": 1, "DIRECTION": 0, "LOGKOD": "R", "CHANGED": "0808290805", "USERID": "LO-JKP", "ST_NAME": "ÖSTRA KYRKOGATAN", "L_REFADDR": "44", "L_NREFADDR": "38", "R_REFADDR": "61", "R_NREFADDR": "53", "SPEED_CAT": "7", "ZIPCODE": "59333", "SHAPE_LEN": 70.100000 }, "geometry": { "type": "LineString", "coordinates": [ [ 1549537.632500, 6403849.605600 ], [ 1549533.320150, 6403859.880000 ], [ 1549518.660050, 6403889.780000 ], [ 1549507.790100, 6403903.010400 ], [ 1549501.325600, 6403908.552800 ] ] } } +, +{ "type": "Feature", "properties": { "LINK_ID": 0.000000, "RP_TYPE": 18, "RP_FUNC": 0, "DIRECTION": 0, "LOGKOD": "D", "CHANGED": "0808290805", "USERID": "LO-JKP", "ST_NAME": "ÖSTRA KYRKOGATAN", "L_REFADDR": "", "L_NREFADDR": "", "R_REFADDR": "", "R_NREFADDR": "", "SPEED_CAT": "", "ZIPCODE": "59333", "SHAPE_LEN": 46.000000 }, "geometry": { "type": "LineString", "coordinates": [ [ 1549537.632500, 6403849.605600 ], [ 1549561.402400, 6403858.662400 ], [ 1549569.328850, 6403839.652000 ] ] } } +, +{ "type": "Feature", "properties": { "LINK_ID": 30760609.000000, "RP_TYPE": 12, "RP_FUNC": 1, "DIRECTION": 0, "LOGKOD": "R", "CHANGED": "0808290829", "USERID": "LO-JKP", "ST_NAME": "ÖSTRA KYRKOGATAN", "L_REFADDR": "46", "L_NREFADDR": "46", "R_REFADDR": "65", "R_NREFADDR": "63", "SPEED_CAT": "7", "ZIPCODE": "59333", "SHAPE_LEN": 26.800000 }, "geometry": { "type": "LineString", "coordinates": [ [ 1549566.450100, 6403780.090400 ], [ 1549555.974950, 6403804.818400 ] ] } } +, +{ "type": "Feature", "properties": { "LINK_ID": 30760609.000000, "RP_TYPE": 12, "RP_FUNC": 1, "DIRECTION": 0, "LOGKOD": "R", "CHANGED": "0808290829", "USERID": "LO-JKP", "ST_NAME": "ÖSTRA KYRKOGATAN", "L_REFADDR": "46", "L_NREFADDR": "46", "R_REFADDR": "65", "R_NREFADDR": "63", "SPEED_CAT": "7", "ZIPCODE": "59333", "SHAPE_LEN": 24.700000 }, "geometry": { "type": "LineString", "coordinates": [ [ 1549555.974950, 6403804.818400 ], [ 1549546.809850, 6403827.740000 ] ] } } +, +{ "type": "Feature", "properties": { "LINK_ID": 0.000000, "RP_TYPE": 18, "RP_FUNC": 0, "DIRECTION": 0, "LOGKOD": "D", "CHANGED": "0808290829", "USERID": "LO-JKP", "ST_NAME": "ÖSTRA KYRKOGATAN", "L_REFADDR": "", "L_NREFADDR": "", "R_REFADDR": "", "R_NREFADDR": "", "SPEED_CAT": "", "ZIPCODE": "59333", "SHAPE_LEN": 37.600000 }, "geometry": { "type": "LineString", "coordinates": [ [ 1549555.974950, 6403804.818400 ], [ 1549577.560500, 6403813.319200 ], [ 1549572.603450, 6403826.820800 ] ] } } +, +{ "type": "Feature", "properties": { "LINK_ID": 30760474.000000, "RP_TYPE": 12, "RP_FUNC": 0, "DIRECTION": 1, "LOGKOD": "R", "CHANGED": "0808290830", "USERID": "LO-JKP", "ST_NAME": "ÖSTRA KYRKOGATAN", "L_REFADDR": "36", "L_NREFADDR": "32", "R_REFADDR": "51", "R_NREFADDR": "49", "SPEED_CAT": "6", "ZIPCODE": "59333", "SHAPE_LEN": 58.000000 }, "geometry": { "type": "LineString", "coordinates": [ [ 1549496.649950, 6403937.400000 ], [ 1549483.100050, 6403973.990400 ], [ 1549475.242550, 6403991.259200 ] ] } } +] +} + --- /dev/null +++ b/openlayers/examples/debug.html @@ -1,1 +1,74 @@ + + + + OpenLayers Debug Example + + + + + + +

Debug Example

+
+ +

+ Demonstrate console calls to a Firebug console. Requires Firefox. Mostly for developers. +

+ +
+

To run OpenLayers in debug mode, include the following script + tag before the tag that loads OpenLayers: + +

    <script src="../lib/Firebug/firebug.js"></script>
+ + The path to firebug.js must be relative to your + html file. With this script included calls to OpenLayers.Console + will be displayed in the Firebug console. For browsers without + the Firebug extension, the script creates a Firebug Lite console. + This console can be opened by hitting F12 or Ctrl+Shift+L + (?+Shift+L on a Mac). If you want the Firebug Lite console + to be open when the page loads, add debug="true" to the opening + html tag of your page. Open the console and click on the links below + to see console calls.

+ +

The Firebug website has a complete list of + console calls. + Note that not all are supported with Firebug Lite.

+
+ + + --- /dev/null +++ b/openlayers/examples/doubleSetCenter.html @@ -1,1 +1,33 @@ + + + OpenLayers Double Set Center Example + + + + + + +

Double Set Center Example

+
+ +

+ Demonstrate the behavior of two calls to set the center after instatiating the layer object. +

+ +
+ + + +
+ + + + --- /dev/null +++ b/openlayers/examples/drag-feature.html @@ -1,1 +1,103 @@ + + + Drag Feature Example + + + + + + + + +

Drag Feature Example

+ +
+ +

+ Demonstrates point, line and polygon creation and editing. +

+ +
+ +
+
    +
  • + + +
  • +
  • + + +
  • +
  • + + +
  • +
  • + + +
  • +
  • + + +
  • +
+
+ +
+ + + --- /dev/null +++ b/openlayers/examples/draw-feature.html @@ -1,1 +1,106 @@ + + + Draw Feature Example + + + + + + + + +

OpenLayers Draw Feature Example

+ +
+ +

+ Demonstrate on-screen digitizing tools for point, line, and polygon creation. +

+ +
+ +
    +
  • + + +
  • +
  • + + +
  • +
  • + + +
  • +
  • + + +
  • +
+ +
+

With the point drawing control active, click on the map to add a point. You can drag the point + before letting the mouse up if you want to adjust the position.

+

With the line drawing control active, click on the map to add the points that make up your line. + Double-click to finish drawing.

+

With the polygon drawing control active, click on the map to add the points that make up your + polygon. Double-click to finish drawing.

+

Hold down the shift key while drawing to activate freehand mode. While drawing lines or polygons + in freehand mode, hold the mouse down and a point will be added with every mouse movement.

+

+ + + --- /dev/null +++ b/openlayers/examples/editingtoolbar-outside.html @@ -1,1 +1,53 @@ + + + + OpenLayers: Custom Editing Toolbar + + + + + + + + +

OpenLayers EditingToolbar Outside Viewport

+

+ Display an editing toolbar panel outside the map viewport. +

+
+
+ + + --- /dev/null +++ b/openlayers/examples/editingtoolbar.html @@ -1,1 +1,48 @@ + + + + OpenLayers Editing Toolbar Example + + + + + + + + +

Editing Toolbar Example

+ +
+ +

+ Demonstrate polygon, polyline and point creation and editing tools. +

+ +
+
+ +
+ + + --- /dev/null +++ b/openlayers/examples/events.html @@ -1,1 +1,151 @@ + + + OpenLayers Event Handling + + + + + + +

Event Handling

+ +
+
+ +

+ Demonstrating various styles of event handling in OpenLayers. +

+ +
+
+ +
+ + + --- /dev/null +++ b/openlayers/examples/example-list.html @@ -1,1 +1,240 @@ + + + + + OpenLayers Examples + + + + + + + + +
+
+

+
+ +
+ show all +

+
+
+
+ + + + + --- /dev/null +++ b/openlayers/examples/example.html @@ -1,1 +1,57 @@ + + + OpenLayers Example + + + + + + +

OpenLayers Example

+
+

+ Demonstrate a simple map with an overlay that includes layer switching controls. +

+
+
+ + + --- /dev/null +++ b/openlayers/examples/filter.html @@ -1,1 +1,100 @@ + + + + + + + + + +

Filter Encoding

+

+ Using the filter format write out filter objects. +

+ +
+ Filter Encoding 1.0 +
+ Filter Encoding 1.1 +
+

+

+ + + --- /dev/null +++ b/openlayers/examples/fractional-zoom.html @@ -1,1 +1,68 @@ + + + + + + + + +

Fractional Zoom Example

+ +
+
+

+ Shows the use of a map with fractional (or non-discrete) zoom levels. +

+ +
+ + + (zoom: ) +

+
+

+ Setting the map.fractionalZoom property to true allows zooming to + an arbitrary level (between the min and max resolutions). This + can be demonstrated by shift-dragging a box to zoom to an arbitrary + extent. +

+
+ + + + + + + --- /dev/null +++ b/openlayers/examples/fullScreen.html @@ -1,1 +1,67 @@ + + + Full Screen Example + + + + + + + +
+ +
+

Full Screen Example

+ +
+ +

+ Demonstrate a map that fill the entire browser window. +

+ +
+ This example uses CSS to define the dimensions of the map element in order to fill the screen. + When the user resizes the window, the map size changes correspondingly. No scroll bars! +
+
+ + + --- /dev/null +++ b/openlayers/examples/geojson.html @@ -1,1 +1,63 @@ + + + + + + + + +

GeoJSON Example

+
+ + + --- /dev/null +++ b/openlayers/examples/georss-flickr.html @@ -1,1 +1,101 @@ + + + + + + + + + +

GeoRSS from Flickr in OpenLayers

+

The displayed GeoRSS feed has a <media:thumbnail/> property for each item. An extended createFeatureFromItem() function is used to add this attribute to the attributes hash of each feature read in by OpenLayers.Format.GeoRSS. The example is configured with a style to render each item with its thumbnail image. Also, to show how rules work, we defined a rule that if the title of an rss item contains "powder", it will be rendered larger than the others.

+
+ + + --- /dev/null +++ b/openlayers/examples/georss-markers.html @@ -1,1 +1,40 @@ + + + OpenLayers GeoRSS Marker Example + + + + + + +

GeoRSS Marker Example

+ +
+ +

+ Demonstrate loading a GeoRSS feed using the GeoRSS parser. +

+ +
+ +
+ + + --- /dev/null +++ b/openlayers/examples/georss.html @@ -1,1 +1,58 @@ + + + OpenLayers GeoRSS Example + + + + + + +

GeoRSS Example

+ +
+ +

+ Display a couple of locally cached georss feeds on an a basemap. +

+ +
+ +
+

This demo uses the OpenLayers GeoRSS parser, which supports GeoRSS Simple and W3C GeoRSS. Only points are + currently supported. The OpenLayers GeoRSS parser will automatically connect an information bubble to the map + markers, similar to Google maps. In addition, the parser can use custom PNG icons for markers. A sample GeoRSS + file (georss.xml) is included. + +

+ GeoRSS URL: + +
+ +

The above input box allows the input of a URL to a GeoRSS feed. This feed can be local to the HTML page -- + for example, entering 'georss.xml' will work by default, because there is a local file in the directory called + georss.xml -- or, with a properly set up ProxyHost variable (as is used here), it will be able to load any + HTTP URL which contains GeoRSS and display it. Anything else will simply have no effect.

+
+ + + --- /dev/null +++ b/openlayers/examples/georss.xml @@ -1,1 +1,379 @@ + + +This is an RSS file. Copy the URL into your aggregator of choice. If you don't know what this means and want to learn more, please see: http://platial.typepad.com/news/2006/04/really_simple_t.html for more info. +http://platial.com +Crschmidt's Places At Platial + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +http://platial.com/place/90306 +Knitting Room +Address: 2 lake St, Arlington, MA
Tags: knitting, yarn, pins and needles, handspun, hand dyed, novelty yarn, fancy, simple, young, hip, friendly, needles, addy, cute hats

Map this on Platial
Grab this on Platial ]]>
+42.405696 -71.142197 +crschmidt +2006-06-08T17:35:01.942452+00:00 +
+ +http://platial.com/place/67230 +Knitting Room +Address: 2 lake St, Arlington, MA
Tags: knitting, yarn, pins and needles, handspun, hand dyed, novelty yarn, fancy, simple, young, hip, friendly, needles, addy, cute hats

Map this on Platial
Grab this on Platial ]]>
+42.405524 -71.142273 +crschmidt +2006-04-24T11:35:26.733857+00:00 +
+ +http://platial.com/place/65645 +†¢¢™£ˆøœ +Address: 151 Erie St., Cambridge, MA
Tags: platial graffiti

Map this on Platial
Grab this on Platial ]]>
+42.352455 -71.110210 +crschmidt +2006-04-20T08:56:12.696224+00:00 +
+ +http://platial.com/place/62200 +Allen Hall +Address: 1301 W Gregory Dr, Urbana, IL
Tags: dorm, uiuc, college



Map this on Platial
Grab this on Platial ]]>
+40.104172 -88.220623 +crschmidt +2006-04-14T08:01:01.872873+00:00 +
+ +http://platial.com/place/28232 +Bagby Hot Springs, OR +Tags: 20s, rosalie, romance, childhood, hike, camping, soak, relax, beautiful, hot springs, bathhouse, favorite, popular, crowded, organized, honeymoon tub, plumbing made from hollowed out trees, hot springs, mt hood, notorious car break in spot, rash, bacteria

Map this on Platial
Grab this on Platial ]]>
+44.936000 -122.173000 +crschmidt +2006-01-03T23:10:18.553063+00:00 +
+ +http://platial.com/place/43666 +Shooting Location for "The Field of Dreams" Film +Address: Dyersville, Iowa
Tags: iowa, baseball, movie locations, field of dreams, kevin costner, costner, dyersville, kinsella, james earl jones, chicago black sox, shoeless joe, joe jackson, famous farms, film, movie, cinema, shooting location

Map this on Platial
Grab this on Platial ]]>
+42.481213 -91.111679 +echinodermata +2006-03-23T11:40:17.654061+00:00 +
+ +http://platial.com/place/28394 +Moffetts (Bonneville) Hot Springs, WA +Tags: soak, hot springs, relax, nature

Map this on Platial
Grab this on Platial ]]>
+45.658000 -121.962000 +crschmidt +2006-01-03T23:16:27.329816+00:00 +
+ +http://platial.com/place/28251 +Austin Hot Springs, OR +Tags: soak, hot springs, relax, nature, popular, crowded

Map this on Platial
Grab this on Platial ]]>
+45.021000 -122.009000 +crschmidt +2006-01-03T23:11:04.489886+00:00 +
+ +http://platial.com/place/28392 +Rock Creek Hot Springs, WA +Tags: soak, hot springs, relax, nature

Map this on Platial
Grab this on Platial ]]>
+45.723000 -121.927000 +crschmidt +2006-01-03T23:16:22.636855+00:00 +
+ +http://platial.com/place/28391 +St. Martins (Wind River) Hot Springs, WA +Tags: hot springs, soak, relax, nature, wonderful

Map this on Platial
Grab this on Platial ]]>
+45.728000 -121.800000 +crschmidt +2006-01-03T23:16:20.383244+00:00 +
+ +http://platial.com/place/28231 +Breitenbush Hot Springs, OR +Tags: hot springs, resort, relax, nature, beautiful, http:www.breitenbush.com, soaking

Map this on Platial
Grab this on Platial ]]>
+44.782000 -121.975000 +crschmidt +2006-01-03T23:10:16.529195+00:00 +
+ +http://platial.com/place/28393 +Collins Hot Springs, WA +Tags: portland, nice, hot springs, soak

Map this on Platial
Grab this on Platial ]]>
+45.701000 -121.728000 +crschmidt +2006-01-03T23:16:24.648745+00:00 +
+ +http://platial.com/place/31685 +Darwin's Ltd. +Address: 148 Mount Auburn St, Cambridge, MA
Tags: coffee, beer, sandwiches, freewifi



Map this on Platial
Grab this on Platial ]]>
+42.373974 -71.125053 +crschmidt +2006-01-10T09:24:08.152985+00:00 +
+ +http://platial.com/place/28596 +Huckleberry Hot Springs, WY +
Map this on Platial
Grab this on Platial ]]>
+44.115000 -110.684000 +crschmidt +2006-01-03T23:24:32.283094+00:00 +
+ +http://platial.com/place/28595 +South Entrance Hot Springs, WY +


Map this on Platial
Grab this on Platial ]]>
+44.142000 -110.656000 +crschmidt +2006-01-03T23:24:30.279497+00:00 +
+ +http://platial.com/place/28594 +Crawfish Creek Hot Springs, WY +


Map this on Platial
Grab this on Platial ]]>
+44.157000 -110.699000 +crschmidt +2006-01-03T23:24:28.280271+00:00 +
+ +http://platial.com/place/28593 +Crawfish Creek Hot Springs, WY +
Map this on Platial
Grab this on Platial ]]>
+44.165000 -110.723000 +crschmidt +2006-01-03T23:24:20.364077+00:00 +
+ +http://platial.com/place/28592 +Snake Hot Springs, WY +
Map this on Platial
Grab this on Platial ]]>
+44.169000 -110.583000 +crschmidt +2006-01-03T23:24:12.234974+00:00 +
+ +http://platial.com/place/28591 +Hot Springs, WY +
Map this on Platial
Grab this on Platial ]]>
+44.187000 -110.726000 +crschmidt +2006-01-03T23:24:10.027857+00:00 +
+ +http://platial.com/place/28590 +Hot Springs on Upper Snake River, WY +
Map this on Platial
Grab this on Platial ]]>
+44.204000 -110.486000 +crschmidt +2006-01-03T23:24:07.79658+00:00 +
+ +http://platial.com/place/28589 +Hot Springs on lewis Lake, WY +
Map this on Platial
Grab this on Platial ]]>
+44.276000 -110.636000 +crschmidt +2006-01-03T23:24:05.683418+00:00 +
+ +http://platial.com/place/28588 +Rustic Geyser, WY +
Map this on Platial
Grab this on Platial ]]>
+44.282000 -110.506000 +crschmidt +2006-01-03T23:24:03.66329+00:00 +
+ +http://platial.com/place/28587 +Bechler River Hot Springs, WY +
Map this on Platial
Grab this on Platial ]]>
+44.285000 -110.900000 +crschmidt +2006-01-03T23:24:01.611442+00:00 +
+ +http://platial.com/place/28586 +Hot Springs, WY +
Map this on Platial
Grab this on Platial ]]>
+44.290000 -110.504000 +crschmidt +2006-01-03T23:23:59.658699+00:00 +
+ +http://platial.com/place/28585 +Heart Lake Geyser Basin, WY +
Map this on Platial
Grab this on Platial ]]>
+44.299000 -110.517000 +crschmidt +2006-01-03T23:23:57.181801+00:00 +
+ +http://platial.com/place/28584 +Hot Springs, WY +
Map this on Platial
Grab this on Platial ]]>
+44.307000 -110.526000 +crschmidt +2006-01-03T23:23:55.240485+00:00 +
+ +http://platial.com/place/28583 +Hot Springs on lewis Lake, WY +
Map this on Platial
Grab this on Platial ]]>
+44.309000 -110.654000 +crschmidt +2006-01-03T23:23:53.22295+00:00 +
+ +http://platial.com/place/28582 +Shoshone Geyser Basin, WY +
Map this on Platial
Grab this on Platial ]]>
+44.354000 -110.800000 +crschmidt +2006-01-03T23:23:51.179049+00:00 +
+ +http://platial.com/place/28581 +Hot Springs on Continental Divide, WY +
Map this on Platial
Grab this on Platial ]]>
+44.401000 -110.936000 +crschmidt +2006-01-03T23:23:49.077176+00:00 +
+ +http://platial.com/place/28580 +Hot Springs on Upper Firehole River, WY +
Map this on Platial
Grab this on Platial ]]>
+44.404000 -110.824000 +crschmidt +2006-01-03T23:23:47.054664+00:00 +
+ +http://platial.com/place/28579 +Summit Lake Hot Springs, WY +
Map this on Platial
Grab this on Platial ]]>
+44.410000 -110.953000 +crschmidt +2006-01-03T23:23:45.039394+00:00 +
+ +http://platial.com/place/28578 +Lone Star Geyser Basin, WY +
Map this on Platial
Grab this on Platial ]]>
+44.414000 -110.817000 +crschmidt +2006-01-03T23:23:42.938808+00:00 +
+ +http://platial.com/place/28577 +West. Thumb Geyser Basin, WY +
Map this on Platial
Grab this on Platial ]]>
+44.417000 -110.570000 +crschmidt +2006-01-03T23:23:40.90238+00:00 +
+ +http://platial.com/place/28576 +Lone Star Geyser, WY +
Map this on Platial
Grab this on Platial ]]>
+44.418000 -110.805000 +crschmidt +2006-01-03T23:23:38.844625+00:00 +
+ +http://platial.com/place/28575 +Smoke Jumper Hot Springs, WY +
Map this on Platial
Grab this on Platial ]]>
+44.421000 -110.952000 +crschmidt +2006-01-03T23:23:36.818513+00:00 +
+ +http://platial.com/place/28574 +West. Thumb Geyser Basin, WY +
Map this on Platial
Grab this on Platial ]]>
+44.422000 -110.574000 +crschmidt +2006-01-03T23:23:34.767729+00:00 +
+ +http://platial.com/place/28573 +Potts Hot Spring Basin, WY +
Map this on Platial
Grab this on Platial ]]>
+44.433000 -110.581000 +crschmidt +2006-01-03T23:23:32.749915+00:00 +
+ +http://platial.com/place/28572 +Hot Springs, WY +
Map this on Platial
Grab this on Platial ]]>
+44.433000 -110.813000 +crschmidt +2006-01-03T23:23:30.829745+00:00 +
+ +http://platial.com/place/28571 +Hot Springs on Continental Divide, WY +
Map this on Platial
Grab this on Platial ]]>
+44.438000 -110.977000 +crschmidt +2006-01-03T23:23:28.730401+00:00 +
+ +http://platial.com/place/28570 +SouthEastern Group, WY +
Map this on Platial
Grab this on Platial ]]>
+44.459000 -110.817000 +crschmidt +2006-01-03T23:23:26.706763+00:00 +
+
+ --- /dev/null +++ b/openlayers/examples/getfeature-wfs.html @@ -1,1 +1,81 @@ + + + + + WFS: GetFeature Example (GeoServer) + + + + +

WFS GetFeature Example (GeoServer)

+ +
+
+ +

+ Shows how to use the GetFeature control to select features from a + WMS layer. Click or drag a box to select features, use the Shift key to + add features to the selection, use the Ctrl key to toggle a feature's + selected status. Note that this control also has a hover option, which is + enabled in this example. This gives you a visual feedback by loading the + feature underneath the mouse pointer from the WFS, but causes a lot of + GetFeature requests to be issued. +

+ +
+ +
+ + + + + + + --- /dev/null +++ b/openlayers/examples/getfeatureinfo-control.html @@ -1,1 +1,218 @@ - + + + OpenLayers WMS Feature Info Example (GeoServer) + + + + + + + +

Feature Info Example

+ +
+ +

+ Demonstrates the WMSGetFeatureInfo control for fetching information about a position from WMS (via GetFeatureInfo request). +

+ +
+

Tasmania

+

Click on the map to get feature info.

+
+
+
+
+ +
+
+
    +
  • + + +
  • +
  • + + +
  • +
+
    +
  • + + +
  • +
  • + + +
  • +
+
    +
  • + + +
  • +
  • + + +
  • +
+ + + --- /dev/null +++ b/openlayers/examples/getfeatureinfo.html @@ -1,1 +1,63 @@ + + + OpenLayers Feature Info Example + + + + + + +

Feature Info Example

+
+ +

+ Demonstrates sending a GetFeatureInfo query to an OWS. Returns information about a map feature in the side DIV. +

+ +
+ +
+

CIA Factbook

+

Click a country to see statistics about the country below.

+
+
+
+
+ + + +
+
+ + + --- /dev/null +++ b/openlayers/examples/gml-layer.html @@ -1,1 +1,37 @@ + + + OpenLayers GML Layer Example + + + + + + +

GML Layer Example

+ +
+ +

+ Loads locally stored GML vector data on a basemap. Includes GML example file. +

+ +
+ +
+ + + --- /dev/null +++ b/openlayers/examples/gml/line.xml @@ -1,1 +1,42 @@ - + + + + + -3.924027,46.037889 2.193186,47.897181 + + + + + + + -0.631235,46.037889 2.193186,46.704963 + + + + + -0.631235,46.307557 -0.262215,46.577225 0.106805,46.477874 0.220349,46.293364 0.475824,46.406909 0.887424,46.350136 1.029354,46.563032 1.213864,46.648191 1.526112,46.421102 1.795780,46.066275 2.108028,46.037889 2.178993,46.250785 2.193186,46.492067 2.193186,46.492067 2.051255,46.704963 2.051255,46.704963 + + + 1 + + 0 + + + + + + + -3.924027,46.279171 -1.127992,47.897181 + + + + + -1.127992,46.279171 -1.369275,46.364329 -1.624750,46.406909 -1.866032,46.492067 -1.993770,46.704963 -2.178280,46.846894 -1.979577,47.059790 -2.164087,47.144948 -2.135700,47.215914 -2.093121,47.357844 -2.277631,47.258493 -2.391176,47.301072 -2.490527,47.315265 -2.476334,47.443003 -2.575686,47.599127 -2.703423,47.542354 -2.873740,47.471389 -3.285339,47.670092 -3.597587,47.769443 -3.824676,47.840409 -3.924027,47.897181 + + + 2 + + 0 + + + --- /dev/null +++ b/openlayers/examples/gml/multipoint.xml @@ -1,1 +1,70 @@ - + + + + + 0.490018,45.001795 3.016384,45.839186 + + + + + + + 0.930003,45.001795 3.016384,45.541131 + + + + + + + 2.079641,45.001795 + + + + + 2.718330,45.541131 + + + + + 3.016384,45.143725 + + + + + 0.930003,45.001795 + + + + + 1 + 4 points + 1 + + + + + + + 0.490018,45.654676 1.157092,45.839186 + + + + + + + 0.490018,45.654676 + + + + + 1.157092,45.839186 + + + + + 2 + 2 points + 2 + + + --- /dev/null +++ b/openlayers/examples/gml/multipolygon.xml @@ -1,1 +1,106 @@ - + + + + + -1.738295,46.307557 3.754424,47.244300 + + + + + + + -1.738295,46.605612 1.767394,47.244300 + + + + + + + + + 1.313216,46.690770 1.000968,46.861087 0.887424,47.059790 1.142899,47.244300 1.355795,47.244300 1.554498,47.017211 1.710622,47.059790 1.767394,46.747542 1.313216,46.690770 1.313216,46.690770 + + + + + + + + + 0.731300,46.605612 -0.191250,46.704963 -0.191250,46.846894 0.177770,46.988824 0.447438,46.960438 0.589369,46.804315 0.688721,46.832701 0.731300,46.605612 0.731300,46.605612 + + + + + + + + + -1.610557,46.733349 -1.184765,46.704963 -1.198958,46.704963 -0.943483,46.619805 -0.915096,46.818508 -0.659621,46.775928 -0.688007,47.017211 -0.943483,47.003018 -1.127992,47.088176 -1.397661,47.102369 -1.624750,47.073983 -1.738295,46.917859 -1.610557,46.733349 + + + + + + + 1 + My first Multipolygon + 0 + + + + + + + 2.789295,46.392716 3.754424,46.903666 + + + + + + + 2.959612,46.392716 2.789295,46.775928 3.172508,46.903666 3.498949,46.903666 3.498949,46.662384 3.754424,46.563032 2.959612,46.392716 + + + + + 2 + My second Multipolygon + 0 + + + + + + + 2.207379,46.307557 2.803488,47.045597 + + + + + + + + + 2.292538,46.804315 2.207379,47.017211 2.391889,47.045597 2.562206,46.832701 2.292538,46.804315 + + + + + + + + + 2.789295,46.307557 2.789295,46.307557 2.803488,46.506260 2.618978,46.676577 2.349310,46.633998 2.448661,46.392716 2.789295,46.307557 + + + + + + + 3 + My third Multipolygon + 0 + + + --- /dev/null +++ b/openlayers/examples/gml/owls.xml @@ -1,1 +1,157 @@ + + + + + -89.817223,45.005555 -74.755001,51.701388 + + + + + + -79.771668,45.891110 -79.771668,45.891110 + + + + + -79.771668,45.891110 + + + + + + + + + -83.755834,46.365277 -83.755834,46.365277 + + + owl + + + -83.755834,46.365277 + + + + + + + + + -83.808612,46.175277 -83.808612,46.175277 + + + + + -83.808612,46.175277 + + + + + + + + + -84.111112,46.309166 -84.111112,46.309166 + + + + + -84.111112,46.309166 + + + + + + + + + -83.678612,46.821110 -83.678612,46.821110 + + + + + -83.678612,46.821110 + + + + + + + + + -83.664445,46.518888 -83.664445,46.518888 + + + + + -83.664445,46.518888 + + + + + + + + + -80.613334,46.730277 -80.613334,46.730277 + + + + + -80.613334,46.730277 + + + + + + + + + -79.676946,45.428054 -79.676946,45.428054 + + + + + -79.676946,45.428054 + + + + + + + + + -83.853056,46.236944 -83.853056,46.236944 + + + + + -83.853056,46.236944 + + + + + + + + + -82.289167,45.896388 -82.289167,45.896388 + + + + + -82.289167,45.896388 + + + + + + --- /dev/null +++ b/openlayers/examples/gml/point.xml @@ -1,1 +1,42 @@ - + + + + + -0.608315,44.857522 -0.021418,45.477577 + + + + + + + -0.608315,44.857522 -0.608315,44.857522 + + + + + -0.608315,44.857522 + + + 1 + Bordeaux + 124 + + + + + + + -0.021418,45.477577 -0.021418,45.477577 + + + + + -0.021418,45.477577 + + + 2 + Barbezieux + 0 + + + --- /dev/null +++ b/openlayers/examples/gml/polygon.xml @@ -1,1 +1,89 @@ - + + + + + -0.768746,47.003018 3.002191,47.925567 + + + + + + + -0.768746,47.003018 0.532597,47.925567 + + + + + + + + + -0.318987,47.003018 -0.768746,47.358268 -0.574463,47.684285 -0.347374,47.854602 -0.006740,47.925567 0.135191,47.726864 0.149384,47.599127 0.419052,47.670092 0.532597,47.428810 0.305508,47.443003 0.475824,47.144948 0.064225,47.201721 -0.318987,47.003018 + + + + + -0.035126,47.485582 -0.035126,47.485582 -0.049319,47.641706 -0.233829,47.655899 -0.375760,47.457196 -0.276408,47.286879 -0.035126,47.485582 + + + + + + + 1 + My Polygon with hole + 0 + + + + + + + 1.511919,47.088176 3.002191,47.882988 + + + + + + + 1.625463,47.357844 1.511919,47.741057 1.880938,47.882988 2.420275,47.797830 2.789295,47.485582 3.002191,47.457196 2.874453,47.088176 2.178993,47.343651 1.625463,47.357844 + + + + + 2 + My simple Polygon + 0 + + + + + + + 0.000000,45.000000 2.000000,47.000000 + + + + + + + + + 0.000000,45.000000 2.000000,45.000000 2.000000,47.000000 0.000000,47.000000 0.000000,45.000000 + + + + + 0.500000,45.500000 1.500000,45.500000 1.500000,46.500000 0.500000,46.500000 0.500000,45.500000 + + + + + + + 3 + my polygon with hole + 3 + + + --- /dev/null +++ b/openlayers/examples/google-reproject.html @@ -1,1 +1,58 @@ + + + OpenLayers Google with Overlay Example + + + + + + + + +

Google with Overlay Example

+ +
+ +

+ Demonstrate a Google basemap used with boundary overlay layer. +

+ +
+ +
+ An overlay in a Geographic projection can be stretched to somewhat + line up with Google tiles (in a Mercator projection). Results get + worse farther from the equator. Use the "reproject" option on a + layer to get this behavior. Use the sphericalMercator option on + a Google layer to get proper overlays (with other layers in + Spherical Mercator). +
+ + + --- /dev/null +++ b/openlayers/examples/google.html @@ -1,1 +1,64 @@ + + + OpenLayers Google Layer Example + + + + + + + + + +

Google Layer Example

+ +
+ +

+ Demonstrate use of the various types of Google layers. +

+ +
+ +
+

+ For best performance, you must be using a version of the Google Maps + API which is v2.93 or higher. In order to use this version of the API, + it is best to simply set your application to use the string "v=2" in + the request, rather than tying your application to an explicit version.

+

+ In order to position the Google attribution div in the default ocation, + you must include the extra theme/default/google.css stylesheet.

+
+ + + --- /dev/null +++ b/openlayers/examples/graphic-name.html @@ -1,1 +1,88 @@ + + + OpenLayers Graphic Names + + + + + + +

Named Graphics Example

+ +
+ +

+ Shows how to use well-known graphic names. +

+ +
+ +
+ OpenLayers supports well-known names for a few graphics. You can use + the names "star", "cross", "x", "square", "triangle", and "circle" as + the value for the graphicName property of a symbolizer. +
+ + --- /dev/null +++ b/openlayers/examples/gutter.html @@ -1,1 +1,55 @@ + + + OpenLayers Gutter Example + + + + + + + +

Gutter Example

+
+ +

+ Demonstrates map tiling artifacts, and OpenLayer's facility for correcting this distortion. +

+ +
+ +
+

+ When you render tiles with certain types of symbols, there are artifacts + at tile edges that make symbology not look continuous. In the center of + the above map (when it first loads), the large orange road is split + vertically by a tile. Open the layer switcher and change to the layer + with a 15 pixel gutter to see how the symbology looks nicer. +

+
+ + + --- /dev/null +++ b/openlayers/examples/highlight-feature.html @@ -1,1 +1,81 @@ + + + SelectFeature Control for Select and Highlight + + + + + + + + +

OpenLayers Select and Highlight Feature Example

+

+ Select features on click, highlight features on hover. +

+
+

Select features by clicking on them. Just highlight features by hovering over + them.

+ + + --- /dev/null +++ b/openlayers/examples/hover-handler.html @@ -1,1 +1,212 @@ - + + + OpenLayers Hover Handler Example + + + + + + + + +

Hover Handler Example

+
+ +
+
+ +

+ This example shows the use of the hover handler. +

+ +
+

+ The hover handler is to be used to emulate mouseovers on + objects on the map that aren't DOM elements. For example + one can use the hover hander to send WMS/GetFeatureInfo + requests as the user moves the mouse over the map. +

+

+ The "delay" option specifies the number of milliseconds + before the event is considered a hover. Default is 500 + milliseconds. +

+

+ The "pixelTolerance" option specifies the maximum number + of pixels between mousemoves for an event to be + considered a hover. Default is null, which means no + pixel tolerance. +

+

+ The "stopMove" option specifies whether other mousemove + listeners registered before the hover handler listener must + be notified on mousemoves or not. Default is false (meaning + that the other mousemove listeners will be notified on + mousemove). +

+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Controls with hover handlers (toggle on/off to clear output)
long delay (2 sec)
short delay (100 msec)
tolerant (6 pixels)
untolerant (1 pixel)
stop propagation
+
+ + + --- /dev/null +++ b/openlayers/examples/image-layer.html @@ -1,1 +1,70 @@ + + + OpenLayers Image Layer Example + + + + + + + + +

Image Layer Example

+ +
+ +

+ Demonstrate a single non-tiled image as a selectable base layer. +

+ +
+ +
+

+ The "City Lights" layer above is created from a single web accessible + image. If you construct it without any resolution related options, + the layer will be given a single resolution based on the extent/size. + Otherwise, it behaves much like a regular layer. This is primarily + intended to be used in an overview map - where another layer type + might not make a good overview. +

+
+ + + --- /dev/null +++ b/openlayers/examples/intersects.html @@ -1,1 +1,184 @@ + + + Geometry Intersections + + + + + + + + +
+

OpenLayers Geometry Intersection Example

+

+ Use of geometry.intersects method for testing geometry intersections. +

+
+
+ + + +
+
+
+ Features +
+ + Intersections +
+ +
+ + + --- /dev/null +++ b/openlayers/examples/kamap.html @@ -1,1 +1,40 @@ + + + OpenLayers KaMap Example + + + + + + +

KaMap Example

+ +
+ +

+ Demonstrate a tiled kamap layer as the base map, which can be pre-cached for higher performance. +

+ +
+ +
+ + + --- /dev/null +++ b/openlayers/examples/kamap.txt @@ -1,1 +1,509 @@ - +value pair. + * + * The key is the name to be used by the tile caching system to store cached + * tiles within the base cache directory. This key should be a single word + * that uniquely identifies the map. + * + * The value associated with each key is an array of three values. The first + * value is a human-readable name to be presented to the user (should the + * application choose to do so) and the second value is the path to the map + * file. It is assumed that the map file is fully configured for use with + * MapServer/MapScript as no error checking or setting of values is done. The + * third value is an array of scale values for zooming. + */ + +$aszMapFiles = array( + "world" => array( "World", "/path/to/your/mapfile", + array( 10000 ), # in openlayers, the scale array doesn't matter. + "PNG24") + +/* Add more elements to this array to offer multiple mapfiles */ + +); + +/****************************************************************************** + * figure out which map file to use and set up the necessary variables for + * the rest of the code to use. This does need to be done on every page load + * unfortunately. + * + * szMap should be set to the default map file to use but can change if + * this script is called with map=. + */ +$szMap = 'world'; + +/****************************************************************************** + * kaMap! caching + * + * this is the directory within which kaMap! will create its tile cache. The + * directory does NOT have to be web-accessible, but it must be writable by the + * web-server-user and allow creation of both directories AND files. + * + * the tile caching system will create a separate subdirectory within the base + * cache directory for each map file. Within the cache directory for each map + * file, directories will be created for each group of layers. Within the group + * directories, directories will be created at each of the configured scales + * for the application (see mapfile configuration above.) + */ +$szBaseCacheDir = "/var/cache/kamap/"; + +/***** END OF CONFIGURABLE STUFF - unless you know what you are doing *****/ +/***** *****/ +/***** *****/ +/***** *****/ +/***** END OF CONFIGURABLE STUFF - unless you know what you are doing *****/ + +if (isset($_REQUEST['map']) && isset($aszMapFiles[$_REQUEST['map']])) +{ + $szMap = $_REQUEST['map']; +} + +$szMapCacheDir = $szBaseCacheDir.$szMap."/"; +$szMapName = $aszMapFiles[$szMap][0]; +$szMapFile = $aszMapFiles[$szMap][1]; +$anScales = $aszMapFiles[$szMap][2]; +setOutputFormat($aszMapFiles[$szMap][3]); +/****************************************************************************** + * output format of the map and resulting tiles + * + * The output format used with MapServer can greatly affect appearance and + * performance. It is recommended to use an 8 bit format such as PNG + * + * NOTE: the tile caching code in tile.php is not configurable here. It + * currently assumes that it is outputting 8bit PNG files. If you change to + * PNG24 here then you will need to update tile.php to use the gd function + * imagecreatetruecolor. If you change the output format to jpeg then + * you would need to change imagepng() to imagejpeg(). A nice enhancement + * would be to make that fully configurable from here. + */ +function setOutputFormat($szFormat) +{ + switch($szFormat) { + case "PNG24": + $GLOBALS['szMapImageFormat'] = 'PNG24'; //mapscript format name + $GLOBALS['szMapImageCreateFunction'] = "imagecreatefrompng"; // appropriate GD function + $GLOBALS['szImageExtension'] = '.png'; //file extension + $GLOBALS['szImageCreateFunction'] = "imagecreatetruecolor"; //or imagecreatetruecolor if PNG24 ... + $GLOBALS['szImageOutputFunction'] = "imagepng"; //or imagegif, imagejpeg ... + $GLOBALS['szImageHeader'] = 'image/png'; //the content-type of the image + break; + case "GIF": + $GLOBALS['szMapImageFormat'] = 'GIF'; //mapscript format name + $GLOBALS['szMapImageCreateFunction'] = "imagecreatefromgif"; // appropriate GD function + $GLOBALS['szImageExtension'] = '.gif'; //file extension + $GLOBALS['szImageCreateFunction'] = "imagecreate"; //or imagecreatetruecolor if PNG24 ... + $GLOBALS['szImageOutputFunction'] = "imagegif"; //or imagegif, imagejpeg ... + $GLOBALS['szImageHeader'] = 'image/gif'; //the content-type of the image + break; + case "JPEG": + $GLOBALS['szMapImageFormat'] = 'JPEG'; //mapscript format name + $GLOBALS['szMapImageCreateFunction'] = "imagecreatefromjpeg"; // appropriate GD function + $GLOBALS['szImageExtension'] = '.jpg'; //file extension + $GLOBALS['szImageCreateFunction'] = "imagecreatetruecolor"; //or imagecreatetruecolor if PNG24 ... + $GLOBALS['szImageOutputFunction'] = "imagejpeg"; //or imagegif, imagejpeg ... + $GLOBALS['szImageHeader'] = 'image/jpeg'; //the content-type of the image + break; + case "PNG": + $GLOBALS['szMapImageFormat'] = 'PNG'; //mapscript format name + $GLOBALS['szMapImageCreateFunction'] = "imagecreatefrompng"; // appropriate GD function + $GLOBALS['szImageExtension'] = '.png'; //file extension + $GLOBALS['szImageCreateFunction'] = "imagecreate"; //or imagecreatetruecolor if PNG24 ... + $GLOBALS['szImageOutputFunction'] = "imagepng"; //or imagegif, imagejpeg ... + $GLOBALS['szImageHeader'] = 'image/png'; //the content-type of the image + break; + case "DITHERED": + case "PNG8": + $GLOBALS['szMapImageFormat'] = 'dithered'; + $GLOBALS['szMapImageCreateFunction'] = "imagecreatefrompng"; + $GLOBALS['szImageExtension'] = '.png'; + $GLOBALS['szImageCreateFunction'] = "imagecreate"; + $GLOBALS['szImageOutputFunction'] = "imagepng"; + $GLOBALS['szImageHeader'] = 'image/png'; + break; + } +} + +/** + * create all directories in a directory tree - found on the php web site + * under the mkdir function ... + */ +function makeDirs($strPath, $mode = 0775) +{ + return is_dir($strPath) or ( makeDirs(dirname($strPath), $mode) and mkdir($strPath, $mode) ); +} + +/** + * This function replaces all special characters in the given string. + * + * @param szString string - The string to convert. + * + * @return string converted + */ +function normalizeString($szString) +{ + // Normalize string by replacing all special characters + // e.g. "http://my.host.com/cgi-bin/mywms?" + // becomes "http___my_host_com_cgi_bin_mywms_" + return preg_replace("/(\W)/", "_", $szString); +} + +/* bug 1253 - root permissions required to delete cached files */ +$orig_umask = umask(0); + +/* create the main cache directory if necessary */ +if (!@is_dir($szMapCacheDir)) + makeDirs($szMapCacheDir); + +/* get the various request parameters + * also need to make sure inputs are clean, especially those used to + * build paths and filenames + */ + /* + * the tile renderer accepts several parameters and returns a tile image from + * the cache, creating the tile only if necessary. + * + * all requests include the pixel location of the request at a certain scale + * and this script figures out the geographic location of the tile from the + * scale assuming that 0,0 in pixels is 0,0 in geographic units + * + * Request parameters are: + * + * map: the name of the map to use. This is handled by config.php. + * + * t: top pixel position + * l: left pixel position + * s: scale + * g: (optional) comma-delimited list of group names to draw + * layers: (optional) comma-delimited list of layers to draw + * force: optional. If set, force redraw of the meta tile. This was added to + * help with invalid images sometimes being generated. + * tileid: (optional) can be used instead of t+l to specify the tile coord., + * useful in regenerating the cache + */ + +$top = isset( $_REQUEST['t'] ) ? intval($_REQUEST['t']) : 0; +$left = isset( $_REQUEST['l'] ) ? intval($_REQUEST['l']) : 0; +$scale = isset( $_REQUEST['s'] ) ? $_REQUEST['s'] : $anScales[0]; +$bForce = isset($_REQUEST['force'])? true : false; +$groups = isset( $_REQUEST['g'] ) ? $_REQUEST['g'] : ""; +$layers = isset( $_REQUEST['layers'] ) ? $_REQUEST['layers'] : ""; + +// dynamic imageformat ---------------------------------------------- +//use the function in config.php to set the output format +if (isset($_REQUEST['i'])) + setOutputFormat( $_REQUEST['i'] ); +//---------------------------------------------------------------- + +/* tileid=t#####l#### can be used instead of t+l parameters. Useful in + * regenerating the cache for instance. + */ +if (isset( $_REQUEST['tileid']) && + preg_match("/t(-?\d+)l(-?\d+)/", $_REQUEST['tileid'], $aMatch) ) +{ + $top = intval($aMatch[1]); + $left = intval($aMatch[2]); +} + +/* Calculate the metatile's top-left corner coordinates. + * Include the $metaBuffer around the metatile to account for various + * rendering issues happening around the edge of a map + */ +$metaLeft = floor( ($left)/($tileWidth*$metaWidth) ) * $tileWidth * $metaWidth; +$metaTop = floor( ($top)/($tileHeight*$metaHeight) ) * $tileHeight *$metaHeight; +$szMetaTileId = "t".$metaTop."l".$metaLeft; +$metaLeft -= $metaBuffer; +$metaTop -= $metaBuffer; + +/* caching is done by scale value, then groups and layers and finally metatile + * and tile id. Create a new directory if necessary + */ +$szGroupDir = $groups != "" ? normalizeString($groups) : "def"; +$szLayerDir = $layers != "" ? normalizeString($layers) : "def"; + +$szCacheDir = $szMapCacheDir."/".$scale."/".$szGroupDir."/".$szLayerDir."/".$szMetaTileId; +if (!@is_dir($szCacheDir)) + makeDirs($szCacheDir); + +/* resolve cache hit - clear the os stat cache if necessary */ +$szTileId = "t".$top."l".$left; +$szCacheFile = $szCacheDir."/".$szTileId.$szImageExtension; +clearstatcache(); + +$szMetaDir = $szCacheDir."/meta"; +if (!@is_Dir($szMetaDir)) + makeDirs($szMetaDir); + +/* simple locking in case there are several requests for the same meta + tile at the same time - only draw it once to help with performance */ +$szLockFile = $szMetaDir."/lock_".$metaTop."_".$metaLeft; +$fpLockFile = fopen($szLockFile, "a+"); +clearstatcache(); +if (!file_exists($szCacheFile) || $bForce) +{ + flock($fpLockFile, LOCK_EX); + fwrite($fpLockFile, "."); + + //check once more to see if the cache file was created while waiting for + //the lock + clearstatcache(); + if (!file_exists($szCacheFile) || $bForce) + { + if (!extension_loaded('MapScript')) + { + dl( $szPHPMapScriptModule ); + } + if (!extension_loaded('gd')) + { + dl( $szPHPGDModule); + } + + if (!@is_Dir($szMetaDir)) + makeDirs($szMetaDir); + + $oMap = ms_newMapObj($szMapFile); + + /* Metatile width/height include 2x the metaBuffer value */ + $oMap->set('width', $tileWidth * $metaWidth + 2*$metaBuffer); + $oMap->set('height', $tileHeight * $metaHeight + 2*$metaBuffer); + + /* Tell MapServer to not render labels inside the metaBuffer area + * (new in 4.6) + * TODO: Until MapServer bugs 1353/1355 are resolved, we need to + * pass a negative value for "labelcache_map_edge_buffer" + */ + $oMap->setMetadata("labelcache_map_edge_buffer", -$metaBuffer); + + $inchesPerUnit = array(1, 12, 63360.0, 39.3701, 39370.1, 4374754); + $geoWidth = $scale/($oMap->resolution*$inchesPerUnit[$oMap->units]); + $geoHeight = $scale/($oMap->resolution*$inchesPerUnit[$oMap->units]); + + /* draw the metatile */ + $minx = $metaLeft * $geoWidth; + $maxx = $minx + $geoWidth * $oMap->width; + $maxy = -1 * $metaTop * $geoHeight; + $miny = $maxy - $geoHeight * $oMap->height; + + $nLayers = $oMap->numlayers; + $oMap->setExtent($minx,$miny,$maxx,$maxy); + $oMap->selectOutputFormat( $szMapImageFormat ); + $aszLayers = array(); + if ($groups || $layers) + { + /* Draw only specified layers instead of default from mapfile*/ + if ($layers) + { + $aszLayers = explode(",", $layers); + } + + if ($groups) + { + $aszGroups = explode(",", $groups); + } + + for($i=0;$i<$nLayers;$i++) + { + $oLayer = $oMap->getLayer($i); + if (($aszGroups && in_array($oLayer->group,$aszGroups)) || + ($aszLayers && in_array($oLayer->name,$aszLayers)) || + ($aszGroups && $oLayer->group == '' && + in_array( "__base__", $aszGroups))) + { + $oLayer->set("status", MS_ON ); + } + else + { + $oLayer->set("status", MS_OFF ); + } + } + //need transparency if groups or layers are used + $oMap->outputformat->set("transparent", MS_ON ); + } + else + { + $oMap->outputformat->set("transparent", MS_OFF ); + } + + + $szMetaImg = $szMetaDir."/t".$metaTop."l".$metaLeft.$szImageExtension; + $oImg = $oMap->draw(); + $oImg->saveImage($szMetaImg); + $oImg->free(); + eval("\$oGDImg = ".$szMapImageCreateFunction."('".$szMetaImg."');"); + if ($bDebug) + { + $blue = imagecolorallocate($oGDImg, 0, 0, 255); + imagerectangle($oGDImg, 0, 0, $tileWidth * $metaWidth - 1, $tileHeight * $metaHeight - 1, $blue ); + } + for($i=0;$i<$metaWidth;$i++) + { + for ($j=0;$j<$metaHeight;$j++) + { + eval("\$oTile = ".$szImageCreateFunction."( ".$tileWidth.",".$tileHeight." );"); + // Allocate BG color for the tile (in case the metatile has transparent BG) + $nTransparent = imagecolorallocate($oTile, $oMap->imagecolor->red, $oMap->imagecolor->green, $oMap->imagecolor->blue); + //if ($oMap->outputformat->transparent == MS_ON) + //{ + imagecolortransparent( $oTile,$nTransparent); + //} + $tileTop = $j*$tileHeight + $metaBuffer; + $tileLeft = $i*$tileWidth + $metaBuffer; + imagecopy( $oTile, $oGDImg, 0, 0, $tileLeft, $tileTop, $tileWidth, $tileHeight ); + /* debugging stuff */ + if ($bDebug) + { + $black = imagecolorallocate($oTile, 1, 1, 1); + $green = imagecolorallocate($oTile, 0, 128, 0 ); + $red = imagecolorallocate($oTile, 255, 0, 0); + imagerectangle( $oTile, 1, 1, $tileWidth-2, $tileHeight-2, $green ); + imageline( $oTile, 0, $tileHeight/2, $tileWidth-1, $tileHeight/2, $red); + imageline( $oTile, $tileWidth/2, 0, $tileWidth/2, $tileHeight-1, $red); + imagestring ( $oTile, 3, 10, 10, ($metaLeft+$tileLeft)." x ".($metaTop+$tileTop), $black ); + imagestring ( $oTile, 3, 10, 30, ($minx+$i*$geoWidth)." x ".($maxy - $j*$geoHeight), $black ); + } + $szTileImg = $szCacheDir."/t".($metaTop+$tileTop)."l".($metaLeft+$tileLeft).$szImageExtension; + eval("$szImageOutputFunction( \$oTile, '".$szTileImg."' );"); + imagedestroy($oTile); + $oTile = null; + } + } + if ($oGDImg != null) + { + imagedestroy($oGDImg); + $oGDImg = null; + } + if (!$bDebug) + { + unlink( $szMetaImg ); + } + } + //release the exclusive lock + flock($fpLockFile, LOCK_UN ); +} + +//acquire shared lock for reading to prevent a problem that could occur +//if a tile exists but is only partially generated. +flock($fpLockFile, LOCK_SH); + +$h = fopen($szCacheFile, "r"); +header("Content-Type: ".$szImageHeader); +header("Content-Length: " . filesize($szCacheFile)); +header("Expires: " . date( "D, d M Y H:i:s GMT", time() + 31536000 )); +header("Cache-Control: max-age=31536000, must-revalidate" ); +fpassthru($h); +fclose($h); + +//release lock +fclose($fpLockFile); + +/* bug 1253 - root permissions required to delete cached files */ +umask($orig_umask); + +exit; +?> + --- /dev/null +++ b/openlayers/examples/kml-layer.html @@ -1,1 +1,44 @@ + + + + + + + + +

KML Layer Example

+ +
+ +

+ Demonstrates loading and displaying a KML file on top of a basemap. +

+ +
+ +
+ + + --- /dev/null +++ b/openlayers/examples/kml/lines.kml @@ -1,1 +1,276 @@ + + + + KML Samples + 1 + Unleash your creativity with the help of these examples! + + + + + + + + + + + + + + Paths + 0 + Examples of paths. Note that the tessellate tag is by default + set to 0. If you want to create tessellated lines, they must be authored + (or edited) directly in KML. + + Tessellated + 0 + tag has a value of 1, the line will contour to the underlying terrain]]> + + -112.0822680013139 + 36.09825589333556 + 0 + 2889.145007690472 + 62.04855796276328 + 103.8120432044965 + + + 1 + -112.0814237830345,36.10677870477137,0 + -112.0870267752693,36.0905099328766,0 + + + + Untessellated + 0 + tag has a value of 0, the line follow a simple straight-line path from point to point]]> + + -112.0822680013139 + 36.09825589333556 + 0 + 2889.145007690472 + 62.04855796276328 + 103.8120432044965 + + + 0 + -112.080622229595,36.10673460007995,0 + -112.085242575315,36.09049598612422,0 + + + + Absolute + 0 + Transparent purple line + + -112.2719329043177 + 36.08890633450894 + 0 + 2569.386744398339 + 44.60763714063257 + -106.8161545998597 + + #transPurpleLineGreenPoly + + 1 + absolute + -112.265654928602,36.09447672602546,2357 + -112.2660384528238,36.09342608838671,2357 + -112.2668139013453,36.09251058776881,2357 + -112.2677826834445,36.09189827357996,2357 + -112.2688557510952,36.0913137941187,2357 + -112.2694810717219,36.0903677207521,2357 + -112.2695268555611,36.08932171487285,2357 + -112.2690144567276,36.08850916060472,2357 + -112.2681528815339,36.08753813597956,2357 + -112.2670588176031,36.08682685262568,2357 + -112.2657374587321,36.08646312301303,2357 + + + + Absolute Extruded + 0 + Transparent green wall with yellow outlines + + -112.2643334742529 + 36.08563154742419 + 0 + 4451.842204068102 + 44.61038665812578 + -125.7518698668815 + + #yellowLineGreenPoly + + 1 + 1 + absolute + -112.2550785337791,36.07954952145647,2357 + -112.2549277039738,36.08117083492122,2357 + -112.2552505069063,36.08260761307279,2357 + -112.2564540158376,36.08395660588506,2357 + -112.2580238976449,36.08511401044813,2357 + -112.2595218489022,36.08584355239394,2357 + -112.2608216347552,36.08612634548589,2357 + -112.262073428656,36.08626019085147,2357 + -112.2633204928495,36.08621519860091,2357 + -112.2644963846444,36.08627897945274,2357 + -112.2656969554589,36.08649599090644,2357 + + + + Relative + 0 + Black line (10 pixels wide), height tracks terrain + + -112.2580438551384 + 36.1072674824385 + 0 + 2927.61105910266 + 44.61324882043339 + 4.947421249553717 + + #thickBlackLine + + 1 + relativeToGround + -112.2532845153347,36.09886943729116,645 + -112.2540466121145,36.09919570465255,645 + -112.254734666947,36.09984998366178,645 + -112.255493345654,36.10051310621746,645 + -112.2563157098468,36.10108441943419,645 + -112.2568033076439,36.10159722088088,645 + -112.257494011321,36.10204323542867,645 + -112.2584106072308,36.10229131995655,645 + -112.2596588987972,36.10240001286358,645 + -112.2610581199487,36.10213176873407,645 + -112.2626285262793,36.10157011437219,645 + + + + Relative Extruded + 0 + Opaque blue walls with red outline, height tracks terrain + + -112.2683594333433 + 36.09884362144909 + 0 + 2184.193522571467 + 44.60855445139561 + -72.24271551768405 + + #redLineBluePoly + + 1 + 1 + relativeToGround + -112.2656634181359,36.09445214722695,630 + -112.2652238941097,36.09520916122063,630 + -112.2645079986395,36.09580763864907,630 + -112.2638827428817,36.09628572284063,630 + -112.2635746835406,36.09679275951239,630 + -112.2635711822407,36.09740038871899,630 + -112.2640296531825,36.09804913435539,630 + -112.264327720538,36.09880337400301,630 + -112.2642436562271,36.09963644790288,630 + -112.2639148687042,36.10055381117246,630 + -112.2626894973474,36.10149062823369,630 + + + + Blue Icon + Just another blue icon. + kml/styles.kml#blueIcons + + -112.292238941097,36.09520916122063,630 + + + + + --- /dev/null +++ b/openlayers/examples/kml/styles.kml @@ -1,1 +1,22 @@ + + + + + + + + + + --- /dev/null +++ b/openlayers/examples/kml/sundials.kml @@ -1,1 +1,2274 @@ - + + + + Sundial Collection.kmz + + + normal + #sn_sunny_copy69 + + + highlight + #sh_sunny_copy70 + + + + + + + + + normal + #sn_sunny_copy68 + + + highlight + #sh_sunny_copy69 + + + + Sundial Collection + 1 + + -56.6884384968692 + 47.91963617483238 + 0 + 9958750.824018393 + 1.303827428939919e-015 + -16.31426621668193 + + + + High Resolution + + Sundial, Madestein, Den Haag +

]]>
+ + 4.213227700645635 + 52.04260288332888 + 0 + 24.63686803544318 + 0 + 1.387289180270979e-005 + + #msn_sunny_copy69 + + 4.213209970684247,52.04268354765237,0 + +
+ + Sundial, Den Haag - Loosduinen + Thanks to A30

+ +A sundial made of wooden blocks. +The highest block in the middle is the style and casts its shadow each hour on one of the other blocks. + +

+Image source:www.dse.nl]]>
+ + 4.236038669148795 + 52.0499434967447 + 0 + 18.37312193280116 + 2.202011190893535e-011 + -0.3988978466888938 + + #msn_sunny_copy69 + + 4.236026636181407,52.049986562365,0 + +
+ + Sundial with light conductors - Paris, Les Halles +

+ +The sunlight falls on one of the three windows in the column (east, south, west) and over light conductors on the wall is indicated. + +

+ +The clock shows 16,40 o'clock. + +

+ +Quelle:http://www.home.uni-osnabrueck.de/ahaenel/sonnuhr/paris_halles.htm + +http://perso.orange.fr/cadrans.solaires/cadrans/cadran-halles-paris.html]]>
+ + 2.344185113917775 + 48.86294270160059 + 0 + 39.52787486507292 + 0 + -0.003533584730563007 + + #msn_sunny_copy69 + + 2.344143312335305,48.86302323987447,0 + +
+ + Sundial, Plymouth, Devon, UK + The gnonom is 27 foot high, the pool has 21 feet diameter. It was designed by architect Carole Vincent from Boscastle in Cornwall and was unvieled by Her Majesty the Queen on Friday July 22nd 1988 for a cost of cost £70,000 . The sundial runs one hour and seventeen minutes behind local clocks.

+

+

Image source:

+Image source:www.sundials.co.uk]]>
+ + -1.117890647596098 + 50.79319978711329 + 0 + 79.08348690288113 + 0 + 0.02100880488328328 + + #msn_sunny_copy69 + + -1.117887915142518,50.79336425684474,0 + +
+ + Sundial, Britzer Garten, Berlin + See photos on this page: +http://home.arcor.de/ruth.kirsch/sonnenuhr/berlin_1xxxx/berlin_1xxxx.htm + + 13.42078373972489 + 52.4366841172644 + 0 + 102.2086892967038 + 0 + -0.004885703167479627 + + #msn_sunny_copy69 + + 13.4207448482471,52.43682055829985,0 + + + + Sundial, Falkenplatz, Berlin + The original reasoning event for the construction of the sundial was the UNO climate conference 1995 in Berlin. The base stone of the wall spiral was layed at a festivity at the equinox of March 1995. Until June 1995 the main construction was completed, and at another festivity at the summer solstice the gnonom and the totem ("Lebensbaum") was installed by Berlin fire fighters.

+

+

The nearly spiral sundial was planned as a "living sundial" and initiated by the groups of the "Netzwerk Klimagipfel 95", mainly by the journalist T. Römer with the "Verein zur Rettung des Regenwaldes und Naturschutzgebietes La Macarena", and the "Netzwerk Spiel/Kultur" at the Prenzlauer Berg. + + +The covering clay stones were made out of three metric tons of white and brown clay, formed by children of about 50 institutions like school classes and kindergardens of the closer region. The stones were burned and installed in the summer of 1995. Partly they are constructed out of different materials, partly especially formed or ornamented. Six detail images are showing some examples: (White near Red - MC?, Smiley with Heart Eyes, Sun-Moon-Star, Red Broken and Patterned, Rain pits and Stone Hearts in Clay, Red near White - Clay Fish and Sunshine over the Sea). + +In September 1995 the sundial was completed. It was called "living sundial" because it was planned to replace the clay stones regulary when they are destroyed and to add some green to the outside wall of the clock. In December 1995 the clock got a special price of the local environmental administration.

+

.

+ + +

In September 1995 the sundial was completed. It was called "living sundial" because it was planned to replace the clay stones regulary when they are destroyed and to add some green to the outside wall of the clock. In December 1995 the clock got a special price of the local environmental administration.

+

+ +

This sundial was deconstructed at the end of 2002 or at the beginning of 2003:

+

+ +

Image source and infos:www.surveyor.in-berlin.de

]]>
+ + 13.40239121468946 + 52.54640622802566 + 0 + 55.75497205265645 + 1.489511345854323e-009 + 2.6367660621925e-005 + + #msn_sunny_copy69 + + 13.40233774797299,52.54645010247089,0 + +
+ + Sundial, Halde Schwerin, Castrop-Rauxel, Germany + Thanks to htd42

+

+ +http://www.ruhrgebiet.de/freizeit/sehenswuerdigkeiten/cr_halde_schwerin.shtml?print]]>
+ + 7.337404407947669 + 51.54597716006042 + 0 + 51.28632275218226 + 2.512805793870883e-009 + -6.529566789930303e-005 + + #msn_sunny_copy69 + + 7.337359256982781,51.54610609965799,0 + +
+ + Sundial, Lloydminster, Canada +

+

Image source and infos:www.mts.net

]]>
+ + -110.0353754682919 + 53.26386357821667 + 0 + 155.9861269181855 + 0 + -0.01432903343453666 + + #msn_sunny_copy69 + + -110.0355256583979,53.26413794825379,0 + +
+ + Giant Lady's Leg Sundial, Roselawn, Indiana, USA + The Sun Aura Nudist Resort opened in 1933. Back then it was called Club Zoro and its founder was Alois Knapp, a Chicago lawyer, German Nacktkulturist, editor of Sunshine and Health magazine, and "the father of nudism in America."

+ +

+ +

The club eventually passed into the hands of Dale and Mary Drost. Their son, Dick, had big ideas: he renamed the place Naked City, made it the home of the Ms. Nude Teeny Bopper Contest and the "Erin Go Bra-less" Dance on St. Patrick's Day, and had built the giant lady's leg sundial, 63 feet long and properly positioned to tell time -- a useful feature for wristwatchless nudists.

+ +

Naked City closed in 1986 when Dick was run out of Indiana on child molestation charges, but the leg remains and so does the resort, now under new management. The circular main building with the mirror gold windows is a combination office-sauna-restaurant.

+ +

The guy who paints the leg told us that Sun Aura is a "clothing optional" camp -- in other words, you don't have to get nude to take a picture of the big lady's leg. But for those who do choose to get into the spirit of things, a helpful sign on the exit road reads, "Stop. You Must Be Dressed Beyond This Point."

+ +

Roadside America

]]>
+ + -87.32599841452155 + 41.14248697221019 + 0 + 40.06529731982877 + 0 + -108.7495178792767 + + #msn_sunny_copy69 + + -87.32608203713804,41.14242622349031,0 + +
+ + Sundial, Ingleside, San Francisco, USA + Thanks to CostaPacific

+ +Most people in San Francsco have no idea that their city is host to the world's second largest sundial. It was built in 1913 as a gimic to attract people to a new housing development that was built arround the configuration of the old Ingleside Race Track. + +

+

Image source:]]> + + -122.4687521474299 + 37.72475779376939 + 0 + 104.1096478961583 + 0 + -6.694029629862418e-005 + relativeToGround + + #msn_sunny_copy69 + + -122.4687727980979,37.72497790751523,59.97947112427937 + + + + Sundial Bridge + Located in Redding, CA. Opened in 2004 this bridge actually acts as a sundial. The time can be read in a garden on the North side of the bridge. + +http://www.turtlebay.org/sundial/sundial.shtml + + -122.3775376532067 + 40.59329504591046 + 0 + 160.1654912126178 + 7.884938307004504e-010 + 0.008470312235033726 + + #msn_sunny_copy69 + + -122.3777030796087,40.59376952663914,0 + + + + Sundial, Jaipur,India + Villaman + +

+ + + + +

Jaipur Observatory Sundial


+
+

Walk through these doors and up the stairs to begin your journey along a line from Jaipur, India toward the North Celestial Pole. Such cosmic alignments abound in marvelous Indian observatories where the architecture itself allows astronomical measurements. The structures were built in Jaipur and other cities in the eighteenth century by the Maharaja Jai Singh II (1686-1743). Rising about 90 feet high, this stairway actually forms a shadow caster or gnomon, part of what is still perhaps the largest sundial on planet Earth. Testaments to Jai Singh II's passion for astronomy, the design and large scale of his observatories' structures still provide impressively accurate measurements of shadows and sightings of celestial angles. +

Jaipur Observatory Sundial

+

More here. + +

+]]> + + 75.82482649881683 + 26.924766672173 + 0 + 164.397137416247 + 0 + -0.02454798212483729 + relativeToGround + + #msn_sunny_copy69 + + 75.82474437483685,26.92504292845888,0 + + + + Sundial, Schothorstpark, Amersfoort, Netherlands + A large sundial in the Schothorstpark in Amersfoort. +Thanks to Acadvice]]> + + 5.385083481782106 + 52.17868238866643 + 0 + 49.70911801163624 + 5.249316070079438e-010 + 6.699999294207586e-006 + relativeToGround + + #msn_sunny_copy69 + + 5.385063337537176,52.17873082332495,0 + + + + Sundial, Jardin de Reuilly, Paris +

+ +

+Image source:http://perso.orange.fr

]]>
+ + 2.387204592843604 + 48.84242901629369 + 0 + 50.11592463998582 + 8.113900329668256e-010 + -0.001210217218456717 + relativeToGround + + #msn_sunny_copy69 + + 2.38716774037826,48.84252766103683,0 + +
+ + Sundial, Stockgrove, Soulbury, Buckinghamshire, UK + thanks to houdinia

+Sundial with analemmatic clock face. +

]]>
+ + -0.666503881371199 + 51.95548351688392 + 0 + 55.27920580004575 + 6.264058771241075e-010 + 0.06911766261471311 + relativeToGround + + #msn_sunny_copy69 + + -0.6665014664411046,51.95551857959676,0 + +
+ + Sundial, Halde Hoheward, Germany + The Obelisk – The Sundial

+ +

The seeming movement of the sun in the sky, resulting in the discrimination between day and night, was one of the earliest observations of nature performed by men. It enables us to experience the phenomenon “time†with our own senses. The first examples for telling the time with the help of the sun or its shadow date back to the Ancient World. The are numerous archetypes for sundials built inmany different styles, using different techniques.

+ +

The archetype for the horizontal sundial on top of the slagheap Hoheward is the sundial of the Roman Emperor Augustus on the Campus Martius in Rome.

+(It is unknown, whether this ancient obelisk was part of a complete sun dial with hour and declination lines on the morning and afternoon side or whether only a meridian line existed to measure the elevation of the sun in upper culmination. The today's scientific knowledge indicates the existence of a meridian.) The observation of the Obelisk's shadow on the sundial enables the observer to easily determine date and time. Apart from “time†one can also experience the laws of celestial mechanics. Men encounter themselves in relation to the cosmos.

+ +

Representing the first step in the realisation of the Astronomical Theme Park the Obelisk was opened on May 17th, 2005. It is located on the already completed south-eastern plateau of the slagheap at a height of 140 m above sea level. The shadowed area is 62 m in diameter.

+ + + +

+This picture shows the Obelisk after the end of the assembly on the day of the opening. Shortly before it was put on top of the readily prepared pedestal by a helicopter and then bolted. + +

+ +

http://www.horizontastronomie.de

+ + + +

http://de.wikipedia.org/wiki/Halde_Hoheward#Sonnenuhr_mit_Obelisk

+ +

http://www.horizontastronomie.de/animationen.htm

]]>
+ + 7.170033145228383 + 51.56646738931531 + 0 + 96.7791497847863 + 4.155528307086707e-010 + 0.006376147752644328 + relativeToGround + + #msn_sunny_copy69 + + 7.169892708740022,51.56683509795316,0 + +
+ + Sundial, Fachhochschule (FH) Bielefeld +

+http://www.fh-bielefeld.de/article/fh/4412/1/505?NavItemID=0&NavCatID=162]]>
+ + 8.555263115842216 + 52.02672953436973 + 0 + 50.10364671714684 + 0 + 0.001255164290936946 + relativeToGround + + #msn_sunny_copy69 + + 8.555215193531964,52.02681111856448,0 + +
+ + Sundial, Sun City, Arizona +

+ +

]]>
+ + -112.2739996808105 + 33.61902729376313 + 0 + 44.66059102278575 + 0 + 0.0001994953180518285 + relativeToGround + + #msn_sunny_copy69 + + -112.2740228273864,33.61913038777643,0 + +
+ + Sundial, Georgina Blach Intermediate School, Los Altos, CA +

]]>
+ + -122.083063541274 + 37.36394994353518 + 0 + 99.46493929648614 + 0 + -6.524992683547596e-005 + relativeToGround + + #msn_sunny_copy69 + + -122.0831077334675,37.3641379192763,0 + +
+ + Sundial, Hilltop Park, San Francisco +

]]>
+ + -122.3837414260284 + 37.73308769461563 + 0 + 76.96447255875415 + 0 + -0.0001251047167258125 + relativeToGround + + #msn_sunny_copy69 + + -122.3837885185873,37.73313852750733,0 + +
+ + Sundial, Berlin-Weißensee +

+ +http://www.be.schule.de/schulen/wfs/pages/sundials/Weissensee.html]]>
+ + 13.46637059089964 + 52.55408525446345 + 0 + 35.24186259647233 + 0 + -0.002133411261797274 + relativeToGround + + #msn_sunny_copy69 + + 13.46637589519183,52.55412143657096,0 + +
+ + Sundial, Olbers-Planetarium, Bremen +

]]>
+ + 8.806980778676786 + 53.06988134466393 + 0 + 24.09705977000565 + 0 + -0.001876272046377585 + relativeToGround + + #msn_sunny_copy69 + + 8.806963468445417,53.0698959991562,0 + +
+ + Sundial, Westbroekpark, Denn Haag +

]]>
+ + 4.290891177932192 + 52.10450647693549 + 0 + 20.57779559985518 + 0 + -0.8669355345663358 + relativeToGround + + #msn_sunny_copy69 + + 4.290865552422943,52.10453275113748,0 + +
+ + Sundial, Amersfoort, Netherlands +

]]>
+ + 5.374167244217593 + 52.15310253836927 + 0 + 31.45592479376158 + 1.426589610824431e-009 + -0.01164696084898205 + relativeToGround + + #msn_sunny_copy69 + + 5.374145665653813,52.15310809583514,0 + +
+ + Sundial, Botanical Gardens, Sydney, Australia +

+ +

]]>
+ + 151.2154952669206 + -33.86399908828604 + 0 + 16.43666728184123 + 8.675342058213797e-007 + -0.002067228419448193 + relativeToGround + + #msn_sunny_copy69 + + 151.2154882763944,-33.86398565287625,0 + +
+ + Team Disney Sundial, Walt Disney World, Florida + Oftencold + +

+ +http://www.de-zonnewijzerkring.nl/zw-arch/eng-home-zw-07-02.htm]]>
+ + -81.52113085122878 + 28.36541360352638 + 0 + 167.7307771712135 + 1.015026730473625e-011 + -0.006287852151169638 + relativeToGround + + #msn_sunny_copy69 + + -81.52134276012195,28.36559634883421,0 + +
+ + Sundial, Janskerkhof, Utrecht, Netherlands +

+ +

]]>
+ + 5.121095723583527 + 52.09338586502101 + 0 + 24.25734051739648 + 5.490226183683639e-010 + -0.0007122606404517594 + relativeToGround + + #msn_sunny_copy69 + + 5.121088800707085,52.09341776135472,0 + +
+ + Sundial, San Jose Rep Theater, San Jose, CA +

+ +

Image credit:www.groundspeak.com

]]>
+ + -121.8860266085782 + 37.33361545835343 + 0 + 32.31958319185324 + 0 + 1.418565866412994e-005 + relativeToGround + + #msn_sunny_copy69 + + -121.886064353331,37.33364018615777,0 + +
+ + Millennium Sundial, Greenwich Park, London +

+ +

Image credit:www.groundspeak.com

]]>
+ + -0.001522539653513039 + 51.48136176862654 + 0 + 61.96314954770909 + 2.850197260451716e-009 + -0.002911073287638733 + relativeToGround + + #msn_sunny_copy69 + + -0.00156808979284051,51.48142700407306,0 + +
+ + Sundial, Veterans Park, Waukesha, WI +

+ +

Image credit:www.groundspeak.com

]]>
+ + -88.2367572684424 + 43.00995357504599 + 0 + 49.0879478099675 + 0 + -2.769547716555237e-005 + relativeToGround + + #msn_sunny_copy69 + + -88.23678272979073,43.01004377682637,0 + +
+ + Underground Sundial, Munich, Germany +

+ +

Image credit:www.groundspeak.com

]]>
+ + 11.70480163926041 + 48.13338615699044 + 0 + 49.09160069235252 + 7.359413992305611e-011 + 1.363313751616389e-005 + relativeToGround + + #msn_sunny_copy69 + + 11.70474103166116,48.13350333174798,0 + +
+ + Sundial, Crown Hill cemetery, Indianapolis, Indiana, USA +

+ +

Image credit:www.groundspeak.com

]]>
+ + -86.17300915391851 + 39.82668935299838 + 0 + 35.63730089613371 + 0 + 2.616180723282867e-005 + relativeToGround + + #msn_sunny_copy69 + + -86.17304253331795,39.82668119645058,0 + +
+ + Sundial, Coppell, TX +

+ +

Image credit:www.groundspeak.com

]]>
+ + -97.02194975520763 + 32.95633568822581 + 0 + 61.19896168864369 + 0 + 1.826645706530163e-005 + relativeToGround + + #msn_sunny_copy69 + + -97.02199840401494,32.95643533824669,0 + +
+
+ + Low Resolution + + Sundial, Kota Baru Parahyangan + voorburger. +

]]>
+ + 107.4940550739811 + -6.852038750176605 + 0 + 296.7282563680993 + 2.08633946131246e-011 + 0.5509822616366601 + relativeToGround + + #msn_sunny_copy70 + + 107.4939718861608,-6.851748821808833,0 + +
+ + Sundial, Pajala, Sweden + The world's biggest sundial today is in the Torne Valley, north of the Arctic Circle. The Guinness Book of Records has put Pajala, northern Sweden, on the map, and its sundial - formed as a "round square".

+

+

The sundial in Pajala, 38.33 m. in diameter, holds the world record, according to the Guinness Book of Records. The previous record was held by Disney World in Orlando, Florida, with 37.18 m.

+ +

The sundial was inaugurated by the Swedish Minister of Labour Margareta Winberg in July 1996. Pajala is situated at 23.28 ° East, 67.21 ° North, which is 70 km north of the Arctic Circle, making a circular sundial possible. This is due to the fact that the Midnight Sun describes a complete circle over the horizon. + +

Its masts of dried fir form a unique spatiality around a circular "square". The site is especially used for local functions such as Pajala Fair, Romp Week and the Northern Lights Festival.

+ +

The central square in Pajala, through its size and latitude, offered conditions for a sundial dedicated to the Midnight Sun. Architect Mats Winsa took his inspiration from the square in Siena, and for the sculptures in the park - astronomical instruments in India dating back to the 18th century. Naturally, it was a challenge to compete with the previous record from 1991 by the world-famous Japanese architect, Arata Isozaki.

+ +

The sundial captures the sun's movement by allowing the shadow of the central gnomon to fall across the hour divisions of the surrounding posts. The gnomon, like the Earth's axis, points toward the Pole Star, which according to Finnish-Ugrian mythology (the region has Finnish roots) holds up the firmament. The "sun wheel" embedded in the ground here (forming a cross in the circle) is in fact a calendar. Water bubbles up from four sources corresponding to the four principal points of the compass. The water gathers in the central pond, which was designed with children in mind.

+ +

For their survival, humans have followed the rhythm of the sun. The need to observe the changing seasons and days led to the early development of the sundial. Our lives today are characterised by obedience to mechanical and national time - inventions separate from true solar time. The sundial displays true solar time, which in Pajala is half-an-hour ahead of national time.

+ +

The sundial in Pajala celebrates light, and acts as a reminder of its significance for all life by functioning as a biological clock in a world fettered by artificial time. The hormone rush in spring reminds us of our direct dependence on sunlight as living beings.

+ +

Info and image source:www.pajala.se

+ +

+

Image source:http://holmers.com

]]>
+ + 23.36723004664742 + 67.21282676944374 + 0 + 124.9604027877409 + 3.010594647959025e-010 + -1.130925335798896 + relativeToGround + + #msn_sunny_copy70 + + 23.36716252896882,67.21299216873888,0 + +
+ + Sundial, Tenerife, Spain +

+ +

+ +

+ +

+

Image source:http://members.aon.at

+ +

Interactice picture

]]>
+ + -16.56926659562192 + 28.08256590461729 + 0 + 88.29371157400612 + 0 + 8.633540737161729e-005 + relativeToGround + + #msn_sunny_copy70 + + -16.5693071701084,28.08261960124695,0 + +
+ + Sundial, Perranporth, UK +

]]>
+ + -5.157517535037663 + 50.34723421976403 + 0 + 65.69642310338585 + 0 + -0.01795551609583625 + relativeToGround + + #msn_sunny_copy70 + + -5.157537433789316,50.34733238709538,0 + +
+ + Sundial, Council Bluffs, Iowa, USA +

+

+

]]>
+ + -95.84953495410247 + 41.25887711431908 + 0 + 196.5752069699831 + 2.583166383376495e-010 + 0.0002124063872384501 + + #msn_sunny_copy70 + + -95.84981881431206,41.25888611306795,294.4878429401121 + +
+ + Sundial. Meckhofen, Leverkusen, Germany +

+

Image source:www.leverkusen.de

+ +

+ +

+

Image source:www.lev2000.de

]]>
+ + 7.083354426150351 + 51.04845387008112 + 0 + 66.17616066250443 + 9.735256695418331e-010 + 0.0006924896867520876 + relativeToGround + + #msn_sunny_copy70 + + 7.083321386023442,51.04852440832129,0 + +
+ + Sundial, Adler Planetarium, Chicago, USA +

]]>
+ + -87.60711153340705 + 41.86674796371171 + 0 + 27.37440941953917 + 0 + 0.008419825260544345 + + #msn_sunny_copy70 + + -87.60710764637246,41.86681374132155,0 + +
+ + Rose Garden Sundial, Christchurch, New Zealand + Thanks to NormB

+Rose Garden History + +

+Photo - NormB 11th April 2006
+Image Hosted by ImageShack.us

+Photo - NormB 11th April 2006
+Image Hosted by ImageShack.us]]> + + 172.621331272394 + -43.53038034442864 + 0 + 86.04933199573917 + 0 + 1.801092527765711 + + #msn_sunny_copy70 + + 172.6213650004974,-43.53035465311722,0 + + + + Sundial, Natchez Park + Thanks to caroling

+ +In Seaside, NW FL, USA on the Emerald Coast. Panoramic images and movies of a sundial and visions of Xtals (energy crystals) on the March equinox, 2006. See http://www.wholeo.net/Trips/Art/Web/TripsArt/Travel/Florida/borders/flBorders.htm]]>
+ + -86.14177717779702 + 30.32184243688109 + 0 + 46.50596341362312 + 9.523139707563741e-010 + 0.0925379903960088 + + #msn_sunny_copy70 + + relativeToGround + -86.14183223138707,30.32193188899003,3 + +
+ + Sundial, Charlotte, North Carolina, USA + Thanks to BrettHo

+On the roof of the International Trade Center is this gigantic sundial.]]>
+ + -80.84002590296151 + 35.22682691631484 + 0 + 73.21919569418378 + 0 + 12.34188537748346 + + #msn_sunny_copy70 + + -80.84002447413604,35.22696160522812,0 + +
+ + KTPalmerSundial, Carefree, Arizona, USA + seer
. +

+http://www.bigwaste.com/photos/az/sundial/]]> + + -111.9217799027029 + 33.8245907883639 + 0 + 119.8165563905356 + 2.774426682549449e-010 + -1.574999619300427e-005 + + #msn_sunny_copy70 + + -111.9218327194278,33.82468559440962,0 + + + + Sundial, University of Science and Technology, Hong Kong +

+ +

Image source:http://perso.orange.fr

]]>
+ + 114.2630116779084 + 22.33749401387006 + 0 + 111.6162130745504 + 0 + 0.0003913059632004609 + relativeToGround + + #msn_sunny_copy70 + + 114.2629690669868,22.33764072332584,0 + +
+ + Sundial, Pekin, Illinois +

+

Image source:www.pekin.net

+ +

]]>
+ + -89.63076522889526 + 40.56267466732153 + 0 + 161.1716772997438 + 0 + 0.009112399365723663 + relativeToGround + + #msn_sunny_copy70 + + -89.63089561079578,40.56281064339486,0 + +
+ + Sundial, Edinburg, Hidalgo, USA +

+ +

<7p>]]> + + -98.17095602857175 + 26.30618568257091 + 0 + 122.1950947751469 + 0 + -0.005400653570135644 + relativeToGround + + #msn_sunny_copy70 + + -98.17104492887813,26.30639237212602,0 + + + + Sundial, Keppel Henge, +

+

http://www.steveirvine.com/sundial.html

+ + +http://www.mts.net/~sabanski/sundial/sotw_canada_keppel.htm]]>
+ + -80.94374423682251 + 44.79038599160477 + 0 + 164.0454159373261 + 0 + -0.007334046679263517 + relativeToGround + + #msn_sunny_copy70 + + -80.94383190841853,44.79038705635566,0 + +
+ + Sundial at Science North, Sudbury, Ontario +

+ +

+ +

+ +http://www.mts.net/~sabanski/sundial/sotw_canada_sn.htm]]>
+ + -80.99582033913947 + 46.46976830028441 + 0 + 85.82915438648354 + 0 + 0.0003317215281456315 + relativeToGround + + #msn_sunny_copy70 + + -80.99588716181201,46.46988111501548,0 + +
+ + Sundial, Amble, UK +

+http://ourworld.compuserve.com/homepages/Patrick_Powers/amble.htm]]>
+ + -1.581634687429885 + 55.33514811404725 + 0 + 62.55005662709024 + 8.224100904372228e-010 + -0.008198736253532122 + relativeToGround + + #msn_sunny_copy70 + + -1.581720999552488,55.3352025087941,0 + +
+ + Sundial, University of Maryland, College Park +

+http://www.mts.net/~sabanski/sundial/sotw_usa_mland.htm]]>
+ + -76.94256839624576 + 38.98603731470438 + 0 + 69.47353847793947 + 0 + -0.00947513273561203 + relativeToGround + + #msn_sunny_copy70 + + -76.94253686137193,38.98616316295006,0 + +
+ + Sundial, Fort San Felipe del Morro, Puerto Rico +

]]>
+ + -66.11899284422442 + 18.46786530709565 + 0 + 122.9114928009769 + 0 + 0.001639161983653822 + relativeToGround + + #msn_sunny_copy70 + + -66.11900470518663,18.4679529172629,0 + +
+ + Sundial, Rose Garden, Phoenix +

]]>
+ + -112.0911976535298 + 33.47007786030556 + 0 + 26.72933602203598 + 4.053298886062559e-011 + 0.0001093808645832187 + relativeToGround + + #msn_sunny_copy70 + + -112.0912131593616,33.4701136927338,0 + +
+ + Sundial, Tucson, Arizona +

]]>
+ + -110.9748374101104 + 32.21591986778585 + 0 + 42.12321141209996 + 0 + -0.0002919115031976927 + relativeToGround + + #msn_sunny_copy70 + + -110.9748562940359,32.21593667064053,0 + +
+ + Sundial, Flandrau Planetarium, Tucson +

]]>
+ + -110.9477979774635 + 32.23224398378896 + 0 + 33.29181342845133 + 3.6608792363658e-017 + 0.0001605580448802178 + relativeToGround + + #msn_sunny_copy70 + + -110.9478231994691,32.23228861367718,0 + +
+ + Sundial, Vietnam Veterans Memorial, Kentucky +

+ +http://www.vietvet.org/kymem.htm]]>
+ + -84.8640348419774 + 38.17725413584271 + 0 + 136.9757698325458 + 1.322889725758878e-010 + -0.0003615314930558497 + relativeToGround + + #msn_sunny_copy70 + + -84.86405079639164,38.17749508752453,0 + +
+ + Sundial, Claremont, California +

+ +

]]>
+ + -117.7288129576152 + 34.0992297660836 + 0 + 60.73786036422235 + 1.321942869740197e-009 + -0.002677989156069468 + relativeToGround + + #msn_sunny_copy70 + + -117.7288254316814,34.09928418001653,0 + +
+ + Sundial, Hoogezand, Netherlands +

+ +http://www.hoogezand-sappemeer.nl/index.php?simaction=content&mediumid=10&pagid=335&fontsize=10&stukid=2597]]>
+ + 6.73589462010654 + 53.15594584104552 + 0 + 139.6528910743265 + 1.45482979338997e-010 + 0.002950231733866508 + relativeToGround + + #msn_sunny_copy70 + + 6.73578802230557,53.15607461082266,0 + +
+ + Sundial, Hoogeveen, Netherlands +

]]>
+ + 6.469908327982116 + 52.72012840714818 + 0 + 73.67703044709106 + 4.200981642085038e-012 + -0.0001367978398152192 + relativeToGround + + #msn_sunny_copy70 + + 6.469803362243752,52.72017851542101,0 + +
+ + Sundial, Bicentennial Park, Homebush, Australia +

]]>
+ + 151.0785472180646 + -33.84641177017981 + 0 + 163.2808310648841 + 1.201655085829064e-011 + 4.265000695512084e-006 + relativeToGround + + #msn_sunny_copy70 + + 151.0784520824468,-33.84631674048389,0 + +
+ + Sundial, Heerenveen, Netherlands +

]]>
+ + 5.948360581453846 + 52.95021342348947 + 0 + 125.5263208537314 + 3.779142327674902e-010 + 2.174750871196e-005 + relativeToGround + + #msn_sunny_copy70 + + 5.94828156186523,52.95041125062435,0 + +
+ + Sundial, Zoetermeer, Netherlands +

+ +http://www.chabot.demon.nl/sundials/index3.htm]]>
+ + 4.48801136665803 + 52.03630549285332 + 0 + 64.63218166015471 + 0 + -0.0001443420778625332 + relativeToGround + + #msn_sunny_copy70 + + 4.48796065586356,52.03633054351467,0 + +
+ + Sundial, Lancaster, Lancashire, UK +

+ +

+ +

]]>
+ + -2.781711751106886 + 54.04618182827939 + 0 + 59.27999100628823 + 0 + 2.19677695423205e-005 + relativeToGround + + #msn_sunny_copy70 + + -2.781728090880108,54.0462693701831,0 + +
+ + Sundial, Nida, Lithuania +

]]>
+ + 20.99037235133227 + 55.29501544197078 + 0 + 177.9373429950499 + 4.620370977113893e-011 + 0.0005344762650417512 + relativeToGround + + #msn_sunny_copy70 + + 20.99033020665709,55.29525661423606,0 + +
+ + Sundial, Tavel, France +

+ +http://www.de-zonnewijzerkring.nl/zw-arch/eng-home-zw-07-03.htm]]>
+ + 4.700355808916944 + 44.00154771856498 + 0 + 254.0752666918187 + 2.46623000787332e-010 + -0.0002391009248289202 + relativeToGround + + #msn_sunny_copy70 + + 4.700351044280055,44.00172761202828,0 + +
+ + Sundial, St. Michielsgestel, Netherlands +

+ +

]]>
+ + 5.346086124850936 + 51.64327189620946 + 0 + 111.9437734662239 + 6.47253437341193e-010 + 0.002375287388397793 + relativeToGround + + #msn_sunny_copy70 + + 5.346049636943462,51.64334209867396,0 + +
+ + Sundial, Halle Saale, Germany +

+ +More photos:http://home.arcor.de/peter.lindner/sonnenuhr/h/halle_saale_061xx/halle_saale_061xx.htm]]>
+ + 11.94846541701928 + 51.49449346439673 + 0 + 51.14591828296211 + 0 + 0.0003207363265956715 + relativeToGround + + #msn_sunny_copy70 + + 11.9484244247504,51.49452140024068,0 + +
+ + Sundial, Abano Terme, Italy +

+ +http://members.aon.at/sundials/bild43_d.htm]]>
+ + 11.7902588657866 + 45.36024293920432 + 0 + 88.27261765894279 + 5.645767845941023e-011 + 0.0006214879519801648 + relativeToGround + + #msn_sunny_copy70 + + 11.79017178556217,45.36037512888652,0 + +
+ + Sundial, Gasworks Park, Seattle, USA +

]]>
+ + -122.3362979085422 + 47.64532276753428 + 0 + 144.9142629968483 + 1.398328526418574e-010 + -0.002257590778485548 + relativeToGround + + #msn_sunny_copy70 + + -122.3363617333393,47.64542146106401,0 + +
+ + Sundial, Biarritz, France +

]]>
+ + -1.554172472866423 + 43.49326953476108 + 0 + 25.2767811064234 + 1.050991544755056e-009 + 0.004580769709390601 + relativeToGround + + #msn_sunny_copy70 + + -1.554180928484328,43.49329288628002,0 + +
+ + Sundial, Biarritz, France +

]]>
+ + -1.566562523529829 + 43.48379630381277 + 0 + 21.65084680450158 + 3.134801309486055e-010 + -0.003938809314781338 + relativeToGround + + #msn_sunny_copy70 + + -1.566563732160418,43.48381046528652,0 + +
+ + Sundial, Gardens of Easton Lodge, UK +

+ +

+ +http://www.sundials.co.uk/newdials.htm]]>
+ + 0.3149818536825662 + 51.89078052542742 + 0 + 55.96449646091584 + 4.648925194094304e-010 + 0.00100690081475908 + relativeToGround + + #msn_sunny_copy70 + + 0.3149732952370528,51.89085713320831,0 + +
+ + Sundial, Drake University, Des Moines, Iowa, USA +

]]>
+ + -93.65212284615454 + 41.60195541103381 + 0 + 78.72982107342352 + 1.765081595632672e-010 + 0.001286572559733559 + relativeToGround + + #msn_sunny_copy70 + + -93.65218647671061,41.60204463077999,0 + +
+ + Sundial, River State Park, Indianapolis, USA +

]]>
+ + -86.17152524772823 + 39.76773209074677 + 0 + 54.39844455317644 + 0 + 0.002275213067656348 + relativeToGround + + #msn_sunny_copy70 + + -86.17158357583082,39.76772499391254,0 + +
+ + Sundial, Lawrence Hall of Science, Berkeley, CA, USA +

]]>
+ + -122.2467934369336 + 37.87843955912407 + 0 + 75.73772829567238 + 0 + -0.0005027058674008065 + relativeToGround + + #msn_sunny_copy70 + + -122.2468395686324,37.87850249930867,0 + +
+ + Sundial, Riverwalk, Augusta, Georgia, USA +

]]>
+ + -81.96495913544699 + 33.47855115889769 + 0 + 24.10683016246917 + 0 + -0.004039593559848222 + relativeToGround + + #msn_sunny_copy70 + + -81.96497422223469,33.47856125757188,0 + +
+ + Sundial, Reggio nell'Emilia, Italy +

+

Image source:http://perso.orange.fr

]]>
+ + 10.64303919389926 + 44.71779646338597 + 0 + 189.7095730357674 + 0 + 0.0003188808607201916 + relativeToGround + + #msn_sunny_copy70 + + 10.64294491831197,44.71794161105381,0 + +
+ + Sundial, Rennes, France +

+ +

Image source:http://perso.orange.fr

]]>
+ + -1.701676278457902 + 48.13126501865703 + 0 + 61.61200771227915 + 0 + -7.297875936612596e-006 + relativeToGround + + #msn_sunny_copy70 + + -1.701699217745187,48.13129604209563,0 + +
+ + Sundial, Schneverdingen, Germany +

]]>
+ + 9.790867938787324 + 53.12943797238091 + 0 + 106.7617213575405 + 4.722006958129564e-010 + 0.00116683463628678 + relativeToGround + + #msn_sunny_copy70 + + 9.790707601654233,53.12958381093443,0 + +
+ + Sundial Obelisk, Charleston, South Carolina +

+ +

Image credit:www.groundspeak.com

]]>
+ + -79.93166502066842 + 32.76970334074068 + 0 + 75.62015855417492 + 4.052617221081382e-011 + 1.8933011389851e-005 + relativeToGround + + #msn_sunny_copy70 + + -79.93172500691688,32.76973746165206,0 + +
+ + Sundial, Morehead Planetarium, Chapel Hill, North Carolina +

+ +

Image credit:www.groundspeak.com

]]>
+ + -79.050938325099 + 35.91448691988588 + 0 + 64.77863580575449 + 0 + 1.662447442472179e-005 + relativeToGround + + #msn_sunny_copy70 + + -79.05097493816135,35.91457037097104,0 + +
+ + Sundial, Berkeley, +California +

+ +

Image credit:www.groundspeak.com

]]>
+ + -122.3174670551103 + 37.86291969151575 + 0 + 46.45520126730318 + 1.288314315217904e-009 + 2.022127862982459e-005 + relativeToGround + + #msn_sunny_copy70 + + -122.317517078111,37.86295037394118,0 + +
+ + Forest Lawn Cemetery Sundial, Buffalo, NY +

+ +

Image credit:www.groundspeak.com

]]>
+ + -78.85654999999994 + 42.92531666666667 + 0 + 76.25216432595194 + 0 + 1.305178628551349e-014 + relativeToGround + + #msn_sunny_copy70 + + -78.85660856395873,42.92539096384056,0 + +
+ + Ruston Way Sundial ,Tacoma, Washington +

+ +

Image credit:www.groundspeak.com

]]>
+ + -122.4622812282256 + 47.27566486193976 + 0 + 62.22457114364932 + 0 + 0.0001239840172672445 + relativeToGround + + #msn_sunny_copy70 + + -122.4623519976878,47.27567991760397,0 + +
+ + Sundial ,Science Central, Fort Wayne, Indiana +

+ +

Image credit:www.groundspeak.com

]]>
+ + -85.1392719076301 + 41.09135262868964 + 0 + 39.45104173043256 + 0 + -1.439899387224993e-005 + relativeToGround + + #msn_sunny_copy70 + + -85.13931366905783,41.09136114213859,0 + +
+ + Berkswich Millennium Sundial, Broc Hill, Staffordshire, UK +

+ +

Image credit:www.groundspeak.com

]]>
+ + -2.038136129920761 + 52.77711389120437 + 0 + 29.50351061813827 + 0 + 3.758749562499077e-005 + relativeToGround + + #msn_sunny_copy70 + + -2.038162283146562,52.77714418176907,0 + +
+ + Sundial, Tazacorte Beach ,La Palma island +

+ +

Image credit:www.groundspeak.com

]]>
+ + -17.9461489138289 + 28.65121498294262 + 0 + 64.36805201552387 + 0 + 4.047704004228316e-005 + relativeToGround + + #msn_sunny_copy70 + + -17.94620263531645,28.65124065936443,0 + +
+ + Sundial, Rochester, NY +

+ +

Image credit:www.groundspeak.com

]]>
+ + -77.66915367541856 + 43.0844306339545 + 0 + 61.69080872372956 + 0 + 9.994948692290747e-005 + relativeToGround + + #msn_sunny_copy70 + + -77.66917908415978,43.08440844604031,0 + +
+ + Sundial, Center of the World, Felicity, CA +

+ +

+The 15 foot Sundial at Felicity is a three-dimensional bronze of Michelangelo's Arm of God painted on the Sistine Chapel ceiling. The arm was sculpted and cast in bronze in New England. The rock is local but the installation required the assistance of a mining engineer and a special drill. The bronze Roman numerals give the time. A sundial is precisely accurate once a year and this was set at noon on Christmas Day. The arm points to the Hill of Prayer, site of the Church on the Hill at Felicity. +At the entrance to The Center of the World campus is a 25 ft. high section of the original stairway of the Eiffel Tower. In 1983, the Government of France removed approximately 500 ft. of the original stairway. Built with the technology of the 1860's, the weight of approximately 54,000 lbs. was causing sway at the top of the then 94 year old tower. The 6,600 lb. section serves no practical purpose, but is part of the spirit of Felicity. +The idea of making Felicity the Center of the World came to Jacques-André when he'd been mayor only a few months. Somehow he convinced Imperial County, CA, to recognize his claim. Soon he had convinced the Institut Geographique National of France, General Dynamics Corporation, and The People's Republic of China to recognize it as well. "I knew I had to build something, but I didn't know what. My wife said, 'It's a desert; why not a pyramid?' So Jacques-André had built a 21-foot-tall pink marble pyramid, its interior lined with mirrors, a plaque embedded in the floor, marking the exact spot. For a dollar, tourists can now stand on the official Center Of The World and take a picture themselves at the official "Center Of The World". +The Felicity Post Office was dedicated on 5 December 1987 at a time when thousands of small post offices were being eliminated as an economy measure. The town, whose population numbered two, saw over 2,300 letters mailed that day. The dedication ceremony was highlighted by a speech in Chinese by Consul Zhou of the People’s Republic of China who traveled 600 miles for the occasion. It is operated by the town at a cost to the Federal Government of one dollar per year. Twenty uncashed one dollar checks are on file.

+ +

Image and info credit:www.groundspeak.com

]]>
+ + -114.7654750861393 + 32.74988921016088 + 0 + 72.95555856498569 + 0 + 3.146266385893141e-005 + relativeToGround + + #msn_sunny_copy70 + + -114.7655284077745,32.74992976207647,0 + +
+ + University of São Paulo Sundial, Sao Paulo, Brazi +

+ +

Image credit:www.groundspeak.com

]]>
+ + -46.7204986760494 + -23.56120553413547 + 0 + 122.7188487961642 + 0 + 2.610051397350573e-005 + relativeToGround + + #msn_sunny_copy70 + + -46.7205459522717,-23.56115337159118,0 + +
+ + Slate bowl Sundial, Holker, UK +

+ +

Image credit:www.groundspeak.com

]]>
+ + -2.987191130383048 + 54.188865359179 + 0 + 98.16442365143851 + 2.595660029656298e-010 + 0.000142350860720713 + relativeToGround + + #msn_sunny_copy70 + + -2.987342530279506,54.18895843924356,0 + +
+ + Sundial, Jardin des Doms, Avignon +

+ +

Image credit:www.groundspeak.com

]]>
+ + 4.807697613943427 + 43.95301885165002 + 0 + 32.75914708134153 + 1.205283678723288e-009 + 2.147953504845766e-005 + relativeToGround + + #msn_sunny_copy70 + + 4.807672022945837,43.95303620373285,0 + +
+ + Rillito Riverpark Sundial, Tucson, AZ +

+ +

Image credit:www.groundspeak.com

]]>
+ + -111.0075277787534 + 32.30113621710221 + 0 + 94.15682746212195 + 0 + 2.968664599173171e-005 + relativeToGround + + #msn_sunny_copy70 + + -111.0075933392788,32.30113929573149,0 + +
+ + Helium Monument Sundial, Amarillo, TX +

+ +

Image credit:www.groundspeak.com

]]>
+ + -101.9132978901728 + 35.19956726276647 + 0 + 60.53404995378031 + 5.162016480480558e-011 + 3.00374135059527e-005 + relativeToGround + + #msn_sunny_copy70 + + -101.9133182362553,35.19966266329223,0 + +
+ + Sundial, Hershey, Pennsylvania +

+ +

Image credit:www.groundspeak.com

]]>
+ + -76.62980321024054 + 40.27170452963257 + 0 + 72.41553799015709 + 2.798231534250927e-010 + 6.256605840320539e-005 + relativeToGround + + #msn_sunny_copy70 + + -76.6298565862236,40.2718754812139,0 + +
+ + King Neptune Sundial, Hilton Head Island, South Carolina +

+ +

Image credit:www.groundspeak.com

]]>
+ + -80.72801698168738 + 32.18076491029077 + 0 + 86.36647046692004 + 2.346233297172379e-010 + 8.72178085589082e-006 + relativeToGround + + #msn_sunny_copy70 + + -80.72806102317654,32.18084680335104,0 + +
+ + Jane Larue Memorial Sundial - Ann Arbor, Michigan +

+ +

Image credit:www.groundspeak.com

]]>
+ + -83.66222470201295 + 42.30114702626376 + 0 + 26.29476256996721 + 0 + 0.0001291940559531826 + relativeToGround + + #msn_sunny_copy70 + + -83.66224748552365,42.30117333470928,0 + +
+
+ + Schoolyard Sundials + + Sundial, Julius-Brecht-Allee, Bremen + + 8.8674012861685 + 53.07651505713779 + 0 + 20.6687721420542 + 9.001122528249614e-011 + -0.00437506724289509 + relativeToGround + + #msn_sunny_copy69 + + 8.867391721405184,53.07654483342672,0 + + + + Sundial, Drebberstraße, Bremen +

]]>
+ + 8.898052233187912 + 53.0400952944841 + 0 + 14.44345748598086 + 0 + -0.001637659480767247 + relativeToGround + + #msn_sunny_copy69 + + 8.898047664850367,53.04011230005033,0 + +
+ + Sundial, Butjadingersrasse, Bremen +

]]>
+ + 8.759747956980032 + 53.08143879125452 + 0 + 37.83897098076405 + 0 + -0.002629545926081431 + relativeToGround + + #msn_sunny_copy69 + + 8.759712018733111,53.08151322706201,0 + +
+
+ + In Progress + + Sundial, Greenwich, USA +

]]>
+ + -73.61498302559443 + 41.02226092221508 + 0 + 149.2259168633856 + 1.357926888487057e-010 + -0.001539166856947675 + relativeToGround + + #msn_sunny_copy69 + + -73.61504279924034,41.022311140554,0 + +
+ + Sonnenuhr? + + 11.05508326700377 + 49.45922489288633 + 0 + 50.88443884213967 + 8.335955203191607e-009 + 0.0196675278275586 + relativeToGround + + #msn_sunny_copy69 + + 11.0551380716084,49.45927364486676,0 + + + + Sundial, Edgewood Park, New Haven, USA +

+ +

]]> + + -72.95215163561284 + 41.31399188322968 + 0 + 154.2904142456261 + 0 + 0.002187256502984029 + relativeToGround + + #msn_sunny_copy69 + + -72.95224192688632,41.31401837758977,0 + + + + Sundial, Pearl City, Oahu, USA +

]]>
+ + -157.975920511215 + 21.39370171784438 + 0 + 93.82906502148613 + 0 + -0.001426474135915891 + relativeToGround + + #msn_sunny_copy69 + + -157.9759385077734,21.39376422631041,0 + +
+ + Sundial + http://maget.maget.free.fr/SiteMont/index.html + + -1.511135684750573 + 48.63640399624012 + 0 + 623.6899626138724 + 0 + -1.851737885201182e-005 + relativeToGround + + #msn_sunny_copy69 + + -1.511518347366319,48.63786003229999,0 + + + + Mont-Saint-Michel + http://maget.maget.free.fr/SiteMont/MSpage4.htm + + http://maget.maget.free.fr/SiteMont/images/le_Mont_Solaire-Land%20Art.jpg + 0.75 + + + 48.63770978435333 + 48.6344604605756 + -1.5070705975067 + -1.514375149320612 + -11.46597601725745 + + + + Sundial Park, Ludiver park + http://perso.orange.fr/cadrans.solaires/cadrans/cadran-parc-ludiver.html + + -1.727863357864637 + 49.63119498354116 + 0 + 473.1060190443535 + 3.883358970183465e-011 + 0.0005701632901766135 + relativeToGround + + #msn_sunny_copy69 + + -1.728331456927833,49.63191584214422,0 + + + + Sonnenuhr? + + 7.68545763101957 + 51.53642499090419 + 0 + 23.70363190324798 + 4.475657800962137e-010 + -0.1674345977313924 + relativeToGround + + #msn_sunny_copy69 + + 7.685416995069303,51.53648149450991,0 + + + + Sundial at Tower of London + ]]> + + -0.07656780337525181 + 51.50981727675416 + 0 + 61.73369699893549 + 9.251491983355112e-010 + 0.009688876514144714 + + #msn_sunny_copy69 + + -0.07656780337525181,51.50981727675417,0 + + + + War Veterans' Memorial Park Sundial, Florida, United States + + -82.77333790901622 + 27.8036881517592 + 0 + 96.43655563554265 + 4.405141995417006e-010 + 9.892674215924156e-005 + relativeToGround + + #msn_sunny_copy69 + + -82.77341348054247,27.80374932310448,0 + + +
+
+
+
+ --- /dev/null +++ b/openlayers/examples/late-render.html @@ -1,1 +1,43 @@ + + + OpenLayers Late Rendering Example + + + + + + +

Late Rendering

+ +
+ +

+ Demonstrates how a map can be rendered to an empty container after + construction by calling the render method. +

+
+
+ In cases where you need to create a map first and render it to some + container later, call the map constructor without a "div" argument. + In this case, you can provide the options object as the first argument. + To render your map to some container after construction, call the map's + render method with the container id. +
+ + + --- /dev/null +++ b/openlayers/examples/layer-opacity.html @@ -1,1 +1,90 @@ + + + OpenLayers Layer Opacity Example + + + + + + + +

Layer Opacity Example

+
+ +

+ Demonstrate a change in the opacity for an overlay layer. +

+ +
+ +
+

+ Note that if you also have the setOpacity method defined on the Layer + class, you can tweak the layer opacity after it has been added to the map. +

+

Opacity: + << + + >> +

+

IE users: Wait until the shade layer has finished loading to try this.

+
+ + + --- /dev/null +++ b/openlayers/examples/layerLoadMonitoring.html @@ -1,1 +1,130 @@ + + + OpenLayers Layer Load Monitoring Example + + + + + + + + +

Layer Load Monitoring Example

+ +
+ +

+ Demonstrate a method for monitoring tile loading performance. +

+ +
+ +
+
+ Events Log: +
+ + +
+ +
+
+ + + --- /dev/null +++ b/openlayers/examples/layerswitcher.html @@ -1,1 +1,56 @@ + + + OpenLayers Layer Switcher Example + + + + + + +

Layer Switcher Example

+ +
+
+ +

+ Demonstrates the use of the LayerSwitcher outside of the OpenLayers window. +

+ +
+
+
+

This demonstrates use of the LayerSwitcher outside the map div. It also shows use + of the displayInLayerSwitcher option on the Layer to cause it to not display in the + LayerSwitcher.

+
+ + + --- /dev/null +++ b/openlayers/examples/lite.html @@ -1,1 +1,33 @@ + + + OpenLayers Basic Single WMS Example + + + + + + +

Basic Single WMS Example

+
+ +
Show a Simple Map
+ +
+ +
+ This example shows a very simple layout with minimal controls. This example uses a single WMS base layer. +
+ + + --- /dev/null +++ b/openlayers/examples/mapguide.html @@ -1,1 +1,151 @@ + + + OpenLayers MapGuide Layer Example + + + + + + + +

MapGuide Layer Example

+ +
+
+ +

+ Demonstrates how to create MapGuide tiled and untiled layers. +

+ +

If prompted for a password, username is Anonymous and an empty password

+ +
+
+
+
+ + + --- /dev/null +++ b/openlayers/examples/mapserver.html @@ -1,1 +1,36 @@ + + + MapServer Layer + + + + + + +
MapServer Layer
+
+
Shows MapServer Layer
+
+
+ This is an example of using a MapServer Layer with a gutter parameter. The gutter parameter is used to try to limit the edge effects between tiles. +
+ + + --- /dev/null +++ b/openlayers/examples/mapserver_untiled.html @@ -1,1 +1,36 @@ + + + MapServer Single Tile Mode + + + + + + + +
MapServer Single Tile Mode
+
+
Shows single tile MapServer Layer
+
+
+ This shows an example of using a MapServer Layer in single tile mode. Single tile mode can be useful when pulling data from dynamic sources. +
+ + --- /dev/null +++ b/openlayers/examples/marker-shadow.html @@ -1,1 +1,143 @@ + + + OpenLayers: Vector Graphics with Shadows + + + + + + + +

Marker Shadows using Background Graphics/Z-Indexes

+
+
+ +

+ This example shows off marker shadows using background graphics and z-indexes. Move the features around to show the shadows' interaction. +

+ +
+ + + + + + + + + +
+
+
+
+ The features in this map were generated at random. Each of these features have a backgroundGraphic property set in the style map to add a shadow image. Note that the background graphics are not duplicated features with a different style. +

+ The shadows were set to have a different z-index than the markers themselves, using the backgroundGraphicZIndex property. This makes sure all shadows stay behind the markers, keeping a clean look. The shadows were also placed nicely relative to the external graphic using the backgroundXOffset and backgroundYOffset property. +

+ Y-ordering on the layer is enabled. See the ordering example. +
+
+ +
+ + + + + --- /dev/null +++ b/openlayers/examples/markerResize.html @@ -1,1 +1,52 @@ + + + Resize a Marker + + + + + + +
Resize a Marker
+
+
Dynamically resize a marker
+
+
+ This example shows how to create a OpenLayers.Layer.Markers layer, add an icon, put it into a marker, and add the marker to the layer. Once the marker has been added it is possible to use setSize() on the icon in order to resize the marker. +
+
click to resize marker
+ + + --- /dev/null +++ b/openlayers/examples/markers.html @@ -1,1 +1,54 @@ + + + Markers Layer Example + + + + + + +
Markers Layer Example
+
+
Show markers layer with different markers
+
+
+ This is an example of an OpenLayers.Layers.Markers layer that shows some examples of adding markers. Also demonstrated is registering a mousedown effect on a marker. +
+ + + --- /dev/null +++ b/openlayers/examples/markersTextLayer.html @@ -1,1 +1,35 @@ + + + Using a Layer.Text to display markers + + + + + + +

Using a Layer.Text to display markers

+

+ The Layer.Text class reads a Tab seperated values file and displays it as markers on + the map. +

+
+ + + --- /dev/null +++ b/openlayers/examples/measure.html @@ -1,1 +1,170 @@ + + + + + + + + + +

OpenLayers Measure Example

+

+ Demonstrates the measure control to measure distances and areas. +

+
+
+
+
+
    +
  • + + +
  • +
  • + + +
  • +
  • + + +
  • +
  • + + +
  • +
+

Note that the geometries drawn are planar geometries and the + metrics returned by the measure control are planar measures by + default. If your map is in a geographic projection or you have the + appropriate projection definitions to transform your geometries into + geographic coordinates, you can set the "geodesic" property of the control + to true to calculate geodesic measures instead of planar measures.

+
+ + + --- /dev/null +++ b/openlayers/examples/mm.html @@ -1,1 +1,61 @@ + + + MultiMap + + + + + + + + + + + +

MultiMap Example

+

+ An example of using the Layer.MultiMap class. +

+
+
click to add the marker to the map
+
click to remove the marker from the map
+ + + --- /dev/null +++ b/openlayers/examples/modify-feature.html @@ -1,1 +1,173 @@ + + + Modify Feature + + + + + + + + +

OpenLayers Modify Feature Example

+
A demonstration of the ModifyFeature control for editing vector features.
+
+
+
    +
  • + + +
  • +
  • + + +
  • +
  • + + +
  • +
  • + + +
  • +
  • + + + + +
      +
    • + + +
    • +
    +
  • +
  • + + +
      +
    • + + +
    • +
    • + + + ( + ) +
    • +
    • + + +
    • +
    +
  • +
+
+ + + --- /dev/null +++ b/openlayers/examples/mouse-position.html @@ -1,1 +1,41 @@ + + + + MousePosition Control + + + + + + +

MousePosition Control

+

+ Use the MousePosition Control to display the coordinates of the cursor + inside or outside the map div. +

+
+
+

Moving your mouse to the upper left corner of this map should return 'x=0,y=0' -- in the past, it didn't in IE. If it returns 2,2, consider it a bug, and report it.

+ + + --- /dev/null +++ b/openlayers/examples/multimap-mercator.html @@ -1,1 +1,66 @@ + + + MultiMap SphericalMercator + + + + + + + + + + +

MultiMap Mercator Example

+

+ This sphericalMercator example using multimap demonstrates that the + multimap layer does not fully support the sphericalMercator projection at + this time. +

+
+ + + --- /dev/null +++ b/openlayers/examples/multiserver.html @@ -1,1 +1,42 @@ + + + OpenLayers: Tiles from Multiple Servers + + + + + + +

Multiple Server URLS

+ +
+
+

+ Load your tiles faster by pointing to the same server, but with different urls +

+ +
+
+ Browsers typically limit the number of concurrent requests to the same server, based on hostname. In order to ake tiles load more quickly, it often makes sense to distribute requests over multiple hostnames to achieve more concurrency. Typically, browsers perform best with 3 different hostnames -- your performance may vary. (For example, if your server can't handle more than 2 requests simultaneously, then additional hostnames will not help you.) +
+ + + + --- /dev/null +++ b/openlayers/examples/mvs.html @@ -1,1 +1,126 @@ + + + + + + + + + + + + +
+ + + --- /dev/null +++ b/openlayers/examples/navigation-control.html @@ -1,1 +1,35 @@ + + + OpenLayers Navigation Control + + + + + + +

Navigation Control

+
+ +
Demonstrate Navigation Control features
+ +
+ Turn on Wheel Zoom | Turn off Wheel Zoom +
+ This example demonstrates a couple features of the Navigation control. The Navigation control controls most map dragging, movement, zooming, etc. In this case, we have a demonstration of how to create a navigation control with no zoom wheel action, which can then be enabled or disabled by the user. +
+ + + --- /dev/null +++ b/openlayers/examples/navigation-history.html @@ -1,1 +1,58 @@ + + + OpenLayers Navigation History Example + + + + + + +

Map Navigation History Example

+ +
+
+ +

+ A control for zooming to previous and next map extents. +

+ +
+ Map navigation history controls
+
+ + + --- /dev/null +++ b/openlayers/examples/navtoolbar-alwaysZoom.html @@ -1,1 +1,79 @@ + + + A navToolbar with an alwaysZoom ZoomBox + + + + + + + + +

A navToolbar with an alwaysZoom ZoomBox

+

+ Demo of a custom NavToolbar which uses a zoomBox tool that always zoom in even when the zoom box is too big. +

+
+ + + --- /dev/null +++ b/openlayers/examples/navtoolbar-outsidemap.html @@ -1,1 +1,50 @@ + + + OpenLayers: Custom Navigation Toolbar + + + + + + + +

Navigation Toolbar: Outside the Map

+
+
+
+

To place the Naviation Toolbar outside the map:

+
    +
  • Load the default stylesheet into the page.
  • +
  • Override the location of the Navigation toolbar in your CSS by setting #yourElementId div to have a top of 0px
  • +
  • Specify the HTML element as a 'div' option in your NavToolbar constructor
  • +
  • Add the olControlNavToolbar class to your div.
  • +
+
+ + + + --- /dev/null +++ b/openlayers/examples/navtoolbar.html @@ -1,1 +1,40 @@ + + + + + NavToolbar Demo + + + + + +

NavToolbar Demo

+

+ Demo the NavToolbar, a subclass of Control.Panel which shows icons for + navigation. +

+
+ + + --- /dev/null +++ b/openlayers/examples/notile.html @@ -1,1 +1,40 @@ + + + OpenLayers: Single Tile + + + + + + +

Untiled Example

+

+ Create an untiled WMS layer using the singleTile: true, option or the deprecated + WMS.Untiled layer. +

+
+

The first layer is an old OpenLayers.Layer.WMS.Untiled layer, using + a default ratio value of 1.5. +

The second layer is an OpenLayers.Layer.WMS layer with singleTile set + to true, and with a ratio of 1. + + + --- /dev/null +++ b/openlayers/examples/openmnnd.html @@ -1,1 +1,156 @@ + + + OpenLayers: OpenMNND + + + + + + +

OpenMNND WFS

+

This example shows the use of a WFS service rendered using the OpenLayers vector library.

+
+
+

+ This is an example that shows rendering a WFS service using OpenLayer vectors in the browser. The OpenLayers code will download the GML + from the WFS service for each layer, parse it and create features using the OL vector library to draw the features on the map. For + more information on the vector library, please visit vector support wiki. + In this example there are 4 layers shown on the map. The base layer and parcel layer are created from a WMS service using the OpenLayers.Layer.WMS object. + The streams, roads, and plat layers are drawn from a WFS service using the OpenLayers.Layer.WFS object. +

+ +

+ Rendering WFS layers into vectors is possible, but you need to be cautions when showing the features on the map. Testing has shown that when + you renderer more than 200 vectors in the browser the performance decreases dramatically. Also features that have a lot of vertices + can cause performance issues. In this example the parcel layer is rendered as a WMS layer because at the time of developing this example + there where a handful of features that had too many vertices to render without killing the browser resources. + + There are a number of properties that can be set for each WFS layer, such color and line weight using style properties such as strokeColor and strokeWidth. + You can also get feature attributes from the WFS services using the extractAttribute property. View the source to see the example code. +

+
+
+
    +
  • Streams: Feature Count 0
  • +
  • Plat: Feature Count 0
  • +
  • Roads: Feature Count 0
  • +
+
+
+
    +
  • + + +
  • +
  • + + +
  • +
+
+ + + --- /dev/null +++ b/openlayers/examples/ordering.html @@ -1,1 +1,212 @@ - + + + OpenLayers: Z-Ordering and Y-Ordering of Vector Features + + + + + + + +

Z-Index/Y-Order Example

+ +
+
+ +

+ This example shows the use of z-indexing and y-ordering of external graphics. Zoom in and out to see this behavior. +

+ +

Z-Index (with Y-Ordering enabled)

+ + + + + +
+
+
+
+ In this map, the gold features all have the same z-index, and the red features have alternating z-indeces. The gold features' z-index is greater than the red features' z-indeces, which is why gold features look to be drawn on top of the red features. Since each gold feature has the same z-index, gold features succomb to y-ordering: this is where features that seem closest to the viewer (lower lattitude) show up above those that seem farther away (higher lattitude). +

+ You can enable y-ordering by passing the parameter yOrdering: true in the vector layer's options hash. For all configurations (with yOrdering or zIndexing set to true), if features have the same z-index -- and if y-ordering is enabled: the same latitude -- those features will succomb to drawing order, where the last feature to be drawn will appear above the rest. +
+
+
+

Z-Index and Drawing Order (Z-Indexes set, and Y-Ordering disabled)

+ + + + + +
+
+
+
+ In this map, zIndexing is set to true. All features are given the same z-index (0), except for the first feature which has a z-index of 1. The layer's yOrdering parameter is set to the default (false). This configuration makes features succomb to z-index and drawing order (for the features with the same z-index), instead of y-order. +

+ The features in this map were drawn from left to right and bottom to top, diagonally, to show that y-ordering is not enabled. Only the lower-left corner feature is drawn on top of the others, because it has a higher z-index (1 instead of 0). +
+
+ + + + + --- /dev/null +++ b/openlayers/examples/osm-layer.html @@ -1,1 +1,120 @@ + + + OpenLayers: OSM Layer + + + + + + + +

Advanced OSM Layer

+
+ + + + + --- /dev/null +++ b/openlayers/examples/osm/sutton_coldfield.osm @@ -1,1 +1,663 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + --- /dev/null +++ b/openlayers/examples/outOfRangeMarkers.html @@ -1,1 +1,62 @@ + + + Using maxResolution to control overlays + + + + + + + + + + + + + + +

Using maxResolution to control overlays

+

+ See how to control the maximum resolution for a markers layer, + causing it to not be displayed beyond a certain point. +

+
+ + + + --- /dev/null +++ b/openlayers/examples/outOfRangeMarkers.txt @@ -1,1 +1,4 @@ +point title description icon +-4.14,37.90 Kilimanjaro Beskrivning http://www.villageografica.com/Africa-Webmap/img/marker-blue.png +-3.24,34.35 Shinyanga Beskrivning http://www.villageografica.com/Africa-Webmap/img/marker-blue.png --- /dev/null +++ b/openlayers/examples/overviewmap.html @@ -1,1 +1,106 @@ + + + Overview Map Example + + + + + + +

Overview Map

+
+
+

+ Enable a small Overview Map that moves/interacts with your main map. +

+
+

The above map has an overview map control that is created with + the default options. Much like a regular map, the map contained by + the overview map control defaults to a geographic projection.

+
+

The second map has an overview map control that is created with + non-default options. In this case, the mapOptions property of the + control has been set to use non-default projection related properties. + In addition, any other properties of the overview map control can be + set in this way.

+ + + + --- /dev/null +++ b/openlayers/examples/pan-zoom-panels.html @@ -1,1 +1,57 @@ + + + Pan and Zoom Panels + + + + + + + + + + +

Pan and Zoom Panels

+
+

Customizable pan and zoom panels

+

+
+

+ The pan and zoom panels allow you to use CSS styling to change the + look and feel of the panels, including changing their position + and their icons without needing to change any code. +

+ + + --- /dev/null +++ b/openlayers/examples/panel.html @@ -1,1 +1,97 @@ + + + OpenLayers: Control Panel + + + + + + + + +

Custom Control.Panel

+

+ Create a custom control.panel, styled entirely with + CSS, and add your own controls to it. +

+
+
+ + + --- /dev/null +++ b/openlayers/examples/point-track-markers.html @@ -1,1 +1,65 @@ + + + OpenLayers: Point Track Markers + + + + + + +

GeoRSS PointTrack in OpenLayers

+

This demo uses OpenLayers.Layer.GeoRSS and OpenLayers.Layer.PointTrack. The track is created by connecting the points of the GeoRSS feed.

+
+ GeoRSS URL: +
+

The above input box allows the input of a URL to a GeoRSS feed. This feed can be local to the HTML page -- for example, entering 'xml/track1.xml' will work by default.

+

The example shows a track, displayed as a line connecting the points of the feed. It also shows markers at positions that have a title tag in the rss item. If clicked, a popup will show title and description.

+
+ + + --- /dev/null +++ b/openlayers/examples/popupMatrix.html @@ -1,1 +1,905 @@ - + + + OpenLayers: Popup Mayhem + + + + + + + + + +

Popup Matrix

+ +
+
+

+ All kinds of different popup configurations. +

+ +
+ + + + + + +

All of the images in this file a pre-cached, meaning they are + loaded immediately when you load the page (they are just placed + far offscreen, that's why you don't see them). +

+
+

The only image that is *not* preloaded is small.jpg, the brazilian + flag. We do this in order to test out to make sure that our auto-sizing + code does in fact activate itself as the images load. To verify + this, clear your cache and reload this example page. Click on + any of the markers in the 'AutoSize' row. If the popup autosizes + to correctly contain the entire flag: golden. If the popup is + tiny and you can only see a corner of it, then this code is broken. +

+ +
+ + + + + + --- /dev/null +++ b/openlayers/examples/popups.html @@ -1,1 +1,130 @@ + + + + + + + + + +

Popup Mayhem

+ +
+
+

+ All kinds of ways to create and interact with Popups. +

+ +
+

If you open an anchoredbubble and switch to google, it shouldn't crash. If it does, don't release OpenLayers.

+
click to add Popup to map
+
click to add a Marker with an AnchoredBubble popup
+
click to modify popup's attributes
+
click to remove the popup from map
+
click to remove the markers layer
+
marker.onscreen()?
+
click to destroy the popup from map
+
+ Detailed description of this example needs to be written. +
+ + + --- /dev/null +++ b/openlayers/examples/projected-map.html @@ -1,1 +1,68 @@ + + + OpenLayers: Non-Geographic Projection + + + + + + + + +

Layer Projections

+ +
+
+

+ Use different (not default) projections with your map +

+
+

When using alternative projections, you still use OpenLayers.LonLat objects, even though + the properties are actually X/Y values at that point.

+ + + --- /dev/null +++ b/openlayers/examples/protocol-gears.html @@ -1,1 +1,258 @@ - + + + + + + + + + + +

Gears Protocol Example

+ +
+
+

+ Shows the usage of the Gears protocol. +

+ +
+
+
+ +
+ Sync +

The Sync link destroys the features currently in the layer, reads + features from the Gears database, and adds them to the layer. + Uncommitted features will be lost.

+ + Commit +

The Commit link commits to the Gears database the features that are + marked as INSERT, UPDATE or DELETE.

+ + Delete +

The Delete link marks the selected feature as DELETE. To select a feature + click choose the navigation control in the editing toolbar.

+
+ +
+

Status:

+

Result:

+
+ +
+

This example demonstrates the usage of OpenLayers Gears protocol to + read/create/update/delete features from/to the Gears database. + Gears must obviously be installed + in your browser for this example to work.

+
+ + + --- /dev/null +++ b/openlayers/examples/proxy.cgi @@ -1,1 +1,79 @@ +#!/usr/bin/env python + +"""This is a blind proxy that we use to get around browser +restrictions that prevent the Javascript from loading pages not on the +same server as the Javascript. This has several problems: it's less +efficient, it might break some sites, and it's a security risk because +people can use this proxy to browse the web and possibly do bad stuff +with it. It only loads pages via http and https, but it can load any +content type. It supports GET and POST requests.""" + +import urllib2 +import cgi +import sys, os + +# Designed to prevent Open Proxy type stuff. + +allowedHosts = ['www.openlayers.org', 'openlayers.org', + 'labs.metacarta.com', 'world.freemap.in', + 'prototype.openmnnd.org', 'geo.openplans.org', + 'sigma.openplans.org', 'demo.opengeo.org', + 'www.openstreetmap.org', 'sample.avencia.com'] + +method = os.environ["REQUEST_METHOD"] + +if method == "POST": + qs = os.environ["QUERY_STRING"] + d = cgi.parse_qs(qs) + if d.has_key("url"): + url = d["url"][0] + else: + url = "http://www.openlayers.org" +else: + fs = cgi.FieldStorage() + url = fs.getvalue('url', "http://www.openlayers.org") + +try: + host = url.split("/")[2] + if allowedHosts and not host in allowedHosts: + print "Status: 502 Bad Gateway" + print "Content-Type: text/plain" + print + print "This proxy does not allow you to access that location (%s)." % (host,) + print + print os.environ + + elif url.startswith("http://") or url.startswith("https://"): + + if method == "POST": + length = int(os.environ["CONTENT_LENGTH"]) + headers = {"Content-Type": os.environ["CONTENT_TYPE"]} + body = sys.stdin.read(length) + r = urllib2.Request(url, body, headers) + y = urllib2.urlopen(r) + else: + y = urllib2.urlopen(url) + + # print content type header + i = y.info() + if i.has_key("Content-Type"): + print "Content-Type: %s" % (i["Content-Type"]) + else: + print "Content-Type: text/plain" + print + + print y.read() + + y.close() + else: + print "Content-Type: text/plain" + print + print "Illegal request." + +except Exception, E: + print "Status: 500 Unexpected Error" + print "Content-Type: text/plain" + print + print "Some unexpected error occurred. Error text was:", E + --- /dev/null +++ b/openlayers/examples/regular-polygons.html @@ -1,1 +1,167 @@ + + + OpenLayers Regular Polygon Example + + + + + + + +

OpenLayers Regular Polygon Example

+

+ Shows how to use the RegularPolygon handler to draw features with + different numbers of sides. +

+
+
+ +
    Map Controls +
  • + + +
  • +
  • + + +
  • +
+ + + + + + + + + + + + + + + + + + + + + + + +
Draw OptionValue
+ shape + + +
+ snap angle + + +
+ size + + +
+ irregular + + +
+
+

+ Regular polygons can be drawn by pointing a DrawFeature control to the + RegularPolygon handler class. The options above demonstrate how the + handler can be configured. Note if you are in angle snapping mode (if + the snap angle is non-null) and you hold down the Shift key, you + will toggle to non-snapping mode. +

+

+ The irregular option allows drawing of irregular polygons. With this option, the fixed radius option is ignored. + + + --- /dev/null +++ b/openlayers/examples/resize-features.html @@ -1,1 +1,95 @@ + + + OpenLayers Resize Features Example + + + + + + + +

Resize Features Programatically

+

+ Demonstration of how to use the geometry resize methods to + change feature sizes programatically. +

+
+

This example demonstrates how features can be resized. There is not yet + a control built that provides a tool for resizing, but the geometry.resize + method can be accessed to resize programmatically.

+

Make the features bigger + or smaller. + + + --- /dev/null +++ b/openlayers/examples/restricted-extent.html @@ -1,1 +1,71 @@ + + + OpenLayers Restricted Extent Example + + + + + + + +

OpenLayers Restricted Extent Example

+

+ Don't let users drag outside the map extent: instead, limit dragging such + that the extent of the layer is the maximum viewable area. +

+
+

+ Map navigation is limited by a combination of map and layer properties. + The base layer resolutions array controls the resolutions (or zoom + levels) available. The resolutions can be limited by setting a + maxResolution property or by explicitly specifying a resolutions + array. +

+

+ Navigation limited by the maxExtent property. A map cannot be panned + so that the center of the viewport is outside of the bounds specified + in maxExtent. If you wish to further restrict panning, use the + restrictedExtent property. With restrictedExtent set, the map cannot + be panned beyond the given bounds. If the maxResolution allows the + map to be zoomed to a resolution that displays an area bigger than + the restrictedExtent, the viewport will remain centered on the + restrictedExtent. +

+

+ + + + + + --- /dev/null +++ b/openlayers/examples/rotate-features.html @@ -1,1 +1,109 @@ + + + OpenLayers Rotate Features Example + + + + + + + +

Rotate vector features

+ +
+
+

+ Details on how to create and rotate vector features programmatically +

+ +
+
This example shows a few features rotating. There is not yet a control + built that provides a tool for rotating, but the geometry.rotate method + can be accessed to rotate programmatically.
+ + + --- /dev/null +++ b/openlayers/examples/select-feature-multilayer.html @@ -1,1 +1,117 @@ + + + SelectFeature Control on multiple vector layers + + + + + + + +

OpenLayers Select Feature on Multiple Layers Example

+

+ Select a feature on click with the Control.SelectFeature on multiple + vector layers. +

+
+
+ + + --- /dev/null +++ b/openlayers/examples/select-feature-openpopup.html @@ -1,1 +1,100 @@ + + + Open Popup on Layer.Vector + + + + + + + +

Open Popup on Layer.Vector

+

+ Using a Control.SelectFeature, open a popup on click. +

+
+
    +
  • + + +
  • +
  • + + +
  • +
  • + + +
  • +
+

It is possible to use the onSelect/onUnselect hooks on the SelectFeature + to do fun things -- like open a popup. +

+ + + --- /dev/null +++ b/openlayers/examples/select-feature.html @@ -1,1 +1,142 @@ + + + SelectFeature Control on Layer.Vector + + + + + + + +

OpenLayers Select Feature Example

+

+ Select a feature on hover or click with the Control.SelectFeature on a + vector layer. +

+
+
    +
  • + + +
  • +
  • + + +
  • +
  • + + +
  • +
  • + + +
  • +
  • + + +
  • +
  • + + +
      +
    • + + +
    • +
    • + + +
    • +
    +
  • +
+

Use the shift key to select multiple features. Use the ctrl key to + toggle selection on features one at a time. Note: the "clickout" option has no + effect when "hover" is selected.

+ + + --- /dev/null +++ b/openlayers/examples/setextent.html @@ -1,1 +1,35 @@ + + +Setting a visual Extent + + + + + +

Setting a Visual Extent

+

+ Use a boxes layer to visually display the area of interest indicated by a user. +

+

+ Because the ability to set the map to a given extent is limited by the + current resolutions available, zoomToExtent will not always set the map to + exactly the right extent. In order to visually annotate the actual extent, + this example, will use the Boxes layer to visually describe the desired + extent as well as setting the map extent. +

+
+ + + --- /dev/null +++ b/openlayers/examples/sld.html @@ -1,1 +1,117 @@ + + + + + + + + + +

Styled Layer Descriptor (SLD) Example

+

+ Parsing SLD and applying styles to a vector layer. +

+
+

This example uses a SLD + file to style the vector features. To construct layers that use styles + from SLD, create a StyleMap for the layer that uses one of the userStyles in the + namedLayers object of the return from format.read().

+

Select a new style for the WaterBodies layer below:

+
+ Default Styler (zoom in to see more features)
+ Styler Test PropertyIsEqualTo
+ Styler Test WATER_TYPE
+ Styler Test PropertyIsGreaterThanOrEqualTo
+ Styler Test PropertyIsLessThanOrEqualTo
+ Styler Test PropertyIsGreaterThan
+ Styler Test PropertyIsLessThan
+ Styler Test PropertyIsLike
+ Styler Test PropertyIsBetween
+ Styler Test FeatureId
+
+ + + --- /dev/null +++ b/openlayers/examples/snap-split.html @@ -1,1 +1,268 @@ - + + + Snapping & Splitting + + + + + + + + +

Snapping & Splitting Example

+
A demonstration snapping and splitting while editing vector features.
+
+
+ + + + + + + + + + + + + + +
targetnodevertexedge
roads
+
+ + + + + + + + + +
+
+ Clear all features. + + + --- /dev/null +++ b/openlayers/examples/snapping.html @@ -1,1 +1,315 @@ - + + + Snapping + + + + + + + + +

Snapping Example

+
A demonstration snapping while editing vector features.
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + +
targetsnodevertexedge
points
lines
polygons
+

Though all snapping types are shown here for all target layers, not all are sensible. + Points don't have edges, for example.

+ + + --- /dev/null +++ b/openlayers/examples/spherical-mercator.html @@ -1,1 +1,143 @@ + + + OpenLayers: Spherical Mercator + + + + + + + + + + + +

OpenLayers Spherical Mercator Example

+ +
+
+

+ Shows the use of the Spherical Mercator Layers, for overlaying + Google, Yahoo, Microsoft, and other layers with WMS and TMS tiles. +

+
+ +
+
+ + + + + + --- /dev/null +++ b/openlayers/examples/split-feature.html @@ -1,1 +1,104 @@ + + + Split Feature Example + + + + + + + +

OpenLayers Split Feature Example

+

+ Demonstrates splitting of line features. +

+
+
+ The split control can be configured to listen for edits on any vector layer + or it can allow for creation of temporary sketch features. Modified or + newly drawn features will be used to split existing features on any target + layer. This example shows the split control configured to use temporary + sketches for the split. +
+ + + --- /dev/null +++ b/openlayers/examples/strategy-bbox.html @@ -1,1 +1,66 @@ + + + OpenLayers BBOX Strategy Example + + + + + + +

BBOX Strategy Example

+

+ Uses a BBOX strategy to request features within a bounding box. +

+
+
+

The BBOX strategy requests data within a bounding box. When the + previously requested data bounds are invalidated (by browsing to + some area not covered by those bounds), another request for data + is issued.

+
+ + + --- /dev/null +++ b/openlayers/examples/strategy-cluster-threshold.html @@ -1,1 +1,146 @@ + + + OpenLayers Cluster Strategy Threshold + + + + + + + +

Cluster Strategy Threshold

+

+ Demonstrates the use of the cluster strategy threshold property. +

+
+
+

The Cluster strategy lets you display points representing clusters + of features within some pixel distance. You can control the behavior + of the cluster strategy by setting its distance and threshold properties. + The distance determines the search radius (in pixels) for features to + cluster. The threshold determines the minimum number of features to + be considered a cluster.

+
+
+

Cluster details: hover over a feature to see details.

+
    +
  • + + +
  • +
  • + + +
  • +
+ + + + --- /dev/null +++ b/openlayers/examples/strategy-cluster.html @@ -1,1 +1,203 @@ - + + + OpenLayers Cluster Strategy Example + + + + + + + + + +

Cluster Strategy Example

+

+ Uses a cluster strategy to render points representing clusters of features. +

+
+
+

The Cluster strategy lets you display points representing clusters + of features within some pixel distance.

+
+
+

Hover over a cluster on the map to see the photos it includes.

+
+
+
<<
+
+ +
+
>>
+
+
+ + + --- /dev/null +++ b/openlayers/examples/strategy-paging.html @@ -1,1 +1,80 @@ + + + OpenLayers Paging Strategy Example + + + + + + +

Paging Strategy Example

+

+ Uses a paging strategy to cache large batches of features and render a page at a time. +

+
+ Displaying page 0 of ... + + +

+
+

The Paging strategy lets you apply client side paging for protocols + that do not support paging on the server. In this case, the protocol requests a + batch of 100 features, the strategy caches those and supplies a single + page at a time to the layer.

+
+ + + --- /dev/null +++ b/openlayers/examples/style.css @@ -1,1 +1,82 @@ +/** + * CSS Reset + * From Blueprint reset.css + * http://blueprintcss.googlecode.com + */ +html, body, div, span, object, iframe, h1, h2, h3, h4, h5, h6, p, blockquote, pre, a, abbr, acronym, address, code, del, dfn, em, img, q, dl, dt, dd, fieldset, form, label, legend, table, caption, tbody, tfoot, thead, tr, th, td {margin:0;padding:0;border:0;font-weight:inherit;font-style:inherit;font-size:100%;font-family:inherit;vertical-align:baseline;} +body {line-height:1.5;} +table {border-collapse:separate;border-spacing:0;} +caption, th, td {text-align:left;font-weight:normal;} +table, td, th {vertical-align:middle;} +blockquote:before, blockquote:after, q:before, q:after {content:"";} +blockquote, q {quotes:"" "";} +a img {border:none;} +/** + * Basic Typography + */ +body { + font-family: "Lucida Grande", Verdana, Geneva, Lucida, Arial, Helvetica, sans-serif; + font-size: 80%; + color: #222; + background: #fff; + margin: 1em 1.5em; +} +pre, code { + margin: 1.5em 0; + white-space: pre; +} +pre, code { + font: 1em 'andale mono', 'lucida console', monospace; + line-height:1.5; +} +a[href] { + color: #436976; + background-color: transparent; +} +h1, h2, h3, h4, h5, h6 { + color: #003a6b; + background-color: transparent; + font: 100% 'Lucida Grande', Verdana, Geneva, Lucida, Arial, Helvetica, sans-serif; + margin: 0; + padding-top: 0.5em; +} +h1 { + font-size: 130%; + margin-bottom: 0.5em; + border-bottom: 1px solid #fcb100; +} +h2 { + font-size: 120%; + margin-bottom: 0.5em; + border-bottom: 1px solid #aaa; +} +h3 { + font-size: 110%; + margin-bottom: 0.5em; + text-decoration: underline; +} +h4 { + font-size: 100%; + font-weight: bold; +} +h5 { + font-size: 100%; + font-weight: bold; +} +h6 { + font-size: 80%; + font-weight: bold; +} + +/** + * Map Examples Specific + */ +.smallmap { + width: 512px; + height: 256px; + border: 1px solid #ccc; +} +#tags { + display: none; +} --- /dev/null +++ b/openlayers/examples/stylemap.html @@ -1,1 +1,91 @@ + + + OpenLayers StyleMap + + + + + + +

StyleMap Example

+ +
+ +

+ Shows how to use a StyleMap to style features with rule based styling. + A style map references on or more OpenLayers.Style objects. These + OpenLayers.Style objects are collections of OpenLayers.Rule objects + that determine how features are styled. An OpenLayers.Rule object + combines an OpenLayers.Filter object with a symbolizer. A filter is used + to determine whether a rule applies for a given feature, and a symbolizer + is used to draw the feature if the rule applies. +

+ +
+ +
+

A style map is used with vector layers to define styles for various + rendering intents. The style map used here has styles defined for the + "default" and "select" rendering intents. This map also has an active + select feature control. When you hover over features, they are selected + and drawn with the style corresponding the the "select" render intent. +

+
+ + --- /dev/null +++ b/openlayers/examples/styles-context.html @@ -1,1 +1,105 @@ + + + OpenLayers Vector Styles + + + + + + +

Feature Styles Example

+ +
+ +

+ To style features with a custom function that evaluates each feature, use + the context option of an OpenLayers.Style object. If the context object + contains a function and this function is referenced in a symbolizer, the + function will be called with the feature as an argument.. +

+ +
+ +
+

Features in the northern hemisphere are styled according to their + "type" attribute. This is accomplished with a simple template that + is evaluated with the feature attributes as context.

+

Features in the sourthern hemisphere are styled according to a + combination of their attributes and non-attribute properties. This + is accomplished using an advanced template that calls functions + on the context object passed to the Style constructor.

+
+ + --- /dev/null +++ b/openlayers/examples/styles-rotation.html @@ -1,1 +1,86 @@ + + + OpenLayers Styles Rotation Example + + + + + +

Rotation Style Example

+

To style point features with rotation, use the rotation + property of the symbolizer. The center of the rotation is the point of the + image specified by graphicXOffset and graphicYOffset.

+
+
+ + + --- /dev/null +++ b/openlayers/examples/styles-unique.html @@ -1,1 +1,104 @@ + + + OpenLayers Styles Unique Value Styles Example + + + + + + +

Unique Value Styles Example

+ +
+ +

+ Shows how to create a style based on unique feature attribute values (markers) + and feature state values (circles). +

+ +
+ +
+ + + --- /dev/null +++ b/openlayers/examples/sundials-spherical-mercator.html @@ -1,1 +1,129 @@ + + + OpenLayers: Sundials on a Spherical Mercator Map + + + + + + + + +

OSM + Google Maps + KML Reprojection

+ +
+ +

+ Demonstrates loading and displaying a KML file on top of OpenStreetMap (OSM) and Google Maps data. Loads data from a KML file of sundials. +

+ +
+ +
+ + + --- /dev/null +++ b/openlayers/examples/sundials.html @@ -1,1 +1,99 @@ + + + + + + + + + +

KML Layer Example

+ +
+ +

+ Demonstrates loading and displaying a KML file on top of a basemap. +

+ +
+ +
+ + + --- /dev/null +++ b/openlayers/examples/symbolizers-fill-stroke-graphic.html @@ -1,1 +1,123 @@ + + + OpenLayers Fill, Stroke, and Graphic Example + + + + + + +

OpenLayers Example

+
+

+ Demonstrate fill, stroke, and graphic property of symbolizers. +

+
+
+ This example shows how to use symbolizers with defaults for stroke, fill, and graphic. +
+ + + --- /dev/null +++ b/openlayers/examples/tasmania/TasmaniaCities.xml @@ -1,1 +1,41 @@ + + + + + 147.29100045,-42.85100182 147.29100045,-42.85100182 + + + + + + + + + 147.29100045,-42.85100182 + + + + + Hobart + Tasmania + Australia + Provincial capital + 100,000 to 250,000 + + + + + + + + + 147,-41.1 + + + + + Australia + + + --- /dev/null +++ b/openlayers/examples/tasmania/TasmaniaRoads.xml @@ -1,1 +1,205 @@ + + + + + 145.19754,-43.423512 148.27298,-40.852802 + + + + + + + + + 146.468582,-41.241478 146.574768,-41.251186 146.640411,-41.255154 146.766129,-41.332348 146.794189,-41.34417 146.822174,-41.362988 146.863434,-41.380234 146.899521,-41.379452 146.929504,-41.378227 147.008041,-41.356079 147.098343,-41.362919 + + + + + alley + + + + + + + + + 147.098343,-41.362919 147.17305,-41.452778 147.213867,-41.503773 147.234894,-41.546661 147.251129,-41.573826 147.264664,-41.602474 147.284485,-41.617554 147.300583,-41.637878 + + + + + highway + + + + + + + + + 147.300583,-41.637878 147.225815,-41.626938 147.183319,-41.619236 147.082367,-41.577755 147.031326,-41.565205 146.961487,-41.564186 146.924545,-41.568565 146.876328,-41.569614 146.783722,-41.56073 146.684937,-41.536232 146.614258,-41.478153 146.619995,-41.423958 146.582581,-41.365482 146.52478,-41.29541 146.477493,-41.277622 146.468582,-41.241478 + + + + + lane + + + + + + + + + 147.522247,-41.859921 147.551865,-41.927834 147.597321,-42.017418 147.578644,-42.113216 147.541656,-42.217743 147.468674,-42.22662 + + + + + highway + + + + + + + + + 146.103699,-41.171677 146.303619,-41.237202 146.362228,-41.236279 146.39418,-41.245384 146.443726,-41.244308 146.468582,-41.241478 + + + + + gravel + + + + + + + + + 145.856018,-41.08007 145.944839,-41.119896 146.037994,-41.150059 146.103699,-41.171677 + + + + + road + + + + + + + + + 147.468674,-42.22662 147.474945,-42.292259 147.467697,-42.301292 147.451828,-42.341656 147.424545,-42.378723 147.366013,-42.412552 147.345779,-42.432449 147.289322,-42.476475 147.264511,-42.503899 147.259918,-42.547539 147.249405,-42.614006 147.278351,-42.693249 147.284271,-42.757759 147.256744,-42.778393 + + + + + highway + + + + + + + + + 148.249252,-41.860851 148.234436,-41.901783 148.192123,-41.93721 148.155762,-41.953667 148.127731,-41.994537 148.053131,-42.100563 + + + + + road + + + + + + + + + 145.19754,-40.878323 145.246674,-40.86021 145.293289,-40.852802 145.465225,-40.897865 145.538498,-40.936264 145.554062,-40.939201 145.602112,-40.962936 145.646362,-40.98243 145.683838,-40.989883 145.710587,-40.996201 145.744293,-41.007545 145.801956,-41.041782 145.856018,-41.08007 + + + + + logging + + + + + + + + + 147.360001,-42.91993 147.348816,-42.93726 147.285049,-42.979027 147.220886,-42.995876 147.164429,-43.027004 147.068237,-43.06319 146.96463,-43.116447 146.949554,-43.17004 146.95369,-43.209591 146.964127,-43.224545 146.975723,-43.250484 146.980759,-43.2701 146.982605,-43.287716 146.970871,-43.31691 146.940521,-43.33812 146.943054,-43.362263 146.952194,-43.39278 146.955429,-43.423512 + + + + + road + + + + + + + + + 147.300583,-41.637878 147.372009,-41.695503 147.402588,-41.725574 147.444061,-41.749676 147.490433,-41.782482 147.506866,-41.795624 147.522919,-41.835609 147.522247,-41.859921 + + + + + highway + + + + + + + + + 148.053131,-42.100563 148.028229,-42.188286 148.002258,-42.2295 147.969955,-42.254417 147.960297,-42.284897 147.942719,-42.398819 147.926407,-42.486034 147.875092,-42.538582 147.832001,-42.587299 147.744217,-42.631607 147.693298,-42.656067 147.618195,-42.691135 147.575317,-42.743092 147.578293,-42.769539 147.547852,-42.814312 147.506699,-42.842907 147.488312,-42.877041 147.449692,-42.901054 147.416809,-42.902828 + + + + + road + + + + + + + + + 147.098343,-41.362919 147.065445,-41.311977 147.024078,-41.257534 146.981445,-41.211391 146.948227,-41.181595 146.926773,-41.172501 146.905029,-41.147144 146.940765,-41.085857 146.962662,-41.075096 147.021088,-41.080925 147.099228,-41.123959 147.187607,-41.150597 147.282028,-41.104244 147.295715,-41.075798 147.306595,-41.062832 147.325745,-41.053524 147.362991,-41.080441 147.419022,-41.081764 147.465881,-41.06089 147.519302,-41.092793 147.528595,-41.137089 147.552521,-41.193565 147.594223,-41.233875 147.734406,-41.239891 147.829376,-41.196636 147.882614,-41.163197 147.91127,-41.163109 147.985168,-41.226128 148.022156,-41.292599 148.075119,-41.313915 148.200104,-41.323097 148.236191,-41.339245 148.27298,-41.383488 148.25,-41.45713 148.254395,-41.53941 148.262436,-41.585217 148.249252,-41.860851 + + + + + road + + + + + + + + + 147.256744,-42.778393 147.220184,-42.824776 147.179596,-42.82143 147.111328,-42.795731 147.057098,-42.741581 147.003479,-42.704803 146.919098,-42.622734 146.910538,-42.610928 146.889984,-42.585396 146.83844,-42.572792 146.78569,-42.539352 146.724335,-42.485966 146.695023,-42.469582 146.649872,-42.450371 146.604965,-42.432274 146.578781,-42.408531 146.539307,-42.364208 146.525055,-42.30883 146.558044,-42.275948 146.576248,-42.23777 146.581467,-42.203426 146.490005,-42.180222 146.3797,-42.146332 146.334061,-42.138741 146.270966,-42.165703 146.197296,-42.224072 146.167908,-42.244835 146.164932,-42.245171 146.111023,-42.265202 146.037476,-42.239738 145.981628,-42.187851 145.853912,-42.133492 145.819611,-42.129154 145.72052,-42.104084 145.618576,-42.056023 145.541718,-42.027241 145.486282,-41.983326 145.452744,-41.926544 145.494034,-41.896477 145.591736,-41.860214 145.64212,-41.838398 145.669449,-41.830734 145.680923,-41.795753 145.682968,-41.743221 145.675156,-41.710377 145.680115,-41.688908 145.701065,-41.648228 145.714798,-41.609509 145.629196,-41.462051 145.648895,-41.470337 145.633423,-41.420902 145.631866,-41.36528 145.640854,-41.301533 145.700424,-41.242611 145.77243,-41.193897 145.802338,-41.161488 145.856018,-41.08007 + + + + + road + + + --- /dev/null +++ b/openlayers/examples/tasmania/TasmaniaStateBoundaries.xml @@ -1,1 +1,93 @@ + + + + + 143.834824,-43.648056 148.479141,-39.573891 + + + + + + + + + + + 148.01416,-42.753059 148.009979,-42.73111 148.011108,-42.652222 148.012634,-42.628613 148.018738,-42.61972 148.076492,-42.586945 148.128006,-42.590275 148.172897,-42.655277 148.168167,-42.665554 148.154984,-42.668888 148.097748,-42.666107 148.041656,-42.732216 148.01416,-42.753059 + + + + + + + + + 147.361633,-43.263062 147.29303,-43.157082 147.329132,-43.102638 147.357178,-43.075005 147.396515,-43.11972 147.431641,-43.213886 147.432739,-43.241943 147.429688,-43.253616 147.361633,-43.263062 + + + + + + + + + 148.128845,-40.274445 148.115234,-40.271667 148.101074,-40.26722 148.064423,-40.253891 148.049133,-40.245552 148.038589,-40.236248 148.013184,-40.161388 148.018311,-40.140209 147.90387,-39.975555 147.809616,-39.913815 147.773865,-39.894722 147.760742,-39.877983 147.783875,-39.850281 147.881897,-39.754173 147.925812,-39.737503 147.967743,-39.725555 147.971069,-39.736389 147.978302,-39.74472 148.069427,-39.83889 148.165527,-39.929443 148.174408,-39.936111 148.186783,-39.944443 148.202759,-39.950279 148.243851,-39.962082 148.279419,-39.965836 148.288025,-39.99472 148.335236,-40.192223 148.33136,-40.219166 148.32135,-40.231941 148.303314,-40.239025 148.17746,-40.25695 148.128845,-40.274445 + + + + + + + + + 148.339142,-40.503334 148.339691,-40.466942 148.33609,-40.45472 148.329971,-40.442917 148.318298,-40.435272 148.292206,-40.434441 148.129944,-40.44722 148.114685,-40.448883 148.103851,-40.454445 148.086639,-40.458057 148.068573,-40.45472 147.99704,-40.428196 147.993561,-40.417084 147.995514,-40.40139 147.998566,-40.389725 148.008041,-40.379162 148.065247,-40.348194 148.083588,-40.344719 148.099121,-40.34333 148.116913,-40.343613 148.133026,-40.345001 148.148315,-40.347221 148.187744,-40.362503 148.202606,-40.361252 148.288025,-40.324722 148.308868,-40.314163 148.329132,-40.305138 148.343018,-40.306664 148.354675,-40.315552 148.479141,-40.430695 148.477188,-40.441387 148.463226,-40.442081 148.407608,-40.461945 148.358307,-40.490555 148.339142,-40.503334 + + + + + + + + + 147.302765,-43.513336 147.239136,-43.491669 147.175537,-43.501671 147.123016,-43.421944 147.190247,-43.354446 147.289566,-43.26403 147.300262,-43.262779 147.307739,-43.270279 147.362457,-43.374168 147.36496,-43.385834 147.362732,-43.398056 147.320663,-43.502918 147.310516,-43.511948 147.302765,-43.513336 + + + + + + + + + 144.888885,-40.729439 144.878571,-40.726246 144.870941,-40.71944 144.865936,-40.671116 144.926224,-40.617222 144.993286,-40.666664 145.016083,-40.695549 144.926361,-40.722496 144.888885,-40.729439 + + + + + + + + + 146.916702,-43.617844 146.863281,-43.636391 146.833588,-43.648056 146.81517,-43.617912 146.770401,-43.610695 146.686371,-43.603333 146.599548,-43.55611 146.514435,-43.542778 146.296082,-43.534729 146.275177,-43.52375 146.260269,-43.49514 146.231476,-43.488888 146.110367,-43.515423 146.0383,-43.498055 145.932678,-43.376316 145.991913,-43.345833 146.1026,-43.357918 146.156647,-43.379723 146.232529,-43.390972 146.234543,-43.325142 146.163239,-43.28236 146.139709,-43.31472 146.12468,-43.333332 145.858856,-43.30875 145.836914,-43.297226 145.758881,-43.184441 145.726898,-43.133331 145.595245,-42.979164 145.573776,-42.963818 145.54747,-42.961391 145.511581,-42.965656 145.459686,-42.904442 145.423157,-42.846664 145.397766,-42.775558 145.353851,-42.658535 145.310516,-42.623611 145.259979,-42.612431 145.231354,-42.45639 145.197617,-42.313473 145.205231,-42.25695 145.223846,-42.239166 145.250107,-42.27486 145.323029,-42.32 145.378021,-42.349167 145.426361,-42.37458 145.439758,-42.398746 145.445663,-42.457359 145.459061,-42.505627 145.469421,-42.523056 145.47525,-42.520279 145.552048,-42.351109 145.49968,-42.323475 145.458313,-42.326393 145.280273,-42.181114 145.260681,-42.139999 145.265121,-42.111389 145.262772,-42.080002 145.247879,-42.034863 145.184555,-41.938332 145.054962,-41.846664 144.954956,-41.713333 144.858582,-41.544449 144.781647,-41.390556 144.731628,-41.306107 144.685791,-41.216595 144.695389,-41.18111 144.667755,-41.075211 144.653595,-41.046951 144.637207,-41.031944 144.618713,-40.93111 144.648865,-40.901245 144.680664,-40.896114 144.699692,-40.875484 144.708588,-40.825562 144.701355,-40.759171 144.762207,-40.72805 144.985992,-40.74868 145.036102,-40.779167 145.080383,-40.810276 145.116348,-40.822365 145.274994,-40.80278 145.335663,-40.842079 145.539154,-40.892776 145.751373,-40.987778 145.872192,-41.042778 146.169434,-41.149994 146.193176,-41.15694 146.229126,-41.160553 146.36969,-41.170837 146.405411,-41.171669 146.450378,-41.16486 146.499146,-41.150139 146.564697,-41.175278 146.58609,-41.186661 146.581909,-41.151527 146.660385,-41.088749 146.731491,-41.069725 146.76416,-41.073059 146.784409,-41.082291 146.801086,-41.107506 146.806458,-41.148365 146.859131,-41.168335 146.94281,-41.166874 146.912964,-41.134789 146.879837,-41.126804 146.843018,-41.123055 146.823013,-41.108124 146.818726,-41.059792 146.86377,-41.028404 147.017059,-40.976109 147.08609,-40.991943 147.105804,-40.99778 147.124664,-41.005005 147.147354,-41.008892 147.171783,-41.008892 147.199127,-41.002228 147.356079,-40.976387 147.416504,-41.017776 147.461914,-41.001396 147.488373,-40.984997 147.517487,-40.953331 147.541656,-40.924171 147.573166,-40.879028 147.589127,-40.853058 147.611618,-40.842358 147.674835,-40.830837 147.698792,-40.857361 147.803162,-40.89278 147.838165,-40.891251 147.876083,-40.878746 147.901779,-40.863194 147.921631,-40.840836 147.933594,-40.822086 147.944138,-40.795277 147.951157,-40.761322 147.971832,-40.744789 148.01416,-40.745972 148.079407,-40.76889 148.221069,-40.84903 148.273315,-40.901108 148.307419,-40.957478 148.318863,-40.972359 148.328308,-40.995419 148.302185,-41.075562 148.290253,-41.10778 148.279846,-41.130833 148.264343,-41.167221 148.272003,-41.218468 148.313568,-41.259308 148.316925,-41.334724 148.287476,-41.423889 148.273804,-41.454166 148.280396,-41.539234 148.296356,-41.565834 148.312195,-41.591248 148.314285,-41.612919 148.292206,-41.728882 148.270966,-41.782642 148.264709,-41.814587 148.298035,-42.035004 148.311493,-42.063473 148.333984,-42.087639 148.358719,-42.108681 148.363846,-42.222427 148.346619,-42.249168 148.324554,-42.270695 148.311096,-42.277779 148.302765,-42.27639 148.275269,-42.255562 148.270813,-42.231667 148.29776,-42.206249 148.309692,-42.140556 148.238846,-41.998196 148.195267,-41.94545 148.079117,-42.117218 148.004013,-42.522499 147.958725,-42.556389 147.943848,-42.613892 147.955521,-42.666527 147.954956,-42.717499 147.924835,-42.741108 147.898865,-42.756535 147.883179,-42.772221 147.84288,-42.872917 147.856415,-42.888889 147.899796,-42.886631 147.881836,-42.857224 147.910873,-42.840832 147.974197,-42.869511 147.999695,-42.907078 148.004776,-42.976868 147.96701,-42.995449 147.951492,-43.082291 147.979126,-43.126663 148.0047,-43.170837 147.995529,-43.227589 147.970795,-43.229092 147.899414,-43.183434 147.827179,-43.206108 147.789703,-43.246948 147.697205,-43.163612 147.631622,-43.065552 147.618973,-43.017708 147.67392,-42.945133 147.706497,-42.938328 147.730789,-42.95472 147.735794,-42.978886 147.719894,-43.002499 147.759979,-43.039864 147.781784,-43.051109 147.808868,-43.054722 147.867874,-43.046528 147.899414,-43.026875 147.825806,-42.931946 147.591629,-42.826736 147.557465,-42.830559 147.502121,-42.860764 147.521362,-42.928886 147.53595,-42.949024 147.55275,-42.978954 147.525604,-43.018333 147.476624,-43.034172 147.427124,-43.04174 147.403732,-43.000072 147.423019,-42.991112 147.40802,-42.889725 147.351624,-42.861389 147.317474,-42.846664 147.348572,-42.904716 147.34079,-42.951111 147.32608,-43.008614 147.292755,-43.028053 147.268188,-43.060432 147.241913,-43.133614 147.240311,-43.155487 147.262482,-43.203888 147.263321,-43.224861 147.247452,-43.269169 147.213287,-43.285625 147.178162,-43.282223 147.098297,-43.244446 147.041351,-43.199722 147.025742,-43.181873 147.022766,-43.138332 147.01207,-43.118752 146.991287,-43.112431 146.970093,-43.137085 146.964417,-43.164162 146.964142,-43.184307 146.969269,-43.204304 146.993988,-43.223747 147.01944,-43.237778 147.061096,-43.258339 147.095337,-43.288715 147.054962,-43.362503 147.002213,-43.422638 146.952179,-43.528053 146.937469,-43.600624 146.916702,-43.617844 + + + + + + + + + 143.921631,-40.136391 143.913879,-40.134727 143.886307,-40.116734 143.873566,-40.065002 143.892349,-40.054302 143.891937,-39.984722 143.885544,-39.970139 143.870514,-39.956947 143.851624,-39.945274 143.840378,-39.936802 143.834824,-39.927502 143.837738,-39.873055 143.85524,-39.711945 143.871063,-39.700279 143.899719,-39.688606 143.916656,-39.680557 143.925812,-39.674171 143.933594,-39.666946 143.941635,-39.655693 143.945526,-39.640839 143.943848,-39.628883 143.935516,-39.608612 143.931915,-39.598335 143.935455,-39.583054 143.977463,-39.573891 143.987732,-39.57695 144.066788,-39.616112 144.108582,-39.662498 144.112183,-39.673058 144.122192,-39.812218 144.122192,-39.825005 144.146454,-39.92944 144.136917,-39.984306 144.106064,-40.036392 144.008881,-40.087776 143.957733,-40.110001 143.921631,-40.136391 + + + + + + + Tasmania + Australia + Australia Dollar + AUD + + + --- /dev/null +++ b/openlayers/examples/tasmania/TasmaniaWaterBodies.xml @@ -1,1 +1,163 @@ + + + + + 145.971619,-43.031944 147.219696,-41.775558 + + + + + + + + + + + 146.232727,-42.157501 146.238007,-42.16111 146.24411,-42.169724 146.257202,-42.193329 146.272217,-42.209442 146.274689,-42.214165 146.27832,-42.21833 146.282471,-42.228882 146.282745,-42.241943 146.291351,-42.255836 146.290253,-42.261948 146.288025,-42.267502 146.282471,-42.269997 146.274994,-42.271111 146.266663,-42.270279 146.251373,-42.262505 146.246918,-42.258057 146.241333,-42.256111 146.23468,-42.257782 146.221344,-42.269165 146.210785,-42.274445 146.20163,-42.27417 146.196075,-42.271385 146.186646,-42.258057 146.188568,-42.252785 146.193298,-42.249443 146.200806,-42.248055 146.209137,-42.249168 146.217468,-42.248611 146.222473,-42.245277 146.22525,-42.240555 146.224121,-42.22805 146.224396,-42.221382 146.228302,-42.217216 146.231354,-42.212502 146.231628,-42.205559 146.219421,-42.186943 146.21637,-42.17028 146.216644,-42.16333 146.219696,-42.158607 146.225525,-42.156105 146.232727,-42.157501 + + + + + + + 1064866676 + 1071221047 + Lake + Australia + Australia + + + + + + + + + + + 146.284424,-43.031944 146.265808,-43.029442 146.257751,-43.021667 146.252197,-43.01889 146.243561,-43.017776 146.23053,-43.021667 146.21524,-43.02417 146.209686,-43.021942 146.209961,-43.015007 146.21579,-42.991112 146.21524,-42.985001 146.213593,-42.979439 146.21109,-42.974716 146.207458,-42.970276 146.193024,-42.959724 146.181915,-42.95472 146.166931,-42.951393 146.1586,-42.950554 146.123016,-42.951111 146.116364,-42.948883 146.112732,-42.944717 146.110229,-42.93972 146.101349,-42.932777 146.094971,-42.929726 146.084961,-42.922775 146.054138,-42.897781 146.041656,-42.886665 146.038025,-42.882217 146.035522,-42.877495 146.035248,-42.86528 146.036652,-42.852226 146.034424,-42.840279 146.030823,-42.836113 146.026367,-42.832504 146.018036,-42.831673 146.010529,-42.832779 146.003876,-42.834724 145.995514,-42.835274 145.990784,-42.831673 145.990234,-42.825562 145.996338,-42.815277 146.000549,-42.805275 145.997192,-42.800278 145.984406,-42.789726 145.981079,-42.785561 145.976898,-42.775002 145.97995,-42.770279 145.985504,-42.767776 145.994965,-42.768059 146.002472,-42.769447 146.008881,-42.772499 146.025818,-42.786667 146.032196,-42.788895 146.040802,-42.788338 146.061646,-42.783333 146.068848,-42.785004 146.074402,-42.787781 146.086914,-42.799995 146.109131,-42.825279 146.117188,-42.832222 146.122742,-42.834442 146.131073,-42.835274 146.139709,-42.834999 146.147217,-42.833061 146.163025,-42.83139 146.170532,-42.833611 146.174988,-42.837219 146.176636,-42.841942 146.17746,-42.848053 146.173309,-42.852226 146.165802,-42.853333 146.155243,-42.859169 146.141937,-42.86306 146.12912,-42.858612 146.118011,-42.852783 146.110779,-42.851395 146.102173,-42.852501 146.098297,-42.855003 146.097198,-42.861389 146.102173,-42.871666 146.111359,-42.878883 146.121338,-42.884445 146.132446,-42.889442 146.146942,-42.899445 146.154968,-42.907219 146.164978,-42.914444 146.174988,-42.92028 146.181366,-42.923058 146.195251,-42.926109 146.204681,-42.926392 146.220795,-42.924721 146.227448,-42.922775 146.233032,-42.92028 146.241913,-42.913612 146.247742,-42.904167 146.260529,-42.891945 146.265533,-42.888611 146.272217,-42.886948 146.281372,-42.886948 146.289703,-42.888054 146.300812,-42.893616 146.308014,-42.902779 146.308594,-42.908333 146.302185,-42.925278 146.301086,-42.931389 146.301636,-42.9375 146.303314,-42.943054 146.307739,-42.946663 146.320801,-42.951111 146.330261,-42.951393 146.352753,-42.947777 146.360229,-42.949165 146.361908,-42.95472 146.358002,-42.959442 146.347473,-42.965553 146.335785,-42.969994 146.331085,-42.973328 146.328033,-42.97805 146.329681,-42.983612 146.33609,-42.985832 146.36496,-42.9925 146.371338,-42.99472 146.383331,-43.000557 146.389984,-43.002785 146.39444,-43.006393 146.391357,-43.011116 146.383881,-43.012222 146.368561,-43.020836 146.355225,-43.024719 146.339142,-43.02639 146.332458,-43.028336 146.323853,-43.02861 146.313019,-43.023056 146.306366,-43.020836 146.298859,-43.022774 146.290253,-43.029442 146.284424,-43.031944 + + + + + + + 1067509088 + 1073140989 + Lake + Australia + Australia + + + + + + + + + + + 146.191925,-42.116112 146.184692,-42.114449 146.174988,-42.107506 146.171356,-42.103333 146.167755,-42.101944 146.167206,-42.095001 146.170532,-42.077225 146.169128,-42.071671 146.163879,-42.061943 146.159698,-42.057777 146.140808,-42.044167 146.09024,-42.014168 146.08609,-42.010559 146.083313,-42.005005 146.084686,-41.999443 146.089417,-41.996109 146.097748,-41.99778 146.109406,-42.002228 146.129395,-42.008057 146.146637,-42.016113 146.153046,-42.018333 146.169128,-42.026947 146.179138,-42.033615 146.182739,-42.036949 146.203583,-42.062775 146.20636,-42.06778 146.207733,-42.073334 146.206635,-42.079445 146.207184,-42.085556 146.208862,-42.09111 146.214417,-42.094162 146.21579,-42.099724 146.209961,-42.109169 146.205231,-42.11306 146.200256,-42.115555 146.191925,-42.116112 + + + + + + + 1064598241 + 1071187492 + Lake + Australia + Australia + + + + + + + + + + + 146.697205,-41.988892 146.688873,-41.988052 146.682465,-41.985832 146.67746,-41.976105 146.673859,-41.973328 146.674133,-41.966393 146.673309,-41.960281 146.674408,-41.95417 146.680817,-41.937218 146.696625,-41.907219 146.69693,-41.900551 146.694122,-41.895554 146.693573,-41.889442 146.695526,-41.883888 146.702179,-41.875275 146.703583,-41.869164 146.700256,-41.858055 146.697754,-41.853058 146.684418,-41.834999 146.680817,-41.83139 146.675812,-41.821671 146.674988,-41.815552 146.680267,-41.797783 146.683319,-41.792503 146.684418,-41.786949 146.691071,-41.778336 146.69693,-41.775558 146.704132,-41.776947 146.708588,-41.781387 146.714691,-41.789726 146.722748,-41.797226 146.728027,-41.800835 146.733582,-41.803055 146.75,-41.804718 146.761658,-41.816666 146.766663,-41.826393 146.772217,-41.828613 146.780548,-41.828613 146.808319,-41.821671 146.815796,-41.820557 146.823029,-41.822777 146.825531,-41.833061 146.824677,-41.853615 146.822754,-41.858894 146.816925,-41.868607 146.80304,-41.871666 146.786377,-41.872772 146.777191,-41.872498 146.764984,-41.876389 146.761108,-41.880554 146.759979,-41.886665 146.762207,-41.898338 146.767487,-41.908607 146.774414,-41.917221 146.779694,-41.927498 146.777771,-41.93222 146.765259,-41.943611 146.754425,-41.963333 146.749695,-41.96666 146.732727,-41.974716 146.728027,-41.97805 146.703857,-41.987778 146.697205,-41.988892 + + + + + + + 1066494066 + 1071999090 + Lake + Australia + Australia + + + + + + + + + + + 146.899719,-42.032776 146.892487,-42.030556 146.886932,-42.027779 146.882446,-42.02417 146.87912,-42.018608 146.878571,-42.006111 146.876892,-42 146.871338,-41.99778 146.864136,-41.996391 146.859406,-41.993614 146.855225,-41.983055 146.856354,-41.976944 146.866913,-41.963615 146.871613,-41.959999 146.883881,-41.955559 146.88858,-41.951668 146.891663,-41.947495 146.893585,-41.941383 146.88858,-41.92556 146.887756,-41.919167 146.888031,-41.912498 146.891937,-41.907776 146.896637,-41.904999 146.90387,-41.906387 146.907471,-41.910828 146.911652,-41.922501 146.914429,-41.926666 146.919708,-41.929443 146.926361,-41.931671 146.953033,-41.931389 146.961365,-41.93222 146.968567,-41.933884 146.973846,-41.936661 146.983032,-41.943611 146.985504,-41.948334 146.987183,-41.953888 146.982178,-41.965004 146.972748,-41.978333 146.971619,-41.983887 146.966644,-41.99472 146.963593,-41.999443 146.958862,-42.003616 146.956085,-42.007782 146.946625,-42.015007 146.940796,-42.016945 146.932739,-42.016113 146.926086,-42.018059 146.921356,-42.022224 146.914703,-42.030281 146.90802,-42.032219 146.899719,-42.032776 + + + + + + + 1065512599 + 1071304933 + Lake + Australia + Australia + + + + + + + + + + + 147.149719,-42.203056 147.142212,-42.201668 147.131348,-42.195831 147.127747,-42.191666 147.125244,-42.186111 147.12439,-42.180832 147.126343,-42.175278 147.132172,-42.165833 147.136108,-42.16111 147.137207,-42.155556 147.135529,-42.149994 147.12912,-42.14167 147.126617,-42.136948 147.128845,-42.124443 147.12912,-42.117775 147.122742,-42.115555 147.11441,-42.116112 147.101349,-42.120552 147.093842,-42.119164 147.092194,-42.114449 147.093292,-42.108337 147.097198,-42.097221 147.103302,-42.080833 147.108307,-42.07 147.112183,-42.066666 147.117737,-42.063889 147.124115,-42.061943 147.131622,-42.060829 147.138031,-42.063614 147.140808,-42.06778 147.145264,-42.071945 147.150818,-42.074173 147.159973,-42.074173 147.16748,-42.073059 147.180542,-42.069725 147.188873,-42.069168 147.19693,-42.07 147.209686,-42.075005 147.216919,-42.082779 147.219696,-42.087502 147.219421,-42.094444 147.216644,-42.099167 147.211914,-42.103333 147.190521,-42.106949 147.185791,-42.110283 147.182739,-42.115005 147.180542,-42.127495 147.180267,-42.134445 147.18219,-42.140556 147.182739,-42.146111 147.187744,-42.16861 147.188568,-42.175003 147.187195,-42.187775 147.184143,-42.192772 147.180542,-42.196663 147.169128,-42.201942 147.149719,-42.203056 + + + + + + + 1065646817 + 1071606923 + Lake + Australia + Australia + + + + + + + + + + + 146.240784,-42.851112 146.231628,-42.850838 146.228027,-42.846664 146.218842,-42.83889 146.214691,-42.831116 146.206635,-42.823334 146.195801,-42.810829 146.173859,-42.77861 146.171356,-42.773888 146.169708,-42.768333 146.166382,-42.762779 146.160522,-42.748886 146.155243,-42.739166 146.151642,-42.735001 146.142761,-42.727776 146.127747,-42.725555 146.118561,-42.72583 146.111908,-42.726944 146.096344,-42.736389 146.09079,-42.738892 146.082184,-42.739998 146.077759,-42.737221 146.074127,-42.733055 146.060791,-42.722221 146.053314,-42.720833 146.041077,-42.725273 146.031372,-42.731941 146.01886,-42.736946 146.011383,-42.738335 145.994415,-42.739166 145.979675,-42.736115 145.974121,-42.733055 145.971619,-42.728333 145.973022,-42.721382 145.976074,-42.71666 145.985504,-42.709999 146.001923,-42.701668 146.011658,-42.695 146.01944,-42.686943 146.022491,-42.681671 146.02359,-42.676109 146.023041,-42.669724 146.01886,-42.65889 146.018311,-42.653328 146.014984,-42.642227 146.014435,-42.636116 146.016937,-42.623611 146.020813,-42.61972 146.025543,-42.616394 146.032196,-42.614449 146.03775,-42.6175 146.041351,-42.621666 146.043854,-42.626389 146.046936,-42.637505 146.048309,-42.649994 146.048035,-42.656662 146.049133,-42.669167 146.05246,-42.680832 146.054962,-42.684998 146.060516,-42.688049 146.068848,-42.6875 146.073853,-42.683609 146.075806,-42.678886 146.078308,-42.666389 146.079407,-42.645836 146.084412,-42.61528 146.088867,-42.604172 146.095795,-42.595001 146.099701,-42.590836 146.104401,-42.587502 146.111908,-42.589165 146.115234,-42.59333 146.116058,-42.599442 146.113586,-42.612503 146.113281,-42.619446 146.113861,-42.626106 146.113281,-42.639168 146.11441,-42.652222 146.116058,-42.657219 146.11969,-42.661385 146.126068,-42.664162 146.13443,-42.665276 146.161377,-42.665276 146.16803,-42.662498 146.171082,-42.658607 146.172211,-42.652496 146.173035,-42.638611 146.172211,-42.6325 146.173309,-42.611946 146.17746,-42.600281 146.183594,-42.590836 146.195801,-42.586388 146.203308,-42.585274 146.210785,-42.586662 146.21524,-42.590279 146.221344,-42.599998 146.221893,-42.60556 146.2258,-42.622223 146.228577,-42.626945 146.235779,-42.628609 146.247192,-42.623329 146.253876,-42.621384 146.261383,-42.623055 146.265808,-42.62722 146.267487,-42.631943 146.268036,-42.638893 146.264435,-42.656662 146.261658,-42.661385 146.254974,-42.66333 146.246613,-42.662498 146.240784,-42.665001 146.236084,-42.668335 146.233032,-42.673058 146.232727,-42.68 146.236084,-42.684723 146.241638,-42.686943 146.257477,-42.688889 146.265808,-42.688889 146.279694,-42.692772 146.283325,-42.696945 146.288574,-42.706665 146.291931,-42.710831 146.296356,-42.714447 146.307465,-42.720833 146.311096,-42.724442 146.308868,-42.730553 146.304962,-42.734444 146.293579,-42.739166 146.293304,-42.745834 146.29776,-42.749443 146.303314,-42.752502 146.306915,-42.756393 146.309418,-42.761391 146.315521,-42.769722 146.323578,-42.777496 146.334412,-42.790001 146.338867,-42.793617 146.346069,-42.801941 146.348572,-42.806946 146.349121,-42.813057 146.345245,-42.817223 146.340515,-42.820557 146.333862,-42.822227 146.324402,-42.822227 146.3172,-42.820557 146.306091,-42.815002 146.295532,-42.802223 146.28775,-42.787781 146.282196,-42.784729 146.273865,-42.785278 146.268036,-42.787781 146.263306,-42.791115 146.256378,-42.799995 146.254974,-42.806664 146.25415,-42.826668 146.251648,-42.83889 146.249695,-42.844444 146.246613,-42.849167 146.240784,-42.851112 + + + + + + + 1067743969 + 1073212293 + Lake + Australia + Australia + + + --- /dev/null +++ b/openlayers/examples/tasmania/sld-tasmania.xml @@ -1,1 +1,595 @@ - + + + + WaterBodies + + Default Styler + Default Styler + + 1 + + testStyleName + title + abstract + Feature + generic:geometry + + testRuleName + title + Abstract + + + + + 3000000 + + + blue + + 1.0 + + + + + #C0C0C0 + + + butt + + + miter + + + 1 + + + 1 + + + 0 + + + + + + testRuleNameElse + title + Abstract + + + + #aaaaff + + 0.5 + + + + + #C0C0C0 + + + 1 + + + 1 + + + + + + + + + Hover Styler + Hover Styler + + + testStyleHover + title + abstract + Feature + generic:geometry + + testRuleNameHover + title + Abstract + + + + + PERIMETER + 1071304933 + + + AREA + 1065512599 + + + + + + + + black + + + 0.5 + + + + + green + + + butt + + + miter + + + 0.5 + + + 5 + + + 0 + + + + + + testRuleNameHoverElse + title + Abstract + + + + + black + + + 0.5 + + + + + fuchsia + + + 0.5 + + + 5 + + + 0 + + + + + + + + + Attribute Filter Styler + Attribute Filter Styler + + attribute filter type + attribute filter type + Feature + generic:geometry + + + rulePropertyIsEqualTo + rulePropertyIsEqualTo + rulePropertyIsEqualTo + + + name + My simple Polygon + + + + + + #000033 + + + + + + + + + Styler Test PropertyIsEqualTo + Styler Test PropertyIsEqualTo + + attribute filter type + attribute filter type + Feature + generic:geometry + + + rulePropertyIsEqualTo + rulePropertyIsEqualTo + rulePropertyIsEqualTo + + + AREA + 1067743969 + + + + + + red + + + + + + + + + Styler Test WATER_TYPE + Styler Test WATER_TYPE + + attribute filter type + attribute filter type + Feature + generic:geometry + + + rulePropertyIsEqualTo + rulePropertyIsEqualTo + rulePropertyIsEqualTo + + + WATER_TYPE + Lake + + + + + + red + + + + + + + + + Styler Test PropertyIsGreaterThanOrEqualTo + Styler Test PropertyIsGreaterThanOrEqualTo + + attribute filter type + attribute filter type + Feature + generic:geometry + + + PropertyIsGreaterThanOrEqualTo + PropertyIsGreaterThanOrEqualTo + PropertyIsGreaterThanOrEqualTo + + + + WATER_TYPE + Lake + + + AREA + 1067509088 + + + + + + + yellow + + + + + + + + + + Styler Test PropertyIsLessThanOrEqualTo + Styler Test PropertyIsLessThanOrEqualTo + + attribute filter type + attribute filter type + Feature + generic:geometry + + + PropertyIsLessThanOrEqualTo + PropertyIsLessThanOrEqualTo + PropertyIsLessThanOrEqualTo + + + + WATER_TYPE + Lake + + + AREA + 1067509088 + + + + + + + yellow + + + + + + + + + + + Styler Test PropertyIsGreaterThan + Styler Test PropertyIsGreaterThan + + attribute filter type + attribute filter type + Feature + generic:geometry + + + PropertyIsGreaterThan + PropertyIsGreaterThan + PropertyIsGreaterThan + + + + WATER_TYPE + Lake + + + AREA + 1067000000 + + + + + + + yellow + + + + + + + + + Styler Test PropertyIsLessThan + Styler Test PropertyIsLessThan + + attribute filter type + attribute filter type + Feature + generic:geometry + + + PropertyIsLessThan + PropertyIsLessThan + PropertyIsLessThan + + + + WATER_TYPE + Lake + + + AREA + 1067000000 + + + + + + + yellow + + + + + + + + + Styler Test PropertyIsLike + Styler Test PropertyIsLike + + attribute filter type + attribute filter type + Feature + generic:geometry + + + PropertyIsLike + PropertyIsLike + PropertyIsLike + + + AREA + 106774* + + + + + + green + + + + + + + + + Styler Test PropertyIsBetween + Styler Test PropertyIsBetween + + attribute filter type + attribute filter type + Feature + generic:geometry + + + PropertyIsBetween + PropertyIsBetween + PropertyIsBetween + + + AREA + + 1064866676 + + + 1065512599 + + + + + + + blue + + + + + + + + + FeatureId + Styler Test FeatureId + + + + + + + + blue + + + + + + + + + + Roads + + RoadsDefault + 1 + + + justAStyler + + + + red + + + 2 + + + + + + + + + + Cities + + DefaultCities + 1 + + + + + + + + + + image/png + + 0.7 + 14 + + + + + + + + + cross + + 10 + + + + + + + + + Land + + Land Style + 1 + + + + + #ccffaa + + 0.5 + + + + + #C0C0C0 + + + 1 + + + 1 + + + 3 5 1 5 + + + + + + + + + + --- /dev/null +++ b/openlayers/examples/teleportation.html @@ -1,1 +1,71 @@ + + + OpenLayers Teleporter Example + + + + + + + + + +

Map "Teleportation" and Rendering

+ +
+ +

Call the map's render method to change its container.

+ +
+
+
+
+ + + +
+ This example demonstrates how a map can be rendered initially in one + container and then moved to a new container. At any point after map + construction, the map's render method can be called with the id of + an empty container, moving the map to the new container. +
+ + + --- /dev/null +++ b/openlayers/examples/textfile.txt @@ -1,1 +1,5 @@ +point title description icon +10,20 my orange title my orange description +2,4 my aqua title my aqua description +42,-71 my purple title my purple description
is great. http://www.openlayers.org/api/img/zoom-world-mini.png --- /dev/null +++ b/openlayers/examples/tilecache.html @@ -1,1 +1,64 @@ + + + OpenLayers TileCache Example + + + + + + +

TileCache Example

+ +
+ +

+ Demonstrates a TileCache layer that loads tiles from from a web + accessible disk-based cache only. +

+ +
+ +
+ This layer should be used for web accessible disk-based caches only. + It is not used to request new tiles from TileCache. Note that you + should specify resolutions explicitly on this layer so that they match + your TileCache configuration. +
+ + + --- /dev/null +++ b/openlayers/examples/tms.html @@ -1,1 +1,57 @@ + + + OpenLayers Tiled Map Service Example + + + + + + +

Tiled Map Service Example

+ +
+ +

+ Demonstrate the initialization and modification of a Tiled Map Service layer. +

+ +
+ +
+ URL of TMS (Should end in /): layer_name
+

+ Example: http://labs.metacarta.com/wms-c/Basic.py/, satellite, jpg
+ The first input must be an HTTP URL pointing to a TMS instance. The second + input must be a layer name available from that instance, and the third must + be the output format used by that layer. (Any other behavior will result in + broken images being displayed.) +

+
+ + + --- /dev/null +++ b/openlayers/examples/transition.html @@ -1,1 +1,63 @@ + + + OpenLayers Transitions Example + + + + + +

Transition Example

+

+ Demonstrates the use of transition effects in tiled and untiled layers. +

+
+
+ There are two transitions that are currently implemented: null (the + default) and 'resize'. The default transition effect is used when no + transition is specified and is implemented as no transition effect except + for panning singleTile layers. The 'resize' effect resamples the current + tile and displays it stretched or compressed until the new tile is available. +
    +
  • The first layer is an untiled WMS layer with no transition effect.
  • +
  • The second layer is an untiled WMS layer with a 'resize' effect.
  • +
  • The third layer is a tiled WMS layer with no transition effect.
  • +
  • The fourth layer is a tiled WMS layer with a 'resize' effect.
  • +
+
+ + + --- /dev/null +++ b/openlayers/examples/urban.html @@ -1,1 +1,46 @@ + + + OpenLayers WorldWind Example + + + + + + +

WorldWind Example

+ +
+ +

+ Demonstrate the use of a NASA WorldWind base layer. +

+ +
+ +
+

+ Add the Nasa WorldWind "Urban" layer to your map. The "Urban" layer contains aerial imagery for urban areas only. +

+

+ You can do it with a ... +

+ +
+ + + --- /dev/null +++ b/openlayers/examples/ve-novibrate.html @@ -1,1 +1,63 @@ + + + OpenLayers Virtual Earth Example + + + + + + + + +

Virtual Earth Example

+ +
+ +

+ When using the PanZoom buttons with VirtualEarth, some 'drift' is + possible in markers. This page demonstrates how to use the + panMethod and panDuration properties to change the OpenLayers + settings to match VirtualEarth. +

+ +
+ + + + --- /dev/null +++ b/openlayers/examples/ve.html @@ -1,1 +1,69 @@ + + + OpenLayers Virtual Earth Example + + + + + + + +

Virtual Earth Example

+ +
+ +

+ Demonstrates the use of a Virtual Earth base layer. +

+ +
+ + +
This example demonstrates the ability to add VirtualEarth and the and remove markers.
+ + + + + --- /dev/null +++ b/openlayers/examples/vector-features-with-text.html @@ -1,1 +1,113 @@ + + + OpenLayers Labeled Features Example + + + + + + +

OpenLayers Labeled features example

+
+

+ Label vector features with a text symbolizer. +

+
+
+ This example shows drawing simple vector features with a label +
+ + + --- /dev/null +++ b/openlayers/examples/vector-features.html @@ -1,1 +1,139 @@ + + + OpenLayers: Vector Features + + + + + + +

Drawing Simple Vector Features Example

+ +
+
+

+ Shows the use of the shows drawing simple vector features, in different styles. +

+
+
+
+
+

This example shows drawing simple vector features -- point, line, polygon + in different styles, created 'manually', by constructing the entire style + object, via 'copy', extending the default style object, and by + inheriting the default style from the layer.

+

It also shows how to use external graphic files for point features + and how to set their size: If either graphicWidth or graphicHeight is set, + the aspect ratio of the image will be respected. If both graphicWidth and + graphicHeight are set, it will be ignored. Alternatively, if graphicWidth + and graphicHeight are omitted, pointRadius will be used to set the size + of the image, which will then be twice the value of pointRadius with the + original aspect ratio.

+
+ + + + + --- /dev/null +++ b/openlayers/examples/vector-formats.html @@ -1,1 +1,222 @@ - + + + Vector Formats + + + + + + + + +
+

Vector Formats Example

+ +
+
+

+ Shows the wide variety of vector formats that open layers supports. +

+ +
+
+

Use the drop-down below to select the input/output format + for vector features. New features can be added by using the drawing + tools above or by pasting their text representation below.

+ + +   + + +
+ Input Projection:
+ Output Projection: +
+ +
+ +
+ +
+
+ +
+
+

Use the tools to the left to draw new polygons, lines, and points. + After drawing some new features, hover over a feature to see the + serialized version below.

+ +
+ + + + --- /dev/null +++ b/openlayers/examples/wfs-protocol-transactions.html @@ -1,1 +1,198 @@ + + + + + + + + + + + +

WFS Transaction Example

+ +
+
+

+ Shows the use of the WFS Transactions (WFS-T). +

+ +
+ +
+

The WFS protocol allows for creation of new features and reading, + updating, or deleting of existing features.

+

Use the tools to create, modify, and delete (in order from left + to right) features. Use the save tool (picture of a disk) to + save your changes. Use the navigation tool (hand) to stop editing + and use the mouse for map navigation.

+
+ + + + + + + + --- /dev/null +++ b/openlayers/examples/wfs-protocol.html @@ -1,1 +1,49 @@ + + + OpenLayers Vector Behavior Example + + + + + + + +

Vector Behavior Example

+

+ Uses a BBOX strategy, WFS protocol, and GML format. +

+
+
+

The vector layer shown uses the BBOX strategy, the WFS protocol, + and the GML format. The BBOX strategy fetches features within a + bounding box. When the map bounds invalidate the data bounds, + another request is triggered. The WFS protocol gets features + through a WFS request. The GML format is used to serialize + features.

+
+ + + --- /dev/null +++ b/openlayers/examples/wfs-reprojection.html @@ -1,1 +1,115 @@ + + + WFS Reprojection + Canvas Renderer Example + + + + + + + + +

WFS Reprojection + Canvas Renderer Example

+ +
+
+

+ Shows the use of the WFS layer reprojection support +

+ +
+ +
+

This example shows automatic WFS reprojection, displaying an 'unprojected' + WFS layer projected on the client side over Google Maps. The key configuration + here is the 'projection' option on the WFS layer.

+

Also shown is styleMap for the layer with unique value rules. Colors + are assigned based on the STATE_FIPS attribute.

+

Additionally, this map demonstrates the Canvas/SVG renderers in browsers + which support both. See the two different layers in the + LayerSwitcher.

+
+ + + + + + --- /dev/null +++ b/openlayers/examples/wfs-snap-split.html @@ -1,1 +1,289 @@ - + + + + + + + + + + +

Snap/Split and Persist via WFS

+ +
+
+

+ Shows snapping, splitting, and use of the WFS Transactions (WFS-T). +

+ +
+ +
+

The WFS protocol allows for creation of new features and reading, + updating, or deleting of existing features.

+

Use the tools to create, modify, and delete (in order from left + to right) features. Use the save tool (picture of a disk) to + save your changes. Use the navigation tool (hand) to stop editing + and use the mouse for map navigation.

+
+ + + + + + --- /dev/null +++ b/openlayers/examples/wfs-states.html @@ -1,1 +1,46 @@ + + + + + WFS: United States (GeoServer) + + + + +

WFS United States (GeoServer) Example

+ +
+
+ +

+ Shows the use of the WFS United States (GeoServer) +

+ +
+ +
+ + + + + +
+ --- /dev/null +++ b/openlayers/examples/wfs-t.html @@ -1,1 +1,104 @@ + + + OpenLayers: WFS-T + + + + + + + + +

WFS Transaction Example

+ +
+
+

+ Shows the use the WFS layer for transactions. +

+ +
+ +

+ This is an example of using a WFS layer type. Note that it requires a + working GeoServer install, which the OpenLayers project does not maintain; + however, if you're interested, you should be able to point this against + a default GeoServer setup without too much trouble. +

+ + + + + + + + --- /dev/null +++ b/openlayers/examples/wfs.html @@ -1,1 +1,35 @@ + + + + + + + + +

WFS Points

+

+ Using a Layer.WFS with a featureClass, one can take in XML data + from a WFS class and display it any way you like. +

+
+ + + --- /dev/null +++ b/openlayers/examples/wmc.html @@ -1,1 +1,148 @@ + + + + + + + + + + +

WMC Example

+ +
+
+

+ Shows parsing of Web Map Context documents. +

+
+ + + + +
+ This is an example of parsing WMC documents.
+ The format class has a layerOptions property, which can be used + to control the default options of the layer when it is created + by the parser. +
+ + + + + + + --- /dev/null +++ b/openlayers/examples/wms-untiled.html @@ -1,1 +1,46 @@ + + + + + + + + +

WMS Untiled Example

+ +
+
+

+ Shows an example of an "untiled" WMS layer, which requests a single + image for the entire map view. +

+
+
+ An untiled layer will only request a single image at a time. + This is equivalent to using the deprecated + OpenLayers.Layer.WMS.Untiled class, which will be removed at 3.0. +
+ + + + + --- /dev/null +++ b/openlayers/examples/wms.html @@ -1,1 +1,44 @@ + + + + + + + + +

WMS Example

+ +
+
+

+ Shows the basic use of openlayers using a WMS layer +

+ +
+ +
+ This is an example of how to add an WMS layer to the OpenLayers window. The images are tiled in this instance if you wanted to not use a tiled WMS + please use this example and pass the option ‘singleTile’ as true. +
+ + + + + + + --- /dev/null +++ b/openlayers/examples/wmst.html @@ -1,1 +1,56 @@ + + + OpenLayers: WMS + Time + + + + + + +

WMS Time Example

+
+
+

+ Shows the use of the layer WMS-T (time) layer +

+ --T::00 +
+
+ WMS-T example: update the times, and the radar image will change. Uses Layer.mergeNewParams to update the date element with the strings from the input fields every time one of them is changed. + The inputs below describe a timestamp: The minute increments can only be updated in units of 5. +
+ + + + + + --- /dev/null +++ b/openlayers/examples/worldwind.html @@ -1,1 +1,48 @@ + + + + + + + + +

WorldWind layers Example

+ +
+
+

+ Shows the use of the Tiled WorldWind layers. +

+ +
+ +
+ This is a demonstration of using Tiled WorldWind layers. WorldWind requires you to define a "LZTD" -- the 3rd param of the constructor -- and the number of zoom levels it supports. When a worldwind layer is not visible at a given tile level, and empty tile is placed there instead. Note that the maxResolution of the map times 512px, must be a multiple of a power of two different from the LZTD -- in this case, .28125 * 512 is 144, which is 36*4, and 2.25*64. + This example has a 'Bathy' layer, visible as you zoom out, and a 'landsat' layer, visible as you zoom in, both visible at zoom level 6. +
+ + + + + --- /dev/null +++ b/openlayers/examples/wrapDateLine.html @@ -1,1 +1,76 @@ + + + OpenLayers: Wrap Date Line + + + + + + +

Wrapping the Date Line

+
+ Related to: + Layer.WMS, + Layer.MapServer, + wrapDateLine +
+ +

Shows how to work around dateline issues, by wrapping the dateline on a number of layer types.

+
+
+

+ This is an example that shows wrapping the date line. Wrapping the + date line is an option on the layer. +

+

+ You can do it with a 'Layer.WMS' or a 'Layer.MapServer' layer. +

+
+    var mapserv = new OpenLayers.Layer.MapServer( "OpenLayers Basic",
+                "http://labs.metacarta.com/wms/vmap0",
+                {layers: 'basic'},
+                {wrapDateLine: true} );
+    
+
+ + + --- /dev/null +++ b/openlayers/examples/xhtml.html @@ -1,1 +1,39 @@ + + + +XHTML Example + + + + + +

XHTML Example

+
+
+

+ Shows openlayers running in a XHTML 1.0 Strict Doctype +

+ +
+ +
This example shows openlayers using a XHTML 1.0 Strict Doctype click on the below image to validate. +

+ Valid XHTML 1.0! +

+
+ + + + + + --- /dev/null +++ b/openlayers/examples/xml.html @@ -1,1 +1,156 @@ + + + + XML Parsing Example + + + + + + + + +

XML Format Example

+ +
+
+ +

+ Shows the use of the OpenLayers XML format class +

+ +
+

OpenLayers has a very simple XML format class (OpenLayers.Format.XML) + that can be used to read/write XML docs. The methods available on the + XML format (or parser if you like) allow for reading and writing of the + various XML flavors used by the library - in particular the vector data + formats. It is by no means intended to be a full-fledged XML toolset. + Additional methods will be added only as needed elsewhere in the + library.

+

This page loads an XML document and demonstrates a few of the methods + available in the parser.

+

Status: XML document loading...

+

After the XML document loads, see the result of a few of the methods + below. Assume that you start with the following code: +
+ + var format = new OpenLayers.Format.XML(); + +

+ Sample methods + + Output: +
 
+
+ + + + + --- /dev/null +++ b/openlayers/examples/xml/features.xml @@ -1,1 +1,2 @@ - + +-107.7912454726602 43.649560413854424-107.75539905577847 43.6677494686189260430N0910W27Aw2sw;WYB 0016999ABUREAU OF LAND MGMT0.0614.3Authorized310781O&g renewal lease - pdOil & gas06/5/1926Rlty rate - 5%HBPORGAS VENTURES LLC100.00.00.00.06/1/2006-107.75540341813374 43.65318043604783 -107.75540766903033 43.649560413854424 -107.76039213131902 43.64957232716459 -107.76537647481773 43.649584044882054 -107.76600694778301 43.649585553307226 -107.76600544447962 43.65320449790224 -107.76600393275089 43.65682260581091 -107.77035309969853 43.6568319555119 -107.77533746205971 43.65684246461631 -107.77533369030677 43.66046005010295 -107.78032119967183 43.66047517767307 -107.78114989067903 43.660477553258325 -107.7811491411714 43.66409732386495 -107.78530636850998 43.66411137468226 -107.78619730956676 43.664114220754314 -107.79029430779957 43.6641274142625 -107.7912454726602 43.66413046978637 -107.79124472581245 43.66774946861892 -107.79029254907311 43.667746432392896 -107.78530411910795 43.66773049422058 -107.7803154837038 43.66771429284182 -107.77532694645721 43.66769786251535 -107.77034201441859 43.66768723301139 -107.76599111151326 43.667677746482155 -107.76599928176243 43.66406177993355 -107.76600204937104 43.66044527933786 -107.76536482605789 43.660441720601554 -107.76095267723535 43.66043320984291 -107.76037976752744 43.6604312952967 -107.76038385503145 43.656811633534815 -107.75539905577847 43.65680054792165 -107.75540341813374 43.65318043604783-107.76038385503497 43.65314461898675-107.74044949722713 43.6604312953016360430N0910W34Anene;WYB 0017060ABUREAU OF LAND MGMT0.0190.0Authorized310781O&g renewal lease - pdOil & gas08/14/1929Rlty rate - 5%HBPORTEXACO EXPL & PROD INC100.00.00.00.06/1/2006-107.74605488318316 43.65994411135142 -107.74543221894442 43.659942507723265 -107.74543182097408 43.66039495347534 -107.74044949722713 43.66038434024628 -107.74045205662398 43.65676451042827 -107.74045468122058 43.65314461898675 -107.74543785843247 43.65315677493463 -107.74543483251206 43.656775865277204 -107.74792589467117 43.656782141688055 -107.7504169506792 43.65678836105594 -107.75290800688019 43.65679449548188 -107.75539905578172 43.65680054791882 -107.76038385503497 43.6568116335444 -107.76037976752876 43.66043129530163 -107.75590467181928 43.66042401057107 -107.75539470030401 43.66042058014666 -107.75539522492454 43.65996803160492 -107.75477258519014 43.65996648396323 -107.75414984843758 43.659964934969224 -107.75352723875065 43.65996338002915 -107.7529045032101 43.65996182230788 -107.7522817671415 43.65996026343829 -107.75165902657075 43.65995861118674 -107.75103641630865 43.65995704018709 -107.75041368089654 43.65995547101946 -107.74979106335141 43.659953806253434 -107.74916845036381 43.659952225696536 -107.74854571394238 43.659950645819315 -107.74792297797997 43.659949063070066 -107.74730023769644 43.65994738546058 -107.74667762361214 43.6599457929788 -107.74605488318316 43.65994411135142 --- /dev/null +++ b/openlayers/examples/xml/georss-flickr.xml @@ -1,1 +1,730 @@ - + + + + Photos from everyone tagged snowboard and powder, with geodata + http://www.flickr.com/photos/ + + Sat, 24 Nov 2007 16:25:03 -0800 + Sat, 24 Nov 2007 16:25:03 -0800 + + http://www.flickr.com/ + + http://www.flickr.com/images/buddyicon.jpg + Photos from everyone tagged snowboard and powder, with geodata + http://www.flickr.com/photos/ + + + + hofü07_5259 + http://www.flickr.com/photos/-mo-/2061136492/ + I - mo - I posted a photo:

+ +

hofü07_5259

+ + ]]>
+ Sat, 24 Nov 2007 16:25:03 -0800 + 2007-11-17T12:01:18-08:00 + nobody@flickr.com (I - mo - I) + tag:flickr.com,2004:/photo/2061136492 + 47.25162 11.754426 + + + 47.25162 + 11.754426 + + + hofü07_5259 + I - mo - I posted a photo:

+ +

hofü07_5259

+ + ]]>
+ + I - mo - I + sun snow fall fun austria yeah powder snowboard tyrol 2007 hochfügen powderahyeahturn + +
+ + First tracks + + http://www.flickr.com/photos/ruthanddave/2046899659/ + Ruth and Dave posted a photo:

+ +

First tracks

+ +

Not mine, alas.

+ ]]>
+ Mon, 19 Nov 2007 08:20:11 -0800 + 2007-11-18T11:19:50-08:00 + nobody@flickr.com (Ruth and Dave) + tag:flickr.com,2004:/photo/2046899659 + + 50.107423 -122.899847 + + 50.107423 + -122.899847 + + + First tracks + Ruth and Dave posted a photo:

+ +

First tracks

+ +

Not mine, alas.

+ ]]>
+ + Ruth and Dave + november lines whistler snowboarding tracks peak powder snowboard turns freshies + +
+ + + Powder turns + http://www.flickr.com/photos/ruthanddave/2047707124/ + Ruth and Dave posted a photo:

+ +

Powder turns

+ + ]]>
+ Mon, 19 Nov 2007 08:26:04 -0800 + 2007-11-18T11:19:57-08:00 + nobody@flickr.com (Ruth and Dave) + tag:flickr.com,2004:/photo/2047707124 + 50.107423 -122.899847 + + + 50.107423 + -122.899847 + + + Powder turns + Ruth and Dave posted a photo:

+ +

Powder turns

+ + ]]>
+ + Ruth and Dave + november face lines whistler snowboarding tracks first peak powder fresh snowboard turns + +
+ + Splitboard + + http://www.flickr.com/photos/hamsgod/1330385819/ + @nt!x posted a photo:

+ +

Splitboard

+ + ]]>
+ Wed, 5 Sep 2007 09:00:56 -0800 + 2007-03-04T04:40:12-08:00 + nobody@flickr.com (@nt!x) + tag:flickr.com,2004:/photo/1330385819 + 41.931831 -111.646049 + + + 41.931831 + -111.646049 + + + Splitboard + @nt!x posted a photo:

+ +

Splitboard

+ + ]]>
+ + @nt!x + snow mountains snowboarding utah powder yurt snowboard splitboard supershot + +
+ + Fresh powder + + http://www.flickr.com/photos/oliver80/1266854486/ + Oliver Astrologo posted a photo:

+ +

Fresh powder

+ + ]]>
+ Wed, 29 Aug 2007 05:19:36 -0800 + 2007-08-24T10:05:48-08:00 + nobody@flickr.com (Oliver Astrologo) + tag:flickr.com,2004:/photo/1266854486 + 46.528236 10.449789 + + + 46.528236 + 10.449789 + + + Fresh powder + Oliver Astrologo posted a photo:

+ +

Fresh powder

+ + ]]>
+ + Oliver Astrologo + sky people italy panorama mountain snow alps landscape powder snowboard rider snowboarder passo ghiacciaio stelvio + +
+ + Sawatch Range From Vail Pano + + http://www.flickr.com/photos/dman861/498747983/ + dman861 posted a photo:

+ +

Sawatch Range From Vail Pano

+ +

View near the Eagle's Nest at the top of Eagle Bahn Gondola. Mount of the Holy Cross mountain is visible in this picture but you can't make out the cross shape. I have no idea who the lady is. Handheld panorama, stitch of 5 + photos.

+ ]]>
+ Mon, 14 May 2007 17:05:11 -0800 + 2007-05-14T15:00:27-08:00 + nobody@flickr.com (dman861) + tag:flickr.com,2004:/photo/498747983 + + 39.617598 -106.386773 + + 39.617598 + -106.386773 + + + Sawatch Range From Vail Pano + dman861 posted a photo:

+ +

Sawatch Range From Vail Pano

+ +

View near the Eagle's Nest at the top of Eagle Bahn Gondola. Mount of the Holy Cross mountain is visible in this picture but you can't make out the cross shape. I have no idea who the lady is. Handheld panorama, stitch of 5 + photos.

+ ]]>
+ + dman861 + + trees sky panorama mountain snow ski mountains sport pinetree person photo colorado peak wideangle panoramic powder resort panasonic trail vail snowboard gondola beavercreek range avon mountainrange mountoftheholycross sawatch dmctz3 tz3 + + +
+ + + Flash Powder + http://www.flickr.com/photos/eckan/488776800/ + Erik Eckerström posted a photo:

+ +

Flash Powder

+ +

Some late night powder in Ã…re

+ ]]>
+ Mon, 7 May 2007 13:17:48 -0800 + 2007-01-18T15:37:17-08:00 + nobody@flickr.com (Erik Eckerström) + tag:flickr.com,2004:/photo/488776800 + + 63.409238 13.079953 + + 63.409238 + 13.079953 + + + Flash Powder + Erik Eckerström posted a photo:

+ +

Flash Powder

+ +

Some late night powder in Ã…re

+ ]]>
+ + Erik Eckerström + snow forest canon eos board flash powder snowboard portfolio Åre 30d canon30d canoneos30d are + +
+ + + Hannes and Reini + http://www.flickr.com/photos/moschitz/483974969/ + martinom posted a photo:

+ +

Hannes and Reini

+ + ]]>
+ Fri, 4 May 2007 08:56:02 -0800 + 2007-03-26T12:17:53-08:00 + nobody@flickr.com (martinom) + tag:flickr.com,2004:/photo/483974969 + 47.252086 14.365139 + + + 47.252086 + 14.365139 + + + Hannes and Reini + martinom posted a photo:

+ +

Hannes and Reini

+ + ]]>
+ + martinom + powder snowboard autoupload obertauern lachtal + +
+ + Zehnerkar, Obertauern + + http://www.flickr.com/photos/moschitz/483975805/ + martinom posted a photo:

+ +

Zehnerkar, Obertauern

+ + ]]>
+ Fri, 4 May 2007 08:56:46 -0800 + 2007-03-21T15:56:56-08:00 + nobody@flickr.com (martinom) + tag:flickr.com,2004:/photo/483975805 + 47.249698 13.557643 + + + 47.249698 + 13.557643 + + + Zehnerkar, Obertauern + martinom posted a photo:

+ +

Zehnerkar, Obertauern

+ + ]]>
+ + martinom + powder snowboard autoupload obertauern lachtal + +
+ + Uhh, thats high + + http://www.flickr.com/photos/moschitz/483941044/ + martinom posted a photo:

+ +

Uhh, thats high

+ + ]]>
+ Fri, 4 May 2007 08:55:25 -0800 + 2007-03-23T11:38:54-08:00 + nobody@flickr.com (martinom) + tag:flickr.com,2004:/photo/483941044 + 47.258669 14.359989 + + + 47.258669 + 14.359989 + + + Uhh, thats high + martinom posted a photo:

+ +

Uhh, thats high

+ + ]]>
+ + martinom + powder snowboard autoupload obertauern lachtal + +
+ + Powder days + + http://www.flickr.com/photos/moschitz/483973863/ + martinom posted a photo:

+ +

Powder days

+ + ]]>
+ Fri, 4 May 2007 08:55:02 -0800 + 2007-03-26T11:25:01-08:00 + nobody@flickr.com (martinom) + tag:flickr.com,2004:/photo/483973863 + 47.258669 14.359989 + + + 47.258669 + 14.359989 + + + Powder days + martinom posted a photo:

+ +

Powder days

+ + ]]>
+ + martinom + powder snowboard autoupload obertauern lachtal + +
+ + Getting ready + + http://www.flickr.com/photos/moschitz/483940792/ + martinom posted a photo:

+ +

Getting ready

+ + ]]>
+ Fri, 4 May 2007 08:55:14 -0800 + 2007-03-23T11:38:53-08:00 + nobody@flickr.com (martinom) + tag:flickr.com,2004:/photo/483940792 + 47.258669 14.359989 + + + 47.258669 + 14.359989 + + + Getting ready + martinom posted a photo:

+ +

Getting ready

+ + ]]>
+ + martinom + powder snowboard autoupload obertauern lachtal + +
+ + Going up and getting ready for the descent + + http://www.flickr.com/photos/moschitz/483941528/ + martinom posted a photo:

+ +

Going up and getting ready for the descent

+ + ]]>
+ Fri, 4 May 2007 08:55:50 -0800 + 2007-03-26T12:17:17-08:00 + nobody@flickr.com (martinom) + tag:flickr.com,2004:/photo/483941528 + 47.258669 14.359989 + + + 47.258669 + 14.359989 + + + Going up and getting ready for the descent + martinom posted a photo:

+ +

Going up and getting ready for the descent

+ + ]]>
+ + martinom + powder snowboard autoupload obertauern lachtal + +
+ + I made it! + + http://www.flickr.com/photos/moschitz/483941314/ + martinom posted a photo:

+ +

I made it!

+ + ]]>
+ Fri, 4 May 2007 08:55:39 -0800 + 2007-03-23T11:38:54-08:00 + nobody@flickr.com (martinom) + tag:flickr.com,2004:/photo/483941314 + 47.258669 14.359989 + + + 47.258669 + 14.359989 + + + I made it! + martinom posted a photo:

+ +

I made it!

+ + ]]>
+ + martinom + powder snowboard autoupload obertauern lachtal + +
+ + Reini and me + + http://www.flickr.com/photos/moschitz/483942166/ + martinom posted a photo:

+ +

Reini and me

+ + ]]>
+ Fri, 4 May 2007 08:56:24 -0800 + 2007-03-21T15:45:38-08:00 + nobody@flickr.com (martinom) + tag:flickr.com,2004:/photo/483942166 + 47.249698 13.557643 + + + 47.249698 + 13.557643 + + + Reini and me + martinom posted a photo:

+ +

Reini and me

+ + ]]>
+ + martinom + powder snowboard autoupload obertauern lachtal + +
+ + Obertauern + + http://www.flickr.com/photos/moschitz/483976145/ + martinom posted a photo:

+ +

Obertauern

+ + ]]>
+ Fri, 4 May 2007 08:57:04 -0800 + 2007-03-21T15:48:01-08:00 + nobody@flickr.com (martinom) + tag:flickr.com,2004:/photo/483976145 + 47.249698 13.557643 + + + 47.249698 + 13.557643 + + + Obertauern + martinom posted a photo:

+ +

Obertauern

+ + ]]>
+ + martinom + powder snowboard autoupload obertauern lachtal + +
+ + Ross Fall Line 2 + + http://www.flickr.com/photos/pwadsworth/480290173/ + phwadsworth posted a photo:

+ +

Ross Fall Line 2

+ + ]]>
+ Tue, 1 May 2007 12:02:23 -0800 + 2007-05-01T15:02:23-08:00 + nobody@flickr.com (phwadsworth) + tag:flickr.com,2004:/photo/480290173 + 44.196728 -72.926688 + + + 44.196728 + -72.926688 + + + Ross Fall Line 2 + phwadsworth posted a photo:

+ +

Ross Fall Line 2

+ + ]]>
+ + phwadsworth + ski river geotagged powder glen snowboard mad vt mrg vemont + +
+ + Ross Fall Line 1 + + http://www.flickr.com/photos/pwadsworth/480290169/ + phwadsworth posted a photo:

+ +

Ross Fall Line 1

+ + ]]>
+ Tue, 1 May 2007 12:02:23 -0800 + 2007-05-01T15:02:23-08:00 + nobody@flickr.com (phwadsworth) + tag:flickr.com,2004:/photo/480290169 + 44.196728 -72.926688 + + + 44.196728 + -72.926688 + + + Ross Fall Line 1 + phwadsworth posted a photo:

+ +

Ross Fall Line 1

+ + ]]>
+ + phwadsworth + ski river geotagged powder glen snowboard mad vt mrg vemont + +
+ + IMGP0575 + + http://www.flickr.com/photos/beppoegeppa/471446918/ + beppovox posted a photo:

+ +

IMGP0575

+ + ]]>
+ Tue, 24 Apr 2007 10:32:41 -0800 + 2003-12-29T11:07:29-08:00 + nobody@flickr.com (beppovox) + tag:flickr.com,2004:/photo/471446918 + 45.082308 6.761913 + + + 45.082308 + 6.761913 + + + IMGP0575 + beppovox posted a photo:

+ +

IMGP0575

+ + ]]>
+ + beppovox + geotagged free powder snowboard freeride jafferau + +
+ + Slopes + + http://www.flickr.com/photos/blupic/469735045/ + blupic.com posted a photo:

+ +

Slopes

+ +

One day...

+ ]]>
+ Mon, 23 Apr 2007 02:48:17 -0800 + 2007-03-31T17:31:13-08:00 + nobody@flickr.com (blupic.com) + tag:flickr.com,2004:/photo/469735045 + + 50.111882 -122.939436 + + 50.111882 + -122.939436 + + + Slopes + blupic.com posted a photo:

+ +

Slopes

+ +

One day...

+ ]]>
+ + blupic.com + blue winter sky mountain snow canada ski nature vancouver whistler nikon skiing d70 peak columbia powder 1870mmf3545g snowboard british blackcomb mogul + +
+ +
+
--- /dev/null +++ b/openlayers/examples/xml/track1.xml @@ -1,1 +1,99 @@ + + +Title for First trial track +Description for first track +Nelson +1995-12-12T05:00:00Z22.1862861120.30211944Phase ChangeStart Phase A +1995-12-12T05:05:00Z22.1862194420.28514722 +1995-12-12T05:10:00Z22.1860972220.266425 +1995-12-12T05:15:00Z22.1827972220.24935 +1995-12-12T05:16:00Z22.1815388920.24605556Course Change220 degs 4kts +1995-12-12T05:20:00Z22.176420.23299444 +1995-12-12T05:25:00Z22.1699611120.21654167 +1995-12-12T05:30:00Z22.1635194420.20017222 +1995-12-12T05:35:00Z22.1571055620.18375278 +1995-12-12T05:40:00Z22.1506277820.16738889 +1995-12-12T05:42:00Z22.1480333320.16077222CommentXO has bridge +1995-12-12T05:45:00Z22.1441638920.15091944 +1995-12-12T05:50:00Z22.1375777820.13483333 +1995-12-12T05:55:00Z22.1254166720.134125 +1995-12-12T06:00:00Z22.1125055620.13402778 +1995-12-12T06:05:00Z22.0996027820.13395 +1995-12-12T06:10:00Z22.0881222220.14054722Comment +1995-12-12T06:13:00Z22.0814138920.14618333Course Change220 degs 4kts +1995-12-12T06:15:00Z22.0769527820.14992778 +1995-12-12T06:20:00Z22.0658416720.15931111 +1995-12-12T06:25:00Z22.0546027820.16871944 +1995-12-12T06:30:00Z22.0431583320.16791667 +1995-12-12T06:35:00Z22.0311861120.16143056 +1995-12-12T06:40:00Z22.0191222220.15486389 +1995-12-12T06:45:00Z22.0070833320.14833056 +1995-12-12T06:50:00Z21.9950444420.14181111 +1995-12-12T06:55:00Z21.9842388920.14733611 +1995-12-12T07:00:00Z21.9736722220.15803333 +1995-12-12T07:05:00Z21.9630611120.16874444 +1995-12-12T07:10:00Z21.9540722220.17233611 +1995-12-12T07:15:00Z21.9588555620.15766944 +1995-12-12T07:20:00Z21.9663083320.16956944 +1995-12-12T07:25:00Z21.9745055620.18388056 +1995-12-12T07:30:00Z21.9827638920.19838333 +1995-12-12T07:32:00Z21.9860527820.20413056CommentSuspect opponent to North, slowing down +1995-12-12T07:33:00Z21.9869166720.20727778CommentVIP visitors due. Helo retrieved. +1995-12-12T07:34:00Z21.9856694420.20985556CommentWind picked up. Switching off sensor Delta +1995-12-12T07:35:00Z21.984220.21243611CommentHeavenly dusk +1995-12-12T07:40:00Z21.9760944420.226425 +1995-12-12T07:45:00Z21.9678611120.240725 +1995-12-12T07:50:00Z21.959520.25507778 +1995-12-12T07:55:00Z21.9511805620.26941389 +1995-12-12T08:00:00Z21.9486277820.28483056 +1995-12-12T08:05:00Z21.9529520.30239444 +1995-12-12T08:10:00Z21.95732520.32020278 +1995-12-12T08:15:00Z21.9617222220.33795278 +1995-12-12T08:20:00Z21.9661611120.35568611 +1995-12-12T08:25:00Z21.9635555620.371925 +1995-12-12T08:30:00Z21.9587777820.38858333 +1995-12-12T08:35:00Z21.9598888920.40708333 +1995-12-12T08:40:00Z21.9636194420.41884722 +1995-12-12T08:45:00Z21.9694555620.40425278Course Change220 degs 4kts +1995-12-12T08:50:00Z21.975920.38807778 +1995-12-12T08:55:00Z21.9823722220.37183889 +1995-12-12T09:00:00Z21.9888027820.35546667 +1995-12-12T09:05:00Z21.9996861120.34794722 +1995-12-12T09:10:00Z22.0117916720.34156389 +1995-12-12T09:15:00Z22.0216861120.34614444 +1995-12-12T09:20:00Z22.0281111120.36220833 +1995-12-12T09:25:00Z22.0345611120.37856389Phase ChangeEnd Phase A +1995-12-12T09:30:00Z22.0414520.36952222 +1995-12-12T09:35:00Z22.0478222220.35348611 +1995-12-12T09:40:00Z22.0542638920.33713056 +1995-12-12T09:45:00Z22.0606944420.32085 +1995-12-12T09:46:00Z22.0619861120.31761111Phase ChangeStart Phase B +1995-12-12T09:50:00Z22.0647194420.30389444 +1995-12-12T09:55:00Z22.0758666720.29758333 +1995-12-12T10:00:00Z22.0879388920.29113889 +1995-12-12T10:05:00Z22.1000333320.2848 +1995-12-12T10:10:00Z22.1121972220.27842222 +1995-12-12T10:15:00Z22.1153055620.26194167 +1995-12-12T10:20:00Z22.1175694420.24336667 +1995-12-12T10:25:00Z22.1198611120.22466944 +1995-12-12T10:30:00Z22.1223833320.20620833 +1995-12-12T10:35:00Z22.1289027820.216475 +1995-12-12T10:40:00Z22.1235194420.22643333 +1995-12-12T10:45:00Z22.1209972220.23426667 +1995-12-12T10:50:00Z22.1219416720.23018333 +1995-12-12T10:55:00Z22.1187277820.23548056 +1995-12-12T11:00:00Z22.1199416720.23541111 +1995-12-12T11:05:00Z22.1199277820.23948056 +1995-12-12T11:10:00Z22.1180222220.24473889 +1995-12-12T11:15:00Z22.1176444420.24835278 +1995-12-12T11:20:00Z22.1221555620.24788889 +1995-12-12T11:25:00Z22.1172527820.25047778 +1995-12-12T11:30:00Z22.1225972220.24290278 +1995-12-12T11:35:00Z22.1292194420.24653889 +1995-12-12T11:40:00Z22.1397083320.25221389 +1995-12-12T11:41:00Z22.1417388920.25330556Phase ChangeEnd Phase B + + + + --- /dev/null +++ b/openlayers/examples/xml/wmsdescribelayer.xml @@ -1,1 +1,6 @@ + + + + + --- /dev/null +++ b/openlayers/examples/xyz-esri.html @@ -1,1 +1,35 @@ + + + OpenLayers Basic ESRI Map Cache Example + + + + + + +

Basic ESRI Map Cache Example

+
+ +
Show a Simple ESRI map using the layer from ESRI's server.
+ +
+ +
+ Show the use of the XYZ layer to access a map cache provided in spherical mercator by ESRI. +
+ + + --- /dev/null +++ b/openlayers/examples/yahoo.html @@ -1,1 +1,47 @@ + + + OpenLayers: Yahoo Layer + + + + + + + +

Yahoo Base Layer Example

+ +
+
+ +

+ Shows how you would add a yahoo layer and add the LayerSwitcher control +

+ +
+ +
+ This is an example of how to add a yahoo layer to the OpenLayers window. In order to enable a + yahoo layer. Also shown in this example is the LayerSwitcher() control for toggling between both the yahoo layer and + the open layer WMS. +
+ + + + --- /dev/null +++ b/openlayers/examples/yelp-georss.xml @@ -1,1 +1,148 @@ + + + + Copyright 2007 Yelp, Inc. All rights reserved. + Yelp - Recent Reviews Near Ann Arbor, MI + + 2007-05-29T22:58:24-08:00 + + Recent Reviews Near Ann Arbor, MI + + + Sam S.'s Review Of State Theatre - Ann Arbor (4/5) + + http://www.yelp.com/biz/r-dZCCNtld2ik0QRoTwuUQ?hrid=pve0e49KrVsvgX_wXpZHYA + 2007-05-29T22:58:24-08:00 + I gotta give this place props for hosting independent movies. Man, I really tire of that Hollywood bullshit; I actually feel retarded afterwards.<br /><br />My main gripe is that the seats are not ergonomic at&#8230; + -83.7406005859 + 42.2790985107 + + + Sean M.'s Review Of Maize N Blue Deli - Ann Arbor (5/5) + + http://www.yelp.com/biz/GqqBWV8ysYB48_jLPMl2bA?hrid=ZZIVErEJIo-FMsxOBVX1GQ + 2007-05-29T16:04:19-08:00 + Maize N Blue is synonymous with huge sandwiches (lunch+dinner sized) and good but not showy ingredients. It's a much better value than certain other Ann Arbor delis, and eschews the boutique feel that&#8230; + -83.7255020142 + 42.266998291 + + + Coco C.'s Review Of Tom Thompson Flowers - Ann Arbor (5/5) + + http://www.yelp.com/biz/B_lu4c0HgyHlxOjdKxTW7w?hrid=KNa-bOGA0xltz6bHEu3-AQ + 2007-05-29T10:49:06-08:00 + This place isn't much to look at - you go in and there are buckets upon buckets of loose flowers laying around, and there isn't much room to walk around. However, they make kick-ass arrangements! And&#8230; + -83.7490005493 + 42.2747993469 + + + Sam S.'s Review Of Mitch's Place - Ann Arbor (3/5) + + http://www.yelp.com/biz/zlJ37GVxuy-9wfvWqpPm1Q?hrid=qK6d7BOgLoK3gLf2u2OXAQ + 2007-05-28T21:28:33-08:00 + Another generic sports bar with the generic stripe-o crowd. Cheap pitchers, but constant covers.<br /><br />This is where I met Dave, a.k.a. Future Guy. He's skinny, has tall, straggly hair, and wears these&#8230; + -83.7329025269 + 42.275100708 + + + Jane S.'s Review Of Special Moments Photography - Plymouth (1/5) + + http://www.yelp.com/biz/IEsTnmfhN7vFfzt724qojw?hrid=ZvALNYOlSodoOciFsmeLZA + 2007-05-28T10:18:35-08:00 + I was very unsatisfied for several reasons. First, the photographer did not have the lighting for family portraits right at all! Two faces are almost completely obscured by shadows. Also, the backdrop&#8230; + -83.4609985352 + 42.3588981628 + + + Jacqueline D.'s Review Of Embassy Hotel - Ann Arbor (4/5) + + http://www.yelp.com/biz/Vz0hW6UhF6w4JuvgsqDmMQ?hrid=eMKcIyREDk-pFGdH0EC6jg + 2007-05-27T09:23:06-08:00 + The Embassy Hotel does what it does well: It is VERY cheap ($40-50/night) and located in the heart of downtown. It is relatively clean. What to expect: Your bed won't be made, your sheets won't be&#8230; + -83.7469024658 + 42.2812004089 + + + Tony C.'s Review Of Cafe Zola - Ann Arbor (5/5) + + http://www.yelp.com/biz/BlUEgCOGzDwpOWnkQn3odw?hrid=X3rR29JnKIqAjEzo9nC2VQ + 2007-05-26T07:48:01-08:00 + Zola...this is easily one of the best experiences to be had in Ann Arbor. I'll get the negatives for this place right out in the open. It's popular, really popular. It's expensive, but not break&#8230; + -83.7489013672 + 42.2806015015 + + + Tony C.'s Review Of Melange - Ann Arbor (4/5) + + http://www.yelp.com/biz/aPdz29vOWj4fBlLlCBM7UQ?hrid=ETs0aMnHTjSLy1VTsGxCBg + 2007-05-26T07:30:59-08:00 + Melange has an excellent menu. I've tried the scallops, the perch, and the seabass. All were excellent. The two dishes I'd steer clear of are the rock beef thing and the squid appetizers.<br /><br />The rock&#8230; + -83.7481918335 + 42.283493042 + + + Tony C.'s Review Of Bennett Optometry - Ann Arbor (4/5) + + http://www.yelp.com/biz/Oa5c1Zzr6RlkGjx-0KYr1A?hrid=vUiuwPLri6D5LTPrC76UlA + 2007-05-26T07:09:43-08:00 + I just got my eyes checked out here about two months ago and overall, I was satisfied with my experience. The optometrist I got was young, but very knowledgeable and didn't seem to be in a hurry to&#8230; + -83.6924972534 + 42.3031005859 + + + Nedra B.'s Review Of Star's Cafe - Ann Arbor (4/5) + + http://www.yelp.com/biz/aNeaXyQWZ0LGH3FoNnYzmA?hrid=XUd-5ehybDuujOTinekhWA + 2007-05-26T02:53:51-08:00 + As you walk in, you hear arabic music playing the background, and about 4 or 5 tables in front of the main window. You walk up to the menu, choose from many middle eastern foods, give your order, and&#8230; + -83.782539 + 42.281079 + + + Nedra B.'s Review Of Vinology - Ann Arbor (3/5) + + http://www.yelp.com/biz/qkw4xWWgTufvBs1NcxsFnw?hrid=lFMHzQ0GwJ93Ns0kCoPWPQ + 2007-05-26T02:45:15-08:00 + I knew to expect a pricey "high class" restaurant... but in my opinion they went over the top. I went with my sister and some friends, and she ordered the little "mini burgers" and I ordered the only&#8230; + -83.7487030029 + 42.2812004089 + + + Coco C.'s Review Of Eastern Accents - Ann Arbor (4/5) + + http://www.yelp.com/biz/AztY39QdGAkoKrLy9Di2yw?hrid=HL2_XnWdZP1FO95e1q-Xjw + 2007-05-25T15:43:30-08:00 + Oooh baby, this is my dream food come to life - sweet buns with tasty meat inside of them. I'm already a big fan of WowBao in Chicago, so to find this place in Ann Arbor makes me living here a&#8230; + -83.7472991943 + 42.2803001404 + + + Liam C.'s Review Of The Dartmoor - Plymouth (1/5) + + http://www.yelp.com/biz/Q32V2uxVKKv8_fp-hGxyZQ?hrid=7zFDEW0THJGYTUPPHLZucQ + 2007-05-24T14:36:00-08:00 + Okay, so here is how fawked up it is in Plymouth.<br /><br />This place was a hotel, back in the day. Dude converts it to offices and retail, and leases the bar and restaurant out to someone else.<br /><br />the tenant gets&#8230; + -83.4705963135 + 42.3686981201 + + + Liam C.'s Review Of Omelette & Waffle Cafe - Plymouth (2/5) + + http://www.yelp.com/biz/a09i0TiPG4_yhl-fnzPzDA?hrid=I4ENTNhbAK8pLP_qJmivTQ + 2007-05-24T14:32:05-08:00 + Who does this guy pay off? Every year, he wins the Chii cook off but when you order the chili here in the restaurant, it's Hormel right out of a can!<br /><br />tired and uninspired boring diner egg dishes,&#8230; + -83.4709014893 + 42.3681983948 + + + Liam C.'s Review Of Zack's of Plymouth - Plymouth (2/5) + + http://www.yelp.com/biz/F0iuCrXUd_fEJ-LZh8wMFw?hrid=2VLIUcC_oI8b_aLTMjYx8w + 2007-05-24T14:30:12-08:00 + If you like greasy diners with vaguely ethnic workers yelling in orgy-borgy talk back int he kitchen... you're gonna love this place.<br /><br />It's cleaner than it's competitors....gotta give them props on&#8230; + -83.4692993164 + 42.3581008911 + + + --- /dev/null +++ b/openlayers/examples/zoomLevels.html @@ -1,1 +1,77 @@ + + + + + + + + +

Zoom Level

+ +
+
+ +

+ This example shows the use of the resolutions layer option on a number of layer types. +

+ +
+ +
+

+ Set the extent of the viewable map using preset levels of scale available + to the user via the zoom slider bar. You can set the minimum, maximum + scales or resolutions, the number of levels in between and the minimum + and maximum geographic extents in your map's units. +

+

+ Default units for Scale are in inches. Resolution is specified in map units + per pixel where the default map units are decimal degrees(dd).
+ scale = resolution * OpenLayers.INCHES_PER_UNIT[units] * + OpenLayers.DOTS_PER_INCH
+ You can either set the scale or the resolution, there is no need to set both. +

+

+ You can do it with a ... +

+
+ + + + --- /dev/null +++ b/openlayers/lib/Firebug/firebug.css @@ -1,1 +1,210 @@ +html, body { + margin: 0; + background: #FFFFFF; + font-family: Lucida Grande, Tahoma, sans-serif; + font-size: 11px; + overflow: hidden; +} + +a { + text-decoration: none; +} + +a:hover { + text-decoration: underline; +} + +.toolbar { + height: 14px; + border-top: 1px solid ThreeDHighlight; + border-bottom: 1px solid ThreeDShadow; + padding: 2px 6px; + background: ThreeDFace; +} + +.toolbarRight { + position: absolute; + top: 4px; + right: 6px; +} + +#log { + overflow: auto; + position: absolute; + left: 0; + width: 100%; +} + +#commandLine { + position: absolute; + bottom: 0; + left: 0; + width: 100%; + height: 18px; + border: none; + border-top: 1px solid ThreeDShadow; +} + +/************************************************************************************************/ + +.logRow { + position: relative; + border-bottom: 1px solid #D7D7D7; + padding: 2px 4px 1px 6px; + background-color: #FFFFFF; +} + +.logRow-command { + font-family: Monaco, monospace; + color: blue; +} + +.objectBox-null { + padding: 0 2px; + border: 1px solid #666666; + background-color: #888888; + color: #FFFFFF; +} + +.objectBox-string { + font-family: Monaco, monospace; + color: red; + white-space: pre; +} + +.objectBox-number { + color: #000088; +} + +.objectBox-function { + font-family: Monaco, monospace; + color: DarkGreen; +} + +.objectBox-object { + color: DarkGreen; + font-weight: bold; +} + +/************************************************************************************************/ + +.logRow-info, +.logRow-error, +.logRow-warning { + background: #FFFFFF no-repeat 2px 2px; + padding-left: 20px; + padding-bottom: 3px; +} + +.logRow-info { + background-image: url(infoIcon.png); +} + +.logRow-warning { + background-color: cyan; + background-image: url(warningIcon.png); +} + +.logRow-error { + background-color: LightYellow; + background-image: url(errorIcon.png); +} + +.errorMessage { + vertical-align: top; + color: #FF0000; +} + +.objectBox-sourceLink { + position: absolute; + right: 4px; + top: 2px; + padding-left: 8px; + font-family: Lucida Grande, sans-serif; + font-weight: bold; + color: #0000FF; +} + +/************************************************************************************************/ + +.logRow-group { + background: #EEEEEE; + border-bottom: none; +} + +.logGroup { + background: #EEEEEE; +} + +.logGroupBox { + margin-left: 24px; + border-top: 1px solid #D7D7D7; + border-left: 1px solid #D7D7D7; +} + +/************************************************************************************************/ + +.selectorTag, +.selectorId, +.selectorClass { + font-family: Monaco, monospace; + font-weight: normal; +} + +.selectorTag { + color: #0000FF; +} + +.selectorId { + color: DarkBlue; +} + +.selectorClass { + color: red; +} + +/************************************************************************************************/ + +.objectBox-element { + font-family: Monaco, monospace; + color: #000088; +} + +.nodeChildren { + margin-left: 16px; +} + +.nodeTag { + color: blue; +} + +.nodeValue { + color: #FF0000; + font-weight: normal; +} + +.nodeText, +.nodeComment { + margin: 0 2px; + vertical-align: top; +} + +.nodeText { + color: #333333; +} + +.nodeComment { + color: DarkGreen; +} + +/************************************************************************************************/ + +.propertyNameCell { + vertical-align: top; +} + +.propertyName { + font-weight: bold; +} + --- /dev/null +++ b/openlayers/lib/Firebug/firebug.html @@ -1,1 +1,24 @@ + + + + + Firebug + + + + +
+ Clear + + Close + +
+
+ + + + + + --- /dev/null +++ b/openlayers/lib/Firebug/firebug.js @@ -1,1 +1,675 @@ +if (!window.console || !console.firebug) { (function() +{ + window.console = + { + log: function() + { + logFormatted(arguments, ""); + }, + + debug: function() + { + logFormatted(arguments, "debug"); + }, + + info: function() + { + logFormatted(arguments, "info"); + }, + + warn: function() + { + logFormatted(arguments, "warning"); + }, + + error: function() + { + logFormatted(arguments, "error"); + }, + + assert: function(truth, message) + { + if (!truth) + { + var args = []; + for (var i = 1; i < arguments.length; ++i) + args.push(arguments[i]); + + logFormatted(args.length ? args : ["Assertion Failure"], "error"); + throw message ? message : "Assertion Failure"; + } + }, + + dir: function(object) + { + var html = []; + + var pairs = []; + for (var name in object) + { + try + { + pairs.push([name, object[name]]); + } + catch (exc) + { + } + } + + pairs.sort(function(a, b) { return a[0] < b[0] ? -1 : 1; }); + + html.push(''); + for (var i = 0; i < pairs.length; ++i) + { + var name = pairs[i][0], value = pairs[i][1]; + + html.push('', + '', ''); + } + html.push('
', + escapeHTML(name), ''); + appendObject(value, html); + html.push('
'); + + logRow(html, "dir"); + }, + + dirxml: function(node) + { + var html = []; + + appendNode(node, html); + logRow(html, "dirxml"); + }, + + group: function() + { + logRow(arguments, "group", pushGroup); + }, + + groupEnd: function() + { + logRow(arguments, "", popGroup); + }, + + time: function(name) + { + timeMap[name] = (new Date()).getTime(); + }, + + timeEnd: function(name) + { + if (name in timeMap) + { + var delta = (new Date()).getTime() - timeMap[name]; + logFormatted([name+ ":", delta+"ms"]); + delete timeMap[name]; + } + }, + + count: function() + { + this.warn(["count() not supported."]); + }, + + trace: function() + { + this.warn(["trace() not supported."]); + }, + + profile: function() + { + this.warn(["profile() not supported."]); + }, + + profileEnd: function() + { + }, + + clear: function() + { + consoleBody.innerHTML = ""; + }, + + open: function() + { + toggleConsole(true); + }, + + close: function() + { + if (frameVisible) + toggleConsole(); + } + }; + + // ******************************************************************************************** + + var consoleFrame = null; + var consoleBody = null; + var commandLine = null; + + var frameVisible = false; + var messageQueue = []; + var groupStack = []; + var timeMap = {}; + + var clPrefix = ">>> "; + + var isFirefox = navigator.userAgent.indexOf("Firefox") != -1; + var isIE = navigator.userAgent.indexOf("MSIE") != -1; + var isOpera = navigator.userAgent.indexOf("Opera") != -1; + var isSafari = navigator.userAgent.indexOf("AppleWebKit") != -1; + + // ******************************************************************************************** + + function toggleConsole(forceOpen) + { + frameVisible = forceOpen || !frameVisible; + if (consoleFrame) + consoleFrame.style.visibility = frameVisible ? "visible" : "hidden"; + else + waitForBody(); + } + + function focusCommandLine() + { + toggleConsole(true); + if (commandLine) + commandLine.focus(); + } + + function waitForBody() + { + if (document.body) + createFrame(); + else + setTimeout(waitForBody, 200); + } + + function createFrame() + { + if (consoleFrame) + return; + + window.onFirebugReady = function(doc) + { + window.onFirebugReady = null; + + var toolbar = doc.getElementById("toolbar"); + toolbar.onmousedown = onSplitterMouseDown; + + commandLine = doc.getElementById("commandLine"); + addEvent(commandLine, "keydown", onCommandLineKeyDown); + + addEvent(doc, isIE || isSafari ? "keydown" : "keypress", onKeyDown); + + consoleBody = doc.getElementById("log"); + layout(); + flush(); + } + + var baseURL = getFirebugURL(); + + consoleFrame = document.createElement("iframe"); + consoleFrame.setAttribute("src", baseURL+"/firebug.html"); + consoleFrame.setAttribute("frameBorder", "0"); + consoleFrame.style.visibility = (frameVisible ? "visible" : "hidden"); + consoleFrame.style.zIndex = "2147483583"; + consoleFrame.style.position = document.all ? "absolute" : "fixed"; + consoleFrame.style.width = "100%"; + consoleFrame.style.left = "0"; + consoleFrame.style.bottom = "0"; + consoleFrame.style.height = "200px"; + document.body.appendChild(consoleFrame); + } + + function getFirebugURL() + { + var scripts = document.getElementsByTagName("script"); + for (var i = 0; i < scripts.length; ++i) + { + if (scripts[i].src.indexOf("firebug.js") != -1) + { + var lastSlash = scripts[i].src.lastIndexOf("/"); + return scripts[i].src.substr(0, lastSlash); + } + } + } + + function evalCommandLine() + { + var text = commandLine.value; + commandLine.value = ""; + + logRow([clPrefix, text], "command"); + + var value; + try + { + value = eval(text); + } + catch (exc) + { + } + + console.log(value); + } + + function layout() + { + var toolbar = consoleBody.ownerDocument.getElementById("toolbar"); + var height = consoleFrame.offsetHeight - (toolbar.offsetHeight + commandLine.offsetHeight); + height = Math.max(height, 0); + consoleBody.style.top = toolbar.offsetHeight + "px"; + consoleBody.style.height = height + "px"; + + commandLine.style.top = (consoleFrame.offsetHeight - commandLine.offsetHeight) + "px"; + } + + function logRow(message, className, handler) + { + if (consoleBody) + writeMessage(message, className, handler); + else + { + messageQueue.push([message, className, handler]); + waitForBody(); + } + } + + function flush() + { + var queue = messageQueue; + messageQueue = []; + + for (var i = 0; i < queue.length; ++i) + writeMessage(queue[i][0], queue[i][1], queue[i][2]); + } + + function writeMessage(message, className, handler) + { + var isScrolledToBottom = + consoleBody.scrollTop + consoleBody.offsetHeight >= consoleBody.scrollHeight; + + if (!handler) + handler = writeRow; + + handler(message, className); + + if (isScrolledToBottom) + consoleBody.scrollTop = consoleBody.scrollHeight - consoleBody.offsetHeight; + } + + function appendRow(row) + { + var container = groupStack.length ? groupStack[groupStack.length-1] : consoleBody; + container.appendChild(row); + } + + function writeRow(message, className) + { + var row = consoleBody.ownerDocument.createElement("div"); + row.className = "logRow" + (className ? " logRow-"+className : ""); + row.innerHTML = message.join(""); + appendRow(row); + } + + function pushGroup(message, className) + { + logFormatted(message, className); + + var groupRow = consoleBody.ownerDocument.createElement("div"); + groupRow.className = "logGroup"; + var groupRowBox = consoleBody.ownerDocument.createElement("div"); + groupRowBox.className = "logGroupBox"; + groupRow.appendChild(groupRowBox); + appendRow(groupRowBox); + groupStack.push(groupRowBox); + } + + function popGroup() + { + groupStack.pop(); + } + + // ******************************************************************************************** + + function logFormatted(objects, className) + { + var html = []; + + var format = objects[0]; + var objIndex = 0; + + if (typeof(format) != "string") + { + format = ""; + objIndex = -1; + } + + var parts = parseFormat(format); + for (var i = 0; i < parts.length; ++i) + { + var part = parts[i]; + if (part && typeof(part) == "object") + { + var object = objects[++objIndex]; + part.appender(object, html); + } + else + appendText(part, html); + } + + for (var i = objIndex+1; i < objects.length; ++i) + { + appendText(" ", html); + + var object = objects[i]; + if (typeof(object) == "string") + appendText(object, html); + else + appendObject(object, html); + } + + logRow(html, className); + } + + function parseFormat(format) + { + var parts = []; + + var reg = /((^%|[^\\]%)(\d+)?(\.)([a-zA-Z]))|((^%|[^\\]%)([a-zA-Z]))/; + var appenderMap = {s: appendText, d: appendInteger, i: appendInteger, f: appendFloat}; + + for (var m = reg.exec(format); m; m = reg.exec(format)) + { + var type = m[8] ? m[8] : m[5]; + var appender = type in appenderMap ? appenderMap[type] : appendObject; + var precision = m[3] ? parseInt(m[3]) : (m[4] == "." ? -1 : 0); + + parts.push(format.substr(0, m[0][0] == "%" ? m.index : m.index+1)); + parts.push({appender: appender, precision: precision}); + + format = format.substr(m.index+m[0].length); + } + + parts.push(format); + + return parts; + } + + function escapeHTML(value) + { + function replaceChars(ch) + { + switch (ch) + { + case "<": + return "<"; + case ">": + return ">"; + case "&": + return "&"; + case "'": + return "'"; + case '"': + return """; + } + return "?"; + }; + return String(value).replace(/[<>&"']/g, replaceChars); + } + + function objectToString(object) + { + try + { + return object+""; + } + catch (exc) + { + return null; + } + } + + // ******************************************************************************************** + + function appendText(object, html) + { + html.push(escapeHTML(objectToString(object))); + } + + function appendNull(object, html) + { + html.push('', escapeHTML(objectToString(object)), ''); + } + + function appendString(object, html) + { + html.push('"', escapeHTML(objectToString(object)), + '"'); + } + + function appendInteger(object, html) + { + html.push('', escapeHTML(objectToString(object)), ''); + } + + function appendFloat(object, html) + { + html.push('', escapeHTML(objectToString(object)), ''); + } + + function appendFunction(object, html) + { + var reName = /function ?(.*?)\(/; + var m = reName.exec(objectToString(object)); + var name = m ? m[1] : "function"; + html.push('', escapeHTML(name), '()'); + } + + function appendObject(object, html) + { + try + { + if (object == undefined) + appendNull("undefined", html); + else if (object == null) + appendNull("null", html); + else if (typeof object == "string") + appendString(object, html); + else if (typeof object == "number") + appendInteger(object, html); + else if (typeof object == "function") + appendFunction(object, html); + else if (object.nodeType == 1) + appendSelector(object, html); + else if (typeof object == "object") + appendObjectFormatted(object, html); + else + appendText(object, html); + } + catch (exc) + { + } + } + + function appendObjectFormatted(object, html) + { + var text = objectToString(object); + var reObject = /\[object (.*?)\]/; + + var m = reObject.exec(text); + html.push('', m ? m[1] : text, '') + } + + function appendSelector(object, html) + { + html.push(''); + + html.push('', escapeHTML(object.nodeName.toLowerCase()), ''); + if (object.id) + html.push('#', escapeHTML(object.id), ''); + if (object.className) + html.push('.', escapeHTML(object.className), ''); + + html.push(''); + } + + function appendNode(node, html) + { + if (node.nodeType == 1) + { + html.push( + '
', + '<', node.nodeName.toLowerCase(), ''); + + for (var i = 0; i < node.attributes.length; ++i) + { + var attr = node.attributes[i]; + if (!attr.specified) + continue; + + html.push(' ', attr.nodeName.toLowerCase(), + '="', escapeHTML(attr.nodeValue), + '"') + } + + if (node.firstChild) + { + html.push('>
'); + + for (var child = node.firstChild; child; child = child.nextSibling) + appendNode(child, html); + + html.push('
</', + node.nodeName.toLowerCase(), '>
'); + } + else + html.push('/>'); + } + else if (node.nodeType == 3) + { + html.push('
', escapeHTML(node.nodeValue), + '
'); + } + } + + // ******************************************************************************************** + + function addEvent(object, name, handler) + { + if (document.all) + object.attachEvent("on"+name, handler); + else + object.addEventListener(name, handler, false); + } + + function removeEvent(object, name, handler) + { + if (document.all) + object.detachEvent("on"+name, handler); + else + object.removeEventListener(name, handler, false); + } + + function cancelEvent(event) + { + if (document.all) + event.cancelBubble = true; + else + event.stopPropagation(); + } + + function onError(msg, href, lineNo) + { + var html = []; + + var lastSlash = href.lastIndexOf("/"); + var fileName = lastSlash == -1 ? href : href.substr(lastSlash+1); + + html.push( + '', msg, '', + '' + ); + + logRow(html, "error"); + }; + + function onKeyDown(event) + { + if (event.keyCode == 123) + toggleConsole(); + else if ((event.keyCode == 108 || event.keyCode == 76) && event.shiftKey + && (event.metaKey || event.ctrlKey)) + focusCommandLine(); + else + return; + + cancelEvent(event); + } + + function onSplitterMouseDown(event) + { + if (isSafari || isOpera) + return; + + addEvent(document, "mousemove", onSplitterMouseMove); + addEvent(document, "mouseup", onSplitterMouseUp); + + for (var i = 0; i < frames.length; ++i) + { + addEvent(frames[i].document, "mousemove", onSplitterMouseMove); + addEvent(frames[i].document, "mouseup", onSplitterMouseUp); + } + } + + function onSplitterMouseMove(event) + { + var win = document.all + ? event.srcElement.ownerDocument.parentWindow + : event.target.ownerDocument.defaultView; + + var clientY = event.clientY; + if (win != win.parent) + clientY += win.frameElement ? win.frameElement.offsetTop : 0; + + var height = consoleFrame.offsetTop + consoleFrame.clientHeight; + var toolbar = consoleBody.ownerDocument.getElementById("toolbar"); + var y = Math.max(height - clientY, + toolbar.offsetHeight + commandLine.offsetHeight); + + consoleFrame.style.height = y + "px"; + layout(); + } + + function onSplitterMouseUp(event) + { + removeEvent(document, "mousemove", onSplitterMouseMove); + removeEvent(document, "mouseup", onSplitterMouseUp); + + for (var i = 0; i < frames.length; ++i) + { + removeEvent(frames[i].document, "mousemove", onSplitterMouseMove); + removeEvent(frames[i].document, "mouseup", onSplitterMouseUp); + } + } + + function onCommandLineKeyDown(event) + { + if (event.keyCode == 13) + evalCommandLine(); + else if (event.keyCode == 27) + commandLine.value = ""; + } + + window.onerror = onError; + addEvent(document, isIE || isSafari ? "keydown" : "keypress", onKeyDown); + + if (document.documentElement.getAttribute("debug") == "true") + toggleConsole(true); +})(); +} + --- /dev/null +++ b/openlayers/lib/Firebug/firebugx.js @@ -1,1 +1,10 @@ +if (!window.console || !console.firebug) { + var names = ["log", "debug", "info", "warn", "error", "assert", "dir", "dirxml", + "group", "groupEnd", "time", "timeEnd", "count", "trace", "profile", "profileEnd"]; + + window.console = {}; + for (var i = 0; i < names.length; ++i) + window.console[names[i]] = function() {} +} + --- /dev/null +++ b/openlayers/lib/Firebug/license.txt @@ -1,1 +1,31 @@ +Software License Agreement (BSD License) +Copyright (c) 2007, Parakey Inc. +All rights reserved. + +Redistribution and use of this software in source and binary forms, with or without modification, +are permitted provided that the following conditions are met: + +* Redistributions of source code must retain the above + copyright notice, this list of conditions and the + following disclaimer. + +* Redistributions in binary form must reproduce the above + copyright notice, this list of conditions and the + following disclaimer in the documentation and/or other + materials provided with the distribution. + +* Neither the name of Parakey Inc. nor the names of its + contributors may be used to endorse or promote products + derived from this software without specific prior + written permission of Parakey Inc. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR +IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND +FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR +CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER +IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT +OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + --- /dev/null +++ b/openlayers/lib/Firebug/readme.txt @@ -1,1 +1,13 @@ +This directory contains the source for Firebug Lite +(http://www.getfirebug.com/lite.html). This code is distributed with a +BSD License, Copyright (c) 2007, Parakey Inc. See the included license.txt +for the full text of the license. +This is a patched version of the trunk from +http://fbug.googlecode.com/svn/trunk. + +Revision 36 was patched to resolve the issue described here +http://code.google.com/p/fbug/issues/detail?id=85 + +When this issue is resolved, Firebug Lite can be used directly - no further +modifications are needed for OpenLayers. --- /dev/null +++ b/openlayers/lib/Gears/gears_init.js @@ -1,1 +1,89 @@ +/* + * Copyright 2007, Google Inc. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * 3. Neither the name of Google Inc. nor the names of its contributors may be + * used to endorse or promote products derived from this software without + * specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO + * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; + * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR + * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * Sets up google.gears.*, which is *the only* supported way to access Gears. + * + * Circumvent this file at your own risk! + * + * In the future, Gears may automatically define google.gears.* without this + * file. Gears may use these objects to transparently fix bugs and compatibility + * issues. Applications that use the code below will continue to work seamlessly + * when that happens. + */ +(function() { + // We are already defined. Hooray! + if (window.google && google.gears) { + return; + } + + var factory = null; + + // Firefox + if (typeof GearsFactory != 'undefined') { + factory = new GearsFactory(); + } else { + // IE + try { + factory = new ActiveXObject('Gears.Factory'); + // privateSetGlobalObject is only required and supported on WinCE. + if (factory.getBuildInfo().indexOf('ie_mobile') != -1) { + factory.privateSetGlobalObject(this); + } + } catch (e) { + // Safari + if ((typeof navigator.mimeTypes != 'undefined') + && navigator.mimeTypes["application/x-googlegears"]) { + factory = document.createElement("object"); + factory.style.display = "none"; + factory.width = 0; + factory.height = 0; + factory.type = "application/x-googlegears"; + document.documentElement.appendChild(factory); + } + } + } + + // *Do not* define any objects if Gears is not installed. This mimics the + // behavior of Gears defining the objects in the future. + if (!factory) { + return; + } + + // Now set up the objects, being careful not to overwrite anything. + // + // Note: In Internet Explorer for Windows Mobile, you can't add properties to + // the window object. However, global objects are automatically added as + // properties of the window object in all browsers. + if (!window.google) { + google = {}; + } + + if (!google.gears) { + google.gears = {factory: factory}; + } +})(); + --- /dev/null +++ b/openlayers/lib/OpenLayers.js @@ -1,1 +1,300 @@ - +/* Copyright (c) 2006-2008 MetaCarta, Inc., published under the Clear BSD + * license. See http://svn.openlayers.org/trunk/openlayers/license.txt for the + * full text of the license. */ + +/* + * @requires OpenLayers/BaseTypes.js + * @requires OpenLayers/Lang/en.js + * @requires OpenLayers/Console.js + */ + +(function() { + /** + * Before creating the OpenLayers namespace, check to see if + * OpenLayers.singleFile is true. This occurs if the + * OpenLayers/SingleFile.js script is included before this one - as is the + * case with single file builds. + */ + var singleFile = (typeof OpenLayers == "object" && OpenLayers.singleFile); + + /** + * Namespace: OpenLayers + * The OpenLayers object provides a namespace for all things OpenLayers + */ + window.OpenLayers = { + + /** + * Property: _scriptName + * {String} Relative path of this script. + */ + _scriptName: (!singleFile) ? "lib/OpenLayers.js" : "OpenLayers.js", + + /** + * Function: _getScriptLocation + * Return the path to this script. + * + * Returns: + * {String} Path to this script + */ + _getScriptLocation: function () { + var scriptLocation = ""; + var isOL = new RegExp("(^|(.*?\\/))(" + OpenLayers._scriptName + ")(\\?|$)"); + + var scripts = document.getElementsByTagName('script'); + for (var i=0, len=scripts.length; i"; + } else { + var s = document.createElement("script"); + s.src = host + jsfiles[i]; + var h = document.getElementsByTagName("head").length ? + document.getElementsByTagName("head")[0] : + document.body; + h.appendChild(s); + } + } + if (docWrite) { + document.write(allScriptTags.join("")); + } + } +})(); + +/** + * Constant: VERSION_NUMBER + */ +OpenLayers.VERSION_NUMBER="OpenLayers 2.8 -- $Revision: 9492 $"; + --- /dev/null +++ b/openlayers/lib/OpenLayers/Ajax.js @@ -1,1 +1,678 @@ - +/* Copyright (c) 2006-2008 MetaCarta, Inc., published under the Clear BSD + * license. See http://svn.openlayers.org/trunk/openlayers/license.txt for the + * full text of the license. */ + +/** + * @requires OpenLayers/Request/XMLHttpRequest.js + * @requires OpenLayers/Console.js + */ + +OpenLayers.ProxyHost = ""; +//OpenLayers.ProxyHost = "examples/proxy.cgi?url="; + +/** + * Ajax reader for OpenLayers + * + * @uri url to do remote XML http get + * @param {String} 'get' format params (x=y&a=b...) + * @who object to handle callbacks for this request + * @complete the function to be called on success + * @failure the function to be called on failure + * + * example usage from a caller: + * + * caps: function(request) { + * -blah- + * }, + * + * OpenLayers.loadURL(url,params,this,caps); + * + * Notice the above example does not provide an error handler; a default empty + * handler is provided which merely logs the error if a failure handler is not + * supplied + * + */ + + +/** + * Function: OpenLayers.nullHandler + * @param {} request + */ +OpenLayers.nullHandler = function(request) { + OpenLayers.Console.userError(OpenLayers.i18n("unhandledRequest", {'statusText':request.statusText})); +}; + +/** + * APIFunction: loadURL + * Background load a document. For more flexibility in using XMLHttpRequest, + * see the methods. + * + * Parameters: + * uri - {String} URI of source doc + * params - {String} or {Object} GET params. Either a string in the form + * "?hello=world&foo=bar" (do not forget the leading question mark) + * or an object in the form {'hello': 'world', 'foo': 'bar} + * caller - {Object} object which gets callbacks + * onComplete - {Function} Optional callback for success. The callback + * will be called with this set to caller and will receive the request + * object as an argument. Note that if you do not specify an onComplete + * function, will be called (which pops up a + * user friendly error message dialog). + * onFailure - {Function} Optional callback for failure. In the event of + * a failure, the callback will be called with this set to caller and will + * receive the request object as an argument. Note that if you do not + * specify an onComplete function, will be called + * (which pops up a user friendly error message dialog). + * + * Returns: + * {} The request object. To abort loading, + * call request.abort(). + */ +OpenLayers.loadURL = function(uri, params, caller, + onComplete, onFailure) { + + if(typeof params == 'string') { + params = OpenLayers.Util.getParameters(params); + } + var success = (onComplete) ? onComplete : OpenLayers.nullHandler; + var failure = (onFailure) ? onFailure : OpenLayers.nullHandler; + + return OpenLayers.Request.GET({ + url: uri, params: params, + success: success, failure: failure, scope: caller + }); +}; + +/** + * Function: parseXMLString + * Parse XML into a doc structure + * + * Parameters: + * text - {String} + * + * Returns: + * {?} Parsed AJAX Responsev + */ +OpenLayers.parseXMLString = function(text) { + + //MS sucks, if the server is bad it dies + var index = text.indexOf('<'); + if (index > 0) { + text = text.substring(index); + } + + var ajaxResponse = OpenLayers.Util.Try( + function() { + var xmldom = new ActiveXObject('Microsoft.XMLDOM'); + xmldom.loadXML(text); + return xmldom; + }, + function() { + return new DOMParser().parseFromString(text, 'text/xml'); + }, + function() { + var req = new XMLHttpRequest(); + req.open("GET", "data:" + "text/xml" + + ";charset=utf-8," + encodeURIComponent(text), false); + if (req.overrideMimeType) { + req.overrideMimeType("text/xml"); + } + req.send(null); + return req.responseXML; + } + ); + + return ajaxResponse; +}; + + +/** + * Namespace: OpenLayers.Ajax + */ +OpenLayers.Ajax = { + + /** + * Method: emptyFunction + */ + emptyFunction: function () {}, + + /** + * Method: getTransport + * + * Returns: + * {Object} Transport mechanism for whichever browser we're in, or false if + * none available. + */ + getTransport: function() { + return OpenLayers.Util.Try( + function() {return new XMLHttpRequest();}, + function() {return new ActiveXObject('Msxml2.XMLHTTP');}, + function() {return new ActiveXObject('Microsoft.XMLHTTP');} + ) || false; + }, + + /** + * Property: activeRequestCount + * {Integer} + */ + activeRequestCount: 0 +}; + +/** + * Namespace: OpenLayers.Ajax.Responders + * {Object} + */ +OpenLayers.Ajax.Responders = { + + /** + * Property: responders + * {Array} + */ + responders: [], + + /** + * Method: register + * + * Parameters: + * responderToAdd - {?} + */ + register: function(responderToAdd) { + for (var i = 0; i < this.responders.length; i++){ + if (responderToAdd == this.responders[i]){ + return; + } + } + this.responders.push(responderToAdd); + }, + + /** + * Method: unregister + * + * Parameters: + * responderToRemove - {?} + */ + unregister: function(responderToRemove) { + OpenLayers.Util.removeItem(this.reponders, responderToRemove); + }, + + /** + * Method: dispatch + * + * Parameters: + * callback - {?} + * request - {?} + * transport - {?} + */ + dispatch: function(callback, request, transport) { + var responder; + for (var i = 0; i < this.responders.length; i++) { + responder = this.responders[i]; + + if (responder[callback] && + typeof responder[callback] == 'function') { + try { + responder[callback].apply(responder, + [request, transport]); + } catch (e) {} + } + } + } +}; + +OpenLayers.Ajax.Responders.register({ + /** + * Function: onCreate + */ + onCreate: function() { + OpenLayers.Ajax.activeRequestCount++; + }, + + /** + * Function: onComplete + */ + onComplete: function() { + OpenLayers.Ajax.activeRequestCount--; + } +}); + +/** + * Class: OpenLayers.Ajax.Base + */ +OpenLayers.Ajax.Base = OpenLayers.Class({ + + /** + * Constructor: OpenLayers.Ajax.Base + * + * Parameters: + * options - {Object} + */ + initialize: function(options) { + this.options = { + method: 'post', + asynchronous: true, + contentType: 'application/xml', + parameters: '' + }; + OpenLayers.Util.extend(this.options, options || {}); + + this.options.method = this.options.method.toLowerCase(); + + if (typeof this.options.parameters == 'string') { + this.options.parameters = + OpenLayers.Util.getParameters(this.options.parameters); + } + } +}); + +/** + * Class: OpenLayers.Ajax.Request + * *Deprecated*. Use method instead. + * + * Inherit: + * - + */ +OpenLayers.Ajax.Request = OpenLayers.Class(OpenLayers.Ajax.Base, { + + /** + * Property: _complete + * + * {Boolean} + */ + _complete: false, + + /** + * Constructor: OpenLayers.Ajax.Request + * + * Parameters: + * url - {String} + * options - {Object} + */ + initialize: function(url, options) { + OpenLayers.Ajax.Base.prototype.initialize.apply(this, [options]); + + if (OpenLayers.ProxyHost && OpenLayers.String.startsWith(url, "http")) { + url = OpenLayers.ProxyHost + encodeURIComponent(url); + } + + this.transport = OpenLayers.Ajax.getTransport(); + this.request(url); + }, + + /** + * Method: request + * + * Parameters: + * url - {String} + */ + request: function(url) { + this.url = url; + this.method = this.options.method; + var params = OpenLayers.Util.extend({}, this.options.parameters); + + if (this.method != 'get' && this.method != 'post') { + // simulate other verbs over post + params['_method'] = this.method; + this.method = 'post'; + } + + this.parameters = params; + + if (params = OpenLayers.Util.getParameterString(params)) { + // when GET, append parameters to URL + if (this.method == 'get') { + this.url += ((this.url.indexOf('?') > -1) ? '&' : '?') + params; + } else if (/Konqueror|Safari|KHTML/.test(navigator.userAgent)) { + params += '&_='; + } + } + try { + var response = new OpenLayers.Ajax.Response(this); + if (this.options.onCreate) { + this.options.onCreate(response); + } + + OpenLayers.Ajax.Responders.dispatch('onCreate', + this, + response); + + this.transport.open(this.method.toUpperCase(), + this.url, + this.options.asynchronous); + + if (this.options.asynchronous) { + window.setTimeout( + OpenLayers.Function.bind(this.respondToReadyState, this, 1), + 10); + } + + this.transport.onreadystatechange = + OpenLayers.Function.bind(this.onStateChange, this); + this.setRequestHeaders(); + + this.body = this.method == 'post' ? + (this.options.postBody || params) : null; + this.transport.send(this.body); + + // Force Firefox to handle ready state 4 for synchronous requests + if (!this.options.asynchronous && + this.transport.overrideMimeType) { + this.onStateChange(); + } + } catch (e) { + this.dispatchException(e); + } + }, + + /** + * Method: onStateChange + */ + onStateChange: function() { + var readyState = this.transport.readyState; + if (readyState > 1 && !((readyState == 4) && this._complete)) { + this.respondToReadyState(this.transport.readyState); + } + }, + + /** + * Method: setRequestHeaders + */ + setRequestHeaders: function() { + var headers = { + 'X-Requested-With': 'XMLHttpRequest', + 'Accept': 'text/javascript, text/html, application/xml, text/xml, */*', + 'OpenLayers': true + }; + + if (this.method == 'post') { + headers['Content-type'] = this.options.contentType + + (this.options.encoding ? '; charset=' + this.options.encoding : ''); + + /* Force "Connection: close" for older Mozilla browsers to work + * around a bug where XMLHttpRequest sends an incorrect + * Content-length header. See Mozilla Bugzilla #246651. + */ + if (this.transport.overrideMimeType && + (navigator.userAgent.match(/Gecko\/(\d{4})/) || [0,2005])[1] < 2005) { + headers['Connection'] = 'close'; + } + } + // user-defined headers + if (typeof this.options.requestHeaders == 'object') { + var extras = this.options.requestHeaders; + + if (typeof extras.push == 'function') { + for (var i = 0, length = extras.length; i < length; i += 2) { + headers[extras[i]] = extras[i+1]; + } + } else { + for (var i in extras) { + headers[i] = extras[i]; + } + } + } + + for (var name in headers) { + this.transport.setRequestHeader(name, headers[name]); + } + }, + + /** + * Method: success + * + * Returns: + * {Boolean} - + */ + success: function() { + var status = this.getStatus(); + return !status || (status >=200 && status < 300); + }, + + /** + * Method: getStatus + * + * Returns: + * {Integer} - Status + */ + getStatus: function() { + try { + return this.transport.status || 0; + } catch (e) { + return 0; + } + }, + + /** + * Method: respondToReadyState + * + * Parameters: + * readyState - {?} + */ + respondToReadyState: function(readyState) { + var state = OpenLayers.Ajax.Request.Events[readyState]; + var response = new OpenLayers.Ajax.Response(this); + + if (state == 'Complete') { + try { + this._complete = true; + (this.options['on' + response.status] || + this.options['on' + (this.success() ? 'Success' : 'Failure')] || + OpenLayers.Ajax.emptyFunction)(response); + } catch (e) { + this.dispatchException(e); + } + + var contentType = response.getHeader('Content-type'); + } + + try { + (this.options['on' + state] || + OpenLayers.Ajax.emptyFunction)(response); + OpenLayers.Ajax.Responders.dispatch('on' + state, + this, + response); + } catch (e) { + this.dispatchException(e); + } + + if (state == 'Complete') { + // avoid memory leak in MSIE: clean up + this.transport.onreadystatechange = OpenLayers.Ajax.emptyFunction; + } + }, + + /** + * Method: getHeader + * + * Parameters: + * name - {String} Header name + * + * Returns: + * {?} - response header for the given name + */ + getHeader: function(name) { + try { + return this.transport.getResponseHeader(name); + } catch (e) { + return null; + } + }, + + /** + * Method: dispatchException + * If the optional onException function is set, execute it + * and then dispatch the call to any other listener registered + * for onException. + * + * If no optional onException function is set, we suspect that + * the user may have also not used + * OpenLayers.Ajax.Responders.register to register a listener + * for the onException call. To make sure that something + * gets done with this exception, only dispatch the call if there + * are listeners. + * + * If you explicitly want to swallow exceptions, set + * request.options.onException to an empty function (function(){}) + * or register an empty function with + * for onException. + * + * Parameters: + * exception - {?} + */ + dispatchException: function(exception) { + var handler = this.options.onException; + if(handler) { + // call options.onException and alert any other listeners + handler(this, exception); + OpenLayers.Ajax.Responders.dispatch('onException', this, exception); + } else { + // check if there are any other listeners + var listener = false; + var responders = OpenLayers.Ajax.Responders.responders; + for (var i = 0; i < responders.length; i++) { + if(responders[i].onException) { + listener = true; + break; + } + } + if(listener) { + // call all listeners + OpenLayers.Ajax.Responders.dispatch('onException', this, exception); + } else { + // let the exception through + throw exception; + } + } + } +}); + +/** + * Property: Events + * {Array(String)} + */ +OpenLayers.Ajax.Request.Events = + ['Uninitialized', 'Loading', 'Loaded', 'Interactive', 'Complete']; + +/** + * Class: OpenLayers.Ajax.Response + */ +OpenLayers.Ajax.Response = OpenLayers.Class({ + + /** + * Property: status + * + * {Integer} + */ + status: 0, + + + /** + * Property: statusText + * + * {String} + */ + statusText: '', + + /** + * Constructor: OpenLayers.Ajax.Response + * + * Parameters: + * request - {Object} + */ + initialize: function(request) { + this.request = request; + var transport = this.transport = request.transport, + readyState = this.readyState = transport.readyState; + + if ((readyState > 2 && + !(!!(window.attachEvent && !window.opera))) || + readyState == 4) { + this.status = this.getStatus(); + this.statusText = this.getStatusText(); + this.responseText = transport.responseText == null ? + '' : String(transport.responseText); + } + + if(readyState == 4) { + var xml = transport.responseXML; + this.responseXML = xml === undefined ? null : xml; + } + }, + + /** + * Method: getStatus + */ + getStatus: OpenLayers.Ajax.Request.prototype.getStatus, + + /** + * Method: getStatustext + * + * Returns: + * {String} - statusText + */ + getStatusText: function() { + try { + return this.transport.statusText || ''; + } catch (e) { + return ''; + } + }, + + /** + * Method: getHeader + */ + getHeader: OpenLayers.Ajax.Request.prototype.getHeader, + + /** + * Method: getResponseHeader + * + * Returns: + * {?} - response header for given name + */ + getResponseHeader: function(name) { + return this.transport.getResponseHeader(name); + } +}); + + +/** + * Function: getElementsByTagNameNS + * + * Parameters: + * parentnode - {?} + * nsuri - {?} + * nsprefix - {?} + * tagname - {?} + * + * Returns: + * {?} + */ +OpenLayers.Ajax.getElementsByTagNameNS = function(parentnode, nsuri, + nsprefix, tagname) { + var elem = null; + if (parentnode.getElementsByTagNameNS) { + elem = parentnode.getElementsByTagNameNS(nsuri, tagname); + } else { + elem = parentnode.getElementsByTagName(nsprefix + ':' + tagname); + } + return elem; +}; + + +/** + * Function: serializeXMLToString + * Wrapper function around XMLSerializer, which doesn't exist/work in + * IE/Safari. We need to come up with a way to serialize in those browser: + * for now, these browsers will just fail. #535, #536 + * + * Parameters: + * xmldom {XMLNode} xml dom to serialize + * + * Returns: + * {?} + */ +OpenLayers.Ajax.serializeXMLToString = function(xmldom) { + var serializer = new XMLSerializer(); + var data = serializer.serializeToString(xmldom); + return data; +}; + --- /dev/null +++ b/openlayers/lib/OpenLayers/BaseTypes.js @@ -1,1 +1,530 @@ - +/* Copyright (c) 2006-2008 MetaCarta, Inc., published under the Clear BSD + * license. See http://svn.openlayers.org/trunk/openlayers/license.txt for the + * full text of the license. */ + +/** + * @requires OpenLayers/BaseTypes/Class.js + * @requires OpenLayers/BaseTypes/LonLat.js + * @requires OpenLayers/BaseTypes/Size.js + * @requires OpenLayers/BaseTypes/Pixel.js + * @requires OpenLayers/BaseTypes/Bounds.js + * @requires OpenLayers/BaseTypes/Element.js + * @requires OpenLayers/Lang/en.js + * @requires OpenLayers/Console.js + */ + +/** + * Header: OpenLayers Base Types + * OpenLayers custom string, number and function functions are described here. + */ + +/** + * Namespace: OpenLayers.String + * Contains convenience functions for string manipulation. + */ +OpenLayers.String = { + + /** + * APIFunction: startsWith + * Test whether a string starts with another string. + * + * Parameters: + * str - {String} The string to test. + * sub - {Sring} The substring to look for. + * + * Returns: + * {Boolean} The first string starts with the second. + */ + startsWith: function(str, sub) { + return (str.indexOf(sub) == 0); + }, + + /** + * APIFunction: contains + * Test whether a string contains another string. + * + * Parameters: + * str - {String} The string to test. + * sub - {String} The substring to look for. + * + * Returns: + * {Boolean} The first string contains the second. + */ + contains: function(str, sub) { + return (str.indexOf(sub) != -1); + }, + + /** + * APIFunction: trim + * Removes leading and trailing whitespace characters from a string. + * + * Parameters: + * str - {String} The (potentially) space padded string. This string is not + * modified. + * + * Returns: + * {String} A trimmed version of the string with all leading and + * trailing spaces removed. + */ + trim: function(str) { + return str.replace(/^\s\s*/, '').replace(/\s\s*$/, ''); + }, + + /** + * APIFunction: camelize + * Camel-case a hyphenated string. + * Ex. "chicken-head" becomes "chickenHead", and + * "-chicken-head" becomes "ChickenHead". + * + * Parameters: + * str - {String} The string to be camelized. The original is not modified. + * + * Returns: + * {String} The string, camelized + */ + camelize: function(str) { + var oStringList = str.split('-'); + var camelizedString = oStringList[0]; + for (var i=1, len=oStringList.length; i replacement = context[a]; + // 1 -> replacement = context[a][b]; + // 2 -> replacement = context[a][b][c]; + var subs = match.split(/\.+/); + for (var i=0; i< subs.length; i++) { + if (i == 0) { + replacement = context; + } + + replacement = replacement[subs[i]]; + } + + if(typeof replacement == "function") { + replacement = args ? + replacement.apply(null, args) : + replacement(); + } + + // If replacement is undefined, return the string 'undefined'. + // This is a workaround for a bugs in browsers not properly + // dealing with non-participating groups in regular expressions: + // http://blog.stevenlevithan.com/archives/npcg-javascript + if (typeof replacement == 'undefined') { + return 'undefined'; + } else { + return replacement; + } + }; + + return template.replace(OpenLayers.String.tokenRegEx, replacer); + }, + + /** + * Property: OpenLayers.String.tokenRegEx + * Used to find tokens in a string. + * Examples: ${a}, ${a.b.c}, ${a-b}, ${5} + */ + tokenRegEx: /\$\{([\w.]+?)\}/g, + + /** + * Property: OpenLayers.String.numberRegEx + * Used to test strings as numbers. + */ + numberRegEx: /^([+-]?)(?=\d|\.\d)\d*(\.\d*)?([Ee]([+-]?\d+))?$/, + + /** + * APIFunction: OpenLayers.String.isNumeric + * Determine whether a string contains only a numeric value. + * + * Examples: + * (code) + * OpenLayers.String.isNumeric("6.02e23") // true + * OpenLayers.String.isNumeric("12 dozen") // false + * OpenLayers.String.isNumeric("4") // true + * OpenLayers.String.isNumeric(" 4 ") // false + * (end) + * + * Returns: + * {Boolean} String contains only a number. + */ + isNumeric: function(value) { + return OpenLayers.String.numberRegEx.test(value); + }, + + /** + * APIFunction: numericIf + * Converts a string that appears to be a numeric value into a number. + * + * Returns + * {Number|String} a Number if the passed value is a number, a String + * otherwise. + */ + numericIf: function(value) { + return OpenLayers.String.isNumeric(value) ? parseFloat(value) : value; + } + +}; + +if (!String.prototype.startsWith) { + /** + * APIMethod: String.startsWith + * *Deprecated*. Whether or not a string starts with another string. + * + * Parameters: + * sStart - {Sring} The string we're testing for. + * + * Returns: + * {Boolean} Whether or not this string starts with the string passed in. + */ + String.prototype.startsWith = function(sStart) { + OpenLayers.Console.warn(OpenLayers.i18n("methodDeprecated", + {'newMethod':'OpenLayers.String.startsWith'})); + return OpenLayers.String.startsWith(this, sStart); + }; +} + +if (!String.prototype.contains) { + /** + * APIMethod: String.contains + * *Deprecated*. Whether or not a string contains another string. + * + * Parameters: + * str - {String} The string that we're testing for. + * + * Returns: + * {Boolean} Whether or not this string contains with the string passed in. + */ + String.prototype.contains = function(str) { + OpenLayers.Console.warn(OpenLayers.i18n("methodDeprecated", + {'newMethod':'OpenLayers.String.contains'})); + return OpenLayers.String.contains(this, str); + }; +} + +if (!String.prototype.trim) { + /** + * APIMethod: String.trim + * *Deprecated*. Removes leading and trailing whitespace characters from a string. + * + * Returns: + * {String} A trimmed version of the string - all leading and + * trailing spaces removed + */ + String.prototype.trim = function() { + OpenLayers.Console.warn(OpenLayers.i18n("methodDeprecated", + {'newMethod':'OpenLayers.String.trim'})); + return OpenLayers.String.trim(this); + }; +} + +if (!String.prototype.camelize) { + /** + * APIMethod: String.camelize + * *Deprecated*. Camel-case a hyphenated string. + * Ex. "chicken-head" becomes "chickenHead", and + * "-chicken-head" becomes "ChickenHead". + * + * Returns: + * {String} The string, camelized + */ + String.prototype.camelize = function() { + OpenLayers.Console.warn(OpenLayers.i18n("methodDeprecated", + {'newMethod':'OpenLayers.String.camelize'})); + return OpenLayers.String.camelize(this); + }; +} + +/** + * Namespace: OpenLayers.Number + * Contains convenience functions for manipulating numbers. + */ +OpenLayers.Number = { + + /** + * Property: decimalSeparator + * Decimal separator to use when formatting numbers. + */ + decimalSeparator: ".", + + /** + * Property: thousandsSeparator + * Thousands separator to use when formatting numbers. + */ + thousandsSeparator: ",", + + /** + * APIFunction: limitSigDigs + * Limit the number of significant digits on a float. + * + * Parameters: + * num - {Float} + * sig - {Integer} + * + * Returns: + * {Float} The number, rounded to the specified number of significant + * digits. + */ + limitSigDigs: function(num, sig) { + var fig = 0; + if (sig > 0) { + fig = parseFloat(num.toPrecision(sig)); + } + return fig; + }, + + /** + * APIFunction: format + * Formats a number for output. + * + * Parameters: + * num - {Float} + * dec - {Integer} Number of decimal places to round to. + * Defaults to 0. Set to null to leave decimal places unchanged. + * tsep - {String} Thousands separator. + * Default is ",". + * dsep - {String} Decimal separator. + * Default is ".". + * + * Returns: + * {String} A string representing the formatted number. + */ + format: function(num, dec, tsep, dsep) { + dec = (typeof dec != "undefined") ? dec : 0; + tsep = (typeof tsep != "undefined") ? tsep : + OpenLayers.Number.thousandsSeparator; + dsep = (typeof dsep != "undefined") ? dsep : + OpenLayers.Number.decimalSeparator; + + if (dec != null) { + num = parseFloat(num.toFixed(dec)); + } + + var parts = num.toString().split("."); + if (parts.length == 1 && dec == null) { + // integer where we do not want to touch the decimals + dec = 0; + } + + var integer = parts[0]; + if (tsep) { + var thousands = /(-?[0-9]+)([0-9]{3})/; + while(thousands.test(integer)) { + integer = integer.replace(thousands, "$1" + tsep + "$2"); + } + } + + var str; + if (dec == 0) { + str = integer; + } else { + var rem = parts.length > 1 ? parts[1] : "0"; + if (dec != null) { + rem = rem + new Array(dec - rem.length + 1).join("0"); + } + str = integer + dsep + rem; + } + return str; + } +}; + +if (!Number.prototype.limitSigDigs) { + /** + * APIMethod: Number.limitSigDigs + * *Deprecated*. Limit the number of significant digits on an integer. Does *not* + * work with floats! + * + * Parameters: + * sig - {Integer} + * + * Returns: + * {Integer} The number, rounded to the specified number of significant digits. + * If null, 0, or negative value passed in, returns 0 + */ + Number.prototype.limitSigDigs = function(sig) { + OpenLayers.Console.warn(OpenLayers.i18n("methodDeprecated", + {'newMethod':'OpenLayers.Number.limitSigDigs'})); + return OpenLayers.Number.limitSigDigs(this, sig); + }; +} + +/** + * Namespace: OpenLayers.Function + * Contains convenience functions for function manipulation. + */ +OpenLayers.Function = { + /** + * APIFunction: bind + * Bind a function to an object. Method to easily create closures with + * 'this' altered. + * + * Parameters: + * func - {Function} Input function. + * object - {Object} The object to bind to the input function (as this). + * + * Returns: + * {Function} A closure with 'this' set to the passed in object. + */ + bind: function(func, object) { + // create a reference to all arguments past the second one + var args = Array.prototype.slice.apply(arguments, [2]); + return function() { + // Push on any additional arguments from the actual function call. + // These will come after those sent to the bind call. + var newArgs = args.concat( + Array.prototype.slice.apply(arguments, [0]) + ); + return func.apply(object, newArgs); + }; + }, + + /** + * APIFunction: bindAsEventListener + * Bind a function to an object, and configure it to receive the event + * object as first parameter when called. + * + * Parameters: + * func - {Function} Input function to serve as an event listener. + * object - {Object} A reference to this. + * + * Returns: + * {Function} + */ + bindAsEventListener: function(func, object) { + return function(event) { + return func.call(object, event || window.event); + }; + } +}; + +if (!Function.prototype.bind) { + /** + * APIMethod: Function.bind + * *Deprecated*. Bind a function to an object. + * Method to easily create closures with 'this' altered. + * + * Parameters: + * object - {Object} the this parameter + * + * Returns: + * {Function} A closure with 'this' altered to the first + * argument. + */ + Function.prototype.bind = function() { + OpenLayers.Console.warn(OpenLayers.i18n("methodDeprecated", + {'newMethod':'OpenLayers.Function.bind'})); + // new function takes the same arguments with this function up front + Array.prototype.unshift.apply(arguments, [this]); + return OpenLayers.Function.bind.apply(null, arguments); + }; +} + +if (!Function.prototype.bindAsEventListener) { + /** + * APIMethod: Function.bindAsEventListener + * *Deprecated*. Bind a function to an object, and configure it to receive the + * event object as first parameter when called. + * + * Parameters: + * object - {Object} A reference to this. + * + * Returns: + * {Function} + */ + Function.prototype.bindAsEventListener = function(object) { + OpenLayers.Console.warn(OpenLayers.i18n("methodDeprecated", + {'newMethod':'OpenLayers.Function.bindAsEventListener'})); + return OpenLayers.Function.bindAsEventListener(this, object); + }; +} + +/** + * Namespace: OpenLayers.Array + * Contains convenience functions for array manipulation. + */ +OpenLayers.Array = { + + /** + * APIMethod: filter + * Filter an array. Provides the functionality of the + * Array.prototype.filter extension to the ECMA-262 standard. Where + * available, Array.prototype.filter will be used. + * + * Based on well known example from http://developer.mozilla.org/en/docs/Core_JavaScript_1.5_Reference:Objects:Array:filter + * + * Parameters: + * array - {Array} The array to be filtered. This array is not mutated. + * Elements added to this array by the callback will not be visited. + * callback - {Function} A function that is called for each element in + * the array. If this function returns true, the element will be + * included in the return. The function will be called with three + * arguments: the element in the array, the index of that element, and + * the array itself. If the optional caller parameter is specified + * the callback will be called with this set to caller. + * caller - {Object} Optional object to be set as this when the callback + * is called. + * + * Returns: + * {Array} An array of elements from the passed in array for which the + * callback returns true. + */ + filter: function(array, callback, caller) { + var selected = []; + if (Array.prototype.filter) { + selected = array.filter(callback, caller); + } else { + var len = array.length; + if (typeof callback != "function") { + throw new TypeError(); + } + for(var i=0; i bounds = new OpenLayers.Bounds(); + * > bounds.extend(new OpenLayers.LonLat(4,5)); + * > bounds.extend(new OpenLayers.LonLat(5,6)); + * > bounds.toBBOX(); // returns 4,5,5,6 + */ +OpenLayers.Bounds = OpenLayers.Class({ + + /** + * Property: left + * {Number} Minimum horizontal coordinate. + */ + left: null, + + /** + * Property: bottom + * {Number} Minimum vertical coordinate. + */ + bottom: null, + + /** + * Property: right + * {Number} Maximum horizontal coordinate. + */ + right: null, + + /** + * Property: top + * {Number} Maximum vertical coordinate. + */ + top: null, + + /** + * Property: centerLonLat + * {} A cached center location. This should not be + * accessed directly. Use instead. + */ + centerLonLat: null, + + /** + * Constructor: OpenLayers.Bounds + * Construct a new bounds object. + * + * Parameters: + * left - {Number} The left bounds of the box. Note that for width + * calculations, this is assumed to be less than the right value. + * bottom - {Number} The bottom bounds of the box. Note that for height + * calculations, this is assumed to be more than the top value. + * right - {Number} The right bounds. + * top - {Number} The top bounds. + */ + initialize: function(left, bottom, right, top) { + if (left != null) { + this.left = OpenLayers.Util.toFloat(left); + } + if (bottom != null) { + this.bottom = OpenLayers.Util.toFloat(bottom); + } + if (right != null) { + this.right = OpenLayers.Util.toFloat(right); + } + if (top != null) { + this.top = OpenLayers.Util.toFloat(top); + } + }, + + /** + * Method: clone + * Create a cloned instance of this bounds. + * + * Returns: + * {} A fresh copy of the bounds + */ + clone:function() { + return new OpenLayers.Bounds(this.left, this.bottom, + this.right, this.top); + }, + + /** + * Method: equals + * Test a two bounds for equivalence. + * + * Parameters: + * bounds - {} + * + * Returns: + * {Boolean} The passed-in bounds object has the same left, + * right, top, bottom components as this. Note that if bounds + * passed in is null, returns false. + */ + equals:function(bounds) { + var equals = false; + if (bounds != null) { + equals = ((this.left == bounds.left) && + (this.right == bounds.right) && + (this.top == bounds.top) && + (this.bottom == bounds.bottom)); + } + return equals; + }, + + /** + * APIMethod: toString + * + * Returns: + * {String} String representation of bounds object. + * (ex."left-bottom=(5,42) right-top=(10,45)") + */ + toString:function() { + return ( "left-bottom=(" + this.left + "," + this.bottom + ")" + + " right-top=(" + this.right + "," + this.top + ")" ); + }, + + /** + * APIMethod: toArray + * + * Returns: + * {Array} array of left, bottom, right, top + */ + toArray: function() { + return [this.left, this.bottom, this.right, this.top]; + }, + + /** + * APIMethod: toBBOX + * + * Parameters: + * decimal - {Integer} How many significant digits in the bbox coords? + * Default is 6 + * + * Returns: + * {String} Simple String representation of bounds object. + * (ex. "5,42,10,45") + */ + toBBOX:function(decimal) { + if (decimal== null) { + decimal = 6; + } + var mult = Math.pow(10, decimal); + var bbox = Math.round(this.left * mult) / mult + "," + + Math.round(this.bottom * mult) / mult + "," + + Math.round(this.right * mult) / mult + "," + + Math.round(this.top * mult) / mult; + + return bbox; + }, + + /** + * APIMethod: toGeometry + * Create a new polygon geometry based on this bounds. + * + * Returns: + * {} A new polygon with the coordinates + * of this bounds. + */ + toGeometry: function() { + return new OpenLayers.Geometry.Polygon([ + new OpenLayers.Geometry.LinearRing([ + new OpenLayers.Geometry.Point(this.left, this.bottom), + new OpenLayers.Geometry.Point(this.right, this.bottom), + new OpenLayers.Geometry.Point(this.right, this.top), + new OpenLayers.Geometry.Point(this.left, this.top) + ]) + ]); + }, + + /** + * APIMethod: getWidth + * + * Returns: + * {Float} The width of the bounds + */ + getWidth:function() { + return (this.right - this.left); + }, + + /** + * APIMethod: getHeight + * + * Returns: + * {Float} The height of the bounds (top minus bottom). + */ + getHeight:function() { + return (this.top - this.bottom); + }, + + /** + * APIMethod: getSize + * + * Returns: + * {} The size of the box. + */ + getSize:function() { + return new OpenLayers.Size(this.getWidth(), this.getHeight()); + }, + + /** + * APIMethod: getCenterPixel + * + * Returns: + * {} The center of the bounds in pixel space. + */ + getCenterPixel:function() { + return new OpenLayers.Pixel( (this.left + this.right) / 2, + (this.bottom + this.top) / 2); + }, + + /** + * APIMethod: getCenterLonLat + * + * Returns: + * {} The center of the bounds in map space. + */ + getCenterLonLat:function() { + if(!this.centerLonLat) { + this.centerLonLat = new OpenLayers.LonLat( + (this.left + this.right) / 2, (this.bottom + this.top) / 2 + ); + } + return this.centerLonLat; + }, + + /** + * Method: scale + * Scales the bounds around a pixel or lonlat. Note that the new + * bounds may return non-integer properties, even if a pixel + * is passed. + * + * Parameters: + * ratio - {Float} + * origin - { or } + * Default is center. + * + * Returns: + * {} A new bounds that is scaled by ratio + * from origin. + */ + + scale: function(ratio, origin){ + if(origin == null){ + origin = this.getCenterLonLat(); + } + + var bounds = []; + + var origx,origy; + + // get origin coordinates + if(origin.CLASS_NAME == "OpenLayers.LonLat"){ + origx = origin.lon; + origy = origin.lat; + } else { + origx = origin.x; + origy = origin.y; + } + + var left = (this.left - origx) * ratio + origx; + var bottom = (this.bottom - origy) * ratio + origy; + var right = (this.right - origx) * ratio + origx; + var top = (this.top - origy) * ratio + origy; + + return new OpenLayers.Bounds(left, bottom, right, top); + }, + + /** + * APIMethod: add + * + * Parameters: + * x - {Float} + * y - {Float} + * + * Returns: + * {} A new bounds whose coordinates are the same as + * this, but shifted by the passed-in x and y values. + */ + add:function(x, y) { + if ( (x == null) || (y == null) ) { + var msg = OpenLayers.i18n("boundsAddError"); + OpenLayers.Console.error(msg); + return null; + } + return new OpenLayers.Bounds(this.left + x, this.bottom + y, + this.right + x, this.top + y); + }, + + /** + * APIMethod: extend + * Extend the bounds to include the point, lonlat, or bounds specified. + * Note, this function assumes that left < right and bottom < top. + * + * Parameters: + * object - {Object} Can be LonLat, Point, or Bounds + */ + extend:function(object) { + var bounds = null; + if (object) { + // clear cached center location + switch(object.CLASS_NAME) { + case "OpenLayers.LonLat": + bounds = new OpenLayers.Bounds(object.lon, object.lat, + object.lon, object.lat); + break; + case "OpenLayers.Geometry.Point": + bounds = new OpenLayers.Bounds(object.x, object.y, + object.x, object.y); + break; + + case "OpenLayers.Bounds": + bounds = object; + break; + } + + if (bounds) { + this.centerLonLat = null; + if ( (this.left == null) || (bounds.left < this.left)) { + this.left = bounds.left; + } + if ( (this.bottom == null) || (bounds.bottom < this.bottom) ) { + this.bottom = bounds.bottom; + } + if ( (this.right == null) || (bounds.right > this.right) ) { + this.right = bounds.right; + } + if ( (this.top == null) || (bounds.top > this.top) ) { + this.top = bounds.top; + } + } + } + }, + + /** + * APIMethod: containsLonLat + * + * Parameters: + * ll - {} + * inclusive - {Boolean} Whether or not to include the border. + * Default is true. + * + * Returns: + * {Boolean} The passed-in lonlat is within this bounds. + */ + containsLonLat:function(ll, inclusive) { + return this.contains(ll.lon, ll.lat, inclusive); + }, + + /** + * APIMethod: containsPixel + * + * Parameters: + * px - {} + * inclusive - {Boolean} Whether or not to include the border. Default is + * true. + * + * Returns: + * {Boolean} The passed-in pixel is within this bounds. + */ + containsPixel:function(px, inclusive) { + return this.contains(px.x, px.y, inclusive); + }, + + /** + * APIMethod: contains + * + * Parameters: + * x - {Float} + * y - {Float} + * inclusive - {Boolean} Whether or not to include the border. Default is + * true. + * + * Returns: + * {Boolean} Whether or not the passed-in coordinates are within this + * bounds. + */ + contains:function(x, y, inclusive) { + //set default + if (inclusive == null) { + inclusive = true; + } + + if (x == null || y == null) { + return false; + } + + x = OpenLayers.Util.toFloat(x); + y = OpenLayers.Util.toFloat(y); + + var contains = false; + if (inclusive) { + contains = ((x >= this.left) && (x <= this.right) && + (y >= this.bottom) && (y <= this.top)); + } else { + contains = ((x > this.left) && (x < this.right) && + (y > this.bottom) && (y < this.top)); + } + return contains; + }, + + /** + * APIMethod: intersectsBounds + * Determine whether the target bounds intersects this bounds. Bounds are + * considered intersecting if any of their edges intersect or if one + * bounds contains the other. + * + * Parameters: + * bounds - {} The target bounds. + * inclusive - {Boolean} Treat coincident borders as intersecting. Default + * is true. If false, bounds that do not overlap but only touch at the + * border will not be considered as intersecting. + * + * Returns: + * {Boolean} The passed-in bounds object intersects this bounds. + */ + intersectsBounds:function(bounds, inclusive) { + if (inclusive == null) { + inclusive = true; + } + var intersects = false; + var mightTouch = ( + this.left == bounds.right || + this.right == bounds.left || + this.top == bounds.bottom || + this.bottom == bounds.top + ); + + // if the two bounds only touch at an edge, and inclusive is false, + // then the bounds don't *really* intersect. + if (inclusive || !mightTouch) { + // otherwise, if one of the boundaries even partially contains another, + // inclusive of the edges, then they do intersect. + var inBottom = ( + ((bounds.bottom >= this.bottom) && (bounds.bottom <= this.top)) || + ((this.bottom >= bounds.bottom) && (this.bottom <= bounds.top)) + ); + var inTop = ( + ((bounds.top >= this.bottom) && (bounds.top <= this.top)) || + ((this.top > bounds.bottom) && (this.top < bounds.top)) + ); + var inLeft = ( + ((bounds.left >= this.left) && (bounds.left <= this.right)) || + ((this.left >= bounds.left) && (this.left <= bounds.right)) + ); + var inRight = ( + ((bounds.right >= this.left) && (bounds.right <= this.right)) || + ((this.right >= bounds.left) && (this.right <= bounds.right)) + ); + intersects = ((inBottom || inTop) && (inLeft || inRight)); + } + return intersects; + }, + + /** + * APIMethod: containsBounds + * Determine whether the target bounds is contained within this bounds. + * + * bounds - {} The target bounds. + * partial - {Boolean} If any of the target corners is within this bounds + * consider the bounds contained. Default is false. If true, the + * entire target bounds must be contained within this bounds. + * inclusive - {Boolean} Treat shared edges as contained. Default is + * true. + * + * Returns: + * {Boolean} The passed-in bounds object is contained within this bounds. + */ + containsBounds:function(bounds, partial, inclusive) { + if (partial == null) { + partial = false; + } + if (inclusive == null) { + inclusive = true; + } + var bottomLeft = this.contains(bounds.left, bounds.bottom, inclusive); + var bottomRight = this.contains(bounds.right, bounds.bottom, inclusive); + var topLeft = this.contains(bounds.left, bounds.top, inclusive); + var topRight = this.contains(bounds.right, bounds.top, inclusive); + + return (partial) ? (bottomLeft || bottomRight || topLeft || topRight) + : (bottomLeft && bottomRight && topLeft && topRight); + }, + + /** + * APIMethod: determineQuadrant + * + * Parameters: + * lonlat - {} + * + * Returns: + * {String} The quadrant ("br" "tr" "tl" "bl") of the bounds in which the + * coordinate lies. + */ + determineQuadrant: function(lonlat) { + + var quadrant = ""; + var center = this.getCenterLonLat(); + + quadrant += (lonlat.lat < center.lat) ? "b" : "t"; + quadrant += (lonlat.lon < center.lon) ? "l" : "r"; + + return quadrant; + }, + + /** + * APIMethod: transform + * Transform the Bounds object from source to dest. + * + * Parameters: + * source - {} Source projection. + * dest - {} Destination projection. + * + * Returns: + * {} Itself, for use in chaining operations. + */ + transform: function(source, dest) { + // clear cached center location + this.centerLonLat = null; + var ll = OpenLayers.Projection.transform( + {'x': this.left, 'y': this.bottom}, source, dest); + var lr = OpenLayers.Projection.transform( + {'x': this.right, 'y': this.bottom}, source, dest); + var ul = OpenLayers.Projection.transform( + {'x': this.left, 'y': this.top}, source, dest); + var ur = OpenLayers.Projection.transform( + {'x': this.right, 'y': this.top}, source, dest); + this.left = Math.min(ll.x, ul.x); + this.bottom = Math.min(ll.y, lr.y); + this.right = Math.max(lr.x, ur.x); + this.top = Math.max(ul.y, ur.y); + return this; + }, + + /** + * APIMethod: wrapDateLine + * + * Parameters: + * maxExtent - {} + * options - {Object} Some possible options are: + * leftTolerance - {float} Allow for a margin of error + * with the 'left' value of this + * bound. + * Default is 0. + * rightTolerance - {float} Allow for a margin of error + * with the 'right' value of + * this bound. + * Default is 0. + * + * Returns: + * {} A copy of this bounds, but wrapped around the + * "dateline" (as specified by the borders of + * maxExtent). Note that this function only returns + * a different bounds value if this bounds is + * *entirely* outside of the maxExtent. If this + * bounds straddles the dateline (is part in/part + * out of maxExtent), the returned bounds will be + * merely a copy of this one. + */ + wrapDateLine: function(maxExtent, options) { + options = options || {}; + + var leftTolerance = options.leftTolerance || 0; + var rightTolerance = options.rightTolerance || 0; + + var newBounds = this.clone(); + + if (maxExtent) { + + //shift right? + while ( newBounds.left < maxExtent.left && + (newBounds.right - rightTolerance) <= maxExtent.left ) { + newBounds = newBounds.add(maxExtent.getWidth(), 0); + } + + //shift left? + while ( (newBounds.left + leftTolerance) >= maxExtent.right && + newBounds.right > maxExtent.right ) { + newBounds = newBounds.add(-maxExtent.getWidth(), 0); + } + } + + return newBounds; + }, + + CLASS_NAME: "OpenLayers.Bounds" +}); + +/** + * APIFunction: fromString + * Alternative constructor that builds a new OpenLayers.Bounds from a + * parameter string + * + * Parameters: + * str - {String}Comma-separated bounds string. (ex. "5,42,10,45") + * + * Returns: + * {} New bounds object built from the + * passed-in String. + */ +OpenLayers.Bounds.fromString = function(str) { + var bounds = str.split(","); + return OpenLayers.Bounds.fromArray(bounds); +}; + +/** + * APIFunction: fromArray + * Alternative constructor that builds a new OpenLayers.Bounds + * from an array + * + * Parameters: + * bbox - {Array(Float)} Array of bounds values (ex. [5,42,10,45]) + * + * Returns: + * {} New bounds object built from the passed-in Array. + */ +OpenLayers.Bounds.fromArray = function(bbox) { + return new OpenLayers.Bounds(parseFloat(bbox[0]), + parseFloat(bbox[1]), + parseFloat(bbox[2]), + parseFloat(bbox[3])); +}; + +/** + * APIFunction: fromSize + * Alternative constructor that builds a new OpenLayers.Bounds + * from a size + * + * Parameters: + * size - {} + * + * Returns: + * {} New bounds object built from the passed-in size. + */ +OpenLayers.Bounds.fromSize = function(size) { + return new OpenLayers.Bounds(0, + size.h, + size.w, + 0); +}; + +/** + * Function: oppositeQuadrant + * Get the opposite quadrant for a given quadrant string. + * + * Parameters: + * quadrant - {String} two character quadrant shortstring + * + * Returns: + * {String} The opposing quadrant ("br" "tr" "tl" "bl"). For Example, if + * you pass in "bl" it returns "tr", if you pass in "br" it + * returns "tl", etc. + */ +OpenLayers.Bounds.oppositeQuadrant = function(quadrant) { + var opp = ""; + + opp += (quadrant.charAt(0) == 't') ? 'b' : 't'; + opp += (quadrant.charAt(1) == 'l') ? 'r' : 'l'; + + return opp; +}; + --- /dev/null +++ b/openlayers/lib/OpenLayers/BaseTypes/Class.js @@ -1,1 +1,115 @@ +/* Copyright (c) 2006-2008 MetaCarta, Inc., published under the Clear BSD + * license. See http://svn.openlayers.org/trunk/openlayers/license.txt for the + * full text of the license. */ +/** + * Constructor: OpenLayers.Class + * Base class used to construct all other classes. Includes support for + * multiple inheritance. + * + * This constructor is new in OpenLayers 2.5. At OpenLayers 3.0, the old + * syntax for creating classes and dealing with inheritance + * will be removed. + * + * To create a new OpenLayers-style class, use the following syntax: + * > var MyClass = OpenLayers.Class(prototype); + * + * To create a new OpenLayers-style class with multiple inheritance, use the + * following syntax: + * > var MyClass = OpenLayers.Class(Class1, Class2, prototype); + * Note that instanceof reflection will only reveil Class1 as superclass. + * Class2 ff are mixins. + * + */ +OpenLayers.Class = function() { + var Class = function() { + /** + * This following condition can be removed at 3.0 - this is only for + * backwards compatibility while the Class.inherit method is still + * in use. So at 3.0, the following three lines would be replaced with + * simply: + * this.initialize.apply(this, arguments); + */ + if (arguments && arguments[0] != OpenLayers.Class.isPrototype) { + this.initialize.apply(this, arguments); + } + }; + var extended = {}; + var parent, initialize; + for(var i=0, len=arguments.length; i 1) { + initialize = arguments[i].prototype.initialize; + // replace the initialize method with an empty function, + // because we do not want to create a real instance here + arguments[i].prototype.initialize = function() {}; + // the line below makes sure that the new class has a + // superclass + extended = new arguments[i]; + // restore the original initialize method + if(initialize === undefined) { + delete arguments[i].prototype.initialize; + } else { + arguments[i].prototype.initialize = initialize; + } + } + // get the prototype of the superclass + parent = arguments[i].prototype; + } else { + // in this case we're extending with the prototype + parent = arguments[i]; + } + OpenLayers.Util.extend(extended, parent); + } + Class.prototype = extended; + return Class; +}; + +/** + * Property: isPrototype + * *Deprecated*. This is no longer needed and will be removed at 3.0. + */ +OpenLayers.Class.isPrototype = function () {}; + +/** + * APIFunction: OpenLayers.create + * *Deprecated*. Old method to create an OpenLayers style class. Use the + * constructor instead. + * + * Returns: + * An OpenLayers class + */ +OpenLayers.Class.create = function() { + return function() { + if (arguments && arguments[0] != OpenLayers.Class.isPrototype) { + this.initialize.apply(this, arguments); + } + }; +}; + + +/** + * APIFunction: inherit + * *Deprecated*. Old method to inherit from one or more OpenLayers style + * classes. Use the constructor instead. + * + * Parameters: + * class - One or more classes can be provided as arguments + * + * Returns: + * An object prototype + */ +OpenLayers.Class.inherit = function () { + var superClass = arguments[0]; + var proto = new superClass(OpenLayers.Class.isPrototype); + for (var i=1, len=arguments.length; i"lon=5,lat=42") + */ + toString:function() { + return ("lon=" + this.lon + ",lat=" + this.lat); + }, + + /** + * APIMethod: toShortString + * + * Returns: + * {String} Shortened String representation of OpenLayers.LonLat object. + * (ex. "5, 42") + */ + toShortString:function() { + return (this.lon + ", " + this.lat); + }, + + /** + * APIMethod: clone + * + * Returns: + * {} New OpenLayers.LonLat object with the same lon + * and lat values + */ + clone:function() { + return new OpenLayers.LonLat(this.lon, this.lat); + }, + + /** + * APIMethod: add + * + * Parameters: + * lon - {Float} + * lat - {Float} + * + * Returns: + * {} A new OpenLayers.LonLat object with the lon and + * lat passed-in added to this's. + */ + add:function(lon, lat) { + if ( (lon == null) || (lat == null) ) { + var msg = OpenLayers.i18n("lonlatAddError"); + OpenLayers.Console.error(msg); + return null; + } + return new OpenLayers.LonLat(this.lon + lon, this.lat + lat); + }, + + /** + * APIMethod: equals + * + * Parameters: + * ll - {} + * + * Returns: + * {Boolean} Boolean value indicating whether the passed-in + * object has the same lon and lat + * components as this. + * Note: if ll passed in is null, returns false + */ + equals:function(ll) { + var equals = false; + if (ll != null) { + equals = ((this.lon == ll.lon && this.lat == ll.lat) || + (isNaN(this.lon) && isNaN(this.lat) && isNaN(ll.lon) && isNaN(ll.lat))); + } + return equals; + }, + + /** + * APIMethod: transform + * Transform the LonLat object from source to dest. This transformation is + * *in place*: if you want a *new* lonlat, use .clone() first. + * + * Parameters: + * source - {} Source projection. + * dest - {} Destination projection. + * + * Returns: + * {} Itself, for use in chaining operations. + */ + transform: function(source, dest) { + var point = OpenLayers.Projection.transform( + {'x': this.lon, 'y': this.lat}, source, dest); + this.lon = point.x; + this.lat = point.y; + return this; + }, + + /** + * APIMethod: wrapDateLine + * + * Parameters: + * maxExtent - {} + * + * Returns: + * {} A copy of this lonlat, but wrapped around the + * "dateline" (as specified by the borders of + * maxExtent) + */ + wrapDateLine: function(maxExtent) { + + var newLonLat = this.clone(); + + if (maxExtent) { + //shift right? + while (newLonLat.lon < maxExtent.left) { + newLonLat.lon += maxExtent.getWidth(); + } + + //shift left? + while (newLonLat.lon > maxExtent.right) { + newLonLat.lon -= maxExtent.getWidth(); + } + } + + return newLonLat; + }, + + CLASS_NAME: "OpenLayers.LonLat" +}); + +/** + * Function: fromString + * Alternative constructor that builds a new from a + * parameter string + * + * Parameters: + * str - {String} Comma-separated Lon,Lat coordinate string. + * (ex. "5,40") + * + * Returns: + * {} New object built from the + * passed-in String. + */ +OpenLayers.LonLat.fromString = function(str) { + var pair = str.split(","); + return new OpenLayers.LonLat(parseFloat(pair[0]), + parseFloat(pair[1])); +}; + --- /dev/null +++ b/openlayers/lib/OpenLayers/BaseTypes/Pixel.js @@ -1,1 +1,125 @@ +/* Copyright (c) 2006-2008 MetaCarta, Inc., published under the Clear BSD + * license. See http://svn.openlayers.org/trunk/openlayers/license.txt for the + * full text of the license. */ +/** + * @requires OpenLayers/Console.js + */ + +/** + * Class: OpenLayers.Pixel + * This class represents a screen coordinate, in x and y coordinates + */ +OpenLayers.Pixel = OpenLayers.Class({ + + /** + * APIProperty: x + * {Number} The x coordinate + */ + x: 0.0, + + /** + * APIProperty: y + * {Number} The y coordinate + */ + y: 0.0, + + /** + * Constructor: OpenLayers.Pixel + * Create a new OpenLayers.Pixel instance + * + * Parameters: + * x - {Number} The x coordinate + * y - {Number} The y coordinate + * + * Returns: + * An instance of OpenLayers.Pixel + */ + initialize: function(x, y) { + this.x = parseFloat(x); + this.y = parseFloat(y); + }, + + /** + * Method: toString + * Cast this object into a string + * + * Returns: + * {String} The string representation of Pixel. ex: "x=200.4,y=242.2" + */ + toString:function() { + return ("x=" + this.x + ",y=" + this.y); + }, + + /** + * APIMethod: clone + * Return a clone of this pixel object + * + * Returns: + * {} A clone pixel + */ + clone:function() { + return new OpenLayers.Pixel(this.x, this.y); + }, + + /** + * APIMethod: equals + * Determine whether one pixel is equivalent to another + * + * Parameters: + * px - {} + * + * Returns: + * {Boolean} The point passed in as parameter is equal to this. Note that + * if px passed in is null, returns false. + */ + equals:function(px) { + var equals = false; + if (px != null) { + equals = ((this.x == px.x && this.y == px.y) || + (isNaN(this.x) && isNaN(this.y) && isNaN(px.x) && isNaN(px.y))); + } + return equals; + }, + + /** + * APIMethod: add + * + * Parameters: + * x - {Integer} + * y - {Integer} + * + * Returns: + * {} A new Pixel with this pixel's x&y augmented by the + * values passed in. + */ + add:function(x, y) { + if ( (x == null) || (y == null) ) { + var msg = OpenLayers.i18n("pixelAddError"); + OpenLayers.Console.error(msg); + return null; + } + return new OpenLayers.Pixel(this.x + x, this.y + y); + }, + + /** + * APIMethod: offset + * + * Parameters + * px - {} + * + * Returns: + * {} A new Pixel with this pixel's x&y augmented by the + * x&y values of the pixel passed in. + */ + offset:function(px) { + var newPx = this.clone(); + if (px) { + newPx = this.add(px.x, px.y); + } + return newPx; + }, + + CLASS_NAME: "OpenLayers.Pixel" +}); + --- /dev/null +++ b/openlayers/lib/OpenLayers/BaseTypes/Size.js @@ -1,1 +1,85 @@ +/* Copyright (c) 2006-2008 MetaCarta, Inc., published under the Clear BSD + * license. See http://svn.openlayers.org/trunk/openlayers/license.txt for the + * full text of the license. */ +/** + * Class: OpenLayers.Size + * Instances of this class represent a width/height pair + */ +OpenLayers.Size = OpenLayers.Class({ + + /** + * APIProperty: w + * {Number} width + */ + w: 0.0, + + /** + * APIProperty: h + * {Number} height + */ + h: 0.0, + + + /** + * Constructor: OpenLayers.Size + * Create an instance of OpenLayers.Size + * + * Parameters: + * w - {Number} width + * h - {Number} height + */ + initialize: function(w, h) { + this.w = parseFloat(w); + this.h = parseFloat(h); + }, + + /** + * Method: toString + * Return the string representation of a size object + * + * Returns: + * {String} The string representation of OpenLayers.Size object. + * (ex. "w=55,h=66") + */ + toString:function() { + return ("w=" + this.w + ",h=" + this.h); + }, + + /** + * APIMethod: clone + * Create a clone of this size object + * + * Returns: + * {} A new OpenLayers.Size object with the same w and h + * values + */ + clone:function() { + return new OpenLayers.Size(this.w, this.h); + }, + + /** + * + * APIMethod: equals + * Determine where this size is equal to another + * + * Parameters: + * sz - {} + * + * Returns: + * {Boolean} The passed in size has the same h and w properties as this one. + * Note that if sz passed in is null, returns false. + * + */ + equals:function(sz) { + var equals = false; + if (sz != null) { + equals = ((this.w == sz.w && this.h == sz.h) || + (isNaN(this.w) && isNaN(this.h) && isNaN(sz.w) && isNaN(sz.h))); + } + return equals; + }, + + CLASS_NAME: "OpenLayers.Size" +}); + --- /dev/null +++ b/openlayers/lib/OpenLayers/Console.js @@ -1,1 +1,246 @@ - +/* Copyright (c) 2006-2008 MetaCarta, Inc., published under the Clear BSD + * license. See http://svn.openlayers.org/trunk/openlayers/license.txt for the + * full text of the license. */ + +/** + * Namespace: OpenLayers.Console + * The OpenLayers.Console namespace is used for debugging and error logging. + * If the Firebug Lite (../Firebug/firebug.js) is included before this script, + * calls to OpenLayers.Console methods will get redirected to window.console. + * This makes use of the Firebug extension where available and allows for + * cross-browser debugging Firebug style. + * + * Note: + * Note that behavior will differ with the Firebug extention and Firebug Lite. + * Most notably, the Firebug Lite console does not currently allow for + * hyperlinks to code or for clicking on object to explore their properties. + * + */ +OpenLayers.Console = { + /** + * Create empty functions for all console methods. The real value of these + * properties will be set if Firebug Lite (../Firebug/firebug.js script) is + * included. We explicitly require the Firebug Lite script to trigger + * functionality of the OpenLayers.Console methods. + */ + + /** + * APIFunction: log + * Log an object in the console. The Firebug Lite console logs string + * representation of objects. Given multiple arguments, they will + * be cast to strings and logged with a space delimiter. If the first + * argument is a string with printf-like formatting, subsequent arguments + * will be used in string substitution. Any additional arguments (beyond + * the number substituted in a format string) will be appended in a space- + * delimited line. + * + * Parameters: + * object - {Object} + */ + log: function() {}, + + /** + * APIFunction: debug + * Writes a message to the console, including a hyperlink to the line + * where it was called. + * + * May be called with multiple arguments as with OpenLayers.Console.log(). + * + * Parameters: + * object - {Object} + */ + debug: function() {}, + + /** + * APIFunction: info + * Writes a message to the console with the visual "info" icon and color + * coding and a hyperlink to the line where it was called. + * + * May be called with multiple arguments as with OpenLayers.Console.log(). + * + * Parameters: + * object - {Object} + */ + info: function() {}, + + /** + * APIFunction: warn + * Writes a message to the console with the visual "warning" icon and + * color coding and a hyperlink to the line where it was called. + * + * May be called with multiple arguments as with OpenLayers.Console.log(). + * + * Parameters: + * object - {Object} + */ + warn: function() {}, + + /** + * APIFunction: error + * Writes a message to the console with the visual "error" icon and color + * coding and a hyperlink to the line where it was called. + * + * May be called with multiple arguments as with OpenLayers.Console.log(). + * + * Parameters: + * object - {Object} + */ + error: function() {}, + + /** + * APIFunction: userError + * A single interface for showing error messages to the user. The default + * behavior is a Javascript alert, though this can be overridden by + * reassigning OpenLayers.Console.userError to a different function. + * + * Expects a single error message + * + * Parameters: + * object - {Object} + */ + userError: function(error) { + alert(error); + }, + + /** + * APIFunction: assert + * Tests that an expression is true. If not, it will write a message to + * the console and throw an exception. + * + * May be called with multiple arguments as with OpenLayers.Console.log(). + * + * Parameters: + * object - {Object} + */ + assert: function() {}, + + /** + * APIFunction: dir + * Prints an interactive listing of all properties of the object. This + * looks identical to the view that you would see in the DOM tab. + * + * Parameters: + * object - {Object} + */ + dir: function() {}, + + /** + * APIFunction: dirxml + * Prints the XML source tree of an HTML or XML element. This looks + * identical to the view that you would see in the HTML tab. You can click + * on any node to inspect it in the HTML tab. + * + * Parameters: + * object - {Object} + */ + dirxml: function() {}, + + /** + * APIFunction: trace + * Prints an interactive stack trace of JavaScript execution at the point + * where it is called. The stack trace details the functions on the stack, + * as well as the values that were passed as arguments to each function. + * You can click each function to take you to its source in the Script tab, + * and click each argument value to inspect it in the DOM or HTML tabs. + * + */ + trace: function() {}, + + /** + * APIFunction: group + * Writes a message to the console and opens a nested block to indent all + * future messages sent to the console. Call OpenLayers.Console.groupEnd() + * to close the block. + * + * May be called with multiple arguments as with OpenLayers.Console.log(). + * + * Parameters: + * object - {Object} + */ + group: function() {}, + + /** + * APIFunction: groupEnd + * Closes the most recently opened block created by a call to + * OpenLayers.Console.group + */ + groupEnd: function() {}, + + /** + * APIFunction: time + * Creates a new timer under the given name. Call + * OpenLayers.Console.timeEnd(name) + * with the same name to stop the timer and print the time elapsed. + * + * Parameters: + * name - {String} + */ + time: function() {}, + + /** + * APIFunction: timeEnd + * Stops a timer created by a call to OpenLayers.Console.time(name) and + * writes the time elapsed. + * + * Parameters: + * name - {String} + */ + timeEnd: function() {}, + + /** + * APIFunction: profile + * Turns on the JavaScript profiler. The optional argument title would + * contain the text to be printed in the header of the profile report. + * + * This function is not currently implemented in Firebug Lite. + * + * Parameters: + * title - {String} Optional title for the profiler + */ + profile: function() {}, + + /** + * APIFunction: profileEnd + * Turns off the JavaScript profiler and prints its report. + * + * This function is not currently implemented in Firebug Lite. + */ + profileEnd: function() {}, + + /** + * APIFunction: count + * Writes the number of times that the line of code where count was called + * was executed. The optional argument title will print a message in + * addition to the number of the count. + * + * This function is not currently implemented in Firebug Lite. + * + * Parameters: + * title - {String} Optional title to be printed with count + */ + count: function() {}, + + CLASS_NAME: "OpenLayers.Console" +}; + +/** + * Execute an anonymous function to extend the OpenLayers.Console namespace + * if the firebug.js script is included. This closure is used so that the + * "scripts" and "i" variables don't pollute the global namespace. + */ +(function() { + /** + * If Firebug Lite is included (before this script), re-route all + * OpenLayers.Console calls to the console object. + */ + var scripts = document.getElementsByTagName("script"); + for(var i=0, len=scripts.length; i var map = new OpenLayers.Map('map', { controls: [] }); + * > + * > map.addControl(new OpenLayers.Control.PanZoomBar()); + * > map.addControl(new OpenLayers.Control.MouseToolbar()); + * > map.addControl(new OpenLayers.Control.LayerSwitcher({'ascending':false})); + * > map.addControl(new OpenLayers.Control.Permalink()); + * > map.addControl(new OpenLayers.Control.Permalink('permalink')); + * > map.addControl(new OpenLayers.Control.MousePosition()); + * > map.addControl(new OpenLayers.Control.OverviewMap()); + * > map.addControl(new OpenLayers.Control.KeyboardDefaults()); + * + * The next code fragment is a quick example of how to intercept + * shift-mouse click to display the extent of the bounding box + * dragged out by the user. Usually controls are not created + * in exactly this manner. See the source for a more complete + * example: + * + * > var control = new OpenLayers.Control(); + * > OpenLayers.Util.extend(control, { + * > draw: function () { + * > // this Handler.Box will intercept the shift-mousedown + * > // before Control.MouseDefault gets to see it + * > this.box = new OpenLayers.Handler.Box( control, + * > {"done": this.notice}, + * > {keyMask: OpenLayers.Handler.MOD_SHIFT}); + * > this.box.activate(); + * > }, + * > + * > notice: function (bounds) { + * > OpenLayers.Console.userError(bounds); + * > } + * > }); + * > map.addControl(control); + * + */ +OpenLayers.Control = OpenLayers.Class({ + + /** + * Property: id + * {String} + */ + id: null, + + /** + * Property: map + * {} this gets set in the addControl() function in + * OpenLayers.Map + */ + map: null, + + /** + * Property: div + * {DOMElement} + */ + div: null, + + /** + * Property: type + * {OpenLayers.Control.TYPES} Controls can have a 'type'. The type + * determines the type of interactions which are possible with them when + * they are placed into a toolbar. + */ + type: null, + + /** + * Property: allowSelection + * {Boolean} By deafault, controls do not allow selection, because + * it may interfere with map dragging. If this is true, OpenLayers + * will not prevent selection of the control. + * Default is false. + */ + allowSelection: false, + + /** + * Property: displayClass + * {string} This property is used for CSS related to the drawing of the + * Control. + */ + displayClass: "", + + /** + * Property: title + * {string} This property is used for showing a tooltip over the + * Control. + */ + title: "", + + /** + * Property: active + * {Boolean} The control is active. + */ + active: null, + + /** + * Property: handler + * {} null + */ + handler: null, + + /** + * APIProperty: eventListeners + * {Object} If set as an option at construction, the eventListeners + * object will be registered with . Object + * structure must be a listeners object as shown in the example for + * the events.on method. + */ + eventListeners: null, + + /** + * Property: events + * {} Events instance for triggering control specific + * events. + */ + events: null, + + /** + * Constant: EVENT_TYPES + * {Array(String)} Supported application event types. Register a listener + * for a particular event with the following syntax: + * (code) + * control.events.register(type, obj, listener); + * (end) + * + * Listeners will be called with a reference to an event object. The + * properties of this event depends on exactly what happened. + * + * All event objects have at least the following properties: + * object - {Object} A reference to control.events.object (a reference + * to the control). + * element - {DOMElement} A reference to control.events.element (which + * will be null unless documented otherwise). + * + * Supported map event types: + * activate - Triggered when activated. + * deactivate - Triggered when deactivated. + */ + EVENT_TYPES: ["activate", "deactivate"], + + /** + * Constructor: OpenLayers.Control + * Create an OpenLayers Control. The options passed as a parameter + * directly extend the control. For example passing the following: + * + * > var control = new OpenLayers.Control({div: myDiv}); + * + * Overrides the default div attribute value of null. + * + * Parameters: + * options - {Object} + */ + initialize: function (options) { + // We do this before the extend so that instances can override + // className in options. + this.displayClass = + this.CLASS_NAME.replace("OpenLayers.", "ol").replace(/\./g, ""); + + OpenLayers.Util.extend(this, options); + + this.events = new OpenLayers.Events(this, null, this.EVENT_TYPES); + if(this.eventListeners instanceof Object) { + this.events.on(this.eventListeners); + } + if (this.id == null) { + this.id = OpenLayers.Util.createUniqueID(this.CLASS_NAME + "_"); + } + }, + + /** + * Method: destroy + * The destroy method is used to perform any clean up before the control + * is dereferenced. Typically this is where event listeners are removed + * to prevent memory leaks. + */ + destroy: function () { + if(this.events) { + if(this.eventListeners) { + this.events.un(this.eventListeners); + } + this.events.destroy(); + this.events = null; + } + this.eventListeners = null; + + // eliminate circular references + if (this.handler) { + this.handler.destroy(); + this.handler = null; + } + if(this.handlers) { + for(var key in this.handlers) { + if(this.handlers.hasOwnProperty(key) && + typeof this.handlers[key].destroy == "function") { + this.handlers[key].destroy(); + } + } + this.handlers = null; + } + if (this.map) { + this.map.removeControl(this); + this.map = null; + } + }, + + /** + * Method: setMap + * Set the map property for the control. This is done through an accessor + * so that subclasses can override this and take special action once + * they have their map variable set. + * + * Parameters: + * map - {} + */ + setMap: function(map) { + this.map = map; + if (this.handler) { + this.handler.setMap(map); + } + }, + + /** + * Method: draw + * The draw method is called when the control is ready to be displayed + * on the page. If a div has not been created one is created. Controls + * with a visual component will almost always want to override this method + * to customize the look of control. + * + * Parameters: + * px - {} The top-left pixel position of the control + * or null. + * + * Returns: + * {DOMElement} A reference to the DIV DOMElement containing the control + */ + draw: function (px) { + if (this.div == null) { + this.div = OpenLayers.Util.createDiv(this.id); + this.div.className = this.displayClass; + if (!this.allowSelection) { + this.div.className += " olControlNoSelect"; + this.div.setAttribute("unselectable", "on", 0); + this.div.onselectstart = function() { return(false); }; + } + if (this.title != "") { + this.div.title = this.title; + } + } + if (px != null) { + this.position = px.clone(); + } + this.moveTo(this.position); + return this.div; + }, + + /** + * Method: moveTo + * Sets the left and top style attributes to the passed in pixel + * coordinates. + * + * Parameters: + * px - {} + */ + moveTo: function (px) { + if ((px != null) && (this.div != null)) { + this.div.style.left = px.x + "px"; + this.div.style.top = px.y + "px"; + } + }, + + /** + * Method: activate + * Explicitly activates a control and it's associated + * handler if one has been set. Controls can be + * deactivated by calling the deactivate() method. + * + * Returns: + * {Boolean} True if the control was successfully activated or + * false if the control was already active. + */ + activate: function () { + if (this.active) { + return false; + } + if (this.handler) { + this.handler.activate(); + } + this.active = true; + if(this.map) { + OpenLayers.Element.addClass( + this.map.viewPortDiv, + this.displayClass.replace(/ /g, "") + "Active" + ); + } + this.events.triggerEvent("activate"); + return true; + }, + + /** + * Method: deactivate + * Deactivates a control and it's associated handler if any. The exact + * effect of this depends on the control itself. + * + * Returns: + * {Boolean} True if the control was effectively deactivated or false + * if the control was already inactive. + */ + deactivate: function () { + if (this.active) { + if (this.handler) { + this.handler.deactivate(); + } + this.active = false; + if(this.map) { + OpenLayers.Element.removeClass( + this.map.viewPortDiv, + this.displayClass.replace(/ /g, "") + "Active" + ); + } + this.events.triggerEvent("deactivate"); + return true; + } + return false; + }, + + CLASS_NAME: "OpenLayers.Control" +}); + +OpenLayers.Control.TYPE_BUTTON = 1; +OpenLayers.Control.TYPE_TOGGLE = 2; +OpenLayers.Control.TYPE_TOOL = 3; + --- /dev/null +++ b/openlayers/lib/OpenLayers/Control/ArgParser.js @@ -1,1 +1,166 @@ +/* Copyright (c) 2006-2008 MetaCarta, Inc., published under the Clear BSD + * license. See http://svn.openlayers.org/trunk/openlayers/license.txt for the + * full text of the license. */ + +/** + * @requires OpenLayers/Control.js + */ + +/** + * Class: OpenLayers.Control.ArgParser + * The ArgParser control adds location bar querystring parsing functionality + * to an OpenLayers Map. + * When added to a Map control, on a page load/refresh, the Map will + * automatically take the href string and parse it for lon, lat, zoom, and + * layers information. + * + * Inherits from: + * - + */ +OpenLayers.Control.ArgParser = OpenLayers.Class(OpenLayers.Control, { + + /** + * Parameter: center + * {} + */ + center: null, + + /** + * Parameter: zoom + * {int} + */ + zoom: null, + + /** + * Parameter: layers + * {Array()} + */ + layers: null, + + /** + * APIProperty: displayProjection + * {} Requires proj4js support. + * Projection used when reading the coordinates from the URL. This will + * + * reproject the map coordinates from the URL into the map's + * projection. + * + * If you are using this functionality, be aware that any permalink + * which is added to the map will determine the coordinate type which + * is read from the URL, which means you should not add permalinks with + * different displayProjections to the same map. + */ + displayProjection: null, + + /** + * Constructor: OpenLayers.Control.ArgParser + * + * Parameters: + * options - {Object} + */ + initialize: function(options) { + OpenLayers.Control.prototype.initialize.apply(this, arguments); + }, + + /** + * Method: setMap + * Set the map property for the control. + * + * Parameters: + * map - {} + */ + setMap: function(map) { + OpenLayers.Control.prototype.setMap.apply(this, arguments); + + //make sure we dont already have an arg parser attached + for(var i=0, len=this.map.controls.length; i + */ +OpenLayers.Control.Attribution = + OpenLayers.Class(OpenLayers.Control, { + + /** + * APIProperty: seperator + * {String} String used to seperate layers. + */ + separator: ", ", + + /** + * Constructor: OpenLayers.Control.Attribution + * + * Parameters: + * options - {Object} Options for control. + */ + initialize: function(options) { + OpenLayers.Control.prototype.initialize.apply(this, arguments); + }, + + /** + * Method: destroy + * Destroy control. + */ + destroy: function() { + this.map.events.un({ + "removelayer": this.updateAttribution, + "addlayer": this.updateAttribution, + "changelayer": this.updateAttribution, + "changebaselayer": this.updateAttribution, + scope: this + }); + + OpenLayers.Control.prototype.destroy.apply(this, arguments); + }, + + /** + * Method: draw + * Initialize control. + * + * Returns: + * {DOMElement} A reference to the DIV DOMElement containing the control + */ + draw: function() { + OpenLayers.Control.prototype.draw.apply(this, arguments); + + this.map.events.on({ + 'changebaselayer': this.updateAttribution, + 'changelayer': this.updateAttribution, + 'addlayer': this.updateAttribution, + 'removelayer': this.updateAttribution, + scope: this + }); + this.updateAttribution(); + + return this.div; + }, + + /** + * Method: updateAttribution + * Update attribution string. + */ + updateAttribution: function() { + var attributions = []; + if (this.map && this.map.layers) { + for(var i=0, len=this.map.layers.length; i. + * When clicked, the function trigger() is executed. + * + * Inherits from: + * - + * + * Use: + * (code) + * var button = new OpenLayers.Control.Button({ + * displayClass: "MyButton", trigger: myFunction + * }); + * panel.addControls([button]); + * (end) + * + * Will create a button with CSS class MyButtonItemInactive, that + * will call the function MyFunction() when clicked. + */ +OpenLayers.Control.Button = OpenLayers.Class(OpenLayers.Control, { + /** + * Property: type + * {Integer} OpenLayers.Control.TYPE_BUTTON. + */ + type: OpenLayers.Control.TYPE_BUTTON, + + /** + * Method: trigger + * Called by a control panel when the button is clicked. + */ + trigger: function() {}, + + CLASS_NAME: "OpenLayers.Control.Button" +}); + --- /dev/null +++ b/openlayers/lib/OpenLayers/Control/DragFeature.js @@ -1,1 +1,294 @@ - +/* Copyright (c) 2006-2008 MetaCarta, Inc., published under the Clear BSD + * license. See http://svn.openlayers.org/trunk/openlayers/license.txt for the + * full text of the license. */ + + +/** + * @requires OpenLayers/Control.js + * @requires OpenLayers/Handler/Drag.js + * @requires OpenLayers/Handler/Feature.js + */ + +/** + * Class: OpenLayers.Control.DragFeature + * The DragFeature control moves a feature with a drag of the mouse. Create a + * new control with the constructor. + * + * Inherits From: + * - + */ +OpenLayers.Control.DragFeature = OpenLayers.Class(OpenLayers.Control, { + + /** + * APIProperty: geometryTypes + * {Array(String)} To restrict dragging to a limited set of geometry types, + * send a list of strings corresponding to the geometry class names. + */ + geometryTypes: null, + + /** + * APIProperty: onStart + * {Function} Define this function if you want to know when a drag starts. + * The function should expect to receive two arguments: the feature + * that is about to be dragged and the pixel location of the mouse. + * + * Parameters: + * feature - {} The feature that is about to be + * dragged. + * pixel - {} The pixel location of the mouse. + */ + onStart: function(feature, pixel) {}, + + /** + * APIProperty: onDrag + * {Function} Define this function if you want to know about each move of a + * feature. The function should expect to receive two arguments: the + * feature that is being dragged and the pixel location of the mouse. + * + * Parameters: + * feature - {} The feature that was dragged. + * pixel - {} The pixel location of the mouse. + */ + onDrag: function(feature, pixel) {}, + + /** + * APIProperty: onComplete + * {Function} Define this function if you want to know when a feature is + * done dragging. The function should expect to receive two arguments: + * the feature that is being dragged and the pixel location of the + * mouse. + * + * Parameters: + * feature - {} The feature that was dragged. + * pixel - {} The pixel location of the mouse. + */ + onComplete: function(feature, pixel) {}, + + /** + * Property: layer + * {} + */ + layer: null, + + /** + * Property: feature + * {} + */ + feature: null, + + /** + * Property: dragCallbacks + * {Object} The functions that are sent to the drag handler for callback. + */ + dragCallbacks: {}, + + /** + * Property: featureCallbacks + * {Object} The functions that are sent to the feature handler for callback. + */ + featureCallbacks: {}, + + /** + * Property: lastPixel + * {} + */ + lastPixel: null, + + /** + * Constructor: OpenLayers.Control.DragFeature + * Create a new control to drag features. + * + * Parameters: + * layer - {} The layer containing features to be + * dragged. + * options - {Object} Optional object whose properties will be set on the + * control. + */ + initialize: function(layer, options) { + OpenLayers.Control.prototype.initialize.apply(this, [options]); + this.layer = layer; + this.handlers = { + drag: new OpenLayers.Handler.Drag( + this, OpenLayers.Util.extend({ + down: this.downFeature, + move: this.moveFeature, + up: this.upFeature, + out: this.cancel, + done: this.doneDragging + }, this.dragCallbacks) + ), + feature: new OpenLayers.Handler.Feature( + this, this.layer, OpenLayers.Util.extend({ + over: this.overFeature, + out: this.outFeature + }, this.featureCallbacks), + {geometryTypes: this.geometryTypes} + ) + }; + }, + + /** + * APIMethod: destroy + * Take care of things that are not handled in superclass + */ + destroy: function() { + this.layer = null; + OpenLayers.Control.prototype.destroy.apply(this, []); + }, + + /** + * APIMethod: activate + * Activate the control and the feature handler. + * + * Returns: + * {Boolean} Successfully activated the control and feature handler. + */ + activate: function() { + return (this.handlers.feature.activate() && + OpenLayers.Control.prototype.activate.apply(this, arguments)); + }, + + /** + * APIMethod: deactivate + * Deactivate the control and all handlers. + * + * Returns: + * {Boolean} Successfully deactivated the control. + */ + deactivate: function() { + // the return from the handlers is unimportant in this case + this.handlers.drag.deactivate(); + this.handlers.feature.deactivate(); + this.feature = null; + this.dragging = false; + this.lastPixel = null; + OpenLayers.Element.removeClass( + this.map.viewPortDiv, this.displayClass + "Over" + ); + return OpenLayers.Control.prototype.deactivate.apply(this, arguments); + }, + + /** + * Method: overFeature + * Called when the feature handler detects a mouse-over on a feature. + * This activates the drag handler. + * + * Parameters: + * feature - {} The selected feature. + */ + overFeature: function(feature) { + if(!this.handlers.drag.dragging) { + this.feature = feature; + this.handlers.drag.activate(); + this.over = true; + OpenLayers.Element.addClass(this.map.viewPortDiv, this.displayClass + "Over"); + } else { + if(this.feature.id == feature.id) { + this.over = true; + } else { + this.over = false; + } + } + }, + + /** + * Method: downFeature + * Called when the drag handler detects a mouse-down. + * + * Parameters: + * pixel - {} Location of the mouse event. + */ + downFeature: function(pixel) { + this.lastPixel = pixel; + this.onStart(this.feature, pixel); + }, + + /** + * Method: moveFeature + * Called when the drag handler detects a mouse-move. Also calls the + * optional onDrag method. + * + * Parameters: + * pixel - {} Location of the mouse event. + */ + moveFeature: function(pixel) { + var res = this.map.getResolution(); + this.feature.geometry.move(res * (pixel.x - this.lastPixel.x), + res * (this.lastPixel.y - pixel.y)); + this.layer.drawFeature(this.feature); + this.lastPixel = pixel; + this.onDrag(this.feature, pixel); + }, + + /** + * Method: upFeature + * Called when the drag handler detects a mouse-up. + * + * Parameters: + * pixel - {} Location of the mouse event. + */ + upFeature: function(pixel) { + if(!this.over) { + this.handlers.drag.deactivate(); + } + }, + + /** + * Method: doneDragging + * Called when the drag handler is done dragging. + * + * Parameters: + * pixel - {} The last event pixel location. If this event + * came from a mouseout, this may not be in the map viewport. + */ + doneDragging: function(pixel) { + this.onComplete(this.feature, pixel); + }, + + /** + * Method: outFeature + * Called when the feature handler detects a mouse-out on a feature. + * + * Parameters: + * feature - {} The feature that the mouse left. + */ + outFeature: function(feature) { + if(!this.handlers.drag.dragging) { + this.over = false; + this.handlers.drag.deactivate(); + OpenLayers.Element.removeClass( + this.map.viewPortDiv, this.displayClass + "Over" + ); + this.feature = null; + } else { + if(this.feature.id == feature.id) { + this.over = false; + } + } + }, + + /** + * Method: cancel + * Called when the drag handler detects a mouse-out (from the map viewport). + */ + cancel: function() { + this.handlers.drag.deactivate(); + this.over = false; + }, + + /** + * Method: setMap + * Set the map property for the control and all handlers. + * + * Parameters: + * map - {} The control's map. + */ + setMap: function(map) { + this.handlers.drag.setMap(map); + this.handlers.feature.setMap(map); + OpenLayers.Control.prototype.setMap.apply(this, arguments); + }, + + CLASS_NAME: "OpenLayers.Control.DragFeature" +}); + --- /dev/null +++ b/openlayers/lib/OpenLayers/Control/DragPan.js @@ -1,1 +1,86 @@ +/* Copyright (c) 2006-2008 MetaCarta, Inc., published under the Clear BSD + * license. See http://svn.openlayers.org/trunk/openlayers/license.txt for the + * full text of the license. */ +/** + * @requires OpenLayers/Control.js + * @requires OpenLayers/Handler/Drag.js + */ + +/** + * Class: OpenLayers.Control.DragPan + * The DragPan control pans the map with a drag of the mouse. + * + * Inherits from: + * - + */ +OpenLayers.Control.DragPan = OpenLayers.Class(OpenLayers.Control, { + + /** + * Property: type + * {OpenLayers.Control.TYPES} + */ + type: OpenLayers.Control.TYPE_TOOL, + + /** + * Property: panned + * {Boolean} The map moved. + */ + panned: false, + + /** + * Property: interval + * {Integer} The number of milliseconds that should ellapse before + * panning the map again. Set this to increase dragging performance. + * Defaults to 25 milliseconds. + */ + interval: 25, + + /** + * Method: draw + * Creates a Drag handler, using and + * as callbacks. + */ + draw: function() { + this.handler = new OpenLayers.Handler.Drag(this, { + "move": this.panMap, + "done": this.panMapDone + }, { + interval: this.interval + } + ); + }, + + /** + * Method: panMap + * + * Parameters: + * xy - {} Pixel of the mouse position + */ + panMap: function(xy) { + this.panned = true; + this.map.pan( + this.handler.last.x - xy.x, + this.handler.last.y - xy.y, + {dragging: this.handler.dragging, animate: false} + ); + }, + + /** + * Method: panMapDone + * Finish the panning operation. Only call setCenter (through ) + * if the map has actually been moved. + * + * Parameters: + * xy - {} Pixel of the mouse position + */ + panMapDone: function(xy) { + if(this.panned) { + this.panMap(xy); + this.panned = false; + } + }, + + CLASS_NAME: "OpenLayers.Control.DragPan" +}); + --- /dev/null +++ b/openlayers/lib/OpenLayers/Control/DrawFeature.js @@ -1,1 +1,116 @@ +/* Copyright (c) 2006-2008 MetaCarta, Inc., published under the Clear BSD + * license. See http://svn.openlayers.org/trunk/openlayers/license.txt for the + * full text of the license. */ + +/** + * @requires OpenLayers/Control.js + * @requires OpenLayers/Feature/Vector.js + */ + +/** + * Class: OpenLayers.Control.DrawFeature + * The DrawFeature control draws point, line or polygon features on a vector + * layer when active. + * + * Inherits from: + * - + */ +OpenLayers.Control.DrawFeature = OpenLayers.Class(OpenLayers.Control, { + + /** + * Property: layer + * {} + */ + layer: null, + + /** + * Property: callbacks + * {Object} The functions that are sent to the handler for callback + */ + callbacks: null, + + /** + * Constant: EVENT_TYPES + * + * Supported event types: + * featureadded - Triggered when a feature is added + */ + EVENT_TYPES: ["featureadded"], + + /** + * APIProperty: featureAdded + * {Function} Called after each feature is added + */ + featureAdded: function() {}, + + /** + * APIProperty: handlerOptions + * {Object} Used to set non-default properties on the control's handler + */ + handlerOptions: null, + + /** + * Constructor: OpenLayers.Control.DrawFeature + * + * Parameters: + * layer - {} + * handler - {} + * options - {Object} + */ + initialize: function(layer, handler, options) { + + // concatenate events specific to vector with those from the base + this.EVENT_TYPES = + OpenLayers.Control.DrawFeature.prototype.EVENT_TYPES.concat( + OpenLayers.Control.prototype.EVENT_TYPES + ); + + OpenLayers.Control.prototype.initialize.apply(this, [options]); + this.callbacks = OpenLayers.Util.extend( + { + done: this.drawFeature, + modify: function(vertex, feature) { + this.layer.events.triggerEvent( + "sketchmodified", {vertex: vertex, feature: feature} + ); + }, + create: function(vertex, feature) { + this.layer.events.triggerEvent( + "sketchstarted", {vertex: vertex, feature: feature} + ); + } + }, + this.callbacks + ); + this.layer = layer; + var sketchStyle = this.layer.styleMap && this.layer.styleMap.styles.temporary; + if(sketchStyle) { + this.handlerOptions = this.handlerOptions || {}; + this.handlerOptions.layerOptions = OpenLayers.Util.applyDefaults( + this.handlerOptions.layerOptions, + {styleMap: new OpenLayers.StyleMap({"default": sketchStyle})} + ); + } + this.handler = new handler(this, this.callbacks, this.handlerOptions); + }, + + /** + * Method: drawFeature + */ + drawFeature: function(geometry) { + var feature = new OpenLayers.Feature.Vector(geometry); + var proceed = this.layer.events.triggerEvent( + "sketchcomplete", {feature: feature} + ); + if(proceed !== false) { + feature.state = OpenLayers.State.INSERT; + this.layer.addFeatures([feature]); + this.featureAdded(feature); + this.events.triggerEvent("featureadded",{feature : feature}); + } + }, + + CLASS_NAME: "OpenLayers.Control.DrawFeature" +}); + --- /dev/null +++ b/openlayers/lib/OpenLayers/Control/EditingToolbar.js @@ -1,1 +1,63 @@ +/* Copyright (c) 2006-2008 MetaCarta, Inc., published under the Clear BSD + * license. See http://svn.openlayers.org/trunk/openlayers/license.txt for the + * full text of the license. */ +/** + * @requires OpenLayers/Control/Panel.js + * @requires OpenLayers/Control/Navigation.js + * @requires OpenLayers/Control/DrawFeature.js + * @requires OpenLayers/Handler/Point.js + * @requires OpenLayers/Handler/Path.js + * @requires OpenLayers/Handler/Polygon.js + */ + +/** + * Class: OpenLayers.Control.EditingToolbar + * The EditingToolbar is a panel of 4 controls to draw polygons, lines, + * points, or to navigate the map by panning. By default it appears in the + * upper right corner of the map. + * + * Inherits from: + * - + */ +OpenLayers.Control.EditingToolbar = OpenLayers.Class( + OpenLayers.Control.Panel, { + + /** + * Constructor: OpenLayers.Control.EditingToolbar + * Create an editing toolbar for a given layer. + * + * Parameters: + * layer - {} + * options - {Object} + */ + initialize: function(layer, options) { + OpenLayers.Control.Panel.prototype.initialize.apply(this, [options]); + + this.addControls( + [ new OpenLayers.Control.Navigation() ] + ); + var controls = [ + new OpenLayers.Control.DrawFeature(layer, OpenLayers.Handler.Point, {'displayClass': 'olControlDrawFeaturePoint'}), + new OpenLayers.Control.DrawFeature(layer, OpenLayers.Handler.Path, {'displayClass': 'olControlDrawFeaturePath'}), + new OpenLayers.Control.DrawFeature(layer, OpenLayers.Handler.Polygon, {'displayClass': 'olControlDrawFeaturePolygon'}) + ]; + this.addControls(controls); + }, + + /** + * Method: draw + * calls the default draw, and then activates mouse defaults. + * + * Returns: + * {DOMElement} + */ + draw: function() { + var div = OpenLayers.Control.Panel.prototype.draw.apply(this, arguments); + this.activateControl(this.controls[0]); + return div; + }, + + CLASS_NAME: "OpenLayers.Control.EditingToolbar" +}); + --- /dev/null +++ b/openlayers/lib/OpenLayers/Control/GetFeature.js @@ -1,1 +1,559 @@ - +/* Copyright (c) 2006-2008 MetaCarta, Inc., published under the Clear BSD + * license. See http://svn.openlayers.org/trunk/openlayers/license.txt for the + * full text of the license. */ + +/** + * @requires OpenLayers/Control.js + * @requires OpenLayers/Handler/Click.js + * @requires OpenLayers/Handler/Box.js + * @requires OpenLayers/Handler/Hover.js + * @requires OpenLayers/Filter/Spatial.js + */ + +/** + * Class: OpenLayers.Control.GetFeature + * Gets vector features for locations underneath the mouse cursor. Can be + * configured to act on click, hover or dragged boxes. Uses an + * that supports spatial filters (BBOX) to retrieve + * features from a server and fires events that notify applications of the + * selected features. + * + * Inherits from: + * - + */ +OpenLayers.Control.GetFeature = OpenLayers.Class(OpenLayers.Control, { + + /** + * APIProperty: protocol + * {} Required. The protocol used for fetching + * features. + */ + protocol: null, + + /** + * APIProperty: multipleKey + * {String} An event modifier ('altKey' or 'shiftKey') that temporarily sets + * the property to true. Default is null. + */ + multipleKey: null, + + /** + * APIProperty: toggleKey + * {String} An event modifier ('altKey' or 'shiftKey') that temporarily sets + * the property to true. Default is null. + */ + toggleKey: null, + + /** + * Property: modifiers + * {Object} The event modifiers to use, according to the current event + * being handled by this control's handlers + */ + modifiers: null, + + /** + * APIProperty: multiple + * {Boolean} Allow selection of multiple geometries. Default is false. + */ + multiple: false, + + /** + * APIProperty: click + * {Boolean} Use a click handler for selecting/unselecting features. + * Default is true. + */ + click: true, + + /** + * APIProperty: clickout + * {Boolean} Unselect features when clicking outside any feature. + * Applies only if is true. Default is true. + */ + clickout: true, + + /** + * APIProperty: toggle + * {Boolean} Unselect a selected feature on click. Applies only if + * is true. Default is false. + */ + toggle: false, + + /** + * APIProperty: clickTolerance + * {Integer} Tolerance for the BBOX query in pixels. This has the + * same effect as the tolerance parameter on WMS GetFeatureInfo + * requests. Will be ignored for box selections. Applies only if + * is true. Default is 5. + */ + clickTolerance: 5, + + /** + * APIProperty: hover + * {Boolean} Send feature requests on mouse moves. Default is false. + */ + hover: false, + + /** + * APIProperty: box + * {Boolean} Allow feature selection by drawing a box. + */ + box: false, + + /** + * APIProperty: maxFeatures + * {Integer} Maximum number of features to return from a query, if + * supported by the . Default is 10. + */ + maxFeatures: 10, + + /** + * Property: features + * {Object} Hash of {}, keyed by fid, holding + * the currently selected features + */ + features: null, + + /** + * Proeprty: hoverFeature + * {} The feature currently selected by the + * hover handler + */ + hoverFeature: null, + + /** + * APIProperty: handlerOptions + * {Object} Additional options for the handlers used by this control. This + * is a hash with the keys "click", "box" and "hover". + */ + handlerOptions: null, + + /** + * Property: handlers + * {Object} Object with references to multiple + * instances. + */ + handlers: null, + + /** + * Property: hoverResponse + * {} The response object associated with + * the currently running hover request (if any). + */ + hoverResponse: null, + + /** + * Constant: EVENT_TYPES + * + * Supported event types: + * beforefeatureselected - Triggered when is true before a + * feature is selected. The event object has a feature property with + * the feature about to select + * featureselected - Triggered when is true and a feature is + * selected. The event object has a feature property with the + * selected feature + * featureunselected - Triggered when is true and a feature is + * unselected. The event object has a feature property with the + * unselected feature + * clickout - Triggered when when is true and no feature was + * selected. + * hoverfeature - Triggered when is true and the mouse has + * stopped over a feature + * outfeature - Triggered when is true and the mouse moves + * moved away from a hover-selected feature + */ + EVENT_TYPES: ["featureselected", "featureunselected", "clickout", + "beforefeatureselected", "hoverfeature", "outfeature"], + + /** + * Constructor: OpenLayers.Control.GetFeature + * Create a new control for fetching remote features. + * + * Parameters: + * options - {Object} A configuration object which at least has to contain + * a property + */ + initialize: function(options) { + // concatenate events specific to vector with those from the base + this.EVENT_TYPES = + OpenLayers.Control.GetFeature.prototype.EVENT_TYPES.concat( + OpenLayers.Control.prototype.EVENT_TYPES + ); + + options.handlerOptions = options.handlerOptions || {}; + + OpenLayers.Control.prototype.initialize.apply(this, [options]); + + this.features = {}; + + this.handlers = {}; + + if(this.click) { + this.handlers.click = new OpenLayers.Handler.Click(this, + {click: this.selectSingle}, this.handlerOptions.click || {}) + }; + + if(this.box) { + this.handlers.box = new OpenLayers.Handler.Box( + this, {done: this.selectBox}, + OpenLayers.Util.extend(this.handlerOptions.box, { + boxDivClassName: "olHandlerBoxSelectFeature" + }) + ); + } + + if(this.hover) { + this.handlers.hover = new OpenLayers.Handler.Hover( + this, {'move': this.cancelHover, 'pause': this.selectHover}, + OpenLayers.Util.extend(this.handlerOptions.hover, { + 'delay': 250 + }) + ); + } + }, + + /** + * Method: activate + * Activates the control. + * + * Returns: + * {Boolean} The control was effectively activated. + */ + activate: function () { + if (!this.active) { + for(var i in this.handlers) { + this.handlers[i].activate(); + } + } + return OpenLayers.Control.prototype.activate.apply( + this, arguments + ); + }, + + /** + * Method: deactivate + * Deactivates the control. + * + * Returns: + * {Boolean} The control was effectively deactivated. + */ + deactivate: function () { + if (this.active) { + for(var i in this.handlers) { + this.handlers[i].deactivate(); + } + } + return OpenLayers.Control.prototype.deactivate.apply( + this, arguments + ); + }, + + /** + * Method: unselectAll + * Unselect all selected features. To unselect all except for a single + * feature, set the options.except property to the feature. + * + * Parameters: + * options - {Object} Optional configuration object. + */ + unselectAll: function(options) { + // we'll want an option to supress notification here + var feature; + for(var i=this.features.length-1; i>=0; --i) { + feature = this.features[i]; + if(!options || options.except != feature) { + this.unselect(feature); + } + } + }, + + /** + * Method: selectSingle + * Called on click + * + * Parameters: + * evt - {} + */ + selectSingle: function(evt) { + // Set the cursor to "wait" to tell the user we're working on their click. + OpenLayers.Element.addClass(this.map.viewPortDiv, "olCursorWait"); + + var bounds = this.pixelToBounds(evt.xy); + + this.setModifiers(evt); + this.request(bounds, {single: true}); + }, + + /** + * Method: selectBox + * Callback from the handlers.box set up when selection is on + * + * Parameters: + * position - {} + */ + selectBox: function(position) { + if (position instanceof OpenLayers.Bounds) { + var minXY = this.map.getLonLatFromPixel( + new OpenLayers.Pixel(position.left, position.bottom) + ); + var maxXY = this.map.getLonLatFromPixel( + new OpenLayers.Pixel(position.right, position.top) + ); + var bounds = new OpenLayers.Bounds( + minXY.lon, minXY.lat, maxXY.lon, maxXY.lat + ); + + this.setModifiers(this.handlers.box.dragHandler.evt); + this.request(bounds); + } + }, + + /** + * Method selectHover + * Callback from the handlers.hover set up when selection is on + * + * Parameters: + * evt {Object} - event object with an xy property + */ + selectHover: function(evt) { + var bounds = this.pixelToBounds(evt.xy); + this.request(bounds, {single: true, hover: true}); + }, + + /** + * Method: cancelHover + * Callback from the handlers.hover set up when selection is on + */ + cancelHover: function() { + if (this.hoverResponse) { + this.protocol.abort(this.hoverResponse); + this.hoverResponse = null; + } + }, + + /** + * Method: request + * Sends a GetFeature request to the WFS + * + * Parameters: + * bounds - {} bounds for the request's BBOX filter + * options - {Object} additional options for this method. + * + * Supported options include: + * single - {Boolean} A single feature should be returned. + * Note that this will be ignored if the protocol does not + * return the geometries of the features. + * hover - {Boolean} Do the request for the hover handler. + */ + request: function(bounds, options) { + options = options || {}; + var filter = new OpenLayers.Filter.Spatial({ + type: OpenLayers.Filter.Spatial.BBOX, + value: bounds + }); + + var response = this.protocol.read({ + maxFeatures: options.single == true ? this.maxFeatures : undefined, + filter: filter, + callback: function(result) { + if(result.code == 1) { + if(result.features.length) { + if(options.single == true) { + this.selectBestFeature(result.features, + bounds.getCenterLonLat(), options); + } else { + this.select(result.features); + } + } else if(options.hover) { + this.hoverSelect(); + } else { + this.events.triggerEvent("clickout"); + if(this.clickout) { + this.unselectAll(); + } + } + } + // Reset the cursor. + OpenLayers.Element.removeClass(this.map.viewPortDiv, "olCursorWait"); + }, + scope: this + }); + if(options.hover == true) { + this.hoverResponse = response; + } + }, + + /** + * Method: selectBestFeature + * Selects the feature from an array of features that is the best match + * for the click position. + * + * Parameters: + * features - {Array()} + * clickPosition - {} + * options - {Object} additional options for this method + * + * Supported options include: + * hover - {Boolean} Do the selection for the hover handler. + */ + selectBestFeature: function(features, clickPosition, options) { + options = options || {}; + if(features.length) { + var point = new OpenLayers.Geometry.Point(clickPosition.lon, + clickPosition.lat); + var feature, resultFeature, dist; + var minDist = Number.MAX_VALUE; + for(var i=0; i} + */ + setModifiers: function(evt) { + this.modifiers = { + multiple: this.multiple || (this.multipleKey && evt[this.multipleKey]), + toggle: this.toggle || (this.toggleKey && evt[this.toggleKey]) + } + }, + + /** + * Method: select + * Add feature to the hash of selected features and trigger the + * featureselected event. + * + * Parameters: + * features - {} or an array of features + */ + select: function(features) { + if(!this.modifiers.multiple && !this.modifiers.toggle) { + this.unselectAll(); + } + if(!(features instanceof Array)) { + features = [features]; + } + + var feature; + for(var i=0, len=features.length; i + * + * Parameters: + * feature - {} the feature to hover-select. + * If none is provided, the current will be nulled and + * the outfeature event will be triggered. + */ + hoverSelect: function(feature) { + var fid = feature ? feature.fid || feature.id : null; + var hfid = this.hoverFeature ? + this.hoverFeature.fid || this.hoverFeature.id : null; + + if(hfid && hfid != fid) { + this.events.triggerEvent("outfeature", + {feature: this.hoverFeature}); + this.hoverFeature = null; + } + if(fid && fid != hfid) { + this.events.triggerEvent("hoverfeature", {feature: feature}); + this.hoverFeature = feature; + } + }, + + /** + * Method: unselect + * Remove feature from the hash of selected features and trigger the + * featureunselected event. + * + * Parameters: + * feature - {} + */ + unselect: function(feature) { + delete this.features[feature.fid || feature.id]; + this.events.triggerEvent("featureunselected", {feature: feature}); + }, + + /** + * Method: unselectAll + * Unselect all selected features. + */ + unselectAll: function() { + // we'll want an option to supress notification here + for(var fid in this.features) { + this.unselect(this.features[fid]); + } + }, + + /** + * Method: setMap + * Set the map property for the control. + * + * Parameters: + * map - {} + */ + setMap: function(map) { + for(var i in this.handlers) { + this.handlers[i].setMap(map); + } + OpenLayers.Control.prototype.setMap.apply(this, arguments); + }, + + /** + * Method: pixelToBounds + * Takes a pixel as argument and creates bounds after adding the + * . + * + * Parameters: + * pixel - {} + */ + pixelToBounds: function(pixel) { + var llPx = pixel.add(-this.clickTolerance/2, this.clickTolerance/2); + var urPx = pixel.add(this.clickTolerance/2, -this.clickTolerance/2); + var ll = this.map.getLonLatFromPixel(llPx); + var ur = this.map.getLonLatFromPixel(urPx); + return new OpenLayers.Bounds(ll.lon, ll.lat, ur.lon, ur.lat); + }, + + CLASS_NAME: "OpenLayers.Control.GetFeature" +}); + --- /dev/null +++ b/openlayers/lib/OpenLayers/Control/KeyboardDefaults.js @@ -1,1 +1,122 @@ +/* Copyright (c) 2006-2008 MetaCarta, Inc., published under the Clear BSD + * license. See http://svn.openlayers.org/trunk/openlayers/license.txt for the + * full text of the license. */ + +/** + * @requires OpenLayers/Control.js + * @requires OpenLayers/Handler/Keyboard.js + */ + +/** + * Class: OpenLayers.Control.KeyboardDefaults + * The KeyboardDefaults control adds panning and zooming functions, controlled + * with the keyboard. By default arrow keys pan, +/- keys zoom & Page Up/Page + * Down/Home/End scroll by three quarters of a page. + * + * This control has no visible appearance. + * + * Inherits from: + * - + */ +OpenLayers.Control.KeyboardDefaults = OpenLayers.Class(OpenLayers.Control, { + + /** + * APIProperty: slideFactor + * Pixels to slide by. + */ + slideFactor: 75, + + /** + * Constructor: OpenLayers.Control.KeyboardDefaults + */ + initialize: function() { + OpenLayers.Control.prototype.initialize.apply(this, arguments); + }, + + /** + * APIMethod: destroy + */ + destroy: function() { + if (this.handler) { + this.handler.destroy(); + } + this.handler = null; + + OpenLayers.Control.prototype.destroy.apply(this, arguments); + }, + + /** + * Method: draw + * Create handler. + */ + draw: function() { + this.handler = new OpenLayers.Handler.Keyboard( this, { + "keydown": this.defaultKeyPress }); + this.activate(); + }, + + /** + * Method: defaultKeyPress + * When handling the key event, we only use evt.keyCode. This holds + * some drawbacks, though we get around them below. When interpretting + * the keycodes below (including the comments associated with them), + * consult the URL below. For instance, the Safari browser returns + * "IE keycodes", and so is supported by any keycode labeled "IE". + * + * Very informative URL: + * http://unixpapa.com/js/key.html + * + * Parameters: + * code - {Integer} + */ + defaultKeyPress: function (evt) { + switch(evt.keyCode) { + case OpenLayers.Event.KEY_LEFT: + this.map.pan(-this.slideFactor, 0); + break; + case OpenLayers.Event.KEY_RIGHT: + this.map.pan(this.slideFactor, 0); + break; + case OpenLayers.Event.KEY_UP: + this.map.pan(0, -this.slideFactor); + break; + case OpenLayers.Event.KEY_DOWN: + this.map.pan(0, this.slideFactor); + break; + + case 33: // Page Up. Same in all browsers. + var size = this.map.getSize(); + this.map.pan(0, -0.75*size.h); + break; + case 34: // Page Down. Same in all browsers. + var size = this.map.getSize(); + this.map.pan(0, 0.75*size.h); + break; + case 35: // End. Same in all browsers. + var size = this.map.getSize(); + this.map.pan(0.75*size.w, 0); + break; + case 36: // Home. Same in all browsers. + var size = this.map.getSize(); + this.map.pan(-0.75*size.w, 0); + break; + + case 43: // +/= (ASCII), keypad + (ASCII, Opera) + case 61: // +/= (Mozilla, Opera, some ASCII) + case 187: // +/= (IE) + case 107: // keypad + (IE, Mozilla) + this.map.zoomIn(); + break; + case 45: // -/_ (ASCII, Opera), keypad - (ASCII, Opera) + case 109: // -/_ (Mozilla), keypad - (Mozilla, IE) + case 189: // -/_ (IE) + case 95: // -/_ (some ASCII) + this.map.zoomOut(); + break; + } + }, + + CLASS_NAME: "OpenLayers.Control.KeyboardDefaults" +}); + --- /dev/null +++ b/openlayers/lib/OpenLayers/Control/LayerSwitcher.js @@ -1,1 +1,639 @@ - +/* Copyright (c) 2006-2008 MetaCarta, Inc., published under the Clear BSD + * license. See http://svn.openlayers.org/trunk/openlayers/license.txt for the + * full text of the license. */ + +/** + * @requires OpenLayers/Control.js + */ + +/** + * Class: OpenLayers.Control.LayerSwitcher + * The LayerSwitcher control displays a table of contents for the map. This + * allows the user interface to switch between BaseLasyers and to show or hide + * Overlays. By default the switcher is shown minimized on the right edge of + * the map, the user may expand it by clicking on the handle. + * + * To create the LayerSwitcher outside of the map, pass the Id of a html div + * as the first argument to the constructor. + * + * Inherits from: + * - + */ +OpenLayers.Control.LayerSwitcher = + OpenLayers.Class(OpenLayers.Control, { + + /** + * Property: activeColor + * {String} + */ + activeColor: "darkblue", + + /** + * Property: layerStates + * {Array(Object)} Basically a copy of the "state" of the map's layers + * the last time the control was drawn. We have this in order to avoid + * unnecessarily redrawing the control. + */ + layerStates: null, + + + // DOM Elements + + /** + * Property: layersDiv + * {DOMElement} + */ + layersDiv: null, + + /** + * Property: baseLayersDiv + * {DOMElement} + */ + baseLayersDiv: null, + + /** + * Property: baseLayers + * {Array()} + */ + baseLayers: null, + + + /** + * Property: dataLbl + * {DOMElement} + */ + dataLbl: null, + + /** + * Property: dataLayersDiv + * {DOMElement} + */ + dataLayersDiv: null, + + /** + * Property: dataLayers + * {Array()} + */ + dataLayers: null, + + + /** + * Property: minimizeDiv + * {DOMElement} + */ + minimizeDiv: null, + + /** + * Property: maximizeDiv + * {DOMElement} + */ + maximizeDiv: null, + + /** + * APIProperty: ascending + * {Boolean} + */ + ascending: true, + + /** + * Constructor: OpenLayers.Control.LayerSwitcher + * + * Parameters: + * options - {Object} + */ + initialize: function(options) { + OpenLayers.Control.prototype.initialize.apply(this, arguments); + this.layerStates = []; + }, + + /** + * APIMethod: destroy + */ + destroy: function() { + + OpenLayers.Event.stopObservingElement(this.div); + + OpenLayers.Event.stopObservingElement(this.minimizeDiv); + OpenLayers.Event.stopObservingElement(this.maximizeDiv); + + //clear out layers info and unregister their events + this.clearLayersArray("base"); + this.clearLayersArray("data"); + + this.map.events.un({ + "addlayer": this.redraw, + "changelayer": this.redraw, + "removelayer": this.redraw, + "changebaselayer": this.redraw, + scope: this + }); + + OpenLayers.Control.prototype.destroy.apply(this, arguments); + }, + + /** + * Method: setMap + * + * Properties: + * map - {} + */ + setMap: function(map) { + OpenLayers.Control.prototype.setMap.apply(this, arguments); + + this.map.events.on({ + "addlayer": this.redraw, + "changelayer": this.redraw, + "removelayer": this.redraw, + "changebaselayer": this.redraw, + scope: this + }); + }, + + /** + * Method: draw + * + * Returns: + * {DOMElement} A reference to the DIV DOMElement containing the + * switcher tabs. + */ + draw: function() { + OpenLayers.Control.prototype.draw.apply(this); + + // create layout divs + this.loadContents(); + + // set mode to minimize + if(!this.outsideViewport) { + this.minimizeControl(); + } + + // populate div with current info + this.redraw(); + + return this.div; + }, + + /** + * Method: clearLayersArray + * User specifies either "base" or "data". we then clear all the + * corresponding listeners, the div, and reinitialize a new array. + * + * Parameters: + * layersType - {String} + */ + clearLayersArray: function(layersType) { + var layers = this[layersType + "Layers"]; + if (layers) { + for(var i=0, len=layers.length; i} layerSwitcher + * - {} layer + */ + + onInputClick: function(e) { + + if (!this.inputElem.disabled) { + if (this.inputElem.type == "radio") { + this.inputElem.checked = true; + this.layer.map.setBaseLayer(this.layer); + } else { + this.inputElem.checked = !this.inputElem.checked; + this.layerSwitcher.updateMap(); + } + } + OpenLayers.Event.stop(e); + }, + + /** + * Method: onLayerClick + * Need to update the map accordingly whenever user clicks in either of + * the layers. + * + * Parameters: + * e - {Event} + */ + onLayerClick: function(e) { + this.updateMap(); + }, + + + /** + * Method: updateMap + * Cycles through the loaded data and base layer input arrays and makes + * the necessary calls to the Map object such that that the map's + * visual state corresponds to what the user has selected in + * the control. + */ + updateMap: function() { + + // set the newly selected base layer + for(var i=0, len=this.baseLayers.length; i + */ +OpenLayers.Control.Measure = OpenLayers.Class(OpenLayers.Control, { + + /** + * Constant: EVENT_TYPES + * {Array(String)} Supported application event types. Register a listener + * for a particular event with the following syntax: + * (code) + * control.events.register(type, obj, listener); + * (end) + * + * Listeners will be called with a reference to an event object. The + * properties of this event depends on exactly what happened. + * + * Supported control event types (in addition to those from ): + * measure - Triggered when a measurement sketch is complete. Listeners + * will receive an event with measure, units, order, and geometry + * properties. + * measurepartial - Triggered when a new point is added to the + * measurement sketch. Listeners receive an event with measure, + * units, order, and geometry. + */ + EVENT_TYPES: ['measure', 'measurepartial'], + + /** + * APIProperty: handlerOptions + * {Object} Used to set non-default properties on the control's handler + */ + handlerOptions: null, + + /** + * Property: callbacks + * {Object} The functions that are sent to the handler for callback + */ + callbacks: null, + + /** + * Property: displaySystem + * {String} Display system for output measurements. Supported values + * are 'english', 'metric', and 'geographic'. Default is 'metric'. + */ + displaySystem: 'metric', + + /** + * Property: geodesic + * {Boolean} Calculate geodesic metrics instead of planar metrics. This + * requires that geometries can be transformed into Geographic/WGS84 + * (if that is not already the map projection). Default is false. + */ + geodesic: false, + + /** + * Property: displaySystemUnits + * {Object} Units for various measurement systems. Values are arrays + * of unit abbreviations (from OpenLayers.INCHES_PER_UNIT) in decreasing + * order of length. + */ + displaySystemUnits: { + geographic: ['dd'], + english: ['mi', 'ft', 'in'], + metric: ['km', 'm'] + }, + + /** + * Property: delay + * {Number} Number of milliseconds between clicks before the event is + * considered a double-click. The "measurepartial" event will not + * be triggered if the sketch is completed within this time. This + * is required for IE where creating a browser reflow (if a listener + * is modifying the DOM by displaying the measurement values) messes + * with the dblclick listener in the sketch handler. + */ + partialDelay: 300, + + /** + * Property: delayedTrigger + * {Number} Timeout id of trigger for measurepartial. + */ + delayedTrigger: null, + + /** + * APIProperty: persist + * {Boolean} Keep the temporary measurement sketch drawn after the + * measurement is complete. The geometry will persist until a new + * measurement is started, the control is deactivated, or is + * called. + */ + persist: false, + + /** + * Constructor: OpenLayers.Control.Measure + * + * Parameters: + * handler - {} + * options - {Object} + */ + initialize: function(handler, options) { + // concatenate events specific to measure with those from the base + this.EVENT_TYPES = + OpenLayers.Control.Measure.prototype.EVENT_TYPES.concat( + OpenLayers.Control.prototype.EVENT_TYPES + ); + OpenLayers.Control.prototype.initialize.apply(this, [options]); + this.callbacks = OpenLayers.Util.extend( + {done: this.measureComplete, point: this.measurePartial}, + this.callbacks + ); + + // let the handler options override, so old code that passes 'persist' + // directly to the handler does not need an update + this.handlerOptions = OpenLayers.Util.extend( + {persist: this.persist}, this.handlerOptions + ); + this.handler = new handler(this, this.callbacks, this.handlerOptions); + }, + + /** + * APIMethod: cancel + * Stop the control from measuring. If is true, the temporary + * sketch will be erased. + */ + cancel: function() { + this.handler.cancel(); + }, + + /** + * Method: updateHandler + * + * Parameters: + * handler - {Function} One of the sketch handler constructors. + * options - {Object} Options for the handler. + */ + updateHandler: function(handler, options) { + var active = this.active; + if(active) { + this.deactivate(); + } + this.handler = new handler(this, this.callbacks, options); + if(active) { + this.activate(); + } + }, + + /** + * Method: measureComplete + * Called when the measurement sketch is done. + * + * Parameters: + * geometry - {} + */ + measureComplete: function(geometry) { + if(this.delayedTrigger) { + window.clearTimeout(this.delayedTrigger); + } + this.measure(geometry, "measure"); + }, + + /** + * Method: measurePartial + * Called each time a new point is added to the measurement sketch. + * + * Parameters: + * point - {} The last point added. + * geometry - {} The sketch geometry. + */ + measurePartial: function(point, geometry) { + this.delayedTrigger = window.setTimeout( + OpenLayers.Function.bind(function() { + this.measure(geometry, "measurepartial"); + }, this), + this.partialDelay + ); + }, + + /** + * Method: measure + * + * Parameters: + * geometry - {} + * eventType - {String} + */ + measure: function(geometry, eventType) { + var stat, order; + if(geometry.CLASS_NAME.indexOf('LineString') > -1) { + stat = this.getBestLength(geometry); + order = 1; + } else { + stat = this.getBestArea(geometry); + order = 2; + } + this.events.triggerEvent(eventType, { + measure: stat[0], + units: stat[1], + order: order, + geometry: geometry + }); + }, + + /** + * Method: getBestArea + * Based on the returns the area of a geometry. + * + * Parameters: + * geometry - {} + * + * Returns: + * {Array([Float, String])} Returns a two item array containing the + * area and the units abbreviation. + */ + getBestArea: function(geometry) { + var units = this.displaySystemUnits[this.displaySystem]; + var unit, area; + for(var i=0, len=units.length; i 1) { + break; + } + } + return [area, unit]; + }, + + /** + * Method: getArea + * + * Parameters: + * geometry - {} + * units - {String} Unit abbreviation + * + * Returns: + * {Float} The geometry area in the given units. + */ + getArea: function(geometry, units) { + var area, geomUnits; + if(this.geodesic) { + area = geometry.getGeodesicArea(this.map.getProjectionObject()); + geomUnits = "m"; + } else { + area = geometry.getArea(); + geomUnits = this.map.getUnits(); + } + var inPerDisplayUnit = OpenLayers.INCHES_PER_UNIT[units]; + if(inPerDisplayUnit) { + var inPerMapUnit = OpenLayers.INCHES_PER_UNIT[geomUnits]; + area *= Math.pow((inPerMapUnit / inPerDisplayUnit), 2); + } + return area; + }, + + /** + * Method: getBestLength + * Based on the returns the length of a geometry. + * + * Parameters: + * geometry - {} + * + * Returns: + * {Array([Float, String])} Returns a two item array containing the + * length and the units abbreviation. + */ + getBestLength: function(geometry) { + var units = this.displaySystemUnits[this.displaySystem]; + var unit, length; + for(var i=0, len=units.length; i 1) { + break; + } + } + return [length, unit]; + }, + + /** + * Method: getLength + * + * Parameters: + * geometry - {} + * units - {String} Unit abbreviation + * + * Returns: + * {Float} The geometry length in the given units. + */ + getLength: function(geometry, units) { + var length, geomUnits; + if(this.geodesic) { + length = geometry.getGeodesicLength(this.map.getProjectionObject()); + geomUnits = "m"; + } else { + length = geometry.getLength(); + geomUnits = this.map.getUnits(); + } + var inPerDisplayUnit = OpenLayers.INCHES_PER_UNIT[units]; + if(inPerDisplayUnit) { + var inPerMapUnit = OpenLayers.INCHES_PER_UNIT[geomUnits]; + length *= (inPerMapUnit / inPerDisplayUnit); + } + return length; + }, + + CLASS_NAME: "OpenLayers.Control.Measure" +}); + --- /dev/null +++ b/openlayers/lib/OpenLayers/Control/ModifyFeature.js @@ -1,1 +1,739 @@ - +/* Copyright (c) 2006 MetaCarta, Inc., published under the Clear BSD license. + * See http://svn.openlayers.org/trunk/openlayers/license.txt + * for the full text of the license. */ + + +/** + * @requires OpenLayers/Control/DragFeature.js + * @requires OpenLayers/Control/SelectFeature.js + * @requires OpenLayers/Handler/Keyboard.js + */ + +/** + * Class: OpenLayers.Control.ModifyFeature + * Control to modify features. When activated, a click renders the vertices + * of a feature - these vertices can then be dragged. By default, the + * delete key will delete the vertex under the mouse. New features are + * added by dragging "virtual vertices" between vertices. Create a new + * control with the constructor. + * + * Inherits From: + * - + */ +OpenLayers.Control.ModifyFeature = OpenLayers.Class(OpenLayers.Control, { + + /** + * APIProperty: geometryTypes + * {Array(String)} To restrict modification to a limited set of geometry + * types, send a list of strings corresponding to the geometry class + * names. + */ + geometryTypes: null, + + /** + * APIProperty: clickout + * {Boolean} Unselect features when clicking outside any feature. + * Default is true. + */ + clickout: true, + + /** + * APIProperty: toggle + * {Boolean} Unselect a selected feature on click. + * Default is true. + */ + toggle: true, + + /** + * Property: layer + * {} + */ + layer: null, + + /** + * Property: feature + * {} Feature currently available for modification. + */ + feature: null, + + /** + * Property: vertices + * {Array()} Verticies currently available + * for dragging. + */ + vertices: null, + + /** + * Property: virtualVertices + * {Array()} Virtual vertices in the middle + * of each edge. + */ + virtualVertices: null, + + /** + * Property: selectControl + * {} + */ + selectControl: null, + + /** + * Property: dragControl + * {} + */ + dragControl: null, + + /** + * Property: handlers + * {Object} + */ + handlers: null, + + /** + * APIProperty: deleteCodes + * {Array(Integer)} Keycodes for deleting verticies. Set to null to disable + * vertex deltion by keypress. If non-null, keypresses with codes + * in this array will delete vertices under the mouse. Default + * is 46 and 68, the 'delete' and lowercase 'd' keys. + */ + deleteCodes: null, + + /** + * APIProperty: virtualStyle + * {Object} A symbolizer to be used for virtual vertices. + */ + virtualStyle: null, + + /** + * APIProperty: mode + * {Integer} Bitfields specifying the modification mode. Defaults to + * OpenLayers.Control.ModifyFeature.RESHAPE. To set the mode to a + * combination of options, use the | operator. or example, to allow + * the control to both resize and rotate features, use the following + * syntax + * (code) + * control.mode = OpenLayers.Control.ModifyFeature.RESIZE | + * OpenLayers.Control.ModifyFeature.ROTATE; + * (end) + */ + mode: null, + + /** + * Property: modified + * {Boolean} The currently selected feature has been modified. + */ + modified: false, + + /** + * Property: radiusHandle + * {} A handle for rotating/resizing a feature. + */ + radiusHandle: null, + + /** + * Property: dragHandle + * {} A handle for dragging a feature. + */ + dragHandle: null, + + /** + * APIProperty: onModificationStart + * {Function} *Deprecated*. Register for "beforefeaturemodified" instead. + * The "beforefeaturemodified" event is triggered on the layer before + * any modification begins. + * + * Optional function to be called when a feature is selected + * to be modified. The function should expect to be called with a + * feature. This could be used for example to allow to lock the + * feature on server-side. + */ + onModificationStart: function() {}, + + /** + * APIProperty: onModification + * {Function} *Deprecated*. Register for "featuremodified" instead. + * The "featuremodified" event is triggered on the layer with each + * feature modification. + * + * Optional function to be called when a feature has been + * modified. The function should expect to be called with a feature. + */ + onModification: function() {}, + + /** + * APIProperty: onModificationEnd + * {Function} *Deprecated*. Register for "afterfeaturemodified" instead. + * The "afterfeaturemodified" event is triggered on the layer after + * a feature has been modified. + * + * Optional function to be called when a feature is finished + * being modified. The function should expect to be called with a + * feature. + */ + onModificationEnd: function() {}, + + /** + * Constructor: OpenLayers.Control.ModifyFeature + * Create a new modify feature control. + * + * Parameters: + * layer - {} Layer that contains features that + * will be modified. + * options - {Object} Optional object whose properties will be set on the + * control. + */ + initialize: function(layer, options) { + this.layer = layer; + this.vertices = []; + this.virtualVertices = []; + this.virtualStyle = OpenLayers.Util.extend({}, + this.layer.style || this.layer.styleMap.createSymbolizer()); + this.virtualStyle.fillOpacity = 0.3; + this.virtualStyle.strokeOpacity = 0.3; + this.deleteCodes = [46, 68]; + this.mode = OpenLayers.Control.ModifyFeature.RESHAPE; + OpenLayers.Control.prototype.initialize.apply(this, [options]); + if(!(this.deleteCodes instanceof Array)) { + this.deleteCodes = [this.deleteCodes]; + } + var control = this; + + // configure the select control + var selectOptions = { + geometryTypes: this.geometryTypes, + clickout: this.clickout, + toggle: this.toggle, + onBeforeSelect: this.beforeSelectFeature, + onSelect: this.selectFeature, + onUnselect: this.unselectFeature, + scope: this + }; + this.selectControl = new OpenLayers.Control.SelectFeature( + layer, selectOptions + ); + + // configure the drag control + var dragOptions = { + geometryTypes: ["OpenLayers.Geometry.Point"], + snappingOptions: this.snappingOptions, + onStart: function(feature, pixel) { + control.dragStart.apply(control, [feature, pixel]); + }, + onDrag: function(feature, pixel) { + control.dragVertex.apply(control, [feature, pixel]); + }, + onComplete: function(feature) { + control.dragComplete.apply(control, [feature]); + } + }; + this.dragControl = new OpenLayers.Control.DragFeature( + layer, dragOptions + ); + + // configure the keyboard handler + var keyboardOptions = { + keydown: this.handleKeypress + }; + this.handlers = { + keyboard: new OpenLayers.Handler.Keyboard(this, keyboardOptions) + }; + }, + + /** + * APIMethod: destroy + * Take care of things that are not handled in superclass. + */ + destroy: function() { + this.layer = null; + this.selectControl.destroy(); + this.dragControl.destroy(); + OpenLayers.Control.prototype.destroy.apply(this, []); + }, + + /** + * APIMethod: activate + * Activate the control. + * + * Returns: + * {Boolean} Successfully activated the control. + */ + activate: function() { + return (this.selectControl.activate() && + this.handlers.keyboard.activate() && + OpenLayers.Control.prototype.activate.apply(this, arguments)); + }, + + /** + * APIMethod: deactivate + * Deactivate the control. + * + * Returns: + * {Boolean} Successfully deactivated the control. + */ + deactivate: function() { + var deactivated = false; + // the return from the controls is unimportant in this case + if(OpenLayers.Control.prototype.deactivate.apply(this, arguments)) { + this.layer.removeFeatures(this.vertices, {silent: true}); + this.layer.removeFeatures(this.virtualVertices, {silent: true}); + this.vertices = []; + this.dragControl.deactivate(); + if(this.feature && this.feature.geometry && this.feature.layer) { + this.selectControl.unselect.apply(this.selectControl, + [this.feature]); + } + this.selectControl.deactivate(); + this.handlers.keyboard.deactivate(); + deactivated = true; + } + return deactivated; + }, + + /** + * Method: beforeSelectFeature + * Called before a feature is selected. + * + * Parameters: + * feature - {} The feature about to be selected. + */ + beforeSelectFeature: function(feature) { + return this.layer.events.triggerEvent( + "beforefeaturemodified", {feature: feature} + ); + }, + + /** + * Method: selectFeature + * Called when the select feature control selects a feature. + * + * Parameters: + * feature - {} the selected feature. + */ + selectFeature: function(feature) { + this.feature = feature; + this.modified = false; + this.resetVertices(); + this.dragControl.activate(); + this.onModificationStart(this.feature); + }, + + /** + * Method: unselectFeature + * Called when the select feature control unselects a feature. + * + * Parameters: + * feature - {} The unselected feature. + */ + unselectFeature: function(feature) { + this.layer.removeFeatures(this.vertices, {silent: true}); + this.vertices = []; + this.layer.destroyFeatures(this.virtualVertices, {silent: true}); + this.virtualVertices = []; + if(this.dragHandle) { + this.layer.destroyFeatures([this.dragHandle], {silent: true}); + delete this.dragHandle; + } + if(this.radiusHandle) { + this.layer.destroyFeatures([this.radiusHandle], {silent: true}); + delete this.radiusHandle; + } + this.feature = null; + this.dragControl.deactivate(); + this.onModificationEnd(feature); + this.layer.events.triggerEvent("afterfeaturemodified", { + feature: feature, + modified: this.modified + }); + this.modified = false; + }, + + /** + * Method: dragStart + * Called by the drag feature control with before a feature is dragged. + * This method is used to differentiate between points and vertices + * of higher order geometries. This respects the + * property and forces a select of points when the drag control is + * already active (and stops events from propagating to the select + * control). + * + * Parameters: + * feature - {} The point or vertex about to be + * dragged. + * pixel - {} Pixel location of the mouse event. + */ + dragStart: function(feature, pixel) { + // only change behavior if the feature is not in the vertices array + if(feature != this.feature && !feature.geometry.parent && + feature != this.dragHandle && feature != this.radiusHandle) { + if(this.feature) { + // unselect the currently selected feature + this.selectControl.clickFeature.apply(this.selectControl, + [this.feature]); + } + // check any constraints on the geometry type + if(this.geometryTypes == null || + OpenLayers.Util.indexOf(this.geometryTypes, + feature.geometry.CLASS_NAME) != -1) { + // select the point + this.selectControl.clickFeature.apply(this.selectControl, + [feature]); + /** + * TBD: These lines improve workflow by letting the user + * immediately start dragging after the mouse down. + * However, it is very ugly to be messing with controls + * and their handlers in this way. I'd like a better + * solution if the workflow change is necessary. + */ + // prepare the point for dragging + this.dragControl.overFeature.apply(this.dragControl, + [feature]); + this.dragControl.lastPixel = pixel; + this.dragControl.handlers.drag.started = true; + this.dragControl.handlers.drag.start = pixel; + this.dragControl.handlers.drag.last = pixel; + } + } + }, + + /** + * Method: dragVertex + * Called by the drag feature control with each drag move of a vertex. + * + * Parameters: + * vertex - {} The vertex being dragged. + * pixel - {} Pixel location of the mouse event. + */ + dragVertex: function(vertex, pixel) { + this.modified = true; + /** + * Five cases: + * 1) dragging a simple point + * 2) dragging a virtual vertex + * 3) dragging a drag handle + * 4) dragging a real vertex + * 5) dragging a radius handle + */ + if(this.feature.geometry.CLASS_NAME == "OpenLayers.Geometry.Point") { + // dragging a simple point + if(this.feature != vertex) { + this.feature = vertex; + } + this.layer.events.triggerEvent("vertexmodified", { + vertex: vertex.geometry, + feature: this.feature, + pixel: pixel + }); + } else { + if(vertex._index) { + // dragging a virtual vertex + vertex.geometry.parent.addComponent(vertex.geometry, + vertex._index); + // move from virtual to real vertex + delete vertex._index; + OpenLayers.Util.removeItem(this.virtualVertices, vertex); + this.vertices.push(vertex); + } else if(vertex == this.dragHandle) { + // dragging a drag handle + this.layer.removeFeatures(this.vertices, {silent: true}); + this.vertices = []; + if(this.radiusHandle) { + this.layer.destroyFeatures([this.radiusHandle], {silent: true}); + this.radiusHandle = null; + } + } else if(vertex !== this.radiusHandle) { + // dragging a real vertex + this.layer.events.triggerEvent("vertexmodified", { + vertex: vertex.geometry, + feature: this.feature, + pixel: pixel + }); + } + // dragging a radius handle - no special treatment + if(this.virtualVertices.length > 0) { + this.layer.destroyFeatures(this.virtualVertices, {silent: true}); + this.virtualVertices = []; + } + this.layer.drawFeature(this.feature, this.selectControl.renderIntent); + } + // keep the vertex on top so it gets the mouseout after dragging + // this should be removed in favor of an option to draw under or + // maintain node z-index + this.layer.drawFeature(vertex); + }, + + /** + * Method: dragComplete + * Called by the drag feature control when the feature dragging is complete. + * + * Parameters: + * vertex - {} The vertex being dragged. + */ + dragComplete: function(vertex) { + this.resetVertices(); + this.setFeatureState(); + this.onModification(this.feature); + this.layer.events.triggerEvent("featuremodified", + {feature: this.feature}); + }, + + /** + * Method: setFeatureState + * Called when the feature is modified. If the current state is not + * INSERT or DELETE, the state is set to UPDATE. + */ + setFeatureState: function() { + if(this.feature.state != OpenLayers.State.INSERT && + this.feature.state != OpenLayers.State.DELETE) { + this.feature.state = OpenLayers.State.UPDATE; + } + }, + + /** + * Method: resetVertices + */ + resetVertices: function() { + // if coming from a drag complete we're about to destroy the vertex + // that was just dragged. For that reason, the drag feature control + // will never detect a mouse-out on that vertex, meaning that the drag + // handler won't be deactivated. This can cause errors because the drag + // feature control still has a feature to drag but that feature is + // destroyed. To prevent this, we call outFeature on the drag feature + // control if the control actually has a feature to drag. + if(this.dragControl.feature) { + this.dragControl.outFeature(this.dragControl.feature); + } + if(this.vertices.length > 0) { + this.layer.removeFeatures(this.vertices, {silent: true}); + this.vertices = []; + } + if(this.virtualVertices.length > 0) { + this.layer.removeFeatures(this.virtualVertices, {silent: true}); + this.virtualVertices = []; + } + if(this.dragHandle) { + this.layer.destroyFeatures([this.dragHandle], {silent: true}); + this.dragHandle = null; + } + if(this.radiusHandle) { + this.layer.destroyFeatures([this.radiusHandle], {silent: true}); + this.radiusHandle = null; + } + if(this.feature && + this.feature.geometry.CLASS_NAME != "OpenLayers.Geometry.Point") { + if((this.mode & OpenLayers.Control.ModifyFeature.DRAG)) { + this.collectDragHandle(); + } + if((this.mode & (OpenLayers.Control.ModifyFeature.ROTATE | + OpenLayers.Control.ModifyFeature.RESIZE))) { + this.collectRadiusHandle(); + } + if(this.mode & OpenLayers.Control.ModifyFeature.RESHAPE){ + // Don't collect vertices when we're resizing + if (!(this.mode & OpenLayers.Control.ModifyFeature.RESIZE)){ + this.collectVertices(); + } + } + } + }, + + /** + * Method: handleKeypress + * Called by the feature handler on keypress. This is used to delete + * vertices. If the property is set, vertices will + * be deleted when a feature is selected for modification and + * the mouse is over a vertex. + * + * Parameters: + * {Integer} Key code corresponding to the keypress event. + */ + handleKeypress: function(evt) { + var code = evt.keyCode; + + // check for delete key + if(this.feature && + OpenLayers.Util.indexOf(this.deleteCodes, code) != -1) { + var vertex = this.dragControl.feature; + if(vertex && + OpenLayers.Util.indexOf(this.vertices, vertex) != -1 && + !this.dragControl.handlers.drag.dragging && + vertex.geometry.parent) { + // remove the vertex + vertex.geometry.parent.removeComponent(vertex.geometry); + this.layer.drawFeature(this.feature, + this.selectControl.renderIntent); + this.resetVertices(); + this.setFeatureState(); + this.onModification(this.feature); + this.layer.events.triggerEvent("featuremodified", + {feature: this.feature}); + } + } + }, + + /** + * Method: collectVertices + * Collect the vertices from the modifiable feature's geometry and push + * them on to the control's vertices array. + */ + collectVertices: function() { + this.vertices = []; + this.virtualVertices = []; + var control = this; + function collectComponentVertices(geometry) { + var i, vertex, component, len; + if(geometry.CLASS_NAME == "OpenLayers.Geometry.Point") { + vertex = new OpenLayers.Feature.Vector(geometry); + vertex._sketch = true; + control.vertices.push(vertex); + } else { + var numVert = geometry.components.length; + if(geometry.CLASS_NAME == "OpenLayers.Geometry.LinearRing") { + numVert -= 1; + } + for(i=0; i} The control's map. + */ + setMap: function(map) { + this.selectControl.setMap(map); + this.dragControl.setMap(map); + OpenLayers.Control.prototype.setMap.apply(this, arguments); + }, + + CLASS_NAME: "OpenLayers.Control.ModifyFeature" +}); + +/** + * Constant: RESHAPE + * {Integer} Constant used to make the control work in reshape mode + */ +OpenLayers.Control.ModifyFeature.RESHAPE = 1; +/** + * Constant: RESIZE + * {Integer} Constant used to make the control work in resize mode + */ +OpenLayers.Control.ModifyFeature.RESIZE = 2; +/** + * Constant: ROTATE + * {Integer} Constant used to make the control work in rotate mode + */ +OpenLayers.Control.ModifyFeature.ROTATE = 4; +/** + * Constant: DRAG + * {Integer} Constant used to make the control work in drag mode + */ +OpenLayers.Control.ModifyFeature.DRAG = 8; + --- /dev/null +++ b/openlayers/lib/OpenLayers/Control/MouseDefaults.js @@ -1,1 +1,368 @@ - +/* Copyright (c) 2006-2008 MetaCarta, Inc., published under the Clear BSD + * license. See http://svn.openlayers.org/trunk/openlayers/license.txt for the + * full text of the license. */ + +/** + * @requires OpenLayers/Control.js + */ + +/** + * Class: OpenLayers.Control.MouseDefaults + * This class is DEPRECATED in 2.4 and will be removed by 3.0. + * If you need this functionality, use + * instead!!! + * + * This class is DEPRECATED in 2.4 and will be removed by 3.0. + * If you need this functionality, use Control.Navigation instead!!! + * + * Inherits from: + * - + */ +OpenLayers.Control.MouseDefaults = OpenLayers.Class(OpenLayers.Control, { + + /** WARNING WARNING WARNING!!! + This class is DEPRECATED in 2.4 and will be removed by 3.0. + If you need this functionality, use Control.Navigation instead!!! */ + + /** + * Property: performedDrag + * {Boolean} + */ + performedDrag: false, + + /** + * Property: wheelObserver + * {Function} + */ + wheelObserver: null, + + /** + * Constructor: OpenLayers.Control.MouseDefaults + */ + initialize: function() { + OpenLayers.Control.prototype.initialize.apply(this, arguments); + }, + + /** + * APIMethod: destroy + */ + destroy: function() { + + if (this.handler) { + this.handler.destroy(); + } + this.handler = null; + + this.map.events.un({ + "click": this.defaultClick, + "dblclick": this.defaultDblClick, + "mousedown": this.defaultMouseDown, + "mouseup": this.defaultMouseUp, + "mousemove": this.defaultMouseMove, + "mouseout": this.defaultMouseOut, + scope: this + }); + + //unregister mousewheel events specifically on the window and document + OpenLayers.Event.stopObserving(window, "DOMMouseScroll", + this.wheelObserver); + OpenLayers.Event.stopObserving(window, "mousewheel", + this.wheelObserver); + OpenLayers.Event.stopObserving(document, "mousewheel", + this.wheelObserver); + this.wheelObserver = null; + + OpenLayers.Control.prototype.destroy.apply(this, arguments); + }, + + /** + * Method: draw + */ + draw: function() { + this.map.events.on({ + "click": this.defaultClick, + "dblclick": this.defaultDblClick, + "mousedown": this.defaultMouseDown, + "mouseup": this.defaultMouseUp, + "mousemove": this.defaultMouseMove, + "mouseout": this.defaultMouseOut, + scope: this + }); + + this.registerWheelEvents(); + + }, + + /** + * Method: registerWheelEvents + */ + registerWheelEvents: function() { + + this.wheelObserver = OpenLayers.Function.bindAsEventListener( + this.onWheelEvent, this + ); + + //register mousewheel events specifically on the window and document + OpenLayers.Event.observe(window, "DOMMouseScroll", this.wheelObserver); + OpenLayers.Event.observe(window, "mousewheel", this.wheelObserver); + OpenLayers.Event.observe(document, "mousewheel", this.wheelObserver); + }, + + /** + * Method: defaultClick + * + * Parameters: + * evt - {Event} + * + * Returns: + * {Boolean} + */ + defaultClick: function (evt) { + if (!OpenLayers.Event.isLeftClick(evt)) { + return; + } + var notAfterDrag = !this.performedDrag; + this.performedDrag = false; + return notAfterDrag; + }, + + /** + * Method: defaultDblClick + * + * Parameters: + * evt - {Event} + */ + defaultDblClick: function (evt) { + var newCenter = this.map.getLonLatFromViewPortPx( evt.xy ); + this.map.setCenter(newCenter, this.map.zoom + 1); + OpenLayers.Event.stop(evt); + return false; + }, + + /** + * Method: defaultMouseDown + * + * Parameters: + * evt - {Event} + */ + defaultMouseDown: function (evt) { + if (!OpenLayers.Event.isLeftClick(evt)) { + return; + } + this.mouseDragStart = evt.xy.clone(); + this.performedDrag = false; + if (evt.shiftKey) { + this.map.div.style.cursor = "crosshair"; + this.zoomBox = OpenLayers.Util.createDiv('zoomBox', + this.mouseDragStart, + null, + null, + "absolute", + "2px solid red"); + this.zoomBox.style.backgroundColor = "white"; + this.zoomBox.style.filter = "alpha(opacity=50)"; // IE + this.zoomBox.style.opacity = "0.50"; + this.zoomBox.style.fontSize = "1px"; + this.zoomBox.style.zIndex = this.map.Z_INDEX_BASE["Popup"] - 1; + this.map.viewPortDiv.appendChild(this.zoomBox); + } + document.onselectstart=function() { return false; }; + OpenLayers.Event.stop(evt); + }, + + /** + * Method: defaultMouseMove + * + * Parameters: + * evt - {Event} + */ + defaultMouseMove: function (evt) { + // record the mouse position, used in onWheelEvent + this.mousePosition = evt.xy.clone(); + + if (this.mouseDragStart != null) { + if (this.zoomBox) { + var deltaX = Math.abs(this.mouseDragStart.x - evt.xy.x); + var deltaY = Math.abs(this.mouseDragStart.y - evt.xy.y); + this.zoomBox.style.width = Math.max(1, deltaX) + "px"; + this.zoomBox.style.height = Math.max(1, deltaY) + "px"; + if (evt.xy.x < this.mouseDragStart.x) { + this.zoomBox.style.left = evt.xy.x+"px"; + } + if (evt.xy.y < this.mouseDragStart.y) { + this.zoomBox.style.top = evt.xy.y+"px"; + } + } else { + var deltaX = this.mouseDragStart.x - evt.xy.x; + var deltaY = this.mouseDragStart.y - evt.xy.y; + var size = this.map.getSize(); + var newXY = new OpenLayers.Pixel(size.w / 2 + deltaX, + size.h / 2 + deltaY); + var newCenter = this.map.getLonLatFromViewPortPx( newXY ); + this.map.setCenter(newCenter, null, true); + this.mouseDragStart = evt.xy.clone(); + this.map.div.style.cursor = "move"; + } + this.performedDrag = true; + } + }, + + /** + * Method: defaultMouseUp + * + * Parameters: + * evt - {} + */ + defaultMouseUp: function (evt) { + if (!OpenLayers.Event.isLeftClick(evt)) { + return; + } + if (this.zoomBox) { + this.zoomBoxEnd(evt); + } else { + if (this.performedDrag) { + this.map.setCenter(this.map.center); + } + } + document.onselectstart=null; + this.mouseDragStart = null; + this.map.div.style.cursor = ""; + }, + + /** + * Method: defaultMouseOut + * + * Parameters: + * evt - {Event} + */ + defaultMouseOut: function (evt) { + if (this.mouseDragStart != null && + OpenLayers.Util.mouseLeft(evt, this.map.div)) { + if (this.zoomBox) { + this.removeZoomBox(); + } + this.mouseDragStart = null; + } + }, + + + /** + * Method: defaultWheelUp + * User spun scroll wheel up + * + */ + defaultWheelUp: function(evt) { + if (this.map.getZoom() <= this.map.getNumZoomLevels()) { + this.map.setCenter(this.map.getLonLatFromPixel(evt.xy), + this.map.getZoom() + 1); + } + }, + + /** + * Method: defaultWheelDown + * User spun scroll wheel down + */ + defaultWheelDown: function(evt) { + if (this.map.getZoom() > 0) { + this.map.setCenter(this.map.getLonLatFromPixel(evt.xy), + this.map.getZoom() - 1); + } + }, + + /** + * Method: zoomBoxEnd + * Zoombox function. + */ + zoomBoxEnd: function(evt) { + if (this.mouseDragStart != null) { + if (Math.abs(this.mouseDragStart.x - evt.xy.x) > 5 || + Math.abs(this.mouseDragStart.y - evt.xy.y) > 5) { + var start = this.map.getLonLatFromViewPortPx( this.mouseDragStart ); + var end = this.map.getLonLatFromViewPortPx( evt.xy ); + var top = Math.max(start.lat, end.lat); + var bottom = Math.min(start.lat, end.lat); + var left = Math.min(start.lon, end.lon); + var right = Math.max(start.lon, end.lon); + var bounds = new OpenLayers.Bounds(left, bottom, right, top); + this.map.zoomToExtent(bounds); + } else { + var end = this.map.getLonLatFromViewPortPx( evt.xy ); + this.map.setCenter(new OpenLayers.LonLat( + (end.lon), + (end.lat) + ), this.map.getZoom() + 1); + } + this.removeZoomBox(); + } + }, + + /** + * Method: removeZoomBox + * Remove the zoombox from the screen and nullify our reference to it. + */ + removeZoomBox: function() { + this.map.viewPortDiv.removeChild(this.zoomBox); + this.zoomBox = null; + }, + + +/** + * Mouse ScrollWheel code thanks to http://adomas.org/javascript-mouse-wheel/ + */ + + + /** + * Method: onWheelEvent + * Catch the wheel event and handle it xbrowserly + * + * Parameters: + * e - {Event} + */ + onWheelEvent: function(e){ + + // first determine whether or not the wheeling was inside the map + var inMap = false; + var elem = OpenLayers.Event.element(e); + while(elem != null) { + if (this.map && elem == this.map.div) { + inMap = true; + break; + } + elem = elem.parentNode; + } + + if (inMap) { + + var delta = 0; + if (!e) { + e = window.event; + } + if (e.wheelDelta) { + delta = e.wheelDelta/120; + if (window.opera && window.opera.version() < 9.2) { + delta = -delta; + } + } else if (e.detail) { + delta = -e.detail / 3; + } + if (delta) { + // add the mouse position to the event because mozilla has a bug + // with clientX and clientY (see https://bugzilla.mozilla.org/show_bug.cgi?id=352179) + // getLonLatFromViewPortPx(e) returns wrong values + e.xy = this.mousePosition; + + if (delta < 0) { + this.defaultWheelDown(e); + } else { + this.defaultWheelUp(e); + } + } + + //only wheel the map, not the window + OpenLayers.Event.stop(e); + } + }, + + CLASS_NAME: "OpenLayers.Control.MouseDefaults" +}); + --- /dev/null +++ b/openlayers/lib/OpenLayers/Control/MousePosition.js @@ -1,1 +1,172 @@ +/* Copyright (c) 2006-2008 MetaCarta, Inc., published under the Clear BSD + * license. See http://svn.openlayers.org/trunk/openlayers/license.txt for the + * full text of the license. */ + +/** + * @requires OpenLayers/Control.js + */ + +/** + * Class: OpenLayers.Control.MousePosition + * The MousePosition control displays geographic coordinates of the mouse + * pointer, as it is moved about the map. + * + * Inherits from: + * - + */ +OpenLayers.Control.MousePosition = OpenLayers.Class(OpenLayers.Control, { + + /** + * Property: element + * {DOMElement} + */ + element: null, + + /** + * APIProperty: prefix + * {String} + */ + prefix: '', + + /** + * APIProperty: separator + * {String} + */ + separator: ', ', + + /** + * APIProperty: suffix + * {String} + */ + suffix: '', + + /** + * APIProperty: numDigits + * {Integer} + */ + numDigits: 5, + + /** + * APIProperty: granularity + * {Integer} + */ + granularity: 10, + + /** + * Property: lastXy + * {} + */ + lastXy: null, + + /** + * APIProperty: displayProjection + * {} The projection in which the + * mouse position is displayed + */ + displayProjection: null, + + /** + * Constructor: OpenLayers.Control.MousePosition + * + * Parameters: + * options - {Object} Options for control. + */ + initialize: function(options) { + OpenLayers.Control.prototype.initialize.apply(this, arguments); + }, + + /** + * Method: destroy + */ + destroy: function() { + if (this.map) { + this.map.events.unregister('mousemove', this, this.redraw); + } + OpenLayers.Control.prototype.destroy.apply(this, arguments); + }, + + /** + * Method: draw + * {DOMElement} + */ + draw: function() { + OpenLayers.Control.prototype.draw.apply(this, arguments); + + if (!this.element) { + this.div.left = ""; + this.div.top = ""; + this.element = this.div; + } + + this.redraw(); + return this.div; + }, + + /** + * Method: redraw + */ + redraw: function(evt) { + + var lonLat; + + if (evt == null) { + lonLat = new OpenLayers.LonLat(0, 0); + } else { + if (this.lastXy == null || + Math.abs(evt.xy.x - this.lastXy.x) > this.granularity || + Math.abs(evt.xy.y - this.lastXy.y) > this.granularity) + { + this.lastXy = evt.xy; + return; + } + + lonLat = this.map.getLonLatFromPixel(evt.xy); + if (!lonLat) { + // map has not yet been properly initialized + return; + } + if (this.displayProjection) { + lonLat.transform(this.map.getProjectionObject(), + this.displayProjection ); + } + this.lastXy = evt.xy; + + } + + var newHtml = this.formatOutput(lonLat); + + if (newHtml != this.element.innerHTML) { + this.element.innerHTML = newHtml; + } + }, + + /** + * Method: formatOutput + * Override to provide custom display output + * + * Parameters: + * lonLat - {} Location to display + */ + formatOutput: function(lonLat) { + var digits = parseInt(this.numDigits); + var newHtml = + this.prefix + + lonLat.lon.toFixed(digits) + + this.separator + + lonLat.lat.toFixed(digits) + + this.suffix; + return newHtml; + }, + + /** + * Method: setMap + */ + setMap: function() { + OpenLayers.Control.prototype.setMap.apply(this, arguments); + this.map.events.register( 'mousemove', this, this.redraw); + }, + + CLASS_NAME: "OpenLayers.Control.MousePosition" +}); + --- /dev/null +++ b/openlayers/lib/OpenLayers/Control/MouseToolbar.js @@ -1,1 +1,406 @@ - +/* Copyright (c) 2006-2008 MetaCarta, Inc., published under the Clear BSD + * license. See http://svn.openlayers.org/trunk/openlayers/license.txt for the + * full text of the license. */ + + +/** + * @requires OpenLayers/Control.js + * @requires OpenLayers/Control/MouseDefaults.js + */ + +/** + * Class: OpenLayers.Control.MouseToolbar + * This class is DEPRECATED in 2.4 and will be removed by 3.0. + * If you need this functionality, use + * instead!!! + */ +OpenLayers.Control.MouseToolbar = OpenLayers.Class( + OpenLayers.Control.MouseDefaults, { + + /** + * Property: mode + */ + mode: null, + /** + * Property: buttons + */ + buttons: null, + + /** + * APIProperty: direction + * {String} 'vertical' or 'horizontal' + */ + direction: "vertical", + + /** + * Property: buttonClicked + * {String} + */ + buttonClicked: null, + + /** + * Constructor: OpenLayers.Control.MouseToolbar + * + * Parameters: + * position - {} + * direction - {String} + */ + initialize: function(position, direction) { + OpenLayers.Control.prototype.initialize.apply(this, arguments); + this.position = new OpenLayers.Pixel(OpenLayers.Control.MouseToolbar.X, + OpenLayers.Control.MouseToolbar.Y); + if (position) { + this.position = position; + } + if (direction) { + this.direction = direction; + } + this.measureDivs = []; + }, + + /** + * APIMethod: destroy + */ + destroy: function() { + for( var btnId in this.buttons) { + var btn = this.buttons[btnId]; + btn.map = null; + btn.events.destroy(); + } + OpenLayers.Control.MouseDefaults.prototype.destroy.apply(this, + arguments); + }, + + /** + * Method: draw + */ + draw: function() { + OpenLayers.Control.prototype.draw.apply(this, arguments); + OpenLayers.Control.MouseDefaults.prototype.draw.apply(this, arguments); + this.buttons = {}; + var sz = new OpenLayers.Size(28,28); + var centered = new OpenLayers.Pixel(OpenLayers.Control.MouseToolbar.X,0); + this._addButton("zoombox", "drag-rectangle-off.png", "drag-rectangle-on.png", centered, sz, "Shift->Drag to zoom to area"); + centered = centered.add((this.direction == "vertical" ? 0 : sz.w), (this.direction == "vertical" ? sz.h : 0)); + this._addButton("pan", "panning-hand-off.png", "panning-hand-on.png", centered, sz, "Drag the map to pan."); + centered = centered.add((this.direction == "vertical" ? 0 : sz.w), (this.direction == "vertical" ? sz.h : 0)); + this.switchModeTo("pan"); + + return this.div; + }, + + /** + * Method: _addButton + */ + _addButton:function(id, img, activeImg, xy, sz, title) { + var imgLocation = OpenLayers.Util.getImagesLocation() + img; + var activeImgLocation = OpenLayers.Util.getImagesLocation() + activeImg; + // var btn = new ol.AlphaImage("_"+id, imgLocation, xy, sz); + var btn = OpenLayers.Util.createAlphaImageDiv( + "OpenLayers_Control_MouseToolbar_" + id, + xy, sz, imgLocation, "absolute"); + + //we want to add the outer div + this.div.appendChild(btn); + btn.imgLocation = imgLocation; + btn.activeImgLocation = activeImgLocation; + + btn.events = new OpenLayers.Events(this, btn, null, true); + btn.events.on({ + "mousedown": this.buttonDown, + "mouseup": this.buttonUp, + "dblclick": OpenLayers.Event.stop, + scope: this + }); + btn.action = id; + btn.title = title; + btn.alt = title; + btn.map = this.map; + + //we want to remember/reference the outer div + this.buttons[id] = btn; + return btn; + }, + + /** + * Method: buttonDown + * + * Parameters: + * evt - {Event} + */ + buttonDown: function(evt) { + if (!OpenLayers.Event.isLeftClick(evt)) { + return; + } + this.buttonClicked = evt.element.action; + OpenLayers.Event.stop(evt); + }, + + /** + * Method: buttonUp + * + * Parameters: + * evt - {Event} + */ + buttonUp: function(evt) { + if (!OpenLayers.Event.isLeftClick(evt)) { + return; + } + if (this.buttonClicked != null) { + if (this.buttonClicked == evt.element.action) { + this.switchModeTo(evt.element.action); + } + OpenLayers.Event.stop(evt); + this.buttonClicked = null; + } + }, + + /** + * Method: defaultDblClick + * + * Parameters: + * evt - {Event} + */ + defaultDblClick: function (evt) { + this.switchModeTo("pan"); + this.performedDrag = false; + var newCenter = this.map.getLonLatFromViewPortPx( evt.xy ); + this.map.setCenter(newCenter, this.map.zoom + 1); + OpenLayers.Event.stop(evt); + return false; + }, + + /** + * Method: defaultMouseDown + * + * Parameters: + * evt - {Event} + */ + defaultMouseDown: function (evt) { + if (!OpenLayers.Event.isLeftClick(evt)) { + return; + } + this.mouseDragStart = evt.xy.clone(); + this.performedDrag = false; + this.startViaKeyboard = false; + if (evt.shiftKey && this.mode !="zoombox") { + this.switchModeTo("zoombox"); + this.startViaKeyboard = true; + } else if (evt.altKey && this.mode !="measure") { + this.switchModeTo("measure"); + } else if (!this.mode) { + this.switchModeTo("pan"); + } + + switch (this.mode) { + case "zoombox": + this.map.div.style.cursor = "crosshair"; + this.zoomBox = OpenLayers.Util.createDiv('zoomBox', + this.mouseDragStart, + null, + null, + "absolute", + "2px solid red"); + this.zoomBox.style.backgroundColor = "white"; + this.zoomBox.style.filter = "alpha(opacity=50)"; // IE + this.zoomBox.style.opacity = "0.50"; + this.zoomBox.style.fontSize = "1px"; + this.zoomBox.style.zIndex = this.map.Z_INDEX_BASE["Popup"] - 1; + this.map.viewPortDiv.appendChild(this.zoomBox); + this.performedDrag = true; + break; + case "measure": + var distance = ""; + if (this.measureStart) { + var measureEnd = this.map.getLonLatFromViewPortPx(this.mouseDragStart); + distance = OpenLayers.Util.distVincenty(this.measureStart, measureEnd); + distance = Math.round(distance * 100) / 100; + distance = distance + "km"; + this.measureStartBox = this.measureBox; + } + this.measureStart = this.map.getLonLatFromViewPortPx(this.mouseDragStart);; + this.measureBox = OpenLayers.Util.createDiv(null, + this.mouseDragStart.add( + -2-parseInt(this.map.layerContainerDiv.style.left), + -2-parseInt(this.map.layerContainerDiv.style.top)), + null, + null, + "absolute"); + this.measureBox.style.width="4px"; + this.measureBox.style.height="4px"; + this.measureBox.style.fontSize = "1px"; + this.measureBox.style.backgroundColor="red"; + this.measureBox.style.zIndex = this.map.Z_INDEX_BASE["Popup"] - 1; + this.map.layerContainerDiv.appendChild(this.measureBox); + if (distance) { + this.measureBoxDistance = OpenLayers.Util.createDiv(null, + this.mouseDragStart.add( + -2-parseInt(this.map.layerContainerDiv.style.left), + 2-parseInt(this.map.layerContainerDiv.style.top)), + null, + null, + "absolute"); + + this.measureBoxDistance.innerHTML = distance; + this.measureBoxDistance.style.zIndex = this.map.Z_INDEX_BASE["Popup"] - 1; + this.map.layerContainerDiv.appendChild(this.measureBoxDistance); + this.measureDivs.push(this.measureBoxDistance); + } + this.measureBox.style.zIndex = this.map.Z_INDEX_BASE["Popup"] - 1; + this.map.layerContainerDiv.appendChild(this.measureBox); + this.measureDivs.push(this.measureBox); + break; + default: + this.map.div.style.cursor = "move"; + break; + } + document.onselectstart = function() { return false; }; + OpenLayers.Event.stop(evt); + }, + + /** + * Method: switchModeTo + * + * Parameters: + * mode - {String} + */ + switchModeTo: function(mode) { + if (mode != this.mode) { + + + if (this.mode && this.buttons[this.mode]) { + OpenLayers.Util.modifyAlphaImageDiv(this.buttons[this.mode], null, null, null, this.buttons[this.mode].imgLocation); + } + if (this.mode == "measure" && mode != "measure") { + for(var i=0, len=this.measureDivs.length; i + */ +OpenLayers.Control.NavToolbar = OpenLayers.Class(OpenLayers.Control.Panel, { + + /** + * Constructor: OpenLayers.Control.NavToolbar + * Add our two mousedefaults controls. + * + * Parameters: + * options - {Object} An optional object whose properties will be used + * to extend the control. + */ + initialize: function(options) { + OpenLayers.Control.Panel.prototype.initialize.apply(this, [options]); + this.addControls([ + new OpenLayers.Control.Navigation(), + new OpenLayers.Control.ZoomBox() + ]); + }, + + /** + * Method: draw + * calls the default draw, and then activates mouse defaults. + */ + draw: function() { + var div = OpenLayers.Control.Panel.prototype.draw.apply(this, arguments); + this.activateControl(this.controls[0]); + return div; + }, + + CLASS_NAME: "OpenLayers.Control.NavToolbar" +}); + --- /dev/null +++ b/openlayers/lib/OpenLayers/Control/Navigation.js @@ -1,1 +1,248 @@ - +/* Copyright (c) 2006-2008 MetaCarta, Inc., published under the Clear BSD + * license. See http://svn.openlayers.org/trunk/openlayers/license.txt for the + * full text of the license. */ + +/** + * @requires OpenLayers/Control/ZoomBox.js + * @requires OpenLayers/Control/DragPan.js + * @requires OpenLayers/Handler/MouseWheel.js + * @requires OpenLayers/Handler/Click.js + */ + +/** + * Class: OpenLayers.Control.Navigation + * The navigation control handles map browsing with mouse events (dragging, + * double-clicking, and scrolling the wheel). Create a new navigation + * control with the control. + * + * Note that this control is added to the map by default (if no controls + * array is sent in the options object to the + * constructor). + * + * Inherits: + * - + */ +OpenLayers.Control.Navigation = OpenLayers.Class(OpenLayers.Control, { + + /** + * Property: dragPan + * {} + */ + dragPan: null, + + /** + * APIProprety: dragPanOptions + * {Object} Options passed to the DragPan control. + */ + dragPanOptions: null, + + /** + * Property: zoomBox + * {} + */ + zoomBox: null, + + /** + * APIProperty: zoomWheelEnabled + * {Boolean} Whether the mousewheel should zoom the map + */ + zoomWheelEnabled: true, + + /** + * APIProperty: handleRightClicks + * {Boolean} Whether or not to handle right clicks. Default is false. + */ + handleRightClicks: false, + + /** + * APIProperty: zoomBoxKeyMask + * {Integer} key code of the key, which has to be + * pressed, while drawing the zoom box with the mouse on the screen. + * You should probably set handleRightClicks to true if you use this + * with MOD_CTRL, to disable the context menu for machines which use + * CTRL-Click as a right click. + * Default: and . Call the trigger method + * on the and controls to restore previous and next + * history states. The previous and next controls will become active + * when there are available states to restore and will become deactive + * when there are no states to restore. + * + * Inherits from: + * - + */ +OpenLayers.Control.NavigationHistory = OpenLayers.Class(OpenLayers.Control, { + + /** + * Property: type + * {String} Note that this control is not intended to be added directly + * to a control panel. Instead, add the sub-controls previous and + * next. These sub-controls are button type controls that activate + * and deactivate themselves. If this parent control is added to + * a panel, it will act as a toggle. + */ + type: OpenLayers.Control.TYPE_TOGGLE, + + /** + * APIProperty: previous + * {} A button type control whose trigger method restores + * the previous state managed by this control. + */ + previous: null, + + /** + * APIProperty: previousOptions + * {Object} Set this property on the options argument of the constructor + * to set optional properties on the control. + */ + previousOptions: null, + + /** + * APIProperty: next + * {} A button type control whose trigger method restores + * the next state managed by this control. + */ + next: null, + + /** + * APIProperty: nextOptions + * {Object} Set this property on the options argument of the constructor + * to set optional properties on the control. + */ + nextOptions: null, + + /** + * APIProperty: limit + * {Integer} Optional limit on the number of history items to retain. If + * null, there is no limit. Default is 50. + */ + limit: 50, + + /** + * Property: activateOnDraw + * {Boolean} Activate the control when it is first added to the map. + * Default is true. + */ + activateOnDraw: true, + + /** + * Property: clearOnDeactivate + * {Boolean} Clear the history when the control is deactivated. Default + * is false. + */ + clearOnDeactivate: false, + + /** + * Property: registry + * {Object} An object with keys corresponding to event types. Values + * are functions that return an object representing the current state. + */ + registry: null, + + /** + * Property: nextStack + * {Array} Array of items in the history. + */ + nextStack: null, + + /** + * Property: previousStack + * {Array} List of items in the history. First item represents the current + * state. + */ + previousStack: null, + + /** + * Property: listeners + * {Object} An object containing properties corresponding to event types. + * This object is used to configure the control and is modified on + * construction. + */ + listeners: null, + + /** + * Property: restoring + * {Boolean} Currently restoring a history state. This is set to true + * before calling restore and set to false after restore returns. + */ + restoring: false, + + /** + * Constructor: OpenLayers.Control.NavigationHistory + * + * Parameters: + * options - {Object} An optional object whose properties will be used + * to extend the control. + */ + initialize: function(options) { + OpenLayers.Control.prototype.initialize.apply(this, [options]); + + this.registry = OpenLayers.Util.extend({ + "moveend": function() { + return { + center: this.map.getCenter(), + resolution: this.map.getResolution() + }; + } + }, this.registry); + + this.clear(); + + var previousOptions = { + trigger: OpenLayers.Function.bind(this.previousTrigger, this), + displayClass: this.displayClass + " " + this.displayClass + "Previous" + }; + OpenLayers.Util.extend(previousOptions, this.previousOptions); + this.previous = new OpenLayers.Control.Button(previousOptions); + + var nextOptions = { + trigger: OpenLayers.Function.bind(this.nextTrigger, this), + displayClass: this.displayClass + " " + this.displayClass + "Next" + }; + OpenLayers.Util.extend(nextOptions, this.nextOptions); + this.next = new OpenLayers.Control.Button(nextOptions); + + }, + + /** + * Method: onPreviousChange + * Called when the previous history stack changes. + * + * Parameters: + * state - {Object} An object representing the state to be restored + * if previous is triggered again or null if no previous states remain. + * length - {Integer} The number of remaining previous states that can + * be restored. + */ + onPreviousChange: function(state, length) { + if(state && !this.previous.active) { + this.previous.activate(); + } else if(!state && this.previous.active) { + this.previous.deactivate(); + } + }, + + /** + * Method: onNextChange + * Called when the next history stack changes. + * + * Parameters: + * state - {Object} An object representing the state to be restored + * if next is triggered again or null if no next states remain. + * length - {Integer} The number of remaining next states that can + * be restored. + */ + onNextChange: function(state, length) { + if(state && !this.next.active) { + this.next.activate(); + } else if(!state && this.next.active) { + this.next.deactivate(); + } + }, + + /** + * APIMethod: destroy + * Destroy the control. + */ + destroy: function() { + OpenLayers.Control.prototype.destroy.apply(this); + this.previous.destroy(); + this.next.destroy(); + this.deactivate(); + for(var prop in this) { + this[prop] = null; + } + }, + + /** + * Method: setMap + * Set the map property for the control and and child + * controls. + * + * Parameters: + * map - {} + */ + setMap: function(map) { + this.map = map; + this.next.setMap(map); + this.previous.setMap(map); + }, + + /** + * Method: draw + * Called when the control is added to the map. + */ + draw: function() { + OpenLayers.Control.prototype.draw.apply(this, arguments); + this.next.draw(); + this.previous.draw(); + if(this.activateOnDraw) { + this.activate(); + } + }, + + /** + * Method: previousTrigger + * Restore the previous state. If no items are in the previous history + * stack, this has no effect. + * + * Returns: + * {Object} Item representing state that was restored. Undefined if no + * items are in the previous history stack. + */ + previousTrigger: function() { + var current = this.previousStack.shift(); + var state = this.previousStack.shift(); + if(state != undefined) { + this.nextStack.unshift(current); + this.previousStack.unshift(state); + this.restoring = true; + this.restore(state); + this.restoring = false; + this.onNextChange(this.nextStack[0], this.nextStack.length); + this.onPreviousChange( + this.previousStack[1], this.previousStack.length - 1 + ); + } else { + this.previousStack.unshift(current); + } + return state; + }, + + /** + * APIMethod: nextTrigger + * Restore the next state. If no items are in the next history + * stack, this has no effect. The next history stack is populated + * as states are restored from the previous history stack. + * + * Returns: + * {Object} Item representing state that was restored. Undefined if no + * items are in the next history stack. + */ + nextTrigger: function() { + var state = this.nextStack.shift(); + if(state != undefined) { + this.previousStack.unshift(state); + this.restoring = true; + this.restore(state); + this.restoring = false; + this.onNextChange(this.nextStack[0], this.nextStack.length); + this.onPreviousChange( + this.previousStack[1], this.previousStack.length - 1 + ); + } + return state; + }, + + /** + * APIMethod: clear + * Clear history. + */ + clear: function() { + this.previousStack = []; + this.nextStack = []; + }, + + /** + * Method: restore + * Update the state with the given object. + * + * Parameters: + * state - {Object} An object representing the state to restore. + */ + restore: function(state) { + var zoom = this.map.getZoomForResolution(state.resolution); + this.map.setCenter(state.center, zoom); + }, + + /** + * Method: setListeners + * Sets functions to be registered in the listeners object. + */ + setListeners: function() { + this.listeners = {}; + for(var type in this.registry) { + this.listeners[type] = OpenLayers.Function.bind(function() { + if(!this.restoring) { + var state = this.registry[type].apply(this, arguments); + this.previousStack.unshift(state); + if(this.previousStack.length > 1) { + this.onPreviousChange( + this.previousStack[1], this.previousStack.length - 1 + ); + } + if(this.previousStack.length > (this.limit + 1)) { + this.previousStack.pop(); + } + if(this.nextStack.length > 0) { + this.nextStack = []; + this.onNextChange(null, 0); + } + } + return true; + }, this); + } + }, + + /** + * APIMethod: activate + * Activate the control. This registers any listeners. + * + * Returns: + * {Boolean} Control successfully activated. + */ + activate: function() { + var activated = false; + if(this.map) { + if(OpenLayers.Control.prototype.activate.apply(this)) { + if(this.listeners == null) { + this.setListeners(); + } + for(var type in this.listeners) { + this.map.events.register(type, this, this.listeners[type]); + } + activated = true; + if(this.previousStack.length == 0) { + this.initStack(); + } + } + } + return activated; + }, + + /** + * Method: initStack + * Called after the control is activated if the previous history stack is + * empty. + */ + initStack: function() { + if(this.map.getCenter()) { + this.listeners.moveend(); + } + }, + + /** + * APIMethod: deactivate + * Deactivate the control. This unregisters any listeners. + * + * Returns: + * {Boolean} Control successfully deactivated. + */ + deactivate: function() { + var deactivated = false; + if(this.map) { + if(OpenLayers.Control.prototype.deactivate.apply(this)) { + for(var type in this.listeners) { + this.map.events.unregister( + type, this, this.listeners[type] + ); + } + if(this.clearOnDeactivate) { + this.clear(); + } + deactivated = true; + } + } + return deactivated; + }, + + CLASS_NAME: "OpenLayers.Control.NavigationHistory" +}); + + --- /dev/null +++ b/openlayers/lib/OpenLayers/Control/OverviewMap.js @@ -1,1 +1,701 @@ - +/* Copyright (c) 2006-2008 MetaCarta, Inc., published under the Clear BSD + * license. See http://svn.openlayers.org/trunk/openlayers/license.txt for the + * full text of the license. */ + +/** + * @requires OpenLayers/Control.js + * @requires OpenLayers/BaseTypes.js + * @requires OpenLayers/Events.js + */ + +/** + * Class: OpenLayers.Control.OverviewMap + * The OverMap control creates a small overview map, useful to display the + * extent of a zoomed map and your main map and provide additional + * navigation options to the User. By default the overview map is drawn in + * the lower right corner of the main map. Create a new overview map with the + * constructor. + * + * Inerits from: + * - + */ +OpenLayers.Control.OverviewMap = OpenLayers.Class(OpenLayers.Control, { + + /** + * Property: element + * {DOMElement} The DOM element that contains the overview map + */ + element: null, + + /** + * APIProperty: ovmap + * {} A reference to the overview map itself. + */ + ovmap: null, + + /** + * APIProperty: size + * {} The overvew map size in pixels. Note that this is + * the size of the map itself - the element that contains the map (default + * class name olControlOverviewMapElement) may have padding or other style + * attributes added via CSS. + */ + size: new OpenLayers.Size(180, 90), + + /** + * APIProperty: layers + * {Array()} Ordered list of layers in the overview map. + * If none are sent at construction, the base layer for the main map is used. + */ + layers: null, + + /** + * APIProperty: minRectSize + * {Integer} The minimum width or height (in pixels) of the extent + * rectangle on the overview map. When the extent rectangle reaches + * this size, it will be replaced depending on the value of the + * property. Default is 15 pixels. + */ + minRectSize: 15, + + /** + * APIProperty: minRectDisplayClass + * {String} Replacement style class name for the extent rectangle when + * is reached. This string will be suffixed on to the + * displayClass. Default is "RectReplacement". + * + * Example CSS declaration: + * (code) + * .olControlOverviewMapRectReplacement { + * overflow: hidden; + * cursor: move; + * background-image: url("img/overview_replacement.gif"); + * background-repeat: no-repeat; + * background-position: center; + * } + * (end) + */ + minRectDisplayClass: "RectReplacement", + + /** + * APIProperty: minRatio + * {Float} The ratio of the overview map resolution to the main map + * resolution at which to zoom farther out on the overview map. + */ + minRatio: 8, + + /** + * APIProperty: maxRatio + * {Float} The ratio of the overview map resolution to the main map + * resolution at which to zoom farther in on the overview map. + */ + maxRatio: 32, + + /** + * APIProperty: mapOptions + * {Object} An object containing any non-default properties to be sent to + * the overview map's map constructor. These should include any + * non-default options that the main map was constructed with. + */ + mapOptions: null, + + /** + * APIProperty: autoPan + * {Boolean} Always pan the overview map, so the extent marker remains in + * the center. Default is false. If true, when you drag the extent + * marker, the overview map will update itself so the marker returns + * to the center. + */ + autoPan: false, + + /** + * Property: handlers + * {Object} + */ + handlers: null, + + /** + * Property: resolutionFactor + * {Object} + */ + resolutionFactor: 1, + + /** + * Constructor: OpenLayers.Control.OverviewMap + * Create a new overview map + * + * Parameters: + * object - {Object} Properties of this object will be set on the overview + * map object. Note, to set options on the map object contained in this + * control, set as one of the options properties. + */ + initialize: function(options) { + this.layers = []; + this.handlers = {}; + OpenLayers.Control.prototype.initialize.apply(this, [options]); + }, + + /** + * APIMethod: destroy + * Deconstruct the control + */ + destroy: function() { + if (!this.mapDiv) { // we've already been destroyed + return; + } + this.handlers.click.destroy(); + + this.mapDiv.removeChild(this.extentRectangle); + this.extentRectangle = null; + this.rectEvents.destroy(); + this.rectEvents = null; + + this.ovmap.destroy(); + this.ovmap = null; + + this.element.removeChild(this.mapDiv); + this.mapDiv = null; + + this.div.removeChild(this.element); + this.element = null; + + if (this.maximizeDiv) { + OpenLayers.Event.stopObservingElement(this.maximizeDiv); + this.div.removeChild(this.maximizeDiv); + this.maximizeDiv = null; + } + + if (this.minimizeDiv) { + OpenLayers.Event.stopObservingElement(this.minimizeDiv); + this.div.removeChild(this.minimizeDiv); + this.minimizeDiv = null; + } + + this.map.events.un({ + "moveend": this.update, + "changebaselayer": this.baseLayerDraw, + scope: this + }); + + OpenLayers.Control.prototype.destroy.apply(this, arguments); + }, + + /** + * Method: draw + * Render the control in the browser. + */ + draw: function() { + OpenLayers.Control.prototype.draw.apply(this, arguments); + if(!(this.layers.length > 0)) { + if (this.map.baseLayer) { + var layer = this.map.baseLayer.clone(); + this.layers = [layer]; + } else { + this.map.events.register("changebaselayer", this, this.baseLayerDraw); + return this.div; + } + } + + // create overview map DOM elements + this.element = document.createElement('div'); + this.element.className = this.displayClass + 'Element'; + this.element.style.display = 'none'; + + this.mapDiv = document.createElement('div'); + this.mapDiv.style.width = this.size.w + 'px'; + this.mapDiv.style.height = this.size.h + 'px'; + this.mapDiv.style.position = 'relative'; + this.mapDiv.style.overflow = 'hidden'; + this.mapDiv.id = OpenLayers.Util.createUniqueID('overviewMap'); + + this.extentRectangle = document.createElement('div'); + this.extentRectangle.style.position = 'absolute'; + this.extentRectangle.style.zIndex = 1000; //HACK + this.extentRectangle.className = this.displayClass+'ExtentRectangle'; + this.mapDiv.appendChild(this.extentRectangle); + + this.element.appendChild(this.mapDiv); + + this.div.appendChild(this.element); + + // Optionally add min/max buttons if the control will go in the + // map viewport. + if(!this.outsideViewport) { + this.div.className += " " + this.displayClass + 'Container'; + var imgLocation = OpenLayers.Util.getImagesLocation(); + // maximize button div + var img = imgLocation + 'layer-switcher-maximize.png'; + this.maximizeDiv = OpenLayers.Util.createAlphaImageDiv( + this.displayClass + 'MaximizeButton', + null, + new OpenLayers.Size(18,18), + img, + 'absolute'); + this.maximizeDiv.style.display = 'none'; + this.maximizeDiv.className = this.displayClass + 'MaximizeButton'; + OpenLayers.Event.observe(this.maximizeDiv, 'click', + OpenLayers.Function.bindAsEventListener(this.maximizeControl, + this) + ); + this.div.appendChild(this.maximizeDiv); + + // minimize button div + var img = imgLocation + 'layer-switcher-minimize.png'; + this.minimizeDiv = OpenLayers.Util.createAlphaImageDiv( + 'OpenLayers_Control_minimizeDiv', + null, + new OpenLayers.Size(18,18), + img, + 'absolute'); + this.minimizeDiv.style.display = 'none'; + this.minimizeDiv.className = this.displayClass + 'MinimizeButton'; + OpenLayers.Event.observe(this.minimizeDiv, 'click', + OpenLayers.Function.bindAsEventListener(this.minimizeControl, + this) + ); + this.div.appendChild(this.minimizeDiv); + + var eventsToStop = ['dblclick','mousedown']; + + for (var i=0, len=eventsToStop.length; i} The pixel location of the drag. + */ + rectDrag: function(px) { + var deltaX = this.handlers.drag.last.x - px.x; + var deltaY = this.handlers.drag.last.y - px.y; + if(deltaX != 0 || deltaY != 0) { + var rectTop = this.rectPxBounds.top; + var rectLeft = this.rectPxBounds.left; + var rectHeight = Math.abs(this.rectPxBounds.getHeight()); + var rectWidth = this.rectPxBounds.getWidth(); + // don't allow dragging off of parent element + var newTop = Math.max(0, (rectTop - deltaY)); + newTop = Math.min(newTop, + this.ovmap.size.h - this.hComp - rectHeight); + var newLeft = Math.max(0, (rectLeft - deltaX)); + newLeft = Math.min(newLeft, + this.ovmap.size.w - this.wComp - rectWidth); + this.setRectPxBounds(new OpenLayers.Bounds(newLeft, + newTop + rectHeight, + newLeft + rectWidth, + newTop)); + } + }, + + /** + * Method: mapDivClick + * Handle browser events + * + * Parameters: + * evt - {} evt + */ + mapDivClick: function(evt) { + var pxCenter = this.rectPxBounds.getCenterPixel(); + var deltaX = evt.xy.x - pxCenter.x; + var deltaY = evt.xy.y - pxCenter.y; + var top = this.rectPxBounds.top; + var left = this.rectPxBounds.left; + var height = Math.abs(this.rectPxBounds.getHeight()); + var width = this.rectPxBounds.getWidth(); + var newTop = Math.max(0, (top + deltaY)); + newTop = Math.min(newTop, this.ovmap.size.h - height); + var newLeft = Math.max(0, (left + deltaX)); + newLeft = Math.min(newLeft, this.ovmap.size.w - width); + this.setRectPxBounds(new OpenLayers.Bounds(newLeft, + newTop + height, + newLeft + width, + newTop)); + this.updateMapToRect(); + }, + + /** + * Method: maximizeControl + * Unhide the control. Called when the control is in the map viewport. + * + * Parameters: + * e - {} + */ + maximizeControl: function(e) { + this.element.style.display = ''; + this.showToggle(false); + if (e != null) { + OpenLayers.Event.stop(e); + } + }, + + /** + * Method: minimizeControl + * Hide all the contents of the control, shrink the size, + * add the maximize icon + * + * Parameters: + * e - {} + */ + minimizeControl: function(e) { + this.element.style.display = 'none'; + this.showToggle(true); + if (e != null) { + OpenLayers.Event.stop(e); + } + }, + + /** + * Method: showToggle + * Hide/Show the toggle depending on whether the control is minimized + * + * Parameters: + * minimize - {Boolean} + */ + showToggle: function(minimize) { + this.maximizeDiv.style.display = minimize ? '' : 'none'; + this.minimizeDiv.style.display = minimize ? 'none' : ''; + }, + + /** + * Method: update + * Update the overview map after layers move. + */ + update: function() { + if(this.ovmap == null) { + this.createMap(); + } + + if(this.autoPan || !this.isSuitableOverview()) { + this.updateOverview(); + } + + // update extent rectangle + this.updateRectToMap(); + }, + + /** + * Method: isSuitableOverview + * Determines if the overview map is suitable given the extent and + * resolution of the main map. + */ + isSuitableOverview: function() { + var mapExtent = this.map.getExtent(); + var maxExtent = this.map.maxExtent; + var testExtent = new OpenLayers.Bounds( + Math.max(mapExtent.left, maxExtent.left), + Math.max(mapExtent.bottom, maxExtent.bottom), + Math.min(mapExtent.right, maxExtent.right), + Math.min(mapExtent.top, maxExtent.top)); + + if (this.ovmap.getProjection() != this.map.getProjection()) { + testExtent = testExtent.transform( + this.map.getProjectionObject(), + this.ovmap.getProjectionObject() ); + } + + var resRatio = this.ovmap.getResolution() / this.map.getResolution(); + return ((resRatio > this.minRatio) && + (resRatio <= this.maxRatio) && + (this.ovmap.getExtent().containsBounds(testExtent))); + }, + + /** + * Method updateOverview + * Called by if returns true + */ + updateOverview: function() { + var mapRes = this.map.getResolution(); + var targetRes = this.ovmap.getResolution(); + var resRatio = targetRes / mapRes; + if(resRatio > this.maxRatio) { + // zoom in overview map + targetRes = this.minRatio * mapRes; + } else if(resRatio <= this.minRatio) { + // zoom out overview map + targetRes = this.maxRatio * mapRes; + } + var center; + if (this.ovmap.getProjection() != this.map.getProjection()) { + center = this.map.center.clone(); + center.transform(this.map.getProjectionObject(), + this.ovmap.getProjectionObject() ); + } else { + center = this.map.center; + } + this.ovmap.setCenter(center, this.ovmap.getZoomForResolution( + targetRes * this.resolutionFactor)); + this.updateRectToMap(); + }, + + /** + * Method: createMap + * Construct the map that this control contains + */ + createMap: function() { + // create the overview map + var options = OpenLayers.Util.extend( + {controls: [], maxResolution: 'auto', + fallThrough: false}, this.mapOptions); + this.ovmap = new OpenLayers.Map(this.mapDiv, options); + + // prevent ovmap from being destroyed when the page unloads, because + // the OverviewMap control has to do this (and does it). + OpenLayers.Event.stopObserving(window, 'unload', this.ovmap.unloadDestroy); + + this.ovmap.addLayers(this.layers); + this.ovmap.zoomToMaxExtent(); + // check extent rectangle border width + this.wComp = parseInt(OpenLayers.Element.getStyle(this.extentRectangle, + 'border-left-width')) + + parseInt(OpenLayers.Element.getStyle(this.extentRectangle, + 'border-right-width')); + this.wComp = (this.wComp) ? this.wComp : 2; + this.hComp = parseInt(OpenLayers.Element.getStyle(this.extentRectangle, + 'border-top-width')) + + parseInt(OpenLayers.Element.getStyle(this.extentRectangle, + 'border-bottom-width')); + this.hComp = (this.hComp) ? this.hComp : 2; + + this.handlers.drag = new OpenLayers.Handler.Drag( + this, {move: this.rectDrag, done: this.updateMapToRect}, + {map: this.ovmap} + ); + this.handlers.click = new OpenLayers.Handler.Click( + this, { + "click": this.mapDivClick + },{ + "single": true, "double": false, + "stopSingle": true, "stopDouble": true, + "pixelTolerance": 1, + map: this.ovmap + } + ); + this.handlers.click.activate(); + + this.rectEvents = new OpenLayers.Events(this, this.extentRectangle, + null, true); + this.rectEvents.register("mouseover", this, function(e) { + if(!this.handlers.drag.active && !this.map.dragging) { + this.handlers.drag.activate(); + } + }); + this.rectEvents.register("mouseout", this, function(e) { + if(!this.handlers.drag.dragging) { + this.handlers.drag.deactivate(); + } + }); + + if (this.ovmap.getProjection() != this.map.getProjection()) { + var sourceUnits = this.map.getProjectionObject().getUnits() || + this.map.units || this.map.baseLayer.units; + var targetUnits = this.ovmap.getProjectionObject().getUnits() || + this.ovmap.units || this.ovmap.baseLayer.units; + this.resolutionFactor = sourceUnits && targetUnits ? + OpenLayers.INCHES_PER_UNIT[sourceUnits] / + OpenLayers.INCHES_PER_UNIT[targetUnits] : 1; + } + }, + + /** + * Method: updateRectToMap + * Updates the extent rectangle position and size to match the map extent + */ + updateRectToMap: function() { + // If the projections differ we need to reproject + var bounds; + if (this.ovmap.getProjection() != this.map.getProjection()) { + bounds = this.map.getExtent().transform( + this.map.getProjectionObject(), + this.ovmap.getProjectionObject() ); + } else { + bounds = this.map.getExtent(); + } + var pxBounds = this.getRectBoundsFromMapBounds(bounds); + if (pxBounds) { + this.setRectPxBounds(pxBounds); + } + }, + + /** + * Method: updateMapToRect + * Updates the map extent to match the extent rectangle position and size + */ + updateMapToRect: function() { + var lonLatBounds = this.getMapBoundsFromRectBounds(this.rectPxBounds); + if (this.ovmap.getProjection() != this.map.getProjection()) { + lonLatBounds = lonLatBounds.transform( + this.ovmap.getProjectionObject(), + this.map.getProjectionObject() ); + } + this.map.panTo(lonLatBounds.getCenterLonLat()); + }, + + /** + * Method: setRectPxBounds + * Set extent rectangle pixel bounds. + * + * Parameters: + * pxBounds - {} + */ + setRectPxBounds: function(pxBounds) { + var top = Math.max(pxBounds.top, 0); + var left = Math.max(pxBounds.left, 0); + var bottom = Math.min(pxBounds.top + Math.abs(pxBounds.getHeight()), + this.ovmap.size.h - this.hComp); + var right = Math.min(pxBounds.left + pxBounds.getWidth(), + this.ovmap.size.w - this.wComp); + var width = Math.max(right - left, 0); + var height = Math.max(bottom - top, 0); + if(width < this.minRectSize || height < this.minRectSize) { + this.extentRectangle.className = this.displayClass + + this.minRectDisplayClass; + var rLeft = left + (width / 2) - (this.minRectSize / 2); + var rTop = top + (height / 2) - (this.minRectSize / 2); + this.extentRectangle.style.top = Math.round(rTop) + 'px'; + this.extentRectangle.style.left = Math.round(rLeft) + 'px'; + this.extentRectangle.style.height = this.minRectSize + 'px'; + this.extentRectangle.style.width = this.minRectSize + 'px'; + } else { + this.extentRectangle.className = this.displayClass + + 'ExtentRectangle'; + this.extentRectangle.style.top = Math.round(top) + 'px'; + this.extentRectangle.style.left = Math.round(left) + 'px'; + this.extentRectangle.style.height = Math.round(height) + 'px'; + this.extentRectangle.style.width = Math.round(width) + 'px'; + } + this.rectPxBounds = new OpenLayers.Bounds( + Math.round(left), Math.round(bottom), + Math.round(right), Math.round(top) + ); + }, + + /** + * Method: getRectBoundsFromMapBounds + * Get the rect bounds from the map bounds. + * + * Parameters: + * lonLatBounds - {} + * + * Returns: + * {}A bounds which is the passed-in map lon/lat extent + * translated into pixel bounds for the overview map + */ + getRectBoundsFromMapBounds: function(lonLatBounds) { + var leftBottomLonLat = new OpenLayers.LonLat(lonLatBounds.left, + lonLatBounds.bottom); + var rightTopLonLat = new OpenLayers.LonLat(lonLatBounds.right, + lonLatBounds.top); + var leftBottomPx = this.getOverviewPxFromLonLat(leftBottomLonLat); + var rightTopPx = this.getOverviewPxFromLonLat(rightTopLonLat); + var bounds = null; + if (leftBottomPx && rightTopPx) { + bounds = new OpenLayers.Bounds(leftBottomPx.x, leftBottomPx.y, + rightTopPx.x, rightTopPx.y); + } + return bounds; + }, + + /** + * Method: getMapBoundsFromRectBounds + * Get the map bounds from the rect bounds. + * + * Parameters: + * pxBounds - {} + * + * Returns: + * {} Bounds which is the passed-in overview rect bounds + * translated into lon/lat bounds for the overview map + */ + getMapBoundsFromRectBounds: function(pxBounds) { + var leftBottomPx = new OpenLayers.Pixel(pxBounds.left, + pxBounds.bottom); + var rightTopPx = new OpenLayers.Pixel(pxBounds.right, + pxBounds.top); + var leftBottomLonLat = this.getLonLatFromOverviewPx(leftBottomPx); + var rightTopLonLat = this.getLonLatFromOverviewPx(rightTopPx); + return new OpenLayers.Bounds(leftBottomLonLat.lon, leftBottomLonLat.lat, + rightTopLonLat.lon, rightTopLonLat.lat); + }, + + /** + * Method: getLonLatFromOverviewPx + * Get a map location from a pixel location + * + * Parameters: + * overviewMapPx - {} + * + * Returns: + * {} Location which is the passed-in overview map + * OpenLayers.Pixel, translated into lon/lat by the overview map + */ + getLonLatFromOverviewPx: function(overviewMapPx) { + var size = this.ovmap.size; + var res = this.ovmap.getResolution(); + var center = this.ovmap.getExtent().getCenterLonLat(); + + var delta_x = overviewMapPx.x - (size.w / 2); + var delta_y = overviewMapPx.y - (size.h / 2); + + return new OpenLayers.LonLat(center.lon + delta_x * res , + center.lat - delta_y * res); + }, + + /** + * Method: getOverviewPxFromLonLat + * Get a pixel location from a map location + * + * Parameters: + * lonlat - {} + * + * Returns: + * {} Location which is the passed-in OpenLayers.LonLat, + * translated into overview map pixels + */ + getOverviewPxFromLonLat: function(lonlat) { + var res = this.ovmap.getResolution(); + var extent = this.ovmap.getExtent(); + var px = null; + if (extent) { + px = new OpenLayers.Pixel( + Math.round(1/res * (lonlat.lon - extent.left)), + Math.round(1/res * (extent.top - lonlat.lat))); + } + return px; + }, + + CLASS_NAME: 'OpenLayers.Control.OverviewMap' +}); + --- /dev/null +++ b/openlayers/lib/OpenLayers/Control/Pan.js @@ -1,1 +1,86 @@ +/* Copyright (c) 2006-2008 MetaCarta, Inc., published under the Clear BSD + * license. See http://svn.openlayers.org/trunk/openlayers/license.txt for the + * full text of the license. */ +/** + * @requires OpenLayers/Control.js + */ + +/** + * Class: OpenLayers.Control.Pan + * The Pan control is a single button to pan the map in one direction. For + * a more complete control see . + * + * Inherits from: + * - + */ +OpenLayers.Control.Pan = OpenLayers.Class(OpenLayers.Control, { + + /** + * APIProperty: slideFactor + * {Integer} Number of pixels by which we'll pan the map in any direction + * on clicking the arrow buttons. + */ + slideFactor: 50, + + /** + * Property: direction + * {String} in {'North', 'South', 'East', 'West'} + */ + direction: null, + + /** + * Property: type + * {String} The type of -- When added to a + * , 'type' is used by the panel to determine how to + * handle our events. + */ + type: OpenLayers.Control.TYPE_BUTTON, + + /** + * Constructor: OpenLayers.Control.Pan + * Control which handles the panning (in any of the cardinal directions) + * of the map by a set px distance. + * + * Parameters: + * direction - {String} The direction this button should pan. + * options - {Object} An optional object whose properties will be used + * to extend the control. + */ + initialize: function(direction, options) { + + this.direction = direction; + this.CLASS_NAME += this.direction; + + OpenLayers.Control.prototype.initialize.apply(this, [options]); + }, + + /** + * Method: trigger + */ + trigger: function(){ + + switch (this.direction) { + case OpenLayers.Control.Pan.NORTH: + this.map.pan(0, -this.slideFactor); + break; + case OpenLayers.Control.Pan.SOUTH: + this.map.pan(0, this.slideFactor); + break; + case OpenLayers.Control.Pan.WEST: + this.map.pan(-this.slideFactor, 0); + break; + case OpenLayers.Control.Pan.EAST: + this.map.pan(this.slideFactor, 0); + break; + } + }, + + CLASS_NAME: "OpenLayers.Control.Pan" +}); + +OpenLayers.Control.Pan.NORTH = "North"; +OpenLayers.Control.Pan.SOUTH = "South"; +OpenLayers.Control.Pan.EAST = "East"; +OpenLayers.Control.Pan.WEST = "West"; + --- /dev/null +++ b/openlayers/lib/OpenLayers/Control/PanPanel.js @@ -1,1 +1,48 @@ +/** + * @requires OpenLayers/Control/Panel.js + * @requires OpenLayers/Control/Pan.js + */ +/** + * Class: OpenLayers.Control.PanPanel + * The PanPanel is visible control for panning the map North, South, East or + * West in small steps. By default it is drawn in the top left corner of the + * map. + * + * Note: + * If you wish to use this class with the default images and you want + * it to look nice in ie6, you should add the following, conditionally + * added css stylesheet to your HTML file: + * + * (code) + * + * (end) + * + * Inherits from: + * - + */ +OpenLayers.Control.PanPanel = OpenLayers.Class(OpenLayers.Control.Panel, { + + /** + * Constructor: OpenLayers.Control.PanPanel + * Add the four directional pan buttons. + * + * Parameters: + * options - {Object} An optional object whose properties will be used + * to extend the control. + */ + initialize: function(options) { + OpenLayers.Control.Panel.prototype.initialize.apply(this, [options]); + this.addControls([ + new OpenLayers.Control.Pan(OpenLayers.Control.Pan.NORTH), + new OpenLayers.Control.Pan(OpenLayers.Control.Pan.SOUTH), + new OpenLayers.Control.Pan(OpenLayers.Control.Pan.EAST), + new OpenLayers.Control.Pan(OpenLayers.Control.Pan.WEST) + ]); + }, + + CLASS_NAME: "OpenLayers.Control.PanPanel" +}); + --- /dev/null +++ b/openlayers/lib/OpenLayers/Control/PanZoom.js @@ -1,1 +1,245 @@ - +/* Copyright (c) 2006-2008 MetaCarta, Inc., published under the Clear BSD + * license. See http://svn.openlayers.org/trunk/openlayers/license.txt for the + * full text of the license. */ + + +/** + * @requires OpenLayers/Control.js + */ + +/** + * Class: OpenLayers.Control.PanZoom + * The PanZoom is a visible control, composed of a + * and a . By + * default it is drawn in the upper left corner of the map. + * + * Inherits from: + * - + */ +OpenLayers.Control.PanZoom = OpenLayers.Class(OpenLayers.Control, { + + /** + * APIProperty: slideFactor + * {Integer} Number of pixels by which we'll pan the map in any direction + * on clicking the arrow buttons. If you want to pan by some ratio + * of the map dimensions, use instead. + */ + slideFactor: 50, + + /** + * APIProperty: slideRatio + * {Number} The fraction of map width/height by which we'll pan the map + * on clicking the arrow buttons. Default is null. If set, will + * override . E.g. if slideRatio is .5, then the Pan Up + * button will pan up half the map height. + */ + slideRatio: null, + + /** + * Property: buttons + * {Array(DOMElement)} Array of Button Divs + */ + buttons: null, + + /** + * Property: position + * {} + */ + position: null, + + /** + * Constructor: OpenLayers.Control.PanZoom + * + * Parameters: + * options - {Object} + */ + initialize: function(options) { + this.position = new OpenLayers.Pixel(OpenLayers.Control.PanZoom.X, + OpenLayers.Control.PanZoom.Y); + OpenLayers.Control.prototype.initialize.apply(this, arguments); + }, + + /** + * APIMethod: destroy + */ + destroy: function() { + OpenLayers.Control.prototype.destroy.apply(this, arguments); + this.removeButtons(); + this.buttons = null; + this.position = null; + }, + + /** + * Method: draw + * + * Parameters: + * px - {} + * + * Returns: + * {DOMElement} A reference to the container div for the PanZoom control. + */ + draw: function(px) { + // initialize our internal div + OpenLayers.Control.prototype.draw.apply(this, arguments); + px = this.position; + + // place the controls + this.buttons = []; + + var sz = new OpenLayers.Size(18,18); + var centered = new OpenLayers.Pixel(px.x+sz.w/2, px.y); + + this._addButton("panup", "north-mini.png", centered, sz); + px.y = centered.y+sz.h; + this._addButton("panleft", "west-mini.png", px, sz); + this._addButton("panright", "east-mini.png", px.add(sz.w, 0), sz); + this._addButton("pandown", "south-mini.png", + centered.add(0, sz.h*2), sz); + this._addButton("zoomin", "zoom-plus-mini.png", + centered.add(0, sz.h*3+5), sz); + this._addButton("zoomworld", "zoom-world-mini.png", + centered.add(0, sz.h*4+5), sz); + this._addButton("zoomout", "zoom-minus-mini.png", + centered.add(0, sz.h*5+5), sz); + return this.div; + }, + + /** + * Method: _addButton + * + * Parameters: + * id - {String} + * img - {String} + * xy - {} + * sz - {} + * + * Returns: + * {DOMElement} A Div (an alphaImageDiv, to be precise) that contains the + * image of the button, and has all the proper event handlers set. + */ + _addButton:function(id, img, xy, sz) { + var imgLocation = OpenLayers.Util.getImagesLocation() + img; + var btn = OpenLayers.Util.createAlphaImageDiv( + this.id + "_" + id, + xy, sz, imgLocation, "absolute"); + + //we want to add the outer div + this.div.appendChild(btn); + + OpenLayers.Event.observe(btn, "mousedown", + OpenLayers.Function.bindAsEventListener(this.buttonDown, btn)); + OpenLayers.Event.observe(btn, "dblclick", + OpenLayers.Function.bindAsEventListener(this.doubleClick, btn)); + OpenLayers.Event.observe(btn, "click", + OpenLayers.Function.bindAsEventListener(this.doubleClick, btn)); + btn.action = id; + btn.map = this.map; + + if(!this.slideRatio){ + var slideFactorPixels = this.slideFactor; + var getSlideFactor = function() { + return slideFactorPixels; + }; + } else { + var slideRatio = this.slideRatio; + var getSlideFactor = function(dim) { + return this.map.getSize()[dim] * slideRatio; + }; + } + + btn.getSlideFactor = getSlideFactor; + + //we want to remember/reference the outer div + this.buttons.push(btn); + return btn; + }, + + /** + * Method: _removeButton + * + * Parameters: + * btn - {Object} + */ + _removeButton: function(btn) { + OpenLayers.Event.stopObservingElement(btn); + btn.map = null; + this.div.removeChild(btn); + OpenLayers.Util.removeItem(this.buttons, btn); + }, + + /** + * Method: removeButtons + */ + removeButtons: function() { + for(var i=this.buttons.length-1; i>=0; --i) { + this._removeButton(this.buttons[i]); + } + }, + + /** + * Method: doubleClick + * + * Parameters: + * evt - {Event} + * + * Returns: + * {Boolean} + */ + doubleClick: function (evt) { + OpenLayers.Event.stop(evt); + return false; + }, + + /** + * Method: buttonDown + * + * Parameters: + * evt - {Event} + */ + buttonDown: function (evt) { + if (!OpenLayers.Event.isLeftClick(evt)) { + return; + } + + switch (this.action) { + case "panup": + this.map.pan(0, -this.getSlideFactor("h")); + break; + case "pandown": + this.map.pan(0, this.getSlideFactor("h")); + break; + case "panleft": + this.map.pan(-this.getSlideFactor("w"), 0); + break; + case "panright": + this.map.pan(this.getSlideFactor("w"), 0); + break; + case "zoomin": + this.map.zoomIn(); + break; + case "zoomout": + this.map.zoomOut(); + break; + case "zoomworld": + this.map.zoomToMaxExtent(); + break; + } + + OpenLayers.Event.stop(evt); + }, + + CLASS_NAME: "OpenLayers.Control.PanZoom" +}); + +/** + * Constant: X + * {Integer} + */ +OpenLayers.Control.PanZoom.X = 4; + +/** + * Constant: Y + * {Integer} + */ +OpenLayers.Control.PanZoom.Y = 4; + --- /dev/null +++ b/openlayers/lib/OpenLayers/Control/PanZoomBar.js @@ -1,1 +1,374 @@ - +/* Copyright (c) 2006-2008 MetaCarta, Inc., published under the Clear BSD + * license. See http://svn.openlayers.org/trunk/openlayers/license.txt for the + * full text of the license. */ + + +/** + * @requires OpenLayers/Control/PanZoom.js + */ + +/** + * Class: OpenLayers.Control.PanZoomBar + * The PanZoomBar is a visible control composed of a + * and a . + * By default it is displayed in the upper left corner of the map as 4 + * directional arrows above a vertical slider. + * + * Inherits from: + * - + */ +OpenLayers.Control.PanZoomBar = OpenLayers.Class(OpenLayers.Control.PanZoom, { + + /** + * APIProperty: zoomStopWidth + */ + zoomStopWidth: 18, + + /** + * APIProperty: zoomStopHeight + */ + zoomStopHeight: 11, + + /** + * Property: slider + */ + slider: null, + + /** + * Property: sliderEvents + * {} + */ + sliderEvents: null, + + /** + * Property: zoomBarDiv + * {DOMElement} + */ + zoomBarDiv: null, + + /** + * Property: divEvents + * {} + */ + divEvents: null, + + /** + * APIProperty: zoomWorldIcon + * {Boolean} + */ + zoomWorldIcon: false, + + /** + * Constructor: OpenLayers.Control.PanZoomBar + */ + initialize: function() { + OpenLayers.Control.PanZoom.prototype.initialize.apply(this, arguments); + }, + + /** + * APIMethod: destroy + */ + destroy: function() { + + this._removeZoomBar(); + + this.map.events.un({ + "changebaselayer": this.redraw, + scope: this + }); + + OpenLayers.Control.PanZoom.prototype.destroy.apply(this, arguments); + }, + + /** + * Method: setMap + * + * Parameters: + * map - {} + */ + setMap: function(map) { + OpenLayers.Control.PanZoom.prototype.setMap.apply(this, arguments); + this.map.events.register("changebaselayer", this, this.redraw); + }, + + /** + * Method: redraw + * clear the div and start over. + */ + redraw: function() { + if (this.div != null) { + this.removeButtons(); + this._removeZoomBar(); + } + this.draw(); + }, + + /** + * Method: draw + * + * Parameters: + * px - {} + */ + draw: function(px) { + // initialize our internal div + OpenLayers.Control.prototype.draw.apply(this, arguments); + px = this.position.clone(); + + // place the controls + this.buttons = []; + + var sz = new OpenLayers.Size(18,18); + var centered = new OpenLayers.Pixel(px.x+sz.w/2, px.y); + var wposition = sz.w; + + if (this.zoomWorldIcon) { + centered = new OpenLayers.Pixel(px.x+sz.w, px.y); + } + + this._addButton("panup", "north-mini.png", centered, sz); + px.y = centered.y+sz.h; + this._addButton("panleft", "west-mini.png", px, sz); + if (this.zoomWorldIcon) { + this._addButton("zoomworld", "zoom-world-mini.png", px.add(sz.w, 0), sz); + + wposition *= 2; + } + this._addButton("panright", "east-mini.png", px.add(wposition, 0), sz); + this._addButton("pandown", "south-mini.png", centered.add(0, sz.h*2), sz); + this._addButton("zoomin", "zoom-plus-mini.png", centered.add(0, sz.h*3+5), sz); + centered = this._addZoomBar(centered.add(0, sz.h*4 + 5)); + this._addButton("zoomout", "zoom-minus-mini.png", centered, sz); + return this.div; + }, + + /** + * Method: _addZoomBar + * + * Parameters: + * location - {} where zoombar drawing is to start. + */ + _addZoomBar:function(centered) { + var imgLocation = OpenLayers.Util.getImagesLocation(); + + var id = this.id + "_" + this.map.id; + var zoomsToEnd = this.map.getNumZoomLevels() - 1 - this.map.getZoom(); + var slider = OpenLayers.Util.createAlphaImageDiv(id, + centered.add(-1, zoomsToEnd * this.zoomStopHeight), + new OpenLayers.Size(20,9), + imgLocation+"slider.png", + "absolute"); + this.slider = slider; + + this.sliderEvents = new OpenLayers.Events(this, slider, null, true, + {includeXY: true}); + this.sliderEvents.on({ + "mousedown": this.zoomBarDown, + "mousemove": this.zoomBarDrag, + "mouseup": this.zoomBarUp, + "dblclick": this.doubleClick, + "click": this.doubleClick + }); + + var sz = new OpenLayers.Size(); + sz.h = this.zoomStopHeight * this.map.getNumZoomLevels(); + sz.w = this.zoomStopWidth; + var div = null; + + if (OpenLayers.Util.alphaHack()) { + var id = this.id + "_" + this.map.id; + div = OpenLayers.Util.createAlphaImageDiv(id, centered, + new OpenLayers.Size(sz.w, + this.zoomStopHeight), + imgLocation + "zoombar.png", + "absolute", null, "crop"); + div.style.height = sz.h + "px"; + } else { + div = OpenLayers.Util.createDiv( + 'OpenLayers_Control_PanZoomBar_Zoombar' + this.map.id, + centered, + sz, + imgLocation+"zoombar.png"); + } + + this.zoombarDiv = div; + + this.divEvents = new OpenLayers.Events(this, div, null, true, + {includeXY: true}); + this.divEvents.on({ + "mousedown": this.divClick, + "mousemove": this.passEventToSlider, + "dblclick": this.doubleClick, + "click": this.doubleClick + }); + + this.div.appendChild(div); + + this.startTop = parseInt(div.style.top); + this.div.appendChild(slider); + + this.map.events.register("zoomend", this, this.moveZoomBar); + + centered = centered.add(0, + this.zoomStopHeight * this.map.getNumZoomLevels()); + return centered; + }, + + /** + * Method: _removeZoomBar + */ + _removeZoomBar: function() { + this.sliderEvents.un({ + "mousedown": this.zoomBarDown, + "mousemove": this.zoomBarDrag, + "mouseup": this.zoomBarUp, + "dblclick": this.doubleClick, + "click": this.doubleClick + }); + this.sliderEvents.destroy(); + + this.divEvents.un({ + "mousedown": this.divClick, + "mousemove": this.passEventToSlider, + "dblclick": this.doubleClick, + "click": this.doubleClick + }); + this.divEvents.destroy(); + + this.div.removeChild(this.zoombarDiv); + this.zoombarDiv = null; + this.div.removeChild(this.slider); + this.slider = null; + + this.map.events.unregister("zoomend", this, this.moveZoomBar); + }, + + /** + * Method: passEventToSlider + * This function is used to pass events that happen on the div, or the map, + * through to the slider, which then does its moving thing. + * + * Parameters: + * evt - {} + */ + passEventToSlider:function(evt) { + this.sliderEvents.handleBrowserEvent(evt); + }, + + /** + * Method: divClick + * Picks up on clicks directly on the zoombar div + * and sets the zoom level appropriately. + */ + divClick: function (evt) { + if (!OpenLayers.Event.isLeftClick(evt)) { + return; + } + var y = evt.xy.y; + var top = OpenLayers.Util.pagePosition(evt.object)[1]; + var levels = (y - top)/this.zoomStopHeight; + if(!this.map.fractionalZoom) { + levels = Math.floor(levels); + } + var zoom = (this.map.getNumZoomLevels() - 1) - levels; + zoom = Math.min(Math.max(zoom, 0), this.map.getNumZoomLevels() - 1); + this.map.zoomTo(zoom); + OpenLayers.Event.stop(evt); + }, + + /* + * Method: zoomBarDown + * event listener for clicks on the slider + * + * Parameters: + * evt - {} + */ + zoomBarDown:function(evt) { + if (!OpenLayers.Event.isLeftClick(evt)) { + return; + } + this.map.events.on({ + "mousemove": this.passEventToSlider, + "mouseup": this.passEventToSlider, + scope: this + }); + this.mouseDragStart = evt.xy.clone(); + this.zoomStart = evt.xy.clone(); + this.div.style.cursor = "move"; + // reset the div offsets just in case the div moved + this.zoombarDiv.offsets = null; + OpenLayers.Event.stop(evt); + }, + + /* + * Method: zoomBarDrag + * This is what happens when a click has occurred, and the client is + * dragging. Here we must ensure that the slider doesn't go beyond the + * bottom/top of the zoombar div, as well as moving the slider to its new + * visual location + * + * Parameters: + * evt - {} + */ + zoomBarDrag:function(evt) { + if (this.mouseDragStart != null) { + var deltaY = this.mouseDragStart.y - evt.xy.y; + var offsets = OpenLayers.Util.pagePosition(this.zoombarDiv); + if ((evt.clientY - offsets[1]) > 0 && + (evt.clientY - offsets[1]) < parseInt(this.zoombarDiv.style.height) - 2) { + var newTop = parseInt(this.slider.style.top) - deltaY; + this.slider.style.top = newTop+"px"; + this.mouseDragStart = evt.xy.clone(); + } + OpenLayers.Event.stop(evt); + } + }, + + /* + * Method: zoomBarUp + * Perform cleanup when a mouseup event is received -- discover new zoom + * level and switch to it. + * + * Parameters: + * evt - {} + */ + zoomBarUp:function(evt) { + if (!OpenLayers.Event.isLeftClick(evt)) { + return; + } + if (this.zoomStart) { + this.div.style.cursor=""; + this.map.events.un({ + "mouseup": this.passEventToSlider, + "mousemove": this.passEventToSlider, + scope: this + }); + var deltaY = this.zoomStart.y - evt.xy.y; + var zoomLevel = this.map.zoom; + if (this.map.fractionalZoom) { + zoomLevel += deltaY/this.zoomStopHeight; + zoomLevel = Math.min(Math.max(zoomLevel, 0), + this.map.getNumZoomLevels() - 1); + } else { + zoomLevel += Math.round(deltaY/this.zoomStopHeight); + } + this.map.zoomTo(zoomLevel); + this.moveZoomBar(); + this.mouseDragStart = null; + OpenLayers.Event.stop(evt); + } + }, + + /* + * Method: moveZoomBar + * Change the location of the slider to match the current zoom level. + */ + moveZoomBar:function() { + var newTop = + ((this.map.getNumZoomLevels()-1) - this.map.getZoom()) * + this.zoomStopHeight + this.startTop + 1; + this.slider.style.top = newTop + "px"; + }, + + CLASS_NAME: "OpenLayers.Control.PanZoomBar" +}); + --- /dev/null +++ b/openlayers/lib/OpenLayers/Control/Panel.js @@ -1,1 +1,287 @@ - +/* Copyright (c) 2006-2008 MetaCarta, Inc., published under the Clear BSD + * license. See http://svn.openlayers.org/trunk/openlayers/license.txt for the + * full text of the license. */ + +/** + * @requires OpenLayers/Control.js + */ + +/** + * Class: OpenLayers.Control.Panel + * The Panel control is a container for other controls. With it toolbars + * may be composed. + * + * Inherits from: + * - + */ +OpenLayers.Control.Panel = OpenLayers.Class(OpenLayers.Control, { + /** + * Property: controls + * {Array()} + */ + controls: null, + + /** + * APIProperty: defaultControl + * {} The control which is activated when the control is + * activated (turned on), which also happens at instantiation. + */ + defaultControl: null, + + /** + * Constructor: OpenLayers.Control.Panel + * Create a new control panel. + * + * Parameters: + * options - {Object} An optional object whose properties will be used + * to extend the control. + */ + initialize: function(options) { + OpenLayers.Control.prototype.initialize.apply(this, [options]); + this.controls = []; + }, + + /** + * APIMethod: destroy + */ + destroy: function() { + OpenLayers.Control.prototype.destroy.apply(this, arguments); + for(var i = this.controls.length - 1 ; i >= 0; i--) { + if(this.controls[i].events) { + this.controls[i].events.un({ + "activate": this.redraw, + "deactivate": this.redraw, + scope: this + }); + } + OpenLayers.Event.stopObservingElement(this.controls[i].panel_div); + this.controls[i].panel_div = null; + } + }, + + /** + * APIMethod: activate + */ + activate: function() { + if (OpenLayers.Control.prototype.activate.apply(this, arguments)) { + for(var i=0, len=this.controls.length; i} + */ + activateControl: function (control) { + if (!this.active) { return false; } + if (control.type == OpenLayers.Control.TYPE_BUTTON) { + control.trigger(); + this.redraw(); + return; + } + if (control.type == OpenLayers.Control.TYPE_TOGGLE) { + if (control.active) { + control.deactivate(); + } else { + control.activate(); + } + this.redraw(); + return; + } + for (var i=0, len=this.controls.length; i} + */ + addControls: function(controls) { + if (!(controls instanceof Array)) { + controls = [controls]; + } + this.controls = this.controls.concat(controls); + + // Give each control a panel_div which will be used later. + // Access to this div is via the panel_div attribute of the + // control added to the panel. + // Also, stop mousedowns and clicks, but don't stop mouseup, + // since they need to pass through. + for (var i=0, len=controls.length; i)} A list of controls matching the given criteria. + * An empty array is returned if no matches are found. + */ + getControlsBy: function(property, match) { + var test = (typeof match.test == "function"); + var found = OpenLayers.Array.filter(this.controls, function(item) { + return item[property] == match || (test && match.test(item[property])); + }); + return found; + }, + + /** + * APIMethod: getControlsByName + * Get a list of contorls with names matching the given name. + * + * Parameter: + * match - {String | Object} A control name. The name can also be a regular + * expression literal or object. In addition, it can be any object + * with a method named test. For reqular expressions or other, if + * name.test(control.name) evaluates to true, the control will be included + * in the list of controls returned. If no controls are found, an empty + * array is returned. + * + * Returns: + * {Array()} A list of controls matching the given name. + * An empty array is returned if no matches are found. + */ + getControlsByName: function(match) { + return this.getControlsBy("name", match); + }, + + /** + * APIMethod: getControlsByClass + * Get a list of controls of a given type (CLASS_NAME). + * + * Parameter: + * match - {String | Object} A control class name. The type can also be a + * regular expression literal or object. In addition, it can be any + * object with a method named test. For reqular expressions or other, + * if type.test(control.CLASS_NAME) evaluates to true, the control will + * be included in the list of controls returned. If no controls are + * found, an empty array is returned. + * + * Returns: + * {Array()} A list of controls matching the given type. + * An empty array is returned if no matches are found. + */ + getControlsByClass: function(match) { + return this.getControlsBy("CLASS_NAME", match); + }, + + CLASS_NAME: "OpenLayers.Control.Panel" +}); + + --- /dev/null +++ b/openlayers/lib/OpenLayers/Control/Permalink.js @@ -1,1 +1,220 @@ - +/* Copyright (c) 2006-2008 MetaCarta, Inc., published under the Clear BSD + * license. See http://svn.openlayers.org/trunk/openlayers/license.txt for the + * full text of the license. */ + + +/** + * @requires OpenLayers/Control.js + * @requires OpenLayers/Control/ArgParser.js + */ + +/** + * Class: OpenLayers.Control.Permalink + * The Permalink control is hyperlink that will return the user to the + * current map view. By default it is drawn in the lower right corner of the + * map. The href is updated as the map is zoomed, panned and whilst layers + * are switched. + * ` + * Inherits from: + * - + */ +OpenLayers.Control.Permalink = OpenLayers.Class(OpenLayers.Control, { + + /** + * APIProperty: argParserClass + * {Class} The ArgParser control class (not instance) to use with this + * control. + */ + argParserClass: OpenLayers.Control.ArgParser, + + /** + * Property: element + * {DOMElement} + */ + element: null, + + /** + * APIProperty: base + * {String} + */ + base: '', + + /** + * APIProperty: displayProjection + * {} Requires proj4js support. Projection used + * when creating the coordinates in the link. This will reproject the + * map coordinates into display coordinates. If you are using this + * functionality, the permalink which is last added to the map will + * determine the coordinate type which is read from the URL, which + * means you should not add permalinks with different + * displayProjections to the same map. + */ + displayProjection: null, + + /** + * Constructor: OpenLayers.Control.Permalink + * + * Parameters: + * element - {DOMElement} + * base - {String} + * options - {Object} options to the control. + */ + initialize: function(element, base, options) { + OpenLayers.Control.prototype.initialize.apply(this, [options]); + this.element = OpenLayers.Util.getElement(element); + this.base = base || document.location.href; + }, + + /** + * APIMethod: destroy + */ + destroy: function() { + if (this.element.parentNode == this.div) { + this.div.removeChild(this.element); + } + this.element = null; + + this.map.events.unregister('moveend', this, this.updateLink); + + OpenLayers.Control.prototype.destroy.apply(this, arguments); + }, + + /** + * Method: setMap + * Set the map property for the control. + * + * Parameters: + * map - {} + */ + setMap: function(map) { + OpenLayers.Control.prototype.setMap.apply(this, arguments); + + //make sure we have an arg parser attached + for(var i=0, len=this.map.controls.length; i} center to encode in the permalink. + * Defaults to the current map center. + * zoom - {Integer} zoom level to encode in the permalink. Defaults to the + * current map zoom level. + * layers - {Array()} layers to encode in the permalink. + * Defaults to the current map layers. + * + * Returns: + * {Object} Hash of parameters that will be url-encoded into the + * permalink. + */ + createParams: function(center, zoom, layers) { + center = center || this.map.getCenter(); + + var params = OpenLayers.Util.getParameters(this.base); + + // If there's still no center, map is not initialized yet. + // Break out of this function, and simply return the params from the + // base link. + if (center) { + + //zoom + params.zoom = zoom || this.map.getZoom(); + + //lon,lat + var lat = center.lat; + var lon = center.lon; + + if (this.displayProjection) { + var mapPosition = OpenLayers.Projection.transform( + { x: lon, y: lat }, + this.map.getProjectionObject(), + this.displayProjection ); + lon = mapPosition.x; + lat = mapPosition.y; + } + params.lat = Math.round(lat*100000)/100000; + params.lon = Math.round(lon*100000)/100000; + + //layers + layers = layers || this.map.layers; + params.layers = ''; + for (var i=0, len=layers.length; i + */ +OpenLayers.Control.Scale = OpenLayers.Class(OpenLayers.Control, { + + /** + * Parameter: element + * {DOMElement} + */ + element: null, + + /** + * Constructor: OpenLayers.Control.Scale + * + * Parameters: + * element - {DOMElement} + * options - {Object} + */ + initialize: function(element, options) { + OpenLayers.Control.prototype.initialize.apply(this, [options]); + this.element = OpenLayers.Util.getElement(element); + }, + + /** + * Method: draw + * + * Returns: + * {DOMElement} + */ + draw: function() { + OpenLayers.Control.prototype.draw.apply(this, arguments); + if (!this.element) { + this.element = document.createElement("div"); + this.div.appendChild(this.element); + } + this.map.events.register( 'moveend', this, this.updateScale); + this.updateScale(); + return this.div; + }, + + /** + * Method: updateScale + */ + updateScale: function() { + var scale = this.map.getScale(); + if (!scale) { + return; + } + + if (scale >= 9500 && scale <= 950000) { + scale = Math.round(scale / 1000) + "K"; + } else if (scale >= 950000) { + scale = Math.round(scale / 1000000) + "M"; + } else { + scale = Math.round(scale); + } + + this.element.innerHTML = OpenLayers.i18n("scale", {'scaleDenom':scale}); + }, + + CLASS_NAME: "OpenLayers.Control.Scale" +}); + + --- /dev/null +++ b/openlayers/lib/OpenLayers/Control/ScaleLine.js @@ -1,1 +1,209 @@ - +/* Copyright (c) 2006-2007 MetaCarta, Inc., published under a modified BSD license. + * See http://svn.openlayers.org/trunk/openlayers/repository-license.txt + * for the full text of the license. */ + +/** + * @requires OpenLayers/Control.js + */ + +/** + * Class: OpenLayers.Control.ScaleLine + * The ScaleLine displays a small line indicator representing the current + * map scale on the map. By default it is drawn in the lower left corner of + * the map. + * + * Inherits from: + * - + * + * Is a very close copy of: + * - + */ +OpenLayers.Control.ScaleLine = OpenLayers.Class(OpenLayers.Control, { + + /** + * Property: maxWidth + * {Integer} Maximum width of the scale line in pixels. Default is 100. + */ + maxWidth: 100, + + /** + * Property: topOutUnits + * {String} Units for zoomed out on top bar. Default is km. + */ + topOutUnits: "km", + + /** + * Property: topInUnits + * {String} Units for zoomed in on top bar. Default is m. + */ + topInUnits: "m", + + /** + * Property: bottomOutUnits + * {String} Units for zoomed out on bottom bar. Default is mi. + */ + bottomOutUnits: "mi", + + /** + * Property: bottomInUnits + * {String} Units for zoomed in on bottom bar. Default is ft. + */ + bottomInUnits: "ft", + + /** + * Property: eTop + * {DOMElement} + */ + eTop: null, + + /** + * Property: eBottom + * {DOMElement} + */ + eBottom:null, + + /** + * Constructor: OpenLayers.Control.ScaleLine + * Create a new scale line control. + * + * Parameters: + * options - {Object} An optional object whose properties will be used + * to extend the control. + */ + initialize: function(options) { + OpenLayers.Control.prototype.initialize.apply(this, [options]); + }, + + /** + * Method: draw + * + * Returns: + * {DOMElement} + */ + draw: function() { + OpenLayers.Control.prototype.draw.apply(this, arguments); + if (!this.eTop) { + this.div.style.display = "block"; + this.div.style.position = "absolute"; + + // stick in the top bar + this.eTop = document.createElement("div"); + this.eTop.className = this.displayClass + "Top"; + var theLen = this.topInUnits.length; + this.div.appendChild(this.eTop); + if((this.topOutUnits == "") || (this.topInUnits == "")) { + this.eTop.style.visibility = "hidden"; + } else { + this.eTop.style.visibility = "visible"; + } + + // and the bottom bar + this.eBottom = document.createElement("div"); + this.eBottom.className = this.displayClass + "Bottom"; + this.div.appendChild(this.eBottom); + if((this.bottomOutUnits == "") || (this.bottomInUnits == "")) { + this.eBottom.style.visibility = "hidden"; + } else { + this.eBottom.style.visibility = "visible"; + } + } + this.map.events.register('moveend', this, this.update); + this.update(); + return this.div; + }, + + /** + * Method: getBarLen + * Given a number, round it down to the nearest 1,2,5 times a power of 10. + * That seems a fairly useful set of number groups to use. + * + * Parameters: + * maxLen - {float} the number we're rounding down from + * + * Returns: + * {Float} the rounded number (less than or equal to maxLen) + */ + getBarLen: function(maxLen) { + // nearest power of 10 lower than maxLen + var digits = parseInt(Math.log(maxLen) / Math.log(10)); + var pow10 = Math.pow(10, digits); + + // ok, find first character + var firstChar = parseInt(maxLen / pow10); + + // right, put it into the correct bracket + var barLen; + if(firstChar > 5) { + barLen = 5; + } else if(firstChar > 2) { + barLen = 2; + } else { + barLen = 1; + } + + // scale it up the correct power of 10 + return barLen * pow10; + }, + + /** + * Method: update + * Update the size of the bars, and the labels they contain. + */ + update: function() { + var res = this.map.getResolution(); + if (!res) { + return; + } + + var curMapUnits = this.map.getUnits(); + var inches = OpenLayers.INCHES_PER_UNIT; + + // convert maxWidth to map units + var maxSizeData = this.maxWidth * res * inches[curMapUnits]; + + // decide whether to use large or small scale units + var topUnits; + var bottomUnits; + if(maxSizeData > 100000) { + topUnits = this.topOutUnits; + bottomUnits = this.bottomOutUnits; + } else { + topUnits = this.topInUnits; + bottomUnits = this.bottomInUnits; + } + + // and to map units units + var topMax = maxSizeData / inches[topUnits]; + var bottomMax = maxSizeData / inches[bottomUnits]; + + // now trim this down to useful block length + var topRounded = this.getBarLen(topMax); + var bottomRounded = this.getBarLen(bottomMax); + + // and back to display units + topMax = topRounded / inches[curMapUnits] * inches[topUnits]; + bottomMax = bottomRounded / inches[curMapUnits] * inches[bottomUnits]; + + // and to pixel units + var topPx = topMax / res; + var bottomPx = bottomMax / res; + + // now set the pixel widths + // and the values inside them + + if (this.eBottom.style.visibility == "visible"){ + this.eBottom.style.width = Math.round(bottomPx) + "px"; + this.eBottom.innerHTML = bottomRounded + " " + bottomUnits ; + } + + if (this.eTop.style.visibility == "visible"){ + this.eTop.style.width = Math.round(topPx) + "px"; + this.eTop.innerHTML = topRounded + " " + topUnits; + } + + }, + + CLASS_NAME: "OpenLayers.Control.ScaleLine" +}); + + --- /dev/null +++ b/openlayers/lib/OpenLayers/Control/SelectFeature.js @@ -1,1 +1,563 @@ - +/* Copyright (c) 2006-2008 MetaCarta, Inc., published under the Clear BSD + * license. See http://svn.openlayers.org/trunk/openlayers/license.txt for the + * full text of the license. */ + + +/** + * @requires OpenLayers/Control.js + * @requires OpenLayers/Feature/Vector.js + * @requires OpenLayers/Handler/Feature.js + * @requires OpenLayers/Layer/Vector/RootContainer.js + */ + +/** + * Class: OpenLayers.Control.SelectFeature + * The SelectFeature control selects vector features from a given layer on + * click or hover. + * + * Inherits from: + * - + */ +OpenLayers.Control.SelectFeature = OpenLayers.Class(OpenLayers.Control, { + + /** + * Constant: EVENT_TYPES + * + * Supported event types: + * - *beforefeaturehighlighted* Triggered before a feature is highlighted + * - *featurehighlighted* Triggered when a feature is highlighted + * - *featureunhighlighted* Triggered when a feature is unhighlighted + */ + EVENT_TYPES: ["beforefeaturehighlighted", "featurehighlighted", "featureunhighlighted"], + + /** + * Property: multipleKey + * {String} An event modifier ('altKey' or 'shiftKey') that temporarily sets + * the property to true. Default is null. + */ + multipleKey: null, + + /** + * Property: toggleKey + * {String} An event modifier ('altKey' or 'shiftKey') that temporarily sets + * the property to true. Default is null. + */ + toggleKey: null, + + /** + * APIProperty: multiple + * {Boolean} Allow selection of multiple geometries. Default is false. + */ + multiple: false, + + /** + * APIProperty: clickout + * {Boolean} Unselect features when clicking outside any feature. + * Default is true. + */ + clickout: true, + + /** + * APIProperty: toggle + * {Boolean} Unselect a selected feature on click. Default is false. Only + * has meaning if hover is false. + */ + toggle: false, + + /** + * APIProperty: hover + * {Boolean} Select on mouse over and deselect on mouse out. If true, this + * ignores clicks and only listens to mouse moves. + */ + hover: false, + + /** + * APIProperty: highlightOnly + * {Boolean} If true do not actually select features (i.e. place them in the + * layer's selected features array), just highlight them. This property has + * no effect if hover is false. Defaults to false. + */ + highlightOnly: false, + + /** + * APIProperty: box + * {Boolean} Allow feature selection by drawing a box. + */ + box: false, + + /** + * Property: onBeforeSelect + * {Function} Optional function to be called before a feature is selected. + * The function should expect to be called with a feature. + */ + onBeforeSelect: function() {}, + + /** + * APIProperty: onSelect + * {Function} Optional function to be called when a feature is selected. + * The function should expect to be called with a feature. + */ + onSelect: function() {}, + + /** + * APIProperty: onUnselect + * {Function} Optional function to be called when a feature is unselected. + * The function should expect to be called with a feature. + */ + onUnselect: function() {}, + + /** + * Property: scope + * {Object} The scope to use with the onBeforeSelect, onSelect, onUnselect + * callbacks. If null the scope will be this control. + */ + scope: null, + + /** + * APIProperty: geometryTypes + * {Array(String)} To restrict selecting to a limited set of geometry types, + * send a list of strings corresponding to the geometry class names. + */ + geometryTypes: null, + + /** + * Property: layer + * {} The vector layer with a common renderer + * root for all layers this control is configured with (if an array of + * layers was passed to the constructor), or the vector layer the control + * was configured with (if a single layer was passed to the constructor). + */ + layer: null, + + /** + * Property: layers + * {Array(} The layers this control will work on, + * or null if the control was configured with a single layer + */ + layers: null, + + /** + * APIProperty: callbacks + * {Object} The functions that are sent to the handlers.feature for callback + */ + callbacks: null, + + /** + * APIProperty: selectStyle + * {Object} Hash of styles + */ + selectStyle: null, + + /** + * Property: renderIntent + * {String} key used to retrieve the select style from the layer's + * style map. + */ + renderIntent: "select", + + /** + * Property: handlers + * {Object} Object with references to multiple + * instances. + */ + handlers: null, + + /** + * Constructor: OpenLayers.Control.SelectFeature + * Create a new control for selecting features. + * + * Parameters: + * layers - {}, or an array of vector layers. The + * layer(s) this control will select features from. + * options - {Object} + */ + initialize: function(layers, options) { + // concatenate events specific to this control with those from the base + this.EVENT_TYPES = + OpenLayers.Control.SelectFeature.prototype.EVENT_TYPES.concat( + OpenLayers.Control.prototype.EVENT_TYPES + ); + OpenLayers.Control.prototype.initialize.apply(this, [options]); + + if(this.scope === null) { + this.scope = this; + } + if(layers instanceof Array) { + this.layers = layers; + this.layer = new OpenLayers.Layer.Vector.RootContainer( + this.id + "_container", { + layers: layers + } + ); + } else { + this.layer = layers; + } + var callbacks = { + click: this.clickFeature, + clickout: this.clickoutFeature + }; + if (this.hover) { + callbacks.over = this.overFeature; + callbacks.out = this.outFeature; + } + + this.callbacks = OpenLayers.Util.extend(callbacks, this.callbacks); + this.handlers = { + feature: new OpenLayers.Handler.Feature( + this, this.layer, this.callbacks, + {geometryTypes: this.geometryTypes} + ) + }; + + if (this.box) { + this.handlers.box = new OpenLayers.Handler.Box( + this, {done: this.selectBox}, + {boxDivClassName: "olHandlerBoxSelectFeature"} + ); + } + }, + + /** + * Method: destroy + */ + destroy: function() { + OpenLayers.Control.prototype.destroy.apply(this, arguments); + if(this.layers) { + this.layer.destroy(); + } + }, + + /** + * Method: activate + * Activates the control. + * + * Returns: + * {Boolean} The control was effectively activated. + */ + activate: function () { + if (!this.active) { + if(this.layers) { + this.map.addLayer(this.layer); + } + this.handlers.feature.activate(); + if(this.box && this.handlers.box) { + this.handlers.box.activate(); + } + } + return OpenLayers.Control.prototype.activate.apply( + this, arguments + ); + }, + + /** + * Method: deactivate + * Deactivates the control. + * + * Returns: + * {Boolean} The control was effectively deactivated. + */ + deactivate: function () { + if (this.active) { + this.handlers.feature.deactivate(); + if(this.handlers.box) { + this.handlers.box.deactivate(); + } + if(this.layers) { + this.map.removeLayer(this.layer); + } + } + return OpenLayers.Control.prototype.deactivate.apply( + this, arguments + ); + }, + + /** + * Method: unselectAll + * Unselect all selected features. To unselect all except for a single + * feature, set the options.except property to the feature. + * + * Parameters: + * options - {Object} Optional configuration object. + */ + unselectAll: function(options) { + // we'll want an option to supress notification here + var layers = this.layers || [this.layer]; + var layer, feature; + for(var l=0; l=0; --i) { + feature = layer.selectedFeatures[i]; + if(!options || options.except != feature) { + this.unselect(feature); + } + } + } + }, + + /** + * Method: clickFeature + * Called on click in a feature + * Only responds if this.hover is false. + * + * Parameters: + * feature - {} + */ + clickFeature: function(feature) { + if(!this.hover) { + var selected = (OpenLayers.Util.indexOf( + feature.layer.selectedFeatures, feature) > -1); + if(selected) { + if(this.toggleSelect()) { + this.unselect(feature); + } else if(!this.multipleSelect()) { + this.unselectAll({except: feature}); + } + } else { + if(!this.multipleSelect()) { + this.unselectAll({except: feature}); + } + this.select(feature); + } + } + }, + + /** + * Method: multipleSelect + * Allow for multiple selected features based on property and + * event modifier. + * + * Returns: + * {Boolean} Allow for multiple selected features. + */ + multipleSelect: function() { + return this.multiple || (this.handlers.feature.evt && + this.handlers.feature.evt[this.multipleKey]); + }, + + /** + * Method: toggleSelect + * Event should toggle the selected state of a feature based on + * property and event modifier. + * + * Returns: + * {Boolean} Toggle the selected state of a feature. + */ + toggleSelect: function() { + return this.toggle || (this.handlers.feature.evt && + this.handlers.feature.evt[this.toggleKey]); + }, + + /** + * Method: clickoutFeature + * Called on click outside a previously clicked (selected) feature. + * Only responds if this.hover is false. + * + * Parameters: + * feature - {} + */ + clickoutFeature: function(feature) { + if(!this.hover && this.clickout) { + this.unselectAll(); + } + }, + + /** + * Method: overFeature + * Called on over a feature. + * Only responds if this.hover is true. + * + * Parameters: + * feature - {} + */ + overFeature: function(feature) { + var layer = feature.layer; + if(this.hover) { + if(this.highlightOnly) { + this.highlight(feature); + } else if(OpenLayers.Util.indexOf( + layer.selectedFeatures, feature) == -1) { + this.select(feature); + } + } + }, + + /** + * Method: outFeature + * Called on out of a selected feature. + * Only responds if this.hover is true. + * + * Parameters: + * feature - {} + */ + outFeature: function(feature) { + if(this.hover) { + if(this.highlightOnly) { + // we do nothing if we're not the last highlighter of the + // feature + if(feature._lastHighlighter == this.id) { + // if another select control had highlighted the feature before + // we did it ourself then we use that control to highlight the + // feature as it was before we highlighted it, else we just + // unhighlight it + if(feature._prevHighlighter && + feature._prevHighlighter != this.id) { + delete feature._lastHighlighter; + var control = this.map.getControl( + feature._prevHighlighter); + if(control) { + control.highlight(feature); + } + } else { + this.unhighlight(feature); + } + } + } else { + this.unselect(feature); + } + } + }, + + /** + * Method: highlight + * Redraw feature with the select style. + * + * Parameters: + * feature - {} + */ + highlight: function(feature) { + var layer = feature.layer; + var cont = this.events.triggerEvent("beforefeaturehighlighted", { + feature : feature + }); + if(cont !== false) { + feature._prevHighlighter = feature._lastHighlighter; + feature._lastHighlighter = this.id; + var style = this.selectStyle || this.renderIntent; + layer.drawFeature(feature, style); + this.events.triggerEvent("featurehighlighted", {feature : feature}); + } + }, + + /** + * Method: unhighlight + * Redraw feature with the "default" style + * + * Parameters: + * feature - {} + */ + unhighlight: function(feature) { + var layer = feature.layer; + feature._lastHighlighter = feature._prevHighlighter; + delete feature._prevHighlighter; + layer.drawFeature(feature, feature.style || feature.layer.style || + "default"); + this.events.triggerEvent("featureunhighlighted", {feature : feature}); + }, + + /** + * Method: select + * Add feature to the layer's selectedFeature array, render the feature as + * selected, and call the onSelect function. + * + * Parameters: + * feature - {} + */ + select: function(feature) { + var cont = this.onBeforeSelect.call(this.scope, feature); + var layer = feature.layer; + if(cont !== false) { + cont = layer.events.triggerEvent("beforefeatureselected", { + feature: feature + }); + if(cont !== false) { + layer.selectedFeatures.push(feature); + this.highlight(feature); + layer.events.triggerEvent("featureselected", {feature: feature}); + this.onSelect.call(this.scope, feature); + } + } + }, + + /** + * Method: unselect + * Remove feature from the layer's selectedFeature array, render the feature as + * normal, and call the onUnselect function. + * + * Parameters: + * feature - {} + */ + unselect: function(feature) { + var layer = feature.layer; + // Store feature style for restoration later + this.unhighlight(feature); + OpenLayers.Util.removeItem(layer.selectedFeatures, feature); + layer.events.triggerEvent("featureunselected", {feature: feature}); + this.onUnselect.call(this.scope, feature); + }, + + /** + * Method: selectBox + * Callback from the handlers.box set up when selection is true + * on. + * + * Parameters: + * position - { || } + */ + selectBox: function(position) { + if (position instanceof OpenLayers.Bounds) { + var minXY = this.map.getLonLatFromPixel( + new OpenLayers.Pixel(position.left, position.bottom) + ); + var maxXY = this.map.getLonLatFromPixel( + new OpenLayers.Pixel(position.right, position.top) + ); + var bounds = new OpenLayers.Bounds( + minXY.lon, minXY.lat, maxXY.lon, maxXY.lat + ); + + // if multiple is false, first deselect currently selected features + if (!this.multipleSelect()) { + this.unselectAll(); + } + + // because we're using a box, we consider we want multiple selection + var prevMultiple = this.multiple; + this.multiple = true; + var layers = this.layers || [this.layer]; + var layer; + for(var l=0; l -1) { + if (bounds.toGeometry().intersects(feature.geometry)) { + if (OpenLayers.Util.indexOf(layer.selectedFeatures, feature) == -1) { + this.select(feature); + } + } + } + } + } + this.multiple = prevMultiple; + } + }, + + /** + * Method: setMap + * Set the map property for the control. + * + * Parameters: + * map - {} + */ + setMap: function(map) { + this.handlers.feature.setMap(map); + if (this.box) { + this.handlers.box.setMap(map); + } + OpenLayers.Control.prototype.setMap.apply(this, arguments); + }, + + CLASS_NAME: "OpenLayers.Control.SelectFeature" +}); + --- /dev/null +++ b/openlayers/lib/OpenLayers/Control/Snapping.js @@ -1,1 +1,547 @@ - +/* Copyright (c) 2006-2008 MetaCarta, Inc., published under the Clear BSD + * license. See http://svn.openlayers.org/trunk/openlayers/license.txt for the + * full text of the license. */ + +/** + * @requires OpenLayers/Control.js + * @requires OpenLayers/Layer/Vector.js + */ + +/** + * Class: OpenLayers.Control.Snapping + * Acts as a snapping agent while editing vector features. + * + * Inherits from: + * - + */ +OpenLayers.Control.Snapping = OpenLayers.Class(OpenLayers.Control, { + + /** + * Constant: EVENT_TYPES + * {Array(String)} Supported application event types. Register a listener + * for a particular event with the following syntax: + * (code) + * control.events.register(type, obj, listener); + * (end) + * + * Listeners will be called with a reference to an event object. The + * properties of this event depends on exactly what happened. + * + * Supported control event types (in addition to those from ): + * beforesnap - Triggered before a snap occurs. Listeners receive an + * event object with *point*, *x*, *y*, *distance*, *layer*, and + * *snapType* properties. The point property will be original point + * geometry considered for snapping. The x and y properties represent + * coordinates the point will receive. The distance is the distance + * of the snap. The layer is the target layer. The snapType property + * will be one of "node", "vertex", or "edge". Return false to stop + * snapping from occurring. + * snap - Triggered when a snap occurs. Listeners receive an event with + * *point*, *snapType*, *layer*, and *distance* properties. The point + * will be the location snapped to. The snapType will be one of "node", + * "vertex", or "edge". The layer will be the target layer. The + * distance will be the distance of the snap in map units. + * unsnap - Triggered when a vertex is unsnapped. Listeners receive an + * event with a *point* property. + */ + EVENT_TYPES: ["beforesnap", "snap", "unsnap"], + + /** + * CONSTANT: DEFAULTS + * Default target properties. + */ + DEFAULTS: { + tolerance: 10, + node: true, + edge: true, + vertex: true + }, + + /** + * Property: greedy + * {Boolean} Snap to closest feature in first layer with an eligible + * feature. Default is true. + */ + greedy: true, + + /** + * Property: precedence + * {Array} List representing precedence of different snapping types. + * Default is "node", "vertex", "edge". + */ + precedence: ["node", "vertex", "edge"], + + /** + * Property: resolution + * {Float} The map resolution for the previously considered snap. + */ + resolution: null, + + /** + * Property: geoToleranceCache + * {Object} A cache of geo-tolerances. Tolerance values (in map units) are + * calculated when the map resolution changes. + */ + geoToleranceCache: null, + + /** + * Property: layer + * {} The current editable layer. Set at + * construction or after construction with . + */ + layer: null, + + /** + * Property: feature + * {} The current editable feature. + */ + feature: null, + + /** + * Property: point + * {} The currently snapped vertex. + */ + point: null, + + /** + * Constructor: OpenLayers.Control.Snapping + * Creates a new snapping control. A control is constructed with an editable + * layer and a set of configuration objects for target layers. While the + * control is active, dragging vertices while drawing new features or + * modifying existing features on the editable layer will engage + * snapping to features on the target layers. Whether a vertex snaps to + * a feature on a target layer depends on the target layer configuration. + * + * Parameters: + * options - {Object} An object containing all configuration properties for + * the control. + * + * Valid options: + * layer - {OpenLayers.Layer.Vector} The editable layer. Features from this + * layer that are digitized or modified may have vertices snapped to + * features from any of the target layers. + * targets - {Array(Object | OpenLayers.Layer.Vector)} A list of objects for + * configuring target layers. See valid properties of the target + * objects below. If the items in the targets list are vector layers + * (instead of configuration objects), the defaults from the + * property will apply. The editable layer itself may be a target + * layer - allowing newly created or edited features to be snapped to + * existing features from the same layer. If no targets are provided + * the layer given in the constructor (as ) will become the + * initial target. + * defaults - {Object} An object with default properties to be applied + * to all target objects. + * greedy - {Boolean} Snap to closest feature in first target layer that + * applies. Default is true. If false, all features in all target + * layers will be checked and the closest feature in all target layers + * will be chosen. The greedy property determines if the order of the + * target layers is significant. By default, the order of the target + * layers is significant where layers earlier in the target layer list + * have precedence over layers later in the list. Within a single + * layer, the closest feature is always chosen for snapping. This + * property only determines whether the search for a closer feature + * continues after an eligible feature is found in a target layer. + * + * Valid target properties: + * layer - {OpenLayers.Layer.Vector} A target layer. Features from this + * layer will be eligible to act as snapping target for the editable + * layer. + * tolerance - {Float} The distance (in pixels) at which snapping may occur. + * Default is 10. + * node - {Boolean} Snap to nodes (first or last point in a geometry) in + * target layer. Default is true. + * nodeTolerance - {Float} Optional distance at which snapping may occur + * for nodes specifically. If none is provided, will be + * used. + * vertex - {Boolean} Snap to vertices in target layer. Default is true. + * vertexTolerance - {Float} Optional distance at which snapping may occur + * for vertices specifically. If none is provided, will be + * used. + * edge - {Boolean} Snap to edges in target layer. Default is true. + * edgeTolerance - {Float} Optional distance at which snapping may occur + * for edges specifically. If none is provided, will be + * used. + * filter - {OpenLayers.Filter} Optional filter to evaluate to determine if + * feature is eligible for snapping. If filter evaluates to true for a + * target feature a vertex may be snapped to the feature. + */ + initialize: function(options) { + // concatenate events specific to measure with those from the base + Array.prototype.push.apply( + this.EVENT_TYPES, OpenLayers.Control.prototype.EVENT_TYPES + ); + OpenLayers.Control.prototype.initialize.apply(this, [options]); + this.options = options || {}; // TODO: this could be done by the super + + // set the editable layer if provided + if(this.options.layer) { + this.setLayer(this.options.layer); + } + // configure target layers + var defaults = OpenLayers.Util.extend({}, this.options.defaults); + this.defaults = OpenLayers.Util.applyDefaults(defaults, this.DEFAULTS); + this.setTargets(this.options.targets); + if(this.targets.length === 0 && this.layer) { + this.addTargetLayer(this.layer); + } + + this.geoToleranceCache = {}; + }, + + /** + * APIMethod: setLayer + * Set the editable layer. Call the setLayer method if the editable layer + * changes and the same control should be used on a new editable layer. + * If the control is already active, it will be active after the new + * layer is set. + * + * Parameters: + * layer - {OpenLayers.Layer.Vector} The new editable layer. + */ + setLayer: function(layer) { + if(this.active) { + this.deactivate(); + this.layer = layer; + this.activate(); + } else { + this.layer = layer; + } + }, + + /** + * Method: setTargets + * Set the targets for the snapping agent. + * + * Parameters: + * targets - {Array} An array of target configs or target layers. + */ + setTargets: function(targets) { + this.targets = []; + if(targets && targets.length) { + var target; + for(var i=0, len=targets.length; i} A target layer. + */ + addTargetLayer: function(layer) { + this.addTarget({layer: layer}); + }, + + /** + * Method: addTarget + * Add a configured target layer. + * + * Parameters: + * target - {Object} A target config. + */ + addTarget: function(target) { + target = OpenLayers.Util.applyDefaults(target, this.defaults); + target.nodeTolerance = target.nodeTolerance || target.tolerance; + target.vertexTolerance = target.vertexTolerance || target.tolerance; + target.edgeTolerance = target.edgeTolerance || target.tolerance; + this.targets.push(target); + }, + + /** + * Method: removeTargetLayer + * Remove a target layer. + * + * Parameters: + * layer - {} The target layer to remove. + */ + removeTargetLayer: function(layer) { + var target; + for(var i=this.targets.length-1; i>=0; --i) { + target = this.targets[i]; + if(target.layer === layer) { + this.removeTarget(target); + } + } + }, + + /** + * Method: removeTarget + * Remove a target. + * + * Parameters: + * target - {Object} A target config. + * + * Returns: + * {Array} The targets array. + */ + removeTarget: function(target) { + return OpenLayers.Util.removeItem(this.targets, target); + }, + + /** + * APIMethod: activate + * Activate the control. Activating the control registers listeners for + * editing related events so that during feature creation and + * modification, moving vertices will trigger snapping. + */ + activate: function() { + var activated = OpenLayers.Control.prototype.activate.call(this); + if(activated) { + if(this.layer && this.layer.events) { + this.layer.events.on({ + sketchstarted: this.onSketchModified, + sketchmodified: this.onSketchModified, + vertexmodified: this.onVertexModified, + scope: this + }); + } + } + return activated; + }, + + /** + * APIMethod: deactivate + * Deactivate the control. Deactivating the control unregisters listeners + * so feature editing may proceed without engaging the snapping agent. + */ + deactivate: function() { + var deactivated = OpenLayers.Control.prototype.deactivate.call(this); + if(deactivated) { + if(this.layer && this.layer.events) { + this.layer.events.un({ + sketchstarted: this.onSketchModified, + sketchmodified: this.onSketchModified, + vertexmodified: this.onVertexModified, + scope: this + }); + } + } + this.feature = null; + this.point = null; + return deactivated; + }, + + /** + * Method: onSketchModified + * Registered as a listener for the sketchmodified event on the editable + * layer. + * + * Parameters: + * event - {Object} The sketch modified event. + */ + onSketchModified: function(event) { + this.feature = event.feature; + this.considerSnapping(event.vertex, event.vertex); + }, + + /** + * Method: onVertexModified + * Registered as a listener for the vertexmodified event on the editable + * layer. + * + * Parameters: + * event - {Object} The vertex modified event. + */ + onVertexModified: function(event) { + this.feature = event.feature; + var loc = this.layer.map.getLonLatFromViewPortPx(event.pixel); + this.considerSnapping( + event.vertex, new OpenLayers.Geometry.Point(loc.lon, loc.lat) + ); + }, + + /** + * Method: considerSnapping + * + * Parameters: + * point - {} The location of the mouse in map + * coords. + */ + considerSnapping: function(point, loc) { + var best = { + rank: Number.POSITIVE_INFINITY, + dist: Number.POSITIVE_INFINITY, + x: null, y: null + }; + var snapped = false; + var result, target; + for(var i=0, len=this.targets.length; i} The location of the mouse in map + * coords. + * + * Returns: + * {Object} A result object with rank, dist, x, and y properties. + * Returns null if candidate is not eligible for snapping. + */ + testTarget: function(target, loc) { + var tolerance = { + node: this.getGeoTolerance(target.nodeTolerance), + vertex: this.getGeoTolerance(target.vertexTolerance), + edge: this.getGeoTolerance(target.edgeTolerance) + }; + // this could be cached if we don't support setting tolerance values directly + var maxTolerance = Math.max( + tolerance.node, tolerance.vertex, tolerance.edge + ); + var result = { + rank: Number.POSITIVE_INFINITY, dist: Number.POSITIVE_INFINITY + }; + var eligible = false; + var features = target.layer.features; + var feature, type, vertices, vertex, closest, dist, found; + var numTypes = this.precedence.length; + var ll = new OpenLayers.LonLat(loc.x, loc.y); + for(var i=0, len=features.length; i when the map resolution + * has not changed. + * + * Parameters: + * tolerance - {Number} A tolerance value in pixels. + * + * Returns: + * {Number} A tolerance value in map units. + */ + getGeoTolerance: function(tolerance) { + var resolution = this.layer.map.getResolution(); + if(resolution !== this.resolution) { + this.resolution = resolution; + this.geoToleranceCache = {}; + } + var geoTolerance = this.geoToleranceCache[tolerance]; + if(geoTolerance === undefined) { + geoTolerance = tolerance * resolution; + this.geoToleranceCache[tolerance] = geoTolerance; + } + return geoTolerance; + }, + + /** + * Method: destroy + * Clean up the control. + */ + destroy: function() { + if(this.active) { + this.deactivate(); // TODO: this should be handled by the super + } + delete this.layer; + delete this.targets; + OpenLayers.Control.prototype.destroy.call(this); + }, + + CLASS_NAME: "OpenLayers.Control.Snapping" +}); + --- /dev/null +++ b/openlayers/lib/OpenLayers/Control/Split.js @@ -1,1 +1,498 @@ - +/* Copyright (c) 2006-2008 MetaCarta, Inc., published under the Clear BSD + * license. See http://svn.openlayers.org/trunk/openlayers/license.txt for the + * full text of the license. */ + +/** + * @requires OpenLayers/Control.js + * @requires OpenLayers/Handler/Path.js + * @requires OpenLayers/Layer/Vector.js + */ + +/** + * Class: OpenLayers.Control.Split + * Acts as a split feature agent while editing vector features. + * + * Inherits from: + * - + */ +OpenLayers.Control.Split = OpenLayers.Class(OpenLayers.Control, { + + /** + * Constant: EVENT_TYPES + * {Array(String)} Supported application event types. Register a listener + * for a particular event with the following syntax: + * (code) + * control.events.register(type, obj, listener); + * (end) + * + * Listeners will be called with a reference to an event object. The + * properties of this event depends on exactly what happened. + * + * Supported control event types (in addition to those from ): + * beforesplit - Triggered before a split occurs. Listeners receive an + * event object with *source* and *target* properties. + * split - Triggered when a split occurs. Listeners receive an event with + * an *original* property and a *features* property. The original + * is a reference to the target feature that the sketch or modified + * feature intersects. The features property is a list of all features + * that result from this single split. This event is triggered before + * the resulting features are added to the layer (while the layer still + * has a reference to the original). + * aftersplit - Triggered after all splits resulting from a single sketch + * or feature modification have occurred. The original features + * have been destroyed and features that result from the split + * have already been added to the layer. Listeners receive an event + * with a *source* and *features* property. The source references the + * sketch or modified feature used as a splitter. The features + * property is a list of all resulting features. + */ + EVENT_TYPES: ["beforesplit", "split", "aftersplit"], + + /** + * APIProperty: layer + * {} The target layer with features to be split. + * Set at construction or after construction with . + */ + layer: null, + + /** + * Property: source + * {} Optional source layer. Any newly created + * or modified features from this layer will be used to split features + * on the target layer. If not provided, a temporary sketch layer will + * be created. + */ + source: null, + + /** + * Property: sourceOptions + * {Options} If a temporary sketch layer is created, these layer options + * will be applied. + */ + sourceOptions: null, + + /** + * APIProperty: tolerance + * {Number} Distance between the calculated intersection and a vertex on + * the source geometry below which the existing vertex will be used + * for the split. Default is null. + */ + tolerance: null, + + /** + * APIProperty: edge + * {Boolean} Allow splits given intersection of edges only. Default is + * true. If false, a vertex on the source must be within the + * distance of the calculated intersection for a split + * to occur. + */ + edge: true, + + /** + * APIProperty: deferDelete + * {Boolean} Instead of removing features from the layer, set feature + * states of split features to DELETE. This assumes a save strategy + * or other component is in charge of removing features from the + * layer. Default is false. If false, split features will be + * immediately deleted from the layer. + */ + deferDelete: false, + + /** + * APIProperty: mutual + * {Boolean} If source and target layers are the same, split source + * features and target features where they intersect. Default is + * true. If false, only target features will be split. + */ + mutual: true, + + /** + * APIProperty: targetFilter + * {OpenLayers.Filter} Optional filter that will be evaluated + * to determine if a feature from the target layer is eligible for + * splitting. + */ + targetFilter: null, + + /** + * APIProperty: sourceFilter + * {OpenLayers.Filter} Optional filter that will be evaluated + * to determine if a feature from the target layer is eligible for + * splitting. + */ + sourceFilter: null, + + /** + * Property: handler + * {} The temporary sketch handler created if + * no source layer is provided. + */ + handler: null, + + /** + * Constructor: OpenLayers.Control.Split + * Creates a new split control. A control is constructed with a target + * layer and an optional source layer. While the control is active, + * creating new features or modifying existing features on the source + * layer will result in splitting any eligible features on the target + * layer. If no source layer is provided, a temporary sketch layer will + * be created to create lines for splitting features on the target. + * + * Parameters: + * options - {Object} An object containing all configuration properties for + * the control. + * + * Valid options: + * layer - {OpenLayers.Layer.Vector} The target layer. Features from this + * layer will be split by new or modified features on the source layer + * or temporary sketch layer. + * source - {OpenLayers.Layer.Vector} Optional source layer. If provided + * newly created features or modified features will be used to split + * features on the target layer. If not provided, a temporary sketch + * layer will be created for drawing lines. + * tolerance - {Number} Optional value for the distance between a source + * vertex and the calculated intersection below which the split will + * occur at the vertex. + * edge - {Boolean} Allow splits given intersection of edges only. Default + * is true. If false, a vertex on the source must be within the + * distance of the calculated intersection for a split + * to occur. + * mutual - {Boolean} If source and target are the same, split source + * features and target features where they intersect. Default is + * true. If false, only target features will be split. + * targetFilter - {OpenLayers.Filter} Optional filter that will be evaluated + * to determine if a feature from the target layer is eligible for + * splitting. + * sourceFilter - {OpenLayers.Filter} Optional filter that will be evaluated + * to determine if a feature from the target layer is eligible for + * splitting. + */ + initialize: function(options) { + // concatenate events specific to measure with those from the base + Array.prototype.push.apply( + this.EVENT_TYPES, OpenLayers.Control.prototype.EVENT_TYPES + ); + OpenLayers.Control.prototype.initialize.apply(this, [options]); + this.options = options || {}; // TODO: this could be done by the super + + // set the source layer if provided + if(this.options.source) { + this.setSource(this.options.source); + } + }, + + /** + * APIMethod: setSource + * Set the source layer for edits layer. + * + * Parameters: + * layer - {OpenLayers.Layer.Vector} The new source layer layer. If + * null, a temporary sketch layer will be created. + */ + setSource: function(layer) { + if(this.active) { + this.deactivate(); + if(this.handler) { + this.handler.destroy(); + delete this.handler; + } + this.source = layer; + this.activate(); + } else { + this.source = layer; + } + }, + + /** + * APIMethod: activate + * Activate the control. Activating the control registers listeners for + * editing related events so that during feature creation and + * modification, features in the target will be considered for + * splitting. + */ + activate: function() { + var activated = OpenLayers.Control.prototype.activate.call(this); + if(activated) { + if(!this.source) { + if(!this.handler) { + this.handler = new OpenLayers.Handler.Path(this, + {done: function(geometry) { + this.onSketchComplete({ + feature: new OpenLayers.Feature.Vector(geometry) + }); + }}, + {layerOptions: this.sourceOptions} + ); + } + this.handler.activate(); + } else if(this.source.events) { + this.source.events.on({ + sketchcomplete: this.onSketchComplete, + afterfeaturemodified: this.afterFeatureModified, + scope: this + }); + } + } + return activated; + }, + + /** + * APIMethod: deactivate + * Deactivate the control. Deactivating the control unregisters listeners + * so feature editing may proceed without engaging the split agent. + */ + deactivate: function() { + var deactivated = OpenLayers.Control.prototype.deactivate.call(this); + if(deactivated) { + if(this.source && this.source.events) { + this.layer.events.un({ + sketchcomplete: this.onSketchComplete, + afterfeaturemodified: this.afterFeatureModified, + scope: this + }); + } + } + return deactivated; + }, + + /** + * Method: onSketchComplete + * Registered as a listener for the sketchcomplete event on the editable + * layer. + * + * Parameters: + * event - {Object} The sketch complete event. + * + * Returns: + * {Boolean} Stop the sketch from being added to the layer (it has been + * split). + */ + onSketchComplete: function(event) { + this.feature = null; + return !this.considerSplit(event.feature); + }, + + /** + * Method: afterFeatureModified + * Registered as a listener for the afterfeaturemodified event on the + * editable layer. + * + * Parameters: + * event - {Object} The after feature modified event. + */ + afterFeatureModified: function(event) { + if(event.modified) { + var feature = event.feature; + if(feature.geometry instanceof OpenLayers.Geometry.LineString || + feature.geometry instanceof OpenLayers.Geometry.MultiLineString) { + this.feature = event.feature; + this.considerSplit(event.feature); + } + } + }, + + /** + * Method: removeByGeometry + * Remove a feature from a list based on the given geometry. + * + * Parameters: + * features - {Array(} A list of features. + * geometry - {} A geometry. + */ + removeByGeometry: function(features, geometry) { + for(var i=0, len=features.length; i} The target feature. + * + * Returns: + * {Boolean} The target is eligible for splitting. + */ + isEligible: function(target) { + return ( + target.state !== OpenLayers.State.DELETE + ) && ( + target.geometry instanceof OpenLayers.Geometry.LineString || + target.geometry instanceof OpenLayers.Geometry.MultiLineString + ) && ( + this.feature !== target + ) && ( + !this.targetFilter || + this.targetFilter.evaluate(target.attributes) + ); + }, + + /** + * Method: considerSplit + * Decide whether or not to split target features with the supplied + * feature. If is true, both the source and target features + * will be split if eligible. + * + * Parameters: + * feature - { 1) { + // splice in new source parts + parts.unshift(j, 1); // add args for splice below + Array.prototype.splice.apply(sourceParts, parts); + j += parts.length - 3; + } + results = results[1]; + } + // handle parts that result from target splitting + if(results.length > 1) { + // splice in new target parts + results.unshift(k, 1); // add args for splice below + Array.prototype.splice.apply(targetParts, results); + k += results.length - 3; + } + } + } + } + } + } + if(targetParts && targetParts.length > 1) { + this.geomsToFeatures(targetFeature, targetParts); + this.events.triggerEvent("split", { + original: targetFeature, + features: targetParts + }); + Array.prototype.push.apply(additions, targetParts); + removals.push(targetFeature); + targetSplit = true; + } + } + } + if(sourceParts && sourceParts.length > 1) { + this.geomsToFeatures(feature, sourceParts); + this.events.triggerEvent("split", { + original: feature, + features: sourceParts + }); + Array.prototype.push.apply(additions, sourceParts); + removals.push(feature); + sourceSplit = true; + } + if(sourceSplit || targetSplit) { + // remove and add feature events are suppressed + // listen for split event on this control instead + if(this.deferDelete) { + // Set state instead of removing. Take care to avoid + // setting delete for features that have not yet been + // inserted - those should be destroyed immediately. + var feat, destroys = []; + for(var i=0, len=removals.length; i} The feature to be cloned. + * geoms - {Array()} List of goemetries. This will + * become a list of new features. + */ + geomsToFeatures: function(feature, geoms) { + var clone = feature.clone(); + delete clone.geometry; + var newFeature; + for(var i=0, len=geoms.length; i + */ +OpenLayers.Control.WMSGetFeatureInfo = OpenLayers.Class(OpenLayers.Control, { + + /** + * APIProperty: hover + * {Boolean} Send GetFeatureInfo requests when mouse stops moving. + * Default is false. + */ + hover: false, + + /** + * APIProperty: maxFeatures + * {Integer} Maximum number of features to return from a WMS query. This + * sets the feature_count parameter on WMS GetFeatureInfo + * requests. + */ + maxFeatures: 10, + + /** + * Property: layers + * {Array()} The layers to query for feature info. + * If omitted, all map WMS layers with a url that matches this or + * will be considered. + */ + layers: null, + + /** + * Property: queryVisible + * {Boolean} If true, filter out hidden layers when searching the map for + * layers to query. Default is false. + */ + queryVisible: false, + + /** + * Property: url + * {String} The URL of the WMS service to use. If not provided, the url + * of the first eligible layer will be used. + */ + url: null, + + /** + * Property: layerUrls + * {Array(String)} Optional list of urls for layers that should be queried. + * This can be used when the layer url differs from the url used for + * making GetFeatureInfo requests (in the case of a layer using cached + * tiles). + */ + layerUrls: null, + + /** + * Property: infoFormat + * {String} The mimetype to request from the server + */ + infoFormat: 'text/html', + + /** + * Property: vendorParams + * {Object} Additional parameters that will be added to the request, for + * WMS implementations that support them. This could e.g. look like + * (start code) + * { + * radius: 5 + * } + * (end) + */ + vendorParams: {}, + + /** + * Property: format + * {} A format for parsing GetFeatureInfo responses. + * Default is . + */ + format: null, + + /** + * Property: formatOptions + * {Object} Optional properties to set on the format (if one is not provided + * in the property. + */ + formatOptions: null, + + /** + * APIProperty: handlerOptions + * {Object} Additional options for the handlers used by this control, e.g. + * (start code) + * { + * "click": {delay: 100}, + * "hover": {delay: 300} + * } + * (end) + */ + handlerOptions: null, + + /** + * Property: handler + * {Object} Reference to the for this control + */ + handler: null, + + /** + * Property: hoverRequest + * {} contains the currently running hover request + * (if any). + */ + hoverRequest: null, + + /** + * Constant: EVENT_TYPES + * + * Supported event types (in addition to those from ): + * getfeatureinfo - Triggered when a GetFeatureInfo response is received. + * The event object has a *text* property with the body of the + * response (String), a *features* property with an array of the + * parsed features, an *xy* property with the position of the mouse + * click or hover event that triggered the request, and a *request* + * property with the request itself. + */ + EVENT_TYPES: ["getfeatureinfo"], + + /** + * Constructor: + * + * Parameters: + * options - {Object} + */ + initialize: function(options) { + // concatenate events specific to vector with those from the base + this.EVENT_TYPES = + OpenLayers.Control.WMSGetFeatureInfo.prototype.EVENT_TYPES.concat( + OpenLayers.Control.prototype.EVENT_TYPES + ); + + options = options || {}; + options.handlerOptions = options.handlerOptions || {}; + + OpenLayers.Control.prototype.initialize.apply(this, [options]); + + if(!this.format) { + this.format = new OpenLayers.Format.WMSGetFeatureInfo( + options.formatOptions + ); + } + + if (this.hover) { + this.handler = new OpenLayers.Handler.Hover( + this, { + 'move': this.cancelHover, + 'pause': this.getInfoForHover + }, + OpenLayers.Util.extend(this.handlerOptions.hover || {}, { + 'delay': 250 + })); + } else { + this.handler = new OpenLayers.Handler.Click(this, + {click: this.getInfoForClick}, this.handlerOptions.click || {}); + } + }, + + /** + * Method: activate + * Activates the control. + * + * Returns: + * {Boolean} The control was effectively activated. + */ + activate: function () { + if (!this.active) { + this.handler.activate(); + } + return OpenLayers.Control.prototype.activate.apply( + this, arguments + ); + }, + + /** + * Method: deactivate + * Deactivates the control. + * + * Returns: + * {Boolean} The control was effectively deactivated. + */ + deactivate: function () { + return OpenLayers.Control.prototype.deactivate.apply( + this, arguments + ); + }, + + /** + * Method: getInfoForClick + * Called on click + * + * Parameters: + * evt - {} + */ + getInfoForClick: function(evt) { + // Set the cursor to "wait" to tell the user we're working on their + // click. + OpenLayers.Element.addClass(this.map.viewPortDiv, "olCursorWait"); + this.request(evt.xy, {}); + }, + + /** + * Method: getInfoForHover + * Pause callback for the hover handler + * + * Parameters: + * evt - {Object} + */ + getInfoForHover: function(evt) { + this.request(evt.xy, {hover: true}); + }, + + /** + * Method: cancelHover + * Cancel callback for the hover handler + */ + cancelHover: function() { + if (this.hoverRequest) { + this.hoverRequest.abort(); + this.hoverRequest = null; + } + }, + + /** + * Method: findLayers + * Internal method to get the layers, independent of whether we are + * inspecting the map or using a client-provided array + */ + findLayers: function() { + + var layers = []; + + var candidates = this.layers || this.map.layers; + var layer, url; + for(var i=0, len=candidates.length; i or one + * of the . + * + * Parameters: + * url - {String} The url to test. + * + * Returns: + * {Boolean} The provided url matches the control or one of the + * . + */ + urlMatches: function(url) { + var matches = OpenLayers.Util.isEquivalentUrl(this.url, url); + if(!matches && this.layerUrls) { + for(var i=0, len=this.layerUrls.length; i} The position on the map where the + * mouse event occurred. + * options - {Object} additional options for this method. + * + * Valid options: + * - *hover* {Boolean} true if we do the request for the hover handler + */ + request: function(clickPosition, options) { + options = options || {}; + var layerNames = []; + var styleNames = []; + + var layers = this.findLayers(); + if(layers.length > 0) { + + for (var i = 0, len = layers.length; i < len; i++) { + layerNames = layerNames.concat(layers[i].params.LAYERS); + // in the event of a WMS layer bundling multiple layers but not + // specifying styles,we need the same number of commas to specify + // the default style for each of the layers. We can't just leave it + // blank as we may be including other layers that do specify styles. + if (layers[i].params.STYLES) { + styleNames = styleNames.concat(layers[i].params.STYLES); + } else { + if (layers[i].params.LAYERS instanceof Array) { + styleNames = styleNames.concat(new Array(layers[i].params.LAYERS.length)); + } else { // Assume it's a String + styleNames = styleNames.concat(layers[i].params.LAYERS.replace(/[^,]/g, "")); + } + } + } + + var wmsOptions = { + url: this.url, + params: OpenLayers.Util.applyDefaults({ + service: "WMS", + version: "1.1.0", + request: "GetFeatureInfo", + layers: layerNames, + query_layers: layerNames, + styles: styleNames, + bbox: this.map.getExtent().toBBOX(), + srs: this.map.getProjection(), + feature_count: this.maxFeatures, + x: clickPosition.x, + y: clickPosition.y, + height: this.map.getSize().h, + width: this.map.getSize().w, + info_format: this.infoFormat + }, this.vendorParams), + callback: function(request) { + this.handleResponse(clickPosition, request); + }, + scope: this + }; + + var response = OpenLayers.Request.GET(wmsOptions); + + if (options.hover === true) { + this.hoverRequest = response.priv; + } + } else { + // Reset the cursor. + OpenLayers.Element.removeClass(this.map.viewPortDiv, "olCursorWait"); + } + }, + + /** + * Method: handleResponse + * Handler for the GetFeatureInfo response. + * + * Parameters: + * xy - {} The position on the map where the + * mouse event occurred. + * request - {XMLHttpRequest} The request object. + */ + handleResponse: function(xy, request) { + + var doc = request.responseXML; + if(!doc || !doc.documentElement) { + doc = request.responseText; + } + var features = this.format.read(doc); + + this.events.triggerEvent("getfeatureinfo", { + text: request.responseText, + features: features, + request: request, + xy: xy + }); + + // Reset the cursor. + OpenLayers.Element.removeClass(this.map.viewPortDiv, "olCursorWait"); + }, + + /** + * Method: setMap + * Set the map property for the control. + * + * Parameters: + * map - {} + */ + setMap: function(map) { + this.handler.setMap(map); + OpenLayers.Control.prototype.setMap.apply(this, arguments); + }, + + CLASS_NAME: "OpenLayers.Control.WMSGetFeatureInfo" +}); + --- /dev/null +++ b/openlayers/lib/OpenLayers/Control/ZoomBox.js @@ -1,1 +1,94 @@ +/* Copyright (c) 2006-2008 MetaCarta, Inc., published under the Clear BSD + * license. See http://svn.openlayers.org/trunk/openlayers/license.txt for the + * full text of the license. */ +/** + * @requires OpenLayers/Control.js + * @requires OpenLayers/Handler/Box.js + */ + +/** + * Class: OpenLayers.Control.ZoomBox + * The ZoomBox control enables zooming directly to a given extent, by drawing + * a box on the map. The box is drawn by holding down shift, whilst dragging + * the mouse. + * + * Inherits from: + * - + */ +OpenLayers.Control.ZoomBox = OpenLayers.Class(OpenLayers.Control, { + /** + * Property: type + * {OpenLayers.Control.TYPE} + */ + type: OpenLayers.Control.TYPE_TOOL, + + /** + * Property: out + * {Boolean} Should the control be used for zooming out? + */ + out: false, + + /** + * Property: alwaysZoom + * {Boolean} Always zoom in/out, when box drawed + */ + alwaysZoom: false, + + /** + * Method: draw + */ + draw: function() { + this.handler = new OpenLayers.Handler.Box( this, + {done: this.zoomBox}, {keyMask: this.keyMask} ); + }, + + /** + * Method: zoomBox + * + * Parameters: + * position - {} or {} + */ + zoomBox: function (position) { + if (position instanceof OpenLayers.Bounds) { + if (!this.out) { + var minXY = this.map.getLonLatFromPixel( + new OpenLayers.Pixel(position.left, position.bottom)); + var maxXY = this.map.getLonLatFromPixel( + new OpenLayers.Pixel(position.right, position.top)); + var bounds = new OpenLayers.Bounds(minXY.lon, minXY.lat, + maxXY.lon, maxXY.lat); + } else { + var pixWidth = Math.abs(position.right-position.left); + var pixHeight = Math.abs(position.top-position.bottom); + var zoomFactor = Math.min((this.map.size.h / pixHeight), + (this.map.size.w / pixWidth)); + var extent = this.map.getExtent(); + var center = this.map.getLonLatFromPixel( + position.getCenterPixel()); + var xmin = center.lon - (extent.getWidth()/2)*zoomFactor; + var xmax = center.lon + (extent.getWidth()/2)*zoomFactor; + var ymin = center.lat - (extent.getHeight()/2)*zoomFactor; + var ymax = center.lat + (extent.getHeight()/2)*zoomFactor; + var bounds = new OpenLayers.Bounds(xmin, ymin, xmax, ymax); + } + // always zoom in/out + var lastZoom = this.map.getZoom(); + this.map.zoomToExtent(bounds); + if (lastZoom == this.map.getZoom() && this.alwaysZoom == true){ + this.map.zoomTo(lastZoom + (this.out ? -1 : 1)); + } + } else { // it's a pixel + if (!this.out) { + this.map.setCenter(this.map.getLonLatFromPixel(position), + this.map.getZoom() + 1); + } else { + this.map.setCenter(this.map.getLonLatFromPixel(position), + this.map.getZoom() - 1); + } + } + }, + + CLASS_NAME: "OpenLayers.Control.ZoomBox" +}); + --- /dev/null +++ b/openlayers/lib/OpenLayers/Control/ZoomIn.js @@ -1,1 +1,35 @@ +/* Copyright (c) 2006-2008 MetaCarta, Inc., published under the Clear BSD + * license. See http://svn.openlayers.org/trunk/openlayers/license.txt for the + * full text of the license. */ +/** + * @requires OpenLayers/Control.js + */ + +/** + * Class: OpenLayers.Control.ZoomIn + * The ZoomIn control is a button to increase the zoom level of a map. + * + * Inherits from: + * - + */ +OpenLayers.Control.ZoomIn = OpenLayers.Class(OpenLayers.Control, { + + /** + * Property: type + * {String} The type of -- When added to a + * , 'type' is used by the panel to determine how to + * handle our events. + */ + type: OpenLayers.Control.TYPE_BUTTON, + + /** + * Method: trigger + */ + trigger: function(){ + this.map.zoomIn(); + }, + + CLASS_NAME: "OpenLayers.Control.ZoomIn" +}); + --- /dev/null +++ b/openlayers/lib/OpenLayers/Control/ZoomOut.js @@ -1,1 +1,35 @@ +/* Copyright (c) 2006-2008 MetaCarta, Inc., published under the Clear BSD + * license. See http://svn.openlayers.org/trunk/openlayers/license.txt for the + * full text of the license. */ +/** + * @requires OpenLayers/Control.js + */ + +/** + * Class: OpenLayers.Control.ZoomOut + * The ZoomOut control is a button to decrease the zoom level of a map. + * + * Inherits from: + * - + */ +OpenLayers.Control.ZoomOut = OpenLayers.Class(OpenLayers.Control, { + + /** + * Property: type + * {String} The type of -- When added to a + * , 'type' is used by the panel to determine how to + * handle our events. + */ + type: OpenLayers.Control.TYPE_BUTTON, + + /** + * Method: trigger + */ + trigger: function(){ + this.map.zoomOut(); + }, + + CLASS_NAME: "OpenLayers.Control.ZoomOut" +}); + --- /dev/null +++ b/openlayers/lib/OpenLayers/Control/ZoomPanel.js @@ -1,1 +1,54 @@ +/* Copyright (c) 2006-2008 MetaCarta, Inc., published under the Clear BSD + * license. See http://svn.openlayers.org/trunk/openlayers/license.txt for the + * full text of the license. */ +/** + * @requires OpenLayers/Control/Panel.js + * @requires OpenLayers/Control/ZoomIn.js + * @requires OpenLayers/Control/ZoomOut.js + * @requires OpenLayers/Control/ZoomToMaxExtent.js + */ + +/** + * Class: OpenLayers.Control.ZoomPanel + * The ZoomPanel control is a compact collecton of 3 zoom controls: a + * , a , and a + * . By default it is drawn in the upper left + * corner of the map. + * + * Note: + * If you wish to use this class with the default images and you want + * it to look nice in ie6, you should add the following, conditionally + * added css stylesheet to your HTML file: + * + * (code) + * + * (end) + * + * Inherits from: + * - + */ +OpenLayers.Control.ZoomPanel = OpenLayers.Class(OpenLayers.Control.Panel, { + + /** + * Constructor: OpenLayers.Control.ZoomPanel + * Add the three zooming controls. + * + * Parameters: + * options - {Object} An optional object whose properties will be used + * to extend the control. + */ + initialize: function(options) { + OpenLayers.Control.Panel.prototype.initialize.apply(this, [options]); + this.addControls([ + new OpenLayers.Control.ZoomIn(), + new OpenLayers.Control.ZoomToMaxExtent(), + new OpenLayers.Control.ZoomOut() + ]); + }, + + CLASS_NAME: "OpenLayers.Control.ZoomPanel" +}); + --- /dev/null +++ b/openlayers/lib/OpenLayers/Control/ZoomToMaxExtent.js @@ -1,1 +1,40 @@ +/* Copyright (c) 2006-2008 MetaCarta, Inc., published under the Clear BSD + * license. See http://svn.openlayers.org/trunk/openlayers/license.txt for the + * full text of the license. */ +/** + * @requires OpenLayers/Control.js + */ + +/** + * Class: OpenLayers.Control.ZoomToMaxExtent + * The ZoomToMaxExtent control is a button that zooms out to the maximum + * extent of the map. It is designed to be used with a + * . + * + * Inherits from: + * - + */ +OpenLayers.Control.ZoomToMaxExtent = OpenLayers.Class(OpenLayers.Control, { + + /** + * Property: type + * {String} The type of -- When added to a + * , 'type' is used by the panel to determine how to + * handle our events. + */ + type: OpenLayers.Control.TYPE_BUTTON, + + /* + * Method: trigger + * Do the zoom. + */ + trigger: function() { + if (this.map) { + this.map.zoomToMaxExtent(); + } + }, + + CLASS_NAME: "OpenLayers.Control.ZoomToMaxExtent" +}); + --- /dev/null +++ b/openlayers/lib/OpenLayers/Events.js @@ -1,1 +1,829 @@ - +/* Copyright (c) 2006-2008 MetaCarta, Inc., published under the Clear BSD + * license. See http://svn.openlayers.org/trunk/openlayers/license.txt for the + * full text of the license. */ + + +/** + * @requires OpenLayers/Util.js + */ + +/** + * Namespace: OpenLayers.Event + * Utility functions for event handling. + */ +OpenLayers.Event = { + + /** + * Property: observers + * {Object} A hashtable cache of the event observers. Keyed by + * element._eventCacheID + */ + observers: false, + + /** + * Constant: KEY_BACKSPACE + * {int} + */ + KEY_BACKSPACE: 8, + + /** + * Constant: KEY_TAB + * {int} + */ + KEY_TAB: 9, + + /** + * Constant: KEY_RETURN + * {int} + */ + KEY_RETURN: 13, + + /** + * Constant: KEY_ESC + * {int} + */ + KEY_ESC: 27, + + /** + * Constant: KEY_LEFT + * {int} + */ + KEY_LEFT: 37, + + /** + * Constant: KEY_UP + * {int} + */ + KEY_UP: 38, + + /** + * Constant: KEY_RIGHT + * {int} + */ + KEY_RIGHT: 39, + + /** + * Constant: KEY_DOWN + * {int} + */ + KEY_DOWN: 40, + + /** + * Constant: KEY_DELETE + * {int} + */ + KEY_DELETE: 46, + + + /** + * Method: element + * Cross browser event element detection. + * + * Parameters: + * event - {Event} + * + * Returns: + * {DOMElement} The element that caused the event + */ + element: function(event) { + return event.target || event.srcElement; + }, + + /** + * Method: isLeftClick + * Determine whether event was caused by a left click. + * + * Parameters: + * event - {Event} + * + * Returns: + * {Boolean} + */ + isLeftClick: function(event) { + return (((event.which) && (event.which == 1)) || + ((event.button) && (event.button == 1))); + }, + + /** + * Method: isRightClick + * Determine whether event was caused by a right mouse click. + * + * Parameters: + * event - {Event} + * + * Returns: + * {Boolean} + */ + isRightClick: function(event) { + return (((event.which) && (event.which == 3)) || + ((event.button) && (event.button == 2))); + }, + + /** + * Method: stop + * Stops an event from propagating. + * + * Parameters: + * event - {Event} + * allowDefault - {Boolean} If true, we stop the event chain but + * still allow the default browser + * behaviour (text selection, radio-button + * clicking, etc) + * Default false + */ + stop: function(event, allowDefault) { + + if (!allowDefault) { + if (event.preventDefault) { + event.preventDefault(); + } else { + event.returnValue = false; + } + } + + if (event.stopPropagation) { + event.stopPropagation(); + } else { + event.cancelBubble = true; + } + }, + + /** + * Method: findElement + * + * Parameters: + * event - {Event} + * tagName - {String} + * + * Returns: + * {DOMElement} The first node with the given tagName, starting from the + * node the event was triggered on and traversing the DOM upwards + */ + findElement: function(event, tagName) { + var element = OpenLayers.Event.element(event); + while (element.parentNode && (!element.tagName || + (element.tagName.toUpperCase() != tagName.toUpperCase()))){ + element = element.parentNode; + } + return element; + }, + + /** + * Method: observe + * + * Parameters: + * elementParam - {DOMElement || String} + * name - {String} + * observer - {function} + * useCapture - {Boolean} + */ + observe: function(elementParam, name, observer, useCapture) { + var element = OpenLayers.Util.getElement(elementParam); + useCapture = useCapture || false; + + if (name == 'keypress' && + (navigator.appVersion.match(/Konqueror|Safari|KHTML/) + || element.attachEvent)) { + name = 'keydown'; + } + + //if observers cache has not yet been created, create it + if (!this.observers) { + this.observers = {}; + } + + //if not already assigned, make a new unique cache ID + if (!element._eventCacheID) { + var idPrefix = "eventCacheID_"; + if (element.id) { + idPrefix = element.id + "_" + idPrefix; + } + element._eventCacheID = OpenLayers.Util.createUniqueID(idPrefix); + } + + var cacheID = element._eventCacheID; + + //if there is not yet a hash entry for this element, add one + if (!this.observers[cacheID]) { + this.observers[cacheID] = []; + } + + //add a new observer to this element's list + this.observers[cacheID].push({ + 'element': element, + 'name': name, + 'observer': observer, + 'useCapture': useCapture + }); + + //add the actual browser event listener + if (element.addEventListener) { + element.addEventListener(name, observer, useCapture); + } else if (element.attachEvent) { + element.attachEvent('on' + name, observer); + } + }, + + /** + * Method: stopObservingElement + * Given the id of an element to stop observing, cycle through the + * element's cached observers, calling stopObserving on each one, + * skipping those entries which can no longer be removed. + * + * parameters: + * elementParam - {DOMElement || String} + */ + stopObservingElement: function(elementParam) { + var element = OpenLayers.Util.getElement(elementParam); + var cacheID = element._eventCacheID; + + this._removeElementObservers(OpenLayers.Event.observers[cacheID]); + }, + + /** + * Method: _removeElementObservers + * + * Parameters: + * elementObservers - {Array(Object)} Array of (element, name, + * observer, usecapture) objects, + * taken directly from hashtable + */ + _removeElementObservers: function(elementObservers) { + if (elementObservers) { + for(var i = elementObservers.length-1; i >= 0; i--) { + var entry = elementObservers[i]; + var args = new Array(entry.element, + entry.name, + entry.observer, + entry.useCapture); + var removed = OpenLayers.Event.stopObserving.apply(this, args); + } + } + }, + + /** + * Method: stopObserving + * + * Parameters: + * elementParam - {DOMElement || String} + * name - {String} + * observer - {function} + * useCapture - {Boolean} + * + * Returns: + * {Boolean} Whether or not the event observer was removed + */ + stopObserving: function(elementParam, name, observer, useCapture) { + useCapture = useCapture || false; + + var element = OpenLayers.Util.getElement(elementParam); + var cacheID = element._eventCacheID; + + if (name == 'keypress') { + if ( navigator.appVersion.match(/Konqueror|Safari|KHTML/) || + element.detachEvent) { + name = 'keydown'; + } + } + + // find element's entry in this.observers cache and remove it + var foundEntry = false; + var elementObservers = OpenLayers.Event.observers[cacheID]; + if (elementObservers) { + + // find the specific event type in the element's list + var i=0; + while(!foundEntry && i < elementObservers.length) { + var cacheEntry = elementObservers[i]; + + if ((cacheEntry.name == name) && + (cacheEntry.observer == observer) && + (cacheEntry.useCapture == useCapture)) { + + elementObservers.splice(i, 1); + if (elementObservers.length == 0) { + delete OpenLayers.Event.observers[cacheID]; + } + foundEntry = true; + break; + } + i++; + } + } + + //actually remove the event listener from browser + if (foundEntry) { + if (element.removeEventListener) { + element.removeEventListener(name, observer, useCapture); + } else if (element && element.detachEvent) { + element.detachEvent('on' + name, observer); + } + } + return foundEntry; + }, + + /** + * Method: unloadCache + * Cycle through all the element entries in the events cache and call + * stopObservingElement on each. + */ + unloadCache: function() { + // check for OpenLayers.Event before checking for observers, because + // OpenLayers.Event may be undefined in IE if no map instance was + // created + if (OpenLayers.Event && OpenLayers.Event.observers) { + for (var cacheID in OpenLayers.Event.observers) { + var elementObservers = OpenLayers.Event.observers[cacheID]; + OpenLayers.Event._removeElementObservers.apply(this, + [elementObservers]); + } + OpenLayers.Event.observers = false; + } + }, + + CLASS_NAME: "OpenLayers.Event" +}; + +/* prevent memory leaks in IE */ +OpenLayers.Event.observe(window, 'unload', OpenLayers.Event.unloadCache, false); + +// FIXME: Remove this in 3.0. In 3.0, Event.stop will no longer be provided +// by OpenLayers. +if (window.Event) { + OpenLayers.Util.applyDefaults(window.Event, OpenLayers.Event); +} else { + var Event = OpenLayers.Event; +} + +/** + * Class: OpenLayers.Events + */ +OpenLayers.Events = OpenLayers.Class({ + + /** + * Constant: BROWSER_EVENTS + * {Array(String)} supported events + */ + BROWSER_EVENTS: [ + "mouseover", "mouseout", + "mousedown", "mouseup", "mousemove", + "click", "dblclick", "rightclick", "dblrightclick", + "resize", "focus", "blur" + ], + + /** + * Property: listeners + * {Object} Hashtable of Array(Function): events listener functions + */ + listeners: null, + + /** + * Property: object + * {Object} the code object issuing application events + */ + object: null, + + /** + * Property: element + * {DOMElement} the DOM element receiving browser events + */ + element: null, + + /** + * Property: eventTypes + * {Array(String)} list of support application events + */ + eventTypes: null, + + /** + * Property: eventHandler + * {Function} bound event handler attached to elements + */ + eventHandler: null, + + /** + * APIProperty: fallThrough + * {Boolean} + */ + fallThrough: null, + + /** + * APIProperty: includeXY + * {Boolean} Should the .xy property automatically be created for browser + * mouse events? In general, this should be false. If it is true, then + * mouse events will automatically generate a '.xy' property on the + * event object that is passed. (Prior to OpenLayers 2.7, this was true + * by default.) Otherwise, you can call the getMousePosition on the + * relevant events handler on the object available via the 'evt.object' + * property of the evt object. So, for most events, you can call: + * function named(evt) { + * this.xy = this.object.events.getMousePosition(evt) + * } + * + * This option typically defaults to false for performance reasons: + * when creating an events object whose primary purpose is to manage + * relatively positioned mouse events within a div, it may make + * sense to set it to true. + * + * This option is also used to control whether the events object caches + * offsets. If this is false, it will not: the reason for this is that + * it is only expected to be called many times if the includeXY property + * is set to true. If you set this to true, you are expected to clear + * the offset cache manually (using this.clearMouseCache()) if: + * the border of the element changes + * the location of the element in the page changes + */ + includeXY: false, + + /** + * Method: clearMouseListener + * A version of that is bound to this instance so that + * it can be used with and + * . + */ + clearMouseListener: null, + + /** + * Constructor: OpenLayers.Events + * Construct an OpenLayers.Events object. + * + * Parameters: + * object - {Object} The js object to which this Events object is being + * added element - {DOMElement} A dom element to respond to browser events + * eventTypes - {Array(String)} Array of custom application events + * fallThrough - {Boolean} Allow events to fall through after these have + * been handled? + * options - {Object} Options for the events object. + */ + initialize: function (object, element, eventTypes, fallThrough, options) { + OpenLayers.Util.extend(this, options); + this.object = object; + this.fallThrough = fallThrough; + this.listeners = {}; + + // keep a bound copy of handleBrowserEvent() so that we can + // pass the same function to both Event.observe() and .stopObserving() + this.eventHandler = OpenLayers.Function.bindAsEventListener( + this.handleBrowserEvent, this + ); + + // to be used with observe and stopObserving + this.clearMouseListener = OpenLayers.Function.bind( + this.clearMouseCache, this + ); + + // if eventTypes is specified, create a listeners list for each + // custom application event. + this.eventTypes = []; + if (eventTypes != null) { + for (var i=0, len=eventTypes.length; i} The current xy coordinate of the mouse, adjusted + * for offsets + */ + getMousePosition: function (evt) { + if (!this.includeXY) { + this.clearMouseCache(); + } else if (!this.element.hasScrollEvent) { + OpenLayers.Event.observe(window, "scroll", this.clearMouseListener); + this.element.hasScrollEvent = true; + } + + if (!this.element.scrolls) { + this.element.scrolls = [ + (document.documentElement.scrollLeft + || document.body.scrollLeft), + (document.documentElement.scrollTop + || document.body.scrollTop) + ]; + } + + if (!this.element.lefttop) { + this.element.lefttop = [ + (document.documentElement.clientLeft || 0), + (document.documentElement.clientTop || 0) + ]; + } + + if (!this.element.offsets) { + this.element.offsets = OpenLayers.Util.pagePosition(this.element); + this.element.offsets[0] += this.element.scrolls[0]; + this.element.offsets[1] += this.element.scrolls[1]; + } + return new OpenLayers.Pixel( + (evt.clientX + this.element.scrolls[0]) - this.element.offsets[0] + - this.element.lefttop[0], + (evt.clientY + this.element.scrolls[1]) - this.element.offsets[1] + - this.element.lefttop[1] + ); + }, + + CLASS_NAME: "OpenLayers.Events" +}); + --- /dev/null +++ b/openlayers/lib/OpenLayers/Feature.js @@ -1,1 +1,223 @@ - +/* Copyright (c) 2006-2008 MetaCarta, Inc., published under the Clear BSD + * license. See http://svn.openlayers.org/trunk/openlayers/license.txt for the + * full text of the license. */ + + +/** + * @requires OpenLayers/Util.js + * @requires OpenLayers/Marker.js + * @requires OpenLayers/Popup/AnchoredBubble.js + */ + +/** + * Class: OpenLayers.Feature + * Features are combinations of geography and attributes. The OpenLayers.Feature + * class specifically combines a marker and a lonlat. + */ +OpenLayers.Feature = OpenLayers.Class({ + + /** + * Property: layer + * {} + */ + layer: null, + + /** + * Property: id + * {String} + */ + id: null, + + /** + * Property: lonlat + * {} + */ + lonlat: null, + + /** + * Property: data + * {Object} + */ + data: null, + + /** + * Property: marker + * {} + */ + marker: null, + + /** + * APIProperty: popupClass + * {} The class which will be used to instantiate + * a new Popup. Default is . + */ + popupClass: OpenLayers.Popup.AnchoredBubble, + + /** + * Property: popup + * {} + */ + popup: null, + + /** + * Constructor: OpenLayers.Feature + * Constructor for features. + * + * Parameters: + * layer - {} + * lonlat - {} + * data - {Object} + * + * Returns: + * {} + */ + initialize: function(layer, lonlat, data) { + this.layer = layer; + this.lonlat = lonlat; + this.data = (data != null) ? data : {}; + this.id = OpenLayers.Util.createUniqueID(this.CLASS_NAME + "_"); + }, + + /** + * Method: destroy + * nullify references to prevent circular references and memory leaks + */ + destroy: function() { + + //remove the popup from the map + if ((this.layer != null) && (this.layer.map != null)) { + if (this.popup != null) { + this.layer.map.removePopup(this.popup); + } + } + + this.layer = null; + this.id = null; + this.lonlat = null; + this.data = null; + if (this.marker != null) { + this.destroyMarker(this.marker); + this.marker = null; + } + if (this.popup != null) { + this.destroyPopup(this.popup); + this.popup = null; + } + }, + + /** + * Method: onScreen + * + * Returns: + * {Boolean} Whether or not the feature is currently visible on screen + * (based on its 'lonlat' property) + */ + onScreen:function() { + + var onScreen = false; + if ((this.layer != null) && (this.layer.map != null)) { + var screenBounds = this.layer.map.getExtent(); + onScreen = screenBounds.containsLonLat(this.lonlat); + } + return onScreen; + }, + + + /** + * Method: createMarker + * Based on the data associated with the Feature, create and return a marker object. + * + * Returns: + * {} A Marker Object created from the 'lonlat' and 'icon' properties + * set in this.data. If no 'lonlat' is set, returns null. If no + * 'icon' is set, OpenLayers.Marker() will load the default image. + * + * Note - this.marker is set to return value + * + */ + createMarker: function() { + + if (this.lonlat != null) { + this.marker = new OpenLayers.Marker(this.lonlat, this.data.icon); + } + return this.marker; + }, + + /** + * Method: destroyMarker + * Destroys marker. + * If user overrides the createMarker() function, s/he should be able + * to also specify an alternative function for destroying it + */ + destroyMarker: function() { + this.marker.destroy(); + }, + + /** + * Method: createPopup + * Creates a popup object created from the 'lonlat', 'popupSize', + * and 'popupContentHTML' properties set in this.data. It uses + * this.marker.icon as default anchor. + * + * If no 'lonlat' is set, returns null. + * If no this.marker has been created, no anchor is sent. + * + * Note - the returned popup object is 'owned' by the feature, so you + * cannot use the popup's destroy method to discard the popup. + * Instead, you must use the feature's destroyPopup + * + * Note - this.popup is set to return value + * + * Parameters: + * closeBox - {Boolean} create popup with closebox or not + * + * Returns: + * {} Returns the created popup, which is also set + * as 'popup' property of this feature. Will be of whatever type + * specified by this feature's 'popupClass' property, but must be + * of type . + * + */ + createPopup: function(closeBox) { + + if (this.lonlat != null) { + + var id = this.id + "_popup"; + var anchor = (this.marker) ? this.marker.icon : null; + + if (!this.popup) { + this.popup = new this.popupClass(id, + this.lonlat, + this.data.popupSize, + this.data.popupContentHTML, + anchor, + closeBox); + } + if (this.data.overflow != null) { + this.popup.contentDiv.style.overflow = this.data.overflow; + } + + this.popup.feature = this; + } + return this.popup; + }, + + + /** + * Method: destroyPopup + * Destroys the popup created via createPopup. + * + * As with the marker, if user overrides the createPopup() function, s/he + * should also be able to override the destruction + */ + destroyPopup: function() { + if (this.popup) { + this.popup.feature = null; + this.popup.destroy(); + this.popup = null; + } + }, + + CLASS_NAME: "OpenLayers.Feature" +}); + --- /dev/null +++ b/openlayers/lib/OpenLayers/Feature/Vector.js @@ -1,1 +1,420 @@ - +/* Copyright (c) 2006-2008 MetaCarta, Inc., published under the Clear BSD + * license. See http://svn.openlayers.org/trunk/openlayers/license.txt for the + * full text of the license. */ + +// TRASH THIS +OpenLayers.State = { + /** states */ + UNKNOWN: 'Unknown', + INSERT: 'Insert', + UPDATE: 'Update', + DELETE: 'Delete' +}; + +/** + * @requires OpenLayers/Feature.js + * @requires OpenLayers/Util.js + */ + +/** + * Class: OpenLayers.Feature.Vector + * Vector features use the OpenLayers.Geometry classes as geometry description. + * They have an 'attributes' property, which is the data object, and a 'style' + * property, the default values of which are defined in the + * objects. + * + * Inherits from: + * - + */ +OpenLayers.Feature.Vector = OpenLayers.Class(OpenLayers.Feature, { + + /** + * Property: fid + * {String} + */ + fid: null, + + /** + * APIProperty: geometry + * {} + */ + geometry: null, + + /** + * APIProperty: attributes + * {Object} This object holds arbitrary properties that describe the + * feature. + */ + attributes: null, + + /** + * Property: bounds + * {} The box bounding that feature's geometry, that + * property can be set by an object when + * deserializing the feature, so in most cases it represents an + * information set by the server. + */ + bounds: null, + + /** + * Property: state + * {String} + */ + state: null, + + /** + * APIProperty: style + * {Object} + */ + style: null, + + /** + * Property: renderIntent + * {String} rendering intent currently being used + */ + renderIntent: "default", + + /** + * Constructor: OpenLayers.Feature.Vector + * Create a vector feature. + * + * Parameters: + * geometry - {} The geometry that this feature + * represents. + * attributes - {Object} An optional object that will be mapped to the + * property. + * style - {Object} An optional style object. + */ + initialize: function(geometry, attributes, style) { + OpenLayers.Feature.prototype.initialize.apply(this, + [null, null, attributes]); + this.lonlat = null; + this.geometry = geometry ? geometry : null; + this.state = null; + this.attributes = {}; + if (attributes) { + this.attributes = OpenLayers.Util.extend(this.attributes, + attributes); + } + this.style = style ? style : null; + }, + + /** + * Method: destroy + * nullify references to prevent circular references and memory leaks + */ + destroy: function() { + if (this.layer) { + this.layer.removeFeatures(this); + this.layer = null; + } + + this.geometry = null; + OpenLayers.Feature.prototype.destroy.apply(this, arguments); + }, + + /** + * Method: clone + * Create a clone of this vector feature. Does not set any non-standard + * properties. + * + * Returns: + * {} An exact clone of this vector feature. + */ + clone: function () { + return new OpenLayers.Feature.Vector( + this.geometry ? this.geometry.clone() : null, + this.attributes, + this.style); + }, + + /** + * Method: onScreen + * Determine whether the feature is within the map viewport. This method + * tests for an intersection between the geometry and the viewport + * bounds. If a more effecient but less precise geometry bounds + * intersection is desired, call the method with the boundsOnly + * parameter true. + * + * Parameters: + * boundsOnly - {Boolean} Only test whether a feature's bounds intersects + * the viewport bounds. Default is false. If false, the feature's + * geometry must intersect the viewport for onScreen to return true. + * + * Returns: + * {Boolean} The feature is currently visible on screen (optionally + * based on its bounds if boundsOnly is true). + */ + onScreen:function(boundsOnly) { + var onScreen = false; + if(this.layer && this.layer.map) { + var screenBounds = this.layer.map.getExtent(); + if(boundsOnly) { + var featureBounds = this.geometry.getBounds(); + onScreen = screenBounds.intersectsBounds(featureBounds); + } else { + var screenPoly = screenBounds.toGeometry(); + onScreen = screenPoly.intersects(this.geometry); + } + } + return onScreen; + }, + + /** + * Method: createMarker + * HACK - we need to decide if all vector features should be able to + * create markers + * + * Returns: + * {} For now just returns null + */ + createMarker: function() { + return null; + }, + + /** + * Method: destroyMarker + * HACK - we need to decide if all vector features should be able to + * delete markers + * + * If user overrides the createMarker() function, s/he should be able + * to also specify an alternative function for destroying it + */ + destroyMarker: function() { + // pass + }, + + /** + * Method: createPopup + * HACK - we need to decide if all vector features should be able to + * create popups + * + * Returns: + * {} For now just returns null + */ + createPopup: function() { + return null; + }, + + /** + * Method: atPoint + * Determins whether the feature intersects with the specified location. + * + * Parameters: + * lonlat - {} + * toleranceLon - {float} Optional tolerance in Geometric Coords + * toleranceLat - {float} Optional tolerance in Geographic Coords + * + * Returns: + * {Boolean} Whether or not the feature is at the specified location + */ + atPoint: function(lonlat, toleranceLon, toleranceLat) { + var atPoint = false; + if(this.geometry) { + atPoint = this.geometry.atPoint(lonlat, toleranceLon, + toleranceLat); + } + return atPoint; + }, + + /** + * Method: destroyPopup + * HACK - we need to decide if all vector features should be able to + * delete popups + */ + destroyPopup: function() { + // pass + }, + + /** + * Method: move + * Moves the feature and redraws it at its new location + * + * Parameters: + * state - {OpenLayers.LonLat or OpenLayers.Pixel} the + * location to which to move the feature. + */ + move: function(location) { + + if(!this.layer || !this.geometry.move){ + //do nothing if no layer or immoveable geometry + return; + } + + var pixel; + if (location.CLASS_NAME == "OpenLayers.LonLat") { + pixel = this.layer.getViewPortPxFromLonLat(location); + } else { + pixel = location; + } + + var lastPixel = this.layer.getViewPortPxFromLonLat(this.geometry.getBounds().getCenterLonLat()); + var res = this.layer.map.getResolution(); + this.geometry.move(res * (pixel.x - lastPixel.x), + res * (lastPixel.y - pixel.y)); + this.layer.drawFeature(this); + return lastPixel; + }, + + /** + * Method: toState + * Sets the new state + * + * Parameters: + * state - {String} + */ + toState: function(state) { + if (state == OpenLayers.State.UPDATE) { + switch (this.state) { + case OpenLayers.State.UNKNOWN: + case OpenLayers.State.DELETE: + this.state = state; + break; + case OpenLayers.State.UPDATE: + case OpenLayers.State.INSERT: + break; + } + } else if (state == OpenLayers.State.INSERT) { + switch (this.state) { + case OpenLayers.State.UNKNOWN: + break; + default: + this.state = state; + break; + } + } else if (state == OpenLayers.State.DELETE) { + switch (this.state) { + case OpenLayers.State.INSERT: + // the feature should be destroyed + break; + case OpenLayers.State.DELETE: + break; + case OpenLayers.State.UNKNOWN: + case OpenLayers.State.UPDATE: + this.state = state; + break; + } + } else if (state == OpenLayers.State.UNKNOWN) { + this.state = state; + } + }, + + CLASS_NAME: "OpenLayers.Feature.Vector" +}); + + +/** + * Constant: OpenLayers.Feature.Vector.style + * OpenLayers features can have a number of style attributes. The 'default' + * style will typically be used if no other style is specified. These + * styles correspond for the most part, to the styling properties defined + * by the SVG standard. + * Information on fill properties: http://www.w3.org/TR/SVG/painting.html#FillProperties + * Information on stroke properties: http://www.w3.org/TR/SVG/painting.html#StrokeProperties + * + * Symbolizer properties: + * fill - {Boolean} Set to false if no fill is desired. + * fillColor - {String} Hex fill color. Default is "#ee9900". + * fillOpacity - {Number} Fill opacity (0-1). Default is 0.4 + * stroke - {Boolean} Set to false if no stroke is desired. + * strokeColor - {String} Hex stroke color. Default is "#ee9900". + * strokeOpacity - {Number} Stroke opacity (0-1). Default is 1. + * strokeWidth - {Number} Pixel stroke width. Default is 1. + * strokeLinecap - {String} Stroke cap type. Default is "round". [butt | round | square] + * strokeDashstyle - {String} Stroke dash style. Default is "solid". [dot | dash | dashdot | longdash | longdashdot | solid] + * graphic - {Boolean} Set to false if no graphic is desired. + * pointRadius - {Number} Pixel point radius. Default is 6. + * pointerEvents - {String} Default is "visiblePainted". + * cursor - {String} Default is "". + * externalGraphic - {String} Url to an external graphic that will be used for rendering points. + * graphicWidth - {Number} Pixel width for sizing an external graphic. + * graphicHeight - {Number} Pixel height for sizing an external graphic. + * graphicOpacity - {Number} Opacity (0-1) for an external graphic. + * graphicXOffset - {Number} Pixel offset along the positive x axis for displacing an external graphic. + * graphicYOffset - {Number} Pixel offset along the positive y axis for displacing an external graphic. + * graphicZIndex - {Number} The integer z-index value to use in rendering. + * graphicName - {String} Named graphic to use when rendering points. Supported values include "circle" (default), + * "square", "star", "x", "cross", "triangle". + * graphicTitle - {String} Tooltip for an external graphic. Only supported in Firefox and Internet Explorer. + * backgroundGraphic - {String} Url to a graphic to be used as the background under an externalGraphic. + * backgroundGraphicZIndex - {Number} The integer z-index value to use in rendering the background graphic. + * backgroundXOffset - {Number} The x offset (in pixels) for the background graphic. + * backgroundYOffset - {Number} The y offset (in pixels) for the background graphic. + * backgroundHeight - {Number} The height of the background graphic. If not provided, the graphicHeight will be used. + * backgroundWidth - {Number} The width of the background width. If not provided, the graphicWidth will be used. + * label - {String} The text for an optional label. For browsers that use the canvas renderer, this requires either + * fillText or mozDrawText to be available. + * labelAlign - {String} Label alignment. This specifies the insertion point relative to the text. It is a string + * composed of two characters. The first character is for the horizontal alignment, the second for the vertical + * alignment. Valid values for horizontal alignment: "l"=left, "c"=center, "r"=right. Valid values for vertical + * alignment: "t"=top, "m"=middle, "b"=bottom. Example values: "lt", "cm", "rb". The canvas renderer does not + * support vertical alignment, it will always use "b". + * fontColor - {String} The font color for the label, to be provided like CSS. + * fontFamily - {String} The font family for the label, to be provided like in CSS. + * fontSize - {String} The font size for the label, to be provided like in CSS. + * fontWeight - {String} The font weight for the label, to be provided like in CSS. + * display - {String} Symbolizers will have no effect if display is set to "none". All other values have no effect. + */ +OpenLayers.Feature.Vector.style = { + 'default': { + fillColor: "#ee9900", + fillOpacity: 0.4, + hoverFillColor: "white", + hoverFillOpacity: 0.8, + strokeColor: "#ee9900", + strokeOpacity: 1, + strokeWidth: 1, + strokeLinecap: "round", + strokeDashstyle: "solid", + hoverStrokeColor: "red", + hoverStrokeOpacity: 1, + hoverStrokeWidth: 0.2, + pointRadius: 6, + hoverPointRadius: 1, + hoverPointUnit: "%", + pointerEvents: "visiblePainted", + cursor: "inherit" + }, + 'select': { + fillColor: "blue", + fillOpacity: 0.4, + hoverFillColor: "white", + hoverFillOpacity: 0.8, + strokeColor: "blue", + strokeOpacity: 1, + strokeWidth: 2, + strokeLinecap: "round", + strokeDashstyle: "solid", + hoverStrokeColor: "red", + hoverStrokeOpacity: 1, + hoverStrokeWidth: 0.2, + pointRadius: 6, + hoverPointRadius: 1, + hoverPointUnit: "%", + pointerEvents: "visiblePainted", + cursor: "pointer" + }, + 'temporary': { + fillColor: "#66cccc", + fillOpacity: 0.2, + hoverFillColor: "white", + hoverFillOpacity: 0.8, + strokeColor: "#66cccc", + strokeOpacity: 1, + strokeLinecap: "round", + strokeWidth: 2, + strokeDashstyle: "solid", + hoverStrokeColor: "red", + hoverStrokeOpacity: 1, + hoverStrokeWidth: 0.2, + pointRadius: 6, + hoverPointRadius: 1, + hoverPointUnit: "%", + pointerEvents: "visiblePainted", + cursor: "inherit" + }, + 'delete': { + display: "none" + } +}; + --- /dev/null +++ b/openlayers/lib/OpenLayers/Feature/WFS.js @@ -1,1 +1,80 @@ +/* Copyright (c) 2006-2008 MetaCarta, Inc., published under the Clear BSD + * license. See http://svn.openlayers.org/trunk/openlayers/license.txt for the + * full text of the license. */ +/** + * @requires OpenLayers/Feature.js + */ + +/** + * Class: OpenLayers.Feature.WFS + * WFS handling class, for use as a featureClass on the WFS layer for handling + * 'point' WFS types. Good for subclassing when creating a custom WFS like + * XML application. + * + * Inherits from: + * - + */ +OpenLayers.Feature.WFS = OpenLayers.Class(OpenLayers.Feature, { + + /** + * Constructor: OpenLayers.Feature.WFS + * Create a WFS feature. + * + * Parameters: + * layer - {} + * xmlNode - {XMLNode} + */ + initialize: function(layer, xmlNode) { + var newArguments = arguments; + var data = this.processXMLNode(xmlNode); + newArguments = new Array(layer, data.lonlat, data); + OpenLayers.Feature.prototype.initialize.apply(this, newArguments); + this.createMarker(); + this.layer.addMarker(this.marker); + }, + + /** + * Method: destroy + * nullify references to prevent circular references and memory leaks + */ + destroy: function() { + if (this.marker != null) { + this.layer.removeMarker(this.marker); + } + OpenLayers.Feature.prototype.destroy.apply(this, arguments); + }, + + /** + * Method: processXMLNode + * When passed an xmlNode, parses it for a GML point, and passes + * back an object describing that point. + * + * For subclasses of Feature.WFS, this is the feature to change. + * + * Parameters: + * xmlNode - {XMLNode} + * + * Returns: + * {Object} Data Object with 'id', 'lonlat', and private properties set + */ + processXMLNode: function(xmlNode) { + //this should be overridden by subclasses + // must return an Object with 'id' and 'lonlat' values set + var point = OpenLayers.Ajax.getElementsByTagNameNS(xmlNode, "http://www.opengis.net/gml", "gml", "Point"); + var text = OpenLayers.Util.getXmlNodeValue(OpenLayers.Ajax.getElementsByTagNameNS(point[0], "http://www.opengis.net/gml","gml", "coordinates")[0]); + var floats = text.split(","); + return {lonlat: new OpenLayers.LonLat(parseFloat(floats[0]), + parseFloat(floats[1])), + id: null}; + + }, + + CLASS_NAME: "OpenLayers.Feature.WFS" +}); + + + + + + --- /dev/null +++ b/openlayers/lib/OpenLayers/Filter.js @@ -1,1 +1,67 @@ +/* Copyright (c) 2006 MetaCarta, Inc., published under a modified BSD license. + * See http://svn.openlayers.org/trunk/openlayers/repository-license.txt + * for the full text of the license. */ + +/** + * @requires OpenLayers/Util.js + * @requires OpenLayers/Style.js + */ + +/** + * Class: OpenLayers.Filter + * This class represents an OGC Filter. + */ +OpenLayers.Filter = OpenLayers.Class({ + + /** + * Constructor: OpenLayers.Filter + * This is an abstract class. Create an instance of a filter subclass. + * + * Parameters: + * options - {Object} Optional object whose properties will be set on the + * instance. + * + * Returns: + * {} + */ + initialize: function(options) { + OpenLayers.Util.extend(this, options); + }, + + /** + * APIMethod: destroy + * Remove reference to anything added. + */ + destroy: function() { + }, + + /** + * APIMethod: evaluate + * Evaluates this filter in a specific context. Should be implemented by + * subclasses. + * + * Parameters: + * context - {Object} Context to use in evaluating the filter. + * + * Returns: + * {Boolean} The filter applies. + */ + evaluate: function(context) { + return true; + }, + + /** + * APIMethod: clone + * Clones this filter. Should be implementted by subclasses. + * + * Returns: + * {} Clone of this filter. + */ + clone: function() { + return null; + }, + + CLASS_NAME: "OpenLayers.Filter" +}); + --- /dev/null +++ b/openlayers/lib/OpenLayers/Filter/Comparison.js @@ -1,1 +1,256 @@ - +/* Copyright (c) 2006-2008 MetaCarta, Inc., published under the Clear BSD + * license. See http://svn.openlayers.org/trunk/openlayers/license.txt for the + * full text of the license. */ + +/** + * @requires OpenLayers/Filter.js + * @requires OpenLayers/Console.js + */ + +/** + * Class: OpenLayers.Filter.Comparison + * This class represents a comparison filter. + * + * Inherits from + * - + */ +OpenLayers.Filter.Comparison = OpenLayers.Class(OpenLayers.Filter, { + + /** + * APIProperty: type + * {String} type: type of the comparison. This is one of + * - OpenLayers.Filter.Comparison.EQUAL_TO = "=="; + * - OpenLayers.Filter.Comparison.NOT_EQUAL_TO = "!="; + * - OpenLayers.Filter.Comparison.LESS_THAN = "<"; + * - OpenLayers.Filter.Comparison.GREATER_THAN = ">"; + * - OpenLayers.Filter.Comparison.LESS_THAN_OR_EQUAL_TO = "<="; + * - OpenLayers.Filter.Comparison.GREATER_THAN_OR_EQUAL_TO = ">="; + * - OpenLayers.Filter.Comparison.BETWEEN = ".."; + * - OpenLayers.Filter.Comparison.LIKE = "~"; + */ + type: null, + + /** + * APIProperty: property + * {String} + * name of the context property to compare + */ + property: null, + + /** + * APIProperty: value + * {Number} or {String} + * comparison value for binary comparisons. In the case of a String, this + * can be a combination of text and propertyNames in the form + * "literal ${propertyName}" + */ + value: null, + + /** + * Property: matchCase + * {Boolean} Force case sensitive searches for EQUAL_TO and NOT_EQUAL_TO + * comparisons. The Filter Encoding 1.1 specification added a matchCase + * attribute to ogc:PropertyIsEqualTo and ogc:PropertyIsNotEqualTo + * elements. This property will be serialized with those elements only + * if using the v1.1.0 filter format. However, when evaluating filters + * here, the matchCase property will always be respected (for EQUAL_TO + * and NOT_EQUAL_TO). Default is true. + */ + matchCase: true, + + /** + * APIProperty: lowerBoundary + * {Number} or {String} + * lower boundary for between comparisons. In the case of a String, this + * can be a combination of text and propertyNames in the form + * "literal ${propertyName}" + */ + lowerBoundary: null, + + /** + * APIProperty: upperBoundary + * {Number} or {String} + * upper boundary for between comparisons. In the case of a String, this + * can be a combination of text and propertyNames in the form + * "literal ${propertyName}" + */ + upperBoundary: null, + + /** + * Constructor: OpenLayers.Filter.Comparison + * Creates a comparison rule. + * + * Parameters: + * options - {Object} An optional object with properties to set on the + * rule + * + * Returns: + * {} + */ + initialize: function(options) { + OpenLayers.Filter.prototype.initialize.apply(this, [options]); + }, + + /** + * APIMethod: evaluate + * Evaluates this filter in a specific context. Should be implemented by + * subclasses. + * + * Parameters: + * context - {Object} Context to use in evaluating the filter. + * + * Returns: + * {Boolean} The filter applies. + */ + evaluate: function(context) { + var result = false; + switch(this.type) { + case OpenLayers.Filter.Comparison.EQUAL_TO: + var got = context[this.property]; + var exp = this.value; + if(!this.matchCase && + typeof got == "string" && typeof exp == "string") { + result = (got.toUpperCase() == exp.toUpperCase()); + } else { + result = (got == exp); + } + break; + case OpenLayers.Filter.Comparison.NOT_EQUAL_TO: + var got = context[this.property]; + var exp = this.value; + if(!this.matchCase && + typeof got == "string" && typeof exp == "string") { + result = (got.toUpperCase() != exp.toUpperCase()); + } else { + result = (got != exp); + } + break; + case OpenLayers.Filter.Comparison.LESS_THAN: + result = context[this.property] < this.value; + break; + case OpenLayers.Filter.Comparison.GREATER_THAN: + result = context[this.property] > this.value; + break; + case OpenLayers.Filter.Comparison.LESS_THAN_OR_EQUAL_TO: + result = context[this.property] <= this.value; + break; + case OpenLayers.Filter.Comparison.GREATER_THAN_OR_EQUAL_TO: + result = context[this.property] >= this.value; + break; + case OpenLayers.Filter.Comparison.BETWEEN: + result = (context[this.property] >= this.lowerBoundary) && + (context[this.property] <= this.upperBoundary); + break; + case OpenLayers.Filter.Comparison.LIKE: + var regexp = new RegExp(this.value, "gi"); + result = regexp.test(context[this.property]); + break; + } + return result; + }, + + /** + * APIMethod: value2regex + * Converts the value of this rule into a regular expression string, + * according to the wildcard characters specified. This method has to + * be called after instantiation of this class, if the value is not a + * regular expression already. + * + * Parameters: + * wildCard - {} wildcard character in the above value, default + * is "*" + * singleChar - {) single-character wildcard in the above value + * default is "." + * escape - {) escape character in the above value, default is + * "!" + * + * Returns: + * {String} regular expression string + */ + value2regex: function(wildCard, singleChar, escapeChar) { + if (wildCard == ".") { + var msg = "'.' is an unsupported wildCard character for "+ + "OpenLayers.Filter.Comparison"; + OpenLayers.Console.error(msg); + return null; + } + + + // set UMN MapServer defaults for unspecified parameters + wildCard = wildCard ? wildCard : "*"; + singleChar = singleChar ? singleChar : "."; + escapeChar = escapeChar ? escapeChar : "!"; + + this.value = this.value.replace( + new RegExp("\\"+escapeChar+"(.|$)", "g"), "\\$1"); + this.value = this.value.replace( + new RegExp("\\"+singleChar, "g"), "."); + this.value = this.value.replace( + new RegExp("\\"+wildCard, "g"), ".*"); + this.value = this.value.replace( + new RegExp("\\\\.\\*", "g"), "\\"+wildCard); + this.value = this.value.replace( + new RegExp("\\\\\\.", "g"), "\\"+singleChar); + + return this.value; + }, + + /** + * Method: regex2value + * Convert the value of this rule from a regular expression string into an + * ogc literal string using a wildCard of *, a singleChar of ., and an + * escape of !. Leaves the property unmodified. + * + * Returns: + * {String} A string value. + */ + regex2value: function() { + + var value = this.value; + + // replace ! with !! + value = value.replace(/!/g, "!!"); + + // replace \. with !. (watching out for \\.) + value = value.replace(/(\\)?\\\./g, function($0, $1) { + return $1 ? $0 : "!."; + }); + + // replace \* with #* (watching out for \\*) + value = value.replace(/(\\)?\\\*/g, function($0, $1) { + return $1 ? $0 : "!*"; + }); + + // replace \\ with \ + value = value.replace(/\\\\/g, "\\"); + + // convert .* to * (the sequence #.* is not allowed) + value = value.replace(/\.\*/g, "*"); + + return value; + }, + + /** + * APIMethod: clone + * Clones this filter. + * + * Returns: + * {} Clone of this filter. + */ + clone: function() { + return OpenLayers.Util.extend(new OpenLayers.Filter.Comparison(), this); + }, + + CLASS_NAME: "OpenLayers.Filter.Comparison" +}); + + +OpenLayers.Filter.Comparison.EQUAL_TO = "=="; +OpenLayers.Filter.Comparison.NOT_EQUAL_TO = "!="; +OpenLayers.Filter.Comparison.LESS_THAN = "<"; +OpenLayers.Filter.Comparison.GREATER_THAN = ">"; +OpenLayers.Filter.Comparison.LESS_THAN_OR_EQUAL_TO = "<="; +OpenLayers.Filter.Comparison.GREATER_THAN_OR_EQUAL_TO = ">="; +OpenLayers.Filter.Comparison.BETWEEN = ".."; +OpenLayers.Filter.Comparison.LIKE = "~"; + --- /dev/null +++ b/openlayers/lib/OpenLayers/Filter/FeatureId.js @@ -1,1 +1,81 @@ +/* Copyright (c) 2006-2008 MetaCarta, Inc., published under the Clear BSD + * license. See http://svn.openlayers.org/trunk/openlayers/license.txt for the + * full text of the license. */ + +/** + * @requires OpenLayers/Filter.js + */ + +/** + * Class: OpenLayers.Filter.FeatureId + * This class represents a ogc:FeatureId Filter, as being used for rule-based SLD + * styling + * + * Inherits from + * - + */ +OpenLayers.Filter.FeatureId = OpenLayers.Class(OpenLayers.Filter, { + + /** + * APIProperty: fids + * {Array(String)} Feature Ids to evaluate this rule against. To be passed + * To be passed inside the params object. + */ + fids: null, + + /** + * Constructor: OpenLayers.Filter.FeatureId + * Creates an ogc:FeatureId rule. + * + * Parameters: + * options - {Object} An optional object with properties to set on the + * rule + * + * Returns: + * {} + */ + initialize: function(options) { + this.fids = []; + OpenLayers.Filter.prototype.initialize.apply(this, [options]); + }, + + /** + * APIMethod: evaluate + * evaluates this rule for a specific feature + * + * Parameters: + * feature - {} feature to apply the rule to. + * For vector features, the check is run against the fid, + * for plain features against the id. + * + * Returns: + * {Boolean} true if the rule applies, false if it does not + */ + evaluate: function(feature) { + for (var i=0, len=this.fids.length; i} Clone of this filter. + */ + clone: function() { + var filter = new OpenLayers.Filter.FeatureId(); + OpenLayers.Util.extend(filter, this); + filter.fids = this.fids.slice(); + return filter; + }, + + CLASS_NAME: "OpenLayers.Filter.FeatureId" +}); + --- /dev/null +++ b/openlayers/lib/OpenLayers/Filter/Logical.js @@ -1,1 +1,118 @@ +/* Copyright (c) 2006-2008 MetaCarta, Inc., published under the Clear BSD + * license. See http://svn.openlayers.org/trunk/openlayers/license.txt for the + * full text of the license. */ + +/** + * @requires OpenLayers/Filter.js + */ + +/** + * Class: OpenLayers.Filter.Logical + * This class represents ogc:And, ogc:Or and ogc:Not rules. + * + * Inherits from + * - + */ +OpenLayers.Filter.Logical = OpenLayers.Class(OpenLayers.Filter, { + + /** + * APIProperty: filters + * {Array()} Child filters for this filter. + */ + filters: null, + + /** + * APIProperty: type + * {String} type of logical operator. Available types are: + * - OpenLayers.Filter.Logical.AND = "&&"; + * - OpenLayers.Filter.Logical.OR = "||"; + * - OpenLayers.Filter.Logical.NOT = "!"; + */ + type: null, + + /** + * Constructor: OpenLayers.Filter.Logical + * Creates a logical filter (And, Or, Not). + * + * Parameters: + * options - {Object} An optional object with properties to set on the + * filter. + * + * Returns: + * {} + */ + initialize: function(options) { + this.filters = []; + OpenLayers.Filter.prototype.initialize.apply(this, [options]); + }, + + /** + * APIMethod: destroy + * Remove reference to child filters. + */ + destroy: function() { + this.filters = null; + OpenLayers.Filter.prototype.destroy.apply(this); + }, + + /** + * APIMethod: evaluate + * Evaluates this filter in a specific context. Should be implemented by + * subclasses. + * + * Parameters: + * context - {Object} Context to use in evaluating the filter. + * + * Returns: + * {Boolean} The filter applies. + */ + evaluate: function(context) { + switch(this.type) { + case OpenLayers.Filter.Logical.AND: + for (var i=0, len=this.filters.length; i} Clone of this filter. + */ + clone: function() { + var filters = []; + for(var i=0, len=this.filters.length; i + */ +OpenLayers.Filter.Spatial = OpenLayers.Class(OpenLayers.Filter, { + + /** + * APIProperty: type + * {String} Type of spatial filter. + * + * The type should be one of: + * - OpenLayers.Filter.Spatial.BBOX + * - OpenLayers.Filter.Spatial.INTERSECTS + * - OpenLayers.Filter.Spatial.DWITHIN + * - OpenLayers.Filter.Spatial.WITHIN + * - OpenLayers.Filter.Spatial.CONTAINS + */ + type: null, + + /** + * APIProperty: property + * {String} Name of the context property to compare. + */ + property: null, + + /** + * APIProperty: value + * { || } The bounds or geometry + * to be used by the filter. Use bounds for BBOX filters and geometry + * for INTERSECTS or DWITHIN filters. + */ + value: null, + + /** + * APIProperty: distance + * {Number} The distance to use in a DWithin spatial filter. + */ + distance: null, + + /** + * APIProperty: distanceUnits + * {String} The units to use for the distance, e.g. 'm'. + */ + distanceUnits: null, + + /** + * Constructor: OpenLayers.Filter.Spatial + * Creates a spatial filter. + * + * Parameters: + * options - {Object} An optional object with properties to set on the + * filter. + * + * Returns: + * {} + */ + initialize: function(options) { + OpenLayers.Filter.prototype.initialize.apply(this, [options]); + }, + + /** + * Method: evaluate + * Evaluates this filter for a specific feature. + * + * Parameters: + * feature - {} feature to apply the filter to. + * + * Returns: + * {Boolean} The feature meets filter criteria. + */ + evaluate: function(feature) { + var intersect = false; + switch(this.type) { + case OpenLayers.Filter.Spatial.BBOX: + case OpenLayers.Filter.Spatial.INTERSECTS: + if(feature.geometry) { + var geom = this.value; + if(this.value.CLASS_NAME == "OpenLayers.Bounds") { + geom = this.value.toGeometry(); + } + if(feature.geometry.intersects(geom)) { + intersect = true; + } + } + break; + default: + OpenLayers.Console.error( + OpenLayers.i18n("filterEvaluateNotImplemented")); + break; + } + return intersect; + }, + + /** + * APIMethod: clone + * Clones this filter. + * + * Returns: + * {} Clone of this filter. + */ + clone: function() { + var options = OpenLayers.Util.applyDefaults({ + value: this.value && this.value.clone && this.value.clone() + }, this); + return new OpenLayers.Filter.Spatial(options); + }, + CLASS_NAME: "OpenLayers.Filter.Spatial" +}); + +OpenLayers.Filter.Spatial.BBOX = "BBOX"; +OpenLayers.Filter.Spatial.INTERSECTS = "INTERSECTS"; +OpenLayers.Filter.Spatial.DWITHIN = "DWITHIN"; +OpenLayers.Filter.Spatial.WITHIN = "WITHIN"; +OpenLayers.Filter.Spatial.CONTAINS = "CONTAINS"; + --- /dev/null +++ b/openlayers/lib/OpenLayers/Format.js @@ -1,1 +1,123 @@ +/* Copyright (c) 2006-2008 MetaCarta, Inc., published under the Clear BSD + * license. See http://svn.openlayers.org/trunk/openlayers/license.txt for the + * full text of the license. */ +/** + * @requires OpenLayers/Util.js + * @requires OpenLayers/Console.js + */ + +/** + * Class: OpenLayers.Format + * Base class for format reading/writing a variety of formats. Subclasses + * of OpenLayers.Format are expected to have read and write methods. + */ +OpenLayers.Format = OpenLayers.Class({ + + /** + * Property: options + * {Object} A reference to options passed to the constructor. + */ + options: null, + + /** + * APIProperty: externalProjection + * {} When passed a externalProjection and + * internalProjection, the format will reproject the geometries it + * reads or writes. The externalProjection is the projection used by + * the content which is passed into read or which comes out of write. + * In order to reproject, a projection transformation function for the + * specified projections must be available. This support may be + * provided via proj4js or via a custom transformation function. See + * {} for more information on + * custom transformations. + */ + externalProjection: null, + + /** + * APIProperty: internalProjection + * {} When passed a externalProjection and + * internalProjection, the format will reproject the geometries it + * reads or writes. The internalProjection is the projection used by + * the geometries which are returned by read or which are passed into + * write. In order to reproject, a projection transformation function + * for the specified projections must be available. This support may be + * provided via proj4js or via a custom transformation function. See + * {} for more information on + * custom transformations. + */ + internalProjection: null, + + /** + * APIProperty: data + * {Object} When is true, this is the parsed string sent to + * . + */ + data: null, + + /** + * APIProperty: keepData + * {Object} Maintain a reference () to the most recently read data. + * Default is false. + */ + keepData: false, + + /** + * Constructor: OpenLayers.Format + * Instances of this class are not useful. See one of the subclasses. + * + * Parameters: + * options - {Object} An optional object with properties to set on the + * format + * + * Valid options: + * keepData - {Boolean} If true, upon , the data property will be + * set to the parsed object (e.g. the json or xml object). + * + * Returns: + * An instance of OpenLayers.Format + */ + initialize: function(options) { + OpenLayers.Util.extend(this, options); + this.options = options; + }, + + /** + * APIMethod: destroy + * Clean up. + */ + destroy: function() { + }, + + /** + * Method: read + * Read data from a string, and return an object whose type depends on the + * subclass. + * + * Parameters: + * data - {string} Data to read/parse. + * + * Returns: + * Depends on the subclass + */ + read: function(data) { + OpenLayers.Console.userError(OpenLayers.i18n("readNotImplemented")); + }, + + /** + * Method: write + * Accept an object, and return a string. + * + * Parameters: + * object - {Object} Object to be serialized + * + * Returns: + * {String} A string representation of the object. + */ + write: function(object) { + OpenLayers.Console.userError(OpenLayers.i18n("writeNotImplemented")); + }, + + CLASS_NAME: "OpenLayers.Format" +}); + --- /dev/null +++ b/openlayers/lib/OpenLayers/Format/ArcXML.js @@ -1,1 +1,1026 @@ - +/* Copyright (c) 2009 MetaCarta, Inc., published under the Clear BSD + * license. See http://svn.openlayers.org/trunk/openlayers/license.txt for the + * full text of the license. */ + +/** + * @requires OpenLayers/Format/XML.js + * @requires OpenLayers/Geometry/Polygon.js + * @requires OpenLayers/Geometry/Point.js + * @requires OpenLayers/Geometry/MultiPolygon.js + * @requires OpenLayers/Geometry/LinearRing.js + */ + +/** + * Class: OpenLayers.Format.ArcXML + * Read/Wite ArcXML. Create a new instance with the + * constructor. + * + * Inherits from: + * - + */ +OpenLayers.Format.ArcXML = OpenLayers.Class(OpenLayers.Format.XML, { + + /** + * Property: fontStyleKeys + * {Array} List of keys used in font styling. + */ + fontStyleKeys: [ + 'antialiasing', 'blockout', 'font', 'fontcolor','fontsize', 'fontstyle', + 'glowing', 'interval', 'outline', 'printmode', 'shadow', 'transparency' + ], + + /** + * Property: request + * A get_image request destined for an ArcIMS server. + */ + request: null, + + /** + * Property: response + * A parsed response from an ArcIMS server. + */ + response: null, + + /** + * Constructor: OpenLayers.Format.ArcXML + * Create a new parser/writer for ArcXML. Create an instance of this class + * to begin authoring a request to an ArcIMS service. This is used + * primarily by the ArcIMS layer, but could be used to do other wild + * stuff, like geocoding. + * + * Parameters: + * options - {Object} An optional object whose properties will be set on + * this instance. + */ + initialize: function(options) { + this.request = new OpenLayers.Format.ArcXML.Request(); + this.response = new OpenLayers.Format.ArcXML.Response(); + + if (options) { + if (options.requesttype == "feature") { + this.request.get_image = null; + + var qry = this.request.get_feature.query; + this.addCoordSys(qry.featurecoordsys, options.featureCoordSys); + this.addCoordSys(qry.filtercoordsys, options.filterCoordSys); + + if (options.polygon) { + qry.isspatial = true; + qry.spatialfilter.polygon = options.polygon; + } else if (options.envelope) { + qry.isspatial = true; + qry.spatialfilter.envelope = {minx:0, miny:0, maxx:0, maxy:0}; + this.parseEnvelope(qry.spatialfilter.envelope, options.envelope); + } + } else if (options.requesttype == "image") { + this.request.get_feature = null; + + var props = this.request.get_image.properties; + this.parseEnvelope(props.envelope, options.envelope); + + this.addLayers(props.layerlist, options.layers); + this.addImageSize(props.imagesize, options.tileSize); + this.addCoordSys(props.featurecoordsys, options.featureCoordSys); + this.addCoordSys(props.filtercoordsys, options.filterCoordSys); + } else { + // if an arcxml object is being created with no request type, it is + // probably going to consume a response, so do not throw an error if + // the requesttype is not defined + this.request = null; + } + } + + OpenLayers.Format.XML.prototype.initialize.apply(this, [options]); + }, + + /** + * Method: parseEnvelope + * Parse an array of coordinates into an ArcXML envelope structure. + * + * Parameters: + * env - {Object} An envelope object that will contain the parsed coordinates. + * arr - {Array(double)} An array of coordinates in the order: [ minx, miny, maxx, maxy ] + */ + parseEnvelope: function(env, arr) { + if (arr && arr.length == 4) { + env.minx = arr[0]; + env.miny = arr[1]; + env.maxx = arr[2]; + env.maxy = arr[3]; + } + }, + + /** + * Method: addLayers + * Add a collection of layers to another collection of layers. Each layer in the list is tuple of + * { id, visible }. These layer collections represent the + * /ARCXML/REQUEST/get_image/PROPERTIES/LAYERLIST/LAYERDEF items in ArcXML + * + * TODO: Add support for dynamic layer rendering. + * + * Parameters: + * ll - {Array({id,visible})} A list of layer definitions. + * lyrs - {Array({id,visible})} A list of layer definitions. + */ + addLayers: function(ll, lyrs) { + for(var lind = 0, len=lyrs.length; lind < len; lind++) { + ll.push(lyrs[lind]); + } + }, + + /** + * Method: addImageSize + * Set the size of the requested image. + * + * Parameters: + * imsize - {Object} An ArcXML imagesize object. + * olsize - {OpenLayers.Size} The image size to set. + */ + addImageSize: function(imsize, olsize) { + if (olsize !== null) { + imsize.width = olsize.w; + imsize.height = olsize.h; + imsize.printwidth = olsize.w; + imsize.printheight = olsize.h; + } + }, + + /** + * Method: addCoordSys + * Add the coordinate system information to an object. The object may be + * + * Parameters: + * featOrFilt - {Object} A featurecoordsys or filtercoordsys ArcXML structure. + * fsys - {String} or {OpenLayers.Projection} or {filtercoordsys} or + * {featurecoordsys} A projection representation. If it's a {String}, + * the value is assumed to be the SRID. If it's a {OpenLayers.Projection} + * AND Proj4js is available, the projection number and name are extracted + * from there. If it's a filter or feature ArcXML structure, it is copied. + */ + addCoordSys: function(featOrFilt, fsys) { + if (typeof fsys == "string") { + featOrFilt.id = parseInt(fsys); + featOrFilt.string = fsys; + } + // is this a proj4js instance? + else if (typeof fsys == "object" && fsys.proj !== null){ + featOrFilt.id = fsys.proj.srsProjNumber; + featOrFilt.string = fsys.proj.srsCode; + } else { + featOrFilt = fsys; + } + }, + + /** + * APIMethod: iserror + * Check to see if the response from the server was an error. + * + * Parameters: + * data - {String} or {DOMElement} data to read/parse. If nothing is supplied, + * the current response is examined. + * + * Returns: + * {Boolean} true if the response was an error. + */ + iserror: function(data) { + var ret = null; + + if (!data) { + ret = (this.response.error !== ''); + } else { + data = OpenLayers.Format.XML.prototype.read.apply(this, [data]); + var errorNodes = data.documentElement.getElementsByTagName("ERROR"); + ret = (errorNodes !== null && errorNodes.length > 0); + } + + return ret; + }, + + /** + * APIMethod: read + * Read data from a string, and return an response. + * + * Parameters: + * data - {String} or {DOMElement} data to read/parse. + * + * Returns: + * {OpenLayers.Format.ArcXML.Response} An ArcXML response. Note that this response + * data may change in the future. + */ + read: function(data) { + if(typeof data == "string") { + data = OpenLayers.Format.XML.prototype.read.apply(this, [data]); + } + + var arcNode = null; + if (data && data.documentElement) { + if(data.documentElement.nodeName == "ARCXML") { + arcNode = data.documentElement; + } else { + arcNode = data.documentElement.getElementsByTagName("ARCXML")[0]; + } + } + + if (!arcNode) { + var error, source; + try { + error = data.firstChild.nodeValue; + source = data.firstChild.childNodes[1].firstChild.nodeValue; + } catch (err) { + // pass + } + throw { + message: "Error parsing the ArcXML request", + error: error, + source: source + }; + } + + var response = this.parseResponse(arcNode); + return response; + }, + + /** + * APIMethod: write + * Generate an ArcXml document string for sending to an ArcIMS server. + * + * Returns: + * {String} A string representing the ArcXML document request. + */ + write: function(request) { + if (!request) { + request = this.request; + } + var root = this.createElementNS("", "ARCXML"); + root.setAttribute("version","1.1"); + + var reqElem = this.createElementNS("", "REQUEST"); + + if (request.get_image != null) { + var getElem = this.createElementNS("", "GET_IMAGE"); + reqElem.appendChild(getElem); + + var propElem = this.createElementNS("", "PROPERTIES"); + getElem.appendChild(propElem); + + var props = request.get_image.properties; + if (props.featurecoordsys != null) { + var feat = this.createElementNS("", "FEATURECOORDSYS"); + propElem.appendChild(feat); + + if (props.featurecoordsys.id === 0) { + feat.setAttribute("string", props.featurecoordsys['string']); + } + else { + feat.setAttribute("id", props.featurecoordsys.id); + } + } + + if (props.filtercoordsys != null) { + var filt = this.createElementNS("", "FILTERCOORDSYS"); + propElem.appendChild(filt); + + if (props.filtercoordsys.id === 0) { + filt.setAttribute("string", props.filtercoordsys.string); + } + else { + filt.setAttribute("id", props.filtercoordsys.id); + } + } + + if (props.envelope != null) { + var env = this.createElementNS("", "ENVELOPE"); + propElem.appendChild(env); + + env.setAttribute("minx", props.envelope.minx); + env.setAttribute("miny", props.envelope.miny); + env.setAttribute("maxx", props.envelope.maxx); + env.setAttribute("maxy", props.envelope.maxy); + } + + var imagesz = this.createElementNS("", "IMAGESIZE"); + propElem.appendChild(imagesz); + + imagesz.setAttribute("height", props.imagesize.height); + imagesz.setAttribute("width", props.imagesize.width); + + if (props.imagesize.height != props.imagesize.printheight || + props.imagesize.width != props.imagesize.printwidth) { + imagesz.setAttribute("printheight", props.imagesize.printheight); + imagesz.setArrtibute("printwidth", props.imagesize.printwidth); + } + + if (props.background != null) { + var backgrnd = this.createElementNS("", "BACKGROUND"); + propElem.appendChild(backgrnd); + + backgrnd.setAttribute("color", + props.background.color.r + "," + + props.background.color.g + "," + + props.background.color.b); + + if (props.background.transcolor !== null) { + backgrnd.setAttribute("transcolor", + props.background.transcolor.r + "," + + props.background.transcolor.g + "," + + props.background.transcolor.b); + } + } + + if (props.layerlist != null && props.layerlist.length > 0) { + var layerlst = this.createElementNS("", "LAYERLIST"); + propElem.appendChild(layerlst); + + for (var ld = 0; ld < props.layerlist.length; ld++) { + var ldef = this.createElementNS("", "LAYERDEF"); + layerlst.appendChild(ldef); + + ldef.setAttribute("id", props.layerlist[ld].id); + ldef.setAttribute("visible", props.layerlist[ld].visible); + + if (typeof props.layerlist[ld].query == "object") { + var query = props.layerlist[ld].query; + + if (query.where.length < 0) { + continue; + } + + var queryElem = null; + if (typeof query.spatialfilter == "boolean" && query.spatialfilter) { + // handle spatial filter madness + queryElem = this.createElementNS("", "SPATIALQUERY"); + } + else { + queryElem = this.createElementNS("", "QUERY"); + } + + queryElem.setAttribute("where", query.where); + + if (typeof query.accuracy == "number" && query.accuracy > 0) { + queryElem.setAttribute("accuracy", query.accuracy); + } + if (typeof query.featurelimit == "number" && query.featurelimit < 2000) { + queryElem.setAttribute("featurelimit", query.featurelimit); + } + if (typeof query.subfields == "string" && query.subfields != "#ALL#") { + queryElem.setAttribute("subfields", query.subfields); + } + if (typeof query.joinexpression == "string" && query.joinexpression.length > 0) { + queryElem.setAttribute("joinexpression", query.joinexpression); + } + if (typeof query.jointables == "string" && query.jointables.length > 0) { + queryElem.setAttribute("jointables", query.jointables); + } + + ldef.appendChild(queryElem); + } + + if (typeof props.layerlist[ld].renderer == "object") { + this.addRenderer(ldef, props.layerlist[ld].renderer); + } + } + } + } else if (request.get_feature != null) { + var getElem = this.createElementNS("", "GET_FEATURES"); + getElem.setAttribute("outputmode", "newxml"); + getElem.setAttribute("checkesc", "true"); + + if (request.get_feature.geometry) { + getElem.setAttribute("geometry", request.get_feature.geometry); + } + else { + getElem.setAttribute("geometry", "false"); + } + + if (request.get_feature.compact) { + getElem.setAttribute("compact", request.get_feature.compact); + } + + if (request.get_feature.featurelimit == "number") { + getElem.setAttribute("featurelimit", request.get_feature.featurelimit); + } + + getElem.setAttribute("globalenvelope", "true"); + reqElem.appendChild(getElem); + + if (request.get_feature.layer != null && request.get_feature.layer.length > 0) { + var lyrElem = this.createElementNS("", "LAYER"); + lyrElem.setAttribute("id", request.get_feature.layer); + getElem.appendChild(lyrElem); + } + + var fquery = request.get_feature.query; + if (fquery != null) { + var qElem = null; + if (fquery.isspatial) { + qElem = this.createElementNS("", "SPATIALQUERY"); + } else { + qElem = this.createElementNS("", "QUERY"); + } + getElem.appendChild(qElem); + + if (typeof fquery.accuracy == "number") { + qElem.setAttribute("accuracy", fquery.accuracy); + } + //qElem.setAttribute("featurelimit", "5"); + + if (fquery.featurecoordsys != null) { + var fcsElem1 = this.createElementNS("", "FEATURECOORDSYS"); + + if (fquery.featurecoordsys.id == 0) { + fcsElem1.setAttribute("string", fquery.featurecoordsys.string); + } else { + fcsElem1.setAttribute("id", fquery.featurecoordsys.id); + } + qElem.appendChild(fcsElem1); + } + + if (fquery.filtercoordsys != null) { + var fcsElem2 = this.createElementNS("", "FILTERCOORDSYS"); + + if (fquery.filtercoordsys.id === 0) { + fcsElem2.setAttribute("string", fquery.filtercoordsys.string); + } else { + fcsElem2.setAttribute("id", fquery.filtercoordsys.id); + } + qElem.appendChild(fcsElem2); + } + + if (fquery.buffer > 0) { + var bufElem = this.createElementNS("", "BUFFER"); + bufElem.setAttribute("distance", fquery.buffer); + qElem.appendChild(bufElem); + } + + if (fquery.isspatial) { + var spfElem = this.createElementNS("", "SPATIALFILTER"); + spfElem.setAttribute("relation", fquery.spatialfilter.relation); + qElem.appendChild(spfElem); + + if (fquery.spatialfilter.envelope) { + var envElem = this.createElementNS("", "ENVELOPE"); + envElem.setAttribute("minx", fquery.spatialfilter.envelope.minx); + envElem.setAttribute("miny", fquery.spatialfilter.envelope.miny); + envElem.setAttribute("maxx", fquery.spatialfilter.envelope.maxx); + envElem.setAttribute("maxy", fquery.spatialfilter.envelope.maxy); + spfElem.appendChild(envElem); + } else if(typeof fquery.spatialfilter.polygon == "object") { + spfElem.appendChild(this.writePolygonGeometry(fquery.spatialfilter.polygon)); + } + } + + if (fquery.where != null && fquery.where.length > 0) { + qElem.setAttribute("where", fquery.where); + } + } + } + + root.appendChild(reqElem); + + return OpenLayers.Format.XML.prototype.write.apply(this, [root]); + }, + + + addGroupRenderer: function(ldef, toprenderer) { + var topRelem = this.createElementNS("", "GROUPRENDERER"); + ldef.appendChild(topRelem); + + for (var rind = 0; rind < toprenderer.length; rind++) { + var renderer = toprenderer[rind]; + this.addRenderer(topRelem, renderer); + } + }, + + + addRenderer: function(topRelem, renderer) { + if (renderer instanceof Array) { + this.addGroupRenderer(topRelem, renderer); + } else { + var renderElem = this.createElementNS("", renderer.type.toUpperCase() + "RENDERER"); + topRelem.appendChild(renderElem); + + if (renderElem.tagName == "VALUEMAPRENDERER") { + this.addValueMapRenderer(renderElem, renderer); + } else if (renderElem.tagName == "VALUEMAPLABELRENDERER") { + this.addValueMapLabelRenderer(renderElem, renderer); + } else if (renderElem.tagName == "SIMPLELABELRENDERER") { + this.addSimpleLabelRenderer(renderElem, renderer); + } else if (renderElem.tagName == "SCALEDEPENDENTRENDERER") { + this.addScaleDependentRenderer(renderElem, renderer); + } + } + }, + + + addScaleDependentRenderer: function(renderElem, renderer) { + if (typeof renderer.lower == "string" || typeof renderer.lower == "number") { + renderElem.setAttribute("lower", renderer.lower); + } + if (typeof renderer.upper == "string" || typeof renderer.upper == "number") { + renderElem.setAttribute("upper", renderer.upper); + } + + this.addRenderer(renderElem, renderer.renderer); + }, + + + addValueMapLabelRenderer: function(renderElem, renderer) { + renderElem.setAttribute("lookupfield", renderer.lookupfield); + renderElem.setAttribute("labelfield", renderer.labelfield); + + if (typeof renderer.exacts == "object") { + for (var ext=0, extlen=renderer.exacts.length; ext 0) { + response.error = this.getChildValue(errorNode, "Unknown error."); + } else { + var responseNode = data.getElementsByTagName("RESPONSE"); + + if (responseNode == null || responseNode.length == 0) { + response.error = "No RESPONSE tag found in ArcXML response."; + return response; + } + + var rtype = responseNode[0].firstChild.nodeName; + if (rtype == "#text") { + rtype = responseNode[0].firstChild.nextSibling.nodeName; + } + + if (rtype == "IMAGE") { + var envelopeNode = data.getElementsByTagName("ENVELOPE"); + var outputNode = data.getElementsByTagName("OUTPUT"); + + if (envelopeNode == null || envelopeNode.length == 0) { + response.error = "No ENVELOPE tag found in ArcXML response."; + } else if (outputNode == null || outputNode.length == 0) { + response.error = "No OUTPUT tag found in ArcXML response."; + } else { + var envAttr = this.parseAttributes(envelopeNode[0]); + var outputAttr = this.parseAttributes(outputNode[0]); + + if (typeof outputAttr.type == "string") { + response.image = { + envelope: envAttr, + output: { + type: outputAttr.type, + data: this.getChildValue(outputNode[0]) + } + }; + } else { + response.image = { envelope: envAttr, output: outputAttr }; + } + } + } else if (rtype == "FEATURES") { + var features = responseNode[0].getElementsByTagName("FEATURES"); + + // get the feature count + var featureCount = features[0].getElementsByTagName("FEATURECOUNT"); + response.features.featurecount = featureCount[0].getAttribute("count"); + + if (response.features.featurecount > 0) { + // get the feature envelope + var envelope = features[0].getElementsByTagName("ENVELOPE"); + response.features.envelope = this.parseAttributes(envelope[0], typeof(0)); + + // get the field values per feature + var featureList = features[0].getElementsByTagName("FEATURE"); + for (var fn = 0; fn < featureList.length; fn++) { + var feature = new OpenLayers.Feature.Vector(); + var fields = featureList[fn].getElementsByTagName("FIELD"); + + for (var fdn = 0; fdn < fields.length; fdn++) { + var fieldName = fields[fdn].getAttribute("name"); + var fieldValue = fields[fdn].getAttribute("value"); + feature.attributes[ fieldName ] = fieldValue; + } + + var geom = featureList[fn].getElementsByTagName("POLYGON"); + + if (geom.length > 0) { + // if there is a polygon, create an openlayers polygon, and assign + // it to the .geometry property of the feature + var ring = geom[0].getElementsByTagName("RING"); + + var polys = []; + for (var rn = 0; rn < ring.length; rn++) { + var linearRings = []; + linearRings.push(this.parsePointGeometry(ring[rn])); + + var holes = ring[rn].getElementsByTagName("HOLE"); + for (var hn = 0; hn < holes.length; hn++) { + linearRings.push(this.parsePointGeometry(holes[hn])); + } + holes = null; + polys.push(new OpenLayers.Geometry.Polygon(linearRings)); + linearRings = null; + } + ring = null; + + if (polys.length == 1) { + feature.geometry = polys[0]; + } else + { + feature.geometry = new OpenLayers.Geometry.MultiPolygon(polys); + } + } + + response.features.feature.push(feature); + } + } + } else { + response.error = "Unidentified response type."; + } + } + return response; + }, + + + /** + * Method: parseAttributes + * + * Parameters: + * node - {} An element to parse attributes from. + * + * Returns: + * {Object} An attributes object, with properties set to attribute values. + */ + parseAttributes: function(node,type) { + var attributes = {}; + for(var attr = 0; attr < node.attributes.length; attr++) { + if (type == "number") { + attributes[node.attributes[attr].nodeName] = parseFloat(node.attributes[attr].nodeValue); + } else { + attributes[node.attributes[attr].nodeName] = node.attributes[attr].nodeValue; + } + } + return attributes; + }, + + + /** + * Method: parsePointGeometry + * + * Parameters: + * node - {} An element to parse or arcxml data from. + * + * Returns: + * {OpenLayers.Geometry.LinearRing} A linear ring represented by the node's points. + */ + parsePointGeometry: function(node) { + var ringPoints = []; + var coords = node.getElementsByTagName("COORDS"); + + if (coords.length > 0) { + // if coords is present, it's the only coords item + var coordArr = this.getChildValue(coords[0]); + coordArr = coordArr.split(/;/); + for (var cn = 0; cn < coordArr.length; cn++) { + var coordItems = coordArr[cn].split(/ /); + ringPoints.push(new OpenLayers.Geometry.Point(parseFloat(coordItems[0]), parseFloat(coordItems[1]))); + } + coords = null; + } else { + var point = node.getElementsByTagName("POINT"); + if (point.length > 0) { + for (var pn = 0; pn < point.length; pn++) { + ringPoints.push( + new OpenLayers.Geometry.Point( + parseFloat(point[pn].getAttribute("x")), + parseFloat(point[pn].getAttribute("y")) + ) + ); + } + } + point = null; + } + + return new OpenLayers.Geometry.LinearRing(ringPoints); + }, + + CLASS_NAME: "OpenLayers.Format.ArcXML" +}); + +OpenLayers.Format.ArcXML.Request = OpenLayers.Class({ + initialize: function(params) { + var defaults = { + get_image: { + properties: { + background: null, + /*{ + color: { r:255, g:255, b:255 }, + transcolor: null + },*/ + draw: true, + envelope: { + minx: 0, + miny: 0, + maxx: 0, + maxy: 0 + }, + featurecoordsys: { + id:0, + string:"", + datumtransformid:0, + datumtransformstring:"" + }, + filtercoordsys:{ + id:0, + string:"", + datumtransformid:0, + datumtransformstring:"" + }, + imagesize:{ + height:0, + width:0, + dpi:96, + printheight:0, + printwidth:0, + scalesymbols:false + }, + layerlist:[], + /* no support for legends */ + output:{ + baseurl:"", + legendbaseurl:"", + legendname:"", + legendpath:"", + legendurl:"", + name:"", + path:"", + type:"jpg", + url:"" + } + } + }, + + get_feature: { + layer: "", + query: { + isspatial: false, + featurecoordsys: { + id:0, + string:"", + datumtransformid:0, + datumtransformstring:"" + }, + filtercoordsys: { + id:0, + string:"", + datumtransformid:0, + datumtransformstring:"" + }, + buffer:0, + where:"", + spatialfilter: { + relation: "envelope_intersection", + envelope: null + } + } + }, + + environment: { + separators: { + cs:" ", + ts:";" + } + }, + + layer: [], + workspaces: [] + }; + + return OpenLayers.Util.extend(this, defaults); + }, + + CLASS_NAME: "OpenLayers.Format.ArcXML.Request" +}); + +OpenLayers.Format.ArcXML.Response = OpenLayers.Class({ + initialize: function(params) { + var defaults = { + image: { + envelope:null, + output:'' + }, + + features: { + featurecount: 0, + envelope: null, + feature: [] + }, + + error:'' + }; + + return OpenLayers.Util.extend(this, defaults); + }, + + CLASS_NAME: "OpenLayers.Format.ArcXML.Response" +}); + --- /dev/null +++ b/openlayers/lib/OpenLayers/Format/ArcXML/Features.js @@ -1,1 +1,49 @@ +/* Copyright (c) 2009 MetaCarta, Inc., published under the Clear BSD + * license. See http://svn.openlayers.org/trunk/openlayers/license.txt for the + * full text of the license. */ +/** + * @requires OpenLayers/Format/ArcXML.js + */ + +/** + * Class: OpenLayers.Format.ArcXML.Features + * Read/Wite ArcXML features. Create a new instance with the + * constructor. + * + * Inherits from: + * - + */ +OpenLayers.Format.ArcXML.Features = OpenLayers.Class(OpenLayers.Format.XML, { + + /** + * Constructor: OpenLayers.Format.ArcXML.Features + * Create a new parser/writer for ArcXML Features. Create an instance of this class + * to get a set of features from an ArcXML response. + * + * Parameters: + * options - {Object} An optional object whose properties will be set on + * this instance. + */ + initialize: function(options) { + OpenLayers.Format.XML.prototype.initialize.apply(this, [options]); + }, + + /** + * APIMethod: read + * Read data from a string of ArcXML, and return a set of OpenLayers features. + * + * Parameters: + * data - {String} or {DOMElement} data to read/parse. + * + * Returns: + * {Array()} A collection of features. + */ + read: function(data) { + var axl = new OpenLayers.Format.ArcXML(); + var parsed = axl.read(data); + + return parsed.features.feature; + } +}); + --- /dev/null +++ b/openlayers/lib/OpenLayers/Format/Filter.js @@ -1,1 +1,115 @@ +/* Copyright (c) 2006 MetaCarta, Inc., published under a modified BSD license. + * See http://svn.openlayers.org/trunk/openlayers/repository-license.txt + * for the full text of the license. */ +/** + * @requires OpenLayers/Format/XML.js + * @requires OpenLayers/Filter/FeatureId.js + * @requires OpenLayers/Filter/Logical.js + * @requires OpenLayers/Filter/Comparison.js + */ + +/** + * Class: OpenLayers.Format.Filter + * Read/Wite ogc:Filter. Create a new instance with the + * constructor. + * + * Inherits from: + * - + */ +OpenLayers.Format.Filter = OpenLayers.Class(OpenLayers.Format.XML, { + + /** + * APIProperty: defaultVersion + * {String} Version number to assume if none found. Default is "1.0.0". + */ + defaultVersion: "1.0.0", + + /** + * APIProperty: version + * {String} Specify a version string if one is known. + */ + version: null, + + /** + * Property: parser + * {Object} Instance of the versioned parser. Cached for multiple read and + * write calls of the same version. + */ + parser: null, + + /** + * Constructor: OpenLayers.Format.Filter + * Create a new parser for Filter. + * + * Parameters: + * options - {Object} An optional object whose properties will be set on + * this instance. + */ + initialize: function(options) { + OpenLayers.Format.XML.prototype.initialize.apply(this, [options]); + }, + + /** + * APIMethod: write + * Write an ogc:Filter given a filter object. + * + * Parameters: + * filter - {} An filter. + * options - {Object} Optional configuration object. + * + * Returns: + * {Elment} An ogc:Filter element node. + */ + write: function(filter, options) { + var version = (options && options.version) || + this.version || this.defaultVersion; + if(!this.parser || this.parser.VERSION != version) { + var format = OpenLayers.Format.Filter[ + "v" + version.replace(/\./g, "_") + ]; + if(!format) { + throw "Can't find a Filter parser for version " + + version; + } + this.parser = new format(this.options); + } + return this.parser.write(filter); + //return OpenLayers.Format.XML.prototype.write.apply(this, [root]); + }, + + /** + * APIMethod: read + * Read and Filter doc and return an object representing the Filter. + * + * Parameters: + * data - {String | DOMElement} Data to read. + * + * Returns: + * {} A filter object. + */ + read: function(data) { + if(typeof data == "string") { + data = OpenLayers.Format.XML.prototype.read.apply(this, [data]); + } + var version = this.version; + if(!version) { + version = this.defaultVersion; + } + if(!this.parser || this.parser.VERSION != version) { + var format = OpenLayers.Format.Filter[ + "v" + version.replace(/\./g, "_") + ]; + if(!format) { + throw "Can't find a Filter parser for version " + + version; + } + this.parser = new format(this.options); + } + var filter = this.parser.read(data); + return filter; + }, + + CLASS_NAME: "OpenLayers.Format.Filter" +}); + --- /dev/null +++ b/openlayers/lib/OpenLayers/Format/Filter/v1.js @@ -1,1 +1,450 @@ - +/* Copyright (c) 2006-2008 MetaCarta, Inc., published under the Clear BSD + * license. See http://svn.openlayers.org/trunk/openlayers/license.txt for the + * full text of the license. */ + +/** + * @requires OpenLayers/Format/Filter.js + * @requires OpenLayers/Format/XML.js + */ + +/** + * Class: OpenLayers.Format.Filter.v1 + * Superclass for Filter version 1 parsers. + * + * Inherits from: + * - + */ +OpenLayers.Format.Filter.v1 = OpenLayers.Class(OpenLayers.Format.XML, { + + /** + * Property: namespaces + * {Object} Mapping of namespace aliases to namespace URIs. + */ + namespaces: { + ogc: "http://www.opengis.net/ogc", + gml: "http://www.opengis.net/gml", + xlink: "http://www.w3.org/1999/xlink", + xsi: "http://www.w3.org/2001/XMLSchema-instance" + }, + + /** + * Property: defaultPrefix + */ + defaultPrefix: "ogc", + + /** + * Property: schemaLocation + * {String} Schema location for a particular minor version. + */ + schemaLocation: null, + + /** + * Constructor: OpenLayers.Format.Filter.v1 + * Instances of this class are not created directly. Use the + * constructor instead. + * + * Parameters: + * options - {Object} An optional object whose properties will be set on + * this instance. + */ + initialize: function(options) { + OpenLayers.Format.XML.prototype.initialize.apply(this, [options]); + }, + + /** + * Method: read + * + * Parameters: + * data - {DOMElement} A Filter document element. + * + * Returns: + * {} A filter object. + */ + read: function(data) { + var obj = {}; + this.readers.ogc["Filter"].apply(this, [data, obj]); + return obj.filter; + }, + + /** + * Property: readers + * Contains public functions, grouped by namespace prefix, that will + * be applied when a namespaced node is found matching the function + * name. The function will be applied in the scope of this parser + * with two arguments: the node being read and a context object passed + * from the parent. + */ + readers: { + "ogc": { + "Filter": function(node, parent) { + // Filters correspond to subclasses of OpenLayers.Filter. + // Since they contain information we don't persist, we + // create a temporary object and then pass on the filter + // (ogc:Filter) to the parent obj. + var obj = { + fids: [], + filters: [] + }; + this.readChildNodes(node, obj); + if(obj.fids.length > 0) { + parent.filter = new OpenLayers.Filter.FeatureId({ + fids: obj.fids + }); + } else if(obj.filters.length > 0) { + parent.filter = obj.filters[0]; + } + }, + "FeatureId": function(node, obj) { + var fid = node.getAttribute("fid"); + if(fid) { + obj.fids.push(fid); + } + }, + "And": function(node, obj) { + var filter = new OpenLayers.Filter.Logical({ + type: OpenLayers.Filter.Logical.AND + }); + this.readChildNodes(node, filter); + obj.filters.push(filter); + }, + "Or": function(node, obj) { + var filter = new OpenLayers.Filter.Logical({ + type: OpenLayers.Filter.Logical.OR + }); + this.readChildNodes(node, filter); + obj.filters.push(filter); + }, + "Not": function(node, obj) { + var filter = new OpenLayers.Filter.Logical({ + type: OpenLayers.Filter.Logical.NOT + }); + this.readChildNodes(node, filter); + obj.filters.push(filter); + }, + "PropertyIsLessThan": function(node, obj) { + var filter = new OpenLayers.Filter.Comparison({ + type: OpenLayers.Filter.Comparison.LESS_THAN + }); + this.readChildNodes(node, filter); + obj.filters.push(filter); + }, + "PropertyIsGreaterThan": function(node, obj) { + var filter = new OpenLayers.Filter.Comparison({ + type: OpenLayers.Filter.Comparison.GREATER_THAN + }); + this.readChildNodes(node, filter); + obj.filters.push(filter); + }, + "PropertyIsLessThanOrEqualTo": function(node, obj) { + var filter = new OpenLayers.Filter.Comparison({ + type: OpenLayers.Filter.Comparison.LESS_THAN_OR_EQUAL_TO + }); + this.readChildNodes(node, filter); + obj.filters.push(filter); + }, + "PropertyIsGreaterThanOrEqualTo": function(node, obj) { + var filter = new OpenLayers.Filter.Comparison({ + type: OpenLayers.Filter.Comparison.GREATER_THAN_OR_EQUAL_TO + }); + this.readChildNodes(node, filter); + obj.filters.push(filter); + }, + "PropertyIsBetween": function(node, obj) { + var filter = new OpenLayers.Filter.Comparison({ + type: OpenLayers.Filter.Comparison.BETWEEN + }); + this.readChildNodes(node, filter); + obj.filters.push(filter); + }, + "PropertyIsLike": function(node, obj) { + var filter = new OpenLayers.Filter.Comparison({ + type: OpenLayers.Filter.Comparison.LIKE + }); + this.readChildNodes(node, filter); + var wildCard = node.getAttribute("wildCard"); + var singleChar = node.getAttribute("singleChar"); + var esc = node.getAttribute("escape"); + filter.value2regex(wildCard, singleChar, esc); + obj.filters.push(filter); + }, + "Literal": function(node, obj) { + obj.value = OpenLayers.String.numericIf( + this.getChildValue(node)); + }, + "PropertyName": function(node, filter) { + filter.property = this.getChildValue(node); + }, + "LowerBoundary": function(node, filter) { + filter.lowerBoundary = OpenLayers.String.numericIf( + this.readOgcExpression(node)); + }, + "UpperBoundary": function(node, filter) { + filter.upperBoundary = OpenLayers.String.numericIf( + this.readOgcExpression(node)); + }, + "Intersects": function(node, obj) { + this.readSpatial(node, obj, OpenLayers.Filter.Spatial.INTERSECTS); + }, + "Within": function(node, obj) { + this.readSpatial(node, obj, OpenLayers.Filter.Spatial.WITHIN); + }, + "Contains": function(node, obj) { + this.readSpatial(node, obj, OpenLayers.Filter.Spatial.CONTAINS); + }, + "DWithin": function(node, obj) { + this.readSpatial(node, obj, OpenLayers.Filter.Spatial.DWITHIN); + }, + "Distance": function(node, obj) { + obj.distance = parseInt(this.getChildValue(node)); + obj.distanceUnits = node.getAttribute("units"); + } + } + }, + + /** + * Method: readSpatial + * + * Read a {} filter. + * + * Parameters: + * node - {DOMElement} A DOM element that contains an ogc:expression. + * obj - {Object} The target object. + * type - {String} One of the OpenLayers.Filter.Spatial.* constants. + * + * Returns: + * {} The created filter. + */ + readSpatial: function(node, obj, type) { + var filter = new OpenLayers.Filter.Spatial({ + type: type + }); + this.readChildNodes(node, filter); + filter.value = filter.components[0]; + delete filter.components; + obj.filters.push(filter); + }, + + /** + * Method: readOgcExpression + * Limited support for OGC expressions. + * + * Parameters: + * node - {DOMElement} A DOM element that contains an ogc:expression. + * + * Returns: + * {String} A value to be used in a symbolizer. + */ + readOgcExpression: function(node) { + var obj = {}; + this.readChildNodes(node, obj); + var value = obj.value; + if(!value) { + value = this.getChildValue(node); + } + return value; + }, + + /** + * Method: write + * + * Parameters: + * filter - {} A filter object. + * + * Returns: + * {DOMElement} An ogc:Filter element. + */ + write: function(filter) { + return this.writers.ogc["Filter"].apply(this, [filter]); + }, + + /** + * Property: writers + * As a compliment to the readers property, this structure contains public + * writing functions grouped by namespace alias and named like the + * node names they produce. + */ + writers: { + "ogc": { + "Filter": function(filter) { + var node = this.createElementNSPlus("ogc:Filter"); + var sub = filter.CLASS_NAME.split(".").pop(); + if(sub == "FeatureId") { + for(var i=0; i": "PropertyIsGreaterThan", + "<=": "PropertyIsLessThanOrEqualTo", + ">=": "PropertyIsGreaterThanOrEqualTo", + "..": "PropertyIsBetween", + "~": "PropertyIsLike", + "BBOX": "BBOX", + "DWITHIN": "DWITHIN", + "WITHIN": "WITHIN", + "CONTAINS": "CONTAINS", + "INTERSECTS": "INTERSECTS" + }, + + CLASS_NAME: "OpenLayers.Format.Filter.v1" + +}); + --- /dev/null +++ b/openlayers/lib/OpenLayers/Format/Filter/v1_0_0.js @@ -1,1 +1,145 @@ +/* Copyright (c) 2006-2008 MetaCarta, Inc., published under the Clear BSD + * license. See http://svn.openlayers.org/trunk/openlayers/license.txt for the + * full text of the license. */ +/** + * @requires OpenLayers/Format/GML/v2.js + * @requires OpenLayers/Format/Filter/v1.js + */ + +/** + * Class: OpenLayers.Format.Filter.v1_0_0 + * Write ogc:Filter version 1.0.0. + * + * Inherits from: + * - + * - + */ +OpenLayers.Format.Filter.v1_0_0 = OpenLayers.Class( + OpenLayers.Format.GML.v2, OpenLayers.Format.Filter.v1, { + + /** + * Constant: VERSION + * {String} 1.0.0 + */ + VERSION: "1.0.0", + + /** + * Property: schemaLocation + * {String} http://www.opengis.net/ogc/filter/1.0.0/filter.xsd + */ + schemaLocation: "http://www.opengis.net/ogc/filter/1.0.0/filter.xsd", + + /** + * Constructor: OpenLayers.Format.Filter.v1_0_0 + * Instances of this class are not created directly. Use the + * constructor instead. + * + * Parameters: + * options - {Object} An optional object whose properties will be set on + * this instance. + */ + initialize: function(options) { + OpenLayers.Format.GML.v2.prototype.initialize.apply( + this, [options] + ); + }, + + /** + * Property: readers + * Contains public functions, grouped by namespace prefix, that will + * be applied when a namespaced node is found matching the function + * name. The function will be applied in the scope of this parser + * with two arguments: the node being read and a context object passed + * from the parent. + */ + readers: { + "ogc": OpenLayers.Util.applyDefaults({ + "PropertyIsEqualTo": function(node, obj) { + var filter = new OpenLayers.Filter.Comparison({ + type: OpenLayers.Filter.Comparison.EQUAL_TO + }); + this.readChildNodes(node, filter); + obj.filters.push(filter); + }, + "PropertyIsNotEqualTo": function(node, obj) { + var filter = new OpenLayers.Filter.Comparison({ + type: OpenLayers.Filter.Comparison.NOT_EQUAL_TO + }); + this.readChildNodes(node, filter); + obj.filters.push(filter); + } + }, OpenLayers.Format.Filter.v1.prototype.readers["ogc"]), + "gml": OpenLayers.Format.GML.v2.prototype.readers["gml"], + "feature": OpenLayers.Format.GML.v2.prototype.readers["feature"] + }, + + /** + * Property: writers + * As a compliment to the readers property, this structure contains public + * writing functions grouped by namespace alias and named like the + * node names they produce. + */ + writers: { + "ogc": OpenLayers.Util.applyDefaults({ + "PropertyIsEqualTo": function(filter) { + var node = this.createElementNSPlus("ogc:PropertyIsEqualTo"); + // no ogc:expression handling for now + this.writeNode("PropertyName", filter, node); + this.writeNode("Literal", filter.value, node); + return node; + }, + "PropertyIsNotEqualTo": function(filter) { + var node = this.createElementNSPlus("ogc:PropertyIsNotEqualTo"); + // no ogc:expression handling for now + this.writeNode("PropertyName", filter, node); + this.writeNode("Literal", filter.value, node); + return node; + }, + "BBOX": function(filter) { + var node = this.createElementNSPlus("ogc:BBOX"); + this.writeNode("PropertyName", filter, node); + var box = this.writeNode("gml:Box", filter.value, node); + if(filter.projection) { + box.setAttribute("srsName", filter.projection); + } + return node; + }}, OpenLayers.Format.Filter.v1.prototype.writers["ogc"]), + + "gml": OpenLayers.Format.GML.v2.prototype.writers["gml"], + "feature": OpenLayers.Format.GML.v2.prototype.writers["feature"] + + }, + + /** + * Method: writeSpatial + * + * Read a {} filter and converts it into XML. + * + * Parameters: + * filter - {} The filter. + * name - {String} Name of the generated XML element. + * + * Returns: + * {DOMElement} The created XML element. + */ + writeSpatial: function(filter, name) { + var node = this.createElementNSPlus("ogc:"+name); + this.writeNode("PropertyName", filter, node); + var child; + if(filter.value instanceof OpenLayers.Geometry) { + child = this.writeNode("feature:_geometry", filter.value).firstChild; + } else { + child = this.writeNode("gml:Box", filter.value); + } + if(filter.projection) { + child.setAttribute("srsName", filter.projection); + } + node.appendChild(child); + return node; + }, + + + CLASS_NAME: "OpenLayers.Format.Filter.v1_0_0" + +}); --- /dev/null +++ b/openlayers/lib/OpenLayers/Format/Filter/v1_1_0.js @@ -1,1 +1,158 @@ +/* Copyright (c) 2006-2008 MetaCarta, Inc., published under the Clear BSD + * license. See http://svn.openlayers.org/trunk/openlayers/license.txt for the + * full text of the license. */ +/** + * @requires OpenLayers/Format/Filter/v1.js + * @requires OpenLayers/Format/GML/v3.js + */ + +/** + * Class: OpenLayers.Format.Filter.v1_1_0 + * Write ogc:Filter version 1.1.0. + * + * Differences from the v1.0.0 parser: + * - uses GML v3 instead of GML v2 + * - reads matchCase attribute on ogc:PropertyIsEqual and + * ogc:PropertyIsNotEqualelements. + * - writes matchCase attribute from comparison filters of type EQUAL_TO and + * type NOT_EQUAL_TO. + * + * Inherits from: + * - + */ +OpenLayers.Format.Filter.v1_1_0 = OpenLayers.Class( + OpenLayers.Format.GML.v3, OpenLayers.Format.Filter.v1, { + + /** + * Constant: VERSION + * {String} 1.1.0 + */ + VERSION: "1.1.0", + + /** + * Property: schemaLocation + * {String} http://www.opengis.net/ogc/filter/1.1.0/filter.xsd + */ + schemaLocation: "http://www.opengis.net/ogc/filter/1.1.0/filter.xsd", + + /** + * Constructor: OpenLayers.Format.Filter.v1_1_0 + * Instances of this class are not created directly. Use the + * constructor instead. + * + * Parameters: + * options - {Object} An optional object whose properties will be set on + * this instance. + */ + initialize: function(options) { + OpenLayers.Format.GML.v3.prototype.initialize.apply( + this, [options] + ); + }, + + /** + * Property: readers + * Contains public functions, grouped by namespace prefix, that will + * be applied when a namespaced node is found matching the function + * name. The function will be applied in the scope of this parser + * with two arguments: the node being read and a context object passed + * from the parent. + */ + readers: { + "ogc": OpenLayers.Util.applyDefaults({ + "PropertyIsEqualTo": function(node, obj) { + var matchCase = node.getAttribute("matchCase"); + var filter = new OpenLayers.Filter.Comparison({ + type: OpenLayers.Filter.Comparison.EQUAL_TO, + matchCase: !(matchCase === "false" || matchCase === "0") + }); + this.readChildNodes(node, filter); + obj.filters.push(filter); + }, + "PropertyIsNotEqualTo": function(node, obj) { + var matchCase = node.getAttribute("matchCase"); + var filter = new OpenLayers.Filter.Comparison({ + type: OpenLayers.Filter.Comparison.NOT_EQUAL_TO, + matchCase: !(matchCase === "false" || matchCase === "0") + }); + this.readChildNodes(node, filter); + obj.filters.push(filter); + } + }, OpenLayers.Format.Filter.v1.prototype.readers["ogc"]), + "gml": OpenLayers.Format.GML.v3.prototype.readers["gml"], + "feature": OpenLayers.Format.GML.v3.prototype.readers["feature"] + }, + + /** + * Property: writers + * As a compliment to the readers property, this structure contains public + * writing functions grouped by namespace alias and named like the + * node names they produce. + */ + writers: { + "ogc": OpenLayers.Util.applyDefaults({ + "PropertyIsEqualTo": function(filter) { + var node = this.createElementNSPlus("ogc:PropertyIsEqualTo", { + attributes: {matchCase: filter.matchCase} + }); + // no ogc:expression handling for now + this.writeNode("PropertyName", filter, node); + this.writeNode("Literal", filter.value, node); + return node; + }, + "PropertyIsNotEqualTo": function(filter) { + var node = this.createElementNSPlus("ogc:PropertyIsNotEqualTo", { + attributes: {matchCase: filter.matchCase} + }); + // no ogc:expression handling for now + this.writeNode("PropertyName", filter, node); + this.writeNode("Literal", filter.value, node); + return node; + }, + "BBOX": function(filter) { + var node = this.createElementNSPlus("ogc:BBOX"); + this.writeNode("PropertyName", filter, node); + var box = this.writeNode("gml:Envelope", filter.value); + if(filter.projection) { + box.setAttribute("srsName", filter.projection); + } + node.appendChild(box); + return node; + }}, OpenLayers.Format.Filter.v1.prototype.writers["ogc"]), + + "gml": OpenLayers.Format.GML.v3.prototype.writers["gml"], + "feature": OpenLayers.Format.GML.v3.prototype.writers["feature"] + }, + + /** + * Method: writeSpatial + * + * Read a {} filter and converts it into XML. + * + * Parameters: + * filter - {} The filter. + * name - {String} Name of the generated XML element. + * + * Returns: + * {DOMElement} The created XML element. + */ + writeSpatial: function(filter, name) { + var node = this.createElementNSPlus("ogc:"+name); + this.writeNode("PropertyName", filter, node); + var child; + if(filter.value instanceof OpenLayers.Geometry) { + child = this.writeNode("feature:_geometry", filter.value).firstChild; + } else { + child = this.writeNode("gml:Envelope", filter.value); + } + if(filter.projection) { + child.setAttribute("srsName", filter.projection); + } + node.appendChild(child); + return node; + }, + + CLASS_NAME: "OpenLayers.Format.Filter.v1_1_0" + +}); --- /dev/null +++ b/openlayers/lib/OpenLayers/Format/GML.js @@ -1,1 +1,873 @@ - +/* Copyright (c) 2006-2008 MetaCarta, Inc., published under the Clear BSD + * license. See http://svn.openlayers.org/trunk/openlayers/license.txt for the + * full text of the license. */ + +/** + * @requires OpenLayers/Format/XML.js + * @requires OpenLayers/Feature/Vector.js + * @requires OpenLayers/Geometry/Point.js + * @requires OpenLayers/Geometry/MultiPoint.js + * @requires OpenLayers/Geometry/LineString.js + * @requires OpenLayers/Geometry/MultiLineString.js + * @requires OpenLayers/Geometry/Polygon.js + * @requires OpenLayers/Geometry/MultiPolygon.js + * @requires OpenLayers/Console.js + */ + +/** + * Class: OpenLayers.Format.GML + * Read/Wite GML. Create a new instance with the + * constructor. Supports the GML simple features profile. + * + * Inherits from: + * - + */ +OpenLayers.Format.GML = OpenLayers.Class(OpenLayers.Format.XML, { + + /* + * APIProperty: featureNS + * {String} Namespace used for feature attributes. Default is + * "http://mapserver.gis.umn.edu/mapserver". + */ + featureNS: "http://mapserver.gis.umn.edu/mapserver", + + /** + * APIProperty: featurePrefix + * {String} Namespace alias (or prefix) for feature nodes. Default is + * "feature". + */ + featurePrefix: "feature", + + /* + * APIProperty: featureName + * {String} Element name for features. Default is "featureMember". + */ + featureName: "featureMember", + + /* + * APIProperty: layerName + * {String} Name of data layer. Default is "features". + */ + layerName: "features", + + /** + * APIProperty: geometryName + * {String} Name of geometry element. Defaults to "geometry". + */ + geometryName: "geometry", + + /** + * APIProperty: collectionName + * {String} Name of featureCollection element. + */ + collectionName: "FeatureCollection", + + /** + * APIProperty: gmlns + * {String} GML Namespace. + */ + gmlns: "http://www.opengis.net/gml", + + /** + * APIProperty: extractAttributes + * {Boolean} Extract attributes from GML. + */ + extractAttributes: true, + + /** + * APIProperty: xy + * {Boolean} Order of the GML coordinate true:(x,y) or false:(y,x) + * Changing is not recommended, a new Format should be instantiated. + */ + xy: true, + + /** + * Constructor: OpenLayers.Format.GML + * Create a new parser for GML. + * + * Parameters: + * options - {Object} An optional object whose properties will be set on + * this instance. + */ + initialize: function(options) { + // compile regular expressions once instead of every time they are used + this.regExes = { + trimSpace: (/^\s*|\s*$/g), + removeSpace: (/\s*/g), + splitSpace: (/\s+/), + trimComma: (/\s*,\s*/g) + }; + OpenLayers.Format.XML.prototype.initialize.apply(this, [options]); + }, + + /** + * APIMethod: read + * Read data from a string, and return a list of features. + * + * Parameters: + * data - {String} or {DOMElement} data to read/parse. + * + * Returns: + * {Array()} An array of features. + */ + read: function(data) { + if(typeof data == "string") { + data = OpenLayers.Format.XML.prototype.read.apply(this, [data]); + } + var featureNodes = this.getElementsByTagNameNS(data.documentElement, + this.gmlns, + this.featureName); + var features = []; + for(var i=0; i 0) { + // only deal with first geometry of this type + var parser = this.parseGeometry[type.toLowerCase()]; + if(parser) { + geometry = parser.apply(this, [nodeList[0]]); + if (this.internalProjection && this.externalProjection) { + geometry.transform(this.externalProjection, + this.internalProjection); + } + } else { + OpenLayers.Console.error(OpenLayers.i18n( + "unsupportedGeometryType", {'geomType':type})); + } + // stop looking for different geometry types + break; + } + } + + // construct feature (optionally with attributes) + var attributes; + if(this.extractAttributes) { + attributes = this.parseAttributes(node); + } + var feature = new OpenLayers.Feature.Vector(geometry, attributes); + + feature.gml = { + featureType: node.firstChild.nodeName.split(":")[1], + featureNS: node.firstChild.namespaceURI, + featureNSPrefix: node.firstChild.prefix + }; + + // assign fid - this can come from a "fid" or "id" attribute + var childNode = node.firstChild; + var fid; + while(childNode) { + if(childNode.nodeType == 1) { + fid = childNode.getAttribute("fid") || + childNode.getAttribute("id"); + if(fid) { + break; + } + } + childNode = childNode.nextSibling; + } + feature.fid = fid; + return feature; + }, + + /** + * Property: parseGeometry + * Properties of this object are the functions that parse geometries based + * on their type. + */ + parseGeometry: { + + /** + * Method: parseGeometry.point + * Given a GML node representing a point geometry, create an OpenLayers + * point geometry. + * + * Parameters: + * node - {DOMElement} A GML node. + * + * Returns: + * {} A point geometry. + */ + point: function(node) { + /** + * Three coordinate variations to consider: + * 1) x y z + * 2) x, y, z + * 3) xy + */ + var nodeList, coordString; + var coords = []; + + // look for + var nodeList = this.getElementsByTagNameNS(node, this.gmlns, "pos"); + if(nodeList.length > 0) { + coordString = nodeList[0].firstChild.nodeValue; + coordString = coordString.replace(this.regExes.trimSpace, ""); + coords = coordString.split(this.regExes.splitSpace); + } + + // look for + if(coords.length == 0) { + nodeList = this.getElementsByTagNameNS(node, this.gmlns, + "coordinates"); + if(nodeList.length > 0) { + coordString = nodeList[0].firstChild.nodeValue; + coordString = coordString.replace(this.regExes.removeSpace, + ""); + coords = coordString.split(","); + } + } + + // look for + if(coords.length == 0) { + nodeList = this.getElementsByTagNameNS(node, this.gmlns, + "coord"); + if(nodeList.length > 0) { + var xList = this.getElementsByTagNameNS(nodeList[0], + this.gmlns, "X"); + var yList = this.getElementsByTagNameNS(nodeList[0], + this.gmlns, "Y"); + if(xList.length > 0 && yList.length > 0) { + coords = [xList[0].firstChild.nodeValue, + yList[0].firstChild.nodeValue]; + } + } + } + + // preserve third dimension + if(coords.length == 2) { + coords[2] = null; + } + + if (this.xy) { + return new OpenLayers.Geometry.Point(coords[0], coords[1], + coords[2]); + } + else{ + return new OpenLayers.Geometry.Point(coords[1], coords[0], + coords[2]); + } + }, + + /** + * Method: parseGeometry.multipoint + * Given a GML node representing a multipoint geometry, create an + * OpenLayers multipoint geometry. + * + * Parameters: + * node - {DOMElement} A GML node. + * + * Returns: + * {} A multipoint geometry. + */ + multipoint: function(node) { + var nodeList = this.getElementsByTagNameNS(node, this.gmlns, + "Point"); + var components = []; + if(nodeList.length > 0) { + var point; + for(var i=0; i} A linestring geometry. + */ + linestring: function(node, ring) { + /** + * Two coordinate variations to consider: + * 1) x0 y0 z0 x1 y1 z1 + * 2) x0, y0, z0 x1, y1, z1 + */ + var nodeList, coordString; + var coords = []; + var points = []; + + // look for + nodeList = this.getElementsByTagNameNS(node, this.gmlns, "posList"); + if(nodeList.length > 0) { + coordString = this.getChildValue(nodeList[0]); + coordString = coordString.replace(this.regExes.trimSpace, ""); + coords = coordString.split(this.regExes.splitSpace); + var dim = parseInt(nodeList[0].getAttribute("dimension")); + var j, x, y, z; + for(var i=0; i + if(coords.length == 0) { + nodeList = this.getElementsByTagNameNS(node, this.gmlns, + "coordinates"); + if(nodeList.length > 0) { + coordString = this.getChildValue(nodeList[0]); + coordString = coordString.replace(this.regExes.trimSpace, + ""); + coordString = coordString.replace(this.regExes.trimComma, + ","); + var pointList = coordString.split(this.regExes.splitSpace); + for(var i=0; i} A multilinestring geometry. + */ + multilinestring: function(node) { + var nodeList = this.getElementsByTagNameNS(node, this.gmlns, + "LineString"); + var components = []; + if(nodeList.length > 0) { + var line; + for(var i=0; i} A polygon geometry. + */ + polygon: function(node) { + var nodeList = this.getElementsByTagNameNS(node, this.gmlns, + "LinearRing"); + var components = []; + if(nodeList.length > 0) { + // this assumes exterior ring first, inner rings after + var ring; + for(var i=0; i} A multipolygon geometry. + */ + multipolygon: function(node) { + var nodeList = this.getElementsByTagNameNS(node, this.gmlns, + "Polygon"); + var components = []; + if(nodeList.length > 0) { + var polygon; + for(var i=0; i 0) { + var coords = []; + + if(lpoint.length > 0) { + coordString = lpoint[0].firstChild.nodeValue; + coordString = coordString.replace(this.regExes.trimSpace, ""); + coords = coordString.split(this.regExes.splitSpace); + } + + if(coords.length == 2) { + coords[2] = null; + } + if (this.xy) { + var lowerPoint = new OpenLayers.Geometry.Point(coords[0], coords[1],coords[2]); + } else { + var lowerPoint = new OpenLayers.Geometry.Point(coords[1], coords[0],coords[2]); + } + } + + var upoint = this.getElementsByTagNameNS(node, this.gmlns, "upperCorner"); + if (upoint.length > 0) { + var coords = []; + + if(upoint.length > 0) { + coordString = upoint[0].firstChild.nodeValue; + coordString = coordString.replace(this.regExes.trimSpace, ""); + coords = coordString.split(this.regExes.splitSpace); + } + + if(coords.length == 2) { + coords[2] = null; + } + if (this.xy) { + var upperPoint = new OpenLayers.Geometry.Point(coords[0], coords[1],coords[2]); + } else { + var upperPoint = new OpenLayers.Geometry.Point(coords[1], coords[0],coords[2]); + } + } + + if (lowerPoint && upperPoint) { + components.push(new OpenLayers.Geometry.Point(lowerPoint.x, lowerPoint.y)); + components.push(new OpenLayers.Geometry.Point(upperPoint.x, lowerPoint.y)); + components.push(new OpenLayers.Geometry.Point(upperPoint.x, upperPoint.y)); + components.push(new OpenLayers.Geometry.Point(lowerPoint.x, upperPoint.y)); + components.push(new OpenLayers.Geometry.Point(lowerPoint.x, lowerPoint.y)); + + var ring = new OpenLayers.Geometry.LinearRing(components); + envelope = new OpenLayers.Geometry.Polygon([ring]); + } + return envelope; + } + }, + + /** + * Method: parseAttributes + * + * Parameters: + * node - {} + * + * Returns: + * {Object} An attributes object. + */ + parseAttributes: function(node) { + var attributes = {}; + // assume attributes are children of the first type 1 child + var childNode = node.firstChild; + var children, i, child, grandchildren, grandchild, name, value; + while(childNode) { + if(childNode.nodeType == 1) { + // attributes are type 1 children with one type 3 child + children = childNode.childNodes; + for(i=0; i becomes + // {fieldname: null} + attributes[child.nodeName.split(":").pop()] = null; + } + } + } + break; + } + childNode = childNode.nextSibling; + } + return attributes; + }, + + /** + * APIMethod: write + * Generate a GML document string given a list of features. + * + * Parameters: + * features - {Array()} List of features to + * serialize into a string. + * + * Returns: + * {String} A string representing the GML document. + */ + write: function(features) { + if(!(features instanceof Array)) { + features = [features]; + } + var gml = this.createElementNS("http://www.opengis.net/wfs", + "wfs:" + this.collectionName); + for(var i=0; i} The feature to be built as GML. + * + * Returns: + * {DOMElement} A node reprensting the feature in GML. + */ + createFeatureXML: function(feature) { + var geometry = feature.geometry; + var geometryNode = this.buildGeometryNode(geometry); + var geomContainer = this.createElementNS(this.featureNS, + this.featurePrefix + ":" + + this.geometryName); + geomContainer.appendChild(geometryNode); + var featureNode = this.createElementNS(this.gmlns, + "gml:" + this.featureName); + var featureContainer = this.createElementNS(this.featureNS, + this.featurePrefix + ":" + + this.layerName); + var fid = feature.fid || feature.id; + featureContainer.setAttribute("fid", fid); + featureContainer.appendChild(geomContainer); + for(var attr in feature.attributes) { + var attrText = this.createTextNode(feature.attributes[attr]); + var nodename = attr.substring(attr.lastIndexOf(":") + 1); + var attrContainer = this.createElementNS(this.featureNS, + this.featurePrefix + ":" + + nodename); + attrContainer.appendChild(attrText); + featureContainer.appendChild(attrContainer); + } + featureNode.appendChild(featureContainer); + return featureNode; + }, + + /** + * APIMethod: buildGeometryNode + */ + buildGeometryNode: function(geometry) { + if (this.externalProjection && this.internalProjection) { + geometry = geometry.clone(); + geometry.transform(this.internalProjection, + this.externalProjection); + } + var className = geometry.CLASS_NAME; + var type = className.substring(className.lastIndexOf(".") + 1); + var builder = this.buildGeometry[type.toLowerCase()]; + return builder.apply(this, [geometry]); + }, + + /** + * Property: buildGeometry + * Object containing methods to do the actual geometry node building + * based on geometry type. + */ + buildGeometry: { + // TBD retrieve the srs from layer + // srsName is non-standard, so not including it until it's right. + // gml.setAttribute("srsName", + // "http://www.opengis.net/gml/srs/epsg.xml#4326"); + + /** + * Method: buildGeometry.point + * Given an OpenLayers point geometry, create a GML point. + * + * Parameters: + * geometry - {} A point geometry. + * + * Returns: + * {DOMElement} A GML point node. + */ + point: function(geometry) { + var gml = this.createElementNS(this.gmlns, "gml:Point"); + gml.appendChild(this.buildCoordinatesNode(geometry)); + return gml; + }, + + /** + * Method: buildGeometry.multipoint + * Given an OpenLayers multipoint geometry, create a GML multipoint. + * + * Parameters: + * geometry - {} A multipoint geometry. + * + * Returns: + * {DOMElement} A GML multipoint node. + */ + multipoint: function(geometry) { + var gml = this.createElementNS(this.gmlns, "gml:MultiPoint"); + var points = geometry.components; + var pointMember, pointGeom; + for(var i=0; i} A linestring geometry. + * + * Returns: + * {DOMElement} A GML linestring node. + */ + linestring: function(geometry) { + var gml = this.createElementNS(this.gmlns, "gml:LineString"); + gml.appendChild(this.buildCoordinatesNode(geometry)); + return gml; + }, + + /** + * Method: buildGeometry.multilinestring + * Given an OpenLayers multilinestring geometry, create a GML + * multilinestring. + * + * Parameters: + * geometry - {} A multilinestring + * geometry. + * + * Returns: + * {DOMElement} A GML multilinestring node. + */ + multilinestring: function(geometry) { + var gml = this.createElementNS(this.gmlns, "gml:MultiLineString"); + var lines = geometry.components; + var lineMember, lineGeom; + for(var i=0; i} A linearring geometry. + * + * Returns: + * {DOMElement} A GML linearring node. + */ + linearring: function(geometry) { + var gml = this.createElementNS(this.gmlns, "gml:LinearRing"); + gml.appendChild(this.buildCoordinatesNode(geometry)); + return gml; + }, + + /** + * Method: buildGeometry.polygon + * Given an OpenLayers polygon geometry, create a GML polygon. + * + * Parameters: + * geometry - {} A polygon geometry. + * + * Returns: + * {DOMElement} A GML polygon node. + */ + polygon: function(geometry) { + var gml = this.createElementNS(this.gmlns, "gml:Polygon"); + var rings = geometry.components; + var ringMember, ringGeom, type; + for(var i=0; i} A multipolygon + * geometry. + * + * Returns: + * {DOMElement} A GML multipolygon node. + */ + multipolygon: function(geometry) { + var gml = this.createElementNS(this.gmlns, "gml:MultiPolygon"); + var polys = geometry.components; + var polyMember, polyGeom; + for(var i=0; i} A bounds object. + * + * Returns: + * {DOMElement} A GML box node. + */ + bounds: function(bounds) { + var gml = this.createElementNS(this.gmlns, "gml:Box"); + gml.appendChild(this.buildCoordinatesNode(bounds)); + return gml; + } + }, + + /** + * Method: buildCoordinates + * builds the coordinates XmlNode + * (code) + * ... + * (end) + * Parameters: + * geometry - {} + * + * Returns: + * {XmlNode} created xmlNode + */ + buildCoordinatesNode: function(geometry) { + var coordinatesNode = this.createElementNS(this.gmlns, + "gml:coordinates"); + coordinatesNode.setAttribute("decimal", "."); + coordinatesNode.setAttribute("cs", ","); + coordinatesNode.setAttribute("ts", " "); + + var parts = []; + + if(geometry instanceof OpenLayers.Bounds){ + parts.push(geometry.left + "," + geometry.bottom); + parts.push(geometry.right + "," + geometry.top); + } else { + var points = (geometry.components) ? geometry.components : [geometry]; + for(var i=0; i + */ +OpenLayers.Format.GML.Base = OpenLayers.Class(OpenLayers.Format.XML, { + + /** + * Property: namespaces + * {Object} Mapping of namespace aliases to namespace URIs. + */ + namespaces: { + gml: "http://www.opengis.net/gml", + xlink: "http://www.w3.org/1999/xlink", + xsi: "http://www.w3.org/2001/XMLSchema-instance", + wfs: "http://www.opengis.net/wfs" // this is a convenience for reading wfs:FeatureCollection + }, + + /** + * Property: defaultPrefix + */ + defaultPrefix: "gml", + + /** + * Property: schemaLocation + * {String} Schema location for a particular minor version. + */ + schemaLocation: null, + + /** + * APIProperty: featureType + * {Array(String) or String} The local (without prefix) feature typeName(s). + */ + featureType: null, + + /** + * APIProperty: featureNS + * {String} The feature namespace. Must be set in the options at + * construction. + */ + featureNS: null, + + /** + * APIProperty: geometry + * {String} Name of geometry element. Defaults to "geometry". + */ + geometryName: "geometry", + + /** + * APIProperty: extractAttributes + * {Boolean} Extract attributes from GML. Default is true. + */ + extractAttributes: true, + + /** + * APIProperty: srsName + * {String} URI for spatial reference system. This is optional for + * single part geometries and mandatory for collections and multis. + * If set, the srsName attribute will be written for all geometries. + * Default is null. + */ + srsName: null, + + /** + * APIProperty: xy + * {Boolean} Order of the GML coordinate true:(x,y) or false:(y,x) + * Changing is not recommended, a new Format should be instantiated. + */ + xy: true, + + /** + * Property: geometryTypes + * {Object} Maps OpenLayers geometry class names to GML element names. + * Use before accessing this property. + */ + geometryTypes: null, + + /** + * Property: singleFeatureType + * {Boolean} True if there is only 1 featureType, and not an array + * of featuretypes. + */ + singleFeatureType: null, + + /** + * Property: regExes + * Compiled regular expressions for manipulating strings. + */ + regExes: { + trimSpace: (/^\s*|\s*$/g), + removeSpace: (/\s*/g), + splitSpace: (/\s+/), + trimComma: (/\s*,\s*/g) + }, + + /** + * Constructor: OpenLayers.Format.GML.Base + * Instances of this class are not created directly. Use the + * or constructor + * instead. + * + * Parameters: + * options - {Object} An optional object whose properties will be set on + * this instance. + * + * Valid options properties: + * featureType - {Array(String) or String} Local (without prefix) feature + * typeName(s) (required). + * featureNS - {String} Feature namespace (required). + * geometryName - {String} Geometry element name. + */ + initialize: function(options) { + OpenLayers.Format.XML.prototype.initialize.apply(this, [options]); + this.setGeometryTypes(); + if(options && options.featureNS) { + this.setNamespace("feature", options.featureNS); + } + this.singleFeatureType = !options || (typeof options.featureType === "string"); + }, + + /** + * Method: read + * + * Parameters: + * data - {DOMElement} A gml:featureMember element, a gml:featureMembers + * element, or an element containing either of the above at any level. + * + * Returns: + * {Array()} An array of features. + */ + read: function(data) { + if(typeof data == "string") { + data = OpenLayers.Format.XML.prototype.read.apply(this, [data]); + } + if(data && data.nodeType == 9) { + data = data.documentElement; + } + var features = []; + this.readNode(data, {features: features}); + if(features.length == 0) { + // look for gml:featureMember elements + var elements = this.getElementsByTagNameNS( + data, this.namespaces.gml, "featureMember" + ); + if(elements.length) { + for(var i=0, len=elements.length; i 0) { + obj.bounds = container.components[0]; + } + }, + "Point": function(node, container) { + var obj = {points: []}; + this.readChildNodes(node, obj); + if(!container.components) { + container.components = []; + } + container.components.push(obj.points[0]); + }, + "coordinates": function(node, obj) { + var str = this.getChildValue(node).replace( + this.regExes.trimSpace, "" + ); + str = str.replace(this.regExes.trimComma, ","); + var pointList = str.split(this.regExes.splitSpace); + var coords; + var numPoints = pointList.length; + var points = new Array(numPoints); + for(var i=0; i) | OpenLayers.Feature.Vector} + * An array of features or a single feature. + * + * Returns: + * {String} Given an array of features, a doc with a gml:featureMembers + * element will be returned. Given a single feature, a doc with a + * gml:featureMember element will be returned. + */ + write: function(features) { + var name; + if(features instanceof Array) { + name = "featureMembers"; + } else { + name = "featureMember"; + } + var root = this.writeNode("gml:" + name, features); + this.setAttributeNS( + root, this.namespaces["xsi"], + "xsi:schemaLocation", this.schemaLocation + ); + + return OpenLayers.Format.XML.prototype.write.apply(this, [root]); + }, + + /** + * Property: writers + * As a compliment to the readers property, this structure contains public + * writing functions grouped by namespace alias and named like the + * node names they produce. + */ + writers: { + "gml": { + "featureMember": function(feature) { + var node = this.createElementNSPlus("gml:featureMember"); + this.writeNode("feature:_typeName", feature, node); + return node; + }, + "MultiPoint": function(geometry) { + var node = this.createElementNSPlus("gml:MultiPoint"); + for(var i=0; i mapping. + */ + setGeometryTypes: function() { + this.geometryTypes = { + "OpenLayers.Geometry.Point": "Point", + "OpenLayers.Geometry.MultiPoint": "MultiPoint", + "OpenLayers.Geometry.LineString": "LineString", + "OpenLayers.Geometry.MultiLineString": "MultiLineString", + "OpenLayers.Geometry.Polygon": "Polygon", + "OpenLayers.Geometry.MultiPolygon": "MultiPolygon", + "OpenLayers.Geometry.Collection": "GeometryCollection" + }; + }, + + CLASS_NAME: "OpenLayers.Format.GML.Base" + +}); + --- /dev/null +++ b/openlayers/lib/OpenLayers/Format/GML/v2.js @@ -1,1 +1,193 @@ +/* Copyright (c) 2006-2008 MetaCarta, Inc., published under the Clear BSD + * license. See http://svn.openlayers.org/trunk/openlayers/license.txt for the + * full text of the license. */ +/** + * @requires OpenLayers/Format/GML/Base.js + */ + +/** + * Class: OpenLayers.Format.GML.v2 + * Parses GML version 2. + * + * Inherits from: + * - + */ +OpenLayers.Format.GML.v2 = OpenLayers.Class(OpenLayers.Format.GML.Base, { + + /** + * Property: schemaLocation + * {String} Schema location for a particular minor version. + */ + schemaLocation: "http://www.opengis.net/gml http://schemas.opengis.net/gml/2.1.2/feature.xsd", + + /** + * Constructor: OpenLayers.Format.GML.v2 + * Create a parser for GML v2. + * + * Parameters: + * options - {Object} An optional object whose properties will be set on + * this instance. + * + * Valid options properties: + * featureType - {String} Local (without prefix) feature typeName (required). + * featureNS - {String} Feature namespace (required). + * geometryName - {String} Geometry element name. + */ + initialize: function(options) { + OpenLayers.Format.GML.Base.prototype.initialize.apply(this, [options]); + }, + + /** + * Property: readers + * Contains public functions, grouped by namespace prefix, that will + * be applied when a namespaced node is found matching the function + * name. The function will be applied in the scope of this parser + * with two arguments: the node being read and a context object passed + * from the parent. + */ + readers: { + "gml": OpenLayers.Util.applyDefaults({ + "outerBoundaryIs": function(node, container) { + var obj = {}; + this.readChildNodes(node, obj); + container.outer = obj.components[0]; + }, + "innerBoundaryIs": function(node, container) { + var obj = {}; + this.readChildNodes(node, obj); + container.inner.push(obj.components[0]); + }, + "Box": function(node, container) { + var obj = {}; + this.readChildNodes(node, obj); + if(!container.components) { + container.components = []; + } + var min = obj.points[0]; + var max = obj.points[1]; + container.components.push( + new OpenLayers.Bounds(min.x, min.y, max.x, max.y) + ); + } + }, OpenLayers.Format.GML.Base.prototype.readers["gml"]), + "feature": OpenLayers.Format.GML.Base.prototype.readers["feature"], + "wfs": OpenLayers.Format.GML.Base.prototype.readers["wfs"] + }, + + /** + * Method: write + * + * Parameters: + * features - {Array() | OpenLayers.Feature.Vector} + * An array of features or a single feature. + * + * Returns: + * {String} Given an array of features, a doc with a gml:featureMembers + * element will be returned. Given a single feature, a doc with a + * gml:featureMember element will be returned. + */ + write: function(features) { + var name; + if(features instanceof Array) { + // GML2 only has abstract feature collections + // wfs provides a feature collection from a well-known schema + name = "wfs:FeatureCollection"; + } else { + name = "gml:featureMember"; + } + var root = this.writeNode(name, features); + this.setAttributeNS( + root, this.namespaces["xsi"], + "xsi:schemaLocation", this.schemaLocation + ); + + return OpenLayers.Format.XML.prototype.write.apply(this, [root]); + }, + + /** + * Property: writers + * As a compliment to the readers property, this structure contains public + * writing functions grouped by namespace alias and named like the + * node names they produce. + */ + writers: { + "gml": OpenLayers.Util.applyDefaults({ + "Point": function(geometry) { + var node = this.createElementNSPlus("gml:Point"); + this.writeNode("coordinates", [geometry], node); + return node; + }, + "coordinates": function(points) { + var numPoints = points.length; + var parts = new Array(numPoints); + var point; + for(var i=0; i + */ +OpenLayers.Format.GML.v3 = OpenLayers.Class(OpenLayers.Format.GML.Base, { + + /** + * Property: schemaLocation + * {String} Schema location for a particular minor version. The writers + * conform with the Simple Features Profile for GML. + */ + schemaLocation: "http://www.opengis.net/gml http://schemas.opengis.net/gml/3.1.1/profiles/gmlsfProfile/1.0.0/gmlsf.xsd", + + /** + * Property: curve + * {Boolean} Write gml:Curve instead of gml:LineString elements. This also + * affects the elements in multi-part geometries. Default is false. + * To write gml:Curve elements instead of gml:LineString, set curve + * to true in the options to the contstructor (cannot be changed after + * instantiation). + */ + curve: false, + + /** + * Property: multiCurve + * {Boolean} Write gml:MultiCurve instead of gml:MultiLineString. Since + * the latter is deprecated in GML 3, the default is true. To write + * gml:MultiLineString instead of gml:MultiCurve, set multiCurve to + * false in the options to the constructor (cannot be changed after + * instantiation). + */ + multiCurve: true, + + /** + * Property: surface + * {Boolean} Write gml:Surface instead of gml:Polygon elements. This also + * affects the elements in multi-part geometries. Default is false. + * To write gml:Surface elements instead of gml:Polygon, set surface + * to true in the options to the contstructor (cannot be changed after + * instantiation). + */ + surface: false, + + /** + * Property: multiSurface + * {Boolean} Write gml:multiSurface instead of gml:MultiPolygon. Since + * the latter is deprecated in GML 3, the default is true. To write + * gml:MultiPolygon instead of gml:multiSurface, set multiSurface to + * false in the options to the constructor (cannot be changed after + * instantiation). + */ + multiSurface: true, + + /** + * Constructor: OpenLayers.Format.GML.v3 + * Create a parser for GML v3. + * + * Parameters: + * options - {Object} An optional object whose properties will be set on + * this instance. + * + * Valid options properties: + * featureType - {String} Local (without prefix) feature typeName (required). + * featureNS - {String} Feature namespace (required). + * geometryName - {String} Geometry element name. + */ + initialize: function(options) { + OpenLayers.Format.GML.Base.prototype.initialize.apply(this, [options]); + }, + + /** + * Property: readers + * Contains public functions, grouped by namespace prefix, that will + * be applied when a namespaced node is found matching the function + * name. The function will be applied in the scope of this parser + * with two arguments: the node being read and a context object passed + * from the parent. + */ + readers: { + "gml": OpenLayers.Util.applyDefaults({ + "featureMembers": function(node, obj) { + this.readChildNodes(node, obj); + }, + "Curve": function(node, container) { + var obj = {points: []}; + this.readChildNodes(node, obj); + if(!container.components) { + container.components = []; + } + container.components.push( + new OpenLayers.Geometry.LineString(obj.points) + ); + }, + "segments": function(node, obj) { + this.readChildNodes(node, obj); + }, + "LineStringSegment": function(node, container) { + var obj = {}; + this.readChildNodes(node, obj); + if(obj.points) { + Array.prototype.push.apply(container.points, obj.points); + } + }, + "pos": function(node, obj) { + var str = this.getChildValue(node).replace( + this.regExes.trimSpace, "" + ); + var coords = str.split(this.regExes.splitSpace); + var point; + if(this.xy) { + point = new OpenLayers.Geometry.Point( + coords[0], coords[1], coords[2] + ); + } else { + point = new OpenLayers.Geometry.Point( + coords[1], coords[0], coords[2] + ); + } + obj.points = [point]; + }, + "posList": function(node, obj) { + var str = this.getChildValue(node).replace( + this.regExes.trimSpace, "" + ); + var coords = str.split(this.regExes.splitSpace); + var dim = parseInt(node.getAttribute("dimension")) || 2; + var j, x, y, z; + var numPoints = coords.length / dim; + var points = new Array(numPoints); + for(var i=0, len=coords.length; i 0) { + container.components = [ + new OpenLayers.Geometry.MultiLineString(obj.components) + ]; + } + }, + "curveMember": function(node, obj) { + this.readChildNodes(node, obj); + }, + "MultiSurface": function(node, container) { + var obj = {components: []}; + this.readChildNodes(node, obj); + if(obj.components.length > 0) { + container.components = [ + new OpenLayers.Geometry.MultiPolygon(obj.components) + ]; + } + }, + "surfaceMember": function(node, obj) { + this.readChildNodes(node, obj); + }, + "surfaceMembers": function(node, obj) { + this.readChildNodes(node, obj); + }, + "pointMembers": function(node, obj) { + this.readChildNodes(node, obj); + }, + "lineStringMembers": function(node, obj) { + this.readChildNodes(node, obj); + }, + "polygonMembers": function(node, obj) { + this.readChildNodes(node, obj); + }, + "geometryMembers": function(node, obj) { + this.readChildNodes(node, obj); + }, + "Envelope": function(node, container) { + var obj = {points: new Array(2)}; + this.readChildNodes(node, obj); + if(!container.components) { + container.components = []; + } + var min = obj.points[0]; + var max = obj.points[1]; + container.components.push( + new OpenLayers.Bounds(min.x, min.y, max.x, max.y) + ); + }, + "lowerCorner": function(node, container) { + var obj = {}; + this.readers.gml.pos.apply(this, [node, obj]); + container.points[0] = obj.points[0]; + }, + "upperCorner": function(node, container) { + var obj = {}; + this.readers.gml.pos.apply(this, [node, obj]); + container.points[1] = obj.points[0]; + } + }, OpenLayers.Format.GML.Base.prototype.readers["gml"]), + "feature": OpenLayers.Format.GML.Base.prototype.readers["feature"], + "wfs": OpenLayers.Format.GML.Base.prototype.readers["wfs"] + }, + + /** + * Method: write + * + * Parameters: + * features - {Array() | OpenLayers.Feature.Vector} + * An array of features or a single feature. + * + * Returns: + * {String} Given an array of features, a doc with a gml:featureMembers + * element will be returned. Given a single feature, a doc with a + * gml:featureMember element will be returned. + */ + write: function(features) { + var name; + if(features instanceof Array) { + name = "featureMembers"; + } else { + name = "featureMember"; + } + var root = this.writeNode("gml:" + name, features); + this.setAttributeNS( + root, this.namespaces["xsi"], + "xsi:schemaLocation", this.schemaLocation + ); + + return OpenLayers.Format.XML.prototype.write.apply(this, [root]); + }, + + /** + * Property: writers + * As a compliment to the readers property, this structure contains public + * writing functions grouped by namespace alias and named like the + * node names they produce. + */ + writers: { + "gml": OpenLayers.Util.applyDefaults({ + "featureMembers": function(features) { + var node = this.createElementNSPlus("gml:featureMembers"); + for(var i=0, len=features.length; i mapping. + */ + setGeometryTypes: function() { + this.geometryTypes = { + "OpenLayers.Geometry.Point": "Point", + "OpenLayers.Geometry.MultiPoint": "MultiPoint", + "OpenLayers.Geometry.LineString": (this.curve === true) ? "Curve": "LineString", + "OpenLayers.Geometry.MultiLineString": (this.multiCurve === false) ? "MultiLineString" : "MultiCurve", + "OpenLayers.Geometry.Polygon": (this.surface === true) ? "Surface" : "Polygon", + "OpenLayers.Geometry.MultiPolygon": (this.multiSurface === false) ? "MultiPolygon" : "MultiSurface", + "OpenLayers.Geometry.Collection": "GeometryCollection" + }; + }, + + CLASS_NAME: "OpenLayers.Format.GML.v3" + +}); + --- /dev/null +++ b/openlayers/lib/OpenLayers/Format/GPX.js @@ -1,1 +1,180 @@ +/* Copyright (c) 2006-2007 MetaCarta, Inc., published under a modified BSD license. + * See http://svn.openlayers.org/trunk/openlayers/repository-license.txt + * for the full text of the license. */ +/** + * @requires OpenLayers/Format/XML.js + * @requires OpenLayers/Feature/Vector.js + * @requires OpenLayers/Geometry/Point.js + * @requires OpenLayers/Geometry/LineString.js + */ + +/** + * Class: OpenLayers.Format.GPX + * Read/write GPX parser. Create a new instance with the + * constructor. + * + * Inherits from: + * - + */ +OpenLayers.Format.GPX = OpenLayers.Class(OpenLayers.Format.XML, { + /** + * APIProperty: extractWaypoints + * {Boolean} Extract waypoints from GPX. (default: true) + */ + extractWaypoints: true, + + /** + * APIProperty: extractTracks + * {Boolean} Extract tracks from GPX. (default: true) + */ + extractTracks: true, + + /** + * APIProperty: extractRoutes + * {Boolean} Extract routes from GPX. (default: true) + */ + extractRoutes: true, + + /** + * APIProperty: extractAttributes + * {Boolean} Extract feature attributes from GPX. (default: true) + * NOTE: Attributes as part of extensions to the GPX standard may not + * be extracted. + */ + extractAttributes: true, + + /** + * Constructor: OpenLayers.Format.GPX + * Create a new parser for GPX. + * + * Parameters: + * options - {Object} An optional object whose properties will be set on + * this instance. + */ + initialize: function(options) { + OpenLayers.Format.XML.prototype.initialize.apply(this, [options]); + }, + + /** + * APIMethod: read + * Return a list of features from a GPX doc + * + * Parameters: + * doc - {Element} + * + * Returns: + * An Array of s + */ + read: function(doc) { + if (typeof doc == "string") { + doc = OpenLayers.Format.XML.prototype.read.apply(this, [doc]); + } + var features = []; + + if(this.extractTracks) { + var tracks = doc.getElementsByTagName("trk"); + for (var i=0, len=tracks.length; i} a trkseg or rte node to parse + * segmentType - {String} nodeName of waypoints that form the line + * + * Returns: + * {} A linestring geometry + */ + extractSegment: function(segment, segmentType) { + var points = this.getElementsByTagNameNS(segment, segment.namespaceURI, segmentType); + var point_features = []; + for (var i = 0, len = points.length; i < len; i++) { + point_features.push(new OpenLayers.Geometry.Point(points[i].getAttribute("lon"), points[i].getAttribute("lat"))); + } + return new OpenLayers.Geometry.LineString(point_features); + }, + + /** + * Method: parseAttributes + * + * Parameters: + * node - {} + * + * Returns: + * {Object} An attributes object. + */ + parseAttributes: function(node) { + // node is either a wpt, trk or rte + // attributes are children of the form value + var attributes = {}; + var attrNode = node.firstChild; + while(attrNode) { + if(attrNode.nodeType == 1) { + var value = attrNode.firstChild; + if(value.nodeType == 3 || value.nodeType == 4) { + name = (attrNode.prefix) ? + attrNode.nodeName.split(":")[1] : + attrNode.nodeName; + if(name != "trkseg" && name != "rtept") { + attributes[name] = value.nodeValue; + } + } + } + attrNode = attrNode.nextSibling; + } + return attributes; + }, + + CLASS_NAME: "OpenLayers.Format.GPX" +}); + --- /dev/null +++ b/openlayers/lib/OpenLayers/Format/GeoJSON.js @@ -1,1 +1,708 @@ - +/* Copyright (c) 2006-2008 MetaCarta, Inc., published under the Clear BSD + * license. See http://svn.openlayers.org/trunk/openlayers/license.txt for the + * full text of the license. */ + +/** + * @requires OpenLayers/Format/JSON.js + * @requires OpenLayers/Feature/Vector.js + * @requires OpenLayers/Geometry/Point.js + * @requires OpenLayers/Geometry/MultiPoint.js + * @requires OpenLayers/Geometry/LineString.js + * @requires OpenLayers/Geometry/MultiLineString.js + * @requires OpenLayers/Geometry/Polygon.js + * @requires OpenLayers/Geometry/MultiPolygon.js + * @requires OpenLayers/Console.js + */ + +/** + * Class: OpenLayers.Format.GeoJSON + * Read and write GeoJSON. Create a new parser with the + * constructor. + * + * Inherits from: + * - + */ +OpenLayers.Format.GeoJSON = OpenLayers.Class(OpenLayers.Format.JSON, { + + /** + * Constructor: OpenLayers.Format.GeoJSON + * Create a new parser for GeoJSON. + * + * Parameters: + * options - {Object} An optional object whose properties will be set on + * this instance. + */ + initialize: function(options) { + OpenLayers.Format.JSON.prototype.initialize.apply(this, [options]); + }, + + /** + * APIMethod: read + * Deserialize a GeoJSON string. + * + * Parameters: + * json - {String} A GeoJSON string + * type - {String} Optional string that determines the structure of + * the output. Supported values are "Geometry", "Feature", and + * "FeatureCollection". If absent or null, a default of + * "FeatureCollection" is assumed. + * filter - {Function} A function which will be called for every key and + * value at every level of the final result. Each value will be + * replaced by the result of the filter function. This can be used to + * reform generic objects into instances of classes, or to transform + * date strings into Date objects. + * + * Returns: + * {Object} The return depends on the value of the type argument. If type + * is "FeatureCollection" (the default), the return will be an array + * of . If type is "Geometry", the input json + * must represent a single geometry, and the return will be an + * . If type is "Feature", the input json must + * represent a single feature, and the return will be an + * . + */ + read: function(json, type, filter) { + type = (type) ? type : "FeatureCollection"; + var results = null; + var obj = null; + if (typeof json == "string") { + obj = OpenLayers.Format.JSON.prototype.read.apply(this, + [json, filter]); + } else { + obj = json; + } + if(!obj) { + OpenLayers.Console.error("Bad JSON: " + json); + } else if(typeof(obj.type) != "string") { + OpenLayers.Console.error("Bad GeoJSON - no type: " + json); + } else if(this.isValidType(obj, type)) { + switch(type) { + case "Geometry": + try { + results = this.parseGeometry(obj); + } catch(err) { + OpenLayers.Console.error(err); + } + break; + case "Feature": + try { + results = this.parseFeature(obj); + results.type = "Feature"; + } catch(err) { + OpenLayers.Console.error(err); + } + break; + case "FeatureCollection": + // for type FeatureCollection, we allow input to be any type + results = []; + switch(obj.type) { + case "Feature": + try { + results.push(this.parseFeature(obj)); + } catch(err) { + results = null; + OpenLayers.Console.error(err); + } + break; + case "FeatureCollection": + for(var i=0, len=obj.features.length; i. + * + * Parameters: + * obj - {Object} An object created from a GeoJSON object + * + * Returns: + * {} A feature. + */ + parseFeature: function(obj) { + var feature, geometry, attributes, bbox; + attributes = (obj.properties) ? obj.properties : {}; + bbox = (obj.geometry && obj.geometry.bbox) || obj.bbox; + try { + geometry = this.parseGeometry(obj.geometry); + } catch(err) { + // deal with bad geometries + throw err; + } + feature = new OpenLayers.Feature.Vector(geometry, attributes); + if(bbox) { + feature.bounds = OpenLayers.Bounds.fromArray(bbox); + } + if(obj.id) { + feature.fid = obj.id; + } + return feature; + }, + + /** + * Method: parseGeometry + * Convert a geometry object from GeoJSON into an . + * + * Parameters: + * obj - {Object} An object created from a GeoJSON object + * + * Returns: + * {} A geometry. + */ + parseGeometry: function(obj) { + if (obj == null) { + return null; + } + var geometry, collection = false; + if(obj.type == "GeometryCollection") { + if(!(obj.geometries instanceof Array)) { + throw "GeometryCollection must have geometries array: " + obj; + } + var numGeom = obj.geometries.length; + var components = new Array(numGeom); + for(var i=0; i. + * + * Parameters: + * array - {Object} The coordinates array from the GeoJSON fragment. + * + * Returns: + * {} A geometry. + */ + "point": function(array) { + if(array.length != 2) { + throw "Only 2D points are supported: " + array; + } + return new OpenLayers.Geometry.Point(array[0], array[1]); + }, + + /** + * Method: parseCoords.multipoint + * Convert a coordinate array from GeoJSON into an + * . + * + * Parameters: + * array {Object} The coordinates array from the GeoJSON fragment. + * + * Returns: + * {} A geometry. + */ + "multipoint": function(array) { + var points = []; + var p = null; + for(var i=0, len=array.length; i. + * + * Parameters: + * array - {Object} The coordinates array from the GeoJSON fragment. + * + * Returns: + * {} A geometry. + */ + "linestring": function(array) { + var points = []; + var p = null; + for(var i=0, len=array.length; i. + * + * Parameters: + * array - {Object} The coordinates array from the GeoJSON fragment. + * + * Returns: + * {} A geometry. + */ + "multilinestring": function(array) { + var lines = []; + var l = null; + for(var i=0, len=array.length; i. + * + * Returns: + * {} A geometry. + */ + "polygon": function(array) { + var rings = []; + var r, l; + for(var i=0, len=array.length; i. + * + * Parameters: + * array - {Object} The coordinates array from the GeoJSON fragment. + * + * Returns: + * {} A geometry. + */ + "multipolygon": function(array) { + var polys = []; + var p = null; + for(var i=0, len=array.length; i. + * + * Parameters: + * array - {Object} The coordinates array from the GeoJSON fragment. + * + * Returns: + * {} A geometry. + */ + "box": function(array) { + if(array.length != 2) { + throw "GeoJSON box coordinates must have 2 elements"; + } + return new OpenLayers.Geometry.Polygon([ + new OpenLayers.Geometry.LinearRing([ + new OpenLayers.Geometry.Point(array[0][0], array[0][1]), + new OpenLayers.Geometry.Point(array[1][0], array[0][1]), + new OpenLayers.Geometry.Point(array[1][0], array[1][1]), + new OpenLayers.Geometry.Point(array[0][0], array[1][1]), + new OpenLayers.Geometry.Point(array[0][0], array[0][1]) + ]) + ]); + } + + }, + + /** + * APIMethod: write + * Serialize a feature, geometry, array of features into a GeoJSON string. + * + * Parameters: + * obj - {Object} An , , + * or an array of features. + * pretty - {Boolean} Structure the output with newlines and indentation. + * Default is false. + * + * Returns: + * {String} The GeoJSON string representation of the input geometry, + * features, or array of features. + */ + write: function(obj, pretty) { + var geojson = { + "type": null + }; + if(obj instanceof Array) { + geojson.type = "FeatureCollection"; + var numFeatures = obj.length; + geojson.features = new Array(numFeatures); + for(var i=0; i} + * + * Returns: + * {Object} An object which can be assigned to the crs property + * of a GeoJSON object. + */ + createCRSObject: function(object) { + var proj = object.layer.projection.toString(); + var crs = {}; + if (proj.match(/epsg:/i)) { + var code = parseInt(proj.substring(proj.indexOf(":") + 1)); + if (code == 4326) { + crs = { + "type": "OGC", + "properties": { + "urn": "urn:ogc:def:crs:OGC:1.3:CRS84" + } + }; + } else { + crs = { + "type": "EPSG", + "properties": { + "code": code + } + }; + } + } + return crs; + }, + + /** + * Property: extract + * Object with properties corresponding to the GeoJSON types. + * Property values are functions that do the actual value extraction. + */ + extract: { + /** + * Method: extract.feature + * Return a partial GeoJSON object representing a single feature. + * + * Parameters: + * feature - {} + * + * Returns: + * {Object} An object representing the point. + */ + 'feature': function(feature) { + var geom = this.extract.geometry.apply(this, [feature.geometry]); + return { + "type": "Feature", + "id": feature.fid == null ? feature.id : feature.fid, + "properties": feature.attributes, + "geometry": geom + }; + }, + + /** + * Method: extract.geometry + * Return a GeoJSON object representing a single geometry. + * + * Parameters: + * geometry - {} + * + * Returns: + * {Object} An object representing the geometry. + */ + 'geometry': function(geometry) { + if (geometry == null) { + return null; + } + if (this.internalProjection && this.externalProjection) { + geometry = geometry.clone(); + geometry.transform(this.internalProjection, + this.externalProjection); + } + var geometryType = geometry.CLASS_NAME.split('.')[2]; + var data = this.extract[geometryType.toLowerCase()].apply(this, [geometry]); + var json; + if(geometryType == "Collection") { + json = { + "type": "GeometryCollection", + "geometries": data + }; + } else { + json = { + "type": geometryType, + "coordinates": data + }; + } + + return json; + }, + + /** + * Method: extract.point + * Return an array of coordinates from a point. + * + * Parameters: + * point - {} + * + * Returns: + * {Array} An array of coordinates representing the point. + */ + 'point': function(point) { + return [point.x, point.y]; + }, + + /** + * Method: extract.multipoint + * Return an array of point coordinates from a multipoint. + * + * Parameters: + * multipoint - {} + * + * Returns: + * {Array} An array of point coordinate arrays representing + * the multipoint. + */ + 'multipoint': function(multipoint) { + var array = []; + for(var i=0, len=multipoint.components.length; i} + * + * Returns: + * {Array} An array of coordinate arrays representing + * the linestring. + */ + 'linestring': function(linestring) { + var array = []; + for(var i=0, len=linestring.components.length; i} + * + * Returns: + * {Array} An array of linestring arrays representing + * the multilinestring. + */ + 'multilinestring': function(multilinestring) { + var array = []; + for(var i=0, len=multilinestring.components.length; i} + * + * Returns: + * {Array} An array of linear ring arrays representing the polygon. + */ + 'polygon': function(polygon) { + var array = []; + for(var i=0, len=polygon.components.length; i} + * + * Returns: + * {Array} An array of polygon arrays representing + * the multipolygon + */ + 'multipolygon': function(multipolygon) { + var array = []; + for(var i=0, len=multipolygon.components.length; i} + * + * Returns: + * {Array} An array of geometry objects representing the geometry + * collection. + */ + 'collection': function(collection) { + var len = collection.components.length; + var array = new Array(len); + for(var i=0; i constructor. + * + * Inherits from: + * - + */ +OpenLayers.Format.GeoRSS = OpenLayers.Class(OpenLayers.Format.XML, { + + /** + * APIProperty: rssns + * {String} RSS namespace to use. Defaults to + * "http://backend.userland.com/rss2" + */ + rssns: "http://backend.userland.com/rss2", + + /** + * APIProperty: featurens + * {String} Feature Attributes namespace. Defaults to + * "http://mapserver.gis.umn.edu/mapserver" + */ + featureNS: "http://mapserver.gis.umn.edu/mapserver", + + /** + * APIProperty: georssns + * {String} GeoRSS namespace to use. Defaults to + * "http://www.georss.org/georss" + */ + georssns: "http://www.georss.org/georss", + + /** + * APIProperty: geons + * {String} W3C Geo namespace to use. Defaults to + * "http://www.w3.org/2003/01/geo/wgs84_pos#" + */ + geons: "http://www.w3.org/2003/01/geo/wgs84_pos#", + + /** + * APIProperty: featureTitle + * {String} Default title for features. Defaults to "Untitled" + */ + featureTitle: "Untitled", + + /** + * APIProperty: featureDescription + * {String} Default description for features. Defaults to "No Description" + */ + featureDescription: "No Description", + + /** + * Property: gmlParse + * {Object} GML Format object for parsing features + * Non-API and only created if necessary + */ + gmlParser: null, + + /** + * APIProperty: xy + * {Boolean} Order of the GML coordinate: true:(x,y) or false:(y,x) + * For GeoRSS the default is (y,x), therefore: false + */ + xy: false, + + /** + * Constructor: OpenLayers.Format.GeoRSS + * Create a new parser for GeoRSS. + * + * Parameters: + * options - {Object} An optional object whose properties will be set on + * this instance. + */ + initialize: function(options) { + OpenLayers.Format.XML.prototype.initialize.apply(this, [options]); + }, + + /** + * Method: createGeometryFromItem + * Return a geometry from a GeoRSS Item. + * + * Parameters: + * item - {DOMElement} A GeoRSS item node. + * + * Returns: + * {} A geometry representing the node. + */ + createGeometryFromItem: function(item) { + var point = this.getElementsByTagNameNS(item, this.georssns, "point"); + var lat = this.getElementsByTagNameNS(item, this.geons, 'lat'); + var lon = this.getElementsByTagNameNS(item, this.geons, 'long'); + + var line = this.getElementsByTagNameNS(item, + this.georssns, + "line"); + var polygon = this.getElementsByTagNameNS(item, + this.georssns, + "polygon"); + var where = this.getElementsByTagNameNS(item, + this.georssns, + "where"); + var box = this.getElementsByTagNameNS(item, + this.georssns, + "box"); + + if (point.length > 0 || (lat.length > 0 && lon.length > 0)) { + var location; + if (point.length > 0) { + location = OpenLayers.String.trim( + point[0].firstChild.nodeValue).split(/\s+/); + if (location.length !=2) { + location = OpenLayers.String.trim( + point[0].firstChild.nodeValue).split(/\s*,\s*/); + } + } else { + location = [parseFloat(lat[0].firstChild.nodeValue), + parseFloat(lon[0].firstChild.nodeValue)]; + } + + var geometry = new OpenLayers.Geometry.Point(parseFloat(location[1]), + parseFloat(location[0])); + + } else if (line.length > 0) { + var coords = OpenLayers.String.trim(this.concatChildValues(line[0])).split(/\s+/); + var components = []; + var point; + for (var i=0, len=coords.length; i 0) { + var coords = OpenLayers.String.trim(this.concatChildValues(polygon[0])).split(/\s+/); + var components = []; + var point; + for (var i=0, len=coords.length; i 0) { + if (!this.gmlParser) { + this.gmlParser = new OpenLayers.Format.GML({'xy': this.xy}); + } + var feature = this.gmlParser.parseFeature(where[0]); + geometry = feature.geometry; + } else if (box.length > 0) { + var coords = OpenLayers.String.trim(box[0].firstChild.nodeValue).split(/\s+/); + var components = []; + var point; + if (coords.length > 3) { + point = new OpenLayers.Geometry.Point(parseFloat(coords[1]), + parseFloat(coords[0])); + components.push(point); + point = new OpenLayers.Geometry.Point(parseFloat(coords[1]), + parseFloat(coords[2])); + components.push(point); + point = new OpenLayers.Geometry.Point(parseFloat(coords[3]), + parseFloat(coords[2])); + components.push(point); + point = new OpenLayers.Geometry.Point(parseFloat(coords[3]), + parseFloat(coords[0])); + components.push(point); + point = new OpenLayers.Geometry.Point(parseFloat(coords[1]), + parseFloat(coords[0])); + components.push(point); + } + geometry = new OpenLayers.Geometry.Polygon([new OpenLayers.Geometry.LinearRing(components)]); + } + + if (geometry && this.internalProjection && this.externalProjection) { + geometry.transform(this.externalProjection, + this.internalProjection); + } + + return geometry; + }, + + /** + * Method: createFeatureFromItem + * Return a feature from a GeoRSS Item. + * + * Parameters: + * item - {DOMElement} A GeoRSS item node. + * + * Returns: + * {} A feature representing the item. + */ + createFeatureFromItem: function(item) { + var geometry = this.createGeometryFromItem(item); + + /* Provide defaults for title and description */ + var title = this.getChildValue(item, "*", "title", this.featureTitle); + + /* First try RSS descriptions, then Atom summaries */ + var description = this.getChildValue( + item, "*", "description", + this.getChildValue(item, "*", "content", + this.getChildValue(item, "*", "summary", this.featureDescription))); + + /* If no link URL is found in the first child node, try the + href attribute */ + var link = this.getChildValue(item, "*", "link"); + if(!link) { + try { + link = this.getElementsByTagNameNS(item, "*", "link")[0].getAttribute("href"); + } catch(e) { + link = null; + } + } + + var id = this.getChildValue(item, "*", "id", null); + + var data = { + "title": title, + "description": description, + "link": link + }; + var feature = new OpenLayers.Feature.Vector(geometry, data); + feature.fid = id; + return feature; + }, + + /** + * Method: getChildValue + * + * Parameters: + * node - {DOMElement} + * nsuri - {String} Child node namespace uri ("*" for any). + * name - {String} Child node name. + * def - {String} Optional string default to return if no child found. + * + * Returns: + * {String} The value of the first child with the given tag name. Returns + * default value or empty string if none found. + */ + getChildValue: function(node, nsuri, name, def) { + var value; + var eles = this.getElementsByTagNameNS(node, nsuri, name); + if(eles && eles[0] && eles[0].firstChild + && eles[0].firstChild.nodeValue) { + value = eles[0].firstChild.nodeValue; + } else { + value = (def == undefined) ? "" : def; + } + return value; + }, + + /** + * APIMethod: read + * Return a list of features from a GeoRSS doc + + * Parameters: + * data - {Element} + * + * Returns: + * An Array of s + */ + read: function(doc) { + if (typeof doc == "string") { + doc = OpenLayers.Format.XML.prototype.read.apply(this, [doc]); + } + + /* Try RSS items first, then Atom entries */ + var itemlist = null; + itemlist = this.getElementsByTagNameNS(doc, '*', 'item'); + if (itemlist.length == 0) { + itemlist = this.getElementsByTagNameNS(doc, '*', 'entry'); + } + + var numItems = itemlist.length; + var features = new Array(numItems); + for(var i=0; i)} List of features to serialize into a string. + */ + write: function(features) { + var georss; + if(features instanceof Array) { + georss = this.createElementNS(this.rssns, "rss"); + for(var i=0, len=features.length; i, and build a geometry for it. + * + * Parameters: + * feature - {} + * + * Returns: + * {DOMElement} + */ + createFeatureXML: function(feature) { + var geometryNode = this.buildGeometryNode(feature.geometry); + var featureNode = this.createElementNS(this.rssns, "item"); + var titleNode = this.createElementNS(this.rssns, "title"); + titleNode.appendChild(this.createTextNode(feature.attributes.title ? feature.attributes.title : "")); + var descNode = this.createElementNS(this.rssns, "description"); + descNode.appendChild(this.createTextNode(feature.attributes.description ? feature.attributes.description : "")); + featureNode.appendChild(titleNode); + featureNode.appendChild(descNode); + if (feature.attributes.link) { + var linkNode = this.createElementNS(this.rssns, "link"); + linkNode.appendChild(this.createTextNode(feature.attributes.link)); + featureNode.appendChild(linkNode); + } + for(var attr in feature.attributes) { + if (attr == "link" || attr == "title" || attr == "description") { continue; } + var attrText = this.createTextNode(feature.attributes[attr]); + var nodename = attr; + if (attr.search(":") != -1) { + nodename = attr.split(":")[1]; + } + var attrContainer = this.createElementNS(this.featureNS, "feature:"+nodename); + attrContainer.appendChild(attrText); + featureNode.appendChild(attrContainer); + } + featureNode.appendChild(geometryNode); + return featureNode; + }, + + /** + * Method: buildGeometryNode + * builds a GeoRSS node with a given geometry + * + * Parameters: + * geometry - {} + * + * Returns: + * {DOMElement} A gml node. + */ + buildGeometryNode: function(geometry) { + if (this.internalProjection && this.externalProjection) { + geometry = geometry.clone(); + geometry.transform(this.internalProjection, + this.externalProjection); + } + var node; + // match Polygon + if (geometry.CLASS_NAME == "OpenLayers.Geometry.Polygon") { + node = this.createElementNS(this.georssns, 'georss:polygon'); + + node.appendChild(this.buildCoordinatesNode(geometry.components[0])); + } + // match LineString + else if (geometry.CLASS_NAME == "OpenLayers.Geometry.LineString") { + node = this.createElementNS(this.georssns, 'georss:line'); + + node.appendChild(this.buildCoordinatesNode(geometry)); + } + // match Point + else if (geometry.CLASS_NAME == "OpenLayers.Geometry.Point") { + node = this.createElementNS(this.georssns, 'georss:point'); + node.appendChild(this.buildCoordinatesNode(geometry)); + } else { + throw "Couldn't parse " + geometry.CLASS_NAME; + } + return node; + }, + + /** + * Method: buildCoordinatesNode + * + * Parameters: + * geometry - {} + */ + buildCoordinatesNode: function(geometry) { + var points = null; + + if (geometry.components) { + points = geometry.components; + } + + var path; + if (points) { + var numPoints = points.length; + var parts = new Array(numPoints); + for (var i = 0; i < numPoints; i++) { + parts[i] = points[i].y + " " + points[i].x; + } + path = parts.join(" "); + } else { + path = geometry.y + " " + geometry.x; + } + return this.createTextNode(path); + }, + + CLASS_NAME: "OpenLayers.Format.GeoRSS" +}); + --- /dev/null +++ b/openlayers/lib/OpenLayers/Format/JSON.js @@ -1,1 +1,389 @@ - +/* Copyright (c) 2006-2008 MetaCarta, Inc., published under the Clear BSD + * license. See http://svn.openlayers.org/trunk/openlayers/license.txt for the + * full text of the license. */ + +/** + * Note: + * This work draws heavily from the public domain JSON serializer/deserializer + * at http://www.json.org/json.js. Rewritten so that it doesn't modify + * basic data prototypes. + */ + +/** + * @requires OpenLayers/Format.js + */ + +/** + * Class: OpenLayers.Format.JSON + * A parser to read/write JSON safely. Create a new instance with the + * constructor. + * + * Inherits from: + * - + */ +OpenLayers.Format.JSON = OpenLayers.Class(OpenLayers.Format, { + + /** + * APIProperty: indent + * {String} For "pretty" printing, the indent string will be used once for + * each indentation level. + */ + indent: " ", + + /** + * APIProperty: space + * {String} For "pretty" printing, the space string will be used after + * the ":" separating a name/value pair. + */ + space: " ", + + /** + * APIProperty: newline + * {String} For "pretty" printing, the newline string will be used at the + * end of each name/value pair or array item. + */ + newline: "\n", + + /** + * Property: level + * {Integer} For "pretty" printing, this is incremented/decremented during + * serialization. + */ + level: 0, + + /** + * Property: pretty + * {Boolean} Serialize with extra whitespace for structure. This is set + * by the method. + */ + pretty: false, + + /** + * Constructor: OpenLayers.Format.JSON + * Create a new parser for JSON. + * + * Parameters: + * options - {Object} An optional object whose properties will be set on + * this instance. + */ + initialize: function(options) { + OpenLayers.Format.prototype.initialize.apply(this, [options]); + }, + + /** + * APIMethod: read + * Deserialize a json string. + * + * Parameters: + * json - {String} A JSON string + * filter - {Function} A function which will be called for every key and + * value at every level of the final result. Each value will be + * replaced by the result of the filter function. This can be used to + * reform generic objects into instances of classes, or to transform + * date strings into Date objects. + * + * Returns: + * {Object} An object, array, string, or number . + */ + read: function(json, filter) { + /** + * Parsing happens in three stages. In the first stage, we run the text + * against a regular expression which looks for non-JSON + * characters. We are especially concerned with '()' and 'new' + * because they can cause invocation, and '=' because it can cause + * mutation. But just to be safe, we will reject all unexpected + * characters. + */ + try { + if (/^[\],:{}\s]*$/.test(json.replace(/\\["\\\/bfnrtu]/g, '@'). + replace(/"[^"\\\n\r]*"|true|false|null|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?/g, ']'). + replace(/(?:^|:|,)(?:\s*\[)+/g, ''))) { + + /** + * In the second stage we use the eval function to compile the + * text into a JavaScript structure. The '{' operator is + * subject to a syntactic ambiguity in JavaScript - it can + * begin a block or an object literal. We wrap the text in + * parens to eliminate the ambiguity. + */ + var object = eval('(' + json + ')'); + + /** + * In the optional third stage, we recursively walk the new + * structure, passing each name/value pair to a filter + * function for possible transformation. + */ + if(typeof filter === 'function') { + function walk(k, v) { + if(v && typeof v === 'object') { + for(var i in v) { + if(v.hasOwnProperty(i)) { + v[i] = walk(i, v[i]); + } + } + } + return filter(k, v); + } + object = walk('', object); + } + + if(this.keepData) { + this.data = object; + } + + return object; + } + } catch(e) { + // Fall through if the regexp test fails. + } + return null; + }, + + /** + * APIMethod: write + * Serialize an object into a JSON string. + * + * Parameters: + * value - {String} The object, array, string, number, boolean or date + * to be serialized. + * pretty - {Boolean} Structure the output with newlines and indentation. + * Default is false. + * + * Returns: + * {String} The JSON string representation of the input value. + */ + write: function(value, pretty) { + this.pretty = !!pretty; + var json = null; + var type = typeof value; + if(this.serialize[type]) { + try { + json = this.serialize[type].apply(this, [value]); + } catch(err) { + OpenLayers.Console.error("Trouble serializing: " + err); + } + } + return json; + }, + + /** + * Method: writeIndent + * Output an indentation string depending on the indentation level. + * + * Returns: + * {String} An appropriate indentation string. + */ + writeIndent: function() { + var pieces = []; + if(this.pretty) { + for(var i=0; i 0) { + pieces.push(','); + } + pieces.push(this.writeNewline(), this.writeIndent(), json); + } + } + + this.level -= 1; + pieces.push(this.writeNewline(), this.writeIndent(), ']'); + return pieces.join(''); + }, + + /** + * Method: serialize.string + * Transform a string into a JSON string. + * + * Parameters: + * string - {String} The string to be serialized + * + * Returns: + * {String} A JSON string representing the string. + */ + 'string': function(string) { + // If the string contains no control characters, no quote characters, and no + // backslash characters, then we can simply slap some quotes around it. + // Otherwise we must also replace the offending characters with safe + // sequences. + var m = { + '\b': '\\b', + '\t': '\\t', + '\n': '\\n', + '\f': '\\f', + '\r': '\\r', + '"' : '\\"', + '\\': '\\\\' + }; + if(/["\\\x00-\x1f]/.test(string)) { + return '"' + string.replace(/([\x00-\x1f\\"])/g, function(a, b) { + var c = m[b]; + if(c) { + return c; + } + c = b.charCodeAt(); + return '\\u00' + + Math.floor(c / 16).toString(16) + + (c % 16).toString(16); + }) + '"'; + } + return '"' + string + '"'; + }, + + /** + * Method: serialize.number + * Transform a number into a JSON string. + * + * Parameters: + * number - {Number} The number to be serialized. + * + * Returns: + * {String} A JSON string representing the number. + */ + 'number': function(number) { + return isFinite(number) ? String(number) : "null"; + }, + + /** + * Method: serialize.boolean + * Transform a boolean into a JSON string. + * + * Parameters: + * bool - {Boolean} The boolean to be serialized. + * + * Returns: + * {String} A JSON string representing the boolean. + */ + 'boolean': function(bool) { + return String(bool); + }, + + /** + * Method: serialize.object + * Transform a date into a JSON string. + * + * Parameters: + * date - {Date} The date to be serialized. + * + * Returns: + * {String} A JSON string representing the date. + */ + 'date': function(date) { + function format(number) { + // Format integers to have at least two digits. + return (number < 10) ? '0' + number : number; + } + return '"' + date.getFullYear() + '-' + + format(date.getMonth() + 1) + '-' + + format(date.getDate()) + 'T' + + format(date.getHours()) + ':' + + format(date.getMinutes()) + ':' + + format(date.getSeconds()) + '"'; + } + }, + + CLASS_NAME: "OpenLayers.Format.JSON" + +}); + --- /dev/null +++ b/openlayers/lib/OpenLayers/Format/KML.js @@ -1,1 +1,1239 @@ - +/* Copyright (c) 2006-2008 MetaCarta, Inc., published under the Clear BSD + * license. See http://svn.openlayers.org/trunk/openlayers/license.txt for the + * full text of the license. */ + +/** + * @requires OpenLayers/Format/XML.js + * @requires OpenLayers/Feature/Vector.js + * @requires OpenLayers/Geometry/Point.js + * @requires OpenLayers/Geometry/LineString.js + * @requires OpenLayers/Geometry/Polygon.js + * @requires OpenLayers/Geometry/Collection.js + * @requires OpenLayers/Request/XMLHttpRequest.js + * @requires OpenLayers/Console.js + */ + +/** + * Class: OpenLayers.Format.KML + * Read/Wite KML. Create a new instance with the + * constructor. + * + * Inherits from: + * - + */ +OpenLayers.Format.KML = OpenLayers.Class(OpenLayers.Format.XML, { + + /** + * APIProperty: kmlns + * {String} KML Namespace to use. Defaults to 2.0 namespace. + */ + kmlns: "http://earth.google.com/kml/2.0", + + /** + * APIProperty: placemarksDesc + * {String} Name of the placemarks. Default is "No description available." + */ + placemarksDesc: "No description available", + + /** + * APIProperty: foldersName + * {String} Name of the folders. Default is "OpenLayers export." + */ + foldersName: "OpenLayers export", + + /** + * APIProperty: foldersDesc + * {String} Description of the folders. Default is "Exported on [date]." + */ + foldersDesc: "Exported on " + new Date(), + + /** + * APIProperty: extractAttributes + * {Boolean} Extract attributes from KML. Default is true. + * Extracting styleUrls requires this to be set to true + */ + extractAttributes: true, + + /** + * Property: extractStyles + * {Boolean} Extract styles from KML. Default is false. + * Extracting styleUrls also requires extractAttributes to be + * set to true + */ + extractStyles: false, + + /** + * Property: internalns + * {String} KML Namespace to use -- defaults to the namespace of the + * Placemark node being parsed, but falls back to kmlns. + */ + internalns: null, + + /** + * Property: features + * {Array} Array of features + * + */ + features: null, + + /** + * Property: styles + * {Object} Storage of style objects + * + */ + styles: null, + + /** + * Property: styleBaseUrl + * {String} + */ + styleBaseUrl: "", + + /** + * Property: fetched + * {Object} Storage of KML URLs that have been fetched before + * in order to prevent reloading them. + */ + fetched: null, + + /** + * APIProperty: maxDepth + * {Integer} Maximum depth for recursive loading external KML URLs + * Defaults to 0: do no external fetching + */ + maxDepth: 0, + + /** + * Constructor: OpenLayers.Format.KML + * Create a new parser for KML. + * + * Parameters: + * options - {Object} An optional object whose properties will be set on + * this instance. + */ + initialize: function(options) { + // compile regular expressions once instead of every time they are used + this.regExes = { + trimSpace: (/^\s*|\s*$/g), + removeSpace: (/\s*/g), + splitSpace: (/\s+/), + trimComma: (/\s*,\s*/g), + kmlColor: (/(\w{2})(\w{2})(\w{2})(\w{2})/), + kmlIconPalette: (/root:\/\/icons\/palette-(\d+)(\.\w+)/), + straightBracket: (/\$\[(.*?)\]/g) + }; + OpenLayers.Format.XML.prototype.initialize.apply(this, [options]); + }, + + /** + * APIMethod: read + * Read data from a string, and return a list of features. + * + * Parameters: + * data - {String} or {DOMElement} data to read/parse. + * + * Returns: + * {Array()} List of features. + */ + read: function(data) { + this.features = []; + this.styles = {}; + this.fetched = {}; + + // Set default options + var options = { + depth: 0, + styleBaseUrl: this.styleBaseUrl + }; + + return this.parseData(data, options); + }, + + /** + * Method: parseData + * Read data from a string, and return a list of features. + * + * Parameters: + * data - {String} or {DOMElement} data to read/parse. + * options - {Object} Hash of options + * + * Returns: + * {Array()} List of features. + */ + parseData: function(data, options) { + if(typeof data == "string") { + data = OpenLayers.Format.XML.prototype.read.apply(this, [data]); + } + + // Loop throught the following node types in this order and + // process the nodes found + var types = ["Link", "NetworkLink", "Style", "StyleMap", "Placemark"]; + for(var i=0, len=types.length; i and + // Don't do anything if we have reached our maximum depth for recursion + if (options.depth >= this.maxDepth) { + return false; + } + + // increase depth + var newOptions = OpenLayers.Util.extend({}, options); + newOptions.depth++; + + for(var i=0, len=nodes.length; i nodes in the data and parses them + * Also parses nodes, but only uses the 'normal' key + * + * Parameters: + * nodes - {Array} of {DOMElement} data to read/parse. + * options - {Object} Hash of options + * + */ + parseStyles: function(nodes, options) { + for(var i=0, len=nodes.length; i node and builds the style hash + * accordingly + * + * Parameters: + * node - {DOMElement} + + + + + + + +
+
+ ... +
+
+
[agency]
+
+
+ + +
+
+ +
bottom bar
+ + + + --- /dev/null +++ b/origin-src/transitfeed-1.2.5/build/lib/gtfsscheduleviewer/files/labeled_marker.js @@ -1,1 +1,186 @@ +/* +* LabeledMarker Class +* +* Copyright 2007 Mike Purvis (http://uwmike.com) +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +* +* This class extends the Maps API's standard GMarker class with the ability +* to support markers with textual labels. Please see articles here: +* +* http://googlemapsbook.com/2007/01/22/extending-gmarker/ +* http://googlemapsbook.com/2007/03/06/clickable-labeledmarker/ +*/ +/** + * Constructor for LabeledMarker, which picks up on strings from the GMarker + * options array, and then calls the GMarker constructor. + * + * @param {GLatLng} latlng + * @param {GMarkerOptions} Named optional arguments: + * opt_opts.labelText {String} text to place in the overlay div. + * opt_opts.labelClass {String} class to use for the overlay div. + * (default "markerLabel") + * opt_opts.labelOffset {GSize} label offset, the x- and y-distance between + * the marker's latlng and the upper-left corner of the text div. + */ +function LabeledMarker(latlng, opt_opts){ + this.latlng_ = latlng; + this.opts_ = opt_opts; + + this.initText_ = opt_opts.labelText || ""; + this.labelClass_ = opt_opts.labelClass || "markerLabel"; + this.labelOffset_ = opt_opts.labelOffset || new GSize(0, 0); + + this.clickable_ = opt_opts.clickable || true; + + if (opt_opts.draggable) { + // This version of LabeledMarker doesn't support dragging. + opt_opts.draggable = false; + } + + GMarker.apply(this, arguments); +} + + +// It's a limitation of JavaScript inheritance that we can't conveniently +// inherit from GMarker without having to run its constructor. In order for +// the constructor to run, it requires some dummy GLatLng. +LabeledMarker.prototype = new GMarker(new GLatLng(0, 0)); + +/** + * Is called by GMap2's addOverlay method. Creates the text div and adds it + * to the relevant parent div. + * + * @param {GMap2} map the map that has had this labeledmarker added to it. + */ +LabeledMarker.prototype.initialize = function(map) { + // Do the GMarker constructor first. + GMarker.prototype.initialize.apply(this, arguments); + + this.map_ = map; + this.setText(this.initText_); +} + +/** + * Create a new div for this label. + */ +LabeledMarker.prototype.makeDiv_ = function(map) { + if (this.div_) { + return; + } + this.div_ = document.createElement("div"); + this.div_.className = this.labelClass_; + this.div_.style.position = "absolute"; + this.div_.style.cursor = "pointer"; + this.map_.getPane(G_MAP_MARKER_PANE).appendChild(this.div_); + + if (this.clickable_) { + /** + * Creates a closure for passing events through to the source marker + * This is located in here to avoid cluttering the global namespace. + * The downside is that the local variables from initialize() continue + * to occupy space on the stack. + * + * @param {Object} object to receive event trigger. + * @param {GEventListener} event to be triggered. + */ + function newEventPassthru(obj, event) { + return function() { + GEvent.trigger(obj, event); + }; + } + + // Pass through events fired on the text div to the marker. + var eventPassthrus = ['click', 'dblclick', 'mousedown', 'mouseup', 'mouseover', 'mouseout']; + for(var i = 0; i < eventPassthrus.length; i++) { + var name = eventPassthrus[i]; + GEvent.addDomListener(this.div_, name, newEventPassthru(this, name)); + } + } +} + +/** + * Return the html in the div of this label, or "" if none is set + */ +LabeledMarker.prototype.getText = function(text) { + if (this.div_) { + return this.div_.innerHTML; + } else { + return ""; + } +} + +/** + * Set the html in the div of this label to text. If text is "" or null remove + * the div. + */ +LabeledMarker.prototype.setText = function(text) { + if (this.div_) { + if (text) { + this.div_.innerHTML = text; + } else { + // remove div + GEvent.clearInstanceListeners(this.div_); + this.div_.parentNode.removeChild(this.div_); + this.div_ = null; + } + } else { + if (text) { + this.makeDiv_(); + this.div_.innerHTML = text; + this.redraw(); + } + } +} + +/** + * Move the text div based on current projection and zoom level, call the redraw() + * handler in GMarker. + * + * @param {Boolean} force will be true when pixel coordinates need to be recomputed. + */ +LabeledMarker.prototype.redraw = function(force) { + GMarker.prototype.redraw.apply(this, arguments); + + if (this.div_) { + // Calculate the DIV coordinates of two opposite corners of our bounds to + // get the size and position of our rectangle + var p = this.map_.fromLatLngToDivPixel(this.latlng_); + var z = GOverlay.getZIndex(this.latlng_.lat()); + + // Now position our div based on the div coordinates of our bounds + this.div_.style.left = (p.x + this.labelOffset_.width) + "px"; + this.div_.style.top = (p.y + this.labelOffset_.height) + "px"; + this.div_.style.zIndex = z; // in front of the marker + } +} + +/** + * Remove the text div from the map pane, destroy event passthrus, and calls the + * default remove() handler in GMarker. + */ + LabeledMarker.prototype.remove = function() { + this.setText(null); + GMarker.prototype.remove.apply(this, arguments); +} + +/** + * Return a copy of this overlay, for the parent Map to duplicate itself in full. This + * is part of the Overlay interface and is used, for example, to copy everything in the + * main view into the mini-map. + */ +LabeledMarker.prototype.copy = function() { + return new LabeledMarker(this.latlng_, this.opt_opts_); +} + --- /dev/null +++ b/origin-src/transitfeed-1.2.5/build/lib/gtfsscheduleviewer/files/style.css @@ -1,1 +1,162 @@ +html { overflow: hidden; } +html, body { + margin: 0; + padding: 0; + height: 100%; +} + +body { margin: 5px; } + +#content { + position: relative; + margin-top: 5px; +} + +#map-wrapper { + position: relative; + height: 100%; + width: auto; + left: 0; + top: 0; + z-index: 100; +} + +#map { + position: relative; + height: 100%; + width: auto; + border: 1px solid #aaa; +} + +#sidebar-wrapper { + position: absolute; + height: 100%; + width: 220px; + top: 0; + border: 1px solid #aaa; + overflow: auto; + z-index: 300; +} + +#sidebar { + position: relative; + width: auto; + padding: 4px; + overflow: hidden; +} + +#topbar { + position: relative; + padding: 2px; + border: 1px solid #aaa; + margin: 0; +} + +#topbar h1 { + white-space: nowrap; + overflow: hidden; + font-size: 14pt; + font-weight: bold; + font-face: + margin: 0; +} + + +body.sidebar-right #map-wrapper { margin-right: 229px; } +body.sidebar-right #sidebar-wrapper { right: 0; } + +body.sidebar-left #map { margin-left: 229px; } +body.sidebar-left #sidebar { left: 0; } + +body.nosidebar #map { margin: 0; } +body.nosidebar #sidebar { display: none; } + +#bottombar { + position: relative; + padding: 2px; + border: 1px solid #aaa; + margin-top: 5px; + display: none; +} + +/* holly hack for IE to get position:bottom right + see: http://www.positioniseverything.net/abs_relbugs.html + \*/ +* html #topbar { height: 1px; } +/* */ + +body { + font-family:helvetica,arial,sans, sans-serif; +} +h1 { + margin-top: 0.5em; + margin-bottom: 0.5em; +} +h2 { + margin-top: 0.2em; + margin-bottom: 0.2em; +} +h3 { + margin-top: 0.2em; + margin-bottom: 0.2em; +} +.tooltip { + white-space: nowrap; + padding: 2px; + color: black; + font-size: 12px; + background-color: white; + border: 1px solid black; + cursor: pointer; + filter:alpha(opacity=60); + -moz-opacity: 0.6; + opacity: 0.6; +} +#routeList { + border: 1px solid black; + overflow: auto; +} +.shortName { + font-size: bigger; + font-weight: bold; +} +.routeChoice,.tripChoice,.routeChoiceSelected,.tripChoiceSelected { + white-space: nowrap; + cursor: pointer; + padding: 0px 2px; + color: black; + line-height: 1.4em; + font-size: smaller; + overflow: hidden; +} +.tripChoice { + color: blue; +} +.routeChoiceSelected,.tripChoiceSelected { + background-color: blue; + color: white; +} +.tripSection { + padding-left: 0px; + font-size: 10pt; + background-color: lightblue; +} +.patternSection { + margin-left: 8px; + padding-left: 2px; + border-bottom: 1px solid grey; +} +.unusualPattern { + background-color: #aaa; + color: #444; +} +/* Following styles are used by location_editor.py */ +#edit { + visibility: hidden; + float: right; + font-size: 80%; +} +#edit form { + display: inline; +} --- /dev/null +++ b/origin-src/transitfeed-1.2.5/build/lib/gtfsscheduleviewer/files/svgcheck.vbs @@ -1,1 +1,8 @@ +' Copyright 1999-2000 Adobe Systems Inc. All rights reserved. Permission to redistribute +' granted provided that this file is not modified in any way. This file is provided with +' absolutely no warranties of any kind. +Function isSVGControlInstalled() + on error resume next + isSVGControlInstalled = IsObject(CreateObject("Adobe.SVGCtl")) +end Function --- /dev/null +++ b/origin-src/transitfeed-1.2.5/build/lib/gtfsscheduleviewer/marey_graph.py @@ -1,1 +1,470 @@ - +#!/usr/bin/python2.5 +# +# Copyright (C) 2007 Google Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +"""Output svg/xml data for a marey graph + +Marey graphs are a visualization form typically used for timetables. Time +is on the x-axis and position on the y-axis. This module reads data from a +transitfeed.Schedule and creates a marey graph in svg/xml format. The graph +shows the speed between stops for each trip of a route. + +TODO: This module was taken from an internal Google tool. It works but is not +well intergrated into transitfeed and schedule_viewer. Also, it has lots of +ugly hacks to compensate set canvas size and so on which could be cleaned up. + +For a little more information see (I didn't make this URL ;-) +http://transliteracies.english.ucsb.edu/post/research-project/research-clearinghouse-individual/research-reports/the-indexical-imagination-marey%e2%80%99s-graphic-method-and-the-technological-transformation-of-writing-in-the-nineteenth-century + + MareyGraph: Class, keeps cache of graph data and graph properties + and draws marey graphs in svg/xml format on request. + +""" + +import itertools +import transitfeed + + +class MareyGraph: + """Produces and caches marey graph from transit feed data.""" + + _MAX_ZOOM = 5.0 # change docstring of ChangeScaleFactor if this changes + _DUMMY_SEPARATOR = 10 #pixel + + def __init__(self): + # Timetablerelated state + self._cache = str() + self._stoplist = [] + self._tlist = [] + self._stations = [] + self._decorators = [] + + # TODO: Initialize default values via constructor parameters + # or via a class constants + + # Graph properties + self._tspan = 30 # number of hours to display + self._offset = 0 # starting hour + self._hour_grid = 60 # number of pixels for an hour + self._min_grid = 5 # number of pixels between subhour lines + + # Canvas properties + self._zoomfactor = 0.9 # svg Scaling factor + self._xoffset = 0 # move graph horizontally + self._yoffset = 0 # move graph veritcally + self._bgcolor = "lightgrey" + + # height/width of graph canvas before transform + self._gwidth = self._tspan * self._hour_grid + + def Draw(self, stoplist=None, triplist=None, height=520): + """Main interface for drawing the marey graph. + + If called without arguments, the data generated in the previous call + will be used. New decorators can be added between calls. + + Args: + # Class Stop is defined in transitfeed.py + stoplist: [Stop, Stop, ...] + # Class Trip is defined in transitfeed.py + triplist: [Trip, Trip, ...] + + Returns: + # A string that contain a svg/xml web-page with a marey graph. + " + + + + + + + """ % (self._gwidth + self._xoffset + 20, self._gheight + 15, + self._offset, self._gheight + 10, + self._xoffset, self._yoffset, self._zoomfactor) + + return svg_header + + def _DrawFooter(self): + return "" + + def _DrawDecorators(self): + """Used to draw fancy overlays on trip graphs.""" + return " ".join(self._decorators) + + def _DrawBox(self): + tmpstr = """ + """ % (0, 0, self._gwidth + 20, self._gheight, self._bgcolor) + return tmpstr + + def _BuildStations(self, stoplist): + """Dispatches the best algorithm for calculating station line position. + + Args: + # Class Stop is defined in transitfeed.py + stoplist: [Stop, Stop, ...] + # Class Trip is defined in transitfeed.py + triplist: [Trip, Trip, ...] + + Returns: + # One integer y-coordinate for each station normalized between + # 0 and X, where X is the height of the graph in pixels + [0, 33, 140, ... , X] + """ + stations = [] + dists = self._EuclidianDistances(stoplist) + stations = self._CalculateYLines(dists) + return stations + + def _EuclidianDistances(self,slist): + """Calculate euclidian distances between stops. + + Uses the stoplists long/lats to approximate distances + between stations and build a list with y-coordinates for the + horizontal lines in the graph. + + Args: + # Class Stop is defined in transitfeed.py + stoplist: [Stop, Stop, ...] + + Returns: + # One integer for each pair of stations + # indicating the approximate distance + [0,33,140, ... ,X] + """ + e_dists2 = [transitfeed.ApproximateDistanceBetweenStops(stop, tail) for + (stop,tail) in itertools.izip(slist, slist[1:])] + + return e_dists2 + + def _CalculateYLines(self, dists): + """Builds a list with y-coordinates for the horizontal lines in the graph. + + Args: + # One integer for each pair of stations + # indicating the approximate distance + dists: [0,33,140, ... ,X] + + Returns: + # One integer y-coordinate for each station normalized between + # 0 and X, where X is the height of the graph in pixels + [0, 33, 140, ... , X] + """ + tot_dist = sum(dists) + if tot_dist > 0: + pixel_dist = [float(d * (self._gheight-20))/tot_dist for d in dists] + pixel_grid = [0]+[int(pd + sum(pixel_dist[0:i])) for i,pd in + enumerate(pixel_dist)] + else: + pixel_grid = [] + + return pixel_grid + + def _TravelTimes(self,triplist,index=0): + """ Calculate distances and plot stops. + + Uses a timetable to approximate distances + between stations + + Args: + # Class Trip is defined in transitfeed.py + triplist: [Trip, Trip, ...] + # (Optional) Index of Triplist prefered for timetable Calculation + index: 3 + + Returns: + # One integer for each pair of stations + # indicating the approximate distance + [0,33,140, ... ,X] + """ + + def DistanceInTravelTime(dep_secs, arr_secs): + t_dist = arr_secs-dep_secs + if t_dist<0: + t_dist = self._DUMMY_SEPARATOR # min separation + return t_dist + + if not triplist: + return [] + + if 0 < index < len(triplist): + trip = triplist[index] + else: + trip = triplist[0] + + t_dists2 = [DistanceInTravelTime(stop[3],tail[2]) for (stop,tail) + in itertools.izip(trip.GetTimeStops(),trip.GetTimeStops()[1:])] + return t_dists2 + + def _AddWarning(self, str): + print str + + def _DrawTrips(self,triplist,colpar=""): + """Generates svg polylines for each transit trip. + + Args: + # Class Trip is defined in transitfeed.py + [Trip, Trip, ...] + + Returns: + # A string containing a polyline tag for each trip + ' 0 and not colpar: + color="purple" + scriptcall = 'onmouseover="LineClick(\'%s\',\'Trip %s starting %s\')"' % (t.trip_id, + t.trip_id, transitfeed.FormatSecondsSinceMidnight(t.GetStartTime())) + tmpstrhead = '') + return "".join(tmpstrs) + + def _Uniform(self, triplist): + """Fallback to assuming uniform distance between stations""" + # This should not be neseccary, but we are in fallback mode + longest = max([len(t.GetTimeStops()) for t in triplist]) + return [100] * longest + + def _DrawStations(self, color="#aaa"): + """Generates svg with a horizontal line for each station/stop. + + Args: + # Class Stop is defined in transitfeed.py + stations: [Stop, Stop, ...] + + Returns: + # A string containing a polyline tag for each stop + " ' %(color,20,20+y+.5,self._gwidth+20,20+y+.5)) + return "".join(tmpstrs) + + def _DrawHours(self): + """Generates svg to show a vertical hour and sub-hour grid + + Returns: + # A string containing a polyline tag for each grid line + " ' \ + % (i + .5 + 20, 20, i + .5 + 20, self._gheight)) + tmpstrs.append('%d' + % (i + 20, 20, + (i / self._hour_grid + self._offset) % 24)) + else: + tmpstrs.append('' \ + % (i + .5 + 20, 20, i + .5 + 20, self._gheight)) + return "".join(tmpstrs) + + def AddStationDecoration(self, index, color="#f00"): + """Flushes existing decorations and highlights the given station-line. + + Args: + # Integer, index of stop to be highlighted. + index: 4 + # An optional string with a html color code + color: "#fff" + """ + tmpstr = str() + num_stations = len(self._stations) + ind = int(index) + if self._stations: + if 0 0 and float(newfactor) < self._MAX_ZOOM: + self._zoomfactor = newfactor + + def ScaleLarger(self): + """Increases the zoom of the graph one step (0.1 units).""" + newfactor = self._zoomfactor + 0.1 + if float(newfactor) > 0 and float(newfactor) < self._MAX_ZOOM: + self._zoomfactor = newfactor + + def ScaleSmaller(self): + """Decreases the zoom of the graph one step(0.1 units).""" + newfactor = self._zoomfactor - 0.1 + if float(newfactor) > 0 and float(newfactor) < self._MAX_ZOOM: + self._zoomfactor = newfactor + + def ClearDecorators(self): + """Removes all the current decorators. + """ + self._decorators = [] + + def AddTextStripDecoration(self,txtstr): + tmpstr = '%s' % (0, + 20 + self._gheight, txtstr) + self._decorators.append(tmpstr) + + def SetSpan(self, first_arr, last_arr, mint=5 ,maxt=30): + s_hour = (first_arr / 3600) - 1 + e_hour = (last_arr / 3600) + 1 + self._offset = max(min(s_hour, 23), 0) + self._tspan = max(min(e_hour - s_hour, maxt), mint) + self._gwidth = self._tspan * self._hour_grid + --- /dev/null +++ b/origin-src/transitfeed-1.2.5/build/lib/transitfeed/__init__.py @@ -1,1 +1,35 @@ +#!/usr/bin/python2.5 +# Copyright (C) 2007 Google Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +"""Expose some modules in this package. + +Before transitfeed version 1.2.4 all our library code was distributed in a +one file module, transitfeed.py, and could be used as + +import transitfeed +schedule = transitfeed.Schedule() + +At that time the module (one file, transitfeed.py) was converted into a +package (a directory named transitfeed containing __init__.py and multiple .py +files). Classes and attributes exposed by the old module may still be imported +in the same way. Indeed, code that depends on the library should +continue to use import commands such as the above and ignore _transitfeed. +""" + +from _transitfeed import * + +__version__ = _transitfeed.__version__ + --- /dev/null +++ b/origin-src/transitfeed-1.2.5/build/lib/transitfeed/_transitfeed.py @@ -1,1 +1,4599 @@ - +#!/usr/bin/python2.5 + +# Copyright (C) 2007 Google Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +"""Easy interface for handling a Google Transit Feed file. + +Do not import this module directly. Thanks to __init__.py you should do +something like: + + import transitfeed + schedule = transitfeed.Schedule() + ... + +This module is a library to help you create, read and write Google +Transit Feed files. Refer to the feed specification, available at +http://code.google.com/transit/spec/transit_feed_specification.htm, for a +complete description how the transit feed represents a transit schedule. This +library supports all required parts of the specification but does not yet +support all optional parts. Patches welcome! + +The specification describes several tables such as stops, routes and trips. +In a feed file these are stored as comma separeted value files. This library +represents each row of these tables with a single Python object. This object has +attributes for each value on the row. For example, schedule.AddStop returns a +Stop object which has attributes such as stop_lat and stop_name. + + Schedule: Central object of the parser + GenericGTFSObject: A base class for each of the objects below + Route: Represents a single route + Trip: Represents a single trip + Stop: Represents a single stop + ServicePeriod: Represents a single service, a set of dates + Agency: Represents the agency in this feed + Transfer: Represents a single transfer rule + TimeToSecondsSinceMidnight(): Convert HH:MM:SS into seconds since midnight. + FormatSecondsSinceMidnight(s): Formats number of seconds past midnight into a string +""" + +# TODO: Preserve arbitrary columns? + +import bisect +import cStringIO as StringIO +import codecs +from transitfeed.util import defaultdict +import csv +import datetime +import logging +import math +import os +import random +try: + import sqlite3 as sqlite +except ImportError: + from pysqlite2 import dbapi2 as sqlite +import re +import tempfile +import time +import warnings +# Objects in a schedule (Route, Trip, etc) should not keep a strong reference +# to the Schedule object to avoid a reference cycle. Schedule needs to use +# __del__ to cleanup its temporary file. The garbage collector can't handle +# reference cycles containing objects with custom cleanup code. +import weakref +import zipfile + +OUTPUT_ENCODING = 'utf-8' +MAX_DISTANCE_FROM_STOP_TO_SHAPE = 1000 +MAX_DISTANCE_BETWEEN_STOP_AND_PARENT_STATION_WARNING = 100.0 +MAX_DISTANCE_BETWEEN_STOP_AND_PARENT_STATION_ERROR = 1000.0 + +__version__ = '1.2.5' + + +def EncodeUnicode(text): + """ + Optionally encode text and return it. The result should be safe to print. + """ + if type(text) == type(u''): + return text.encode(OUTPUT_ENCODING) + else: + return text + + +# These are used to distinguish between errors (not allowed by the spec) +# and warnings (not recommended) when reporting issues. +TYPE_ERROR = 0 +TYPE_WARNING = 1 + + +class ProblemReporterBase: + """Base class for problem reporters. Tracks the current context and creates + an exception object for each problem. Subclasses must implement + _Report(self, e)""" + + def __init__(self): + self.ClearContext() + + def ClearContext(self): + """Clear any previous context.""" + self._context = None + + def SetFileContext(self, file_name, row_num, row, headers): + """Save the current context to be output with any errors. + + Args: + file_name: string + row_num: int + row: list of strings + headers: list of column headers, its order corresponding to row's + """ + self._context = (file_name, row_num, row, headers) + + def FeedNotFound(self, feed_name, context=None): + e = FeedNotFound(feed_name=feed_name, context=context, + context2=self._context) + self._Report(e) + + def UnknownFormat(self, feed_name, context=None): + e = UnknownFormat(feed_name=feed_name, context=context, + context2=self._context) + self._Report(e) + + def FileFormat(self, problem, context=None): + e = FileFormat(problem=problem, context=context, + context2=self._context) + self._Report(e) + + def MissingFile(self, file_name, context=None): + e = MissingFile(file_name=file_name, context=context, + context2=self._context) + self._Report(e) + + def UnknownFile(self, file_name, context=None): + e = UnknownFile(file_name=file_name, context=context, + context2=self._context, type=TYPE_WARNING) + self._Report(e) + + def EmptyFile(self, file_name, context=None): + e = EmptyFile(file_name=file_name, context=context, + context2=self._context) + self._Report(e) + + def MissingColumn(self, file_name, column_name, context=None): + e = MissingColumn(file_name=file_name, column_name=column_name, + context=context, context2=self._context) + self._Report(e) + + def UnrecognizedColumn(self, file_name, column_name, context=None): + e = UnrecognizedColumn(file_name=file_name, column_name=column_name, + context=context, context2=self._context, + type=TYPE_WARNING) + self._Report(e) + + def CsvSyntax(self, description=None, context=None, type=TYPE_ERROR): + e = CsvSyntax(description=description, context=context, + context2=self._context, type=type) + self._Report(e) + + def DuplicateColumn(self, file_name, header, count, type=TYPE_ERROR, + context=None): + e = DuplicateColumn(file_name=file_name, + header=header, + count=count, + type=type, + context=context, + context2=self._context) + self._Report(e) + + def MissingValue(self, column_name, reason=None, context=None): + e = MissingValue(column_name=column_name, reason=reason, context=context, + context2=self._context) + self._Report(e) + + def InvalidValue(self, column_name, value, reason=None, context=None, + type=TYPE_ERROR): + e = InvalidValue(column_name=column_name, value=value, reason=reason, + context=context, context2=self._context, type=type) + self._Report(e) + + def DuplicateID(self, column_names, values, context=None, type=TYPE_ERROR): + if isinstance(column_names, tuple): + column_names = '(' + ', '.join(column_names) + ')' + if isinstance(values, tuple): + values = '(' + ', '.join(values) + ')' + e = DuplicateID(column_name=column_names, value=values, + context=context, context2=self._context, type=type) + self._Report(e) + + def UnusedStop(self, stop_id, stop_name, context=None): + e = UnusedStop(stop_id=stop_id, stop_name=stop_name, + context=context, context2=self._context, type=TYPE_WARNING) + self._Report(e) + + def UsedStation(self, stop_id, stop_name, context=None): + e = UsedStation(stop_id=stop_id, stop_name=stop_name, + context=context, context2=self._context, type=TYPE_ERROR) + self._Report(e) + + def StopTooFarFromParentStation(self, stop_id, stop_name, parent_stop_id, + parent_stop_name, distance, + type=TYPE_WARNING, context=None): + e = StopTooFarFromParentStation( + stop_id=stop_id, stop_name=stop_name, + parent_stop_id=parent_stop_id, + parent_stop_name=parent_stop_name, distance=distance, + context=context, context2=self._context, type=type) + self._Report(e) + + def StopsTooClose(self, stop_name_a, stop_id_a, stop_name_b, stop_id_b, + distance, type=TYPE_WARNING, context=None): + e = StopsTooClose( + stop_name_a=stop_name_a, stop_id_a=stop_id_a, stop_name_b=stop_name_b, + stop_id_b=stop_id_b, distance=distance, context=context, + context2=self._context, type=type) + self._Report(e) + + def StationsTooClose(self, stop_name_a, stop_id_a, stop_name_b, stop_id_b, + distance, type=TYPE_WARNING, context=None): + e = StationsTooClose( + stop_name_a=stop_name_a, stop_id_a=stop_id_a, stop_name_b=stop_name_b, + stop_id_b=stop_id_b, distance=distance, context=context, + context2=self._context, type=type) + self._Report(e) + + def DifferentStationTooClose(self, stop_name, stop_id, + station_stop_name, station_stop_id, + distance, type=TYPE_WARNING, context=None): + e = DifferentStationTooClose( + stop_name=stop_name, stop_id=stop_id, + station_stop_name=station_stop_name, station_stop_id=station_stop_id, + distance=distance, context=context, context2=self._context, type=type) + self._Report(e) + + def StopTooFarFromShapeWithDistTraveled(self, trip_id, stop_name, stop_id, + shape_dist_traveled, shape_id, + distance, max_distance, + type=TYPE_WARNING): + e = StopTooFarFromShapeWithDistTraveled( + trip_id=trip_id, stop_name=stop_name, stop_id=stop_id, + shape_dist_traveled=shape_dist_traveled, shape_id=shape_id, + distance=distance, max_distance=max_distance, type=type) + self._Report(e) + + def ExpirationDate(self, expiration, context=None): + e = ExpirationDate(expiration=expiration, context=context, + context2=self._context, type=TYPE_WARNING) + self._Report(e) + + def FutureService(self, start_date, context=None): + e = FutureService(start_date=start_date, context=context, + context2=self._context, type=TYPE_WARNING) + self._Report(e) + + def InvalidLineEnd(self, bad_line_end, context=None): + """bad_line_end is a human readable string.""" + e = InvalidLineEnd(bad_line_end=bad_line_end, context=context, + context2=self._context, type=TYPE_WARNING) + self._Report(e) + + def TooFastTravel(self, trip_id, prev_stop, next_stop, dist, time, speed, + type=TYPE_ERROR): + e = TooFastTravel(trip_id=trip_id, prev_stop=prev_stop, + next_stop=next_stop, time=time, dist=dist, speed=speed, + context=None, context2=self._context, type=type) + self._Report(e) + + def StopWithMultipleRouteTypes(self, stop_name, stop_id, route_id1, route_id2, + context=None): + e = StopWithMultipleRouteTypes(stop_name=stop_name, stop_id=stop_id, + route_id1=route_id1, route_id2=route_id2, + context=context, context2=self._context, + type=TYPE_WARNING) + self._Report(e) + + def DuplicateTrip(self, trip_id1, route_id1, trip_id2, route_id2, + context=None): + e = DuplicateTrip(trip_id1=trip_id1, route_id1=route_id1, trip_id2=trip_id2, + route_id2=route_id2, context=context, + context2=self._context, type=TYPE_WARNING) + self._Report(e) + + def OtherProblem(self, description, context=None, type=TYPE_ERROR): + e = OtherProblem(description=description, + context=context, context2=self._context, type=type) + self._Report(e) + + def TooManyDaysWithoutService(self, + first_day_without_service, + last_day_without_service, + consecutive_days_without_service, + context=None, + type=TYPE_WARNING): + e = TooManyDaysWithoutService( + first_day_without_service=first_day_without_service, + last_day_without_service=last_day_without_service, + consecutive_days_without_service=consecutive_days_without_service, + context=context, + context2=self._context, + type=type) + self._Report(e) + +class ProblemReporter(ProblemReporterBase): + """This is a basic problem reporter that just prints to console.""" + def _Report(self, e): + context = e.FormatContext() + if context: + print context + print EncodeUnicode(self._LineWrap(e.FormatProblem(), 78)) + + @staticmethod + def _LineWrap(text, width): + """ + A word-wrap function that preserves existing line breaks + and most spaces in the text. Expects that existing line + breaks are posix newlines (\n). + + Taken from: + http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/148061 + """ + return reduce(lambda line, word, width=width: '%s%s%s' % + (line, + ' \n'[(len(line) - line.rfind('\n') - 1 + + len(word.split('\n', 1)[0]) >= width)], + word), + text.split(' ') + ) + + +class ExceptionWithContext(Exception): + def __init__(self, context=None, context2=None, **kwargs): + """Initialize an exception object, saving all keyword arguments in self. + context and context2, if present, must be a tuple of (file_name, row_num, + row, headers). context2 comes from ProblemReporter.SetFileContext. context + was passed in with the keyword arguments. context2 is ignored if context + is present.""" + Exception.__init__(self) + + if context: + self.__dict__.update(self.ContextTupleToDict(context)) + elif context2: + self.__dict__.update(self.ContextTupleToDict(context2)) + self.__dict__.update(kwargs) + + if ('type' in kwargs) and (kwargs['type'] == TYPE_WARNING): + self._type = TYPE_WARNING + else: + self._type = TYPE_ERROR + + def GetType(self): + return self._type + + def IsError(self): + return self._type == TYPE_ERROR + + def IsWarning(self): + return self._type == TYPE_WARNING + + CONTEXT_PARTS = ['file_name', 'row_num', 'row', 'headers'] + @staticmethod + def ContextTupleToDict(context): + """Convert a tuple representing a context into a dict of (key, value) pairs""" + d = {} + if not context: + return d + for k, v in zip(ExceptionWithContext.CONTEXT_PARTS, context): + if v != '' and v != None: # Don't ignore int(0), a valid row_num + d[k] = v + return d + + def __str__(self): + return self.FormatProblem() + + def GetDictToFormat(self): + """Return a copy of self as a dict, suitable for passing to FormatProblem""" + d = {} + for k, v in self.__dict__.items(): + # TODO: Better handling of unicode/utf-8 within Schedule objects. + # Concatinating a unicode and utf-8 str object causes an exception such + # as "UnicodeDecodeError: 'ascii' codec can't decode byte ..." as python + # tries to convert the str to a unicode. To avoid that happening within + # the problem reporter convert all unicode attributes to utf-8. + # Currently valid utf-8 fields are converted to unicode in _ReadCsvDict. + # Perhaps all fields should be left as utf-8. + d[k] = EncodeUnicode(v) + return d + + def FormatProblem(self, d=None): + """Return a text string describing the problem. + + Args: + d: map returned by GetDictToFormat with with formatting added + """ + if not d: + d = self.GetDictToFormat() + + output_error_text = self.__class__.ERROR_TEXT % d + if ('reason' in d) and d['reason']: + return '%s\n%s' % (output_error_text, d['reason']) + else: + return output_error_text + + def FormatContext(self): + """Return a text string describing the context""" + text = '' + if hasattr(self, 'feed_name'): + text += "In feed '%s': " % self.feed_name + if hasattr(self, 'file_name'): + text += self.file_name + if hasattr(self, 'row_num'): + text += ":%i" % self.row_num + if hasattr(self, 'column_name'): + text += " column %s" % self.column_name + return text + + def __cmp__(self, y): + """Return an int <0/0/>0 when self is more/same/less significant than y. + + Subclasses should define this if exceptions should be listed in something + other than the order they are reported. + + Args: + y: object to compare to self + + Returns: + An int which is negative if self is more significant than y, 0 if they + are similar significance and positive if self is less significant than + y. Returning a float won't work. + + Raises: + TypeError by default, meaning objects of the type can not be compared. + """ + raise TypeError("__cmp__ not defined") + + +class MissingFile(ExceptionWithContext): + ERROR_TEXT = "File %(file_name)s is not found" + +class EmptyFile(ExceptionWithContext): + ERROR_TEXT = "File %(file_name)s is empty" + +class UnknownFile(ExceptionWithContext): + ERROR_TEXT = 'The file named %(file_name)s was not expected.\n' \ + 'This may be a misspelled file name or the file may be ' \ + 'included in a subdirectory. Please check spellings and ' \ + 'make sure that there are no subdirectories within the feed' + +class FeedNotFound(ExceptionWithContext): + ERROR_TEXT = 'Couldn\'t find a feed named %(feed_name)s' + +class UnknownFormat(ExceptionWithContext): + ERROR_TEXT = 'The feed named %(feed_name)s had an unknown format:\n' \ + 'feeds should be either .zip files or directories.' + +class FileFormat(ExceptionWithContext): + ERROR_TEXT = 'Files must be encoded in utf-8 and may not contain ' \ + 'any null bytes (0x00). %(file_name)s %(problem)s.' + +class MissingColumn(ExceptionWithContext): + ERROR_TEXT = 'Missing column %(column_name)s in file %(file_name)s' + +class UnrecognizedColumn(ExceptionWithContext): + ERROR_TEXT = 'Unrecognized column %(column_name)s in file %(file_name)s. ' \ + 'This might be a misspelled column name (capitalization ' \ + 'matters!). Or it could be extra information (such as a ' \ + 'proposed feed extension) that the validator doesn\'t know ' \ + 'about yet. Extra information is fine; this warning is here ' \ + 'to catch misspelled optional column names.' + +class CsvSyntax(ExceptionWithContext): + ERROR_TEXT = '%(description)s' + +class DuplicateColumn(ExceptionWithContext): + ERROR_TEXT = 'Column %(header)s appears %(count)i times in file %(file_name)s' + +class MissingValue(ExceptionWithContext): + ERROR_TEXT = 'Missing value for column %(column_name)s' + +class InvalidValue(ExceptionWithContext): + ERROR_TEXT = 'Invalid value %(value)s in field %(column_name)s' + +class DuplicateID(ExceptionWithContext): + ERROR_TEXT = 'Duplicate ID %(value)s in column %(column_name)s' + +class UnusedStop(ExceptionWithContext): + ERROR_TEXT = "%(stop_name)s (ID %(stop_id)s) isn't used in any trips" + +class UsedStation(ExceptionWithContext): + ERROR_TEXT = "%(stop_name)s (ID %(stop_id)s) has location_type=1 " \ + "(station) so it should not appear in stop_times" + +class StopTooFarFromParentStation(ExceptionWithContext): + ERROR_TEXT = ( + "%(stop_name)s (ID %(stop_id)s) is too far from its parent station " + "%(parent_stop_name)s (ID %(parent_stop_id)s) : %(distance).2f meters.") + def __cmp__(self, y): + # Sort in decreasing order because more distance is more significant. + return cmp(y.distance, self.distance) + + +class StopsTooClose(ExceptionWithContext): + ERROR_TEXT = ( + "The stops \"%(stop_name_a)s\" (ID %(stop_id_a)s) and \"%(stop_name_b)s\"" + " (ID %(stop_id_b)s) are %(distance)0.2fm apart and probably represent " + "the same location.") + def __cmp__(self, y): + # Sort in increasing order because less distance is more significant. + return cmp(self.distance, y.distance) + +class StationsTooClose(ExceptionWithContext): + ERROR_TEXT = ( + "The stations \"%(stop_name_a)s\" (ID %(stop_id_a)s) and " + "\"%(stop_name_b)s\" (ID %(stop_id_b)s) are %(distance)0.2fm apart and " + "probably represent the same location.") + def __cmp__(self, y): + # Sort in increasing order because less distance is more significant. + return cmp(self.distance, y.distance) + +class DifferentStationTooClose(ExceptionWithContext): + ERROR_TEXT = ( + "The parent_station of stop \"%(stop_name)s\" (ID %(stop_id)s) is not " + "station \"%(station_stop_name)s\" (ID %(station_stop_id)s) but they are " + "only %(distance)0.2fm apart.") + def __cmp__(self, y): + # Sort in increasing order because less distance is more significant. + return cmp(self.distance, y.distance) + +class StopTooFarFromShapeWithDistTraveled(ExceptionWithContext): + ERROR_TEXT = ( + "For trip %(trip_id)s the stop \"%(stop_name)s\" (ID %(stop_id)s) is " + "%(distance).0f meters away from the corresponding point " + "(shape_dist_traveled: %(shape_dist_traveled)f) on shape %(shape_id)s. " + "It should be closer than %(max_distance).0f meters.") + def __cmp__(self, y): + # Sort in decreasing order because more distance is more significant. + return cmp(y.distance, self.distance) + + +class TooManyDaysWithoutService(ExceptionWithContext): + ERROR_TEXT = "There are %(consecutive_days_without_service)i consecutive"\ + " days, from %(first_day_without_service)s to" \ + " %(last_day_without_service)s, without any scheduled service." \ + " Please ensure this is intentional." + + +class ExpirationDate(ExceptionWithContext): + def FormatProblem(self, d=None): + if not d: + d = self.GetDictToFormat() + expiration = d['expiration'] + formatted_date = time.strftime("%B %d, %Y", + time.localtime(expiration)) + if (expiration < time.mktime(time.localtime())): + return "This feed expired on %s" % formatted_date + else: + return "This feed will soon expire, on %s" % formatted_date + +class FutureService(ExceptionWithContext): + def FormatProblem(self, d=None): + if not d: + d = self.GetDictToFormat() + formatted_date = time.strftime("%B %d, %Y", time.localtime(d['start_date'])) + return ("The earliest service date in this feed is in the future, on %s. " + "Published feeds must always include the current date." % + formatted_date) + + +class InvalidLineEnd(ExceptionWithContext): + ERROR_TEXT = "Each line must end with CR LF or LF except for the last line " \ + "of the file. This line ends with \"%(bad_line_end)s\"." + +class StopWithMultipleRouteTypes(ExceptionWithContext): + ERROR_TEXT = "Stop %(stop_name)s (ID=%(stop_id)s) belongs to both " \ + "subway (ID=%(route_id1)s) and bus line (ID=%(route_id2)s)." + +class TooFastTravel(ExceptionWithContext): + def FormatProblem(self, d=None): + if not d: + d = self.GetDictToFormat() + if not d['speed']: + return "High speed travel detected in trip %(trip_id)s: %(prev_stop)s" \ + " to %(next_stop)s. %(dist).0f meters in %(time)d seconds." % d + else: + return "High speed travel detected in trip %(trip_id)s: %(prev_stop)s" \ + " to %(next_stop)s. %(dist).0f meters in %(time)d seconds." \ + " (%(speed).0f km/h)." % d + def __cmp__(self, y): + # Sort in decreasing order because more distance is more significant. We + # can't sort by speed because not all TooFastTravel objects have a speed. + return cmp(y.dist, self.dist) + +class DuplicateTrip(ExceptionWithContext): + ERROR_TEXT = "Trip %(trip_id1)s of route %(route_id1)s might be duplicated " \ + "with trip %(trip_id2)s of route %(route_id2)s. They go " \ + "through the same stops with same service." + +class OtherProblem(ExceptionWithContext): + ERROR_TEXT = '%(description)s' + + +class ExceptionProblemReporter(ProblemReporter): + def __init__(self, raise_warnings=False): + ProblemReporterBase.__init__(self) + self.raise_warnings = raise_warnings + + def _Report(self, e): + if self.raise_warnings or e.IsError(): + raise e + else: + ProblemReporter._Report(self, e) + + +default_problem_reporter = ExceptionProblemReporter() + +# Add a default handler to send log messages to console +console = logging.StreamHandler() +console.setLevel(logging.WARNING) +log = logging.getLogger("schedule_builder") +log.addHandler(console) + + +class Error(Exception): + pass + + +def IsValidURL(url): + """Checks the validity of a URL value.""" + # TODO: Add more thorough checking of URL + return url.startswith(u'http://') or url.startswith(u'https://') + + +def IsValidColor(color): + """Checks the validity of a hex color value.""" + return not re.match('^[0-9a-fA-F]{6}$', color) == None + + +def ColorLuminance(color): + """Compute the brightness of an sRGB color using the formula from + http://www.w3.org/TR/2000/WD-AERT-20000426#color-contrast. + + Args: + color: a string of six hex digits in the format verified by IsValidColor(). + + Returns: + A floating-point number between 0.0 (black) and 255.0 (white). """ + r = int(color[0:2], 16) + g = int(color[2:4], 16) + b = int(color[4:6], 16) + return (299*r + 587*g + 114*b) / 1000.0 + + +def IsEmpty(value): + return value is None or (isinstance(value, basestring) and not value.strip()) + + +def FindUniqueId(dic): + """Return a string not used as a key in the dictionary dic""" + name = str(len(dic)) + while name in dic: + name = str(random.randint(1, 999999999)) + return name + + +def TimeToSecondsSinceMidnight(time_string): + """Convert HHH:MM:SS into seconds since midnight. + + For example "01:02:03" returns 3723. The leading zero of the hours may be + omitted. HH may be more than 23 if the time is on the following day.""" + m = re.match(r'(\d{1,3}):([0-5]\d):([0-5]\d)$', time_string) + # ignored: matching for leap seconds + if not m: + raise Error, 'Bad HH:MM:SS "%s"' % time_string + return int(m.group(1)) * 3600 + int(m.group(2)) * 60 + int(m.group(3)) + + +def FormatSecondsSinceMidnight(s): + """Formats an int number of seconds past midnight into a string + as "HH:MM:SS".""" + return "%02d:%02d:%02d" % (s / 3600, (s / 60) % 60, s % 60) + + +def DateStringToDateObject(date_string): + """Return a date object for a string "YYYYMMDD".""" + # If this becomes a bottleneck date objects could be cached + return datetime.date(int(date_string[0:4]), int(date_string[4:6]), + int(date_string[6:8])) + + +def FloatStringToFloat(float_string): + """Convert a float as a string to a float or raise an exception""" + # Will raise TypeError unless a string + if not re.match(r"^[+-]?\d+(\.\d+)?$", float_string): + raise ValueError() + return float(float_string) + + +def NonNegIntStringToInt(int_string): + """Convert an non-negative integer string to an int or raise an exception""" + # Will raise TypeError unless a string + if not re.match(r"^(?:0|[1-9]\d*)$", int_string): + raise ValueError() + return int(int_string) + + +EARTH_RADIUS = 6378135 # in meters +def ApproximateDistance(degree_lat1, degree_lng1, degree_lat2, degree_lng2): + """Compute approximate distance between two points in meters. Assumes the + Earth is a sphere.""" + # TODO: change to ellipsoid approximation, such as + # http://www.codeguru.com/Cpp/Cpp/algorithms/article.php/c5115/ + lat1 = math.radians(degree_lat1) + lng1 = math.radians(degree_lng1) + lat2 = math.radians(degree_lat2) + lng2 = math.radians(degree_lng2) + dlat = math.sin(0.5 * (lat2 - lat1)) + dlng = math.sin(0.5 * (lng2 - lng1)) + x = dlat * dlat + dlng * dlng * math.cos(lat1) * math.cos(lat2) + return EARTH_RADIUS * (2 * math.atan2(math.sqrt(x), + math.sqrt(max(0.0, 1.0 - x)))) + + +def ApproximateDistanceBetweenStops(stop1, stop2): + """Compute approximate distance between two stops in meters. Assumes the + Earth is a sphere.""" + return ApproximateDistance(stop1.stop_lat, stop1.stop_lon, + stop2.stop_lat, stop2.stop_lon) + + +class GenericGTFSObject(object): + """Object with arbitrary attributes which may be added to a schedule. + + This class should be used as the base class for GTFS objects which may + be stored in a Schedule. It defines some methods for reading and writing + attributes. If self._schedule is None than the object is not in a Schedule. + + Subclasses must: + * define an __init__ method which sets the _schedule member to None or a + weakref to a Schedule + * Set the _TABLE_NAME class variable to a name such as 'stops', 'agency', ... + * define methods to validate objects of that type + """ + def __getitem__(self, name): + """Return a unicode or str representation of name or "" if not set.""" + if name in self.__dict__ and self.__dict__[name] is not None: + return "%s" % self.__dict__[name] + else: + return "" + + def __getattr__(self, name): + """Return None or the default value if name is a known attribute. + + This method is only called when name is not found in __dict__. + """ + if name in self.__class__._FIELD_NAMES: + return None + else: + raise AttributeError(name) + + def iteritems(self): + """Return a iterable for (name, value) pairs of public attributes.""" + for name, value in self.__dict__.iteritems(): + if (not name) or name[0] == "_": + continue + yield name, value + + def __setattr__(self, name, value): + """Set an attribute, adding name to the list of columns as needed.""" + object.__setattr__(self, name, value) + if name[0] != '_' and self._schedule: + self._schedule.AddTableColumn(self.__class__._TABLE_NAME, name) + + def __eq__(self, other): + """Return true iff self and other are equivalent""" + if not other: + return False + + if id(self) == id(other): + return True + + for k in self.keys().union(other.keys()): + # use __getitem__ which returns "" for missing columns values + if self[k] != other[k]: + return False + return True + + def __ne__(self, other): + return not self.__eq__(other) + + def __repr__(self): + return "<%s %s>" % (self.__class__.__name__, sorted(self.iteritems())) + + def keys(self): + """Return iterable of columns used by this object.""" + columns = set() + for name in vars(self): + if (not name) or name[0] == "_": + continue + columns.add(name) + return columns + + def _ColumnNames(self): + return self.keys() + + +class Stop(GenericGTFSObject): + """Represents a single stop. A stop must have a latitude, longitude and name. + + Callers may assign arbitrary values to instance attributes. + Stop.ParseAttributes validates attributes according to GTFS and converts some + into native types. ParseAttributes may delete invalid attributes. + Accessing an attribute that is a column in GTFS will return None if this + object does not have a value or it is ''. + A Stop object acts like a dict with string values. + + Attributes: + stop_lat: a float representing the latitude of the stop + stop_lon: a float representing the longitude of the stop + All other attributes are strings. + """ + _REQUIRED_FIELD_NAMES = ['stop_id', 'stop_name', 'stop_lat', 'stop_lon'] + _FIELD_NAMES = _REQUIRED_FIELD_NAMES + \ + ['stop_desc', 'zone_id', 'stop_url', 'stop_code', + 'location_type', 'parent_station'] + _TABLE_NAME = 'stops' + + def __init__(self, lat=None, lng=None, name=None, stop_id=None, + field_dict=None, stop_code=None): + """Initialize a new Stop object. + + Args: + field_dict: A dictionary mapping attribute name to unicode string + lat: a float, ignored when field_dict is present + lng: a float, ignored when field_dict is present + name: a string, ignored when field_dict is present + stop_id: a string, ignored when field_dict is present + stop_code: a string, ignored when field_dict is present + """ + self._schedule = None + if field_dict: + if isinstance(field_dict, Stop): + # Special case so that we don't need to re-parse the attributes to + # native types iteritems returns all attributes that don't start with _ + for k, v in field_dict.iteritems(): + self.__dict__[k] = v + else: + self.__dict__.update(field_dict) + else: + if lat is not None: + self.stop_lat = lat + if lng is not None: + self.stop_lon = lng + if name is not None: + self.stop_name = name + if stop_id is not None: + self.stop_id = stop_id + if stop_code is not None: + self.stop_code = stop_code + + def GetTrips(self, schedule=None): + """Return iterable containing trips that visit this stop.""" + return [trip for trip, ss in self._GetTripSequence(schedule)] + + def _GetTripSequence(self, schedule=None): + """Return a list of (trip, stop_sequence) for all trips visiting this stop. + + A trip may be in the list multiple times with different index. + stop_sequence is an integer. + + Args: + schedule: Deprecated, do not use. + """ + if schedule is None: + schedule = getattr(self, "_schedule", None) + if schedule is None: + warnings.warn("No longer supported. _schedule attribute is used to get " + "stop_times table", DeprecationWarning) + cursor = schedule._connection.cursor() + cursor.execute("SELECT trip_id,stop_sequence FROM stop_times " + "WHERE stop_id=?", + (self.stop_id, )) + return [(schedule.GetTrip(row[0]), row[1]) for row in cursor] + + def _GetTripIndex(self, schedule=None): + """Return a list of (trip, index). + + trip: a Trip object + index: an offset in trip.GetStopTimes() + """ + trip_index = [] + for trip, sequence in self._GetTripSequence(schedule): + for index, st in enumerate(trip.GetStopTimes()): + if st.stop_sequence == sequence: + trip_index.append((trip, index)) + break + else: + raise RuntimeError("stop_sequence %d not found in trip_id %s" % + sequence, trip.trip_id) + return trip_index + + def GetStopTimeTrips(self, schedule=None): + """Return a list of (time, (trip, index), is_timepoint). + + time: an integer. It might be interpolated. + trip: a Trip object. + index: the offset of this stop in trip.GetStopTimes(), which may be + different from the stop_sequence. + is_timepoint: a bool + """ + time_trips = [] + for trip, index in self._GetTripIndex(schedule): + secs, stoptime, is_timepoint = trip.GetTimeInterpolatedStops()[index] + time_trips.append((secs, (trip, index), is_timepoint)) + return time_trips + + def ParseAttributes(self, problems): + """Parse all attributes, calling problems as needed.""" + # Need to use items() instead of iteritems() because _CheckAndSetAttr may + # modify self.__dict__ + for name, value in vars(self).items(): + if name[0] == "_": + continue + self._CheckAndSetAttr(name, value, problems) + + def _CheckAndSetAttr(self, name, value, problems): + """If value is valid for attribute name store it. + + If value is not valid call problems. Return a new value of the correct type + or None if value couldn't be converted. + """ + if name == 'stop_lat': + try: + if isinstance(value, (float, int)): + self.stop_lat = value + else: + self.stop_lat = FloatStringToFloat(value) + except (ValueError, TypeError): + problems.InvalidValue('stop_lat', value) + del self.stop_lat + else: + if self.stop_lat > 90 or self.stop_lat < -90: + problems.InvalidValue('stop_lat', value) + elif name == 'stop_lon': + try: + if isinstance(value, (float, int)): + self.stop_lon = value + else: + self.stop_lon = FloatStringToFloat(value) + except (ValueError, TypeError): + problems.InvalidValue('stop_lon', value) + del self.stop_lon + else: + if self.stop_lon > 180 or self.stop_lon < -180: + problems.InvalidValue('stop_lon', value) + elif name == 'stop_url': + if value and not IsValidURL(value): + problems.InvalidValue('stop_url', value) + del self.stop_url + elif name == 'location_type': + if value == '': + self.location_type = 0 + else: + try: + self.location_type = int(value) + except (ValueError, TypeError): + problems.InvalidValue('location_type', value) + del self.location_type + else: + if self.location_type not in (0, 1): + problems.InvalidValue('location_type', value, type=TYPE_WARNING) + + def __getattr__(self, name): + """Return None or the default value if name is a known attribute. + + This method is only called when name is not found in __dict__. + """ + if name == "location_type": + return 0 + elif name == "trip_index": + return self._GetTripIndex() + elif name in Stop._FIELD_NAMES: + return None + else: + raise AttributeError(name) + + def Validate(self, problems=default_problem_reporter): + # First check that all required fields are present because ParseAttributes + # may remove invalid attributes. + for required in Stop._REQUIRED_FIELD_NAMES: + if IsEmpty(getattr(self, required, None)): + # TODO: For now I'm keeping the API stable but it would be cleaner to + # treat whitespace stop_id as invalid, instead of missing + problems.MissingValue(required) + + # Check individual values and convert to native types + self.ParseAttributes(problems) + + # Check that this object is consistent with itself + if (self.stop_lat is not None and self.stop_lon is not None and + abs(self.stop_lat) < 1.0) and (abs(self.stop_lon) < 1.0): + problems.InvalidValue('stop_lat', self.stop_lat, + 'Stop location too close to 0, 0', + type=TYPE_WARNING) + if (self.stop_desc is not None and self.stop_name is not None and + self.stop_desc and self.stop_name and + not IsEmpty(self.stop_desc) and + self.stop_name.strip().lower() == self.stop_desc.strip().lower()): + problems.InvalidValue('stop_desc', self.stop_desc, + 'stop_desc should not be the same as stop_name') + + if self.parent_station and self.location_type == 1: + problems.InvalidValue('parent_station', self.parent_station, + 'Stop row with location_type=1 (a station) must ' + 'not have a parent_station') + + +class Route(GenericGTFSObject): + """Represents a single route.""" + + _REQUIRED_FIELD_NAMES = [ + 'route_id', 'route_short_name', 'route_long_name', 'route_type' + ] + _FIELD_NAMES = _REQUIRED_FIELD_NAMES + [ + 'agency_id', 'route_desc', 'route_url', 'route_color', 'route_text_color' + ] + _ROUTE_TYPES = { + 0: {'name':'Tram', 'max_speed':100}, + 1: {'name':'Subway', 'max_speed':150}, + 2: {'name':'Rail', 'max_speed':300}, + 3: {'name':'Bus', 'max_speed':100}, + 4: {'name':'Ferry', 'max_speed':80}, + 5: {'name':'Cable Car', 'max_speed':50}, + 6: {'name':'Gondola', 'max_speed':50}, + 7: {'name':'Funicular', 'max_speed':50}, + } + # Create a reverse lookup dict of route type names to route types. + _ROUTE_TYPE_IDS = set(_ROUTE_TYPES.keys()) + _ROUTE_TYPE_NAMES = dict((v['name'], k) for k, v in _ROUTE_TYPES.items()) + _TABLE_NAME = 'routes' + + def __init__(self, short_name=None, long_name=None, route_type=None, + route_id=None, agency_id=None, field_dict=None): + self._schedule = None + self._trips = [] + + if not field_dict: + field_dict = {} + if short_name is not None: + field_dict['route_short_name'] = short_name + if long_name is not None: + field_dict['route_long_name'] = long_name + if route_type is not None: + if route_type in Route._ROUTE_TYPE_NAMES: + self.route_type = Route._ROUTE_TYPE_NAMES[route_type] + else: + field_dict['route_type'] = route_type + if route_id is not None: + field_dict['route_id'] = route_id + if agency_id is not None: + field_dict['agency_id'] = agency_id + self.__dict__.update(field_dict) + + def AddTrip(self, schedule, headsign, service_period=None, trip_id=None): + """ Adds a trip to this route. + + Args: + headsign: headsign of the trip as a string + + Returns: + a new Trip object + """ + if trip_id is None: + trip_id = unicode(len(schedule.trips)) + if service_period is None: + service_period = schedule.GetDefaultServicePeriod() + trip = Trip(route=self, headsign=headsign, service_period=service_period, + trip_id=trip_id) + schedule.AddTripObject(trip) + return trip + + def _AddTripObject(self, trip): + # Only class Schedule may call this. Users of the API should call + # Route.AddTrip or schedule.AddTripObject. + self._trips.append(trip) + + def __getattr__(self, name): + """Return None or the default value if name is a known attribute. + + This method overrides GenericGTFSObject.__getattr__ to provide backwards + compatible access to trips. + """ + if name == 'trips': + return self._trips + else: + return GenericGTFSObject.__getattr__(self, name) + + def GetPatternIdTripDict(self): + """Return a dictionary that maps pattern_id to a list of Trip objects.""" + d = {} + for t in self._trips: + d.setdefault(t.pattern_id, []).append(t) + return d + + def Validate(self, problems=default_problem_reporter): + if IsEmpty(self.route_id): + problems.MissingValue('route_id') + if IsEmpty(self.route_type): + problems.MissingValue('route_type') + + if IsEmpty(self.route_short_name) and IsEmpty(self.route_long_name): + problems.InvalidValue('route_short_name', + self.route_short_name, + 'Both route_short_name and ' + 'route_long name are blank.') + + if self.route_short_name and len(self.route_short_name) > 6: + problems.InvalidValue('route_short_name', + self.route_short_name, + 'This route_short_name is relatively long, which ' + 'probably means that it contains a place name. ' + 'You should only use this field to hold a short ' + 'code that riders use to identify a route. ' + 'If this route doesn\'t have such a code, it\'s ' + 'OK to leave this field empty.', type=TYPE_WARNING) + + if self.route_short_name and self.route_long_name: + short_name = self.route_short_name.strip().lower() + long_name = self.route_long_name.strip().lower() + if (long_name.startswith(short_name + ' ') or + long_name.startswith(short_name + '(') or + long_name.startswith(short_name + '-')): + problems.InvalidValue('route_long_name', + self.route_long_name, + 'route_long_name shouldn\'t contain ' + 'the route_short_name value, as both ' + 'fields are often displayed ' + 'side-by-side.', type=TYPE_WARNING) + if long_name == short_name: + problems.InvalidValue('route_long_name', + self.route_long_name, + 'route_long_name shouldn\'t be the same ' + 'the route_short_name value, as both ' + 'fields are often displayed ' + 'side-by-side. It\'s OK to omit either the ' + 'short or long name (but not both).', + type=TYPE_WARNING) + if (self.route_desc and + ((self.route_desc == self.route_short_name) or + (self.route_desc == self.route_long_name))): + problems.InvalidValue('route_desc', + self.route_desc, + 'route_desc shouldn\'t be the same as ' + 'route_short_name or route_long_name') + + if self.route_type is not None: + try: + if not isinstance(self.route_type, int): + self.route_type = NonNegIntStringToInt(self.route_type) + except (TypeError, ValueError): + problems.InvalidValue('route_type', self.route_type) + else: + if self.route_type not in Route._ROUTE_TYPE_IDS: + problems.InvalidValue('route_type', + self.route_type, + type=TYPE_WARNING) + + if self.route_url and not IsValidURL(self.route_url): + problems.InvalidValue('route_url', self.route_url) + + txt_lum = ColorLuminance('000000') # black (default) + bg_lum = ColorLuminance('ffffff') # white (default) + if self.route_color: + if IsValidColor(self.route_color): + bg_lum = ColorLuminance(self.route_color) + else: + problems.InvalidValue('route_color', self.route_color, + 'route_color should be a valid color description ' + 'which consists of 6 hexadecimal characters ' + 'representing the RGB values. Example: 44AA06') + if self.route_text_color: + if IsValidColor(self.route_text_color): + txt_lum = ColorLuminance(self.route_text_color) + else: + problems.InvalidValue('route_text_color', self.route_text_color, + 'route_text_color should be a valid color ' + 'description, which consists of 6 hexadecimal ' + 'characters representing the RGB values. ' + 'Example: 44AA06') + if abs(txt_lum - bg_lum) < 510/7.: + # http://www.w3.org/TR/2000/WD-AERT-20000426#color-contrast recommends + # a threshold of 125, but that is for normal text and too harsh for + # big colored logos like line names, so we keep the original threshold + # from r541 (but note that weight has shifted between RGB components). + problems.InvalidValue('route_color', self.route_color, + 'The route_text_color and route_color should ' + 'be set to contrasting colors, as they are used ' + 'as the text and background color (respectively) ' + 'for displaying route names. When left blank, ' + 'route_text_color defaults to 000000 (black) and ' + 'route_color defaults to FFFFFF (white). A common ' + 'source of issues here is setting route_color to ' + 'a dark color, while leaving route_text_color set ' + 'to black. In this case, route_text_color should ' + 'be set to a lighter color like FFFFFF to ensure ' + 'a legible contrast between the two.', + type=TYPE_WARNING) + + +def SortListOfTripByTime(trips): + trips.sort(key=Trip.GetStartTime) + + +class StopTime(object): + """ + Represents a single stop of a trip. StopTime contains most of the columns + from the stop_times.txt file. It does not contain trip_id, which is implied + by the Trip used to access it. + + See the Google Transit Feed Specification for the semantic details. + + stop: A Stop object + arrival_time: str in the form HH:MM:SS; readonly after __init__ + departure_time: str in the form HH:MM:SS; readonly after __init__ + arrival_secs: int number of seconds since midnight + departure_secs: int number of seconds since midnight + stop_headsign: str + pickup_type: int + drop_off_type: int + shape_dist_traveled: float + stop_id: str; readonly + stop_time: The only time given for this stop. If present, it is used + for both arrival and departure time. + stop_sequence: int + """ + _REQUIRED_FIELD_NAMES = ['trip_id', 'arrival_time', 'departure_time', + 'stop_id', 'stop_sequence'] + _OPTIONAL_FIELD_NAMES = ['stop_headsign', 'pickup_type', + 'drop_off_type', 'shape_dist_traveled'] + _FIELD_NAMES = _REQUIRED_FIELD_NAMES + _OPTIONAL_FIELD_NAMES + _SQL_FIELD_NAMES = ['trip_id', 'arrival_secs', 'departure_secs', + 'stop_id', 'stop_sequence', 'stop_headsign', + 'pickup_type', 'drop_off_type', 'shape_dist_traveled'] + + __slots__ = ('arrival_secs', 'departure_secs', 'stop_headsign', 'stop', + 'stop_headsign', 'pickup_type', 'drop_off_type', + 'shape_dist_traveled', 'stop_sequence') + def __init__(self, problems, stop, + arrival_time=None, departure_time=None, + stop_headsign=None, pickup_type=None, drop_off_type=None, + shape_dist_traveled=None, arrival_secs=None, + departure_secs=None, stop_time=None, stop_sequence=None): + if stop_time != None: + arrival_time = departure_time = stop_time + + if arrival_secs != None: + self.arrival_secs = arrival_secs + elif arrival_time in (None, ""): + self.arrival_secs = None # Untimed + arrival_time = None + else: + try: + self.arrival_secs = TimeToSecondsSinceMidnight(arrival_time) + except Error: + problems.InvalidValue('arrival_time', arrival_time) + self.arrival_secs = None + + if departure_secs != None: + self.departure_secs = departure_secs + elif departure_time in (None, ""): + self.departure_secs = None + departure_time = None + else: + try: + self.departure_secs = TimeToSecondsSinceMidnight(departure_time) + except Error: + problems.InvalidValue('departure_time', departure_time) + self.departure_secs = None + + if not isinstance(stop, Stop): + # Not quite correct, but better than letting the problem propagate + problems.InvalidValue('stop', stop) + self.stop = stop + self.stop_headsign = stop_headsign + + if pickup_type in (None, ""): + self.pickup_type = None + else: + try: + pickup_type = int(pickup_type) + except ValueError: + problems.InvalidValue('pickup_type', pickup_type) + else: + if pickup_type < 0 or pickup_type > 3: + problems.InvalidValue('pickup_type', pickup_type) + self.pickup_type = pickup_type + + if drop_off_type in (None, ""): + self.drop_off_type = None + else: + try: + drop_off_type = int(drop_off_type) + except ValueError: + problems.InvalidValue('drop_off_type', drop_off_type) + else: + if drop_off_type < 0 or drop_off_type > 3: + problems.InvalidValue('drop_off_type', drop_off_type) + self.drop_off_type = drop_off_type + + if (self.pickup_type == 1 and self.drop_off_type == 1 and + self.arrival_secs == None and self.departure_secs == None): + problems.OtherProblem('This stop time has a pickup_type and ' + 'drop_off_type of 1, indicating that riders ' + 'can\'t get on or off here. Since it doesn\'t ' + 'define a timepoint either, this entry serves no ' + 'purpose and should be excluded from the trip.', + type=TYPE_WARNING) + + if ((self.arrival_secs != None) and (self.departure_secs != None) and + (self.departure_secs < self.arrival_secs)): + problems.InvalidValue('departure_time', departure_time, + 'The departure time at this stop (%s) is before ' + 'the arrival time (%s). This is often caused by ' + 'problems in the feed exporter\'s time conversion') + + # If the caller passed a valid arrival time but didn't attempt to pass a + # departure time complain + if (self.arrival_secs != None and + self.departure_secs == None and departure_time == None): + # self.departure_secs might be None because departure_time was invalid, + # so we need to check both + problems.MissingValue('departure_time', + 'arrival_time and departure_time should either ' + 'both be provided or both be left blank. ' + 'It\'s OK to set them both to the same value.') + # If the caller passed a valid departure time but didn't attempt to pass a + # arrival time complain + if (self.departure_secs != None and + self.arrival_secs == None and arrival_time == None): + problems.MissingValue('arrival_time', + 'arrival_time and departure_time should either ' + 'both be provided or both be left blank. ' + 'It\'s OK to set them both to the same value.') + + if shape_dist_traveled in (None, ""): + self.shape_dist_traveled = None + else: + try: + self.shape_dist_traveled = float(shape_dist_traveled) + except ValueError: + problems.InvalidValue('shape_dist_traveled', shape_dist_traveled) + + if stop_sequence is not None: + self.stop_sequence = stop_sequence + + def GetFieldValuesTuple(self, trip_id): + """Return a tuple that outputs a row of _FIELD_NAMES. + + trip must be provided because it is not stored in StopTime. + """ + result = [] + for fn in StopTime._FIELD_NAMES: + if fn == 'trip_id': + result.append(trip_id) + else: + result.append(getattr(self, fn) or '' ) + return tuple(result) + + def GetSqlValuesTuple(self, trip_id): + result = [] + for fn in StopTime._SQL_FIELD_NAMES: + if fn == 'trip_id': + result.append(trip_id) + else: + # This might append None, which will be inserted into SQLite as NULL + result.append(getattr(self, fn)) + return tuple(result) + + def GetTimeSecs(self): + """Return the first of arrival_secs and departure_secs that is not None. + If both are None return None.""" + if self.arrival_secs != None: + return self.arrival_secs + elif self.departure_secs != None: + return self.departure_secs + else: + return None + + def __getattr__(self, name): + if name == 'stop_id': + return self.stop.stop_id + elif name == 'arrival_time': + return (self.arrival_secs != None and + FormatSecondsSinceMidnight(self.arrival_secs) or '') + elif name == 'departure_time': + return (self.departure_secs != None and + FormatSecondsSinceMidnight(self.departure_secs) or '') + elif name == 'shape_dist_traveled': + return '' + raise AttributeError(name) + + +class Trip(GenericGTFSObject): + _REQUIRED_FIELD_NAMES = ['route_id', 'service_id', 'trip_id'] + _FIELD_NAMES = _REQUIRED_FIELD_NAMES + [ + 'trip_headsign', 'direction_id', 'block_id', 'shape_id' + ] + _FIELD_NAMES_HEADWAY = ['trip_id', 'start_time', 'end_time', 'headway_secs'] + _TABLE_NAME= "trips" + + def __init__(self, headsign=None, service_period=None, + route=None, trip_id=None, field_dict=None): + self._schedule = None + self._headways = [] # [(start_time, end_time, headway_secs)] + if not field_dict: + field_dict = {} + if headsign is not None: + field_dict['trip_headsign'] = headsign + if route: + field_dict['route_id'] = route.route_id + if trip_id is not None: + field_dict['trip_id'] = trip_id + if service_period is not None: + field_dict['service_id'] = service_period.service_id + # Earlier versions of transitfeed.py assigned self.service_period here + # and allowed the caller to set self.service_id. Schedule.Validate + # checked the service_id attribute if it was assigned and changed it to a + # service_period attribute. Now only the service_id attribute is used and + # it is validated by Trip.Validate. + if service_period is not None: + # For backwards compatibility + self.service_id = service_period.service_id + self.__dict__.update(field_dict) + + def GetFieldValuesTuple(self): + return [getattr(self, fn) or '' for fn in Trip._FIELD_NAMES] + + def AddStopTime(self, stop, problems=None, schedule=None, **kwargs): + """Add a stop to this trip. Stops must be added in the order visited. + + Args: + stop: A Stop object + kwargs: remaining keyword args passed to StopTime.__init__ + + Returns: + None + """ + if problems is None: + # TODO: delete this branch when StopTime.__init__ doesn't need a + # ProblemReporter + problems = default_problem_reporter + stoptime = StopTime(problems=problems, stop=stop, **kwargs) + self.AddStopTimeObject(stoptime, schedule) + + def _AddStopTimeObjectUnordered(self, stoptime, schedule): + """Add StopTime object to this trip. + + The trip isn't checked for duplicate sequence numbers so it must be + validated later.""" + cursor = schedule._connection.cursor() + insert_query = "INSERT INTO stop_times (%s) VALUES (%s);" % ( + ','.join(StopTime._SQL_FIELD_NAMES), + ','.join(['?'] * len(StopTime._SQL_FIELD_NAMES))) + cursor = schedule._connection.cursor() + cursor.execute( + insert_query, stoptime.GetSqlValuesTuple(self.trip_id)) + + def ReplaceStopTimeObject(self, stoptime, schedule=None): + """Replace a StopTime object from this trip with the given one. + + Keys the StopTime object to be replaced by trip_id, stop_sequence + and stop_id as 'stoptime', with the object 'stoptime'. + """ + + if schedule is None: + schedule = self._schedule + + new_secs = stoptime.GetTimeSecs() + cursor = schedule._connection.cursor() + cursor.execute("DELETE FROM stop_times WHERE trip_id=? and " + "stop_sequence=? and stop_id=?", + (self.trip_id, stoptime.stop_sequence, stoptime.stop_id)) + if cursor.rowcount == 0: + raise Error, 'Attempted replacement of StopTime object which does not exist' + self._AddStopTimeObjectUnordered(stoptime, schedule) + + def AddStopTimeObject(self, stoptime, schedule=None, problems=None): + """Add a StopTime object to the end of this trip. + + Args: + stoptime: A StopTime object. Should not be reused in multiple trips. + schedule: Schedule object containing this trip which must be + passed to Trip.__init__ or here + problems: ProblemReporter object for validating the StopTime in its new + home + + Returns: + None + """ + if schedule is None: + schedule = self._schedule + if schedule is None: + warnings.warn("No longer supported. _schedule attribute is used to get " + "stop_times table", DeprecationWarning) + if problems is None: + problems = schedule.problem_reporter + + new_secs = stoptime.GetTimeSecs() + cursor = schedule._connection.cursor() + cursor.execute("SELECT max(stop_sequence), max(arrival_secs), " + "max(departure_secs) FROM stop_times WHERE trip_id=?", + (self.trip_id,)) + row = cursor.fetchone() + if row[0] is None: + # This is the first stop_time of the trip + stoptime.stop_sequence = 1 + if new_secs == None: + problems.OtherProblem( + 'No time for first StopTime of trip_id "%s"' % (self.trip_id,)) + else: + stoptime.stop_sequence = row[0] + 1 + prev_secs = max(row[1], row[2]) + if new_secs != None and new_secs < prev_secs: + problems.OtherProblem( + 'out of order stop time for stop_id=%s trip_id=%s %s < %s' % + (EncodeUnicode(stoptime.stop_id), EncodeUnicode(self.trip_id), + FormatSecondsSinceMidnight(new_secs), + FormatSecondsSinceMidnight(prev_secs))) + self._AddStopTimeObjectUnordered(stoptime, schedule) + + def GetTimeStops(self): + """Return a list of (arrival_secs, departure_secs, stop) tuples. + + Caution: arrival_secs and departure_secs may be 0, a false value meaning a + stop at midnight or None, a false value meaning the stop is untimed.""" + return [(st.arrival_secs, st.departure_secs, st.stop) for st in + self.GetStopTimes()] + + def GetCountStopTimes(self): + """Return the number of stops made by this trip.""" + cursor = self._schedule._connection.cursor() + cursor.execute( + 'SELECT count(*) FROM stop_times WHERE trip_id=?', (self.trip_id,)) + return cursor.fetchone()[0] + + def GetTimeInterpolatedStops(self): + """Return a list of (secs, stoptime, is_timepoint) tuples. + + secs will always be an int. If the StopTime object does not have explict + times this method guesses using distance. stoptime is a StopTime object and + is_timepoint is a bool. + + Raises: + ValueError if this trip does not have the times needed to interpolate + """ + rv = [] + + stoptimes = self.GetStopTimes() + # If there are no stoptimes [] is the correct return value but if the start + # or end are missing times there is no correct return value. + if not stoptimes: + return [] + if (stoptimes[0].GetTimeSecs() is None or + stoptimes[-1].GetTimeSecs() is None): + raise ValueError("%s must have time at first and last stop" % (self)) + + cur_timepoint = None + next_timepoint = None + distance_between_timepoints = 0 + distance_traveled_between_timepoints = 0 + + for i, st in enumerate(stoptimes): + if st.GetTimeSecs() != None: + cur_timepoint = st + distance_between_timepoints = 0 + distance_traveled_between_timepoints = 0 + if i + 1 < len(stoptimes): + k = i + 1 + distance_between_timepoints += ApproximateDistanceBetweenStops(stoptimes[k-1].stop, stoptimes[k].stop) + while stoptimes[k].GetTimeSecs() == None: + k += 1 + distance_between_timepoints += ApproximateDistanceBetweenStops(stoptimes[k-1].stop, stoptimes[k].stop) + next_timepoint = stoptimes[k] + rv.append( (st.GetTimeSecs(), st, True) ) + else: + distance_traveled_between_timepoints += ApproximateDistanceBetweenStops(stoptimes[i-1].stop, st.stop) + distance_percent = distance_traveled_between_timepoints / distance_between_timepoints + total_time = next_timepoint.GetTimeSecs() - cur_timepoint.GetTimeSecs() + time_estimate = distance_percent * total_time + cur_timepoint.GetTimeSecs() + rv.append( (int(round(time_estimate)), st, False) ) + + return rv + + def ClearStopTimes(self): + """Remove all stop times from this trip. + + StopTime objects previously returned by GetStopTimes are unchanged but are + no longer associated with this trip. + """ + cursor = self._schedule._connection.cursor() + cursor.execute('DELETE FROM stop_times WHERE trip_id=?', (self.trip_id,)) + + def GetStopTimes(self, problems=None): + """Return a sorted list of StopTime objects for this trip.""" + # In theory problems=None should be safe because data from database has been + # validated. See comment in _LoadStopTimes for why this isn't always true. + cursor = self._schedule._connection.cursor() + cursor.execute( + 'SELECT arrival_secs,departure_secs,stop_headsign,pickup_type,' + 'drop_off_type,shape_dist_traveled,stop_id,stop_sequence FROM ' + 'stop_times WHERE ' + 'trip_id=? ORDER BY stop_sequence', (self.trip_id,)) + stop_times = [] + for row in cursor.fetchall(): + stop = self._schedule.GetStop(row[6]) + stop_times.append(StopTime(problems=problems, stop=stop, arrival_secs=row[0], + departure_secs=row[1], + stop_headsign=row[2], + pickup_type=row[3], + drop_off_type=row[4], + shape_dist_traveled=row[5], + stop_sequence=row[7])) + return stop_times + + def GetHeadwayStopTimes(self, problems=None): + """Return a list of StopTime objects for each headway-based run. + + Returns: + a list of list of StopTime objects. Each list of StopTime objects + represents one run. If this trip doesn't have headways returns an empty + list. + """ + stoptimes_list = [] # list of stoptime lists to be returned + stoptime_pattern = self.GetStopTimes() + first_secs = stoptime_pattern[0].arrival_secs # first time of the trip + # for each start time of a headway run + for run_secs in self.GetHeadwayStartTimes(): + # stop time list for a headway run + stoptimes = [] + # go through the pattern and generate stoptimes + for st in stoptime_pattern: + arrival_secs, departure_secs = None, None # default value if the stoptime is not timepoint + if st.arrival_secs != None: + arrival_secs = st.arrival_secs - first_secs + run_secs + if st.departure_secs != None: + departure_secs = st.departure_secs - first_secs + run_secs + # append stoptime + stoptimes.append(StopTime(problems=problems, stop=st.stop, + arrival_secs=arrival_secs, + departure_secs=departure_secs, + stop_headsign=st.stop_headsign, + pickup_type=st.pickup_type, + drop_off_type=st.drop_off_type, + shape_dist_traveled=st.shape_dist_traveled, + stop_sequence=st.stop_sequence)) + # add stoptimes to the stoptimes_list + stoptimes_list.append ( stoptimes ) + return stoptimes_list + + def GetStartTime(self, problems=default_problem_reporter): + """Return the first time of the trip. TODO: For trips defined by frequency + return the first time of the first trip.""" + cursor = self._schedule._connection.cursor() + cursor.execute( + 'SELECT arrival_secs,departure_secs FROM stop_times WHERE ' + 'trip_id=? ORDER BY stop_sequence LIMIT 1', (self.trip_id,)) + (arrival_secs, departure_secs) = cursor.fetchone() + if arrival_secs != None: + return arrival_secs + elif departure_secs != None: + return departure_secs + else: + problems.InvalidValue('departure_time', '', + 'The first stop_time in trip %s is missing ' + 'times.' % self.trip_id) + + def GetHeadwayStartTimes(self): + """Return a list of start time for each headway-based run. + + Returns: + a sorted list of seconds since midnight, the start time of each run. If + this trip doesn't have headways returns an empty list.""" + start_times = [] + # for each headway period of the trip + for start_secs, end_secs, headway_secs in self.GetHeadwayPeriodTuples(): + # reset run secs to the start of the timeframe + run_secs = start_secs + while run_secs < end_secs: + start_times.append(run_secs) + # increment current run secs by headway secs + run_secs += headway_secs + return start_times + + def GetEndTime(self, problems=default_problem_reporter): + """Return the last time of the trip. TODO: For trips defined by frequency + return the last time of the last trip.""" + cursor = self._schedule._connection.cursor() + cursor.execute( + 'SELECT arrival_secs,departure_secs FROM stop_times WHERE ' + 'trip_id=? ORDER BY stop_sequence DESC LIMIT 1', (self.trip_id,)) + (arrival_secs, departure_secs) = cursor.fetchone() + if departure_secs != None: + return departure_secs + elif arrival_secs != None: + return arrival_secs + else: + problems.InvalidValue('arrival_time', '', + 'The last stop_time in trip %s is missing ' + 'times.' % self.trip_id) + + def _GenerateStopTimesTuples(self): + """Generator for rows of the stop_times file""" + stoptimes = self.GetStopTimes() + for i, st in enumerate(stoptimes): + yield st.GetFieldValuesTuple(self.trip_id) + + def GetStopTimesTuples(self): + results = [] + for time_tuple in self._GenerateStopTimesTuples(): + results.append(time_tuple) + return results + + def GetPattern(self): + """Return a tuple of Stop objects, in the order visited""" + stoptimes = self.GetStopTimes() + return tuple(st.stop for st in stoptimes) + + def AddHeadwayPeriod(self, start_time, end_time, headway_secs, + problem_reporter=default_problem_reporter): + """Adds a period to this trip during which the vehicle travels + at regular intervals (rather than specifying exact times for each stop). + + Args: + start_time: The time at which this headway period starts, either in + numerical seconds since midnight or as "HH:MM:SS" since midnight. + end_time: The time at which this headway period ends, either in + numerical seconds since midnight or as "HH:MM:SS" since midnight. + This value should be larger than start_time. + headway_secs: The amount of time, in seconds, between occurences of + this trip. + problem_reporter: Optional parameter that can be used to select + how any errors in the other input parameters will be reported. + Returns: + None + """ + if start_time == None or start_time == '': # 0 is OK + problem_reporter.MissingValue('start_time') + return + if isinstance(start_time, basestring): + try: + start_time = TimeToSecondsSinceMidnight(start_time) + except Error: + problem_reporter.InvalidValue('start_time', start_time) + return + elif start_time < 0: + problem_reporter.InvalidValue('start_time', start_time) + + if end_time == None or end_time == '': + problem_reporter.MissingValue('end_time') + return + if isinstance(end_time, basestring): + try: + end_time = TimeToSecondsSinceMidnight(end_time) + except Error: + problem_reporter.InvalidValue('end_time', end_time) + return + elif end_time < 0: + problem_reporter.InvalidValue('end_time', end_time) + return + + if not headway_secs: + problem_reporter.MissingValue('headway_secs') + return + try: + headway_secs = int(headway_secs) + except ValueError: + problem_reporter.InvalidValue('headway_secs', headway_secs) + return + + if headway_secs <= 0: + problem_reporter.InvalidValue('headway_secs', headway_secs) + return + + if end_time <= start_time: + problem_reporter.InvalidValue('end_time', end_time, + 'should be greater than start_time') + + self._headways.append((start_time, end_time, headway_secs)) + + def ClearHeadwayPeriods(self): + self._headways = [] + + def _HeadwayOutputTuple(self, headway): + return (self.trip_id, + FormatSecondsSinceMidnight(headway[0]), + FormatSecondsSinceMidnight(headway[1]), + unicode(headway[2])) + + def GetHeadwayPeriodOutputTuples(self): + tuples = [] + for headway in self._headways: + tuples.append(self._HeadwayOutputTuple(headway)) + return tuples + + def GetHeadwayPeriodTuples(self): + return self._headways + + def __getattr__(self, name): + if name == 'service_period': + assert self._schedule, "Must be in a schedule to get service_period" + return self._schedule.GetServicePeriod(self.service_id) + elif name == 'pattern_id': + if '_pattern_id' not in self.__dict__: + self.__dict__['_pattern_id'] = hash(self.GetPattern()) + return self.__dict__['_pattern_id'] + else: + return GenericGTFSObject.__getattr__(self, name) + + def Validate(self, problems, validate_children=True): + """Validate attributes of this object. + + Check that this object has all required values set to a valid value without + reference to the rest of the schedule. If the _schedule attribute is set + then check that references such as route_id and service_id are correct. + + Args: + problems: A ProblemReporter object + validate_children: if True and the _schedule attribute is set than call + ValidateChildren + """ + if IsEmpty(self.route_id): + problems.MissingValue('route_id') + if 'service_period' in self.__dict__: + # Some tests assign to the service_period attribute. Patch up self before + # proceeding with validation. See also comment in Trip.__init__. + self.service_id = self.__dict__['service_period'].service_id + del self.service_period + if IsEmpty(self.service_id): + problems.MissingValue('service_id') + if IsEmpty(self.trip_id): + problems.MissingValue('trip_id') + if hasattr(self, 'direction_id') and (not IsEmpty(self.direction_id)) and \ + (self.direction_id != '0') and (self.direction_id != '1'): + problems.InvalidValue('direction_id', self.direction_id, + 'direction_id must be "0" or "1"') + if self._schedule: + if self.shape_id and self.shape_id not in self._schedule._shapes: + problems.InvalidValue('shape_id', self.shape_id) + if self.route_id and self.route_id not in self._schedule.routes: + problems.InvalidValue('route_id', self.route_id) + if (self.service_id and + self.service_id not in self._schedule.service_periods): + problems.InvalidValue('service_id', self.service_id) + + if validate_children: + self.ValidateChildren(problems) + + def ValidateChildren(self, problems): + """Validate StopTimes and headways of this trip.""" + assert self._schedule, "Trip must be in a schedule to ValidateChildren" + # TODO: validate distance values in stop times (if applicable) + cursor = self._schedule._connection.cursor() + cursor.execute("SELECT COUNT(stop_sequence) AS a FROM stop_times " + "WHERE trip_id=? GROUP BY stop_sequence HAVING a > 1", + (self.trip_id,)) + for row in cursor: + problems.InvalidValue('stop_sequence', row[0], + 'Duplicate stop_sequence in trip_id %s' % + self.trip_id) + + stoptimes = self.GetStopTimes(problems) + if stoptimes: + if stoptimes[0].arrival_time is None and stoptimes[0].departure_time is None: + problems.OtherProblem( + 'No time for start of trip_id "%s""' % (self.trip_id)) + if stoptimes[-1].arrival_time is None and stoptimes[-1].departure_time is None: + problems.OtherProblem( + 'No time for end of trip_id "%s""' % (self.trip_id)) + + # Sorts the stoptimes by sequence and then checks that the arrival time + # for each time point is after the departure time of the previous. + stoptimes.sort(key=lambda x: x.stop_sequence) + prev_departure = 0 + prev_stop = None + prev_distance = None + try: + route_type = self._schedule.GetRoute(self.route_id).route_type + max_speed = Route._ROUTE_TYPES[route_type]['max_speed'] + except KeyError, e: + # If route_type cannot be found, assume it is 0 (Tram) for checking + # speeds between stops. + max_speed = Route._ROUTE_TYPES[0]['max_speed'] + for timepoint in stoptimes: + # Distance should be a nonnegative float number, so it should be + # always larger than None. + distance = timepoint.shape_dist_traveled + if distance is not None: + if distance > prev_distance and distance >= 0: + prev_distance = distance + else: + if distance == prev_distance: + type = TYPE_WARNING + else: + type = TYPE_ERROR + problems.InvalidValue('stoptimes.shape_dist_traveled', distance, + 'For the trip %s the stop %s has shape_dist_traveled=%s, ' + 'which should be larger than the previous ones. In this ' + 'case, the previous distance was %s.' % + (self.trip_id, timepoint.stop_id, distance, prev_distance), + type=type) + + if timepoint.arrival_secs is not None: + self._CheckSpeed(prev_stop, timepoint.stop, prev_departure, + timepoint.arrival_secs, max_speed, problems) + + if timepoint.arrival_secs >= prev_departure: + prev_departure = timepoint.departure_secs + prev_stop = timepoint.stop + else: + problems.OtherProblem('Timetravel detected! Arrival time ' + 'is before previous departure ' + 'at sequence number %s in trip %s' % + (timepoint.stop_sequence, self.trip_id)) + + if self.shape_id and self.shape_id in self._schedule._shapes: + shape = self._schedule.GetShape(self.shape_id) + max_shape_dist = shape.max_distance + st = stoptimes[-1] + if (st.shape_dist_traveled and + st.shape_dist_traveled > max_shape_dist): + problems.OtherProblem( + 'In stop_times.txt, the stop with trip_id=%s and ' + 'stop_sequence=%d has shape_dist_traveled=%f, which is larger ' + 'than the max shape_dist_traveled=%f of the corresponding ' + 'shape (shape_id=%s)' % + (self.trip_id, st.stop_sequence, st.shape_dist_traveled, + max_shape_dist, self.shape_id), type=TYPE_WARNING) + + # shape_dist_traveled is valid in shape if max_shape_dist larger than + # 0. + if max_shape_dist > 0: + for st in stoptimes: + if st.shape_dist_traveled is None: + continue + pt = shape.GetPointWithDistanceTraveled(st.shape_dist_traveled) + if pt: + stop = self._schedule.GetStop(st.stop_id) + distance = ApproximateDistance(stop.stop_lat, stop.stop_lon, + pt[0], pt[1]) + if distance > MAX_DISTANCE_FROM_STOP_TO_SHAPE: + problems.StopTooFarFromShapeWithDistTraveled( + self.trip_id, stop.stop_name, stop.stop_id, pt[2], + self.shape_id, distance, MAX_DISTANCE_FROM_STOP_TO_SHAPE) + + # O(n^2), but we don't anticipate many headway periods per trip + for headway_index, headway in enumerate(self._headways[0:-1]): + for other in self._headways[headway_index + 1:]: + if (other[0] < headway[1]) and (other[1] > headway[0]): + problems.OtherProblem('Trip contains overlapping headway periods ' + '%s and %s' % + (self._HeadwayOutputTuple(headway), + self._HeadwayOutputTuple(other))) + + def _CheckSpeed(self, prev_stop, next_stop, depart_time, + arrive_time, max_speed, problems): + # Checks that the speed between two stops is not faster than max_speed + if prev_stop != None: + try: + time_between_stops = arrive_time - depart_time + except TypeError: + return + + try: + dist_between_stops = \ + ApproximateDistanceBetweenStops(next_stop, prev_stop) + except TypeError, e: + return + + if time_between_stops == 0: + # HASTUS makes it hard to output GTFS with times to the nearest second; + # it rounds times to the nearest minute. Therefore stop_times at the + # same time ending in :00 are fairly common. These times off by no more + # than 30 have not caused a problem. See + # http://code.google.com/p/googletransitdatafeed/issues/detail?id=193 + # Show a warning if times are not rounded to the nearest minute or + # distance is more than max_speed for one minute. + if depart_time % 60 != 0 or dist_between_stops / 1000 * 60 > max_speed: + problems.TooFastTravel(self.trip_id, + prev_stop.stop_name, + next_stop.stop_name, + dist_between_stops, + time_between_stops, + speed=None, + type=TYPE_WARNING) + return + # This needs floating point division for precision. + speed_between_stops = ((float(dist_between_stops) / 1000) / + (float(time_between_stops) / 3600)) + if speed_between_stops > max_speed: + problems.TooFastTravel(self.trip_id, + prev_stop.stop_name, + next_stop.stop_name, + dist_between_stops, + time_between_stops, + speed_between_stops, + type=TYPE_WARNING) + +# TODO: move these into a separate file +class ISO4217(object): + """Represents the set of currencies recognized by the ISO-4217 spec.""" + codes = { # map of alpha code to numerical code + 'AED': 784, 'AFN': 971, 'ALL': 8, 'AMD': 51, 'ANG': 532, 'AOA': 973, + 'ARS': 32, 'AUD': 36, 'AWG': 533, 'AZN': 944, 'BAM': 977, 'BBD': 52, + 'BDT': 50, 'BGN': 975, 'BHD': 48, 'BIF': 108, 'BMD': 60, 'BND': 96, + 'BOB': 68, 'BOV': 984, 'BRL': 986, 'BSD': 44, 'BTN': 64, 'BWP': 72, + 'BYR': 974, 'BZD': 84, 'CAD': 124, 'CDF': 976, 'CHE': 947, 'CHF': 756, + 'CHW': 948, 'CLF': 990, 'CLP': 152, 'CNY': 156, 'COP': 170, 'COU': 970, + 'CRC': 188, 'CUP': 192, 'CVE': 132, 'CYP': 196, 'CZK': 203, 'DJF': 262, + 'DKK': 208, 'DOP': 214, 'DZD': 12, 'EEK': 233, 'EGP': 818, 'ERN': 232, + 'ETB': 230, 'EUR': 978, 'FJD': 242, 'FKP': 238, 'GBP': 826, 'GEL': 981, + 'GHC': 288, 'GIP': 292, 'GMD': 270, 'GNF': 324, 'GTQ': 320, 'GYD': 328, + 'HKD': 344, 'HNL': 340, 'HRK': 191, 'HTG': 332, 'HUF': 348, 'IDR': 360, + 'ILS': 376, 'INR': 356, 'IQD': 368, 'IRR': 364, 'ISK': 352, 'JMD': 388, + 'JOD': 400, 'JPY': 392, 'KES': 404, 'KGS': 417, 'KHR': 116, 'KMF': 174, + 'KPW': 408, 'KRW': 410, 'KWD': 414, 'KYD': 136, 'KZT': 398, 'LAK': 418, + 'LBP': 422, 'LKR': 144, 'LRD': 430, 'LSL': 426, 'LTL': 440, 'LVL': 428, + 'LYD': 434, 'MAD': 504, 'MDL': 498, 'MGA': 969, 'MKD': 807, 'MMK': 104, + 'MNT': 496, 'MOP': 446, 'MRO': 478, 'MTL': 470, 'MUR': 480, 'MVR': 462, + 'MWK': 454, 'MXN': 484, 'MXV': 979, 'MYR': 458, 'MZN': 943, 'NAD': 516, + 'NGN': 566, 'NIO': 558, 'NOK': 578, 'NPR': 524, 'NZD': 554, 'OMR': 512, + 'PAB': 590, 'PEN': 604, 'PGK': 598, 'PHP': 608, 'PKR': 586, 'PLN': 985, + 'PYG': 600, 'QAR': 634, 'ROL': 642, 'RON': 946, 'RSD': 941, 'RUB': 643, + 'RWF': 646, 'SAR': 682, 'SBD': 90, 'SCR': 690, 'SDD': 736, 'SDG': 938, + 'SEK': 752, 'SGD': 702, 'SHP': 654, 'SKK': 703, 'SLL': 694, 'SOS': 706, + 'SRD': 968, 'STD': 678, 'SYP': 760, 'SZL': 748, 'THB': 764, 'TJS': 972, + 'TMM': 795, 'TND': 788, 'TOP': 776, 'TRY': 949, 'TTD': 780, 'TWD': 901, + 'TZS': 834, 'UAH': 980, 'UGX': 800, 'USD': 840, 'USN': 997, 'USS': 998, + 'UYU': 858, 'UZS': 860, 'VEB': 862, 'VND': 704, 'VUV': 548, 'WST': 882, + 'XAF': 950, 'XAG': 961, 'XAU': 959, 'XBA': 955, 'XBB': 956, 'XBC': 957, + 'XBD': 958, 'XCD': 951, 'XDR': 960, 'XFO': None, 'XFU': None, 'XOF': 952, + 'XPD': 964, 'XPF': 953, 'XPT': 962, 'XTS': 963, 'XXX': 999, 'YER': 886, + 'ZAR': 710, 'ZMK': 894, 'ZWD': 716, + } + + +class Fare(object): + """Represents a fare type.""" + _REQUIRED_FIELD_NAMES = ['fare_id', 'price', 'currency_type', + 'payment_method', 'transfers'] + _FIELD_NAMES = _REQUIRED_FIELD_NAMES + ['transfer_duration'] + + def __init__(self, + fare_id=None, price=None, currency_type=None, + payment_method=None, transfers=None, transfer_duration=None, + field_list=None): + self.rules = [] + (self.fare_id, self.price, self.currency_type, self.payment_method, + self.transfers, self.transfer_duration) = \ + (fare_id, price, currency_type, payment_method, + transfers, transfer_duration) + if field_list: + (self.fare_id, self.price, self.currency_type, self.payment_method, + self.transfers, self.transfer_duration) = field_list + + try: + self.price = float(self.price) + except (TypeError, ValueError): + pass + try: + self.payment_method = int(self.payment_method) + except (TypeError, ValueError): + pass + if self.transfers == None or self.transfers == "": + self.transfers = None + else: + try: + self.transfers = int(self.transfers) + except (TypeError, ValueError): + pass + if self.transfer_duration == None or self.transfer_duration == "": + self.transfer_duration = None + else: + try: + self.transfer_duration = int(self.transfer_duration) + except (TypeError, ValueError): + pass + + def GetFareRuleList(self): + return self.rules + + def ClearFareRules(self): + self.rules = [] + + def GetFieldValuesTuple(self): + return [getattr(self, fn) for fn in Fare._FIELD_NAMES] + + def __getitem__(self, name): + return getattr(self, name) + + def __eq__(self, other): + if not other: + return False + + if id(self) == id(other): + return True + + if self.GetFieldValuesTuple() != other.GetFieldValuesTuple(): + return False + + self_rules = [r.GetFieldValuesTuple() for r in self.GetFareRuleList()] + self_rules.sort() + other_rules = [r.GetFieldValuesTuple() for r in other.GetFareRuleList()] + other_rules.sort() + return self_rules == other_rules + + def __ne__(self, other): + return not self.__eq__(other) + + def Validate(self, problems=default_problem_reporter): + if IsEmpty(self.fare_id): + problems.MissingValue("fare_id") + + if self.price == None: + problems.MissingValue("price") + elif not isinstance(self.price, float) and not isinstance(self.price, int): + problems.InvalidValue("price", self.price) + elif self.price < 0: + problems.InvalidValue("price", self.price) + + if IsEmpty(self.currency_type): + problems.MissingValue("currency_type") + elif self.currency_type not in ISO4217.codes: + problems.InvalidValue("currency_type", self.currency_type) + + if self.payment_method == "" or self.payment_method == None: + problems.MissingValue("payment_method") + elif (not isinstance(self.payment_method, int) or + self.payment_method not in range(0, 2)): + problems.InvalidValue("payment_method", self.payment_method) + + if not ((self.transfers == None) or + (isinstance(self.transfers, int) and + self.transfers in range(0, 3))): + problems.InvalidValue("transfers", self.transfers) + + if ((self.transfer_duration != None) and + not isinstance(self.transfer_duration, int)): + problems.InvalidValue("transfer_duration", self.transfer_duration) + if self.transfer_duration and (self.transfer_duration < 0): + problems.InvalidValue("transfer_duration", self.transfer_duration) + if (self.transfer_duration and (self.transfer_duration > 0) and + self.transfers == 0): + problems.InvalidValue("transfer_duration", self.transfer_duration, + "can't have a nonzero transfer_duration for " + "a fare that doesn't allow transfers!") + + +class FareRule(object): + """This class represents a rule that determines which itineraries a + fare rule applies to.""" + _REQUIRED_FIELD_NAMES = ['fare_id'] + _FIELD_NAMES = _REQUIRED_FIELD_NAMES + ['route_id', + 'origin_id', 'destination_id', + 'contains_id'] + + def __init__(self, fare_id=None, route_id=None, + origin_id=None, destination_id=None, contains_id=None, + field_list=None): + (self.fare_id, self.route_id, self.origin_id, self.destination_id, + self.contains_id) = \ + (fare_id, route_id, origin_id, destination_id, contains_id) + if field_list: + (self.fare_id, self.route_id, self.origin_id, self.destination_id, + self.contains_id) = field_list + + # canonicalize non-content values as None + if not self.route_id: + self.route_id = None + if not self.origin_id: + self.origin_id = None + if not self.destination_id: + self.destination_id = None + if not self.contains_id: + self.contains_id = None + + def GetFieldValuesTuple(self): + return [getattr(self, fn) for fn in FareRule._FIELD_NAMES] + + def __getitem__(self, name): + return getattr(self, name) + + def __eq__(self, other): + if not other: + return False + + if id(self) == id(other): + return True + + return self.GetFieldValuesTuple() == other.GetFieldValuesTuple() + + def __ne__(self, other): + return not self.__eq__(other) + + +class Shape(object): + """This class represents a geographic shape that corresponds to the route + taken by one or more Trips.""" + _REQUIRED_FIELD_NAMES = ['shape_id', 'shape_pt_lat', 'shape_pt_lon', + 'shape_pt_sequence'] + _FIELD_NAMES = _REQUIRED_FIELD_NAMES + ['shape_dist_traveled'] + def __init__(self, shape_id): + # List of shape point tuple (lat, lng, shape_dist_traveled), where lat and + # lon is the location of the shape point, and shape_dist_traveled is an + # increasing metric representing the distance traveled along the shape. + self.points = [] + # An ID that uniquely identifies a shape in the dataset. + self.shape_id = shape_id + # The max shape_dist_traveled of shape points in this shape. + self.max_distance = 0 + # List of shape_dist_traveled of each shape point. + self.distance = [] + + def AddPoint(self, lat, lon, distance=None, + problems=default_problem_reporter): + + try: + lat = float(lat) + if abs(lat) > 90.0: + problems.InvalidValue('shape_pt_lat', lat) + return + except (TypeError, ValueError): + problems.InvalidValue('shape_pt_lat', lat) + return + + try: + lon = float(lon) + if abs(lon) > 180.0: + problems.InvalidValue('shape_pt_lon', lon) + return + except (TypeError, ValueError): + problems.InvalidValue('shape_pt_lon', lon) + return + + if (abs(lat) < 1.0) and (abs(lon) < 1.0): + problems.InvalidValue('shape_pt_lat', lat, + 'Point location too close to 0, 0, which means ' + 'that it\'s probably an incorrect location.', + type=TYPE_WARNING) + return + + if distance == '': # canonicalizing empty string to None for comparison + distance = None + + if distance != None: + try: + distance = float(distance) + if (distance < self.max_distance and not + (len(self.points) == 0 and distance == 0)): # first one can be 0 + problems.InvalidValue('shape_dist_traveled', distance, + 'Each subsequent point in a shape should ' + 'have a distance value that\'s at least as ' + 'large as the previous ones. In this case, ' + 'the previous distance was %f.' % + self.max_distance) + return + else: + self.max_distance = distance + self.distance.append(distance) + except (TypeError, ValueError): + problems.InvalidValue('shape_dist_traveled', distance, + 'This value should be a positive number.') + return + + self.points.append((lat, lon, distance)) + + def ClearPoints(self): + self.points = [] + + def __eq__(self, other): + if not other: + return False + + if id(self) == id(other): + return True + + return self.points == other.points + + def __ne__(self, other): + return not self.__eq__(other) + + def __repr__(self): + return "" % self.__dict__ + + def Validate(self, problems=default_problem_reporter): + if IsEmpty(self.shape_id): + problems.MissingValue('shape_id') + + if not self.points: + problems.OtherProblem('The shape with shape_id "%s" contains no points.' % + self.shape_id, type=TYPE_WARNING) + + def GetPointWithDistanceTraveled(self, shape_dist_traveled): + """Returns a point on the shape polyline with the input shape_dist_traveled. + + Args: + shape_dist_traveled: The input shape_dist_traveled. + + Returns: + The shape point as a tuple (lat, lng, shape_dist_traveled), where lat and + lng is the location of the shape point, and shape_dist_traveled is an + increasing metric representing the distance traveled along the shape. + Returns None if there is data error in shape. + """ + if not self.distance: + return None + if shape_dist_traveled <= self.distance[0]: + return self.points[0] + if shape_dist_traveled >= self.distance[-1]: + return self.points[-1] + + index = bisect.bisect(self.distance, shape_dist_traveled) + (lat0, lng0, dist0) = self.points[index - 1] + (lat1, lng1, dist1) = self.points[index] + + # Interpolate if shape_dist_traveled does not equal to any of the point + # in shape segment. + # (lat0, lng0) (lat, lng) (lat1, lng1) + # -----|--------------------|---------------------|------ + # dist0 shape_dist_traveled dist1 + # \------- ca --------/ \-------- bc -------/ + # \----------------- ba ------------------/ + ca = shape_dist_traveled - dist0 + bc = dist1 - shape_dist_traveled + ba = bc + ca + if ba == 0: + # This only happens when there's data error in shapes and should have been + # catched before. Check to avoid crash. + return None + # This won't work crossing longitude 180 and is only an approximation which + # works well for short distance. + lat = (lat1 * ca + lat0 * bc) / ba + lng = (lng1 * ca + lng0 * bc) / ba + return (lat, lng, shape_dist_traveled) + + +class ISO639(object): + # Set of all the 2-letter ISO 639-1 language codes. + codes_2letter = set([ + 'aa', 'ab', 'ae', 'af', 'ak', 'am', 'an', 'ar', 'as', 'av', 'ay', 'az', + 'ba', 'be', 'bg', 'bh', 'bi', 'bm', 'bn', 'bo', 'br', 'bs', 'ca', 'ce', + 'ch', 'co', 'cr', 'cs', 'cu', 'cv', 'cy', 'da', 'de', 'dv', 'dz', 'ee', + 'el', 'en', 'eo', 'es', 'et', 'eu', 'fa', 'ff', 'fi', 'fj', 'fo', 'fr', + 'fy', 'ga', 'gd', 'gl', 'gn', 'gu', 'gv', 'ha', 'he', 'hi', 'ho', 'hr', + 'ht', 'hu', 'hy', 'hz', 'ia', 'id', 'ie', 'ig', 'ii', 'ik', 'io', 'is', + 'it', 'iu', 'ja', 'jv', 'ka', 'kg', 'ki', 'kj', 'kk', 'kl', 'km', 'kn', + 'ko', 'kr', 'ks', 'ku', 'kv', 'kw', 'ky', 'la', 'lb', 'lg', 'li', 'ln', + 'lo', 'lt', 'lu', 'lv', 'mg', 'mh', 'mi', 'mk', 'ml', 'mn', 'mo', 'mr', + 'ms', 'mt', 'my', 'na', 'nb', 'nd', 'ne', 'ng', 'nl', 'nn', 'no', 'nr', + 'nv', 'ny', 'oc', 'oj', 'om', 'or', 'os', 'pa', 'pi', 'pl', 'ps', 'pt', + 'qu', 'rm', 'rn', 'ro', 'ru', 'rw', 'sa', 'sc', 'sd', 'se', 'sg', 'si', + 'sk', 'sl', 'sm', 'sn', 'so', 'sq', 'sr', 'ss', 'st', 'su', 'sv', 'sw', + 'ta', 'te', 'tg', 'th', 'ti', 'tk', 'tl', 'tn', 'to', 'tr', 'ts', 'tt', + 'tw', 'ty', 'ug', 'uk', 'ur', 'uz', 've', 'vi', 'vo', 'wa', 'wo', 'xh', + 'yi', 'yo', 'za', 'zh', 'zu', + ]) + + +class Agency(GenericGTFSObject): + """Represents an agency in a schedule. + + Callers may assign arbitrary values to instance attributes. __init__ makes no + attempt at validating the attributes. Call Validate() to check that + attributes are valid and the agency object is consistent with itself. + + Attributes: + All attributes are strings. + """ + _REQUIRED_FIELD_NAMES = ['agency_name', 'agency_url', 'agency_timezone'] + _FIELD_NAMES = _REQUIRED_FIELD_NAMES + ['agency_id', 'agency_lang', + 'agency_phone'] + _TABLE_NAME = 'agency' + + def __init__(self, name=None, url=None, timezone=None, id=None, + field_dict=None, lang=None, **kwargs): + """Initialize a new Agency object. + + Args: + field_dict: A dictionary mapping attribute name to unicode string + name: a string, ignored when field_dict is present + url: a string, ignored when field_dict is present + timezone: a string, ignored when field_dict is present + id: a string, ignored when field_dict is present + kwargs: arbitrary keyword arguments may be used to add attributes to the + new object, ignored when field_dict is present + """ + self._schedule = None + + if not field_dict: + if name: + kwargs['agency_name'] = name + if url: + kwargs['agency_url'] = url + if timezone: + kwargs['agency_timezone'] = timezone + if id: + kwargs['agency_id'] = id + if lang: + kwargs['agency_lang'] = lang + field_dict = kwargs + + self.__dict__.update(field_dict) + + def Validate(self, problems=default_problem_reporter): + """Validate attribute values and this object's internal consistency. + + Returns: + True iff all validation checks passed. + """ + found_problem = False + for required in Agency._REQUIRED_FIELD_NAMES: + if IsEmpty(getattr(self, required, None)): + problems.MissingValue(required) + found_problem = True + + if self.agency_url and not IsValidURL(self.agency_url): + problems.InvalidValue('agency_url', self.agency_url) + found_problem = True + + if (not IsEmpty(self.agency_lang) and + self.agency_lang.lower() not in ISO639.codes_2letter): + problems.InvalidValue('agency_lang', self.agency_lang) + found_problem = True + + try: + import pytz + if self.agency_timezone not in pytz.common_timezones: + problems.InvalidValue( + 'agency_timezone', + self.agency_timezone, + '"%s" is not a common timezone name according to pytz version %s' % + (self.agency_timezone, pytz.VERSION)) + found_problem = True + except ImportError: # no pytz + print ("Timezone not checked " + "(install pytz package for timezone validation)") + return not found_problem + + +class Transfer(object): + """Represents a transfer in a schedule""" + _REQUIRED_FIELD_NAMES = ['from_stop_id', 'to_stop_id', 'transfer_type'] + _FIELD_NAMES = _REQUIRED_FIELD_NAMES + ['min_transfer_time'] + + def __init__(self, schedule=None, from_stop_id=None, to_stop_id=None, transfer_type=None, + min_transfer_time=None, field_dict=None): + if schedule is not None: + self._schedule = weakref.proxy(schedule) # See weakref comment at top + else: + self._schedule = None + if field_dict: + self.__dict__.update(field_dict) + else: + self.from_stop_id = from_stop_id + self.to_stop_id = to_stop_id + self.transfer_type = transfer_type + self.min_transfer_time = min_transfer_time + + if getattr(self, 'transfer_type', None) in ("", None): + # Use the default, recommended transfer, if attribute is not set or blank + self.transfer_type = 0 + else: + try: + self.transfer_type = NonNegIntStringToInt(self.transfer_type) + except (TypeError, ValueError): + pass + + if hasattr(self, 'min_transfer_time'): + try: + self.min_transfer_time = NonNegIntStringToInt(self.min_transfer_time) + except (TypeError, ValueError): + pass + else: + self.min_transfer_time = None + + def GetFieldValuesTuple(self): + return [getattr(self, fn) for fn in Transfer._FIELD_NAMES] + + def __getitem__(self, name): + return getattr(self, name) + + def __eq__(self, other): + if not other: + return False + + if id(self) == id(other): + return True + + return self.GetFieldValuesTuple() == other.GetFieldValuesTuple() + + def __ne__(self, other): + return not self.__eq__(other) + + def __repr__(self): + return "" % self.__dict__ + + def Validate(self, problems=default_problem_reporter): + if IsEmpty(self.from_stop_id): + problems.MissingValue('from_stop_id') + elif self._schedule: + if self.from_stop_id not in self._schedule.stops.keys(): + problems.InvalidValue('from_stop_id', self.from_stop_id) + + if IsEmpty(self.to_stop_id): + problems.MissingValue('to_stop_id') + elif self._schedule: + if self.to_stop_id not in self._schedule.stops.keys(): + problems.InvalidValue('to_stop_id', self.to_stop_id) + + if not IsEmpty(self.transfer_type): + if (not isinstance(self.transfer_type, int)) or \ + (self.transfer_type not in range(0, 4)): + problems.InvalidValue('transfer_type', self.transfer_type) + + if not IsEmpty(self.min_transfer_time): + if (not isinstance(self.min_transfer_time, int)) or \ + self.min_transfer_time < 0: + problems.InvalidValue('min_transfer_time', self.min_transfer_time) + + +class ServicePeriod(object): + """Represents a service, which identifies a set of dates when one or more + trips operate.""" + _DAYS_OF_WEEK = [ + 'monday', 'tuesday', 'wednesday', 'thursday', 'friday', + 'saturday', 'sunday' + ] + _FIELD_NAMES_REQUIRED = [ + 'service_id', 'start_date', 'end_date' + ] + _DAYS_OF_WEEK + _FIELD_NAMES = _FIELD_NAMES_REQUIRED # no optional fields in this one + _FIELD_NAMES_CALENDAR_DATES = ['service_id', 'date', 'exception_type'] + + def __init__(self, id=None, field_list=None): + self.original_day_values = [] + if field_list: + self.service_id = field_list[self._FIELD_NAMES.index('service_id')] + self.day_of_week = [False] * len(self._DAYS_OF_WEEK) + + for day in self._DAYS_OF_WEEK: + value = field_list[self._FIELD_NAMES.index(day)] or '' # can be None + self.original_day_values += [value.strip()] + self.day_of_week[self._DAYS_OF_WEEK.index(day)] = (value == u'1') + + self.start_date = field_list[self._FIELD_NAMES.index('start_date')] + self.end_date = field_list[self._FIELD_NAMES.index('end_date')] + else: + self.service_id = id + self.day_of_week = [False] * 7 + self.start_date = None + self.end_date = None + self.date_exceptions = {} # Map from 'YYYYMMDD' to 1 (add) or 2 (remove) + + def _IsValidDate(self, date): + if re.match('^\d{8}$', date) == None: + return False + + try: + time.strptime(date, "%Y%m%d") + return True + except ValueError: + return False + + def GetDateRange(self): + """Return the range over which this ServicePeriod is valid. + + The range includes exception dates that add service outside of + (start_date, end_date), but doesn't shrink the range if exception + dates take away service at the edges of the range. + + Returns: + A tuple of "YYYYMMDD" strings, (start date, end date) or (None, None) if + no dates have been given. + """ + start = self.start_date + end = self.end_date + + for date in self.date_exceptions: + if self.date_exceptions[date] == 2: + continue + if not start or (date < start): + start = date + if not end or (date > end): + end = date + if start is None: + start = end + elif end is None: + end = start + # If start and end are None we did a little harmless shuffling + return (start, end) + + def GetCalendarFieldValuesTuple(self): + """Return the tuple of calendar.txt values or None if this ServicePeriod + should not be in calendar.txt .""" + if self.start_date and self.end_date: + return [getattr(self, fn) for fn in ServicePeriod._FIELD_NAMES] + + def GenerateCalendarDatesFieldValuesTuples(self): + """Generates tuples of calendar_dates.txt values. Yield zero tuples if + this ServicePeriod should not be in calendar_dates.txt .""" + for date, exception_type in self.date_exceptions.items(): + yield (self.service_id, date, unicode(exception_type)) + + def GetCalendarDatesFieldValuesTuples(self): + """Return a list of date execeptions""" + result = [] + for date_tuple in self.GenerateCalendarDatesFieldValuesTuples(): + result.append(date_tuple) + result.sort() # helps with __eq__ + return result + + def SetDateHasService(self, date, has_service=True, problems=None): + if date in self.date_exceptions and problems: + problems.DuplicateID(('service_id', 'date'), + (self.service_id, date), + type=TYPE_WARNING) + self.date_exceptions[date] = has_service and 1 or 2 + + def ResetDateToNormalService(self, date): + if date in self.date_exceptions: + del self.date_exceptions[date] + + def SetStartDate(self, start_date): + """Set the first day of service as a string in YYYYMMDD format""" + self.start_date = start_date + + def SetEndDate(self, end_date): + """Set the last day of service as a string in YYYYMMDD format""" + self.end_date = end_date + + def SetDayOfWeekHasService(self, dow, has_service=True): + """Set service as running (or not) on a day of the week. By default the + service does not run on any days. + + Args: + dow: 0 for Monday through 6 for Sunday + has_service: True if this service operates on dow, False if it does not. + + Returns: + None + """ + assert(dow >= 0 and dow < 7) + self.day_of_week[dow] = has_service + + def SetWeekdayService(self, has_service=True): + """Set service as running (or not) on all of Monday through Friday.""" + for i in range(0, 5): + self.SetDayOfWeekHasService(i, has_service) + + def SetWeekendService(self, has_service=True): + """Set service as running (or not) on Saturday and Sunday.""" + self.SetDayOfWeekHasService(5, has_service) + self.SetDayOfWeekHasService(6, has_service) + + def SetServiceId(self, service_id): + """Set the service_id for this schedule. Generally the default will + suffice so you won't need to call this method.""" + self.service_id = service_id + + def IsActiveOn(self, date, date_object=None): + """Test if this service period is active on a date. + + Args: + date: a string of form "YYYYMMDD" + date_object: a date object representing the same date as date. + This parameter is optional, and present only for performance + reasons. + If the caller constructs the date string from a date object + that date object can be passed directly, thus avoiding the + costly conversion from string to date object. + + Returns: + True iff this service is active on date. + """ + if date in self.date_exceptions: + if self.date_exceptions[date] == 1: + return True + else: + return False + if (self.start_date and self.end_date and self.start_date <= date and + date <= self.end_date): + if date_object is None: + date_object = DateStringToDateObject(date) + return self.day_of_week[date_object.weekday()] + return False + + def ActiveDates(self): + """Return dates this service period is active as a list of "YYYYMMDD".""" + (earliest, latest) = self.GetDateRange() + if earliest is None: + return [] + dates = [] + date_it = DateStringToDateObject(earliest) + date_end = DateStringToDateObject(latest) + delta = datetime.timedelta(days=1) + while date_it <= date_end: + date_it_string = date_it.strftime("%Y%m%d") + if self.IsActiveOn(date_it_string, date_it): + dates.append(date_it_string) + date_it = date_it + delta + return dates + + def __getattr__(self, name): + try: + # Return 1 if value in day_of_week is True, 0 otherwise + return (self.day_of_week[ServicePeriod._DAYS_OF_WEEK.index(name)] + and 1 or 0) + except KeyError: + pass + except ValueError: # not a day of the week + pass + raise AttributeError(name) + + def __getitem__(self, name): + return getattr(self, name) + + def __eq__(self, other): + if not other: + return False + + if id(self) == id(other): + return True + + if (self.GetCalendarFieldValuesTuple() != + other.GetCalendarFieldValuesTuple()): + return False + + if (self.GetCalendarDatesFieldValuesTuples() != + other.GetCalendarDatesFieldValuesTuples()): + return False + + return True + + def __ne__(self, other): + return not self.__eq__(other) + + def Validate(self, problems=default_problem_reporter): + if IsEmpty(self.service_id): + problems.MissingValue('service_id') + # self.start_date/self.end_date is None in 3 cases: + # ServicePeriod created by loader and + # 1a) self.service_id wasn't in calendar.txt + # 1b) calendar.txt didn't have a start_date/end_date column + # ServicePeriod created directly and + # 2) start_date/end_date wasn't set + # In case 1a no problem is reported. In case 1b the missing required column + # generates an error in _ReadCSV so this method should not report another + # problem. There is no way to tell the difference between cases 1b and 2 + # so case 2 is ignored because making the feedvalidator pretty is more + # important than perfect validation when an API users makes a mistake. + start_date = None + if self.start_date is not None: + if IsEmpty(self.start_date): + problems.MissingValue('start_date') + elif self._IsValidDate(self.start_date): + start_date = self.start_date + else: + problems.InvalidValue('start_date', self.start_date) + end_date = None + if self.end_date is not None: + if IsEmpty(self.end_date): + problems.MissingValue('end_date') + elif self._IsValidDate(self.end_date): + end_date = self.end_date + else: + problems.InvalidValue('end_date', self.end_date) + if start_date and end_date and end_date < start_date: + problems.InvalidValue('end_date', end_date, + 'end_date of %s is earlier than ' + 'start_date of "%s"' % + (end_date, start_date)) + if self.original_day_values: + index = 0 + for value in self.original_day_values: + column_name = self._DAYS_OF_WEEK[index] + if IsEmpty(value): + problems.MissingValue(column_name) + elif (value != u'0') and (value != '1'): + problems.InvalidValue(column_name, value) + index += 1 + if (True not in self.day_of_week and + 1 not in self.date_exceptions.values()): + problems.OtherProblem('Service period with service_id "%s" ' + 'doesn\'t have service on any days ' + 'of the week.' % self.service_id, + type=TYPE_WARNING) + for date in self.date_exceptions: + if not self._IsValidDate(date): + problems.InvalidValue('date', date) + + +class CsvUnicodeWriter: + """ + Create a wrapper around a csv writer object which can safely write unicode + values. Passes all arguments to csv.writer. + """ + def __init__(self, *args, **kwargs): + self.writer = csv.writer(*args, **kwargs) + + def writerow(self, row): + """Write row to the csv file. Any unicode strings in row are encoded as + utf-8.""" + encoded_row = [] + for s in row: + if isinstance(s, unicode): + encoded_row.append(s.encode("utf-8")) + else: + encoded_row.append(s) + try: + self.writer.writerow(encoded_row) + except Exception, e: + print 'error writing %s as %s' % (row, encoded_row) + raise e + + def writerows(self, rows): + """Write rows to the csv file. Any unicode strings in rows are encoded as + utf-8.""" + for row in rows: + self.writerow(row) + + def __getattr__(self, name): + return getattr(self.writer, name) + + +class Schedule: + """Represents a Schedule, a collection of stops, routes, trips and + an agency. This is the main class for this module.""" + + def __init__(self, problem_reporter=default_problem_reporter, + memory_db=True, check_duplicate_trips=False): + # Map from table name to list of columns present in this schedule + self._table_columns = {} + + self._agencies = {} + self.stops = {} + self.routes = {} + self.trips = {} + self.service_periods = {} + self.fares = {} + self.fare_zones = {} # represents the set of all known fare zones + self._shapes = {} # shape_id to Shape + self._transfers = [] # list of transfers + self._default_service_period = None + self._default_agency = None + self.problem_reporter = problem_reporter + self._check_duplicate_trips = check_duplicate_trips + self.ConnectDb(memory_db) + + def AddTableColumn(self, table, column): + """Add column to table if it is not already there.""" + if column not in self._table_columns[table]: + self._table_columns[table].append(column) + + def AddTableColumns(self, table, columns): + """Add columns to table if they are not already there. + + Args: + table: table name as a string + columns: an iterable of column names""" + table_columns = self._table_columns.setdefault(table, []) + for attr in columns: + if attr not in table_columns: + table_columns.append(attr) + + def GetTableColumns(self, table): + """Return list of columns in a table.""" + return self._table_columns[table] + + def __del__(self): + if hasattr(self, '_temp_db_filename'): + os.remove(self._temp_db_filename) + + def ConnectDb(self, memory_db): + if memory_db: + self._connection = sqlite.connect(":memory:") + else: + try: + self._temp_db_file = tempfile.NamedTemporaryFile() + self._connection = sqlite.connect(self._temp_db_file.name) + except sqlite.OperationalError: + # Windows won't let a file be opened twice. mkstemp does not remove the + # file when all handles to it are closed. + self._temp_db_file = None + (fd, self._temp_db_filename) = tempfile.mkstemp(".db") + os.close(fd) + self._connection = sqlite.connect(self._temp_db_filename) + + cursor = self._connection.cursor() + cursor.execute("""CREATE TABLE stop_times ( + trip_id CHAR(50), + arrival_secs INTEGER, + departure_secs INTEGER, + stop_id CHAR(50), + stop_sequence INTEGER, + stop_headsign VAR CHAR(100), + pickup_type INTEGER, + drop_off_type INTEGER, + shape_dist_traveled FLOAT);""") + cursor.execute("""CREATE INDEX trip_index ON stop_times (trip_id);""") + cursor.execute("""CREATE INDEX stop_index ON stop_times (stop_id);""") + + def GetStopBoundingBox(self): + return (min(s.stop_lat for s in self.stops.values()), + min(s.stop_lon for s in self.stops.values()), + max(s.stop_lat for s in self.stops.values()), + max(s.stop_lon for s in self.stops.values()), + ) + + def AddAgency(self, name, url, timezone, agency_id=None): + """Adds an agency to this schedule.""" + agency = Agency(name, url, timezone, agency_id) + self.AddAgencyObject(agency) + return agency + + def AddAgencyObject(self, agency, problem_reporter=None, validate=True): + assert agency._schedule is None + + if not problem_reporter: + problem_reporter = self.problem_reporter + + if agency.agency_id in self._agencies: + problem_reporter.DuplicateID('agency_id', agency.agency_id) + return + + self.AddTableColumns('agency', agency._ColumnNames()) + agency._schedule = weakref.proxy(self) + + if validate: + agency.Validate(problem_reporter) + self._agencies[agency.agency_id] = agency + + def GetAgency(self, agency_id): + """Return Agency with agency_id or throw a KeyError""" + return self._agencies[agency_id] + + def GetDefaultAgency(self): + """Return the default Agency. If no default Agency has been set select the + default depending on how many Agency objects are in the Schedule. If there + are 0 make a new Agency the default, if there is 1 it becomes the default, + if there is more than 1 then return None. + """ + if not self._default_agency: + if len(self._agencies) == 0: + self.NewDefaultAgency() + elif len(self._agencies) == 1: + self._default_agency = self._agencies.values()[0] + return self._default_agency + + def NewDefaultAgency(self, **kwargs): + """Create a new Agency object and make it the default agency for this Schedule""" + agency = Agency(**kwargs) + if not agency.agency_id: + agency.agency_id = FindUniqueId(self._agencies) + self._default_agency = agency + self.SetDefaultAgency(agency, validate=False) # Blank agency won't validate + return agency + + def SetDefaultAgency(self, agency, validate=True): + """Make agency the default and add it to the schedule if not already added""" + assert isinstance(agency, Agency) + self._default_agency = agency + if agency.agency_id not in self._agencies: + self.AddAgencyObject(agency, validate=validate) + + def GetAgencyList(self): + """Returns the list of Agency objects known to this Schedule.""" + return self._agencies.values() + + def GetServicePeriod(self, service_id): + """Returns the ServicePeriod object with the given ID.""" + return self.service_periods[service_id] + + def GetDefaultServicePeriod(self): + """Return the default ServicePeriod. If no default ServicePeriod has been + set select the default depending on how many ServicePeriod objects are in + the Schedule. If there are 0 make a new ServicePeriod the default, if there + is 1 it becomes the default, if there is more than 1 then return None. + """ + if not self._default_service_period: + if len(self.service_periods) == 0: + self.NewDefaultServicePeriod() + elif len(self.service_periods) == 1: + self._default_service_period = self.service_periods.values()[0] + return self._default_service_period + + def NewDefaultServicePeriod(self): + """Create a new ServicePeriod object, make it the default service period and + return it. The default service period is used when you create a trip without + providing an explict service period. """ + service_period = ServicePeriod() + service_period.service_id = FindUniqueId(self.service_periods) + # blank service won't validate in AddServicePeriodObject + self.SetDefaultServicePeriod(service_period, validate=False) + return service_period + + def SetDefaultServicePeriod(self, service_period, validate=True): + assert isinstance(service_period, ServicePeriod) + self._default_service_period = service_period + if service_period.service_id not in self.service_periods: + self.AddServicePeriodObject(service_period, validate=validate) + + def AddServicePeriodObject(self, service_period, problem_reporter=None, + validate=True): + if not problem_reporter: + problem_reporter = self.problem_reporter + + if service_period.service_id in self.service_periods: + problem_reporter.DuplicateID('service_id', service_period.service_id) + return + + if validate: + service_period.Validate(problem_reporter) + self.service_periods[service_period.service_id] = service_period + + def GetServicePeriodList(self): + return self.service_periods.values() + + def GetDateRange(self): + """Returns a tuple of (earliest, latest) dates on which the service + periods in the schedule define service, in YYYYMMDD form.""" + + ranges = [period.GetDateRange() for period in self.GetServicePeriodList()] + starts = filter(lambda x: x, [item[0] for item in ranges]) + ends = filter(lambda x: x, [item[1] for item in ranges]) + + if not starts or not ends: + return (None, None) + + return (min(starts), max(ends)) + + def GetServicePeriodsActiveEachDate(self, date_start, date_end): + """Return a list of tuples (date, [period1, period2, ...]). + + For each date in the range [date_start, date_end) make list of each + ServicePeriod object which is active. + + Args: + date_start: The first date in the list, a date object + date_end: The first date after the list, a date object + + Returns: + A list of tuples. Each tuple contains a date object and a list of zero or + more ServicePeriod objects. + """ + date_it = date_start + one_day = datetime.timedelta(days=1) + date_service_period_list = [] + while date_it < date_end: + periods_today = [] + date_it_string = date_it.strftime("%Y%m%d") + for service in self.GetServicePeriodList(): + if service.IsActiveOn(date_it_string, date_it): + periods_today.append(service) + date_service_period_list.append((date_it, periods_today)) + date_it += one_day + return date_service_period_list + + + def AddStop(self, lat, lng, name): + """Add a stop to this schedule. + + A new stop_id is created for this stop. Do not use this method unless all + stops in this Schedule are created with it. See source for details. + + Args: + lat: Latitude of the stop as a float or string + lng: Longitude of the stop as a float or string + name: Name of the stop, which will appear in the feed + + Returns: + A new Stop object + """ + # TODO: stop_id isn't guarenteed to be unique and conflicts are not + # handled. Please fix. + stop_id = unicode(len(self.stops)) + stop = Stop(stop_id=stop_id, lat=lat, lng=lng, name=name) + self.AddStopObject(stop) + return stop + + def AddStopObject(self, stop, problem_reporter=None): + """Add Stop object to this schedule if stop_id is non-blank.""" + assert stop._schedule is None + if not problem_reporter: + problem_reporter = self.problem_reporter + + if not stop.stop_id: + return + + if stop.stop_id in self.stops: + problem_reporter.DuplicateID('stop_id', stop.stop_id) + return + + stop._schedule = weakref.proxy(self) + self.AddTableColumns('stops', stop._ColumnNames()) + self.stops[stop.stop_id] = stop + if hasattr(stop, 'zone_id') and stop.zone_id: + self.fare_zones[stop.zone_id] = True + + def GetStopList(self): + return self.stops.values() + + def AddRoute(self, short_name, long_name, route_type): + """Add a route to this schedule. + + Args: + short_name: Short name of the route, such as "71L" + long_name: Full name of the route, such as "NW 21st Ave/St Helens Rd" + route_type: A type such as "Tram", "Subway" or "Bus" + Returns: + A new Route object + """ + route_id = unicode(len(self.routes)) + route = Route(short_name=short_name, long_name=long_name, + route_type=route_type, route_id=route_id) + route.agency_id = self.GetDefaultAgency().agency_id + self.AddRouteObject(route) + return route + + def AddRouteObject(self, route, problem_reporter=None): + if not problem_reporter: + problem_reporter = self.problem_reporter + + route.Validate(problem_reporter) + + if route.route_id in self.routes: + problem_reporter.DuplicateID('route_id', route.route_id) + return + + if route.agency_id not in self._agencies: + if not route.agency_id and len(self._agencies) == 1: + # we'll just assume that the route applies to the only agency + pass + else: + problem_reporter.InvalidValue('agency_id', route.agency_id, + 'Route uses an unknown agency_id.') + return + + self.AddTableColumns('routes', route._ColumnNames()) + route._schedule = weakref.proxy(self) + self.routes[route.route_id] = route + + def GetRouteList(self): + return self.routes.values() + + def GetRoute(self, route_id): + return self.routes[route_id] + + def AddShapeObject(self, shape, problem_reporter=None): + if not problem_reporter: + problem_reporter = self.problem_reporter + + shape.Validate(problem_reporter) + + if shape.shape_id in self._shapes: + problem_reporter.DuplicateID('shape_id', shape.shape_id) + return + + self._shapes[shape.shape_id] = shape + + def GetShapeList(self): + return self._shapes.values() + + def GetShape(self, shape_id): + return self._shapes[shape_id] + + def AddTripObject(self, trip, problem_reporter=None, validate=True): + if not problem_reporter: + problem_reporter = self.problem_reporter + + if trip.trip_id in self.trips: + problem_reporter.DuplicateID('trip_id', trip.trip_id) + return + + self.AddTableColumns('trips', trip._ColumnNames()) + trip._schedule = weakref.proxy(self) + self.trips[trip.trip_id] = trip + + # Call Trip.Validate after setting trip._schedule so that references + # are checked. trip.ValidateChildren will be called directly by + # schedule.Validate, after stop_times has been loaded. + if validate: + if not problem_reporter: + problem_reporter = self.problem_reporter + trip.Validate(problem_reporter, validate_children=False) + try: + self.routes[trip.route_id]._AddTripObject(trip) + except KeyError: + # Invalid route_id was reported in the Trip.Validate call above + pass + + def GetTripList(self): + return self.trips.values() + + def GetTrip(self, trip_id): + return self.trips[trip_id] + + def AddFareObject(self, fare, problem_reporter=None): + if not problem_reporter: + problem_reporter = self.problem_reporter + fare.Validate(problem_reporter) + + if fare.fare_id in self.fares: + problem_reporter.DuplicateID('fare_id', fare.fare_id) + return + + self.fares[fare.fare_id] = fare + + def GetFareList(self): + return self.fares.values() + + def GetFare(self, fare_id): + return self.fares[fare_id] + + def AddFareRuleObject(self, rule, problem_reporter=None): + if not problem_reporter: + problem_reporter = self.problem_reporter + + if IsEmpty(rule.fare_id): + problem_reporter.MissingValue('fare_id') + return + + if rule.route_id and rule.route_id not in self.routes: + problem_reporter.InvalidValue('route_id', rule.route_id) + if rule.origin_id and rule.origin_id not in self.fare_zones: + problem_reporter.InvalidValue('origin_id', rule.origin_id) + if rule.destination_id and rule.destination_id not in self.fare_zones: + problem_reporter.InvalidValue('destination_id', rule.destination_id) + if rule.contains_id and rule.contains_id not in self.fare_zones: + problem_reporter.InvalidValue('contains_id', rule.contains_id) + + if rule.fare_id in self.fares: + self.GetFare(rule.fare_id).rules.append(rule) + else: + problem_reporter.InvalidValue('fare_id', rule.fare_id, + '(This fare_id doesn\'t correspond to any ' + 'of the IDs defined in the ' + 'fare attributes.)') + + def AddTransferObject(self, transfer, problem_reporter=None): + assert transfer._schedule is None, "only add Transfer to a schedule once" + transfer._schedule = weakref.proxy(self) # See weakref comment at top + if not problem_reporter: + problem_reporter = self.problem_reporter + + transfer.Validate(problem_reporter) + self._transfers.append(transfer) + + def GetTransferList(self): + return self._transfers + + def GetStop(self, id): + return self.stops[id] + + def GetFareZones(self): + """Returns the list of all fare zones that have been identified by + the stops that have been added.""" + return self.fare_zones.keys() + + def GetNearestStops(self, lat, lon, n=1): + """Return the n nearest stops to lat,lon""" + dist_stop_list = [] + for s in self.stops.values(): + # TODO: Use ApproximateDistanceBetweenStops? + dist = (s.stop_lat - lat)**2 + (s.stop_lon - lon)**2 + if len(dist_stop_list) < n: + bisect.insort(dist_stop_list, (dist, s)) + elif dist < dist_stop_list[-1][0]: + bisect.insort(dist_stop_list, (dist, s)) + dist_stop_list.pop() # Remove stop with greatest distance + return [stop for dist, stop in dist_stop_list] + + def GetStopsInBoundingBox(self, north, east, south, west, n): + """Return a sample of up to n stops in a bounding box""" + stop_list = [] + for s in self.stops.values(): + if (s.stop_lat <= north and s.stop_lat >= south and + s.stop_lon <= east and s.stop_lon >= west): + stop_list.append(s) + if len(stop_list) == n: + break + return stop_list + + def Load(self, feed_path, extra_validation=False): + loader = Loader(feed_path, self, problems=self.problem_reporter, + extra_validation=extra_validation) + loader.Load() + + def _WriteArchiveString(self, archive, filename, stringio): + zi = zipfile.ZipInfo(filename) + # See + # http://stackoverflow.com/questions/434641/how-do-i-set-permissions-attributes-on-a-file-in-a-zip-file-using-pythons-zipf + zi.external_attr = 0666 << 16L # Set unix permissions to -rw-rw-rw + # ZIP_DEFLATED requires zlib. zlib comes with Python 2.4 and 2.5 + zi.compress_type = zipfile.ZIP_DEFLATED + archive.writestr(zi, stringio.getvalue()) + + def WriteGoogleTransitFeed(self, file): + """Output this schedule as a Google Transit Feed in file_name. + + Args: + file: path of new feed file (a string) or a file-like object + + Returns: + None + """ + # Compression type given when adding each file + archive = zipfile.ZipFile(file, 'w') + + if 'agency' in self._table_columns: + agency_string = StringIO.StringIO() + writer = CsvUnicodeWriter(agency_string) + columns = self.GetTableColumns('agency') + writer.writerow(columns) + for a in self._agencies.values(): + writer.writerow([EncodeUnicode(a[c]) for c in columns]) + self._WriteArchiveString(archive, 'agency.txt', agency_string) + + calendar_dates_string = StringIO.StringIO() + writer = CsvUnicodeWriter(calendar_dates_string) + writer.writerow(ServicePeriod._FIELD_NAMES_CALENDAR_DATES) + has_data = False + for period in self.service_periods.values(): + for row in period.GenerateCalendarDatesFieldValuesTuples(): + has_data = True + writer.writerow(row) + wrote_calendar_dates = False + if has_data: + wrote_calendar_dates = True + self._WriteArchiveString(archive, 'calendar_dates.txt', + calendar_dates_string) + + calendar_string = StringIO.StringIO() + writer = CsvUnicodeWriter(calendar_string) + writer.writerow(ServicePeriod._FIELD_NAMES) + has_data = False + for s in self.service_periods.values(): + row = s.GetCalendarFieldValuesTuple() + if row: + has_data = True + writer.writerow(row) + if has_data or not wrote_calendar_dates: + self._WriteArchiveString(archive, 'calendar.txt', calendar_string) + + if 'stops' in self._table_columns: + stop_string = StringIO.StringIO() + writer = CsvUnicodeWriter(stop_string) + columns = self.GetTableColumns('stops') + writer.writerow(columns) + for s in self.stops.values(): + writer.writerow([EncodeUnicode(s[c]) for c in columns]) + self._WriteArchiveString(archive, 'stops.txt', stop_string) + + if 'routes' in self._table_columns: + route_string = StringIO.StringIO() + writer = CsvUnicodeWriter(route_string) + columns = self.GetTableColumns('routes') + writer.writerow(columns) + for r in self.routes.values(): + writer.writerow([EncodeUnicode(r[c]) for c in columns]) + self._WriteArchiveString(archive, 'routes.txt', route_string) + + if 'trips' in self._table_columns: + trips_string = StringIO.StringIO() + writer = CsvUnicodeWriter(trips_string) + columns = self.GetTableColumns('trips') + writer.writerow(columns) + for t in self.trips.values(): + writer.writerow([EncodeUnicode(t[c]) for c in columns]) + self._WriteArchiveString(archive, 'trips.txt', trips_string) + + # write frequencies.txt (if applicable) + headway_rows = [] + for trip in self.GetTripList(): + headway_rows += trip.GetHeadwayPeriodOutputTuples() + if headway_rows: + headway_string = StringIO.StringIO() + writer = CsvUnicodeWriter(headway_string) + writer.writerow(Trip._FIELD_NAMES_HEADWAY) + writer.writerows(headway_rows) + self._WriteArchiveString(archive, 'frequencies.txt', headway_string) + + # write fares (if applicable) + if self.GetFareList(): + fare_string = StringIO.StringIO() + writer = CsvUnicodeWriter(fare_string) + writer.writerow(Fare._FIELD_NAMES) + writer.writerows(f.GetFieldValuesTuple() for f in self.GetFareList()) + self._WriteArchiveString(archive, 'fare_attributes.txt', fare_string) + + # write fare rules (if applicable) + rule_rows = [] + for fare in self.GetFareList(): + for rule in fare.GetFareRuleList(): + rule_rows.append(rule.GetFieldValuesTuple()) + if rule_rows: + rule_string = StringIO.StringIO() + writer = CsvUnicodeWriter(rule_string) + writer.writerow(FareRule._FIELD_NAMES) + writer.writerows(rule_rows) + self._WriteArchiveString(archive, 'fare_rules.txt', rule_string) + stop_times_string = StringIO.StringIO() + writer = CsvUnicodeWriter(stop_times_string) + writer.writerow(StopTime._FIELD_NAMES) + for t in self.trips.values(): + writer.writerows(t._GenerateStopTimesTuples()) + self._WriteArchiveString(archive, 'stop_times.txt', stop_times_string) + + # write shapes (if applicable) + shape_rows = [] + for shape in self.GetShapeList(): + seq = 1 + for (lat, lon, dist) in shape.points: + shape_rows.append((shape.shape_id, lat, lon, seq, dist)) + seq += 1 + if shape_rows: + shape_string = StringIO.StringIO() + writer = CsvUnicodeWriter(shape_string) + writer.writerow(Shape._FIELD_NAMES) + writer.writerows(shape_rows) + self._WriteArchiveString(archive, 'shapes.txt', shape_string) + + # write transfers (if applicable) + if self.GetTransferList(): + transfer_string = StringIO.StringIO() + writer = CsvUnicodeWriter(transfer_string) + writer.writerow(Transfer._FIELD_NAMES) + writer.writerows(f.GetFieldValuesTuple() for f in self.GetTransferList()) + self._WriteArchiveString(archive, 'transfers.txt', transfer_string) + + archive.close() + + def GenerateDateTripsDeparturesList(self, date_start, date_end): + """Return a list of (date object, number of trips, number of departures). + + The list is generated for dates in the range [date_start, date_end). + + Args: + date_start: The first date in the list, a date object + date_end: The first date after the list, a date object + + Returns: + a list of (date object, number of trips, number of departures) tuples + """ + + service_id_to_trips = defaultdict(lambda: 0) + service_id_to_departures = defaultdict(lambda: 0) + for trip in self.GetTripList(): + headway_start_times = trip.GetHeadwayStartTimes() + if headway_start_times: + trip_runs = len(headway_start_times) + else: + trip_runs = 1 + + service_id_to_trips[trip.service_id] += trip_runs + service_id_to_departures[trip.service_id] += ( + (trip.GetCountStopTimes() - 1) * trip_runs) + + date_services = self.GetServicePeriodsActiveEachDate(date_start, date_end) + date_trips = [] + + for date, services in date_services: + day_trips = sum(service_id_to_trips[s.service_id] for s in services) + day_departures = sum( + service_id_to_departures[s.service_id] for s in services) + date_trips.append((date, day_trips, day_departures)) + return date_trips + + def ValidateFeedStartAndExpirationDates(self, + problems, + first_date, + last_date, + today): + """Validate the start and expiration dates of the feed. + Issue a warning if it only starts in the future, or if + it expires within 60 days. + + Args: + problems: The problem reporter object + first_date: A date object representing the first day the feed is active + last_date: A date object representing the last day the feed is active + today: A date object representing the date the validation is being run on + + Returns: + None + """ + warning_cutoff = today + datetime.timedelta(days=60) + if last_date < warning_cutoff: + problems.ExpirationDate(time.mktime(last_date.timetuple())) + + if first_date > today: + problems.FutureService(time.mktime(first_date.timetuple())) + + def ValidateServiceGaps(self, + problems, + validation_start_date, + validation_end_date, + service_gap_interval): + """Validate consecutive dates without service in the feed. + Issue a warning if it finds service gaps of at least + "service_gap_interval" consecutive days in the date range + [validation_start_date, last_service_date) + + Args: + problems: The problem reporter object + validation_start_date: A date object representing the date from which the + validation should take place + validation_end_date: A date object representing the first day the feed is + active + service_gap_interval: An integer indicating how many consecutive days the + service gaps need to have for a warning to be issued + + Returns: + None + """ + if service_gap_interval is None: + return + + departures = self.GenerateDateTripsDeparturesList(validation_start_date, + validation_end_date) + + # The first day without service of the _current_ gap + first_day_without_service = validation_start_date + # The last day without service of the _current_ gap + last_day_without_service = validation_start_date + + consecutive_days_without_service = 0 + + for day_date, day_trips, _ in departures: + if day_trips == 0: + if consecutive_days_without_service == 0: + first_day_without_service = day_date + consecutive_days_without_service += 1 + last_day_without_service = day_date + else: + if consecutive_days_without_service >= service_gap_interval: + problems.TooManyDaysWithoutService(first_day_without_service, + last_day_without_service, + consecutive_days_without_service) + + consecutive_days_without_service = 0 + + # We have to check if there is a gap at the end of the specified date range + if consecutive_days_without_service >= service_gap_interval: + problems.TooManyDaysWithoutService(first_day_without_service, + last_day_without_service, + consecutive_days_without_service) + + def Validate(self, + problems=None, + validate_children=True, + today=None, + service_gap_interval=None): + """Validates various holistic aspects of the schedule + (mostly interrelationships between the various data sets).""" + + if today is None: + today = datetime.date.today() + + if not problems: + problems = self.problem_reporter + + (start_date, end_date) = self.GetDateRange() + if not end_date or not start_date: + problems.OtherProblem('This feed has no effective service dates!', + type=TYPE_WARNING) + else: + try: + last_service_day = datetime.datetime( + *(time.strptime(end_date, "%Y%m%d")[0:6])).date() + first_service_day = datetime.datetime( + *(time.strptime(start_date, "%Y%m%d")[0:6])).date() + + except ValueError: + # Format of start_date and end_date checked in class ServicePeriod + pass + + else: + + self.ValidateFeedStartAndExpirationDates(problems, + first_service_day, + last_service_day, + today) + + # We start checking for service gaps a bit in the past if the + # feed was active then. See + # http://code.google.com/p/googletransitdatafeed/issues/detail?id=188 + # + # We subtract 1 from service_gap_interval so that if today has + # service no warning is issued. + # + # Service gaps are searched for only up to one year from today + if service_gap_interval is not None: + service_gap_timedelta = datetime.timedelta( + days=service_gap_interval - 1) + one_year = datetime.timedelta(days=365) + self.ValidateServiceGaps( + problems, + max(first_service_day, + today - service_gap_timedelta), + min(last_service_day, + today + one_year), + service_gap_interval) + + # TODO: Check Trip fields against valid values + + # Check for stops that aren't referenced by any trips and broken + # parent_station references. Also check that the parent station isn't too + # far from its child stops. + for stop in self.stops.values(): + if validate_children: + stop.Validate(problems) + cursor = self._connection.cursor() + cursor.execute("SELECT count(*) FROM stop_times WHERE stop_id=? LIMIT 1", + (stop.stop_id,)) + count = cursor.fetchone()[0] + if stop.location_type == 0 and count == 0: + problems.UnusedStop(stop.stop_id, stop.stop_name) + elif stop.location_type == 1 and count != 0: + problems.UsedStation(stop.stop_id, stop.stop_name) + + if stop.location_type != 1 and stop.parent_station: + if stop.parent_station not in self.stops: + problems.InvalidValue("parent_station", + EncodeUnicode(stop.parent_station), + "parent_station '%s' not found for stop_id " + "'%s' in stops.txt" % + (EncodeUnicode(stop.parent_station), + EncodeUnicode(stop.stop_id))) + elif self.stops[stop.parent_station].location_type != 1: + problems.InvalidValue("parent_station", + EncodeUnicode(stop.parent_station), + "parent_station '%s' of stop_id '%s' must " + "have location_type=1 in stops.txt" % + (EncodeUnicode(stop.parent_station), + EncodeUnicode(stop.stop_id))) + else: + parent_station = self.stops[stop.parent_station] + distance = ApproximateDistanceBetweenStops(stop, parent_station) + if distance > MAX_DISTANCE_BETWEEN_STOP_AND_PARENT_STATION_ERROR: + problems.StopTooFarFromParentStation( + stop.stop_id, stop.stop_name, parent_station.stop_id, + parent_station.stop_name, distance, TYPE_ERROR) + elif distance > MAX_DISTANCE_BETWEEN_STOP_AND_PARENT_STATION_WARNING: + problems.StopTooFarFromParentStation( + stop.stop_id, stop.stop_name, parent_station.stop_id, + parent_station.stop_name, distance, TYPE_WARNING) + + #TODO: check that every station is used. + # Then uncomment testStationWithoutReference. + + # Check for stops that might represent the same location (specifically, + # stops that are less that 2 meters apart) First filter out stops without a + # valid lat and lon. Then sort by latitude, then find the distance between + # each pair of stations within 2 meters latitude of each other. This avoids + # doing n^2 comparisons in the average case and doesn't need a spatial + # index. + sorted_stops = filter(lambda s: s.stop_lat and s.stop_lon, + self.GetStopList()) + sorted_stops.sort(key=(lambda x: x.stop_lat)) + TWO_METERS_LAT = 0.000018 + for index, stop in enumerate(sorted_stops[:-1]): + index += 1 + while ((index < len(sorted_stops)) and + ((sorted_stops[index].stop_lat - stop.stop_lat) < TWO_METERS_LAT)): + distance = ApproximateDistanceBetweenStops(stop, sorted_stops[index]) + if distance < 2: + other_stop = sorted_stops[index] + if stop.location_type == 0 and other_stop.location_type == 0: + problems.StopsTooClose( + EncodeUnicode(stop.stop_name), + EncodeUnicode(stop.stop_id), + EncodeUnicode(other_stop.stop_name), + EncodeUnicode(other_stop.stop_id), distance) + elif stop.location_type == 1 and other_stop.location_type == 1: + problems.StationsTooClose( + EncodeUnicode(stop.stop_name), EncodeUnicode(stop.stop_id), + EncodeUnicode(other_stop.stop_name), + EncodeUnicode(other_stop.stop_id), distance) + elif (stop.location_type in (0, 1) and + other_stop.location_type in (0, 1)): + if stop.location_type == 0 and other_stop.location_type == 1: + this_stop = stop + this_station = other_stop + elif stop.location_type == 1 and other_stop.location_type == 0: + this_stop = other_stop + this_station = stop + if this_stop.parent_station != this_station.stop_id: + problems.DifferentStationTooClose( + EncodeUnicode(this_stop.stop_name), + EncodeUnicode(this_stop.stop_id), + EncodeUnicode(this_station.stop_name), + EncodeUnicode(this_station.stop_id), distance) + index += 1 + + # Check for multiple routes using same short + long name + route_names = {} + for route in self.routes.values(): + if validate_children: + route.Validate(problems) + short_name = '' + if not IsEmpty(route.route_short_name): + short_name = route.route_short_name.lower().strip() + long_name = '' + if not IsEmpty(route.route_long_name): + long_name = route.route_long_name.lower().strip() + name = (short_name, long_name) + if name in route_names: + problems.InvalidValue('route_long_name', + long_name, + 'The same combination of ' + 'route_short_name and route_long_name ' + 'shouldn\'t be used for more than one ' + 'route, as it is for the for the two routes ' + 'with IDs "%s" and "%s".' % + (route.route_id, route_names[name].route_id), + type=TYPE_WARNING) + else: + route_names[name] = route + + stop_types = {} # a dict mapping stop_id to [route_id, route_type, is_match] + trips = {} # a dict mapping tuple to (route_id, trip_id) + for trip in sorted(self.trips.values()): + if trip.route_id not in self.routes: + continue + route_type = self.GetRoute(trip.route_id).route_type + arrival_times = [] + stop_ids = [] + for index, st in enumerate(trip.GetStopTimes(problems)): + stop_id = st.stop.stop_id + arrival_times.append(st.arrival_time) + stop_ids.append(stop_id) + # Check a stop if which belongs to both subway and bus. + if (route_type == Route._ROUTE_TYPE_NAMES['Subway'] or + route_type == Route._ROUTE_TYPE_NAMES['Bus']): + if stop_id not in stop_types: + stop_types[stop_id] = [trip.route_id, route_type, 0] + elif (stop_types[stop_id][1] != route_type and + stop_types[stop_id][2] == 0): + stop_types[stop_id][2] = 1 + if stop_types[stop_id][1] == Route._ROUTE_TYPE_NAMES['Subway']: + subway_route_id = stop_types[stop_id][0] + bus_route_id = trip.route_id + else: + subway_route_id = trip.route_id + bus_route_id = stop_types[stop_id][0] + problems.StopWithMultipleRouteTypes(st.stop.stop_name, stop_id, + subway_route_id, bus_route_id) + + # Check duplicate trips which go through the same stops with same + # service and start times. + if self._check_duplicate_trips: + if not stop_ids or not arrival_times: + continue + key = (trip.service_id, min(arrival_times), str(stop_ids)) + if key not in trips: + trips[key] = (trip.route_id, trip.trip_id) + else: + problems.DuplicateTrip(trips[key][1], trips[key][0], trip.trip_id, + trip.route_id) + + # Check that routes' agency IDs are valid, if set + for route in self.routes.values(): + if (not IsEmpty(route.agency_id) and + not route.agency_id in self._agencies): + problems.InvalidValue('agency_id', + route.agency_id, + 'The route with ID "%s" specifies agency_id ' + '"%s", which doesn\'t exist.' % + (route.route_id, route.agency_id)) + + # Make sure all trips have stop_times + # We're doing this here instead of in Trip.Validate() so that + # Trips can be validated without error during the reading of trips.txt + for trip in self.trips.values(): + trip.ValidateChildren(problems) + count_stop_times = trip.GetCountStopTimes() + if not count_stop_times: + problems.OtherProblem('The trip with the trip_id "%s" doesn\'t have ' + 'any stop times defined.' % trip.trip_id, + type=TYPE_WARNING) + if len(trip._headways) > 0: # no stoptimes, but there are headways + problems.OtherProblem('Frequencies defined, but no stop times given ' + 'in trip %s' % trip.trip_id, type=TYPE_ERROR) + elif count_stop_times == 1: + problems.OtherProblem('The trip with the trip_id "%s" only has one ' + 'stop on it; it should have at least one more ' + 'stop so that the riders can leave!' % + trip.trip_id, type=TYPE_WARNING) + else: + # These methods report InvalidValue if there's no first or last time + trip.GetStartTime(problems=problems) + trip.GetEndTime(problems=problems) + + # Check for unused shapes + known_shape_ids = set(self._shapes.keys()) + used_shape_ids = set() + for trip in self.GetTripList(): + used_shape_ids.add(trip.shape_id) + unused_shape_ids = known_shape_ids - used_shape_ids + if unused_shape_ids: + problems.OtherProblem('The shapes with the following shape_ids aren\'t ' + 'used by any trips: %s' % + ', '.join(unused_shape_ids), + type=TYPE_WARNING) + + +# Map from literal string that should never be found in the csv data to a human +# readable description +INVALID_LINE_SEPARATOR_UTF8 = { + "\x0c": "ASCII Form Feed 0x0C", + # May be part of end of line, but not found elsewhere + "\x0d": "ASCII Carriage Return 0x0D, \\r", + "\xe2\x80\xa8": "Unicode LINE SEPARATOR U+2028", + "\xe2\x80\xa9": "Unicode PARAGRAPH SEPARATOR U+2029", + "\xc2\x85": "Unicode NEXT LINE SEPARATOR U+0085", +} + +class EndOfLineChecker: + """Wrapper for a file-like object that checks for consistent line ends. + + The check for consistent end of lines (all CR LF or all LF) only happens if + next() is called until it raises StopIteration. + """ + def __init__(self, f, name, problems): + """Create new object. + + Args: + f: file-like object to wrap + name: name to use for f. StringIO objects don't have a name attribute. + problems: a ProblemReporterBase object + """ + self._f = f + self._name = name + self._crlf = 0 + self._crlf_examples = [] + self._lf = 0 + self._lf_examples = [] + self._line_number = 0 # first line will be number 1 + self._problems = problems + + def __iter__(self): + return self + + def next(self): + """Return next line without end of line marker or raise StopIteration.""" + try: + next_line = self._f.next() + except StopIteration: + self._FinalCheck() + raise + + self._line_number += 1 + m_eol = re.search(r"[\x0a\x0d]*$", next_line) + if m_eol.group() == "\x0d\x0a": + self._crlf += 1 + if self._crlf <= 5: + self._crlf_examples.append(self._line_number) + elif m_eol.group() == "\x0a": + self._lf += 1 + if self._lf <= 5: + self._lf_examples.append(self._line_number) + elif m_eol.group() == "": + # Should only happen at the end of the file + try: + self._f.next() + raise RuntimeError("Unexpected row without new line sequence") + except StopIteration: + # Will be raised again when EndOfLineChecker.next() is next called + pass + else: + self._problems.InvalidLineEnd( + codecs.getencoder('string_escape')(m_eol.group())[0], + (self._name, self._line_number)) + next_line_contents = next_line[0:m_eol.start()] + for seq, name in INVALID_LINE_SEPARATOR_UTF8.items(): + if next_line_contents.find(seq) != -1: + self._problems.OtherProblem( + "Line contains %s" % name, + context=(self._name, self._line_number)) + return next_line_contents + + def _FinalCheck(self): + if self._crlf > 0 and self._lf > 0: + crlf_plural = self._crlf > 1 and "s" or "" + crlf_lines = ", ".join(["%s" % e for e in self._crlf_examples]) + if self._crlf > len(self._crlf_examples): + crlf_lines += ", ..." + lf_plural = self._lf > 1 and "s" or "" + lf_lines = ", ".join(["%s" % e for e in self._lf_examples]) + if self._lf > len(self._lf_examples): + lf_lines += ", ..." + + self._problems.OtherProblem( + "Found %d CR LF \"\\r\\n\" line end%s (line%s %s) and " + "%d LF \"\\n\" line end%s (line%s %s). A file must use a " + "consistent line end." % (self._crlf, crlf_plural, crlf_plural, + crlf_lines, self._lf, lf_plural, + lf_plural, lf_lines), + (self._name,)) + # Prevent _FinalCheck() from reporting the problem twice, in the unlikely + # case that it is run twice + self._crlf = 0 + self._lf = 0 + + +# Filenames specified in GTFS spec +KNOWN_FILENAMES = [ + 'agency.txt', + 'stops.txt', + 'routes.txt', + 'trips.txt', + 'stop_times.txt', + 'calendar.txt', + 'calendar_dates.txt', + 'fare_attributes.txt', + 'fare_rules.txt', + 'shapes.txt', + 'frequencies.txt', + 'transfers.txt', +] + +class Loader: + def __init__(self, + feed_path=None, + schedule=None, + problems=default_problem_reporter, + extra_validation=False, + load_stop_times=True, + memory_db=True, + zip=None, + check_duplicate_trips=False): + """Initialize a new Loader object. + + Args: + feed_path: string path to a zip file or directory + schedule: a Schedule object or None to have one created + problems: a ProblemReporter object, the default reporter raises an + exception for each problem + extra_validation: True if you would like extra validation + load_stop_times: load the stop_times table, used to speed load time when + times are not needed. The default is True. + memory_db: if creating a new Schedule object use an in-memory sqlite + database instead of creating one in a temporary file + zip: a zipfile.ZipFile object, optionally used instead of path + """ + if not schedule: + schedule = Schedule(problem_reporter=problems, memory_db=memory_db, + check_duplicate_trips=check_duplicate_trips) + self._extra_validation = extra_validation + self._schedule = schedule + self._problems = problems + self._path = feed_path + self._zip = zip + self._load_stop_times = load_stop_times + + def _DetermineFormat(self): + """Determines whether the feed is in a form that we understand, and + if so, returns True.""" + if self._zip: + # If zip was passed to __init__ then path isn't used + assert not self._path + return True + + if not isinstance(self._path, basestring) and hasattr(self._path, 'read'): + # A file-like object, used for testing with a StringIO file + self._zip = zipfile.ZipFile(self._path, mode='r') + return True + + if not os.path.exists(self._path): + self._problems.FeedNotFound(self._path) + return False + + if self._path.endswith('.zip'): + try: + self._zip = zipfile.ZipFile(self._path, mode='r') + except IOError: # self._path is a directory + pass + except zipfile.BadZipfile: + self._problems.UnknownFormat(self._path) + return False + + if not self._zip and not os.path.isdir(self._path): + self._problems.UnknownFormat(self._path) + return False + + return True + + def _GetFileNames(self): + """Returns a list of file names in the feed.""" + if self._zip: + return self._zip.namelist() + else: + return os.listdir(self._path) + + def _CheckFileNames(self): + filenames = self._GetFileNames() + for feed_file in filenames: + if feed_file not in KNOWN_FILENAMES: + if not feed_file.startswith('.'): + # Don't worry about .svn files and other hidden files + # as this will break the tests. + self._problems.UnknownFile(feed_file) + + def _GetUtf8Contents(self, file_name): + """Check for errors in file_name and return a string for csv reader.""" + contents = self._FileContents(file_name) + if not contents: # Missing file + return + + # Check for errors that will prevent csv.reader from working + if len(contents) >= 2 and contents[0:2] in (codecs.BOM_UTF16_BE, + codecs.BOM_UTF16_LE): + self._problems.FileFormat("appears to be encoded in utf-16", (file_name, )) + # Convert and continue, so we can find more errors + contents = codecs.getdecoder('utf-16')(contents)[0].encode('utf-8') + + null_index = contents.find('\0') + if null_index != -1: + # It is easier to get some surrounding text than calculate the exact + # row_num + m = re.search(r'.{,20}\0.{,20}', contents, re.DOTALL) + self._problems.FileFormat( + "contains a null in text \"%s\" at byte %d" % + (codecs.getencoder('string_escape')(m.group()), null_index + 1), + (file_name, )) + return + + # strip out any UTF-8 Byte Order Marker (otherwise it'll be + # treated as part of the first column name, causing a mis-parse) + contents = contents.lstrip(codecs.BOM_UTF8) + return contents + + def _ReadCsvDict(self, file_name, all_cols, required): + """Reads lines from file_name, yielding a dict of unicode values.""" + assert file_name.endswith(".txt") + table_name = file_name[0:-4] + contents = self._GetUtf8Contents(file_name) + if not contents: + return + + eol_checker = EndOfLineChecker(StringIO.StringIO(contents), + file_name, self._problems) + # The csv module doesn't provide a way to skip trailing space, but when I + # checked 15/675 feeds had trailing space in a header row and 120 had spaces + # after fields. Space after header fields can cause a serious parsing + # problem, so warn. Space after body fields can cause a problem time, + # integer and id fields; they will be validated at higher levels. + reader = csv.reader(eol_checker, skipinitialspace=True) + + raw_header = reader.next() + header_occurrences = defaultdict(lambda: 0) + header = [] + valid_columns = [] # Index into raw_header and raw_row + for i, h in enumerate(raw_header): + h_stripped = h.strip() + if not h_stripped: + self._problems.CsvSyntax( + description="The header row should not contain any blank values. " + "The corresponding column will be skipped for the " + "entire file.", + context=(file_name, 1, [''] * len(raw_header), raw_header), + type=TYPE_ERROR) + continue + elif h != h_stripped: + self._problems.CsvSyntax( + description="The header row should not contain any " + "space characters.", + context=(file_name, 1, [''] * len(raw_header), raw_header), + type=TYPE_WARNING) + header.append(h_stripped) + valid_columns.append(i) + header_occurrences[h_stripped] += 1 + + for name, count in header_occurrences.items(): + if count > 1: + self._problems.DuplicateColumn( + header=name, + file_name=file_name, + count=count) + + self._schedule._table_columns[table_name] = header + + # check for unrecognized columns, which are often misspellings + unknown_cols = set(header) - set(all_cols) + if len(unknown_cols) == len(header): + self._problems.CsvSyntax( + description="The header row did not contain any known column " + "names. The file is most likely missing the header row " + "or not in the expected CSV format.", + context=(file_name, 1, [''] * len(raw_header), raw_header), + type=TYPE_ERROR) + else: + for col in unknown_cols: + # this is provided in order to create a nice colored list of + # columns in the validator output + context = (file_name, 1, [''] * len(header), header) + self._problems.UnrecognizedColumn(file_name, col, context) + + missing_cols = set(required) - set(header) + for col in missing_cols: + # this is provided in order to create a nice colored list of + # columns in the validator output + context = (file_name, 1, [''] * len(header), header) + self._problems.MissingColumn(file_name, col, context) + + line_num = 1 # First line read by reader.next() above + for raw_row in reader: + line_num += 1 + if len(raw_row) == 0: # skip extra empty lines in file + continue + + if len(raw_row) > len(raw_header): + self._problems.OtherProblem('Found too many cells (commas) in line ' + '%d of file "%s". Every row in the file ' + 'should have the same number of cells as ' + 'the header (first line) does.' % + (line_num, file_name), + (file_name, line_num), + type=TYPE_WARNING) + + if len(raw_row) < len(raw_header): + self._problems.OtherProblem('Found missing cells (commas) in line ' + '%d of file "%s". Every row in the file ' + 'should have the same number of cells as ' + 'the header (first line) does.' % + (line_num, file_name), + (file_name, line_num), + type=TYPE_WARNING) + + # raw_row is a list of raw bytes which should be valid utf-8. Convert each + # valid_columns of raw_row into Unicode. + valid_values = [] + unicode_error_columns = [] # index of valid_values elements with an error + for i in valid_columns: + try: + valid_values.append(raw_row[i].decode('utf-8')) + except UnicodeDecodeError: + # Replace all invalid characters with REPLACEMENT CHARACTER (U+FFFD) + valid_values.append(codecs.getdecoder("utf8") + (raw_row[i], errors="replace")[0]) + unicode_error_columns.append(len(valid_values) - 1) + except IndexError: + break + + # The error report may contain a dump of all values in valid_values so + # problems can not be reported until after converting all of raw_row to + # Unicode. + for i in unicode_error_columns: + self._problems.InvalidValue(header[i], valid_values[i], + 'Unicode error', + (file_name, line_num, + valid_values, header)) + + + d = dict(zip(header, valid_values)) + yield (d, line_num, header, valid_values) + + # TODO: Add testing for this specific function + def _ReadCSV(self, file_name, cols, required): + """Reads lines from file_name, yielding a list of unicode values + corresponding to the column names in cols.""" + contents = self._GetUtf8Contents(file_name) + if not contents: + return + + eol_checker = EndOfLineChecker(StringIO.StringIO(contents), + file_name, self._problems) + reader = csv.reader(eol_checker) # Use excel dialect + + header = reader.next() + header = map(lambda x: x.strip(), header) # trim any whitespace + header_occurrences = defaultdict(lambda: 0) + for column_header in header: + header_occurrences[column_header] += 1 + + for name, count in header_occurrences.items(): + if count > 1: + self._problems.DuplicateColumn( + header=name, + file_name=file_name, + count=count) + + # check for unrecognized columns, which are often misspellings + unknown_cols = set(header).difference(set(cols)) + for col in unknown_cols: + # this is provided in order to create a nice colored list of + # columns in the validator output + context = (file_name, 1, [''] * len(header), header) + self._problems.UnrecognizedColumn(file_name, col, context) + + col_index = [-1] * len(cols) + for i in range(len(cols)): + if cols[i] in header: + col_index[i] = header.index(cols[i]) + elif cols[i] in required: + self._problems.MissingColumn(file_name, cols[i]) + + row_num = 1 + for row in reader: + row_num += 1 + if len(row) == 0: # skip extra empty lines in file + continue + + if len(row) > len(header): + self._problems.OtherProblem('Found too many cells (commas) in line ' + '%d of file "%s". Every row in the file ' + 'should have the same number of cells as ' + 'the header (first line) does.' % + (row_num, file_name), (file_name, row_num), + type=TYPE_WARNING) + + if len(row) < len(header): + self._problems.OtherProblem('Found missing cells (commas) in line ' + '%d of file "%s". Every row in the file ' + 'should have the same number of cells as ' + 'the header (first line) does.' % + (row_num, file_name), (file_name, row_num), + type=TYPE_WARNING) + + result = [None] * len(cols) + unicode_error_columns = [] # A list of column numbers with an error + for i in range(len(cols)): + ci = col_index[i] + if ci >= 0: + if len(row) <= ci: # handle short CSV rows + result[i] = u'' + else: + try: + result[i] = row[ci].decode('utf-8').strip() + except UnicodeDecodeError: + # Replace all invalid characters with + # REPLACEMENT CHARACTER (U+FFFD) + result[i] = codecs.getdecoder("utf8")(row[ci], + errors="replace")[0].strip() + unicode_error_columns.append(i) + + for i in unicode_error_columns: + self._problems.InvalidValue(cols[i], result[i], + 'Unicode error', + (file_name, row_num, result, cols)) + yield (result, row_num, cols) + + def _HasFile(self, file_name): + """Returns True if there's a file in the current feed with the + given file_name in the current feed.""" + if self._zip: + return file_name in self._zip.namelist() + else: + file_path = os.path.join(self._path, file_name) + return os.path.exists(file_path) and os.path.isfile(file_path) + + def _FileContents(self, file_name): + results = None + if self._zip: + try: + results = self._zip.read(file_name) + except KeyError: # file not found in archve + self._problems.MissingFile(file_name) + return None + else: + try: + data_file = open(os.path.join(self._path, file_name), 'rb') + results = data_file.read() + except IOError: # file not found + self._problems.MissingFile(file_name) + return None + + if not results: + self._problems.EmptyFile(file_name) + return results + + def _LoadAgencies(self): + for (d, row_num, header, row) in self._ReadCsvDict('agency.txt', + Agency._FIELD_NAMES, + Agency._REQUIRED_FIELD_NAMES): + self._problems.SetFileContext('agency.txt', row_num, row, header) + agency = Agency(field_dict=d) + self._schedule.AddAgencyObject(agency, self._problems) + self._problems.ClearContext() + + def _LoadStops(self): + for (d, row_num, header, row) in self._ReadCsvDict( + 'stops.txt', + Stop._FIELD_NAMES, + Stop._REQUIRED_FIELD_NAMES): + self._problems.SetFileContext('stops.txt', row_num, row, header) + + stop = Stop(field_dict=d) + stop.Validate(self._problems) + self._schedule.AddStopObject(stop, self._problems) + + self._problems.ClearContext() + + def _LoadRoutes(self): + for (d, row_num, header, row) in self._ReadCsvDict( + 'routes.txt', + Route._FIELD_NAMES, + Route._REQUIRED_FIELD_NAMES): + self._problems.SetFileContext('routes.txt', row_num, row, header) + + route = Route(field_dict=d) + self._schedule.AddRouteObject(route, self._problems) + + self._problems.ClearContext() + + def _LoadCalendar(self): + file_name = 'calendar.txt' + file_name_dates = 'calendar_dates.txt' + if not self._HasFile(file_name) and not self._HasFile(file_name_dates): + self._problems.MissingFile(file_name) + return + + # map period IDs to (period object, (file_name, row_num, row, cols)) + periods = {} + + # process calendar.txt + if self._HasFile(file_name): + has_useful_contents = False + for (row, row_num, cols) in \ + self._ReadCSV(file_name, + ServicePeriod._FIELD_NAMES, + ServicePeriod._FIELD_NAMES_REQUIRED): + context = (file_name, row_num, row, cols) + self._problems.SetFileContext(*context) + + period = ServicePeriod(field_list=row) + + if period.service_id in periods: + self._problems.DuplicateID('service_id', period.service_id) + else: + periods[period.service_id] = (period, context) + self._problems.ClearContext() + + # process calendar_dates.txt + if self._HasFile(file_name_dates): + # ['service_id', 'date', 'exception_type'] + fields = ServicePeriod._FIELD_NAMES_CALENDAR_DATES + for (row, row_num, cols) in self._ReadCSV(file_name_dates, + fields, fields): + context = (file_name_dates, row_num, row, cols) + self._problems.SetFileContext(*context) + + service_id = row[0] + + period = None + if service_id in periods: + period = periods[service_id][0] + else: + period = ServicePeriod(service_id) + periods[period.service_id] = (period, context) + + exception_type = row[2] + if exception_type == u'1': + period.SetDateHasService(row[1], True, self._problems) + elif exception_type == u'2': + period.SetDateHasService(row[1], False, self._problems) + else: + self._problems.InvalidValue('exception_type', exception_type) + self._problems.ClearContext() + + # Now insert the periods into the schedule object, so that they're + # validated with both calendar and calendar_dates info present + for period, context in periods.values(): + self._problems.SetFileContext(*context) + self._schedule.AddServicePeriodObject(period, self._problems) + self._problems.ClearContext() + + def _LoadShapes(self): + if not self._HasFile('shapes.txt'): + return + + shapes = {} # shape_id to tuple + for (row, row_num, cols) in self._ReadCSV('shapes.txt', + Shape._FIELD_NAMES, + Shape._REQUIRED_FIELD_NAMES): + file_context = ('shapes.txt', row_num, row, cols) + self._problems.SetFileContext(*file_context) + + (shape_id, lat, lon, seq, dist) = row + if IsEmpty(shape_id): + self._problems.MissingValue('shape_id') + continue + try: + seq = int(seq) + except (TypeError, ValueError): + self._problems.InvalidValue('shape_pt_sequence', seq, + 'Value should be a number (0 or higher)') + continue + + shapes.setdefault(shape_id, []).append((seq, lat, lon, dist, file_context)) + self._problems.ClearContext() + + for shape_id, points in shapes.items(): + shape = Shape(shape_id) + points.sort() + if points and points[0][0] < 0: + self._problems.InvalidValue('shape_pt_sequence', points[0][0], + 'In shape %s, a negative sequence number ' + '%d was found; sequence numbers should be ' + '0 or higher.' % (shape_id, points[0][0])) + + last_seq = None + for (seq, lat, lon, dist, file_context) in points: + if (seq == last_seq): + self._problems.SetFileContext(*file_context) + self._problems.InvalidValue('shape_pt_sequence', seq, + 'The sequence number %d occurs more ' + 'than once in shape %s.' % + (seq, shape_id)) + last_seq = seq + shape.AddPoint(lat, lon, dist, self._problems) + self._problems.ClearContext() + + self._schedule.AddShapeObject(shape, self._problems) + + def _LoadTrips(self): + for (d, row_num, header, row) in self._ReadCsvDict( + 'trips.txt', + Trip._FIELD_NAMES, + Trip._REQUIRED_FIELD_NAMES): + self._problems.SetFileContext('trips.txt', row_num, row, header) + + trip = Trip(field_dict=d) + self._schedule.AddTripObject(trip, self._problems) + + self._problems.ClearContext() + + def _LoadFares(self): + if not self._HasFile('fare_attributes.txt'): + return + for (row, row_num, cols) in self._ReadCSV('fare_attributes.txt', + Fare._FIELD_NAMES, + Fare._REQUIRED_FIELD_NAMES): + self._problems.SetFileContext('fare_attributes.txt', row_num, row, cols) + + fare = Fare(field_list=row) + self._schedule.AddFareObject(fare, self._problems) + + self._problems.ClearContext() + + def _LoadFareRules(self): + if not self._HasFile('fare_rules.txt'): + return + for (row, row_num, cols) in self._ReadCSV('fare_rules.txt', + FareRule._FIELD_NAMES, + FareRule._REQUIRED_FIELD_NAMES): + self._problems.SetFileContext('fare_rules.txt', row_num, row, cols) + + rule = FareRule(field_list=row) + self._schedule.AddFareRuleObject(rule, self._problems) + + self._problems.ClearContext() + + def _LoadHeadways(self): + file_name = 'frequencies.txt' + if not self._HasFile(file_name): # headways are an optional feature + return + + # ['trip_id', 'start_time', 'end_time', 'headway_secs'] + fields = Trip._FIELD_NAMES_HEADWAY + modified_trips = {} + for (row, row_num, cols) in self._ReadCSV(file_name, fields, fields): + self._problems.SetFileContext(file_name, row_num, row, cols) + (trip_id, start_time, end_time, headway_secs) = row + try: + trip = self._schedule.GetTrip(trip_id) + trip.AddHeadwayPeriod(start_time, end_time, headway_secs, + self._problems) + modified_trips[trip_id] = trip + except KeyError: + self._problems.InvalidValue('trip_id', trip_id) + self._problems.ClearContext() + + for trip in modified_trips.values(): + trip.Validate(self._problems) + + def _LoadStopTimes(self): + for (row, row_num, cols) in self._ReadCSV('stop_times.txt', + StopTime._FIELD_NAMES, + StopTime._REQUIRED_FIELD_NAMES): + file_context = ('stop_times.txt', row_num, row, cols) + self._problems.SetFileContext(*file_context) + + (trip_id, arrival_time, departure_time, stop_id, stop_sequence, + stop_headsign, pickup_type, drop_off_type, shape_dist_traveled) = row + + try: + sequence = int(stop_sequence) + except (TypeError, ValueError): + self._problems.InvalidValue('stop_sequence', stop_sequence, + 'This should be a number.') + continue + if sequence < 0: + self._problems.InvalidValue('stop_sequence', sequence, + 'Sequence numbers should be 0 or higher.') + + if stop_id not in self._schedule.stops: + self._problems.InvalidValue('stop_id', stop_id, + 'This value wasn\'t defined in stops.txt') + continue + stop = self._schedule.stops[stop_id] + if trip_id not in self._schedule.trips: + self._problems.InvalidValue('trip_id', trip_id, + 'This value wasn\'t defined in trips.txt') + continue + trip = self._schedule.trips[trip_id] + + # If self._problems.Report returns then StopTime.__init__ will return + # even if the StopTime object has an error. Thus this code may add a + # StopTime that didn't validate to the database. + # Trip.GetStopTimes then tries to make a StopTime from the invalid data + # and calls the problem reporter for errors. An ugly solution is to + # wrap problems and a better solution is to move all validation out of + # __init__. For now make sure Trip.GetStopTimes gets a problem reporter + # when called from Trip.Validate. + stop_time = StopTime(self._problems, stop, arrival_time, + departure_time, stop_headsign, + pickup_type, drop_off_type, + shape_dist_traveled, stop_sequence=sequence) + trip._AddStopTimeObjectUnordered(stop_time, self._schedule) + self._problems.ClearContext() + + # stop_times are validated in Trip.ValidateChildren, called by + # Schedule.Validate + + def _LoadTransfers(self): + file_name = 'transfers.txt' + if not self._HasFile(file_name): # transfers are an optional feature + return + for (d, row_num, header, row) in self._ReadCsvDict(file_name, + Transfer._FIELD_NAMES, + Transfer._REQUIRED_FIELD_NAMES): + self._problems.SetFileContext(file_name, row_num, row, header) + transfer = Transfer(field_dict=d) + self._schedule.AddTransferObject(transfer, self._problems) + self._problems.ClearContext() + + def Load(self): + self._problems.ClearContext() + if not self._DetermineFormat(): + return self._schedule + + self._CheckFileNames() + + self._LoadAgencies() + self._LoadStops() + self._LoadRoutes() + self._LoadCalendar() + self._LoadShapes() + self._LoadTrips() + self._LoadHeadways() + if self._load_stop_times: + self._LoadStopTimes() + self._LoadFares() + self._LoadFareRules() + self._LoadTransfers() + + if self._zip: + self._zip.close() + self._zip = None + + if self._extra_validation: + self._schedule.Validate(self._problems, validate_children=False) + + return self._schedule + + +class ShapeLoader(Loader): + """A subclass of Loader that only loads the shapes from a GTFS file.""" + + def __init__(self, *args, **kwargs): + """Initialize a new ShapeLoader object. + + See Loader.__init__ for argument documentation. + """ + Loader.__init__(self, *args, **kwargs) + + def Load(self): + self._LoadShapes() + return self._schedule + --- /dev/null +++ b/origin-src/transitfeed-1.2.5/build/lib/transitfeed/shapelib.py @@ -1,1 +1,613 @@ - +#!/usr/bin/python2.4 +# +# Copyright 2007 Google Inc. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +"""A library for manipulating points and polylines. + +This is a library for creating and manipulating points on the unit +sphere, as an approximate model of Earth. The primary use of this +library is to make manipulation and matching of polylines easy in the +transitfeed library. + +NOTE: in this library, Earth is modelled as a sphere, whereas +GTFS specifies that latitudes and longitudes are in WGS84. For the +purpose of comparing and matching latitudes and longitudes that +are relatively close together on the surface of the earth, this +is adequate; for other purposes, this library may not be accurate +enough. +""" + +__author__ = 'chris.harrelson.code@gmail.com (Chris Harrelson)' + +import copy +import decimal +import heapq +import math + +class ShapeError(Exception): + """Thrown whenever there is a shape parsing error.""" + pass + + +EARTH_RADIUS_METERS = 6371010.0 + + +class Point(object): + """ + A class representing a point on the unit sphere in three dimensions. + """ + def __init__(self, x, y, z): + self.x = x + self.y = y + self.z = z + + def __hash__(self): + return hash((self.x, self.y, self.z)) + + def __cmp__(self, other): + if not isinstance(other, Point): + raise TypeError('Point.__cmp__(x,y) requires y to be a "Point", ' + 'not a "%s"' % type(other).__name__) + return cmp((self.x, self.y, self.z), (other.x, other.y, other.z)) + + def __str__(self): + return "(%.15f, %.15f, %.15f) " % (self.x, self.y, self.z) + + def Norm2(self): + """ + Returns the L_2 (Euclidean) norm of self. + """ + sum = self.x * self.x + self.y * self.y + self.z * self.z + return math.sqrt(float(sum)) + + def IsUnitLength(self): + return abs(self.Norm2() - 1.0) < 1e-14 + + def Plus(self, other): + """ + Returns a new point which is the pointwise sum of self and other. + """ + return Point(self.x + other.x, + self.y + other.y, + self.z + other.z) + + def Minus(self, other): + """ + Returns a new point which is the pointwise subtraction of other from + self. + """ + return Point(self.x - other.x, + self.y - other.y, + self.z - other.z) + + def DotProd(self, other): + """ + Returns the (scalar) dot product of self with other. + """ + return self.x * other.x + self.y * other.y + self.z * other.z + + def Times(self, val): + """ + Returns a new point which is pointwise multiplied by val. + """ + return Point(self.x * val, self.y * val, self.z * val) + + def Normalize(self): + """ + Returns a unit point in the same direction as self. + """ + return self.Times(1 / self.Norm2()) + + def RobustCrossProd(self, other): + """ + A robust version of cross product. If self and other + are not nearly the same point, returns the same value + as CrossProd() modulo normalization. Otherwise returns + an arbitrary unit point orthogonal to self. + """ + assert(self.IsUnitLength() and other.IsUnitLength()) + x = self.Plus(other).CrossProd(other.Minus(self)) + if abs(x.x) > 1e-15 or abs(x.y) > 1e-15 or abs(x.z) > 1e-15: + return x.Normalize() + else: + return self.Ortho() + + def LargestComponent(self): + """ + Returns (i, val) where i is the component index (0 - 2) + which has largest absolute value and val is the value + of the component. + """ + if abs(self.x) > abs(self.y): + if abs(self.x) > abs(self.z): + return (0, self.x) + else: + return (2, self.z) + else: + if abs(self.y) > abs(self.z): + return (1, self.y) + else: + return (2, self.z) + + def Ortho(self): + """Returns a unit-length point orthogonal to this point""" + (index, val) = self.LargestComponent() + index = index - 1 + if index < 0: + index = 2 + temp = Point(0.012, 0.053, 0.00457) + if index == 0: + temp.x = 1 + elif index == 1: + temp.y = 1 + elif index == 2: + temp.z = 1 + return self.CrossProd(temp).Normalize() + + def CrossProd(self, other): + """ + Returns the cross product of self and other. + """ + return Point( + self.y * other.z - self.z * other.y, + self.z * other.x - self.x * other.z, + self.x * other.y - self.y * other.x) + + @staticmethod + def _approxEq(a, b): + return abs(a - b) < 1e-11 + + def Equals(self, other): + """ + Returns true of self and other are approximately equal. + """ + return (self._approxEq(self.x, other.x) + and self._approxEq(self.y, other.y) + and self._approxEq(self.z, other.z)) + + def Angle(self, other): + """ + Returns the angle in radians between self and other. + """ + return math.atan2(self.CrossProd(other).Norm2(), + self.DotProd(other)) + + def ToLatLng(self): + """ + Returns that latitude and longitude that this point represents + under a spherical Earth model. + """ + rad_lat = math.atan2(self.z, math.sqrt(self.x * self.x + self.y * self.y)) + rad_lng = math.atan2(self.y, self.x) + return (rad_lat * 180.0 / math.pi, rad_lng * 180.0 / math.pi) + + @staticmethod + def FromLatLng(lat, lng): + """ + Returns a new point representing this latitude and longitude under + a spherical Earth model. + """ + phi = lat * (math.pi / 180.0) + theta = lng * (math.pi / 180.0) + cosphi = math.cos(phi) + return Point(math.cos(theta) * cosphi, + math.sin(theta) * cosphi, + math.sin(phi)) + + def GetDistanceMeters(self, other): + assert(self.IsUnitLength() and other.IsUnitLength()) + return self.Angle(other) * EARTH_RADIUS_METERS + + +def SimpleCCW(a, b, c): + """ + Returns true if the triangle abc is oriented counterclockwise. + """ + return c.CrossProd(a).DotProd(b) > 0 + +def GetClosestPoint(x, a, b): + """ + Returns the point on the great circle segment ab closest to x. + """ + assert(x.IsUnitLength()) + assert(a.IsUnitLength()) + assert(b.IsUnitLength()) + + a_cross_b = a.RobustCrossProd(b) + # project to the great circle going through a and b + p = x.Minus( + a_cross_b.Times( + x.DotProd(a_cross_b) / a_cross_b.Norm2())) + + # if p lies between a and b, return it + if SimpleCCW(a_cross_b, a, p) and SimpleCCW(p, b, a_cross_b): + return p.Normalize() + + # otherwise return the closer of a or b + if x.Minus(a).Norm2() <= x.Minus(b).Norm2(): + return a + else: + return b + + +class Poly(object): + """ + A class representing a polyline. + """ + def __init__(self, points = [], name=None): + self._points = list(points) + self._name = name + + def AddPoint(self, p): + """ + Adds a new point to the end of the polyline. + """ + assert(p.IsUnitLength()) + self._points.append(p) + + def GetName(self): + return self._name + + def GetPoint(self, i): + return self._points[i] + + def GetPoints(self): + return self._points + + def GetNumPoints(self): + return len(self._points) + + def _GetPointSafe(self, i): + try: + return self.GetPoint(i) + except IndexError: + return None + + def GetClosestPoint(self, p): + """ + Returns (closest_p, closest_i), where closest_p is the closest point + to p on the piecewise linear curve represented by the polyline, + and closest_i is the index of the point on the polyline just before + the polyline segment that contains closest_p. + """ + assert(len(self._points) > 0) + closest_point = self._points[0] + closest_i = 0 + + for i in range(0, len(self._points) - 1): + (a, b) = (self._points[i], self._points[i+1]) + cur_closest_point = GetClosestPoint(p, a, b) + if p.Angle(cur_closest_point) < p.Angle(closest_point): + closest_point = cur_closest_point.Normalize() + closest_i = i + + return (closest_point, closest_i) + + def LengthMeters(self): + """Return length of this polyline in meters.""" + assert(len(self._points) > 0) + length = 0 + for i in range(0, len(self._points) - 1): + length += self._points[i].GetDistanceMeters(self._points[i+1]) + return length + + def Reversed(self): + """Return a polyline that is the reverse of this polyline.""" + return Poly(reversed(self.GetPoints()), self.GetName()) + + def CutAtClosestPoint(self, p): + """ + Let x be the point on the polyline closest to p. Then + CutAtClosestPoint returns two new polylines, one representing + the polyline from the beginning up to x, and one representing + x onwards to the end of the polyline. x is the first point + returned in the second polyline. + """ + (closest, i) = self.GetClosestPoint(p) + + tmp = [closest] + tmp.extend(self._points[i+1:]) + return (Poly(self._points[0:i+1]), + Poly(tmp)) + + def GreedyPolyMatchDist(self, shape): + """ + Tries a greedy matching algorithm to match self to the + given shape. Returns the maximum distance in meters of + any point in self to its matched point in shape under the + algorithm. + + Args: shape, a Poly object. + """ + tmp_shape = Poly(shape.GetPoints()) + max_radius = 0 + for (i, point) in enumerate(self._points): + tmp_shape = tmp_shape.CutAtClosestPoint(point)[1] + dist = tmp_shape.GetPoint(0).GetDistanceMeters(point) + max_radius = max(max_radius, dist) + return max_radius + + @staticmethod + def MergePolys(polys, merge_point_threshold=10): + """ + Merge multiple polylines, in the order that they were passed in. + Merged polyline will have the names of their component parts joined by ';'. + Example: merging [a,b], [c,d] and [e,f] will result in [a,b,c,d,e,f]. + However if the endpoints of two adjacent polylines are less than + merge_point_threshold meters apart, we will only use the first endpoint in + the merged polyline. + """ + name = ";".join((p.GetName(), '')[p.GetName() is None] for p in polys) + merged = Poly([], name) + if polys: + first_poly = polys[0] + for p in first_poly.GetPoints(): + merged.AddPoint(p) + last_point = merged._GetPointSafe(-1) + for poly in polys[1:]: + first_point = poly._GetPointSafe(0) + if (last_point and first_point and + last_point.GetDistanceMeters(first_point) <= merge_point_threshold): + points = poly.GetPoints()[1:] + else: + points = poly.GetPoints() + for p in points: + merged.AddPoint(p) + last_point = merged._GetPointSafe(-1) + return merged + + + def __str__(self): + return self._ToString(str) + + def ToLatLngString(self): + return self._ToString(lambda p: str(p.ToLatLng())) + + def _ToString(self, pointToStringFn): + return "%s: %s" % (self.GetName() or "", + ", ".join([pointToStringFn(p) for p in self._points])) + + +class PolyCollection(object): + """ + A class representing a collection of polylines. + """ + def __init__(self): + self._name_to_shape = {} + pass + + def AddPoly(self, poly, smart_duplicate_handling=True): + """ + Adds a new polyline to the collection. + """ + inserted_name = poly.GetName() + if poly.GetName() in self._name_to_shape: + if not smart_duplicate_handling: + raise ShapeError("Duplicate shape found: " + poly.GetName()) + + print ("Warning: duplicate shape id being added to collection: " + + poly.GetName()) + if poly.GreedyPolyMatchDist(self._name_to_shape[poly.GetName()]) < 10: + print " (Skipping as it apears to be an exact duplicate)" + else: + print " (Adding new shape variant with uniquified name)" + inserted_name = "%s-%d" % (inserted_name, len(self._name_to_shape)) + self._name_to_shape[inserted_name] = poly + + def NumPolys(self): + return len(self._name_to_shape) + + def FindMatchingPolys(self, start_point, end_point, max_radius=150): + """ + Returns a list of polylines in the collection that have endpoints + within max_radius of the given start and end points. + """ + matches = [] + for shape in self._name_to_shape.itervalues(): + if start_point.GetDistanceMeters(shape.GetPoint(0)) < max_radius and \ + end_point.GetDistanceMeters(shape.GetPoint(-1)) < max_radius: + matches.append(shape) + return matches + +class PolyGraph(PolyCollection): + """ + A class representing a graph where the edges are polylines. + """ + def __init__(self): + PolyCollection.__init__(self) + self._nodes = {} + + def AddPoly(self, poly, smart_duplicate_handling=True): + PolyCollection.AddPoly(self, poly, smart_duplicate_handling) + start_point = poly.GetPoint(0) + end_point = poly.GetPoint(-1) + self._AddNodeWithEdge(start_point, poly) + self._AddNodeWithEdge(end_point, poly) + + def _AddNodeWithEdge(self, point, edge): + if point in self._nodes: + self._nodes[point].add(edge) + else: + self._nodes[point] = set([edge]) + + def ShortestPath(self, start, goal): + """Uses the A* algorithm to find a shortest path between start and goal. + + For more background see http://en.wikipedia.org/wiki/A-star_algorithm + + Some definitions: + g(x): The actual shortest distance traveled from initial node to current + node. + h(x): The estimated (or "heuristic") distance from current node to goal. + We use the distance on Earth from node to goal as the heuristic. + This heuristic is both admissible and monotonic (see wikipedia for + more details). + f(x): The sum of g(x) and h(x), used to prioritize elements to look at. + + Arguments: + start: Point that is in the graph, start point of the search. + goal: Point that is in the graph, end point for the search. + + Returns: + A Poly object representing the shortest polyline through the graph from + start to goal, or None if no path found. + """ + + assert start in self._nodes + assert goal in self._nodes + closed_set = set() # Set of nodes already evaluated. + open_heap = [(0, start)] # Nodes to visit, heapified by f(x). + open_set = set([start]) # Same as open_heap, but a set instead of a heap. + g_scores = { start: 0 } # Distance from start along optimal path + came_from = {} # Map to reconstruct optimal path once we're done. + while open_set: + (f_x, x) = heapq.heappop(open_heap) + open_set.remove(x) + if x == goal: + return self._ReconstructPath(came_from, goal) + closed_set.add(x) + edges = self._nodes[x] + for edge in edges: + if edge.GetPoint(0) == x: + y = edge.GetPoint(-1) + else: + y = edge.GetPoint(0) + if y in closed_set: + continue + tentative_g_score = g_scores[x] + edge.LengthMeters() + tentative_is_better = False + if y not in open_set: + h_y = y.GetDistanceMeters(goal) + f_y = tentative_g_score + h_y + open_set.add(y) + heapq.heappush(open_heap, (f_y, y)) + tentative_is_better = True + elif tentative_g_score < g_scores[y]: + tentative_is_better = True + if tentative_is_better: + came_from[y] = (x, edge) + g_scores[y] = tentative_g_score + return None + + def _ReconstructPath(self, came_from, current_node): + """ + Helper method for ShortestPath, to reconstruct path. + + Arguments: + came_from: a dictionary mapping Point to (Point, Poly) tuples. + This dictionary keeps track of the previous neighbor to a node, and + the edge used to get from the previous neighbor to the node. + current_node: the current Point in the path. + + Returns: + A Poly that represents the path through the graph from the start of the + search to current_node. + """ + if current_node in came_from: + (previous_node, previous_edge) = came_from[current_node] + if previous_edge.GetPoint(0) == current_node: + previous_edge = previous_edge.Reversed() + p = self._ReconstructPath(came_from, previous_node) + return Poly.MergePolys([p, previous_edge], merge_point_threshold=0) + else: + return Poly([], '') + + def FindShortestMultiPointPath(self, points, max_radius=150, keep_best_n=10, + verbosity=0): + """ + Return a polyline, representing the shortest path through this graph that + has edge endpoints on each of a given list of points in sequence. We allow + fuzziness in matching of input points to points in this graph. + + We limit ourselves to a view of the best keep_best_n paths at any time, as a + greedy optimization. + """ + assert len(points) > 1 + nearby_points = [] + paths_found = [] # A heap sorted by inverse path length. + + for i, point in enumerate(points): + nearby = [p for p in self._nodes.iterkeys() + if p.GetDistanceMeters(point) < max_radius] + if verbosity >= 2: + print ("Nearby points for point %d %s: %s" + % (i + 1, + str(point.ToLatLng()), + ", ".join([str(n.ToLatLng()) for n in nearby]))) + if nearby: + nearby_points.append(nearby) + else: + print "No nearby points found for point %s" % str(point.ToLatLng()) + return None + + pathToStr = lambda start, end, path: (" Best path %s -> %s: %s" + % (str(start.ToLatLng()), + str(end.ToLatLng()), + path and path.GetName() or + "None")) + if verbosity >= 3: + print "Step 1" + step = 2 + + start_points = nearby_points[0] + end_points = nearby_points[1] + + for start in start_points: + for end in end_points: + path = self.ShortestPath(start, end) + if verbosity >= 3: + print pathToStr(start, end, path) + PolyGraph._AddPathToHeap(paths_found, path, keep_best_n) + + for possible_points in nearby_points[2:]: + if verbosity >= 3: + print "\nStep %d" % step + step += 1 + new_paths_found = [] + + start_end_paths = {} # cache of shortest paths between (start, end) pairs + for score, path in paths_found: + start = path.GetPoint(-1) + for end in possible_points: + if (start, end) in start_end_paths: + new_segment = start_end_paths[(start, end)] + else: + new_segment = self.ShortestPath(start, end) + if verbosity >= 3: + print pathToStr(start, end, new_segment) + start_end_paths[(start, end)] = new_segment + + if new_segment: + new_path = Poly.MergePolys([path, new_segment], + merge_point_threshold=0) + PolyGraph._AddPathToHeap(new_paths_found, new_path, keep_best_n) + paths_found = new_paths_found + + if paths_found: + best_score, best_path = max(paths_found) + return best_path + else: + return None + + @staticmethod + def _AddPathToHeap(heap, path, keep_best_n): + if path and path.GetNumPoints(): + new_item = (-path.LengthMeters(), path) + if new_item not in heap: + if len(heap) < keep_best_n: + heapq.heappush(heap, new_item) + else: + heapq.heapreplace(heap, new_item) + --- /dev/null +++ b/origin-src/transitfeed-1.2.5/build/lib/transitfeed/util.py @@ -1,1 +1,163 @@ +#!/usr/bin/python2.5 +# Copyright (C) 2009 Google Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + + +import optparse +import sys + + +class OptionParserLongError(optparse.OptionParser): + """OptionParser subclass that includes list of options above error message.""" + def error(self, msg): + print >>sys.stderr, self.format_help() + print >>sys.stderr, '\n\n%s: error: %s\n\n' % (self.get_prog_name(), msg) + sys.exit(2) + + +def RunWithCrashHandler(f): + try: + exit_code = f() + sys.exit(exit_code) + except (SystemExit, KeyboardInterrupt): + raise + except: + import inspect + import traceback + + # Save trace and exception now. These calls look at the most recently + # raised exception. The code that makes the report might trigger other + # exceptions. + original_trace = inspect.trace(3)[1:] + formatted_exception = traceback.format_exception_only(*(sys.exc_info()[:2])) + + apology = """Yikes, the program threw an unexpected exception! + +Hopefully a complete report has been saved to transitfeedcrash.txt, +though if you are seeing this message we've already disappointed you once +today. Please include the report in a new issue at +http://code.google.com/p/googletransitdatafeed/issues/entry +or an email to the public group googletransitdatafeed@googlegroups.com. Sorry! + +""" + dashes = '%s\n' % ('-' * 60) + dump = [] + dump.append(apology) + dump.append(dashes) + try: + import transitfeed + dump.append("transitfeed version %s\n\n" % transitfeed.__version__) + except NameError: + # Oh well, guess we won't put the version in the report + pass + + for (frame_obj, filename, line_num, fun_name, context_lines, + context_index) in original_trace: + dump.append('File "%s", line %d, in %s\n' % (filename, line_num, + fun_name)) + if context_lines: + for (i, line) in enumerate(context_lines): + if i == context_index: + dump.append(' --> %s' % line) + else: + dump.append(' %s' % line) + for local_name, local_val in frame_obj.f_locals.items(): + try: + truncated_val = str(local_val)[0:500] + except Exception, e: + dump.append(' Exception in str(%s): %s' % (local_name, e)) + else: + if len(truncated_val) >= 500: + truncated_val = '%s...' % truncated_val[0:499] + dump.append(' %s = %s\n' % (local_name, truncated_val)) + dump.append('\n') + + dump.append(''.join(formatted_exception)) + + open('transitfeedcrash.txt', 'w').write(''.join(dump)) + + print ''.join(dump) + print + print dashes + print apology + + try: + raw_input('Press enter to continue...') + except EOFError: + # Ignore stdin being closed. This happens during some tests. + pass + sys.exit(127) + + +# Pick one of two defaultdict implementations. A native version was added to +# the collections library in python 2.5. If that is not available use Jason's +# pure python recipe. He gave us permission to distribute it. + +# On Mon, Nov 30, 2009 at 07:27, jason kirtland wrote: +# > +# > Hi Tom, sure thing! It's not easy to find on the cookbook site, but the +# > recipe is under the Python license. +# > +# > Cheers, +# > Jason +# > +# > On Thu, Nov 26, 2009 at 3:03 PM, Tom Brown wrote: +# > +# >> I would like to include http://code.activestate.com/recipes/523034/ in +# >> http://code.google.com/p/googletransitdatafeed/wiki/TransitFeedDistribution +# >> which is distributed under the Apache License, Version 2.0 with Copyright +# >> Google. May we include your code with a comment in the source pointing at +# >> the original URL? Thanks, Tom Brown + +try: + # Try the native implementation first + from collections import defaultdict +except: + # Fallback for python2.4, which didn't include collections.defaultdict + class defaultdict(dict): + def __init__(self, default_factory=None, *a, **kw): + if (default_factory is not None and + not hasattr(default_factory, '__call__')): + raise TypeError('first argument must be callable') + dict.__init__(self, *a, **kw) + self.default_factory = default_factory + def __getitem__(self, key): + try: + return dict.__getitem__(self, key) + except KeyError: + return self.__missing__(key) + def __missing__(self, key): + if self.default_factory is None: + raise KeyError(key) + self[key] = value = self.default_factory() + return value + def __reduce__(self): + if self.default_factory is None: + args = tuple() + else: + args = self.default_factory, + return type(self), args, None, None, self.items() + def copy(self): + return self.__copy__() + def __copy__(self): + return type(self)(self.default_factory, self) + def __deepcopy__(self, memo): + import copy + return type(self)(self.default_factory, + copy.deepcopy(self.items())) + def __repr__(self): + return 'defaultdict(%s, %s)' % (self.default_factory, + dict.__repr__(self)) + --- /dev/null +++ b/origin-src/transitfeed-1.2.5/build/scripts-2.6/feedvalidator.py @@ -1,1 +1,723 @@ - +#!/usr/bin/python + +# Copyright (C) 2007 Google Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + + +"""Validates a GTFS file. + +For usage information run feedvalidator.py --help +""" + +import bisect +import codecs +import datetime +from transitfeed.util import defaultdict +import optparse +import os +import os.path +import re +import socket +import sys +import time +import transitfeed +from transitfeed import TYPE_ERROR, TYPE_WARNING +from urllib2 import Request, urlopen, HTTPError, URLError +from transitfeed import util +import webbrowser + +SVN_TAG_URL = 'http://googletransitdatafeed.googlecode.com/svn/tags/' + + +def MaybePluralizeWord(count, word): + if count == 1: + return word + else: + return word + 's' + + +def PrettyNumberWord(count, word): + return '%d %s' % (count, MaybePluralizeWord(count, word)) + + +def UnCamelCase(camel): + return re.sub(r'([a-z])([A-Z])', r'\1 \2', camel) + + +def ProblemCountText(error_count, warning_count): + results = [] + if error_count: + results.append(PrettyNumberWord(error_count, 'error')) + if warning_count: + results.append(PrettyNumberWord(warning_count, 'warning')) + + return ' and '.join(results) + + +def CalendarSummary(schedule): + today = datetime.date.today() + summary_end_date = today + datetime.timedelta(days=60) + start_date, end_date = schedule.GetDateRange() + + if not start_date or not end_date: + return {} + + try: + start_date_object = transitfeed.DateStringToDateObject(start_date) + end_date_object = transitfeed.DateStringToDateObject(end_date) + except ValueError: + return {} + + # Get the list of trips only during the period the feed is active. + # As such we have to check if it starts in the future and/or if + # if it ends in less than 60 days. + date_trips_departures = schedule.GenerateDateTripsDeparturesList( + max(today, start_date_object), + min(summary_end_date, end_date_object)) + + if not date_trips_departures: + return {} + + # Check that the dates which will be shown in summary agree with these + # calculations. Failure implies a bug which should be fixed. It isn't good + # for users to discover assertion failures but means it will likely be fixed. + assert start_date <= date_trips_departures[0][0].strftime("%Y%m%d") + assert end_date >= date_trips_departures[-1][0].strftime("%Y%m%d") + + # Generate a map from int number of trips in a day to a list of date objects + # with that many trips. The list of dates is sorted. + trips_dates = defaultdict(lambda: []) + trips = 0 + for date, day_trips, day_departures in date_trips_departures: + trips += day_trips + trips_dates[day_trips].append(date) + mean_trips = trips / len(date_trips_departures) + max_trips = max(trips_dates.keys()) + min_trips = min(trips_dates.keys()) + + calendar_summary = {} + calendar_summary['mean_trips'] = mean_trips + calendar_summary['max_trips'] = max_trips + calendar_summary['max_trips_dates'] = FormatDateList(trips_dates[max_trips]) + calendar_summary['min_trips'] = min_trips + calendar_summary['min_trips_dates'] = FormatDateList(trips_dates[min_trips]) + calendar_summary['date_trips_departures'] = date_trips_departures + calendar_summary['date_summary_range'] = "%s to %s" % ( + date_trips_departures[0][0].strftime("%a %b %d"), + date_trips_departures[-1][0].strftime("%a %b %d")) + + return calendar_summary + + +def FormatDateList(dates): + if not dates: + return "0 service dates" + + formatted = [d.strftime("%a %b %d") for d in dates[0:3]] + if len(dates) > 3: + formatted.append("...") + return "%s (%s)" % (PrettyNumberWord(len(dates), "service date"), + ", ".join(formatted)) + + +def MaxVersion(versions): + versions = filter(None, versions) + versions.sort(lambda x,y: -cmp([int(item) for item in x.split('.')], + [int(item) for item in y.split('.')])) + if len(versions) > 0: + return versions[0] + + +class CountingConsoleProblemReporter(transitfeed.ProblemReporter): + def __init__(self): + transitfeed.ProblemReporter.__init__(self) + self._error_count = 0 + self._warning_count = 0 + + def _Report(self, e): + transitfeed.ProblemReporter._Report(self, e) + if e.IsError(): + self._error_count += 1 + else: + self._warning_count += 1 + + def ErrorCount(self): + return self._error_count + + def WarningCount(self): + return self._warning_count + + def FormatCount(self): + return ProblemCountText(self.ErrorCount(), self.WarningCount()) + + def HasIssues(self): + return self.ErrorCount() or self.WarningCount() + + +class BoundedProblemList(object): + """A list of one type of ExceptionWithContext objects with bounded size.""" + def __init__(self, size_bound): + self._count = 0 + self._exceptions = [] + self._size_bound = size_bound + + def Add(self, e): + self._count += 1 + try: + bisect.insort(self._exceptions, e) + except TypeError: + # The base class ExceptionWithContext raises this exception in __cmp__ + # to signal that an object is not comparable. Instead of keeping the most + # significant issue keep the first reported. + if self._count <= self._size_bound: + self._exceptions.append(e) + else: + # self._exceptions is in order. Drop the least significant if the list is + # now too long. + if self._count > self._size_bound: + del self._exceptions[-1] + + def _GetDroppedCount(self): + return self._count - len(self._exceptions) + + def __repr__(self): + return "" % repr(self._exceptions) + + count = property(lambda s: s._count) + dropped_count = property(_GetDroppedCount) + problems = property(lambda s: s._exceptions) + + +class LimitPerTypeProblemReporter(transitfeed.ProblemReporter): + def __init__(self, limit_per_type): + transitfeed.ProblemReporter.__init__(self) + + # {TYPE_WARNING: {"ClassName": BoundedProblemList()}} + self._type_to_name_to_problist = { + TYPE_WARNING: defaultdict(lambda: BoundedProblemList(limit_per_type)), + TYPE_ERROR: defaultdict(lambda: BoundedProblemList(limit_per_type)) + } + + def HasIssues(self): + return (self._type_to_name_to_problist[TYPE_ERROR] or + self._type_to_name_to_problist[TYPE_WARNING]) + + def _Report(self, e): + self._type_to_name_to_problist[e.GetType()][e.__class__.__name__].Add(e) + + def ErrorCount(self): + error_sets = self._type_to_name_to_problist[TYPE_ERROR].values() + return sum(map(lambda v: v.count, error_sets)) + + def WarningCount(self): + warning_sets = self._type_to_name_to_problist[TYPE_WARNING].values() + return sum(map(lambda v: v.count, warning_sets)) + + def ProblemList(self, problem_type, class_name): + """Return the BoundedProblemList object for given type and class.""" + return self._type_to_name_to_problist[problem_type][class_name] + + def ProblemListMap(self, problem_type): + """Return the map from class name to BoundedProblemList object.""" + return self._type_to_name_to_problist[problem_type] + + +class HTMLCountingProblemReporter(LimitPerTypeProblemReporter): + def FormatType(self, f, level_name, class_problist): + """Write the HTML dumping all problems of one type. + + Args: + f: file object open for writing + level_name: string such as "Error" or "Warning" + class_problist: sequence of tuples (class name, + BoundedProblemList object) + """ + class_problist.sort() + output = [] + for classname, problist in class_problist: + output.append('

%s

    \n' % + (level_name, classname, UnCamelCase(classname))) + for e in problist.problems: + self.FormatException(e, output) + if problist.dropped_count: + output.append('
  • and %d more of this type.' % + (problist.dropped_count)) + output.append('
\n') + f.write(''.join(output)) + + def FormatTypeSummaryTable(self, level_name, name_to_problist): + """Return an HTML table listing the number of problems by class name. + + Args: + level_name: string such as "Error" or "Warning" + name_to_problist: dict mapping class name to an BoundedProblemList object + + Returns: + HTML in a string + """ + output = [] + output.append('') + for classname in sorted(name_to_problist.keys()): + problist = name_to_problist[classname] + human_name = MaybePluralizeWord(problist.count, UnCamelCase(classname)) + output.append('\n' % + (problist.count, level_name, classname, human_name)) + output.append('
%d%s
\n') + return ''.join(output) + + def FormatException(self, e, output): + """Append HTML version of e to list output.""" + d = e.GetDictToFormat() + for k in ('file_name', 'feedname', 'column_name'): + if k in d.keys(): + d[k] = '%s' % d[k] + problem_text = e.FormatProblem(d).replace('\n', '
') + output.append('
  • ') + output.append('
    %s
    ' % + transitfeed.EncodeUnicode(problem_text)) + try: + if hasattr(e, 'row_num'): + line_str = 'line %d of ' % e.row_num + else: + line_str = '' + output.append('in %s%s
    \n' % + (line_str, e.file_name)) + row = e.row + headers = e.headers + column_name = e.column_name + table_header = '' # HTML + table_data = '' # HTML + for header, value in zip(headers, row): + attributes = '' + if header == column_name: + attributes = ' class="problem"' + table_header += '%s' % (attributes, header) + table_data += '%s' % (attributes, value) + # Make sure output is encoded into UTF-8 + output.append('%s\n' % + transitfeed.EncodeUnicode(table_header)) + output.append('%s
    \n' % + transitfeed.EncodeUnicode(table_data)) + except AttributeError, e: + pass # Hope this was getting an attribute from e ;-) + output.append('
  • \n') + + def FormatCount(self): + return ProblemCountText(self.ErrorCount(), self.WarningCount()) + + def CountTable(self): + output = [] + output.append('\n') + output.append('') + if self.ProblemListMap(TYPE_ERROR): + output.append('' % + PrettyNumberWord(self.ErrorCount(), "error")) + if self.ProblemListMap(TYPE_WARNING): + output.append('' % + PrettyNumberWord(self.WarningCount(), "warning")) + output.append('\n') + if self.ProblemListMap(TYPE_ERROR): + output.append('\n') + if self.ProblemListMap(TYPE_WARNING): + output.append('\n') + output.append('
    %s%s
    \n') + output.append(self.FormatTypeSummaryTable("Error", + self.ProblemListMap(TYPE_ERROR))) + output.append('\n') + output.append(self.FormatTypeSummaryTable("Warning", + self.ProblemListMap(TYPE_WARNING))) + output.append('
    ') + return ''.join(output) + + def WriteOutput(self, feed_location, f, schedule, other_problems): + """Write the html output to f.""" + if self.HasIssues(): + if self.ErrorCount() + self.WarningCount() == 1: + summary = ('Found this problem:\n%s' % + self.CountTable()) + else: + summary = ('Found these problems:\n%s' % + self.CountTable()) + else: + summary = 'feed validated successfully' + if other_problems is not None: + summary = ('\n%s

    ' % + other_problems) + summary + + basename = os.path.basename(feed_location) + feed_path = (feed_location[:feed_location.rfind(basename)], basename) + + agencies = ', '.join(['%s' % (a.agency_url, a.agency_name) + for a in schedule.GetAgencyList()]) + if not agencies: + agencies = '?' + + dates = "No valid service dates found" + (start, end) = schedule.GetDateRange() + if start and end: + def FormatDate(yyyymmdd): + src_format = "%Y%m%d" + dst_format = "%B %d, %Y" + try: + return time.strftime(dst_format, + time.strptime(yyyymmdd, src_format)) + except ValueError: + return yyyymmdd + + formatted_start = FormatDate(start) + formatted_end = FormatDate(end) + dates = "%s to %s" % (formatted_start, formatted_end) + + calendar_summary = CalendarSummary(schedule) + if calendar_summary: + calendar_summary_html = """
    +During the upcoming service dates %(date_summary_range)s: + + + + +
    Average trips per date:%(mean_trips)s
    Most trips on a date:%(max_trips)s, on %(max_trips_dates)s
    Least trips on a date:%(min_trips)s, on %(min_trips_dates)s
    """ % calendar_summary + else: + calendar_summary_html = "" + + output_prefix = """ + + + +FeedValidator: %(feed_file)s + + + +GTFS validation results for feed:
    +%(feed_dir)s%(feed_file)s +

    + + + + + + + +
    Agencies:%(agencies)s
    Routes:%(routes)s
    Stops:%(stops)s
    Trips:%(trips)s
    Shapes:%(shapes)s
    Effective:%(dates)s
    +%(calendar_summary)s +
    +%(problem_summary)s +

    +""" % { "feed_file": feed_path[1], + "feed_dir": feed_path[0], + "agencies": agencies, + "routes": len(schedule.GetRouteList()), + "stops": len(schedule.GetStopList()), + "trips": len(schedule.GetTripList()), + "shapes": len(schedule.GetShapeList()), + "dates": dates, + "problem_summary": summary, + "calendar_summary": calendar_summary_html} + +# In output_suffix string +# time.strftime() returns a regular local time string (not a Unicode one) with +# default system encoding. And decode() will then convert this time string back +# into a Unicode string. We use decode() here because we don't want the operating +# system to do any system encoding (which may cause some problem if the string +# contains some non-English characters) for the string. Therefore we decode it +# back to its original Unicode code print. + + time_unicode = (time.strftime('%B %d, %Y at %I:%M %p %Z'). + decode(sys.getfilesystemencoding())) + output_suffix = """ + + +""" % (transitfeed.__version__, time_unicode) + + f.write(transitfeed.EncodeUnicode(output_prefix)) + if self.ProblemListMap(TYPE_ERROR): + f.write('

    Errors:

    ') + self.FormatType(f, "Error", + self.ProblemListMap(TYPE_ERROR).items()) + if self.ProblemListMap(TYPE_WARNING): + f.write('

    Warnings:

    ') + self.FormatType(f, "Warning", + self.ProblemListMap(TYPE_WARNING).items()) + f.write(transitfeed.EncodeUnicode(output_suffix)) + + +def RunValidationOutputFromOptions(feed, options): + """Validate feed, output results per options and return an exit code.""" + if options.output.upper() == "CONSOLE": + return RunValidationOutputToConsole(feed, options) + else: + return RunValidationOutputToFilename(feed, options, options.output) + + +def RunValidationOutputToFilename(feed, options, output_filename): + """Validate feed, save HTML at output_filename and return an exit code.""" + try: + output_file = open(output_filename, 'w') + exit_code = RunValidationOutputToFile(feed, options, output_file) + output_file.close() + except IOError, e: + print 'Error while writing %s: %s' % (output_filename, e) + output_filename = None + exit_code = 2 + + if options.manual_entry and output_filename: + webbrowser.open('file://%s' % os.path.abspath(output_filename)) + + return exit_code + + +def RunValidationOutputToFile(feed, options, output_file): + """Validate feed, write HTML to output_file and return an exit code.""" + problems = HTMLCountingProblemReporter(options.limit_per_type) + schedule, exit_code, other_problems_string = RunValidation(feed, options, + problems) + if isinstance(feed, basestring): + feed_location = feed + else: + feed_location = getattr(feed, 'name', repr(feed)) + problems.WriteOutput(feed_location, output_file, schedule, + other_problems_string) + return exit_code + + +def RunValidationOutputToConsole(feed, options): + """Validate feed, print reports and return an exit code.""" + problems = CountingConsoleProblemReporter() + _, exit_code, _ = RunValidation(feed, options, problems) + return exit_code + + +def RunValidation(feed, options, problems): + """Validate feed, returning the loaded Schedule and exit code. + + Args: + feed: GTFS file, either path of the file as a string or a file object + options: options object returned by optparse + problems: transitfeed.ProblemReporter instance + + Returns: + a transitfeed.Schedule object, exit code and plain text string of other + problems + Exit code is 1 if problems are found and 0 if the Schedule is problem free. + plain text string is '' if no other problems are found. + """ + other_problems_string = CheckVersion(latest_version=options.latest_version) + print 'validating %s' % feed + loader = transitfeed.Loader(feed, problems=problems, extra_validation=False, + memory_db=options.memory_db, + check_duplicate_trips=\ + options.check_duplicate_trips) + schedule = loader.Load() + schedule.Validate(service_gap_interval=options.service_gap_interval) + + if feed == 'IWantMyvalidation-crash.txt': + # See test/testfeedvalidator.py + raise Exception('For testing the feed validator crash handler.') + + if other_problems_string: + print other_problems_string + + if problems.HasIssues(): + print 'ERROR: %s found' % problems.FormatCount() + return schedule, 1, other_problems_string + else: + print 'feed validated successfully' + return schedule, 0, other_problems_string + + +def CheckVersion(latest_version=''): + """ + Check there is newer version of this project. + + Codes are based on http://www.voidspace.org.uk/python/articles/urllib2.shtml + Already got permission from the copyright holder. + """ + current_version = transitfeed.__version__ + if not latest_version: + timeout = 20 + socket.setdefaulttimeout(timeout) + request = Request(SVN_TAG_URL) + + try: + response = urlopen(request) + content = response.read() + versions = re.findall(r'>transitfeed-([\d\.]+)\/<\/a>', content) + latest_version = MaxVersion(versions) + + except HTTPError, e: + return('The server couldn\'t fulfill the request. Error code: %s.' + % e.code) + except URLError, e: + return('We failed to reach transitfeed server. Reason: %s.' % e.reason) + + if not latest_version: + return('We had trouble parsing the contents of %s.' % SVN_TAG_URL) + + newest_version = MaxVersion([latest_version, current_version]) + if current_version != newest_version: + return('A new version %s of transitfeed is available. Please visit ' + 'http://code.google.com/p/googletransitdatafeed and download.' + % newest_version) + + +def main(): + usage = \ +'''%prog [options] [] + +Validates GTFS file (or directory) and writes a HTML +report of the results to validation-results.html. + +If is ommited the filename is read from the console. Dragging +a file into the console may enter the filename. + +For more information see +http://code.google.com/p/googletransitdatafeed/wiki/FeedValidator +''' + + parser = util.OptionParserLongError( + usage=usage, version='%prog '+transitfeed.__version__) + parser.add_option('-n', '--noprompt', action='store_false', + dest='manual_entry', + help='do not prompt for feed location or load output in ' + 'browser') + parser.add_option('-o', '--output', dest='output', metavar='FILE', + help='write html output to FILE or --output=CONSOLE to ' + 'print all errors and warnings to the command console') + parser.add_option('-p', '--performance', action='store_true', + dest='performance', + help='output memory and time performance (Availability: ' + 'Unix') + parser.add_option('-m', '--memory_db', dest='memory_db', action='store_true', + help='Use in-memory sqlite db instead of a temporary file. ' + 'It is faster but uses more RAM.') + parser.add_option('-d', '--duplicate_trip_check', + dest='check_duplicate_trips', action='store_true', + help='Check for duplicate trips which go through the same ' + 'stops with same service and start times') + parser.add_option('-l', '--limit_per_type', + dest='limit_per_type', action='store', type='int', + help='Maximum number of errors and warnings to keep of ' + 'each type') + parser.add_option('--latest_version', dest='latest_version', + action='store', + help='a version number such as 1.2.1 or None to get the ' + 'latest version from code.google.com. Output a warning if ' + 'transitfeed.py is older than this version.') + parser.add_option('--service_gap_interval', + dest='service_gap_interval', + action='store', + type='int', + help='the number of consecutive days to search for with no ' + 'scheduled service. For each interval with no service ' + 'having this number of days or more a warning will be ' + 'issued') + + parser.set_defaults(manual_entry=True, output='validation-results.html', + memory_db=False, check_duplicate_trips=False, + limit_per_type=5, latest_version='', + service_gap_interval=13) + (options, args) = parser.parse_args() + + if not len(args) == 1: + if options.manual_entry: + feed = raw_input('Enter Feed Location: ') + else: + parser.error('You must provide the path of a single feed') + else: + feed = args[0] + + feed = feed.strip('"') + + if options.performance: + return ProfileRunValidationOutputFromOptions(feed, options) + else: + return RunValidationOutputFromOptions(feed, options) + + +def ProfileRunValidationOutputFromOptions(feed, options): + """Run RunValidationOutputFromOptions, print profile and return exit code.""" + import cProfile + import pstats + # runctx will modify a dict, but not locals(). We need a way to get rv back. + locals_for_exec = locals() + cProfile.runctx('rv = RunValidationOutputFromOptions(feed, options)', + globals(), locals_for_exec, 'validate-stats') + + # Only available on Unix, http://docs.python.org/lib/module-resource.html + import resource + print "Time: %d seconds" % ( + resource.getrusage(resource.RUSAGE_SELF).ru_utime + + resource.getrusage(resource.RUSAGE_SELF).ru_stime) + + # http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/286222 + # http://aspn.activestate.com/ASPN/Cookbook/ "The recipes are freely + # available for review and use." + def _VmB(VmKey): + """Return size from proc status in bytes.""" + _proc_status = '/proc/%d/status' % os.getpid() + _scale = {'kB': 1024.0, 'mB': 1024.0*1024.0, + 'KB': 1024.0, 'MB': 1024.0*1024.0} + + # get pseudo file /proc//status + try: + t = open(_proc_status) + v = t.read() + t.close() + except: + raise Exception("no proc file %s" % _proc_status) + return 0 # non-Linux? + # get VmKey line e.g. 'VmRSS: 9999 kB\n ...' + i = v.index(VmKey) + v = v[i:].split(None, 3) # whitespace + if len(v) < 3: + raise Exception("%s" % v) + return 0 # invalid format? + # convert Vm value to bytes + return int(float(v[1]) * _scale[v[2]]) + + # I ran this on over a hundred GTFS files, comparing VmSize to VmRSS + # (resident set size). The difference was always under 2% or 3MB. + print "Virtual Memory Size: %d bytes" % _VmB('VmSize:') + + # Output report of where CPU time was spent. + p = pstats.Stats('validate-stats') + p.strip_dirs() + p.sort_stats('cumulative').print_stats(30) + p.sort_stats('cumulative').print_callers(30) + return locals_for_exec['rv'] + + +if __name__ == '__main__': + util.RunWithCrashHandler(main) + --- /dev/null +++ b/origin-src/transitfeed-1.2.5/build/scripts-2.6/kmlparser.py @@ -1,1 +1,147 @@ +#!/usr/bin/python +# Copyright (C) 2007 Google Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +""" +This package provides implementation of a converter from a kml +file format into Google transit feed format. + +The KmlParser class is the main class implementing the parser. + +Currently only information about stops is extracted from a kml file. +The extractor expects the stops to be represented as placemarks with +a single point. +""" + +import re +import string +import sys +import transitfeed +from transitfeed import util +import xml.dom.minidom as minidom +import zipfile + + +class Placemark(object): + def __init__(self): + self.name = "" + self.coordinates = [] + + def IsPoint(self): + return len(self.coordinates) == 1 + + def IsLine(self): + return len(self.coordinates) > 1 + +class KmlParser(object): + def __init__(self, stopNameRe = '(.*)'): + """ + Args: + stopNameRe - a regular expression to extract a stop name from a + placemaker name + """ + self.stopNameRe = re.compile(stopNameRe) + + def Parse(self, filename, feed): + """ + Reads the kml file, parses it and updated the Google transit feed + object with the extracted information. + + Args: + filename - kml file name + feed - an instance of Schedule class to be updated + """ + dom = minidom.parse(filename) + self.ParseDom(dom, feed) + + def ParseDom(self, dom, feed): + """ + Parses the given kml dom tree and updates the Google transit feed object. + + Args: + dom - kml dom tree + feed - an instance of Schedule class to be updated + """ + shape_num = 0 + for node in dom.getElementsByTagName('Placemark'): + p = self.ParsePlacemark(node) + if p.IsPoint(): + (lon, lat) = p.coordinates[0] + m = self.stopNameRe.search(p.name) + feed.AddStop(lat, lon, m.group(1)) + elif p.IsLine(): + shape_num = shape_num + 1 + shape = transitfeed.Shape("kml_shape_" + str(shape_num)) + for (lon, lat) in p.coordinates: + shape.AddPoint(lat, lon) + feed.AddShapeObject(shape) + + def ParsePlacemark(self, node): + ret = Placemark() + for child in node.childNodes: + if child.nodeName == 'name': + ret.name = self.ExtractText(child) + if child.nodeName == 'Point' or child.nodeName == 'LineString': + ret.coordinates = self.ExtractCoordinates(child) + return ret + + def ExtractText(self, node): + for child in node.childNodes: + if child.nodeType == child.TEXT_NODE: + return child.wholeText # is a unicode string + return "" + + def ExtractCoordinates(self, node): + coordinatesText = "" + for child in node.childNodes: + if child.nodeName == 'coordinates': + coordinatesText = self.ExtractText(child) + break + ret = [] + for point in coordinatesText.split(): + coords = point.split(',') + ret.append((float(coords[0]), float(coords[1]))) + return ret + + +def main(): + usage = \ +"""%prog + +Reads KML file and creates GTFS file with +placemarks in the KML represented as stops. +""" + + parser = util.OptionParserLongError( + usage=usage, version='%prog '+transitfeed.__version__) + (options, args) = parser.parse_args() + if len(args) != 2: + parser.error('You did not provide all required command line arguments.') + + if args[0] == 'IWantMyCrash': + raise Exception('For testCrashHandler') + + parser = KmlParser() + feed = transitfeed.Schedule() + feed.save_all_stops = True + parser.Parse(args[0], feed) + feed.WriteGoogleTransitFeed(args[1]) + + print "Done." + + +if __name__ == '__main__': + util.RunWithCrashHandler(main) + --- /dev/null +++ b/origin-src/transitfeed-1.2.5/build/scripts-2.6/kmlwriter.py @@ -1,1 +1,648 @@ - +#!/usr/bin/python +# +# Copyright 2008 Google Inc. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +"""A module for writing GTFS feeds out into Google Earth KML format. + +For usage information run kmlwriter.py --help + +If no output filename is specified, the output file will be given the same +name as the feed file (with ".kml" appended) and will be placed in the same +directory as the input feed. + +The resulting KML file has a folder hierarchy which looks like this: + + - Stops + * stop1 + * stop2 + - Routes + - route1 + - Shapes + * shape1 + * shape2 + - Patterns + - pattern1 + - pattern2 + - Trips + * trip1 + * trip2 + - Shapes + * shape1 + - Shape Points + * shape_point1 + * shape_point2 + * shape2 + - Shape Points + * shape_point1 + * shape_point2 + +where the hyphens represent folders and the asteriks represent placemarks. + +In a trip, a vehicle visits stops in a certain sequence. Such a sequence of +stops is called a pattern. A pattern is represented by a linestring connecting +the stops. The "Shapes" subfolder of a route folder contains placemarks for +each shape used by a trip in the route. The "Patterns" subfolder contains a +placemark for each unique pattern used by a trip in the route. The "Trips" +subfolder contains a placemark for each trip in the route. + +Since there can be many trips and trips for the same route are usually similar, +they are not exported unless the --showtrips option is used. There is also +another option --splitroutes that groups the routes by vehicle type resulting +in a folder hierarchy which looks like this at the top level: + + - Stops + - Routes - Bus + - Routes - Tram + - Routes - Rail + - Shapes +""" + +try: + import xml.etree.ElementTree as ET # python 2.5 +except ImportError, e: + import elementtree.ElementTree as ET # older pythons +import optparse +import os.path +import sys +import transitfeed +from transitfeed import util + + +class KMLWriter(object): + """This class knows how to write out a transit feed as KML. + + Sample usage: + KMLWriter().Write(, ) + + Attributes: + show_trips: True if the individual trips should be included in the routes. + show_trips: True if the individual trips should be placed on ground. + split_routes: True if the routes should be split by type. + shape_points: True if individual shape points should be plotted. + """ + + def __init__(self): + """Initialise.""" + self.show_trips = False + self.split_routes = False + self.shape_points = False + self.altitude_per_sec = 0.0 + self.date_filter = None + + def _SetIndentation(self, elem, level=0): + """Indented the ElementTree DOM. + + This is the recommended way to cause an ElementTree DOM to be + prettyprinted on output, as per: http://effbot.org/zone/element-lib.htm + + Run this on the root element before outputting the tree. + + Args: + elem: The element to start indenting from, usually the document root. + level: Current indentation level for recursion. + """ + i = "\n" + level*" " + if len(elem): + if not elem.text or not elem.text.strip(): + elem.text = i + " " + for elem in elem: + self._SetIndentation(elem, level+1) + if not elem.tail or not elem.tail.strip(): + elem.tail = i + else: + if level and (not elem.tail or not elem.tail.strip()): + elem.tail = i + + def _CreateFolder(self, parent, name, visible=True, description=None): + """Create a KML Folder element. + + Args: + parent: The parent ElementTree.Element instance. + name: The folder name as a string. + visible: Whether the folder is initially visible or not. + description: A description string or None. + + Returns: + The folder ElementTree.Element instance. + """ + folder = ET.SubElement(parent, 'Folder') + name_tag = ET.SubElement(folder, 'name') + name_tag.text = name + if description is not None: + desc_tag = ET.SubElement(folder, 'description') + desc_tag.text = description + if not visible: + visibility = ET.SubElement(folder, 'visibility') + visibility.text = '0' + return folder + + def _CreateStyleForRoute(self, doc, route): + """Create a KML Style element for the route. + + The style sets the line colour if the route colour is specified. The + line thickness is set depending on the vehicle type. + + Args: + doc: The KML Document ElementTree.Element instance. + route: The transitfeed.Route to create the style for. + + Returns: + The id of the style as a string. + """ + style_id = 'route_%s' % route.route_id + style = ET.SubElement(doc, 'Style', {'id': style_id}) + linestyle = ET.SubElement(style, 'LineStyle') + width = ET.SubElement(linestyle, 'width') + type_to_width = {0: '3', # Tram + 1: '3', # Subway + 2: '5', # Rail + 3: '1'} # Bus + width.text = type_to_width.get(route.route_type, '1') + if route.route_color: + color = ET.SubElement(linestyle, 'color') + red = route.route_color[0:2].lower() + green = route.route_color[2:4].lower() + blue = route.route_color[4:6].lower() + color.text = 'ff%s%s%s' % (blue, green, red) + return style_id + + def _CreatePlacemark(self, parent, name, style_id=None, visible=True, + description=None): + """Create a KML Placemark element. + + Args: + parent: The parent ElementTree.Element instance. + name: The placemark name as a string. + style_id: If not None, the id of a style to use for the placemark. + visible: Whether the placemark is initially visible or not. + description: A description string or None. + + Returns: + The placemark ElementTree.Element instance. + """ + placemark = ET.SubElement(parent, 'Placemark') + placemark_name = ET.SubElement(placemark, 'name') + placemark_name.text = name + if description is not None: + desc_tag = ET.SubElement(placemark, 'description') + desc_tag.text = description + if style_id is not None: + styleurl = ET.SubElement(placemark, 'styleUrl') + styleurl.text = '#%s' % style_id + if not visible: + visibility = ET.SubElement(placemark, 'visibility') + visibility.text = '0' + return placemark + + def _CreateLineString(self, parent, coordinate_list): + """Create a KML LineString element. + + The points of the string are given in coordinate_list. Every element of + coordinate_list should be one of a tuple (longitude, latitude) or a tuple + (longitude, latitude, altitude). + + Args: + parent: The parent ElementTree.Element instance. + coordinate_list: The list of coordinates. + + Returns: + The LineString ElementTree.Element instance or None if coordinate_list is + empty. + """ + if not coordinate_list: + return None + linestring = ET.SubElement(parent, 'LineString') + tessellate = ET.SubElement(linestring, 'tessellate') + tessellate.text = '1' + if len(coordinate_list[0]) == 3: + altitude_mode = ET.SubElement(linestring, 'altitudeMode') + altitude_mode.text = 'absolute' + coordinates = ET.SubElement(linestring, 'coordinates') + if len(coordinate_list[0]) == 3: + coordinate_str_list = ['%f,%f,%f' % t for t in coordinate_list] + else: + coordinate_str_list = ['%f,%f' % t for t in coordinate_list] + coordinates.text = ' '.join(coordinate_str_list) + return linestring + + def _CreateLineStringForShape(self, parent, shape): + """Create a KML LineString using coordinates from a shape. + + Args: + parent: The parent ElementTree.Element instance. + shape: The transitfeed.Shape instance. + + Returns: + The LineString ElementTree.Element instance or None if coordinate_list is + empty. + """ + coordinate_list = [(longitude, latitude) for + (latitude, longitude, distance) in shape.points] + return self._CreateLineString(parent, coordinate_list) + + def _CreateStopsFolder(self, schedule, doc): + """Create a KML Folder containing placemarks for each stop in the schedule. + + If there are no stops in the schedule then no folder is created. + + Args: + schedule: The transitfeed.Schedule instance. + doc: The KML Document ElementTree.Element instance. + + Returns: + The Folder ElementTree.Element instance or None if there are no stops. + """ + if not schedule.GetStopList(): + return None + stop_folder = self._CreateFolder(doc, 'Stops') + stops = list(schedule.GetStopList()) + stops.sort(key=lambda x: x.stop_name) + for stop in stops: + desc_items = [] + if stop.stop_desc: + desc_items.append(stop.stop_desc) + if stop.stop_url: + desc_items.append('Stop info page: %s' % ( + stop.stop_url, stop.stop_url)) + description = '
    '.join(desc_items) or None + placemark = self._CreatePlacemark(stop_folder, stop.stop_name, + description=description) + point = ET.SubElement(placemark, 'Point') + coordinates = ET.SubElement(point, 'coordinates') + coordinates.text = '%.6f,%.6f' % (stop.stop_lon, stop.stop_lat) + return stop_folder + + def _CreateRoutePatternsFolder(self, parent, route, + style_id=None, visible=True): + """Create a KML Folder containing placemarks for each pattern in the route. + + A pattern is a sequence of stops used by one of the trips in the route. + + If there are not patterns for the route then no folder is created and None + is returned. + + Args: + parent: The parent ElementTree.Element instance. + route: The transitfeed.Route instance. + style_id: The id of a style to use if not None. + visible: Whether the folder is initially visible or not. + + Returns: + The Folder ElementTree.Element instance or None if there are no patterns. + """ + pattern_id_to_trips = route.GetPatternIdTripDict() + if not pattern_id_to_trips: + return None + + # sort by number of trips using the pattern + pattern_trips = pattern_id_to_trips.values() + pattern_trips.sort(lambda a, b: cmp(len(b), len(a))) + + folder = self._CreateFolder(parent, 'Patterns', visible) + for n, trips in enumerate(pattern_trips): + trip_ids = [trip.trip_id for trip in trips] + name = 'Pattern %d (trips: %d)' % (n+1, len(trips)) + description = 'Trips using this pattern (%d in total): %s' % ( + len(trips), ', '.join(trip_ids)) + placemark = self._CreatePlacemark(folder, name, style_id, visible, + description) + coordinates = [(stop.stop_lon, stop.stop_lat) + for stop in trips[0].GetPattern()] + self._CreateLineString(placemark, coordinates) + return folder + + def _CreateRouteShapesFolder(self, schedule, parent, route, + style_id=None, visible=True): + """Create a KML Folder for the shapes of a route. + + The folder contains a placemark for each shape referenced by a trip in the + route. If there are no such shapes, no folder is created and None is + returned. + + Args: + schedule: The transitfeed.Schedule instance. + parent: The parent ElementTree.Element instance. + route: The transitfeed.Route instance. + style_id: The id of a style to use if not None. + visible: Whether the placemark is initially visible or not. + + Returns: + The Folder ElementTree.Element instance or None. + """ + shape_id_to_trips = {} + for trip in route.trips: + if trip.shape_id: + shape_id_to_trips.setdefault(trip.shape_id, []).append(trip) + if not shape_id_to_trips: + return None + + # sort by the number of trips using the shape + shape_id_to_trips_items = shape_id_to_trips.items() + shape_id_to_trips_items.sort(lambda a, b: cmp(len(b[1]), len(a[1]))) + + folder = self._CreateFolder(parent, 'Shapes', visible) + for shape_id, trips in shape_id_to_trips_items: + trip_ids = [trip.trip_id for trip in trips] + name = '%s (trips: %d)' % (shape_id, len(trips)) + description = 'Trips using this shape (%d in total): %s' % ( + len(trips), ', '.join(trip_ids)) + placemark = self._CreatePlacemark(folder, name, style_id, visible, + description) + self._CreateLineStringForShape(placemark, schedule.GetShape(shape_id)) + return folder + + def _CreateRouteTripsFolder(self, parent, route, style_id=None, schedule=None): + """Create a KML Folder containing all the trips in the route. + + The folder contains a placemark for each of these trips. If there are no + trips in the route, no folder is created and None is returned. + + Args: + parent: The parent ElementTree.Element instance. + route: The transitfeed.Route instance. + style_id: A style id string for the placemarks or None. + + Returns: + The Folder ElementTree.Element instance or None. + """ + if not route.trips: + return None + trips = list(route.trips) + trips.sort(key=lambda x: x.trip_id) + trips_folder = self._CreateFolder(parent, 'Trips', visible=False) + for trip in trips: + if (self.date_filter and + not trip.service_period.IsActiveOn(self.date_filter)): + continue + + if trip.trip_headsign: + description = 'Headsign: %s' % trip.trip_headsign + else: + description = None + + coordinate_list = [] + for secs, stoptime, tp in trip.GetTimeInterpolatedStops(): + if self.altitude_per_sec > 0: + coordinate_list.append((stoptime.stop.stop_lon, stoptime.stop.stop_lat, + (secs - 3600 * 4) * self.altitude_per_sec)) + else: + coordinate_list.append((stoptime.stop.stop_lon, + stoptime.stop.stop_lat)) + placemark = self._CreatePlacemark(trips_folder, + trip.trip_id, + style_id=style_id, + visible=False, + description=description) + self._CreateLineString(placemark, coordinate_list) + return trips_folder + + def _CreateRoutesFolder(self, schedule, doc, route_type=None): + """Create a KML Folder containing routes in a schedule. + + The folder contains a subfolder for each route in the schedule of type + route_type. If route_type is None, then all routes are selected. Each + subfolder contains a flattened graph placemark, a route shapes placemark + and, if show_trips is True, a subfolder containing placemarks for each of + the trips in the route. + + If there are no routes in the schedule then no folder is created and None + is returned. + + Args: + schedule: The transitfeed.Schedule instance. + doc: The KML Document ElementTree.Element instance. + route_type: The route type integer or None. + + Returns: + The Folder ElementTree.Element instance or None. + """ + + def GetRouteName(route): + """Return a placemark name for the route. + + Args: + route: The transitfeed.Route instance. + + Returns: + The name as a string. + """ + name_parts = [] + if route.route_short_name: + name_parts.append('%s' % route.route_short_name) + if route.route_long_name: + name_parts.append(route.route_long_name) + return ' - '.join(name_parts) or route.route_id + + def GetRouteDescription(route): + """Return a placemark description for the route. + + Args: + route: The transitfeed.Route instance. + + Returns: + The description as a string. + """ + desc_items = [] + if route.route_desc: + desc_items.append(route.route_desc) + if route.route_url: + desc_items.append('Route info page: %s' % ( + route.route_url, route.route_url)) + description = '
    '.join(desc_items) + return description or None + + routes = [route for route in schedule.GetRouteList() + if route_type is None or route.route_type == route_type] + if not routes: + return None + routes.sort(key=lambda x: GetRouteName(x)) + + if route_type is not None: + route_type_names = {0: 'Tram, Streetcar or Light rail', + 1: 'Subway or Metro', + 2: 'Rail', + 3: 'Bus', + 4: 'Ferry', + 5: 'Cable car', + 6: 'Gondola or suspended cable car', + 7: 'Funicular'} + type_name = route_type_names.get(route_type, str(route_type)) + folder_name = 'Routes - %s' % type_name + else: + folder_name = 'Routes' + routes_folder = self._CreateFolder(doc, folder_name, visible=False) + + for route in routes: + style_id = self._CreateStyleForRoute(doc, route) + route_folder = self._CreateFolder(routes_folder, + GetRouteName(route), + description=GetRouteDescription(route)) + self._CreateRouteShapesFolder(schedule, route_folder, route, + style_id, False) + self._CreateRoutePatternsFolder(route_folder, route, style_id, False) + if self.show_trips: + self._CreateRouteTripsFolder(route_folder, route, style_id, schedule) + return routes_folder + + def _CreateShapesFolder(self, schedule, doc): + """Create a KML Folder containing all the shapes in a schedule. + + The folder contains a placemark for each shape. If there are no shapes in + the schedule then the folder is not created and None is returned. + + Args: + schedule: The transitfeed.Schedule instance. + doc: The KML Document ElementTree.Element instance. + + Returns: + The Folder ElementTree.Element instance or None. + """ + if not schedule.GetShapeList(): + return None + shapes_folder = self._CreateFolder(doc, 'Shapes') + shapes = list(schedule.GetShapeList()) + shapes.sort(key=lambda x: x.shape_id) + for shape in shapes: + placemark = self._CreatePlacemark(shapes_folder, shape.shape_id) + self._CreateLineStringForShape(placemark, shape) + if self.shape_points: + self._CreateShapePointFolder(shapes_folder, shape) + return shapes_folder + + def _CreateShapePointFolder(self, shapes_folder, shape): + """Create a KML Folder containing all the shape points in a shape. + + The folder contains placemarks for each shapepoint. + + Args: + shapes_folder: A KML Shape Folder ElementTree.Element instance + shape: The shape to plot. + + Returns: + The Folder ElementTree.Element instance or None. + """ + + folder_name = shape.shape_id + ' Shape Points' + folder = self._CreateFolder(shapes_folder, folder_name, visible=False) + for (index, (lat, lon, dist)) in enumerate(shape.points): + placemark = self._CreatePlacemark(folder, str(index+1)) + point = ET.SubElement(placemark, 'Point') + coordinates = ET.SubElement(point, 'coordinates') + coordinates.text = '%.6f,%.6f' % (lon, lat) + return folder + + def Write(self, schedule, output_file): + """Writes out a feed as KML. + + Args: + schedule: A transitfeed.Schedule object containing the feed to write. + output_file: The name of the output KML file, or file object to use. + """ + # Generate the DOM to write + root = ET.Element('kml') + root.attrib['xmlns'] = 'http://earth.google.com/kml/2.1' + doc = ET.SubElement(root, 'Document') + open_tag = ET.SubElement(doc, 'open') + open_tag.text = '1' + self._CreateStopsFolder(schedule, doc) + if self.split_routes: + route_types = set() + for route in schedule.GetRouteList(): + route_types.add(route.route_type) + route_types = list(route_types) + route_types.sort() + for route_type in route_types: + self._CreateRoutesFolder(schedule, doc, route_type) + else: + self._CreateRoutesFolder(schedule, doc) + self._CreateShapesFolder(schedule, doc) + + # Make sure we pretty-print + self._SetIndentation(root) + + # Now write the output + if isinstance(output_file, file): + output = output_file + else: + output = open(output_file, 'w') + output.write("""\n""") + ET.ElementTree(root).write(output, 'utf-8') + + +def main(): + usage = \ +'''%prog [options] [] + +Reads GTFS file or directory and creates a KML file + that contains the geographical features of the input. If + is omitted a default filename is picked based on +. By default the KML contains all stops and shapes. +''' + + parser = util.OptionParserLongError( + usage=usage, version='%prog '+transitfeed.__version__) + parser.add_option('-t', '--showtrips', action='store_true', + dest='show_trips', + help='include the individual trips for each route') + parser.add_option('-a', '--altitude_per_sec', action='store', type='float', + dest='altitude_per_sec', + help='if greater than 0 trips are drawn with time axis ' + 'set to this many meters high for each second of time') + parser.add_option('-s', '--splitroutes', action='store_true', + dest='split_routes', + help='split the routes by type') + parser.add_option('-d', '--date_filter', action='store', type='string', + dest='date_filter', + help='Restrict to trips active on date YYYYMMDD') + parser.add_option('-p', '--display_shape_points', action='store_true', + dest='shape_points', + help='shows the actual points along shapes') + + parser.set_defaults(altitude_per_sec=1.0) + options, args = parser.parse_args() + + if len(args) < 1: + parser.error('You must provide the path of an input GTFS file.') + + if args[0] == 'IWantMyCrash': + raise Exception('For testCrashHandler') + + input_path = args[0] + if len(args) >= 2: + output_path = args[1] + else: + path = os.path.normpath(input_path) + (feed_dir, feed) = os.path.split(path) + if '.' in feed: + feed = feed.rsplit('.', 1)[0] # strip extension + output_filename = '%s.kml' % feed + output_path = os.path.join(feed_dir, output_filename) + + loader = transitfeed.Loader(input_path, + problems=transitfeed.ProblemReporter()) + feed = loader.Load() + print "Writing %s" % output_path + writer = KMLWriter() + writer.show_trips = options.show_trips + writer.altitude_per_sec = options.altitude_per_sec + writer.split_routes = options.split_routes + writer.date_filter = options.date_filter + writer.shape_points = options.shape_points + writer.Write(feed, output_path) + + +if __name__ == '__main__': + util.RunWithCrashHandler(main) + --- /dev/null +++ b/origin-src/transitfeed-1.2.5/build/scripts-2.6/merge.py @@ -1,1 +1,1766 @@ - +#!/usr/bin/python +# +# Copyright 2007 Google Inc. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +"""A tool for merging two Google Transit feeds. + +Given two Google Transit feeds intending to cover two disjoint calendar +intervals, this tool will attempt to produce a single feed by merging as much +of the two feeds together as possible. + +For example, most stops remain the same throughout the year. Therefore, many +of the stops given in stops.txt for the first feed represent the same stops +given in the second feed. This tool will try to merge these stops so they +only appear once in the resultant feed. + +A note on terminology: The first schedule is referred to as the "old" schedule; +the second as the "new" schedule. The resultant schedule is referred to as +the "merged" schedule. Names of things in the old schedule are variations of +the letter "a" while names of things from the new schedule are variations of +"b". The objects that represents routes, agencies and so on are called +"entities". + +usage: merge.py [options] old_feed_path new_feed_path merged_feed_path + +Run merge.py --help for a list of the possible options. +""" + + +__author__ = 'timothy.stranex@gmail.com (Timothy Stranex)' + + +import datetime +import optparse +import os +import re +import sys +import time +import transitfeed +from transitfeed import util +import webbrowser + + +# TODO: +# 1. write unit tests that use actual data +# 2. write a proper trip and stop_times merger +# 3. add a serialised access method for stop_times and shapes to transitfeed +# 4. add support for merging schedules which have some service period overlap + + +def ApproximateDistanceBetweenPoints(pa, pb): + """Finds the distance between two points on the Earth's surface. + + This is an approximate distance based on assuming that the Earth is a sphere. + The points are specified by their lattitude and longitude. + + Args: + pa: the first (lat, lon) point tuple + pb: the second (lat, lon) point tuple + + Returns: + The distance as a float in metres. + """ + alat, alon = pa + blat, blon = pb + sa = transitfeed.Stop(lat=alat, lng=alon) + sb = transitfeed.Stop(lat=blat, lng=blon) + return transitfeed.ApproximateDistanceBetweenStops(sa, sb) + + +class Error(Exception): + """The base exception class for this module.""" + + +class MergeError(Error): + """An error produced when two entities could not be merged.""" + + +class MergeProblemWithContext(transitfeed.ExceptionWithContext): + """The base exception class for problem reporting in the merge module. + + Attributes: + dataset_merger: The DataSetMerger that generated this problem. + entity_type_name: The entity type of the dataset_merger. This is just + dataset_merger.ENTITY_TYPE_NAME. + ERROR_TEXT: The text used for generating the problem message. + """ + + def __init__(self, dataset_merger, problem_type=transitfeed.TYPE_WARNING, + **kwargs): + """Initialise the exception object. + + Args: + dataset_merger: The DataSetMerger instance that generated this problem. + problem_type: The problem severity. This should be set to one of the + corresponding constants in transitfeed. + kwargs: Keyword arguments to be saved as instance attributes. + """ + kwargs['type'] = problem_type + kwargs['entity_type_name'] = dataset_merger.ENTITY_TYPE_NAME + transitfeed.ExceptionWithContext.__init__(self, None, None, **kwargs) + self.dataset_merger = dataset_merger + + def FormatContext(self): + return "In files '%s'" % self.dataset_merger.FILE_NAME + + +class SameIdButNotMerged(MergeProblemWithContext): + ERROR_TEXT = ("There is a %(entity_type_name)s in the old feed with id " + "'%(id)s' and one from the new feed with the same id but " + "they could not be merged:") + + +class CalendarsNotDisjoint(MergeProblemWithContext): + ERROR_TEXT = ("The service periods could not be merged since they are not " + "disjoint.") + + +class MergeNotImplemented(MergeProblemWithContext): + ERROR_TEXT = ("The feed merger does not currently support merging in this " + "file. The entries have been duplicated instead.") + + +class FareRulesBroken(MergeProblemWithContext): + ERROR_TEXT = ("The feed merger is currently unable to handle fare rules " + "properly.") + + +class MergeProblemReporterBase(transitfeed.ProblemReporterBase): + """The base problem reporter class for the merge module.""" + + def SameIdButNotMerged(self, dataset, entity_id, reason): + self._Report(SameIdButNotMerged(dataset, id=entity_id, reason=reason)) + + def CalendarsNotDisjoint(self, dataset): + self._Report(CalendarsNotDisjoint(dataset, + problem_type=transitfeed.TYPE_ERROR)) + + def MergeNotImplemented(self, dataset): + self._Report(MergeNotImplemented(dataset)) + + def FareRulesBroken(self, dataset): + self._Report(FareRulesBroken(dataset)) + + +class ExceptionProblemReporter(MergeProblemReporterBase): + """A problem reporter that reports errors by raising exceptions.""" + + def __init__(self, raise_warnings=False): + """Initialise. + + Args: + raise_warnings: If this is True then warnings are also raised as + exceptions. + """ + MergeProblemReporterBase.__init__(self) + self._raise_warnings = raise_warnings + + def _Report(self, merge_problem): + if self._raise_warnings or merge_problem.IsError(): + raise merge_problem + + +class HTMLProblemReporter(MergeProblemReporterBase): + """A problem reporter which generates HTML output.""" + + def __init__(self): + """Initialise.""" + MergeProblemReporterBase.__init__(self) + self._dataset_warnings = {} # a map from DataSetMergers to their warnings + self._dataset_errors = {} + self._warning_count = 0 + self._error_count = 0 + + def _Report(self, merge_problem): + if merge_problem.IsWarning(): + dataset_problems = self._dataset_warnings + self._warning_count += 1 + else: + dataset_problems = self._dataset_errors + self._error_count += 1 + + problem_html = '
  • %s
  • ' % ( + merge_problem.FormatProblem().replace('\n', '
    ')) + dataset_problems.setdefault(merge_problem.dataset_merger, []).append( + problem_html) + + def _GenerateStatsTable(self, feed_merger): + """Generate an HTML table of merge statistics. + + Args: + feed_merger: The FeedMerger instance. + + Returns: + The generated HTML as a string. + """ + rows = [] + rows.append('Merged' + 'Copied from old feed' + 'Copied from new feed') + for merger in feed_merger.GetMergerList(): + stats = merger.GetMergeStats() + if stats is None: + continue + merged, not_merged_a, not_merged_b = stats + rows.append('%s' + '%d' + '%d' + '%d' % + (merger.DATASET_NAME, merged, not_merged_a, not_merged_b)) + return '%s
    ' % '\n'.join(rows) + + def _GenerateSection(self, problem_type): + """Generate a listing of the given type of problems. + + Args: + problem_type: The type of problem. This is one of the problem type + constants from transitfeed. + + Returns: + The generated HTML as a string. + """ + if problem_type == transitfeed.TYPE_WARNING: + dataset_problems = self._dataset_warnings + heading = 'Warnings' + else: + dataset_problems = self._dataset_errors + heading = 'Errors' + + if not dataset_problems: + return '' + + prefix = '

    %s:

    ' % heading + dataset_sections = [] + for dataset_merger, problems in dataset_problems.items(): + dataset_sections.append('

    %s

      %s
    ' % ( + dataset_merger.FILE_NAME, '\n'.join(problems))) + body = '\n'.join(dataset_sections) + return prefix + body + + def _GenerateSummary(self): + """Generate a summary of the warnings and errors. + + Returns: + The generated HTML as a string. + """ + items = [] + if self._dataset_errors: + items.append('errors: %d' % self._error_count) + if self._dataset_warnings: + items.append('warnings: %d' % self._warning_count) + + if items: + return '

    %s

    ' % '
    '.join(items) + else: + return '

    feeds merged successfully

    ' + + def WriteOutput(self, output_file, feed_merger, + old_feed_path, new_feed_path, merged_feed_path): + """Write the HTML output to a file. + + Args: + output_file: The file object that the HTML output will be written to. + feed_merger: The FeedMerger instance. + old_feed_path: The path to the old feed file as a string. + new_feed_path: The path to the new feed file as a string + merged_feed_path: The path to the merged feed file as a string. This + may be None if no merged feed was written. + """ + if merged_feed_path is None: + html_merged_feed_path = '' + else: + html_merged_feed_path = '

    Merged feed created: %s

    ' % ( + merged_feed_path) + + html_header = """ + + +Feed Merger Results + + + +

    Feed merger results

    +

    Old feed: %(old_feed_path)s

    +

    New feed: %(new_feed_path)s

    +%(html_merged_feed_path)s""" % locals() + + html_stats = self._GenerateStatsTable(feed_merger) + html_summary = self._GenerateSummary() + html_errors = self._GenerateSection(transitfeed.TYPE_ERROR) + html_warnings = self._GenerateSection(transitfeed.TYPE_WARNING) + + html_footer = """ + + +""" % (transitfeed.__version__, + time.strftime('%B %d, %Y at %I:%M %p %Z')) + + output_file.write(transitfeed.EncodeUnicode(html_header)) + output_file.write(transitfeed.EncodeUnicode(html_stats)) + output_file.write(transitfeed.EncodeUnicode(html_summary)) + output_file.write(transitfeed.EncodeUnicode(html_errors)) + output_file.write(transitfeed.EncodeUnicode(html_warnings)) + output_file.write(transitfeed.EncodeUnicode(html_footer)) + + +class ConsoleWarningRaiseErrorProblemReporter(transitfeed.ProblemReporterBase): + """Problem reporter to use when loading feeds for merge.""" + + def _Report(self, e): + if e.IsError(): + raise e + else: + print transitfeed.EncodeUnicode(e.FormatProblem()) + context = e.FormatContext() + if context: + print context + + +def LoadWithoutErrors(path, memory_db): + """"Return a Schedule object loaded from path; sys.exit for any error.""" + loading_problem_handler = ConsoleWarningRaiseErrorProblemReporter() + try: + schedule = transitfeed.Loader(path, + memory_db=memory_db, + problems=loading_problem_handler).Load() + except transitfeed.ExceptionWithContext, e: + print >>sys.stderr, ( + "\n\nFeeds to merge must load without any errors.\n" + "While loading %s the following error was found:\n%s\n%s\n" % + (path, e.FormatContext(), transitfeed.EncodeUnicode(e.FormatProblem()))) + sys.exit(1) + return schedule + + +class DataSetMerger(object): + """A DataSetMerger is in charge of merging a set of entities. + + This is an abstract class and should be subclassed for each different entity + type. + + Attributes: + ENTITY_TYPE_NAME: The name of the entity type like 'agency' or 'stop'. + FILE_NAME: The name of the file containing this data set like 'agency.txt'. + DATASET_NAME: A name for the dataset like 'Agencies' or 'Stops'. + """ + + def __init__(self, feed_merger): + """Initialise. + + Args: + feed_merger: The FeedMerger. + """ + self.feed_merger = feed_merger + self._num_merged = 0 + self._num_not_merged_a = 0 + self._num_not_merged_b = 0 + + def _MergeIdentical(self, a, b): + """Tries to merge two values. The values are required to be identical. + + Args: + a: The first value. + b: The second value. + + Returns: + The trivially merged value. + + Raises: + MergeError: The values were not identical. + """ + if a != b: + raise MergeError("values must be identical ('%s' vs '%s')" % + (transitfeed.EncodeUnicode(a), + transitfeed.EncodeUnicode(b))) + return b + + def _MergeIdenticalCaseInsensitive(self, a, b): + """Tries to merge two strings. + + The string are required to be the same ignoring case. The second string is + always used as the merged value. + + Args: + a: The first string. + b: The second string. + + Returns: + The merged string. This is equal to the second string. + + Raises: + MergeError: The strings were not the same ignoring case. + """ + if a.lower() != b.lower(): + raise MergeError("values must be the same (case insensitive) " + "('%s' vs '%s')" % (transitfeed.EncodeUnicode(a), + transitfeed.EncodeUnicode(b))) + return b + + def _MergeOptional(self, a, b): + """Tries to merge two values which may be None. + + If both values are not None, they are required to be the same and the + merge is trivial. If one of the values is None and the other is not None, + the merge results in the one which is not None. If both are None, the merge + results in None. + + Args: + a: The first value. + b: The second value. + + Returns: + The merged value. + + Raises: + MergeError: If both values are not None and are not the same. + """ + if a and b: + if a != b: + raise MergeError("values must be identical if both specified " + "('%s' vs '%s')" % (transitfeed.EncodeUnicode(a), + transitfeed.EncodeUnicode(b))) + return a or b + + def _MergeSameAgency(self, a_agency_id, b_agency_id): + """Merge agency ids to the corresponding agency id in the merged schedule. + + Args: + a_agency_id: an agency id from the old schedule + b_agency_id: an agency id from the new schedule + + Returns: + The agency id of the corresponding merged agency. + + Raises: + MergeError: If a_agency_id and b_agency_id do not correspond to the same + merged agency. + KeyError: Either aaid or baid is not a valid agency id. + """ + a_agency_id = (a_agency_id or + self.feed_merger.a_schedule.GetDefaultAgency().agency_id) + b_agency_id = (b_agency_id or + self.feed_merger.b_schedule.GetDefaultAgency().agency_id) + a_agency = self.feed_merger.a_merge_map[ + self.feed_merger.a_schedule.GetAgency(a_agency_id)] + b_agency = self.feed_merger.b_merge_map[ + self.feed_merger.b_schedule.GetAgency(b_agency_id)] + if a_agency != b_agency: + raise MergeError('agency must be the same') + return a_agency.agency_id + + def _SchemedMerge(self, scheme, a, b): + """Tries to merge two entities according to a merge scheme. + + A scheme is specified by a map where the keys are entity attributes and the + values are merge functions like Merger._MergeIdentical or + Merger._MergeOptional. The entity is first migrated to the merged schedule. + Then the attributes are individually merged as specified by the scheme. + + Args: + scheme: The merge scheme, a map from entity attributes to merge + functions. + a: The entity from the old schedule. + b: The entity from the new schedule. + + Returns: + The migrated and merged entity. + + Raises: + MergeError: One of the attributes was not able to be merged. + """ + migrated = self._Migrate(b, self.feed_merger.b_schedule, False) + for attr, merger in scheme.items(): + a_attr = getattr(a, attr, None) + b_attr = getattr(b, attr, None) + try: + merged_attr = merger(a_attr, b_attr) + except MergeError, merge_error: + raise MergeError("Attribute '%s' could not be merged: %s." % ( + attr, merge_error)) + if migrated is not None: + setattr(migrated, attr, merged_attr) + return migrated + + def _MergeSameId(self): + """Tries to merge entities based on their ids. + + This tries to merge only the entities from the old and new schedules which + have the same id. These are added into the merged schedule. Entities which + do not merge or do not have the same id as another entity in the other + schedule are simply migrated into the merged schedule. + + This method is less flexible than _MergeDifferentId since it only tries + to merge entities which have the same id while _MergeDifferentId tries to + merge everything. However, it is faster and so should be used whenever + possible. + + This method makes use of various methods like _Merge and _Migrate which + are not implemented in the abstract DataSetMerger class. These method + should be overwritten in a subclass to allow _MergeSameId to work with + different entity types. + + Returns: + The number of merged entities. + """ + a_not_merged = [] + b_not_merged = [] + + for a in self._GetIter(self.feed_merger.a_schedule): + try: + b = self._GetById(self.feed_merger.b_schedule, self._GetId(a)) + except KeyError: + # there was no entity in B with the same id as a + a_not_merged.append(a) + continue + try: + self._Add(a, b, self._MergeEntities(a, b)) + self._num_merged += 1 + except MergeError, merge_error: + a_not_merged.append(a) + b_not_merged.append(b) + self._ReportSameIdButNotMerged(self._GetId(a), merge_error) + + for b in self._GetIter(self.feed_merger.b_schedule): + try: + a = self._GetById(self.feed_merger.a_schedule, self._GetId(b)) + except KeyError: + # there was no entity in A with the same id as b + b_not_merged.append(b) + + # migrate the remaining entities + for a in a_not_merged: + newid = self._HasId(self.feed_merger.b_schedule, self._GetId(a)) + self._Add(a, None, self._Migrate(a, self.feed_merger.a_schedule, newid)) + for b in b_not_merged: + newid = self._HasId(self.feed_merger.a_schedule, self._GetId(b)) + self._Add(None, b, self._Migrate(b, self.feed_merger.b_schedule, newid)) + + self._num_not_merged_a = len(a_not_merged) + self._num_not_merged_b = len(b_not_merged) + return self._num_merged + + def _MergeDifferentId(self): + """Tries to merge all possible combinations of entities. + + This tries to merge every entity in the old schedule with every entity in + the new schedule. Unlike _MergeSameId, the ids do not need to match. + However, _MergeDifferentId is much slower than _MergeSameId. + + This method makes use of various methods like _Merge and _Migrate which + are not implemented in the abstract DataSetMerger class. These method + should be overwritten in a subclass to allow _MergeSameId to work with + different entity types. + + Returns: + The number of merged entities. + """ + # TODO: The same entity from A could merge with multiple from B. + # This should either generate an error or should be prevented from + # happening. + for a in self._GetIter(self.feed_merger.a_schedule): + for b in self._GetIter(self.feed_merger.b_schedule): + try: + self._Add(a, b, self._MergeEntities(a, b)) + self._num_merged += 1 + except MergeError: + continue + + for a in self._GetIter(self.feed_merger.a_schedule): + if a not in self.feed_merger.a_merge_map: + self._num_not_merged_a += 1 + newid = self._HasId(self.feed_merger.b_schedule, self._GetId(a)) + self._Add(a, None, + self._Migrate(a, self.feed_merger.a_schedule, newid)) + for b in self._GetIter(self.feed_merger.b_schedule): + if b not in self.feed_merger.b_merge_map: + self._num_not_merged_b += 1 + newid = self._HasId(self.feed_merger.a_schedule, self._GetId(b)) + self._Add(None, b, + self._Migrate(b, self.feed_merger.b_schedule, newid)) + + return self._num_merged + + def _ReportSameIdButNotMerged(self, entity_id, reason): + """Report that two entities have the same id but could not be merged. + + Args: + entity_id: The id of the entities. + reason: A string giving a reason why they could not be merged. + """ + self.feed_merger.problem_reporter.SameIdButNotMerged(self, + entity_id, + reason) + + def _GetIter(self, schedule): + """Returns an iterator of entities for this data set in the given schedule. + + This method usually corresponds to one of the methods from + transitfeed.Schedule like GetAgencyList() or GetRouteList(). + + Note: This method must be overwritten in a subclass if _MergeSameId or + _MergeDifferentId are to be used. + + Args: + schedule: Either the old or new schedule from the FeedMerger. + + Returns: + An iterator of entities. + """ + raise NotImplementedError() + + def _GetById(self, schedule, entity_id): + """Returns an entity given its id. + + This method usually corresponds to one of the methods from + transitfeed.Schedule like GetAgency() or GetRoute(). + + Note: This method must be overwritten in a subclass if _MergeSameId or + _MergeDifferentId are to be used. + + Args: + schedule: Either the old or new schedule from the FeedMerger. + entity_id: The id string of the entity. + + Returns: + The entity with the given id. + + Raises: + KeyError: There is not entity with the given id. + """ + raise NotImplementedError() + + def _HasId(self, schedule, entity_id): + """Check if the schedule has an entity with the given id. + + Args: + schedule: The transitfeed.Schedule instance to look in. + entity_id: The id of the entity. + + Returns: + True if the schedule has an entity with the id or False if not. + """ + try: + self._GetById(schedule, entity_id) + has = True + except KeyError: + has = False + return has + + def _MergeEntities(self, a, b): + """Tries to merge the two entities. + + Note: This method must be overwritten in a subclass if _MergeSameId or + _MergeDifferentId are to be used. + + Args: + a: The entity from the old schedule. + b: The entity from the new schedule. + + Returns: + The merged migrated entity. + + Raises: + MergeError: The entities were not able to be merged. + """ + raise NotImplementedError() + + def _Migrate(self, entity, schedule, newid): + """Migrates the entity to the merge schedule. + + This involves copying the entity and updating any ids to point to the + corresponding entities in the merged schedule. If newid is True then + a unique id is generated for the migrated entity using the original id + as a prefix. + + Note: This method must be overwritten in a subclass if _MergeSameId or + _MergeDifferentId are to be used. + + Args: + entity: The entity to migrate. + schedule: The schedule from the FeedMerger that contains ent. + newid: Whether to generate a new id (True) or keep the original (False). + + Returns: + The migrated entity. + """ + raise NotImplementedError() + + def _Add(self, a, b, migrated): + """Adds the migrated entity to the merged schedule. + + If a and b are both not None, it means that a and b were merged to create + migrated. If one of a or b is None, it means that the other was not merged + but has been migrated. This mapping is registered with the FeedMerger. + + Note: This method must be overwritten in a subclass if _MergeSameId or + _MergeDifferentId are to be used. + + Args: + a: The original entity from the old schedule. + b: The original entity from the new schedule. + migrated: The migrated entity for the merged schedule. + """ + raise NotImplementedError() + + def _GetId(self, entity): + """Returns the id of the given entity. + + Note: This method must be overwritten in a subclass if _MergeSameId or + _MergeDifferentId are to be used. + + Args: + entity: The entity. + + Returns: + The id of the entity as a string or None. + """ + raise NotImplementedError() + + def MergeDataSets(self): + """Merge the data sets. + + This method is called in FeedMerger.MergeSchedule(). + + Note: This method must be overwritten in a subclass. + + Returns: + A boolean which is False if the dataset was unable to be merged and + as a result the entire merge should be aborted. In this case, the problem + will have been reported using the FeedMerger's problem reporter. + """ + raise NotImplementedError() + + def GetMergeStats(self): + """Returns some merge statistics. + + These are given as a tuple (merged, not_merged_a, not_merged_b) where + "merged" is the number of merged entities, "not_merged_a" is the number of + entities from the old schedule that were not merged and "not_merged_b" is + the number of entities from the new schedule that were not merged. + + The return value can also be None. This means that there are no statistics + for this entity type. + + The statistics are only available after MergeDataSets() has been called. + + Returns: + Either the statistics tuple or None. + """ + return (self._num_merged, self._num_not_merged_a, self._num_not_merged_b) + + +class AgencyMerger(DataSetMerger): + """A DataSetMerger for agencies.""" + + ENTITY_TYPE_NAME = 'agency' + FILE_NAME = 'agency.txt' + DATASET_NAME = 'Agencies' + + def _GetIter(self, schedule): + return schedule.GetAgencyList() + + def _GetById(self, schedule, agency_id): + return schedule.GetAgency(agency_id) + + def _MergeEntities(self, a, b): + """Merges two agencies. + + To be merged, they are required to have the same id, name, url and + timezone. The remaining language attribute is taken from the new agency. + + Args: + a: The first agency. + b: The second agency. + + Returns: + The merged agency. + + Raises: + MergeError: The agencies could not be merged. + """ + + def _MergeAgencyId(a_agency_id, b_agency_id): + """Merge two agency ids. + + The only difference between this and _MergeIdentical() is that the values + None and '' are regarded as being the same. + + Args: + a_agency_id: The first agency id. + b_agency_id: The second agency id. + + Returns: + The merged agency id. + + Raises: + MergeError: The agency ids could not be merged. + """ + a_agency_id = a_agency_id or None + b_agency_id = b_agency_id or None + return self._MergeIdentical(a_agency_id, b_agency_id) + + scheme = {'agency_id': _MergeAgencyId, + 'agency_name': self._MergeIdentical, + 'agency_url': self._MergeIdentical, + 'agency_timezone': self._MergeIdentical} + return self._SchemedMerge(scheme, a, b) + + def _Migrate(self, entity, schedule, newid): + a = transitfeed.Agency(field_dict=entity) + if newid: + a.agency_id = self.feed_merger.GenerateId(entity.agency_id) + return a + + def _Add(self, a, b, migrated): + self.feed_merger.Register(a, b, migrated) + self.feed_merger.merged_schedule.AddAgencyObject(migrated) + + def _GetId(self, entity): + return entity.agency_id + + def MergeDataSets(self): + self._MergeSameId() + return True + + +class StopMerger(DataSetMerger): + """A DataSetMerger for stops. + + Attributes: + largest_stop_distance: The largest distance allowed between stops that + will be merged in metres. + """ + + ENTITY_TYPE_NAME = 'stop' + FILE_NAME = 'stops.txt' + DATASET_NAME = 'Stops' + + largest_stop_distance = 10.0 + + def __init__(self, feed_merger): + DataSetMerger.__init__(self, feed_merger) + self._merged = [] + self._a_not_merged = [] + self._b_not_merged = [] + + def SetLargestStopDistance(self, distance): + """Sets largest_stop_distance.""" + self.largest_stop_distance = distance + + def _GetIter(self, schedule): + return schedule.GetStopList() + + def _GetById(self, schedule, stop_id): + return schedule.GetStop(stop_id) + + def _MergeEntities(self, a, b): + """Merges two stops. + + For the stops to be merged, they must have: + - the same stop_id + - the same stop_name (case insensitive) + - the same zone_id + - locations less than largest_stop_distance apart + The other attributes can have arbitary changes. The merged attributes are + taken from the new stop. + + Args: + a: The first stop. + b: The second stop. + + Returns: + The merged stop. + + Raises: + MergeError: The stops could not be merged. + """ + distance = transitfeed.ApproximateDistanceBetweenStops(a, b) + if distance > self.largest_stop_distance: + raise MergeError("Stops are too far apart: %.1fm " + "(largest_stop_distance is %.1fm)." % + (distance, self.largest_stop_distance)) + scheme = {'stop_id': self._MergeIdentical, + 'stop_name': self._MergeIdenticalCaseInsensitive, + 'zone_id': self._MergeIdentical, + 'location_type': self._MergeIdentical} + return self._SchemedMerge(scheme, a, b) + + def _Migrate(self, entity, schedule, newid): + migrated_stop = transitfeed.Stop(field_dict=entity) + if newid: + migrated_stop.stop_id = self.feed_merger.GenerateId(entity.stop_id) + return migrated_stop + + def _Add(self, a, b, migrated_stop): + self.feed_merger.Register(a, b, migrated_stop) + + # The migrated_stop will be added to feed_merger.merged_schedule later + # since adding must be done after the zone_ids have been finalized. + if a and b: + self._merged.append((a, b, migrated_stop)) + elif a: + self._a_not_merged.append((a, migrated_stop)) + elif b: + self._b_not_merged.append((b, migrated_stop)) + + def _GetId(self, entity): + return entity.stop_id + + def MergeDataSets(self): + num_merged = self._MergeSameId() + fm = self.feed_merger + + # now we do all the zone_id and parent_station mapping + + # the zone_ids for merged stops can be preserved + for (a, b, merged_stop) in self._merged: + assert a.zone_id == b.zone_id + fm.a_zone_map[a.zone_id] = a.zone_id + fm.b_zone_map[b.zone_id] = b.zone_id + merged_stop.zone_id = a.zone_id + if merged_stop.parent_station: + # Merged stop has a parent. Update it to be the parent it had in b. + parent_in_b = fm.b_schedule.GetStop(b.parent_station) + merged_stop.parent_station = fm.b_merge_map[parent_in_b].stop_id + fm.merged_schedule.AddStopObject(merged_stop) + + self._UpdateAndMigrateUnmerged(self._a_not_merged, fm.a_zone_map, + fm.a_merge_map, fm.a_schedule) + self._UpdateAndMigrateUnmerged(self._b_not_merged, fm.b_zone_map, + fm.b_merge_map, fm.b_schedule) + + print 'Stops merged: %d of %d, %d' % ( + num_merged, + len(fm.a_schedule.GetStopList()), + len(fm.b_schedule.GetStopList())) + return True + + def _UpdateAndMigrateUnmerged(self, not_merged_stops, zone_map, merge_map, + schedule): + """Correct references in migrated unmerged stops and add to merged_schedule. + + For stops migrated from one of the input feeds to the output feed update the + parent_station and zone_id references to point to objects in the output + feed. Then add the migrated stop to the new schedule. + + Args: + not_merged_stops: list of stops from one input feed that have not been + merged + zone_map: map from zone_id in the input feed to zone_id in the output feed + merge_map: map from Stop objects in the input feed to Stop objects in + the output feed + schedule: the input Schedule object + """ + # for the unmerged stops, we use an already mapped zone_id if possible + # if not, we generate a new one and add it to the map + for stop, migrated_stop in not_merged_stops: + if stop.zone_id in zone_map: + migrated_stop.zone_id = zone_map[stop.zone_id] + else: + migrated_stop.zone_id = self.feed_merger.GenerateId(stop.zone_id) + zone_map[stop.zone_id] = migrated_stop.zone_id + if stop.parent_station: + parent_original = schedule.GetStop(stop.parent_station) + migrated_stop.parent_station = merge_map[parent_original].stop_id + self.feed_merger.merged_schedule.AddStopObject(migrated_stop) + + +class RouteMerger(DataSetMerger): + """A DataSetMerger for routes.""" + + ENTITY_TYPE_NAME = 'route' + FILE_NAME = 'routes.txt' + DATASET_NAME = 'Routes' + + def _GetIter(self, schedule): + return schedule.GetRouteList() + + def _GetById(self, schedule, route_id): + return schedule.GetRoute(route_id) + + def _MergeEntities(self, a, b): + scheme = {'route_short_name': self._MergeIdentical, + 'route_long_name': self._MergeIdentical, + 'agency_id': self._MergeSameAgency, + 'route_type': self._MergeIdentical, + 'route_id': self._MergeIdentical, + 'route_url': self._MergeOptional, + 'route_color': self._MergeOptional, + 'route_text_color': self._MergeOptional} + return self._SchemedMerge(scheme, a, b) + + def _Migrate(self, entity, schedule, newid): + migrated_route = transitfeed.Route(field_dict=entity) + if newid: + migrated_route.route_id = self.feed_merger.GenerateId(entity.route_id) + if entity.agency_id: + original_agency = schedule.GetAgency(entity.agency_id) + else: + original_agency = schedule.GetDefaultAgency() + + migrated_agency = self.feed_merger.GetMergedObject(original_agency) + migrated_route.agency_id = migrated_agency.agency_id + return migrated_route + + def _Add(self, a, b, migrated_route): + self.feed_merger.Register(a, b, migrated_route) + self.feed_merger.merged_schedule.AddRouteObject(migrated_route) + + def _GetId(self, entity): + return entity.route_id + + def MergeDataSets(self): + self._MergeSameId() + return True + + +class ServicePeriodMerger(DataSetMerger): + """A DataSetMerger for service periods. + + Attributes: + require_disjoint_calendars: A boolean specifying whether to require + disjoint calendars when merging (True) or not (False). + """ + + ENTITY_TYPE_NAME = 'service period' + FILE_NAME = 'calendar.txt/calendar_dates.txt' + DATASET_NAME = 'Service Periods' + + def __init__(self, feed_merger): + DataSetMerger.__init__(self, feed_merger) + self.require_disjoint_calendars = True + + def _ReportSameIdButNotMerged(self, entity_id, reason): + pass + + def _GetIter(self, schedule): + return schedule.GetServicePeriodList() + + def _GetById(self, schedule, service_id): + return schedule.GetServicePeriod(service_id) + + def _MergeEntities(self, a, b): + """Tries to merge two service periods. + + Note: Currently this just raises a MergeError since service periods cannot + be merged. + + Args: + a: The first service period. + b: The second service period. + + Returns: + The merged service period. + + Raises: + MergeError: When the service periods could not be merged. + """ + raise MergeError('Cannot merge service periods') + + def _Migrate(self, original_service_period, schedule, newid): + migrated_service_period = transitfeed.ServicePeriod() + migrated_service_period.day_of_week = list( + original_service_period.day_of_week) + migrated_service_period.start_date = original_service_period.start_date + migrated_service_period.end_date = original_service_period.end_date + migrated_service_period.date_exceptions = dict( + original_service_period.date_exceptions) + if newid: + migrated_service_period.service_id = self.feed_merger.GenerateId( + original_service_period.service_id) + else: + migrated_service_period.service_id = original_service_period.service_id + return migrated_service_period + + def _Add(self, a, b, migrated_service_period): + self.feed_merger.Register(a, b, migrated_service_period) + self.feed_merger.merged_schedule.AddServicePeriodObject( + migrated_service_period) + + def _GetId(self, entity): + return entity.service_id + + def MergeDataSets(self): + if self.require_disjoint_calendars and not self.CheckDisjointCalendars(): + self.feed_merger.problem_reporter.CalendarsNotDisjoint(self) + return False + self._MergeSameId() + self.feed_merger.problem_reporter.MergeNotImplemented(self) + return True + + def DisjoinCalendars(self, cutoff): + """Forces the old and new calendars to be disjoint about a cutoff date. + + This truncates the service periods of the old schedule so that service + stops one day before the given cutoff date and truncates the new schedule + so that service only begins on the cutoff date. + + Args: + cutoff: The cutoff date as a string in YYYYMMDD format. The timezone + is the same as used in the calendar.txt file. + """ + + def TruncatePeriod(service_period, start, end): + """Truncate the service period to into the range [start, end]. + + Args: + service_period: The service period to truncate. + start: The start date as a string in YYYYMMDD format. + end: The end date as a string in YYYYMMDD format. + """ + service_period.start_date = max(service_period.start_date, start) + service_period.end_date = min(service_period.end_date, end) + dates_to_delete = [] + for k in service_period.date_exceptions: + if (k < start) or (k > end): + dates_to_delete.append(k) + for k in dates_to_delete: + del service_period.date_exceptions[k] + + # find the date one day before cutoff + year = int(cutoff[:4]) + month = int(cutoff[4:6]) + day = int(cutoff[6:8]) + cutoff_date = datetime.date(year, month, day) + one_day_delta = datetime.timedelta(days=1) + before = (cutoff_date - one_day_delta).strftime('%Y%m%d') + + for a in self.feed_merger.a_schedule.GetServicePeriodList(): + TruncatePeriod(a, 0, before) + for b in self.feed_merger.b_schedule.GetServicePeriodList(): + TruncatePeriod(b, cutoff, '9'*8) + + def CheckDisjointCalendars(self): + """Check whether any old service periods intersect with any new ones. + + This is a rather coarse check based on + transitfeed.SevicePeriod.GetDateRange. + + Returns: + True if the calendars are disjoint or False if not. + """ + # TODO: Do an exact check here. + + a_service_periods = self.feed_merger.a_schedule.GetServicePeriodList() + b_service_periods = self.feed_merger.b_schedule.GetServicePeriodList() + + for a_service_period in a_service_periods: + a_start, a_end = a_service_period.GetDateRange() + for b_service_period in b_service_periods: + b_start, b_end = b_service_period.GetDateRange() + overlap_start = max(a_start, b_start) + overlap_end = min(a_end, b_end) + if overlap_end >= overlap_start: + return False + return True + + def GetMergeStats(self): + return None + + +class FareMerger(DataSetMerger): + """A DataSetMerger for fares.""" + + ENTITY_TYPE_NAME = 'fare' + FILE_NAME = 'fare_attributes.txt' + DATASET_NAME = 'Fares' + + def _GetIter(self, schedule): + return schedule.GetFareList() + + def _GetById(self, schedule, fare_id): + return schedule.GetFare(fare_id) + + def _MergeEntities(self, a, b): + """Merges the fares if all the attributes are the same.""" + scheme = {'price': self._MergeIdentical, + 'currency_type': self._MergeIdentical, + 'payment_method': self._MergeIdentical, + 'transfers': self._MergeIdentical, + 'transfer_duration': self._MergeIdentical} + return self._SchemedMerge(scheme, a, b) + + def _Migrate(self, original_fare, schedule, newid): + migrated_fare = transitfeed.Fare( + field_list=original_fare.GetFieldValuesTuple()) + if newid: + migrated_fare.fare_id = self.feed_merger.GenerateId( + original_fare.fare_id) + return migrated_fare + + def _Add(self, a, b, migrated_fare): + self.feed_merger.Register(a, b, migrated_fare) + self.feed_merger.merged_schedule.AddFareObject(migrated_fare) + + def _GetId(self, fare): + return fare.fare_id + + def MergeDataSets(self): + num_merged = self._MergeSameId() + print 'Fares merged: %d of %d, %d' % ( + num_merged, + len(self.feed_merger.a_schedule.GetFareList()), + len(self.feed_merger.b_schedule.GetFareList())) + return True + + +class ShapeMerger(DataSetMerger): + """A DataSetMerger for shapes. + + In this implementation, merging shapes means just taking the new shape. + The only conditions for a merge are that the shape_ids are the same and + the endpoints of the old and new shapes are no further than + largest_shape_distance apart. + + Attributes: + largest_shape_distance: The largest distance between the endpoints of two + shapes allowed for them to be merged in metres. + """ + + ENTITY_TYPE_NAME = 'shape' + FILE_NAME = 'shapes.txt' + DATASET_NAME = 'Shapes' + + largest_shape_distance = 10.0 + + def SetLargestShapeDistance(self, distance): + """Sets largest_shape_distance.""" + self.largest_shape_distance = distance + + def _GetIter(self, schedule): + return schedule.GetShapeList() + + def _GetById(self, schedule, shape_id): + return schedule.GetShape(shape_id) + + def _MergeEntities(self, a, b): + """Merges the shapes by taking the new shape. + + Args: + a: The first transitfeed.Shape instance. + b: The second transitfeed.Shape instance. + + Returns: + The merged shape. + + Raises: + MergeError: If the ids are different or if the endpoints are further + than largest_shape_distance apart. + """ + if a.shape_id != b.shape_id: + raise MergeError('shape_id must be the same') + + distance = max(ApproximateDistanceBetweenPoints(a.points[0][:2], + b.points[0][:2]), + ApproximateDistanceBetweenPoints(a.points[-1][:2], + b.points[-1][:2])) + if distance > self.largest_shape_distance: + raise MergeError('The shape endpoints are too far away: %.1fm ' + '(largest_shape_distance is %.1fm)' % + (distance, self.largest_shape_distance)) + + return self._Migrate(b, self.feed_merger.b_schedule, False) + + def _Migrate(self, original_shape, schedule, newid): + migrated_shape = transitfeed.Shape(original_shape.shape_id) + if newid: + migrated_shape.shape_id = self.feed_merger.GenerateId( + original_shape.shape_id) + for (lat, lon, dist) in original_shape.points: + migrated_shape.AddPoint(lat=lat, lon=lon, distance=dist) + return migrated_shape + + def _Add(self, a, b, migrated_shape): + self.feed_merger.Register(a, b, migrated_shape) + self.feed_merger.merged_schedule.AddShapeObject(migrated_shape) + + def _GetId(self, shape): + return shape.shape_id + + def MergeDataSets(self): + self._MergeSameId() + return True + + +class TripMerger(DataSetMerger): + """A DataSetMerger for trips. + + This implementation makes no attempt to merge trips, it simply migrates + them all to the merged feed. + """ + + ENTITY_TYPE_NAME = 'trip' + FILE_NAME = 'trips.txt' + DATASET_NAME = 'Trips' + + def _ReportSameIdButNotMerged(self, trip_id, reason): + pass + + def _GetIter(self, schedule): + return schedule.GetTripList() + + def _GetById(self, schedule, trip_id): + return schedule.GetTrip(trip_id) + + def _MergeEntities(self, a, b): + """Raises a MergeError because currently trips cannot be merged.""" + raise MergeError('Cannot merge trips') + + def _Migrate(self, original_trip, schedule, newid): + migrated_trip = transitfeed.Trip(field_dict=original_trip) + # Make new trip_id first. AddTripObject reports a problem if it conflicts + # with an existing id. + if newid: + migrated_trip.trip_id = self.feed_merger.GenerateId( + original_trip.trip_id) + # Need to add trip to schedule before copying stoptimes + self.feed_merger.merged_schedule.AddTripObject(migrated_trip, + validate=False) + + if schedule == self.feed_merger.a_schedule: + merge_map = self.feed_merger.a_merge_map + else: + merge_map = self.feed_merger.b_merge_map + + original_route = schedule.GetRoute(original_trip.route_id) + migrated_trip.route_id = merge_map[original_route].route_id + + original_service_period = schedule.GetServicePeriod( + original_trip.service_id) + migrated_trip.service_id = merge_map[original_service_period].service_id + + if original_trip.block_id: + migrated_trip.block_id = '%s_%s' % ( + self.feed_merger.GetScheduleName(schedule), + original_trip.block_id) + + if original_trip.shape_id: + original_shape = schedule.GetShape(original_trip.shape_id) + migrated_trip.shape_id = merge_map[original_shape].shape_id + + for original_stop_time in original_trip.GetStopTimes(): + migrated_stop_time = transitfeed.StopTime( + None, + merge_map[original_stop_time.stop], + original_stop_time.arrival_time, + original_stop_time.departure_time, + original_stop_time.stop_headsign, + original_stop_time.pickup_type, + original_stop_time.drop_off_type, + original_stop_time.shape_dist_traveled, + original_stop_time.arrival_secs, + original_stop_time.departure_secs) + migrated_trip.AddStopTimeObject(migrated_stop_time) + + for headway_period in original_trip.GetHeadwayPeriodTuples(): + migrated_trip.AddHeadwayPeriod(*headway_period) + + return migrated_trip + + def _Add(self, a, b, migrated_trip): + # Validate now, since it wasn't done in _Migrate + migrated_trip.Validate(self.feed_merger.merged_schedule.problem_reporter) + self.feed_merger.Register(a, b, migrated_trip) + + def _GetId(self, trip): + return trip.trip_id + + def MergeDataSets(self): + self._MergeSameId() + self.feed_merger.problem_reporter.MergeNotImplemented(self) + return True + + def GetMergeStats(self): + return None + + +class FareRuleMerger(DataSetMerger): + """A DataSetMerger for fare rules.""" + + ENTITY_TYPE_NAME = 'fare rule' + FILE_NAME = 'fare_rules.txt' + DATASET_NAME = 'Fare Rules' + + def MergeDataSets(self): + """Merge the fare rule datasets. + + The fare rules are first migrated. Merging is done by removing any + duplicate rules. + + Returns: + True since fare rules can always be merged. + """ + rules = set() + for (schedule, merge_map, zone_map) in ([self.feed_merger.a_schedule, + self.feed_merger.a_merge_map, + self.feed_merger.a_zone_map], + [self.feed_merger.b_schedule, + self.feed_merger.b_merge_map, + self.feed_merger.b_zone_map]): + for fare in schedule.GetFareList(): + for fare_rule in fare.GetFareRuleList(): + fare_id = merge_map[schedule.GetFare(fare_rule.fare_id)].fare_id + route_id = (fare_rule.route_id and + merge_map[schedule.GetRoute(fare_rule.route_id)].route_id) + origin_id = (fare_rule.origin_id and + zone_map[fare_rule.origin_id]) + destination_id = (fare_rule.destination_id and + zone_map[fare_rule.destination_id]) + contains_id = (fare_rule.contains_id and + zone_map[fare_rule.contains_id]) + rules.add((fare_id, route_id, origin_id, destination_id, + contains_id)) + for fare_rule_tuple in rules: + migrated_fare_rule = transitfeed.FareRule(*fare_rule_tuple) + self.feed_merger.merged_schedule.AddFareRuleObject(migrated_fare_rule) + + if rules: + self.feed_merger.problem_reporter.FareRulesBroken(self) + print 'Fare Rules: union has %d fare rules' % len(rules) + return True + + def GetMergeStats(self): + return None + + +class FeedMerger(object): + """A class for merging two whole feeds. + + This class takes two instances of transitfeed.Schedule and uses + DataSetMerger instances to merge the feeds and produce the resultant + merged feed. + + Attributes: + a_schedule: The old transitfeed.Schedule instance. + b_schedule: The new transitfeed.Schedule instance. + problem_reporter: The merge problem reporter. + merged_schedule: The merged transitfeed.Schedule instance. + a_merge_map: A map from old entities to merged entities. + b_merge_map: A map from new entities to merged entities. + a_zone_map: A map from old zone ids to merged zone ids. + b_zone_map: A map from new zone ids to merged zone ids. + """ + + def __init__(self, a_schedule, b_schedule, merged_schedule, + problem_reporter=None): + """Initialise the merger. + + Once this initialiser has been called, a_schedule and b_schedule should + not be modified. + + Args: + a_schedule: The old schedule, an instance of transitfeed.Schedule. + b_schedule: The new schedule, an instance of transitfeed.Schedule. + problem_reporter: The problem reporter, an instance of + transitfeed.ProblemReporterBase. This can be None in + which case the ExceptionProblemReporter is used. + """ + self.a_schedule = a_schedule + self.b_schedule = b_schedule + self.merged_schedule = merged_schedule + self.a_merge_map = {} + self.b_merge_map = {} + self.a_zone_map = {} + self.b_zone_map = {} + self._mergers = [] + self._idnum = max(self._FindLargestIdPostfixNumber(self.a_schedule), + self._FindLargestIdPostfixNumber(self.b_schedule)) + + if problem_reporter is not None: + self.problem_reporter = problem_reporter + else: + self.problem_reporter = ExceptionProblemReporter() + + def _FindLargestIdPostfixNumber(self, schedule): + """Finds the largest integer used as the ending of an id in the schedule. + + Args: + schedule: The schedule to check. + + Returns: + The maximum integer used as an ending for an id. + """ + postfix_number_re = re.compile('(\d+)$') + + def ExtractPostfixNumber(entity_id): + """Try to extract an integer from the end of entity_id. + + If entity_id is None or if there is no integer ending the id, zero is + returned. + + Args: + entity_id: An id string or None. + + Returns: + An integer ending the entity_id or zero. + """ + if entity_id is None: + return 0 + match = postfix_number_re.search(entity_id) + if match is not None: + return int(match.group(1)) + else: + return 0 + + id_data_sets = {'agency_id': schedule.GetAgencyList(), + 'stop_id': schedule.GetStopList(), + 'route_id': schedule.GetRouteList(), + 'trip_id': schedule.GetTripList(), + 'service_id': schedule.GetServicePeriodList(), + 'fare_id': schedule.GetFareList(), + 'shape_id': schedule.GetShapeList()} + + max_postfix_number = 0 + for id_name, entity_list in id_data_sets.items(): + for entity in entity_list: + entity_id = getattr(entity, id_name) + postfix_number = ExtractPostfixNumber(entity_id) + max_postfix_number = max(max_postfix_number, postfix_number) + return max_postfix_number + + def GetScheduleName(self, schedule): + """Returns a single letter identifier for the schedule. + + This only works for the old and new schedules which return 'a' and 'b' + respectively. The purpose of such identifiers is for generating ids. + + Args: + schedule: The transitfeed.Schedule instance. + + Returns: + The schedule identifier. + + Raises: + KeyError: schedule is not the old or new schedule. + """ + return {self.a_schedule: 'a', self.b_schedule: 'b'}[schedule] + + def GenerateId(self, entity_id=None): + """Generate a unique id based on the given id. + + This is done by appending a counter which is then incremented. The + counter is initialised at the maximum number used as an ending for + any id in the old and new schedules. + + Args: + entity_id: The base id string. This is allowed to be None. + + Returns: + The generated id. + """ + self._idnum += 1 + if entity_id: + return '%s_merged_%d' % (entity_id, self._idnum) + else: + return 'merged_%d' % self._idnum + + def Register(self, a, b, migrated_entity): + """Registers a merge mapping. + + If a and b are both not None, this means that entities a and b were merged + to produce migrated_entity. If one of a or b are not None, then it means + it was not merged but simply migrated. + + The effect of a call to register is to update a_merge_map and b_merge_map + according to the merge. + + Args: + a: The entity from the old feed or None. + b: The entity from the new feed or None. + migrated_entity: The migrated entity. + """ + if a is not None: self.a_merge_map[a] = migrated_entity + if b is not None: self.b_merge_map[b] = migrated_entity + + def AddMerger(self, merger): + """Add a DataSetMerger to be run by Merge(). + + Args: + merger: The DataSetMerger instance. + """ + self._mergers.append(merger) + + def AddDefaultMergers(self): + """Adds the default DataSetMergers defined in this module.""" + self.AddMerger(AgencyMerger(self)) + self.AddMerger(StopMerger(self)) + self.AddMerger(RouteMerger(self)) + self.AddMerger(ServicePeriodMerger(self)) + self.AddMerger(FareMerger(self)) + self.AddMerger(ShapeMerger(self)) + self.AddMerger(TripMerger(self)) + self.AddMerger(FareRuleMerger(self)) + + def GetMerger(self, cls): + """Looks for an added DataSetMerger derived from the given class. + + Args: + cls: A class derived from DataSetMerger. + + Returns: + The matching DataSetMerger instance. + + Raises: + LookupError: No matching DataSetMerger has been added. + """ + for merger in self._mergers: + if isinstance(merger, cls): + return merger + raise LookupError('No matching DataSetMerger found') + + def GetMergerList(self): + """Returns the list of DataSetMerger instances that have been added.""" + return self._mergers + + def MergeSchedules(self): + """Merge the schedules. + + This is done by running the DataSetMergers that have been added with + AddMerger() in the order that they were added. + + Returns: + True if the merge was successful. + """ + for merger in self._mergers: + if not merger.MergeDataSets(): + return False + return True + + def GetMergedSchedule(self): + """Returns the merged schedule. + + This will be empty before MergeSchedules() is called. + + Returns: + The merged schedule. + """ + return self.merged_schedule + + def GetMergedObject(self, original): + """Returns an object that represents original in the merged schedule.""" + # TODO: I think this would be better implemented by adding a private + # attribute to the objects in the original feeds + merged = (self.a_merge_map.get(original) or + self.b_merge_map.get(original)) + if merged: + return merged + else: + raise KeyError() + + +def main(): + """Run the merge driver program.""" + usage = \ +"""%prog [options] + +Merges and into a new GTFS file +. +""" + + parser = util.OptionParserLongError( + usage=usage, version='%prog '+transitfeed.__version__) + parser.add_option('--cutoff_date', + dest='cutoff_date', + default=None, + help='a transition date from the old feed to the new ' + 'feed in the format YYYYMMDD') + parser.add_option('--largest_stop_distance', + dest='largest_stop_distance', + default=StopMerger.largest_stop_distance, + help='the furthest distance two stops can be apart and ' + 'still be merged, in metres') + parser.add_option('--largest_shape_distance', + dest='largest_shape_distance', + default=ShapeMerger.largest_shape_distance, + help='the furthest distance the endpoints of two shapes ' + 'can be apart and the shape still be merged, in metres') + parser.add_option('--html_output_path', + dest='html_output_path', + default='merge-results.html', + help='write the html output to this file') + parser.add_option('--no_browser', + dest='no_browser', + action='store_true', + help='prevents the merge results from being opened in a ' + 'browser') + parser.add_option('-m', '--memory_db', dest='memory_db', action='store_true', + help='Use in-memory sqlite db instead of a temporary file. ' + 'It is faster but uses more RAM.') + parser.set_defaults(memory_db=False) + (options, args) = parser.parse_args() + + if len(args) != 3: + parser.error('You did not provide all required command line arguments.') + + old_feed_path = os.path.abspath(args[0]) + new_feed_path = os.path.abspath(args[1]) + merged_feed_path = os.path.abspath(args[2]) + + if old_feed_path.find("IWantMyCrash") != -1: + # See test/testmerge.py + raise Exception('For testing the merge crash handler.') + + a_schedule = LoadWithoutErrors(old_feed_path, options.memory_db) + b_schedule = LoadWithoutErrors(new_feed_path, options.memory_db) + merged_schedule = transitfeed.Schedule(memory_db=options.memory_db) + problem_reporter = HTMLProblemReporter() + feed_merger = FeedMerger(a_schedule, b_schedule, merged_schedule, + problem_reporter) + feed_merger.AddDefaultMergers() + + feed_merger.GetMerger(StopMerger).SetLargestStopDistance(float( + options.largest_stop_distance)) + feed_merger.GetMerger(ShapeMerger).SetLargestShapeDistance(float( + options.largest_shape_distance)) + + if options.cutoff_date is not None: + service_period_merger = feed_merger.GetMerger(ServicePeriodMerger) + service_period_merger.DisjoinCalendars(options.cutoff_date) + + if feed_merger.MergeSchedules(): + feed_merger.GetMergedSchedule().WriteGoogleTransitFeed(merged_feed_path) + else: + merged_feed_path = None + + output_file = file(options.html_output_path, 'w') + problem_reporter.WriteOutput(output_file, feed_merger, + old_feed_path, new_feed_path, merged_feed_path) + output_file.close() + + if not options.no_browser: + webbrowser.open('file://%s' % os.path.abspath(options.html_output_path)) + + +if __name__ == '__main__': + util.RunWithCrashHandler(main) + --- /dev/null +++ b/origin-src/transitfeed-1.2.5/build/scripts-2.6/schedule_viewer.py @@ -1,1 +1,524 @@ - +#!/usr/bin/python + +# Copyright (C) 2007 Google Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +""" +An example application that uses the transitfeed module. + +You must provide a Google Maps API key. +""" + + +import BaseHTTPServer, sys, urlparse +import bisect +from gtfsscheduleviewer.marey_graph import MareyGraph +import gtfsscheduleviewer +import mimetypes +import os.path +import re +import signal +import simplejson +import socket +import time +import transitfeed +from transitfeed import util +import urllib + + +# By default Windows kills Python with Ctrl+Break. Instead make Ctrl+Break +# raise a KeyboardInterrupt. +if hasattr(signal, 'SIGBREAK'): + signal.signal(signal.SIGBREAK, signal.default_int_handler) + + +mimetypes.add_type('text/plain', '.vbs') + + +class ResultEncoder(simplejson.JSONEncoder): + def default(self, obj): + try: + iterable = iter(obj) + except TypeError: + pass + else: + return list(iterable) + return simplejson.JSONEncoder.default(self, obj) + +# Code taken from +# http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/425210/index_txt +# An alternate approach is shown at +# http://mail.python.org/pipermail/python-list/2003-July/212751.html +# but it requires multiple threads. A sqlite object can only be used from one +# thread. +class StoppableHTTPServer(BaseHTTPServer.HTTPServer): + def server_bind(self): + BaseHTTPServer.HTTPServer.server_bind(self) + self.socket.settimeout(1) + self._run = True + + def get_request(self): + while self._run: + try: + sock, addr = self.socket.accept() + sock.settimeout(None) + return (sock, addr) + except socket.timeout: + pass + + def stop(self): + self._run = False + + def serve(self): + while self._run: + self.handle_request() + + +def StopToTuple(stop): + """Return tuple as expected by javascript function addStopMarkerFromList""" + return (stop.stop_id, stop.stop_name, float(stop.stop_lat), + float(stop.stop_lon), stop.location_type) + + +class ScheduleRequestHandler(BaseHTTPServer.BaseHTTPRequestHandler): + def do_GET(self): + scheme, host, path, x, params, fragment = urlparse.urlparse(self.path) + parsed_params = {} + for k in params.split('&'): + k = urllib.unquote(k) + if '=' in k: + k, v = k.split('=', 1) + parsed_params[k] = unicode(v, 'utf8') + else: + parsed_params[k] = '' + + if path == '/': + return self.handle_GET_home() + + m = re.match(r'/json/([a-z]{1,64})', path) + if m: + handler_name = 'handle_json_GET_%s' % m.group(1) + handler = getattr(self, handler_name, None) + if callable(handler): + return self.handle_json_wrapper_GET(handler, parsed_params) + + # Restrict allowable file names to prevent relative path attacks etc + m = re.match(r'/file/([a-z0-9_-]{1,64}\.?[a-z0-9_-]{1,64})$', path) + if m and m.group(1): + try: + f, mime_type = self.OpenFile(m.group(1)) + return self.handle_static_file_GET(f, mime_type) + except IOError, e: + print "Error: unable to open %s" % m.group(1) + # Ignore and treat as 404 + + m = re.match(r'/([a-z]{1,64})', path) + if m: + handler_name = 'handle_GET_%s' % m.group(1) + handler = getattr(self, handler_name, None) + if callable(handler): + return handler(parsed_params) + + return self.handle_GET_default(parsed_params, path) + + def OpenFile(self, filename): + """Try to open filename in the static files directory of this server. + Return a tuple (file object, string mime_type) or raise an exception.""" + (mime_type, encoding) = mimetypes.guess_type(filename) + assert mime_type + # A crude guess of when we should use binary mode. Without it non-unix + # platforms may corrupt binary files. + if mime_type.startswith('text/'): + mode = 'r' + else: + mode = 'rb' + return open(os.path.join(self.server.file_dir, filename), mode), mime_type + + def handle_GET_default(self, parsed_params, path): + self.send_error(404) + + def handle_static_file_GET(self, fh, mime_type): + content = fh.read() + self.send_response(200) + self.send_header('Content-Type', mime_type) + self.send_header('Content-Length', str(len(content))) + self.end_headers() + self.wfile.write(content) + + def AllowEditMode(self): + return False + + def handle_GET_home(self): + schedule = self.server.schedule + (min_lat, min_lon, max_lat, max_lon) = schedule.GetStopBoundingBox() + forbid_editing = ('true', 'false')[self.AllowEditMode()] + + agency = ', '.join(a.agency_name for a in schedule.GetAgencyList()).encode('utf-8') + + key = self.server.key + host = self.server.host + + # A very simple template system. For a fixed set of values replace [xxx] + # with the value of local variable xxx + f, _ = self.OpenFile('index.html') + content = f.read() + for v in ('agency', 'min_lat', 'min_lon', 'max_lat', 'max_lon', 'key', + 'host', 'forbid_editing'): + content = content.replace('[%s]' % v, str(locals()[v])) + + self.send_response(200) + self.send_header('Content-Type', 'text/html') + self.send_header('Content-Length', str(len(content))) + self.end_headers() + self.wfile.write(content) + + def handle_json_GET_routepatterns(self, params): + """Given a route_id generate a list of patterns of the route. For each + pattern include some basic information and a few sample trips.""" + schedule = self.server.schedule + route = schedule.GetRoute(params.get('route', None)) + if not route: + self.send_error(404) + return + time = int(params.get('time', 0)) + sample_size = 3 # For each pattern return the start time for this many trips + + pattern_id_trip_dict = route.GetPatternIdTripDict() + patterns = [] + + for pattern_id, trips in pattern_id_trip_dict.items(): + time_stops = trips[0].GetTimeStops() + if not time_stops: + continue + has_non_zero_trip_type = False; + for trip in trips: + if trip['trip_type'] and trip['trip_type'] != '0': + has_non_zero_trip_type = True + name = u'%s to %s, %d stops' % (time_stops[0][2].stop_name, time_stops[-1][2].stop_name, len(time_stops)) + transitfeed.SortListOfTripByTime(trips) + + num_trips = len(trips) + if num_trips <= sample_size: + start_sample_index = 0 + num_after_sample = 0 + else: + # Will return sample_size trips that start after the 'time' param. + + # Linear search because I couldn't find a built-in way to do a binary + # search with a custom key. + start_sample_index = len(trips) + for i, trip in enumerate(trips): + if trip.GetStartTime() >= time: + start_sample_index = i + break + + num_after_sample = num_trips - (start_sample_index + sample_size) + if num_after_sample < 0: + # Less than sample_size trips start after 'time' so return all the + # last sample_size trips. + num_after_sample = 0 + start_sample_index = num_trips - sample_size + + sample = [] + for t in trips[start_sample_index:start_sample_index + sample_size]: + sample.append( (t.GetStartTime(), t.trip_id) ) + + patterns.append((name, pattern_id, start_sample_index, sample, + num_after_sample, (0,1)[has_non_zero_trip_type])) + + patterns.sort() + return patterns + + def handle_json_wrapper_GET(self, handler, parsed_params): + """Call handler and output the return value in JSON.""" + schedule = self.server.schedule + result = handler(parsed_params) + content = ResultEncoder().encode(result) + self.send_response(200) + self.send_header('Content-Type', 'text/plain') + self.send_header('Content-Length', str(len(content))) + self.end_headers() + self.wfile.write(content) + + def handle_json_GET_routes(self, params): + """Return a list of all routes.""" + schedule = self.server.schedule + result = [] + for r in schedule.GetRouteList(): + result.append( (r.route_id, r.route_short_name, r.route_long_name) ) + result.sort(key = lambda x: x[1:3]) + return result + + def handle_json_GET_routerow(self, params): + schedule = self.server.schedule + route = schedule.GetRoute(params.get('route', None)) + return [transitfeed.Route._FIELD_NAMES, route.GetFieldValuesTuple()] + + def handle_json_GET_triprows(self, params): + """Return a list of rows from the feed file that are related to this + trip.""" + schedule = self.server.schedule + try: + trip = schedule.GetTrip(params.get('trip', None)) + except KeyError: + # if a non-existent trip is searched for, the return nothing + return + route = schedule.GetRoute(trip.route_id) + trip_row = dict(trip.iteritems()) + route_row = dict(route.iteritems()) + return [['trips.txt', trip_row], ['routes.txt', route_row]] + + def handle_json_GET_tripstoptimes(self, params): + schedule = self.server.schedule + try: + trip = schedule.GetTrip(params.get('trip')) + except KeyError: + # if a non-existent trip is searched for, the return nothing + return + time_stops = trip.GetTimeStops() + stops = [] + times = [] + for arr,dep,stop in time_stops: + stops.append(StopToTuple(stop)) + times.append(arr) + return [stops, times] + + def handle_json_GET_tripshape(self, params): + schedule = self.server.schedule + try: + trip = schedule.GetTrip(params.get('trip')) + except KeyError: + # if a non-existent trip is searched for, the return nothing + return + points = [] + if trip.shape_id: + shape = schedule.GetShape(trip.shape_id) + for (lat, lon, dist) in shape.points: + points.append((lat, lon)) + else: + time_stops = trip.GetTimeStops() + for arr,dep,stop in time_stops: + points.append((stop.stop_lat, stop.stop_lon)) + return points + + def handle_json_GET_neareststops(self, params): + """Return a list of the nearest 'limit' stops to 'lat', 'lon'""" + schedule = self.server.schedule + lat = float(params.get('lat')) + lon = float(params.get('lon')) + limit = int(params.get('limit')) + stops = schedule.GetNearestStops(lat=lat, lon=lon, n=limit) + return [StopToTuple(s) for s in stops] + + def handle_json_GET_boundboxstops(self, params): + """Return a list of up to 'limit' stops within bounding box with 'n','e' + and 's','w' in the NE and SW corners. Does not handle boxes crossing + longitude line 180.""" + schedule = self.server.schedule + n = float(params.get('n')) + e = float(params.get('e')) + s = float(params.get('s')) + w = float(params.get('w')) + limit = int(params.get('limit')) + stops = schedule.GetStopsInBoundingBox(north=n, east=e, south=s, west=w, n=limit) + return [StopToTuple(s) for s in stops] + + def handle_json_GET_stopsearch(self, params): + schedule = self.server.schedule + query = params.get('q', None).lower() + matches = [] + for s in schedule.GetStopList(): + if s.stop_id.lower().find(query) != -1 or s.stop_name.lower().find(query) != -1: + matches.append(StopToTuple(s)) + return matches + + def handle_json_GET_stoptrips(self, params): + """Given a stop_id and time in seconds since midnight return the next + trips to visit the stop.""" + schedule = self.server.schedule + stop = schedule.GetStop(params.get('stop', None)) + time = int(params.get('time', 0)) + time_trips = stop.GetStopTimeTrips(schedule) + time_trips.sort() # OPT: use bisect.insort to make this O(N*ln(N)) -> O(N) + # Keep the first 5 after param 'time'. + # Need make a tuple to find correct bisect point + time_trips = time_trips[bisect.bisect_left(time_trips, (time, 0)):] + time_trips = time_trips[:5] + # TODO: combine times for a route to show next 2 departure times + result = [] + for time, (trip, index), tp in time_trips: + headsign = None + # Find the most recent headsign from the StopTime objects + for stoptime in trip.GetStopTimes()[index::-1]: + if stoptime.stop_headsign: + headsign = stoptime.stop_headsign + break + # If stop_headsign isn't found, look for a trip_headsign + if not headsign: + headsign = trip.trip_headsign + route = schedule.GetRoute(trip.route_id) + trip_name = '' + if route.route_short_name: + trip_name += route.route_short_name + if route.route_long_name: + if len(trip_name): + trip_name += " - " + trip_name += route.route_long_name + if headsign: + trip_name += " (Direction: %s)" % headsign + + result.append((time, (trip.trip_id, trip_name, trip.service_id), tp)) + return result + + def handle_GET_ttablegraph(self,params): + """Draw a Marey graph in SVG for a pattern (collection of trips in a route + that visit the same sequence of stops).""" + schedule = self.server.schedule + marey = MareyGraph() + trip = schedule.GetTrip(params.get('trip', None)) + route = schedule.GetRoute(trip.route_id) + height = int(params.get('height', 300)) + + if not route: + print 'no such route' + self.send_error(404) + return + + pattern_id_trip_dict = route.GetPatternIdTripDict() + pattern_id = trip.pattern_id + if pattern_id not in pattern_id_trip_dict: + print 'no pattern %s found in %s' % (pattern_id, pattern_id_trip_dict.keys()) + self.send_error(404) + return + triplist = pattern_id_trip_dict[pattern_id] + + pattern_start_time = min((t.GetStartTime() for t in triplist)) + pattern_end_time = max((t.GetEndTime() for t in triplist)) + + marey.SetSpan(pattern_start_time,pattern_end_time) + marey.Draw(triplist[0].GetPattern(), triplist, height) + + content = marey.Draw() + + self.send_response(200) + self.send_header('Content-Type', 'image/svg+xml') + self.send_header('Content-Length', str(len(content))) + self.end_headers() + self.wfile.write(content) + + +def FindPy2ExeBase(): + """If this is running in py2exe return the install directory else return + None""" + # py2exe puts gtfsscheduleviewer in library.zip. For py2exe setup.py is + # configured to put the data next to library.zip. + windows_ending = gtfsscheduleviewer.__file__.find('\\library.zip\\') + if windows_ending != -1: + return transitfeed.__file__[:windows_ending] + else: + return None + + +def FindDefaultFileDir(): + """Return the path of the directory containing the static files. By default + the directory is called 'files'. The location depends on where setup.py put + it.""" + base = FindPy2ExeBase() + if base: + return os.path.join(base, 'schedule_viewer_files') + else: + # For all other distributions 'files' is in the gtfsscheduleviewer + # directory. + base = os.path.dirname(gtfsscheduleviewer.__file__) # Strip __init__.py + return os.path.join(base, 'files') + + +def GetDefaultKeyFilePath(): + """In py2exe return absolute path of file in the base directory and in all + other distributions return relative path 'key.txt'""" + windows_base = FindPy2ExeBase() + if windows_base: + return os.path.join(windows_base, 'key.txt') + else: + return 'key.txt' + + +def main(RequestHandlerClass = ScheduleRequestHandler): + usage = \ +'''%prog [options] [] + +Runs a webserver that lets you explore a in your browser. + +If is omited the filename is read from the console. Dragging +a file into the console may enter the filename. +''' + parser = util.OptionParserLongError( + usage=usage, version='%prog '+transitfeed.__version__) + parser.add_option('--feed_filename', '--feed', dest='feed_filename', + help='file name of feed to load') + parser.add_option('--key', dest='key', + help='Google Maps API key or the name ' + 'of a text file that contains an API key') + parser.add_option('--host', dest='host', help='Host name of Google Maps') + parser.add_option('--port', dest='port', type='int', + help='port on which to listen') + parser.add_option('--file_dir', dest='file_dir', + help='directory containing static files') + parser.add_option('-n', '--noprompt', action='store_false', + dest='manual_entry', + help='disable interactive prompts') + parser.set_defaults(port=8765, + host='maps.google.com', + file_dir=FindDefaultFileDir(), + manual_entry=True) + (options, args) = parser.parse_args() + + if not os.path.isfile(os.path.join(options.file_dir, 'index.html')): + print "Can't find index.html with --file_dir=%s" % options.file_dir + exit(1) + + if not options.feed_filename and len(args) == 1: + options.feed_filename = args[0] + + if not options.feed_filename and options.manual_entry: + options.feed_filename = raw_input('Enter Feed Location: ').strip('"') + + default_key_file = GetDefaultKeyFilePath() + if not options.key and os.path.isfile(default_key_file): + options.key = open(default_key_file).read().strip() + + if options.key and os.path.isfile(options.key): + options.key = open(options.key).read().strip() + + schedule = transitfeed.Schedule(problem_reporter=transitfeed.ProblemReporter()) + print 'Loading data from feed "%s"...' % options.feed_filename + print '(this may take a few minutes for larger cities)' + schedule.Load(options.feed_filename) + + server = StoppableHTTPServer(server_address=('', options.port), + RequestHandlerClass=RequestHandlerClass) + server.key = options.key + server.schedule = schedule + server.file_dir = options.file_dir + server.host = options.host + server.feed_path = options.feed_filename + + print ("To view, point your browser at http://localhost:%d/" % + (server.server_port)) + server.serve_forever() + + +if __name__ == '__main__': + main() + --- /dev/null +++ b/origin-src/transitfeed-1.2.5/build/scripts-2.6/shape_importer.py @@ -1,1 +1,291 @@ - +#!/usr/bin/python +# +# Copyright 2007 Google Inc. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +"""A utility program to help add shapes to an existing GTFS feed. + +Requires the ogr python package. +""" + +__author__ = 'chris.harrelson.code@gmail.com (Chris Harrelson)' + +import csv +import glob +import ogr +import os +import shutil +import sys +import tempfile +import transitfeed +from transitfeed import shapelib +from transitfeed import util +import zipfile + + +class ShapeImporterError(Exception): + pass + + +def PrintColumns(shapefile): + """ + Print the columns of layer 0 of the shapefile to the screen. + """ + ds = ogr.Open(shapefile) + layer = ds.GetLayer(0) + if len(layer) == 0: + raise ShapeImporterError("Layer 0 has no elements!") + + feature = layer.GetFeature(0) + print "%d features" % feature.GetFieldCount() + for j in range(0, feature.GetFieldCount()): + print '--' + feature.GetFieldDefnRef(j).GetName() + \ + ': ' + feature.GetFieldAsString(j) + + +def AddShapefile(shapefile, graph, key_cols): + """ + Adds shapes found in the given shape filename to the given polyline + graph object. + """ + ds = ogr.Open(shapefile) + layer = ds.GetLayer(0) + + for i in range(0, len(layer)): + feature = layer.GetFeature(i) + + geometry = feature.GetGeometryRef() + + if key_cols: + key_list = [] + for col in key_cols: + key_list.append(str(feature.GetField(col))) + shape_id = '-'.join(key_list) + else: + shape_id = '%s-%d' % (shapefile, i) + + poly = shapelib.Poly(name=shape_id) + for j in range(0, geometry.GetPointCount()): + (lat, lng) = (round(geometry.GetY(j), 15), round(geometry.GetX(j), 15)) + poly.AddPoint(shapelib.Point.FromLatLng(lat, lng)) + graph.AddPoly(poly) + + return graph + + +def GetMatchingShape(pattern_poly, trip, matches, max_distance, verbosity=0): + """ + Tries to find a matching shape for the given pattern Poly object, + trip, and set of possibly matching Polys from which to choose a match. + """ + if len(matches) == 0: + print ('No matching shape found within max-distance %d for trip %s ' + % (max_distance, trip.trip_id)) + return None + + if verbosity >= 1: + for match in matches: + print "match: size %d" % match.GetNumPoints() + scores = [(pattern_poly.GreedyPolyMatchDist(match), match) + for match in matches] + + scores.sort() + + if scores[0][0] > max_distance: + print ('No matching shape found within max-distance %d for trip %s ' + '(min score was %f)' + % (max_distance, trip.trip_id, scores[0][0])) + return None + + return scores[0][1] + +def AddExtraShapes(extra_shapes_txt, graph): + """ + Add extra shapes into our input set by parsing them out of a GTFS-formatted + shapes.txt file. Useful for manually adding lines to a shape file, since it's + a pain to edit .shp files. + """ + + print "Adding extra shapes from %s" % extra_shapes_txt + try: + tmpdir = tempfile.mkdtemp() + shutil.copy(extra_shapes_txt, os.path.join(tmpdir, 'shapes.txt')) + loader = transitfeed.ShapeLoader(tmpdir) + schedule = loader.Load() + for shape in schedule.GetShapeList(): + print "Adding extra shape: %s" % shape.shape_id + graph.AddPoly(ShapeToPoly(shape)) + finally: + if tmpdir: + shutil.rmtree(tmpdir) + + +# Note: this method lives here to avoid cross-dependencies between +# shapelib and transitfeed. +def ShapeToPoly(shape): + poly = shapelib.Poly(name=shape.shape_id) + for lat, lng, distance in shape.points: + point = shapelib.Point.FromLatLng(round(lat, 15), round(lng, 15)) + poly.AddPoint(point) + return poly + + +def ValidateArgs(options_parser, options, args): + if not (args and options.source_gtfs and options.dest_gtfs): + options_parser.error("You must specify a source and dest GTFS file, " + "and at least one source shapefile") + + +def DefineOptions(): + usage = \ +"""%prog [options] --source_gtfs= --dest_gtfs=\ + [...] + +Try to match shapes in one or more SHP files to trips in a GTFS file.""" + options_parser = util.OptionParserLongError( + usage=usage, version='%prog '+transitfeed.__version__) + options_parser.add_option("--print_columns", + action="store_true", + default=False, + dest="print_columns", + help="Print column names in shapefile DBF and exit") + options_parser.add_option("--keycols", + default="", + dest="keycols", + help="Comma-separated list of the column names used" + "to index shape ids") + options_parser.add_option("--max_distance", + type="int", + default=150, + dest="max_distance", + help="Max distance from a shape to which to match") + options_parser.add_option("--source_gtfs", + default="", + dest="source_gtfs", + metavar="FILE", + help="Read input GTFS from FILE") + options_parser.add_option("--dest_gtfs", + default="", + dest="dest_gtfs", + metavar="FILE", + help="Write output GTFS with shapes to FILE") + options_parser.add_option("--extra_shapes", + default="", + dest="extra_shapes", + metavar="FILE", + help="Extra shapes.txt (CSV) formatted file") + options_parser.add_option("--verbosity", + type="int", + default=0, + dest="verbosity", + help="Verbosity level. Higher is more verbose") + return options_parser + + +def main(key_cols): + print 'Parsing shapefile(s)...' + graph = shapelib.PolyGraph() + for arg in args: + print ' ' + arg + AddShapefile(arg, graph, key_cols) + + if options.extra_shapes: + AddExtraShapes(options.extra_shapes, graph) + + print 'Loading GTFS from %s...' % options.source_gtfs + schedule = transitfeed.Loader(options.source_gtfs).Load() + shape_count = 0 + pattern_count = 0 + + verbosity = options.verbosity + + print 'Matching shapes to trips...' + for route in schedule.GetRouteList(): + print 'Processing route', route.route_short_name + patterns = route.GetPatternIdTripDict() + for pattern_id, trips in patterns.iteritems(): + pattern_count += 1 + pattern = trips[0].GetPattern() + + poly_points = [shapelib.Point.FromLatLng(p.stop_lat, p.stop_lon) + for p in pattern] + if verbosity >= 2: + print "\npattern %d, %d points:" % (pattern_id, len(poly_points)) + for i, (stop, point) in enumerate(zip(pattern, poly_points)): + print "Stop %d '%s': %s" % (i + 1, stop.stop_name, point.ToLatLng()) + + # First, try to find polys that run all the way from + # the start of the trip to the end. + matches = graph.FindMatchingPolys(poly_points[0], poly_points[-1], + options.max_distance) + if not matches: + # Try to find a path through the graph, joining + # multiple edges to find a path that covers all the + # points in the trip. Some shape files are structured + # this way, with a polyline for each segment between + # stations instead of a polyline covering an entire line. + shortest_path = graph.FindShortestMultiPointPath(poly_points, + options.max_distance, + verbosity=verbosity) + if shortest_path: + matches = [shortest_path] + else: + matches = [] + + pattern_poly = shapelib.Poly(poly_points) + shape_match = GetMatchingShape(pattern_poly, trips[0], + matches, options.max_distance, + verbosity=verbosity) + if shape_match: + shape_count += 1 + # Rename shape for readability. + shape_match = shapelib.Poly(points=shape_match.GetPoints(), + name="shape_%d" % shape_count) + for trip in trips: + try: + shape = schedule.GetShape(shape_match.GetName()) + except KeyError: + shape = transitfeed.Shape(shape_match.GetName()) + for point in shape_match.GetPoints(): + (lat, lng) = point.ToLatLng() + shape.AddPoint(lat, lng) + schedule.AddShapeObject(shape) + trip.shape_id = shape.shape_id + + print "Matched %d shapes out of %d patterns" % (shape_count, pattern_count) + schedule.WriteGoogleTransitFeed(options.dest_gtfs) + + +if __name__ == '__main__': + # Import psyco if available for better performance. + try: + import psyco + psyco.full() + except ImportError: + pass + + options_parser = DefineOptions() + (options, args) = options_parser.parse_args() + + ValidateArgs(options_parser, options, args) + + if options.print_columns: + for arg in args: + PrintColumns(arg) + sys.exit(0) + + key_cols = options.keycols.split(',') + + main(key_cols) + --- /dev/null +++ b/origin-src/transitfeed-1.2.5/build/scripts-2.6/unusual_trip_filter.py @@ -1,1 +1,157 @@ +#!/usr/bin/python +# Copyright (C) 2007 Google Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +""" +Filters out trips which are not on the defualt routes and + set their trip_typeattribute accordingly. + +For usage information run unusual_trip_filter.py --help +""" + +__author__ = 'Jiri Semecky ' + +import codecs +import os +import os.path +import sys +import time +import transitfeed +from transitfeed import util + + +class UnusualTripFilter(object): + """Class filtering trips going on unusual paths. + + Those are usually trips going to/from depot or changing to another route + in the middle. Sets the 'trip_type' attribute of the trips.txt dataset + so that non-standard trips are marked as special (value 1) + instead of regular (default value 0). + """ + + def __init__ (self, threshold=0.1, force=False, quiet=False, route_type=None): + self._threshold = threshold + self._quiet = quiet + self._force = force + if route_type in transitfeed.Route._ROUTE_TYPE_NAMES: + self._route_type = transitfeed.Route._ROUTE_TYPE_NAMES[route_type] + elif route_type is None: + self._route_type = None + else: + self._route_type = int(route_type) + + def filter_line(self, route): + """Mark unusual trips for the given route.""" + if self._route_type is not None and self._route_type != route.route_type: + self.info('Skipping route %s due to different route_type value (%s)' % + (route['route_id'], route['route_type'])) + return + self.info('Filtering infrequent trips for route %s.' % route.route_id) + trip_count = len(route.trips) + for pattern_id, pattern in route.GetPatternIdTripDict().items(): + ratio = float(1.0 * len(pattern) / trip_count) + if not self._force: + if (ratio < self._threshold): + self.info("\t%d trips on route %s with headsign '%s' recognized " + "as unusual (ratio %f)" % + (len(pattern), + route['route_short_name'], + pattern[0]['trip_headsign'], + ratio)) + for trip in pattern: + trip.trip_type = 1 # special + self.info("\t\tsetting trip_type of trip %s as special" % + trip.trip_id) + else: + self.info("\t%d trips on route %s with headsign '%s' recognized " + "as %s (ratio %f)" % + (len(pattern), + route['route_short_name'], + pattern[0]['trip_headsign'], + ('regular', 'unusual')[ratio < self._threshold], + ratio)) + for trip in pattern: + trip.trip_type = ('0','1')[ratio < self._threshold] + self.info("\t\tsetting trip_type of trip %s as %s" % + (trip.trip_id, + ('regular', 'unusual')[ratio < self._threshold])) + + def filter(self, dataset): + """Mark unusual trips for all the routes in the dataset.""" + self.info('Going to filter infrequent routes in the dataset') + for route in dataset.routes.values(): + self.filter_line(route) + + def info(self, text): + if not self._quiet: + print text.encode("utf-8") + + +def main(): + usage = \ +'''%prog [options] + +Filters out trips which do not follow the most common stop sequences and +sets their trip_type attribute accordingly. is overwritten with +the modifed GTFS file unless the --output option is used. +''' + parser = util.OptionParserLongError( + usage=usage, version='%prog '+transitfeed.__version__) + parser.add_option('-o', '--output', dest='output', metavar='FILE', + help='Name of the output GTFS file (writing to input feed if omitted).') + parser.add_option('-m', '--memory_db', dest='memory_db', action='store_true', + help='Force use of in-memory sqlite db.') + parser.add_option('-t', '--threshold', default=0.1, + dest='threshold', type='float', + help='Frequency threshold for considering pattern as non-regular.') + parser.add_option('-r', '--route_type', default=None, + dest='route_type', type='string', + help='Filter only selected route type (specified by number' + 'or one of the following names: ' + \ + ', '.join(transitfeed.Route._ROUTE_TYPE_NAMES) + ').') + parser.add_option('-f', '--override_trip_type', default=False, + dest='override_trip_type', action='store_true', + help='Forces overwrite of current trip_type values.') + parser.add_option('-q', '--quiet', dest='quiet', + default=False, action='store_true', + help='Suppress information output.') + + (options, args) = parser.parse_args() + if len(args) != 1: + parser.error('You must provide the path of a single feed.') + + filter = UnusualTripFilter(float(options.threshold), + force=options.override_trip_type, + quiet=options.quiet, + route_type=options.route_type) + feed_name = args[0] + feed_name = feed_name.strip() + filter.info('Loading %s' % feed_name) + loader = transitfeed.Loader(feed_name, extra_validation=True, + memory_db=options.memory_db) + data = loader.Load() + filter.filter(data) + print 'Saving data' + + # Write the result + if options.output is None: + data.WriteGoogleTransitFeed(feed_name) + else: + data.WriteGoogleTransitFeed(options.output) + + +if __name__ == '__main__': + util.RunWithCrashHandler(main) + --- /dev/null +++ b/origin-src/transitfeed-1.2.5/examples/filter_unused_stops.py @@ -1,1 +1,63 @@ +#!/usr/bin/python2.5 +# Copyright (C) 2007 Google Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + + +"""Filter the unused stops out of a transit feed file.""" + +import optparse +import sys +import transitfeed + + +def main(): + parser = optparse.OptionParser( + usage="usage: %prog [options] input_feed output_feed", + version="%prog "+transitfeed.__version__) + parser.add_option("-l", "--list_removed", dest="list_removed", + default=False, + action="store_true", + help="Print removed stops to stdout") + (options, args) = parser.parse_args() + if len(args) != 2: + print >>sys.stderr, parser.format_help() + print >>sys.stderr, "\n\nYou must provide input_feed and output_feed\n\n" + sys.exit(2) + input_path = args[0] + output_path = args[1] + + loader = transitfeed.Loader(input_path) + schedule = loader.Load() + + print "Removing unused stops..." + removed = 0 + for stop_id, stop in schedule.stops.items(): + if not stop.GetTrips(schedule): + removed += 1 + del schedule.stops[stop_id] + if options.list_removed: + print "Removing %s (%s)" % (stop_id, stop.stop_name) + if removed == 0: + print "No unused stops." + elif removed == 1: + print "Removed 1 stop" + else: + print "Removed %d stops" % removed + + schedule.WriteGoogleTransitFeed(output_path) + +if __name__ == "__main__": + main() + --- /dev/null +++ b/origin-src/transitfeed-1.2.5/examples/google_random_queries.py @@ -1,1 +1,225 @@ - +#!/usr/bin/python2.5 + +# Copyright (C) 2007 Google Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + + +"""Output Google Transit URLs for queries near stops. + +The output can be used to speed up manual testing. Load the output from this +file and then open many of the links in new tabs. In each result check that the +polyline looks okay (no unnecassary loops, no jumps to a far away location) and +look at the time of each leg. Also check the route names and headsigns are +formatted correctly and not redundant. +""" + +from datetime import datetime +from datetime import timedelta +import math +import optparse +import os.path +import random +import sys +import transitfeed +import urllib +import urlparse + + +def Distance(lat0, lng0, lat1, lng1): + """ + Compute the geodesic distance in meters between two points on the + surface of the Earth. The latitude and longitude angles are in + degrees. + + Approximate geodesic distance function (Haversine Formula) assuming + a perfect sphere of radius 6367 km (see "What are some algorithms + for calculating the distance between 2 points?" in the GIS Faq at + http://www.census.gov/geo/www/faq-index.html). The approximate + radius is adequate for our needs here, but a more sophisticated + geodesic function should be used if greater accuracy is required + (see "When is it NOT okay to assume the Earth is a sphere?" in the + same faq). + """ + deg2rad = math.pi / 180.0 + lat0 = lat0 * deg2rad + lng0 = lng0 * deg2rad + lat1 = lat1 * deg2rad + lng1 = lng1 * deg2rad + dlng = lng1 - lng0 + dlat = lat1 - lat0 + a = math.sin(dlat*0.5) + b = math.sin(dlng*0.5) + a = a * a + math.cos(lat0) * math.cos(lat1) * b * b + c = 2.0 * math.atan2(math.sqrt(a), math.sqrt(1.0 - a)) + return 6367000.0 * c + + +def AddNoiseToLatLng(lat, lng): + """Add up to 500m of error to each coordinate of lat, lng.""" + m_per_tenth_lat = Distance(lat, lng, lat + 0.1, lng) + m_per_tenth_lng = Distance(lat, lng, lat, lng + 0.1) + lat_per_100m = 1 / m_per_tenth_lat * 10 + lng_per_100m = 1 / m_per_tenth_lng * 10 + return (lat + (lat_per_100m * 5 * (random.random() * 2 - 1)), + lng + (lng_per_100m * 5 * (random.random() * 2 - 1))) + + +def GetRandomLocationsNearStops(schedule): + """Return a list of (lat, lng) tuples.""" + locations = [] + for s in schedule.GetStopList(): + locations.append(AddNoiseToLatLng(s.stop_lat, s.stop_lon)) + return locations + + +def GetRandomDatetime(): + """Return a datetime in the next week.""" + seconds_offset = random.randint(0, 60 * 60 * 24 * 7) + dt = datetime.today() + timedelta(seconds=seconds_offset) + return dt.replace(second=0, microsecond=0) + + +def FormatLatLng(lat_lng): + """Format a (lat, lng) tuple into a string for maps.google.com.""" + return "%0.6f,%0.6f" % lat_lng + + +def LatLngsToGoogleUrl(source, destination, dt): + """Return a URL for routing between two (lat, lng) at a datetime.""" + params = {"saddr": FormatLatLng(source), + "daddr": FormatLatLng(destination), + "time": dt.strftime("%I:%M%p"), + "date": dt.strftime("%Y-%m-%d"), + "dirflg": "r", + "ie": "UTF8", + "oe": "UTF8"} + url = urlparse.urlunsplit(("http", "maps.google.com", "/maps", + urllib.urlencode(params), "")) + return url + + +def LatLngsToGoogleLink(source, destination): + """Return a string "from:%s to:%s on %s" % ( + LatLngsToGoogleUrl(source, destination, dt), + FormatLatLng(source), FormatLatLng(destination), + dt.ctime()) + + +def WriteOutput(title, locations, limit, f): + """Write html to f for up to limit trips between locations. + + Args: + title: String used in html title + locations: list of (lat, lng) tuples + limit: maximum number of queries in the html + f: a file object + """ + output_prefix = """ + + + +%(title)s + + +Random queries for %(title)s

    +This list of random queries should speed up important manual testing. Here are +some things to check when looking at the results of a query. +

      +
    • Check the agency attribution under the trip results: +
        +
      • has correct name and spelling of the agency +
      • opens a page with general information about the service +
      +
    • For each alternate trip check that each of these is reasonable: +
        +
      • the total time of the trip +
      • the time for each leg. Bad data frequently results in a leg going a long + way in a few minutes. +
      • the icons and mode names (Tram, Bus, etc) are correct for each leg +
      • the route names and headsigns are correctly formatted and not + redundant. + For a good example see the + screenshots in the Google Transit Feed Specification. +
      • the shape line on the map looks correct. Make sure the polyline does + not zig-zag, loop, skip stops or jump far away unless the trip does the + same thing. +
      • the route is active on the day the trip planner returns +
      +
    +If you find a problem be sure to save the URL. This file is generated randomly. +
      +""" % locals() + + output_suffix = """ +
    + + +""" % locals() + + f.write(transitfeed.EncodeUnicode(output_prefix)) + for source, destination in zip(locations[0:limit], locations[1:limit + 1]): + f.write(transitfeed.EncodeUnicode("
  • %s\n" % + LatLngsToGoogleLink(source, destination))) + f.write(transitfeed.EncodeUnicode(output_suffix)) + + +def ParentAndBaseName(path): + """Given a path return only the parent name and file name as a string.""" + dirname, basename = os.path.split(path) + dirname = dirname.rstrip(os.path.sep) + if os.path.altsep: + dirname = dirname.rstrip(os.path.altsep) + _, parentname = os.path.split(dirname) + return os.path.join(parentname, basename) + + +def main(): + parser = optparse.OptionParser( + usage="usage: %prog [options] feed_filename output_filename", + version="%prog "+transitfeed.__version__) + parser.add_option("-l", "--limit", dest="limit", type="int", + help="Maximum number of URLs to generate") + parser.add_option('-o', '--output', dest='output', metavar='FILE', + help='write html output to FILE') + parser.set_defaults(output="google_random_queries.html", limit=50) + (options, args) = parser.parse_args() + if len(args) != 1: + print >>sys.stderr, parser.format_help() + print >>sys.stderr, "\n\nYou must provide the path of a single feed\n\n" + sys.exit(2) + feed_path = args[0] + + # ProblemReporter prints problems on console. + loader = transitfeed.Loader(feed_path, problems=transitfeed.ProblemReporter(), + load_stop_times=False) + schedule = loader.Load() + locations = GetRandomLocationsNearStops(schedule) + random.shuffle(locations) + agencies = ", ".join([a.agency_name for a in schedule.GetAgencyList()]) + title = "%s (%s)" % (agencies, ParentAndBaseName(feed_path)) + + WriteOutput(title, + locations, + options.limit, + open(options.output, "w")) + print ("Load %s in your web browser. It contains more instructions." % + options.output) + + +if __name__ == "__main__": + main() + --- /dev/null +++ b/origin-src/transitfeed-1.2.5/examples/shuttle_from_xmlfeed.py @@ -1,1 +1,134 @@ +#!/usr/bin/python2.5 +# Copyright (C) 2007 Google Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +"""Google has a homegrown database for managing the company shuttle. The +database dumps its contents in XML. This scripts converts the proprietary XML +format into a Google Transit Feed Specification file. +""" + +import datetime +from optparse import OptionParser +import os.path +import re +import transitfeed +import urllib + +try: + import xml.etree.ElementTree as ET # python 2.5 +except ImportError, e: + import elementtree.ElementTree as ET # older pythons + + +class NoUnusedStopExceptionProblemReporter( + transitfeed.ExceptionProblemReporter): + """The company shuttle database has a few unused stops for reasons unrelated + to this script. Ignore them. + """ + def UnusedStop(self, stop_id, stop_name): + pass + +def SaveFeed(input, output): + tree = ET.parse(urllib.urlopen(input)) + + schedule = transitfeed.Schedule() + service_period = schedule.GetDefaultServicePeriod() + service_period.SetWeekdayService() + service_period.SetStartDate('20070314') + service_period.SetEndDate('20071231') + # Holidays for 2007 + service_period.SetDateHasService('20070528', has_service=False) + service_period.SetDateHasService('20070704', has_service=False) + service_period.SetDateHasService('20070903', has_service=False) + service_period.SetDateHasService('20071122', has_service=False) + service_period.SetDateHasService('20071123', has_service=False) + service_period.SetDateHasService('20071224', has_service=False) + service_period.SetDateHasService('20071225', has_service=False) + service_period.SetDateHasService('20071226', has_service=False) + service_period.SetDateHasService('20071231', has_service=False) + + stops = {} # Map from xml stop id to python Stop object + agency = schedule.NewDefaultAgency(name='GBus', url='http://shuttle/', + timezone='America/Los_Angeles') + + for xml_stop in tree.getiterator('stop'): + stop = schedule.AddStop(lat=float(xml_stop.attrib['lat']), + lng=float(xml_stop.attrib['lng']), + name=xml_stop.attrib['name']) + stops[xml_stop.attrib['id']] = stop + + for xml_shuttleGroup in tree.getiterator('shuttleGroup'): + if xml_shuttleGroup.attrib['name'] == 'Test': + continue + r = schedule.AddRoute(short_name="", + long_name=xml_shuttleGroup.attrib['name'], route_type='Bus') + for xml_route in xml_shuttleGroup.getiterator('route'): + t = r.AddTrip(schedule=schedule, headsign=xml_route.attrib['name'], + trip_id=xml_route.attrib['id']) + trip_stops = [] # Build a list of (time, Stop) tuples + for xml_schedule in xml_route.getiterator('schedule'): + trip_stops.append( (int(xml_schedule.attrib['time']) / 1000, + stops[xml_schedule.attrib['stopId']]) ) + trip_stops.sort() # Sort by time + for (time, stop) in trip_stops: + t.AddStopTime(stop=stop, arrival_secs=time, departure_secs=time) + + schedule.Validate(problems=NoUnusedStopExceptionProblemReporter()) + schedule.WriteGoogleTransitFeed(output) + + +def main(): + parser = OptionParser() + parser.add_option('--input', dest='input', + help='Path or URL of input') + parser.add_option('--output', dest='output', + help='Path of output file. Should end in .zip and if it ' + 'contains the substring YYYYMMDD it will be replaced with ' + 'today\'s date. It is impossible to include the literal ' + 'string YYYYYMMDD in the path of the output file.') + parser.add_option('--execute', dest='execute', + help='Commands to run to copy the output. %(path)s is ' + 'replaced with full path of the output and %(name)s is ' + 'replaced with name part of the path. Try ' + 'scp %(path)s myhost:www/%(name)s', + action='append') + parser.set_defaults(input=None, output=None, execute=[]) + (options, args) = parser.parse_args() + + today = datetime.date.today().strftime('%Y%m%d') + options.output = re.sub(r'YYYYMMDD', today, options.output) + (_, name) = os.path.split(options.output) + path = options.output + + SaveFeed(options.input, options.output) + + for command in options.execute: + import subprocess + def check_call(cmd): + """Convenience function that is in the docs for subprocess but not + installed on my system.""" + retcode = subprocess.call(cmd, shell=True) + if retcode < 0: + raise Exception("Child '%s' was terminated by signal %d" % (cmd, + -retcode)) + elif retcode != 0: + raise Exception("Child '%s' returned %d" % (cmd, retcode)) + + # path_output and filename_current can be used to run arbitrary commands + check_call(command % locals()) + +if __name__ == '__main__': + main() + --- /dev/null +++ b/origin-src/transitfeed-1.2.5/examples/shuttle_from_xmlfeed.xml @@ -1,1 +1,30 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + --- /dev/null +++ b/origin-src/transitfeed-1.2.5/examples/small_builder.py @@ -1,1 +1,40 @@ +#!/usr/bin/python2.5 +# A really simple example of using transitfeed to build a Google Transit +# Feed Specification file. + +import transitfeed +from optparse import OptionParser + + +parser = OptionParser() +parser.add_option('--output', dest='output', + help='Path of output file. Should end in .zip') +parser.set_defaults(output='google_transit.zip') +(options, args) = parser.parse_args() + +schedule = transitfeed.Schedule() +schedule.AddAgency("Fly Agency", "http://iflyagency.com", + "America/Los_Angeles") + +service_period = schedule.GetDefaultServicePeriod() +service_period.SetWeekdayService(True) +service_period.SetDateHasService('20070704') + +stop1 = schedule.AddStop(lng=-122, lat=37.2, name="Suburbia") +stop2 = schedule.AddStop(lng=-122.001, lat=37.201, name="Civic Center") + +route = schedule.AddRoute(short_name="22", long_name="Civic Center Express", + route_type="Bus") + +trip = route.AddTrip(schedule, headsign="To Downtown") +trip.AddStopTime(stop1, stop_time='09:00:00') +trip.AddStopTime(stop2, stop_time='09:15:00') + +trip = route.AddTrip(schedule, headsign="To Suburbia") +trip.AddStopTime(stop1, stop_time='17:30:00') +trip.AddStopTime(stop2, stop_time='17:45:00') + +schedule.Validate() +schedule.WriteGoogleTransitFeed(options.output) + --- /dev/null +++ b/origin-src/transitfeed-1.2.5/examples/table.py @@ -1,1 +1,177 @@ +#!/usr/bin/python2.5 +# Copyright (C) 2007 Google Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# An example script that demonstrates converting a proprietary format to a +# Google Transit Feed Specification file. +# +# You can load table.txt, the example input, in Excel. It contains three +# sections: +# 1) A list of global options, starting with a line containing the word +# 'options'. Each option has an name in the first column and most options +# have a value in the second column. +# 2) A table of stops, starting with a line containing the word 'stops'. Each +# row of the table has 3 columns: name, latitude, longitude +# 3) A list of routes. There is an empty row between each route. The first row +# for a route lists the short_name and long_name. After the first row the +# left-most column lists the stop names visited by the route. Each column +# contains the times a single trip visits the stops. +# +# This is very simple example which you could use as a base for your own +# transit feed builder. + +import transitfeed +from optparse import OptionParser +import re + +stops = {} + +# table is a list of lists in this form +# [ ['Short Name', 'Long Name'], +# ['Stop 1', 'Stop 2', ...] +# [time_at_1, time_at_2, ...] # times for trip 1 +# [time_at_1, time_at_2, ...] # times for trip 2 +# ... ] +def AddRouteToSchedule(schedule, table): + if len(table) >= 2: + r = schedule.AddRoute(short_name=table[0][0], long_name=table[0][1], route_type='Bus') + for trip in table[2:]: + if len(trip) > len(table[1]): + print "ignoring %s" % trip[len(table[1]):] + trip = trip[0:len(table[1])] + t = r.AddTrip(schedule, headsign='My headsign') + trip_stops = [] # Build a list of (time, stopname) tuples + for i in range(0, len(trip)): + if re.search(r'\S', trip[i]): + trip_stops.append( (transitfeed.TimeToSecondsSinceMidnight(trip[i]), table[1][i]) ) + trip_stops.sort() # Sort by time + for (time, stopname) in trip_stops: + t.AddStopTime(stop=stops[stopname.lower()], arrival_secs=time, + departure_secs=time) + +def TransposeTable(table): + """Transpose a list of lists, using None to extend all input lists to the + same length. + + For example: + >>> TransposeTable( + [ [11, 12, 13], + [21, 22], + [31, 32, 33, 34]]) + + [ [11, 21, 31], + [12, 22, 32], + [13, None, 33], + [None, None, 34]] + """ + transposed = [] + rows = len(table) + cols = max(len(row) for row in table) + for x in range(cols): + transposed.append([]) + for y in range(rows): + if x < len(table[y]): + transposed[x].append(table[y][x]) + else: + transposed[x].append(None) + return transposed + +def ProcessOptions(schedule, table): + service_period = schedule.GetDefaultServicePeriod() + agency_name, agency_url, agency_timezone = (None, None, None) + + for row in table[1:]: + command = row[0].lower() + if command == 'weekday': + service_period.SetWeekdayService() + elif command == 'start_date': + service_period.SetStartDate(row[1]) + elif command == 'end_date': + service_period.SetEndDate(row[1]) + elif command == 'add_date': + service_period.SetDateHasService(date=row[1]) + elif command == 'remove_date': + service_period.SetDateHasService(date=row[1], has_service=False) + elif command == 'agency_name': + agency_name = row[1] + elif command == 'agency_url': + agency_url = row[1] + elif command == 'agency_timezone': + agency_timezone = row[1] + + if not (agency_name and agency_url and agency_timezone): + print "You must provide agency information" + + schedule.NewDefaultAgency(agency_name=agency_name, agency_url=agency_url, + agency_timezone=agency_timezone) + + +def AddStops(schedule, table): + for name, lat_str, lng_str in table[1:]: + stop = schedule.AddStop(lat=float(lat_str), lng=float(lng_str), name=name) + stops[name.lower()] = stop + + +def ProcessTable(schedule, table): + if table[0][0].lower() == 'options': + ProcessOptions(schedule, table) + elif table[0][0].lower() == 'stops': + AddStops(schedule, table) + else: + transposed = [table[0]] # Keep route_short_name and route_long_name on first row + + # Transpose rest of table. Input contains the stop names in table[x][0], x + # >= 1 with trips found in columns, so we need to transpose table[1:]. + # As a diagram Transpose from + # [['stop 1', '10:00', '11:00', '12:00'], + # ['stop 2', '10:10', '11:10', '12:10'], + # ['stop 3', '10:20', '11:20', '12:20']] + # to + # [['stop 1', 'stop 2', 'stop 3'], + # ['10:00', '10:10', '10:20'], + # ['11:00', '11:11', '11:20'], + # ['12:00', '12:12', '12:20']] + transposed.extend(TransposeTable(table[1:])) + AddRouteToSchedule(schedule, transposed) + + +def main(): + parser = OptionParser() + parser.add_option('--input', dest='input', + help='Path of input file') + parser.add_option('--output', dest='output', + help='Path of output file, should end in .zip') + parser.set_defaults(output='feed.zip') + (options, args) = parser.parse_args() + + schedule = transitfeed.Schedule() + + table = [] + for line in open(options.input): + line = line.rstrip() + if not line: + ProcessTable(schedule, table) + table = [] + else: + table.append(line.split('\t')) + + ProcessTable(schedule, table) + + schedule.WriteGoogleTransitFeed(options.output) + + +if __name__ == '__main__': + main() + --- /dev/null +++ b/origin-src/transitfeed-1.2.5/examples/table.txt @@ -1,1 +1,30 @@ +options +weekday +start_date 20070315 +end_date 20071215 +remove_date 20070704 +agency_name Gbus +agency_url http://shuttle/ +agency_timezone America/Los_Angeles +stops +Stagecoach 36.915682 -116.751677 +N Ave / A Ave N 36.914944 -116.761472 +N Ave / D Ave N 36.914893 -116.76821 +Doing / D Ave N 36.909489 -116.768242 +E Main / S Irving 36.905697 -116.76218 + +O in Bar Circle Inbound +Stagecoach 9:00:00 9:30:00 10:00:00 12:00:00 +N Ave / A Ave N 9:05:00 9:35:00 10:05:00 12:05:00 +N Ave / D Ave N 9:07:00 9:37:00 10:07:00 12:07:00 +Doing / D Ave N 9:09:00 9:39:00 10:09:00 12:09:00 +E Main / S Irving 9:11:00 9:41:00 10:11:00 12:11:00 + +O out Bar Circle Outbound +E Main / S Irving 15:00:00 15:30:00 16:00:00 18:00:00 +Doing / D Ave N 15:05:00 15:35:00 16:05:00 18:05:00 +N Ave / D Ave N 15:07:00 15:37:00 16:07:00 18:07:00 +N Ave / A Ave N 15:09:00 15:39:00 16:09:00 18:09:00 +Stagecoach 15:11:00 15:41:00 16:11:00 18:11:00 + --- /dev/null +++ b/origin-src/transitfeed-1.2.5/feedvalidator.py @@ -1,1 +1,723 @@ - +#!/usr/bin/python2.5 + +# Copyright (C) 2007 Google Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + + +"""Validates a GTFS file. + +For usage information run feedvalidator.py --help +""" + +import bisect +import codecs +import datetime +from transitfeed.util import defaultdict +import optparse +import os +import os.path +import re +import socket +import sys +import time +import transitfeed +from transitfeed import TYPE_ERROR, TYPE_WARNING +from urllib2 import Request, urlopen, HTTPError, URLError +from transitfeed import util +import webbrowser + +SVN_TAG_URL = 'http://googletransitdatafeed.googlecode.com/svn/tags/' + + +def MaybePluralizeWord(count, word): + if count == 1: + return word + else: + return word + 's' + + +def PrettyNumberWord(count, word): + return '%d %s' % (count, MaybePluralizeWord(count, word)) + + +def UnCamelCase(camel): + return re.sub(r'([a-z])([A-Z])', r'\1 \2', camel) + + +def ProblemCountText(error_count, warning_count): + results = [] + if error_count: + results.append(PrettyNumberWord(error_count, 'error')) + if warning_count: + results.append(PrettyNumberWord(warning_count, 'warning')) + + return ' and '.join(results) + + +def CalendarSummary(schedule): + today = datetime.date.today() + summary_end_date = today + datetime.timedelta(days=60) + start_date, end_date = schedule.GetDateRange() + + if not start_date or not end_date: + return {} + + try: + start_date_object = transitfeed.DateStringToDateObject(start_date) + end_date_object = transitfeed.DateStringToDateObject(end_date) + except ValueError: + return {} + + # Get the list of trips only during the period the feed is active. + # As such we have to check if it starts in the future and/or if + # if it ends in less than 60 days. + date_trips_departures = schedule.GenerateDateTripsDeparturesList( + max(today, start_date_object), + min(summary_end_date, end_date_object)) + + if not date_trips_departures: + return {} + + # Check that the dates which will be shown in summary agree with these + # calculations. Failure implies a bug which should be fixed. It isn't good + # for users to discover assertion failures but means it will likely be fixed. + assert start_date <= date_trips_departures[0][0].strftime("%Y%m%d") + assert end_date >= date_trips_departures[-1][0].strftime("%Y%m%d") + + # Generate a map from int number of trips in a day to a list of date objects + # with that many trips. The list of dates is sorted. + trips_dates = defaultdict(lambda: []) + trips = 0 + for date, day_trips, day_departures in date_trips_departures: + trips += day_trips + trips_dates[day_trips].append(date) + mean_trips = trips / len(date_trips_departures) + max_trips = max(trips_dates.keys()) + min_trips = min(trips_dates.keys()) + + calendar_summary = {} + calendar_summary['mean_trips'] = mean_trips + calendar_summary['max_trips'] = max_trips + calendar_summary['max_trips_dates'] = FormatDateList(trips_dates[max_trips]) + calendar_summary['min_trips'] = min_trips + calendar_summary['min_trips_dates'] = FormatDateList(trips_dates[min_trips]) + calendar_summary['date_trips_departures'] = date_trips_departures + calendar_summary['date_summary_range'] = "%s to %s" % ( + date_trips_departures[0][0].strftime("%a %b %d"), + date_trips_departures[-1][0].strftime("%a %b %d")) + + return calendar_summary + + +def FormatDateList(dates): + if not dates: + return "0 service dates" + + formatted = [d.strftime("%a %b %d") for d in dates[0:3]] + if len(dates) > 3: + formatted.append("...") + return "%s (%s)" % (PrettyNumberWord(len(dates), "service date"), + ", ".join(formatted)) + + +def MaxVersion(versions): + versions = filter(None, versions) + versions.sort(lambda x,y: -cmp([int(item) for item in x.split('.')], + [int(item) for item in y.split('.')])) + if len(versions) > 0: + return versions[0] + + +class CountingConsoleProblemReporter(transitfeed.ProblemReporter): + def __init__(self): + transitfeed.ProblemReporter.__init__(self) + self._error_count = 0 + self._warning_count = 0 + + def _Report(self, e): + transitfeed.ProblemReporter._Report(self, e) + if e.IsError(): + self._error_count += 1 + else: + self._warning_count += 1 + + def ErrorCount(self): + return self._error_count + + def WarningCount(self): + return self._warning_count + + def FormatCount(self): + return ProblemCountText(self.ErrorCount(), self.WarningCount()) + + def HasIssues(self): + return self.ErrorCount() or self.WarningCount() + + +class BoundedProblemList(object): + """A list of one type of ExceptionWithContext objects with bounded size.""" + def __init__(self, size_bound): + self._count = 0 + self._exceptions = [] + self._size_bound = size_bound + + def Add(self, e): + self._count += 1 + try: + bisect.insort(self._exceptions, e) + except TypeError: + # The base class ExceptionWithContext raises this exception in __cmp__ + # to signal that an object is not comparable. Instead of keeping the most + # significant issue keep the first reported. + if self._count <= self._size_bound: + self._exceptions.append(e) + else: + # self._exceptions is in order. Drop the least significant if the list is + # now too long. + if self._count > self._size_bound: + del self._exceptions[-1] + + def _GetDroppedCount(self): + return self._count - len(self._exceptions) + + def __repr__(self): + return "" % repr(self._exceptions) + + count = property(lambda s: s._count) + dropped_count = property(_GetDroppedCount) + problems = property(lambda s: s._exceptions) + + +class LimitPerTypeProblemReporter(transitfeed.ProblemReporter): + def __init__(self, limit_per_type): + transitfeed.ProblemReporter.__init__(self) + + # {TYPE_WARNING: {"ClassName": BoundedProblemList()}} + self._type_to_name_to_problist = { + TYPE_WARNING: defaultdict(lambda: BoundedProblemList(limit_per_type)), + TYPE_ERROR: defaultdict(lambda: BoundedProblemList(limit_per_type)) + } + + def HasIssues(self): + return (self._type_to_name_to_problist[TYPE_ERROR] or + self._type_to_name_to_problist[TYPE_WARNING]) + + def _Report(self, e): + self._type_to_name_to_problist[e.GetType()][e.__class__.__name__].Add(e) + + def ErrorCount(self): + error_sets = self._type_to_name_to_problist[TYPE_ERROR].values() + return sum(map(lambda v: v.count, error_sets)) + + def WarningCount(self): + warning_sets = self._type_to_name_to_problist[TYPE_WARNING].values() + return sum(map(lambda v: v.count, warning_sets)) + + def ProblemList(self, problem_type, class_name): + """Return the BoundedProblemList object for given type and class.""" + return self._type_to_name_to_problist[problem_type][class_name] + + def ProblemListMap(self, problem_type): + """Return the map from class name to BoundedProblemList object.""" + return self._type_to_name_to_problist[problem_type] + + +class HTMLCountingProblemReporter(LimitPerTypeProblemReporter): + def FormatType(self, f, level_name, class_problist): + """Write the HTML dumping all problems of one type. + + Args: + f: file object open for writing + level_name: string such as "Error" or "Warning" + class_problist: sequence of tuples (class name, + BoundedProblemList object) + """ + class_problist.sort() + output = [] + for classname, problist in class_problist: + output.append('

    %s

      \n' % + (level_name, classname, UnCamelCase(classname))) + for e in problist.problems: + self.FormatException(e, output) + if problist.dropped_count: + output.append('
    • and %d more of this type.' % + (problist.dropped_count)) + output.append('
    \n') + f.write(''.join(output)) + + def FormatTypeSummaryTable(self, level_name, name_to_problist): + """Return an HTML table listing the number of problems by class name. + + Args: + level_name: string such as "Error" or "Warning" + name_to_problist: dict mapping class name to an BoundedProblemList object + + Returns: + HTML in a string + """ + output = [] + output.append('') + for classname in sorted(name_to_problist.keys()): + problist = name_to_problist[classname] + human_name = MaybePluralizeWord(problist.count, UnCamelCase(classname)) + output.append('\n' % + (problist.count, level_name, classname, human_name)) + output.append('
    %d%s
    \n') + return ''.join(output) + + def FormatException(self, e, output): + """Append HTML version of e to list output.""" + d = e.GetDictToFormat() + for k in ('file_name', 'feedname', 'column_name'): + if k in d.keys(): + d[k] = '%s' % d[k] + problem_text = e.FormatProblem(d).replace('\n', '
    ') + output.append('
  • ') + output.append('
    %s
    ' % + transitfeed.EncodeUnicode(problem_text)) + try: + if hasattr(e, 'row_num'): + line_str = 'line %d of ' % e.row_num + else: + line_str = '' + output.append('in %s%s
    \n' % + (line_str, e.file_name)) + row = e.row + headers = e.headers + column_name = e.column_name + table_header = '' # HTML + table_data = '' # HTML + for header, value in zip(headers, row): + attributes = '' + if header == column_name: + attributes = ' class="problem"' + table_header += '%s' % (attributes, header) + table_data += '%s' % (attributes, value) + # Make sure output is encoded into UTF-8 + output.append('%s\n' % + transitfeed.EncodeUnicode(table_header)) + output.append('%s
    \n' % + transitfeed.EncodeUnicode(table_data)) + except AttributeError, e: + pass # Hope this was getting an attribute from e ;-) + output.append('
  • \n') + + def FormatCount(self): + return ProblemCountText(self.ErrorCount(), self.WarningCount()) + + def CountTable(self): + output = [] + output.append('\n') + output.append('') + if self.ProblemListMap(TYPE_ERROR): + output.append('' % + PrettyNumberWord(self.ErrorCount(), "error")) + if self.ProblemListMap(TYPE_WARNING): + output.append('' % + PrettyNumberWord(self.WarningCount(), "warning")) + output.append('\n') + if self.ProblemListMap(TYPE_ERROR): + output.append('\n') + if self.ProblemListMap(TYPE_WARNING): + output.append('\n') + output.append('
    %s%s
    \n') + output.append(self.FormatTypeSummaryTable("Error", + self.ProblemListMap(TYPE_ERROR))) + output.append('\n') + output.append(self.FormatTypeSummaryTable("Warning", + self.ProblemListMap(TYPE_WARNING))) + output.append('
    ') + return ''.join(output) + + def WriteOutput(self, feed_location, f, schedule, other_problems): + """Write the html output to f.""" + if self.HasIssues(): + if self.ErrorCount() + self.WarningCount() == 1: + summary = ('Found this problem:\n%s' % + self.CountTable()) + else: + summary = ('Found these problems:\n%s' % + self.CountTable()) + else: + summary = 'feed validated successfully' + if other_problems is not None: + summary = ('\n%s

    ' % + other_problems) + summary + + basename = os.path.basename(feed_location) + feed_path = (feed_location[:feed_location.rfind(basename)], basename) + + agencies = ', '.join(['%s' % (a.agency_url, a.agency_name) + for a in schedule.GetAgencyList()]) + if not agencies: + agencies = '?' + + dates = "No valid service dates found" + (start, end) = schedule.GetDateRange() + if start and end: + def FormatDate(yyyymmdd): + src_format = "%Y%m%d" + dst_format = "%B %d, %Y" + try: + return time.strftime(dst_format, + time.strptime(yyyymmdd, src_format)) + except ValueError: + return yyyymmdd + + formatted_start = FormatDate(start) + formatted_end = FormatDate(end) + dates = "%s to %s" % (formatted_start, formatted_end) + + calendar_summary = CalendarSummary(schedule) + if calendar_summary: + calendar_summary_html = """
    +During the upcoming service dates %(date_summary_range)s: + + + + +
    Average trips per date:%(mean_trips)s
    Most trips on a date:%(max_trips)s, on %(max_trips_dates)s
    Least trips on a date:%(min_trips)s, on %(min_trips_dates)s
    """ % calendar_summary + else: + calendar_summary_html = "" + + output_prefix = """ + + + +FeedValidator: %(feed_file)s + + + +GTFS validation results for feed:
    +%(feed_dir)s%(feed_file)s +

    + + + + + + + +
    Agencies:%(agencies)s
    Routes:%(routes)s
    Stops:%(stops)s
    Trips:%(trips)s
    Shapes:%(shapes)s
    Effective:%(dates)s
    +%(calendar_summary)s +
    +%(problem_summary)s +

    +""" % { "feed_file": feed_path[1], + "feed_dir": feed_path[0], + "agencies": agencies, + "routes": len(schedule.GetRouteList()), + "stops": len(schedule.GetStopList()), + "trips": len(schedule.GetTripList()), + "shapes": len(schedule.GetShapeList()), + "dates": dates, + "problem_summary": summary, + "calendar_summary": calendar_summary_html} + +# In output_suffix string +# time.strftime() returns a regular local time string (not a Unicode one) with +# default system encoding. And decode() will then convert this time string back +# into a Unicode string. We use decode() here because we don't want the operating +# system to do any system encoding (which may cause some problem if the string +# contains some non-English characters) for the string. Therefore we decode it +# back to its original Unicode code print. + + time_unicode = (time.strftime('%B %d, %Y at %I:%M %p %Z'). + decode(sys.getfilesystemencoding())) + output_suffix = """ + + +""" % (transitfeed.__version__, time_unicode) + + f.write(transitfeed.EncodeUnicode(output_prefix)) + if self.ProblemListMap(TYPE_ERROR): + f.write('

    Errors:

    ') + self.FormatType(f, "Error", + self.ProblemListMap(TYPE_ERROR).items()) + if self.ProblemListMap(TYPE_WARNING): + f.write('

    Warnings:

    ') + self.FormatType(f, "Warning", + self.ProblemListMap(TYPE_WARNING).items()) + f.write(transitfeed.EncodeUnicode(output_suffix)) + + +def RunValidationOutputFromOptions(feed, options): + """Validate feed, output results per options and return an exit code.""" + if options.output.upper() == "CONSOLE": + return RunValidationOutputToConsole(feed, options) + else: + return RunValidationOutputToFilename(feed, options, options.output) + + +def RunValidationOutputToFilename(feed, options, output_filename): + """Validate feed, save HTML at output_filename and return an exit code.""" + try: + output_file = open(output_filename, 'w') + exit_code = RunValidationOutputToFile(feed, options, output_file) + output_file.close() + except IOError, e: + print 'Error while writing %s: %s' % (output_filename, e) + output_filename = None + exit_code = 2 + + if options.manual_entry and output_filename: + webbrowser.open('file://%s' % os.path.abspath(output_filename)) + + return exit_code + + +def RunValidationOutputToFile(feed, options, output_file): + """Validate feed, write HTML to output_file and return an exit code.""" + problems = HTMLCountingProblemReporter(options.limit_per_type) + schedule, exit_code, other_problems_string = RunValidation(feed, options, + problems) + if isinstance(feed, basestring): + feed_location = feed + else: + feed_location = getattr(feed, 'name', repr(feed)) + problems.WriteOutput(feed_location, output_file, schedule, + other_problems_string) + return exit_code + + +def RunValidationOutputToConsole(feed, options): + """Validate feed, print reports and return an exit code.""" + problems = CountingConsoleProblemReporter() + _, exit_code, _ = RunValidation(feed, options, problems) + return exit_code + + +def RunValidation(feed, options, problems): + """Validate feed, returning the loaded Schedule and exit code. + + Args: + feed: GTFS file, either path of the file as a string or a file object + options: options object returned by optparse + problems: transitfeed.ProblemReporter instance + + Returns: + a transitfeed.Schedule object, exit code and plain text string of other + problems + Exit code is 1 if problems are found and 0 if the Schedule is problem free. + plain text string is '' if no other problems are found. + """ + other_problems_string = CheckVersion(latest_version=options.latest_version) + print 'validating %s' % feed + loader = transitfeed.Loader(feed, problems=problems, extra_validation=False, + memory_db=options.memory_db, + check_duplicate_trips=\ + options.check_duplicate_trips) + schedule = loader.Load() + schedule.Validate(service_gap_interval=options.service_gap_interval) + + if feed == 'IWantMyvalidation-crash.txt': + # See test/testfeedvalidator.py + raise Exception('For testing the feed validator crash handler.') + + if other_problems_string: + print other_problems_string + + if problems.HasIssues(): + print 'ERROR: %s found' % problems.FormatCount() + return schedule, 1, other_problems_string + else: + print 'feed validated successfully' + return schedule, 0, other_problems_string + + +def CheckVersion(latest_version=''): + """ + Check there is newer version of this project. + + Codes are based on http://www.voidspace.org.uk/python/articles/urllib2.shtml + Already got permission from the copyright holder. + """ + current_version = transitfeed.__version__ + if not latest_version: + timeout = 20 + socket.setdefaulttimeout(timeout) + request = Request(SVN_TAG_URL) + + try: + response = urlopen(request) + content = response.read() + versions = re.findall(r'>transitfeed-([\d\.]+)\/<\/a>', content) + latest_version = MaxVersion(versions) + + except HTTPError, e: + return('The server couldn\'t fulfill the request. Error code: %s.' + % e.code) + except URLError, e: + return('We failed to reach transitfeed server. Reason: %s.' % e.reason) + + if not latest_version: + return('We had trouble parsing the contents of %s.' % SVN_TAG_URL) + + newest_version = MaxVersion([latest_version, current_version]) + if current_version != newest_version: + return('A new version %s of transitfeed is available. Please visit ' + 'http://code.google.com/p/googletransitdatafeed and download.' + % newest_version) + + +def main(): + usage = \ +'''%prog [options] [] + +Validates GTFS file (or directory) and writes a HTML +report of the results to validation-results.html. + +If is ommited the filename is read from the console. Dragging +a file into the console may enter the filename. + +For more information see +http://code.google.com/p/googletransitdatafeed/wiki/FeedValidator +''' + + parser = util.OptionParserLongError( + usage=usage, version='%prog '+transitfeed.__version__) + parser.add_option('-n', '--noprompt', action='store_false', + dest='manual_entry', + help='do not prompt for feed location or load output in ' + 'browser') + parser.add_option('-o', '--output', dest='output', metavar='FILE', + help='write html output to FILE or --output=CONSOLE to ' + 'print all errors and warnings to the command console') + parser.add_option('-p', '--performance', action='store_true', + dest='performance', + help='output memory and time performance (Availability: ' + 'Unix') + parser.add_option('-m', '--memory_db', dest='memory_db', action='store_true', + help='Use in-memory sqlite db instead of a temporary file. ' + 'It is faster but uses more RAM.') + parser.add_option('-d', '--duplicate_trip_check', + dest='check_duplicate_trips', action='store_true', + help='Check for duplicate trips which go through the same ' + 'stops with same service and start times') + parser.add_option('-l', '--limit_per_type', + dest='limit_per_type', action='store', type='int', + help='Maximum number of errors and warnings to keep of ' + 'each type') + parser.add_option('--latest_version', dest='latest_version', + action='store', + help='a version number such as 1.2.1 or None to get the ' + 'latest version from code.google.com. Output a warning if ' + 'transitfeed.py is older than this version.') + parser.add_option('--service_gap_interval', + dest='service_gap_interval', + action='store', + type='int', + help='the number of consecutive days to search for with no ' + 'scheduled service. For each interval with no service ' + 'having this number of days or more a warning will be ' + 'issued') + + parser.set_defaults(manual_entry=True, output='validation-results.html', + memory_db=False, check_duplicate_trips=False, + limit_per_type=5, latest_version='', + service_gap_interval=13) + (options, args) = parser.parse_args() + + if not len(args) == 1: + if options.manual_entry: + feed = raw_input('Enter Feed Location: ') + else: + parser.error('You must provide the path of a single feed') + else: + feed = args[0] + + feed = feed.strip('"') + + if options.performance: + return ProfileRunValidationOutputFromOptions(feed, options) + else: + return RunValidationOutputFromOptions(feed, options) + + +def ProfileRunValidationOutputFromOptions(feed, options): + """Run RunValidationOutputFromOptions, print profile and return exit code.""" + import cProfile + import pstats + # runctx will modify a dict, but not locals(). We need a way to get rv back. + locals_for_exec = locals() + cProfile.runctx('rv = RunValidationOutputFromOptions(feed, options)', + globals(), locals_for_exec, 'validate-stats') + + # Only available on Unix, http://docs.python.org/lib/module-resource.html + import resource + print "Time: %d seconds" % ( + resource.getrusage(resource.RUSAGE_SELF).ru_utime + + resource.getrusage(resource.RUSAGE_SELF).ru_stime) + + # http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/286222 + # http://aspn.activestate.com/ASPN/Cookbook/ "The recipes are freely + # available for review and use." + def _VmB(VmKey): + """Return size from proc status in bytes.""" + _proc_status = '/proc/%d/status' % os.getpid() + _scale = {'kB': 1024.0, 'mB': 1024.0*1024.0, + 'KB': 1024.0, 'MB': 1024.0*1024.0} + + # get pseudo file /proc//status + try: + t = open(_proc_status) + v = t.read() + t.close() + except: + raise Exception("no proc file %s" % _proc_status) + return 0 # non-Linux? + # get VmKey line e.g. 'VmRSS: 9999 kB\n ...' + i = v.index(VmKey) + v = v[i:].split(None, 3) # whitespace + if len(v) < 3: + raise Exception("%s" % v) + return 0 # invalid format? + # convert Vm value to bytes + return int(float(v[1]) * _scale[v[2]]) + + # I ran this on over a hundred GTFS files, comparing VmSize to VmRSS + # (resident set size). The difference was always under 2% or 3MB. + print "Virtual Memory Size: %d bytes" % _VmB('VmSize:') + + # Output report of where CPU time was spent. + p = pstats.Stats('validate-stats') + p.strip_dirs() + p.sort_stats('cumulative').print_stats(30) + p.sort_stats('cumulative').print_callers(30) + return locals_for_exec['rv'] + + +if __name__ == '__main__': + util.RunWithCrashHandler(main) + --- /dev/null +++ b/origin-src/transitfeed-1.2.5/gtfsscheduleviewer/__init__.py @@ -1,1 +1,9 @@ +__doc__ = """ +Package holding files for Google Transit Feed Specification Schedule Viewer. +""" +# This package contains the data files for schedule_viewer.py, a script that +# comes with the transitfeed distribution. According to the thread +# "[Distutils] distutils data_files and setuptools.pkg_resources are driving +# me crazy" this is the easiest way to include data files. My experience +# agrees. - Tom 2007-05-29 --- /dev/null +++ b/origin-src/transitfeed-1.2.5/gtfsscheduleviewer/files/index.html @@ -1,1 +1,706 @@ - + + + + + [agency] + + + + + + + + + +
    +
    + ... +
    +
    +
    [agency]
    +
    +
    + + +
    +
    + +
    bottom bar
    + + + + --- /dev/null +++ b/origin-src/transitfeed-1.2.5/gtfsscheduleviewer/files/labeled_marker.js @@ -1,1 +1,186 @@ +/* +* LabeledMarker Class +* +* Copyright 2007 Mike Purvis (http://uwmike.com) +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +* +* This class extends the Maps API's standard GMarker class with the ability +* to support markers with textual labels. Please see articles here: +* +* http://googlemapsbook.com/2007/01/22/extending-gmarker/ +* http://googlemapsbook.com/2007/03/06/clickable-labeledmarker/ +*/ +/** + * Constructor for LabeledMarker, which picks up on strings from the GMarker + * options array, and then calls the GMarker constructor. + * + * @param {GLatLng} latlng + * @param {GMarkerOptions} Named optional arguments: + * opt_opts.labelText {String} text to place in the overlay div. + * opt_opts.labelClass {String} class to use for the overlay div. + * (default "markerLabel") + * opt_opts.labelOffset {GSize} label offset, the x- and y-distance between + * the marker's latlng and the upper-left corner of the text div. + */ +function LabeledMarker(latlng, opt_opts){ + this.latlng_ = latlng; + this.opts_ = opt_opts; + + this.initText_ = opt_opts.labelText || ""; + this.labelClass_ = opt_opts.labelClass || "markerLabel"; + this.labelOffset_ = opt_opts.labelOffset || new GSize(0, 0); + + this.clickable_ = opt_opts.clickable || true; + + if (opt_opts.draggable) { + // This version of LabeledMarker doesn't support dragging. + opt_opts.draggable = false; + } + + GMarker.apply(this, arguments); +} + + +// It's a limitation of JavaScript inheritance that we can't conveniently +// inherit from GMarker without having to run its constructor. In order for +// the constructor to run, it requires some dummy GLatLng. +LabeledMarker.prototype = new GMarker(new GLatLng(0, 0)); + +/** + * Is called by GMap2's addOverlay method. Creates the text div and adds it + * to the relevant parent div. + * + * @param {GMap2} map the map that has had this labeledmarker added to it. + */ +LabeledMarker.prototype.initialize = function(map) { + // Do the GMarker constructor first. + GMarker.prototype.initialize.apply(this, arguments); + + this.map_ = map; + this.setText(this.initText_); +} + +/** + * Create a new div for this label. + */ +LabeledMarker.prototype.makeDiv_ = function(map) { + if (this.div_) { + return; + } + this.div_ = document.createElement("div"); + this.div_.className = this.labelClass_; + this.div_.style.position = "absolute"; + this.div_.style.cursor = "pointer"; + this.map_.getPane(G_MAP_MARKER_PANE).appendChild(this.div_); + + if (this.clickable_) { + /** + * Creates a closure for passing events through to the source marker + * This is located in here to avoid cluttering the global namespace. + * The downside is that the local variables from initialize() continue + * to occupy space on the stack. + * + * @param {Object} object to receive event trigger. + * @param {GEventListener} event to be triggered. + */ + function newEventPassthru(obj, event) { + return function() { + GEvent.trigger(obj, event); + }; + } + + // Pass through events fired on the text div to the marker. + var eventPassthrus = ['click', 'dblclick', 'mousedown', 'mouseup', 'mouseover', 'mouseout']; + for(var i = 0; i < eventPassthrus.length; i++) { + var name = eventPassthrus[i]; + GEvent.addDomListener(this.div_, name, newEventPassthru(this, name)); + } + } +} + +/** + * Return the html in the div of this label, or "" if none is set + */ +LabeledMarker.prototype.getText = function(text) { + if (this.div_) { + return this.div_.innerHTML; + } else { + return ""; + } +} + +/** + * Set the html in the div of this label to text. If text is "" or null remove + * the div. + */ +LabeledMarker.prototype.setText = function(text) { + if (this.div_) { + if (text) { + this.div_.innerHTML = text; + } else { + // remove div + GEvent.clearInstanceListeners(this.div_); + this.div_.parentNode.removeChild(this.div_); + this.div_ = null; + } + } else { + if (text) { + this.makeDiv_(); + this.div_.innerHTML = text; + this.redraw(); + } + } +} + +/** + * Move the text div based on current projection and zoom level, call the redraw() + * handler in GMarker. + * + * @param {Boolean} force will be true when pixel coordinates need to be recomputed. + */ +LabeledMarker.prototype.redraw = function(force) { + GMarker.prototype.redraw.apply(this, arguments); + + if (this.div_) { + // Calculate the DIV coordinates of two opposite corners of our bounds to + // get the size and position of our rectangle + var p = this.map_.fromLatLngToDivPixel(this.latlng_); + var z = GOverlay.getZIndex(this.latlng_.lat()); + + // Now position our div based on the div coordinates of our bounds + this.div_.style.left = (p.x + this.labelOffset_.width) + "px"; + this.div_.style.top = (p.y + this.labelOffset_.height) + "px"; + this.div_.style.zIndex = z; // in front of the marker + } +} + +/** + * Remove the text div from the map pane, destroy event passthrus, and calls the + * default remove() handler in GMarker. + */ + LabeledMarker.prototype.remove = function() { + this.setText(null); + GMarker.prototype.remove.apply(this, arguments); +} + +/** + * Return a copy of this overlay, for the parent Map to duplicate itself in full. This + * is part of the Overlay interface and is used, for example, to copy everything in the + * main view into the mini-map. + */ +LabeledMarker.prototype.copy = function() { + return new LabeledMarker(this.latlng_, this.opt_opts_); +} + --- /dev/null +++ b/origin-src/transitfeed-1.2.5/gtfsscheduleviewer/files/style.css @@ -1,1 +1,162 @@ +html { overflow: hidden; } +html, body { + margin: 0; + padding: 0; + height: 100%; +} + +body { margin: 5px; } + +#content { + position: relative; + margin-top: 5px; +} + +#map-wrapper { + position: relative; + height: 100%; + width: auto; + left: 0; + top: 0; + z-index: 100; +} + +#map { + position: relative; + height: 100%; + width: auto; + border: 1px solid #aaa; +} + +#sidebar-wrapper { + position: absolute; + height: 100%; + width: 220px; + top: 0; + border: 1px solid #aaa; + overflow: auto; + z-index: 300; +} + +#sidebar { + position: relative; + width: auto; + padding: 4px; + overflow: hidden; +} + +#topbar { + position: relative; + padding: 2px; + border: 1px solid #aaa; + margin: 0; +} + +#topbar h1 { + white-space: nowrap; + overflow: hidden; + font-size: 14pt; + font-weight: bold; + font-face: + margin: 0; +} + + +body.sidebar-right #map-wrapper { margin-right: 229px; } +body.sidebar-right #sidebar-wrapper { right: 0; } + +body.sidebar-left #map { margin-left: 229px; } +body.sidebar-left #sidebar { left: 0; } + +body.nosidebar #map { margin: 0; } +body.nosidebar #sidebar { display: none; } + +#bottombar { + position: relative; + padding: 2px; + border: 1px solid #aaa; + margin-top: 5px; + display: none; +} + +/* holly hack for IE to get position:bottom right + see: http://www.positioniseverything.net/abs_relbugs.html + \*/ +* html #topbar { height: 1px; } +/* */ + +body { + font-family:helvetica,arial,sans, sans-serif; +} +h1 { + margin-top: 0.5em; + margin-bottom: 0.5em; +} +h2 { + margin-top: 0.2em; + margin-bottom: 0.2em; +} +h3 { + margin-top: 0.2em; + margin-bottom: 0.2em; +} +.tooltip { + white-space: nowrap; + padding: 2px; + color: black; + font-size: 12px; + background-color: white; + border: 1px solid black; + cursor: pointer; + filter:alpha(opacity=60); + -moz-opacity: 0.6; + opacity: 0.6; +} +#routeList { + border: 1px solid black; + overflow: auto; +} +.shortName { + font-size: bigger; + font-weight: bold; +} +.routeChoice,.tripChoice,.routeChoiceSelected,.tripChoiceSelected { + white-space: nowrap; + cursor: pointer; + padding: 0px 2px; + color: black; + line-height: 1.4em; + font-size: smaller; + overflow: hidden; +} +.tripChoice { + color: blue; +} +.routeChoiceSelected,.tripChoiceSelected { + background-color: blue; + color: white; +} +.tripSection { + padding-left: 0px; + font-size: 10pt; + background-color: lightblue; +} +.patternSection { + margin-left: 8px; + padding-left: 2px; + border-bottom: 1px solid grey; +} +.unusualPattern { + background-color: #aaa; + color: #444; +} +/* Following styles are used by location_editor.py */ +#edit { + visibility: hidden; + float: right; + font-size: 80%; +} +#edit form { + display: inline; +} --- /dev/null +++ b/origin-src/transitfeed-1.2.5/gtfsscheduleviewer/files/svgcheck.vbs @@ -1,1 +1,8 @@ +' Copyright 1999-2000 Adobe Systems Inc. All rights reserved. Permission to redistribute +' granted provided that this file is not modified in any way. This file is provided with +' absolutely no warranties of any kind. +Function isSVGControlInstalled() + on error resume next + isSVGControlInstalled = IsObject(CreateObject("Adobe.SVGCtl")) +end Function --- /dev/null +++ b/origin-src/transitfeed-1.2.5/gtfsscheduleviewer/marey_graph.py @@ -1,1 +1,470 @@ - +#!/usr/bin/python2.5 +# +# Copyright (C) 2007 Google Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +"""Output svg/xml data for a marey graph + +Marey graphs are a visualization form typically used for timetables. Time +is on the x-axis and position on the y-axis. This module reads data from a +transitfeed.Schedule and creates a marey graph in svg/xml format. The graph +shows the speed between stops for each trip of a route. + +TODO: This module was taken from an internal Google tool. It works but is not +well intergrated into transitfeed and schedule_viewer. Also, it has lots of +ugly hacks to compensate set canvas size and so on which could be cleaned up. + +For a little more information see (I didn't make this URL ;-) +http://transliteracies.english.ucsb.edu/post/research-project/research-clearinghouse-individual/research-reports/the-indexical-imagination-marey%e2%80%99s-graphic-method-and-the-technological-transformation-of-writing-in-the-nineteenth-century + + MareyGraph: Class, keeps cache of graph data and graph properties + and draws marey graphs in svg/xml format on request. + +""" + +import itertools +import transitfeed + + +class MareyGraph: + """Produces and caches marey graph from transit feed data.""" + + _MAX_ZOOM = 5.0 # change docstring of ChangeScaleFactor if this changes + _DUMMY_SEPARATOR = 10 #pixel + + def __init__(self): + # Timetablerelated state + self._cache = str() + self._stoplist = [] + self._tlist = [] + self._stations = [] + self._decorators = [] + + # TODO: Initialize default values via constructor parameters + # or via a class constants + + # Graph properties + self._tspan = 30 # number of hours to display + self._offset = 0 # starting hour + self._hour_grid = 60 # number of pixels for an hour + self._min_grid = 5 # number of pixels between subhour lines + + # Canvas properties + self._zoomfactor = 0.9 # svg Scaling factor + self._xoffset = 0 # move graph horizontally + self._yoffset = 0 # move graph veritcally + self._bgcolor = "lightgrey" + + # height/width of graph canvas before transform + self._gwidth = self._tspan * self._hour_grid + + def Draw(self, stoplist=None, triplist=None, height=520): + """Main interface for drawing the marey graph. + + If called without arguments, the data generated in the previous call + will be used. New decorators can be added between calls. + + Args: + # Class Stop is defined in transitfeed.py + stoplist: [Stop, Stop, ...] + # Class Trip is defined in transitfeed.py + triplist: [Trip, Trip, ...] + + Returns: + # A string that contain a svg/xml web-page with a marey graph. + " + + + + + + + """ % (self._gwidth + self._xoffset + 20, self._gheight + 15, + self._offset, self._gheight + 10, + self._xoffset, self._yoffset, self._zoomfactor) + + return svg_header + + def _DrawFooter(self): + return "" + + def _DrawDecorators(self): + """Used to draw fancy overlays on trip graphs.""" + return " ".join(self._decorators) + + def _DrawBox(self): + tmpstr = """ + """ % (0, 0, self._gwidth + 20, self._gheight, self._bgcolor) + return tmpstr + + def _BuildStations(self, stoplist): + """Dispatches the best algorithm for calculating station line position. + + Args: + # Class Stop is defined in transitfeed.py + stoplist: [Stop, Stop, ...] + # Class Trip is defined in transitfeed.py + triplist: [Trip, Trip, ...] + + Returns: + # One integer y-coordinate for each station normalized between + # 0 and X, where X is the height of the graph in pixels + [0, 33, 140, ... , X] + """ + stations = [] + dists = self._EuclidianDistances(stoplist) + stations = self._CalculateYLines(dists) + return stations + + def _EuclidianDistances(self,slist): + """Calculate euclidian distances between stops. + + Uses the stoplists long/lats to approximate distances + between stations and build a list with y-coordinates for the + horizontal lines in the graph. + + Args: + # Class Stop is defined in transitfeed.py + stoplist: [Stop, Stop, ...] + + Returns: + # One integer for each pair of stations + # indicating the approximate distance + [0,33,140, ... ,X] + """ + e_dists2 = [transitfeed.ApproximateDistanceBetweenStops(stop, tail) for + (stop,tail) in itertools.izip(slist, slist[1:])] + + return e_dists2 + + def _CalculateYLines(self, dists): + """Builds a list with y-coordinates for the horizontal lines in the graph. + + Args: + # One integer for each pair of stations + # indicating the approximate distance + dists: [0,33,140, ... ,X] + + Returns: + # One integer y-coordinate for each station normalized between + # 0 and X, where X is the height of the graph in pixels + [0, 33, 140, ... , X] + """ + tot_dist = sum(dists) + if tot_dist > 0: + pixel_dist = [float(d * (self._gheight-20))/tot_dist for d in dists] + pixel_grid = [0]+[int(pd + sum(pixel_dist[0:i])) for i,pd in + enumerate(pixel_dist)] + else: + pixel_grid = [] + + return pixel_grid + + def _TravelTimes(self,triplist,index=0): + """ Calculate distances and plot stops. + + Uses a timetable to approximate distances + between stations + + Args: + # Class Trip is defined in transitfeed.py + triplist: [Trip, Trip, ...] + # (Optional) Index of Triplist prefered for timetable Calculation + index: 3 + + Returns: + # One integer for each pair of stations + # indicating the approximate distance + [0,33,140, ... ,X] + """ + + def DistanceInTravelTime(dep_secs, arr_secs): + t_dist = arr_secs-dep_secs + if t_dist<0: + t_dist = self._DUMMY_SEPARATOR # min separation + return t_dist + + if not triplist: + return [] + + if 0 < index < len(triplist): + trip = triplist[index] + else: + trip = triplist[0] + + t_dists2 = [DistanceInTravelTime(stop[3],tail[2]) for (stop,tail) + in itertools.izip(trip.GetTimeStops(),trip.GetTimeStops()[1:])] + return t_dists2 + + def _AddWarning(self, str): + print str + + def _DrawTrips(self,triplist,colpar=""): + """Generates svg polylines for each transit trip. + + Args: + # Class Trip is defined in transitfeed.py + [Trip, Trip, ...] + + Returns: + # A string containing a polyline tag for each trip + ' 0 and not colpar: + color="purple" + scriptcall = 'onmouseover="LineClick(\'%s\',\'Trip %s starting %s\')"' % (t.trip_id, + t.trip_id, transitfeed.FormatSecondsSinceMidnight(t.GetStartTime())) + tmpstrhead = '') + return "".join(tmpstrs) + + def _Uniform(self, triplist): + """Fallback to assuming uniform distance between stations""" + # This should not be neseccary, but we are in fallback mode + longest = max([len(t.GetTimeStops()) for t in triplist]) + return [100] * longest + + def _DrawStations(self, color="#aaa"): + """Generates svg with a horizontal line for each station/stop. + + Args: + # Class Stop is defined in transitfeed.py + stations: [Stop, Stop, ...] + + Returns: + # A string containing a polyline tag for each stop + " ' %(color,20,20+y+.5,self._gwidth+20,20+y+.5)) + return "".join(tmpstrs) + + def _DrawHours(self): + """Generates svg to show a vertical hour and sub-hour grid + + Returns: + # A string containing a polyline tag for each grid line + " ' \ + % (i + .5 + 20, 20, i + .5 + 20, self._gheight)) + tmpstrs.append('%d' + % (i + 20, 20, + (i / self._hour_grid + self._offset) % 24)) + else: + tmpstrs.append('' \ + % (i + .5 + 20, 20, i + .5 + 20, self._gheight)) + return "".join(tmpstrs) + + def AddStationDecoration(self, index, color="#f00"): + """Flushes existing decorations and highlights the given station-line. + + Args: + # Integer, index of stop to be highlighted. + index: 4 + # An optional string with a html color code + color: "#fff" + """ + tmpstr = str() + num_stations = len(self._stations) + ind = int(index) + if self._stations: + if 0 0 and float(newfactor) < self._MAX_ZOOM: + self._zoomfactor = newfactor + + def ScaleLarger(self): + """Increases the zoom of the graph one step (0.1 units).""" + newfactor = self._zoomfactor + 0.1 + if float(newfactor) > 0 and float(newfactor) < self._MAX_ZOOM: + self._zoomfactor = newfactor + + def ScaleSmaller(self): + """Decreases the zoom of the graph one step(0.1 units).""" + newfactor = self._zoomfactor - 0.1 + if float(newfactor) > 0 and float(newfactor) < self._MAX_ZOOM: + self._zoomfactor = newfactor + + def ClearDecorators(self): + """Removes all the current decorators. + """ + self._decorators = [] + + def AddTextStripDecoration(self,txtstr): + tmpstr = '%s' % (0, + 20 + self._gheight, txtstr) + self._decorators.append(tmpstr) + + def SetSpan(self, first_arr, last_arr, mint=5 ,maxt=30): + s_hour = (first_arr / 3600) - 1 + e_hour = (last_arr / 3600) + 1 + self._offset = max(min(s_hour, 23), 0) + self._tspan = max(min(e_hour - s_hour, maxt), mint) + self._gwidth = self._tspan * self._hour_grid + --- /dev/null +++ b/origin-src/transitfeed-1.2.5/kmlparser.py @@ -1,1 +1,147 @@ +#!/usr/bin/python2.5 +# Copyright (C) 2007 Google Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +""" +This package provides implementation of a converter from a kml +file format into Google transit feed format. + +The KmlParser class is the main class implementing the parser. + +Currently only information about stops is extracted from a kml file. +The extractor expects the stops to be represented as placemarks with +a single point. +""" + +import re +import string +import sys +import transitfeed +from transitfeed import util +import xml.dom.minidom as minidom +import zipfile + + +class Placemark(object): + def __init__(self): + self.name = "" + self.coordinates = [] + + def IsPoint(self): + return len(self.coordinates) == 1 + + def IsLine(self): + return len(self.coordinates) > 1 + +class KmlParser(object): + def __init__(self, stopNameRe = '(.*)'): + """ + Args: + stopNameRe - a regular expression to extract a stop name from a + placemaker name + """ + self.stopNameRe = re.compile(stopNameRe) + + def Parse(self, filename, feed): + """ + Reads the kml file, parses it and updated the Google transit feed + object with the extracted information. + + Args: + filename - kml file name + feed - an instance of Schedule class to be updated + """ + dom = minidom.parse(filename) + self.ParseDom(dom, feed) + + def ParseDom(self, dom, feed): + """ + Parses the given kml dom tree and updates the Google transit feed object. + + Args: + dom - kml dom tree + feed - an instance of Schedule class to be updated + """ + shape_num = 0 + for node in dom.getElementsByTagName('Placemark'): + p = self.ParsePlacemark(node) + if p.IsPoint(): + (lon, lat) = p.coordinates[0] + m = self.stopNameRe.search(p.name) + feed.AddStop(lat, lon, m.group(1)) + elif p.IsLine(): + shape_num = shape_num + 1 + shape = transitfeed.Shape("kml_shape_" + str(shape_num)) + for (lon, lat) in p.coordinates: + shape.AddPoint(lat, lon) + feed.AddShapeObject(shape) + + def ParsePlacemark(self, node): + ret = Placemark() + for child in node.childNodes: + if child.nodeName == 'name': + ret.name = self.ExtractText(child) + if child.nodeName == 'Point' or child.nodeName == 'LineString': + ret.coordinates = self.ExtractCoordinates(child) + return ret + + def ExtractText(self, node): + for child in node.childNodes: + if child.nodeType == child.TEXT_NODE: + return child.wholeText # is a unicode string + return "" + + def ExtractCoordinates(self, node): + coordinatesText = "" + for child in node.childNodes: + if child.nodeName == 'coordinates': + coordinatesText = self.ExtractText(child) + break + ret = [] + for point in coordinatesText.split(): + coords = point.split(',') + ret.append((float(coords[0]), float(coords[1]))) + return ret + + +def main(): + usage = \ +"""%prog + +Reads KML file and creates GTFS file with +placemarks in the KML represented as stops. +""" + + parser = util.OptionParserLongError( + usage=usage, version='%prog '+transitfeed.__version__) + (options, args) = parser.parse_args() + if len(args) != 2: + parser.error('You did not provide all required command line arguments.') + + if args[0] == 'IWantMyCrash': + raise Exception('For testCrashHandler') + + parser = KmlParser() + feed = transitfeed.Schedule() + feed.save_all_stops = True + parser.Parse(args[0], feed) + feed.WriteGoogleTransitFeed(args[1]) + + print "Done." + + +if __name__ == '__main__': + util.RunWithCrashHandler(main) + --- /dev/null +++ b/origin-src/transitfeed-1.2.5/kmlwriter.py @@ -1,1 +1,648 @@ - +#!/usr/bin/python2.5 +# +# Copyright 2008 Google Inc. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +"""A module for writing GTFS feeds out into Google Earth KML format. + +For usage information run kmlwriter.py --help + +If no output filename is specified, the output file will be given the same +name as the feed file (with ".kml" appended) and will be placed in the same +directory as the input feed. + +The resulting KML file has a folder hierarchy which looks like this: + + - Stops + * stop1 + * stop2 + - Routes + - route1 + - Shapes + * shape1 + * shape2 + - Patterns + - pattern1 + - pattern2 + - Trips + * trip1 + * trip2 + - Shapes + * shape1 + - Shape Points + * shape_point1 + * shape_point2 + * shape2 + - Shape Points + * shape_point1 + * shape_point2 + +where the hyphens represent folders and the asteriks represent placemarks. + +In a trip, a vehicle visits stops in a certain sequence. Such a sequence of +stops is called a pattern. A pattern is represented by a linestring connecting +the stops. The "Shapes" subfolder of a route folder contains placemarks for +each shape used by a trip in the route. The "Patterns" subfolder contains a +placemark for each unique pattern used by a trip in the route. The "Trips" +subfolder contains a placemark for each trip in the route. + +Since there can be many trips and trips for the same route are usually similar, +they are not exported unless the --showtrips option is used. There is also +another option --splitroutes that groups the routes by vehicle type resulting +in a folder hierarchy which looks like this at the top level: + + - Stops + - Routes - Bus + - Routes - Tram + - Routes - Rail + - Shapes +""" + +try: + import xml.etree.ElementTree as ET # python 2.5 +except ImportError, e: + import elementtree.ElementTree as ET # older pythons +import optparse +import os.path +import sys +import transitfeed +from transitfeed import util + + +class KMLWriter(object): + """This class knows how to write out a transit feed as KML. + + Sample usage: + KMLWriter().Write(, ) + + Attributes: + show_trips: True if the individual trips should be included in the routes. + show_trips: True if the individual trips should be placed on ground. + split_routes: True if the routes should be split by type. + shape_points: True if individual shape points should be plotted. + """ + + def __init__(self): + """Initialise.""" + self.show_trips = False + self.split_routes = False + self.shape_points = False + self.altitude_per_sec = 0.0 + self.date_filter = None + + def _SetIndentation(self, elem, level=0): + """Indented the ElementTree DOM. + + This is the recommended way to cause an ElementTree DOM to be + prettyprinted on output, as per: http://effbot.org/zone/element-lib.htm + + Run this on the root element before outputting the tree. + + Args: + elem: The element to start indenting from, usually the document root. + level: Current indentation level for recursion. + """ + i = "\n" + level*" " + if len(elem): + if not elem.text or not elem.text.strip(): + elem.text = i + " " + for elem in elem: + self._SetIndentation(elem, level+1) + if not elem.tail or not elem.tail.strip(): + elem.tail = i + else: + if level and (not elem.tail or not elem.tail.strip()): + elem.tail = i + + def _CreateFolder(self, parent, name, visible=True, description=None): + """Create a KML Folder element. + + Args: + parent: The parent ElementTree.Element instance. + name: The folder name as a string. + visible: Whether the folder is initially visible or not. + description: A description string or None. + + Returns: + The folder ElementTree.Element instance. + """ + folder = ET.SubElement(parent, 'Folder') + name_tag = ET.SubElement(folder, 'name') + name_tag.text = name + if description is not None: + desc_tag = ET.SubElement(folder, 'description') + desc_tag.text = description + if not visible: + visibility = ET.SubElement(folder, 'visibility') + visibility.text = '0' + return folder + + def _CreateStyleForRoute(self, doc, route): + """Create a KML Style element for the route. + + The style sets the line colour if the route colour is specified. The + line thickness is set depending on the vehicle type. + + Args: + doc: The KML Document ElementTree.Element instance. + route: The transitfeed.Route to create the style for. + + Returns: + The id of the style as a string. + """ + style_id = 'route_%s' % route.route_id + style = ET.SubElement(doc, 'Style', {'id': style_id}) + linestyle = ET.SubElement(style, 'LineStyle') + width = ET.SubElement(linestyle, 'width') + type_to_width = {0: '3', # Tram + 1: '3', # Subway + 2: '5', # Rail + 3: '1'} # Bus + width.text = type_to_width.get(route.route_type, '1') + if route.route_color: + color = ET.SubElement(linestyle, 'color') + red = route.route_color[0:2].lower() + green = route.route_color[2:4].lower() + blue = route.route_color[4:6].lower() + color.text = 'ff%s%s%s' % (blue, green, red) + return style_id + + def _CreatePlacemark(self, parent, name, style_id=None, visible=True, + description=None): + """Create a KML Placemark element. + + Args: + parent: The parent ElementTree.Element instance. + name: The placemark name as a string. + style_id: If not None, the id of a style to use for the placemark. + visible: Whether the placemark is initially visible or not. + description: A description string or None. + + Returns: + The placemark ElementTree.Element instance. + """ + placemark = ET.SubElement(parent, 'Placemark') + placemark_name = ET.SubElement(placemark, 'name') + placemark_name.text = name + if description is not None: + desc_tag = ET.SubElement(placemark, 'description') + desc_tag.text = description + if style_id is not None: + styleurl = ET.SubElement(placemark, 'styleUrl') + styleurl.text = '#%s' % style_id + if not visible: + visibility = ET.SubElement(placemark, 'visibility') + visibility.text = '0' + return placemark + + def _CreateLineString(self, parent, coordinate_list): + """Create a KML LineString element. + + The points of the string are given in coordinate_list. Every element of + coordinate_list should be one of a tuple (longitude, latitude) or a tuple + (longitude, latitude, altitude). + + Args: + parent: The parent ElementTree.Element instance. + coordinate_list: The list of coordinates. + + Returns: + The LineString ElementTree.Element instance or None if coordinate_list is + empty. + """ + if not coordinate_list: + return None + linestring = ET.SubElement(parent, 'LineString') + tessellate = ET.SubElement(linestring, 'tessellate') + tessellate.text = '1' + if len(coordinate_list[0]) == 3: + altitude_mode = ET.SubElement(linestring, 'altitudeMode') + altitude_mode.text = 'absolute' + coordinates = ET.SubElement(linestring, 'coordinates') + if len(coordinate_list[0]) == 3: + coordinate_str_list = ['%f,%f,%f' % t for t in coordinate_list] + else: + coordinate_str_list = ['%f,%f' % t for t in coordinate_list] + coordinates.text = ' '.join(coordinate_str_list) + return linestring + + def _CreateLineStringForShape(self, parent, shape): + """Create a KML LineString using coordinates from a shape. + + Args: + parent: The parent ElementTree.Element instance. + shape: The transitfeed.Shape instance. + + Returns: + The LineString ElementTree.Element instance or None if coordinate_list is + empty. + """ + coordinate_list = [(longitude, latitude) for + (latitude, longitude, distance) in shape.points] + return self._CreateLineString(parent, coordinate_list) + + def _CreateStopsFolder(self, schedule, doc): + """Create a KML Folder containing placemarks for each stop in the schedule. + + If there are no stops in the schedule then no folder is created. + + Args: + schedule: The transitfeed.Schedule instance. + doc: The KML Document ElementTree.Element instance. + + Returns: + The Folder ElementTree.Element instance or None if there are no stops. + """ + if not schedule.GetStopList(): + return None + stop_folder = self._CreateFolder(doc, 'Stops') + stops = list(schedule.GetStopList()) + stops.sort(key=lambda x: x.stop_name) + for stop in stops: + desc_items = [] + if stop.stop_desc: + desc_items.append(stop.stop_desc) + if stop.stop_url: + desc_items.append('Stop info page: %s' % ( + stop.stop_url, stop.stop_url)) + description = '
    '.join(desc_items) or None + placemark = self._CreatePlacemark(stop_folder, stop.stop_name, + description=description) + point = ET.SubElement(placemark, 'Point') + coordinates = ET.SubElement(point, 'coordinates') + coordinates.text = '%.6f,%.6f' % (stop.stop_lon, stop.stop_lat) + return stop_folder + + def _CreateRoutePatternsFolder(self, parent, route, + style_id=None, visible=True): + """Create a KML Folder containing placemarks for each pattern in the route. + + A pattern is a sequence of stops used by one of the trips in the route. + + If there are not patterns for the route then no folder is created and None + is returned. + + Args: + parent: The parent ElementTree.Element instance. + route: The transitfeed.Route instance. + style_id: The id of a style to use if not None. + visible: Whether the folder is initially visible or not. + + Returns: + The Folder ElementTree.Element instance or None if there are no patterns. + """ + pattern_id_to_trips = route.GetPatternIdTripDict() + if not pattern_id_to_trips: + return None + + # sort by number of trips using the pattern + pattern_trips = pattern_id_to_trips.values() + pattern_trips.sort(lambda a, b: cmp(len(b), len(a))) + + folder = self._CreateFolder(parent, 'Patterns', visible) + for n, trips in enumerate(pattern_trips): + trip_ids = [trip.trip_id for trip in trips] + name = 'Pattern %d (trips: %d)' % (n+1, len(trips)) + description = 'Trips using this pattern (%d in total): %s' % ( + len(trips), ', '.join(trip_ids)) + placemark = self._CreatePlacemark(folder, name, style_id, visible, + description) + coordinates = [(stop.stop_lon, stop.stop_lat) + for stop in trips[0].GetPattern()] + self._CreateLineString(placemark, coordinates) + return folder + + def _CreateRouteShapesFolder(self, schedule, parent, route, + style_id=None, visible=True): + """Create a KML Folder for the shapes of a route. + + The folder contains a placemark for each shape referenced by a trip in the + route. If there are no such shapes, no folder is created and None is + returned. + + Args: + schedule: The transitfeed.Schedule instance. + parent: The parent ElementTree.Element instance. + route: The transitfeed.Route instance. + style_id: The id of a style to use if not None. + visible: Whether the placemark is initially visible or not. + + Returns: + The Folder ElementTree.Element instance or None. + """ + shape_id_to_trips = {} + for trip in route.trips: + if trip.shape_id: + shape_id_to_trips.setdefault(trip.shape_id, []).append(trip) + if not shape_id_to_trips: + return None + + # sort by the number of trips using the shape + shape_id_to_trips_items = shape_id_to_trips.items() + shape_id_to_trips_items.sort(lambda a, b: cmp(len(b[1]), len(a[1]))) + + folder = self._CreateFolder(parent, 'Shapes', visible) + for shape_id, trips in shape_id_to_trips_items: + trip_ids = [trip.trip_id for trip in trips] + name = '%s (trips: %d)' % (shape_id, len(trips)) + description = 'Trips using this shape (%d in total): %s' % ( + len(trips), ', '.join(trip_ids)) + placemark = self._CreatePlacemark(folder, name, style_id, visible, + description) + self._CreateLineStringForShape(placemark, schedule.GetShape(shape_id)) + return folder + + def _CreateRouteTripsFolder(self, parent, route, style_id=None, schedule=None): + """Create a KML Folder containing all the trips in the route. + + The folder contains a placemark for each of these trips. If there are no + trips in the route, no folder is created and None is returned. + + Args: + parent: The parent ElementTree.Element instance. + route: The transitfeed.Route instance. + style_id: A style id string for the placemarks or None. + + Returns: + The Folder ElementTree.Element instance or None. + """ + if not route.trips: + return None + trips = list(route.trips) + trips.sort(key=lambda x: x.trip_id) + trips_folder = self._CreateFolder(parent, 'Trips', visible=False) + for trip in trips: + if (self.date_filter and + not trip.service_period.IsActiveOn(self.date_filter)): + continue + + if trip.trip_headsign: + description = 'Headsign: %s' % trip.trip_headsign + else: + description = None + + coordinate_list = [] + for secs, stoptime, tp in trip.GetTimeInterpolatedStops(): + if self.altitude_per_sec > 0: + coordinate_list.append((stoptime.stop.stop_lon, stoptime.stop.stop_lat, + (secs - 3600 * 4) * self.altitude_per_sec)) + else: + coordinate_list.append((stoptime.stop.stop_lon, + stoptime.stop.stop_lat)) + placemark = self._CreatePlacemark(trips_folder, + trip.trip_id, + style_id=style_id, + visible=False, + description=description) + self._CreateLineString(placemark, coordinate_list) + return trips_folder + + def _CreateRoutesFolder(self, schedule, doc, route_type=None): + """Create a KML Folder containing routes in a schedule. + + The folder contains a subfolder for each route in the schedule of type + route_type. If route_type is None, then all routes are selected. Each + subfolder contains a flattened graph placemark, a route shapes placemark + and, if show_trips is True, a subfolder containing placemarks for each of + the trips in the route. + + If there are no routes in the schedule then no folder is created and None + is returned. + + Args: + schedule: The transitfeed.Schedule instance. + doc: The KML Document ElementTree.Element instance. + route_type: The route type integer or None. + + Returns: + The Folder ElementTree.Element instance or None. + """ + + def GetRouteName(route): + """Return a placemark name for the route. + + Args: + route: The transitfeed.Route instance. + + Returns: + The name as a string. + """ + name_parts = [] + if route.route_short_name: + name_parts.append('%s' % route.route_short_name) + if route.route_long_name: + name_parts.append(route.route_long_name) + return ' - '.join(name_parts) or route.route_id + + def GetRouteDescription(route): + """Return a placemark description for the route. + + Args: + route: The transitfeed.Route instance. + + Returns: + The description as a string. + """ + desc_items = [] + if route.route_desc: + desc_items.append(route.route_desc) + if route.route_url: + desc_items.append('Route info page: %s' % ( + route.route_url, route.route_url)) + description = '
    '.join(desc_items) + return description or None + + routes = [route for route in schedule.GetRouteList() + if route_type is None or route.route_type == route_type] + if not routes: + return None + routes.sort(key=lambda x: GetRouteName(x)) + + if route_type is not None: + route_type_names = {0: 'Tram, Streetcar or Light rail', + 1: 'Subway or Metro', + 2: 'Rail', + 3: 'Bus', + 4: 'Ferry', + 5: 'Cable car', + 6: 'Gondola or suspended cable car', + 7: 'Funicular'} + type_name = route_type_names.get(route_type, str(route_type)) + folder_name = 'Routes - %s' % type_name + else: + folder_name = 'Routes' + routes_folder = self._CreateFolder(doc, folder_name, visible=False) + + for route in routes: + style_id = self._CreateStyleForRoute(doc, route) + route_folder = self._CreateFolder(routes_folder, + GetRouteName(route), + description=GetRouteDescription(route)) + self._CreateRouteShapesFolder(schedule, route_folder, route, + style_id, False) + self._CreateRoutePatternsFolder(route_folder, route, style_id, False) + if self.show_trips: + self._CreateRouteTripsFolder(route_folder, route, style_id, schedule) + return routes_folder + + def _CreateShapesFolder(self, schedule, doc): + """Create a KML Folder containing all the shapes in a schedule. + + The folder contains a placemark for each shape. If there are no shapes in + the schedule then the folder is not created and None is returned. + + Args: + schedule: The transitfeed.Schedule instance. + doc: The KML Document ElementTree.Element instance. + + Returns: + The Folder ElementTree.Element instance or None. + """ + if not schedule.GetShapeList(): + return None + shapes_folder = self._CreateFolder(doc, 'Shapes') + shapes = list(schedule.GetShapeList()) + shapes.sort(key=lambda x: x.shape_id) + for shape in shapes: + placemark = self._CreatePlacemark(shapes_folder, shape.shape_id) + self._CreateLineStringForShape(placemark, shape) + if self.shape_points: + self._CreateShapePointFolder(shapes_folder, shape) + return shapes_folder + + def _CreateShapePointFolder(self, shapes_folder, shape): + """Create a KML Folder containing all the shape points in a shape. + + The folder contains placemarks for each shapepoint. + + Args: + shapes_folder: A KML Shape Folder ElementTree.Element instance + shape: The shape to plot. + + Returns: + The Folder ElementTree.Element instance or None. + """ + + folder_name = shape.shape_id + ' Shape Points' + folder = self._CreateFolder(shapes_folder, folder_name, visible=False) + for (index, (lat, lon, dist)) in enumerate(shape.points): + placemark = self._CreatePlacemark(folder, str(index+1)) + point = ET.SubElement(placemark, 'Point') + coordinates = ET.SubElement(point, 'coordinates') + coordinates.text = '%.6f,%.6f' % (lon, lat) + return folder + + def Write(self, schedule, output_file): + """Writes out a feed as KML. + + Args: + schedule: A transitfeed.Schedule object containing the feed to write. + output_file: The name of the output KML file, or file object to use. + """ + # Generate the DOM to write + root = ET.Element('kml') + root.attrib['xmlns'] = 'http://earth.google.com/kml/2.1' + doc = ET.SubElement(root, 'Document') + open_tag = ET.SubElement(doc, 'open') + open_tag.text = '1' + self._CreateStopsFolder(schedule, doc) + if self.split_routes: + route_types = set() + for route in schedule.GetRouteList(): + route_types.add(route.route_type) + route_types = list(route_types) + route_types.sort() + for route_type in route_types: + self._CreateRoutesFolder(schedule, doc, route_type) + else: + self._CreateRoutesFolder(schedule, doc) + self._CreateShapesFolder(schedule, doc) + + # Make sure we pretty-print + self._SetIndentation(root) + + # Now write the output + if isinstance(output_file, file): + output = output_file + else: + output = open(output_file, 'w') + output.write("""\n""") + ET.ElementTree(root).write(output, 'utf-8') + + +def main(): + usage = \ +'''%prog [options] [] + +Reads GTFS file or directory and creates a KML file + that contains the geographical features of the input. If + is omitted a default filename is picked based on +. By default the KML contains all stops and shapes. +''' + + parser = util.OptionParserLongError( + usage=usage, version='%prog '+transitfeed.__version__) + parser.add_option('-t', '--showtrips', action='store_true', + dest='show_trips', + help='include the individual trips for each route') + parser.add_option('-a', '--altitude_per_sec', action='store', type='float', + dest='altitude_per_sec', + help='if greater than 0 trips are drawn with time axis ' + 'set to this many meters high for each second of time') + parser.add_option('-s', '--splitroutes', action='store_true', + dest='split_routes', + help='split the routes by type') + parser.add_option('-d', '--date_filter', action='store', type='string', + dest='date_filter', + help='Restrict to trips active on date YYYYMMDD') + parser.add_option('-p', '--display_shape_points', action='store_true', + dest='shape_points', + help='shows the actual points along shapes') + + parser.set_defaults(altitude_per_sec=1.0) + options, args = parser.parse_args() + + if len(args) < 1: + parser.error('You must provide the path of an input GTFS file.') + + if args[0] == 'IWantMyCrash': + raise Exception('For testCrashHandler') + + input_path = args[0] + if len(args) >= 2: + output_path = args[1] + else: + path = os.path.normpath(input_path) + (feed_dir, feed) = os.path.split(path) + if '.' in feed: + feed = feed.rsplit('.', 1)[0] # strip extension + output_filename = '%s.kml' % feed + output_path = os.path.join(feed_dir, output_filename) + + loader = transitfeed.Loader(input_path, + problems=transitfeed.ProblemReporter()) + feed = loader.Load() + print "Writing %s" % output_path + writer = KMLWriter() + writer.show_trips = options.show_trips + writer.altitude_per_sec = options.altitude_per_sec + writer.split_routes = options.split_routes + writer.date_filter = options.date_filter + writer.shape_points = options.shape_points + writer.Write(feed, output_path) + + +if __name__ == '__main__': + util.RunWithCrashHandler(main) + --- /dev/null +++ b/origin-src/transitfeed-1.2.5/merge.py @@ -1,1 +1,1766 @@ - +#!/usr/bin/python2.5 +# +# Copyright 2007 Google Inc. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +"""A tool for merging two Google Transit feeds. + +Given two Google Transit feeds intending to cover two disjoint calendar +intervals, this tool will attempt to produce a single feed by merging as much +of the two feeds together as possible. + +For example, most stops remain the same throughout the year. Therefore, many +of the stops given in stops.txt for the first feed represent the same stops +given in the second feed. This tool will try to merge these stops so they +only appear once in the resultant feed. + +A note on terminology: The first schedule is referred to as the "old" schedule; +the second as the "new" schedule. The resultant schedule is referred to as +the "merged" schedule. Names of things in the old schedule are variations of +the letter "a" while names of things from the new schedule are variations of +"b". The objects that represents routes, agencies and so on are called +"entities". + +usage: merge.py [options] old_feed_path new_feed_path merged_feed_path + +Run merge.py --help for a list of the possible options. +""" + + +__author__ = 'timothy.stranex@gmail.com (Timothy Stranex)' + + +import datetime +import optparse +import os +import re +import sys +import time +import transitfeed +from transitfeed import util +import webbrowser + + +# TODO: +# 1. write unit tests that use actual data +# 2. write a proper trip and stop_times merger +# 3. add a serialised access method for stop_times and shapes to transitfeed +# 4. add support for merging schedules which have some service period overlap + + +def ApproximateDistanceBetweenPoints(pa, pb): + """Finds the distance between two points on the Earth's surface. + + This is an approximate distance based on assuming that the Earth is a sphere. + The points are specified by their lattitude and longitude. + + Args: + pa: the first (lat, lon) point tuple + pb: the second (lat, lon) point tuple + + Returns: + The distance as a float in metres. + """ + alat, alon = pa + blat, blon = pb + sa = transitfeed.Stop(lat=alat, lng=alon) + sb = transitfeed.Stop(lat=blat, lng=blon) + return transitfeed.ApproximateDistanceBetweenStops(sa, sb) + + +class Error(Exception): + """The base exception class for this module.""" + + +class MergeError(Error): + """An error produced when two entities could not be merged.""" + + +class MergeProblemWithContext(transitfeed.ExceptionWithContext): + """The base exception class for problem reporting in the merge module. + + Attributes: + dataset_merger: The DataSetMerger that generated this problem. + entity_type_name: The entity type of the dataset_merger. This is just + dataset_merger.ENTITY_TYPE_NAME. + ERROR_TEXT: The text used for generating the problem message. + """ + + def __init__(self, dataset_merger, problem_type=transitfeed.TYPE_WARNING, + **kwargs): + """Initialise the exception object. + + Args: + dataset_merger: The DataSetMerger instance that generated this problem. + problem_type: The problem severity. This should be set to one of the + corresponding constants in transitfeed. + kwargs: Keyword arguments to be saved as instance attributes. + """ + kwargs['type'] = problem_type + kwargs['entity_type_name'] = dataset_merger.ENTITY_TYPE_NAME + transitfeed.ExceptionWithContext.__init__(self, None, None, **kwargs) + self.dataset_merger = dataset_merger + + def FormatContext(self): + return "In files '%s'" % self.dataset_merger.FILE_NAME + + +class SameIdButNotMerged(MergeProblemWithContext): + ERROR_TEXT = ("There is a %(entity_type_name)s in the old feed with id " + "'%(id)s' and one from the new feed with the same id but " + "they could not be merged:") + + +class CalendarsNotDisjoint(MergeProblemWithContext): + ERROR_TEXT = ("The service periods could not be merged since they are not " + "disjoint.") + + +class MergeNotImplemented(MergeProblemWithContext): + ERROR_TEXT = ("The feed merger does not currently support merging in this " + "file. The entries have been duplicated instead.") + + +class FareRulesBroken(MergeProblemWithContext): + ERROR_TEXT = ("The feed merger is currently unable to handle fare rules " + "properly.") + + +class MergeProblemReporterBase(transitfeed.ProblemReporterBase): + """The base problem reporter class for the merge module.""" + + def SameIdButNotMerged(self, dataset, entity_id, reason): + self._Report(SameIdButNotMerged(dataset, id=entity_id, reason=reason)) + + def CalendarsNotDisjoint(self, dataset): + self._Report(CalendarsNotDisjoint(dataset, + problem_type=transitfeed.TYPE_ERROR)) + + def MergeNotImplemented(self, dataset): + self._Report(MergeNotImplemented(dataset)) + + def FareRulesBroken(self, dataset): + self._Report(FareRulesBroken(dataset)) + + +class ExceptionProblemReporter(MergeProblemReporterBase): + """A problem reporter that reports errors by raising exceptions.""" + + def __init__(self, raise_warnings=False): + """Initialise. + + Args: + raise_warnings: If this is True then warnings are also raised as + exceptions. + """ + MergeProblemReporterBase.__init__(self) + self._raise_warnings = raise_warnings + + def _Report(self, merge_problem): + if self._raise_warnings or merge_problem.IsError(): + raise merge_problem + + +class HTMLProblemReporter(MergeProblemReporterBase): + """A problem reporter which generates HTML output.""" + + def __init__(self): + """Initialise.""" + MergeProblemReporterBase.__init__(self) + self._dataset_warnings = {} # a map from DataSetMergers to their warnings + self._dataset_errors = {} + self._warning_count = 0 + self._error_count = 0 + + def _Report(self, merge_problem): + if merge_problem.IsWarning(): + dataset_problems = self._dataset_warnings + self._warning_count += 1 + else: + dataset_problems = self._dataset_errors + self._error_count += 1 + + problem_html = '
  • %s
  • ' % ( + merge_problem.FormatProblem().replace('\n', '
    ')) + dataset_problems.setdefault(merge_problem.dataset_merger, []).append( + problem_html) + + def _GenerateStatsTable(self, feed_merger): + """Generate an HTML table of merge statistics. + + Args: + feed_merger: The FeedMerger instance. + + Returns: + The generated HTML as a string. + """ + rows = [] + rows.append('Merged' + 'Copied from old feed' + 'Copied from new feed') + for merger in feed_merger.GetMergerList(): + stats = merger.GetMergeStats() + if stats is None: + continue + merged, not_merged_a, not_merged_b = stats + rows.append('%s' + '%d' + '%d' + '%d' % + (merger.DATASET_NAME, merged, not_merged_a, not_merged_b)) + return '%s
    ' % '\n'.join(rows) + + def _GenerateSection(self, problem_type): + """Generate a listing of the given type of problems. + + Args: + problem_type: The type of problem. This is one of the problem type + constants from transitfeed. + + Returns: + The generated HTML as a string. + """ + if problem_type == transitfeed.TYPE_WARNING: + dataset_problems = self._dataset_warnings + heading = 'Warnings' + else: + dataset_problems = self._dataset_errors + heading = 'Errors' + + if not dataset_problems: + return '' + + prefix = '

    %s:

    ' % heading + dataset_sections = [] + for dataset_merger, problems in dataset_problems.items(): + dataset_sections.append('

    %s

      %s
    ' % ( + dataset_merger.FILE_NAME, '\n'.join(problems))) + body = '\n'.join(dataset_sections) + return prefix + body + + def _GenerateSummary(self): + """Generate a summary of the warnings and errors. + + Returns: + The generated HTML as a string. + """ + items = [] + if self._dataset_errors: + items.append('errors: %d' % self._error_count) + if self._dataset_warnings: + items.append('warnings: %d' % self._warning_count) + + if items: + return '

    %s

    ' % '
    '.join(items) + else: + return '

    feeds merged successfully

    ' + + def WriteOutput(self, output_file, feed_merger, + old_feed_path, new_feed_path, merged_feed_path): + """Write the HTML output to a file. + + Args: + output_file: The file object that the HTML output will be written to. + feed_merger: The FeedMerger instance. + old_feed_path: The path to the old feed file as a string. + new_feed_path: The path to the new feed file as a string + merged_feed_path: The path to the merged feed file as a string. This + may be None if no merged feed was written. + """ + if merged_feed_path is None: + html_merged_feed_path = '' + else: + html_merged_feed_path = '

    Merged feed created: %s

    ' % ( + merged_feed_path) + + html_header = """ + + +Feed Merger Results + + + +

    Feed merger results

    +

    Old feed: %(old_feed_path)s

    +

    New feed: %(new_feed_path)s

    +%(html_merged_feed_path)s""" % locals() + + html_stats = self._GenerateStatsTable(feed_merger) + html_summary = self._GenerateSummary() + html_errors = self._GenerateSection(transitfeed.TYPE_ERROR) + html_warnings = self._GenerateSection(transitfeed.TYPE_WARNING) + + html_footer = """ + + +""" % (transitfeed.__version__, + time.strftime('%B %d, %Y at %I:%M %p %Z')) + + output_file.write(transitfeed.EncodeUnicode(html_header)) + output_file.write(transitfeed.EncodeUnicode(html_stats)) + output_file.write(transitfeed.EncodeUnicode(html_summary)) + output_file.write(transitfeed.EncodeUnicode(html_errors)) + output_file.write(transitfeed.EncodeUnicode(html_warnings)) + output_file.write(transitfeed.EncodeUnicode(html_footer)) + + +class ConsoleWarningRaiseErrorProblemReporter(transitfeed.ProblemReporterBase): + """Problem reporter to use when loading feeds for merge.""" + + def _Report(self, e): + if e.IsError(): + raise e + else: + print transitfeed.EncodeUnicode(e.FormatProblem()) + context = e.FormatContext() + if context: + print context + + +def LoadWithoutErrors(path, memory_db): + """"Return a Schedule object loaded from path; sys.exit for any error.""" + loading_problem_handler = ConsoleWarningRaiseErrorProblemReporter() + try: + schedule = transitfeed.Loader(path, + memory_db=memory_db, + problems=loading_problem_handler).Load() + except transitfeed.ExceptionWithContext, e: + print >>sys.stderr, ( + "\n\nFeeds to merge must load without any errors.\n" + "While loading %s the following error was found:\n%s\n%s\n" % + (path, e.FormatContext(), transitfeed.EncodeUnicode(e.FormatProblem()))) + sys.exit(1) + return schedule + + +class DataSetMerger(object): + """A DataSetMerger is in charge of merging a set of entities. + + This is an abstract class and should be subclassed for each different entity + type. + + Attributes: + ENTITY_TYPE_NAME: The name of the entity type like 'agency' or 'stop'. + FILE_NAME: The name of the file containing this data set like 'agency.txt'. + DATASET_NAME: A name for the dataset like 'Agencies' or 'Stops'. + """ + + def __init__(self, feed_merger): + """Initialise. + + Args: + feed_merger: The FeedMerger. + """ + self.feed_merger = feed_merger + self._num_merged = 0 + self._num_not_merged_a = 0 + self._num_not_merged_b = 0 + + def _MergeIdentical(self, a, b): + """Tries to merge two values. The values are required to be identical. + + Args: + a: The first value. + b: The second value. + + Returns: + The trivially merged value. + + Raises: + MergeError: The values were not identical. + """ + if a != b: + raise MergeError("values must be identical ('%s' vs '%s')" % + (transitfeed.EncodeUnicode(a), + transitfeed.EncodeUnicode(b))) + return b + + def _MergeIdenticalCaseInsensitive(self, a, b): + """Tries to merge two strings. + + The string are required to be the same ignoring case. The second string is + always used as the merged value. + + Args: + a: The first string. + b: The second string. + + Returns: + The merged string. This is equal to the second string. + + Raises: + MergeError: The strings were not the same ignoring case. + """ + if a.lower() != b.lower(): + raise MergeError("values must be the same (case insensitive) " + "('%s' vs '%s')" % (transitfeed.EncodeUnicode(a), + transitfeed.EncodeUnicode(b))) + return b + + def _MergeOptional(self, a, b): + """Tries to merge two values which may be None. + + If both values are not None, they are required to be the same and the + merge is trivial. If one of the values is None and the other is not None, + the merge results in the one which is not None. If both are None, the merge + results in None. + + Args: + a: The first value. + b: The second value. + + Returns: + The merged value. + + Raises: + MergeError: If both values are not None and are not the same. + """ + if a and b: + if a != b: + raise MergeError("values must be identical if both specified " + "('%s' vs '%s')" % (transitfeed.EncodeUnicode(a), + transitfeed.EncodeUnicode(b))) + return a or b + + def _MergeSameAgency(self, a_agency_id, b_agency_id): + """Merge agency ids to the corresponding agency id in the merged schedule. + + Args: + a_agency_id: an agency id from the old schedule + b_agency_id: an agency id from the new schedule + + Returns: + The agency id of the corresponding merged agency. + + Raises: + MergeError: If a_agency_id and b_agency_id do not correspond to the same + merged agency. + KeyError: Either aaid or baid is not a valid agency id. + """ + a_agency_id = (a_agency_id or + self.feed_merger.a_schedule.GetDefaultAgency().agency_id) + b_agency_id = (b_agency_id or + self.feed_merger.b_schedule.GetDefaultAgency().agency_id) + a_agency = self.feed_merger.a_merge_map[ + self.feed_merger.a_schedule.GetAgency(a_agency_id)] + b_agency = self.feed_merger.b_merge_map[ + self.feed_merger.b_schedule.GetAgency(b_agency_id)] + if a_agency != b_agency: + raise MergeError('agency must be the same') + return a_agency.agency_id + + def _SchemedMerge(self, scheme, a, b): + """Tries to merge two entities according to a merge scheme. + + A scheme is specified by a map where the keys are entity attributes and the + values are merge functions like Merger._MergeIdentical or + Merger._MergeOptional. The entity is first migrated to the merged schedule. + Then the attributes are individually merged as specified by the scheme. + + Args: + scheme: The merge scheme, a map from entity attributes to merge + functions. + a: The entity from the old schedule. + b: The entity from the new schedule. + + Returns: + The migrated and merged entity. + + Raises: + MergeError: One of the attributes was not able to be merged. + """ + migrated = self._Migrate(b, self.feed_merger.b_schedule, False) + for attr, merger in scheme.items(): + a_attr = getattr(a, attr, None) + b_attr = getattr(b, attr, None) + try: + merged_attr = merger(a_attr, b_attr) + except MergeError, merge_error: + raise MergeError("Attribute '%s' could not be merged: %s." % ( + attr, merge_error)) + if migrated is not None: + setattr(migrated, attr, merged_attr) + return migrated + + def _MergeSameId(self): + """Tries to merge entities based on their ids. + + This tries to merge only the entities from the old and new schedules which + have the same id. These are added into the merged schedule. Entities which + do not merge or do not have the same id as another entity in the other + schedule are simply migrated into the merged schedule. + + This method is less flexible than _MergeDifferentId since it only tries + to merge entities which have the same id while _MergeDifferentId tries to + merge everything. However, it is faster and so should be used whenever + possible. + + This method makes use of various methods like _Merge and _Migrate which + are not implemented in the abstract DataSetMerger class. These method + should be overwritten in a subclass to allow _MergeSameId to work with + different entity types. + + Returns: + The number of merged entities. + """ + a_not_merged = [] + b_not_merged = [] + + for a in self._GetIter(self.feed_merger.a_schedule): + try: + b = self._GetById(self.feed_merger.b_schedule, self._GetId(a)) + except KeyError: + # there was no entity in B with the same id as a + a_not_merged.append(a) + continue + try: + self._Add(a, b, self._MergeEntities(a, b)) + self._num_merged += 1 + except MergeError, merge_error: + a_not_merged.append(a) + b_not_merged.append(b) + self._ReportSameIdButNotMerged(self._GetId(a), merge_error) + + for b in self._GetIter(self.feed_merger.b_schedule): + try: + a = self._GetById(self.feed_merger.a_schedule, self._GetId(b)) + except KeyError: + # there was no entity in A with the same id as b + b_not_merged.append(b) + + # migrate the remaining entities + for a in a_not_merged: + newid = self._HasId(self.feed_merger.b_schedule, self._GetId(a)) + self._Add(a, None, self._Migrate(a, self.feed_merger.a_schedule, newid)) + for b in b_not_merged: + newid = self._HasId(self.feed_merger.a_schedule, self._GetId(b)) + self._Add(None, b, self._Migrate(b, self.feed_merger.b_schedule, newid)) + + self._num_not_merged_a = len(a_not_merged) + self._num_not_merged_b = len(b_not_merged) + return self._num_merged + + def _MergeDifferentId(self): + """Tries to merge all possible combinations of entities. + + This tries to merge every entity in the old schedule with every entity in + the new schedule. Unlike _MergeSameId, the ids do not need to match. + However, _MergeDifferentId is much slower than _MergeSameId. + + This method makes use of various methods like _Merge and _Migrate which + are not implemented in the abstract DataSetMerger class. These method + should be overwritten in a subclass to allow _MergeSameId to work with + different entity types. + + Returns: + The number of merged entities. + """ + # TODO: The same entity from A could merge with multiple from B. + # This should either generate an error or should be prevented from + # happening. + for a in self._GetIter(self.feed_merger.a_schedule): + for b in self._GetIter(self.feed_merger.b_schedule): + try: + self._Add(a, b, self._MergeEntities(a, b)) + self._num_merged += 1 + except MergeError: + continue + + for a in self._GetIter(self.feed_merger.a_schedule): + if a not in self.feed_merger.a_merge_map: + self._num_not_merged_a += 1 + newid = self._HasId(self.feed_merger.b_schedule, self._GetId(a)) + self._Add(a, None, + self._Migrate(a, self.feed_merger.a_schedule, newid)) + for b in self._GetIter(self.feed_merger.b_schedule): + if b not in self.feed_merger.b_merge_map: + self._num_not_merged_b += 1 + newid = self._HasId(self.feed_merger.a_schedule, self._GetId(b)) + self._Add(None, b, + self._Migrate(b, self.feed_merger.b_schedule, newid)) + + return self._num_merged + + def _ReportSameIdButNotMerged(self, entity_id, reason): + """Report that two entities have the same id but could not be merged. + + Args: + entity_id: The id of the entities. + reason: A string giving a reason why they could not be merged. + """ + self.feed_merger.problem_reporter.SameIdButNotMerged(self, + entity_id, + reason) + + def _GetIter(self, schedule): + """Returns an iterator of entities for this data set in the given schedule. + + This method usually corresponds to one of the methods from + transitfeed.Schedule like GetAgencyList() or GetRouteList(). + + Note: This method must be overwritten in a subclass if _MergeSameId or + _MergeDifferentId are to be used. + + Args: + schedule: Either the old or new schedule from the FeedMerger. + + Returns: + An iterator of entities. + """ + raise NotImplementedError() + + def _GetById(self, schedule, entity_id): + """Returns an entity given its id. + + This method usually corresponds to one of the methods from + transitfeed.Schedule like GetAgency() or GetRoute(). + + Note: This method must be overwritten in a subclass if _MergeSameId or + _MergeDifferentId are to be used. + + Args: + schedule: Either the old or new schedule from the FeedMerger. + entity_id: The id string of the entity. + + Returns: + The entity with the given id. + + Raises: + KeyError: There is not entity with the given id. + """ + raise NotImplementedError() + + def _HasId(self, schedule, entity_id): + """Check if the schedule has an entity with the given id. + + Args: + schedule: The transitfeed.Schedule instance to look in. + entity_id: The id of the entity. + + Returns: + True if the schedule has an entity with the id or False if not. + """ + try: + self._GetById(schedule, entity_id) + has = True + except KeyError: + has = False + return has + + def _MergeEntities(self, a, b): + """Tries to merge the two entities. + + Note: This method must be overwritten in a subclass if _MergeSameId or + _MergeDifferentId are to be used. + + Args: + a: The entity from the old schedule. + b: The entity from the new schedule. + + Returns: + The merged migrated entity. + + Raises: + MergeError: The entities were not able to be merged. + """ + raise NotImplementedError() + + def _Migrate(self, entity, schedule, newid): + """Migrates the entity to the merge schedule. + + This involves copying the entity and updating any ids to point to the + corresponding entities in the merged schedule. If newid is True then + a unique id is generated for the migrated entity using the original id + as a prefix. + + Note: This method must be overwritten in a subclass if _MergeSameId or + _MergeDifferentId are to be used. + + Args: + entity: The entity to migrate. + schedule: The schedule from the FeedMerger that contains ent. + newid: Whether to generate a new id (True) or keep the original (False). + + Returns: + The migrated entity. + """ + raise NotImplementedError() + + def _Add(self, a, b, migrated): + """Adds the migrated entity to the merged schedule. + + If a and b are both not None, it means that a and b were merged to create + migrated. If one of a or b is None, it means that the other was not merged + but has been migrated. This mapping is registered with the FeedMerger. + + Note: This method must be overwritten in a subclass if _MergeSameId or + _MergeDifferentId are to be used. + + Args: + a: The original entity from the old schedule. + b: The original entity from the new schedule. + migrated: The migrated entity for the merged schedule. + """ + raise NotImplementedError() + + def _GetId(self, entity): + """Returns the id of the given entity. + + Note: This method must be overwritten in a subclass if _MergeSameId or + _MergeDifferentId are to be used. + + Args: + entity: The entity. + + Returns: + The id of the entity as a string or None. + """ + raise NotImplementedError() + + def MergeDataSets(self): + """Merge the data sets. + + This method is called in FeedMerger.MergeSchedule(). + + Note: This method must be overwritten in a subclass. + + Returns: + A boolean which is False if the dataset was unable to be merged and + as a result the entire merge should be aborted. In this case, the problem + will have been reported using the FeedMerger's problem reporter. + """ + raise NotImplementedError() + + def GetMergeStats(self): + """Returns some merge statistics. + + These are given as a tuple (merged, not_merged_a, not_merged_b) where + "merged" is the number of merged entities, "not_merged_a" is the number of + entities from the old schedule that were not merged and "not_merged_b" is + the number of entities from the new schedule that were not merged. + + The return value can also be None. This means that there are no statistics + for this entity type. + + The statistics are only available after MergeDataSets() has been called. + + Returns: + Either the statistics tuple or None. + """ + return (self._num_merged, self._num_not_merged_a, self._num_not_merged_b) + + +class AgencyMerger(DataSetMerger): + """A DataSetMerger for agencies.""" + + ENTITY_TYPE_NAME = 'agency' + FILE_NAME = 'agency.txt' + DATASET_NAME = 'Agencies' + + def _GetIter(self, schedule): + return schedule.GetAgencyList() + + def _GetById(self, schedule, agency_id): + return schedule.GetAgency(agency_id) + + def _MergeEntities(self, a, b): + """Merges two agencies. + + To be merged, they are required to have the same id, name, url and + timezone. The remaining language attribute is taken from the new agency. + + Args: + a: The first agency. + b: The second agency. + + Returns: + The merged agency. + + Raises: + MergeError: The agencies could not be merged. + """ + + def _MergeAgencyId(a_agency_id, b_agency_id): + """Merge two agency ids. + + The only difference between this and _MergeIdentical() is that the values + None and '' are regarded as being the same. + + Args: + a_agency_id: The first agency id. + b_agency_id: The second agency id. + + Returns: + The merged agency id. + + Raises: + MergeError: The agency ids could not be merged. + """ + a_agency_id = a_agency_id or None + b_agency_id = b_agency_id or None + return self._MergeIdentical(a_agency_id, b_agency_id) + + scheme = {'agency_id': _MergeAgencyId, + 'agency_name': self._MergeIdentical, + 'agency_url': self._MergeIdentical, + 'agency_timezone': self._MergeIdentical} + return self._SchemedMerge(scheme, a, b) + + def _Migrate(self, entity, schedule, newid): + a = transitfeed.Agency(field_dict=entity) + if newid: + a.agency_id = self.feed_merger.GenerateId(entity.agency_id) + return a + + def _Add(self, a, b, migrated): + self.feed_merger.Register(a, b, migrated) + self.feed_merger.merged_schedule.AddAgencyObject(migrated) + + def _GetId(self, entity): + return entity.agency_id + + def MergeDataSets(self): + self._MergeSameId() + return True + + +class StopMerger(DataSetMerger): + """A DataSetMerger for stops. + + Attributes: + largest_stop_distance: The largest distance allowed between stops that + will be merged in metres. + """ + + ENTITY_TYPE_NAME = 'stop' + FILE_NAME = 'stops.txt' + DATASET_NAME = 'Stops' + + largest_stop_distance = 10.0 + + def __init__(self, feed_merger): + DataSetMerger.__init__(self, feed_merger) + self._merged = [] + self._a_not_merged = [] + self._b_not_merged = [] + + def SetLargestStopDistance(self, distance): + """Sets largest_stop_distance.""" + self.largest_stop_distance = distance + + def _GetIter(self, schedule): + return schedule.GetStopList() + + def _GetById(self, schedule, stop_id): + return schedule.GetStop(stop_id) + + def _MergeEntities(self, a, b): + """Merges two stops. + + For the stops to be merged, they must have: + - the same stop_id + - the same stop_name (case insensitive) + - the same zone_id + - locations less than largest_stop_distance apart + The other attributes can have arbitary changes. The merged attributes are + taken from the new stop. + + Args: + a: The first stop. + b: The second stop. + + Returns: + The merged stop. + + Raises: + MergeError: The stops could not be merged. + """ + distance = transitfeed.ApproximateDistanceBetweenStops(a, b) + if distance > self.largest_stop_distance: + raise MergeError("Stops are too far apart: %.1fm " + "(largest_stop_distance is %.1fm)." % + (distance, self.largest_stop_distance)) + scheme = {'stop_id': self._MergeIdentical, + 'stop_name': self._MergeIdenticalCaseInsensitive, + 'zone_id': self._MergeIdentical, + 'location_type': self._MergeIdentical} + return self._SchemedMerge(scheme, a, b) + + def _Migrate(self, entity, schedule, newid): + migrated_stop = transitfeed.Stop(field_dict=entity) + if newid: + migrated_stop.stop_id = self.feed_merger.GenerateId(entity.stop_id) + return migrated_stop + + def _Add(self, a, b, migrated_stop): + self.feed_merger.Register(a, b, migrated_stop) + + # The migrated_stop will be added to feed_merger.merged_schedule later + # since adding must be done after the zone_ids have been finalized. + if a and b: + self._merged.append((a, b, migrated_stop)) + elif a: + self._a_not_merged.append((a, migrated_stop)) + elif b: + self._b_not_merged.append((b, migrated_stop)) + + def _GetId(self, entity): + return entity.stop_id + + def MergeDataSets(self): + num_merged = self._MergeSameId() + fm = self.feed_merger + + # now we do all the zone_id and parent_station mapping + + # the zone_ids for merged stops can be preserved + for (a, b, merged_stop) in self._merged: + assert a.zone_id == b.zone_id + fm.a_zone_map[a.zone_id] = a.zone_id + fm.b_zone_map[b.zone_id] = b.zone_id + merged_stop.zone_id = a.zone_id + if merged_stop.parent_station: + # Merged stop has a parent. Update it to be the parent it had in b. + parent_in_b = fm.b_schedule.GetStop(b.parent_station) + merged_stop.parent_station = fm.b_merge_map[parent_in_b].stop_id + fm.merged_schedule.AddStopObject(merged_stop) + + self._UpdateAndMigrateUnmerged(self._a_not_merged, fm.a_zone_map, + fm.a_merge_map, fm.a_schedule) + self._UpdateAndMigrateUnmerged(self._b_not_merged, fm.b_zone_map, + fm.b_merge_map, fm.b_schedule) + + print 'Stops merged: %d of %d, %d' % ( + num_merged, + len(fm.a_schedule.GetStopList()), + len(fm.b_schedule.GetStopList())) + return True + + def _UpdateAndMigrateUnmerged(self, not_merged_stops, zone_map, merge_map, + schedule): + """Correct references in migrated unmerged stops and add to merged_schedule. + + For stops migrated from one of the input feeds to the output feed update the + parent_station and zone_id references to point to objects in the output + feed. Then add the migrated stop to the new schedule. + + Args: + not_merged_stops: list of stops from one input feed that have not been + merged + zone_map: map from zone_id in the input feed to zone_id in the output feed + merge_map: map from Stop objects in the input feed to Stop objects in + the output feed + schedule: the input Schedule object + """ + # for the unmerged stops, we use an already mapped zone_id if possible + # if not, we generate a new one and add it to the map + for stop, migrated_stop in not_merged_stops: + if stop.zone_id in zone_map: + migrated_stop.zone_id = zone_map[stop.zone_id] + else: + migrated_stop.zone_id = self.feed_merger.GenerateId(stop.zone_id) + zone_map[stop.zone_id] = migrated_stop.zone_id + if stop.parent_station: + parent_original = schedule.GetStop(stop.parent_station) + migrated_stop.parent_station = merge_map[parent_original].stop_id + self.feed_merger.merged_schedule.AddStopObject(migrated_stop) + + +class RouteMerger(DataSetMerger): + """A DataSetMerger for routes.""" + + ENTITY_TYPE_NAME = 'route' + FILE_NAME = 'routes.txt' + DATASET_NAME = 'Routes' + + def _GetIter(self, schedule): + return schedule.GetRouteList() + + def _GetById(self, schedule, route_id): + return schedule.GetRoute(route_id) + + def _MergeEntities(self, a, b): + scheme = {'route_short_name': self._MergeIdentical, + 'route_long_name': self._MergeIdentical, + 'agency_id': self._MergeSameAgency, + 'route_type': self._MergeIdentical, + 'route_id': self._MergeIdentical, + 'route_url': self._MergeOptional, + 'route_color': self._MergeOptional, + 'route_text_color': self._MergeOptional} + return self._SchemedMerge(scheme, a, b) + + def _Migrate(self, entity, schedule, newid): + migrated_route = transitfeed.Route(field_dict=entity) + if newid: + migrated_route.route_id = self.feed_merger.GenerateId(entity.route_id) + if entity.agency_id: + original_agency = schedule.GetAgency(entity.agency_id) + else: + original_agency = schedule.GetDefaultAgency() + + migrated_agency = self.feed_merger.GetMergedObject(original_agency) + migrated_route.agency_id = migrated_agency.agency_id + return migrated_route + + def _Add(self, a, b, migrated_route): + self.feed_merger.Register(a, b, migrated_route) + self.feed_merger.merged_schedule.AddRouteObject(migrated_route) + + def _GetId(self, entity): + return entity.route_id + + def MergeDataSets(self): + self._MergeSameId() + return True + + +class ServicePeriodMerger(DataSetMerger): + """A DataSetMerger for service periods. + + Attributes: + require_disjoint_calendars: A boolean specifying whether to require + disjoint calendars when merging (True) or not (False). + """ + + ENTITY_TYPE_NAME = 'service period' + FILE_NAME = 'calendar.txt/calendar_dates.txt' + DATASET_NAME = 'Service Periods' + + def __init__(self, feed_merger): + DataSetMerger.__init__(self, feed_merger) + self.require_disjoint_calendars = True + + def _ReportSameIdButNotMerged(self, entity_id, reason): + pass + + def _GetIter(self, schedule): + return schedule.GetServicePeriodList() + + def _GetById(self, schedule, service_id): + return schedule.GetServicePeriod(service_id) + + def _MergeEntities(self, a, b): + """Tries to merge two service periods. + + Note: Currently this just raises a MergeError since service periods cannot + be merged. + + Args: + a: The first service period. + b: The second service period. + + Returns: + The merged service period. + + Raises: + MergeError: When the service periods could not be merged. + """ + raise MergeError('Cannot merge service periods') + + def _Migrate(self, original_service_period, schedule, newid): + migrated_service_period = transitfeed.ServicePeriod() + migrated_service_period.day_of_week = list( + original_service_period.day_of_week) + migrated_service_period.start_date = original_service_period.start_date + migrated_service_period.end_date = original_service_period.end_date + migrated_service_period.date_exceptions = dict( + original_service_period.date_exceptions) + if newid: + migrated_service_period.service_id = self.feed_merger.GenerateId( + original_service_period.service_id) + else: + migrated_service_period.service_id = original_service_period.service_id + return migrated_service_period + + def _Add(self, a, b, migrated_service_period): + self.feed_merger.Register(a, b, migrated_service_period) + self.feed_merger.merged_schedule.AddServicePeriodObject( + migrated_service_period) + + def _GetId(self, entity): + return entity.service_id + + def MergeDataSets(self): + if self.require_disjoint_calendars and not self.CheckDisjointCalendars(): + self.feed_merger.problem_reporter.CalendarsNotDisjoint(self) + return False + self._MergeSameId() + self.feed_merger.problem_reporter.MergeNotImplemented(self) + return True + + def DisjoinCalendars(self, cutoff): + """Forces the old and new calendars to be disjoint about a cutoff date. + + This truncates the service periods of the old schedule so that service + stops one day before the given cutoff date and truncates the new schedule + so that service only begins on the cutoff date. + + Args: + cutoff: The cutoff date as a string in YYYYMMDD format. The timezone + is the same as used in the calendar.txt file. + """ + + def TruncatePeriod(service_period, start, end): + """Truncate the service period to into the range [start, end]. + + Args: + service_period: The service period to truncate. + start: The start date as a string in YYYYMMDD format. + end: The end date as a string in YYYYMMDD format. + """ + service_period.start_date = max(service_period.start_date, start) + service_period.end_date = min(service_period.end_date, end) + dates_to_delete = [] + for k in service_period.date_exceptions: + if (k < start) or (k > end): + dates_to_delete.append(k) + for k in dates_to_delete: + del service_period.date_exceptions[k] + + # find the date one day before cutoff + year = int(cutoff[:4]) + month = int(cutoff[4:6]) + day = int(cutoff[6:8]) + cutoff_date = datetime.date(year, month, day) + one_day_delta = datetime.timedelta(days=1) + before = (cutoff_date - one_day_delta).strftime('%Y%m%d') + + for a in self.feed_merger.a_schedule.GetServicePeriodList(): + TruncatePeriod(a, 0, before) + for b in self.feed_merger.b_schedule.GetServicePeriodList(): + TruncatePeriod(b, cutoff, '9'*8) + + def CheckDisjointCalendars(self): + """Check whether any old service periods intersect with any new ones. + + This is a rather coarse check based on + transitfeed.SevicePeriod.GetDateRange. + + Returns: + True if the calendars are disjoint or False if not. + """ + # TODO: Do an exact check here. + + a_service_periods = self.feed_merger.a_schedule.GetServicePeriodList() + b_service_periods = self.feed_merger.b_schedule.GetServicePeriodList() + + for a_service_period in a_service_periods: + a_start, a_end = a_service_period.GetDateRange() + for b_service_period in b_service_periods: + b_start, b_end = b_service_period.GetDateRange() + overlap_start = max(a_start, b_start) + overlap_end = min(a_end, b_end) + if overlap_end >= overlap_start: + return False + return True + + def GetMergeStats(self): + return None + + +class FareMerger(DataSetMerger): + """A DataSetMerger for fares.""" + + ENTITY_TYPE_NAME = 'fare' + FILE_NAME = 'fare_attributes.txt' + DATASET_NAME = 'Fares' + + def _GetIter(self, schedule): + return schedule.GetFareList() + + def _GetById(self, schedule, fare_id): + return schedule.GetFare(fare_id) + + def _MergeEntities(self, a, b): + """Merges the fares if all the attributes are the same.""" + scheme = {'price': self._MergeIdentical, + 'currency_type': self._MergeIdentical, + 'payment_method': self._MergeIdentical, + 'transfers': self._MergeIdentical, + 'transfer_duration': self._MergeIdentical} + return self._SchemedMerge(scheme, a, b) + + def _Migrate(self, original_fare, schedule, newid): + migrated_fare = transitfeed.Fare( + field_list=original_fare.GetFieldValuesTuple()) + if newid: + migrated_fare.fare_id = self.feed_merger.GenerateId( + original_fare.fare_id) + return migrated_fare + + def _Add(self, a, b, migrated_fare): + self.feed_merger.Register(a, b, migrated_fare) + self.feed_merger.merged_schedule.AddFareObject(migrated_fare) + + def _GetId(self, fare): + return fare.fare_id + + def MergeDataSets(self): + num_merged = self._MergeSameId() + print 'Fares merged: %d of %d, %d' % ( + num_merged, + len(self.feed_merger.a_schedule.GetFareList()), + len(self.feed_merger.b_schedule.GetFareList())) + return True + + +class ShapeMerger(DataSetMerger): + """A DataSetMerger for shapes. + + In this implementation, merging shapes means just taking the new shape. + The only conditions for a merge are that the shape_ids are the same and + the endpoints of the old and new shapes are no further than + largest_shape_distance apart. + + Attributes: + largest_shape_distance: The largest distance between the endpoints of two + shapes allowed for them to be merged in metres. + """ + + ENTITY_TYPE_NAME = 'shape' + FILE_NAME = 'shapes.txt' + DATASET_NAME = 'Shapes' + + largest_shape_distance = 10.0 + + def SetLargestShapeDistance(self, distance): + """Sets largest_shape_distance.""" + self.largest_shape_distance = distance + + def _GetIter(self, schedule): + return schedule.GetShapeList() + + def _GetById(self, schedule, shape_id): + return schedule.GetShape(shape_id) + + def _MergeEntities(self, a, b): + """Merges the shapes by taking the new shape. + + Args: + a: The first transitfeed.Shape instance. + b: The second transitfeed.Shape instance. + + Returns: + The merged shape. + + Raises: + MergeError: If the ids are different or if the endpoints are further + than largest_shape_distance apart. + """ + if a.shape_id != b.shape_id: + raise MergeError('shape_id must be the same') + + distance = max(ApproximateDistanceBetweenPoints(a.points[0][:2], + b.points[0][:2]), + ApproximateDistanceBetweenPoints(a.points[-1][:2], + b.points[-1][:2])) + if distance > self.largest_shape_distance: + raise MergeError('The shape endpoints are too far away: %.1fm ' + '(largest_shape_distance is %.1fm)' % + (distance, self.largest_shape_distance)) + + return self._Migrate(b, self.feed_merger.b_schedule, False) + + def _Migrate(self, original_shape, schedule, newid): + migrated_shape = transitfeed.Shape(original_shape.shape_id) + if newid: + migrated_shape.shape_id = self.feed_merger.GenerateId( + original_shape.shape_id) + for (lat, lon, dist) in original_shape.points: + migrated_shape.AddPoint(lat=lat, lon=lon, distance=dist) + return migrated_shape + + def _Add(self, a, b, migrated_shape): + self.feed_merger.Register(a, b, migrated_shape) + self.feed_merger.merged_schedule.AddShapeObject(migrated_shape) + + def _GetId(self, shape): + return shape.shape_id + + def MergeDataSets(self): + self._MergeSameId() + return True + + +class TripMerger(DataSetMerger): + """A DataSetMerger for trips. + + This implementation makes no attempt to merge trips, it simply migrates + them all to the merged feed. + """ + + ENTITY_TYPE_NAME = 'trip' + FILE_NAME = 'trips.txt' + DATASET_NAME = 'Trips' + + def _ReportSameIdButNotMerged(self, trip_id, reason): + pass + + def _GetIter(self, schedule): + return schedule.GetTripList() + + def _GetById(self, schedule, trip_id): + return schedule.GetTrip(trip_id) + + def _MergeEntities(self, a, b): + """Raises a MergeError because currently trips cannot be merged.""" + raise MergeError('Cannot merge trips') + + def _Migrate(self, original_trip, schedule, newid): + migrated_trip = transitfeed.Trip(field_dict=original_trip) + # Make new trip_id first. AddTripObject reports a problem if it conflicts + # with an existing id. + if newid: + migrated_trip.trip_id = self.feed_merger.GenerateId( + original_trip.trip_id) + # Need to add trip to schedule before copying stoptimes + self.feed_merger.merged_schedule.AddTripObject(migrated_trip, + validate=False) + + if schedule == self.feed_merger.a_schedule: + merge_map = self.feed_merger.a_merge_map + else: + merge_map = self.feed_merger.b_merge_map + + original_route = schedule.GetRoute(original_trip.route_id) + migrated_trip.route_id = merge_map[original_route].route_id + + original_service_period = schedule.GetServicePeriod( + original_trip.service_id) + migrated_trip.service_id = merge_map[original_service_period].service_id + + if original_trip.block_id: + migrated_trip.block_id = '%s_%s' % ( + self.feed_merger.GetScheduleName(schedule), + original_trip.block_id) + + if original_trip.shape_id: + original_shape = schedule.GetShape(original_trip.shape_id) + migrated_trip.shape_id = merge_map[original_shape].shape_id + + for original_stop_time in original_trip.GetStopTimes(): + migrated_stop_time = transitfeed.StopTime( + None, + merge_map[original_stop_time.stop], + original_stop_time.arrival_time, + original_stop_time.departure_time, + original_stop_time.stop_headsign, + original_stop_time.pickup_type, + original_stop_time.drop_off_type, + original_stop_time.shape_dist_traveled, + original_stop_time.arrival_secs, + original_stop_time.departure_secs) + migrated_trip.AddStopTimeObject(migrated_stop_time) + + for headway_period in original_trip.GetHeadwayPeriodTuples(): + migrated_trip.AddHeadwayPeriod(*headway_period) + + return migrated_trip + + def _Add(self, a, b, migrated_trip): + # Validate now, since it wasn't done in _Migrate + migrated_trip.Validate(self.feed_merger.merged_schedule.problem_reporter) + self.feed_merger.Register(a, b, migrated_trip) + + def _GetId(self, trip): + return trip.trip_id + + def MergeDataSets(self): + self._MergeSameId() + self.feed_merger.problem_reporter.MergeNotImplemented(self) + return True + + def GetMergeStats(self): + return None + + +class FareRuleMerger(DataSetMerger): + """A DataSetMerger for fare rules.""" + + ENTITY_TYPE_NAME = 'fare rule' + FILE_NAME = 'fare_rules.txt' + DATASET_NAME = 'Fare Rules' + + def MergeDataSets(self): + """Merge the fare rule datasets. + + The fare rules are first migrated. Merging is done by removing any + duplicate rules. + + Returns: + True since fare rules can always be merged. + """ + rules = set() + for (schedule, merge_map, zone_map) in ([self.feed_merger.a_schedule, + self.feed_merger.a_merge_map, + self.feed_merger.a_zone_map], + [self.feed_merger.b_schedule, + self.feed_merger.b_merge_map, + self.feed_merger.b_zone_map]): + for fare in schedule.GetFareList(): + for fare_rule in fare.GetFareRuleList(): + fare_id = merge_map[schedule.GetFare(fare_rule.fare_id)].fare_id + route_id = (fare_rule.route_id and + merge_map[schedule.GetRoute(fare_rule.route_id)].route_id) + origin_id = (fare_rule.origin_id and + zone_map[fare_rule.origin_id]) + destination_id = (fare_rule.destination_id and + zone_map[fare_rule.destination_id]) + contains_id = (fare_rule.contains_id and + zone_map[fare_rule.contains_id]) + rules.add((fare_id, route_id, origin_id, destination_id, + contains_id)) + for fare_rule_tuple in rules: + migrated_fare_rule = transitfeed.FareRule(*fare_rule_tuple) + self.feed_merger.merged_schedule.AddFareRuleObject(migrated_fare_rule) + + if rules: + self.feed_merger.problem_reporter.FareRulesBroken(self) + print 'Fare Rules: union has %d fare rules' % len(rules) + return True + + def GetMergeStats(self): + return None + + +class FeedMerger(object): + """A class for merging two whole feeds. + + This class takes two instances of transitfeed.Schedule and uses + DataSetMerger instances to merge the feeds and produce the resultant + merged feed. + + Attributes: + a_schedule: The old transitfeed.Schedule instance. + b_schedule: The new transitfeed.Schedule instance. + problem_reporter: The merge problem reporter. + merged_schedule: The merged transitfeed.Schedule instance. + a_merge_map: A map from old entities to merged entities. + b_merge_map: A map from new entities to merged entities. + a_zone_map: A map from old zone ids to merged zone ids. + b_zone_map: A map from new zone ids to merged zone ids. + """ + + def __init__(self, a_schedule, b_schedule, merged_schedule, + problem_reporter=None): + """Initialise the merger. + + Once this initialiser has been called, a_schedule and b_schedule should + not be modified. + + Args: + a_schedule: The old schedule, an instance of transitfeed.Schedule. + b_schedule: The new schedule, an instance of transitfeed.Schedule. + problem_reporter: The problem reporter, an instance of + transitfeed.ProblemReporterBase. This can be None in + which case the ExceptionProblemReporter is used. + """ + self.a_schedule = a_schedule + self.b_schedule = b_schedule + self.merged_schedule = merged_schedule + self.a_merge_map = {} + self.b_merge_map = {} + self.a_zone_map = {} + self.b_zone_map = {} + self._mergers = [] + self._idnum = max(self._FindLargestIdPostfixNumber(self.a_schedule), + self._FindLargestIdPostfixNumber(self.b_schedule)) + + if problem_reporter is not None: + self.problem_reporter = problem_reporter + else: + self.problem_reporter = ExceptionProblemReporter() + + def _FindLargestIdPostfixNumber(self, schedule): + """Finds the largest integer used as the ending of an id in the schedule. + + Args: + schedule: The schedule to check. + + Returns: + The maximum integer used as an ending for an id. + """ + postfix_number_re = re.compile('(\d+)$') + + def ExtractPostfixNumber(entity_id): + """Try to extract an integer from the end of entity_id. + + If entity_id is None or if there is no integer ending the id, zero is + returned. + + Args: + entity_id: An id string or None. + + Returns: + An integer ending the entity_id or zero. + """ + if entity_id is None: + return 0 + match = postfix_number_re.search(entity_id) + if match is not None: + return int(match.group(1)) + else: + return 0 + + id_data_sets = {'agency_id': schedule.GetAgencyList(), + 'stop_id': schedule.GetStopList(), + 'route_id': schedule.GetRouteList(), + 'trip_id': schedule.GetTripList(), + 'service_id': schedule.GetServicePeriodList(), + 'fare_id': schedule.GetFareList(), + 'shape_id': schedule.GetShapeList()} + + max_postfix_number = 0 + for id_name, entity_list in id_data_sets.items(): + for entity in entity_list: + entity_id = getattr(entity, id_name) + postfix_number = ExtractPostfixNumber(entity_id) + max_postfix_number = max(max_postfix_number, postfix_number) + return max_postfix_number + + def GetScheduleName(self, schedule): + """Returns a single letter identifier for the schedule. + + This only works for the old and new schedules which return 'a' and 'b' + respectively. The purpose of such identifiers is for generating ids. + + Args: + schedule: The transitfeed.Schedule instance. + + Returns: + The schedule identifier. + + Raises: + KeyError: schedule is not the old or new schedule. + """ + return {self.a_schedule: 'a', self.b_schedule: 'b'}[schedule] + + def GenerateId(self, entity_id=None): + """Generate a unique id based on the given id. + + This is done by appending a counter which is then incremented. The + counter is initialised at the maximum number used as an ending for + any id in the old and new schedules. + + Args: + entity_id: The base id string. This is allowed to be None. + + Returns: + The generated id. + """ + self._idnum += 1 + if entity_id: + return '%s_merged_%d' % (entity_id, self._idnum) + else: + return 'merged_%d' % self._idnum + + def Register(self, a, b, migrated_entity): + """Registers a merge mapping. + + If a and b are both not None, this means that entities a and b were merged + to produce migrated_entity. If one of a or b are not None, then it means + it was not merged but simply migrated. + + The effect of a call to register is to update a_merge_map and b_merge_map + according to the merge. + + Args: + a: The entity from the old feed or None. + b: The entity from the new feed or None. + migrated_entity: The migrated entity. + """ + if a is not None: self.a_merge_map[a] = migrated_entity + if b is not None: self.b_merge_map[b] = migrated_entity + + def AddMerger(self, merger): + """Add a DataSetMerger to be run by Merge(). + + Args: + merger: The DataSetMerger instance. + """ + self._mergers.append(merger) + + def AddDefaultMergers(self): + """Adds the default DataSetMergers defined in this module.""" + self.AddMerger(AgencyMerger(self)) + self.AddMerger(StopMerger(self)) + self.AddMerger(RouteMerger(self)) + self.AddMerger(ServicePeriodMerger(self)) + self.AddMerger(FareMerger(self)) + self.AddMerger(ShapeMerger(self)) + self.AddMerger(TripMerger(self)) + self.AddMerger(FareRuleMerger(self)) + + def GetMerger(self, cls): + """Looks for an added DataSetMerger derived from the given class. + + Args: + cls: A class derived from DataSetMerger. + + Returns: + The matching DataSetMerger instance. + + Raises: + LookupError: No matching DataSetMerger has been added. + """ + for merger in self._mergers: + if isinstance(merger, cls): + return merger + raise LookupError('No matching DataSetMerger found') + + def GetMergerList(self): + """Returns the list of DataSetMerger instances that have been added.""" + return self._mergers + + def MergeSchedules(self): + """Merge the schedules. + + This is done by running the DataSetMergers that have been added with + AddMerger() in the order that they were added. + + Returns: + True if the merge was successful. + """ + for merger in self._mergers: + if not merger.MergeDataSets(): + return False + return True + + def GetMergedSchedule(self): + """Returns the merged schedule. + + This will be empty before MergeSchedules() is called. + + Returns: + The merged schedule. + """ + return self.merged_schedule + + def GetMergedObject(self, original): + """Returns an object that represents original in the merged schedule.""" + # TODO: I think this would be better implemented by adding a private + # attribute to the objects in the original feeds + merged = (self.a_merge_map.get(original) or + self.b_merge_map.get(original)) + if merged: + return merged + else: + raise KeyError() + + +def main(): + """Run the merge driver program.""" + usage = \ +"""%prog [options] + +Merges and into a new GTFS file +. +""" + + parser = util.OptionParserLongError( + usage=usage, version='%prog '+transitfeed.__version__) + parser.add_option('--cutoff_date', + dest='cutoff_date', + default=None, + help='a transition date from the old feed to the new ' + 'feed in the format YYYYMMDD') + parser.add_option('--largest_stop_distance', + dest='largest_stop_distance', + default=StopMerger.largest_stop_distance, + help='the furthest distance two stops can be apart and ' + 'still be merged, in metres') + parser.add_option('--largest_shape_distance', + dest='largest_shape_distance', + default=ShapeMerger.largest_shape_distance, + help='the furthest distance the endpoints of two shapes ' + 'can be apart and the shape still be merged, in metres') + parser.add_option('--html_output_path', + dest='html_output_path', + default='merge-results.html', + help='write the html output to this file') + parser.add_option('--no_browser', + dest='no_browser', + action='store_true', + help='prevents the merge results from being opened in a ' + 'browser') + parser.add_option('-m', '--memory_db', dest='memory_db', action='store_true', + help='Use in-memory sqlite db instead of a temporary file. ' + 'It is faster but uses more RAM.') + parser.set_defaults(memory_db=False) + (options, args) = parser.parse_args() + + if len(args) != 3: + parser.error('You did not provide all required command line arguments.') + + old_feed_path = os.path.abspath(args[0]) + new_feed_path = os.path.abspath(args[1]) + merged_feed_path = os.path.abspath(args[2]) + + if old_feed_path.find("IWantMyCrash") != -1: + # See test/testmerge.py + raise Exception('For testing the merge crash handler.') + + a_schedule = LoadWithoutErrors(old_feed_path, options.memory_db) + b_schedule = LoadWithoutErrors(new_feed_path, options.memory_db) + merged_schedule = transitfeed.Schedule(memory_db=options.memory_db) + problem_reporter = HTMLProblemReporter() + feed_merger = FeedMerger(a_schedule, b_schedule, merged_schedule, + problem_reporter) + feed_merger.AddDefaultMergers() + + feed_merger.GetMerger(StopMerger).SetLargestStopDistance(float( + options.largest_stop_distance)) + feed_merger.GetMerger(ShapeMerger).SetLargestShapeDistance(float( + options.largest_shape_distance)) + + if options.cutoff_date is not None: + service_period_merger = feed_merger.GetMerger(ServicePeriodMerger) + service_period_merger.DisjoinCalendars(options.cutoff_date) + + if feed_merger.MergeSchedules(): + feed_merger.GetMergedSchedule().WriteGoogleTransitFeed(merged_feed_path) + else: + merged_feed_path = None + + output_file = file(options.html_output_path, 'w') + problem_reporter.WriteOutput(output_file, feed_merger, + old_feed_path, new_feed_path, merged_feed_path) + output_file.close() + + if not options.no_browser: + webbrowser.open('file://%s' % os.path.abspath(options.html_output_path)) + + +if __name__ == '__main__': + util.RunWithCrashHandler(main) + --- /dev/null +++ b/origin-src/transitfeed-1.2.5/schedule_viewer.py @@ -1,1 +1,524 @@ - +#!/usr/bin/python2.5 + +# Copyright (C) 2007 Google Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +""" +An example application that uses the transitfeed module. + +You must provide a Google Maps API key. +""" + + +import BaseHTTPServer, sys, urlparse +import bisect +from gtfsscheduleviewer.marey_graph import MareyGraph +import gtfsscheduleviewer +import mimetypes +import os.path +import re +import signal +import simplejson +import socket +import time +import transitfeed +from transitfeed import util +import urllib + + +# By default Windows kills Python with Ctrl+Break. Instead make Ctrl+Break +# raise a KeyboardInterrupt. +if hasattr(signal, 'SIGBREAK'): + signal.signal(signal.SIGBREAK, signal.default_int_handler) + + +mimetypes.add_type('text/plain', '.vbs') + + +class ResultEncoder(simplejson.JSONEncoder): + def default(self, obj): + try: + iterable = iter(obj) + except TypeError: + pass + else: + return list(iterable) + return simplejson.JSONEncoder.default(self, obj) + +# Code taken from +# http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/425210/index_txt +# An alternate approach is shown at +# http://mail.python.org/pipermail/python-list/2003-July/212751.html +# but it requires multiple threads. A sqlite object can only be used from one +# thread. +class StoppableHTTPServer(BaseHTTPServer.HTTPServer): + def server_bind(self): + BaseHTTPServer.HTTPServer.server_bind(self) + self.socket.settimeout(1) + self._run = True + + def get_request(self): + while self._run: + try: + sock, addr = self.socket.accept() + sock.settimeout(None) + return (sock, addr) + except socket.timeout: + pass + + def stop(self): + self._run = False + + def serve(self): + while self._run: + self.handle_request() + + +def StopToTuple(stop): + """Return tuple as expected by javascript function addStopMarkerFromList""" + return (stop.stop_id, stop.stop_name, float(stop.stop_lat), + float(stop.stop_lon), stop.location_type) + + +class ScheduleRequestHandler(BaseHTTPServer.BaseHTTPRequestHandler): + def do_GET(self): + scheme, host, path, x, params, fragment = urlparse.urlparse(self.path) + parsed_params = {} + for k in params.split('&'): + k = urllib.unquote(k) + if '=' in k: + k, v = k.split('=', 1) + parsed_params[k] = unicode(v, 'utf8') + else: + parsed_params[k] = '' + + if path == '/': + return self.handle_GET_home() + + m = re.match(r'/json/([a-z]{1,64})', path) + if m: + handler_name = 'handle_json_GET_%s' % m.group(1) + handler = getattr(self, handler_name, None) + if callable(handler): + return self.handle_json_wrapper_GET(handler, parsed_params) + + # Restrict allowable file names to prevent relative path attacks etc + m = re.match(r'/file/([a-z0-9_-]{1,64}\.?[a-z0-9_-]{1,64})$', path) + if m and m.group(1): + try: + f, mime_type = self.OpenFile(m.group(1)) + return self.handle_static_file_GET(f, mime_type) + except IOError, e: + print "Error: unable to open %s" % m.group(1) + # Ignore and treat as 404 + + m = re.match(r'/([a-z]{1,64})', path) + if m: + handler_name = 'handle_GET_%s' % m.group(1) + handler = getattr(self, handler_name, None) + if callable(handler): + return handler(parsed_params) + + return self.handle_GET_default(parsed_params, path) + + def OpenFile(self, filename): + """Try to open filename in the static files directory of this server. + Return a tuple (file object, string mime_type) or raise an exception.""" + (mime_type, encoding) = mimetypes.guess_type(filename) + assert mime_type + # A crude guess of when we should use binary mode. Without it non-unix + # platforms may corrupt binary files. + if mime_type.startswith('text/'): + mode = 'r' + else: + mode = 'rb' + return open(os.path.join(self.server.file_dir, filename), mode), mime_type + + def handle_GET_default(self, parsed_params, path): + self.send_error(404) + + def handle_static_file_GET(self, fh, mime_type): + content = fh.read() + self.send_response(200) + self.send_header('Content-Type', mime_type) + self.send_header('Content-Length', str(len(content))) + self.end_headers() + self.wfile.write(content) + + def AllowEditMode(self): + return False + + def handle_GET_home(self): + schedule = self.server.schedule + (min_lat, min_lon, max_lat, max_lon) = schedule.GetStopBoundingBox() + forbid_editing = ('true', 'false')[self.AllowEditMode()] + + agency = ', '.join(a.agency_name for a in schedule.GetAgencyList()).encode('utf-8') + + key = self.server.key + host = self.server.host + + # A very simple template system. For a fixed set of values replace [xxx] + # with the value of local variable xxx + f, _ = self.OpenFile('index.html') + content = f.read() + for v in ('agency', 'min_lat', 'min_lon', 'max_lat', 'max_lon', 'key', + 'host', 'forbid_editing'): + content = content.replace('[%s]' % v, str(locals()[v])) + + self.send_response(200) + self.send_header('Content-Type', 'text/html') + self.send_header('Content-Length', str(len(content))) + self.end_headers() + self.wfile.write(content) + + def handle_json_GET_routepatterns(self, params): + """Given a route_id generate a list of patterns of the route. For each + pattern include some basic information and a few sample trips.""" + schedule = self.server.schedule + route = schedule.GetRoute(params.get('route', None)) + if not route: + self.send_error(404) + return + time = int(params.get('time', 0)) + sample_size = 3 # For each pattern return the start time for this many trips + + pattern_id_trip_dict = route.GetPatternIdTripDict() + patterns = [] + + for pattern_id, trips in pattern_id_trip_dict.items(): + time_stops = trips[0].GetTimeStops() + if not time_stops: + continue + has_non_zero_trip_type = False; + for trip in trips: + if trip['trip_type'] and trip['trip_type'] != '0': + has_non_zero_trip_type = True + name = u'%s to %s, %d stops' % (time_stops[0][2].stop_name, time_stops[-1][2].stop_name, len(time_stops)) + transitfeed.SortListOfTripByTime(trips) + + num_trips = len(trips) + if num_trips <= sample_size: + start_sample_index = 0 + num_after_sample = 0 + else: + # Will return sample_size trips that start after the 'time' param. + + # Linear search because I couldn't find a built-in way to do a binary + # search with a custom key. + start_sample_index = len(trips) + for i, trip in enumerate(trips): + if trip.GetStartTime() >= time: + start_sample_index = i + break + + num_after_sample = num_trips - (start_sample_index + sample_size) + if num_after_sample < 0: + # Less than sample_size trips start after 'time' so return all the + # last sample_size trips. + num_after_sample = 0 + start_sample_index = num_trips - sample_size + + sample = [] + for t in trips[start_sample_index:start_sample_index + sample_size]: + sample.append( (t.GetStartTime(), t.trip_id) ) + + patterns.append((name, pattern_id, start_sample_index, sample, + num_after_sample, (0,1)[has_non_zero_trip_type])) + + patterns.sort() + return patterns + + def handle_json_wrapper_GET(self, handler, parsed_params): + """Call handler and output the return value in JSON.""" + schedule = self.server.schedule + result = handler(parsed_params) + content = ResultEncoder().encode(result) + self.send_response(200) + self.send_header('Content-Type', 'text/plain') + self.send_header('Content-Length', str(len(content))) + self.end_headers() + self.wfile.write(content) + + def handle_json_GET_routes(self, params): + """Return a list of all routes.""" + schedule = self.server.schedule + result = [] + for r in schedule.GetRouteList(): + result.append( (r.route_id, r.route_short_name, r.route_long_name) ) + result.sort(key = lambda x: x[1:3]) + return result + + def handle_json_GET_routerow(self, params): + schedule = self.server.schedule + route = schedule.GetRoute(params.get('route', None)) + return [transitfeed.Route._FIELD_NAMES, route.GetFieldValuesTuple()] + + def handle_json_GET_triprows(self, params): + """Return a list of rows from the feed file that are related to this + trip.""" + schedule = self.server.schedule + try: + trip = schedule.GetTrip(params.get('trip', None)) + except KeyError: + # if a non-existent trip is searched for, the return nothing + return + route = schedule.GetRoute(trip.route_id) + trip_row = dict(trip.iteritems()) + route_row = dict(route.iteritems()) + return [['trips.txt', trip_row], ['routes.txt', route_row]] + + def handle_json_GET_tripstoptimes(self, params): + schedule = self.server.schedule + try: + trip = schedule.GetTrip(params.get('trip')) + except KeyError: + # if a non-existent trip is searched for, the return nothing + return + time_stops = trip.GetTimeStops() + stops = [] + times = [] + for arr,dep,stop in time_stops: + stops.append(StopToTuple(stop)) + times.append(arr) + return [stops, times] + + def handle_json_GET_tripshape(self, params): + schedule = self.server.schedule + try: + trip = schedule.GetTrip(params.get('trip')) + except KeyError: + # if a non-existent trip is searched for, the return nothing + return + points = [] + if trip.shape_id: + shape = schedule.GetShape(trip.shape_id) + for (lat, lon, dist) in shape.points: + points.append((lat, lon)) + else: + time_stops = trip.GetTimeStops() + for arr,dep,stop in time_stops: + points.append((stop.stop_lat, stop.stop_lon)) + return points + + def handle_json_GET_neareststops(self, params): + """Return a list of the nearest 'limit' stops to 'lat', 'lon'""" + schedule = self.server.schedule + lat = float(params.get('lat')) + lon = float(params.get('lon')) + limit = int(params.get('limit')) + stops = schedule.GetNearestStops(lat=lat, lon=lon, n=limit) + return [StopToTuple(s) for s in stops] + + def handle_json_GET_boundboxstops(self, params): + """Return a list of up to 'limit' stops within bounding box with 'n','e' + and 's','w' in the NE and SW corners. Does not handle boxes crossing + longitude line 180.""" + schedule = self.server.schedule + n = float(params.get('n')) + e = float(params.get('e')) + s = float(params.get('s')) + w = float(params.get('w')) + limit = int(params.get('limit')) + stops = schedule.GetStopsInBoundingBox(north=n, east=e, south=s, west=w, n=limit) + return [StopToTuple(s) for s in stops] + + def handle_json_GET_stopsearch(self, params): + schedule = self.server.schedule + query = params.get('q', None).lower() + matches = [] + for s in schedule.GetStopList(): + if s.stop_id.lower().find(query) != -1 or s.stop_name.lower().find(query) != -1: + matches.append(StopToTuple(s)) + return matches + + def handle_json_GET_stoptrips(self, params): + """Given a stop_id and time in seconds since midnight return the next + trips to visit the stop.""" + schedule = self.server.schedule + stop = schedule.GetStop(params.get('stop', None)) + time = int(params.get('time', 0)) + time_trips = stop.GetStopTimeTrips(schedule) + time_trips.sort() # OPT: use bisect.insort to make this O(N*ln(N)) -> O(N) + # Keep the first 5 after param 'time'. + # Need make a tuple to find correct bisect point + time_trips = time_trips[bisect.bisect_left(time_trips, (time, 0)):] + time_trips = time_trips[:5] + # TODO: combine times for a route to show next 2 departure times + result = [] + for time, (trip, index), tp in time_trips: + headsign = None + # Find the most recent headsign from the StopTime objects + for stoptime in trip.GetStopTimes()[index::-1]: + if stoptime.stop_headsign: + headsign = stoptime.stop_headsign + break + # If stop_headsign isn't found, look for a trip_headsign + if not headsign: + headsign = trip.trip_headsign + route = schedule.GetRoute(trip.route_id) + trip_name = '' + if route.route_short_name: + trip_name += route.route_short_name + if route.route_long_name: + if len(trip_name): + trip_name += " - " + trip_name += route.route_long_name + if headsign: + trip_name += " (Direction: %s)" % headsign + + result.append((time, (trip.trip_id, trip_name, trip.service_id), tp)) + return result + + def handle_GET_ttablegraph(self,params): + """Draw a Marey graph in SVG for a pattern (collection of trips in a route + that visit the same sequence of stops).""" + schedule = self.server.schedule + marey = MareyGraph() + trip = schedule.GetTrip(params.get('trip', None)) + route = schedule.GetRoute(trip.route_id) + height = int(params.get('height', 300)) + + if not route: + print 'no such route' + self.send_error(404) + return + + pattern_id_trip_dict = route.GetPatternIdTripDict() + pattern_id = trip.pattern_id + if pattern_id not in pattern_id_trip_dict: + print 'no pattern %s found in %s' % (pattern_id, pattern_id_trip_dict.keys()) + self.send_error(404) + return + triplist = pattern_id_trip_dict[pattern_id] + + pattern_start_time = min((t.GetStartTime() for t in triplist)) + pattern_end_time = max((t.GetEndTime() for t in triplist)) + + marey.SetSpan(pattern_start_time,pattern_end_time) + marey.Draw(triplist[0].GetPattern(), triplist, height) + + content = marey.Draw() + + self.send_response(200) + self.send_header('Content-Type', 'image/svg+xml') + self.send_header('Content-Length', str(len(content))) + self.end_headers() + self.wfile.write(content) + + +def FindPy2ExeBase(): + """If this is running in py2exe return the install directory else return + None""" + # py2exe puts gtfsscheduleviewer in library.zip. For py2exe setup.py is + # configured to put the data next to library.zip. + windows_ending = gtfsscheduleviewer.__file__.find('\\library.zip\\') + if windows_ending != -1: + return transitfeed.__file__[:windows_ending] + else: + return None + + +def FindDefaultFileDir(): + """Return the path of the directory containing the static files. By default + the directory is called 'files'. The location depends on where setup.py put + it.""" + base = FindPy2ExeBase() + if base: + return os.path.join(base, 'schedule_viewer_files') + else: + # For all other distributions 'files' is in the gtfsscheduleviewer + # directory. + base = os.path.dirname(gtfsscheduleviewer.__file__) # Strip __init__.py + return os.path.join(base, 'files') + + +def GetDefaultKeyFilePath(): + """In py2exe return absolute path of file in the base directory and in all + other distributions return relative path 'key.txt'""" + windows_base = FindPy2ExeBase() + if windows_base: + return os.path.join(windows_base, 'key.txt') + else: + return 'key.txt' + + +def main(RequestHandlerClass = ScheduleRequestHandler): + usage = \ +'''%prog [options] [] + +Runs a webserver that lets you explore a in your browser. + +If is omited the filename is read from the console. Dragging +a file into the console may enter the filename. +''' + parser = util.OptionParserLongError( + usage=usage, version='%prog '+transitfeed.__version__) + parser.add_option('--feed_filename', '--feed', dest='feed_filename', + help='file name of feed to load') + parser.add_option('--key', dest='key', + help='Google Maps API key or the name ' + 'of a text file that contains an API key') + parser.add_option('--host', dest='host', help='Host name of Google Maps') + parser.add_option('--port', dest='port', type='int', + help='port on which to listen') + parser.add_option('--file_dir', dest='file_dir', + help='directory containing static files') + parser.add_option('-n', '--noprompt', action='store_false', + dest='manual_entry', + help='disable interactive prompts') + parser.set_defaults(port=8765, + host='maps.google.com', + file_dir=FindDefaultFileDir(), + manual_entry=True) + (options, args) = parser.parse_args() + + if not os.path.isfile(os.path.join(options.file_dir, 'index.html')): + print "Can't find index.html with --file_dir=%s" % options.file_dir + exit(1) + + if not options.feed_filename and len(args) == 1: + options.feed_filename = args[0] + + if not options.feed_filename and options.manual_entry: + options.feed_filename = raw_input('Enter Feed Location: ').strip('"') + + default_key_file = GetDefaultKeyFilePath() + if not options.key and os.path.isfile(default_key_file): + options.key = open(default_key_file).read().strip() + + if options.key and os.path.isfile(options.key): + options.key = open(options.key).read().strip() + + schedule = transitfeed.Schedule(problem_reporter=transitfeed.ProblemReporter()) + print 'Loading data from feed "%s"...' % options.feed_filename + print '(this may take a few minutes for larger cities)' + schedule.Load(options.feed_filename) + + server = StoppableHTTPServer(server_address=('', options.port), + RequestHandlerClass=RequestHandlerClass) + server.key = options.key + server.schedule = schedule + server.file_dir = options.file_dir + server.host = options.host + server.feed_path = options.feed_filename + + print ("To view, point your browser at http://localhost:%d/" % + (server.server_port)) + server.serve_forever() + + +if __name__ == '__main__': + main() + --- /dev/null +++ b/origin-src/transitfeed-1.2.5/setup.py @@ -1,1 +1,121 @@ +#!/usr/bin/python2.5 +# Copyright (C) 2007 Google Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +""" +This script can be used to create a source distribution, binary distribution +or Windows executable files. The output is put in dist/ + +See +http://code.google.com/p/googletransitdatafeed/wiki/BuildingPythonWindowsExecutables +for help on creating Windows executables. +""" + +from distutils.core import setup +import glob +import os.path +from transitfeed import __version__ as VERSION + +try: + import py2exe + has_py2exe = True +except ImportError, e: + # Won't be able to generate win32 exe + has_py2exe = False + + +# py2exe doesn't automatically include pytz dependency because it is optional +options = {'py2exe': {'packages': ['pytz']}} +scripts_for_py2exe = ['feedvalidator.py', 'schedule_viewer.py', 'kmlparser.py', + 'kmlwriter.py', 'merge.py', 'unusual_trip_filter.py'] +# On Nov 23, 2009 Tom Brown said: I'm not confident that we can include a +# working copy of this script in the py2exe distribution because it depends on +# ogr. I do want it included in the source tar.gz. +scripts_for_source_only = ['shape_importer.py'] +kwargs = {} + +if has_py2exe: + kwargs['console'] = scripts_for_py2exe + # py2exe seems to ignore package_data and not add marey_graph. This makes it + # work. + kwargs['data_files'] = \ + [('schedule_viewer_files', + glob.glob(os.path.join('gtfsscheduleviewer', 'files', '*')))] + options['py2exe'] = {'dist_dir': 'transitfeed-windows-binary-%s' % VERSION} + +setup( + version=VERSION, + name='transitfeed', + url='http://code.google.com/p/googletransitdatafeed/', + download_url='http://googletransitdatafeed.googlecode.com/' + 'files/transitfeed-%s.tar.gz' % VERSION, + maintainer='Tom Brown', + maintainer_email='tom.brown.code@gmail.com', + description='Google Transit Feed Specification library and tools', + long_description='This module provides a library for reading, writing and ' + 'validating Google Transit Feed Specification files. It includes some ' + 'scripts that validate a feed, display it using the Google Maps API and ' + 'the start of a KML importer and exporter.', + platforms='OS Independent', + license='Apache License, Version 2.0', + packages=['gtfsscheduleviewer', 'transitfeed'], + # Also need to list package_data contents in MANIFEST.in for it to be + # included in sdist. See "[Distutils] package_data not used by sdist + # command" Feb 2, 2007 + package_data={'gtfsscheduleviewer': ['files/*']}, + scripts=scripts_for_py2exe + scripts_for_source_only, + zip_safe=False, + classifiers=[ + 'Development Status :: 4 - Beta', + 'Intended Audience :: Developers', + 'Intended Audience :: Information Technology', + 'Intended Audience :: Other Audience', + 'License :: OSI Approved :: Apache Software License', + 'Operating System :: OS Independent', + 'Programming Language :: Python', + 'Topic :: Scientific/Engineering :: GIS', + 'Topic :: Software Development :: Libraries :: Python Modules' + ], + options=options, + **kwargs + ) + +if has_py2exe: + # Sometime between pytz-2008a and pytz-2008i common_timezones started to + # include only names of zones with a corresponding data file in zoneinfo. + # pytz installs the zoneinfo directory tree in the same directory + # as the pytz/__init__.py file. These data files are loaded using + # pkg_resources.resource_stream. py2exe does not copy this to library.zip so + # resource_stream can't find the files and common_timezones is empty when + # read in the py2exe executable. + # This manually copies zoneinfo into the zip. See also + # http://code.google.com/p/googletransitdatafeed/issues/detail?id=121 + import pytz + import zipfile + # Make sure the layout of pytz hasn't changed + assert (pytz.__file__.endswith('__init__.pyc') or + pytz.__file__.endswith('__init__.py')), pytz.__file__ + zoneinfo_dir = os.path.join(os.path.dirname(pytz.__file__), 'zoneinfo') + # '..\\Lib\\pytz\\__init__.py' -> '..\\Lib' + disk_basedir = os.path.dirname(os.path.dirname(pytz.__file__)) + zipfile_path = os.path.join(options['py2exe']['dist_dir'], 'library.zip') + z = zipfile.ZipFile(zipfile_path, 'a') + for absdir, directories, filenames in os.walk(zoneinfo_dir): + assert absdir.startswith(disk_basedir), (absdir, disk_basedir) + zip_dir = absdir[len(disk_basedir):] + for f in filenames: + z.write(os.path.join(absdir, f), os.path.join(zip_dir, f)) + z.close() + --- /dev/null +++ b/origin-src/transitfeed-1.2.5/shape_importer.py @@ -1,1 +1,291 @@ - +#!/usr/bin/python2.4 +# +# Copyright 2007 Google Inc. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +"""A utility program to help add shapes to an existing GTFS feed. + +Requires the ogr python package. +""" + +__author__ = 'chris.harrelson.code@gmail.com (Chris Harrelson)' + +import csv +import glob +import ogr +import os +import shutil +import sys +import tempfile +import transitfeed +from transitfeed import shapelib +from transitfeed import util +import zipfile + + +class ShapeImporterError(Exception): + pass + + +def PrintColumns(shapefile): + """ + Print the columns of layer 0 of the shapefile to the screen. + """ + ds = ogr.Open(shapefile) + layer = ds.GetLayer(0) + if len(layer) == 0: + raise ShapeImporterError("Layer 0 has no elements!") + + feature = layer.GetFeature(0) + print "%d features" % feature.GetFieldCount() + for j in range(0, feature.GetFieldCount()): + print '--' + feature.GetFieldDefnRef(j).GetName() + \ + ': ' + feature.GetFieldAsString(j) + + +def AddShapefile(shapefile, graph, key_cols): + """ + Adds shapes found in the given shape filename to the given polyline + graph object. + """ + ds = ogr.Open(shapefile) + layer = ds.GetLayer(0) + + for i in range(0, len(layer)): + feature = layer.GetFeature(i) + + geometry = feature.GetGeometryRef() + + if key_cols: + key_list = [] + for col in key_cols: + key_list.append(str(feature.GetField(col))) + shape_id = '-'.join(key_list) + else: + shape_id = '%s-%d' % (shapefile, i) + + poly = shapelib.Poly(name=shape_id) + for j in range(0, geometry.GetPointCount()): + (lat, lng) = (round(geometry.GetY(j), 15), round(geometry.GetX(j), 15)) + poly.AddPoint(shapelib.Point.FromLatLng(lat, lng)) + graph.AddPoly(poly) + + return graph + + +def GetMatchingShape(pattern_poly, trip, matches, max_distance, verbosity=0): + """ + Tries to find a matching shape for the given pattern Poly object, + trip, and set of possibly matching Polys from which to choose a match. + """ + if len(matches) == 0: + print ('No matching shape found within max-distance %d for trip %s ' + % (max_distance, trip.trip_id)) + return None + + if verbosity >= 1: + for match in matches: + print "match: size %d" % match.GetNumPoints() + scores = [(pattern_poly.GreedyPolyMatchDist(match), match) + for match in matches] + + scores.sort() + + if scores[0][0] > max_distance: + print ('No matching shape found within max-distance %d for trip %s ' + '(min score was %f)' + % (max_distance, trip.trip_id, scores[0][0])) + return None + + return scores[0][1] + +def AddExtraShapes(extra_shapes_txt, graph): + """ + Add extra shapes into our input set by parsing them out of a GTFS-formatted + shapes.txt file. Useful for manually adding lines to a shape file, since it's + a pain to edit .shp files. + """ + + print "Adding extra shapes from %s" % extra_shapes_txt + try: + tmpdir = tempfile.mkdtemp() + shutil.copy(extra_shapes_txt, os.path.join(tmpdir, 'shapes.txt')) + loader = transitfeed.ShapeLoader(tmpdir) + schedule = loader.Load() + for shape in schedule.GetShapeList(): + print "Adding extra shape: %s" % shape.shape_id + graph.AddPoly(ShapeToPoly(shape)) + finally: + if tmpdir: + shutil.rmtree(tmpdir) + + +# Note: this method lives here to avoid cross-dependencies between +# shapelib and transitfeed. +def ShapeToPoly(shape): + poly = shapelib.Poly(name=shape.shape_id) + for lat, lng, distance in shape.points: + point = shapelib.Point.FromLatLng(round(lat, 15), round(lng, 15)) + poly.AddPoint(point) + return poly + + +def ValidateArgs(options_parser, options, args): + if not (args and options.source_gtfs and options.dest_gtfs): + options_parser.error("You must specify a source and dest GTFS file, " + "and at least one source shapefile") + + +def DefineOptions(): + usage = \ +"""%prog [options] --source_gtfs= --dest_gtfs=\ + [...] + +Try to match shapes in one or more SHP files to trips in a GTFS file.""" + options_parser = util.OptionParserLongError( + usage=usage, version='%prog '+transitfeed.__version__) + options_parser.add_option("--print_columns", + action="store_true", + default=False, + dest="print_columns", + help="Print column names in shapefile DBF and exit") + options_parser.add_option("--keycols", + default="", + dest="keycols", + help="Comma-separated list of the column names used" + "to index shape ids") + options_parser.add_option("--max_distance", + type="int", + default=150, + dest="max_distance", + help="Max distance from a shape to which to match") + options_parser.add_option("--source_gtfs", + default="", + dest="source_gtfs", + metavar="FILE", + help="Read input GTFS from FILE") + options_parser.add_option("--dest_gtfs", + default="", + dest="dest_gtfs", + metavar="FILE", + help="Write output GTFS with shapes to FILE") + options_parser.add_option("--extra_shapes", + default="", + dest="extra_shapes", + metavar="FILE", + help="Extra shapes.txt (CSV) formatted file") + options_parser.add_option("--verbosity", + type="int", + default=0, + dest="verbosity", + help="Verbosity level. Higher is more verbose") + return options_parser + + +def main(key_cols): + print 'Parsing shapefile(s)...' + graph = shapelib.PolyGraph() + for arg in args: + print ' ' + arg + AddShapefile(arg, graph, key_cols) + + if options.extra_shapes: + AddExtraShapes(options.extra_shapes, graph) + + print 'Loading GTFS from %s...' % options.source_gtfs + schedule = transitfeed.Loader(options.source_gtfs).Load() + shape_count = 0 + pattern_count = 0 + + verbosity = options.verbosity + + print 'Matching shapes to trips...' + for route in schedule.GetRouteList(): + print 'Processing route', route.route_short_name + patterns = route.GetPatternIdTripDict() + for pattern_id, trips in patterns.iteritems(): + pattern_count += 1 + pattern = trips[0].GetPattern() + + poly_points = [shapelib.Point.FromLatLng(p.stop_lat, p.stop_lon) + for p in pattern] + if verbosity >= 2: + print "\npattern %d, %d points:" % (pattern_id, len(poly_points)) + for i, (stop, point) in enumerate(zip(pattern, poly_points)): + print "Stop %d '%s': %s" % (i + 1, stop.stop_name, point.ToLatLng()) + + # First, try to find polys that run all the way from + # the start of the trip to the end. + matches = graph.FindMatchingPolys(poly_points[0], poly_points[-1], + options.max_distance) + if not matches: + # Try to find a path through the graph, joining + # multiple edges to find a path that covers all the + # points in the trip. Some shape files are structured + # this way, with a polyline for each segment between + # stations instead of a polyline covering an entire line. + shortest_path = graph.FindShortestMultiPointPath(poly_points, + options.max_distance, + verbosity=verbosity) + if shortest_path: + matches = [shortest_path] + else: + matches = [] + + pattern_poly = shapelib.Poly(poly_points) + shape_match = GetMatchingShape(pattern_poly, trips[0], + matches, options.max_distance, + verbosity=verbosity) + if shape_match: + shape_count += 1 + # Rename shape for readability. + shape_match = shapelib.Poly(points=shape_match.GetPoints(), + name="shape_%d" % shape_count) + for trip in trips: + try: + shape = schedule.GetShape(shape_match.GetName()) + except KeyError: + shape = transitfeed.Shape(shape_match.GetName()) + for point in shape_match.GetPoints(): + (lat, lng) = point.ToLatLng() + shape.AddPoint(lat, lng) + schedule.AddShapeObject(shape) + trip.shape_id = shape.shape_id + + print "Matched %d shapes out of %d patterns" % (shape_count, pattern_count) + schedule.WriteGoogleTransitFeed(options.dest_gtfs) + + +if __name__ == '__main__': + # Import psyco if available for better performance. + try: + import psyco + psyco.full() + except ImportError: + pass + + options_parser = DefineOptions() + (options, args) = options_parser.parse_args() + + ValidateArgs(options_parser, options, args) + + if options.print_columns: + for arg in args: + PrintColumns(arg) + sys.exit(0) + + key_cols = options.keycols.split(',') + + main(key_cols) + --- /dev/null +++ b/origin-src/transitfeed-1.2.5/test/data/bad_date_format/agency.txt @@ -1,1 +1,3 @@ +agency_id,agency_name,agency_url,agency_timezone,agency_phone +DTA,Autorité de passage de démonstration,http://google.com,America/Los_Angeles,123 12314 --- /dev/null +++ b/origin-src/transitfeed-1.2.5/test/data/bad_date_format/calendar.txt @@ -1,1 +1,4 @@ +service_id,monday,tuesday,wednesday,thursday,friday,saturday,sunday,start_date,end_date +FULLW,1,1,1,1,1,1,1,2007.01.01,20101231 +WE,0,0,0,0,0,1,1,20070101,20101231 --- /dev/null +++ b/origin-src/transitfeed-1.2.5/test/data/bad_date_format/calendar_dates.txt @@ -1,1 +1,3 @@ +service_id,date,exception_type +FULLW,2007-06-04,2 --- /dev/null +++ b/origin-src/transitfeed-1.2.5/test/data/bad_date_format/fare_attributes.txt @@ -1,1 +1,4 @@ +fare_id,price,currency_type,payment_method,transfers,transfer_duration +p,1.25,USD,0,0, +a,5.25,USD,0,0, --- /dev/null +++ b/origin-src/transitfeed-1.2.5/test/data/bad_date_format/fare_rules.txt @@ -1,1 +1,6 @@ +fare_id,route_id,origin_id,destination_id,contains_id +p,AB,,, +p,STBA,,, +p,BFC,,, +a,AAMV,,, --- /dev/null +++ b/origin-src/transitfeed-1.2.5/test/data/bad_date_format/routes.txt @@ -1,1 +1,7 @@ +route_id,agency_id,route_short_name,route_long_name,route_desc,route_type,route_url,route_color,route_text_color +AB,DTA,,Airport ⇒ Bullfrog,,3,,, +BFC,DTA,,Bullfrog ⇒ Furnace Creek Resort,,3,,, +STBA,DTA,,Stagecoach ⇒ Airport Shuttle,,3,,, +CITY,DTA,ÅŒ,Bar Circle,Route with ĸool unicode shortname,3,,, +AAMV,DTA,,Airport ⇒ Amargosa Valley,,3,,, --- /dev/null +++ b/origin-src/transitfeed-1.2.5/test/data/bad_date_format/stop_times.txt @@ -1,1 +1,30 @@ +trip_id,arrival_time,departure_time,stop_id,stop_sequence,stop_headsign,pickup_type,drop_off_type,shape_dist_traveled +STBA,6:00:00,6:00:00,STAGECOACH,0,to airport,1,0,0.212 +STBA,6:20:00,6:20:00,BEATTY_AIRPORT,2,,0,0,1.043 +CITY1,6:00:00,6:00:00,STAGECOACH,0,,,, +CITY1,6:05:00,6:07:00,NANAA,5,going to nadav,2,3, +CITY1,6:12:00,6:14:00,NADAV,10,,,, +CITY1,6:19:00,6:21:00,DADAN,15,,,, +CITY1,6:26:00,6:28:00,EMSI,20,,,, +CITY2,6:28:00,6:30:00,EMSI,100,,,, +CITY2,6:35:00,6:37:00,DADAN,200,,,, +CITY2,6:42:00,6:44:00,NADAV,300,,,, +CITY2,6:49:00,6:51:00,NANAA,400,,,, +CITY2,6:56:00,6:58:00,STAGECOACH,500,,,, +AB1,8:00:00,8:00:00,BEATTY_AIRPORT,1,,,, +AB1,8:10:00,8:15:00,BULLFROG,2,,,, +AB2,12:05:00,12:05:00,BULLFROG,1,,,, +AB2,12:15:00,12:15:00,BEATTY_AIRPORT,2,,,, +BFC1,8:20:00,8:20:00,BULLFROG,1,,,, +BFC1,9:20:00,9:20:00,FUR_CREEK_RES,2,,,, +BFC2,11:00:00,11:00:00,FUR_CREEK_RES,1,,,, +BFC2,12:00:00,12:00:00,BULLFROG,2,,,, +AAMV1,8:00:00,8:00:00,BEATTY_AIRPORT,1,,,, +AAMV1,9:00:00,9:00:00,AMV,2,,,, +AAMV2,10:00:00,10:00:00,AMV,1,,,, +AAMV2,11:00:00,11:00:00,BEATTY_AIRPORT,2,,,, +AAMV3,13:00:00,13:00:00,BEATTY_AIRPORT,1,,,, +AAMV3,14:00:00,14:00:00,AMV,2,,,, +AAMV4,15:00:00,15:00:00,AMV,1,,,, +AAMV4,16:00:00,16:00:00,BEATTY_AIRPORT,2,,,, --- /dev/null +++ b/origin-src/transitfeed-1.2.5/test/data/bad_date_format/stops.txt @@ -1,1 +1,12 @@ +stop_id,stop_name,stop_desc,stop_lat,stop_lon,zone_id,stop_url,stop_code,location_type,parent_station +FUR_CREEK_RES,Furnace Creek Resort (Demo),,36.425288,-117.133162,,,1234,, +BEATTY_AIRPORT,Nye County Airport (Demo),,36.868446,-116.784582,,,1235,0,BEATTY_AIRPORT_STATION +BEATTY_AIRPORT_STATION,Nye County Airport (Demo),,36.868446,-116.784582,,,1235,1, +BULLFROG,Bullfrog (Demo),,36.88108,-116.81797,,,,, +STAGECOACH,Stagecoach Hotel & Casino (Demo),,36.915682,-116.751677,,,1236,, +NADAV,North Ave / D Ave N (Demo),,36.914893,-116.76821,,,1237,, +NANAA,North Ave / N A Ave (Demo),,36.914944,-116.761472,,,1238,, +DADAN,Doing Ave / D Ave N (Demo),,36.909489,-116.768242,,,,, +EMSI,E Main St / S Irving St (Demo),,36.905697,-116.76218,,,,, +AMV,Amargosa Valley (Demo),,36.641496,-116.40094,,,,, --- /dev/null +++ b/origin-src/transitfeed-1.2.5/test/data/bad_date_format/transfers.txt @@ -1,1 +1,4 @@ +from_stop_id,to_stop_id,transfer_type,min_transfer_time +NADAV,NANAA,3, +EMSI,NANAA,2,1200 --- /dev/null +++ b/origin-src/transitfeed-1.2.5/test/data/bad_date_format/trips.txt @@ -1,1 +1,12 @@ - +route_id,service_id,trip_id,trip_headsign,direction_id,block_id,shape_id +AB,FULLW,AB1,to Bullfrog,0,1, +AB,FULLW,AB2,to Airport,1,2, +STBA,FULLW,STBA,Shuttle,,, +CITY,FULLW,CITY1,,0,, +CITY,FULLW,CITY2,,1,, +BFC,FULLW,BFC1,to Furnace Creek Resort,0,1, +BFC,FULLW,BFC2,to Bullfrog,1,2, +AAMV,WE,AAMV1,to Amargosa Valley,0,, +AAMV,WE,AAMV2,to Airport,1,, +AAMV,WE,AAMV3,to Amargosa Valley,0,, +AAMV,WE,AAMV4,to Airport,1,, --- /dev/null +++ b/origin-src/transitfeed-1.2.5/test/data/bad_utf8/agency.txt @@ -1,1 +1,3 @@ +agency_id,agency_name,agency_url,agency_timezone,badheaderì +DTA,Demo Transit Authority ì ,http://google.com,America/Los_Angeles, --- /dev/null +++ b/origin-src/transitfeed-1.2.5/test/data/bad_utf8/calendar.txt @@ -1,1 +1,3 @@ - +service_id,monday,tuesday,wednesday,thursday,friday,saturday,sunday,start_date,end_date +FULLW,1,1,1,1,1,1,1,20070101,20101231 +WE,0,0,0,0,0,1,1,20070101,20101231 --- /dev/null +++ b/origin-src/transitfeed-1.2.5/test/data/bad_utf8/calendar_dates.txt @@ -1,1 +1,2 @@ - +service_id,date,exception_type +FULLW,20070604,2 --- /dev/null +++ b/origin-src/transitfeed-1.2.5/test/data/bad_utf8/fare_attributes.txt @@ -1,1 +1,4 @@ +fare_id,price,currency_type,payment_method,transfers,transfer_duration +p,1.25,USD,0,0, +a,5.25,USD,0,0, --- /dev/null +++ b/origin-src/transitfeed-1.2.5/test/data/bad_utf8/fare_rules.txt @@ -1,1 +1,6 @@ +fare_id,route_id,origin_id,destination_id,contains_id +p,AB,,, +p,STBA,,, +p,BFC,,, +a,AAMV,,, --- /dev/null +++ b/origin-src/transitfeed-1.2.5/test/data/bad_utf8/frequencies.txt @@ -1,1 +1,12 @@ - +trip_id,start_time,end_time,headway_secs +STBA,6:00:00,22:00:00,1800 +CITY1,6:00:00,7:59:59,1800 +CITY2,6:00:00,7:59:59,1800 +CITY1,8:00:00,9:59:59,600 +CITY2,8:00:00,9:59:59,600 +CITY1,10:00:00,15:59:59,1800 +CITY2,10:00:00,15:59:59,1800 +CITY1,16:00:00,18:59:59,600 +CITY2,16:00:00,18:59:59,600 +CITY1,19:00:00,22:00:00,1800 +CITY2,19:00:00,22:00:00,1800 --- /dev/null +++ b/origin-src/transitfeed-1.2.5/test/data/bad_utf8/routes.txt @@ -1,1 +1,7 @@ +route_id,agency_id,route_short_name,route_long_name,route_desc,route_type,route_url,route_color,route_text_color +AB,DTA,,Airport - Bullfrog,,3,,, +BFC,DTA,,Bullfrog - Furnace Creek Resort,,3,,, +STBA,DTA,,Stagecoach - Airport Shuttle,,3,,, +CITY,DTA,C.œ ,City .œ ,This is a route for 유,3,,, +AAMV,DTA,,Airport - Amargosa Valley,,3,,, --- /dev/null +++ b/origin-src/transitfeed-1.2.5/test/data/bad_utf8/stop_times.txt @@ -1,1 +1,30 @@ +trip_id,arrival_time,departure_time,stop_id,stop_sequence,stop_headsign,pickup_type,drop_off_type,shape_dist_traveled +STBA,6:00:00,6:00:00,STAGECOACH,0,to ì,1,0,0.212 +STBA,6:20:00,6:20:00,BEATTY_AIRPORT,2,,0,0,1.043 +CITY1,6:00:00,6:00:00,STAGECOACH,0,,,, +CITY1,6:05:00,6:07:00,NANAA,5,going to nadav,2,3, +CITY1,6:12:00,6:14:00,NADAV,10,,,, +CITY1,6:19:00,6:21:00,DADAN,15,,,, +CITY1,6:26:00,6:28:00,EMSI,20,,,, +CITY2,6:28:00,6:30:00,EMSI,100,,,, +CITY2,6:35:00,6:37:00,DADAN,200,,,, +CITY2,6:42:00,6:44:00,NADAV,300,,,, +CITY2,6:49:00,6:51:00,NANAA,400,,,, +CITY2,6:56:00,6:58:00,STAGECOACH,500,,,, +AB1,8:00:00,8:00:00,BEATTY_AIRPORT,1,,,, +AB1,8:10:00,8:15:00,BULLFROG,2,,,, +AB2,12:05:00,12:05:00,BULLFROG,1,,,, +AB2,12:15:00,12:15:00,BEATTY_AIRPORT,2,,,, +BFC1,8:20:00,8:20:00,BULLFROG,1,,,, +BFC1,9:20:00,9:20:00,FUR_CREEK_RES,2,,,, +BFC2,11:00:00,11:00:00,FUR_CREEK_RES,1,,,, +BFC2,12:00:00,12:00:00,BULLFROG,2,,,, +AAMV1,8:00:00,8:00:00,BEATTY_AIRPORT,1,,,, +AAMV1,9:00:00,9:00:00,AMV,2,,,, +AAMV2,10:00:00,10:00:00,AMV,1,,,, +AAMV2,11:00:00,11:00:00,BEATTY_AIRPORT,2,,,, +AAMV3,13:00:00,13:00:00,BEATTY_AIRPORT,1,,,, +AAMV3,14:00:00,14:00:00,AMV,2,,,, +AAMV4,15:00:00,15:00:00,AMV,1,,,, +AAMV4,16:00:00,16:00:00,BEATTY_AIRPORT,2,,,, --- /dev/null +++ b/origin-src/transitfeed-1.2.5/test/data/bad_utf8/stops.txt @@ -1,1 +1,11 @@ +stop_id,stop_name,stop_desc,stop_lat,stop_lon,zone_id,stop_url +FUR_CREEK_RES,Furnace Creek Resort (Demo) œ ,,36.425288,-117.133162,, +BEATTY_AIRPORT,Nye County Airport (Demo),,36.868446,-116.784582,, +BULLFROG,Bullfrog (Demo),,36.88108,-116.81797,, +STAGECOACH,Stagecoach Hotel & Casino (Demo),,36.915682,-116.751677,, +NADAV,North Ave / D Ave N (Demo),,36.914893,-116.76821,, +NANAA,North Ave / N A Ave (Demo),,36.914944,-116.761472,, +DADAN,Doing Ave / D Ave N (Demo),,36.909489,-116.768242,, +EMSI,E Main St / S Irving St (Demo),,36.905697,-116.76218,, +AMV,Amargosa Valley (Demo),,36.641496,-116.40094,, --- /dev/null +++ b/origin-src/transitfeed-1.2.5/test/data/bad_utf8/trips.txt @@ -1,1 +1,13 @@ +route_id,service_id,trip_id,trip_headsign,direction_id,block_id,shape_id +AB,FULLW,AB1,to Bullfrog œ ,0,1, +AB,FULLW,AB2,to Airport,1,2, +STBA,FULLW,STBA,Shuttle,,, +CITY,FULLW,CITY1,,0,, +CITY,FULLW,CITY2,,1,, +BFC,FULLW,BFC1,to Furnace Creek Resort,0,1, +BFC,FULLW,BFC2,to Bullfrog,1,2, +AAMV,WE,AAMV1,to Amargosa Valley,0,, +AAMV,WE,AAMV2,to Airport,1,, +AAMV,WE,AAMV3,to Amargosa Valley,0,, +AAMV,WE,AAMV4,to Airport,1,, --- /dev/null +++ b/origin-src/transitfeed-1.2.5/test/data/contains_null/agency.txt @@ -1,1 +1,3 @@ +agency_id,agency_name,agency_url,agency_timezone +DTA,Autorité de passage de démonstration,http://google.com,America/Los_Angeles --- /dev/null +++ b/origin-src/transitfeed-1.2.5/test/data/contains_null/calendar.txt @@ -1,1 +1,3 @@ - +service_id,monday,tuesday,wednesday,thursday,friday,saturday,sunday,start_date,end_date +FULLW,1,1,1,1,1,1,1,20070101,20101231 +WE,0,0,0,0,0,1,1,20070101,20101231 --- /dev/null +++ b/origin-src/transitfeed-1.2.5/test/data/contains_null/calendar_dates.txt @@ -1,1 +1,2 @@ - +service_id,date,exception_type +FULLW,20070604,2 --- /dev/null +++ b/origin-src/transitfeed-1.2.5/test/data/contains_null/fare_attributes.txt @@ -1,1 +1,4 @@ +fare_id,price,currency_type,payment_method,transfers,transfer_duration +p,1.25,USD,0,0, +a,5.25,USD,0,0, --- /dev/null +++ b/origin-src/transitfeed-1.2.5/test/data/contains_null/fare_rules.txt @@ -1,1 +1,6 @@ +fare_id,route_id,origin_id,destination_id,contains_id +p,AB,,, +p,STBA,,, +p,BFC,,, +a,AAMV,,, --- /dev/null +++ b/origin-src/transitfeed-1.2.5/test/data/contains_null/frequencies.txt @@ -1,1 +1,12 @@ - +trip_id,start_time,end_time,headway_secs +STBA,6:00:00,22:00:00,1800 +CITY1,6:00:00,7:59:59,1800 +CITY2,6:00:00,7:59:59,1800 +CITY1,8:00:00,9:59:59,600 +CITY2,8:00:00,9:59:59,600 +CITY1,10:00:00,15:59:59,1800 +CITY2,10:00:00,15:59:59,1800 +CITY1,16:00:00,18:59:59,600 +CITY2,16:00:00,18:59:59,600 +CITY1,19:00:00,22:00:00,1800 +CITY2,19:00:00,22:00:00,1800 --- /dev/null +++ b/origin-src/transitfeed-1.2.5/test/data/contains_null/routes.txt @@ -1,1 +1,7 @@ +route_id,agency_id,route_short_name,route_long_name,route_desc,route_type,route_url,route_color,route_text_color +AB,DTA,,Airport ⇒ Bullfrog,,3 +BFC,DTA,,Bullfrog ⇒ Furnace Creek Resort,,3 +STBA,DTA,,Stagecoach ⇒ Airport Shuttle,,3 +CITY,DTA,ÅŒ,Bar Circle,Route with ĸool unicode short name,3 +AAMV,DTA,,Airport ⇒ Amargosa Valley,,3 --- /dev/null +++ b/origin-src/transitfeed-1.2.5/test/data/contains_null/stop_times.txt @@ -1,1 +1,30 @@ +trip_id,arrival_time,departure_time,stop_id,stop_sequence,stop_headsign,pickup_type,drop_off_type,shape_dist_traveled +STBA,6:00:00,6:00:00,STAGECOACH,1,to airport,1,0,0.212 +STBA,6:20:00,6:20:00,BEATTY_AIRPORT,2,,0,0,1.043 +CITY1,6:00:00,6:00:00,STAGECOACH,1 +CITY1,6:05:00,6:07:00,NANAA,2,going to nadav,2,3, +CITY1,6:12:00,6:14:00,NADAV,3 +CITY1,6:19:00,6:21:00,DADAN,4 +CITY1,6:26:00,6:28:00,EMSI,5 +CITY2,6:28:00,6:30:00,EMSI,1 +CITY2,6:35:00,6:37:00,DADAN,2 +CITY2,6:42:00,6:44:00,NADAV,3 +CITY2,6:49:00,6:51:00,NANAA,4 +CITY2,6:56:00,6:58:00,STAGECOACH,5 +AB1,8:00:00,8:00:00,BEATTY_AIRPORT,1 +AB1,8:10:00,8:15:00,BULLFROG,2 +AB2,12:05:00,12:05:00,BULLFROG,1 +AB2,12:15:00,12:15:00,BEATTY_AIRPORT,2 +BFC1,8:20:00,8:20:00,BULLFROG,1 +BFC1,9:20:00,9:20:00,FUR_CREEK_RES,2 +BFC2,11:00:00,11:00:00,FUR_CREEK_RES,1 +BFC2,12:00:00,12:00:00,BULLFROG,2 +AAMV1,8:00:00,8:00:00,BEATTY_AIRPORT,1 +AAMV1,9:00:00,9:00:00,AMV,2 +AAMV2,10:00:00,10:00:00,AMV,1 +AAMV2,11:00:00,11:00:00,BEATTY_AIRPORT,2 +AAMV3,13:00:00,13:00:00,BEATTY_AIRPORT,1 +AAMV3,14:00:00,14:00:00,AMV,2 +AAMV4,15:00:00,15:00:00,AMV,1 +AAMV4,16:00:00,16:00:00,BEATTY_AIRPORT,2 --- /dev/null +++ b/origin-src/transitfeed-1.2.5/test/data/contains_null/trips.txt @@ -1,1 +1,13 @@ +route_id,service_id,trip_id,trip_headsign,direction_id,block_id,shape_id +AB,FULLW,AB1,to Bullfrog,0,1 +AB,FULLW,AB2,to Airport,1,2 +STBA,FULLW,STBA,Shuttle +CITY,FULLW,CITY1,ÅŒ,0 +CITY,FULLW,CITY2,ÅŒ,1 +BFC,FULLW,BFC1,to Furnace Creek Resort,0,1 +BFC,FULLW,BFC2,to Bullfrog,1,2 +AAMV,WE,AAMV1,to Amargosa Valley,0 +AAMV,WE,AAMV2,to Airport,1 +AAMV,WE,AAMV3,to Amargosa Valley,0 +AAMV,WE,AAMV4,to Airport,1 --- /dev/null +++ b/origin-src/transitfeed-1.2.5/test/data/duplicate_schedule_id/agency.txt @@ -1,1 +1,3 @@ +agency_id,agency_name,agency_url,agency_timezone +DTA,Autorité de passage de démonstration,http://google.com,America/Los_Angeles --- /dev/null +++ b/origin-src/transitfeed-1.2.5/test/data/duplicate_schedule_id/calendar.txt @@ -1,1 +1,5 @@ +service_id,monday,tuesday,wednesday,thursday,friday,saturday,sunday,start_date,end_date +FULLW,1,1,1,1,1,1,1,20070101,20101231 +WE,0,0,0,0,0,1,1,20070101,20101231 +WE,0,0,0,0,0,1,1,20070101,20101231 --- /dev/null +++ b/origin-src/transitfeed-1.2.5/test/data/duplicate_schedule_id/calendar_dates.txt @@ -1,1 +1,2 @@ - +service_id,date,exception_type +FULLW,20070604,2 --- /dev/null +++ b/origin-src/transitfeed-1.2.5/test/data/duplicate_schedule_id/fare_attributes.txt @@ -1,1 +1,4 @@ +fare_id,price,currency_type,payment_method,transfers,transfer_duration +p,1.25,USD,0,0, +a,5.25,USD,0,0, --- /dev/null +++ b/origin-src/transitfeed-1.2.5/test/data/duplicate_schedule_id/fare_rules.txt @@ -1,1 +1,6 @@ +fare_id,route_id,origin_id,destination_id,contains_id +p,AB,,, +p,STBA,,, +p,BFC,,, +a,AAMV,,, --- /dev/null +++ b/origin-src/transitfeed-1.2.5/test/data/duplicate_schedule_id/frequencies.txt @@ -1,1 +1,12 @@ - +trip_id,start_time,end_time,headway_secs +STBA,6:00:00,22:00:00,1800 +CITY1,6:00:00,7:59:59,1800 +CITY2,6:00:00,7:59:59,1800 +CITY1,8:00:00,9:59:59,600 +CITY2,8:00:00,9:59:59,600 +CITY1,10:00:00,15:59:59,1800 +CITY2,10:00:00,15:59:59,1800 +CITY1,16:00:00,18:59:59,600 +CITY2,16:00:00,18:59:59,600 +CITY1,19:00:00,22:00:00,1800 +CITY2,19:00:00,22:00:00,1800 --- /dev/null +++ b/origin-src/transitfeed-1.2.5/test/data/duplicate_schedule_id/routes.txt @@ -1,1 +1,7 @@ +route_id,agency_id,route_short_name,route_long_name,route_desc,route_type,route_url,route_color,route_text_color +AB,DTA,,Airport ⇒ Bullfrog,,3,,, +BFC,DTA,,Bullfrog ⇒ Furnace Creek Resort,,3,,, +STBA,DTA,,Stagecoach ⇒ Airport Shuttle,,3,,, +CITY,DTA,ÅŒ,Bar Circle,Route with ĸool unicode short name,3,,, +AAMV,DTA,,Airport ⇒ Amargosa Valley,,3,,, --- /dev/null +++ b/origin-src/transitfeed-1.2.5/test/data/duplicate_schedule_id/stop_times.txt @@ -1,1 +1,30 @@ +trip_id,arrival_time,departure_time,stop_id,stop_sequence,stop_headsign,pickup_type,drop_off_type,shape_dist_traveled +STBA,6:00:00,6:00:00,STAGECOACH,1,to airport,1,0,0.212 +STBA,6:20:00,6:20:00,BEATTY_AIRPORT,2,,0,0,1.043 +CITY1,6:00:00,6:00:00,STAGECOACH,1,,,, +CITY1,6:05:00,6:07:00,NANAA,2,going to nadav,2,3, +CITY1,6:12:00,6:14:00,NADAV,3,,,, +CITY1,6:19:00,6:21:00,DADAN,4,,,, +CITY1,6:26:00,6:28:00,EMSI,5,,,, +CITY2,6:28:00,6:30:00,EMSI,1,,,, +CITY2,6:35:00,6:37:00,DADAN,2,,,, +CITY2,6:42:00,6:44:00,NADAV,3,,,, +CITY2,6:49:00,6:51:00,NANAA,4,,,, +CITY2,6:56:00,6:58:00,STAGECOACH,5,,,, +AB1,8:00:00,8:00:00,BEATTY_AIRPORT,1,,,, +AB1,8:10:00,8:15:00,BULLFROG,2,,,, +AB2,12:05:00,12:05:00,BULLFROG,1,,,, +AB2,12:15:00,12:15:00,BEATTY_AIRPORT,2,,,, +BFC1,8:20:00,8:20:00,BULLFROG,1,,,, +BFC1,9:20:00,9:20:00,FUR_CREEK_RES,2,,,, +BFC2,11:00:00,11:00:00,FUR_CREEK_RES,1,,,, +BFC2,12:00:00,12:00:00,BULLFROG,2,,,, +AAMV1,8:00:00,8:00:00,BEATTY_AIRPORT,1,,,, +AAMV1,9:00:00,9:00:00,AMV,2,,,, +AAMV2,10:00:00,10:00:00,AMV,1,,,, +AAMV2,11:00:00,11:00:00,BEATTY_AIRPORT,2,,,, +AAMV3,13:00:00,13:00:00,BEATTY_AIRPORT,1,,,, +AAMV3,14:00:00,14:00:00,AMV,2,,,, +AAMV4,15:00:00,15:00:00,AMV,1,,,, +AAMV4,16:00:00,16:00:00,BEATTY_AIRPORT,2,,,, --- /dev/null +++ b/origin-src/transitfeed-1.2.5/test/data/duplicate_schedule_id/stops.txt @@ -1,1 +1,11 @@ +stop_id,stop_name,stop_desc,stop_lat,stop_lon,zone_id,stop_url +FUR_CREEK_RES,Furnace Creek Resort (Démonstration),,36.425288,-117.133162,, +BEATTY_AIRPORT,Nye County Airport (Demo),,36.868446,-116.784582,, +BULLFROG,Bullfrog (Demo),,36.88108,-116.81797,, +STAGECOACH,Stagecoach Hotel & Casino (Demo),,36.915682,-116.751677,, +NADAV,North Ave / D Ave N (Demo),,36.914893,-116.76821,, +NANAA,North Ave / N A Ave (Demo),,36.914944,-116.761472,, +DADAN,Doing Ave / D Ave N (Demo),,36.909489,-116.768242,, +EMSI,E Main St / S Irving St (Demo),,36.905697,-116.76218,, +AMV,Amargosa Valley (Demo),,36.641496,-116.40094,, --- /dev/null +++ b/origin-src/transitfeed-1.2.5/test/data/duplicate_schedule_id/trips.txt @@ -1,1 +1,13 @@ +route_id,service_id,trip_id,trip_headsign,direction_id,block_id,shape_id +AB,FULLW,AB1,to Bullfrog,0,1, +AB,FULLW,AB2,to Airport,1,2, +STBA,FULLW,STBA,Shuttle,,, +CITY,FULLW,CITY1,ÅŒ,0,, +CITY,FULLW,CITY2,ÅŒ,1,, +BFC,FULLW,BFC1,to Furnace Creek Resort,0,1, +BFC,FULLW,BFC2,to Bullfrog,1,2, +AAMV,WE,AAMV1,to Amargosa Valley,0,, +AAMV,WE,AAMV2,to Airport,1,, +AAMV,WE,AAMV3,to Amargosa Valley,0,, +AAMV,WE,AAMV4,to Airport,1,, --- /dev/null +++ b/origin-src/transitfeed-1.2.5/test/data/duplicate_stop/agency.txt @@ -1,1 +1,2 @@ - +agency_id,agency_name,agency_url,agency_timezone +DTA,Demo Transit Authority,http://google.com,America/Los_Angeles --- /dev/null +++ b/origin-src/transitfeed-1.2.5/test/data/duplicate_stop/calendar.txt @@ -1,1 +1,3 @@ - +service_id,monday,tuesday,wednesday,thursday,friday,saturday,sunday,start_date,end_date +FULLW,1,1,1,1,1,1,1,20070101,20101231 +WE,0,0,0,0,0,1,1,20070101,20101231 --- /dev/null +++ b/origin-src/transitfeed-1.2.5/test/data/duplicate_stop/calendar_dates.txt @@ -1,1 +1,2 @@ - +service_id,date,exception_type +FULLW,20070604,2 --- /dev/null +++ b/origin-src/transitfeed-1.2.5/test/data/duplicate_stop/fare_attributes.txt @@ -1,1 +1,4 @@ +fare_id,price,currency_type,payment_method,transfers,transfer_duration +p,1.25,USD,0,0, +a,5.25,USD,0,0, --- /dev/null +++ b/origin-src/transitfeed-1.2.5/test/data/duplicate_stop/fare_rules.txt @@ -1,1 +1,6 @@ +fare_id,route_id,origin_id,destination_id,contains_id +p,AB,,, +p,STBA,,, +p,BFC,,, +a,AAMV,,, --- /dev/null +++ b/origin-src/transitfeed-1.2.5/test/data/duplicate_stop/frequencies.txt @@ -1,1 +1,12 @@ - +trip_id,start_time,end_time,headway_secs +STBA,6:00:00,22:00:00,1800 +CITY1,6:00:00,7:59:59,1800 +CITY2,6:00:00,7:59:59,1800 +CITY1,8:00:00,9:59:59,600 +CITY2,8:00:00,9:59:59,600 +CITY1,10:00:00,15:59:59,1800 +CITY2,10:00:00,15:59:59,1800 +CITY1,16:00:00,18:59:59,600 +CITY2,16:00:00,18:59:59,600 +CITY1,19:00:00,22:00:00,1800 +CITY2,19:00:00,22:00:00,1800 --- /dev/null +++ b/origin-src/transitfeed-1.2.5/test/data/duplicate_stop/routes.txt @@ -1,1 +1,6 @@ - +route_id,agency_id,route_short_name,route_long_name,route_desc,route_type,route_url,route_color,route_text_color +AB,DTA,,Airport - Bullfrog,,3 +BFC,DTA,,Bullfrog - Furnace Creek Resort,,3 +STBA,DTA,,Stagecoach - Airport Shuttle,,3 +CITY,DTA,,City,,3 +AAMV,DTA,,Airport - Amargosa Valley,,3 --- /dev/null +++ b/origin-src/transitfeed-1.2.5/test/data/duplicate_stop/stop_times.txt @@ -1,1 +1,29 @@ - +trip_id,arrival_time,departure_time,stop_id,stop_sequence,stop_headsign,pickup_type,drop_off_type,shape_dist_traveled +STBA,6:00:00,6:00:00,STAGECOACH,1 +STBA,6:20:00,6:20:00,BEATTY_AIRPORT,2 +CITY1,6:00:00,6:00:00,STAGECOACH,1 +CITY1,6:05:00,6:07:00,NANAA,2 +CITY1,6:12:00,6:14:00,NADAV,3 +CITY1,6:19:00,6:21:00,DADAN,4 +CITY1,6:26:00,6:28:00,EMSI,5 +CITY2,6:28:00,6:30:00,EMSI,1 +CITY2,6:35:00,6:37:00,DADAN,2 +CITY2,6:42:00,6:44:00,NADAV,3 +CITY2,6:49:00,6:51:00,NANAA,4 +CITY2,6:56:00,6:58:00,STAGECOACH,5 +AB1,8:00:00,8:00:00,BEATTY_AIRPORT,1 +AB1,8:10:00,8:15:00,BULLFROG,2 +AB2,12:05:00,12:05:00,BULLFROG,1 +AB2,12:15:00,12:15:00,BEATTY_AIRPORT,2 +BFC1,8:20:00,8:20:00,FROG,1 +BFC1,9:20:00,9:20:00,FUR_CREEK_RES,2 +BFC2,11:00:00,11:00:00,FUR_CREEK_RES,1 +BFC2,12:00:00,12:00:00,BULLFROG,2 +AAMV1,8:00:00,8:00:00,BEATTY_AIRPORT,1 +AAMV1,9:00:00,9:00:00,AMV,2 +AAMV2,10:00:00,10:00:00,AMV,1 +AAMV2,11:00:00,11:00:00,BEATTY_AIRPORT,2 +AAMV3,13:00:00,13:00:00,BEATTY_AIRPORT,1 +AAMV3,14:00:00,14:00:00,AMV,2 +AAMV4,15:00:00,15:00:00,AMV,1 +AAMV4,16:00:00,16:00:00,BEATTY_AIRPORT,2 --- /dev/null +++ b/origin-src/transitfeed-1.2.5/test/data/duplicate_stop/stops.txt @@ -1,1 +1,11 @@ - +stop_id,stop_name,stop_desc,stop_lat,stop_lon,zone_id,stop_url +FUR_CREEK_RES,Furnace Creek Resort (Demo),,36.425288,-117.133162 +BEATTY_AIRPORT,Nye County Airport (Demo),,36.868446,-116.784582 +BULLFROG,Bullfrog (Demo),,36.88108,-116.81797 +FROG,Bull Frog,,36.881083,-116.817968 +STAGECOACH,Stagecoach Hotel & Casino (Demo),,36.915682,-116.751677 +NADAV,North Ave / D Ave N (Demo),,36.914893,-116.76821 +NANAA,North Ave / N A Ave (Demo),,36.914944,-116.761472 +DADAN,Doing Ave / D Ave N (Demo),,36.909489,-116.768242 +EMSI,E Main St / S Irving St (Demo),,36.905697,-116.76218 +AMV,Amargosa Valley (Demo),,36.641496,-116.40094 --- /dev/null +++ b/origin-src/transitfeed-1.2.5/test/data/duplicate_stop/trips.txt @@ -1,1 +1,12 @@ - +route_id,service_id,trip_id,trip_headsign,direction_id,block_id,shape_id +AB,FULLW,AB1,to Bullfrog,0,1 +AB,FULLW,AB2,to Airport,1,2 +STBA,FULLW,STBA,Shuttle +CITY,FULLW,CITY1,,0 +CITY,FULLW,CITY2,,1 +BFC,FULLW,BFC1,to Furnace Creek Resort,0,1 +BFC,FULLW,BFC2,to Bullfrog,1,2 +AAMV,WE,AAMV1,to Amargosa Valley,0 +AAMV,WE,AAMV2,to Airport,1 +AAMV,WE,AAMV3,to Amargosa Valley,0 +AAMV,WE,AAMV4,to Airport,1 --- /dev/null +++ b/origin-src/transitfeed-1.2.5/test/data/duplicate_stop_sequence/agency.txt @@ -1,1 +1,3 @@ +agency_id,agency_name,agency_url,agency_timezone +DTA,Autorité de passage de démonstration,http://google.com,America/Los_Angeles --- /dev/null +++ b/origin-src/transitfeed-1.2.5/test/data/duplicate_stop_sequence/calendar.txt @@ -1,1 +1,3 @@ +service_id,monday,tuesday,wednesday,thursday,friday,saturday,sunday,start_date,end_date +FULLW,1,1,1,1,1,1,1,20070101,20101231 --- /dev/null +++ b/origin-src/transitfeed-1.2.5/test/data/duplicate_stop_sequence/routes.txt @@ -1,1 +1,3 @@ +route_id,agency_id,route_short_name,route_long_name,route_desc,route_type,route_url,route_color,route_text_color +CITY,DTA,ÅŒ,Bar Circle,Route with ĸool unicode shortname,3,,, --- /dev/null +++ b/origin-src/transitfeed-1.2.5/test/data/duplicate_stop_sequence/stop_times.txt @@ -1,1 +1,7 @@ +trip_id,arrival_time,departure_time,stop_id,stop_sequence,stop_headsign,pickup_type,drop_off_type,shape_dist_traveled +CITY1,6:00:00,6:00:00,STAGECOACH,0,,,, +CITY1,6:05:00,6:07:00,NANAA,10,going to nadav,2,3, +CITY1,6:12:00,6:14:00,NADAV,10,,,, +CITY1,6:19:00,6:21:00,DADAN,15,,,, +CITY1,6:26:00,6:28:00,EMSI,20,,,, --- /dev/null +++ b/origin-src/transitfeed-1.2.5/test/data/duplicate_stop_sequence/stops.txt @@ -1,1 +1,7 @@ +stop_id,stop_name,stop_desc,stop_lat,stop_lon,zone_id,stop_url,stop_code +STAGECOACH,Stagecoach Hotel & Casino (Demo),,36.915682,-116.751677,,,1236 +NADAV,North Ave / D Ave N (Demo),,36.914893,-116.76821,,,1237 +NANAA,North Ave / N A Ave (Demo),,36.914944,-116.761472,,,1238 +DADAN,Doing Ave / D Ave N (Demo),,36.909489,-116.768242,,, +EMSI,E Main St / S Irving St (Demo),,36.905697,-116.76218,,, --- /dev/null +++ b/origin-src/transitfeed-1.2.5/test/data/duplicate_stop_sequence/trips.txt @@ -1,1 +1,3 @@ +route_id,service_id,trip_id,trip_headsign,direction_id,block_id,shape_id +CITY,FULLW,CITY1,,0,, --- /dev/null +++ b/origin-src/transitfeed-1.2.5/test/data/empty_file/agency.txt --- /dev/null +++ b/origin-src/transitfeed-1.2.5/test/data/empty_file/calendar.txt @@ -1,1 +1,3 @@ - +service_id,monday,tuesday,wednesday,thursday,friday,saturday,sunday,start_date,end_date +FULLW,1,1,1,1,1,1,1,20070101,20101231 +WE,0,0,0,0,0,1,1,20070101,20101231 --- /dev/null +++ b/origin-src/transitfeed-1.2.5/test/data/empty_file/calendar_dates.txt @@ -1,1 +1,2 @@ - +service_id,date,exception_type +FULLW,20070604,2 --- /dev/null +++ b/origin-src/transitfeed-1.2.5/test/data/empty_file/fare_attributes.txt @@ -1,1 +1,4 @@ +fare_id,price,currency_type,payment_method,transfers,transfer_duration +p,1.25,USD,0,0, +a,5.25,USD,0,0, --- /dev/null +++ b/origin-src/transitfeed-1.2.5/test/data/empty_file/fare_rules.txt @@ -1,1 +1,6 @@ +fare_id,route_id,origin_id,destination_id,contains_id +p,AB,,, +p,STBA,,, +p,BFC,,, +a,AAMV,,, --- /dev/null +++ b/origin-src/transitfeed-1.2.5/test/data/empty_file/frequencies.txt @@ -1,1 +1,12 @@ - +trip_id,start_time,end_time,headway_secs +STBA,6:00:00,22:00:00,1800 +CITY1,6:00:00,7:59:59,1800 +CITY2,6:00:00,7:59:59,1800 +CITY1,8:00:00,9:59:59,600 +CITY2,8:00:00,9:59:59,600 +CITY1,10:00:00,15:59:59,1800 +CITY2,10:00:00,15:59:59,1800 +CITY1,16:00:00,18:59:59,600 +CITY2,16:00:00,18:59:59,600 +CITY1,19:00:00,22:00:00,1800 +CITY2,19:00:00,22:00:00,1800 --- /dev/null +++ b/origin-src/transitfeed-1.2.5/test/data/empty_file/routes.txt @@ -1,1 +1,6 @@ - +route_id,agency_id,route_short_name,route_long_name,route_desc,route_type,route_url,route_color,route_text_color +AB,DTA,,Airport - Bullfrog,,3 +BFC,DTA,,Bullfrog - Furnace Creek Resort,,3 +STBA,DTA,,Stagecoach - Airport Shuttle,,3 +CITY,DTA,,City,,3 +AAMV,DTA,,Airport - Amargosa Valley,,3 --- /dev/null +++ b/origin-src/transitfeed-1.2.5/test/data/empty_file/stop_times.txt @@ -1,1 +1,30 @@ +trip_id,arrival_time,departure_time,stop_id,stop_sequence +STBA,6:00:00,6:00:00,STAGECOACH,1 +STBA,6:20:00,6:20:00,BEATTY_AIRPORT,2 +CITY1,6:00:00,6:00:00,STAGECOACH,1 +CITY1,6:05:00,6:07:00,NANAA,2 +CITY1,6:12:00,6:14:00,NADAV,3 +CITY1,6:19:00,6:21:00,DADAN,4 +CITY1,6:26:00,6:28:00,EMSI,5 +CITY2,6:28:00,6:30:00,EMSI,1 +CITY2,6:35:00,6:37:00,DADAN,2 +CITY2,6:42:00,6:44:00,NADAV,3 +CITY2,6:49:00,6:51:00,NANAA,4 +CITY2,6:56:00,6:58:00,STAGECOACH,5 +AB1,8:00:00,8:00:00,BEATTY_AIRPORT,1 +AB1,8:10:00,8:15:00,BULLFROG,2 +AB2,12:05:00,12:05:00,BULLFROG,1 +AB2,12:15:00,12:15:00,BEATTY_AIRPORT,2 +BFC1,8:20:00,8:20:00,BULLFROG,1 +BFC1,9:20:00,9:20:00,FUR_CREEK_RES,2 +BFC2,11:00:00,11:00:00,FUR_CREEK_RES,1 +BFC2,12:00:00,12:00:00,BULLFROG,2 +AAMV1,8:00:00,8:00:00,BEATTY_AIRPORT,1 +AAMV1,9:00:00,9:00:00,AMV,2 +AAMV2,10:00:00,10:00:00,AMV,1 +AAMV2,11:00:00,11:00:00,BEATTY_AIRPORT,2 +AAMV3,13:00:00,13:00:00,BEATTY_AIRPORT,1 +AAMV3,14:00:00,14:00:00,AMV,2 +AAMV4,15:00:00,15:00:00,AMV,1 +AAMV4,16:00:00,16:00:00,BEATTY_AIRPORT,2 --- /dev/null +++ b/origin-src/transitfeed-1.2.5/test/data/empty_file/stops.txt @@ -1,1 +1,10 @@ - +stop_id,stop_name,stop_desc,stop_lat,stop_lon,zone_id,stop_url +FUR_CREEK_RES,Furnace Creek Resort (Demo),,36.425288,-117.133162 +BEATTY_AIRPORT,Nye County Airport (Demo),,36.868446,-116.784582 +BULLFROG,Bullfrog (Demo),,36.88108,-116.81797 +STAGECOACH,Stagecoach Hotel & Casino (Demo),,36.915682,-116.751677 +NADAV,North Ave / D Ave N (Demo),,36.914893,-116.76821 +NANAA,North Ave / N A Ave (Demo),,36.914944,-116.761472 +DADAN,Doing Ave / D Ave N (Demo),,36.909489,-116.768242 +EMSI,E Main St / S Irving St (Demo),,36.905697,-116.76218 +AMV,Amargosa Valley (Demo),,36.641496,-116.40094 --- /dev/null +++ b/origin-src/transitfeed-1.2.5/test/data/empty_file/trips.txt @@ -1,1 +1,12 @@ - +route_id,service_id,trip_id,trip_headsign,direction_id,block_id,shape_id +AB,FULLW,AB1,to Bullfrog,0,1 +AB,FULLW,AB2,to Airport,1,2 +STBA,FULLW,STBA,Shuttle +CITY,FULLW,CITY1,,0 +CITY,FULLW,CITY2,,1 +BFC,FULLW,BFC1,to Furnace Creek Resort,0,1 +BFC,FULLW,BFC2,to Bullfrog,1,2 +AAMV,WE,AAMV1,to Amargosa Valley,0 +AAMV,WE,AAMV2,to Airport,1 +AAMV,WE,AAMV3,to Amargosa Valley,0 +AAMV,WE,AAMV4,to Airport,1 --- /dev/null +++ b/origin-src/transitfeed-1.2.5/test/data/extra_row_cells/agency.txt @@ -1,1 +1,2 @@ - +agency_id,agency_name,agency_url,agency_timezone +DTA,Demo Transit Authority,http://google.com,America/Los_Angeles --- /dev/null +++ b/origin-src/transitfeed-1.2.5/test/data/extra_row_cells/calendar.txt @@ -1,1 +1,3 @@ - +service_id,monday,tuesday,wednesday,thursday,friday,saturday,sunday,start_date,end_date +FULLW,1,1,1,1,1,1,1,20070101,20101231 +WE,0,0,0,0,0,1,1,20070101,20101231 --- /dev/null +++ b/origin-src/transitfeed-1.2.5/test/data/extra_row_cells/calendar_dates.txt @@ -1,1 +1,2 @@ - +service_id,date,exception_type +FULLW,20070604,2 --- /dev/null +++ b/origin-src/transitfeed-1.2.5/test/data/extra_row_cells/fare_attributes.txt @@ -1,1 +1,4 @@ +fare_id,price,currency_type,payment_method,transfers,transfer_duration +p,1.25,USD,0,0, +a,5.25,USD,0,0, --- /dev/null +++ b/origin-src/transitfeed-1.2.5/test/data/extra_row_cells/fare_rules.txt @@ -1,1 +1,6 @@ +fare_id,route_id,origin_id,destination_id,contains_id +p,AB,,, +p,STBA,,, +p,BFC,,, +a,AAMV,,, --- /dev/null +++ b/origin-src/transitfeed-1.2.5/test/data/extra_row_cells/frequencies.txt @@ -1,1 +1,12 @@ - +trip_id,start_time,end_time,headway_secs +STBA,6:00:00,22:00:00,1800 +CITY1,6:00:00,7:59:59,1800 +CITY2,6:00:00,7:59:59,1800 +CITY1,8:00:00,9:59:59,600 +CITY2,8:00:00,9:59:59,600 +CITY1,10:00:00,15:59:59,1800 +CITY2,10:00:00,15:59:59,1800 +CITY1,16:00:00,18:59:59,600 +CITY2,16:00:00,18:59:59,600 +CITY1,19:00:00,22:00:00,1800 +CITY2,19:00:00,22:00:00,1800 --- /dev/null +++ b/origin-src/transitfeed-1.2.5/test/data/extra_row_cells/routes.txt @@ -1,1 +1,7 @@ +route_id,agency_id,route_short_name,route_long_name,route_desc,route_type +AB,DTA,,Airport - Bullfrog,,3 +BFC,DTA,,Bullfrog - Furnace Creek Resort,,3 +STBA,DTA,,Stagecoach - Airport Shuttle,,3, +CITY,DTA,,City,,3 +AAMV,DTA,,Airport - Amargosa Valley,,3 --- /dev/null +++ b/origin-src/transitfeed-1.2.5/test/data/extra_row_cells/stop_times.txt @@ -1,1 +1,30 @@ +trip_id,arrival_time,departure_time,stop_id,stop_sequence +STBA,6:00:00,6:00:00,STAGECOACH,1 +STBA,6:20:00,6:20:00,BEATTY_AIRPORT,2 +CITY1,6:00:00,6:00:00,STAGECOACH,1 +CITY1,6:05:00,6:07:00,NANAA,2 +CITY1,6:12:00,6:14:00,NADAV,3 +CITY1,6:19:00,6:21:00,DADAN,4 +CITY1,6:26:00,6:28:00,EMSI,5 +CITY2,6:28:00,6:30:00,EMSI,1 +CITY2,6:35:00,6:37:00,DADAN,2 +CITY2,6:42:00,6:44:00,NADAV,3 +CITY2,6:49:00,6:51:00,NANAA,4 +CITY2,6:56:00,6:58:00,STAGECOACH,5 +AB1,8:00:00,8:00:00,BEATTY_AIRPORT,1 +AB1,8:10:00,8:15:00,BULLFROG,2 +AB2,12:05:00,12:05:00,BULLFROG,1 +AB2,12:15:00,12:15:00,BEATTY_AIRPORT,2 +BFC1,8:20:00,8:20:00,BULLFROG,1 +BFC1,9:20:00,9:20:00,FUR_CREEK_RES,2 +BFC2,11:00:00,11:00:00,FUR_CREEK_RES,1 +BFC2,12:00:00,12:00:00,BULLFROG,2 +AAMV1,8:00:00,8:00:00,BEATTY_AIRPORT,1 +AAMV1,9:00:00,9:00:00,AMV,2 +AAMV2,10:00:00,10:00:00,AMV,1 +AAMV2,11:00:00,11:00:00,BEATTY_AIRPORT,2 +AAMV3,13:00:00,13:00:00,BEATTY_AIRPORT,1 +AAMV3,14:00:00,14:00:00,AMV,2 +AAMV4,15:00:00,15:00:00,AMV,1 +AAMV4,16:00:00,16:00:00,BEATTY_AIRPORT,2 --- /dev/null +++ b/origin-src/transitfeed-1.2.5/test/data/extra_row_cells/stops.txt @@ -1,1 +1,11 @@ +stop_id,stop_name,stop_desc,stop_lat,stop_lon +FUR_CREEK_RES,Furnace Creek Resort (Demo),,36.425288,-117.133162 +BEATTY_AIRPORT,Nye County Airport (Demo),,36.868446,-116.784582 +BULLFROG,Bullfrog (Demo),,36.88108,-116.81797 +STAGECOACH,Stagecoach Hotel & Casino (Demo),,36.915682,-116.751677 +NADAV,North Ave / D Ave N (Demo),,36.914893,-116.76821 +NANAA,North Ave / N A Ave (Demo),,36.914944,-116.761472 +DADAN,Doing Ave / D Ave N (Demo),,36.909489,-116.768242 +EMSI,E Main St / S Irving St (Demo),,36.905697,-116.76218 +AMV,Amargosa Valley (Demo),,36.641496,-116.40094 --- /dev/null +++ b/origin-src/transitfeed-1.2.5/test/data/extra_row_cells/trips.txt @@ -1,1 +1,13 @@ +route_id,service_id,trip_id,trip_headsign,direction_id,block_id +AB,FULLW,AB1,to Bullfrog,0,1 +AB,FULLW,AB2,to Airport,1,2 +STBA,FULLW,STBA,Shuttle,1, +CITY,FULLW,CITY1,,0, +CITY,FULLW,CITY2,,1, +BFC,FULLW,BFC1,to Furnace Creek Resort,0,1 +BFC,FULLW,BFC2,to Bullfrog,1,2 +AAMV,WE,AAMV1,to Amargosa Valley,0, +AAMV,WE,AAMV2,to Airport,1, +AAMV,WE,AAMV3,to Amargosa Valley,0, +AAMV,WE,AAMV4,to Airport,1, --- /dev/null +++ b/origin-src/transitfeed-1.2.5/test/data/filter_unusual_trips/agency.txt @@ -1,1 +1,2 @@ - +agency_id,agency_name,agency_url,agency_timezone +DTA,Demo Transit Authority,http://google.com,America/Los_Angeles --- /dev/null +++ b/origin-src/transitfeed-1.2.5/test/data/filter_unusual_trips/calendar.txt @@ -1,1 +1,3 @@ - +service_id,monday,tuesday,wednesday,thursday,friday,saturday,sunday,start_date,end_date +FULLW,1,1,1,1,1,1,1,20070101,20101231 +WE,0,0,0,0,0,1,1,20070101,20101231 --- /dev/null +++ b/origin-src/transitfeed-1.2.5/test/data/filter_unusual_trips/calendar_dates.txt @@ -1,1 +1,2 @@ - +service_id,date,exception_type +FULLW,20070604,2 --- /dev/null +++ b/origin-src/transitfeed-1.2.5/test/data/filter_unusual_trips/fare_attributes.txt @@ -1,1 +1,3 @@ - +fare_id,price,currency_type,payment_method,transfers,transfer_duration +p,1.25,USD,0,0, +a,5.25,USD,0,0, --- /dev/null +++ b/origin-src/transitfeed-1.2.5/test/data/filter_unusual_trips/fare_rules.txt @@ -1,1 +1,5 @@ - +fare_id,route_id,origin_id,destination_id,contains_id +p,AB,,, +p,STBA,,, +p,BFC,,, +a,AAMV,,, --- /dev/null +++ b/origin-src/transitfeed-1.2.5/test/data/filter_unusual_trips/frequencies.txt @@ -1,1 +1,12 @@ - +trip_id,start_time,end_time,headway_secs +STBA,6:00:00,22:00:00,1800 +CITY1,6:00:00,7:59:59,1800 +CITY2,6:00:00,7:59:59,1800 +CITY1,8:00:00,9:59:59,600 +CITY2,8:00:00,9:59:59,600 +CITY1,10:00:00,15:59:59,1800 +CITY2,10:00:00,15:59:59,1800 +CITY1,16:00:00,18:59:59,600 +CITY2,16:00:00,18:59:59,600 +CITY1,19:00:00,22:00:00,1800 +CITY2,19:00:00,22:00:00,1800 --- /dev/null +++ b/origin-src/transitfeed-1.2.5/test/data/filter_unusual_trips/routes.txt @@ -1,1 +1,6 @@ - +route_id,agency_id,route_short_name,route_long_name,route_desc,route_type,route_url,route_color,route_text_color +AB,DTA,10,Airport - Bullfrog,,3,,, +BFC,DTA,20,Bullfrog - Furnace Creek Resort,,3,,, +STBA,DTA,30,Stagecoach - Airport Shuttle,,3,,, +CITY,DTA,40,City,,3,,, +AAMV,DTA,50,Airport - Amargosa Valley,,3,,, --- /dev/null +++ b/origin-src/transitfeed-1.2.5/test/data/filter_unusual_trips/shapes.txt @@ -1,1 +1,1 @@ - +shape_id,shape_pt_lat,shape_pt_lon,shape_pt_sequence,shape_dist_traveled --- /dev/null +++ b/origin-src/transitfeed-1.2.5/test/data/filter_unusual_trips/stop_times.txt @@ -1,1 +1,80 @@ +trip_id,arrival_time,departure_time,stop_id,stop_sequence,stop_headsign,pickup_type,shape_dist_traveled +STBA,6:00:00,6:00:00,STAGECOACH,1,,, +STBA,6:20:00,6:20:00,BEATTY_AIRPORT,2,,, +CITY1,6:00:00,6:00:00,STAGECOACH,1,,, +CITY1,6:05:00,6:07:00,NANAA,2,,, +CITY1,6:12:00,6:14:00,NADAV,3,,, +CITY1,6:19:00,6:21:00,DADAN,4,,, +CITY1,6:26:00,6:28:00,EMSI,5,,, +CITY2,6:28:00,6:30:00,EMSI,1,,, +CITY2,6:35:00,6:37:00,DADAN,2,,, +CITY2,6:42:00,6:44:00,NADAV,3,,, +CITY2,6:49:00,6:51:00,NANAA,4,,, +CITY2,6:56:00,6:58:00,STAGECOACH,5,,, +CITY3,6:00:00,6:00:00,STAGECOACH,1,,, +CITY3,6:05:00,6:07:00,NANAA,2,,, +CITY3,6:12:00,6:14:00,NADAV,3,,, +CITY3,6:19:00,6:21:00,DADAN,4,,, +CITY3,6:26:00,6:28:00,EMSI,5,,, +CITY4,6:28:00,6:30:00,EMSI,1,,, +CITY4,6:35:00,6:37:00,DADAN,2,,, +CITY4,6:42:00,6:44:00,NADAV,3,,, +CITY4,6:49:00,6:51:00,NANAA,4,,, +CITY4,6:56:00,6:58:00,STAGECOACH,5,,, +CITY5,6:00:00,6:00:00,STAGECOACH,1,,, +CITY5,6:05:00,6:07:00,NANAA,2,,, +CITY5,6:12:00,6:14:00,NADAV,3,,, +CITY5,6:19:00,6:21:00,DADAN,4,,, +CITY5,6:26:00,6:28:00,EMSI,5,,, +CITY6,6:28:00,6:30:00,EMSI,1,,, +CITY6,6:35:00,6:37:00,DADAN,2,,, +CITY6,6:42:00,6:44:00,NADAV,3,,, +CITY6,6:49:00,6:51:00,NANAA,4,,, +CITY6,6:56:00,6:58:00,STAGECOACH,5,,, +CITY7,6:00:00,6:00:00,STAGECOACH,1,,, +CITY7,6:05:00,6:07:00,NANAA,2,,, +CITY7,6:12:00,6:14:00,NADAV,3,,, +CITY7,6:19:00,6:21:00,DADAN,4,,, +CITY7,6:26:00,6:28:00,EMSI,5,,, +CITY8,6:28:00,6:30:00,EMSI,1,,, +CITY8,6:35:00,6:37:00,DADAN,2,,, +CITY8,6:42:00,6:44:00,NADAV,3,,, +CITY8,6:49:00,6:51:00,NANAA,4,,, +CITY8,6:56:00,6:58:00,STAGECOACH,5,,, +CITY9,6:00:00,6:00:00,STAGECOACH,1,,, +CITY9,6:05:00,6:07:00,NANAA,2,,, +CITY9,6:12:00,6:14:00,NADAV,3,,, +CITY9,6:19:00,6:21:00,DADAN,4,,, +CITY9,6:26:00,6:28:00,EMSI,5,,, +CITY10,6:28:00,6:30:00,EMSI,1,,, +CITY10,6:35:00,6:37:00,DADAN,2,,, +CITY10,6:42:00,6:44:00,NADAV,3,,, +CITY10,6:49:00,6:51:00,NANAA,4,,, +CITY10,6:56:00,6:58:00,STAGECOACH,5,,, +CITY11,6:00:00,6:00:00,NANAA,1,,, +CITY11,6:05:00,6:07:00,BEATTY_AIRPORT,2,,, +CITY11,6:12:00,6:14:00,BULLFROG,3,,, +CITY11,6:19:00,6:21:00,DADAN,4,,, +CITY11,6:26:00,6:28:00,EMSI,5,,, +CITY12,6:28:00,6:30:00,EMSI,1,,, +CITY12,6:35:00,6:37:00,DADAN,2,,, +CITY12,7:07:00,7:09:00,AMV,3,,, +CITY12,7:39:00,7:41:00,BEATTY_AIRPORT,4,,, +CITY12,7:46:00,7:48:00,STAGECOACH,5,,, +AB1,8:00:00,8:00:00,BEATTY_AIRPORT,1,,, +AB1,8:10:00,8:15:00,BULLFROG,2,,, +AB2,12:05:00,12:05:00,BULLFROG,1,,, +AB2,12:15:00,12:15:00,BEATTY_AIRPORT,2,,, +BFC1,8:20:00,8:20:00,BULLFROG,1,,, +BFC1,9:20:00,9:20:00,FUR_CREEK_RES,2,,, +BFC2,11:00:00,11:00:00,FUR_CREEK_RES,1,,, +BFC2,12:00:00,12:00:00,BULLFROG,2,,, +AAMV1,8:00:00,8:00:00,BEATTY_AIRPORT,1,,, +AAMV1,9:00:00,9:00:00,AMV,2,,, +AAMV2,10:00:00,10:00:00,AMV,1,,, +AAMV2,11:00:00,11:00:00,BEATTY_AIRPORT,2,,, +AAMV3,13:00:00,13:00:00,BEATTY_AIRPORT,1,,, +AAMV3,14:00:00,14:00:00,AMV,2,,, +AAMV4,15:00:00,15:00:00,AMV,1,,, +AAMV4,16:00:00,16:00:00,BEATTY_AIRPORT,2,,, --- /dev/null +++ b/origin-src/transitfeed-1.2.5/test/data/filter_unusual_trips/stops.txt @@ -1,1 +1,10 @@ - +stop_id,stop_name,stop_desc,stop_lat,stop_lon,zone_id,stop_url +FUR_CREEK_RES,Furnace Creek Resort (Demo),,36.425288,-117.133162,, +BEATTY_AIRPORT,Nye County Airport (Demo),,36.868446,-116.784582,, +BULLFROG,Bullfrog (Demo),,36.88108,-116.81797,, +STAGECOACH,Stagecoach Hotel & Casino (Demo),,36.915682,-116.751677,, +NADAV,North Ave / D Ave N (Demo),,36.914893,-116.76821,, +NANAA,North Ave / N A Ave (Demo),,36.914944,-116.761472,, +DADAN,Doing Ave / D Ave N (Demo),,36.909489,-116.768242,, +EMSI,E Main St / S Irving St (Demo),,36.905697,-116.76218,, +AMV,Amargosa Valley (Demo),,36.641496,-116.40094,, --- /dev/null +++ b/origin-src/transitfeed-1.2.5/test/data/filter_unusual_trips/trips.txt @@ -1,1 +1,22 @@ - +route_id,service_id,trip_id,trip_headsign,direction_id,block_id,shape_id +AB,FULLW,AB1,to Bullfrog,0,1, +AB,FULLW,AB2,to Airport,1,2, +STBA,FULLW,STBA,Shuttle,,, +CITY,FULLW,CITY1,,0,, +CITY,FULLW,CITY2,,1,, +CITY,FULLW,CITY3,,0,, +CITY,FULLW,CITY4,,1,, +CITY,FULLW,CITY5,,0,, +CITY,FULLW,CITY6,,1,, +CITY,FULLW,CITY7,,0,, +CITY,FULLW,CITY8,,1,, +CITY,FULLW,CITY9,,0,, +CITY,FULLW,CITY10,,1,, +CITY,FULLW,CITY11,,0,, +CITY,FULLW,CITY12,,1,, +BFC,FULLW,BFC1,to Furnace Creek Resort,0,1, +BFC,FULLW,BFC2,to Bullfrog,1,2, +AAMV,WE,AAMV1,to Amargosa Valley,0,, +AAMV,WE,AAMV2,to Airport,1,, +AAMV,WE,AAMV3,to Amargosa Valley,0,, +AAMV,WE,AAMV4,to Airport,1,, --- /dev/null +++ b/origin-src/transitfeed-1.2.5/test/data/flatten_feed/agency.txt @@ -1,1 +1,2 @@ - +agency_id,agency_name,agency_url,agency_timezone +DTA,Demo Transit Authority,http://google.com,America/Los_Angeles --- /dev/null +++ b/origin-src/transitfeed-1.2.5/test/data/flatten_feed/calendar.txt @@ -1,1 +1,3 @@ - +service_id,monday,tuesday,wednesday,thursday,friday,saturday,sunday,start_date,end_date +FULLW,1,1,1,1,1,1,1,20070101,20101231 +WE,0,0,0,0,0,1,1,20070101,20101231 --- /dev/null +++ b/origin-src/transitfeed-1.2.5/test/data/flatten_feed/calendar_dates.txt @@ -1,1 +1,4 @@ +service_id,date,exception_type +FULLW,20070604,2 +WE,20070604,1 --- /dev/null +++ b/origin-src/transitfeed-1.2.5/test/data/flatten_feed/fare_attributes.txt @@ -1,1 +1,4 @@ +fare_id,price,currency_type,payment_method,transfers,transfer_duration +p,1.25,USD,0,0, +a,5.25,USD,0,0, --- /dev/null +++ b/origin-src/transitfeed-1.2.5/test/data/flatten_feed/routes.txt @@ -1,1 +1,10 @@ +route_id,agency_id,route_short_name,route_long_name,route_desc,route_type,route_url,route_color,route_text_color +route_1,DTA,1,route with a single trip,,0,http://routes.com/route_1,FF0000, +route_2,DTA,2,route with two trips and one component,test route desc 2,1,,00FF00, +route_3,DTA,3,route with two trips and two components,test route desc 3,2,http://routes.com/route_3,, +route_4,DTA,4,route with two equal trips,test route desc 4,3,http://routes.com/route_4,FFFF00, +route_5,DTA,5,route with two trip but no graph,test route desc 5,4,http://routes.com/route_5,FF00FF, +route_6,DTA,6,route with one trip and no stops,test route desc 6,5,http://routes.com/route_6,00FFFF, +route_7,DTA,7,route with no trips,test route desc 7,6,http://routes.com/route_7,, +route_8,DTA,8,route with a cyclic pattern,test route desc 8,7,http://routes.com/route_8,, --- /dev/null +++ b/origin-src/transitfeed-1.2.5/test/data/flatten_feed/shapes.txt @@ -1,1 +1,14 @@ +shape_id,shape_pt_sequence,shape_pt_lat,shape_pt_lon +shape_1,1,1,1 +shape_1,2,2,4 +shape_1,3,3,9 +shape_1,4,4,16 +shape_2,1,11,11 +shape_2,2,12,14 +shape_2,3,13,19 +shape_2,4,14,26 +shape_3,1,21,21 +shape_3,2,22,24 +shape_3,3,23,29 +shape_3,4,24,36 --- /dev/null +++ b/origin-src/transitfeed-1.2.5/test/data/flatten_feed/stop_times.txt @@ -1,1 +1,29 @@ +trip_id,arrival_time,departure_time,stop_id,stop_sequence +route_1_1,6:00:00,6:00:00,stop1,1 +route_1_1,7:00:00,7:00:00,stop2,2 +route_1_1,8:00:00,8:00:00,stop3,3 +route_2_1,6:00:00,6:00:00,stop1,1 +route_2_1,7:00:00,7:00:00,stop2,2 +route_2_1,8:00:00,8:00:00,stop3,3 +route_2_2,6:00:00,6:00:00,stop2,1 +route_2_2,7:00:00,7:00:00,stop4,2 +route_2_2,8:00:00,8:00:00,stop5,3 +route_3_1,6:00:00,6:00:00,stop1,1 +route_3_1,7:00:00,7:00:00,stop2,2 +route_3_1,8:00:00,8:00:00,stop3,3 +route_3_2,6:00:00,6:00:00,stop4,1 +route_3_2,7:00:00,7:00:00,stop5,2 +route_3_2,8:00:00,8:00:00,stop6,3 +route_4_1,6:00:00,6:00:00,stop1,1 +route_4_1,7:00:00,7:00:00,stop2,2 +route_4_1,8:00:00,8:00:00,stop3,3 +route_4_2,6:00:00,6:00:00,stop1,1 +route_4_2,7:00:00,7:00:00,stop2,2 +route_4_2,8:00:00,8:00:00,stop3,3 +route_5_1,6:00:00,6:00:00,stop1,1 +route_5_2,6:00:00,6:00:00,stop2,1 +route_8_1,6:00:00,6:00:00,stop1,1 +route_8_1,7:00:00,7:00:00,stop2,2 +route_8_1,8:00:00,8:00:00,stop3,3 +route_8_1,9:00:00,9:00:00,stop1,4 --- /dev/null +++ b/origin-src/transitfeed-1.2.5/test/data/flatten_feed/stops.txt @@ -1,1 +1,11 @@ +stop_id,stop_name,stop_desc,stop_lat,stop_lon,zone_id,stop_url +stop1,Furnace Creek Resort (Demo),,36.425288,-117.133162,,http://stops.com/stop1 +stop2,Nye County Airport (Demo),the stop at Nye County Airport,36.868446,-116.784582,, +stop3,Bullfrog (Demo),the stop at Bullfrog,36.88108,-116.81797,,http://stops.com/stop3 +stop4,Stagecoach Hotel & Casino (Demo),the stop at Stagecoach Hotel & Casino,36.915682,-116.751677,,http://stops.com/stop4 +stop5,North Ave / D Ave N (Demo),the stop at North Ave / D Ave N,36.914893,-116.76821,,http://stops.com/stop5 +stop6,North Ave / N A Ave (Demo),the stop at North Ave / N A Ave,36.914944,-116.761472,,http://stops.com/stop6 +stop7,Doing Ave / D Ave N (Demo),the stop at Doing Ave / D Ave N,36.909489,-116.768242,,http://stops.com/stop7 +stop8,E Main St / S Irving St (Demo),the stop at E Main St / S Irving St,36.905697,-116.76218,,http://stops.com/stop8 +stop9,Amargosa Valley (Demo),the stop at Amargosa Valley,36.641496,-116.40094,,http://stops.com/stop9 --- /dev/null +++ b/origin-src/transitfeed-1.2.5/test/data/flatten_feed/trips.txt @@ -1,1 +1,13 @@ +route_id,service_id,trip_id,shape_id +route_1,FULLW,route_1_1,shape_1 +route_2,FULLW,route_2_1,shape_2 +route_2,FULLW,route_2_2,shape_3 +route_3,FULLW,route_3_1,shape_1 +route_3,FULLW,route_3_2,shape_1 +route_4,FULLW,route_4_1, +route_4,FULLW,route_4_2, +route_5,FULLW,route_5_1, +route_5,FULLW,route_5_2, +route_8,FULLW,route_8_1, +route_8,WE,route_8_2, --- /dev/null +++ b/origin-src/transitfeed-1.2.5/test/data/good_feed/agency.txt @@ -1,1 +1,3 @@ +agency_id,agency_name,agency_url,agency_timezone,agency_phone +DTA,Autorité de passage de démonstration,http://google.com,America/Los_Angeles,123 12314 --- /dev/null +++ b/origin-src/transitfeed-1.2.5/test/data/good_feed/calendar.txt @@ -1,1 +1,4 @@ +service_id,monday,tuesday,wednesday,thursday,friday,saturday,sunday,start_date,end_date +FULLW,1,1,1,1,1,1,1,20070101,20111231 +WE,0,0,0,0,0,1,1,20070101,20111231 --- /dev/null +++ b/origin-src/transitfeed-1.2.5/test/data/good_feed/calendar_dates.txt @@ -1,1 +1,2 @@ - +service_id,date,exception_type +FULLW,20070604,2 --- /dev/null +++ b/origin-src/transitfeed-1.2.5/test/data/good_feed/fare_attributes.txt @@ -1,1 +1,4 @@ +fare_id,price,currency_type,payment_method,transfers,transfer_duration +p,1.25,USD,0,0, +a,5.25,USD,0,0, --- /dev/null +++ b/origin-src/transitfeed-1.2.5/test/data/good_feed/fare_rules.txt @@ -1,1 +1,6 @@ +fare_id,route_id,origin_id,destination_id,contains_id +p,AB,,, +p,STBA,,, +p,BFC,,, +a,AAMV,,, --- /dev/null +++ b/origin-src/transitfeed-1.2.5/test/data/good_feed/frequencies.txt @@ -1,1 +1,12 @@ - +trip_id,start_time,end_time,headway_secs +STBA,6:00:00,22:00:00,1800 +CITY1,6:00:00,7:59:59,1800 +CITY2,6:00:00,7:59:59,1800 +CITY1,8:00:00,9:59:59,600 +CITY2,8:00:00,9:59:59,600 +CITY1,10:00:00,15:59:59,1800 +CITY2,10:00:00,15:59:59,1800 +CITY1,16:00:00,18:59:59,600 +CITY2,16:00:00,18:59:59,600 +CITY1,19:00:00,22:00:00,1800 +CITY2,19:00:00,22:00:00,1800 --- /dev/null +++ b/origin-src/transitfeed-1.2.5/test/data/good_feed/routes.txt @@ -1,1 +1,7 @@ +route_id,agency_id,route_short_name,route_long_name,route_desc,route_type,route_url,route_color,route_text_color +AB,DTA,,Airport ⇒ Bullfrog,,3,,, +BFC,DTA,,Bullfrog ⇒ Furnace Creek Resort,,3,,, +STBA,DTA,,Stagecoach ⇒ Airport Shuttle,,3,,, +CITY,DTA,ÅŒ,Bar Circle,Route with ĸool unicode shortname,3,,, +AAMV,DTA,,Airport ⇒ Amargosa Valley,,3,,, --- /dev/null +++ b/origin-src/transitfeed-1.2.5/test/data/good_feed/stop_times.txt @@ -1,1 +1,30 @@ +trip_id,arrival_time,departure_time,stop_id,stop_sequence,stop_headsign,pickup_type,drop_off_type,shape_dist_traveled +STBA,6:00:00,6:00:00,STAGECOACH,0,to airport,1,0,0.212 +STBA,6:20:00,6:20:00,BEATTY_AIRPORT,2,,0,0,1.043 +CITY1,6:00:00,6:00:00,STAGECOACH,0,,,, +CITY1,6:05:00,6:07:00,NANAA,5,going to nadav,2,3, +CITY1,6:12:00,6:14:00,NADAV,10,,,, +CITY1,6:19:00,6:21:00,DADAN,15,,,, +CITY1,6:26:00,6:28:00,EMSI,20,,,, +CITY2,6:28:00,6:30:00,EMSI,100,,,, +CITY2,6:35:00,6:37:00,DADAN,200,,,, +CITY2,6:42:00,6:44:00,NADAV,300,,,, +CITY2,6:49:00,6:51:00,NANAA,400,,,, +CITY2,6:56:00,6:58:00,STAGECOACH,500,,,, +AB1,8:00:00,8:00:00,BEATTY_AIRPORT,1,,,, +AB1,8:10:00,8:15:00,BULLFROG,2,,,, +AB2,12:05:00,12:05:00,BULLFROG,1,,,, +AB2,12:15:00,12:15:00,BEATTY_AIRPORT,2,,,, +BFC1,8:20:00,8:20:00,BULLFROG,1,,,, +BFC1,9:20:00,9:20:00,FUR_CREEK_RES,2,,,, +BFC2,11:00:00,11:00:00,FUR_CREEK_RES,1,,,, +BFC2,12:00:00,12:00:00,BULLFROG,2,,,, +AAMV1,8:00:00,8:00:00,BEATTY_AIRPORT,1,,,, +AAMV1,9:00:00,9:00:00,AMV,2,,,, +AAMV2,10:00:00,10:00:00,AMV,1,,,, +AAMV2,11:00:00,11:00:00,BEATTY_AIRPORT,2,,,, +AAMV3,13:00:00,13:00:00,BEATTY_AIRPORT,1,,,, +AAMV3,14:00:00,14:00:00,AMV,2,,,, +AAMV4,15:00:00,15:00:00,AMV,1,,,, +AAMV4,16:00:00,16:00:00,BEATTY_AIRPORT,2,,,, --- /dev/null +++ b/origin-src/transitfeed-1.2.5/test/data/good_feed/stops.txt @@ -1,1 +1,12 @@ +stop_id,stop_name,stop_desc,stop_lat,stop_lon,zone_id,stop_url,stop_code,location_type,parent_station +FUR_CREEK_RES,Furnace Creek Resort (Demo),,36.425288,-117.133162,,,1234,, +BEATTY_AIRPORT,Nye County Airport (Demo),,36.868446,-116.784582,,,1235,0,BEATTY_AIRPORT_STATION +BEATTY_AIRPORT_STATION,Nye County Airport (Demo),,36.868446,-116.784582,,,1235,1, +BULLFROG,Bullfrog (Demo),,36.88108,-116.81797,,,,, +STAGECOACH,Stagecoach Hotel & Casino (Demo),,36.915682,-116.751677,,,1236,, +NADAV,North Ave / D Ave N (Demo),,36.914893,-116.76821,,,1237,, +NANAA,North Ave / N A Ave (Demo),,36.914944,-116.761472,,,1238,, +DADAN,Doing Ave / D Ave N (Demo),,36.909489,-116.768242,,,,, +EMSI,E Main St / S Irving St (Demo),,36.905697,-116.76218,,,,, +AMV,Amargosa Valley (Demo),,36.641496,-116.40094,,,,, --- /dev/null +++ b/origin-src/transitfeed-1.2.5/test/data/good_feed/transfers.txt @@ -1,1 +1,4 @@ +from_stop_id,to_stop_id,transfer_type,min_transfer_time +NADAV,NANAA,3, +EMSI,NANAA,2,1200 --- /dev/null +++ b/origin-src/transitfeed-1.2.5/test/data/good_feed/trips.txt @@ -1,1 +1,12 @@ - +route_id,service_id,trip_id,trip_headsign,direction_id,block_id,shape_id +AB,FULLW,AB1,to Bullfrog,0,1, +AB,FULLW,AB2,to Airport,1,2, +STBA,FULLW,STBA,Shuttle,,, +CITY,FULLW,CITY1,,0,, +CITY,FULLW,CITY2,,1,, +BFC,FULLW,BFC1,to Furnace Creek Resort,0,1, +BFC,FULLW,BFC2,to Bullfrog,1,2, +AAMV,WE,AAMV1,to Amargosa Valley,0,, +AAMV,WE,AAMV2,to Airport,1,, +AAMV,WE,AAMV3,to Amargosa Valley,0,, +AAMV,WE,AAMV4,to Airport,1,, --- /dev/null +++ b/origin-src/transitfeed-1.2.5/test/data/invalid_route_agency/agency.txt @@ -1,1 +1,2 @@ - +agency_id,agency_name,agency_url,agency_timezone +DTA,Demo Transit Authority,http://google.com,America/Los_Angeles --- /dev/null +++ b/origin-src/transitfeed-1.2.5/test/data/invalid_route_agency/calendar.txt @@ -1,1 +1,3 @@ - +service_id,monday,tuesday,wednesday,thursday,friday,saturday,sunday,start_date,end_date +FULLW,1,1,1,1,1,1,1,20070101,20101231 +WE,0,0,0,0,0,1,1,20070101,20101231 --- /dev/null +++ b/origin-src/transitfeed-1.2.5/test/data/invalid_route_agency/calendar_dates.txt @@ -1,1 +1,2 @@ - +service_id,date,exception_type +FULLW,20070604,2 --- /dev/null +++ b/origin-src/transitfeed-1.2.5/test/data/invalid_route_agency/routes.txt @@ -1,1 +1,7 @@ +route_id,agency_id,route_short_name,route_long_name,route_desc,route_type,route_url,route_color,route_text_color +AB,DTA,,Airport - Bullfrog,,3,,, +BFC,DTA,,Bullfrog - Furnace Creek Resort,,3,,, +STBA,DVT,,Stagecoach - Airport Shuttle,,3,,, +CITY,DTA,,City,,3,,, +AAMV,DTA,,Airport - Amargosa Valley,,3,,, --- /dev/null +++ b/origin-src/transitfeed-1.2.5/test/data/invalid_route_agency/stop_times.txt @@ -1,1 +1,29 @@ - +trip_id,arrival_time,departure_time,stop_id,stop_sequence,stop_headsign,pickup_type,drop_off_type,shape_dist_traveled +STBA,6:00:00,6:00:00,STAGECOACH,1,,,, +STBA,6:20:00,6:20:00,BEATTY_AIRPORT,2,,,, +CITY1,6:00:00,6:00:00,STAGECOACH,1,,,, +CITY1,6:05:00,6:07:00,NANAA,2,,,, +CITY1,6:12:00,6:14:00,NADAV,3,,,, +CITY1,6:19:00,6:21:00,DADAN,4,,,, +CITY1,6:26:00,6:28:00,EMSI,5,,,, +CITY2,6:28:00,6:30:00,EMSI,1,,,, +CITY2,6:35:00,6:37:00,DADAN,2,,,, +CITY2,6:42:00,6:44:00,NADAV,3,,,, +CITY2,6:49:00,6:51:00,NANAA,4,,,, +CITY2,6:56:00,6:58:00,STAGECOACH,5,,,, +AB1,8:00:00,8:00:00,BEATTY_AIRPORT,1,,,, +AB1,8:10:00,8:15:00,BULLFROG,2,,,, +AB2,12:05:00,12:05:00,BULLFROG,1,,,, +AB2,12:15:00,12:15:00,BEATTY_AIRPORT,2,,,, +BFC1,8:20:00,8:20:00,BULLFROG,1,,,, +BFC1,9:20:00,9:20:00,FUR_CREEK_RES,2,,,, +BFC2,11:00:00,11:00:00,FUR_CREEK_RES,1,,,, +BFC2,12:00:00,12:00:00,BULLFROG,2,,,, +AAMV1,8:00:00,8:00:00,BEATTY_AIRPORT,1,,,, +AAMV1,9:00:00,9:00:00,AMV,2,,,, +AAMV2,10:00:00,10:00:00,AMV,1,,,, +AAMV2,11:00:00,11:00:00,BEATTY_AIRPORT,2,,,, +AAMV3,13:00:00,13:00:00,BEATTY_AIRPORT,1,,,, +AAMV3,14:00:00,14:00:00,AMV,2,,,, +AAMV4,15:00:00,15:00:00,AMV,1,,,, +AAMV4,16:00:00,16:00:00,BEATTY_AIRPORT,2,,,, --- /dev/null +++ b/origin-src/transitfeed-1.2.5/test/data/invalid_route_agency/stops.txt @@ -1,1 +1,10 @@ - +stop_id,stop_name,stop_desc,stop_lat,stop_lon,zone_id,stop_url +FUR_CREEK_RES,Furnace Creek Resort (Demo),,36.425288,-117.133162,, +BEATTY_AIRPORT,Nye County Airport (Demo),,36.868446,-116.784582,, +BULLFROG,Bullfrog (Demo),,36.88108,-116.81797,, +STAGECOACH,Stagecoach Hotel & Casino (Demo),,36.915682,-116.751677,, +NADAV,North Ave / D Ave N (Demo),,36.914893,-116.76821,, +NANAA,North Ave / N A Ave (Demo),,36.914944,-116.761472,, +DADAN,Doing Ave / D Ave N (Demo),,36.909489,-116.768242,, +EMSI,E Main St / S Irving St (Demo),,36.905697,-116.76218,, +AMV,Amargosa Valley (Demo),,36.641496,-116.40094,, --- /dev/null +++ b/origin-src/transitfeed-1.2.5/test/data/invalid_route_agency/trips.txt @@ -1,1 +1,12 @@ - +route_id,service_id,trip_id,trip_headsign,direction_id,block_id,shape_id +AB,FULLW,AB1,to Bullfrog,0,1, +AB,FULLW,AB2,to Airport,1,2, +STBA,FULLW,STBA,Shuttle,,, +CITY,FULLW,CITY1,,0,, +CITY,FULLW,CITY2,,1,, +BFC,FULLW,BFC1,to Furnace Creek Resort,0,1, +BFC,FULLW,BFC2,to Bullfrog,1,2, +AAMV,WE,AAMV1,to Amargosa Valley,0,, +AAMV,WE,AAMV2,to Airport,1,, +AAMV,WE,AAMV3,to Amargosa Valley,0,, +AAMV,WE,AAMV4,to Airport,1,, --- /dev/null +++ b/origin-src/transitfeed-1.2.5/test/data/missing_agency/calendar.txt @@ -1,1 +1,3 @@ - +service_id,monday,tuesday,wednesday,thursday,friday,saturday,sunday,start_date,end_date +FULLW,1,1,1,1,1,1,1,20070101,20101231 +WE,0,0,0,0,0,1,1,20070101,20101231 --- /dev/null +++ b/origin-src/transitfeed-1.2.5/test/data/missing_agency/calendar_dates.txt @@ -1,1 +1,2 @@ - +service_id,date,exception_type +FULLW,20070604,2 --- /dev/null +++ b/origin-src/transitfeed-1.2.5/test/data/missing_agency/fare_attributes.txt @@ -1,1 +1,4 @@ +fare_id,price,currency_type,payment_method,transfers,transfer_duration +p,1.25,USD,0,0, +a,5.25,USD,0,0, --- /dev/null +++ b/origin-src/transitfeed-1.2.5/test/data/missing_agency/fare_rules.txt @@ -1,1 +1,6 @@ +fare_id,route_id,origin_id,destination_id,contains_id +p,AB,,, +p,STBA,,, +p,BFC,,, +a,AAMV,,, --- /dev/null +++ b/origin-src/transitfeed-1.2.5/test/data/missing_agency/frequencies.txt @@ -1,1 +1,12 @@ - +trip_id,start_time,end_time,headway_secs +STBA,6:00:00,22:00:00,1800 +CITY1,6:00:00,7:59:59,1800 +CITY2,6:00:00,7:59:59,1800 +CITY1,8:00:00,9:59:59,600 +CITY2,8:00:00,9:59:59,600 +CITY1,10:00:00,15:59:59,1800 +CITY2,10:00:00,15:59:59,1800 +CITY1,16:00:00,18:59:59,600 +CITY2,16:00:00,18:59:59,600 +CITY1,19:00:00,22:00:00,1800 +CITY2,19:00:00,22:00:00,1800 --- /dev/null +++ b/origin-src/transitfeed-1.2.5/test/data/missing_agency/routes.txt @@ -1,1 +1,7 @@ +route_id,agency_id,route_short_name,route_long_name,route_desc,route_type +AB,DTA,,Airport - Bullfrog,,3 +BFC,DTA,,Bullfrog - Furnace Creek Resort,,3 +STBA,DTA,,Stagecoach - Airport Shuttle,,3 +CITY,DTA,,City,,3 +AAMV,DTA,,Airport - Amargosa Valley,,3 --- /dev/null +++ b/origin-src/transitfeed-1.2.5/test/data/missing_agency/stop_times.txt @@ -1,1 +1,30 @@ +trip_id,arrival_time,departure_time,stop_id,stop_sequence +STBA,6:00:00,6:00:00,STAGECOACH,1 +STBA,6:20:00,6:20:00,BEATTY_AIRPORT,2 +CITY1,6:00:00,6:00:00,STAGECOACH,1 +CITY1,6:05:00,6:07:00,NANAA,2 +CITY1,6:12:00,6:14:00,NADAV,3 +CITY1,6:19:00,6:21:00,DADAN,4 +CITY1,6:26:00,6:28:00,EMSI,5 +CITY2,6:28:00,6:30:00,EMSI,1 +CITY2,6:35:00,6:37:00,DADAN,2 +CITY2,6:42:00,6:44:00,NADAV,3 +CITY2,6:49:00,6:51:00,NANAA,4 +CITY2,6:56:00,6:58:00,STAGECOACH,5 +AB1,8:00:00,8:00:00,BEATTY_AIRPORT,1 +AB1,8:10:00,8:15:00,BULLFROG,2 +AB2,12:05:00,12:05:00,BULLFROG,1 +AB2,12:15:00,12:15:00,BEATTY_AIRPORT,2 +BFC1,8:20:00,8:20:00,BULLFROG,1 +BFC1,9:20:00,9:20:00,FUR_CREEK_RES,2 +BFC2,11:00:00,11:00:00,FUR_CREEK_RES,1 +BFC2,12:00:00,12:00:00,BULLFROG,2 +AAMV1,8:00:00,8:00:00,BEATTY_AIRPORT,1 +AAMV1,9:00:00,9:00:00,AMV,2 +AAMV2,10:00:00,10:00:00,AMV,1 +AAMV2,11:00:00,11:00:00,BEATTY_AIRPORT,2 +AAMV3,13:00:00,13:00:00,BEATTY_AIRPORT,1 +AAMV3,14:00:00,14:00:00,AMV,2 +AAMV4,15:00:00,15:00:00,AMV,1 +AAMV4,16:00:00,16:00:00,BEATTY_AIRPORT,2 --- /dev/null +++ b/origin-src/transitfeed-1.2.5/test/data/missing_agency/stops.txt @@ -1,1 +1,11 @@ +stop_id,stop_name,stop_desc,stop_lat,stop_lon +FUR_CREEK_RES,Furnace Creek Resort (Demo),,36.425288,-117.133162 +BEATTY_AIRPORT,Nye County Airport (Demo),,36.868446,-116.784582 +BULLFROG,Bullfrog (Demo),,36.88108,-116.81797 +STAGECOACH,Stagecoach Hotel & Casino (Demo),,36.915682,-116.751677 +NADAV,North Ave / D Ave N (Demo),,36.914893,-116.76821 +NANAA,North Ave / N A Ave (Demo),,36.914944,-116.761472 +DADAN,Doing Ave / D Ave N (Demo),,36.909489,-116.768242 +EMSI,E Main St / S Irving St (Demo),,36.905697,-116.76218 +AMV,Amargosa Valley (Demo),,36.641496,-116.40094 --- /dev/null +++ b/origin-src/transitfeed-1.2.5/test/data/missing_agency/trips.txt @@ -1,1 +1,13 @@ +route_id,service_id,trip_id,trip_headsign,direction_id +AB,FULLW,AB1,to Bullfrog,0 +AB,FULLW,AB2,to Airport,1 +STBA,FULLW,STBA,Shuttle +CITY,FULLW,CITY1,,0 +CITY,FULLW,CITY2,,1 +BFC,FULLW,BFC1,to Furnace Creek Resort,0 +BFC,FULLW,BFC2,to Bullfrog,1 +AAMV,WE,AAMV1,to Amargosa Valley,0 +AAMV,WE,AAMV2,to Airport,1 +AAMV,WE,AAMV3,to Amargosa Valley,0 +AAMV,WE,AAMV4,to Airport,1 --- /dev/null +++ b/origin-src/transitfeed-1.2.5/test/data/missing_calendar/agency.txt @@ -1,1 +1,2 @@ - +agency_id,agency_name,agency_url,agency_timezone +DTA,Demo Transit Authority,http://google.com,America/Los_Angeles --- /dev/null +++ b/origin-src/transitfeed-1.2.5/test/data/missing_calendar/fare_attributes.txt @@ -1,1 +1,4 @@ +fare_id,price,currency_type,payment_method,transfers,transfer_duration +p,1.25,USD,0,0, +a,5.25,USD,0,0, --- /dev/null +++ b/origin-src/transitfeed-1.2.5/test/data/missing_calendar/fare_rules.txt @@ -1,1 +1,6 @@ +fare_id,route_id,origin_id,destination_id,contains_id +p,AB,,, +p,STBA,,, +p,BFC,,, +a,AAMV,,, --- /dev/null +++ b/origin-src/transitfeed-1.2.5/test/data/missing_calendar/frequencies.txt @@ -1,1 +1,12 @@ - +trip_id,start_time,end_time,headway_secs +STBA,6:00:00,22:00:00,1800 +CITY1,6:00:00,7:59:59,1800 +CITY2,6:00:00,7:59:59,1800 +CITY1,8:00:00,9:59:59,600 +CITY2,8:00:00,9:59:59,600 +CITY1,10:00:00,15:59:59,1800 +CITY2,10:00:00,15:59:59,1800 +CITY1,16:00:00,18:59:59,600 +CITY2,16:00:00,18:59:59,600 +CITY1,19:00:00,22:00:00,1800 +CITY2,19:00:00,22:00:00,1800 --- /dev/null +++ b/origin-src/transitfeed-1.2.5/test/data/missing_calendar/routes.txt @@ -1,1 +1,6 @@ - +route_id,agency_id,route_short_name,route_long_name,route_desc,route_type,route_url,route_color,route_text_color +AB,DTA,,Airport - Bullfrog,,3,,, +BFC,DTA,,Bullfrog - Furnace Creek Resort,,3,,, +STBA,DTA,,Stagecoach - Airport Shuttle,,3,,, +CITY,DTA,,City,,3,,, +AAMV,DTA,,Airport - Amargosa Valley,,3,,, --- /dev/null +++ b/origin-src/transitfeed-1.2.5/test/data/missing_calendar/stop_times.txt @@ -1,1 +1,29 @@ - +trip_id,arrival_time,departure_time,stop_id,stop_sequence,stop_headsign,pickup_type,drop_off_type,shape_dist_traveled +STBA,6:00:00,6:00:00,STAGECOACH,1,,,, +STBA,6:20:00,6:20:00,BEATTY_AIRPORT,2,,,, +CITY1,6:00:00,6:00:00,STAGECOACH,1,,,, +CITY1,6:05:00,6:07:00,NANAA,2,,,, +CITY1,6:12:00,6:14:00,NADAV,3,,,, +CITY1,6:19:00,6:21:00,DADAN,4,,,, +CITY1,6:26:00,6:28:00,EMSI,5,,,, +CITY2,6:28:00,6:30:00,EMSI,1,,,, +CITY2,6:35:00,6:37:00,DADAN,2,,,, +CITY2,6:42:00,6:44:00,NADAV,3,,,, +CITY2,6:49:00,6:51:00,NANAA,4,,,, +CITY2,6:56:00,6:58:00,STAGECOACH,5,,,, +AB1,8:00:00,8:00:00,BEATTY_AIRPORT,1,,,, +AB1,8:10:00,8:15:00,BULLFROG,2,,,, +AB2,12:05:00,12:05:00,BULLFROG,1,,,, +AB2,12:15:00,12:15:00,BEATTY_AIRPORT,2,,,, +BFC1,8:20:00,8:20:00,BULLFROG,1,,,, +BFC1,9:20:00,9:20:00,FUR_CREEK_RES,2,,,, +BFC2,11:00:00,11:00:00,FUR_CREEK_RES,1,,,, +BFC2,12:00:00,12:00:00,BULLFROG,2,,,, +AAMV1,8:00:00,8:00:00,BEATTY_AIRPORT,1,,,, +AAMV1,9:00:00,9:00:00,AMV,2,,,, +AAMV2,10:00:00,10:00:00,AMV,1,,,, +AAMV2,11:00:00,11:00:00,BEATTY_AIRPORT,2,,,, +AAMV3,13:00:00,13:00:00,BEATTY_AIRPORT,1,,,, +AAMV3,14:00:00,14:00:00,AMV,2,,,, +AAMV4,15:00:00,15:00:00,AMV,1,,,, +AAMV4,16:00:00,16:00:00,BEATTY_AIRPORT,2,,,, --- /dev/null +++ b/origin-src/transitfeed-1.2.5/test/data/missing_calendar/stops.txt @@ -1,1 +1,10 @@ - +stop_id,stop_name,stop_desc,stop_lat,stop_lon,zone_id,stop_url +FUR_CREEK_RES,Furnace Creek Resort (Demo),,36.425288,-117.133162,, +BEATTY_AIRPORT,Nye County Airport (Demo),,36.868446,-116.784582,, +BULLFROG,Bullfrog (Demo),,36.88108,-116.81797,, +STAGECOACH,Stagecoach Hotel & Casino (Demo),,36.915682,-116.751677,, +NADAV,North Ave / D Ave N (Demo),,36.914893,-116.76821,, +NANAA,North Ave / N A Ave (Demo),,36.914944,-116.761472,, +DADAN,Doing Ave / D Ave N (Demo),,36.909489,-116.768242,, +EMSI,E Main St / S Irving St (Demo),,36.905697,-116.76218,, +AMV,Amargosa Valley (Demo),,36.641496,-116.40094,, --- /dev/null +++ b/origin-src/transitfeed-1.2.5/test/data/missing_calendar/trips.txt @@ -1,1 +1,12 @@ - +route_id,service_id,trip_id,trip_headsign,direction_id,block_id,shape_id +AB,FULLW,AB1,to Bullfrog,0,1, +AB,FULLW,AB2,to Airport,1,2, +STBA,FULLW,STBA,Shuttle,,, +CITY,FULLW,CITY1,,0,, +CITY,FULLW,CITY2,,1,, +BFC,FULLW,BFC1,to Furnace Creek Resort,0,1, +BFC,FULLW,BFC2,to Bullfrog,1,2, +AAMV,WE,AAMV1,to Amargosa Valley,0,, +AAMV,WE,AAMV2,to Airport,1,, +AAMV,WE,AAMV3,to Amargosa Valley,0,, +AAMV,WE,AAMV4,to Airport,1,, --- /dev/null +++ b/origin-src/transitfeed-1.2.5/test/data/missing_column/agency.txt @@ -1,1 +1,3 @@ +agency_id,agency_url,agency_timezone +DTA,http://google.com,America/Los_Angeles --- /dev/null +++ b/origin-src/transitfeed-1.2.5/test/data/missing_column/calendar.txt @@ -1,1 +1,3 @@ - +service_id,monday,tuesday,wednesday,thursday,friday,saturday,sunday,start_date,end_date +FULLW,1,1,1,1,1,1,1,20070101,20101231 +WE,0,0,0,0,0,1,1,20070101,20101231 --- /dev/null +++ b/origin-src/transitfeed-1.2.5/test/data/missing_column/calendar_dates.txt @@ -1,1 +1,2 @@ - +service_id,date,exception_type +FULLW,20070604,2 --- /dev/null +++ b/origin-src/transitfeed-1.2.5/test/data/missing_column/fare_attributes.txt @@ -1,1 +1,4 @@ +fare_id,price,currency_type,payment_method,transfers,transfer_duration +p,1.25,USD,0,0, +a,5.25,USD,0,0, --- /dev/null +++ b/origin-src/transitfeed-1.2.5/test/data/missing_column/fare_rules.txt @@ -1,1 +1,6 @@ +fare_id,route_id,origin_id,destination_id,contains_id +p,AB,,, +p,STBA,,, +p,BFC,,, +a,AAMV,,, --- /dev/null +++ b/origin-src/transitfeed-1.2.5/test/data/missing_column/frequencies.txt @@ -1,1 +1,12 @@ - +trip_id,start_time,end_time,headway_secs +STBA,6:00:00,22:00:00,1800 +CITY1,6:00:00,7:59:59,1800 +CITY2,6:00:00,7:59:59,1800 +CITY1,8:00:00,9:59:59,600 +CITY2,8:00:00,9:59:59,600 +CITY1,10:00:00,15:59:59,1800 +CITY2,10:00:00,15:59:59,1800 +CITY1,16:00:00,18:59:59,600 +CITY2,16:00:00,18:59:59,600 +CITY1,19:00:00,22:00:00,1800 +CITY2,19:00:00,22:00:00,1800 --- /dev/null +++ b/origin-src/transitfeed-1.2.5/test/data/missing_column/routes.txt @@ -1,1 +1,6 @@ - +route_id,agency_id,route_short_name,route_long_name,route_desc,route_type,route_url,route_color,route_text_color +AB,DTA,,Airport - Bullfrog,,3 +BFC,DTA,,Bullfrog - Furnace Creek Resort,,3 +STBA,DTA,,Stagecoach - Airport Shuttle,,3 +CITY,DTA,,City,,3 +AAMV,DTA,,Airport - Amargosa Valley,,3 --- /dev/null +++ b/origin-src/transitfeed-1.2.5/test/data/missing_column/stop_times.txt @@ -1,1 +1,30 @@ +trip_id,arrival_time,departure_time,stop_id,stop_sequence +STBA,6:00:00,6:00:00,STAGECOACH,1 +STBA,6:20:00,6:20:00,BEATTY_AIRPORT,2 +CITY1,6:00:00,6:00:00,STAGECOACH,1 +CITY1,6:05:00,6:07:00,NANAA,2 +CITY1,6:12:00,6:14:00,NADAV,3 +CITY1,6:19:00,6:21:00,DADAN,4 +CITY1,6:26:00,6:28:00,EMSI,5 +CITY2,6:28:00,6:30:00,EMSI,1 +CITY2,6:35:00,6:37:00,DADAN,2 +CITY2,6:42:00,6:44:00,NADAV,3 +CITY2,6:49:00,6:51:00,NANAA,4 +CITY2,6:56:00,6:58:00,STAGECOACH,5 +AB1,8:00:00,8:00:00,BEATTY_AIRPORT,1 +AB1,8:10:00,8:15:00,BULLFROG,2 +AB2,12:05:00,12:05:00,BULLFROG,1 +AB2,12:15:00,12:15:00,BEATTY_AIRPORT,2 +BFC1,8:20:00,8:20:00,BULLFROG,1 +BFC1,9:20:00,9:20:00,FUR_CREEK_RES,2 +BFC2,11:00:00,11:00:00,FUR_CREEK_RES,1 +BFC2,12:00:00,12:00:00,BULLFROG,2 +AAMV1,8:00:00,8:00:00,BEATTY_AIRPORT,1 +AAMV1,9:00:00,9:00:00,AMV,2 +AAMV2,10:00:00,10:00:00,AMV,1 +AAMV2,11:00:00,11:00:00,BEATTY_AIRPORT,2 +AAMV3,13:00:00,13:00:00,BEATTY_AIRPORT,1 +AAMV3,14:00:00,14:00:00,AMV,2 +AAMV4,15:00:00,15:00:00,AMV,1 +AAMV4,16:00:00,16:00:00,BEATTY_AIRPORT,2 --- /dev/null +++ b/origin-src/transitfeed-1.2.5/test/data/missing_column/stops.txt @@ -1,1 +1,10 @@ - +stop_id,stop_name,stop_desc,stop_lat,stop_lon,zone_id,stop_url +FUR_CREEK_RES,Furnace Creek Resort (Demo),,36.425288,-117.133162 +BEATTY_AIRPORT,Nye County Airport (Demo),,36.868446,-116.784582 +BULLFROG,Bullfrog (Demo),,36.88108,-116.81797 +STAGECOACH,Stagecoach Hotel & Casino (Demo),,36.915682,-116.751677 +NADAV,North Ave / D Ave N (Demo),,36.914893,-116.76821 +NANAA,North Ave / N A Ave (Demo),,36.914944,-116.761472 +DADAN,Doing Ave / D Ave N (Demo),,36.909489,-116.768242 +EMSI,E Main St / S Irving St (Demo),,36.905697,-116.76218 +AMV,Amargosa Valley (Demo),,36.641496,-116.40094 --- /dev/null +++ b/origin-src/transitfeed-1.2.5/test/data/missing_column/trips.txt @@ -1,1 +1,12 @@ - +route_id,service_id,trip_id,trip_headsign,direction_id,block_id,shape_id +AB,FULLW,AB1,to Bullfrog,0,1 +AB,FULLW,AB2,to Airport,1,2 +STBA,FULLW,STBA,Shuttle +CITY,FULLW,CITY1,,0 +CITY,FULLW,CITY2,,1 +BFC,FULLW,BFC1,to Furnace Creek Resort,0,1 +BFC,FULLW,BFC2,to Bullfrog,1,2 +AAMV,WE,AAMV1,to Amargosa Valley,0 +AAMV,WE,AAMV2,to Airport,1 +AAMV,WE,AAMV3,to Amargosa Valley,0 +AAMV,WE,AAMV4,to Airport,1 --- /dev/null +++ b/origin-src/transitfeed-1.2.5/test/data/missing_departure_time/agency.txt @@ -1,1 +1,3 @@ +agency_id,agency_name,agency_url,agency_timezone +DTA,Autorité de passage de démonstration,http://google.com,America/Los_Angeles --- /dev/null +++ b/origin-src/transitfeed-1.2.5/test/data/missing_departure_time/calendar.txt @@ -1,1 +1,3 @@ +service_id,monday,tuesday,wednesday,thursday,friday,saturday,sunday,start_date,end_date +FULLW,1,1,1,1,1,1,1,20070101,20101231 --- /dev/null +++ b/origin-src/transitfeed-1.2.5/test/data/missing_departure_time/routes.txt @@ -1,1 +1,3 @@ +route_id,agency_id,route_short_name,route_long_name,route_desc,route_type,route_url,route_color,route_text_color +CITY,DTA,ÅŒ,Bar Circle,Route with ĸool unicode shortname,3,,, --- /dev/null +++ b/origin-src/transitfeed-1.2.5/test/data/missing_departure_time/stop_times.txt @@ -1,1 +1,6 @@ +trip_id,arrival_time,departure_time,stop_id,stop_sequence,stop_headsign,pickup_type,drop_off_type,shape_dist_traveled +CITY1,6:00:00,6:00:00,STAGECOACH,0,,,, +CITY1,6:12:00,,NADAV,10,,,, +CITY1,6:19:00,6:21:00,DADAN,15,,,, +CITY1,6:26:00,6:28:00,EMSI,20,,,, --- /dev/null +++ b/origin-src/transitfeed-1.2.5/test/data/missing_departure_time/stops.txt @@ -1,1 +1,7 @@ +stop_id,stop_name,stop_desc,stop_lat,stop_lon,zone_id,stop_url,stop_code +STAGECOACH,Stagecoach Hotel & Casino (Demo),,36.915682,-116.751677,,,1236 +NADAV,North Ave / D Ave N (Demo),,36.914893,-116.76821,,,1237 +NANAA,North Ave / N A Ave (Demo),,36.914944,-116.761472,,,1238 +DADAN,Doing Ave / D Ave N (Demo),,36.909489,-116.768242,,, +EMSI,E Main St / S Irving St (Demo),,36.905697,-116.76218,,, --- /dev/null +++ b/origin-src/transitfeed-1.2.5/test/data/missing_departure_time/trips.txt @@ -1,1 +1,3 @@ +route_id,service_id,trip_id,trip_headsign,direction_id,block_id,shape_id +CITY,FULLW,CITY1,,0,, --- /dev/null +++ b/origin-src/transitfeed-1.2.5/test/data/missing_endpoint_times/agency.txt @@ -1,1 +1,3 @@ +agency_id,agency_name,agency_url,agency_timezone +DTA,Autorité de passage de démonstration,http://google.com,America/Los_Angeles --- /dev/null +++ b/origin-src/transitfeed-1.2.5/test/data/missing_endpoint_times/calendar.txt @@ -1,1 +1,3 @@ - +service_id,monday,tuesday,wednesday,thursday,friday,saturday,sunday,start_date,end_date +FULLW,1,1,1,1,1,1,1,20070101,20101231 +WE,0,0,0,0,0,1,1,20070101,20101231 --- /dev/null +++ b/origin-src/transitfeed-1.2.5/test/data/missing_endpoint_times/calendar_dates.txt @@ -1,1 +1,2 @@ - +service_id,date,exception_type +FULLW,20070604,2 --- /dev/null +++ b/origin-src/transitfeed-1.2.5/test/data/missing_endpoint_times/fare_attributes.txt @@ -1,1 +1,4 @@ +fare_id,price,currency_type,payment_method,transfers,transfer_duration +p,1.25,USD,0,0, +a,5.25,USD,0,0, --- /dev/null +++ b/origin-src/transitfeed-1.2.5/test/data/missing_endpoint_times/fare_rules.txt @@ -1,1 +1,6 @@ +fare_id,route_id,origin_id,destination_id,contains_id +p,AB,,, +p,STBA,,, +p,BFC,,, +a,AAMV,,, --- /dev/null +++ b/origin-src/transitfeed-1.2.5/test/data/missing_endpoint_times/frequencies.txt @@ -1,1 +1,12 @@ - +trip_id,start_time,end_time,headway_secs +STBA,6:00:00,22:00:00,1800 +CITY1,6:00:00,7:59:59,1800 +CITY2,6:00:00,7:59:59,1800 +CITY1,8:00:00,9:59:59,600 +CITY2,8:00:00,9:59:59,600 +CITY1,10:00:00,15:59:59,1800 +CITY2,10:00:00,15:59:59,1800 +CITY1,16:00:00,18:59:59,600 +CITY2,16:00:00,18:59:59,600 +CITY1,19:00:00,22:00:00,1800 +CITY2,19:00:00,22:00:00,1800 --- /dev/null +++ b/origin-src/transitfeed-1.2.5/test/data/missing_endpoint_times/routes.txt @@ -1,1 +1,7 @@ +route_id,agency_id,route_short_name,route_long_name,route_desc,route_type,route_url,route_color,route_text_color +AB,DTA,,Airport ⇒ Bullfrog,,3,,, +BFC,DTA,,Bullfrog ⇒ Furnace Creek Resort,,3,,, +STBA,DTA,,Stagecoach ⇒ Airport Shuttle,,3,,, +CITY,DTA,ÅŒ,Bar Circle,Route with ĸool unicode short name,3,,, +AAMV,DTA,,Airport ⇒ Amargosa Valley,,3,,, --- /dev/null +++ b/origin-src/transitfeed-1.2.5/test/data/missing_endpoint_times/stop_times.txt @@ -1,1 +1,30 @@ +trip_id,arrival_time,departure_time,stop_id,stop_sequence,stop_headsign,pickup_type,drop_off_type,shape_dist_traveled +STBA,6:00:00,6:00:00,STAGECOACH,1,to airport,1,0,0.212 +STBA,6:20:00,6:20:00,BEATTY_AIRPORT,2,,0,0,1.043 +CITY1,6:00:00,6:00:00,STAGECOACH,1,,,, +CITY1,6:05:00,6:07:00,NANAA,2,going to nadav,2,3, +CITY1,6:12:00,6:14:00,NADAV,3,,,, +CITY1,6:19:00,6:21:00,DADAN,4,,,, +CITY1,6:26:00,6:28:00,EMSI,5,,,, +CITY2,6:28:00,6:30:00,EMSI,1,,,, +CITY2,6:35:00,6:37:00,DADAN,2,,,, +CITY2,6:42:00,6:44:00,NADAV,3,,,, +CITY2,6:49:00,6:51:00,NANAA,4,,,, +CITY2,6:56:00,6:58:00,STAGECOACH,5,,,, +AB1,8:00:00,8:00:00,BEATTY_AIRPORT,1,,,, +AB1,8:10:00,8:15:00,BULLFROG,2,,,, +AB2,,,BULLFROG,1,,,, +AB2,12:15:00,12:15:00,BEATTY_AIRPORT,2,,,, +BFC1,8:20:00,8:20:00,BULLFROG,1,,,, +BFC1,9:20:00,9:20:00,FUR_CREEK_RES,2,,,, +BFC2,11:00:00,11:00:00,FUR_CREEK_RES,1,,,, +BFC2,,,BULLFROG,2,,,, +AAMV1,8:00:00,8:00:00,BEATTY_AIRPORT,1,,,, +AAMV1,9:00:00,9:00:00,AMV,2,,,, +AAMV2,10:00:00,10:00:00,AMV,1,,,, +AAMV2,11:00:00,11:00:00,BEATTY_AIRPORT,2,,,, +AAMV3,13:00:00,13:00:00,BEATTY_AIRPORT,1,,,, +AAMV3,14:00:00,14:00:00,AMV,2,,,, +AAMV4,15:00:00,15:00:00,AMV,1,,,, +AAMV4,16:00:00,16:00:00,BEATTY_AIRPORT,2,,,, --- /dev/null +++ b/origin-src/transitfeed-1.2.5/test/data/missing_endpoint_times/stops.txt @@ -1,1 +1,11 @@ +stop_id,stop_name,stop_desc,stop_lat,stop_lon,zone_id,stop_url +FUR_CREEK_RES,Furnace Creek Resort (Démonstration),,36.425288,-117.133162,, +BEATTY_AIRPORT,Nye County Airport (Demo),,36.868446,-116.784582,, +BULLFROG,Bullfrog (Demo),,36.88108,-116.81797,, +STAGECOACH,Stagecoach Hotel & Casino (Demo),,36.915682,-116.751677,, +NADAV,North Ave / D Ave N (Demo),,36.914893,-116.76821,, +NANAA,North Ave / N A Ave (Demo),,36.914944,-116.761472,, +DADAN,Doing Ave / D Ave N (Demo),,36.909489,-116.768242,, +EMSI,E Main St / S Irving St (Demo),,36.905697,-116.76218,, +AMV,Amargosa Valley (Demo),,36.641496,-116.40094,, --- /dev/null +++ b/origin-src/transitfeed-1.2.5/test/data/missing_endpoint_times/trips.txt @@ -1,1 +1,13 @@ +route_id,service_id,trip_id,trip_headsign,direction_id,block_id,shape_id +AB,FULLW,AB1,to Bullfrog,0,1, +AB,FULLW,AB2,to Airport,1,2, +STBA,FULLW,STBA,Shuttle,,, +CITY,FULLW,CITY1,ÅŒ,0,, +CITY,FULLW,CITY2,ÅŒ,1,, +BFC,FULLW,BFC1,to Furnace Creek Resort,0,1, +BFC,FULLW,BFC2,to Bullfrog,1,2, +AAMV,WE,AAMV1,to Amargosa Valley,0,, +AAMV,WE,AAMV2,to Airport,1,, +AAMV,WE,AAMV3,to Amargosa Valley,0,, +AAMV,WE,AAMV4,to Airport,1,, --- /dev/null +++ b/origin-src/transitfeed-1.2.5/test/data/missing_routes/agency.txt @@ -1,1 +1,2 @@ - +agency_id,agency_name,agency_url,agency_timezone +DTA,Demo Transit Authority,http://google.com,America/Los_Angeles --- /dev/null +++ b/origin-src/transitfeed-1.2.5/test/data/missing_routes/calendar.txt @@ -1,1 +1,3 @@ - +service_id,monday,tuesday,wednesday,thursday,friday,saturday,sunday,start_date,end_date +FULLW,1,1,1,1,1,1,1,20070101,20101231 +WE,0,0,0,0,0,1,1,20070101,20101231 --- /dev/null +++ b/origin-src/transitfeed-1.2.5/test/data/missing_routes/calendar_dates.txt @@ -1,1 +1,2 @@ - +service_id,date,exception_type +FULLW,20070604,2 --- /dev/null +++ b/origin-src/transitfeed-1.2.5/test/data/missing_routes/fare_attributes.txt @@ -1,1 +1,4 @@ +fare_id,price,currency_type,payment_method,transfers,transfer_duration +p,1.25,USD,0,0, +a,5.25,USD,0,0, --- /dev/null +++ b/origin-src/transitfeed-1.2.5/test/data/missing_routes/fare_rules.txt @@ -1,1 +1,6 @@ +fare_id,route_id,origin_id,destination_id,contains_id +p,AB,,, +p,STBA,,, +p,BFC,,, +a,AAMV,,, --- /dev/null +++ b/origin-src/transitfeed-1.2.5/test/data/missing_routes/frequencies.txt @@ -1,1 +1,12 @@ - +trip_id,start_time,end_time,headway_secs +STBA,6:00:00,22:00:00,1800 +CITY1,6:00:00,7:59:59,1800 +CITY2,6:00:00,7:59:59,1800 +CITY1,8:00:00,9:59:59,600 +CITY2,8:00:00,9:59:59,600 +CITY1,10:00:00,15:59:59,1800 +CITY2,10:00:00,15:59:59,1800 +CITY1,16:00:00,18:59:59,600 +CITY2,16:00:00,18:59:59,600 +CITY1,19:00:00,22:00:00,1800 +CITY2,19:00:00,22:00:00,1800 --- /dev/null +++ b/origin-src/transitfeed-1.2.5/test/data/missing_routes/stop_times.txt @@ -1,1 +1,29 @@ - +trip_id,arrival_time,departure_time,stop_id,stop_sequence,stop_headsign,pickup_type,drop_off_type,shape_dist_traveled +STBA,6:00:00,6:00:00,STAGECOACH,1,,,, +STBA,6:20:00,6:20:00,BEATTY_AIRPORT,2,,,, +CITY1,6:00:00,6:00:00,STAGECOACH,1,,,, +CITY1,6:05:00,6:07:00,NANAA,2,,,, +CITY1,6:12:00,6:14:00,NADAV,3,,,, +CITY1,6:19:00,6:21:00,DADAN,4,,,, +CITY1,6:26:00,6:28:00,EMSI,5,,,, +CITY2,6:28:00,6:30:00,EMSI,1,,,, +CITY2,6:35:00,6:37:00,DADAN,2,,,, +CITY2,6:42:00,6:44:00,NADAV,3,,,, +CITY2,6:49:00,6:51:00,NANAA,4,,,, +CITY2,6:56:00,6:58:00,STAGECOACH,5,,,, +AB1,8:00:00,8:00:00,BEATTY_AIRPORT,1,,,, +AB1,8:10:00,8:15:00,BULLFROG,2,,,, +AB2,12:05:00,12:05:00,BULLFROG,1,,,, +AB2,12:15:00,12:15:00,BEATTY_AIRPORT,2,,,, +BFC1,8:20:00,8:20:00,BULLFROG,1,,,, +BFC1,9:20:00,9:20:00,FUR_CREEK_RES,2,,,, +BFC2,11:00:00,11:00:00,FUR_CREEK_RES,1,,,, +BFC2,12:00:00,12:00:00,BULLFROG,2,,,, +AAMV1,8:00:00,8:00:00,BEATTY_AIRPORT,1,,,, +AAMV1,9:00:00,9:00:00,AMV,2,,,, +AAMV2,10:00:00,10:00:00,AMV,1,,,, +AAMV2,11:00:00,11:00:00,BEATTY_AIRPORT,2,,,, +AAMV3,13:00:00,13:00:00,BEATTY_AIRPORT,1,,,, +AAMV3,14:00:00,14:00:00,AMV,2,,,, +AAMV4,15:00:00,15:00:00,AMV,1,,,, +AAMV4,16:00:00,16:00:00,BEATTY_AIRPORT,2,,,, --- /dev/null +++ b/origin-src/transitfeed-1.2.5/test/data/missing_routes/stops.txt @@ -1,1 +1,10 @@ - +stop_id,stop_name,stop_desc,stop_lat,stop_lon,zone_id,stop_url +FUR_CREEK_RES,Furnace Creek Resort (Demo),,36.425288,-117.133162,, +BEATTY_AIRPORT,Nye County Airport (Demo),,36.868446,-116.784582,, +BULLFROG,Bullfrog (Demo),,36.88108,-116.81797,, +STAGECOACH,Stagecoach Hotel & Casino (Demo),,36.915682,-116.751677,, +NADAV,North Ave / D Ave N (Demo),,36.914893,-116.76821,, +NANAA,North Ave / N A Ave (Demo),,36.914944,-116.761472,, +DADAN,Doing Ave / D Ave N (Demo),,36.909489,-116.768242,, +EMSI,E Main St / S Irving St (Demo),,36.905697,-116.76218,, +AMV,Amargosa Valley (Demo),,36.641496,-116.40094,, --- /dev/null +++ b/origin-src/transitfeed-1.2.5/test/data/missing_routes/trips.txt @@ -1,1 +1,12 @@ - +route_id,service_id,trip_id,trip_headsign,direction_id,block_id,shape_id +AB,FULLW,AB1,to Bullfrog,0,1, +AB,FULLW,AB2,to Airport,1,2, +STBA,FULLW,STBA,Shuttle,,, +CITY,FULLW,CITY1,,0,, +CITY,FULLW,CITY2,,1,, +BFC,FULLW,BFC1,to Furnace Creek Resort,0,1, +BFC,FULLW,BFC2,to Bullfrog,1,2, +AAMV,WE,AAMV1,to Amargosa Valley,0,, +AAMV,WE,AAMV2,to Airport,1,, +AAMV,WE,AAMV3,to Amargosa Valley,0,, +AAMV,WE,AAMV4,to Airport,1,, --- /dev/null +++ b/origin-src/transitfeed-1.2.5/test/data/missing_row_cells/agency.txt @@ -1,1 +1,3 @@ +agency_id,agency_name,agency_url,agency_timezone +DTA,Autorité de passage de démonstration,http://google.com,America/Los_Angeles --- /dev/null +++ b/origin-src/transitfeed-1.2.5/test/data/missing_row_cells/calendar.txt @@ -1,1 +1,3 @@ - +service_id,monday,tuesday,wednesday,thursday,friday,saturday,sunday,start_date,end_date +FULLW,1,1,1,1,1,1,1,20070101,20101231 +WE,0,0,0,0,0,1,1,20070101,20101231 --- /dev/null +++ b/origin-src/transitfeed-1.2.5/test/data/missing_row_cells/calendar_dates.txt @@ -1,1 +1,2 @@ - +service_id,date,exception_type +FULLW,20070604,2 --- /dev/null +++ b/origin-src/transitfeed-1.2.5/test/data/missing_row_cells/fare_attributes.txt @@ -1,1 +1,4 @@ +fare_id,price,currency_type,payment_method,transfers,transfer_duration +p,1.25,USD,0,0, +a,5.25,USD,0,0, --- /dev/null +++ b/origin-src/transitfeed-1.2.5/test/data/missing_row_cells/fare_rules.txt @@ -1,1 +1,6 @@ +fare_id,route_id,origin_id,destination_id,contains_id +p,AB,,, +p,STBA,,, +p,BFC,,, +a,AAMV,,, --- /dev/null +++ b/origin-src/transitfeed-1.2.5/test/data/missing_row_cells/frequencies.txt @@ -1,1 +1,12 @@ - +trip_id,start_time,end_time,headway_secs +STBA,6:00:00,22:00:00,1800 +CITY1,6:00:00,7:59:59,1800 +CITY2,6:00:00,7:59:59,1800 +CITY1,8:00:00,9:59:59,600 +CITY2,8:00:00,9:59:59,600 +CITY1,10:00:00,15:59:59,1800 +CITY2,10:00:00,15:59:59,1800 +CITY1,16:00:00,18:59:59,600 +CITY2,16:00:00,18:59:59,600 +CITY1,19:00:00,22:00:00,1800 +CITY2,19:00:00,22:00:00,1800 --- /dev/null +++ b/origin-src/transitfeed-1.2.5/test/data/missing_row_cells/routes.txt @@ -1,1 +1,7 @@ +route_id,agency_id,route_short_name,route_long_name,route_desc,route_type,route_url +AB,DTA,,Airport ⇒ Bullfrog,,3, +BFC,DTA,,Bullfrog ⇒ Furnace Creek Resort,,3,http://google.com +STBA,DTA,,Stagecoach ⇒ Airport Shuttle,,3 +CITY,DTA,ÅŒ,Bar Circle,Route with ĸool unicode shortname,3, +AAMV,DTA,,Airport ⇒ Amargosa Valley,,3, --- /dev/null +++ b/origin-src/transitfeed-1.2.5/test/data/missing_row_cells/stop_times.txt @@ -1,1 +1,30 @@ +trip_id,arrival_time,departure_time,stop_id,stop_sequence,stop_headsign,pickup_type,drop_off_type,shape_dist_traveled +STBA,6:00:00,6:00:00,STAGECOACH,0,to airport,1,0,0.212 +STBA,6:20:00,6:20:00,BEATTY_AIRPORT,2,,0,0,1.043 +CITY1,6:00:00,6:00:00,STAGECOACH,0,,,, +CITY1,6:05:00,6:07:00,NANAA,5,going to nadav,2,3, +CITY1,6:12:00,6:14:00,NADAV,10,,,, +CITY1,6:19:00,6:21:00,DADAN,15,,,, +CITY1,6:26:00,6:28:00,EMSI,20,,,, +CITY2,6:28:00,6:30:00,EMSI,100,,,, +CITY2,6:35:00,6:37:00,DADAN,200,,,, +CITY2,6:42:00,6:44:00,NADAV,300,,,, +CITY2,6:49:00,6:51:00,NANAA,400,,,, +CITY2,6:56:00,6:58:00,STAGECOACH,500,,,, +AB1,8:00:00,8:00:00,BEATTY_AIRPORT,1,,,, +AB1,8:10:00,8:15:00,BULLFROG,2,,,, +AB2,12:05:00,12:05:00,BULLFROG,1,,,, +AB2,12:15:00,12:15:00,BEATTY_AIRPORT,2,,,, +BFC1,8:20:00,8:20:00,BULLFROG,1,,,, +BFC1,9:20:00,9:20:00,FUR_CREEK_RES,2,,,, +BFC2,11:00:00,11:00:00,FUR_CREEK_RES,1,,,, +BFC2,12:00:00,12:00:00,BULLFROG,2,,,, +AAMV1,8:00:00,8:00:00,BEATTY_AIRPORT,1,,,, +AAMV1,9:00:00,9:00:00,AMV,2,,,, +AAMV2,10:00:00,10:00:00,AMV,1,,,, +AAMV2,11:00:00,11:00:00,BEATTY_AIRPORT,2,,,, +AAMV3,13:00:00,13:00:00,BEATTY_AIRPORT,1,,,, +AAMV3,14:00:00,14:00:00,AMV,2,,,, +AAMV4,15:00:00,15:00:00,AMV,1,,,, +AAMV4,16:00:00,16:00:00,BEATTY_AIRPORT,2,,,, --- /dev/null +++ b/origin-src/transitfeed-1.2.5/test/data/missing_row_cells/stops.txt @@ -1,1 +1,11 @@ +stop_id,stop_name,stop_desc,stop_lat,stop_lon +FUR_CREEK_RES,Furnace Creek Resort (Démonstration),,36.425288,-117.13316 +BEATTY_AIRPORT,Nye County Airport (Demo),,36.868446,-116.784582 +BULLFROG,Bullfrog (Demo),,36.88108,-116.81797 +STAGECOACH,Stagecoach Hotel & Casino (Demo),,36.915682,-116.751677 +NADAV,North Ave / D Ave N (Demo),,36.914893,-116.76821 +NANAA,North Ave / N A Ave (Demo),,36.914944,-116.761472 +DADAN,Doing Ave / D Ave N (Demo),,36.909489,-116.768242 +EMSI,E Main St / S Irving St (Demo),,36.905697,-116.76218 +AMV,Amargosa Valley (Demo),,36.641496,-116.40094 --- /dev/null +++ b/origin-src/transitfeed-1.2.5/test/data/missing_row_cells/trips.txt @@ -1,1 +1,13 @@ +route_id,service_id,trip_id,trip_headsign,direction_id,block_id,shape_id +AB,FULLW,AB1,to Bullfrog,0,1, +AB,FULLW,AB2,to Airport,1,2, +STBA,FULLW,STBA,Shuttle,,, +CITY,FULLW,CITY1,ÅŒ,,, +CITY,FULLW,CITY2,ÅŒ,,, +BFC,FULLW,BFC1,to Furnace Creek Resort,0,1, +BFC,FULLW,BFC2,to Bullfrog,1,2, +AAMV,WE,AAMV1,to Amargosa Valley,0,, +AAMV,WE,AAMV2,to Airport,1,, +AAMV,WE,AAMV3,to Amargosa Valley,0,, +AAMV,WE,AAMV4,to Airport,1,, --- /dev/null +++ b/origin-src/transitfeed-1.2.5/test/data/missing_stop_times/agency.txt @@ -1,1 +1,2 @@ - +agency_id,agency_name,agency_url,agency_timezone +DTA,Demo Transit Authority,http://google.com,America/Los_Angeles --- /dev/null +++ b/origin-src/transitfeed-1.2.5/test/data/missing_stop_times/calendar.txt @@ -1,1 +1,3 @@ - +service_id,monday,tuesday,wednesday,thursday,friday,saturday,sunday,start_date,end_date +FULLW,1,1,1,1,1,1,1,20070101,20101231 +WE,0,0,0,0,0,1,1,20070101,20101231 --- /dev/null +++ b/origin-src/transitfeed-1.2.5/test/data/missing_stop_times/calendar_dates.txt @@ -1,1 +1,2 @@ - +service_id,date,exception_type +FULLW,20070604,2 --- /dev/null +++ b/origin-src/transitfeed-1.2.5/test/data/missing_stop_times/fare_attributes.txt @@ -1,1 +1,4 @@ +fare_id,price,currency_type,payment_method,transfers,transfer_duration +p,1.25,USD,0,0, +a,5.25,USD,0,0, --- /dev/null +++ b/origin-src/transitfeed-1.2.5/test/data/missing_stop_times/fare_rules.txt @@ -1,1 +1,6 @@ +fare_id,route_id,origin_id,destination_id,contains_id +p,AB,,, +p,STBA,,, +p,BFC,,, +a,AAMV,,, --- /dev/null +++ b/origin-src/transitfeed-1.2.5/test/data/missing_stop_times/frequencies.txt @@ -1,1 +1,12 @@ - +trip_id,start_time,end_time,headway_secs +STBA,6:00:00,22:00:00,1800 +CITY1,6:00:00,7:59:59,1800 +CITY2,6:00:00,7:59:59,1800 +CITY1,8:00:00,9:59:59,600 +CITY2,8:00:00,9:59:59,600 +CITY1,10:00:00,15:59:59,1800 +CITY2,10:00:00,15:59:59,1800 +CITY1,16:00:00,18:59:59,600 +CITY2,16:00:00,18:59:59,600 +CITY1,19:00:00,22:00:00,1800 +CITY2,19:00:00,22:00:00,1800 --- /dev/null +++ b/origin-src/transitfeed-1.2.5/test/data/missing_stop_times/routes.txt @@ -1,1 +1,6 @@ - +route_id,agency_id,route_short_name,route_long_name,route_desc,route_type,route_url,route_color,route_text_color +AB,DTA,,Airport - Bullfrog,,3,,, +BFC,DTA,,Bullfrog - Furnace Creek Resort,,3,,, +STBA,DTA,,Stagecoach - Airport Shuttle,,3,,, +CITY,DTA,,City,,3,,, +AAMV,DTA,,Airport - Amargosa Valley,,3,,, --- /dev/null +++ b/origin-src/transitfeed-1.2.5/test/data/missing_stop_times/stops.txt @@ -1,1 +1,10 @@ - +stop_id,stop_name,stop_desc,stop_lat,stop_lon,zone_id,stop_url +FUR_CREEK_RES,Furnace Creek Resort (Demo),,36.425288,-117.133162,, +BEATTY_AIRPORT,Nye County Airport (Demo),,36.868446,-116.784582,, +BULLFROG,Bullfrog (Demo),,36.88108,-116.81797,, +STAGECOACH,Stagecoach Hotel & Casino (Demo),,36.915682,-116.751677,, +NADAV,North Ave / D Ave N (Demo),,36.914893,-116.76821,, +NANAA,North Ave / N A Ave (Demo),,36.914944,-116.761472,, +DADAN,Doing Ave / D Ave N (Demo),,36.909489,-116.768242,, +EMSI,E Main St / S Irving St (Demo),,36.905697,-116.76218,, +AMV,Amargosa Valley (Demo),,36.641496,-116.40094,, --- /dev/null +++ b/origin-src/transitfeed-1.2.5/test/data/missing_stop_times/trips.txt @@ -1,1 +1,12 @@ - +route_id,service_id,trip_id,trip_headsign,direction_id,block_id,shape_id +AB,FULLW,AB1,to Bullfrog,0,1, +AB,FULLW,AB2,to Airport,1,2, +STBA,FULLW,STBA,Shuttle,,, +CITY,FULLW,CITY1,,0,, +CITY,FULLW,CITY2,,1,, +BFC,FULLW,BFC1,to Furnace Creek Resort,0,1, +BFC,FULLW,BFC2,to Bullfrog,1,2, +AAMV,WE,AAMV1,to Amargosa Valley,0,, +AAMV,WE,AAMV2,to Airport,1,, +AAMV,WE,AAMV3,to Amargosa Valley,0,, +AAMV,WE,AAMV4,to Airport,1,, --- /dev/null +++ b/origin-src/transitfeed-1.2.5/test/data/missing_stops/agency.txt @@ -1,1 +1,2 @@ - +agency_id,agency_name,agency_url,agency_timezone +DTA,Demo Transit Authority,http://google.com,America/Los_Angeles --- /dev/null +++ b/origin-src/transitfeed-1.2.5/test/data/missing_stops/calendar.txt @@ -1,1 +1,3 @@ - +service_id,monday,tuesday,wednesday,thursday,friday,saturday,sunday,start_date,end_date +FULLW,1,1,1,1,1,1,1,20070101,20101231 +WE,0,0,0,0,0,1,1,20070101,20101231 --- /dev/null +++ b/origin-src/transitfeed-1.2.5/test/data/missing_stops/calendar_dates.txt @@ -1,1 +1,2 @@ - +service_id,date,exception_type +FULLW,20070604,2 --- /dev/null +++ b/origin-src/transitfeed-1.2.5/test/data/missing_stops/fare_attributes.txt @@ -1,1 +1,4 @@ +fare_id,price,currency_type,payment_method,transfers,transfer_duration +p,1.25,USD,0,0, +a,5.25,USD,0,0, --- /dev/null +++ b/origin-src/transitfeed-1.2.5/test/data/missing_stops/fare_rules.txt @@ -1,1 +1,6 @@ +fare_id,route_id,origin_id,destination_id,contains_id +p,AB,,, +p,STBA,,, +p,BFC,,, +a,AAMV,,, --- /dev/null +++ b/origin-src/transitfeed-1.2.5/test/data/missing_stops/frequencies.txt @@ -1,1 +1,12 @@ - +trip_id,start_time,end_time,headway_secs +STBA,6:00:00,22:00:00,1800 +CITY1,6:00:00,7:59:59,1800 +CITY2,6:00:00,7:59:59,1800 +CITY1,8:00:00,9:59:59,600 +CITY2,8:00:00,9:59:59,600 +CITY1,10:00:00,15:59:59,1800 +CITY2,10:00:00,15:59:59,1800 +CITY1,16:00:00,18:59:59,600 +CITY2,16:00:00,18:59:59,600 +CITY1,19:00:00,22:00:00,1800 +CITY2,19:00:00,22:00:00,1800 --- /dev/null +++ b/origin-src/transitfeed-1.2.5/test/data/missing_stops/routes.txt @@ -1,1 +1,6 @@ - +route_id,agency_id,route_short_name,route_long_name,route_desc,route_type,route_url,route_color,route_text_color +AB,DTA,,Airport - Bullfrog,,3 +BFC,DTA,,Bullfrog - Furnace Creek Resort,,3 +STBA,DTA,,Stagecoach - Airport Shuttle,,3 +CITY,DTA,,City,,3 +AAMV,DTA,,Airport - Amargosa Valley,,3 --- /dev/null +++ b/origin-src/transitfeed-1.2.5/test/data/missing_stops/stop_times.txt @@ -1,1 +1,29 @@ - +trip_id,arrival_time,departure_time,stop_id,stop_sequence,stop_headsign,pickup_type,drop_off_type,shape_dist_traveled +STBA,6:00:00,6:00:00,STAGECOACH,1,,,, +STBA,6:20:00,6:20:00,BEATTY_AIRPORT,2,,,, +CITY1,6:00:00,6:00:00,STAGECOACH,1,,,, +CITY1,6:05:00,6:07:00,NANAA,2,,,, +CITY1,6:12:00,6:14:00,NADAV,3,,,, +CITY1,6:19:00,6:21:00,DADAN,4,,,, +CITY1,6:26:00,6:28:00,EMSI,5,,,, +CITY2,6:28:00,6:30:00,EMSI,1,,,, +CITY2,6:35:00,6:37:00,DADAN,2,,,, +CITY2,6:42:00,6:44:00,NADAV,3,,,, +CITY2,6:49:00,6:51:00,NANAA,4,,,, +CITY2,6:56:00,6:58:00,STAGECOACH,5,,,, +AB1,8:00:00,8:00:00,BEATTY_AIRPORT,1,,,, +AB1,8:10:00,8:15:00,BULLFROG,2,,,, +AB2,12:05:00,12:05:00,BULLFROG,1,,,, +AB2,12:15:00,12:15:00,BEATTY_AIRPORT,2,,,, +BFC1,8:20:00,8:20:00,BULLFROG,1,,,, +BFC1,9:20:00,9:20:00,FUR_CREEK_RES,2,,,, +BFC2,11:00:00,11:00:00,FUR_CREEK_RES,1,,,, +BFC2,12:00:00,12:00:00,BULLFROG,2,,,, +AAMV1,8:00:00,8:00:00,BEATTY_AIRPORT,1,,,, +AAMV1,9:00:00,9:00:00,AMV,2,,,, +AAMV2,10:00:00,10:00:00,AMV,1,,,, +AAMV2,11:00:00,11:00:00,BEATTY_AIRPORT,2,,,, +AAMV3,13:00:00,13:00:00,BEATTY_AIRPORT,1,,,, +AAMV3,14:00:00,14:00:00,AMV,2,,,, +AAMV4,15:00:00,15:00:00,AMV,1,,,, +AAMV4,16:00:00,16:00:00,BEATTY_AIRPORT,2,,,, --- /dev/null +++ b/origin-src/transitfeed-1.2.5/test/data/missing_stops/trips.txt @@ -1,1 +1,12 @@ - +route_id,service_id,trip_id,trip_headsign,direction_id,block_id,shape_id +AB,FULLW,AB1,to Bullfrog,0,1, +AB,FULLW,AB2,to Airport,1,2, +STBA,FULLW,STBA,Shuttle,,, +CITY,FULLW,CITY1,,0,, +CITY,FULLW,CITY2,,1,, +BFC,FULLW,BFC1,to Furnace Creek Resort,0,1, +BFC,FULLW,BFC2,to Bullfrog,1,2, +AAMV,WE,AAMV1,to Amargosa Valley,0,, +AAMV,WE,AAMV2,to Airport,1,, +AAMV,WE,AAMV3,to Amargosa Valley,0,, +AAMV,WE,AAMV4,to Airport,1,, --- /dev/null +++ b/origin-src/transitfeed-1.2.5/test/data/missing_trips/agency.txt @@ -1,1 +1,2 @@ - +agency_id,agency_name,agency_url,agency_timezone +DTA,Demo Transit Authority,http://google.com,America/Los_Angeles --- /dev/null +++ b/origin-src/transitfeed-1.2.5/test/data/missing_trips/calendar.txt @@ -1,1 +1,3 @@ - +service_id,monday,tuesday,wednesday,thursday,friday,saturday,sunday,start_date,end_date +FULLW,1,1,1,1,1,1,1,20070101,20101231 +WE,0,0,0,0,0,1,1,20070101,20101231 --- /dev/null +++ b/origin-src/transitfeed-1.2.5/test/data/missing_trips/calendar_dates.txt @@ -1,1 +1,2 @@ - +service_id,date,exception_type +FULLW,20070604,2 --- /dev/null +++ b/origin-src/transitfeed-1.2.5/test/data/missing_trips/routes.txt @@ -1,1 +1,6 @@ - +route_id,agency_id,route_short_name,route_long_name,route_desc,route_type,route_url,route_color,route_text_color +AB,DTA,,Airport - Bullfrog,,3,,, +BFC,DTA,,Bullfrog - Furnace Creek Resort,,3,,, +STBA,DTA,,Stagecoach - Airport Shuttle,,3,,, +CITY,DTA,,City,,3,,, +AAMV,DTA,,Airport - Amargosa Valley,,3,,, --- /dev/null +++ b/origin-src/transitfeed-1.2.5/test/data/missing_trips/stop_times.txt @@ -1,1 +1,29 @@ - +trip_id,arrival_time,departure_time,stop_id,stop_sequence,stop_headsign,pickup_type,drop_off_type,shape_dist_traveled +STBA,6:00:00,6:00:00,STAGECOACH,1,,,, +STBA,6:20:00,6:20:00,BEATTY_AIRPORT,2,,,, +CITY1,6:00:00,6:00:00,STAGECOACH,1,,,, +CITY1,6:05:00,6:07:00,NANAA,2,,,, +CITY1,6:12:00,6:14:00,NADAV,3,,,, +CITY1,6:19:00,6:21:00,DADAN,4,,,, +CITY1,6:26:00,6:28:00,EMSI,5,,,, +CITY2,6:28:00,6:30:00,EMSI,1,,,, +CITY2,6:35:00,6:37:00,DADAN,2,,,, +CITY2,6:42:00,6:44:00,NADAV,3,,,, +CITY2,6:49:00,6:51:00,NANAA,4,,,, +CITY2,6:56:00,6:58:00,STAGECOACH,5,,,, +AB1,8:00:00,8:00:00,BEATTY_AIRPORT,1,,,, +AB1,8:10:00,8:15:00,BULLFROG,2,,,, +AB2,12:05:00,12:05:00,BULLFROG,1,,,, +AB2,12:15:00,12:15:00,BEATTY_AIRPORT,2,,,, +BFC1,8:20:00,8:20:00,BULLFROG,1,,,, +BFC1,9:20:00,9:20:00,FUR_CREEK_RES,2,,,, +BFC2,11:00:00,11:00:00,FUR_CREEK_RES,1,,,, +BFC2,12:00:00,12:00:00,BULLFROG,2,,,, +AAMV1,8:00:00,8:00:00,BEATTY_AIRPORT,1,,,, +AAMV1,9:00:00,9:00:00,AMV,2,,,, +AAMV2,10:00:00,10:00:00,AMV,1,,,, +AAMV2,11:00:00,11:00:00,BEATTY_AIRPORT,2,,,, +AAMV3,13:00:00,13:00:00,BEATTY_AIRPORT,1,,,, +AAMV3,14:00:00,14:00:00,AMV,2,,,, +AAMV4,15:00:00,15:00:00,AMV,1,,,, +AAMV4,16:00:00,16:00:00,BEATTY_AIRPORT,2,,,, --- /dev/null +++ b/origin-src/transitfeed-1.2.5/test/data/missing_trips/stops.txt @@ -1,1 +1,10 @@ - +stop_id,stop_name,stop_desc,stop_lat,stop_lon,zone_id,stop_url +FUR_CREEK_RES,Furnace Creek Resort (Demo),,36.425288,-117.133162,, +BEATTY_AIRPORT,Nye County Airport (Demo),,36.868446,-116.784582,, +BULLFROG,Bullfrog (Demo),,36.88108,-116.81797,, +STAGECOACH,Stagecoach Hotel & Casino (Demo),,36.915682,-116.751677,, +NADAV,North Ave / D Ave N (Demo),,36.914893,-116.76821,, +NANAA,North Ave / N A Ave (Demo),,36.914944,-116.761472,, +DADAN,Doing Ave / D Ave N (Demo),,36.909489,-116.768242,, +EMSI,E Main St / S Irving St (Demo),,36.905697,-116.76218,, +AMV,Amargosa Valley (Demo),,36.641496,-116.40094,, --- /dev/null +++ b/origin-src/transitfeed-1.2.5/test/data/missing_weekday_column/agency.txt @@ -1,1 +1,2 @@ - +agency_id,agency_name,agency_url,agency_timezone +DTA,Demo Transit Authority,http://google.com,America/Los_Angeles --- /dev/null +++ b/origin-src/transitfeed-1.2.5/test/data/missing_weekday_column/calendar.txt @@ -1,1 +1,4 @@ +"service_id","monday","tuesday","wednesday","friday","saturday","sunday","start_date","end_date" +"FULLW",1,1,1,1,1,1,20070101,20101231 +"WE",0,0,0,0,1,1,20070101,20101231 --- /dev/null +++ b/origin-src/transitfeed-1.2.5/test/data/missing_weekday_column/calendar_dates.txt @@ -1,1 +1,2 @@ - +service_id,date,exception_type +FULLW,20070604,2 --- /dev/null +++ b/origin-src/transitfeed-1.2.5/test/data/missing_weekday_column/fare_attributes.txt @@ -1,1 +1,4 @@ +fare_id,price,currency_type,payment_method,transfers,transfer_duration +p,1.25,USD,0,0, +a,5.25,USD,0,0, --- /dev/null +++ b/origin-src/transitfeed-1.2.5/test/data/missing_weekday_column/fare_rules.txt @@ -1,1 +1,6 @@ +fare_id,route_id,origin_id,destination_id,contains_id +p,AB,,, +p,STBA,,, +p,BFC,,, +a,AAMV,,, --- /dev/null +++ b/origin-src/transitfeed-1.2.5/test/data/missing_weekday_column/frequencies.txt @@ -1,1 +1,12 @@ - +trip_id,start_time,end_time,headway_secs +STBA,6:00:00,22:00:00,1800 +CITY1,6:00:00,7:59:59,1800 +CITY2,6:00:00,7:59:59,1800 +CITY1,8:00:00,9:59:59,600 +CITY2,8:00:00,9:59:59,600 +CITY1,10:00:00,15:59:59,1800 +CITY2,10:00:00,15:59:59,1800 +CITY1,16:00:00,18:59:59,600 +CITY2,16:00:00,18:59:59,600 +CITY1,19:00:00,22:00:00,1800 +CITY2,19:00:00,22:00:00,1800 --- /dev/null +++ b/origin-src/transitfeed-1.2.5/test/data/missing_weekday_column/routes.txt @@ -1,1 +1,6 @@ - +route_id,agency_id,route_short_name,route_long_name,route_desc,route_type,route_url,route_color,route_text_color +AB,DTA,,Airport - Bullfrog,,3 +BFC,DTA,,Bullfrog - Furnace Creek Resort,,3 +STBA,DTA,,Stagecoach - Airport Shuttle,,3 +CITY,DTA,,City,,3 +AAMV,DTA,,Airport - Amargosa Valley,,3 --- /dev/null +++ b/origin-src/transitfeed-1.2.5/test/data/missing_weekday_column/stop_times.txt @@ -1,1 +1,30 @@ +trip_id,arrival_time,departure_time,stop_id,stop_sequence +STBA,6:00:00,6:00:00,STAGECOACH,1 +STBA,6:20:00,6:20:00,BEATTY_AIRPORT,2 +CITY1,6:00:00,6:00:00,STAGECOACH,1 +CITY1,6:05:00,6:07:00,NANAA,2 +CITY1,6:12:00,6:14:00,NADAV,3 +CITY1,6:19:00,6:21:00,DADAN,4 +CITY1,6:26:00,6:28:00,EMSI,5 +CITY2,6:28:00,6:30:00,EMSI,1 +CITY2,6:35:00,6:37:00,DADAN,2 +CITY2,6:42:00,6:44:00,NADAV,3 +CITY2,6:49:00,6:51:00,NANAA,4 +CITY2,6:56:00,6:58:00,STAGECOACH,5 +AB1,8:00:00,8:00:00,BEATTY_AIRPORT,1 +AB1,8:10:00,8:15:00,BULLFROG,2 +AB2,12:05:00,12:05:00,BULLFROG,1 +AB2,12:15:00,12:15:00,BEATTY_AIRPORT,2 +BFC1,8:20:00,8:20:00,BULLFROG,1 +BFC1,9:20:00,9:20:00,FUR_CREEK_RES,2 +BFC2,11:00:00,11:00:00,FUR_CREEK_RES,1 +BFC2,12:00:00,12:00:00,BULLFROG,2 +AAMV1,8:00:00,8:00:00,BEATTY_AIRPORT,1 +AAMV1,9:00:00,9:00:00,AMV,2 +AAMV2,10:00:00,10:00:00,AMV,1 +AAMV2,11:00:00,11:00:00,BEATTY_AIRPORT,2 +AAMV3,13:00:00,13:00:00,BEATTY_AIRPORT,1 +AAMV3,14:00:00,14:00:00,AMV,2 +AAMV4,15:00:00,15:00:00,AMV,1 +AAMV4,16:00:00,16:00:00,BEATTY_AIRPORT,2 --- /dev/null +++ b/origin-src/transitfeed-1.2.5/test/data/missing_weekday_column/stops.txt @@ -1,1 +1,10 @@ - +stop_id,stop_name,stop_desc,stop_lat,stop_lon,zone_id,stop_url +FUR_CREEK_RES,Furnace Creek Resort (Demo),,36.425288,-117.133162 +BEATTY_AIRPORT,Nye County Airport (Demo),,36.868446,-116.784582 +BULLFROG,Bullfrog (Demo),,36.88108,-116.81797 +STAGECOACH,Stagecoach Hotel & Casino (Demo),,36.915682,-116.751677 +NADAV,North Ave / D Ave N (Demo),,36.914893,-116.76821 +NANAA,North Ave / N A Ave (Demo),,36.914944,-116.761472 +DADAN,Doing Ave / D Ave N (Demo),,36.909489,-116.768242 +EMSI,E Main St / S Irving St (Demo),,36.905697,-116.76218 +AMV,Amargosa Valley (Demo),,36.641496,-116.40094 --- /dev/null +++ b/origin-src/transitfeed-1.2.5/test/data/missing_weekday_column/trips.txt @@ -1,1 +1,12 @@ - +route_id,service_id,trip_id,trip_headsign,direction_id,block_id,shape_id +AB,FULLW,AB1,to Bullfrog,0,1 +AB,FULLW,AB2,to Airport,1,2 +STBA,FULLW,STBA,Shuttle +CITY,FULLW,CITY1,,0 +CITY,FULLW,CITY2,,1 +BFC,FULLW,BFC1,to Furnace Creek Resort,0,1 +BFC,FULLW,BFC2,to Bullfrog,1,2 +AAMV,WE,AAMV1,to Amargosa Valley,0 +AAMV,WE,AAMV2,to Airport,1 +AAMV,WE,AAMV3,to Amargosa Valley,0 +AAMV,WE,AAMV4,to Airport,1 --- /dev/null +++ b/origin-src/transitfeed-1.2.5/test/data/negative_stop_sequence/agency.txt @@ -1,1 +1,2 @@ - +agency_id,agency_name,agency_url,agency_timezone +DTA,Demo Transit Authority,http://google.com,America/Los_Angeles --- /dev/null +++ b/origin-src/transitfeed-1.2.5/test/data/negative_stop_sequence/calendar.txt @@ -1,1 +1,3 @@ - +service_id,monday,tuesday,wednesday,thursday,friday,saturday,sunday,start_date,end_date +FULLW,1,1,1,1,1,1,1,20070101,20101231 +WE,0,0,0,0,0,1,1,20070101,20101231 --- /dev/null +++ b/origin-src/transitfeed-1.2.5/test/data/negative_stop_sequence/calendar_dates.txt @@ -1,1 +1,2 @@ - +service_id,date,exception_type +FULLW,20070604,2 --- /dev/null +++ b/origin-src/transitfeed-1.2.5/test/data/negative_stop_sequence/fare_attributes.txt @@ -1,1 +1,4 @@ +fare_id,price,currency_type,payment_method,transfers,transfer_duration +p,1.25,USD,0,0, +a,5.25,USD,0,0, --- /dev/null +++ b/origin-src/transitfeed-1.2.5/test/data/negative_stop_sequence/fare_rules.txt @@ -1,1 +1,6 @@ +fare_id,route_id,origin_id,destination_id,contains_id +p,AB,,, +p,STBA,,, +p,BFC,,, +a,AAMV,,, --- /dev/null +++ b/origin-src/transitfeed-1.2.5/test/data/negative_stop_sequence/frequencies.txt @@ -1,1 +1,12 @@ - +trip_id,start_time,end_time,headway_secs +STBA,6:00:00,22:00:00,1800 +CITY1,6:00:00,7:59:59,1800 +CITY2,6:00:00,7:59:59,1800 +CITY1,8:00:00,9:59:59,600 +CITY2,8:00:00,9:59:59,600 +CITY1,10:00:00,15:59:59,1800 +CITY2,10:00:00,15:59:59,1800 +CITY1,16:00:00,18:59:59,600 +CITY2,16:00:00,18:59:59,600 +CITY1,19:00:00,22:00:00,1800 +CITY2,19:00:00,22:00:00,1800 --- /dev/null +++ b/origin-src/transitfeed-1.2.5/test/data/negative_stop_sequence/routes.txt @@ -1,1 +1,6 @@ - +route_id,agency_id,route_short_name,route_long_name,route_desc,route_type,route_url,route_color,route_text_color +AB,DTA,,Airport - Bullfrog,,3,,, +BFC,DTA,,Bullfrog - Furnace Creek Resort,,3,,, +STBA,DTA,,Stagecoach - Airport Shuttle,,3,,, +CITY,DTA,,City,,3,,, +AAMV,DTA,,Airport - Amargosa Valley,,3,,, --- /dev/null +++ b/origin-src/transitfeed-1.2.5/test/data/negative_stop_sequence/stop_times.txt @@ -1,1 +1,29 @@ - +trip_id,arrival_time,departure_time,stop_id,stop_sequence,stop_headsign,pickup_type,drop_off_type,shape_dist_traveled +STBA,6:00:00,6:00:00,STAGECOACH,0,,,, +STBA,6:20:00,6:20:00,BEATTY_AIRPORT,1,,,, +CITY1,6:00:00,6:00:00,STAGECOACH,0,,,, +CITY1,6:05:00,6:07:00,NANAA,1,,,, +CITY1,6:12:00,6:14:00,NADAV,2,,,, +CITY1,6:19:00,6:21:00,DADAN,3,,,, +CITY1,6:26:00,6:28:00,EMSI,4,,,, +CITY2,6:28:00,6:30:00,EMSI,-2,,,, +CITY2,6:35:00,6:37:00,DADAN,1,,,, +CITY2,6:42:00,6:44:00,NADAV,2,,,, +CITY2,6:49:00,6:51:00,NANAA,3,,,, +CITY2,6:56:00,6:58:00,STAGECOACH,4,,,, +AB1,8:00:00,8:00:00,BEATTY_AIRPORT,0,,,, +AB1,8:10:00,8:15:00,BULLFROG,1,,,, +AB2,12:05:00,12:05:00,BULLFROG,0,,,, +AB2,12:15:00,12:15:00,BEATTY_AIRPORT,1,,,, +BFC1,8:20:00,8:20:00,BULLFROG,0,,,, +BFC1,9:20:00,9:20:00,FUR_CREEK_RES,1,,,, +BFC2,11:00:00,11:00:00,FUR_CREEK_RES,0,,,, +BFC2,12:00:00,12:00:00,BULLFROG,1,,,, +AAMV1,8:00:00,8:00:00,BEATTY_AIRPORT,0,,,, +AAMV1,9:00:00,9:00:00,AMV,1,,,, +AAMV2,10:00:00,10:00:00,AMV,0,,,, +AAMV2,11:00:00,11:00:00,BEATTY_AIRPORT,1,,,, +AAMV3,13:00:00,13:00:00,BEATTY_AIRPORT,0,,,, +AAMV3,14:00:00,14:00:00,AMV,1,,,, +AAMV4,15:00:00,15:00:00,AMV,0,,,, +AAMV4,16:00:00,16:00:00,BEATTY_AIRPORT,1,,,, --- /dev/null +++ b/origin-src/transitfeed-1.2.5/test/data/negative_stop_sequence/stops.txt @@ -1,1 +1,10 @@ - +stop_id,stop_name,stop_desc,stop_lat,stop_lon,zone_id,stop_url +FUR_CREEK_RES,Furnace Creek Resort (Demo),,36.425288,-117.133162,, +BEATTY_AIRPORT,Nye County Airport (Demo),,36.868446,-116.784582,, +BULLFROG,Bullfrog (Demo),,36.88108,-116.81797,, +STAGECOACH,Stagecoach Hotel & Casino (Demo),,36.915682,-116.751677,, +NADAV,North Ave / D Ave N (Demo),,36.914893,-116.76821,, +NANAA,North Ave / N A Ave (Demo),,36.914944,-116.761472,, +DADAN,Doing Ave / D Ave N (Demo),,36.909489,-116.768242,, +EMSI,E Main St / S Irving St (Demo),,36.905697,-116.76218,, +AMV,Amargosa Valley (Demo),,36.641496,-116.40094,, --- /dev/null +++ b/origin-src/transitfeed-1.2.5/test/data/negative_stop_sequence/trips.txt @@ -1,1 +1,12 @@ - +route_id,service_id,trip_id,trip_headsign,direction_id,block_id,shape_id +AB,FULLW,AB1,to Bullfrog,0,1, +AB,FULLW,AB2,to Airport,1,2, +STBA,FULLW,STBA,Shuttle,,, +CITY,FULLW,CITY1,,0,, +CITY,FULLW,CITY2,,1,, +BFC,FULLW,BFC1,to Furnace Creek Resort,0,1, +BFC,FULLW,BFC2,to Bullfrog,1,2, +AAMV,WE,AAMV1,to Amargosa Valley,0,, +AAMV,WE,AAMV2,to Airport,1,, +AAMV,WE,AAMV3,to Amargosa Valley,0,, +AAMV,WE,AAMV4,to Airport,1,, --- /dev/null +++ b/origin-src/transitfeed-1.2.5/test/data/one_line.kml @@ -1,1 +1,19 @@ + + + + A test file with one placemark + + + Test + + + + -93.238861,44.854240,0.000000 + -93.238708,44.853081,0.000000 + -93.237923,44.852638,0.000000 + + + + + --- /dev/null +++ b/origin-src/transitfeed-1.2.5/test/data/one_stop.kml @@ -1,1 +1,15 @@ + + + + A test file with one placemark + + + Stop Name + + + -93.239037,44.854164,0.000000 + + + + --- /dev/null +++ b/origin-src/transitfeed-1.2.5/test/data/only_calendar_dates/agency.txt @@ -1,1 +1,2 @@ - +agency_id,agency_name,agency_url,agency_timezone +DTA,Demo Transit Authority,http://google.com,America/Los_Angeles --- /dev/null +++ b/origin-src/transitfeed-1.2.5/test/data/only_calendar_dates/calendar_dates.txt @@ -1,1 +1,4 @@ +service_id,date,exception_type +FULLW,20070604,1 +WE,20070605,1 --- /dev/null +++ b/origin-src/transitfeed-1.2.5/test/data/only_calendar_dates/fare_attributes.txt @@ -1,1 +1,4 @@ +fare_id,price,currency_type,payment_method,transfers,transfer_duration +p,1.25,USD,0,0, +a,5.25,USD,0,0, --- /dev/null +++ b/origin-src/transitfeed-1.2.5/test/data/only_calendar_dates/fare_rules.txt @@ -1,1 +1,6 @@ +fare_id,route_id,origin_id,destination_id,contains_id +p,AB,,, +p,STBA,,, +p,BFC,,, +a,AAMV,,, --- /dev/null +++ b/origin-src/transitfeed-1.2.5/test/data/only_calendar_dates/frequencies.txt @@ -1,1 +1,12 @@ - +trip_id,start_time,end_time,headway_secs +STBA,6:00:00,22:00:00,1800 +CITY1,6:00:00,7:59:59,1800 +CITY2,6:00:00,7:59:59,1800 +CITY1,8:00:00,9:59:59,600 +CITY2,8:00:00,9:59:59,600 +CITY1,10:00:00,15:59:59,1800 +CITY2,10:00:00,15:59:59,1800 +CITY1,16:00:00,18:59:59,600 +CITY2,16:00:00,18:59:59,600 +CITY1,19:00:00,22:00:00,1800 +CITY2,19:00:00,22:00:00,1800 --- /dev/null +++ b/origin-src/transitfeed-1.2.5/test/data/only_calendar_dates/routes.txt @@ -1,1 +1,6 @@ - +route_id,agency_id,route_short_name,route_long_name,route_desc,route_type,route_url,route_color,route_text_color +AB,DTA,,Airport - Bullfrog,,3,,, +BFC,DTA,,Bullfrog - Furnace Creek Resort,,3,,, +STBA,DTA,,Stagecoach - Airport Shuttle,,3,,, +CITY,DTA,,City,,3,,, +AAMV,DTA,,Airport - Amargosa Valley,,3,,, --- /dev/null +++ b/origin-src/transitfeed-1.2.5/test/data/only_calendar_dates/stop_times.txt @@ -1,1 +1,29 @@ - +trip_id,arrival_time,departure_time,stop_id,stop_sequence,stop_headsign,pickup_type,drop_off_type,shape_dist_traveled +STBA,6:00:00,6:00:00,STAGECOACH,1,,,, +STBA,6:20:00,6:20:00,BEATTY_AIRPORT,2,,,, +CITY1,6:00:00,6:00:00,STAGECOACH,1,,,, +CITY1,6:05:00,6:07:00,NANAA,2,,,, +CITY1,6:12:00,6:14:00,NADAV,3,,,, +CITY1,6:19:00,6:21:00,DADAN,4,,,, +CITY1,6:26:00,6:28:00,EMSI,5,,,, +CITY2,6:28:00,6:30:00,EMSI,1,,,, +CITY2,6:35:00,6:37:00,DADAN,2,,,, +CITY2,6:42:00,6:44:00,NADAV,3,,,, +CITY2,6:49:00,6:51:00,NANAA,4,,,, +CITY2,6:56:00,6:58:00,STAGECOACH,5,,,, +AB1,8:00:00,8:00:00,BEATTY_AIRPORT,1,,,, +AB1,8:10:00,8:15:00,BULLFROG,2,,,, +AB2,12:05:00,12:05:00,BULLFROG,1,,,, +AB2,12:15:00,12:15:00,BEATTY_AIRPORT,2,,,, +BFC1,8:20:00,8:20:00,BULLFROG,1,,,, +BFC1,9:20:00,9:20:00,FUR_CREEK_RES,2,,,, +BFC2,11:00:00,11:00:00,FUR_CREEK_RES,1,,,, +BFC2,12:00:00,12:00:00,BULLFROG,2,,,, +AAMV1,8:00:00,8:00:00,BEATTY_AIRPORT,1,,,, +AAMV1,9:00:00,9:00:00,AMV,2,,,, +AAMV2,10:00:00,10:00:00,AMV,1,,,, +AAMV2,11:00:00,11:00:00,BEATTY_AIRPORT,2,,,, +AAMV3,13:00:00,13:00:00,BEATTY_AIRPORT,1,,,, +AAMV3,14:00:00,14:00:00,AMV,2,,,, +AAMV4,15:00:00,15:00:00,AMV,1,,,, +AAMV4,16:00:00,16:00:00,BEATTY_AIRPORT,2,,,, --- /dev/null +++ b/origin-src/transitfeed-1.2.5/test/data/only_calendar_dates/stops.txt @@ -1,1 +1,10 @@ - +stop_id,stop_name,stop_desc,stop_lat,stop_lon,zone_id,stop_url +FUR_CREEK_RES,Furnace Creek Resort (Demo),,36.425288,-117.133162,, +BEATTY_AIRPORT,Nye County Airport (Demo),,36.868446,-116.784582,, +BULLFROG,Bullfrog (Demo),,36.88108,-116.81797,, +STAGECOACH,Stagecoach Hotel & Casino (Demo),,36.915682,-116.751677,, +NADAV,North Ave / D Ave N (Demo),,36.914893,-116.76821,, +NANAA,North Ave / N A Ave (Demo),,36.914944,-116.761472,, +DADAN,Doing Ave / D Ave N (Demo),,36.909489,-116.768242,, +EMSI,E Main St / S Irving St (Demo),,36.905697,-116.76218,, +AMV,Amargosa Valley (Demo),,36.641496,-116.40094,, --- /dev/null +++ b/origin-src/transitfeed-1.2.5/test/data/only_calendar_dates/trips.txt @@ -1,1 +1,12 @@ - +route_id,service_id,trip_id,trip_headsign,direction_id,block_id,shape_id +AB,FULLW,AB1,to Bullfrog,0,1, +AB,FULLW,AB2,to Airport,1,2, +STBA,FULLW,STBA,Shuttle,,, +CITY,FULLW,CITY1,,0,, +CITY,FULLW,CITY2,,1,, +BFC,FULLW,BFC1,to Furnace Creek Resort,0,1, +BFC,FULLW,BFC2,to Bullfrog,1,2, +AAMV,WE,AAMV1,to Amargosa Valley,0,, +AAMV,WE,AAMV2,to Airport,1,, +AAMV,WE,AAMV3,to Amargosa Valley,0,, +AAMV,WE,AAMV4,to Airport,1,, --- /dev/null +++ b/origin-src/transitfeed-1.2.5/test/data/repeated_route_name/agency.txt @@ -1,1 +1,2 @@ - +agency_id,agency_name,agency_url,agency_timezone +DTA,Demo Transit Authority,http://google.com,America/Los_Angeles --- /dev/null +++ b/origin-src/transitfeed-1.2.5/test/data/repeated_route_name/calendar.txt @@ -1,1 +1,3 @@ - +service_id,monday,tuesday,wednesday,thursday,friday,saturday,sunday,start_date,end_date +FULLW,1,1,1,1,1,1,1,20070101,20101231 +WE,0,0,0,0,0,1,1,20070101,20101231 --- /dev/null +++ b/origin-src/transitfeed-1.2.5/test/data/repeated_route_name/calendar_dates.txt @@ -1,1 +1,2 @@ - +service_id,date,exception_type +FULLW,20070604,2 --- /dev/null +++ b/origin-src/transitfeed-1.2.5/test/data/repeated_route_name/fare_attributes.txt @@ -1,1 +1,4 @@ +fare_id,price,currency_type,payment_method,transfers,transfer_duration +p,1.25,USD,0,0, +a,5.25,USD,0,0, --- /dev/null +++ b/origin-src/transitfeed-1.2.5/test/data/repeated_route_name/fare_rules.txt @@ -1,1 +1,6 @@ +fare_id,route_id,origin_id,destination_id,contains_id +p,AB,,, +p,STBA,,, +p,BFC,,, +a,AAMV,,, --- /dev/null +++ b/origin-src/transitfeed-1.2.5/test/data/repeated_route_name/frequencies.txt @@ -1,1 +1,12 @@ - +trip_id,start_time,end_time,headway_secs +STBA,6:00:00,22:00:00,1800 +CITY1,6:00:00,7:59:59,1800 +CITY2,6:00:00,7:59:59,1800 +CITY1,8:00:00,9:59:59,600 +CITY2,8:00:00,9:59:59,600 +CITY1,10:00:00,15:59:59,1800 +CITY2,10:00:00,15:59:59,1800 +CITY1,16:00:00,18:59:59,600 +CITY2,16:00:00,18:59:59,600 +CITY1,19:00:00,22:00:00,1800 +CITY2,19:00:00,22:00:00,1800 --- /dev/null +++ b/origin-src/transitfeed-1.2.5/test/data/repeated_route_name/routes.txt @@ -1,1 +1,8 @@ +route_id,agency_id,route_short_name,route_long_name,route_desc,route_type,route_url,route_color,route_text_color +AB,DTA,,Airport ⇒ Bullfrog,,3,,, +BFC,DTA,,Bullfrog ⇒ Furnace Creek Resort,,3,,, +STBA,DTA,,Stagecoach ⇒ Airport Shuttle,,3,,, +STBB,DTA,,Stagecoach ⇒ Airport Shuttle,,3,,, +CITY,DTA,,City,,3,,, +AAMV,DTA,,Airport ⇒ Amargosa Valley,,3,,, --- /dev/null +++ b/origin-src/transitfeed-1.2.5/test/data/repeated_route_name/stop_times.txt @@ -1,1 +1,29 @@ - +trip_id,arrival_time,departure_time,stop_id,stop_sequence,stop_headsign,pickup_type,drop_off_type,shape_dist_traveled +STBA,6:00:00,6:00:00,STAGECOACH,1,,,, +STBA,6:20:00,6:20:00,BEATTY_AIRPORT,2,,,, +CITY1,6:00:00,6:00:00,STAGECOACH,1,,,, +CITY1,6:05:00,6:07:00,NANAA,2,,,, +CITY1,6:12:00,6:14:00,NADAV,3,,,, +CITY1,6:19:00,6:21:00,DADAN,4,,,, +CITY1,6:26:00,6:28:00,EMSI,5,,,, +CITY2,6:28:00,6:30:00,EMSI,1,,,, +CITY2,6:35:00,6:37:00,DADAN,2,,,, +CITY2,6:42:00,6:44:00,NADAV,3,,,, +CITY2,6:49:00,6:51:00,NANAA,4,,,, +CITY2,6:56:00,6:58:00,STAGECOACH,5,,,, +AB1,8:00:00,8:00:00,BEATTY_AIRPORT,1,,,, +AB1,8:10:00,8:15:00,BULLFROG,2,,,, +AB2,12:05:00,12:05:00,BULLFROG,1,,,, +AB2,12:15:00,12:15:00,BEATTY_AIRPORT,2,,,, +BFC1,8:20:00,8:20:00,BULLFROG,1,,,, +BFC1,9:20:00,9:20:00,FUR_CREEK_RES,2,,,, +BFC2,11:00:00,11:00:00,FUR_CREEK_RES,1,,,, +BFC2,12:00:00,12:00:00,BULLFROG,2,,,, +AAMV1,8:00:00,8:00:00,BEATTY_AIRPORT,1,,,, +AAMV1,9:00:00,9:00:00,AMV,2,,,, +AAMV2,10:00:00,10:00:00,AMV,1,,,, +AAMV2,11:00:00,11:00:00,BEATTY_AIRPORT,2,,,, +AAMV3,13:00:00,13:00:00,BEATTY_AIRPORT,1,,,, +AAMV3,14:00:00,14:00:00,AMV,2,,,, +AAMV4,15:00:00,15:00:00,AMV,1,,,, +AAMV4,16:00:00,16:00:00,BEATTY_AIRPORT,2,,,, --- /dev/null +++ b/origin-src/transitfeed-1.2.5/test/data/repeated_route_name/stops.txt @@ -1,1 +1,10 @@ - +stop_id,stop_name,stop_desc,stop_lat,stop_lon,zone_id,stop_url +FUR_CREEK_RES,Furnace Creek Resort (Demo),,36.425288,-117.133162,, +BEATTY_AIRPORT,Nye County Airport (Demo),,36.868446,-116.784582,, +BULLFROG,Bullfrog (Demo),,36.88108,-116.81797,, +STAGECOACH,Stagecoach Hotel & Casino (Demo),,36.915682,-116.751677,, +NADAV,North Ave / D Ave N (Demo),,36.914893,-116.76821,, +NANAA,North Ave / N A Ave (Demo),,36.914944,-116.761472,, +DADAN,Doing Ave / D Ave N (Demo),,36.909489,-116.768242,, +EMSI,E Main St / S Irving St (Demo),,36.905697,-116.76218,, +AMV,Amargosa Valley (Demo),,36.641496,-116.40094,, --- /dev/null +++ b/origin-src/transitfeed-1.2.5/test/data/repeated_route_name/trips.txt @@ -1,1 +1,12 @@ - +route_id,service_id,trip_id,trip_headsign,direction_id,block_id,shape_id +AB,FULLW,AB1,to Bullfrog,0,1, +AB,FULLW,AB2,to Airport,1,2, +STBA,FULLW,STBA,Shuttle,,, +CITY,FULLW,CITY1,,0,, +CITY,FULLW,CITY2,,1,, +BFC,FULLW,BFC1,to Furnace Creek Resort,0,1, +BFC,FULLW,BFC2,to Bullfrog,1,2, +AAMV,WE,AAMV1,to Amargosa Valley,0,, +AAMV,WE,AAMV2,to Airport,1,, +AAMV,WE,AAMV3,to Amargosa Valley,0,, +AAMV,WE,AAMV4,to Airport,1,, --- /dev/null +++ b/origin-src/transitfeed-1.2.5/test/data/same_short_long_name/agency.txt @@ -1,1 +1,2 @@ - +agency_id,agency_name,agency_url,agency_timezone +DTA,Demo Transit Authority,http://google.com,America/Los_Angeles --- /dev/null +++ b/origin-src/transitfeed-1.2.5/test/data/same_short_long_name/calendar.txt @@ -1,1 +1,3 @@ - +service_id,monday,tuesday,wednesday,thursday,friday,saturday,sunday,start_date,end_date +FULLW,1,1,1,1,1,1,1,20070101,20101231 +WE,0,0,0,0,0,1,1,20070101,20101231 --- /dev/null +++ b/origin-src/transitfeed-1.2.5/test/data/same_short_long_name/calendar_dates.txt @@ -1,1 +1,2 @@ - +service_id,date,exception_type +FULLW,20070604,2 --- /dev/null +++ b/origin-src/transitfeed-1.2.5/test/data/same_short_long_name/fare_attributes.txt @@ -1,1 +1,4 @@ +fare_id,price,currency_type,payment_method,transfers,transfer_duration +p,1.25,USD,0,0, +a,5.25,USD,0,0, --- /dev/null +++ b/origin-src/transitfeed-1.2.5/test/data/same_short_long_name/fare_rules.txt @@ -1,1 +1,6 @@ +fare_id,route_id,origin_id,destination_id,contains_id +p,AB,,, +p,STBA,,, +p,BFC,,, +a,AAMV,,, --- /dev/null +++ b/origin-src/transitfeed-1.2.5/test/data/same_short_long_name/frequencies.txt @@ -1,1 +1,12 @@ - +trip_id,start_time,end_time,headway_secs +STBA,6:00:00,22:00:00,1800 +CITY1,6:00:00,7:59:59,1800 +CITY2,6:00:00,7:59:59,1800 +CITY1,8:00:00,9:59:59,600 +CITY2,8:00:00,9:59:59,600 +CITY1,10:00:00,15:59:59,1800 +CITY2,10:00:00,15:59:59,1800 +CITY1,16:00:00,18:59:59,600 +CITY2,16:00:00,18:59:59,600 +CITY1,19:00:00,22:00:00,1800 +CITY2,19:00:00,22:00:00,1800 --- /dev/null +++ b/origin-src/transitfeed-1.2.5/test/data/same_short_long_name/routes.txt @@ -1,1 +1,7 @@ +route_id,agency_id,route_short_name,route_long_name,route_desc,route_type,route_url,route_color,route_text_color +AB,DTA,,Airport - Bullfrog,,3,,, +BFC,DTA,,Bullfrog - Furnace Creek Resort,,3,,, +STBA,DTA,,Stagecoach - Airport Shuttle,,3,,, +CITY,DTA,City,City,,3,,, +AAMV,DTA,,Airport - Amargosa Valley,,3,,, --- /dev/null +++ b/origin-src/transitfeed-1.2.5/test/data/same_short_long_name/stop_times.txt @@ -1,1 +1,30 @@ +trip_id,arrival_time,departure_time,stop_id,stop_sequence,stop_headsign,pickup_type,drop_off_type,shape_dist_traveled +STBA,6:00:00,6:00:00,STAGECOACH,1,,,, +STBA,6:20:00,6:20:00,BEATTY_AIRPORT,2,,,, +CITY1,6:00:00,6:00:00,STAGECOACH,1,,,, +CITY1,6:05:00,6:07:00,NANAA,2,,,, +CITY1,6:12:00,6:14:00,NADAV,3,,,, +CITY1,6:19:00,6:21:00,DADAN,4,,,, +CITY1,6:26:00,6:28:00,EMSI,5,,,, +CITY2,6:28:00,6:30:00,EMSI,1,,,, +CITY2,6:35:00,6:37:00,DADAN,2,,,, +CITY2,6:42:00,6:44:00,NADAV,3,,,, +CITY2,6:49:00,6:51:00,NANAA,4,,,, +CITY2,6:56:00,6:58:00,STAGECOACH,5,,,, +AB1,8:00:00,8:00:00,BEATTY_AIRPORT,1,,,, +AB1,8:10:00,8:15:00,BULLFROG,2,,,, +AB2,12:05:00,12:05:00,BULLFROG,1,,,, +AB2,12:15:00,12:15:00,BEATTY_AIRPORT,2,,,, +BFC1,8:20:00,8:20:00,BULLFROG,1,,,, +BFC1,9:20:00,9:20:00,FUR_CREEK_RES,2,,,, +BFC2,11:00:00,11:00:00,FUR_CREEK_RES,1,,,, +BFC2,12:00:00,12:00:00,BULLFROG,2,,,, +AAMV1,8:00:00,8:00:00,BEATTY_AIRPORT,1,,,, +AAMV1,9:00:00,9:00:00,AMV,2,,,, +AAMV2,10:00:00,10:00:00,AMV,1,,,, +AAMV2,11:00:00,11:00:00,BEATTY_AIRPORT,2,,,, +AAMV3,13:00:00,13:00:00,BEATTY_AIRPORT,1,,,, +AAMV3,14:00:00,14:00:00,AMV,2,,,, +AAMV4,15:00:00,15:00:00,AMV,1,,,, +AAMV4,16:00:00,16:00:00,BEATTY_AIRPORT,2,,,, --- /dev/null +++ b/origin-src/transitfeed-1.2.5/test/data/same_short_long_name/stops.txt @@ -1,1 +1,10 @@ - +stop_id,stop_name,stop_desc,stop_lat,stop_lon,zone_id,stop_url +FUR_CREEK_RES,Furnace Creek Resort (Demo),,36.425288,-117.133162,, +BEATTY_AIRPORT,Nye County Airport (Demo),,36.868446,-116.784582,, +BULLFROG,Bullfrog (Demo),,36.88108,-116.81797,, +STAGECOACH,Stagecoach Hotel & Casino (Demo),,36.915682,-116.751677,, +NADAV,North Ave / D Ave N (Demo),,36.914893,-116.76821,, +NANAA,North Ave / N A Ave (Demo),,36.914944,-116.761472,, +DADAN,Doing Ave / D Ave N (Demo),,36.909489,-116.768242,, +EMSI,E Main St / S Irving St (Demo),,36.905697,-116.76218,, +AMV,Amargosa Valley (Demo),,36.641496,-116.40094,, --- /dev/null +++ b/origin-src/transitfeed-1.2.5/test/data/same_short_long_name/trips.txt @@ -1,1 +1,12 @@ - +route_id,service_id,trip_id,trip_headsign,direction_id,block_id,shape_id +AB,FULLW,AB1,to Bullfrog,0,1, +AB,FULLW,AB2,to Airport,1,2, +STBA,FULLW,STBA,Shuttle,,, +CITY,FULLW,CITY1,,0,, +CITY,FULLW,CITY2,,1,, +BFC,FULLW,BFC1,to Furnace Creek Resort,0,1, +BFC,FULLW,BFC2,to Bullfrog,1,2, +AAMV,WE,AAMV1,to Amargosa Valley,0,, +AAMV,WE,AAMV2,to Airport,1,, +AAMV,WE,AAMV3,to Amargosa Valley,0,, +AAMV,WE,AAMV4,to Airport,1,, --- /dev/null +++ b/origin-src/transitfeed-1.2.5/test/data/undefined_stop/agency.txt @@ -1,1 +1,2 @@ - +agency_id,agency_name,agency_url,agency_timezone +DTA,Demo Transit Authority,http://google.com,America/Los_Angeles --- /dev/null +++ b/origin-src/transitfeed-1.2.5/test/data/undefined_stop/calendar.txt @@ -1,1 +1,3 @@ - +service_id,monday,tuesday,wednesday,thursday,friday,saturday,sunday,start_date,end_date +FULLW,1,1,1,1,1,1,1,20070101,20101231 +WE,0,0,0,0,0,1,1,20070101,20101231 --- /dev/null +++ b/origin-src/transitfeed-1.2.5/test/data/undefined_stop/calendar_dates.txt @@ -1,1 +1,2 @@ - +service_id,date,exception_type +FULLW,20070604,2 --- /dev/null +++ b/origin-src/transitfeed-1.2.5/test/data/undefined_stop/fare_attributes.txt @@ -1,1 +1,4 @@ +fare_id,price,currency_type,payment_method,transfers,transfer_duration +p,1.25,USD,0,0, +a,5.25,USD,0,0, --- /dev/null +++ b/origin-src/transitfeed-1.2.5/test/data/undefined_stop/fare_rules.txt @@ -1,1 +1,6 @@ +fare_id,route_id,origin_id,destination_id,contains_id +p,AB,,, +p,STBA,,, +p,BFC,,, +a,AAMV,,, --- /dev/null +++ b/origin-src/transitfeed-1.2.5/test/data/undefined_stop/frequencies.txt @@ -1,1 +1,12 @@ - +trip_id,start_time,end_time,headway_secs +STBA,6:00:00,22:00:00,1800 +CITY1,6:00:00,7:59:59,1800 +CITY2,6:00:00,7:59:59,1800 +CITY1,8:00:00,9:59:59,600 +CITY2,8:00:00,9:59:59,600 +CITY1,10:00:00,15:59:59,1800 +CITY2,10:00:00,15:59:59,1800 +CITY1,16:00:00,18:59:59,600 +CITY2,16:00:00,18:59:59,600 +CITY1,19:00:00,22:00:00,1800 +CITY2,19:00:00,22:00:00,1800 --- /dev/null +++ b/origin-src/transitfeed-1.2.5/test/data/undefined_stop/routes.txt @@ -1,1 +1,6 @@ - +route_id,agency_id,route_short_name,route_long_name,route_desc,route_type,route_url,route_color,route_text_color +AB,DTA,,Airport - Bullfrog,,3,,, +BFC,DTA,,Bullfrog - Furnace Creek Resort,,3,,, +STBA,DTA,,Stagecoach - Airport Shuttle,,3,,, +CITY,DTA,,City,,3,,, +AAMV,DTA,,Airport - Amargosa Valley,,3,,, --- /dev/null +++ b/origin-src/transitfeed-1.2.5/test/data/undefined_stop/stop_times.txt @@ -1,1 +1,31 @@ +trip_id,arrival_time,departure_time,stop_id,stop_sequence,stop_headsign,pickup_type,drop_off_type,shape_dist_traveled +STBA,6:00:00,6:00:00,STAGECOACH,1,,,, +STBA,6:20:00,6:20:00,BEATTY_AIRPORT,2,,,, +CITY1,6:00:00,6:00:00,STAGECOACH,1,,,, +CITY1,6:05:00,6:07:00,NANAA,2,,,, +CITY1,6:12:00,6:14:00,NADAV,3,,,, +CITY1,6:19:00,6:21:00,DADAN,4,,,, +CITY1,6:26:00,6:28:00,EMSI,5,,,, +CITY2,6:28:00,6:30:00,EMSI,1,,,, +CITY2,6:35:00,6:37:00,DADAN,2,,,, +CITY2,6:40:00,6:41:00,NADAR,3,,,, +CITY2,6:42:00,6:44:00,NADAV,4,,,, +CITY2,6:49:00,6:51:00,NANAA,5,,,, +CITY2,6:56:00,6:58:00,STAGECOACH,6,,,, +AB1,8:00:00,8:00:00,BEATTY_AIRPORT,1,,,, +AB1,8:10:00,8:15:00,BULLFROG,2,,,, +AB2,12:05:00,12:05:00,BULLFROG,1,,,, +AB2,12:15:00,12:15:00,BEATTY_AIRPORT,2,,,, +BFC1,8:20:00,8:20:00,BULLFROG,1,,,, +BFC1,9:20:00,9:20:00,FUR_CREEK_RES,2,,,, +BFC2,11:00:00,11:00:00,FUR_CREEK_RES,1,,,, +BFC2,12:00:00,12:00:00,BULLFROG,2,,,, +AAMV1,8:00:00,8:00:00,BEATTY_AIRPORT,1,,,, +AAMV1,9:00:00,9:00:00,AMV,2,,,, +AAMV2,10:00:00,10:00:00,AMV,1,,,, +AAMV2,11:00:00,11:00:00,BEATTY_AIRPORT,2,,,, +AAMV3,13:00:00,13:00:00,BEATTY_AIRPORT,1,,,, +AAMV3,14:00:00,14:00:00,AMV,2,,,, +AAMV4,15:00:00,15:00:00,AMV,1,,,, +AAMV4,16:00:00,16:00:00,BEATTY_AIRPORT,2,,,, --- /dev/null +++ b/origin-src/transitfeed-1.2.5/test/data/undefined_stop/stops.txt @@ -1,1 +1,10 @@ - +stop_id,stop_name,stop_desc,stop_lat,stop_lon,zone_id,stop_url +FUR_CREEK_RES,Furnace Creek Resort (Demo),,36.425288,-117.133162,, +BEATTY_AIRPORT,Nye County Airport (Demo),,36.868446,-116.784582,, +BULLFROG,Bullfrog (Demo),,36.88108,-116.81797,, +STAGECOACH,Stagecoach Hotel & Casino (Demo),,36.915682,-116.751677,, +NADAV,North Ave / D Ave N (Demo),,36.914893,-116.76821,, +NANAA,North Ave / N A Ave (Demo),,36.914944,-116.761472,, +DADAN,Doing Ave / D Ave N (Demo),,36.909489,-116.768242,, +EMSI,E Main St / S Irving St (Demo),,36.905697,-116.76218,, +AMV,Amargosa Valley (Demo),,36.641496,-116.40094,, --- /dev/null +++ b/origin-src/transitfeed-1.2.5/test/data/undefined_stop/trips.txt @@ -1,1 +1,12 @@ - +route_id,service_id,trip_id,trip_headsign,direction_id,block_id,shape_id +AB,FULLW,AB1,to Bullfrog,0,1, +AB,FULLW,AB2,to Airport,1,2, +STBA,FULLW,STBA,Shuttle,,, +CITY,FULLW,CITY1,,0,, +CITY,FULLW,CITY2,,1,, +BFC,FULLW,BFC1,to Furnace Creek Resort,0,1, +BFC,FULLW,BFC2,to Bullfrog,1,2, +AAMV,WE,AAMV1,to Amargosa Valley,0,, +AAMV,WE,AAMV2,to Airport,1,, +AAMV,WE,AAMV3,to Amargosa Valley,0,, +AAMV,WE,AAMV4,to Airport,1,, --- /dev/null +++ b/origin-src/transitfeed-1.2.5/test/data/unknown_file/agency.txt @@ -1,1 +1,3 @@ +agency_id,agency_name,agency_url,agency_timezone,agency_phone +DTA,Autorité de passage de démonstration,http://google.com,America/Los_Angeles,123 12314 --- /dev/null +++ b/origin-src/transitfeed-1.2.5/test/data/unknown_file/calendar.txt @@ -1,1 +1,3 @@ - +service_id,monday,tuesday,wednesday,thursday,friday,saturday,sunday,start_date,end_date +FULLW,1,1,1,1,1,1,1,20070101,20101231 +WE,0,0,0,0,0,1,1,20070101,20101231 --- /dev/null +++ b/origin-src/transitfeed-1.2.5/test/data/unknown_file/calendar_dates.txt @@ -1,1 +1,2 @@ - +service_id,date,exception_type +FULLW,20070604,2 --- /dev/null +++ b/origin-src/transitfeed-1.2.5/test/data/unknown_file/fare_attributes.txt @@ -1,1 +1,4 @@ +fare_id,price,currency_type,payment_method,transfers,transfer_duration +p,1.25,USD,0,0, +a,5.25,USD,0,0, --- /dev/null +++ b/origin-src/transitfeed-1.2.5/test/data/unknown_file/fare_rules.txt @@ -1,1 +1,6 @@ +fare_id,route_id,origin_id,destination_id,contains_id +p,AB,,, +p,STBA,,, +p,BFC,,, +a,AAMV,,, --- /dev/null +++ b/origin-src/transitfeed-1.2.5/test/data/unknown_file/frecuencias.txt @@ -1,1 +1,12 @@ - +trip_id,start_time,end_time,headway_secs +STBA,6:00:00,22:00:00,1800 +CITY1,6:00:00,7:59:59,1800 +CITY2,6:00:00,7:59:59,1800 +CITY1,8:00:00,9:59:59,600 +CITY2,8:00:00,9:59:59,600 +CITY1,10:00:00,15:59:59,1800 +CITY2,10:00:00,15:59:59,1800 +CITY1,16:00:00,18:59:59,600 +CITY2,16:00:00,18:59:59,600 +CITY1,19:00:00,22:00:00,1800 +CITY2,19:00:00,22:00:00,1800 --- /dev/null +++ b/origin-src/transitfeed-1.2.5/test/data/unknown_file/routes.txt @@ -1,1 +1,7 @@ +route_id,agency_id,route_short_name,route_long_name,route_desc,route_type,route_url,route_color,route_text_color +AB,DTA,,Airport ⇒ Bullfrog,,3,,, +BFC,DTA,,Bullfrog ⇒ Furnace Creek Resort,,3,,, +STBA,DTA,,Stagecoach ⇒ Airport Shuttle,,3,,, +CITY,DTA,ÅŒ,Bar Circle,Route with ĸool unicode shortname,3,,, +AAMV,DTA,,Airport ⇒ Amargosa Valley,,3,,, --- /dev/null +++ b/origin-src/transitfeed-1.2.5/test/data/unknown_file/stop_times.txt @@ -1,1 +1,30 @@ +trip_id,arrival_time,departure_time,stop_id,stop_sequence,stop_headsign,pickup_type,drop_off_type,shape_dist_traveled +STBA,6:00:00,6:00:00,STAGECOACH,0,to airport,1,0,0.212 +STBA,6:20:00,6:20:00,BEATTY_AIRPORT,2,,0,0,1.043 +CITY1,6:00:00,6:00:00,STAGECOACH,0,,,, +CITY1,6:05:00,6:07:00,NANAA,5,going to nadav,2,3, +CITY1,6:12:00,6:14:00,NADAV,10,,,, +CITY1,6:19:00,6:21:00,DADAN,15,,,, +CITY1,6:26:00,6:28:00,EMSI,20,,,, +CITY2,6:28:00,6:30:00,EMSI,100,,,, +CITY2,6:35:00,6:37:00,DADAN,200,,,, +CITY2,6:42:00,6:44:00,NADAV,300,,,, +CITY2,6:49:00,6:51:00,NANAA,400,,,, +CITY2,6:56:00,6:58:00,STAGECOACH,500,,,, +AB1,8:00:00,8:00:00,BEATTY_AIRPORT,1,,,, +AB1,8:10:00,8:15:00,BULLFROG,2,,,, +AB2,12:05:00,12:05:00,BULLFROG,1,,,, +AB2,12:15:00,12:15:00,BEATTY_AIRPORT,2,,,, +BFC1,8:20:00,8:20:00,BULLFROG,1,,,, +BFC1,9:20:00,9:20:00,FUR_CREEK_RES,2,,,, +BFC2,11:00:00,11:00:00,FUR_CREEK_RES,1,,,, +BFC2,12:00:00,12:00:00,BULLFROG,2,,,, +AAMV1,8:00:00,8:00:00,BEATTY_AIRPORT,1,,,, +AAMV1,9:00:00,9:00:00,AMV,2,,,, +AAMV2,10:00:00,10:00:00,AMV,1,,,, +AAMV2,11:00:00,11:00:00,BEATTY_AIRPORT,2,,,, +AAMV3,13:00:00,13:00:00,BEATTY_AIRPORT,1,,,, +AAMV3,14:00:00,14:00:00,AMV,2,,,, +AAMV4,15:00:00,15:00:00,AMV,1,,,, +AAMV4,16:00:00,16:00:00,BEATTY_AIRPORT,2,,,, --- /dev/null +++ b/origin-src/transitfeed-1.2.5/test/data/unknown_file/stops.txt @@ -1,1 +1,12 @@ +stop_id,stop_name,stop_desc,stop_lat,stop_lon,zone_id,stop_url,stop_code,location_type,parent_station +FUR_CREEK_RES,Furnace Creek Resort (Demo),,36.425288,-117.133162,,,1234,, +BEATTY_AIRPORT,Nye County Airport (Demo),,36.868446,-116.784582,,,1235,0,BEATTY_AIRPORT_STATION +BEATTY_AIRPORT_STATION,Nye County Airport (Demo),,36.868446,-116.784582,,,1235,1, +BULLFROG,Bullfrog (Demo),,36.88108,-116.81797,,,,, +STAGECOACH,Stagecoach Hotel & Casino (Demo),,36.915682,-116.751677,,,1236,, +NADAV,North Ave / D Ave N (Demo),,36.914893,-116.76821,,,1237,, +NANAA,North Ave / N A Ave (Demo),,36.914944,-116.761472,,,1238,, +DADAN,Doing Ave / D Ave N (Demo),,36.909489,-116.768242,,,,, +EMSI,E Main St / S Irving St (Demo),,36.905697,-116.76218,,,,, +AMV,Amargosa Valley (Demo),,36.641496,-116.40094,,,,, --- /dev/null +++ b/origin-src/transitfeed-1.2.5/test/data/unknown_file/transfers.txt @@ -1,1 +1,4 @@ +from_stop_id,to_stop_id,transfer_type,min_transfer_time +NADAV,NANAA,3, +EMSI,NANAA,2,1200 --- /dev/null +++ b/origin-src/transitfeed-1.2.5/test/data/unknown_file/trips.txt @@ -1,1 +1,12 @@ - +route_id,service_id,trip_id,trip_headsign,direction_id,block_id,shape_id +AB,FULLW,AB1,to Bullfrog,0,1, +AB,FULLW,AB2,to Airport,1,2, +STBA,FULLW,STBA,Shuttle,,, +CITY,FULLW,CITY1,,0,, +CITY,FULLW,CITY2,,1,, +BFC,FULLW,BFC1,to Furnace Creek Resort,0,1, +BFC,FULLW,BFC2,to Bullfrog,1,2, +AAMV,WE,AAMV1,to Amargosa Valley,0,, +AAMV,WE,AAMV2,to Airport,1,, +AAMV,WE,AAMV3,to Amargosa Valley,0,, +AAMV,WE,AAMV4,to Airport,1,, --- /dev/null +++ b/origin-src/transitfeed-1.2.5/test/data/unknown_format.zip @@ -1,1 +1,2 @@ +not a real zip file --- /dev/null +++ b/origin-src/transitfeed-1.2.5/test/data/unrecognized_columns/agency.txt @@ -1,1 +1,2 @@ - +agency_id,agency_name,agency_url,agency_timezone,agency_lange +DTA,Demo Transit Authority,http://google.com,America/Los_Angeles,en --- /dev/null +++ b/origin-src/transitfeed-1.2.5/test/data/unrecognized_columns/calendar.txt @@ -1,1 +1,3 @@ - +service_id,monday,tuesday,wednesday,thursday,friday,saturday,sunday,start_date,end_date,leap_day +FULLW,1,1,1,1,1,1,1,20070101,20101231, +WE,0,0,0,0,0,1,1,20070101,20101231, --- /dev/null +++ b/origin-src/transitfeed-1.2.5/test/data/unrecognized_columns/calendar_dates.txt @@ -1,1 +1,2 @@ - +service_id,date,exception_type,leap_day +FULLW,20070604,2, --- /dev/null +++ b/origin-src/transitfeed-1.2.5/test/data/unrecognized_columns/fare_attributes.txt @@ -1,1 +1,4 @@ +fare_id,price,currency_type,payment_method,transfers,transfer_time +p,1.25,USD,0,0, +a,5.25,USD,0,0, --- /dev/null +++ b/origin-src/transitfeed-1.2.5/test/data/unrecognized_columns/fare_rules.txt @@ -1,1 +1,6 @@ +fare_id,route_id,source_id,destination_id,contains_id +p,AB,,, +p,STBA,,, +p,BFC,,, +a,AAMV,,, --- /dev/null +++ b/origin-src/transitfeed-1.2.5/test/data/unrecognized_columns/frequencies.txt @@ -1,1 +1,12 @@ - +trip_id,start_time,end_time,headway_secs,superfluous +STBA,6:00:00,22:00:00,1800, +CITY1,6:00:00,7:59:59,1800, +CITY2,6:00:00,7:59:59,1800, +CITY1,8:00:00,9:59:59,600, +CITY2,8:00:00,9:59:59,600, +CITY1,10:00:00,15:59:59,1800, +CITY2,10:00:00,15:59:59,1800, +CITY1,16:00:00,18:59:59,600, +CITY2,16:00:00,18:59:59,600, +CITY1,19:00:00,22:00:00,1800, +CITY2,19:00:00,22:00:00,1800, --- /dev/null +++ b/origin-src/transitfeed-1.2.5/test/data/unrecognized_columns/routes.txt @@ -1,1 +1,6 @@ - +route_id,agency_id,route_short_name,route_long_name,route_desc,route_type,route_url,route_color,Route_Text_Color +AB,DTA,,Airport - Bullfrog,,3,,, +BFC,DTA,,Bullfrog - Furnace Creek Resort,,3,,, +STBA,DTA,,Stagecoach - Airport Shuttle,,3,,, +CITY,DTA,,City,,3,,, +AAMV,DTA,,Airport - Amargosa Valley,,3,,, --- /dev/null +++ b/origin-src/transitfeed-1.2.5/test/data/unrecognized_columns/stop_times.txt @@ -1,1 +1,30 @@ +trip_id,arrival_time,departure_time,stop_id,stop_sequence,stop_headsign,pickup_type,drop_off_time,shapedisttraveled +STBA,6:00:00,6:00:00,STAGECOACH,1,,,, +STBA,6:20:00,6:20:00,BEATTY_AIRPORT,2,,,, +CITY1,6:00:00,6:00:00,STAGECOACH,1,,,, +CITY1,6:05:00,6:07:00,NANAA,2,,,, +CITY1,6:12:00,6:14:00,NADAV,3,,,, +CITY1,6:19:00,6:21:00,DADAN,4,,,, +CITY1,6:26:00,6:28:00,EMSI,5,,,, +CITY2,6:28:00,6:30:00,EMSI,1,,,, +CITY2,6:35:00,6:37:00,DADAN,2,,,, +CITY2,6:42:00,6:44:00,NADAV,3,,,, +CITY2,6:49:00,6:51:00,NANAA,4,,,, +CITY2,6:56:00,6:58:00,STAGECOACH,5,,,, +AB1,8:00:00,8:00:00,BEATTY_AIRPORT,1,,,, +AB1,8:10:00,8:15:00,BULLFROG,2,,,, +AB2,12:05:00,12:05:00,BULLFROG,1,,,, +AB2,12:15:00,12:15:00,BEATTY_AIRPORT,2,,,, +BFC1,8:20:00,8:20:00,BULLFROG,1,,,, +BFC1,9:20:00,9:20:00,FUR_CREEK_RES,2,,,, +BFC2,11:00:00,11:00:00,FUR_CREEK_RES,1,,,, +BFC2,12:00:00,12:00:00,BULLFROG,2,,,, +AAMV1,8:00:00,8:00:00,BEATTY_AIRPORT,1,,,, +AAMV1,9:00:00,9:00:00,AMV,2,,,, +AAMV2,10:00:00,10:00:00,AMV,1,,,, +AAMV2,11:00:00,11:00:00,BEATTY_AIRPORT,2,,,, +AAMV3,13:00:00,13:00:00,BEATTY_AIRPORT,1,,,, +AAMV3,14:00:00,14:00:00,AMV,2,,,, +AAMV4,15:00:00,15:00:00,AMV,1,,,, +AAMV4,16:00:00,16:00:00,BEATTY_AIRPORT,2,,,, --- /dev/null +++ b/origin-src/transitfeed-1.2.5/test/data/unrecognized_columns/stops.txt @@ -1,1 +1,10 @@ - +stop_id,stop_name,stop_desc,stop_lat,stop_lon,zone_id,stop_uri +FUR_CREEK_RES,Furnace Creek Resort (Demo),,36.425288,-117.133162,, +BEATTY_AIRPORT,Nye County Airport (Demo),,36.868446,-116.784582,, +BULLFROG,Bullfrog (Demo),,36.88108,-116.81797,, +STAGECOACH,Stagecoach Hotel & Casino (Demo),,36.915682,-116.751677,, +NADAV,North Ave / D Ave N (Demo),,36.914893,-116.76821,, +NANAA,North Ave / N A Ave (Demo),,36.914944,-116.761472,, +DADAN,Doing Ave / D Ave N (Demo),,36.909489,-116.768242,, +EMSI,E Main St / S Irving St (Demo),,36.905697,-116.76218,, +AMV,Amargosa Valley (Demo),,36.641496,-116.40094,, --- /dev/null +++ b/origin-src/transitfeed-1.2.5/test/data/unrecognized_columns/transfers.txt @@ -1,1 +1,3 @@ +from_stop_id,to_stop_id,transfer_type,min_transfer_time,to_stop +NADAV,NANAA,3,, --- /dev/null +++ b/origin-src/transitfeed-1.2.5/test/data/unrecognized_columns/trips.txt @@ -1,1 +1,12 @@ - +route_id,service_id,trip_id,trip_headsign,direction_id,block_id,sharpe_id +AB,FULLW,AB1,to Bullfrog,0,1, +AB,FULLW,AB2,to Airport,1,2, +STBA,FULLW,STBA,Shuttle,,, +CITY,FULLW,CITY1,,0,, +CITY,FULLW,CITY2,,1,, +BFC,FULLW,BFC1,to Furnace Creek Resort,0,1, +BFC,FULLW,BFC2,to Bullfrog,1,2, +AAMV,WE,AAMV1,to Amargosa Valley,0,, +AAMV,WE,AAMV2,to Airport,1,, +AAMV,WE,AAMV3,to Amargosa Valley,0,, +AAMV,WE,AAMV4,to Airport,1,, --- /dev/null +++ b/origin-src/transitfeed-1.2.5/test/data/unused_stop/agency.txt @@ -1,1 +1,2 @@ - +agency_id,agency_name,agency_url,agency_timezone +DTA,Demo Transit Authority,http://google.com,America/Los_Angeles --- /dev/null +++ b/origin-src/transitfeed-1.2.5/test/data/unused_stop/calendar.txt @@ -1,1 +1,3 @@ - +service_id,monday,tuesday,wednesday,thursday,friday,saturday,sunday,start_date,end_date +FULLW,1,1,1,1,1,1,1,20070101,20101231 +WE,0,0,0,0,0,1,1,20070101,20101231 --- /dev/null +++ b/origin-src/transitfeed-1.2.5/test/data/unused_stop/calendar_dates.txt @@ -1,1 +1,2 @@ - +service_id,date,exception_type +FULLW,20070604,2 --- /dev/null +++ b/origin-src/transitfeed-1.2.5/test/data/unused_stop/fare_attributes.txt @@ -1,1 +1,4 @@ +fare_id,price,currency_type,payment_method,transfers,transfer_duration +p,1.25,USD,0,0, +a,5.25,USD,0,0, --- /dev/null +++ b/origin-src/transitfeed-1.2.5/test/data/unused_stop/fare_rules.txt @@ -1,1 +1,6 @@ +fare_id,route_id,origin_id,destination_id,contains_id +p,AB,,, +p,STBA,,, +p,BFC,,, +a,AAMV,,, --- /dev/null +++ b/origin-src/transitfeed-1.2.5/test/data/unused_stop/frequencies.txt @@ -1,1 +1,12 @@ - +trip_id,start_time,end_time,headway_secs +STBA,6:00:00,22:00:00,1800 +CITY1,6:00:00,7:59:59,1800 +CITY2,6:00:00,7:59:59,1800 +CITY1,8:00:00,9:59:59,600 +CITY2,8:00:00,9:59:59,600 +CITY1,10:00:00,15:59:59,1800 +CITY2,10:00:00,15:59:59,1800 +CITY1,16:00:00,18:59:59,600 +CITY2,16:00:00,18:59:59,600 +CITY1,19:00:00,22:00:00,1800 +CITY2,19:00:00,22:00:00,1800 --- /dev/null +++ b/origin-src/transitfeed-1.2.5/test/data/unused_stop/routes.txt @@ -1,1 +1,6 @@ - +route_id,agency_id,route_short_name,route_long_name,route_desc,route_type,route_url,route_color,route_text_color +AB,DTA,,Airport - Bullfrog,,3,,, +BFC,DTA,,Bullfrog - Furnace Creek Resort,,3,,, +STBA,DTA,,Stagecoach - Airport Shuttle,,3,,, +CITY,DTA,,City,,3,,, +AAMV,DTA,,Airport - Amargosa Valley,,3,,, --- /dev/null +++ b/origin-src/transitfeed-1.2.5/test/data/unused_stop/stop_times.txt @@ -1,1 +1,29 @@ - +trip_id,arrival_time,departure_time,stop_id,stop_sequence,stop_headsign,pickup_type,drop_off_type,shape_dist_traveled +STBA,6:00:00,6:00:00,STAGECOACH,1,,,, +STBA,6:20:00,6:20:00,BEATTY_AIRPORT,2,,,, +CITY1,6:00:00,6:00:00,STAGECOACH,1,,,, +CITY1,6:05:00,6:07:00,NANAA,2,,,, +CITY1,6:12:00,6:14:00,NADAV,3,,,, +CITY1,6:19:00,6:21:00,DADAN,4,,,, +CITY1,6:26:00,6:28:00,EMSI,5,,,, +CITY2,6:28:00,6:30:00,EMSI,1,,,, +CITY2,6:35:00,6:37:00,DADAN,2,,,, +CITY2,6:42:00,6:44:00,NADAV,3,,,, +CITY2,6:49:00,6:51:00,NANAA,4,,,, +CITY2,6:56:00,6:58:00,STAGECOACH,5,,,, +AB1,8:00:00,8:00:00,BEATTY_AIRPORT,1,,,, +AB1,8:10:00,8:15:00,BULLFROG,2,,,, +AB2,12:05:00,12:05:00,BULLFROG,1,,,, +AB2,12:15:00,12:15:00,BEATTY_AIRPORT,2,,,, +BFC1,8:20:00,8:20:00,BULLFROG,1,,,, +BFC1,9:20:00,9:20:00,FUR_CREEK_RES,2,,,, +BFC2,11:00:00,11:00:00,FUR_CREEK_RES,1,,,, +BFC2,12:00:00,12:00:00,BULLFROG,2,,,, +AAMV1,8:00:00,8:00:00,BEATTY_AIRPORT,1,,,, +AAMV1,9:00:00,9:00:00,AMV,2,,,, +AAMV2,10:00:00,10:00:00,AMV,1,,,, +AAMV2,11:00:00,11:00:00,BEATTY_AIRPORT,2,,,, +AAMV3,13:00:00,13:00:00,BEATTY_AIRPORT,1,,,, +AAMV3,14:00:00,14:00:00,AMV,2,,,, +AAMV4,15:00:00,15:00:00,AMV,1,,,, +AAMV4,16:00:00,16:00:00,BEATTY_AIRPORT,2,,,, --- /dev/null +++ b/origin-src/transitfeed-1.2.5/test/data/unused_stop/stops.txt @@ -1,1 +1,12 @@ +stop_id,stop_name,stop_desc,stop_lat,stop_lon,zone_id,stop_url +FUR_CREEK_RES,Furnace Creek Resort (Demo),,36.425288,-117.133162,, +BEATTY_AIRPORT,Nye County Airport (Demo),,36.868446,-116.784582,, +BULLFROG,Bullfrog (Demo),,36.88108,-116.81797,, +STAGECOACH,Stagecoach Hotel & Casino (Demo),,36.915682,-116.751677,, +BOGUS,Bogus Stop (Demo),,36.914682,-116.750677,, +NADAV,North Ave / D Ave N (Demo),,36.914893,-116.76821,, +NANAA,North Ave / N A Ave (Demo),,36.914944,-116.761472,, +DADAN,Doing Ave / D Ave N (Demo),,36.909489,-116.768242,, +EMSI,E Main St / S Irving St (Demo),,36.905697,-116.76218,, +AMV,Amargosa Valley (Demo),,36.641496,-116.40094,, --- /dev/null +++ b/origin-src/transitfeed-1.2.5/test/data/unused_stop/trips.txt @@ -1,1 +1,12 @@ - +route_id,service_id,trip_id,trip_headsign,direction_id,block_id,shape_id +AB,FULLW,AB1,to Bullfrog,0,1, +AB,FULLW,AB2,to Airport,1,2, +STBA,FULLW,STBA,Shuttle,,, +CITY,FULLW,CITY1,,0,, +CITY,FULLW,CITY2,,1,, +BFC,FULLW,BFC1,to Furnace Creek Resort,0,1, +BFC,FULLW,BFC2,to Bullfrog,1,2, +AAMV,WE,AAMV1,to Amargosa Valley,0,, +AAMV,WE,AAMV2,to Airport,1,, +AAMV,WE,AAMV3,to Amargosa Valley,0,, +AAMV,WE,AAMV4,to Airport,1,, --- /dev/null +++ b/origin-src/transitfeed-1.2.5/test/data/utf8bom/agency.txt @@ -1,1 +1,2 @@ - +agency_id,agency_name,agency_url,agency_timezone +DTA,Demo Transit Authority,http://google.com,America/Los_Angeles --- /dev/null +++ b/origin-src/transitfeed-1.2.5/test/data/utf8bom/calendar.txt @@ -1,1 +1,3 @@ - +service_id,monday,tuesday,wednesday,thursday,friday,saturday,sunday,start_date,end_date +FULLW,1,1,1,1,1,1,1,20070101,20101231 +WE,0,0,0,0,0,1,1,20070101,20101231 --- /dev/null +++ b/origin-src/transitfeed-1.2.5/test/data/utf8bom/calendar_dates.txt @@ -1,1 +1,2 @@ - +service_id,date,exception_type +FULLW,20070604,2 --- /dev/null +++ b/origin-src/transitfeed-1.2.5/test/data/utf8bom/fare_attributes.txt @@ -1,1 +1,4 @@ +fare_id,price,currency_type,payment_method,transfers,transfer_duration +p,1.25,USD,0,0, +a,5.25,USD,0,0, --- /dev/null +++ b/origin-src/transitfeed-1.2.5/test/data/utf8bom/fare_rules.txt @@ -1,1 +1,6 @@ +fare_id,route_id,origin_id,destination_id,contains_id +p,AB,,, +p,STBA,,, +p,BFC,,, +a,AAMV,,, --- /dev/null +++ b/origin-src/transitfeed-1.2.5/test/data/utf8bom/frequencies.txt @@ -1,1 +1,12 @@ - +trip_id,start_time,end_time,headway_secs +STBA,6:00:00,22:00:00,1800 +CITY1,6:00:00,7:59:59,1800 +CITY2,6:00:00,7:59:59,1800 +CITY1,8:00:00,9:59:59,600 +CITY2,8:00:00,9:59:59,600 +CITY1,10:00:00,15:59:59,1800 +CITY2,10:00:00,15:59:59,1800 +CITY1,16:00:00,18:59:59,600 +CITY2,16:00:00,18:59:59,600 +CITY1,19:00:00,22:00:00,1800 +CITY2,19:00:00,22:00:00,1800 --- /dev/null +++ b/origin-src/transitfeed-1.2.5/test/data/utf8bom/routes.txt @@ -1,1 +1,6 @@ - +route_id,agency_id,route_short_name,route_long_name,route_desc,route_type,route_url,route_color,route_text_color +AB,DTA,,Airport - Bullfrog,,3,,, +BFC,DTA,,Bullfrog - Furnace Creek Resort,,3,,, +STBA,DTA,,Stagecoach - Airport Shuttle,,3,,, +CITY,DTA,,City,,3,,, +AAMV,DTA,,Airport - Amargosa Valley,,3,,, --- /dev/null +++ b/origin-src/transitfeed-1.2.5/test/data/utf8bom/stop_times.txt @@ -1,1 +1,29 @@ - +trip_id,arrival_time,departure_time,stop_id,stop_sequence,stop_headsign,pickup_type,drop_off_type,shape_dist_traveled +STBA,6:00:00,6:00:00,STAGECOACH,1,,,, +STBA,6:20:00,6:20:00,BEATTY_AIRPORT,2,,,, +CITY1,6:00:00,6:00:00,STAGECOACH,1,,,, +CITY1,6:05:00,6:07:00,NANAA,2,,,, +CITY1,6:12:00,6:14:00,NADAV,3,,,, +CITY1,6:19:00,6:21:00,DADAN,4,,,, +CITY1,6:26:00,6:28:00,EMSI,5,,,, +CITY2,6:28:00,6:30:00,EMSI,1,,,, +CITY2,6:35:00,6:37:00,DADAN,2,,,, +CITY2,6:42:00,6:44:00,NADAV,3,,,, +CITY2,6:49:00,6:51:00,NANAA,4,,,, +CITY2,6:56:00,6:58:00,STAGECOACH,5,,,, +AB1,8:00:00,8:00:00,BEATTY_AIRPORT,1,,,, +AB1,8:10:00,8:15:00,BULLFROG,2,,,, +AB2,12:05:00,12:05:00,BULLFROG,1,,,, +AB2,12:15:00,12:15:00,BEATTY_AIRPORT,2,,,, +BFC1,8:20:00,8:20:00,BULLFROG,1,,,, +BFC1,9:20:00,9:20:00,FUR_CREEK_RES,2,,,, +BFC2,11:00:00,11:00:00,FUR_CREEK_RES,1,,,, +BFC2,12:00:00,12:00:00,BULLFROG,2,,,, +AAMV1,8:00:00,8:00:00,BEATTY_AIRPORT,1,,,, +AAMV1,9:00:00,9:00:00,AMV,2,,,, +AAMV2,10:00:00,10:00:00,AMV,1,,,, +AAMV2,11:00:00,11:00:00,BEATTY_AIRPORT,2,,,, +AAMV3,13:00:00,13:00:00,BEATTY_AIRPORT,1,,,, +AAMV3,14:00:00,14:00:00,AMV,2,,,, +AAMV4,15:00:00,15:00:00,AMV,1,,,, +AAMV4,16:00:00,16:00:00,BEATTY_AIRPORT,2,,,, --- /dev/null +++ b/origin-src/transitfeed-1.2.5/test/data/utf8bom/stops.txt @@ -1,1 +1,10 @@ - +stop_id,stop_name,stop_desc,stop_lat,stop_lon,zone_id,stop_url +FUR_CREEK_RES,Furnace Creek Resort (Demo),,36.425288,-117.133162,, +BEATTY_AIRPORT,Nye County Airport (Demo),,36.868446,-116.784582,, +BULLFROG,Bullfrog (Demo),,36.88108,-116.81797,, +STAGECOACH,Stagecoach Hotel & Casino (Demo),,36.915682,-116.751677,, +NADAV,North Ave / D Ave N (Demo),,36.914893,-116.76821,, +NANAA,North Ave / N A Ave (Demo),,36.914944,-116.761472,, +DADAN,Doing Ave / D Ave N (Demo),,36.909489,-116.768242,, +EMSI,E Main St / S Irving St (Demo),,36.905697,-116.76218,, +AMV,Amargosa Valley (Demo),,36.641496,-116.40094,, --- /dev/null +++ b/origin-src/transitfeed-1.2.5/test/data/utf8bom/trips.txt @@ -1,1 +1,12 @@ - +route_id,service_id,trip_id,trip_headsign,direction_id,block_id,shape_id +AB,FULLW,AB1,to Bullfrog,0,1, +AB,FULLW,AB2,to Airport,1,2, +STBA,FULLW,STBA,Shuttle,,, +CITY,FULLW,CITY1,,0,, +CITY,FULLW,CITY2,,1,, +BFC,FULLW,BFC1,to Furnace Creek Resort,0,1, +BFC,FULLW,BFC2,to Bullfrog,1,2, +AAMV,WE,AAMV1,to Amargosa Valley,0,, +AAMV,WE,AAMV2,to Airport,1,, +AAMV,WE,AAMV3,to Amargosa Valley,0,, +AAMV,WE,AAMV4,to Airport,1,, --- /dev/null +++ b/origin-src/transitfeed-1.2.5/test/testexamples.py @@ -1,1 +1,114 @@ +#!/usr/bin/python2.5 +# Test the examples to make sure they are not broken + +import os +import re +import transitfeed +import unittest +import urllib +import util + +class WikiExample(util.TempDirTestCaseBase): + # Download example from wiki and run it + def runTest(self): + wiki_source = urllib.urlopen( + 'http://googletransitdatafeed.googlecode.com/svn/wiki/TransitFeed.wiki' + ).read() + m = re.search(r'{{{(.*import transitfeed.*)}}}', wiki_source, re.DOTALL) + if not m: + raise Exception("Failed to find source code on wiki page") + wiki_code = m.group(1) + exec wiki_code + + +class shuttle_from_xmlfeed(util.TempDirTestCaseBase): + def runTest(self): + self.CheckCallWithPath( + [self.GetExamplePath('shuttle_from_xmlfeed.py'), + '--input', 'file:' + self.GetExamplePath('shuttle_from_xmlfeed.xml'), + '--output', 'shuttle-YYYYMMDD.zip', + # save the path of the dated output to tempfilepath + '--execute', 'echo %(path)s > outputpath']) + + dated_path = open('outputpath').read().strip() + self.assertTrue(re.match(r'shuttle-20\d\d[01]\d[0123]\d.zip$', dated_path)) + if not os.path.exists(dated_path): + raise Exception('did not create expected file') + + +class table(util.TempDirTestCaseBase): + def runTest(self): + self.CheckCallWithPath( + [self.GetExamplePath('table.py'), + '--input', self.GetExamplePath('table.txt'), + '--output', 'google_transit.zip']) + if not os.path.exists('google_transit.zip'): + raise Exception('should have created output') + + +class small_builder(util.TempDirTestCaseBase): + def runTest(self): + self.CheckCallWithPath( + [self.GetExamplePath('small_builder.py'), + '--output', 'google_transit.zip']) + if not os.path.exists('google_transit.zip'): + raise Exception('should have created output') + + +class google_random_queries(util.TempDirTestCaseBase): + def testNormalRun(self): + self.CheckCallWithPath( + [self.GetExamplePath('google_random_queries.py'), + '--output', 'queries.html', + '--limit', '5', + self.GetPath('test', 'data', 'good_feed')]) + if not os.path.exists('queries.html'): + raise Exception('should have created output') + + def testInvalidFeedStillWorks(self): + self.CheckCallWithPath( + [self.GetExamplePath('google_random_queries.py'), + '--output', 'queries.html', + '--limit', '5', + self.GetPath('test', 'data', 'invalid_route_agency')]) + if not os.path.exists('queries.html'): + raise Exception('should have created output') + + def testBadArgs(self): + self.CheckCallWithPath( + [self.GetExamplePath('google_random_queries.py'), + '--output', 'queries.html', + '--limit', '5'], + expected_retcode=2) + if os.path.exists('queries.html'): + raise Exception('should not have created output') + + +class filter_unused_stops(util.TempDirTestCaseBase): + def testNormalRun(self): + unused_stop_path = self.GetPath('test', 'data', 'unused_stop') + # Make sure load fails for input + problem_reporter = transitfeed.ExceptionProblemReporter(raise_warnings=True) + try: + transitfeed.Loader( + unused_stop_path, + problems=problem_reporter, extra_validation=True).Load() + self.fail('UnusedStop exception expected') + except transitfeed.UnusedStop, e: + pass + (stdout, stderr) = self.CheckCallWithPath( + [self.GetExamplePath('filter_unused_stops.py'), + '--list_removed', + unused_stop_path, 'output.zip']) + # Extra stop was listed on stdout + self.assertNotEqual(stdout.find('Bogus Stop'), -1) + # Make sure unused stop was removed and another stop wasn't + schedule = transitfeed.Loader( + 'output.zip', problems=problem_reporter, extra_validation=True).Load() + schedule.GetStop('STAGECOACH') + + +if __name__ == '__main__': + unittest.main() + --- /dev/null +++ b/origin-src/transitfeed-1.2.5/test/testfeedvalidator.py @@ -1,1 +1,442 @@ - +#!/usr/bin/python2.5 + +# Copyright (C) 2007 Google Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# Smoke tests feed validator. Make sure it runs and returns the right things +# for a valid feed and a feed with errors. + +import datetime +import feedvalidator +import os.path +import re +import StringIO +import transitfeed +import unittest +from urllib2 import HTTPError, URLError +import urllib2 +import util +import zipfile + + +class FullTests(util.TempDirTestCaseBase): + def testGoodFeed(self): + (out, err) = self.CheckCallWithPath( + [self.GetPath('feedvalidator.py'), '-n', '--latest_version', + transitfeed.__version__, self.GetPath('test', 'data', 'good_feed')]) + self.assertTrue(re.search(r'feed validated successfully', out)) + self.assertFalse(re.search(r'ERROR', out)) + htmlout = open('validation-results.html').read() + self.assertTrue(re.search(r'feed validated successfully', htmlout)) + self.assertFalse(re.search(r'ERROR', htmlout)) + self.assertFalse(os.path.exists('transitfeedcrash.txt')) + + def testGoodFeedConsoleOutput(self): + (out, err) = self.CheckCallWithPath( + [self.GetPath('feedvalidator.py'), '-n', '--latest_version', + transitfeed.__version__, + '--output=CONSOLE', self.GetPath('test', 'data', 'good_feed')]) + self.assertTrue(re.search(r'feed validated successfully', out)) + self.assertFalse(re.search(r'ERROR', out)) + self.assertFalse(os.path.exists('validation-results.html')) + self.assertFalse(os.path.exists('transitfeedcrash.txt')) + + def testMissingStops(self): + (out, err) = self.CheckCallWithPath( + [self.GetPath('feedvalidator.py'), '-n', '--latest_version', + transitfeed.__version__, + self.GetPath('test', 'data', 'missing_stops')], + expected_retcode=1) + self.assertTrue(re.search(r'ERROR', out)) + self.assertFalse(re.search(r'feed validated successfully', out)) + htmlout = open('validation-results.html').read() + self.assertTrue(re.search(r'Invalid value BEATTY_AIRPORT', htmlout)) + self.assertFalse(re.search(r'feed validated successfully', htmlout)) + self.assertFalse(os.path.exists('transitfeedcrash.txt')) + + def testMissingStopsConsoleOutput(self): + (out, err) = self.CheckCallWithPath( + [self.GetPath('feedvalidator.py'), '-n', '-o', 'console', + '--latest_version', transitfeed.__version__, + self.GetPath('test', 'data', 'missing_stops')], + expected_retcode=1) + self.assertTrue(re.search(r'ERROR', out)) + self.assertFalse(re.search(r'feed validated successfully', out)) + self.assertTrue(re.search(r'Invalid value BEATTY_AIRPORT', out)) + self.assertFalse(os.path.exists('validation-results.html')) + self.assertFalse(os.path.exists('transitfeedcrash.txt')) + + def testLimitedErrors(self): + (out, err) = self.CheckCallWithPath( + [self.GetPath('feedvalidator.py'), '-l', '2', '-n', + '--latest_version', transitfeed.__version__, + self.GetPath('test', 'data', 'missing_stops')], + expected_retcode=1) + self.assertTrue(re.search(r'ERROR', out)) + self.assertFalse(re.search(r'feed validated successfully', out)) + htmlout = open('validation-results.html').read() + self.assertEquals(2, len(re.findall(r'class="problem">stop_id<', htmlout))) + self.assertFalse(os.path.exists('transitfeedcrash.txt')) + + def testBadDateFormat(self): + (out, err) = self.CheckCallWithPath( + [self.GetPath('feedvalidator.py'), '-n', '--latest_version', + transitfeed.__version__, + self.GetPath('test', 'data', 'bad_date_format')], + expected_retcode=1) + self.assertTrue(re.search(r'ERROR', out)) + self.assertFalse(re.search(r'feed validated successfully', out)) + htmlout = open('validation-results.html').read() + self.assertTrue(re.search(r'in field start_date', htmlout)) + self.assertTrue(re.search(r'in field date', htmlout)) + self.assertFalse(re.search(r'feed validated successfully', htmlout)) + self.assertFalse(os.path.exists('transitfeedcrash.txt')) + + def testBadUtf8(self): + (out, err) = self.CheckCallWithPath( + [self.GetPath('feedvalidator.py'), '-n', '--latest_version', + transitfeed.__version__, self.GetPath('test', 'data', 'bad_utf8')], + expected_retcode=1) + self.assertTrue(re.search(r'ERROR', out)) + self.assertFalse(re.search(r'feed validated successfully', out)) + htmlout = open('validation-results.html').read() + self.assertTrue(re.search(r'Unicode error', htmlout)) + self.assertFalse(re.search(r'feed validated successfully', htmlout)) + self.assertFalse(os.path.exists('transitfeedcrash.txt')) + + def testFileNotFound(self): + (out, err) = self.CheckCallWithPath( + [self.GetPath('feedvalidator.py'), '-n', '--latest_version', + transitfeed.__version__, 'file-not-found.zip'], + expected_retcode=1) + self.assertFalse(os.path.exists('transitfeedcrash.txt')) + + def testBadOutputPath(self): + (out, err) = self.CheckCallWithPath( + [self.GetPath('feedvalidator.py'), '-n', '--latest_version', + transitfeed.__version__, '-o', 'path/does/not/exist.html', + self.GetPath('test', 'data', 'good_feed')], + expected_retcode=2) + self.assertFalse(os.path.exists('transitfeedcrash.txt')) + + def testCrashHandler(self): + (out, err) = self.CheckCallWithPath( + [self.GetPath('feedvalidator.py'), '-n', '--latest_version', + transitfeed.__version__, 'IWantMyvalidation-crash.txt'], + expected_retcode=127) + self.assertTrue(re.search(r'Yikes', out)) + self.assertFalse(re.search(r'feed validated successfully', out)) + crashout = open('transitfeedcrash.txt').read() + self.assertTrue(re.search(r'For testing the feed validator crash handler', + crashout)) + + def testCheckVersionIsRun(self): + (out, err) = self.CheckCallWithPath( + [self.GetPath('feedvalidator.py'), '-n', '--latest_version', + '100.100.100', self.GetPath('test', 'data', 'good_feed')]) + self.assertTrue(re.search(r'feed validated successfully', out)) + self.assertTrue(re.search(r'A new version 100.100.100', out)) + htmlout = open('validation-results.html').read() + self.assertTrue(re.search(r'A new version 100.100.100', htmlout)) + self.assertFalse(re.search(r'ERROR', htmlout)) + self.assertFalse(os.path.exists('transitfeedcrash.txt')) + + def testCheckVersionIsRunConsoleOutput(self): + (out, err) = self.CheckCallWithPath( + [self.GetPath('feedvalidator.py'), '-n', '-o', 'console', + '--latest_version=100.100.100', + self.GetPath('test', 'data', 'good_feed')]) + self.assertTrue(re.search(r'feed validated successfully', out)) + self.assertTrue(re.search(r'A new version 100.100.100', out)) + self.assertFalse(os.path.exists('validation-results.html')) + self.assertFalse(os.path.exists('transitfeedcrash.txt')) + + def testUsage(self): + (out, err) = self.CheckCallWithPath( + [self.GetPath('feedvalidator.py'), '--invalid_opt'], expected_retcode=2) + self.assertMatchesRegex(r'[Uu]sage: feedvalidator.py \[options\]', err) + self.assertMatchesRegex(r'wiki/FeedValidator', err) + self.assertMatchesRegex(r'--output', err) # output includes all usage info + self.assertFalse(os.path.exists('transitfeedcrash.txt')) + self.assertFalse(os.path.exists('validation-results.html')) + + +# Regression tests to ensure that CalendarSummary works properly +# even when the feed starts in the future or expires in less than +# 60 days +# See http://code.google.com/p/googletransitdatafeed/issues/detail?id=204 +class CalendarSummaryTestCase(unittest.TestCase): + + # Test feeds starting in the future + def testFutureFeedDoesNotCrashCalendarSummary(self): + today = datetime.date.today() + start_date = today + datetime.timedelta(days=20) + end_date = today + datetime.timedelta(days=80) + + schedule = transitfeed.Schedule() + service_period = schedule.GetDefaultServicePeriod() + + service_period.SetStartDate(start_date.strftime("%Y%m%d")) + service_period.SetEndDate(end_date.strftime("%Y%m%d")) + service_period.SetWeekdayService(True) + + result = feedvalidator.CalendarSummary(schedule) + + self.assertEquals(0, result['max_trips']) + self.assertEquals(0, result['min_trips']) + self.assertTrue(re.search("40 service dates", result['max_trips_dates'])) + + # Test feeds ending in less than 60 days + def testShortFeedDoesNotCrashCalendarSummary(self): + start_date = datetime.date.today() + end_date = start_date + datetime.timedelta(days=15) + + schedule = transitfeed.Schedule() + service_period = schedule.GetDefaultServicePeriod() + + service_period.SetStartDate(start_date.strftime("%Y%m%d")) + service_period.SetEndDate(end_date.strftime("%Y%m%d")) + service_period.SetWeekdayService(True) + + result = feedvalidator.CalendarSummary(schedule) + + self.assertEquals(0, result['max_trips']) + self.assertEquals(0, result['min_trips']) + self.assertTrue(re.search("15 service dates", result['max_trips_dates'])) + + # Test feeds starting in the future *and* ending in less than 60 days + def testFutureAndShortFeedDoesNotCrashCalendarSummary(self): + today = datetime.date.today() + start_date = today + datetime.timedelta(days=2) + end_date = today + datetime.timedelta(days=3) + + schedule = transitfeed.Schedule() + service_period = schedule.GetDefaultServicePeriod() + + service_period.SetStartDate(start_date.strftime("%Y%m%d")) + service_period.SetEndDate(end_date.strftime("%Y%m%d")) + service_period.SetWeekdayService(True) + + result = feedvalidator.CalendarSummary(schedule) + + self.assertEquals(0, result['max_trips']) + self.assertEquals(0, result['min_trips']) + self.assertTrue(re.search("1 service date", result['max_trips_dates'])) + + # Test feeds without service days + def testFeedWithNoDaysDoesNotCrashCalendarSummary(self): + schedule = transitfeed.Schedule() + result = feedvalidator.CalendarSummary(schedule) + + self.assertEquals({}, result) + + +class MockOptions: + """Pretend to be an optparse options object suitable for testing.""" + def __init__(self): + self.limit_per_type = 5 + self.memory_db = True + self.check_duplicate_trips = True + self.latest_version = transitfeed.__version__ + self.output = 'fake-filename.zip' + self.manual_entry = False + self.service_gap_interval = None + + +class FeedValidatorTestCase(util.TempDirTestCaseBase): + def testBadEolContext(self): + """Make sure the filename is included in the report of a bad eol.""" + zipfile_mem = StringIO.StringIO(open( + self.GetPath('test', 'data', 'good_feed.zip'), 'rb').read()) + zip = zipfile.ZipFile(zipfile_mem, 'a') + routes_txt = zip.read('routes.txt') + # routes_txt_modified is invalid because the first line ends with \r\n. + routes_txt_modified = routes_txt.replace('\n', '\r\n', 1) + self.assertNotEquals(routes_txt_modified, routes_txt) + zip.writestr('routes.txt', routes_txt_modified) + zip.close() + options = MockOptions() + output_file = StringIO.StringIO() + feedvalidator.RunValidationOutputToFile(zipfile_mem, options, output_file) + self.assertMatchesRegex("routes.txt", output_file.getvalue()) + + +class LimitPerTypeProblemReporterTestCase(unittest.TestCase): + def assertProblemsAttribute(self, problem_type, class_name, attribute_name, + expected): + """Join the value of each exception's attribute_name in order.""" + problem_attribute_list = [] + for e in self.problems.ProblemList(problem_type, class_name).problems: + problem_attribute_list.append(getattr(e, attribute_name)) + self.assertEquals(expected, " ".join(problem_attribute_list)) + + def testLimitOtherProblems(self): + """The first N of each type should be kept.""" + self.problems = feedvalidator.LimitPerTypeProblemReporter(2) + self.problems.OtherProblem("e1", type=transitfeed.TYPE_ERROR) + self.problems.OtherProblem("w1", type=transitfeed.TYPE_WARNING) + self.problems.OtherProblem("e2", type=transitfeed.TYPE_ERROR) + self.problems.OtherProblem("e3", type=transitfeed.TYPE_ERROR) + self.problems.OtherProblem("w2", type=transitfeed.TYPE_WARNING) + self.assertEquals(2, self.problems.WarningCount()) + self.assertEquals(3, self.problems.ErrorCount()) + + # These are BoundedProblemList objects + warning_bounded_list = self.problems.ProblemList( + transitfeed.TYPE_WARNING, "OtherProblem") + error_bounded_list = self.problems.ProblemList( + transitfeed.TYPE_ERROR, "OtherProblem") + + self.assertEquals(2, warning_bounded_list.count) + self.assertEquals(3, error_bounded_list.count) + + self.assertEquals(0, warning_bounded_list.dropped_count) + self.assertEquals(1, error_bounded_list.dropped_count) + + self.assertProblemsAttribute(transitfeed.TYPE_ERROR, "OtherProblem", + "description", "e1 e2") + self.assertProblemsAttribute(transitfeed.TYPE_WARNING, "OtherProblem", + "description", "w1 w2") + + def testKeepUnsorted(self): + """An imperfect test that insort triggers ExceptionWithContext.__cmp__.""" + # If ExceptionWithContext.__cmp__ doesn't trigger TypeError in + # bisect.insort then the default comparison of object id will be used. The + # id values tend to be given out in order of creation so call + # problems._Report with objects in a different order. This test should + # break if ExceptionWithContext.__cmp__ is removed or changed to return 0 + # or cmp(id(self), id(y)). + exceptions = [] + for i in range(20): + exceptions.append(transitfeed.OtherProblem(description="e%i" % i)) + exceptions = exceptions[10:] + exceptions[:10] + self.problems = feedvalidator.LimitPerTypeProblemReporter(3) + for e in exceptions: + self.problems._Report(e) + + self.assertEquals(0, self.problems.WarningCount()) + self.assertEquals(20, self.problems.ErrorCount()) + + bounded_list = self.problems.ProblemList( + transitfeed.TYPE_ERROR, "OtherProblem") + self.assertEquals(20, bounded_list.count) + self.assertEquals(17, bounded_list.dropped_count) + self.assertProblemsAttribute(transitfeed.TYPE_ERROR, "OtherProblem", + "description", "e10 e11 e12") + + def testLimitSortedTooFastTravel(self): + """Sort by decreasing distance, keeping the N greatest.""" + self.problems = feedvalidator.LimitPerTypeProblemReporter(3) + self.problems.TooFastTravel("t1", "prev stop", "next stop", 11230.4, 5, + None) + self.problems.TooFastTravel("t2", "prev stop", "next stop", 1120.4, 5, None) + self.problems.TooFastTravel("t3", "prev stop", "next stop", 1130.4, 5, None) + self.problems.TooFastTravel("t4", "prev stop", "next stop", 1230.4, 5, None) + self.assertEquals(0, self.problems.WarningCount()) + self.assertEquals(4, self.problems.ErrorCount()) + self.assertProblemsAttribute(transitfeed.TYPE_ERROR, "TooFastTravel", + "trip_id", "t1 t4 t3") + + def testLimitSortedStopTooFarFromParentStation(self): + """Sort by decreasing distance, keeping the N greatest.""" + self.problems = feedvalidator.LimitPerTypeProblemReporter(3) + for i, distance in enumerate((1000, 3002.0, 1500, 2434.1, 5023.21)): + self.problems.StopTooFarFromParentStation( + "s%d" % i, "S %d" % i, "p%d" % i, "P %d" % i, distance) + self.assertEquals(5, self.problems.WarningCount()) + self.assertEquals(0, self.problems.ErrorCount()) + self.assertProblemsAttribute(transitfeed.TYPE_WARNING, + "StopTooFarFromParentStation", "stop_id", "s4 s1 s3") + + def testLimitSortedStopsTooClose(self): + """Sort by increasing distance, keeping the N closest.""" + self.problems = feedvalidator.LimitPerTypeProblemReporter(3) + for i, distance in enumerate((4.0, 3.0, 2.5, 2.2, 1.0, 0.0)): + self.problems.StopsTooClose( + "Sa %d" % i, "sa%d" % i, "Sb %d" % i, "sb%d" % i, distance) + self.assertEquals(6, self.problems.WarningCount()) + self.assertEquals(0, self.problems.ErrorCount()) + self.assertProblemsAttribute(transitfeed.TYPE_WARNING, + "StopsTooClose", "stop_id_a", "sa5 sa4 sa3") + + +class CheckVersionTestCase(util.TempDirTestCaseBase): + def setUp(self): + self.mock = MockURLOpen() + + def tearDown(self): + self.mock = None + feedvalidator.urlopen = urllib2.urlopen + + def testAssignedDifferentVersion(self): + problems = feedvalidator.CheckVersion('100.100.100') + self.assertTrue(re.search(r'A new version 100.100.100', problems)) + + def testAssignedSameVersion(self): + problems = feedvalidator.CheckVersion(transitfeed.__version__) + self.assertEquals(problems, None) + + def testGetCorrectReturns(self): + feedvalidator.urlopen = self.mock.mockedConnectSuccess + problems = feedvalidator.CheckVersion() + self.assertTrue(re.search(r'A new version 100.0.1', problems)) + + def testPageNotFound(self): + feedvalidator.urlopen = self.mock.mockedPageNotFound + problems = feedvalidator.CheckVersion() + self.assertTrue(re.search(r'The server couldn\'t', problems)) + self.assertTrue(re.search(r'Error code: 404', problems)) + + def testConnectionTimeOut(self): + feedvalidator.urlopen = self.mock.mockedConnectionTimeOut + problems = feedvalidator.CheckVersion() + self.assertTrue(re.search(r'We failed to reach', problems)) + self.assertTrue(re.search(r'Reason: Connection timed', problems)) + + def testGetAddrInfoFailed(self): + feedvalidator.urlopen = self.mock.mockedGetAddrInfoFailed + problems = feedvalidator.CheckVersion() + self.assertTrue(re.search(r'We failed to reach', problems)) + self.assertTrue(re.search(r'Reason: Getaddrinfo failed', problems)) + + def testEmptyIsReturned(self): + feedvalidator.urlopen = self.mock.mockedEmptyIsReturned + problems = feedvalidator.CheckVersion() + self.assertTrue(re.search(r'We had trouble parsing', problems)) + + +class MockURLOpen: + """Pretend to be a urllib2.urlopen suitable for testing.""" + def mockedConnectSuccess(self, request): + return StringIO.StringIO('
  • transitfeed-' + '1.0.0/
  • ' + 'transitfeed-100.0.1/
  • ') + + def mockedPageNotFound(self, request): + raise HTTPError(request.get_full_url(), 404, 'Not Found', + request.header_items(), None) + + def mockedConnectionTimeOut(self, request): + raise URLError('Connection timed out') + + def mockedGetAddrInfoFailed(self, request): + raise URLError('Getaddrinfo failed') + + def mockedEmptyIsReturned(self, request): + return StringIO.StringIO() + + +if __name__ == '__main__': + unittest.main() + --- /dev/null +++ b/origin-src/transitfeed-1.2.5/test/testkmlparser.py @@ -1,1 +1,89 @@ +#!/usr/bin/python2.5 +# Copyright (C) 2007 Google Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# Unit tests for the kmlparser module. + +import kmlparser +import os.path +import shutil +from StringIO import StringIO +import transitfeed +import unittest +import util + + +class TestStopsParsing(util.GetPathTestCase): + def testSingleStop(self): + feed = transitfeed.Schedule() + kmlFile = self.GetTestDataPath('one_stop.kml') + kmlparser.KmlParser().Parse(kmlFile, feed) + stops = feed.GetStopList() + self.assertEqual(1, len(stops)) + stop = stops[0] + self.assertEqual(u'Stop Name', stop.stop_name) + self.assertAlmostEqual(-93.239037, stop.stop_lon) + self.assertAlmostEqual(44.854164, stop.stop_lat) + write_output = StringIO() + feed.WriteGoogleTransitFeed(write_output) + + def testSingleShape(self): + feed = transitfeed.Schedule() + kmlFile = self.GetTestDataPath('one_line.kml') + kmlparser.KmlParser().Parse(kmlFile, feed) + shapes = feed.GetShapeList() + self.assertEqual(1, len(shapes)) + shape = shapes[0] + self.assertEqual(3, len(shape.points)) + self.assertAlmostEqual(44.854240, shape.points[0][0]) + self.assertAlmostEqual(-93.238861, shape.points[0][1]) + self.assertAlmostEqual(44.853081, shape.points[1][0]) + self.assertAlmostEqual(-93.238708, shape.points[1][1]) + self.assertAlmostEqual(44.852638, shape.points[2][0]) + self.assertAlmostEqual(-93.237923, shape.points[2][1]) + write_output = StringIO() + feed.WriteGoogleTransitFeed(write_output) + + +class FullTests(util.TempDirTestCaseBase): + def testNormalRun(self): + shutil.copyfile(self.GetTestDataPath('one_stop.kml'), 'one_stop.kml') + (out, err) = self.CheckCallWithPath( + [self.GetPath('kmlparser.py'), 'one_stop.kml', 'one_stop.zip']) + # There will be lots of problems, but ignore them + problems = util.RecordingProblemReporter(self) + schedule = transitfeed.Loader('one_stop.zip', problems=problems).Load() + self.assertEquals(len(schedule.GetStopList()), 1) + self.assertFalse(os.path.exists('transitfeedcrash.txt')) + + def testCommandLineError(self): + (out, err) = self.CheckCallWithPath([self.GetPath('kmlparser.py')], + expected_retcode=2) + self.assertMatchesRegex(r'did not provide .+ arguments', err) + self.assertMatchesRegex(r'[Uu]sage:', err) + self.assertFalse(os.path.exists('transitfeedcrash.txt')) + + def testCrashHandler(self): + (out, err) = self.CheckCallWithPath( + [self.GetPath('kmlparser.py'), 'IWantMyCrash', 'output.zip'], + stdin_str="\n", expected_retcode=127) + self.assertMatchesRegex(r'Yikes', out) + crashout = open('transitfeedcrash.txt').read() + self.assertMatchesRegex(r'For testCrashHandler', crashout) + + +if __name__ == '__main__': + unittest.main() + --- /dev/null +++ b/origin-src/transitfeed-1.2.5/test/testkmlwriter.py @@ -1,1 +1,394 @@ - +#!/usr/bin/python2.4 +# +# Copyright 2008 Google Inc. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +"""Unit tests for the kmlwriter module.""" + +import os +import StringIO +import tempfile +import unittest +import kmlparser +import kmlwriter +import transitfeed +import util + +try: + import xml.etree.ElementTree as ET # python 2.5 +except ImportError, e: + import elementtree.ElementTree as ET # older pythons + + +def DataPath(path): + """Return the path to a given file in the test data directory. + + Args: + path: The path relative to the test data directory. + + Returns: + The absolute path. + """ + here = os.path.dirname(__file__) + return os.path.join(here, 'data', path) + + +def _ElementToString(root): + """Returns the node as an XML string. + + Args: + root: The ElementTree.Element instance. + + Returns: + The XML string. + """ + output = StringIO.StringIO() + ET.ElementTree(root).write(output, 'utf-8') + return output.getvalue() + + +class TestKMLStopsRoundtrip(unittest.TestCase): + """Checks to see whether all stops are preserved when going to and from KML. + """ + + def setUp(self): + fd, self.kml_output = tempfile.mkstemp('kml') + os.close(fd) + + def tearDown(self): + os.remove(self.kml_output) + + def runTest(self): + gtfs_input = DataPath('good_feed.zip') + feed1 = transitfeed.Loader(gtfs_input).Load() + kmlwriter.KMLWriter().Write(feed1, self.kml_output) + feed2 = transitfeed.Schedule() + kmlparser.KmlParser().Parse(self.kml_output, feed2) + + stop_name_mapper = lambda x: x.stop_name + + stops1 = set(map(stop_name_mapper, feed1.GetStopList())) + stops2 = set(map(stop_name_mapper, feed2.GetStopList())) + + self.assertEqual(stops1, stops2) + + +class TestKMLGeneratorMethods(unittest.TestCase): + """Tests the various KML element creation methods of KMLWriter.""" + + def setUp(self): + self.kmlwriter = kmlwriter.KMLWriter() + self.parent = ET.Element('parent') + + def testCreateFolderVisible(self): + element = self.kmlwriter._CreateFolder(self.parent, 'folder_name') + self.assertEqual(_ElementToString(element), + 'folder_name') + + def testCreateFolderNotVisible(self): + element = self.kmlwriter._CreateFolder(self.parent, 'folder_name', + visible=False) + self.assertEqual(_ElementToString(element), + 'folder_name' + '0') + + def testCreateFolderWithDescription(self): + element = self.kmlwriter._CreateFolder(self.parent, 'folder_name', + description='folder_desc') + self.assertEqual(_ElementToString(element), + 'folder_name' + 'folder_desc') + + def testCreatePlacemark(self): + element = self.kmlwriter._CreatePlacemark(self.parent, 'abcdef') + self.assertEqual(_ElementToString(element), + 'abcdef') + + def testCreatePlacemarkWithStyle(self): + element = self.kmlwriter._CreatePlacemark(self.parent, 'abcdef', + style_id='ghijkl') + self.assertEqual(_ElementToString(element), + 'abcdef' + '#ghijkl') + + def testCreatePlacemarkNotVisible(self): + element = self.kmlwriter._CreatePlacemark(self.parent, 'abcdef', + visible=False) + self.assertEqual(_ElementToString(element), + 'abcdef' + '0') + + def testCreatePlacemarkWithDescription(self): + element = self.kmlwriter._CreatePlacemark(self.parent, 'abcdef', + description='ghijkl') + self.assertEqual(_ElementToString(element), + 'abcdef' + 'ghijkl') + + def testCreateLineString(self): + coord_list = [(2.0, 1.0), (4.0, 3.0), (6.0, 5.0)] + element = self.kmlwriter._CreateLineString(self.parent, coord_list) + self.assertEqual(_ElementToString(element), + '1' + '%f,%f %f,%f %f,%f' + '' % (2.0, 1.0, 4.0, 3.0, 6.0, 5.0)) + + def testCreateLineStringWithAltitude(self): + coord_list = [(2.0, 1.0, 10), (4.0, 3.0, 20), (6.0, 5.0, 30.0)] + element = self.kmlwriter._CreateLineString(self.parent, coord_list) + self.assertEqual(_ElementToString(element), + '1' + 'absolute' + '%f,%f,%f %f,%f,%f %f,%f,%f' + '' % + (2.0, 1.0, 10.0, 4.0, 3.0, 20.0, 6.0, 5.0, 30.0)) + + def testCreateLineStringForShape(self): + shape = transitfeed.Shape('shape') + shape.AddPoint(1.0, 1.0) + shape.AddPoint(2.0, 4.0) + shape.AddPoint(3.0, 9.0) + element = self.kmlwriter._CreateLineStringForShape(self.parent, shape) + self.assertEqual(_ElementToString(element), + '1' + '%f,%f %f,%f %f,%f' + '' % (1.0, 1.0, 4.0, 2.0, 9.0, 3.0)) + + +class TestRouteKML(unittest.TestCase): + """Tests the routes folder KML generation methods of KMLWriter.""" + + def setUp(self): + self.feed = transitfeed.Loader(DataPath('flatten_feed')).Load() + self.kmlwriter = kmlwriter.KMLWriter() + self.parent = ET.Element('parent') + + def testCreateRoutePatternsFolderNoPatterns(self): + folder = self.kmlwriter._CreateRoutePatternsFolder( + self.parent, self.feed.GetRoute('route_7')) + self.assert_(folder is None) + + def testCreateRoutePatternsFolderOnePattern(self): + folder = self.kmlwriter._CreateRoutePatternsFolder( + self.parent, self.feed.GetRoute('route_1')) + placemarks = folder.findall('Placemark') + self.assertEquals(len(placemarks), 1) + + def testCreateRoutePatternsFolderTwoPatterns(self): + folder = self.kmlwriter._CreateRoutePatternsFolder( + self.parent, self.feed.GetRoute('route_3')) + placemarks = folder.findall('Placemark') + self.assertEquals(len(placemarks), 2) + + def testCreateRoutePatternFolderTwoEqualPatterns(self): + folder = self.kmlwriter._CreateRoutePatternsFolder( + self.parent, self.feed.GetRoute('route_4')) + placemarks = folder.findall('Placemark') + self.assertEquals(len(placemarks), 1) + + def testCreateRouteShapesFolderOneTripOneShape(self): + folder = self.kmlwriter._CreateRouteShapesFolder( + self.feed, self.parent, self.feed.GetRoute('route_1')) + self.assertEqual(len(folder.findall('Placemark')), 1) + + def testCreateRouteShapesFolderTwoTripsTwoShapes(self): + folder = self.kmlwriter._CreateRouteShapesFolder( + self.feed, self.parent, self.feed.GetRoute('route_2')) + self.assertEqual(len(folder.findall('Placemark')), 2) + + def testCreateRouteShapesFolderTwoTripsOneShape(self): + folder = self.kmlwriter._CreateRouteShapesFolder( + self.feed, self.parent, self.feed.GetRoute('route_3')) + self.assertEqual(len(folder.findall('Placemark')), 1) + + def testCreateRouteShapesFolderTwoTripsNoShapes(self): + folder = self.kmlwriter._CreateRouteShapesFolder( + self.feed, self.parent, self.feed.GetRoute('route_4')) + self.assert_(folder is None) + + def assertRouteFolderContainsTrips(self, tripids, folder): + """Assert that the route folder contains exactly tripids""" + actual_tripds = set() + for placemark in folder.findall('Placemark'): + actual_tripds.add(placemark.find('name').text) + self.assertEquals(set(tripids), actual_tripds) + + def testCreateTripsFolderForRouteTwoTrips(self): + route = self.feed.GetRoute('route_2') + folder = self.kmlwriter._CreateRouteTripsFolder(self.parent, route) + self.assertRouteFolderContainsTrips(['route_2_1', 'route_2_2'], folder) + + def testCreateTripsFolderForRouteDateFilterNone(self): + self.kmlwriter.date_filter = None + route = self.feed.GetRoute('route_8') + folder = self.kmlwriter._CreateRouteTripsFolder(self.parent, route) + self.assertRouteFolderContainsTrips(['route_8_1', 'route_8_2'], folder) + + def testCreateTripsFolderForRouteDateFilterSet(self): + self.kmlwriter.date_filter = '20070604' + route = self.feed.GetRoute('route_8') + folder = self.kmlwriter._CreateRouteTripsFolder(self.parent, route) + self.assertRouteFolderContainsTrips(['route_8_2'], folder) + + def _GetTripPlacemark(self, route_folder, trip_name): + for trip_placemark in route_folder.findall('Placemark'): + if trip_placemark.find('name').text == trip_name: + return trip_placemark + + def testCreateRouteTripsFolderAltitude0(self): + self.kmlwriter.altitude_per_sec = 0.0 + folder = self.kmlwriter._CreateRouteTripsFolder( + self.parent, self.feed.GetRoute('route_4')) + trip_placemark = self._GetTripPlacemark(folder, 'route_4_1') + self.assertEqual(_ElementToString(trip_placemark.find('LineString')), + '1' + '-117.133162,36.425288 ' + '-116.784582,36.868446 ' + '-116.817970,36.881080') + + def testCreateRouteTripsFolderAltitude1(self): + self.kmlwriter.altitude_per_sec = 0.5 + folder = self.kmlwriter._CreateRouteTripsFolder( + self.parent, self.feed.GetRoute('route_4')) + trip_placemark = self._GetTripPlacemark(folder, 'route_4_1') + self.assertEqual(_ElementToString(trip_placemark.find('LineString')), + '1' + 'absolute' + '-117.133162,36.425288,3600.000000 ' + '-116.784582,36.868446,5400.000000 ' + '-116.817970,36.881080,7200.000000' + '') + + def testCreateRouteTripsFolderNoTrips(self): + folder = self.kmlwriter._CreateRouteTripsFolder( + self.parent, self.feed.GetRoute('route_7')) + self.assert_(folder is None) + + def testCreateRoutesFolderNoRoutes(self): + schedule = transitfeed.Schedule() + folder = self.kmlwriter._CreateRoutesFolder(schedule, self.parent) + self.assert_(folder is None) + + def testCreateRoutesFolderNoRoutesWithRouteType(self): + folder = self.kmlwriter._CreateRoutesFolder(self.feed, self.parent, 999) + self.assert_(folder is None) + + def _TestCreateRoutesFolder(self, show_trips): + self.kmlwriter.show_trips = show_trips + folder = self.kmlwriter._CreateRoutesFolder(self.feed, self.parent) + self.assertEquals(folder.tag, 'Folder') + styles = self.parent.findall('Style') + self.assertEquals(len(styles), len(self.feed.GetRouteList())) + route_folders = folder.findall('Folder') + self.assertEquals(len(route_folders), len(self.feed.GetRouteList())) + + def testCreateRoutesFolder(self): + self._TestCreateRoutesFolder(False) + + def testCreateRoutesFolderShowTrips(self): + self._TestCreateRoutesFolder(True) + + def testCreateRoutesFolderWithRouteType(self): + folder = self.kmlwriter._CreateRoutesFolder(self.feed, self.parent, 1) + route_folders = folder.findall('Folder') + self.assertEquals(len(route_folders), 1) + + +class TestShapesKML(unittest.TestCase): + """Tests the shapes folder KML generation methods of KMLWriter.""" + + def setUp(self): + self.flatten_feed = transitfeed.Loader(DataPath('flatten_feed')).Load() + self.good_feed = transitfeed.Loader(DataPath('good_feed.zip')).Load() + self.kmlwriter = kmlwriter.KMLWriter() + self.parent = ET.Element('parent') + + def testCreateShapesFolderNoShapes(self): + folder = self.kmlwriter._CreateShapesFolder(self.good_feed, self.parent) + self.assertEquals(folder, None) + + def testCreateShapesFolder(self): + folder = self.kmlwriter._CreateShapesFolder(self.flatten_feed, self.parent) + placemarks = folder.findall('Placemark') + self.assertEquals(len(placemarks), 3) + for placemark in placemarks: + self.assert_(placemark.find('LineString') is not None) + + +class TestStopsKML(unittest.TestCase): + """Tests the stops folder KML generation methods of KMLWriter.""" + + def setUp(self): + self.feed = transitfeed.Loader(DataPath('flatten_feed')).Load() + self.kmlwriter = kmlwriter.KMLWriter() + self.parent = ET.Element('parent') + + def testCreateStopsFolderNoStops(self): + schedule = transitfeed.Schedule() + folder = self.kmlwriter._CreateStopsFolder(schedule, self.parent) + self.assert_(folder is None) + + def testCreateStopsFolder(self): + folder = self.kmlwriter._CreateStopsFolder(self.feed, self.parent) + placemarks = folder.findall('Placemark') + self.assertEquals(len(placemarks), len(self.feed.GetStopList())) + + +class TestShapePointsKML(unittest.TestCase): + """Tests the shape points folder KML generation methods of KMLWriter.""" + + def setUp(self): + self.flatten_feed = transitfeed.Loader(DataPath('flatten_feed')).Load() + self.kmlwriter = kmlwriter.KMLWriter() + self.kmlwriter.shape_points = True + self.parent = ET.Element('parent') + + def testCreateShapePointsFolder(self): + folder = self.kmlwriter._CreateShapesFolder(self.flatten_feed, self.parent) + shape_point_folder = folder.find('Folder') + self.assertEquals(shape_point_folder.find('name').text, + 'shape_1 Shape Points') + placemarks = shape_point_folder.findall('Placemark') + self.assertEquals(len(placemarks), 4) + for placemark in placemarks: + self.assert_(placemark.find('Point') is not None) + + +class FullTests(util.TempDirTestCaseBase): + def testNormalRun(self): + (out, err) = self.CheckCallWithPath( + [self.GetPath('kmlwriter.py'), self.GetTestDataPath('good_feed.zip'), + 'good_feed.kml']) + self.assertFalse(os.path.exists('transitfeedcrash.txt')) + self.assertTrue(os.path.exists('good_feed.kml')) + + def testCommandLineError(self): + (out, err) = self.CheckCallWithPath( + [self.GetPath('kmlwriter.py'), '--bad_flag'], expected_retcode=2) + self.assertMatchesRegex(r'no such option.*--bad_flag', err) + self.assertMatchesRegex(r'--showtrips', err) + self.assertFalse(os.path.exists('transitfeedcrash.txt')) + + def testCrashHandler(self): + (out, err) = self.CheckCallWithPath( + [self.GetPath('kmlwriter.py'), 'IWantMyCrash', 'output.zip'], + stdin_str="\n", expected_retcode=127) + self.assertMatchesRegex(r'Yikes', out) + crashout = open('transitfeedcrash.txt').read() + self.assertMatchesRegex(r'For testCrashHandler', crashout) + + +if __name__ == '__main__': + unittest.main() + --- /dev/null +++ b/origin-src/transitfeed-1.2.5/test/testmerge.py @@ -1,1 +1,1404 @@ - +#!/usr/bin/python2.4 +# +# Copyright 2007 Google Inc. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +"""Unit tests for the merge module.""" + + +__author__ = 'timothy.stranex@gmail.com (Timothy Stranex)' + + +import merge +import os.path +import re +import StringIO +import transitfeed +import unittest +import util +import zipfile + + +def CheckAttribs(a, b, attrs, assertEquals): + """Checks that the objects a and b have the same values for the attributes + given in attrs. These checks are done using the given assert function. + + Args: + a: The first object. + b: The second object. + attrs: The list of attribute names (strings). + assertEquals: The assertEquals method from unittest.TestCase. + """ + # For Stop objects (and maybe others in the future) Validate converts some + # attributes from string to native type + a.Validate() + b.Validate() + for k in attrs: + assertEquals(getattr(a, k), getattr(b, k)) + + +def CreateAgency(): + """Create an transitfeed.Agency object for testing. + + Returns: + The agency object. + """ + return transitfeed.Agency(name='agency', + url='http://agency', + timezone='Africa/Johannesburg', + id='agency') + + +class TestingProblemReporter(merge.MergeProblemReporterBase): + """This problem reporter keeps track of all problems. + + Attributes: + problems: The list of problems reported. + """ + + def __init__(self): + merge.MergeProblemReporterBase.__init__(self) + self.problems = [] + self._expect_classes = [] + + def _Report(self, problem): + problem.FormatProblem() # Shouldn't crash + self.problems.append(problem) + for problem_class in self._expect_classes: + if isinstance(problem, problem_class): + return + raise problem + + def CheckReported(self, problem_class): + """Checks if a problem of the given class was reported. + + Args: + problem_class: The problem class, a class inheriting from + MergeProblemWithContext. + + Returns: + True if a matching problem was reported. + """ + for problem in self.problems: + if isinstance(problem, problem_class): + return True + return False + + def ExpectProblemClass(self, problem_class): + """Supresses exception raising for problems inheriting from this class. + + Args: + problem_class: The problem class, a class inheriting from + MergeProblemWithContext. + """ + self._expect_classes.append(problem_class) + + def assertExpectedProblemsReported(self, testcase): + """Asserts that every expected problem class has been reported. + + The assertions are done using the assert_ method of the testcase. + + Args: + testcase: The unittest.TestCase instance. + """ + for problem_class in self._expect_classes: + testcase.assert_(self.CheckReported(problem_class)) + + +class TestApproximateDistanceBetweenPoints(unittest.TestCase): + + def _assertWithinEpsilon(self, a, b, epsilon=1.0): + """Asserts that a and b are equal to within an epsilon. + + Args: + a: The first value (float). + b: The second value (float). + epsilon: The epsilon value (float). + """ + self.assert_(abs(a-b) < epsilon) + + def testDegenerate(self): + p = (30.0, 30.0) + self._assertWithinEpsilon( + merge.ApproximateDistanceBetweenPoints(p, p), 0.0) + + def testFar(self): + p1 = (30.0, 30.0) + p2 = (40.0, 40.0) + self.assert_(merge.ApproximateDistanceBetweenPoints(p1, p2) > 1e4) + + +class TestSchemedMerge(unittest.TestCase): + + class TestEntity: + """A mock entity (like Route or Stop) for testing.""" + + def __init__(self, x, y, z): + self.x = x + self.y = y + self.z = z + + def setUp(self): + a_schedule = transitfeed.Schedule() + b_schedule = transitfeed.Schedule() + merged_schedule = transitfeed.Schedule() + self.fm = merge.FeedMerger(a_schedule, b_schedule, + merged_schedule, + TestingProblemReporter()) + self.ds = merge.DataSetMerger(self.fm) + + def Migrate(ent, sched, newid): + """A migration function for the mock entity.""" + return self.TestEntity(ent.x, ent.y, ent.z) + self.ds._Migrate = Migrate + + def testMergeIdentical(self): + class TestAttrib: + """An object that is equal to everything.""" + + def __cmp__(self, b): + return 0 + + x = 99 + a = TestAttrib() + b = TestAttrib() + + self.assert_(self.ds._MergeIdentical(x, x) == x) + self.assert_(self.ds._MergeIdentical(a, b) is b) + self.assertRaises(merge.MergeError, self.ds._MergeIdentical, 1, 2) + + def testMergeIdenticalCaseInsensitive(self): + self.assert_(self.ds._MergeIdenticalCaseInsensitive('abc', 'ABC') == 'ABC') + self.assert_(self.ds._MergeIdenticalCaseInsensitive('abc', 'AbC') == 'AbC') + self.assertRaises(merge.MergeError, + self.ds._MergeIdenticalCaseInsensitive, 'abc', 'bcd') + self.assertRaises(merge.MergeError, + self.ds._MergeIdenticalCaseInsensitive, 'abc', 'ABCD') + + def testMergeOptional(self): + x = 99 + y = 100 + + self.assertEquals(self.ds._MergeOptional(None, None), None) + self.assertEquals(self.ds._MergeOptional(None, x), x) + self.assertEquals(self.ds._MergeOptional(x, None), x) + self.assertEquals(self.ds._MergeOptional(x, x), x) + self.assertRaises(merge.MergeError, self.ds._MergeOptional, x, y) + + def testMergeSameAgency(self): + kwargs = {'name': 'xxx', + 'agency_url': 'http://www.example.com', + 'agency_timezone': 'Europe/Zurich'} + id1 = 'agency1' + id2 = 'agency2' + id3 = 'agency3' + id4 = 'agency4' + id5 = 'agency5' + + a = self.fm.a_schedule.NewDefaultAgency(id=id1, **kwargs) + b = self.fm.b_schedule.NewDefaultAgency(id=id2, **kwargs) + c = transitfeed.Agency(id=id3, **kwargs) + self.fm.merged_schedule.AddAgencyObject(c) + self.fm.Register(a, b, c) + + d = transitfeed.Agency(id=id4, **kwargs) + e = transitfeed.Agency(id=id5, **kwargs) + self.fm.a_schedule.AddAgencyObject(d) + self.fm.merged_schedule.AddAgencyObject(e) + self.fm.Register(d, None, e) + + self.assertEquals(self.ds._MergeSameAgency(id1, id2), id3) + self.assertEquals(self.ds._MergeSameAgency(None, None), id3) + self.assertEquals(self.ds._MergeSameAgency(id1, None), id3) + self.assertEquals(self.ds._MergeSameAgency(None, id2), id3) + + # id1 is not a valid agency_id in the new schedule so it cannot be merged + self.assertRaises(KeyError, self.ds._MergeSameAgency, id1, id1) + + # this fails because d (id4) and b (id2) don't map to the same agency + # in the merged schedule + self.assertRaises(merge.MergeError, self.ds._MergeSameAgency, id4, id2) + + def testSchemedMerge_Success(self): + + def Merger(a, b): + return a + b + + scheme = {'x': Merger, 'y': Merger, 'z': Merger} + a = self.TestEntity(1, 2, 3) + b = self.TestEntity(4, 5, 6) + c = self.ds._SchemedMerge(scheme, a, b) + + self.assertEquals(c.x, 5) + self.assertEquals(c.y, 7) + self.assertEquals(c.z, 9) + + def testSchemedMerge_Failure(self): + + def Merger(a, b): + raise merge.MergeError() + + scheme = {'x': Merger, 'y': Merger, 'z': Merger} + a = self.TestEntity(1, 2, 3) + b = self.TestEntity(4, 5, 6) + + self.assertRaises(merge.MergeError, self.ds._SchemedMerge, + scheme, a, b) + + def testSchemedMerge_NoNewId(self): + class TestDataSetMerger(merge.DataSetMerger): + def _Migrate(self, entity, schedule, newid): + self.newid = newid + return entity + dataset_merger = TestDataSetMerger(self.fm) + a = self.TestEntity(1, 2, 3) + b = self.TestEntity(4, 5, 6) + dataset_merger._SchemedMerge({}, a, b) + self.assertEquals(dataset_merger.newid, False) + + def testSchemedMerge_ErrorTextContainsAttributeNameAndReason(self): + reason = 'my reason' + attribute_name = 'long_attribute_name' + + def GoodMerger(a, b): + return a + b + + def BadMerger(a, b): + raise merge.MergeError(reason) + + a = self.TestEntity(1, 2, 3) + setattr(a, attribute_name, 1) + b = self.TestEntity(4, 5, 6) + setattr(b, attribute_name, 2) + scheme = {'x': GoodMerger, 'y': GoodMerger, 'z': GoodMerger, + attribute_name: BadMerger} + + try: + self.ds._SchemedMerge(scheme, a, b) + except merge.MergeError, merge_error: + error_text = str(merge_error) + self.assert_(reason in error_text) + self.assert_(attribute_name in error_text) + + +class TestFeedMerger(unittest.TestCase): + + class Merger: + def __init__(self, test, n, should_fail=False): + self.test = test + self.n = n + self.should_fail = should_fail + + def MergeDataSets(self): + self.test.called.append(self.n) + return not self.should_fail + + def setUp(self): + a_schedule = transitfeed.Schedule() + b_schedule = transitfeed.Schedule() + merged_schedule = transitfeed.Schedule() + self.fm = merge.FeedMerger(a_schedule, b_schedule, + merged_schedule, + TestingProblemReporter()) + self.called = [] + + def testDefaultProblemReporter(self): + feed_merger = merge.FeedMerger(self.fm.a_schedule, + self.fm.b_schedule, + self.fm.merged_schedule, + None) + self.assert_(isinstance(feed_merger.problem_reporter, + merge.MergeProblemReporterBase)) + + def testSequence(self): + for i in range(10): + self.fm.AddMerger(TestFeedMerger.Merger(self, i)) + self.assert_(self.fm.MergeSchedules()) + self.assertEquals(self.called, range(10)) + + def testStopsAfterError(self): + for i in range(10): + self.fm.AddMerger(TestFeedMerger.Merger(self, i, i == 5)) + self.assert_(not self.fm.MergeSchedules()) + self.assertEquals(self.called, range(6)) + + def testRegister(self): + self.fm.Register(1, 2, 3) + self.assertEquals(self.fm.a_merge_map, {1: 3}) + self.assertEquals(self.fm.b_merge_map, {2: 3}) + + def testRegisterNone(self): + self.fm.Register(None, 2, 3) + self.assertEquals(self.fm.a_merge_map, {}) + self.assertEquals(self.fm.b_merge_map, {2: 3}) + + def testGenerateId_Prefix(self): + x = 'test' + a = self.fm.GenerateId(x) + b = self.fm.GenerateId(x) + self.assertNotEqual(a, b) + self.assert_(a.startswith(x)) + self.assert_(b.startswith(x)) + + def testGenerateId_None(self): + a = self.fm.GenerateId(None) + b = self.fm.GenerateId(None) + self.assertNotEqual(a, b) + + def testGenerateId_InitialCounter(self): + a_schedule = transitfeed.Schedule() + b_schedule = transitfeed.Schedule() + merged_schedule = transitfeed.Schedule() + + for i in range(10): + agency = transitfeed.Agency(name='agency', url='http://agency', + timezone='Africa/Johannesburg', + id='agency_%d' % i) + if i % 2: + b_schedule.AddAgencyObject(agency) + else: + a_schedule.AddAgencyObject(agency) + + feed_merger = merge.FeedMerger(a_schedule, b_schedule, + merged_schedule, + TestingProblemReporter()) + + # check that the postfix number of any generated ids are greater than + # the postfix numbers of any ids in the old and new schedules + gen_id = feed_merger.GenerateId(None) + postfix_num = int(gen_id[gen_id.rfind('_')+1:]) + self.assert_(postfix_num >= 10) + + def testGetMerger(self): + class MergerA(merge.DataSetMerger): + pass + + class MergerB(merge.DataSetMerger): + pass + + a = MergerA(self.fm) + b = MergerB(self.fm) + + self.fm.AddMerger(a) + self.fm.AddMerger(b) + + self.assertEquals(self.fm.GetMerger(MergerA), a) + self.assertEquals(self.fm.GetMerger(MergerB), b) + + def testGetMerger_Error(self): + self.assertRaises(LookupError, self.fm.GetMerger, TestFeedMerger.Merger) + + +class TestServicePeriodMerger(unittest.TestCase): + + def setUp(self): + a_schedule = transitfeed.Schedule() + b_schedule = transitfeed.Schedule() + merged_schedule = transitfeed.Schedule() + self.fm = merge.FeedMerger(a_schedule, b_schedule, merged_schedule, + TestingProblemReporter()) + self.spm = merge.ServicePeriodMerger(self.fm) + self.fm.AddMerger(self.spm) + + def _AddTwoPeriods(self, start1, end1, start2, end2): + sp1fields = ['test1', start1, end1] + ['1']*7 + self.sp1 = transitfeed.ServicePeriod(field_list=sp1fields) + sp2fields = ['test2', start2, end2] + ['1']*7 + self.sp2 = transitfeed.ServicePeriod(field_list=sp2fields) + + self.fm.a_schedule.AddServicePeriodObject(self.sp1) + self.fm.b_schedule.AddServicePeriodObject(self.sp2) + + def testCheckDisjoint_True(self): + self._AddTwoPeriods('20071213', '20071231', + '20080101', '20080201') + self.assert_(self.spm.CheckDisjointCalendars()) + + def testCheckDisjoint_False1(self): + self._AddTwoPeriods('20071213', '20080201', + '20080101', '20080301') + self.assert_(not self.spm.CheckDisjointCalendars()) + + def testCheckDisjoint_False2(self): + self._AddTwoPeriods('20080101', '20090101', + '20070101', '20080601') + self.assert_(not self.spm.CheckDisjointCalendars()) + + def testCheckDisjoint_False3(self): + self._AddTwoPeriods('20080301', '20080901', + '20080101', '20090101') + self.assert_(not self.spm.CheckDisjointCalendars()) + + def testDisjoinCalendars(self): + self._AddTwoPeriods('20071213', '20080201', + '20080101', '20080301') + self.spm.DisjoinCalendars('20080101') + self.assertEquals(self.sp1.start_date, '20071213') + self.assertEquals(self.sp1.end_date, '20071231') + self.assertEquals(self.sp2.start_date, '20080101') + self.assertEquals(self.sp2.end_date, '20080301') + + def testDisjoinCalendars_Dates(self): + self._AddTwoPeriods('20071213', '20080201', + '20080101', '20080301') + self.sp1.SetDateHasService('20071201') + self.sp1.SetDateHasService('20081231') + self.sp2.SetDateHasService('20071201') + self.sp2.SetDateHasService('20081231') + + self.spm.DisjoinCalendars('20080101') + + self.assert_('20071201' in self.sp1.date_exceptions.keys()) + self.assert_('20081231' not in self.sp1.date_exceptions.keys()) + self.assert_('20071201' not in self.sp2.date_exceptions.keys()) + self.assert_('20081231' in self.sp2.date_exceptions.keys()) + + def testUnion(self): + self._AddTwoPeriods('20071213', '20071231', + '20080101', '20080201') + self.fm.problem_reporter.ExpectProblemClass(merge.MergeNotImplemented) + self.fm.MergeSchedules() + merged_schedule = self.fm.GetMergedSchedule() + self.assertEquals(len(merged_schedule.GetServicePeriodList()), 2) + + # make fields a copy of the service period attributes except service_id + fields = list(transitfeed.ServicePeriod._DAYS_OF_WEEK) + fields += ['start_date', 'end_date'] + + # now check that these attributes are preserved in the merge + CheckAttribs(self.sp1, self.fm.a_merge_map[self.sp1], fields, + self.assertEquals) + CheckAttribs(self.sp2, self.fm.b_merge_map[self.sp2], fields, + self.assertEquals) + + self.fm.problem_reporter.assertExpectedProblemsReported(self) + + def testMerge_RequiredButNotDisjoint(self): + self._AddTwoPeriods('20070101', '20090101', + '20080101', '20100101') + self.fm.problem_reporter.ExpectProblemClass(merge.CalendarsNotDisjoint) + self.assertEquals(self.spm.MergeDataSets(), False) + self.fm.problem_reporter.assertExpectedProblemsReported(self) + + def testMerge_NotRequiredAndNotDisjoint(self): + self._AddTwoPeriods('20070101', '20090101', + '20080101', '20100101') + self.spm.require_disjoint_calendars = False + self.fm.problem_reporter.ExpectProblemClass(merge.MergeNotImplemented) + self.fm.MergeSchedules() + self.fm.problem_reporter.assertExpectedProblemsReported(self) + + +class TestAgencyMerger(unittest.TestCase): + + def setUp(self): + a_schedule = transitfeed.Schedule() + b_schedule = transitfeed.Schedule() + merged_schedule = transitfeed.Schedule() + self.fm = merge.FeedMerger(a_schedule, b_schedule, merged_schedule, + TestingProblemReporter()) + self.am = merge.AgencyMerger(self.fm) + self.fm.AddMerger(self.am) + + self.a1 = transitfeed.Agency(id='a1', agency_name='a1', + agency_url='http://www.a1.com', + agency_timezone='Africa/Johannesburg', + agency_phone='123 456 78 90') + self.a2 = transitfeed.Agency(id='a2', agency_name='a1', + agency_url='http://www.a1.com', + agency_timezone='Africa/Johannesburg', + agency_phone='789 65 43 21') + + def testMerge(self): + self.a2.agency_id = self.a1.agency_id + self.fm.a_schedule.AddAgencyObject(self.a1) + self.fm.b_schedule.AddAgencyObject(self.a2) + self.fm.MergeSchedules() + + merged_schedule = self.fm.GetMergedSchedule() + self.assertEquals(len(merged_schedule.GetAgencyList()), 1) + self.assertEquals(merged_schedule.GetAgencyList()[0], + self.fm.a_merge_map[self.a1]) + self.assertEquals(self.fm.a_merge_map[self.a1], + self.fm.b_merge_map[self.a2]) + # differing values such as agency_phone should be taken from self.a2 + self.assertEquals(merged_schedule.GetAgencyList()[0], self.a2) + self.assertEquals(self.am.GetMergeStats(), (1, 0, 0)) + + # check that id is preserved + self.assertEquals(self.fm.a_merge_map[self.a1].agency_id, + self.a1.agency_id) + + def testNoMerge_DifferentId(self): + self.fm.a_schedule.AddAgencyObject(self.a1) + self.fm.b_schedule.AddAgencyObject(self.a2) + self.fm.MergeSchedules() + + merged_schedule = self.fm.GetMergedSchedule() + self.assertEquals(len(merged_schedule.GetAgencyList()), 2) + + self.assert_(self.fm.a_merge_map[self.a1] in + merged_schedule.GetAgencyList()) + self.assert_(self.fm.b_merge_map[self.a2] in + merged_schedule.GetAgencyList()) + self.assertEquals(self.a1, self.fm.a_merge_map[self.a1]) + self.assertEquals(self.a2, self.fm.b_merge_map[self.a2]) + self.assertEquals(self.am.GetMergeStats(), (0, 1, 1)) + + # check that the ids are preserved + self.assertEquals(self.fm.a_merge_map[self.a1].agency_id, + self.a1.agency_id) + self.assertEquals(self.fm.b_merge_map[self.a2].agency_id, + self.a2.agency_id) + + def testNoMerge_SameId(self): + # Force a1.agency_id to be unicode to make sure it is correctly encoded + # to utf-8 before concatinating to the agency_name containing non-ascii + # characters. + self.a1.agency_id = unicode(self.a1.agency_id) + self.a2.agency_id = str(self.a1.agency_id) + self.a2.agency_name = 'different \xc3\xa9' + self.fm.a_schedule.AddAgencyObject(self.a1) + self.fm.b_schedule.AddAgencyObject(self.a2) + + self.fm.problem_reporter.ExpectProblemClass(merge.SameIdButNotMerged) + self.fm.MergeSchedules() + + merged_schedule = self.fm.GetMergedSchedule() + self.assertEquals(len(merged_schedule.GetAgencyList()), 2) + self.assertEquals(self.am.GetMergeStats(), (0, 1, 1)) + + # check that the merged entities have different ids + self.assertNotEqual(self.fm.a_merge_map[self.a1].agency_id, + self.fm.b_merge_map[self.a2].agency_id) + + self.fm.problem_reporter.assertExpectedProblemsReported(self) + + +class TestStopMerger(unittest.TestCase): + + def setUp(self): + a_schedule = transitfeed.Schedule() + b_schedule = transitfeed.Schedule() + merged_schedule = transitfeed.Schedule() + self.fm = merge.FeedMerger(a_schedule, b_schedule, merged_schedule, + TestingProblemReporter()) + self.sm = merge.StopMerger(self.fm) + self.fm.AddMerger(self.sm) + + self.s1 = transitfeed.Stop(30.0, 30.0, + u'Andr\202' , 's1') + self.s1.stop_desc = 'stop 1' + self.s1.stop_url = 'http://stop/1' + self.s1.zone_id = 'zone1' + self.s2 = transitfeed.Stop(30.0, 30.0, 's2', 's2') + self.s2.stop_desc = 'stop 2' + self.s2.stop_url = 'http://stop/2' + self.s2.zone_id = 'zone1' + + def testMerge(self): + self.s2.stop_id = self.s1.stop_id + self.s2.stop_name = self.s1.stop_name + self.s1.location_type = 1 + self.s2.location_type = 1 + + self.fm.a_schedule.AddStopObject(self.s1) + self.fm.b_schedule.AddStopObject(self.s2) + self.fm.MergeSchedules() + + merged_schedule = self.fm.GetMergedSchedule() + self.assertEquals(len(merged_schedule.GetStopList()), 1) + self.assertEquals(merged_schedule.GetStopList()[0], + self.fm.a_merge_map[self.s1]) + self.assertEquals(self.fm.a_merge_map[self.s1], + self.fm.b_merge_map[self.s2]) + self.assertEquals(self.sm.GetMergeStats(), (1, 0, 0)) + + # check that the remaining attributes are taken from the new stop + fields = ['stop_name', 'stop_lat', 'stop_lon', 'stop_desc', 'stop_url', + 'location_type'] + CheckAttribs(self.fm.a_merge_map[self.s1], self.s2, fields, + self.assertEquals) + + # check that the id is preserved + self.assertEquals(self.fm.a_merge_map[self.s1].stop_id, self.s1.stop_id) + + # check that the zone_id is preserved + self.assertEquals(self.fm.a_merge_map[self.s1].zone_id, self.s1.zone_id) + + def testNoMerge_DifferentId(self): + self.fm.a_schedule.AddStopObject(self.s1) + self.fm.b_schedule.AddStopObject(self.s2) + self.fm.MergeSchedules() + + merged_schedule = self.fm.GetMergedSchedule() + self.assertEquals(len(merged_schedule.GetStopList()), 2) + self.assert_(self.fm.a_merge_map[self.s1] in merged_schedule.GetStopList()) + self.assert_(self.fm.b_merge_map[self.s2] in merged_schedule.GetStopList()) + self.assertEquals(self.sm.GetMergeStats(), (0, 1, 1)) + + def testNoMerge_DifferentName(self): + self.s2.stop_id = self.s1.stop_id + self.fm.a_schedule.AddStopObject(self.s1) + self.fm.b_schedule.AddStopObject(self.s2) + self.fm.problem_reporter.ExpectProblemClass(merge.SameIdButNotMerged) + self.fm.MergeSchedules() + + merged_schedule = self.fm.GetMergedSchedule() + self.assertEquals(len(merged_schedule.GetStopList()), 2) + self.assert_(self.fm.a_merge_map[self.s1] in merged_schedule.GetStopList()) + self.assert_(self.fm.b_merge_map[self.s2] in merged_schedule.GetStopList()) + self.assertEquals(self.sm.GetMergeStats(), (0, 1, 1)) + + def testNoMerge_FarApart(self): + self.s2.stop_id = self.s1.stop_id + self.s2.stop_name = self.s1.stop_name + self.s2.stop_lat = 40.0 + self.s2.stop_lon = 40.0 + + self.fm.a_schedule.AddStopObject(self.s1) + self.fm.b_schedule.AddStopObject(self.s2) + self.fm.problem_reporter.ExpectProblemClass(merge.SameIdButNotMerged) + self.fm.MergeSchedules() + + merged_schedule = self.fm.GetMergedSchedule() + self.assertEquals(len(merged_schedule.GetStopList()), 2) + self.assert_(self.fm.a_merge_map[self.s1] in merged_schedule.GetStopList()) + self.assert_(self.fm.b_merge_map[self.s2] in merged_schedule.GetStopList()) + self.assertEquals(self.sm.GetMergeStats(), (0, 1, 1)) + + # check that the merged ids are different + self.assertNotEquals(self.fm.a_merge_map[self.s1].stop_id, + self.fm.b_merge_map[self.s2].stop_id) + + self.fm.problem_reporter.assertExpectedProblemsReported(self) + + def testMerge_CaseInsensitive(self): + self.s2.stop_id = self.s1.stop_id + self.s2.stop_name = self.s1.stop_name.upper() + self.fm.a_schedule.AddStopObject(self.s1) + self.fm.b_schedule.AddStopObject(self.s2) + self.fm.MergeSchedules() + merged_schedule = self.fm.GetMergedSchedule() + self.assertEquals(len(merged_schedule.GetStopList()), 1) + self.assertEquals(self.sm.GetMergeStats(), (1, 0, 0)) + + def testNoMerge_ZoneId(self): + self.s2.zone_id = 'zone2' + self.fm.a_schedule.AddStopObject(self.s1) + self.fm.b_schedule.AddStopObject(self.s2) + self.fm.MergeSchedules() + + merged_schedule = self.fm.GetMergedSchedule() + self.assertEquals(len(merged_schedule.GetStopList()), 2) + + self.assert_(self.s1.zone_id in self.fm.a_zone_map) + self.assert_(self.s2.zone_id in self.fm.b_zone_map) + self.assertEquals(self.sm.GetMergeStats(), (0, 1, 1)) + + # check that the zones are still different + self.assertNotEqual(self.fm.a_merge_map[self.s1].zone_id, + self.fm.b_merge_map[self.s2].zone_id) + + def testZoneId_SamePreservation(self): + # checks that if the zone_ids of some stops are the same before the + # merge, they are still the same after. + self.fm.a_schedule.AddStopObject(self.s1) + self.fm.a_schedule.AddStopObject(self.s2) + self.fm.MergeSchedules() + self.assertEquals(self.fm.a_merge_map[self.s1].zone_id, + self.fm.a_merge_map[self.s2].zone_id) + + def testZoneId_DifferentSchedules(self): + # zone_ids may be the same in different schedules but unless the stops + # are merged, they should map to different zone_ids + self.fm.a_schedule.AddStopObject(self.s1) + self.fm.b_schedule.AddStopObject(self.s2) + self.fm.MergeSchedules() + self.assertNotEquals(self.fm.a_merge_map[self.s1].zone_id, + self.fm.b_merge_map[self.s2].zone_id) + + def testZoneId_MergePreservation(self): + # check that if two stops are merged, the zone mapping is used for all + # other stops too + self.s2.stop_id = self.s1.stop_id + self.s2.stop_name = self.s1.stop_name + s3 = transitfeed.Stop(field_dict=self.s1) + s3.stop_id = 'different' + + self.fm.a_schedule.AddStopObject(self.s1) + self.fm.a_schedule.AddStopObject(s3) + self.fm.b_schedule.AddStopObject(self.s2) + self.fm.MergeSchedules() + + self.assertEquals(self.fm.a_merge_map[self.s1].zone_id, + self.fm.a_merge_map[s3].zone_id) + self.assertEquals(self.fm.a_merge_map[s3].zone_id, + self.fm.b_merge_map[self.s2].zone_id) + + def testMergeStationType(self): + self.s2.stop_id = self.s1.stop_id + self.s2.stop_name = self.s1.stop_name + self.s1.location_type = 1 + self.s2.location_type = 1 + self.fm.a_schedule.AddStopObject(self.s1) + self.fm.b_schedule.AddStopObject(self.s2) + self.fm.MergeSchedules() + merged_stops = self.fm.GetMergedSchedule().GetStopList() + self.assertEquals(len(merged_stops), 1) + self.assertEquals(merged_stops[0].location_type, 1) + + def testMergeDifferentTypes(self): + self.s2.stop_id = self.s1.stop_id + self.s2.stop_name = self.s1.stop_name + self.s2.location_type = 1 + self.fm.a_schedule.AddStopObject(self.s1) + self.fm.b_schedule.AddStopObject(self.s2) + try: + self.fm.MergeSchedules() + self.fail("Expecting MergeError") + except merge.SameIdButNotMerged, merge_error: + self.assertTrue(("%s" % merge_error).find("location_type") != -1) + + def AssertS1ParentIsS2(self): + """Assert that the merged s1 has parent s2.""" + new_s1 = self.fm.GetMergedObject(self.s1) + new_s2 = self.fm.GetMergedObject(self.s2) + self.assertEquals(new_s1.parent_station, new_s2.stop_id) + self.assertEquals(new_s2.parent_station, None) + self.assertEquals(new_s1.location_type, 0) + self.assertEquals(new_s2.location_type, 1) + + def testMergeMaintainParentRelationship(self): + self.s2.location_type = 1 + self.s1.parent_station = self.s2.stop_id + self.fm.a_schedule.AddStopObject(self.s1) + self.fm.a_schedule.AddStopObject(self.s2) + self.fm.MergeSchedules() + self.AssertS1ParentIsS2() + + def testParentRelationshipAfterMerge(self): + s3 = transitfeed.Stop(field_dict=self.s1) + s3.parent_station = self.s2.stop_id + self.s2.location_type = 1 + self.fm.a_schedule.AddStopObject(self.s1) + self.fm.b_schedule.AddStopObject(self.s2) + self.fm.b_schedule.AddStopObject(s3) + self.fm.MergeSchedules() + self.AssertS1ParentIsS2() + + def testParentRelationshipWithNewParentid(self): + self.s2.location_type = 1 + self.s1.parent_station = self.s2.stop_id + # s3 will have a stop_id conflict with self.s2 so parent_id of the + # migrated self.s1 will need to be updated + s3 = transitfeed.Stop(field_dict=self.s2) + s3.stop_lat = 45 + self.fm.a_schedule.AddStopObject(s3) + self.fm.b_schedule.AddStopObject(self.s1) + self.fm.b_schedule.AddStopObject(self.s2) + self.fm.problem_reporter.ExpectProblemClass(merge.SameIdButNotMerged) + self.fm.MergeSchedules() + self.assertNotEquals(self.fm.GetMergedObject(s3).stop_id, + self.fm.GetMergedObject(self.s2).stop_id) + # Check that s3 got a new id + self.assertNotEquals(self.s2.stop_id, + self.fm.GetMergedObject(self.s2).stop_id) + self.AssertS1ParentIsS2() + + def _AddStopsApart(self): + """Adds two stops to the schedules and returns the distance between them. + + Returns: + The distance between the stops in metres, a value greater than zero. + """ + self.s2.stop_id = self.s1.stop_id + self.s2.stop_name = self.s1.stop_name + self.s2.stop_lat += 1.0e-3 + self.fm.a_schedule.AddStopObject(self.s1) + self.fm.b_schedule.AddStopObject(self.s2) + return transitfeed.ApproximateDistanceBetweenStops(self.s1, self.s2) + + def testSetLargestStopDistanceSmall(self): + largest_stop_distance = self._AddStopsApart() * 0.5 + self.sm.SetLargestStopDistance(largest_stop_distance) + self.assertEquals(self.sm.largest_stop_distance, largest_stop_distance) + self.fm.problem_reporter.ExpectProblemClass(merge.SameIdButNotMerged) + self.fm.MergeSchedules() + self.assertEquals(len(self.fm.GetMergedSchedule().GetStopList()), 2) + self.fm.problem_reporter.assertExpectedProblemsReported(self) + + def testSetLargestStopDistanceLarge(self): + largest_stop_distance = self._AddStopsApart() * 2.0 + self.sm.SetLargestStopDistance(largest_stop_distance) + self.assertEquals(self.sm.largest_stop_distance, largest_stop_distance) + self.fm.MergeSchedules() + self.assertEquals(len(self.fm.GetMergedSchedule().GetStopList()), 1) + + +class TestRouteMerger(unittest.TestCase): + + fields = ['route_short_name', 'route_long_name', 'route_type', + 'route_url'] + + def setUp(self): + a_schedule = transitfeed.Schedule() + b_schedule = transitfeed.Schedule() + merged_schedule = transitfeed.Schedule() + self.fm = merge.FeedMerger(a_schedule, b_schedule, merged_schedule, + TestingProblemReporter()) + self.fm.AddMerger(merge.AgencyMerger(self.fm)) + self.rm = merge.RouteMerger(self.fm) + self.fm.AddMerger(self.rm) + + akwargs = {'id': 'a1', + 'agency_name': 'a1', + 'agency_url': 'http://www.a1.com', + 'agency_timezone': 'Europe/Zurich'} + self.a1 = transitfeed.Agency(**akwargs) + self.a2 = transitfeed.Agency(**akwargs) + a_schedule.AddAgencyObject(self.a1) + b_schedule.AddAgencyObject(self.a2) + + rkwargs = {'route_id': 'r1', + 'agency_id': 'a1', + 'short_name': 'r1', + 'long_name': 'r1r1', + 'route_type': '0'} + self.r1 = transitfeed.Route(**rkwargs) + self.r2 = transitfeed.Route(**rkwargs) + self.r2.route_url = 'http://route/2' + + def testMerge(self): + self.fm.a_schedule.AddRouteObject(self.r1) + self.fm.b_schedule.AddRouteObject(self.r2) + self.fm.MergeSchedules() + + merged_schedule = self.fm.GetMergedSchedule() + self.assertEquals(len(merged_schedule.GetRouteList()), 1) + r = merged_schedule.GetRouteList()[0] + self.assert_(self.fm.a_merge_map[self.r1] is r) + self.assert_(self.fm.b_merge_map[self.r2] is r) + CheckAttribs(self.r2, r, self.fields, self.assertEquals) + self.assertEquals(r.agency_id, self.fm.a_merge_map[self.a1].agency_id) + self.assertEquals(self.rm.GetMergeStats(), (1, 0, 0)) + + # check that the id is preserved + self.assertEquals(self.fm.a_merge_map[self.r1].route_id, self.r1.route_id) + + def testMergeNoAgency(self): + self.r1.agency_id = None + self.r2.agency_id = None + self.fm.a_schedule.AddRouteObject(self.r1) + self.fm.b_schedule.AddRouteObject(self.r2) + self.fm.MergeSchedules() + + merged_schedule = self.fm.GetMergedSchedule() + self.assertEquals(len(merged_schedule.GetRouteList()), 1) + r = merged_schedule.GetRouteList()[0] + CheckAttribs(self.r2, r, self.fields, self.assertEquals) + # Merged route has copy of default agency_id + self.assertEquals(r.agency_id, self.a1.agency_id) + self.assertEquals(self.rm.GetMergeStats(), (1, 0, 0)) + + # check that the id is preserved + self.assertEquals(self.fm.a_merge_map[self.r1].route_id, self.r1.route_id) + + def testMigrateNoAgency(self): + self.r1.agency_id = None + self.fm.a_schedule.AddRouteObject(self.r1) + self.fm.MergeSchedules() + merged_schedule = self.fm.GetMergedSchedule() + self.assertEquals(len(merged_schedule.GetRouteList()), 1) + r = merged_schedule.GetRouteList()[0] + CheckAttribs(self.r1, r, self.fields, self.assertEquals) + # Migrated route has copy of default agency_id + self.assertEquals(r.agency_id, self.a1.agency_id) + + def testNoMerge_DifferentId(self): + self.r2.route_id = 'r2' + self.fm.a_schedule.AddRouteObject(self.r1) + self.fm.b_schedule.AddRouteObject(self.r2) + self.fm.MergeSchedules() + self.assertEquals(len(self.fm.GetMergedSchedule().GetRouteList()), 2) + self.assertEquals(self.rm.GetMergeStats(), (0, 1, 1)) + + def testNoMerge_SameId(self): + self.r2.route_short_name = 'different' + self.fm.a_schedule.AddRouteObject(self.r1) + self.fm.b_schedule.AddRouteObject(self.r2) + self.fm.problem_reporter.ExpectProblemClass(merge.SameIdButNotMerged) + self.fm.MergeSchedules() + self.assertEquals(len(self.fm.GetMergedSchedule().GetRouteList()), 2) + self.assertEquals(self.rm.GetMergeStats(), (0, 1, 1)) + + # check that the merged ids are different + self.assertNotEquals(self.fm.a_merge_map[self.r1].route_id, + self.fm.b_merge_map[self.r2].route_id) + + self.fm.problem_reporter.assertExpectedProblemsReported(self) + + +class TestTripMerger(unittest.TestCase): + + def setUp(self): + a_schedule = transitfeed.Schedule() + b_schedule = transitfeed.Schedule() + merged_schedule = transitfeed.Schedule() + self.fm = merge.FeedMerger(a_schedule, b_schedule, merged_schedule, + TestingProblemReporter()) + self.fm.AddDefaultMergers() + self.tm = self.fm.GetMerger(merge.TripMerger) + + akwargs = {'id': 'a1', + 'agency_name': 'a1', + 'agency_url': 'http://www.a1.com', + 'agency_timezone': 'Europe/Zurich'} + self.a1 = transitfeed.Agency(**akwargs) + + rkwargs = {'route_id': 'r1', + 'agency_id': 'a1', + 'short_name': 'r1', + 'long_name': 'r1r1', + 'route_type': '0'} + self.r1 = transitfeed.Route(**rkwargs) + + self.s1 = transitfeed.ServicePeriod('s1') + self.s1.start_date = '20071201' + self.s1.end_date = '20071231' + self.s1.SetWeekdayService() + + self.shape = transitfeed.Shape('shape1') + self.shape.AddPoint(30.0, 30.0) + + self.t1 = transitfeed.Trip(service_period=self.s1, + route=self.r1, trip_id='t1') + self.t2 = transitfeed.Trip(service_period=self.s1, + route=self.r1, trip_id='t2') + # Must add self.t1 to a schedule before calling self.t1.AddStopTime + a_schedule.AddTripObject(self.t1, validate=False) + a_schedule.AddTripObject(self.t2, validate=False) + self.t1.block_id = 'b1' + self.t2.block_id = 'b1' + self.t1.shape_id = 'shape1' + + self.stop = transitfeed.Stop(30.0, 30.0, stop_id='stop1') + self.t1.AddStopTime(self.stop, arrival_secs=0, departure_secs=0) + + a_schedule.AddAgencyObject(self.a1) + a_schedule.AddStopObject(self.stop) + a_schedule.AddRouteObject(self.r1) + a_schedule.AddServicePeriodObject(self.s1) + a_schedule.AddShapeObject(self.shape) + + def testMigrate(self): + self.fm.problem_reporter.ExpectProblemClass(merge.MergeNotImplemented) + self.fm.MergeSchedules() + self.fm.problem_reporter.assertExpectedProblemsReported(self) + + r = self.fm.a_merge_map[self.r1] + s = self.fm.a_merge_map[self.s1] + shape = self.fm.a_merge_map[self.shape] + t1 = self.fm.a_merge_map[self.t1] + t2 = self.fm.a_merge_map[self.t2] + + self.assertEquals(t1.route_id, r.route_id) + self.assertEquals(t1.service_id, s.service_id) + self.assertEquals(t1.shape_id, shape.shape_id) + self.assertEquals(t1.block_id, t2.block_id) + + self.assertEquals(len(t1.GetStopTimes()), 1) + st = t1.GetStopTimes()[0] + self.assertEquals(st.stop, self.fm.a_merge_map[self.stop]) + + def testReportsNotImplementedProblem(self): + self.fm.problem_reporter.ExpectProblemClass(merge.MergeNotImplemented) + self.fm.MergeSchedules() + self.fm.problem_reporter.assertExpectedProblemsReported(self) + + def testMergeStats(self): + self.assert_(self.tm.GetMergeStats() is None) + + def testConflictingTripid(self): + a1_in_b = transitfeed.Agency(field_dict=self.a1) + r1_in_b = transitfeed.Route(field_dict=self.r1) + t1_in_b = transitfeed.Trip(field_dict=self.t1) + shape_in_b = transitfeed.Shape('shape1') + shape_in_b.AddPoint(30.0, 30.0) + s_in_b = transitfeed.ServicePeriod('s1') + s_in_b.start_date = '20080101' + s_in_b.end_date = '20080131' + s_in_b.SetWeekdayService() + + self.fm.b_schedule.AddAgencyObject(a1_in_b) + self.fm.b_schedule.AddRouteObject(r1_in_b) + self.fm.b_schedule.AddShapeObject(shape_in_b) + self.fm.b_schedule.AddTripObject(t1_in_b, validate=False) + self.fm.b_schedule.AddServicePeriodObject(s_in_b, validate=False) + self.fm.problem_reporter.ExpectProblemClass(merge.MergeNotImplemented) + self.fm.MergeSchedules() + # 3 trips moved to merged_schedule: from a_schedule t1, t2 and from + # b_schedule t1 + self.assertEquals(len(self.fm.merged_schedule.GetTripList()), 3) + + +class TestFareMerger(unittest.TestCase): + + def setUp(self): + a_schedule = transitfeed.Schedule() + b_schedule = transitfeed.Schedule() + merged_schedule = transitfeed.Schedule() + self.fm = merge.FeedMerger(a_schedule, b_schedule, merged_schedule, + TestingProblemReporter()) + self.faremerger = merge.FareMerger(self.fm) + self.fm.AddMerger(self.faremerger) + + self.f1 = transitfeed.Fare('f1', '10', 'ZAR', '1', '0') + self.f2 = transitfeed.Fare('f2', '10', 'ZAR', '1', '0') + + def testMerge(self): + self.f2.fare_id = self.f1.fare_id + self.fm.a_schedule.AddFareObject(self.f1) + self.fm.b_schedule.AddFareObject(self.f2) + self.fm.MergeSchedules() + self.assertEquals(len(self.fm.merged_schedule.GetFareList()), 1) + self.assertEquals(self.faremerger.GetMergeStats(), (1, 0, 0)) + + # check that the id is preserved + self.assertEquals(self.fm.a_merge_map[self.f1].fare_id, self.f1.fare_id) + + def testNoMerge_DifferentPrice(self): + self.f2.fare_id = self.f1.fare_id + self.f2.price = 11.0 + self.fm.a_schedule.AddFareObject(self.f1) + self.fm.b_schedule.AddFareObject(self.f2) + self.fm.problem_reporter.ExpectProblemClass(merge.SameIdButNotMerged) + self.fm.MergeSchedules() + self.assertEquals(len(self.fm.merged_schedule.GetFareList()), 2) + self.assertEquals(self.faremerger.GetMergeStats(), (0, 1, 1)) + + # check that the merged ids are different + self.assertNotEquals(self.fm.a_merge_map[self.f1].fare_id, + self.fm.b_merge_map[self.f2].fare_id) + + self.fm.problem_reporter.assertExpectedProblemsReported(self) + + def testNoMerge_DifferentId(self): + self.fm.a_schedule.AddFareObject(self.f1) + self.fm.b_schedule.AddFareObject(self.f2) + self.fm.MergeSchedules() + self.assertEquals(len(self.fm.merged_schedule.GetFareList()), 2) + self.assertEquals(self.faremerger.GetMergeStats(), (0, 1, 1)) + + # check that the ids are preserved + self.assertEquals(self.fm.a_merge_map[self.f1].fare_id, self.f1.fare_id) + self.assertEquals(self.fm.b_merge_map[self.f2].fare_id, self.f2.fare_id) + + +class TestShapeMerger(unittest.TestCase): + + def setUp(self): + a_schedule = transitfeed.Schedule() + b_schedule = transitfeed.Schedule() + merged_schedule = transitfeed.Schedule() + self.fm = merge.FeedMerger(a_schedule, b_schedule, merged_schedule, + TestingProblemReporter()) + self.sm = merge.ShapeMerger(self.fm) + self.fm.AddMerger(self.sm) + + # setup some shapes + # s1 and s2 have the same endpoints but take different paths + # s3 has different endpoints to s1 and s2 + + self.s1 = transitfeed.Shape('s1') + self.s1.AddPoint(30.0, 30.0) + self.s1.AddPoint(40.0, 30.0) + self.s1.AddPoint(50.0, 50.0) + + self.s2 = transitfeed.Shape('s2') + self.s2.AddPoint(30.0, 30.0) + self.s2.AddPoint(40.0, 35.0) + self.s2.AddPoint(50.0, 50.0) + + self.s3 = transitfeed.Shape('s3') + self.s3.AddPoint(31.0, 31.0) + self.s3.AddPoint(45.0, 35.0) + self.s3.AddPoint(51.0, 51.0) + + def testMerge(self): + self.s2.shape_id = self.s1.shape_id + self.fm.a_schedule.AddShapeObject(self.s1) + self.fm.b_schedule.AddShapeObject(self.s2) + self.fm.MergeSchedules() + self.assertEquals(len(self.fm.merged_schedule.GetShapeList()), 1) + self.assertEquals(self.fm.merged_schedule.GetShapeList()[0], self.s2) + self.assertEquals(self.sm.GetMergeStats(), (1, 0, 0)) + + # check that the id is preserved + self.assertEquals(self.fm.a_merge_map[self.s1].shape_id, self.s1.shape_id) + + def testNoMerge_DifferentId(self): + self.fm.a_schedule.AddShapeObject(self.s1) + self.fm.b_schedule.AddShapeObject(self.s2) + self.fm.MergeSchedules() + self.assertEquals(len(self.fm.merged_schedule.GetShapeList()), 2) + self.assertEquals(self.s1, self.fm.a_merge_map[self.s1]) + self.assertEquals(self.s2, self.fm.b_merge_map[self.s2]) + self.assertEquals(self.sm.GetMergeStats(), (0, 1, 1)) + + # check that the ids are preserved + self.assertEquals(self.fm.a_merge_map[self.s1].shape_id, self.s1.shape_id) + self.assertEquals(self.fm.b_merge_map[self.s2].shape_id, self.s2.shape_id) + + def testNoMerge_FarEndpoints(self): + self.s3.shape_id = self.s1.shape_id + self.fm.a_schedule.AddShapeObject(self.s1) + self.fm.b_schedule.AddShapeObject(self.s3) + self.fm.problem_reporter.ExpectProblemClass(merge.SameIdButNotMerged) + self.fm.MergeSchedules() + self.assertEquals(len(self.fm.merged_schedule.GetShapeList()), 2) + self.assertEquals(self.s1, self.fm.a_merge_map[self.s1]) + self.assertEquals(self.s3, self.fm.b_merge_map[self.s3]) + self.assertEquals(self.sm.GetMergeStats(), (0, 1, 1)) + + # check that the ids are different + self.assertNotEquals(self.fm.a_merge_map[self.s1].shape_id, + self.fm.b_merge_map[self.s3].shape_id) + + self.fm.problem_reporter.assertExpectedProblemsReported(self) + + def _AddShapesApart(self): + """Adds two shapes to the schedules. + + The maximum of the distances between the endpoints is returned. + + Returns: + The distance in metres, a value greater than zero. + """ + self.s3.shape_id = self.s1.shape_id + self.fm.a_schedule.AddShapeObject(self.s1) + self.fm.b_schedule.AddShapeObject(self.s3) + distance1 = merge.ApproximateDistanceBetweenPoints( + self.s1.points[0][:2], self.s3.points[0][:2]) + distance2 = merge.ApproximateDistanceBetweenPoints( + self.s1.points[-1][:2], self.s3.points[-1][:2]) + return max(distance1, distance2) + + def testSetLargestShapeDistanceSmall(self): + largest_shape_distance = self._AddShapesApart() * 0.5 + self.sm.SetLargestShapeDistance(largest_shape_distance) + self.assertEquals(self.sm.largest_shape_distance, largest_shape_distance) + self.fm.problem_reporter.ExpectProblemClass(merge.SameIdButNotMerged) + self.fm.MergeSchedules() + self.assertEquals(len(self.fm.GetMergedSchedule().GetShapeList()), 2) + self.fm.problem_reporter.assertExpectedProblemsReported(self) + + def testSetLargestShapeDistanceLarge(self): + largest_shape_distance = self._AddShapesApart() * 2.0 + self.sm.SetLargestShapeDistance(largest_shape_distance) + self.assertEquals(self.sm.largest_shape_distance, largest_shape_distance) + self.fm.MergeSchedules() + self.assertEquals(len(self.fm.GetMergedSchedule().GetShapeList()), 1) + + +class TestFareRuleMerger(unittest.TestCase): + + def setUp(self): + a_schedule = transitfeed.Schedule() + b_schedule = transitfeed.Schedule() + merged_schedule = transitfeed.Schedule() + self.fm = merge.FeedMerger(a_schedule, b_schedule, merged_schedule, + TestingProblemReporter()) + self.fm.AddDefaultMergers() + self.fare_rule_merger = self.fm.GetMerger(merge.FareRuleMerger) + + akwargs = {'id': 'a1', + 'agency_name': 'a1', + 'agency_url': 'http://www.a1.com', + 'agency_timezone': 'Europe/Zurich'} + self.a1 = transitfeed.Agency(**akwargs) + self.a2 = transitfeed.Agency(**akwargs) + + rkwargs = {'route_id': 'r1', + 'agency_id': 'a1', + 'short_name': 'r1', + 'long_name': 'r1r1', + 'route_type': '0'} + self.r1 = transitfeed.Route(**rkwargs) + self.r2 = transitfeed.Route(**rkwargs) + + self.f1 = transitfeed.Fare('f1', '10', 'ZAR', '1', '0') + self.f2 = transitfeed.Fare('f1', '10', 'ZAR', '1', '0') + self.f3 = transitfeed.Fare('f3', '11', 'USD', '1', '0') + + self.fr1 = transitfeed.FareRule('f1', 'r1') + self.fr2 = transitfeed.FareRule('f1', 'r1') + self.fr3 = transitfeed.FareRule('f3', 'r1') + + self.fm.a_schedule.AddAgencyObject(self.a1) + self.fm.a_schedule.AddRouteObject(self.r1) + self.fm.a_schedule.AddFareObject(self.f1) + self.fm.a_schedule.AddFareObject(self.f3) + self.fm.a_schedule.AddFareRuleObject(self.fr1) + self.fm.a_schedule.AddFareRuleObject(self.fr3) + + self.fm.b_schedule.AddAgencyObject(self.a2) + self.fm.b_schedule.AddRouteObject(self.r2) + self.fm.b_schedule.AddFareObject(self.f2) + self.fm.b_schedule.AddFareRuleObject(self.fr2) + + def testMerge(self): + self.fm.problem_reporter.ExpectProblemClass(merge.FareRulesBroken) + self.fm.problem_reporter.ExpectProblemClass(merge.MergeNotImplemented) + self.fm.MergeSchedules() + + self.assertEquals(len(self.fm.merged_schedule.GetFareList()), 2) + + fare_1 = self.fm.a_merge_map[self.f1] + fare_2 = self.fm.a_merge_map[self.f3] + + self.assertEquals(len(fare_1.GetFareRuleList()), 1) + fare_rule_1 = fare_1.GetFareRuleList()[0] + self.assertEquals(len(fare_2.GetFareRuleList()), 1) + fare_rule_2 = fare_2.GetFareRuleList()[0] + + self.assertEquals(fare_rule_1.fare_id, + self.fm.a_merge_map[self.f1].fare_id) + self.assertEquals(fare_rule_1.route_id, + self.fm.a_merge_map[self.r1].route_id) + self.assertEqual(fare_rule_2.fare_id, + self.fm.a_merge_map[self.f3].fare_id) + self.assertEqual(fare_rule_2.route_id, + self.fm.a_merge_map[self.r1].route_id) + + self.fm.problem_reporter.assertExpectedProblemsReported(self) + + def testMergeStats(self): + self.assert_(self.fare_rule_merger.GetMergeStats() is None) + + +class TestExceptionProblemReporter(unittest.TestCase): + + def setUp(self): + self.dataset_merger = merge.TripMerger(None) + + def testRaisesErrors(self): + problem_reporter = merge.ExceptionProblemReporter() + self.assertRaises(merge.CalendarsNotDisjoint, + problem_reporter.CalendarsNotDisjoint, + self.dataset_merger) + + def testNoRaiseWarnings(self): + problem_reporter = merge.ExceptionProblemReporter() + problem_reporter.MergeNotImplemented(self.dataset_merger) + + def testRaiseWarnings(self): + problem_reporter = merge.ExceptionProblemReporter(True) + self.assertRaises(merge.MergeNotImplemented, + problem_reporter.MergeNotImplemented, + self.dataset_merger) + + +class TestHTMLProblemReporter(unittest.TestCase): + + def setUp(self): + self.problem_reporter = merge.HTMLProblemReporter() + a_schedule = transitfeed.Schedule() + b_schedule = transitfeed.Schedule() + merged_schedule = transitfeed.Schedule() + self.feed_merger = merge.FeedMerger(a_schedule, b_schedule, + merged_schedule, + self.problem_reporter) + self.dataset_merger = merge.TripMerger(None) + + def testGeneratesSomeHTML(self): + self.problem_reporter.CalendarsNotDisjoint(self.dataset_merger) + self.problem_reporter.MergeNotImplemented(self.dataset_merger) + self.problem_reporter.FareRulesBroken(self.dataset_merger) + self.problem_reporter.SameIdButNotMerged(self.dataset_merger, + 'test', 'unknown reason') + + output_file = StringIO.StringIO() + old_feed_path = '/path/to/old/feed' + new_feed_path = '/path/to/new/feed' + merged_feed_path = '/path/to/merged/feed' + self.problem_reporter.WriteOutput(output_file, self.feed_merger, + old_feed_path, new_feed_path, + merged_feed_path) + + html = output_file.getvalue() + self.assert_(html.startswith('')) + self.assert_(html.endswith('')) + + +class MergeInSubprocessTestCase(util.TempDirTestCaseBase): + def CopyAndModifyTestData(self, zip_path, modify_file, old, new): + """Return path of zip_path copy with old replaced by new in modify_file.""" + zipfile_mem = StringIO.StringIO(open(zip_path, 'rb').read()) + new_zip_path = os.path.join(self.tempdirpath, "modified.zip") + zip = zipfile.ZipFile(zipfile_mem, 'a') + modified_contents = zip.read(modify_file).replace(old, new) + zip.writestr(modify_file, modified_contents) + zip.close() + open(new_zip_path, 'wb').write(zipfile_mem.getvalue()) + return new_zip_path + + def testCrashHandler(self): + (out, err) = self.CheckCallWithPath( + [self.GetPath('merge.py'), '--no_browser', + 'IWantMyCrash', 'file2', 'fileout.zip'], + expected_retcode=127) + self.assertMatchesRegex(r'Yikes', out) + crashout = open('transitfeedcrash.txt').read() + self.assertMatchesRegex(r'For testing the merge crash handler', crashout) + + def testMergeBadCommandLine(self): + (out, err) = self.CheckCallWithPath( + [self.GetPath('merge.py'), '--no_browser'], + expected_retcode=2) + self.assertFalse(out) + self.assertMatchesRegex(r'command line arguments', err) + self.assertFalse(os.path.exists('transitfeedcrash.txt')) + + def testMergeWithWarnings(self): + # Make a copy of good_feed.zip which is not active until 20110101. This + # avoids adding another test/data file. good_feed.zip needs to remain error + # free so it can't start in the future. + future_good_feed = self.CopyAndModifyTestData( + self.GetPath('test/data/good_feed.zip'), 'calendar.txt', + '20070101', '20110101') + (out, err) = self.CheckCallWithPath( + [self.GetPath('merge.py'), '--no_browser', + self.GetPath('test/data/unused_stop'), + future_good_feed, + os.path.join(self.tempdirpath, 'merged-warnings.zip')], + expected_retcode=0) + + def testMergeWithErrors(self): + # Make a copy of good_feed.zip which is not active until 20110101. This + # avoids adding another test/data file. good_feed.zip needs to remain error + # free so it can't start in the future. + future_good_feed = self.CopyAndModifyTestData( + self.GetPath('test/data/good_feed.zip'), 'calendar.txt', + '20070101', '20110101') + (out, err) = self.CheckCallWithPath( + [self.GetPath('merge.py'), '--no_browser', + self.GetPath('test/data/unused_stop'), + future_good_feed], + expected_retcode=2) + + +if __name__ == '__main__': + unittest.main() + --- /dev/null +++ b/origin-src/transitfeed-1.2.5/test/testshapelib.py @@ -1,1 +1,372 @@ - +#!/usr/bin/python2.4 +# +# Copyright (C) 2007 Google Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +"""Tests for transitfeed.shapelib.py""" + +__author__ = 'chris.harrelson.code@gmail.com (Chris Harrelson)' + +import math +from transitfeed import shapelib +from transitfeed.shapelib import Point +from transitfeed.shapelib import Poly +from transitfeed.shapelib import PolyCollection +from transitfeed.shapelib import PolyGraph +import unittest + + +def formatPoint(p, precision=12): + formatString = "(%%.%df, %%.%df, %%.%df)" % (precision, precision, precision) + return formatString % (p.x, p.y, p.z) + + +def formatPoints(points): + return "[%s]" % ", ".join([formatPoint(p, precision=4) for p in points]) + + +class ShapeLibTestBase(unittest.TestCase): + def assertApproxEq(self, a, b): + self.assertAlmostEqual(a, b, 8) + + def assertPointApproxEq(self, a, b): + try: + self.assertApproxEq(a.x, b.x) + self.assertApproxEq(a.y, b.y) + self.assertApproxEq(a.z, b.z) + except AssertionError: + print 'ERROR: %s != %s' % (formatPoint(a), formatPoint(b)) + raise + + def assertPointsApproxEq(self, points1, points2): + try: + self.assertEqual(len(points1), len(points2)) + except AssertionError: + print "ERROR: %s != %s" % (formatPoints(points1), formatPoints(points2)) + raise + for i in xrange(len(points1)): + try: + self.assertPointApproxEq(points1[i], points2[i]) + except AssertionError: + print ('ERROR: points not equal in position %d\n%s != %s' + % (i, formatPoints(points1), formatPoints(points2))) + raise + + +class TestPoints(ShapeLibTestBase): + def testPoints(self): + p = Point(1, 1, 1) + + self.assertApproxEq(p.DotProd(p), 3) + + self.assertApproxEq(p.Norm2(), math.sqrt(3)) + + self.assertPointApproxEq(Point(1.5, 1.5, 1.5), + p.Times(1.5)) + + norm = 1.7320508075688772 + self.assertPointApproxEq(p.Normalize(), + Point(1 / norm, + 1 / norm, + 1 / norm)) + + p2 = Point(1, 0, 0) + self.assertPointApproxEq(p2, p2.Normalize()) + + def testCrossProd(self): + p1 = Point(1, 0, 0).Normalize() + p2 = Point(0, 1 ,0).Normalize() + p1_cross_p2 = p1.CrossProd(p2) + self.assertApproxEq(p1_cross_p2.x, 0) + self.assertApproxEq(p1_cross_p2.y, 0) + self.assertApproxEq(p1_cross_p2.z, 1) + + def testRobustCrossProd(self): + p1 = Point(1, 0, 0) + p2 = Point(1, 0, 0) + self.assertPointApproxEq(Point(0, 0, 0), + p1.CrossProd(p2)) + # only needs to be an arbitrary vector perpendicular to (1, 0, 0) + self.assertPointApproxEq( + Point(0.000000000000000, -0.998598452020993, 0.052925717957113), + p1.RobustCrossProd(p2)) + + def testS2LatLong(self): + point = Point.FromLatLng(30, 40) + self.assertPointApproxEq(Point(0.663413948169, + 0.556670399226, + 0.5), point) + (lat, lng) = point.ToLatLng() + self.assertApproxEq(30, lat) + self.assertApproxEq(40, lng) + + def testOrtho(self): + point = Point(1, 1, 1) + ortho = point.Ortho() + self.assertApproxEq(ortho.DotProd(point), 0) + + def testAngle(self): + point1 = Point(1, 1, 0).Normalize() + point2 = Point(0, 1, 0) + self.assertApproxEq(45, point1.Angle(point2) * 360 / (2 * math.pi)) + self.assertApproxEq(point1.Angle(point2), point2.Angle(point1)) + + def testGetDistanceMeters(self): + point1 = Point.FromLatLng(40.536895,-74.203033) + point2 = Point.FromLatLng(40.575239,-74.112825) + self.assertApproxEq(8732.623770873237, + point1.GetDistanceMeters(point2)) + + +class TestClosestPoint(ShapeLibTestBase): + def testGetClosestPoint(self): + x = Point(1, 1, 0).Normalize() + a = Point(1, 0, 0) + b = Point(0, 1, 0) + + closest = shapelib.GetClosestPoint(x, a, b) + self.assertApproxEq(0.707106781187, closest.x) + self.assertApproxEq(0.707106781187, closest.y) + self.assertApproxEq(0.0, closest.z) + + +class TestPoly(ShapeLibTestBase): + def testGetClosestPointShape(self): + poly = Poly() + + poly.AddPoint(Point(1, 1, 0).Normalize()) + self.assertPointApproxEq(Point( + 0.707106781187, 0.707106781187, 0), poly.GetPoint(0)) + + point = Point(0, 1, 1).Normalize() + self.assertPointApproxEq(Point(1, 1, 0).Normalize(), + poly.GetClosestPoint(point)[0]) + + poly.AddPoint(Point(0, 1, 1).Normalize()) + + self.assertPointApproxEq( + Point(0, 1, 1).Normalize(), + poly.GetClosestPoint(point)[0]) + + def testCutAtClosestPoint(self): + poly = Poly() + poly.AddPoint(Point(0, 1, 0).Normalize()) + poly.AddPoint(Point(0, 0.5, 0.5).Normalize()) + poly.AddPoint(Point(0, 0, 1).Normalize()) + + (before, after) = \ + poly.CutAtClosestPoint(Point(0, 0.3, 0.7).Normalize()) + + self.assert_(2 == before.GetNumPoints()) + self.assert_(2 == before.GetNumPoints()) + self.assertPointApproxEq( + Point(0, 0.707106781187, 0.707106781187), before.GetPoint(1)) + + self.assertPointApproxEq( + Point(0, 0.393919298579, 0.919145030018), after.GetPoint(0)) + + poly = Poly() + poly.AddPoint(Point.FromLatLng(40.527035999999995, -74.191265999999999)) + poly.AddPoint(Point.FromLatLng(40.526859999999999, -74.191140000000004)) + poly.AddPoint(Point.FromLatLng(40.524681000000001, -74.189579999999992)) + poly.AddPoint(Point.FromLatLng(40.523128999999997, -74.188467000000003)) + poly.AddPoint(Point.FromLatLng(40.523054999999999, -74.188676000000001)) + pattern = Poly() + pattern.AddPoint(Point.FromLatLng(40.52713, + -74.191146000000003)) + self.assertApproxEq(14.564268281551, pattern.GreedyPolyMatchDist(poly)) + + def testMergePolys(self): + poly1 = Poly(name="Foo") + poly1.AddPoint(Point(0, 1, 0).Normalize()) + poly1.AddPoint(Point(0, 0.5, 0.5).Normalize()) + poly1.AddPoint(Point(0, 0, 1).Normalize()) + poly1.AddPoint(Point(1, 1, 1).Normalize()) + + poly2 = Poly() + poly3 = Poly(name="Bar") + poly3.AddPoint(Point(1, 1, 1).Normalize()) + poly3.AddPoint(Point(2, 0.5, 0.5).Normalize()) + + merged1 = Poly.MergePolys([poly1, poly2]) + self.assertPointsApproxEq(poly1.GetPoints(), merged1.GetPoints()) + self.assertEqual("Foo;", merged1.GetName()) + + merged2 = Poly.MergePolys([poly2, poly3]) + self.assertPointsApproxEq(poly3.GetPoints(), merged2.GetPoints()) + self.assertEqual(";Bar", merged2.GetName()) + + merged3 = Poly.MergePolys([poly1, poly2, poly3], merge_point_threshold=0) + mergedPoints = poly1.GetPoints()[:] + mergedPoints.append(poly3.GetPoint(-1)) + self.assertPointsApproxEq(mergedPoints, merged3.GetPoints()) + self.assertEqual("Foo;;Bar", merged3.GetName()) + + merged4 = Poly.MergePolys([poly2]) + self.assertEqual("", merged4.GetName()) + self.assertEqual(0, merged4.GetNumPoints()) + + # test merging two nearby points + newPoint = poly1.GetPoint(-1).Plus(Point(0.000001, 0, 0)).Normalize() + poly1.AddPoint(newPoint) + distance = poly1.GetPoint(-1).GetDistanceMeters(poly3.GetPoint(0)) + self.assertTrue(distance <= 10) + self.assertTrue(distance > 5) + + merged5 = Poly.MergePolys([poly1, poly2, poly3], merge_point_threshold=10) + mergedPoints = poly1.GetPoints()[:] + mergedPoints.append(poly3.GetPoint(-1)) + self.assertPointsApproxEq(mergedPoints, merged5.GetPoints()) + self.assertEqual("Foo;;Bar", merged5.GetName()) + + merged6 = Poly.MergePolys([poly1, poly2, poly3], merge_point_threshold=5) + mergedPoints = poly1.GetPoints()[:] + mergedPoints += poly3.GetPoints() + self.assertPointsApproxEq(mergedPoints, merged6.GetPoints()) + self.assertEqual("Foo;;Bar", merged6.GetName()) + + def testReversed(self): + p1 = Point(1, 0, 0).Normalize() + p2 = Point(0, 0.5, 0.5).Normalize() + p3 = Point(0.3, 0.8, 0.5).Normalize() + poly1 = Poly([p1, p2, p3]) + self.assertPointsApproxEq([p3, p2, p1], poly1.Reversed().GetPoints()) + + def testLengthMeters(self): + p1 = Point(1, 0, 0).Normalize() + p2 = Point(0, 0.5, 0.5).Normalize() + p3 = Point(0.3, 0.8, 0.5).Normalize() + poly0 = Poly([p1]) + poly1 = Poly([p1, p2]) + poly2 = Poly([p1, p2, p3]) + try: + poly0.LengthMeters() + self.fail("Should have thrown AssertionError") + except AssertionError: + pass + + p1_p2 = p1.GetDistanceMeters(p2) + p2_p3 = p2.GetDistanceMeters(p3) + self.assertEqual(p1_p2, poly1.LengthMeters()) + self.assertEqual(p1_p2 + p2_p3, poly2.LengthMeters()) + self.assertEqual(p1_p2 + p2_p3, poly2.Reversed().LengthMeters()) + + +class TestCollection(ShapeLibTestBase): + def testPolyMatch(self): + poly = Poly() + poly.AddPoint(Point(0, 1, 0).Normalize()) + poly.AddPoint(Point(0, 0.5, 0.5).Normalize()) + poly.AddPoint(Point(0, 0, 1).Normalize()) + + collection = PolyCollection() + collection.AddPoly(poly) + match = collection.FindMatchingPolys(Point(0, 1, 0), + Point(0, 0, 1)) + self.assert_(len(match) == 1 and match[0] == poly) + + match = collection.FindMatchingPolys(Point(0, 1, 0), + Point(0, 1, 0)) + self.assert_(len(match) == 0) + + poly = Poly() + poly.AddPoint(Point.FromLatLng(45.585212,-122.586136)) + poly.AddPoint(Point.FromLatLng(45.586654,-122.587595)) + collection = PolyCollection() + collection.AddPoly(poly) + + match = collection.FindMatchingPolys( + Point.FromLatLng(45.585212,-122.586136), + Point.FromLatLng(45.586654,-122.587595)) + self.assert_(len(match) == 1 and match[0] == poly) + + match = collection.FindMatchingPolys( + Point.FromLatLng(45.585219,-122.586136), + Point.FromLatLng(45.586654,-122.587595)) + self.assert_(len(match) == 1 and match[0] == poly) + + self.assertApproxEq(0.0, poly.GreedyPolyMatchDist(poly)) + + match = collection.FindMatchingPolys( + Point.FromLatLng(45.587212,-122.586136), + Point.FromLatLng(45.586654,-122.587595)) + self.assert_(len(match) == 0) + + +class TestGraph(ShapeLibTestBase): + def testReconstructPath(self): + p1 = Point(1, 0, 0).Normalize() + p2 = Point(0, 0.5, 0.5).Normalize() + p3 = Point(0.3, 0.8, 0.5).Normalize() + poly1 = Poly([p1, p2]) + poly2 = Poly([p3, p2]) + came_from = { + p2: (p1, poly1), + p3: (p2, poly2) + } + + graph = PolyGraph() + reconstructed1 = graph._ReconstructPath(came_from, p1) + self.assertEqual(0, reconstructed1.GetNumPoints()) + + reconstructed2 = graph._ReconstructPath(came_from, p2) + self.assertPointsApproxEq([p1, p2], reconstructed2.GetPoints()) + + reconstructed3 = graph._ReconstructPath(came_from, p3) + self.assertPointsApproxEq([p1, p2, p3], reconstructed3.GetPoints()) + + def testShortestPath(self): + p1 = Point(1, 0, 0).Normalize() + p2 = Point(0, 0.5, 0.5).Normalize() + p3 = Point(0.3, 0.8, 0.5).Normalize() + p4 = Point(0.7, 0.7, 0.5).Normalize() + poly1 = Poly([p1, p2, p3], "poly1") + poly2 = Poly([p4, p3], "poly2") + poly3 = Poly([p4, p1], "poly3") + graph = PolyGraph() + graph.AddPoly(poly1) + graph.AddPoly(poly2) + graph.AddPoly(poly3) + path = graph.ShortestPath(p1, p4) + self.assert_(path is not None) + self.assertPointsApproxEq([p1, p4], path.GetPoints()) + + path = graph.ShortestPath(p1, p3) + self.assert_(path is not None) + self.assertPointsApproxEq([p1, p4, p3], path.GetPoints()) + + path = graph.ShortestPath(p3, p1) + self.assert_(path is not None) + self.assertPointsApproxEq([p3, p4, p1], path.GetPoints()) + + def testFindShortestMultiPointPath(self): + p1 = Point(1, 0, 0).Normalize() + p2 = Point(0.5, 0.5, 0).Normalize() + p3 = Point(0.5, 0.5, 0.1).Normalize() + p4 = Point(0, 1, 0).Normalize() + poly1 = Poly([p1, p2, p3], "poly1") + poly2 = Poly([p4, p3], "poly2") + poly3 = Poly([p4, p1], "poly3") + graph = PolyGraph() + graph.AddPoly(poly1) + graph.AddPoly(poly2) + graph.AddPoly(poly3) + path = graph.FindShortestMultiPointPath([p1, p3, p4]) + self.assert_(path is not None) + self.assertPointsApproxEq([p1, p2, p3, p4], path.GetPoints()) + + +if __name__ == '__main__': + unittest.main() + --- /dev/null +++ b/origin-src/transitfeed-1.2.5/test/testtransitfeed.py @@ -1,1 +1,4705 @@ - +#!/usr/bin/python2.5 + +# Copyright (C) 2007 Google Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# Unit tests for the transitfeed module. + +import datetime +from datetime import date +import dircache +import os.path +import re +import sys +import tempfile +import time +import transitfeed +import unittest +import util +from util import RecordingProblemReporter +from StringIO import StringIO +import zipfile +import zlib + + +def DataPath(path): + here = os.path.dirname(__file__) + return os.path.join(here, 'data', path) + +def GetDataPathContents(): + here = os.path.dirname(__file__) + return dircache.listdir(os.path.join(here, 'data')) + + +class ExceptionProblemReporterNoExpiration( + transitfeed.ExceptionProblemReporter): + """Ignores feed expiration problems. + + Use TestFailureProblemReporter in new code because it fails more cleanly, is + easier to extend and does more thorough checking. + """ + + def __init__(self): + transitfeed.ExceptionProblemReporter.__init__(self, raise_warnings=True) + + def ExpirationDate(self, expiration, context=None): + pass # We don't want to give errors about our test data + + +class TestFailureProblemReporter(transitfeed.ProblemReporter): + """Causes a test failure immediately on any problem.""" + def __init__(self, test_case, ignore_types=("ExpirationDate",)): + transitfeed.ProblemReporter.__init__(self) + self.test_case = test_case + self._ignore_types = ignore_types or set() + + def _Report(self, e): + # These should never crash + formatted_problem = e.FormatProblem() + formatted_context = e.FormatContext() + exception_class = e.__class__.__name__ + if exception_class in self._ignore_types: + return + self.test_case.fail( + "%s: %s\n%s" % (exception_class, formatted_problem, formatted_context)) + + +class UnrecognizedColumnRecorder(RecordingProblemReporter): + """Keeps track of unrecognized column errors.""" + def __init__(self, test_case): + RecordingProblemReporter.__init__(self, test_case, + ignore_types=("ExpirationDate",)) + self.column_errors = [] + + def UnrecognizedColumn(self, file_name, column_name, context=None): + self.column_errors.append((file_name, column_name)) + + +class RedirectStdOutTestCaseBase(unittest.TestCase): + """Save stdout to the StringIO buffer self.this_stdout""" + def setUp(self): + self.saved_stdout = sys.stdout + self.this_stdout = StringIO() + sys.stdout = self.this_stdout + + def tearDown(self): + sys.stdout = self.saved_stdout + self.this_stdout.close() + + +# ensure that there are no exceptions when attempting to load +# (so that the validator won't crash) +class NoExceptionTestCase(RedirectStdOutTestCaseBase): + def runTest(self): + for feed in GetDataPathContents(): + loader = transitfeed.Loader(DataPath(feed), + problems=transitfeed.ProblemReporter(), + extra_validation=True) + schedule = loader.Load() + schedule.Validate() + + +class EndOfLineCheckerTestCase(unittest.TestCase): + def setUp(self): + self.problems = RecordingProblemReporter(self) + + def RunEndOfLineChecker(self, end_of_line_checker): + # Iterating using for calls end_of_line_checker.next() until a + # StopIteration is raised. EndOfLineChecker does the final check for a mix + # of CR LF and LF ends just before raising StopIteration. + for line in end_of_line_checker: + pass + + def testInvalidLineEnd(self): + f = transitfeed.EndOfLineChecker(StringIO("line1\r\r\nline2"), + "", + self.problems) + self.RunEndOfLineChecker(f) + e = self.problems.PopException("InvalidLineEnd") + self.assertEqual(e.file_name, "") + self.assertEqual(e.row_num, 1) + self.assertEqual(e.bad_line_end, r"\r\r\n") + self.problems.AssertNoMoreExceptions() + + def testInvalidLineEndToo(self): + f = transitfeed.EndOfLineChecker( + StringIO("line1\nline2\r\nline3\r\r\r\n"), + "", self.problems) + self.RunEndOfLineChecker(f) + e = self.problems.PopException("InvalidLineEnd") + self.assertEqual(e.file_name, "") + self.assertEqual(e.row_num, 3) + self.assertEqual(e.bad_line_end, r"\r\r\r\n") + e = self.problems.PopException("OtherProblem") + self.assertEqual(e.file_name, "") + self.assertTrue(e.description.find("consistent line end") != -1) + self.problems.AssertNoMoreExceptions() + + def testEmbeddedCr(self): + f = transitfeed.EndOfLineChecker( + StringIO("line1\rline1b"), + "", self.problems) + self.RunEndOfLineChecker(f) + e = self.problems.PopException("OtherProblem") + self.assertEqual(e.file_name, "") + self.assertEqual(e.row_num, 1) + self.assertEqual(e.FormatProblem(), + "Line contains ASCII Carriage Return 0x0D, \\r") + self.problems.AssertNoMoreExceptions() + + def testEmbeddedUtf8NextLine(self): + f = transitfeed.EndOfLineChecker( + StringIO("line1b\xc2\x85"), + "", self.problems) + self.RunEndOfLineChecker(f) + e = self.problems.PopException("OtherProblem") + self.assertEqual(e.file_name, "") + self.assertEqual(e.row_num, 1) + self.assertEqual(e.FormatProblem(), + "Line contains Unicode NEXT LINE SEPARATOR U+0085") + self.problems.AssertNoMoreExceptions() + + def testEndOfLineMix(self): + f = transitfeed.EndOfLineChecker( + StringIO("line1\nline2\r\nline3\nline4"), + "", self.problems) + self.RunEndOfLineChecker(f) + e = self.problems.PopException("OtherProblem") + self.assertEqual(e.file_name, "") + self.assertEqual(e.FormatProblem(), + "Found 1 CR LF \"\\r\\n\" line end (line 2) and " + "2 LF \"\\n\" line ends (lines 1, 3). A file must use a " + "consistent line end.") + self.problems.AssertNoMoreExceptions() + + def testEndOfLineManyMix(self): + f = transitfeed.EndOfLineChecker( + StringIO("1\n2\n3\n4\n5\n6\n7\r\n8\r\n9\r\n10\r\n11\r\n"), + "", self.problems) + self.RunEndOfLineChecker(f) + e = self.problems.PopException("OtherProblem") + self.assertEqual(e.file_name, "") + self.assertEqual(e.FormatProblem(), + "Found 5 CR LF \"\\r\\n\" line ends (lines 7, 8, 9, 10, " + "11) and 6 LF \"\\n\" line ends (lines 1, 2, 3, 4, 5, " + "...). A file must use a consistent line end.") + self.problems.AssertNoMoreExceptions() + + def testLoad(self): + loader = transitfeed.Loader( + DataPath("bad_eol.zip"), problems=self.problems, extra_validation=True) + loader.Load() + + e = self.problems.PopException("InvalidLineEnd") + self.assertEqual(e.file_name, "routes.txt") + self.assertEqual(e.row_num, 5) + self.assertTrue(e.FormatProblem().find(r"\r\r\n") != -1) + + e = self.problems.PopException("OtherProblem") + self.assertEqual(e.file_name, "calendar.txt") + self.assertTrue(re.search( + r"Found 1 CR LF.* \(line 2\) and 2 LF .*\(lines 1, 3\)", + e.FormatProblem())) + + e = self.problems.PopException("OtherProblem") + self.assertEqual(e.file_name, "trips.txt") + self.assertEqual(e.row_num, 1) + self.assertTrue(re.search( + r"contains ASCII Form Feed", + e.FormatProblem())) + # TODO(Tom): avoid this duplicate error for the same issue + e = self.problems.PopException("CsvSyntax") + self.assertEqual(e.row_num, 1) + self.assertTrue(re.search( + r"header row should not contain any space char", + e.FormatProblem())) + + self.problems.AssertNoMoreExceptions() + + +class LoadTestCase(unittest.TestCase): + def setUp(self): + self.problems = RecordingProblemReporter(self, ("ExpirationDate",)) + + def Load(self, feed_name): + loader = transitfeed.Loader( + DataPath(feed_name), problems=self.problems, extra_validation=True) + loader.Load() + + def ExpectInvalidValue(self, feed_name, column_name): + self.Load(feed_name) + self.problems.PopInvalidValue(column_name) + self.problems.AssertNoMoreExceptions() + + def ExpectMissingFile(self, feed_name, file_name): + self.Load(feed_name) + e = self.problems.PopException("MissingFile") + self.assertEqual(file_name, e.file_name) + # Don't call AssertNoMoreExceptions() because a missing file causes + # many errors. + + +class LoadFromZipTestCase(unittest.TestCase): + def runTest(self): + loader = transitfeed.Loader( + DataPath('good_feed.zip'), + problems = TestFailureProblemReporter(self), + extra_validation = True) + loader.Load() + + # now try using Schedule.Load + schedule = transitfeed.Schedule( + problem_reporter=ExceptionProblemReporterNoExpiration()) + schedule.Load(DataPath('good_feed.zip'), extra_validation=True) + + +class LoadAndRewriteFromZipTestCase(unittest.TestCase): + def runTest(self): + schedule = transitfeed.Schedule( + problem_reporter=ExceptionProblemReporterNoExpiration()) + schedule.Load(DataPath('good_feed.zip'), extra_validation=True) + + # Finally see if write crashes + schedule.WriteGoogleTransitFeed(tempfile.TemporaryFile()) + + +class LoadFromDirectoryTestCase(unittest.TestCase): + def runTest(self): + loader = transitfeed.Loader( + DataPath('good_feed'), + problems = TestFailureProblemReporter(self), + extra_validation = True) + loader.Load() + + +class LoadUnknownFeedTestCase(unittest.TestCase): + def runTest(self): + feed_name = DataPath('unknown_feed') + loader = transitfeed.Loader( + feed_name, + problems = ExceptionProblemReporterNoExpiration(), + extra_validation = True) + try: + loader.Load() + self.fail('FeedNotFound exception expected') + except transitfeed.FeedNotFound, e: + self.assertEqual(feed_name, e.feed_name) + +class LoadUnknownFormatTestCase(unittest.TestCase): + def runTest(self): + feed_name = DataPath('unknown_format.zip') + loader = transitfeed.Loader( + feed_name, + problems = ExceptionProblemReporterNoExpiration(), + extra_validation = True) + try: + loader.Load() + self.fail('UnknownFormat exception expected') + except transitfeed.UnknownFormat, e: + self.assertEqual(feed_name, e.feed_name) + +class LoadUnrecognizedColumnsTestCase(unittest.TestCase): + def runTest(self): + problems = UnrecognizedColumnRecorder(self) + loader = transitfeed.Loader(DataPath('unrecognized_columns'), + problems=problems) + loader.Load() + found_errors = set(problems.column_errors) + expected_errors = set([ + ('agency.txt', 'agency_lange'), + ('stops.txt', 'stop_uri'), + ('routes.txt', 'Route_Text_Color'), + ('calendar.txt', 'leap_day'), + ('calendar_dates.txt', 'leap_day'), + ('trips.txt', 'sharpe_id'), + ('stop_times.txt', 'shapedisttraveled'), + ('stop_times.txt', 'drop_off_time'), + ('fare_attributes.txt', 'transfer_time'), + ('fare_rules.txt', 'source_id'), + ('frequencies.txt', 'superfluous'), + ('transfers.txt', 'to_stop') + ]) + + # Now make sure we got the unrecognized column errors that we expected. + not_expected = found_errors.difference(expected_errors) + self.failIf(not_expected, 'unexpected errors: %s' % str(not_expected)) + not_found = expected_errors.difference(found_errors) + self.failIf(not_found, 'expected but not found: %s' % str(not_found)) + +class LoadExtraCellValidationTestCase(LoadTestCase): + """Check that the validation detects too many cells in a row.""" + def runTest(self): + self.Load('extra_row_cells') + e = self.problems.PopException("OtherProblem") + self.assertEquals("routes.txt", e.file_name) + self.assertEquals(4, e.row_num) + self.problems.AssertNoMoreExceptions() + + +class LoadMissingCellValidationTestCase(LoadTestCase): + """Check that the validation detects missing cells in a row.""" + def runTest(self): + self.Load('missing_row_cells') + e = self.problems.PopException("OtherProblem") + self.assertEquals("routes.txt", e.file_name) + self.assertEquals(4, e.row_num) + self.problems.AssertNoMoreExceptions() + +class LoadUnknownFileTestCase(unittest.TestCase): + """Check that the validation detects unknown files.""" + def runTest(self): + feed_name = DataPath('unknown_file') + self.problems = RecordingProblemReporter(self, ("ExpirationDate",)) + loader = transitfeed.Loader( + feed_name, + problems = self.problems, + extra_validation = True) + loader.Load() + e = self.problems.PopException('UnknownFile') + self.assertEqual('frecuencias.txt', e.file_name) + self.problems.AssertNoMoreExceptions() + +class LoadUTF8BOMTestCase(unittest.TestCase): + def runTest(self): + loader = transitfeed.Loader( + DataPath('utf8bom'), + problems = TestFailureProblemReporter(self), + extra_validation = True) + loader.Load() + + +class LoadUTF16TestCase(unittest.TestCase): + def runTest(self): + # utf16 generated by `recode utf8..utf16 *' + loader = transitfeed.Loader( + DataPath('utf16'), + problems = transitfeed.ExceptionProblemReporter(), + extra_validation = True) + try: + loader.Load() + # TODO: make sure processing proceeds beyond the problem + self.fail('FileFormat exception expected') + except transitfeed.FileFormat, e: + # make sure these don't raise an exception + self.assertTrue(re.search(r'encoded in utf-16', e.FormatProblem())) + e.FormatContext() + + +class LoadNullTestCase(unittest.TestCase): + def runTest(self): + loader = transitfeed.Loader( + DataPath('contains_null'), + problems = transitfeed.ExceptionProblemReporter(), + extra_validation = True) + try: + loader.Load() + self.fail('FileFormat exception expected') + except transitfeed.FileFormat, e: + self.assertTrue(re.search(r'contains a null', e.FormatProblem())) + # make sure these don't raise an exception + e.FormatContext() + + +class ProblemReporterTestCase(RedirectStdOutTestCaseBase): + # Unittest for problem reporter + def testContextWithBadUnicodeProblem(self): + pr = transitfeed.ProblemReporter() + # Context has valid unicode values + pr.SetFileContext('filename.foo', 23, + [u'Andr\202', u'Person \uc720 foo', None], + [u'1\202', u'2\202', u'3\202']) + pr.OtherProblem('test string') + pr.OtherProblem(u'\xff\xfe\x80\x88') + # Invalid ascii and utf-8. encode('utf-8') and decode('utf-8') will fail + # for this value + pr.OtherProblem('\xff\xfe\x80\x88') + self.assertTrue(re.search(r"test string", self.this_stdout.getvalue())) + self.assertTrue(re.search(r"filename.foo:23", self.this_stdout.getvalue())) + + def testNoContextWithBadUnicode(self): + pr = transitfeed.ProblemReporter() + pr.OtherProblem('test string') + pr.OtherProblem(u'\xff\xfe\x80\x88') + # Invalid ascii and utf-8. encode('utf-8') and decode('utf-8') will fail + # for this value + pr.OtherProblem('\xff\xfe\x80\x88') + self.assertTrue(re.search(r"test string", self.this_stdout.getvalue())) + + def testBadUnicodeContext(self): + pr = transitfeed.ProblemReporter() + pr.SetFileContext('filename.foo', 23, + [u'Andr\202', 'Person \xff\xfe\x80\x88 foo', None], + [u'1\202', u'2\202', u'3\202']) + pr.OtherProblem("help, my context isn't utf-8!") + self.assertTrue(re.search(r"help, my context", self.this_stdout.getvalue())) + self.assertTrue(re.search(r"filename.foo:23", self.this_stdout.getvalue())) + + def testLongWord(self): + # Make sure LineWrap doesn't puke + pr = transitfeed.ProblemReporter() + pr.OtherProblem('1111untheontuhoenuthoentuhntoehuontehuntoehuntoehunto' + '2222oheuntheounthoeunthoeunthoeuntheontuheontuhoue') + self.assertTrue(re.search(r"1111.+2222", self.this_stdout.getvalue())) + + +class BadProblemReporterTestCase(RedirectStdOutTestCaseBase): + """Make sure ProblemReporter doesn't crash when given bad unicode data and + does find some error""" + # tom.brown.code-utf8_weaknesses fixed a bug with problem reporter and bad + # utf-8 strings + def runTest(self): + loader = transitfeed.Loader( + DataPath('bad_utf8'), + problems = transitfeed.ProblemReporter(), + extra_validation = True) + loader.Load() + # raises exception if not found + self.this_stdout.getvalue().index('Invalid value') + + +class BadUtf8TestCase(LoadTestCase): + def runTest(self): + self.Load('bad_utf8') + self.problems.PopException("UnrecognizedColumn") + self.problems.PopInvalidValue("agency_name", "agency.txt") + self.problems.PopInvalidValue("stop_name", "stops.txt") + self.problems.PopInvalidValue("route_short_name", "routes.txt") + self.problems.PopInvalidValue("route_long_name", "routes.txt") + self.problems.PopInvalidValue("trip_headsign", "trips.txt") + self.problems.PopInvalidValue("stop_headsign", "stop_times.txt") + self.problems.AssertNoMoreExceptions() + + +class LoadMissingAgencyTestCase(LoadTestCase): + def runTest(self): + self.ExpectMissingFile('missing_agency', 'agency.txt') + + +class LoadMissingStopsTestCase(LoadTestCase): + def runTest(self): + self.ExpectMissingFile('missing_stops', 'stops.txt') + + +class LoadMissingRoutesTestCase(LoadTestCase): + def runTest(self): + self.ExpectMissingFile('missing_routes', 'routes.txt') + + +class LoadMissingTripsTestCase(LoadTestCase): + def runTest(self): + self.ExpectMissingFile('missing_trips', 'trips.txt') + + +class LoadMissingStopTimesTestCase(LoadTestCase): + def runTest(self): + self.ExpectMissingFile('missing_stop_times', 'stop_times.txt') + + +class LoadMissingCalendarTestCase(LoadTestCase): + def runTest(self): + self.ExpectMissingFile('missing_calendar', 'calendar.txt') + + +class EmptyFileTestCase(unittest.TestCase): + def runTest(self): + loader = transitfeed.Loader( + DataPath('empty_file'), + problems = ExceptionProblemReporterNoExpiration(), + extra_validation = True) + try: + loader.Load() + self.fail('EmptyFile exception expected') + except transitfeed.EmptyFile, e: + self.assertEqual('agency.txt', e.file_name) + + +class MissingColumnTestCase(unittest.TestCase): + def runTest(self): + loader = transitfeed.Loader( + DataPath('missing_column'), + problems = ExceptionProblemReporterNoExpiration(), + extra_validation = True) + try: + loader.Load() + self.fail('MissingColumn exception expected') + except transitfeed.MissingColumn, e: + self.assertEqual('agency.txt', e.file_name) + self.assertEqual('agency_name', e.column_name) + + +class ZeroBasedStopSequenceTestCase(LoadTestCase): + def runTest(self): + self.ExpectInvalidValue('negative_stop_sequence', 'stop_sequence') + + +class DuplicateStopTestCase(unittest.TestCase): + def runTest(self): + schedule = transitfeed.Schedule( + problem_reporter=ExceptionProblemReporterNoExpiration()) + try: + schedule.Load(DataPath('duplicate_stop'), extra_validation=True) + self.fail('OtherProblem exception expected') + except transitfeed.OtherProblem: + pass + +class DuplicateStopSequenceTestCase(unittest.TestCase): + def runTest(self): + problems = RecordingProblemReporter(self, ("ExpirationDate",)) + schedule = transitfeed.Schedule(problem_reporter=problems) + schedule.Load(DataPath('duplicate_stop_sequence'), extra_validation=True) + e = problems.PopException('InvalidValue') + self.assertEqual('stop_sequence', e.column_name) + problems.AssertNoMoreExceptions() + + +class MissingEndpointTimesTestCase(unittest.TestCase): + def runTest(self): + schedule = transitfeed.Schedule( + problem_reporter=ExceptionProblemReporterNoExpiration()) + try: + schedule.Load(DataPath('missing_endpoint_times'), extra_validation=True) + self.fail('InvalidValue exception expected') + except transitfeed.InvalidValue, e: + self.assertEqual('departure_time', e.column_name) + self.assertEqual('', e.value) + + +class DuplicateScheduleIDTestCase(unittest.TestCase): + def runTest(self): + schedule = transitfeed.Schedule( + problem_reporter=ExceptionProblemReporterNoExpiration()) + try: + schedule.Load(DataPath('duplicate_schedule_id'), extra_validation=True) + self.fail('DuplicateID exception expected') + except transitfeed.DuplicateID: + pass + +class ColorLuminanceTestCase(unittest.TestCase): + def runTest(self): + self.assertEqual(transitfeed.ColorLuminance('000000'), 0, + "ColorLuminance('000000') should be zero") + self.assertEqual(transitfeed.ColorLuminance('FFFFFF'), 255, + "ColorLuminance('FFFFFF') should be 255") + RGBmsg = ("ColorLuminance('RRGGBB') should be " + "0.299* + 0.587* + 0.114*") + decimal_places_tested = 8 + self.assertAlmostEqual(transitfeed.ColorLuminance('640000'), 29.9, + decimal_places_tested, RGBmsg) + self.assertAlmostEqual(transitfeed.ColorLuminance('006400'), 58.7, + decimal_places_tested, RGBmsg) + self.assertAlmostEqual(transitfeed.ColorLuminance('000064'), 11.4, + decimal_places_tested, RGBmsg) + self.assertAlmostEqual(transitfeed.ColorLuminance('1171B3'), + 0.299*17 + 0.587*113 + 0.114*179, + decimal_places_tested, RGBmsg) + +INVALID_VALUE = Exception() +class ValidationTestCase(util.TestCaseAsserts): + def setUp(self): + self.problems = RecordingProblemReporter(self, ("ExpirationDate",)) + + def ExpectNoProblems(self, object): + self.problems.AssertNoMoreExceptions() + object.Validate(self.problems) + self.problems.AssertNoMoreExceptions() + + # TODO: Get rid of Expect*Closure methods. With the + # RecordingProblemReporter it is now possible to replace + # self.ExpectMissingValueInClosure(lambda: o.method(...), foo) + # with + # o.method(...) + # self.ExpectMissingValueInClosure(foo) + # because problems don't raise an exception. This has the advantage of + # making it easy and clear to test the return value of o.method(...) and + # easier to test for a sequence of problems caused by one call. + def ExpectMissingValue(self, object, column_name): + self.ExpectMissingValueInClosure(column_name, + lambda: object.Validate(self.problems)) + + def ExpectMissingValueInClosure(self, column_name, c): + self.problems.AssertNoMoreExceptions() + rv = c() + e = self.problems.PopException('MissingValue') + self.assertEqual(column_name, e.column_name) + # these should not throw any exceptions + e.FormatProblem() + e.FormatContext() + self.problems.AssertNoMoreExceptions() + + def ExpectInvalidValue(self, object, column_name, value=INVALID_VALUE): + self.ExpectInvalidValueInClosure(column_name, value, + lambda: object.Validate(self.problems)) + + def ExpectInvalidValueInClosure(self, column_name, value=INVALID_VALUE, + c=None): + self.problems.AssertNoMoreExceptions() + rv = c() + e = self.problems.PopException('InvalidValue') + self.assertEqual(column_name, e.column_name) + if value != INVALID_VALUE: + self.assertEqual(value, e.value) + # these should not throw any exceptions + e.FormatProblem() + e.FormatContext() + self.problems.AssertNoMoreExceptions() + + def ExpectOtherProblem(self, object): + self.ExpectOtherProblemInClosure(lambda: object.Validate(self.problems)) + + def ExpectOtherProblemInClosure(self, c): + self.problems.AssertNoMoreExceptions() + rv = c() + e = self.problems.PopException('OtherProblem') + # these should not throw any exceptions + e.FormatProblem() + e.FormatContext() + self.problems.AssertNoMoreExceptions() + + +class AgencyValidationTestCase(ValidationTestCase): + def runTest(self): + # success case + agency = transitfeed.Agency(name='Test Agency', url='http://example.com', + timezone='America/Los_Angeles', id='TA', + lang='xh') + self.ExpectNoProblems(agency) + + # bad agency + agency = transitfeed.Agency(name=' ', url='http://example.com', + timezone='America/Los_Angeles', id='TA') + self.ExpectMissingValue(agency, 'agency_name') + + # missing url + agency = transitfeed.Agency(name='Test Agency', + timezone='America/Los_Angeles', id='TA') + self.ExpectMissingValue(agency, 'agency_url') + + # bad url + agency = transitfeed.Agency(name='Test Agency', url='www.example.com', + timezone='America/Los_Angeles', id='TA') + self.ExpectInvalidValue(agency, 'agency_url') + + # bad time zone + agency = transitfeed.Agency(name='Test Agency', url='http://example.com', + timezone='America/Alviso', id='TA') + agency.Validate(self.problems) + e = self.problems.PopInvalidValue('agency_timezone') + self.assertMatchesRegex('"America/Alviso" is not a common timezone', + e.FormatProblem()) + self.problems.AssertNoMoreExceptions() + + # bad language code + agency = transitfeed.Agency(name='Test Agency', url='http://example.com', + timezone='America/Los_Angeles', id='TA', + lang='English') + self.ExpectInvalidValue(agency, 'agency_lang') + + # bad 2-letter lanugage code + agency = transitfeed.Agency(name='Test Agency', url='http://example.com', + timezone='America/Los_Angeles', id='TA', + lang='xx') + self.ExpectInvalidValue(agency, 'agency_lang') + + # capitalized language code is OK + agency = transitfeed.Agency(name='Test Agency', url='http://example.com', + timezone='America/Los_Angeles', id='TA', + lang='EN') + self.ExpectNoProblems(agency) + + # extra attribute in constructor is fine, only checked when loading a file + agency = transitfeed.Agency(name='Test Agency', url='http://example.com', + timezone='America/Los_Angeles', + agency_mission='monorail you there') + self.ExpectNoProblems(agency) + + # extra attribute in assigned later is also fine + agency = transitfeed.Agency(name='Test Agency', url='http://example.com', + timezone='America/Los_Angeles') + agency.agency_mission='monorail you there' + self.ExpectNoProblems(agency) + + # Multiple problems + agency = transitfeed.Agency(name='Test Agency', url='www.example.com', + timezone='America/West Coast', id='TA') + self.assertEquals(False, agency.Validate(self.problems)) + e = self.problems.PopException('InvalidValue') + self.assertEqual(e.column_name, 'agency_url') + e = self.problems.PopException('InvalidValue') + self.assertEqual(e.column_name, 'agency_timezone') + self.problems.AssertNoMoreExceptions() + + + +class AgencyAttributesTestCase(ValidationTestCase): + def testCopy(self): + agency = transitfeed.Agency(field_dict={'agency_name': 'Test Agency', + 'agency_url': 'http://example.com', + 'timezone': 'America/Los_Angeles', + 'agency_mission': 'get you there'}) + self.assertEquals(agency.agency_mission, 'get you there') + agency_copy = transitfeed.Agency(field_dict=agency) + self.assertEquals(agency_copy.agency_mission, 'get you there') + self.assertEquals(agency_copy['agency_mission'], 'get you there') + + def testEq(self): + agency1 = transitfeed.Agency("Test Agency", "http://example.com", + "America/Los_Angeles") + agency2 = transitfeed.Agency("Test Agency", "http://example.com", + "America/Los_Angeles") + # Unknown columns, such as agency_mission, do affect equality + self.assertEquals(agency1, agency2) + agency1.agency_mission = "Get you there" + self.assertNotEquals(agency1, agency2) + agency2.agency_mission = "Move you" + self.assertNotEquals(agency1, agency2) + agency1.agency_mission = "Move you" + self.assertEquals(agency1, agency2) + # Private attributes don't affect equality + agency1._private_attr = "My private message" + self.assertEquals(agency1, agency2) + agency2._private_attr = "Another private thing" + self.assertEquals(agency1, agency2) + + def testDict(self): + agency = transitfeed.Agency("Test Agency", "http://example.com", + "America/Los_Angeles") + agency._private_attribute = "blah" + # Private attributes don't appear when iterating through an agency as a + # dict but can be directly accessed. + self.assertEquals("blah", agency._private_attribute) + self.assertEquals("blah", agency["_private_attribute"]) + self.assertEquals( + set("agency_name agency_url agency_timezone".split()), + set(agency.keys())) + self.assertEquals({"agency_name": "Test Agency", + "agency_url": "http://example.com", + "agency_timezone": "America/Los_Angeles"}, + dict(agency.iteritems())) + + +class StopValidationTestCase(ValidationTestCase): + def runTest(self): + # success case + stop = transitfeed.Stop() + stop.stop_id = '45' + stop.stop_name = 'Couch AT End Table' + stop.stop_lat = 50.0 + stop.stop_lon = 50.0 + stop.stop_desc = 'Edge of the Couch' + stop.zone_id = 'A' + stop.stop_url = 'http://example.com' + stop.Validate(self.problems) + + # latitude too large + stop.stop_lat = 100.0 + self.ExpectInvalidValue(stop, 'stop_lat') + stop.stop_lat = 50.0 + + # latitude as a string works when it is valid + stop.stop_lat = '50.0' + stop.Validate(self.problems) + self.problems.AssertNoMoreExceptions() + stop.stop_lat = '10f' + self.ExpectInvalidValue(stop, 'stop_lat') + stop.stop_lat = 50.0 + + # longitude too large + stop.stop_lon = 200.0 + self.ExpectInvalidValue(stop, 'stop_lon') + stop.stop_lon = 50.0 + + # lat, lon too close to 0, 0 + stop.stop_lat = 0.0 + stop.stop_lon = 0.0 + self.ExpectInvalidValue(stop, 'stop_lat') + stop.stop_lat = 50.0 + stop.stop_lon = 50.0 + + # invalid stop_url + stop.stop_url = 'www.example.com' + self.ExpectInvalidValue(stop, 'stop_url') + stop.stop_url = 'http://example.com' + + stop.stop_id = ' ' + self.ExpectMissingValue(stop, 'stop_id') + stop.stop_id = '45' + + stop.stop_name = '' + self.ExpectMissingValue(stop, 'stop_name') + stop.stop_name = 'Couch AT End Table' + + # description same as name + stop.stop_desc = 'Couch AT End Table' + self.ExpectInvalidValue(stop, 'stop_desc') + stop.stop_desc = 'Edge of the Couch' + self.problems.AssertNoMoreExceptions() + + +class StopAttributes(ValidationTestCase): + def testWithoutSchedule(self): + stop = transitfeed.Stop() + stop.Validate(self.problems) + for name in "stop_id stop_name stop_lat stop_lon".split(): + e = self.problems.PopException('MissingValue') + self.assertEquals(name, e.column_name) + self.problems.AssertNoMoreExceptions() + + stop = transitfeed.Stop() + # Test behaviour for unset and unknown attribute + self.assertEquals(stop['new_column'], '') + try: + t = stop.new_column + self.fail('Expecting AttributeError') + except AttributeError, e: + pass # Expected + stop.stop_id = 'a' + stop.stop_name = 'my stop' + stop.new_column = 'val' + stop.stop_lat = 5.909 + stop.stop_lon = '40.02' + self.assertEquals(stop.new_column, 'val') + self.assertEquals(stop['new_column'], 'val') + self.assertTrue(isinstance(stop['stop_lat'], basestring)) + self.assertAlmostEqual(float(stop['stop_lat']), 5.909) + self.assertTrue(isinstance(stop['stop_lon'], basestring)) + self.assertAlmostEqual(float(stop['stop_lon']), 40.02) + stop.Validate(self.problems) + self.problems.AssertNoMoreExceptions() + # After validation stop.stop_lon has been converted to a float + self.assertAlmostEqual(stop.stop_lat, 5.909) + self.assertAlmostEqual(stop.stop_lon, 40.02) + self.assertEquals(stop.new_column, 'val') + self.assertEquals(stop['new_column'], 'val') + + def testBlankAttributeName(self): + stop1 = transitfeed.Stop(field_dict={"": "a"}) + stop2 = transitfeed.Stop(field_dict=stop1) + self.assertEquals("a", getattr(stop1, "")) + # The attribute "" is treated as private and not copied + self.assertRaises(AttributeError, getattr, stop2, "") + self.assertEquals(set(), set(stop1.keys())) + self.assertEquals(set(), set(stop2.keys())) + + def testWithSchedule(self): + schedule = transitfeed.Schedule(problem_reporter=self.problems) + + stop = transitfeed.Stop(field_dict={}) + # AddStopObject silently fails for Stop objects without stop_id + schedule.AddStopObject(stop) + self.assertFalse(schedule.GetStopList()) + self.assertFalse(stop._schedule) + + # Okay to add a stop with only stop_id + stop = transitfeed.Stop(field_dict={"stop_id": "b"}) + schedule.AddStopObject(stop) + stop.Validate(self.problems) + for name in "stop_name stop_lat stop_lon".split(): + e = self.problems.PopException("MissingValue") + self.assertEquals(name, e.column_name) + self.problems.AssertNoMoreExceptions() + + stop.new_column = "val" + self.assertTrue("new_column" in schedule.GetTableColumns("stops")) + + # Adding a duplicate stop_id fails + schedule.AddStopObject(transitfeed.Stop(field_dict={"stop_id": "b"})) + self.problems.PopException("DuplicateID") + self.problems.AssertNoMoreExceptions() + + +class StopTimeValidationTestCase(ValidationTestCase): + def runTest(self): + stop = transitfeed.Stop() + self.ExpectInvalidValueInClosure('arrival_time', '1a:00:00', + lambda: transitfeed.StopTime(self.problems, stop, + arrival_time="1a:00:00")) + self.ExpectInvalidValueInClosure('departure_time', '1a:00:00', + lambda: transitfeed.StopTime(self.problems, stop, + arrival_time="10:00:00", + departure_time='1a:00:00')) + self.ExpectInvalidValueInClosure('pickup_type', '7.8', + lambda: transitfeed.StopTime(self.problems, stop, + arrival_time="10:00:00", + departure_time='10:05:00', + pickup_type='7.8', + drop_off_type='0')) + self.ExpectInvalidValueInClosure('drop_off_type', 'a', + lambda: transitfeed.StopTime(self.problems, stop, + arrival_time="10:00:00", + departure_time='10:05:00', + pickup_type='3', + drop_off_type='a')) + self.ExpectInvalidValueInClosure('shape_dist_traveled', '$', + lambda: transitfeed.StopTime(self.problems, stop, + arrival_time="10:00:00", + departure_time='10:05:00', + pickup_type='3', + drop_off_type='0', + shape_dist_traveled='$')) + self.ExpectInvalidValueInClosure('shape_dist_traveled', '0,53', + lambda: transitfeed.StopTime(self.problems, stop, + arrival_time="10:00:00", + departure_time='10:05:00', + pickup_type='3', + drop_off_type='0', + shape_dist_traveled='0,53')) + self.ExpectOtherProblemInClosure( + lambda: transitfeed.StopTime(self.problems, stop, + pickup_type='1', drop_off_type='1')) + self.ExpectInvalidValueInClosure('departure_time', '10:00:00', + lambda: transitfeed.StopTime(self.problems, stop, + arrival_time="11:00:00", + departure_time="10:00:00")) + self.ExpectMissingValueInClosure('arrival_time', + lambda: transitfeed.StopTime(self.problems, stop, + departure_time="10:00:00")) + self.ExpectMissingValueInClosure('arrival_time', + lambda: transitfeed.StopTime(self.problems, stop, + departure_time="10:00:00", + arrival_time="")) + self.ExpectMissingValueInClosure('departure_time', + lambda: transitfeed.StopTime(self.problems, stop, + arrival_time="10:00:00")) + self.ExpectMissingValueInClosure('departure_time', + lambda: transitfeed.StopTime(self.problems, stop, + arrival_time="10:00:00", + departure_time="")) + self.ExpectInvalidValueInClosure('departure_time', '10:70:00', + lambda: transitfeed.StopTime(self.problems, stop, + arrival_time="10:00:00", + departure_time="10:70:00")) + self.ExpectInvalidValueInClosure('departure_time', '10:00:62', + lambda: transitfeed.StopTime(self.problems, stop, + arrival_time="10:00:00", + departure_time="10:00:62")) + self.ExpectInvalidValueInClosure('arrival_time', '10:00:63', + lambda: transitfeed.StopTime(self.problems, stop, + arrival_time="10:00:63", + departure_time="10:10:00")) + self.ExpectInvalidValueInClosure('arrival_time', '10:60:00', + lambda: transitfeed.StopTime(self.problems, stop, + arrival_time="10:60:00", + departure_time="11:02:00")) + # The following should work + transitfeed.StopTime(self.problems, stop, arrival_time="10:00:00", + departure_time="10:05:00", pickup_type='1', drop_off_type='1') + transitfeed.StopTime(self.problems, stop, arrival_time="1:00:00", + departure_time="1:05:00") + transitfeed.StopTime(self.problems, stop, arrival_time="24:59:00", + departure_time="25:05:00") + transitfeed.StopTime(self.problems, stop, arrival_time="101:01:00", + departure_time="101:21:00") + transitfeed.StopTime(self.problems, stop) + self.problems.AssertNoMoreExceptions() + +class TooFastTravelTestCase(ValidationTestCase): + def setUp(self): + ValidationTestCase.setUp(self) + self.schedule = transitfeed.Schedule(problem_reporter=self.problems) + self.schedule.NewDefaultAgency(agency_name="Test Agency", + agency_url="http://example.com", + agency_timezone="America/Los_Angeles") + self.route = self.schedule.AddRoute(short_name="54C", + long_name="Polish Hill", route_type=3) + service_period = self.schedule.GetDefaultServicePeriod() + service_period.SetDateHasService("20070101") + self.trip = self.route.AddTrip(self.schedule, 'via Polish Hill') + + def AddStopDistanceTime(self, dist_time_list): + # latitude where each 0.01 degrees longitude is 1km + magic_lat = 26.062468289 + stop = self.schedule.AddStop(magic_lat, 0, "Demo Stop 0") + time = 0 + self.trip.AddStopTime(stop, arrival_secs=time, departure_secs=time) + for i, (dist_delta, time_delta) in enumerate(dist_time_list): + stop = self.schedule.AddStop( + magic_lat, stop.stop_lon + dist_delta * 0.00001, + "Demo Stop %d" % (i + 1)) + time += time_delta + self.trip.AddStopTime(stop, arrival_secs=time, departure_secs=time) + + def testMovingTooFast(self): + self.AddStopDistanceTime([(1691, 60), + (1616, 60)]) + + self.trip.Validate(self.problems) + e = self.problems.PopException('TooFastTravel') + self.assertMatchesRegex(r'High speed travel detected', e.FormatProblem()) + self.assertMatchesRegex(r'Stop 0 to Demo Stop 1', e.FormatProblem()) + self.assertMatchesRegex(r'1691 meters in 60 seconds', e.FormatProblem()) + self.assertMatchesRegex(r'\(101 km/h\)', e.FormatProblem()) + self.assertEqual(e.type, transitfeed.TYPE_WARNING) + self.problems.AssertNoMoreExceptions() + + self.route.route_type = 4 # Ferry with max_speed 80 + self.trip.Validate(self.problems) + e = self.problems.PopException('TooFastTravel') + self.assertMatchesRegex(r'High speed travel detected', e.FormatProblem()) + self.assertMatchesRegex(r'Stop 0 to Demo Stop 1', e.FormatProblem()) + self.assertMatchesRegex(r'1691 meters in 60 seconds', e.FormatProblem()) + self.assertMatchesRegex(r'\(101 km/h\)', e.FormatProblem()) + self.assertEqual(e.type, transitfeed.TYPE_WARNING) + e = self.problems.PopException('TooFastTravel') + self.assertMatchesRegex(r'High speed travel detected', e.FormatProblem()) + self.assertMatchesRegex(r'Stop 1 to Demo Stop 2', e.FormatProblem()) + self.assertMatchesRegex(r'1616 meters in 60 seconds', e.FormatProblem()) + self.assertMatchesRegex(r'97 km/h', e.FormatProblem()) + self.assertEqual(e.type, transitfeed.TYPE_WARNING) + self.problems.AssertNoMoreExceptions() + + # Run test without a route_type + self.route.route_type = None + self.trip.Validate(self.problems) + e = self.problems.PopException('TooFastTravel') + self.assertMatchesRegex(r'High speed travel detected', e.FormatProblem()) + self.assertMatchesRegex(r'Stop 0 to Demo Stop 1', e.FormatProblem()) + self.assertMatchesRegex(r'1691 meters in 60 seconds', e.FormatProblem()) + self.assertMatchesRegex(r'101 km/h', e.FormatProblem()) + self.assertEqual(e.type, transitfeed.TYPE_WARNING) + self.problems.AssertNoMoreExceptions() + + def testNoTimeDelta(self): + # See comments where TooFastTravel is called in transitfeed.py to + # understand why was added. + # Movement more than max_speed in 1 minute with no time change is a warning. + self.AddStopDistanceTime([(1616, 0), + (1000, 120), + (1691, 0)]) + + self.trip.Validate(self.problems) + e = self.problems.PopException('TooFastTravel') + self.assertMatchesRegex('High speed travel detected', e.FormatProblem()) + self.assertMatchesRegex('Stop 2 to Demo Stop 3', e.FormatProblem()) + self.assertMatchesRegex('1691 meters in 0 seconds', e.FormatProblem()) + self.assertEqual(e.type, transitfeed.TYPE_WARNING) + self.problems.AssertNoMoreExceptions() + + self.route.route_type = 4 # Ferry with max_speed 80 + self.trip.Validate(self.problems) + self.assertEqual(e.type, transitfeed.TYPE_WARNING) + e = self.problems.PopException('TooFastTravel') + self.assertMatchesRegex('High speed travel detected', e.FormatProblem()) + self.assertMatchesRegex('Stop 0 to Demo Stop 1', e.FormatProblem()) + self.assertMatchesRegex('1616 meters in 0 seconds', e.FormatProblem()) + self.assertEqual(e.type, transitfeed.TYPE_WARNING) + e = self.problems.PopException('TooFastTravel') + self.assertMatchesRegex('High speed travel detected', e.FormatProblem()) + self.assertMatchesRegex('Stop 2 to Demo Stop 3', e.FormatProblem()) + self.assertMatchesRegex('1691 meters in 0 seconds', e.FormatProblem()) + self.assertEqual(e.type, transitfeed.TYPE_WARNING) + self.problems.AssertNoMoreExceptions() + + # Run test without a route_type + self.route.route_type = None + self.trip.Validate(self.problems) + e = self.problems.PopException('TooFastTravel') + self.assertMatchesRegex('High speed travel detected', e.FormatProblem()) + self.assertMatchesRegex('Stop 2 to Demo Stop 3', e.FormatProblem()) + self.assertMatchesRegex('1691 meters in 0 seconds', e.FormatProblem()) + self.assertEqual(e.type, transitfeed.TYPE_WARNING) + self.problems.AssertNoMoreExceptions() + + def testNoTimeDeltaNotRounded(self): + # See comments where TooFastTravel is called in transitfeed.py to + # understand why was added. + # Any movement with no time change and times not rounded to the nearest + # minute causes a warning. + self.AddStopDistanceTime([(500, 62), + (10, 0)]) + + self.trip.Validate(self.problems) + e = self.problems.PopException('TooFastTravel') + self.assertMatchesRegex('High speed travel detected', e.FormatProblem()) + self.assertMatchesRegex('Stop 1 to Demo Stop 2', e.FormatProblem()) + self.assertMatchesRegex('10 meters in 0 seconds', e.FormatProblem()) + self.assertEqual(e.type, transitfeed.TYPE_WARNING) + self.problems.AssertNoMoreExceptions() + + +class MemoryZipTestCase(util.TestCaseAsserts): + def setUp(self): + self.problems = RecordingProblemReporter(self, ("ExpirationDate",)) + self.zipfile = StringIO() + self.zip = zipfile.ZipFile(self.zipfile, 'a') + self.zip.writestr( + "agency.txt", + "agency_id,agency_name,agency_url,agency_timezone\n" + "DTA,Demo Agency,http://google.com,America/Los_Angeles\n") + self.zip.writestr( + "calendar.txt", + "service_id,monday,tuesday,wednesday,thursday,friday,saturday,sunday," + "start_date,end_date\n" + "FULLW,1,1,1,1,1,1,1,20070101,20101231\n" + "WE,0,0,0,0,0,1,1,20070101,20101231\n") + self.zip.writestr( + "routes.txt", + "route_id,agency_id,route_short_name,route_long_name,route_type\n" + "AB,DTA,,Airport Bullfrog,3\n") + self.zip.writestr( + "trips.txt", + "route_id,service_id,trip_id\n" + "AB,FULLW,AB1\n") + self.zip.writestr( + "stops.txt", + "stop_id,stop_name,stop_lat,stop_lon\n" + "BEATTY_AIRPORT,Airport,36.868446,-116.784582\n" + "BULLFROG,Bullfrog,36.88108,-116.81797\n" + "STAGECOACH,Stagecoach Hotel,36.915682,-116.751677\n") + self.zip.writestr( + "stop_times.txt", + "trip_id,arrival_time,departure_time,stop_id,stop_sequence\n" + "AB1,10:00:00,10:00:00,BEATTY_AIRPORT,1\n" + "AB1,10:20:00,10:20:00,BULLFROG,2\n" + "AB1,10:25:00,10:25:00,STAGECOACH,3\n") + self.loader = transitfeed.Loader( + problems=self.problems, + extra_validation=True, + zip=self.zip) + + def appendToZip(self, file, arcname, s): + """Append s to the arcname in the zip stored in a file object.""" + zip = zipfile.ZipFile(file, 'a') + zip.writestr(arcname, zip.read(arcname) + s) + zip.close() + + +class CsvDictTestCase(unittest.TestCase): + def setUp(self): + self.problems = RecordingProblemReporter(self) + self.zip = zipfile.ZipFile(StringIO(), 'a') + self.loader = transitfeed.Loader( + problems=self.problems, + zip=self.zip) + + def testEmptyFile(self): + self.zip.writestr("test.txt", "") + results = list(self.loader._ReadCsvDict("test.txt", [], [])) + self.assertEquals([], results) + self.problems.PopException("EmptyFile") + self.problems.AssertNoMoreExceptions() + + def testHeaderOnly(self): + self.zip.writestr("test.txt", "test_id,test_name") + results = list(self.loader._ReadCsvDict("test.txt", + ["test_id", "test_name"], [])) + self.assertEquals([], results) + self.problems.AssertNoMoreExceptions() + + def testHeaderAndNewLineOnly(self): + self.zip.writestr("test.txt", "test_id,test_name\n") + results = list(self.loader._ReadCsvDict("test.txt", + ["test_id", "test_name"], [])) + self.assertEquals([], results) + self.problems.AssertNoMoreExceptions() + + def testHeaderWithSpaceBefore(self): + self.zip.writestr("test.txt", " test_id, test_name\n") + results = list(self.loader._ReadCsvDict("test.txt", + ["test_id", "test_name"], [])) + self.assertEquals([], results) + self.problems.AssertNoMoreExceptions() + + def testHeaderWithSpaceBeforeAfter(self): + self.zip.writestr("test.txt", "test_id , test_name\n") + results = list(self.loader._ReadCsvDict("test.txt", + ["test_id", "test_name"], [])) + self.assertEquals([], results) + e = self.problems.PopException("CsvSyntax") + self.problems.AssertNoMoreExceptions() + + def testHeaderQuoted(self): + self.zip.writestr("test.txt", "\"test_id\", \"test_name\"\n") + results = list(self.loader._ReadCsvDict("test.txt", + ["test_id", "test_name"], [])) + self.assertEquals([], results) + self.problems.AssertNoMoreExceptions() + + def testHeaderSpaceAfterQuoted(self): + self.zip.writestr("test.txt", "\"test_id\" , \"test_name\"\n") + results = list(self.loader._ReadCsvDict("test.txt", + ["test_id", "test_name"], [])) + self.assertEquals([], results) + e = self.problems.PopException("CsvSyntax") + self.problems.AssertNoMoreExceptions() + + def testHeaderSpaceInQuotesAfterValue(self): + self.zip.writestr("test.txt", "\"test_id \",\"test_name\"\n") + results = list(self.loader._ReadCsvDict("test.txt", + ["test_id", "test_name"], [])) + self.assertEquals([], results) + e = self.problems.PopException("CsvSyntax") + self.problems.AssertNoMoreExceptions() + + def testHeaderSpaceInQuotesBeforeValue(self): + self.zip.writestr("test.txt", "\"test_id\",\" test_name\"\n") + results = list(self.loader._ReadCsvDict("test.txt", + ["test_id", "test_name"], [])) + self.assertEquals([], results) + e = self.problems.PopException("CsvSyntax") + self.problems.AssertNoMoreExceptions() + + def testHeaderEmptyColumnName(self): + self.zip.writestr("test.txt", 'test_id,test_name,\n') + results = list(self.loader._ReadCsvDict("test.txt", + ["test_id", "test_name"], [])) + self.assertEquals([], results) + e = self.problems.PopException("CsvSyntax") + self.problems.AssertNoMoreExceptions() + + def testHeaderAllUnknownColumnNames(self): + self.zip.writestr("test.txt", 'id,nam\n') + results = list(self.loader._ReadCsvDict("test.txt", + ["test_id", "test_name"], [])) + self.assertEquals([], results) + e = self.problems.PopException("CsvSyntax") + self.assertTrue(e.FormatProblem().find("missing the header") != -1) + self.problems.AssertNoMoreExceptions() + + def testFieldWithSpaces(self): + self.zip.writestr("test.txt", + "test_id,test_name\n" + "id1 , my name\n") + results = list(self.loader._ReadCsvDict("test.txt", + ["test_id", "test_name"], [])) + self.assertEquals([({"test_id": "id1 ", "test_name": "my name"}, 2, + ["test_id", "test_name"], ["id1 ","my name"])], results) + self.problems.AssertNoMoreExceptions() + + def testFieldWithOnlySpaces(self): + self.zip.writestr("test.txt", + "test_id,test_name\n" + "id1, \n") # spaces are skipped to yield empty field + results = list(self.loader._ReadCsvDict("test.txt", + ["test_id", "test_name"], [])) + self.assertEquals([({"test_id": "id1", "test_name": ""}, 2, + ["test_id", "test_name"], ["id1",""])], results) + self.problems.AssertNoMoreExceptions() + + def testQuotedFieldWithSpaces(self): + self.zip.writestr("test.txt", + 'test_id,"test_name",test_size\n' + '"id1" , "my name" , "234 "\n') + results = list(self.loader._ReadCsvDict("test.txt", + ["test_id", "test_name", + "test_size"], [])) + self.assertEquals( + [({"test_id": "id1 ", "test_name": "my name ", "test_size": "234 "}, 2, + ["test_id", "test_name", "test_size"], ["id1 ", "my name ", "234 "])], + results) + self.problems.AssertNoMoreExceptions() + + def testQuotedFieldWithCommas(self): + self.zip.writestr("test.txt", + 'id,name1,name2\n' + '"1", "brown, tom", "brown, ""tom"""\n') + results = list(self.loader._ReadCsvDict("test.txt", + ["id", "name1", "name2"], [])) + self.assertEquals( + [({"id": "1", "name1": "brown, tom", "name2": "brown, \"tom\""}, 2, + ["id", "name1", "name2"], ["1", "brown, tom", "brown, \"tom\""])], + results) + self.problems.AssertNoMoreExceptions() + + def testUnknownColumn(self): + # A small typo (omitting '_' in a header name) is detected + self.zip.writestr("test.txt", "test_id,testname\n") + results = list(self.loader._ReadCsvDict("test.txt", + ["test_id", "test_name"], [])) + self.assertEquals([], results) + e = self.problems.PopException("UnrecognizedColumn") + self.assertEquals("testname", e.column_name) + self.problems.AssertNoMoreExceptions() + + def testMissingRequiredColumn(self): + self.zip.writestr("test.txt", "test_id,test_size\n") + results = list(self.loader._ReadCsvDict("test.txt", + ["test_id", "test_size"], + ["test_name"])) + self.assertEquals([], results) + e = self.problems.PopException("MissingColumn") + self.assertEquals("test_name", e.column_name) + self.problems.AssertNoMoreExceptions() + + def testRequiredNotInAllCols(self): + self.zip.writestr("test.txt", "test_id,test_name,test_size\n") + results = list(self.loader._ReadCsvDict("test.txt", + ["test_id", "test_size"], + ["test_name"])) + self.assertEquals([], results) + e = self.problems.PopException("UnrecognizedColumn") + self.assertEquals("test_name", e.column_name) + self.problems.AssertNoMoreExceptions() + + def testBlankLine(self): + # line_num is increased for an empty line + self.zip.writestr("test.txt", + "test_id,test_name\n" + "\n" + "id1,my name\n") + results = list(self.loader._ReadCsvDict("test.txt", + ["test_id", "test_name"], [])) + self.assertEquals([({"test_id": "id1", "test_name": "my name"}, 3, + ["test_id", "test_name"], ["id1","my name"])], results) + self.problems.AssertNoMoreExceptions() + + def testExtraComma(self): + self.zip.writestr("test.txt", + "test_id,test_name\n" + "id1,my name,\n") + results = list(self.loader._ReadCsvDict("test.txt", + ["test_id", "test_name"], [])) + self.assertEquals([({"test_id": "id1", "test_name": "my name"}, 2, + ["test_id", "test_name"], ["id1","my name"])], + results) + e = self.problems.PopException("OtherProblem") + self.assertTrue(e.FormatProblem().find("too many cells") != -1) + self.problems.AssertNoMoreExceptions() + + def testMissingComma(self): + self.zip.writestr("test.txt", + "test_id,test_name\n" + "id1 my name\n") + results = list(self.loader._ReadCsvDict("test.txt", + ["test_id", "test_name"], [])) + self.assertEquals([({"test_id": "id1 my name"}, 2, + ["test_id", "test_name"], ["id1 my name"])], results) + e = self.problems.PopException("OtherProblem") + self.assertTrue(e.FormatProblem().find("missing cells") != -1) + self.problems.AssertNoMoreExceptions() + + def testDetectsDuplicateHeaders(self): + self.zip.writestr( + "transfers.txt", + "from_stop_id,from_stop_id,to_stop_id,transfer_type,min_transfer_time," + "min_transfer_time,min_transfer_time,min_transfer_time,unknown," + "unknown\n" + "BEATTY_AIRPORT,BEATTY_AIRPORT,BULLFROG,3,,2,,,,\n" + "BULLFROG,BULLFROG,BEATTY_AIRPORT,2,1200,1,,,,\n") + + list(self.loader._ReadCsvDict("transfers.txt", + transitfeed.Transfer._FIELD_NAMES, + transitfeed.Transfer._REQUIRED_FIELD_NAMES)) + + self.problems.PopDuplicateColumn("transfers.txt","min_transfer_time",4) + self.problems.PopDuplicateColumn("transfers.txt","from_stop_id",2) + self.problems.PopDuplicateColumn("transfers.txt","unknown",2) + e = self.problems.PopException("UnrecognizedColumn") + self.assertEquals("unknown", e.column_name) + self.problems.AssertNoMoreExceptions() + + +class ReadCsvTestCase(unittest.TestCase): + def setUp(self): + self.problems = RecordingProblemReporter(self) + self.zip = zipfile.ZipFile(StringIO(), 'a') + self.loader = transitfeed.Loader( + problems=self.problems, + zip=self.zip) + + def testDetectsDuplicateHeaders(self): + self.zip.writestr( + "calendar.txt", + "service_id,monday,tuesday,wednesday,thursday,friday,saturday,sunday," + "start_date,end_date,end_date,end_date,tuesday,unknown,unknown\n" + "FULLW,1,1,1,1,1,1,1,20070101,20101231,,,,,\n") + + list(self.loader._ReadCSV("calendar.txt", + transitfeed.ServicePeriod._FIELD_NAMES, + transitfeed.ServicePeriod._FIELD_NAMES_REQUIRED)) + + self.problems.PopDuplicateColumn("calendar.txt","end_date",3) + self.problems.PopDuplicateColumn("calendar.txt","unknown",2) + self.problems.PopDuplicateColumn("calendar.txt","tuesday",2) + e = self.problems.PopException("UnrecognizedColumn") + self.assertEquals("unknown", e.column_name) + self.problems.AssertNoMoreExceptions() + + +class BasicMemoryZipTestCase(MemoryZipTestCase): + def runTest(self): + self.loader.Load() + self.problems.AssertNoMoreExceptions() + + +class ZipCompressionTestCase(MemoryZipTestCase): + def runTest(self): + schedule = self.loader.Load() + self.zip.close() + write_output = StringIO() + schedule.WriteGoogleTransitFeed(write_output) + recompressedzip = zlib.compress(write_output.getvalue()) + write_size = len(write_output.getvalue()) + recompressedzip_size = len(recompressedzip) + # If zlib can compress write_output it probably wasn't compressed + self.assertFalse( + recompressedzip_size < write_size * 0.60, + "Are you sure WriteGoogleTransitFeed wrote a compressed zip? " + "Orginial size: %d recompressed: %d" % + (write_size, recompressedzip_size)) + + +class StopHierarchyTestCase(MemoryZipTestCase): + def testParentAtSameLatLon(self): + self.zip.writestr( + "stops.txt", + "stop_id,stop_name,stop_lat,stop_lon,location_type,parent_station\n" + "BEATTY_AIRPORT,Airport,36.868446,-116.784582,,STATION\n" + "STATION,Airport,36.868446,-116.784582,1,\n" + "BULLFROG,Bullfrog,36.88108,-116.81797,,\n" + "STAGECOACH,Stagecoach Hotel,36.915682,-116.751677,,\n") + schedule = self.loader.Load() + self.assertEquals(1, schedule.stops["STATION"].location_type) + self.assertEquals(0, schedule.stops["BEATTY_AIRPORT"].location_type) + self.problems.AssertNoMoreExceptions() + + def testBadLocationType(self): + self.zip.writestr( + "stops.txt", + "stop_id,stop_name,stop_lat,stop_lon,location_type\n" + "BEATTY_AIRPORT,Airport,36.868446,-116.784582,2\n" + "BULLFROG,Bullfrog,36.88108,-116.81797,notvalid\n" + "STAGECOACH,Stagecoach Hotel,36.915682,-116.751677,\n") + schedule = self.loader.Load() + e = self.problems.PopException("InvalidValue") + self.assertEquals("location_type", e.column_name) + self.assertEquals(2, e.row_num) + self.assertEquals(1, e.type) + e = self.problems.PopException("InvalidValue") + self.assertEquals("location_type", e.column_name) + self.assertEquals(3, e.row_num) + self.assertEquals(0, e.type) + self.problems.AssertNoMoreExceptions() + + def testBadLocationTypeAtSameLatLon(self): + self.zip.writestr( + "stops.txt", + "stop_id,stop_name,stop_lat,stop_lon,location_type,parent_station\n" + "BEATTY_AIRPORT,Airport,36.868446,-116.784582,,STATION\n" + "STATION,Airport,36.868446,-116.784582,2,\n" + "BULLFROG,Bullfrog,36.88108,-116.81797,,\n" + "STAGECOACH,Stagecoach Hotel,36.915682,-116.751677,,\n") + schedule = self.loader.Load() + e = self.problems.PopException("InvalidValue") + self.assertEquals("location_type", e.column_name) + self.assertEquals(3, e.row_num) + e = self.problems.PopException("InvalidValue") + self.assertEquals("parent_station", e.column_name) + self.problems.AssertNoMoreExceptions() + + def testStationUsed(self): + self.zip.writestr( + "stops.txt", + "stop_id,stop_name,stop_lat,stop_lon,location_type\n" + "BEATTY_AIRPORT,Airport,36.868446,-116.784582,1\n" + "BULLFROG,Bullfrog,36.88108,-116.81797,\n" + "STAGECOACH,Stagecoach Hotel,36.915682,-116.751677,\n") + schedule = self.loader.Load() + self.problems.PopException("UsedStation") + self.problems.AssertNoMoreExceptions() + + def testParentNotFound(self): + self.zip.writestr( + "stops.txt", + "stop_id,stop_name,stop_lat,stop_lon,location_type,parent_station\n" + "BEATTY_AIRPORT,Airport,36.868446,-116.784582,,STATION\n" + "BULLFROG,Bullfrog,36.88108,-116.81797,,\n" + "STAGECOACH,Stagecoach Hotel,36.915682,-116.751677,,\n") + schedule = self.loader.Load() + e = self.problems.PopException("InvalidValue") + self.assertEquals("parent_station", e.column_name) + self.problems.AssertNoMoreExceptions() + + def testParentIsStop(self): + self.zip.writestr( + "stops.txt", + "stop_id,stop_name,stop_lat,stop_lon,location_type,parent_station\n" + "BEATTY_AIRPORT,Airport,36.868446,-116.784582,,BULLFROG\n" + "BULLFROG,Bullfrog,36.88108,-116.81797,,\n" + "STAGECOACH,Stagecoach Hotel,36.915682,-116.751677,,\n") + schedule = self.loader.Load() + e = self.problems.PopException("InvalidValue") + self.assertEquals("parent_station", e.column_name) + self.problems.AssertNoMoreExceptions() + + def testParentOfEntranceIsStop(self): + self.zip.writestr( + "stops.txt", + "stop_id,stop_name,stop_lat,stop_lon,location_type,parent_station\n" + "BEATTY_AIRPORT,Airport,36.868446,-116.784582,2,BULLFROG\n" + "BULLFROG,Bullfrog,36.88108,-116.81797,,\n" + "STAGECOACH,Stagecoach Hotel,36.915682,-116.751677,,\n") + schedule = self.loader.Load() + e = self.problems.PopException("InvalidValue") + self.assertEquals("location_type", e.column_name) + e = self.problems.PopException("InvalidValue") + self.assertEquals("parent_station", e.column_name) + self.assertTrue(e.FormatProblem().find("location_type=1") != -1) + self.problems.AssertNoMoreExceptions() + + def testStationWithParent(self): + self.zip.writestr( + "stops.txt", + "stop_id,stop_name,stop_lat,stop_lon,location_type,parent_station\n" + "BEATTY_AIRPORT,Airport,36.868446,-116.784582,,STATION\n" + "STATION,Airport,36.868446,-116.784582,1,STATION2\n" + "STATION2,Airport 2,36.868000,-116.784000,1,\n" + "BULLFROG,Bullfrog,36.868088,-116.784797,,STATION2\n" + "STAGECOACH,Stagecoach Hotel,36.915682,-116.751677,,\n") + schedule = self.loader.Load() + e = self.problems.PopException("InvalidValue") + self.assertEquals("parent_station", e.column_name) + self.assertEquals(3, e.row_num) + self.problems.AssertNoMoreExceptions() + + def testStationWithSelfParent(self): + self.zip.writestr( + "stops.txt", + "stop_id,stop_name,stop_lat,stop_lon,location_type,parent_station\n" + "BEATTY_AIRPORT,Airport,36.868446,-116.784582,,STATION\n" + "STATION,Airport,36.868446,-116.784582,1,STATION\n" + "BULLFROG,Bullfrog,36.88108,-116.81797,,\n" + "STAGECOACH,Stagecoach Hotel,36.915682,-116.751677,,\n") + schedule = self.loader.Load() + e = self.problems.PopException("InvalidValue") + self.assertEquals("parent_station", e.column_name) + self.assertEquals(3, e.row_num) + self.problems.AssertNoMoreExceptions() + + def testStopNearToNonParentStation(self): + self.zip.writestr( + "stops.txt", + "stop_id,stop_name,stop_lat,stop_lon,location_type,parent_station\n" + "BEATTY_AIRPORT,Airport,36.868446,-116.784582,,\n" + "BULLFROG,Bullfrog,36.868446,-116.784582,,\n" + "BULLFROG_ST,Bullfrog,36.868446,-116.784582,1,\n" + "STAGECOACH,Stagecoach Hotel,36.915682,-116.751677,,\n") + schedule = self.loader.Load() + e = self.problems.PopException("DifferentStationTooClose") + self.assertMatchesRegex( + "The parent_station of stop \"Bullfrog\"", e.FormatProblem()) + e = self.problems.PopException("StopsTooClose") + self.assertMatchesRegex("BEATTY_AIRPORT", e.FormatProblem()) + self.assertMatchesRegex("BULLFROG", e.FormatProblem()) + self.assertMatchesRegex("are 0.00m apart", e.FormatProblem()) + e = self.problems.PopException("DifferentStationTooClose") + self.assertMatchesRegex( + "The parent_station of stop \"Airport\"", e.FormatProblem()) + self.problems.AssertNoMoreExceptions() + + def testStopTooFarFromParentStation(self): + self.zip.writestr( + "stops.txt", + "stop_id,stop_name,stop_lat,stop_lon,location_type,parent_station\n" + "BULLFROG_ST,Bullfrog,36.880,-116.817,1,\n" # Parent station of all. + "BEATTY_AIRPORT,Airport,36.880,-116.816,,BULLFROG_ST\n" # ~ 90m far + "BULLFROG,Bullfrog,36.881,-116.818,,BULLFROG_ST\n" # ~ 150m far + "STAGECOACH,Stagecoach,36.915,-116.751,,BULLFROG_ST\n") # > 3km far + schedule = self.loader.Load() + e = self.problems.PopException("StopTooFarFromParentStation") + self.assertEqual(1, e.type) # Warning + self.assertTrue(e.FormatProblem().find( + "Bullfrog (ID BULLFROG) is too far from its parent" + " station Bullfrog (ID BULLFROG_ST)") != -1) + e = self.problems.PopException("StopTooFarFromParentStation") + self.assertEqual(0, e.type) # Error + self.assertTrue(e.FormatProblem().find( + "Stagecoach (ID STAGECOACH) is too far from its parent" + " station Bullfrog (ID BULLFROG_ST)") != -1) + self.problems.AssertNoMoreExceptions() + + #Uncomment once validation is implemented + #def testStationWithoutReference(self): + # self.zip.writestr( + # "stops.txt", + # "stop_id,stop_name,stop_lat,stop_lon,location_type,parent_station\n" + # "BEATTY_AIRPORT,Airport,36.868446,-116.784582,,\n" + # "STATION,Airport,36.868446,-116.784582,1,\n" + # "BULLFROG,Bullfrog,36.88108,-116.81797,,\n" + # "STAGECOACH,Stagecoach Hotel,36.915682,-116.751677,,\n") + # schedule = self.loader.Load() + # e = self.problems.PopException("OtherProblem") + # self.assertEquals("parent_station", e.column_name) + # self.assertEquals(2, e.row_num) + # self.problems.AssertNoMoreExceptions() + + +class StopSpacesTestCase(MemoryZipTestCase): + def testFieldsWithSpace(self): + self.zip.writestr( + "stops.txt", + "stop_id,stop_code,stop_name,stop_lat,stop_lon,stop_url,location_type," + "parent_station\n" + "BEATTY_AIRPORT, ,Airport,36.868446,-116.784582, , ,\n" + "BULLFROG,,Bullfrog,36.88108,-116.81797,,,\n" + "STAGECOACH,,Stagecoach Hotel,36.915682,-116.751677,,,\n") + schedule = self.loader.Load() + self.problems.AssertNoMoreExceptions() + + +class StopBlankHeaders(MemoryZipTestCase): + def testBlankHeaderValueAtEnd(self): + # Modify the stops.txt added by MemoryZipTestCase.setUp. This allows the + # original stops.txt to be changed without modifying anything in this test. + # Add a column to the end of every row, leaving the header name blank. + new = [] + for i, row in enumerate(self.zip.read("stops.txt").split("\n")): + if i == 0: + new.append(row + ",") + elif row: + new.append(row + "," + str(i)) # Put a junk value in data rows + self.zip.writestr("stops.txt", "\n".join(new)) + schedule = self.loader.Load() + e = self.problems.PopException("CsvSyntax") + self.assertTrue(e.FormatProblem(). + find("header row should not contain any blank") != -1) + self.problems.AssertNoMoreExceptions() + + def testBlankHeaderValueAtStart(self): + # Modify the stops.txt added by MemoryZipTestCase.setUp. This allows the + # original stops.txt to be changed without modifying anything in this test. + # Add a column to the start of every row, leaving the header name blank. + new = [] + for i, row in enumerate(self.zip.read("stops.txt").split("\n")): + if i == 0: + new.append("," + row) + elif row: + new.append(str(i) + "," + row) # Put a junk value in data rows + self.zip.writestr("stops.txt", "\n".join(new)) + schedule = self.loader.Load() + e = self.problems.PopException("CsvSyntax") + self.assertTrue(e.FormatProblem(). + find("header row should not contain any blank") != -1) + self.problems.AssertNoMoreExceptions() + + def testBlankHeaderValueInMiddle(self): + # Modify the stops.txt added by MemoryZipTestCase.setUp. This allows the + # original stops.txt to be changed without modifying anything in this test. + # Add two columns to the start of every row, leaving the second header name + # blank. + new = [] + for i, row in enumerate(self.zip.read("stops.txt").split("\n")): + if i == 0: + new.append("test_name,," + row) + elif row: + # Put a junk value in data rows + new.append(str(i) + "," + str(i) + "," + row) + self.zip.writestr("stops.txt", "\n".join(new)) + schedule = self.loader.Load() + e = self.problems.PopException("CsvSyntax") + self.assertTrue(e.FormatProblem(). + find("header row should not contain any blank") != -1) + e = self.problems.PopException("UnrecognizedColumn") + self.assertEquals("test_name", e.column_name) + self.problems.AssertNoMoreExceptions() + + +class StopsNearEachOther(MemoryZipTestCase): + def testTooNear(self): + self.zip.writestr( + "stops.txt", + "stop_id,stop_name,stop_lat,stop_lon\n" + "BEATTY_AIRPORT,Airport,48.20000,140\n" + "BULLFROG,Bullfrog,48.20001,140\n" + "STAGECOACH,Stagecoach Hotel,48.20016,140\n") + schedule = self.loader.Load() + e = self.problems.PopException('StopsTooClose') + self.assertTrue(e.FormatProblem().find("1.11m apart") != -1) + self.problems.AssertNoMoreExceptions() + + def testJustFarEnough(self): + self.zip.writestr( + "stops.txt", + "stop_id,stop_name,stop_lat,stop_lon\n" + "BEATTY_AIRPORT,Airport,48.20000,140\n" + "BULLFROG,Bullfrog,48.20002,140\n" + "STAGECOACH,Stagecoach Hotel,48.20016,140\n") + schedule = self.loader.Load() + # Stops are 2.2m apart + self.problems.AssertNoMoreExceptions() + + def testSameLocation(self): + self.zip.writestr( + "stops.txt", + "stop_id,stop_name,stop_lat,stop_lon\n" + "BEATTY_AIRPORT,Airport,48.2,140\n" + "BULLFROG,Bullfrog,48.2,140\n" + "STAGECOACH,Stagecoach Hotel,48.20016,140\n") + schedule = self.loader.Load() + e = self.problems.PopException('StopsTooClose') + self.assertTrue(e.FormatProblem().find("0.00m apart") != -1) + self.problems.AssertNoMoreExceptions() + + def testStationsTooNear(self): + self.zip.writestr( + "stops.txt", + "stop_id,stop_name,stop_lat,stop_lon,location_type,parent_station\n" + "BEATTY_AIRPORT,Airport,48.20000,140,,BEATTY_AIRPORT_STATION\n" + "BULLFROG,Bullfrog,48.20003,140,,BULLFROG_STATION\n" + "BEATTY_AIRPORT_STATION,Airport,48.20001,140,1,\n" + "BULLFROG_STATION,Bullfrog,48.20002,140,1,\n" + "STAGECOACH,Stagecoach Hotel,48.20016,140,,\n") + schedule = self.loader.Load() + e = self.problems.PopException('StationsTooClose') + self.assertTrue(e.FormatProblem().find("1.11m apart") != -1) + self.assertTrue(e.FormatProblem().find("BEATTY_AIRPORT_STATION") != -1) + self.problems.AssertNoMoreExceptions() + + def testStopNearNonParentStation(self): + self.zip.writestr( + "stops.txt", + "stop_id,stop_name,stop_lat,stop_lon,location_type,parent_station\n" + "BEATTY_AIRPORT,Airport,48.20000,140,,\n" + "BULLFROG,Bullfrog,48.20005,140,,\n" + "BULLFROG_STATION,Bullfrog,48.20006,140,1,\n" + "STAGECOACH,Stagecoach Hotel,48.20016,140,,\n") + schedule = self.loader.Load() + e = self.problems.PopException('DifferentStationTooClose') + fmt = e.FormatProblem() + self.assertTrue(re.search( + r"parent_station of.*BULLFROG.*station.*BULLFROG_STATION.* 1.11m apart", + fmt), fmt) + self.problems.AssertNoMoreExceptions() + + +class BadLatLonInStopUnitTest(ValidationTestCase): + def runTest(self): + stop = transitfeed.Stop(field_dict={"stop_id": "STOP1", + "stop_name": "Stop one", + "stop_lat": "0x20", + "stop_lon": "140.01"}) + self.ExpectInvalidValue(stop, "stop_lat") + + stop = transitfeed.Stop(field_dict={"stop_id": "STOP1", + "stop_name": "Stop one", + "stop_lat": "13.0", + "stop_lon": "1e2"}) + self.ExpectInvalidValue(stop, "stop_lon") + + +class BadLatLonInFileUnitTest(MemoryZipTestCase): + def runTest(self): + self.zip.writestr( + "stops.txt", + "stop_id,stop_name,stop_lat,stop_lon\n" + "BEATTY_AIRPORT,Airport,0x20,140.00\n" + "BULLFROG,Bullfrog,48.20001,140.0123\n" + "STAGECOACH,Stagecoach Hotel,48.002,bogus\n") + schedule = self.loader.Load() + e = self.problems.PopException('InvalidValue') + self.assertEquals(2, e.row_num) + self.assertEquals("stop_lat", e.column_name) + e = self.problems.PopException('InvalidValue') + self.assertEquals(4, e.row_num) + self.assertEquals("stop_lon", e.column_name) + self.problems.AssertNoMoreExceptions() + + +class LoadUnknownFileInZipTestCase(MemoryZipTestCase): + def runTest(self): + self.zip.writestr( + "stpos.txt", + "stop_id,stop_name,stop_lat,stop_lon,location_type,parent_station\n" + "BEATTY_AIRPORT,Airport,36.868446,-116.784582,,STATION\n" + "STATION,Airport,36.868446,-116.784582,1,\n" + "BULLFROG,Bullfrog,36.88108,-116.81797,,\n" + "STAGECOACH,Stagecoach Hotel,36.915682,-116.751677,,\n") + schedule = self.loader.Load() + e = self.problems.PopException('UnknownFile') + self.assertEquals('stpos.txt', e.file_name) + self.problems.AssertNoMoreExceptions() + + +class TabDelimitedTestCase(MemoryZipTestCase): + def runTest(self): + # Create an extremely corrupt file by replacing each comma with a tab, + # ignoring csv quoting. + for arcname in self.zip.namelist(): + orig = self.zip.read(arcname) + self.zip.writestr(arcname, orig.replace(",", "\t")) + schedule = self.loader.Load() + # Don't call self.problems.AssertNoMoreExceptions() because there are lots + # of problems but I only care that the validator doesn't crash. In the + # magical future the validator will stop when the csv is obviously hosed. + + +class RouteMemoryZipTestCase(MemoryZipTestCase): + def assertLoadAndCheckExtraValues(self, schedule_file): + """Load file-like schedule_file and check for extra route columns.""" + load_problems = TestFailureProblemReporter( + self, ("ExpirationDate", "UnrecognizedColumn")) + loaded_schedule = transitfeed.Loader(schedule_file, + problems=load_problems, + extra_validation=True).Load() + self.assertEqual("foo", loaded_schedule.GetRoute("t")["t_foo"]) + self.assertEqual("", loaded_schedule.GetRoute("AB")["t_foo"]) + self.assertEqual("bar", loaded_schedule.GetRoute("n")["n_foo"]) + self.assertEqual("", loaded_schedule.GetRoute("AB")["n_foo"]) + # Uncomment the following lines to print the string in testExtraFileColumn + # print repr(zipfile.ZipFile(schedule_file).read("routes.txt")) + # self.fail() + + def testExtraObjectAttribute(self): + """Extra columns added to an object are preserved when writing.""" + schedule = self.loader.Load() + # Add an attribute after AddRouteObject + route_t = transitfeed.Route(short_name="T", route_type="Bus", route_id="t") + schedule.AddRouteObject(route_t) + route_t.t_foo = "foo" + # Add an attribute before AddRouteObject + route_n = transitfeed.Route(short_name="N", route_type="Bus", route_id="n") + route_n.n_foo = "bar" + schedule.AddRouteObject(route_n) + saved_schedule_file = StringIO() + schedule.WriteGoogleTransitFeed(saved_schedule_file) + self.problems.AssertNoMoreExceptions() + + self.assertLoadAndCheckExtraValues(saved_schedule_file) + + def testExtraFileColumn(self): + """Extra columns loaded from a file are preserved when writing.""" + # Uncomment the code in assertLoadAndCheckExtraValues to generate this + # string. + self.zip.writestr( + "routes.txt", + "route_id,agency_id,route_short_name,route_long_name,route_type," + "t_foo,n_foo\n" + "AB,DTA,,Airport Bullfrog,3,,\n" + "t,DTA,T,,3,foo,\n" + "n,DTA,N,,3,,bar\n") + load1_problems = TestFailureProblemReporter( + self, ("ExpirationDate", "UnrecognizedColumn")) + schedule = transitfeed.Loader(problems=load1_problems, + extra_validation=True, + zip=self.zip).Load() + saved_schedule_file = StringIO() + schedule.WriteGoogleTransitFeed(saved_schedule_file) + + self.assertLoadAndCheckExtraValues(saved_schedule_file) + + +class RouteConstructorTestCase(unittest.TestCase): + def setUp(self): + self.problems = RecordingProblemReporter(self) + + def testDefault(self): + route = transitfeed.Route() + repr(route) + self.assertEqual({}, dict(route)) + route.Validate(self.problems) + repr(route) + self.assertEqual({}, dict(route)) + + e = self.problems.PopException('MissingValue') + self.assertEqual('route_id', e.column_name) + e = self.problems.PopException('MissingValue') + self.assertEqual('route_type', e.column_name) + e = self.problems.PopException('InvalidValue') + self.assertEqual('route_short_name', e.column_name) + self.problems.AssertNoMoreExceptions() + + def testInitArgs(self): + # route_type name + route = transitfeed.Route(route_id='id1', short_name='22', route_type='Bus') + repr(route) + route.Validate(self.problems) + self.problems.AssertNoMoreExceptions() + self.assertEquals(3, route.route_type) # converted to an int + self.assertEquals({'route_id': 'id1', 'route_short_name': '22', + 'route_type': '3'}, dict(route)) + + # route_type as an int + route = transitfeed.Route(route_id='i1', long_name='Twenty 2', route_type=1) + repr(route) + route.Validate(self.problems) + self.problems.AssertNoMoreExceptions() + self.assertEquals(1, route.route_type) # kept as an int + self.assertEquals({'route_id': 'i1', 'route_long_name': 'Twenty 2', + 'route_type': '1'}, dict(route)) + + # route_type as a string + route = transitfeed.Route(route_id='id1', short_name='22', route_type='1') + repr(route) + route.Validate(self.problems) + self.problems.AssertNoMoreExceptions() + self.assertEquals(1, route.route_type) # converted to an int + self.assertEquals({'route_id': 'id1', 'route_short_name': '22', + 'route_type': '1'}, dict(route)) + + # route_type has undefined int value + route = transitfeed.Route(route_id='id1', short_name='22', + route_type='8') + repr(route) + route.Validate(self.problems) + e = self.problems.PopException('InvalidValue') + self.assertEqual('route_type', e.column_name) + self.assertEqual(1, e.type) + self.problems.AssertNoMoreExceptions() + self.assertEquals({'route_id': 'id1', 'route_short_name': '22', + 'route_type': '8'}, dict(route)) + + # route_type that doesn't parse + route = transitfeed.Route(route_id='id1', short_name='22', + route_type='1foo') + repr(route) + route.Validate(self.problems) + e = self.problems.PopException('InvalidValue') + self.assertEqual('route_type', e.column_name) + self.problems.AssertNoMoreExceptions() + self.assertEquals({'route_id': 'id1', 'route_short_name': '22', + 'route_type': '1foo'}, dict(route)) + + # agency_id + route = transitfeed.Route(route_id='id1', short_name='22', route_type=1, + agency_id='myage') + repr(route) + route.Validate(self.problems) + self.problems.AssertNoMoreExceptions() + self.assertEquals({'route_id': 'id1', 'route_short_name': '22', + 'route_type': '1', 'agency_id': 'myage'}, dict(route)) + + def testInitArgOrder(self): + """Call Route.__init__ without any names so a change in order is noticed.""" + route = transitfeed.Route('short', 'long name', 'Bus', 'r1', 'a1') + self.assertEquals({'route_id': 'r1', 'route_short_name': 'short', + 'route_long_name': 'long name', + 'route_type': '3', 'agency_id': 'a1'}, dict(route)) + + def testFieldDict(self): + route = transitfeed.Route(field_dict={}) + self.assertEquals({}, dict(route)) + + route = transitfeed.Route(field_dict={ + 'route_id': 'id1', 'route_short_name': '22', 'agency_id': 'myage', + 'route_type': '1'}) + route.Validate(self.problems) + self.problems.AssertNoMoreExceptions() + self.assertEquals({'route_id': 'id1', 'route_short_name': '22', + 'agency_id': 'myage', 'route_type': '1'}, dict(route)) + + route = transitfeed.Route(field_dict={ + 'route_id': 'id1', 'route_short_name': '22', 'agency_id': 'myage', + 'route_type': '1', 'my_column': 'v'}) + route.Validate(self.problems) + self.problems.AssertNoMoreExceptions() + self.assertEquals({'route_id': 'id1', 'route_short_name': '22', + 'agency_id': 'myage', 'route_type': '1', + 'my_column':'v'}, dict(route)) + route._private = 0.3 # Isn't copied + route_copy = transitfeed.Route(field_dict=route) + self.assertEquals({'route_id': 'id1', 'route_short_name': '22', + 'agency_id': 'myage', 'route_type': '1', + 'my_column':'v'}, dict(route_copy)) + + +class RouteValidationTestCase(ValidationTestCase): + def runTest(self): + # success case + route = transitfeed.Route() + route.route_id = '054C' + route.route_short_name = '54C' + route.route_long_name = 'South Side - North Side' + route.route_type = 7 + route.Validate(self.problems) + + # blank short & long names + route.route_short_name = '' + route.route_long_name = ' ' + self.ExpectInvalidValue(route, 'route_short_name') + + # short name too long + route.route_short_name = 'South Side' + route.route_long_name = '' + self.ExpectInvalidValue(route, 'route_short_name') + route.route_short_name = 'M7bis' # 5 is OK + route.Validate(self.problems) + + # long name contains short name + route.route_short_name = '54C' + route.route_long_name = '54C South Side - North Side' + self.ExpectInvalidValue(route, 'route_long_name') + route.route_long_name = '54C(South Side - North Side)' + self.ExpectInvalidValue(route, 'route_long_name') + route.route_long_name = '54C-South Side - North Side' + self.ExpectInvalidValue(route, 'route_long_name') + + # long name is same as short name + route.route_short_name = '54C' + route.route_long_name = '54C' + self.ExpectInvalidValue(route, 'route_long_name') + + # route description is same as short name + route.route_desc = '54C' + route.route_short_name = '54C' + route.route_long_name = '' + self.ExpectInvalidValue(route, 'route_desc') + route.route_desc = None + + # route description is same as long name + route.route_desc = 'South Side - North Side' + route.route_long_name = 'South Side - North Side' + self.ExpectInvalidValue(route, 'route_desc') + route.route_desc = None + + # invalid route types + route.route_type = 8 + self.ExpectInvalidValue(route, 'route_type') + route.route_type = -1 + self.ExpectInvalidValue(route, 'route_type') + route.route_type = 7 + + # invalid route URL + route.route_url = 'www.example.com' + self.ExpectInvalidValue(route, 'route_url') + route.route_url = None + + # invalid route color + route.route_color = 'orange' + self.ExpectInvalidValue(route, 'route_color') + route.route_color = None + + # invalid route text color + route.route_text_color = 'orange' + self.ExpectInvalidValue(route, 'route_text_color') + route.route_text_color = None + + # missing route ID + route.route_id = None + self.ExpectMissingValue(route, 'route_id') + route.route_id = '054C' + + # bad color contrast + route.route_text_color = None # black + route.route_color = '0000FF' # Bad + self.ExpectInvalidValue(route, 'route_color') + route.route_color = '00BF00' # OK + route.Validate(self.problems) + route.route_color = '005F00' # Bad + self.ExpectInvalidValue(route, 'route_color') + route.route_color = 'FF00FF' # OK + route.Validate(self.problems) + route.route_text_color = 'FFFFFF' # OK too + route.Validate(self.problems) + route.route_text_color = '00FF00' # think of color-blind people! + self.ExpectInvalidValue(route, 'route_color') + route.route_text_color = '007F00' + route.route_color = 'FF0000' + self.ExpectInvalidValue(route, 'route_color') + route.route_color = '00FFFF' # OK + route.Validate(self.problems) + route.route_text_color = None # black + route.route_color = None # white + route.Validate(self.problems) + self.problems.AssertNoMoreExceptions() + + +class ShapeValidationTestCase(ValidationTestCase): + def ExpectFailedAdd(self, shape, lat, lon, dist, column_name, value): + self.ExpectInvalidValueInClosure( + column_name, value, + lambda: shape.AddPoint(lat, lon, dist, self.problems)) + + def runTest(self): + shape = transitfeed.Shape('TEST') + repr(shape) # shouldn't crash + self.ExpectOtherProblem(shape) # no points! + + self.ExpectFailedAdd(shape, 36.905019, -116.763207, -1, + 'shape_dist_traveled', -1) + + shape.AddPoint(36.915760, -116.751709, 0, self.problems) + shape.AddPoint(36.905018, -116.763206, 5, self.problems) + shape.Validate(self.problems) + + shape.shape_id = None + self.ExpectMissingValue(shape, 'shape_id') + shape.shape_id = 'TEST' + + self.ExpectFailedAdd(shape, 91, -116.751709, 6, 'shape_pt_lat', 91) + self.ExpectFailedAdd(shape, -91, -116.751709, 6, 'shape_pt_lat', -91) + + self.ExpectFailedAdd(shape, 36.915760, -181, 6, 'shape_pt_lon', -181) + self.ExpectFailedAdd(shape, 36.915760, 181, 6, 'shape_pt_lon', 181) + + self.ExpectFailedAdd(shape, 0.5, -0.5, 6, 'shape_pt_lat', 0.5) + self.ExpectFailedAdd(shape, 0, 0, 6, 'shape_pt_lat', 0) + + # distance decreasing is bad, but staying the same is OK + shape.AddPoint(36.905019, -116.763206, 4, self.problems) + e = self.problems.PopException('InvalidValue') + self.assertMatchesRegex('Each subsequent point', e.FormatProblem()) + self.assertMatchesRegex('distance was 5.000000.', e.FormatProblem()) + self.problems.AssertNoMoreExceptions() + + shape.AddPoint(36.925019, -116.764206, 5, self.problems) + self.problems.AssertNoMoreExceptions() + + +class FareValidationTestCase(ValidationTestCase): + def runTest(self): + fare = transitfeed.Fare() + fare.fare_id = "normal" + fare.price = 1.50 + fare.currency_type = "USD" + fare.payment_method = 0 + fare.transfers = 1 + fare.transfer_duration = 7200 + fare.Validate(self.problems) + + fare.fare_id = None + self.ExpectMissingValue(fare, "fare_id") + fare.fare_id = '' + self.ExpectMissingValue(fare, "fare_id") + fare.fare_id = "normal" + + fare.price = "1.50" + self.ExpectInvalidValue(fare, "price") + fare.price = 1 + fare.Validate(self.problems) + fare.price = None + self.ExpectMissingValue(fare, "price") + fare.price = 0.0 + fare.Validate(self.problems) + fare.price = -1.50 + self.ExpectInvalidValue(fare, "price") + fare.price = 1.50 + + fare.currency_type = "" + self.ExpectMissingValue(fare, "currency_type") + fare.currency_type = None + self.ExpectMissingValue(fare, "currency_type") + fare.currency_type = "usd" + self.ExpectInvalidValue(fare, "currency_type") + fare.currency_type = "KML" + self.ExpectInvalidValue(fare, "currency_type") + fare.currency_type = "USD" + + fare.payment_method = "0" + self.ExpectInvalidValue(fare, "payment_method") + fare.payment_method = -1 + self.ExpectInvalidValue(fare, "payment_method") + fare.payment_method = 1 + fare.Validate(self.problems) + fare.payment_method = 2 + self.ExpectInvalidValue(fare, "payment_method") + fare.payment_method = None + self.ExpectMissingValue(fare, "payment_method") + fare.payment_method = "" + self.ExpectMissingValue(fare, "payment_method") + fare.payment_method = 0 + + fare.transfers = "1" + self.ExpectInvalidValue(fare, "transfers") + fare.transfers = -1 + self.ExpectInvalidValue(fare, "transfers") + fare.transfers = 2 + fare.Validate(self.problems) + fare.transfers = 3 + self.ExpectInvalidValue(fare, "transfers") + fare.transfers = None + fare.Validate(self.problems) + fare.transfers = 1 + + fare.transfer_duration = 0 + fare.Validate(self.problems) + fare.transfer_duration = None + fare.Validate(self.problems) + fare.transfer_duration = -3600 + self.ExpectInvalidValue(fare, "transfer_duration") + fare.transfers = 0 # no transfers allowed but duration specified! + fare.transfer_duration = 3600 + self.ExpectInvalidValue(fare, "transfer_duration") + fare.transfers = 1 + fare.transfer_duration = "3600" + self.ExpectInvalidValue(fare, "transfer_duration") + fare.transfer_duration = 7200 + self.problems.AssertNoMoreExceptions() + +class TransferValidationTestCase(ValidationTestCase): + def runTest(self): + # Totally bogus data shouldn't cause a crash + transfer = transitfeed.Transfer(field_dict={"ignored": "foo"}) + self.assertEquals(0, transfer.transfer_type) + + transfer = transitfeed.Transfer(from_stop_id = "S1", to_stop_id = "S2", + transfer_type = "1", min_transfer_time = 2) + self.assertEquals("S1", transfer.from_stop_id) + self.assertEquals("S2", transfer.to_stop_id) + self.assertEquals(1, transfer.transfer_type) + self.assertEquals(2, transfer.min_transfer_time) + transfer.Validate(self.problems) + self.assertEquals("S1", transfer.from_stop_id) + self.assertEquals("S2", transfer.to_stop_id) + self.assertEquals(1, transfer.transfer_type) + self.assertEquals(2, transfer.min_transfer_time) + self.problems.AssertNoMoreExceptions() + + transfer = transitfeed.Transfer(field_dict={"from_stop_id": "S1", \ + "to_stop_id": "S2", \ + "transfer_type": "0", \ + "min_transfer_time": "2"}) + self.assertEquals("S1", transfer.from_stop_id) + self.assertEquals("S2", transfer.to_stop_id) + self.assertEquals(0, transfer.transfer_type) + self.assertEquals(2, transfer.min_transfer_time) + transfer.Validate(self.problems) + self.assertEquals("S1", transfer.from_stop_id) + self.assertEquals("S2", transfer.to_stop_id) + self.assertEquals(0, transfer.transfer_type) + self.assertEquals(2, transfer.min_transfer_time) + self.problems.AssertNoMoreExceptions() + + transfer = transitfeed.Transfer(field_dict={"from_stop_id": "S1", \ + "to_stop_id": "S2", \ + "transfer_type": "-4", \ + "min_transfer_time": "2"}) + self.assertEquals("S1", transfer.from_stop_id) + self.assertEquals("S2", transfer.to_stop_id) + self.assertEquals("-4", transfer.transfer_type) + self.assertEquals(2, transfer.min_transfer_time) + self.ExpectInvalidValue(transfer, "transfer_type") + self.assertEquals("S1", transfer.from_stop_id) + self.assertEquals("S2", transfer.to_stop_id) + self.assertEquals("-4", transfer.transfer_type) + self.assertEquals(2, transfer.min_transfer_time) + + transfer = transitfeed.Transfer(field_dict={"from_stop_id": "S1", \ + "to_stop_id": "S2", \ + "transfer_type": "", \ + "min_transfer_time": "-1"}) + self.assertEquals(0, transfer.transfer_type) + self.ExpectInvalidValue(transfer, "min_transfer_time") + + # simple successes + transfer = transitfeed.Transfer() + transfer.from_stop_id = "S1" + transfer.to_stop_id = "S2" + transfer.transfer_type = 0 + repr(transfer) # shouldn't crash + transfer.Validate(self.problems) + transfer.transfer_type = 3 + transfer.Validate(self.problems) + self.problems.AssertNoMoreExceptions() + + # transfer_type is out of range + transfer.transfer_type = 4 + self.ExpectInvalidValue(transfer, "transfer_type") + transfer.transfer_type = -1 + self.ExpectInvalidValue(transfer, "transfer_type") + transfer.transfer_type = "text" + self.ExpectInvalidValue(transfer, "transfer_type") + transfer.transfer_type = 2 + + # invalid min_transfer_time + transfer.min_transfer_time = -1 + self.ExpectInvalidValue(transfer, "min_transfer_time") + transfer.min_transfer_time = "text" + self.ExpectInvalidValue(transfer, "min_transfer_time") + transfer.min_transfer_time = 250 + transfer.Validate(self.problems) + self.problems.AssertNoMoreExceptions() + + # missing stop ids + transfer.from_stop_id = "" + self.ExpectMissingValue(transfer, 'from_stop_id') + transfer.from_stop_id = "S1" + transfer.to_stop_id = None + self.ExpectMissingValue(transfer, 'to_stop_id') + transfer.to_stop_id = "S2" + + # stops are presented in schedule case + schedule = transitfeed.Schedule() + stop1 = schedule.AddStop(57.5, 30.2, "stop 1") + stop2 = schedule.AddStop(57.5, 30.3, "stop 2") + transfer = transitfeed.Transfer(schedule=schedule) + transfer.from_stop_id = stop1.stop_id + transfer.to_stop_id = stop2.stop_id + transfer.transfer_type = 2 + transfer.min_transfer_time = 250 + repr(transfer) # shouldn't crash + transfer.Validate(self.problems) + self.problems.AssertNoMoreExceptions() + + # stops are not presented in schedule case + schedule = transitfeed.Schedule() + stop1 = schedule.AddStop(57.5, 30.2, "stop 1") + transfer = transitfeed.Transfer(schedule=schedule) + transfer.from_stop_id = stop1.stop_id + transfer.to_stop_id = "unexist" + transfer.transfer_type = 2 + transfer.min_transfer_time = 250 + self.ExpectInvalidValue(transfer, 'to_stop_id') + transfer.from_stop_id = "unexist" + transfer.to_stop_id = stop1.stop_id + self.ExpectInvalidValue(transfer, "from_stop_id") + self.problems.AssertNoMoreExceptions() + + # Transfer can only be added to a schedule once + transfer = transitfeed.Transfer() + transfer.from_stop_id = stop1.stop_id + transfer.to_stop_id = stop1.stop_id + schedule.AddTransferObject(transfer) + self.assertRaises(AssertionError, schedule.AddTransferObject, transfer) + + +class ServicePeriodValidationTestCase(ValidationTestCase): + def runTest(self): + # success case + period = transitfeed.ServicePeriod() + repr(period) # shouldn't crash + period.service_id = 'WEEKDAY' + period.start_date = '20070101' + period.end_date = '20071231' + period.day_of_week[0] = True + repr(period) # shouldn't crash + period.Validate(self.problems) + + # missing start_date. If one of start_date or end_date is None then + # ServicePeriod.Validate assumes the required column is missing and already + # generated an error. Instead set it to an empty string, such as when the + # csv cell is empty. See also comment in ServicePeriod.Validate. + period.start_date = '' + self.ExpectMissingValue(period, 'start_date') + period.start_date = '20070101' + + # missing end_date + period.end_date = '' + self.ExpectMissingValue(period, 'end_date') + period.end_date = '20071231' + + # invalid start_date + period.start_date = '2007-01-01' + self.ExpectInvalidValue(period, 'start_date') + period.start_date = '20070101' + + # impossible start_date + period.start_date = '20070229' + self.ExpectInvalidValue(period, 'start_date') + period.start_date = '20070101' + + # invalid end_date + period.end_date = '2007/12/31' + self.ExpectInvalidValue(period, 'end_date') + period.end_date = '20071231' + + # start & end dates out of order + period.end_date = '20060101' + self.ExpectInvalidValue(period, 'end_date') + period.end_date = '20071231' + + # no service in period + period.day_of_week[0] = False + self.ExpectOtherProblem(period) + period.day_of_week[0] = True + + # invalid exception date + period.SetDateHasService('2007', False) + self.ExpectInvalidValue(period, 'date', '2007') + period.ResetDateToNormalService('2007') + + period2 = transitfeed.ServicePeriod( + field_list=['serviceid1', '20060101', '20071231', '1', '0', 'h', '1', + '1', '1', '1']) + self.ExpectInvalidValue(period2, 'wednesday', 'h') + repr(period) # shouldn't crash + + +class ServicePeriodDateRangeTestCase(ValidationTestCase): + def runTest(self): + period = transitfeed.ServicePeriod() + period.service_id = 'WEEKDAY' + period.start_date = '20070101' + period.end_date = '20071231' + period.SetWeekdayService(True) + period.SetDateHasService('20071231', False) + period.Validate(self.problems) + self.assertEqual(('20070101', '20071231'), period.GetDateRange()) + + period2 = transitfeed.ServicePeriod() + period2.service_id = 'HOLIDAY' + period2.SetDateHasService('20071225', True) + period2.SetDateHasService('20080101', True) + period2.SetDateHasService('20080102', False) + period2.Validate(self.problems) + self.assertEqual(('20071225', '20080101'), period2.GetDateRange()) + + period2.start_date = '20071201' + period2.end_date = '20071225' + period2.Validate(self.problems) + self.assertEqual(('20071201', '20080101'), period2.GetDateRange()) + + period3 = transitfeed.ServicePeriod() + self.assertEqual((None, None), period3.GetDateRange()) + + period4 = transitfeed.ServicePeriod() + period4.service_id = 'halloween' + period4.SetDateHasService('20051031', True) + self.assertEqual(('20051031', '20051031'), period4.GetDateRange()) + period4.Validate(self.problems) + + schedule = transitfeed.Schedule(problem_reporter=self.problems) + self.assertEqual((None, None), schedule.GetDateRange()) + schedule.AddServicePeriodObject(period) + self.assertEqual(('20070101', '20071231'), schedule.GetDateRange()) + schedule.AddServicePeriodObject(period2) + self.assertEqual(('20070101', '20080101'), schedule.GetDateRange()) + schedule.AddServicePeriodObject(period4) + self.assertEqual(('20051031', '20080101'), schedule.GetDateRange()) + self.problems.AssertNoMoreExceptions() + + +class ServicePeriodTestCase(unittest.TestCase): + def testActive(self): + """Test IsActiveOn and ActiveDates""" + period = transitfeed.ServicePeriod() + period.service_id = 'WEEKDAY' + period.start_date = '20071226' + period.end_date = '20071231' + period.SetWeekdayService(True) + period.SetDateHasService('20071230', True) + period.SetDateHasService('20071231', False) + period.SetDateHasService('20080102', True) + # December 2007 + # Su Mo Tu We Th Fr Sa + # 23 24 25 26 27 28 29 + # 30 31 + + # Some tests have named arguments and others do not to ensure that any + # (possibly unwanted) changes to the API get caught + + # calendar_date exceptions near start date + self.assertFalse(period.IsActiveOn(date='20071225')) + self.assertFalse(period.IsActiveOn(date='20071225', + date_object=date(2007, 12, 25))) + self.assertTrue(period.IsActiveOn(date='20071226')) + self.assertTrue(period.IsActiveOn(date='20071226', + date_object=date(2007, 12, 26))) + + # calendar_date exceptions near end date + self.assertTrue(period.IsActiveOn('20071230')) + self.assertTrue(period.IsActiveOn('20071230', date(2007, 12, 30))) + self.assertFalse(period.IsActiveOn('20071231')) + self.assertFalse(period.IsActiveOn('20071231', date(2007, 12, 31))) + + # date just outside range, both weekday and an exception + self.assertFalse(period.IsActiveOn('20080101')) + self.assertFalse(period.IsActiveOn('20080101', date(2008, 1, 1))) + self.assertTrue(period.IsActiveOn('20080102')) + self.assertTrue(period.IsActiveOn('20080102', date(2008, 1, 2))) + + self.assertEquals(period.ActiveDates(), + ['20071226', '20071227', '20071228', '20071230', + '20080102']) + + + # Test of period without start_date, end_date + period_dates = transitfeed.ServicePeriod() + period_dates.SetDateHasService('20071230', True) + period_dates.SetDateHasService('20071231', False) + + self.assertFalse(period_dates.IsActiveOn(date='20071229')) + self.assertFalse(period_dates.IsActiveOn(date='20071229', + date_object=date(2007, 12, 29))) + self.assertTrue(period_dates.IsActiveOn('20071230')) + self.assertTrue(period_dates.IsActiveOn('20071230', date(2007, 12, 30))) + self.assertFalse(period_dates.IsActiveOn('20071231')) + self.assertFalse(period_dates.IsActiveOn('20071231', date(2007, 12, 31))) + self.assertEquals(period_dates.ActiveDates(), ['20071230']) + + # Test with an invalid ServicePeriod; one of start_date, end_date is set + period_no_end = transitfeed.ServicePeriod() + period_no_end.start_date = '20071226' + self.assertFalse(period_no_end.IsActiveOn(date='20071231')) + self.assertFalse(period_no_end.IsActiveOn(date='20071231', + date_object=date(2007, 12, 31))) + self.assertEquals(period_no_end.ActiveDates(), []) + period_no_start = transitfeed.ServicePeriod() + period_no_start.end_date = '20071230' + self.assertFalse(period_no_start.IsActiveOn('20071229')) + self.assertFalse(period_no_start.IsActiveOn('20071229', date(2007, 12, 29))) + self.assertEquals(period_no_start.ActiveDates(), []) + + period_empty = transitfeed.ServicePeriod() + self.assertFalse(period_empty.IsActiveOn('20071231')) + self.assertFalse(period_empty.IsActiveOn('20071231', date(2007, 12, 31))) + self.assertEquals(period_empty.ActiveDates(), []) + + +class GetServicePeriodsActiveEachDateTestCase(unittest.TestCase): + def testEmpty(self): + schedule = transitfeed.Schedule() + self.assertEquals( + [], + schedule.GetServicePeriodsActiveEachDate(date(2009, 1, 1), + date(2009, 1, 1))) + self.assertEquals( + [(date(2008, 12, 31), []), (date(2009, 1, 1), [])], + schedule.GetServicePeriodsActiveEachDate(date(2008, 12, 31), + date(2009, 1, 2))) + def testOneService(self): + schedule = transitfeed.Schedule() + sp1 = transitfeed.ServicePeriod() + sp1.service_id = "sp1" + sp1.SetDateHasService("20090101") + sp1.SetDateHasService("20090102") + schedule.AddServicePeriodObject(sp1) + self.assertEquals( + [], + schedule.GetServicePeriodsActiveEachDate(date(2009, 1, 1), + date(2009, 1, 1))) + self.assertEquals( + [(date(2008, 12, 31), []), (date(2009, 1, 1), [sp1])], + schedule.GetServicePeriodsActiveEachDate(date(2008, 12, 31), + date(2009, 1, 2))) + + def testTwoService(self): + schedule = transitfeed.Schedule() + sp1 = transitfeed.ServicePeriod() + sp1.service_id = "sp1" + sp1.SetDateHasService("20081231") + sp1.SetDateHasService("20090101") + + schedule.AddServicePeriodObject(sp1) + sp2 = transitfeed.ServicePeriod() + sp2.service_id = "sp2" + sp2.SetStartDate("20081201") + sp2.SetEndDate("20081231") + sp2.SetWeekendService() + sp2.SetWeekdayService() + schedule.AddServicePeriodObject(sp2) + self.assertEquals( + [], + schedule.GetServicePeriodsActiveEachDate(date(2009, 1, 1), + date(2009, 1, 1))) + date_services = schedule.GetServicePeriodsActiveEachDate(date(2008, 12, 31), + date(2009, 1, 2)) + self.assertEquals( + [date(2008, 12, 31), date(2009, 1, 1)], [d for d, _ in date_services]) + self.assertEquals(set([sp1, sp2]), set(date_services[0][1])) + self.assertEquals([sp1], date_services[1][1]) + + +class TripMemoryZipTestCase(MemoryZipTestCase): + def assertLoadAndCheckExtraValues(self, schedule_file): + """Load file-like schedule_file and check for extra trip columns.""" + load_problems = TestFailureProblemReporter( + self, ("ExpirationDate", "UnrecognizedColumn")) + loaded_schedule = transitfeed.Loader(schedule_file, + problems=load_problems, + extra_validation=True).Load() + self.assertEqual("foo", loaded_schedule.GetTrip("AB1")["t_foo"]) + self.assertEqual("", loaded_schedule.GetTrip("AB2")["t_foo"]) + self.assertEqual("", loaded_schedule.GetTrip("AB1")["n_foo"]) + self.assertEqual("bar", loaded_schedule.GetTrip("AB2")["n_foo"]) + # Uncomment the following lines to print the string in testExtraFileColumn + # print repr(zipfile.ZipFile(schedule_file).read("trips.txt")) + # self.fail() + + def testExtraObjectAttribute(self): + """Extra columns added to an object are preserved when writing.""" + schedule = self.loader.Load() + # Add an attribute to an existing trip + trip1 = schedule.GetTrip("AB1") + trip1.t_foo = "foo" + # Make a copy of trip_id=AB1 and add an attribute before AddTripObject + trip2 = transitfeed.Trip(field_dict=trip1) + trip2.trip_id = "AB2" + trip2.t_foo = "" + trip2.n_foo = "bar" + schedule.AddTripObject(trip2) + trip2.AddStopTime(stop=schedule.GetStop("BULLFROG"), stop_time="09:00:00") + trip2.AddStopTime(stop=schedule.GetStop("STAGECOACH"), stop_time="09:30:00") + saved_schedule_file = StringIO() + schedule.WriteGoogleTransitFeed(saved_schedule_file) + self.appendToZip(saved_schedule_file, "stop_times.txt","") + self.problems.AssertNoMoreExceptions() + + self.assertLoadAndCheckExtraValues(saved_schedule_file) + + def testExtraFileColumn(self): + """Extra columns loaded from a file are preserved when writing.""" + # Uncomment the code in assertLoadAndCheckExtraValues to generate this + # string. + self.zip.writestr( + "trips.txt", + "route_id,service_id,trip_id,t_foo,n_foo\n" + "AB,FULLW,AB1,foo,\n" + "AB,FULLW,AB2,,bar\n") + self.zip.writestr( + "stop_times.txt", + self.zip.read("stop_times.txt") + + "AB2,09:00:00,09:00:00,BULLFROG,1\n" + "AB2,09:30:00,09:30:00,STAGECOACH,2\n") + load1_problems = TestFailureProblemReporter( + self, ("ExpirationDate", "UnrecognizedColumn")) + schedule = transitfeed.Loader(problems=load1_problems, + extra_validation=True, + zip=self.zip).Load() + saved_schedule_file = StringIO() + schedule.WriteGoogleTransitFeed(saved_schedule_file) + + self.assertLoadAndCheckExtraValues(saved_schedule_file) + + +class TripValidationTestCase(ValidationTestCase): + def runTest(self): + trip = transitfeed.Trip() + repr(trip) # shouldn't crash + + schedule = transitfeed.Schedule() # Needed to find StopTimes + schedule.AddRouteObject( + transitfeed.Route(short_name="54C", long_name="", route_type="Bus", + route_id="054C", + agency_id=schedule.GetDefaultAgency().agency_id)) + schedule.AddServicePeriodObject(transitfeed.ServicePeriod(id="WEEK")) + schedule.GetDefaultServicePeriod().SetDateHasService('20070101') + trip = transitfeed.Trip() + repr(trip) # shouldn't crash + + trip = transitfeed.Trip() + trip.trip_headsign = '\xBA\xDF\x0D' # Not valid ascii or utf8 + repr(trip) # shouldn't crash + + trip.route_id = '054C' + trip.service_id = 'WEEK' + trip.trip_id = '054C-00' + trip.trip_headsign = 'via Polish Hill' + trip.direction_id = '0' + trip.block_id = None + trip.shape_id = None + trip.Validate(self.problems) + self.problems.AssertNoMoreExceptions() + repr(trip) # shouldn't crash + + # missing route ID + trip.route_id = None + self.ExpectMissingValue(trip, 'route_id') + trip.route_id = '054C' + + # missing service ID + trip.service_id = None + self.ExpectMissingValue(trip, 'service_id') + trip.service_id = 'WEEK' + + # missing trip ID + trip.trip_id = None + self.ExpectMissingValue(trip, 'trip_id') + trip.trip_id = '054C-00' + + # invalid direction ID + trip.direction_id = 'NORTH' + self.ExpectInvalidValue(trip, 'direction_id') + trip.direction_id = '0' + + # AddTripObject validates that route_id, service_id, .... are found in the + # schedule. The Validate calls made by self.Expect... above can't make this + # check because trip is not in a schedule. + trip.route_id = '054C-notfound' + schedule.AddTripObject(trip, self.problems) + e = self.problems.PopException('InvalidValue') + self.assertEqual('route_id', e.column_name) + self.problems.AssertNoMoreExceptions() + trip.route_id = '054C' + + # Make sure calling Trip.Validate validates that route_id and service_id + # are found in the schedule. + trip.service_id = 'WEEK-notfound' + trip.Validate(self.problems) + e = self.problems.PopException('InvalidValue') + self.assertEqual('service_id', e.column_name) + self.problems.AssertNoMoreExceptions() + trip.service_id = 'WEEK' + + trip.Validate(self.problems) + self.problems.AssertNoMoreExceptions() + + # expect no problems for non-overlapping periods + trip.AddHeadwayPeriod("06:00:00", "12:00:00", 600) + trip.AddHeadwayPeriod("01:00:00", "02:00:00", 1200) + trip.AddHeadwayPeriod("04:00:00", "05:00:00", 1000) + trip.AddHeadwayPeriod("12:00:00", "19:00:00", 700) + trip.Validate(self.problems) + self.problems.AssertNoMoreExceptions() + trip.ClearHeadwayPeriods() + + # overlapping headway periods + trip.AddHeadwayPeriod("00:00:00", "12:00:00", 600) + trip.AddHeadwayPeriod("06:00:00", "18:00:00", 1200) + self.ExpectOtherProblem(trip) + trip.ClearHeadwayPeriods() + trip.AddHeadwayPeriod("12:00:00", "20:00:00", 600) + trip.AddHeadwayPeriod("06:00:00", "18:00:00", 1200) + self.ExpectOtherProblem(trip) + trip.ClearHeadwayPeriods() + trip.AddHeadwayPeriod("06:00:00", "12:00:00", 600) + trip.AddHeadwayPeriod("00:00:00", "25:00:00", 1200) + self.ExpectOtherProblem(trip) + trip.ClearHeadwayPeriods() + trip.AddHeadwayPeriod("00:00:00", "20:00:00", 600) + trip.AddHeadwayPeriod("06:00:00", "18:00:00", 1200) + self.ExpectOtherProblem(trip) + trip.ClearHeadwayPeriods() + self.problems.AssertNoMoreExceptions() + +class TripSequenceValidationTestCase(ValidationTestCase): + def runTest(self): + trip = transitfeed.Trip() + schedule = transitfeed.Schedule() # Needed to find StopTimes + route = transitfeed.Route(short_name="54C", long_name="", route_type="Bus", + route_id="054C") + route.agency_id = schedule.GetDefaultAgency().agency_id + schedule.AddRouteObject(route) + service_period = transitfeed.ServicePeriod("WEEK") + service_period.SetWeekdayService(True) + schedule.AddServicePeriodObject(service_period) + trip = transitfeed.Trip() + trip.trip_headsign = '\xBA\xDF\x0D' # Not valid ascii or utf8 + trip.route_id = '054C' + trip.service_id = 'WEEK' + trip.trip_id = '054C-00' + trip.trip_headsign = 'via Polish Hill' + trip.direction_id = '0' + trip.block_id = None + trip.shape_id = None + stop1 = transitfeed.Stop(36.425288, -117.133162, "Demo Stop 1", "STOP1") + stop2 = transitfeed.Stop(36.425666, -117.133666, "Demo Stop 2", "STOP2") + stop3 = transitfeed.Stop(36.425999, -117.133999, "Demo Stop 3", "STOP3") + schedule.AddTripObject(trip) + schedule.AddStopObject(stop1) + schedule.AddStopObject(stop2) + schedule.AddStopObject(stop3) + stoptime1 = transitfeed.StopTime(self.problems, stop1, + stop_time='12:00:00', stop_sequence=1) + stoptime2 = transitfeed.StopTime(self.problems, stop2, + stop_time='11:30:00', stop_sequence=2) + stoptime3 = transitfeed.StopTime(self.problems, stop3, + stop_time='12:15:00', stop_sequence=3) + trip._AddStopTimeObjectUnordered(stoptime1, schedule) + trip._AddStopTimeObjectUnordered(stoptime2, schedule) + trip._AddStopTimeObjectUnordered(stoptime3, schedule) + trip.Validate(self.problems) + e = self.problems.PopException('OtherProblem') + self.assertTrue(e.FormatProblem().find('Timetravel detected') != -1) + self.assertTrue(e.FormatProblem().find('number 2 in trip 054C-00') != -1) + self.problems.AssertNoMoreExceptions() + + +class TripServiceIDValidationTestCase(ValidationTestCase): + def runTest(self): + schedule = transitfeed.Schedule(self.problems) + schedule.AddAgency("Test Agency", "http://example.com", + "America/Los_Angeles") + service_period = transitfeed.ServicePeriod("WEEK") + service_period.SetStartDate("20070101") + service_period.SetEndDate("20071231") + service_period.SetWeekdayService(True) + schedule.AddServicePeriodObject(service_period) + + schedule.AddRouteObject( + transitfeed.Route("54C", "Polish Hill", 3, "054C")) + + trip1 = transitfeed.Trip() + trip1.route_id = "054C" + trip1.service_id = "WEEKDAY" + trip1.trip_id = "054C_WEEK" + self.ExpectInvalidValueInClosure(column_name="service_id", + value="WEEKDAY", + c=lambda: schedule.AddTripObject(trip1)) + + +class TripHasStopTimeValidationTestCase(ValidationTestCase): + def runTest(self): + schedule = transitfeed.Schedule(self.problems) + schedule.AddAgency("Test Agency", "http://example.com", + "America/Los_Angeles") + schedule.AddRouteObject( + transitfeed.Route("54C", "Polish Hill", 3, "054C")) + + service_period = transitfeed.ServicePeriod("WEEK") + service_period.SetStartDate("20070101") + service_period.SetEndDate("20071231") + service_period.SetWeekdayService(True) + schedule.AddServicePeriodObject(service_period) + + trip = transitfeed.Trip() + trip.route_id = '054C' + trip.service_id = 'WEEK' + trip.trip_id = '054C-00' + trip.trip_headsign = 'via Polish Hill' + trip.direction_id = '0' + trip.block_id = None + trip.shape_id = None + schedule.AddTripObject(trip) + + # We should get an OtherProblem here because the trip has no stops. + self.ExpectOtherProblem(schedule) + + # It should trigger a TYPE_ERROR if there are frequencies for the trip + # but no stops + trip.AddHeadwayPeriod("01:00:00","12:00:00", 600) + schedule.Validate(self.problems) + self.problems.PopException('OtherProblem') # pop first warning + e = self.problems.PopException('OtherProblem') # pop frequency error + self.assertTrue(e.FormatProblem().find('Frequencies defined, but') != -1) + self.assertTrue(e.FormatProblem().find('given in trip 054C-00') != -1) + self.assertEquals(transitfeed.TYPE_ERROR, e.type) + self.problems.AssertNoMoreExceptions() + trip.ClearHeadwayPeriods() + + # Add a stop, but with only one stop passengers have nowhere to exit! + stop = transitfeed.Stop(36.425288, -117.133162, "Demo Stop 1", "STOP1") + schedule.AddStopObject(stop) + trip.AddStopTime(stop, arrival_time="5:11:00", departure_time="5:12:00") + self.ExpectOtherProblem(schedule) + + # Add another stop, and then validation should be happy. + stop = transitfeed.Stop(36.424288, -117.133142, "Demo Stop 2", "STOP2") + schedule.AddStopObject(stop) + trip.AddStopTime(stop, arrival_time="5:15:00", departure_time="5:16:00") + schedule.Validate(self.problems) + + trip.AddStopTime(stop, stop_time="05:20:00") + trip.AddStopTime(stop, stop_time="05:22:00") + + # Last stop must always have a time + trip.AddStopTime(stop, arrival_secs=None, departure_secs=None) + self.ExpectInvalidValueInClosure( + 'arrival_time', c=lambda: trip.GetEndTime(problems=self.problems)) + + +class ShapeDistTraveledOfStopTimeValidationTestCase(ValidationTestCase): + def runTest(self): + schedule = transitfeed.Schedule(self.problems) + schedule.AddAgency("Test Agency", "http://example.com", + "America/Los_Angeles") + schedule.AddRouteObject( + transitfeed.Route("54C", "Polish Hill", 3, "054C")) + + service_period = transitfeed.ServicePeriod("WEEK") + service_period.SetStartDate("20070101") + service_period.SetEndDate("20071231") + service_period.SetWeekdayService(True) + schedule.AddServicePeriodObject(service_period) + + shape = transitfeed.Shape("shape_1") + shape.AddPoint(36.425288, -117.133162, 0) + shape.AddPoint(36.424288, -117.133142, 1) + schedule.AddShapeObject(shape) + + trip = transitfeed.Trip() + trip.route_id = '054C' + trip.service_id = 'WEEK' + trip.trip_id = '054C-00' + trip.trip_headsign = 'via Polish Hill' + trip.direction_id = '0' + trip.block_id = None + trip.shape_id = 'shape_1' + schedule.AddTripObject(trip) + + stop = transitfeed.Stop(36.425288, -117.133162, "Demo Stop 1", "STOP1") + schedule.AddStopObject(stop) + trip.AddStopTime(stop, arrival_time="5:11:00", departure_time="5:12:00", + stop_sequence=0, shape_dist_traveled=0) + stop = transitfeed.Stop(36.424288, -117.133142, "Demo Stop 2", "STOP2") + schedule.AddStopObject(stop) + trip.AddStopTime(stop, arrival_time="5:15:00", departure_time="5:16:00", + stop_sequence=1, shape_dist_traveled=1) + + stop = transitfeed.Stop(36.423288, -117.133122, "Demo Stop 3", "STOP3") + schedule.AddStopObject(stop) + trip.AddStopTime(stop, arrival_time="5:18:00", departure_time="5:19:00", + stop_sequence=2, shape_dist_traveled=2) + self.problems.AssertNoMoreExceptions() + schedule.Validate(self.problems) + e = self.problems.PopException('OtherProblem') + self.assertTrue(e.FormatProblem().find('shape_dist_traveled=2') != -1) + self.problems.AssertNoMoreExceptions() + + # Error if the distance decreases. + shape.AddPoint(36.421288, -117.133132, 2) + stop = transitfeed.Stop(36.421288, -117.133122, "Demo Stop 4", "STOP4") + schedule.AddStopObject(stop) + stoptime = transitfeed.StopTime(self.problems, stop, + arrival_time="5:29:00", + departure_time="5:29:00",stop_sequence=3, + shape_dist_traveled=1.7) + trip.AddStopTimeObject(stoptime, schedule=schedule) + self.problems.AssertNoMoreExceptions() + schedule.Validate(self.problems) + e = self.problems.PopException('InvalidValue') + self.assertMatchesRegex('stop STOP4 has', e.FormatProblem()) + self.assertMatchesRegex('shape_dist_traveled=1.7', e.FormatProblem()) + self.assertMatchesRegex('distance was 2.0.', e.FormatProblem()) + self.assertEqual(e.type, transitfeed.TYPE_ERROR) + self.problems.AssertNoMoreExceptions() + + # Warning if distance remains the same between two stop_times + stoptime.shape_dist_traveled = 2.0 + trip.ReplaceStopTimeObject(stoptime, schedule=schedule) + schedule.Validate(self.problems) + e = self.problems.PopException('InvalidValue') + self.assertMatchesRegex('stop STOP4 has', e.FormatProblem()) + self.assertMatchesRegex('shape_dist_traveled=2.0', e.FormatProblem()) + self.assertMatchesRegex('distance was 2.0.', e.FormatProblem()) + self.assertEqual(e.type, transitfeed.TYPE_WARNING) + self.problems.AssertNoMoreExceptions() + + +class StopMatchWithShapeTestCase(ValidationTestCase): + def runTest(self): + schedule = transitfeed.Schedule(self.problems) + schedule.AddAgency("Test Agency", "http://example.com", + "America/Los_Angeles") + schedule.AddRouteObject( + transitfeed.Route("54C", "Polish Hill", 3, "054C")) + + service_period = transitfeed.ServicePeriod("WEEK") + service_period.SetDateHasService('20070101') + schedule.AddServicePeriodObject(service_period) + + shape = transitfeed.Shape("shape_1") + shape.AddPoint(36.425288, -117.133162, 0) + shape.AddPoint(36.424288, -117.143142, 1) + schedule.AddShapeObject(shape) + + trip = transitfeed.Trip() + trip.route_id = '054C' + trip.service_id = 'WEEK' + trip.trip_id = '054C-00' + trip.shape_id = 'shape_1' + schedule.AddTripObject(trip) + + # Stop 1 is only 600 meters away from shape, which is allowed. + stop = transitfeed.Stop(36.425288, -117.139162, "Demo Stop 1", "STOP1") + schedule.AddStopObject(stop) + trip.AddStopTime(stop, arrival_time="5:11:00", departure_time="5:12:00", + stop_sequence=0, shape_dist_traveled=0) + # Stop 2 is more than 1000 meters away from shape, which is not allowed. + stop = transitfeed.Stop(36.424288, -117.158142, "Demo Stop 2", "STOP2") + schedule.AddStopObject(stop) + trip.AddStopTime(stop, arrival_time="5:15:00", departure_time="5:16:00", + stop_sequence=1, shape_dist_traveled=1) + + schedule.Validate(self.problems) + e = self.problems.PopException('StopTooFarFromShapeWithDistTraveled') + self.assertTrue(e.FormatProblem().find('Demo Stop 2') != -1) + self.assertTrue(e.FormatProblem().find('1344 meters away') != -1) + self.problems.AssertNoMoreExceptions() + + +class TripAddStopTimeObjectTestCase(ValidationTestCase): + def runTest(self): + schedule = transitfeed.Schedule(problem_reporter=self.problems) + schedule.AddAgency("\xc8\x8b Fly Agency", "http://iflyagency.com", + "America/Los_Angeles") + service_period = schedule.GetDefaultServicePeriod().SetDateHasService('20070101') + stop1 = schedule.AddStop(lng=140, lat=48.2, name="Stop 1") + stop2 = schedule.AddStop(lng=140.001, lat=48.201, name="Stop 2") + route = schedule.AddRoute("B", "Beta", "Bus") + trip = route.AddTrip(schedule, "bus trip") + trip.AddStopTimeObject(transitfeed.StopTime(self.problems, stop1, + arrival_secs=10, + departure_secs=10), + schedule=schedule, problems=self.problems) + trip.AddStopTimeObject(transitfeed.StopTime(self.problems, stop2, + arrival_secs=20, + departure_secs=20), + schedule=schedule, problems=self.problems) + # TODO: Factor out checks or use mock problems object + self.ExpectOtherProblemInClosure(lambda: + trip.AddStopTimeObject(transitfeed.StopTime(self.problems, stop1, + arrival_secs=15, + departure_secs=15), + schedule=schedule, problems=self.problems)) + trip.AddStopTimeObject(transitfeed.StopTime(self.problems, stop1), + schedule=schedule, problems=self.problems) + self.ExpectOtherProblemInClosure(lambda: + trip.AddStopTimeObject(transitfeed.StopTime(self.problems, stop1, + arrival_secs=15, + departure_secs=15), + schedule=schedule, problems=self.problems)) + trip.AddStopTimeObject(transitfeed.StopTime(self.problems, stop1, + arrival_secs=30, + departure_secs=30), + schedule=schedule, problems=self.problems) + self.problems.AssertNoMoreExceptions() + +class DuplicateTripTestCase(ValidationTestCase): + def runTest(self): + + schedule = transitfeed.Schedule(self.problems) + schedule._check_duplicate_trips = True; + + agency = transitfeed.Agency('Demo agency', 'http://google.com', + 'America/Los_Angeles', 'agency1') + schedule.AddAgencyObject(agency) + + service = schedule.GetDefaultServicePeriod() + service.SetDateHasService('20070101') + + route1 = transitfeed.Route('Route1', 'route 1', 3, 'route_1', 'agency1') + schedule.AddRouteObject(route1) + route2 = transitfeed.Route('Route2', 'route 2', 3, 'route_2', 'agency1') + schedule.AddRouteObject(route2) + + trip1 = transitfeed.Trip() + trip1.route_id = 'route_1' + trip1.trip_id = 't1' + trip1.trip_headsign = 'via Polish Hill' + trip1.direction_id = '0' + trip1.service_id = service.service_id + schedule.AddTripObject(trip1) + + trip2 = transitfeed.Trip() + trip2.route_id = 'route_2' + trip2.trip_id = 't2' + trip2.trip_headsign = 'New' + trip2.direction_id = '0' + trip2.service_id = service.service_id + schedule.AddTripObject(trip2) + + trip3 = transitfeed.Trip() + trip3.route_id = 'route_1' + trip3.trip_id = 't3' + trip3.trip_headsign = 'New Demo' + trip3.direction_id = '0' + trip3.service_id = service.service_id + schedule.AddTripObject(trip3) + + stop1 = transitfeed.Stop(36.425288, -117.139162, "Demo Stop 1", "STOP1") + schedule.AddStopObject(stop1) + trip1.AddStopTime(stop1, arrival_time="5:11:00", departure_time="5:12:00", + stop_sequence=0, shape_dist_traveled=0) + trip2.AddStopTime(stop1, arrival_time="5:11:00", departure_time="5:12:00", + stop_sequence=0, shape_dist_traveled=0) + trip3.AddStopTime(stop1, arrival_time="6:11:00", departure_time="6:12:00", + stop_sequence=0, shape_dist_traveled=0) + + stop2 = transitfeed.Stop(36.424288, -117.158142, "Demo Stop 2", "STOP2") + schedule.AddStopObject(stop2) + trip1.AddStopTime(stop2, arrival_time="5:15:00", departure_time="5:16:00", + stop_sequence=1, shape_dist_traveled=1) + trip2.AddStopTime(stop2, arrival_time="5:25:00", departure_time="5:26:00", + stop_sequence=1, shape_dist_traveled=1) + trip3.AddStopTime(stop2, arrival_time="6:15:00", departure_time="6:16:00", + stop_sequence=1, shape_dist_traveled=1) + + schedule.Validate(self.problems) + e = self.problems.PopException('DuplicateTrip') + self.assertTrue(e.FormatProblem().find('t1 of route') != -1) + self.assertTrue(e.FormatProblem().find('t2 of route') != -1) + self.problems.AssertNoMoreExceptions() + + +class StopBelongsToBothSubwayAndBusTestCase(ValidationTestCase): + def runTest(self): + schedule = transitfeed.Schedule(self.problems) + + schedule.AddAgency("Demo Agency", "http://example.com", + "America/Los_Angeles") + route1 = schedule.AddRoute(short_name="route1", long_name="route_1", + route_type=3) + route2 = schedule.AddRoute(short_name="route2", long_name="route_2", + route_type=1) + + service = schedule.GetDefaultServicePeriod() + service.SetDateHasService("20070101") + + trip1 = route1.AddTrip(schedule, "trip1", service, "t1") + trip2 = route2.AddTrip(schedule, "trip2", service, "t2") + + stop1 = schedule.AddStop(36.425288, -117.133162, "stop1") + stop2 = schedule.AddStop(36.424288, -117.133142, "stop2") + stop3 = schedule.AddStop(36.423288, -117.134142, "stop3") + + trip1.AddStopTime(stop1, arrival_time="5:11:00", departure_time="5:12:00") + trip1.AddStopTime(stop2, arrival_time="5:21:00", departure_time="5:22:00") + + trip2.AddStopTime(stop1, arrival_time="6:11:00", departure_time="6:12:00") + trip2.AddStopTime(stop3, arrival_time="6:21:00", departure_time="6:22:00") + + schedule.Validate(self.problems) + e = self.problems.PopException("StopWithMultipleRouteTypes") + self.assertTrue(e.FormatProblem().find("Stop stop1") != -1) + self.assertTrue(e.FormatProblem().find("subway (ID=1)") != -1) + self.assertTrue(e.FormatProblem().find("bus line (ID=0)") != -1) + self.problems.AssertNoMoreExceptions() + + +class TripReplaceStopTimeObjectTestCase(unittest.TestCase): + def runTest(self): + schedule = transitfeed.Schedule() + schedule.AddAgency("\xc8\x8b Fly Agency", "http://iflyagency.com", + "America/Los_Angeles") + service_period = \ + schedule.GetDefaultServicePeriod().SetDateHasService('20070101') + stop1 = schedule.AddStop(lng=140, lat=48.2, name="Stop 1") + route = schedule.AddRoute("B", "Beta", "Bus") + trip = route.AddTrip(schedule, "bus trip") + stoptime = transitfeed.StopTime(transitfeed.default_problem_reporter, stop1, + arrival_secs=10, + departure_secs=10) + trip.AddStopTimeObject(stoptime, schedule=schedule) + stoptimes = trip.GetStopTimes() + stoptime.departure_secs = 20 + trip.ReplaceStopTimeObject(stoptime, schedule=schedule) + stoptimes = trip.GetStopTimes() + self.assertEqual(len(stoptimes), 1) + self.assertEqual(stoptimes[0].departure_secs, 20) + + unknown_stop = schedule.AddStop(lng=140, lat=48.2, name="unknown") + unknown_stoptime = transitfeed.StopTime( + transitfeed.default_problem_reporter, unknown_stop, + arrival_secs=10, + departure_secs=10) + unknown_stoptime.stop_sequence = 5 + # Attempting to replace a non-existent StopTime raises an error + self.assertRaises(transitfeed.Error, trip.ReplaceStopTimeObject, + unknown_stoptime, schedule=schedule) + +class TripStopTimeAccessorsTestCase(unittest.TestCase): + def runTest(self): + schedule = transitfeed.Schedule( + problem_reporter=ExceptionProblemReporterNoExpiration()) + schedule.NewDefaultAgency(agency_name="Test Agency", + agency_url="http://example.com", + agency_timezone="America/Los_Angeles") + route = schedule.AddRoute(short_name="54C", long_name="Polish Hill", route_type=3) + + service_period = schedule.GetDefaultServicePeriod() + service_period.SetDateHasService("20070101") + + trip = route.AddTrip(schedule, 'via Polish Hill') + + stop1 = schedule.AddStop(36.425288, -117.133162, "Demo Stop 1") + stop2 = schedule.AddStop(36.424288, -117.133142, "Demo Stop 2") + + trip.AddStopTime(stop1, arrival_time="5:11:00", departure_time="5:12:00") + trip.AddStopTime(stop2, arrival_time="5:15:00", departure_time="5:16:00") + + # Add some more stop times and test GetEndTime does the correct thing + self.assertEqual(transitfeed.FormatSecondsSinceMidnight(trip.GetStartTime()), + "05:11:00") + self.assertEqual(transitfeed.FormatSecondsSinceMidnight(trip.GetEndTime()), + "05:16:00") + + trip.AddStopTime(stop1, stop_time="05:20:00") + self.assertEqual(transitfeed.FormatSecondsSinceMidnight(trip.GetEndTime()), + "05:20:00") + + trip.AddStopTime(stop2, stop_time="05:22:00") + self.assertEqual(transitfeed.FormatSecondsSinceMidnight(trip.GetEndTime()), + "05:22:00") + self.assertEqual(len(trip.GetStopTimesTuples()), 4) + self.assertEqual(trip.GetStopTimesTuples()[0], (trip.trip_id, "05:11:00", + "05:12:00", stop1.stop_id, + 1, '', '', '', '')) + self.assertEqual(trip.GetStopTimesTuples()[3], (trip.trip_id, "05:22:00", + "05:22:00", stop2.stop_id, + 4, '', '', '', '')) + +class TripClearStopTimesTestCase(unittest.TestCase): + def runTest(self): + schedule = transitfeed.Schedule( + problem_reporter=ExceptionProblemReporterNoExpiration()) + schedule.NewDefaultAgency(agency_name="Test Agency", + agency_timezone="America/Los_Angeles") + route = schedule.AddRoute(short_name="54C", long_name="Hill", route_type=3) + schedule.GetDefaultServicePeriod().SetDateHasService("20070101") + stop1 = schedule.AddStop(36, -117.1, "Demo Stop 1") + stop2 = schedule.AddStop(36, -117.2, "Demo Stop 2") + stop3 = schedule.AddStop(36, -117.3, "Demo Stop 3") + + trip = route.AddTrip(schedule, "via Polish Hill") + trip.ClearStopTimes() + self.assertFalse(trip.GetStopTimes()) + trip.AddStopTime(stop1, stop_time="5:11:00") + self.assertTrue(trip.GetStopTimes()) + trip.ClearStopTimes() + self.assertFalse(trip.GetStopTimes()) + trip.AddStopTime(stop3, stop_time="4:00:00") # Can insert earlier time + trip.AddStopTime(stop2, stop_time="4:15:00") + trip.AddStopTime(stop1, stop_time="4:21:00") + old_stop_times = trip.GetStopTimes() + self.assertTrue(old_stop_times) + trip.ClearStopTimes() + self.assertFalse(trip.GetStopTimes()) + for st in old_stop_times: + trip.AddStopTimeObject(st) + self.assertEqual(trip.GetStartTime(), 4 * 3600) + self.assertEqual(trip.GetEndTime(), 4 * 3600 + 21 * 60) + + +class BasicParsingTestCase(unittest.TestCase): + """Checks that we're getting the number of child objects that we expect.""" + def assertLoadedCorrectly(self, schedule): + """Check that the good_feed looks correct""" + self.assertEqual(1, len(schedule._agencies)) + self.assertEqual(5, len(schedule.routes)) + self.assertEqual(2, len(schedule.service_periods)) + self.assertEqual(10, len(schedule.stops)) + self.assertEqual(11, len(schedule.trips)) + self.assertEqual(0, len(schedule.fare_zones)) + + def assertLoadedStopTimesCorrectly(self, schedule): + self.assertEqual(5, len(schedule.GetTrip('CITY1').GetStopTimes())) + self.assertEqual('to airport', schedule.GetTrip('STBA').GetStopTimes()[0].stop_headsign) + self.assertEqual(2, schedule.GetTrip('CITY1').GetStopTimes()[1].pickup_type) + self.assertEqual(3, schedule.GetTrip('CITY1').GetStopTimes()[1].drop_off_type) + + def test_MemoryDb(self): + loader = transitfeed.Loader( + DataPath('good_feed.zip'), + problems=TestFailureProblemReporter(self), + extra_validation=True, + memory_db=True) + schedule = loader.Load() + self.assertLoadedCorrectly(schedule) + self.assertLoadedStopTimesCorrectly(schedule) + + def test_TemporaryFile(self): + loader = transitfeed.Loader( + DataPath('good_feed.zip'), + problems=TestFailureProblemReporter(self), + extra_validation=True, + memory_db=False) + schedule = loader.Load() + self.assertLoadedCorrectly(schedule) + self.assertLoadedStopTimesCorrectly(schedule) + + def test_NoLoadStopTimes(self): + problems = TestFailureProblemReporter( + self, ignore_types=("ExpirationDate", "UnusedStop", "OtherProblem")) + loader = transitfeed.Loader( + DataPath('good_feed.zip'), + problems=problems, + extra_validation=True, + load_stop_times=False) + schedule = loader.Load() + self.assertLoadedCorrectly(schedule) + self.assertEqual(0, len(schedule.GetTrip('CITY1').GetStopTimes())) + + +class RepeatedRouteNameTestCase(LoadTestCase): + def runTest(self): + self.ExpectInvalidValue('repeated_route_name', 'route_long_name') + + +class InvalidRouteAgencyTestCase(LoadTestCase): + def runTest(self): + self.Load('invalid_route_agency') + self.problems.PopInvalidValue("agency_id", "routes.txt") + self.problems.PopInvalidValue("route_id", "trips.txt") + self.problems.AssertNoMoreExceptions() + + +class UndefinedStopAgencyTestCase(LoadTestCase): + def runTest(self): + self.ExpectInvalidValue('undefined_stop', 'stop_id') + + +class SameShortLongNameTestCase(LoadTestCase): + def runTest(self): + self.ExpectInvalidValue('same_short_long_name', 'route_long_name') + + +class UnusedStopAgencyTestCase(LoadTestCase): + def runTest(self): + self.Load('unused_stop'), + e = self.problems.PopException("UnusedStop") + self.assertEqual("Bogus Stop (Demo)", e.stop_name) + self.assertEqual("BOGUS", e.stop_id) + self.problems.AssertNoMoreExceptions() + + + +class OnlyCalendarDatesTestCase(LoadTestCase): + def runTest(self): + self.Load('only_calendar_dates'), + self.problems.AssertNoMoreExceptions() + + +class DuplicateServiceIdDateWarningTestCase(MemoryZipTestCase): + def runTest(self): + # Two lines with the same value of service_id and date. + # Test for the warning. + self.zip.writestr( + 'calendar_dates.txt', + 'service_id,date,exception_type\n' + 'FULLW,20100604,1\n' + 'FULLW,20100604,2\n') + schedule = self.loader.Load() + e = self.problems.PopException('DuplicateID') + self.assertEquals('(service_id, date)', e.column_name) + self.assertEquals('(FULLW, 20100604)', e.value) + + +class AddStopTimeParametersTestCase(unittest.TestCase): + def runTest(self): + problem_reporter = TestFailureProblemReporter(self) + schedule = transitfeed.Schedule(problem_reporter=problem_reporter) + route = schedule.AddRoute(short_name="10", long_name="", route_type="Bus") + stop = schedule.AddStop(40, -128, "My stop") + # Stop must be added to schedule so that the call + # AddStopTime -> AddStopTimeObject -> GetStopTimes -> GetStop can work + trip = transitfeed.Trip() + trip.route_id = route.route_id + trip.service_id = schedule.GetDefaultServicePeriod().service_id + trip.trip_id = "SAMPLE_TRIP" + schedule.AddTripObject(trip) + + # First stop must have time + trip.AddStopTime(stop, arrival_secs=300, departure_secs=360) + trip.AddStopTime(stop) + trip.AddStopTime(stop, arrival_time="00:07:00", departure_time="00:07:30") + trip.Validate(problem_reporter) + + +class ExpirationDateTestCase(unittest.TestCase): + def runTest(self): + problems = RecordingProblemReporter(self) + schedule = transitfeed.Schedule(problem_reporter=problems) + + now = time.mktime(time.localtime()) + seconds_per_day = 60 * 60 * 24 + two_weeks_ago = time.localtime(now - 14 * seconds_per_day) + two_weeks_from_now = time.localtime(now + 14 * seconds_per_day) + two_months_from_now = time.localtime(now + 60 * seconds_per_day) + date_format = "%Y%m%d" + + service_period = schedule.GetDefaultServicePeriod() + service_period.SetWeekdayService(True) + service_period.SetStartDate("20070101") + + service_period.SetEndDate(time.strftime(date_format, two_months_from_now)) + schedule.Validate() # should have no problems + problems.AssertNoMoreExceptions() + + service_period.SetEndDate(time.strftime(date_format, two_weeks_from_now)) + schedule.Validate() + e = problems.PopException('ExpirationDate') + self.assertTrue(e.FormatProblem().index('will soon expire')) + problems.AssertNoMoreExceptions() + + service_period.SetEndDate(time.strftime(date_format, two_weeks_ago)) + schedule.Validate() + e = problems.PopException('ExpirationDate') + self.assertTrue(e.FormatProblem().index('expired')) + problems.AssertNoMoreExceptions() + + +class FutureServiceStartDateTestCase(unittest.TestCase): + def runTest(self): + problems = RecordingProblemReporter(self) + schedule = transitfeed.Schedule(problem_reporter=problems) + + today = datetime.date.today() + yesterday = today - datetime.timedelta(days=1) + tomorrow = today + datetime.timedelta(days=1) + two_months_from_today = today + datetime.timedelta(days=60) + + service_period = schedule.GetDefaultServicePeriod() + service_period.SetWeekdayService(True) + service_period.SetWeekendService(True) + service_period.SetEndDate(two_months_from_today.strftime("%Y%m%d")) + + service_period.SetStartDate(yesterday.strftime("%Y%m%d")) + schedule.Validate() + problems.AssertNoMoreExceptions() + + service_period.SetStartDate(today.strftime("%Y%m%d")) + schedule.Validate() + problems.AssertNoMoreExceptions() + + service_period.SetStartDate(tomorrow.strftime("%Y%m%d")) + schedule.Validate() + problems.PopException('FutureService') + problems.AssertNoMoreExceptions() + + +class CalendarTxtIntegrationTestCase(MemoryZipTestCase): + def testBadEndDateFormat(self): + # A badly formatted end_date used to generate an InvalidValue report from + # Schedule.Validate and ServicePeriod.Validate. Test for the bug. + self.zip.writestr( + "calendar.txt", + "service_id,monday,tuesday,wednesday,thursday,friday,saturday,sunday," + "start_date,end_date\n" + "FULLW,1,1,1,1,1,1,1,20070101,20101232\n" + "WE,0,0,0,0,0,1,1,20070101,20101231\n") + schedule = self.loader.Load() + e = self.problems.PopInvalidValue('end_date') + self.problems.AssertNoMoreExceptions() + + def testBadStartDateFormat(self): + self.zip.writestr( + "calendar.txt", + "service_id,monday,tuesday,wednesday,thursday,friday,saturday,sunday," + "start_date,end_date\n" + "FULLW,1,1,1,1,1,1,1,200701xx,20101231\n" + "WE,0,0,0,0,0,1,1,20070101,20101231\n") + schedule = self.loader.Load() + e = self.problems.PopInvalidValue('start_date') + self.problems.AssertNoMoreExceptions() + + def testNoStartDateAndEndDate(self): + """Regression test for calendar.txt with empty start_date and end_date. + + See http://code.google.com/p/googletransitdatafeed/issues/detail?id=41 + """ + self.zip.writestr( + "calendar.txt", + "service_id,monday,tuesday,wednesday,thursday,friday,saturday,sunday," + "start_date,end_date\n" + "FULLW,1,1,1,1,1,1,1, ,\t\n" + "WE,0,0,0,0,0,1,1,20070101,20101231\n") + schedule = self.loader.Load() + e = self.problems.PopException("MissingValue") + self.assertEquals(2, e.row_num) + self.assertEquals("start_date", e.column_name) + e = self.problems.PopException("MissingValue") + self.assertEquals(2, e.row_num) + self.assertEquals("end_date", e.column_name) + self.problems.AssertNoMoreExceptions() + + def testNoStartDateAndBadEndDate(self): + self.zip.writestr( + "calendar.txt", + "service_id,monday,tuesday,wednesday,thursday,friday,saturday,sunday," + "start_date,end_date\n" + "FULLW,1,1,1,1,1,1,1,,abc\n" + "WE,0,0,0,0,0,1,1,20070101,20101231\n") + schedule = self.loader.Load() + e = self.problems.PopException("MissingValue") + self.assertEquals(2, e.row_num) + self.assertEquals("start_date", e.column_name) + e = self.problems.PopInvalidValue("end_date") + self.assertEquals(2, e.row_num) + self.problems.AssertNoMoreExceptions() + + def testMissingEndDateColumn(self): + self.zip.writestr( + "calendar.txt", + "service_id,monday,tuesday,wednesday,thursday,friday,saturday,sunday," + "start_date\n" + "FULLW,1,1,1,1,1,1,1,20070101\n" + "WE,0,0,0,0,0,1,1,20070101\n") + schedule = self.loader.Load() + e = self.problems.PopException("MissingColumn") + self.assertEquals("end_date", e.column_name) + self.problems.AssertNoMoreExceptions() + + +class DuplicateTripIDValidationTestCase(unittest.TestCase): + def runTest(self): + schedule = transitfeed.Schedule( + problem_reporter=ExceptionProblemReporterNoExpiration()) + schedule.AddAgency("Sample Agency", "http://example.com", + "America/Los_Angeles") + route = transitfeed.Route() + route.route_id = "SAMPLE_ID" + route.route_type = 3 + route.route_long_name = "Sample Route" + schedule.AddRouteObject(route) + + service_period = transitfeed.ServicePeriod("WEEK") + service_period.SetStartDate("20070101") + service_period.SetEndDate("20071231") + service_period.SetWeekdayService(True) + schedule.AddServicePeriodObject(service_period) + + trip1 = transitfeed.Trip() + trip1.route_id = "SAMPLE_ID" + trip1.service_id = "WEEK" + trip1.trip_id = "SAMPLE_TRIP" + schedule.AddTripObject(trip1) + + trip2 = transitfeed.Trip() + trip2.route_id = "SAMPLE_ID" + trip2.service_id = "WEEK" + trip2.trip_id = "SAMPLE_TRIP" + try: + schedule.AddTripObject(trip2) + self.fail("Expected Duplicate ID validation failure") + except transitfeed.DuplicateID, e: + self.assertEqual("trip_id", e.column_name) + self.assertEqual("SAMPLE_TRIP", e.value) + + +class DuplicateStopValidationTestCase(ValidationTestCase): + def runTest(self): + schedule = transitfeed.Schedule(problem_reporter=self.problems) + schedule.AddAgency("Sample Agency", "http://example.com", + "America/Los_Angeles") + route = transitfeed.Route() + route.route_id = "SAMPLE_ID" + route.route_type = 3 + route.route_long_name = "Sample Route" + schedule.AddRouteObject(route) + + service_period = transitfeed.ServicePeriod("WEEK") + service_period.SetStartDate("20070101") + service_period.SetEndDate("20071231") + service_period.SetWeekdayService(True) + schedule.AddServicePeriodObject(service_period) + + trip = transitfeed.Trip() + trip.route_id = "SAMPLE_ID" + trip.service_id = "WEEK" + trip.trip_id = "SAMPLE_TRIP" + schedule.AddTripObject(trip) + + stop1 = transitfeed.Stop() + stop1.stop_id = "STOP1" + stop1.stop_name = "Stop 1" + stop1.stop_lat = 78.243587 + stop1.stop_lon = 32.258937 + schedule.AddStopObject(stop1) + trip.AddStopTime(stop1, arrival_time="12:00:00", departure_time="12:00:00") + + stop2 = transitfeed.Stop() + stop2.stop_id = "STOP2" + stop2.stop_name = "Stop 2" + stop2.stop_lat = 78.253587 + stop2.stop_lon = 32.258937 + schedule.AddStopObject(stop2) + trip.AddStopTime(stop2, arrival_time="12:05:00", departure_time="12:05:00") + schedule.Validate() + + stop3 = transitfeed.Stop() + stop3.stop_id = "STOP3" + stop3.stop_name = "Stop 3" + stop3.stop_lat = 78.243587 + stop3.stop_lon = 32.268937 + schedule.AddStopObject(stop3) + trip.AddStopTime(stop3, arrival_time="12:10:00", departure_time="12:10:00") + schedule.Validate() + self.problems.AssertNoMoreExceptions() + + stop4 = transitfeed.Stop() + stop4.stop_id = "STOP4" + stop4.stop_name = "Stop 4" + stop4.stop_lat = 78.243588 + stop4.stop_lon = 32.268936 + schedule.AddStopObject(stop4) + trip.AddStopTime(stop4, arrival_time="12:15:00", departure_time="12:15:00") + schedule.Validate() + e = self.problems.PopException('StopsTooClose') + self.problems.AssertNoMoreExceptions() + + +class TempFileTestCaseBase(unittest.TestCase): + """ + Subclass of TestCase which sets self.tempfilepath to a valid temporary zip + file name and removes the file if it exists when the test is done. + """ + def setUp(self): + (fd, self.tempfilepath) = tempfile.mkstemp(".zip") + # Open file handle causes an exception during remove in Windows + os.close(fd) + + def tearDown(self): + if os.path.exists(self.tempfilepath): + os.remove(self.tempfilepath) + + +class MinimalWriteTestCase(TempFileTestCaseBase): + """ + This test case simply constructs an incomplete feed with very few + fields set and ensures that there are no exceptions when writing it out. + + This is very similar to TransitFeedSampleCodeTestCase below, but that one + will no doubt change as the sample code is altered. + """ + def runTest(self): + schedule = transitfeed.Schedule() + schedule.AddAgency("Sample Agency", "http://example.com", + "America/Los_Angeles") + route = transitfeed.Route() + route.route_id = "SAMPLE_ID" + route.route_type = 3 + route.route_short_name = "66" + route.route_long_name = "Sample Route acute letter e\202" + schedule.AddRouteObject(route) + + service_period = transitfeed.ServicePeriod("WEEK") + service_period.SetStartDate("20070101") + service_period.SetEndDate("20071231") + service_period.SetWeekdayService(True) + schedule.AddServicePeriodObject(service_period) + + trip = transitfeed.Trip() + trip.route_id = "SAMPLE_ID" + trip.service_period = service_period + trip.trip_id = "SAMPLE_TRIP" + schedule.AddTripObject(trip) + + stop1 = transitfeed.Stop() + stop1.stop_id = "STOP1" + stop1.stop_name = u'Stop 1 acute letter e\202' + stop1.stop_lat = 78.243587 + stop1.stop_lon = 32.258937 + schedule.AddStopObject(stop1) + trip.AddStopTime(stop1, arrival_time="12:00:00", departure_time="12:00:00") + + stop2 = transitfeed.Stop() + stop2.stop_id = "STOP2" + stop2.stop_name = "Stop 2" + stop2.stop_lat = 78.253587 + stop2.stop_lon = 32.258937 + schedule.AddStopObject(stop2) + trip.AddStopTime(stop2, arrival_time="12:05:00", departure_time="12:05:00") + + schedule.Validate() + schedule.WriteGoogleTransitFeed(self.tempfilepath) + + +class TransitFeedSampleCodeTestCase(unittest.TestCase): + """ + This test should simply contain the sample code printed on the page: + http://code.google.com/p/googletransitdatafeed/wiki/TransitFeed + to ensure that it doesn't cause any exceptions. + """ + def runTest(self): + import transitfeed + + schedule = transitfeed.Schedule() + schedule.AddAgency("Sample Agency", "http://example.com", + "America/Los_Angeles") + route = transitfeed.Route() + route.route_id = "SAMPLE_ID" + route.route_type = 3 + route.route_short_name = "66" + route.route_long_name = "Sample Route" + schedule.AddRouteObject(route) + + service_period = transitfeed.ServicePeriod("WEEK") + service_period.SetStartDate("20070101") + service_period.SetEndDate("20071231") + service_period.SetWeekdayService(True) + schedule.AddServicePeriodObject(service_period) + + trip = transitfeed.Trip() + trip.route_id = "SAMPLE_ID" + trip.service_period = service_period + trip.trip_id = "SAMPLE_TRIP" + trip.direction_id = "0" + trip.block_id = None + schedule.AddTripObject(trip) + + stop1 = transitfeed.Stop() + stop1.stop_id = "STOP1" + stop1.stop_name = "Stop 1" + stop1.stop_lat = 78.243587 + stop1.stop_lon = 32.258937 + schedule.AddStopObject(stop1) + trip.AddStopTime(stop1, arrival_time="12:00:00", departure_time="12:00:00") + + stop2 = transitfeed.Stop() + stop2.stop_id = "STOP2" + stop2.stop_name = "Stop 2" + stop2.stop_lat = 78.253587 + stop2.stop_lon = 32.258937 + schedule.AddStopObject(stop2) + trip.AddStopTime(stop2, arrival_time="12:05:00", departure_time="12:05:00") + + schedule.Validate() # not necessary, but helpful for finding problems + schedule.WriteGoogleTransitFeed("new_feed.zip") + + +class AgencyIDValidationTestCase(unittest.TestCase): + def runTest(self): + schedule = transitfeed.Schedule( + problem_reporter=ExceptionProblemReporterNoExpiration()) + route = transitfeed.Route() + route.route_id = "SAMPLE_ID" + route.route_type = 3 + route.route_long_name = "Sample Route" + # no agency defined yet, failure. + try: + schedule.AddRouteObject(route) + self.fail("Expected validation error") + except transitfeed.InvalidValue, e: + self.assertEqual('agency_id', e.column_name) + self.assertEqual(None, e.value) + + # one agency defined, assume that the route belongs to it + schedule.AddAgency("Test Agency", "http://example.com", + "America/Los_Angeles", "TEST_AGENCY") + schedule.AddRouteObject(route) + + schedule.AddAgency("Test Agency 2", "http://example.com", + "America/Los_Angeles", "TEST_AGENCY_2") + route = transitfeed.Route() + route.route_id = "SAMPLE_ID_2" + route.route_type = 3 + route.route_long_name = "Sample Route 2" + # multiple agencies defined, don't know what omitted agency_id should be + try: + schedule.AddRouteObject(route) + self.fail("Expected validation error") + except transitfeed.InvalidValue, e: + self.assertEqual('agency_id', e.column_name) + self.assertEqual(None, e.value) + + # agency with no agency_id defined, matches route with no agency id + schedule.AddAgency("Test Agency 3", "http://example.com", + "America/Los_Angeles") + schedule.AddRouteObject(route) + + +class AddHeadwayPeriodValidationTestCase(ValidationTestCase): + def ExpectInvalidValue(self, start_time, end_time, headway, + column_name, value): + try: + trip = transitfeed.Trip() + trip.AddHeadwayPeriod(start_time, end_time, headway) + self.fail("Expected InvalidValue error on %s" % column_name) + except transitfeed.InvalidValue, e: + self.assertEqual(column_name, e.column_name) + self.assertEqual(value, e.value) + self.assertEqual(0, len(trip.GetHeadwayPeriodTuples())) + + def ExpectMissingValue(self, start_time, end_time, headway, column_name): + try: + trip = transitfeed.Trip() + trip.AddHeadwayPeriod(start_time, end_time, headway) + self.fail("Expected MissingValue error on %s" % column_name) + except transitfeed.MissingValue, e: + self.assertEqual(column_name, e.column_name) + self.assertEqual(0, len(trip.GetHeadwayPeriodTuples())) + + def runTest(self): + # these should work fine + trip = transitfeed.Trip() + trip.trip_id = "SAMPLE_ID" + trip.AddHeadwayPeriod(0, 50, 1200) + trip.AddHeadwayPeriod("01:00:00", "02:00:00", "600") + trip.AddHeadwayPeriod(u"02:00:00", u"03:00:00", u"1800") + headways = trip.GetHeadwayPeriodTuples() + self.assertEqual(3, len(headways)) + self.assertEqual((0, 50, 1200), headways[0]) + self.assertEqual((3600, 7200, 600), headways[1]) + self.assertEqual((7200, 10800, 1800), headways[2]) + self.assertEqual([("SAMPLE_ID", "00:00:00", "00:00:50", "1200"), + ("SAMPLE_ID", "01:00:00", "02:00:00", "600"), + ("SAMPLE_ID", "02:00:00", "03:00:00", "1800")], + trip.GetHeadwayPeriodOutputTuples()) + + # now test invalid input + self.ExpectMissingValue(None, 50, 1200, "start_time") + self.ExpectMissingValue("", 50, 1200, "start_time") + self.ExpectInvalidValue("midnight", 50, 1200, "start_time", "midnight") + self.ExpectInvalidValue(-50, 50, 1200, "start_time", -50) + self.ExpectMissingValue(0, None, 1200, "end_time") + self.ExpectMissingValue(0, "", 1200, "end_time") + self.ExpectInvalidValue(0, "noon", 1200, "end_time", "noon") + self.ExpectInvalidValue(0, -50, 1200, "end_time", -50) + self.ExpectMissingValue(0, 600, 0, "headway_secs") + self.ExpectMissingValue(0, 600, None, "headway_secs") + self.ExpectMissingValue(0, 600, "", "headway_secs") + self.ExpectInvalidValue(0, 600, "test", "headway_secs", "test") + self.ExpectInvalidValue(0, 600, -60, "headway_secs", -60) + self.ExpectInvalidValue(0, 0, 1200, "end_time", 0) + self.ExpectInvalidValue("12:00:00", "06:00:00", 1200, "end_time", 21600) + + +class MinimalUtf8Builder(TempFileTestCaseBase): + def runTest(self): + problems = TestFailureProblemReporter(self) + schedule = transitfeed.Schedule(problem_reporter=problems) + schedule.AddAgency("\xc8\x8b Fly Agency", "http://iflyagency.com", + "America/Los_Angeles") + service_period = schedule.GetDefaultServicePeriod() + service_period.SetDateHasService('20070101') + # "u020b i with inverted accent breve" encoded in utf-8 + stop1 = schedule.AddStop(lng=140, lat=48.2, name="\xc8\x8b hub") + # "u020b i with inverted accent breve" as unicode string + stop2 = schedule.AddStop(lng=140.001, lat=48.201, name=u"remote \u020b station") + route = schedule.AddRoute(u"\u03b2", "Beta", "Bus") + trip = route.AddTrip(schedule, u"to remote \u020b station") + repr(stop1) + repr(stop2) + repr(route) + repr(trip) + trip.AddStopTime(stop1, schedule=schedule, stop_time='10:00:00') + trip.AddStopTime(stop2, stop_time='10:10:00') + + schedule.Validate(problems) + schedule.WriteGoogleTransitFeed(self.tempfilepath) + read_schedule = \ + transitfeed.Loader(self.tempfilepath, problems=problems, + extra_validation=True).Load() + + +class ScheduleBuilderTestCase(unittest.TestCase): + def runTest(self): + schedule = transitfeed.Schedule() + + schedule.AddAgency("Test Agency", "http://example.com", + "America/Los_Angeles") + + service_period = schedule.GetDefaultServicePeriod() + self.assertTrue(service_period.service_id) + service_period.SetWeekdayService(has_service=True) + service_period.SetStartDate("20070320") + service_period.SetEndDate("20071231") + + stop1 = schedule.AddStop(lng=-140.12, lat=48.921, + name="one forty at forty eight") + stop2 = schedule.AddStop(lng=-140.22, lat=48.421, name="west and south") + stop3 = schedule.AddStop(lng=-140.32, lat=48.121, name="more away") + stop4 = schedule.AddStop(lng=-140.42, lat=48.021, name="more more away") + + route = schedule.AddRoute(short_name="R", long_name="My Route", + route_type="Bus") + self.assertTrue(route.route_id) + self.assertEqual(route.route_short_name, "R") + self.assertEqual(route.route_type, 3) + + trip = route.AddTrip(schedule, headsign="To The End", + service_period=service_period) + trip_id = trip.trip_id + self.assertTrue(trip_id) + trip = schedule.GetTrip(trip_id) + self.assertEqual("To The End", trip.trip_headsign) + self.assertEqual(service_period, trip.service_period) + + trip.AddStopTime(stop=stop1, arrival_secs=3600*8, departure_secs=3600*8) + trip.AddStopTime(stop=stop2) + trip.AddStopTime(stop=stop3, arrival_secs=3600*8 + 60*60, + departure_secs=3600*8 + 60*60) + trip.AddStopTime(stop=stop4, arrival_time="9:13:00", + departure_secs=3600*8 + 60*103, stop_headsign="Last stop", + pickup_type=1, drop_off_type=3) + + schedule.Validate() + self.assertEqual(4, len(trip.GetTimeStops())) + self.assertEqual(1, len(schedule.GetRouteList())) + self.assertEqual(4, len(schedule.GetStopList())) + + +class WriteSampleFeedTestCase(TempFileTestCaseBase): + def assertEqualTimeString(self, a, b): + """Assert that a and b are equal, even if they don't have the same zero + padding on the hour. IE 08:45:00 vs 8:45:00.""" + if a[1] == ':': + a = '0' + a + if b[1] == ':': + b = '0' + b + self.assertEqual(a, b) + + def assertEqualWithDefault(self, a, b, default): + """Assert that a and b are equal. Treat None and default as equal.""" + if a == b: + return + if a in (None, default) and b in (None, default): + return + self.assertTrue(False, "a=%s b=%s" % (a, b)) + + def runTest(self): + problems = RecordingProblemReporter(self, ignore_types=("ExpirationDate",)) + schedule = transitfeed.Schedule(problem_reporter=problems) + agency = transitfeed.Agency() + agency.agency_id = "DTA" + agency.agency_name = "Demo Transit Authority" + agency.agency_url = "http://google.com" + agency.agency_timezone = "America/Los_Angeles" + agency.agency_lang = 'en' + # Test that unknown columns, such as agency_mission, are preserved + agency.agency_mission = "Get You There" + schedule.AddAgencyObject(agency) + + routes = [] + route_data = [ + ("AB", "DTA", "10", "Airport - Bullfrog", 3), + ("BFC", "DTA", "20", "Bullfrog - Furnace Creek Resort", 3), + ("STBA", "DTA", "30", "Stagecoach - Airport Shuttle", 3), + ("CITY", "DTA", "40", "City", 3), + ("AAMV", "DTA", "50", "Airport - Amargosa Valley", 3) + ] + + for route_entry in route_data: + route = transitfeed.Route() + (route.route_id, route.agency_id, route.route_short_name, + route.route_long_name, route.route_type) = route_entry + routes.append(route) + schedule.AddRouteObject(route) + + shape_data = [ + (36.915760, -116.751709), + (36.905018, -116.763206), + (36.902134, -116.777969), + (36.904091, -116.788185), + (36.883602, -116.814537), + (36.874523, -116.795593), + (36.873302, -116.786491), + (36.869202, -116.784241), + (36.868515, -116.784729), + ] + + shape = transitfeed.Shape("BFC1S") + for (lat, lon) in shape_data: + shape.AddPoint(lat, lon) + schedule.AddShapeObject(shape) + + week_period = transitfeed.ServicePeriod() + week_period.service_id = "FULLW" + week_period.start_date = "20070101" + week_period.end_date = "20071231" + week_period.SetWeekdayService() + week_period.SetWeekendService() + week_period.SetDateHasService("20070604", False) + schedule.AddServicePeriodObject(week_period) + + weekend_period = transitfeed.ServicePeriod() + weekend_period.service_id = "WE" + weekend_period.start_date = "20070101" + weekend_period.end_date = "20071231" + weekend_period.SetWeekendService() + schedule.AddServicePeriodObject(weekend_period) + + stops = [] + stop_data = [ + ("FUR_CREEK_RES", "Furnace Creek Resort (Demo)", + 36.425288, -117.133162, "zone-a", "1234"), + ("BEATTY_AIRPORT", "Nye County Airport (Demo)", + 36.868446, -116.784682, "zone-a", "1235"), + ("BULLFROG", "Bullfrog (Demo)", 36.88108, -116.81797, "zone-b", "1236"), + ("STAGECOACH", "Stagecoach Hotel & Casino (Demo)", + 36.915682, -116.751677, "zone-c", "1237"), + ("NADAV", "North Ave / D Ave N (Demo)", 36.914893, -116.76821, "", ""), + ("NANAA", "North Ave / N A Ave (Demo)", 36.914944, -116.761472, "", ""), + ("DADAN", "Doing AVe / D Ave N (Demo)", 36.909489, -116.768242, "", ""), + ("EMSI", "E Main St / S Irving St (Demo)", + 36.905697, -116.76218, "", ""), + ("AMV", "Amargosa Valley (Demo)", 36.641496, -116.40094, "", ""), + ] + for stop_entry in stop_data: + stop = transitfeed.Stop() + (stop.stop_id, stop.stop_name, stop.stop_lat, stop.stop_lon, + stop.zone_id, stop.stop_code) = stop_entry + schedule.AddStopObject(stop) + stops.append(stop) + # Add a value to an unknown column and make sure it is preserved + schedule.GetStop("BULLFROG").stop_sound = "croak!" + + trip_data = [ + ("AB", "FULLW", "AB1", "to Bullfrog", "0", "1", None), + ("AB", "FULLW", "AB2", "to Airport", "1", "2", None), + ("STBA", "FULLW", "STBA", "Shuttle", None, None, None), + ("CITY", "FULLW", "CITY1", None, "0", None, None), + ("CITY", "FULLW", "CITY2", None, "1", None, None), + ("BFC", "FULLW", "BFC1", "to Furnace Creek Resort", "0", "1", "BFC1S"), + ("BFC", "FULLW", "BFC2", "to Bullfrog", "1", "2", None), + ("AAMV", "WE", "AAMV1", "to Amargosa Valley", "0", None, None), + ("AAMV", "WE", "AAMV2", "to Airport", "1", None, None), + ("AAMV", "WE", "AAMV3", "to Amargosa Valley", "0", None, None), + ("AAMV", "WE", "AAMV4", "to Airport", "1", None, None), + ] + + trips = [] + for trip_entry in trip_data: + trip = transitfeed.Trip() + (trip.route_id, trip.service_id, trip.trip_id, trip.trip_headsign, + trip.direction_id, trip.block_id, trip.shape_id) = trip_entry + trips.append(trip) + schedule.AddTripObject(trip) + + stop_time_data = { + "STBA": [("6:00:00", "6:00:00", "STAGECOACH", None, None, None, None), + ("6:20:00", "6:20:00", "BEATTY_AIRPORT", None, None, None, None)], + "CITY1": [("6:00:00", "6:00:00", "STAGECOACH", 1.34, 0, 0, "stop 1"), + ("6:05:00", "6:07:00", "NANAA", 2.40, 1, 2, "stop 2"), + ("6:12:00", "6:14:00", "NADAV", 3.0, 2, 2, "stop 3"), + ("6:19:00", "6:21:00", "DADAN", 4, 2, 2, "stop 4"), + ("6:26:00", "6:28:00", "EMSI", 5.78, 2, 3, "stop 5")], + "CITY2": [("6:28:00", "6:28:00", "EMSI", None, None, None, None), + ("6:35:00", "6:37:00", "DADAN", None, None, None, None), + ("6:42:00", "6:44:00", "NADAV", None, None, None, None), + ("6:49:00", "6:51:00", "NANAA", None, None, None, None), + ("6:56:00", "6:58:00", "STAGECOACH", None, None, None, None)], + "AB1": [("8:00:00", "8:00:00", "BEATTY_AIRPORT", None, None, None, None), + ("8:10:00", "8:15:00", "BULLFROG", None, None, None, None)], + "AB2": [("12:05:00", "12:05:00", "BULLFROG", None, None, None, None), + ("12:15:00", "12:15:00", "BEATTY_AIRPORT", None, None, None, None)], + "BFC1": [("8:20:00", "8:20:00", "BULLFROG", None, None, None, None), + ("9:20:00", "9:20:00", "FUR_CREEK_RES", None, None, None, None)], + "BFC2": [("11:00:00", "11:00:00", "FUR_CREEK_RES", None, None, None, None), + ("12:00:00", "12:00:00", "BULLFROG", None, None, None, None)], + "AAMV1": [("8:00:00", "8:00:00", "BEATTY_AIRPORT", None, None, None, None), + ("9:00:00", "9:00:00", "AMV", None, None, None, None)], + "AAMV2": [("10:00:00", "10:00:00", "AMV", None, None, None, None), + ("11:00:00", "11:00:00", "BEATTY_AIRPORT", None, None, None, None)], + "AAMV3": [("13:00:00", "13:00:00", "BEATTY_AIRPORT", None, None, None, None), + ("14:00:00", "14:00:00", "AMV", None, None, None, None)], + "AAMV4": [("15:00:00", "15:00:00", "AMV", None, None, None, None), + ("16:00:00", "16:00:00", "BEATTY_AIRPORT", None, None, None, None)], + } + + for trip_id, stop_time_list in stop_time_data.items(): + for stop_time_entry in stop_time_list: + (arrival_time, departure_time, stop_id, shape_dist_traveled, + pickup_type, drop_off_type, stop_headsign) = stop_time_entry + trip = schedule.GetTrip(trip_id) + stop = schedule.GetStop(stop_id) + trip.AddStopTime(stop, arrival_time=arrival_time, + departure_time=departure_time, + shape_dist_traveled=shape_dist_traveled, + pickup_type=pickup_type, drop_off_type=drop_off_type, + stop_headsign=stop_headsign) + + self.assertEqual(0, schedule.GetTrip("CITY1").GetStopTimes()[0].pickup_type) + self.assertEqual(1, schedule.GetTrip("CITY1").GetStopTimes()[1].pickup_type) + + headway_data = [ + ("STBA", "6:00:00", "22:00:00", 1800), + ("CITY1", "6:00:00", "7:59:59", 1800), + ("CITY2", "6:00:00", "7:59:59", 1800), + ("CITY1", "8:00:00", "9:59:59", 600), + ("CITY2", "8:00:00", "9:59:59", 600), + ("CITY1", "10:00:00", "15:59:59", 1800), + ("CITY2", "10:00:00", "15:59:59", 1800), + ("CITY1", "16:00:00", "18:59:59", 600), + ("CITY2", "16:00:00", "18:59:59", 600), + ("CITY1", "19:00:00", "22:00:00", 1800), + ("CITY2", "19:00:00", "22:00:00", 1800), + ] + + headway_trips = {} + for headway_entry in headway_data: + (trip_id, start_time, end_time, headway) = headway_entry + headway_trips[trip_id] = [] # adding to set to check later + trip = schedule.GetTrip(trip_id) + trip.AddHeadwayPeriod(start_time, end_time, headway, problems) + for trip_id in headway_trips: + headway_trips[trip_id] = \ + schedule.GetTrip(trip_id).GetHeadwayPeriodTuples() + + fare_data = [ + ("p", 1.25, "USD", 0, 0), + ("a", 5.25, "USD", 0, 0), + ] + + fares = [] + for fare_entry in fare_data: + fare = transitfeed.Fare(fare_entry[0], fare_entry[1], fare_entry[2], + fare_entry[3], fare_entry[4]) + fares.append(fare) + schedule.AddFareObject(fare) + + fare_rule_data = [ + ("p", "AB", "zone-a", "zone-b", None), + ("p", "STBA", "zone-a", None, "zone-c"), + ("p", "BFC", None, "zone-b", "zone-a"), + ("a", "AAMV", None, None, None), + ] + + for fare_id, route_id, orig_id, dest_id, contains_id in fare_rule_data: + rule = transitfeed.FareRule( + fare_id=fare_id, route_id=route_id, origin_id=orig_id, + destination_id=dest_id, contains_id=contains_id) + schedule.AddFareRuleObject(rule, problems) + + schedule.Validate(problems) + problems.AssertNoMoreExceptions() + schedule.WriteGoogleTransitFeed(self.tempfilepath) + + read_schedule = \ + transitfeed.Loader(self.tempfilepath, problems=problems, + extra_validation=True).Load() + e = problems.PopException("UnrecognizedColumn") + self.assertEqual(e.file_name, "agency.txt") + self.assertEqual(e.column_name, "agency_mission") + e = problems.PopException("UnrecognizedColumn") + self.assertEqual(e.file_name, "stops.txt") + self.assertEqual(e.column_name, "stop_sound") + problems.AssertNoMoreExceptions() + + self.assertEqual(1, len(read_schedule.GetAgencyList())) + self.assertEqual(agency, read_schedule.GetAgency(agency.agency_id)) + + self.assertEqual(len(routes), len(read_schedule.GetRouteList())) + for route in routes: + self.assertEqual(route, read_schedule.GetRoute(route.route_id)) + + self.assertEqual(2, len(read_schedule.GetServicePeriodList())) + self.assertEqual(week_period, + read_schedule.GetServicePeriod(week_period.service_id)) + self.assertEqual(weekend_period, + read_schedule.GetServicePeriod(weekend_period.service_id)) + + self.assertEqual(len(stops), len(read_schedule.GetStopList())) + for stop in stops: + self.assertEqual(stop, read_schedule.GetStop(stop.stop_id)) + self.assertEqual("croak!", read_schedule.GetStop("BULLFROG").stop_sound) + + self.assertEqual(len(trips), len(read_schedule.GetTripList())) + for trip in trips: + self.assertEqual(trip, read_schedule.GetTrip(trip.trip_id)) + + for trip_id in headway_trips: + self.assertEqual(headway_trips[trip_id], + read_schedule.GetTrip(trip_id).GetHeadwayPeriodTuples()) + + for trip_id, stop_time_list in stop_time_data.items(): + trip = read_schedule.GetTrip(trip_id) + read_stoptimes = trip.GetStopTimes() + self.assertEqual(len(read_stoptimes), len(stop_time_list)) + for stop_time_entry, read_stoptime in zip(stop_time_list, read_stoptimes): + (arrival_time, departure_time, stop_id, shape_dist_traveled, + pickup_type, drop_off_type, stop_headsign) = stop_time_entry + self.assertEqual(stop_id, read_stoptime.stop_id) + self.assertEqual(read_schedule.GetStop(stop_id), read_stoptime.stop) + self.assertEqualTimeString(arrival_time, read_stoptime.arrival_time) + self.assertEqualTimeString(departure_time, read_stoptime.departure_time) + self.assertEqual(shape_dist_traveled, read_stoptime.shape_dist_traveled) + self.assertEqualWithDefault(pickup_type, read_stoptime.pickup_type, 0) + self.assertEqualWithDefault(drop_off_type, read_stoptime.drop_off_type, 0) + self.assertEqualWithDefault(stop_headsign, read_stoptime.stop_headsign, '') + + self.assertEqual(len(fares), len(read_schedule.GetFareList())) + for fare in fares: + self.assertEqual(fare, read_schedule.GetFare(fare.fare_id)) + + read_fare_rules_data = [] + for fare in read_schedule.GetFareList(): + for rule in fare.GetFareRuleList(): + self.assertEqual(fare.fare_id, rule.fare_id) + read_fare_rules_data.append((fare.fare_id, rule.route_id, + rule.origin_id, rule.destination_id, + rule.contains_id)) + fare_rule_data.sort() + read_fare_rules_data.sort() + self.assertEqual(len(read_fare_rules_data), len(fare_rule_data)) + for rf, f in zip(read_fare_rules_data, fare_rule_data): + self.assertEqual(rf, f) + + + self.assertEqual(1, len(read_schedule.GetShapeList())) + self.assertEqual(shape, read_schedule.GetShape(shape.shape_id)) + +# TODO: test GetPattern + +class DefaultAgencyTestCase(unittest.TestCase): + def freeAgency(self, ex=''): + agency = transitfeed.Agency() + agency.agency_id = 'agencytestid' + ex + agency.agency_name = 'Foo Bus Line' + ex + agency.agency_url = 'http://gofoo.com/' + ex + agency.agency_timezone='America/Los_Angeles' + return agency + + def test_SetDefault(self): + schedule = transitfeed.Schedule() + agency = self.freeAgency() + schedule.SetDefaultAgency(agency) + self.assertEqual(agency, schedule.GetDefaultAgency()) + + def test_NewDefaultAgency(self): + schedule = transitfeed.Schedule() + agency1 = schedule.NewDefaultAgency() + self.assertTrue(agency1.agency_id) + self.assertEqual(agency1.agency_id, schedule.GetDefaultAgency().agency_id) + self.assertEqual(1, len(schedule.GetAgencyList())) + agency2 = schedule.NewDefaultAgency() + self.assertTrue(agency2.agency_id) + self.assertEqual(agency2.agency_id, schedule.GetDefaultAgency().agency_id) + self.assertEqual(2, len(schedule.GetAgencyList())) + self.assertNotEqual(agency1, agency2) + self.assertNotEqual(agency1.agency_id, agency2.agency_id) + + agency3 = schedule.NewDefaultAgency(agency_id='agency3', + agency_name='Agency 3', + agency_url='http://goagency') + self.assertEqual(agency3.agency_id, 'agency3') + self.assertEqual(agency3.agency_name, 'Agency 3') + self.assertEqual(agency3.agency_url, 'http://goagency') + self.assertEqual(agency3, schedule.GetDefaultAgency()) + self.assertEqual('agency3', schedule.GetDefaultAgency().agency_id) + self.assertEqual(3, len(schedule.GetAgencyList())) + + def test_NoAgencyMakeNewDefault(self): + schedule = transitfeed.Schedule() + agency = schedule.GetDefaultAgency() + self.assertTrue(isinstance(agency, transitfeed.Agency)) + self.assertTrue(agency.agency_id) + self.assertEqual(1, len(schedule.GetAgencyList())) + self.assertEqual(agency, schedule.GetAgencyList()[0]) + self.assertEqual(agency.agency_id, schedule.GetAgencyList()[0].agency_id) + + def test_AssumeSingleAgencyIsDefault(self): + schedule = transitfeed.Schedule() + agency1 = self.freeAgency() + schedule.AddAgencyObject(agency1) + agency2 = self.freeAgency('2') # don't add to schedule + # agency1 is default because it is the only Agency in schedule + self.assertEqual(agency1, schedule.GetDefaultAgency()) + + def test_MultipleAgencyCausesNoDefault(self): + schedule = transitfeed.Schedule() + agency1 = self.freeAgency() + schedule.AddAgencyObject(agency1) + agency2 = self.freeAgency('2') + schedule.AddAgencyObject(agency2) + self.assertEqual(None, schedule.GetDefaultAgency()) + + def test_OverwriteExistingAgency(self): + schedule = transitfeed.Schedule() + agency1 = self.freeAgency() + agency1.agency_id = '1' + schedule.AddAgencyObject(agency1) + agency2 = schedule.NewDefaultAgency() + # Make sure agency1 was not overwritten by the new default + self.assertEqual(agency1, schedule.GetAgency(agency1.agency_id)) + self.assertNotEqual('1', agency2.agency_id) + + +class FindUniqueIdTestCase(unittest.TestCase): + def test_simple(self): + d = {} + for i in range(0, 5): + d[transitfeed.FindUniqueId(d)] = 1 + k = d.keys() + k.sort() + self.assertEqual(('0', '1', '2', '3', '4'), tuple(k)) + + def test_AvoidCollision(self): + d = {'1': 1} + d[transitfeed.FindUniqueId(d)] = 1 + self.assertEqual(2, len(d)) + self.assertFalse('2' in d, "Ops, next statement should add something to d") + d['2'] = None + d[transitfeed.FindUniqueId(d)] = 1 + self.assertEqual(4, len(d)) + + +class DefaultServicePeriodTestCase(unittest.TestCase): + def test_SetDefault(self): + schedule = transitfeed.Schedule() + service1 = transitfeed.ServicePeriod() + service1.SetDateHasService('20070101', True) + service1.service_id = 'SERVICE1' + schedule.SetDefaultServicePeriod(service1) + self.assertEqual(service1, schedule.GetDefaultServicePeriod()) + self.assertEqual(service1, schedule.GetServicePeriod(service1.service_id)) + + def test_NewDefault(self): + schedule = transitfeed.Schedule() + service1 = schedule.NewDefaultServicePeriod() + self.assertTrue(service1.service_id) + schedule.GetServicePeriod(service1.service_id) + service1.SetDateHasService('20070101', True) # Make service1 different + service2 = schedule.NewDefaultServicePeriod() + schedule.GetServicePeriod(service2.service_id) + self.assertTrue(service1.service_id) + self.assertTrue(service2.service_id) + self.assertNotEqual(service1, service2) + self.assertNotEqual(service1.service_id, service2.service_id) + + def test_NoServicesMakesNewDefault(self): + schedule = transitfeed.Schedule() + service1 = schedule.GetDefaultServicePeriod() + self.assertEqual(service1, schedule.GetServicePeriod(service1.service_id)) + + def test_AssumeSingleServiceIsDefault(self): + schedule = transitfeed.Schedule() + service1 = transitfeed.ServicePeriod() + service1.SetDateHasService('20070101', True) + service1.service_id = 'SERVICE1' + schedule.AddServicePeriodObject(service1) + self.assertEqual(service1, schedule.GetDefaultServicePeriod()) + self.assertEqual(service1.service_id, schedule.GetDefaultServicePeriod().service_id) + + def test_MultipleServicesCausesNoDefault(self): + schedule = transitfeed.Schedule() + service1 = transitfeed.ServicePeriod() + service1.service_id = 'SERVICE1' + service1.SetDateHasService('20070101', True) + schedule.AddServicePeriodObject(service1) + service2 = transitfeed.ServicePeriod() + service2.service_id = 'SERVICE2' + service2.SetDateHasService('20070201', True) + schedule.AddServicePeriodObject(service2) + service_d = schedule.GetDefaultServicePeriod() + self.assertEqual(service_d, None) + + +class GetTripTimeTestCase(unittest.TestCase): + """Test for GetStopTimeTrips and GetTimeInterpolatedStops""" + def setUp(self): + problems = TestFailureProblemReporter(self) + schedule = transitfeed.Schedule(problem_reporter=problems) + self.schedule = schedule + schedule.AddAgency("Agency", "http://iflyagency.com", + "America/Los_Angeles") + service_period = schedule.GetDefaultServicePeriod() + service_period.SetDateHasService('20070101') + self.stop1 = schedule.AddStop(lng=140.01, lat=0, name="140.01,0") + self.stop2 = schedule.AddStop(lng=140.02, lat=0, name="140.02,0") + self.stop3 = schedule.AddStop(lng=140.03, lat=0, name="140.03,0") + self.stop4 = schedule.AddStop(lng=140.04, lat=0, name="140.04,0") + self.stop5 = schedule.AddStop(lng=140.05, lat=0, name="140.05,0") + self.route1 = schedule.AddRoute("1", "One", "Bus") + + self.trip1 = self.route1.AddTrip(schedule, "trip 1", trip_id='trip1') + self.trip1.AddStopTime(self.stop1, schedule=schedule, departure_secs=100, arrival_secs=100) + self.trip1.AddStopTime(self.stop2, schedule=schedule) + self.trip1.AddStopTime(self.stop3, schedule=schedule) + # loop back to stop2 to test that interpolated stops work ok even when + # a stop between timepoints is further from the timepoint than the + # preceding + self.trip1.AddStopTime(self.stop2, schedule=schedule) + self.trip1.AddStopTime(self.stop4, schedule=schedule, departure_secs=400, arrival_secs=400) + + self.trip2 = self.route1.AddTrip(schedule, "trip 2", trip_id='trip2') + self.trip2.AddStopTime(self.stop2, schedule=schedule, departure_secs=500, arrival_secs=500) + self.trip2.AddStopTime(self.stop3, schedule=schedule, departure_secs=600, arrival_secs=600) + self.trip2.AddStopTime(self.stop4, schedule=schedule, departure_secs=700, arrival_secs=700) + self.trip2.AddStopTime(self.stop3, schedule=schedule, departure_secs=800, arrival_secs=800) + + self.trip3 = self.route1.AddTrip(schedule, "trip 3", trip_id='trip3') + + def testGetTimeInterpolatedStops(self): + rv = self.trip1.GetTimeInterpolatedStops() + self.assertEqual(5, len(rv)) + (secs, stoptimes, istimepoints) = tuple(zip(*rv)) + + self.assertEqual((100, 160, 220, 280, 400), secs) + self.assertEqual(("140.01,0", "140.02,0", "140.03,0", "140.02,0", "140.04,0"), + tuple([st.stop.stop_name for st in stoptimes])) + self.assertEqual((True, False, False, False, True), istimepoints) + + self.assertEqual([], self.trip3.GetTimeInterpolatedStops()) + + def testGetTimeInterpolatedStopsUntimedEnd(self): + self.trip2.AddStopTime(self.stop3, schedule=self.schedule) + self.assertRaises(ValueError, self.trip2.GetTimeInterpolatedStops) + + def testGetTimeInterpolatedStopsUntimedStart(self): + # Temporarily replace the problem reporter so that adding the first + # StopTime without a time doesn't throw an exception. + old_problems = self.schedule.problem_reporter + self.schedule.problem_reporter = TestFailureProblemReporter( + self, ("OtherProblem",)) + self.trip3.AddStopTime(self.stop3, schedule=self.schedule) + self.schedule.problem_reporter = old_problems + self.trip3.AddStopTime(self.stop2, schedule=self.schedule, + departure_secs=500, arrival_secs=500) + self.assertRaises(ValueError, self.trip3.GetTimeInterpolatedStops) + + def testGetTimeInterpolatedStopsSingleStopTime(self): + self.trip3.AddStopTime(self.stop3, schedule=self.schedule, + departure_secs=500, arrival_secs=500) + rv = self.trip3.GetTimeInterpolatedStops() + self.assertEqual(1, len(rv)) + self.assertEqual(500, rv[0][0]) + self.assertEqual(True, rv[0][2]) + + def testGetStopTimeTrips(self): + stopa = self.schedule.GetNearestStops(lon=140.03, lat=0)[0] + self.assertEqual("140.03,0", stopa.stop_name) # Got stop3? + rv = stopa.GetStopTimeTrips(self.schedule) + self.assertEqual(3, len(rv)) + (secs, trip_index, istimepoints) = tuple(zip(*rv)) + self.assertEqual((220, 600, 800), secs) + self.assertEqual(("trip1", "trip2", "trip2"), tuple([ti[0].trip_id for ti in trip_index])) + self.assertEqual((2, 1, 3), tuple([ti[1] for ti in trip_index])) + self.assertEqual((False, True, True), istimepoints) + + def testStopTripIndex(self): + trip_index = self.stop3.trip_index + trip_ids = [t.trip_id for t, i in trip_index] + self.assertEqual(["trip1", "trip2", "trip2"], trip_ids) + self.assertEqual([2, 1, 3], [i for t, i in trip_index]) + + def testGetTrips(self): + self.assertEqual(set([t.trip_id for t in self.stop1.GetTrips(self.schedule)]), + set([self.trip1.trip_id])) + self.assertEqual(set([t.trip_id for t in self.stop2.GetTrips(self.schedule)]), + set([self.trip1.trip_id, self.trip2.trip_id])) + self.assertEqual(set([t.trip_id for t in self.stop3.GetTrips(self.schedule)]), + set([self.trip1.trip_id, self.trip2.trip_id])) + self.assertEqual(set([t.trip_id for t in self.stop4.GetTrips(self.schedule)]), + set([self.trip1.trip_id, self.trip2.trip_id])) + self.assertEqual(set([t.trip_id for t in self.stop5.GetTrips(self.schedule)]), + set()) + + +class ApproximateDistanceBetweenStopsTestCase(unittest.TestCase): + def testEquator(self): + stop1 = transitfeed.Stop(lat=0, lng=100, + name='Stop one', stop_id='1') + stop2 = transitfeed.Stop(lat=0.01, lng=100.01, + name='Stop two', stop_id='2') + self.assertAlmostEqual( + transitfeed.ApproximateDistanceBetweenStops(stop1, stop2), + 1570, -1) # Compare first 3 digits + + def testWhati(self): + stop1 = transitfeed.Stop(lat=63.1, lng=-117.2, + name='Stop whati one', stop_id='1') + stop2 = transitfeed.Stop(lat=63.102, lng=-117.201, + name='Stop whati two', stop_id='2') + self.assertAlmostEqual( + transitfeed.ApproximateDistanceBetweenStops(stop1, stop2), + 228, 0) + + +class TimeConversionHelpersTestCase(unittest.TestCase): + def testTimeToSecondsSinceMidnight(self): + self.assertEqual(transitfeed.TimeToSecondsSinceMidnight("01:02:03"), 3723) + self.assertEqual(transitfeed.TimeToSecondsSinceMidnight("00:00:00"), 0) + self.assertEqual(transitfeed.TimeToSecondsSinceMidnight("25:24:23"), 91463) + try: + transitfeed.TimeToSecondsSinceMidnight("10:15:00am") + except transitfeed.Error: + pass # expected + else: + self.fail("Should have thrown Error") + + def testFormatSecondsSinceMidnight(self): + self.assertEqual(transitfeed.FormatSecondsSinceMidnight(3723), "01:02:03") + self.assertEqual(transitfeed.FormatSecondsSinceMidnight(0), "00:00:00") + self.assertEqual(transitfeed.FormatSecondsSinceMidnight(91463), "25:24:23") + + def testDateStringToDateObject(self): + self.assertEqual(transitfeed.DateStringToDateObject("20080901"), + datetime.date(2008, 9, 1)) + try: + transitfeed.DateStringToDateObject("20080841") + except ValueError: + pass # expected + else: + self.fail("Should have thrown ValueError") + + +class NonNegIntStringToIntTestCase(unittest.TestCase): + def runTest(self): + self.assertEqual(0, transitfeed.NonNegIntStringToInt("0")) + self.assertEqual(0, transitfeed.NonNegIntStringToInt(u"0")) + self.assertEqual(1, transitfeed.NonNegIntStringToInt("1")) + self.assertEqual(2, transitfeed.NonNegIntStringToInt("2")) + self.assertEqual(10, transitfeed.NonNegIntStringToInt("10")) + self.assertEqual(1234567890123456789, + transitfeed.NonNegIntStringToInt("1234567890123456789")) + self.assertRaises(ValueError, transitfeed.NonNegIntStringToInt, "") + self.assertRaises(ValueError, transitfeed.NonNegIntStringToInt, "-1") + self.assertRaises(ValueError, transitfeed.NonNegIntStringToInt, "+1") + self.assertRaises(ValueError, transitfeed.NonNegIntStringToInt, "01") + self.assertRaises(ValueError, transitfeed.NonNegIntStringToInt, "00") + self.assertRaises(ValueError, transitfeed.NonNegIntStringToInt, "0x1") + self.assertRaises(ValueError, transitfeed.NonNegIntStringToInt, "1.0") + self.assertRaises(ValueError, transitfeed.NonNegIntStringToInt, "1e1") + self.assertRaises(TypeError, transitfeed.NonNegIntStringToInt, 1) + self.assertRaises(TypeError, transitfeed.NonNegIntStringToInt, None) + + +class GetHeadwayTimesTestCase(unittest.TestCase): + """Test for GetHeadwayStartTimes and GetHeadwayStopTimes""" + def setUp(self): + problems = TestFailureProblemReporter(self) + schedule = transitfeed.Schedule(problem_reporter=problems) + self.schedule = schedule + schedule.AddAgency("Agency", "http://iflyagency.com", + "America/Los_Angeles") + service_period = schedule.GetDefaultServicePeriod() + service_period.SetStartDate("20080101") + service_period.SetEndDate("20090101") + service_period.SetWeekdayService(True) + self.stop1 = schedule.AddStop(lng=140.01, lat=0, name="140.01,0") + self.stop2 = schedule.AddStop(lng=140.02, lat=0, name="140.02,0") + self.stop3 = schedule.AddStop(lng=140.03, lat=0, name="140.03,0") + self.stop4 = schedule.AddStop(lng=140.04, lat=0, name="140.04,0") + self.stop5 = schedule.AddStop(lng=140.05, lat=0, name="140.05,0") + self.route1 = schedule.AddRoute("1", "One", "Bus") + + self.trip1 = self.route1.AddTrip(schedule, "trip 1", trip_id="trip1") + # add different types of stop times + self.trip1.AddStopTime(self.stop1, arrival_time="17:00:00", departure_time="17:01:00") # both arrival and departure time + self.trip1.AddStopTime(self.stop2, schedule=schedule) # non timed + self.trip1.AddStopTime(self.stop3, stop_time="17:45:00") # only stop_time + + # add headways starting before the trip + self.trip1.AddHeadwayPeriod("16:00:00","18:00:00",1800) # each 30 min + self.trip1.AddHeadwayPeriod("18:00:00","20:00:00",2700) # each 45 min + + def testGetHeadwayStartTimes(self): + start_times = self.trip1.GetHeadwayStartTimes() + self.assertEqual( + ["16:00:00", "16:30:00", "17:00:00", "17:30:00", + "18:00:00", "18:45:00", "19:30:00"], + [transitfeed.FormatSecondsSinceMidnight(secs) for secs in start_times]) + + def testGetHeadwayStopTimes(self): + stoptimes_list = self.trip1.GetHeadwayStopTimes() + arrival_secs = [] + departure_secs = [] + for stoptimes in stoptimes_list: + arrival_secs.append([st.arrival_secs for st in stoptimes]) + departure_secs.append([st.departure_secs for st in stoptimes]) + + self.assertEqual(([57600,None,60300],[59400,None,62100],[61200,None,63900], + [63000,None,65700],[64800,None,67500],[67500,None,70200], + [70200,None,72900]), + tuple(arrival_secs)) + self.assertEqual(([57660,None,60300],[59460,None,62100],[61260,None,63900], + [63060,None,65700],[64860,None,67500],[67560,None,70200], + [70260,None,72900]), + tuple(departure_secs)) + + # test if stoptimes are created with same parameters than the ones from the original trip + stoptimes = self.trip1.GetStopTimes() + for stoptimes_clone in stoptimes_list: + self.assertEqual(len(stoptimes_clone), len(stoptimes)) + for st_clone, st in zip(stoptimes_clone, stoptimes): + for name in st.__slots__: + if name not in ('arrival_secs', 'departure_secs'): + self.assertEqual(getattr(st, name), getattr(st_clone, name)) + + +class ServiceGapsTestCase(MemoryZipTestCase): + + def setUp(self): + super(ServiceGapsTestCase, self).setUp() + self.zip.writestr("calendar.txt", + "service_id,monday,tuesday,wednesday,thursday,friday," + "saturday,sunday,start_date,end_date\n" + "FULLW,1,1,1,1,1,1,1,20090601,20090610\n" + "WE,0,0,0,0,0,1,1,20090718,20101231\n") + self.zip.writestr("calendar_dates.txt", + "service_id,date,exception_type\n" + "WE,20090815,2\n" + "WE,20090816,2\n" + "WE,20090822,2\n" + # The following two lines are a 12-day service gap. + # Shouldn't issue a warning + "WE,20090829,2\n" + "WE,20090830,2\n" + "WE,20100102,2\n" + "WE,20100103,2\n" + "WE,20100109,2\n" + "WE,20100110,2\n" + "WE,20100612,2\n" + "WE,20100613,2\n" + "WE,20100619,2\n" + "WE,20100620,2\n") + self.zip.writestr("trips.txt", + "route_id,service_id,trip_id\n" + "AB,WE,AB1\n" + "AB,FULLW,AB2\n") + self.zip.writestr( + "stop_times.txt", + "trip_id,arrival_time,departure_time,stop_id,stop_sequence\n" + "AB1,10:00:00,10:00:00,BEATTY_AIRPORT,1\n" + "AB1,10:20:00,10:20:00,BULLFROG,2\n" + "AB2,10:25:00,10:25:00,STAGECOACH,1\n" + "AB2,10:55:00,10:55:00,BULLFROG,2\n") + loader = transitfeed.Loader( + problems=self.problems, + extra_validation=False, + zip=self.zip) + self.schedule = loader.Load() + + # If there is a service gap starting before today, and today has no service, + # it should be found - even if tomorrow there is service + def testServiceGapBeforeTodayIsDiscovered(self): + self.schedule.Validate(today=date(2009, 7, 17), + service_gap_interval=13) + exception = self.problems.PopException("TooManyDaysWithoutService") + self.assertEquals(date(2009, 7, 5), + exception.first_day_without_service) + self.assertEquals(date(2009, 7, 17), + exception.last_day_without_service) + + self.AssertCommonExceptions(date(2010, 6, 25)) + + # If today has service past service gaps should not appear + def testNoServiceGapBeforeTodayIfTodayHasService(self): + self.schedule.Validate(today=date(2009, 7, 18), + service_gap_interval=13) + + self.AssertCommonExceptions(date(2010, 6, 25)) + + # If the feed starts today NO previous service gap should be found + # even if today does not have service + def testNoServiceGapBeforeTodayIfTheFeedStartsToday(self): + self.schedule.Validate(today=date(2009, 06, 01), + service_gap_interval=13) + + # This service gap is the one between FULLW and WE + exception = self.problems.PopException("TooManyDaysWithoutService") + self.assertEquals(date(2009, 6, 11), + exception.first_day_without_service) + self.assertEquals(date(2009, 7, 17), + exception.last_day_without_service) + # The one-year period ends before the June 2010 gap, so that last + # service gap should _not_ be found + self.AssertCommonExceptions(None) + + # If there is a gap at the end of the one-year period we should find it + def testGapAtTheEndOfTheOneYearPeriodIsDiscovered(self): + self.schedule.Validate(today=date(2009, 06, 22), + service_gap_interval=13) + + # This service gap is the one between FULLW and WE + exception = self.problems.PopException("TooManyDaysWithoutService") + self.assertEquals(date(2009, 6, 11), + exception.first_day_without_service) + self.assertEquals(date(2009, 7, 17), + exception.last_day_without_service) + + self.AssertCommonExceptions(date(2010, 6, 21)) + + # If we are right in the middle of a big service gap it should be + # report as starting on "today - 12 days" and lasting until + # service resumes + def testCurrentServiceGapIsDiscovered(self): + self.schedule.Validate(today=date(2009, 6, 30), + service_gap_interval=13) + exception = self.problems.PopException("TooManyDaysWithoutService") + self.assertEquals(date(2009, 6, 18), + exception.first_day_without_service) + self.assertEquals(date(2009, 7, 17), + exception.last_day_without_service) + + self.AssertCommonExceptions(date(2010, 6, 25)) + + # Asserts the service gaps that appear towards the end of the calendar + # and which are common to all the tests + def AssertCommonExceptions(self, last_exception_date): + exception = self.problems.PopException("TooManyDaysWithoutService") + self.assertEquals(date(2009, 8, 10), + exception.first_day_without_service) + self.assertEquals(date(2009, 8, 22), + exception.last_day_without_service) + + exception = self.problems.PopException("TooManyDaysWithoutService") + self.assertEquals(date(2009, 12, 28), + exception.first_day_without_service) + self.assertEquals(date(2010, 1, 15), + exception.last_day_without_service) + + if last_exception_date is not None: + exception = self.problems.PopException("TooManyDaysWithoutService") + self.assertEquals(date(2010, 6, 7), + exception.first_day_without_service) + self.assertEquals(last_exception_date, + exception.last_day_without_service) + + self.problems.AssertNoMoreExceptions() + + +if __name__ == '__main__': + unittest.main() + --- /dev/null +++ b/origin-src/transitfeed-1.2.5/test/testunusual_trip_filter.py @@ -1,1 +1,119 @@ +#!/usr/bin/python2.4 +# +# Copyright (C) 2009 Google Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +"""Tests for unusual_trip_filter.py""" + +__author__ = 'Jiri Semecky ' + +import unusual_trip_filter +import transitfeed +import unittest +import util + +class UnusualTripFilterTestCase(util.TempDirTestCaseBase): + """Test of unusual trip filter functionality.""" + + def testFilter(self): + """Test if filtering works properly.""" + expected_values = { + 'CITY1':0, 'CITY2':0, 'CITY3':0, 'CITY4' :0, 'CITY5' :0, 'CITY6' :0, + 'CITY7':0, 'CITY8':0, 'CITY9':0, 'CITY10':0, 'CITY11':1, 'CITY12':1, + } + filter = unusual_trip_filter.UnusualTripFilter(0.1, quiet=True) + input = self.GetPath('test', 'data', 'filter_unusual_trips') + loader = transitfeed.Loader(input, extra_validation=True) + schedule = loader.Load() + filter.filter(schedule) + for trip_id, expected_trip_type in expected_values.items(): + actual_trip_type = schedule.trips[trip_id]['trip_type'] + try: + self.assertEquals(int(actual_trip_type), expected_trip_type) + except ValueError: + self.assertEquals(actual_trip_type, '') + + def testFilterNoForceFilter(self): + """Test that force==False doesn't set default values""" + filter = unusual_trip_filter.UnusualTripFilter(0.1, force=False, quiet=True) + input = self.GetPath('test', 'data', 'filter_unusual_trips') + loader = transitfeed.Loader(input, extra_validation=True) + schedule = loader.Load() + schedule.trips['CITY2'].trip_type = 'odd-trip' + filter.filter(schedule) + trip1 = schedule.trips['CITY1'] + self.assertEquals(trip1['trip_type'], '') + trip2 = schedule.trips['CITY2'] + self.assertEquals(trip2['trip_type'], 'odd-trip') + + def testFilterForceFilter(self): + """Test that force==True does set default values""" + filter = unusual_trip_filter.UnusualTripFilter(0.1, force=True, quiet=False) + input = self.GetPath('test', 'data', 'filter_unusual_trips') + loader = transitfeed.Loader(input, extra_validation=True) + schedule = loader.Load() + schedule.trips['CITY2'].trip_type = 'odd-trip' + filter.filter(schedule) + trip1 = schedule.trips['CITY1'] + self.assertEquals(trip1['trip_type'], '0') + trip2 = schedule.trips['CITY2'] + self.assertEquals(trip2['trip_type'], '0') + + def testFilterAppliedForSpecifiedRouteType(self): + """Setting integer route_type filters trips of this route type.""" + filter = unusual_trip_filter.UnusualTripFilter(0.1, quiet=True, + route_type=3) + input = self.GetPath('test', 'data', 'filter_unusual_trips') + loader = transitfeed.Loader(input, extra_validation=True) + schedule = loader.Load() + filter.filter(schedule) + actual_trip_type = schedule.trips['CITY11']['trip_type'] + self.assertEquals(actual_trip_type, '1') + + def testFilterNotAppliedForUnspecifiedRouteType(self): + """Setting integer route_type filters trips of this route type.""" + filter = unusual_trip_filter.UnusualTripFilter(0.1, quiet=True, + route_type=2) + input = self.GetPath('test', 'data', 'filter_unusual_trips') + loader = transitfeed.Loader(input, extra_validation=True) + schedule = loader.Load() + filter.filter(schedule) + actual_trip_type = schedule.trips['CITY11']['trip_type'] + self.assertEquals(actual_trip_type, '') + + def testFilterAppliedForRouteTypeSpecifiedByName(self): + """Setting integer route_type filters trips of this route type.""" + filter = unusual_trip_filter.UnusualTripFilter(0.1, quiet=True, + route_type='Bus') + input = self.GetPath('test', 'data', 'filter_unusual_trips') + loader = transitfeed.Loader(input, extra_validation=True) + schedule = loader.Load() + filter.filter(schedule) + actual_trip_type = schedule.trips['CITY11']['trip_type'] + self.assertEquals(actual_trip_type, '1') + + def testFilterNotAppliedForDifferentRouteTypeSpecifiedByName(self): + """Setting integer route_type filters trips of this route type.""" + filter = unusual_trip_filter.UnusualTripFilter(0.1, quiet=True, + route_type='Ferry') + input = self.GetPath('test', 'data', 'filter_unusual_trips') + loader = transitfeed.Loader(input, extra_validation=True) + schedule = loader.Load() + filter.filter(schedule) + actual_trip_type = schedule.trips['CITY11']['trip_type'] + self.assertEquals(actual_trip_type, '') + +if __name__ == '__main__': + unittest.main() + --- /dev/null +++ b/origin-src/transitfeed-1.2.5/test/util.py @@ -1,1 +1,200 @@ - +#!/usr/bin/python2.5 + +# Copyright (C) 2007 Google Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# Code shared between tests. + +import os +import os.path +import re +import cStringIO as StringIO +import shutil +import subprocess +import sys +import tempfile +import traceback +import transitfeed +import unittest + + +def check_call(cmd, expected_retcode=0, stdin_str="", **kwargs): + """Convenience function that is in the docs for subprocess but not + installed on my system. Raises an Exception if the return code is not + expected_retcode. Returns a tuple of strings, (stdout, stderr).""" + try: + if 'stdout' in kwargs or 'stderr' in kwargs or 'stdin' in kwargs: + raise Exception("Don't pass stdout or stderr") + p = subprocess.Popen(cmd, stdout=subprocess.PIPE, + stderr=subprocess.PIPE, stdin=subprocess.PIPE, + **kwargs) + (out, err) = p.communicate(stdin_str) + retcode = p.returncode + except Exception, e: + raise Exception("When running %s: %s" % (cmd, e)) + if retcode < 0: + raise Exception( + "Child '%s' was terminated by signal %d. Output:\n%s\n%s\n" % + (cmd, -retcode, out, err)) + elif retcode != expected_retcode: + raise Exception( + "Child '%s' returned %d. Output:\n%s\n%s\n" % + (cmd, retcode, out, err)) + return (out, err) + + +class TestCaseAsserts(unittest.TestCase): + def assertMatchesRegex(self, regex, string): + """Assert that regex is found in string.""" + if not re.search(regex, string): + self.fail("string %r did not match regex %r" % (string, regex)) + + +class GetPathTestCase(TestCaseAsserts): + """TestCase with method to get paths to files in the distribution.""" + def setUp(self): + TestCaseAsserts.setUp(self) + self._origcwd = os.getcwd() + + def GetExamplePath(self, name): + """Return the full path of a file in the examples directory""" + return self.GetPath('examples', name) + + def GetTestDataPath(self, *path): + """Return the full path of a file in the test/data directory""" + return self.GetPath('test', 'data', *path) + + def GetPath(self, *path): + """Return absolute path of path. path is relative main source directory.""" + here = os.path.dirname(__file__) # Relative to _origcwd + return os.path.join(self._origcwd, here, '..', *path) + + +class TempDirTestCaseBase(GetPathTestCase): + """Make a temporary directory the current directory before running the test + and remove it after the test. + """ + def setUp(self): + GetPathTestCase.setUp(self) + self.tempdirpath = tempfile.mkdtemp() + os.chdir(self.tempdirpath) + + def tearDown(self): + os.chdir(self._origcwd) + shutil.rmtree(self.tempdirpath) + GetPathTestCase.tearDown(self) + + def CheckCallWithPath(self, cmd, expected_retcode=0, stdin_str=""): + """Run python script cmd[0] with args cmd[1:], making sure 'import + transitfeed' will use the module in this source tree. Raises an Exception + if the return code is not expected_retcode. Returns a tuple of strings, + (stdout, stderr).""" + tf_path = transitfeed.__file__ + # Path of the directory containing transitfeed. When this is added to + # sys.path importing transitfeed should work independent of if + # transitfeed.__file__ is /transitfeed.py or + # /transitfeed/__init__.py + transitfeed_parent = tf_path[:tf_path.rfind("transitfeed")] + transitfeed_parent = transitfeed_parent.replace("\\", "/").rstrip("/") + script_path = cmd[0].replace("\\", "/") + script_args = cmd[1:] + + # Propogate sys.path of this process to the subprocess. This is done + # because I assume that if this process has a customized sys.path it is + # meant to be used for all processes involved in the tests. The downside + # of this is that the subprocess is no longer a clean version of what you + # get when running "python" after installing transitfeed. Hopefully if this + # process uses a customized sys.path you know what you are doing. + env = {"PYTHONPATH": ":".join(sys.path)} + + # Instead of directly running the script make sure that the transitfeed + # module in this source directory is at the front of sys.path. Then + # adjust sys.argv so it looks like the script was run directly. This lets + # OptionParser use the correct value for %proj. + cmd = [sys.executable, "-c", + "import sys; " + "sys.path.insert(0,'%s'); " + "sys.argv = ['%s'] + sys.argv[1:]; " + "exec(open('%s'))" % + (transitfeed_parent, script_path, script_path)] + script_args + return check_call(cmd, expected_retcode=expected_retcode, shell=False, + env=env, stdin_str=stdin_str) + + +class RecordingProblemReporter(transitfeed.ProblemReporterBase): + """Save all problems for later inspection. + + Args: + test_case: a unittest.TestCase object on which to report problems + ignore_types: sequence of string type names that will be ignored by the + ProblemReporter""" + def __init__(self, test_case, ignore_types=None): + transitfeed.ProblemReporterBase.__init__(self) + self.exceptions = [] + self._test_case = test_case + self._ignore_types = ignore_types or set() + + def _Report(self, e): + # Ensure that these don't crash + e.FormatProblem() + e.FormatContext() + if e.__class__.__name__ in self._ignore_types: + return + # Keep the 7 nearest stack frames. This should be enough to identify + # the code path that created the exception while trimming off most of the + # large test framework's stack. + traceback_list = traceback.format_list(traceback.extract_stack()[-7:-1]) + self.exceptions.append((e, ''.join(traceback_list))) + + def PopException(self, type_name): + """Return the first exception, which must be a type_name.""" + e = self.exceptions.pop(0) + e_name = e[0].__class__.__name__ + self._test_case.assertEqual(e_name, type_name, + "%s != %s\n%s" % + (e_name, type_name, self.FormatException(*e))) + return e[0] + + def FormatException(self, exce, tb): + return ("%s\nwith gtfs file context %s\nand traceback\n%s" % + (exce.FormatProblem(), exce.FormatContext(), tb)) + + def AssertNoMoreExceptions(self): + exceptions_as_text = [] + for e, tb in self.exceptions: + exceptions_as_text.append(self.FormatException(e, tb)) + self._test_case.assertFalse(self.exceptions, "\n".join(exceptions_as_text)) + + def PopInvalidValue(self, column_name, file_name=None): + e = self.PopException("InvalidValue") + self._test_case.assertEquals(column_name, e.column_name) + if file_name: + self._test_case.assertEquals(file_name, e.file_name) + return e + + def PopMissingValue(self, column_name, file_name=None): + e = self.PopException("MissingValue") + self._test_case.assertEquals(column_name, e.column_name) + if file_name: + self._test_case.assertEquals(file_name, e.file_name) + return e + + def PopDuplicateColumn(self, file_name, header, count): + e = self.PopException("DuplicateColumn") + self._test_case.assertEquals(file_name, e.file_name) + self._test_case.assertEquals(header, e.header) + self._test_case.assertEquals(count, e.count) + return e + + --- /dev/null +++ b/origin-src/transitfeed-1.2.5/transitfeed/__init__.py @@ -1,1 +1,35 @@ +#!/usr/bin/python2.5 +# Copyright (C) 2007 Google Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +"""Expose some modules in this package. + +Before transitfeed version 1.2.4 all our library code was distributed in a +one file module, transitfeed.py, and could be used as + +import transitfeed +schedule = transitfeed.Schedule() + +At that time the module (one file, transitfeed.py) was converted into a +package (a directory named transitfeed containing __init__.py and multiple .py +files). Classes and attributes exposed by the old module may still be imported +in the same way. Indeed, code that depends on the library should +continue to use import commands such as the above and ignore _transitfeed. +""" + +from _transitfeed import * + +__version__ = _transitfeed.__version__ + --- /dev/null +++ b/origin-src/transitfeed-1.2.5/transitfeed/_transitfeed.py @@ -1,1 +1,4599 @@ - +#!/usr/bin/python2.5 + +# Copyright (C) 2007 Google Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +"""Easy interface for handling a Google Transit Feed file. + +Do not import this module directly. Thanks to __init__.py you should do +something like: + + import transitfeed + schedule = transitfeed.Schedule() + ... + +This module is a library to help you create, read and write Google +Transit Feed files. Refer to the feed specification, available at +http://code.google.com/transit/spec/transit_feed_specification.htm, for a +complete description how the transit feed represents a transit schedule. This +library supports all required parts of the specification but does not yet +support all optional parts. Patches welcome! + +The specification describes several tables such as stops, routes and trips. +In a feed file these are stored as comma separeted value files. This library +represents each row of these tables with a single Python object. This object has +attributes for each value on the row. For example, schedule.AddStop returns a +Stop object which has attributes such as stop_lat and stop_name. + + Schedule: Central object of the parser + GenericGTFSObject: A base class for each of the objects below + Route: Represents a single route + Trip: Represents a single trip + Stop: Represents a single stop + ServicePeriod: Represents a single service, a set of dates + Agency: Represents the agency in this feed + Transfer: Represents a single transfer rule + TimeToSecondsSinceMidnight(): Convert HH:MM:SS into seconds since midnight. + FormatSecondsSinceMidnight(s): Formats number of seconds past midnight into a string +""" + +# TODO: Preserve arbitrary columns? + +import bisect +import cStringIO as StringIO +import codecs +from transitfeed.util import defaultdict +import csv +import datetime +import logging +import math +import os +import random +try: + import sqlite3 as sqlite +except ImportError: + from pysqlite2 import dbapi2 as sqlite +import re +import tempfile +import time +import warnings +# Objects in a schedule (Route, Trip, etc) should not keep a strong reference +# to the Schedule object to avoid a reference cycle. Schedule needs to use +# __del__ to cleanup its temporary file. The garbage collector can't handle +# reference cycles containing objects with custom cleanup code. +import weakref +import zipfile + +OUTPUT_ENCODING = 'utf-8' +MAX_DISTANCE_FROM_STOP_TO_SHAPE = 1000 +MAX_DISTANCE_BETWEEN_STOP_AND_PARENT_STATION_WARNING = 100.0 +MAX_DISTANCE_BETWEEN_STOP_AND_PARENT_STATION_ERROR = 1000.0 + +__version__ = '1.2.5' + + +def EncodeUnicode(text): + """ + Optionally encode text and return it. The result should be safe to print. + """ + if type(text) == type(u''): + return text.encode(OUTPUT_ENCODING) + else: + return text + + +# These are used to distinguish between errors (not allowed by the spec) +# and warnings (not recommended) when reporting issues. +TYPE_ERROR = 0 +TYPE_WARNING = 1 + + +class ProblemReporterBase: + """Base class for problem reporters. Tracks the current context and creates + an exception object for each problem. Subclasses must implement + _Report(self, e)""" + + def __init__(self): + self.ClearContext() + + def ClearContext(self): + """Clear any previous context.""" + self._context = None + + def SetFileContext(self, file_name, row_num, row, headers): + """Save the current context to be output with any errors. + + Args: + file_name: string + row_num: int + row: list of strings + headers: list of column headers, its order corresponding to row's + """ + self._context = (file_name, row_num, row, headers) + + def FeedNotFound(self, feed_name, context=None): + e = FeedNotFound(feed_name=feed_name, context=context, + context2=self._context) + self._Report(e) + + def UnknownFormat(self, feed_name, context=None): + e = UnknownFormat(feed_name=feed_name, context=context, + context2=self._context) + self._Report(e) + + def FileFormat(self, problem, context=None): + e = FileFormat(problem=problem, context=context, + context2=self._context) + self._Report(e) + + def MissingFile(self, file_name, context=None): + e = MissingFile(file_name=file_name, context=context, + context2=self._context) + self._Report(e) + + def UnknownFile(self, file_name, context=None): + e = UnknownFile(file_name=file_name, context=context, + context2=self._context, type=TYPE_WARNING) + self._Report(e) + + def EmptyFile(self, file_name, context=None): + e = EmptyFile(file_name=file_name, context=context, + context2=self._context) + self._Report(e) + + def MissingColumn(self, file_name, column_name, context=None): + e = MissingColumn(file_name=file_name, column_name=column_name, + context=context, context2=self._context) + self._Report(e) + + def UnrecognizedColumn(self, file_name, column_name, context=None): + e = UnrecognizedColumn(file_name=file_name, column_name=column_name, + context=context, context2=self._context, + type=TYPE_WARNING) + self._Report(e) + + def CsvSyntax(self, description=None, context=None, type=TYPE_ERROR): + e = CsvSyntax(description=description, context=context, + context2=self._context, type=type) + self._Report(e) + + def DuplicateColumn(self, file_name, header, count, type=TYPE_ERROR, + context=None): + e = DuplicateColumn(file_name=file_name, + header=header, + count=count, + type=type, + context=context, + context2=self._context) + self._Report(e) + + def MissingValue(self, column_name, reason=None, context=None): + e = MissingValue(column_name=column_name, reason=reason, context=context, + context2=self._context) + self._Report(e) + + def InvalidValue(self, column_name, value, reason=None, context=None, + type=TYPE_ERROR): + e = InvalidValue(column_name=column_name, value=value, reason=reason, + context=context, context2=self._context, type=type) + self._Report(e) + + def DuplicateID(self, column_names, values, context=None, type=TYPE_ERROR): + if isinstance(column_names, tuple): + column_names = '(' + ', '.join(column_names) + ')' + if isinstance(values, tuple): + values = '(' + ', '.join(values) + ')' + e = DuplicateID(column_name=column_names, value=values, + context=context, context2=self._context, type=type) + self._Report(e) + + def UnusedStop(self, stop_id, stop_name, context=None): + e = UnusedStop(stop_id=stop_id, stop_name=stop_name, + context=context, context2=self._context, type=TYPE_WARNING) + self._Report(e) + + def UsedStation(self, stop_id, stop_name, context=None): + e = UsedStation(stop_id=stop_id, stop_name=stop_name, + context=context, context2=self._context, type=TYPE_ERROR) + self._Report(e) + + def StopTooFarFromParentStation(self, stop_id, stop_name, parent_stop_id, + parent_stop_name, distance, + type=TYPE_WARNING, context=None): + e = StopTooFarFromParentStation( + stop_id=stop_id, stop_name=stop_name, + parent_stop_id=parent_stop_id, + parent_stop_name=parent_stop_name, distance=distance, + context=context, context2=self._context, type=type) + self._Report(e) + + def StopsTooClose(self, stop_name_a, stop_id_a, stop_name_b, stop_id_b, + distance, type=TYPE_WARNING, context=None): + e = StopsTooClose( + stop_name_a=stop_name_a, stop_id_a=stop_id_a, stop_name_b=stop_name_b, + stop_id_b=stop_id_b, distance=distance, context=context, + context2=self._context, type=type) + self._Report(e) + + def StationsTooClose(self, stop_name_a, stop_id_a, stop_name_b, stop_id_b, + distance, type=TYPE_WARNING, context=None): + e = StationsTooClose( + stop_name_a=stop_name_a, stop_id_a=stop_id_a, stop_name_b=stop_name_b, + stop_id_b=stop_id_b, distance=distance, context=context, + context2=self._context, type=type) + self._Report(e) + + def DifferentStationTooClose(self, stop_name, stop_id, + station_stop_name, station_stop_id, + distance, type=TYPE_WARNING, context=None): + e = DifferentStationTooClose( + stop_name=stop_name, stop_id=stop_id, + station_stop_name=station_stop_name, station_stop_id=station_stop_id, + distance=distance, context=context, context2=self._context, type=type) + self._Report(e) + + def StopTooFarFromShapeWithDistTraveled(self, trip_id, stop_name, stop_id, + shape_dist_traveled, shape_id, + distance, max_distance, + type=TYPE_WARNING): + e = StopTooFarFromShapeWithDistTraveled( + trip_id=trip_id, stop_name=stop_name, stop_id=stop_id, + shape_dist_traveled=shape_dist_traveled, shape_id=shape_id, + distance=distance, max_distance=max_distance, type=type) + self._Report(e) + + def ExpirationDate(self, expiration, context=None): + e = ExpirationDate(expiration=expiration, context=context, + context2=self._context, type=TYPE_WARNING) + self._Report(e) + + def FutureService(self, start_date, context=None): + e = FutureService(start_date=start_date, context=context, + context2=self._context, type=TYPE_WARNING) + self._Report(e) + + def InvalidLineEnd(self, bad_line_end, context=None): + """bad_line_end is a human readable string.""" + e = InvalidLineEnd(bad_line_end=bad_line_end, context=context, + context2=self._context, type=TYPE_WARNING) + self._Report(e) + + def TooFastTravel(self, trip_id, prev_stop, next_stop, dist, time, speed, + type=TYPE_ERROR): + e = TooFastTravel(trip_id=trip_id, prev_stop=prev_stop, + next_stop=next_stop, time=time, dist=dist, speed=speed, + context=None, context2=self._context, type=type) + self._Report(e) + + def StopWithMultipleRouteTypes(self, stop_name, stop_id, route_id1, route_id2, + context=None): + e = StopWithMultipleRouteTypes(stop_name=stop_name, stop_id=stop_id, + route_id1=route_id1, route_id2=route_id2, + context=context, context2=self._context, + type=TYPE_WARNING) + self._Report(e) + + def DuplicateTrip(self, trip_id1, route_id1, trip_id2, route_id2, + context=None): + e = DuplicateTrip(trip_id1=trip_id1, route_id1=route_id1, trip_id2=trip_id2, + route_id2=route_id2, context=context, + context2=self._context, type=TYPE_WARNING) + self._Report(e) + + def OtherProblem(self, description, context=None, type=TYPE_ERROR): + e = OtherProblem(description=description, + context=context, context2=self._context, type=type) + self._Report(e) + + def TooManyDaysWithoutService(self, + first_day_without_service, + last_day_without_service, + consecutive_days_without_service, + context=None, + type=TYPE_WARNING): + e = TooManyDaysWithoutService( + first_day_without_service=first_day_without_service, + last_day_without_service=last_day_without_service, + consecutive_days_without_service=consecutive_days_without_service, + context=context, + context2=self._context, + type=type) + self._Report(e) + +class ProblemReporter(ProblemReporterBase): + """This is a basic problem reporter that just prints to console.""" + def _Report(self, e): + context = e.FormatContext() + if context: + print context + print EncodeUnicode(self._LineWrap(e.FormatProblem(), 78)) + + @staticmethod + def _LineWrap(text, width): + """ + A word-wrap function that preserves existing line breaks + and most spaces in the text. Expects that existing line + breaks are posix newlines (\n). + + Taken from: + http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/148061 + """ + return reduce(lambda line, word, width=width: '%s%s%s' % + (line, + ' \n'[(len(line) - line.rfind('\n') - 1 + + len(word.split('\n', 1)[0]) >= width)], + word), + text.split(' ') + ) + + +class ExceptionWithContext(Exception): + def __init__(self, context=None, context2=None, **kwargs): + """Initialize an exception object, saving all keyword arguments in self. + context and context2, if present, must be a tuple of (file_name, row_num, + row, headers). context2 comes from ProblemReporter.SetFileContext. context + was passed in with the keyword arguments. context2 is ignored if context + is present.""" + Exception.__init__(self) + + if context: + self.__dict__.update(self.ContextTupleToDict(context)) + elif context2: + self.__dict__.update(self.ContextTupleToDict(context2)) + self.__dict__.update(kwargs) + + if ('type' in kwargs) and (kwargs['type'] == TYPE_WARNING): + self._type = TYPE_WARNING + else: + self._type = TYPE_ERROR + + def GetType(self): + return self._type + + def IsError(self): + return self._type == TYPE_ERROR + + def IsWarning(self): + return self._type == TYPE_WARNING + + CONTEXT_PARTS = ['file_name', 'row_num', 'row', 'headers'] + @staticmethod + def ContextTupleToDict(context): + """Convert a tuple representing a context into a dict of (key, value) pairs""" + d = {} + if not context: + return d + for k, v in zip(ExceptionWithContext.CONTEXT_PARTS, context): + if v != '' and v != None: # Don't ignore int(0), a valid row_num + d[k] = v + return d + + def __str__(self): + return self.FormatProblem() + + def GetDictToFormat(self): + """Return a copy of self as a dict, suitable for passing to FormatProblem""" + d = {} + for k, v in self.__dict__.items(): + # TODO: Better handling of unicode/utf-8 within Schedule objects. + # Concatinating a unicode and utf-8 str object causes an exception such + # as "UnicodeDecodeError: 'ascii' codec can't decode byte ..." as python + # tries to convert the str to a unicode. To avoid that happening within + # the problem reporter convert all unicode attributes to utf-8. + # Currently valid utf-8 fields are converted to unicode in _ReadCsvDict. + # Perhaps all fields should be left as utf-8. + d[k] = EncodeUnicode(v) + return d + + def FormatProblem(self, d=None): + """Return a text string describing the problem. + + Args: + d: map returned by GetDictToFormat with with formatting added + """ + if not d: + d = self.GetDictToFormat() + + output_error_text = self.__class__.ERROR_TEXT % d + if ('reason' in d) and d['reason']: + return '%s\n%s' % (output_error_text, d['reason']) + else: + return output_error_text + + def FormatContext(self): + """Return a text string describing the context""" + text = '' + if hasattr(self, 'feed_name'): + text += "In feed '%s': " % self.feed_name + if hasattr(self, 'file_name'): + text += self.file_name + if hasattr(self, 'row_num'): + text += ":%i" % self.row_num + if hasattr(self, 'column_name'): + text += " column %s" % self.column_name + return text + + def __cmp__(self, y): + """Return an int <0/0/>0 when self is more/same/less significant than y. + + Subclasses should define this if exceptions should be listed in something + other than the order they are reported. + + Args: + y: object to compare to self + + Returns: + An int which is negative if self is more significant than y, 0 if they + are similar significance and positive if self is less significant than + y. Returning a float won't work. + + Raises: + TypeError by default, meaning objects of the type can not be compared. + """ + raise TypeError("__cmp__ not defined") + + +class MissingFile(ExceptionWithContext): + ERROR_TEXT = "File %(file_name)s is not found" + +class EmptyFile(ExceptionWithContext): + ERROR_TEXT = "File %(file_name)s is empty" + +class UnknownFile(ExceptionWithContext): + ERROR_TEXT = 'The file named %(file_name)s was not expected.\n' \ + 'This may be a misspelled file name or the file may be ' \ + 'included in a subdirectory. Please check spellings and ' \ + 'make sure that there are no subdirectories within the feed' + +class FeedNotFound(ExceptionWithContext): + ERROR_TEXT = 'Couldn\'t find a feed named %(feed_name)s' + +class UnknownFormat(ExceptionWithContext): + ERROR_TEXT = 'The feed named %(feed_name)s had an unknown format:\n' \ + 'feeds should be either .zip files or directories.' + +class FileFormat(ExceptionWithContext): + ERROR_TEXT = 'Files must be encoded in utf-8 and may not contain ' \ + 'any null bytes (0x00). %(file_name)s %(problem)s.' + +class MissingColumn(ExceptionWithContext): + ERROR_TEXT = 'Missing column %(column_name)s in file %(file_name)s' + +class UnrecognizedColumn(ExceptionWithContext): + ERROR_TEXT = 'Unrecognized column %(column_name)s in file %(file_name)s. ' \ + 'This might be a misspelled column name (capitalization ' \ + 'matters!). Or it could be extra information (such as a ' \ + 'proposed feed extension) that the validator doesn\'t know ' \ + 'about yet. Extra information is fine; this warning is here ' \ + 'to catch misspelled optional column names.' + +class CsvSyntax(ExceptionWithContext): + ERROR_TEXT = '%(description)s' + +class DuplicateColumn(ExceptionWithContext): + ERROR_TEXT = 'Column %(header)s appears %(count)i times in file %(file_name)s' + +class MissingValue(ExceptionWithContext): + ERROR_TEXT = 'Missing value for column %(column_name)s' + +class InvalidValue(ExceptionWithContext): + ERROR_TEXT = 'Invalid value %(value)s in field %(column_name)s' + +class DuplicateID(ExceptionWithContext): + ERROR_TEXT = 'Duplicate ID %(value)s in column %(column_name)s' + +class UnusedStop(ExceptionWithContext): + ERROR_TEXT = "%(stop_name)s (ID %(stop_id)s) isn't used in any trips" + +class UsedStation(ExceptionWithContext): + ERROR_TEXT = "%(stop_name)s (ID %(stop_id)s) has location_type=1 " \ + "(station) so it should not appear in stop_times" + +class StopTooFarFromParentStation(ExceptionWithContext): + ERROR_TEXT = ( + "%(stop_name)s (ID %(stop_id)s) is too far from its parent station " + "%(parent_stop_name)s (ID %(parent_stop_id)s) : %(distance).2f meters.") + def __cmp__(self, y): + # Sort in decreasing order because more distance is more significant. + return cmp(y.distance, self.distance) + + +class StopsTooClose(ExceptionWithContext): + ERROR_TEXT = ( + "The stops \"%(stop_name_a)s\" (ID %(stop_id_a)s) and \"%(stop_name_b)s\"" + " (ID %(stop_id_b)s) are %(distance)0.2fm apart and probably represent " + "the same location.") + def __cmp__(self, y): + # Sort in increasing order because less distance is more significant. + return cmp(self.distance, y.distance) + +class StationsTooClose(ExceptionWithContext): + ERROR_TEXT = ( + "The stations \"%(stop_name_a)s\" (ID %(stop_id_a)s) and " + "\"%(stop_name_b)s\" (ID %(stop_id_b)s) are %(distance)0.2fm apart and " + "probably represent the same location.") + def __cmp__(self, y): + # Sort in increasing order because less distance is more significant. + return cmp(self.distance, y.distance) + +class DifferentStationTooClose(ExceptionWithContext): + ERROR_TEXT = ( + "The parent_station of stop \"%(stop_name)s\" (ID %(stop_id)s) is not " + "station \"%(station_stop_name)s\" (ID %(station_stop_id)s) but they are " + "only %(distance)0.2fm apart.") + def __cmp__(self, y): + # Sort in increasing order because less distance is more significant. + return cmp(self.distance, y.distance) + +class StopTooFarFromShapeWithDistTraveled(ExceptionWithContext): + ERROR_TEXT = ( + "For trip %(trip_id)s the stop \"%(stop_name)s\" (ID %(stop_id)s) is " + "%(distance).0f meters away from the corresponding point " + "(shape_dist_traveled: %(shape_dist_traveled)f) on shape %(shape_id)s. " + "It should be closer than %(max_distance).0f meters.") + def __cmp__(self, y): + # Sort in decreasing order because more distance is more significant. + return cmp(y.distance, self.distance) + + +class TooManyDaysWithoutService(ExceptionWithContext): + ERROR_TEXT = "There are %(consecutive_days_without_service)i consecutive"\ + " days, from %(first_day_without_service)s to" \ + " %(last_day_without_service)s, without any scheduled service." \ + " Please ensure this is intentional." + + +class ExpirationDate(ExceptionWithContext): + def FormatProblem(self, d=None): + if not d: + d = self.GetDictToFormat() + expiration = d['expiration'] + formatted_date = time.strftime("%B %d, %Y", + time.localtime(expiration)) + if (expiration < time.mktime(time.localtime())): + return "This feed expired on %s" % formatted_date + else: + return "This feed will soon expire, on %s" % formatted_date + +class FutureService(ExceptionWithContext): + def FormatProblem(self, d=None): + if not d: + d = self.GetDictToFormat() + formatted_date = time.strftime("%B %d, %Y", time.localtime(d['start_date'])) + return ("The earliest service date in this feed is in the future, on %s. " + "Published feeds must always include the current date." % + formatted_date) + + +class InvalidLineEnd(ExceptionWithContext): + ERROR_TEXT = "Each line must end with CR LF or LF except for the last line " \ + "of the file. This line ends with \"%(bad_line_end)s\"." + +class StopWithMultipleRouteTypes(ExceptionWithContext): + ERROR_TEXT = "Stop %(stop_name)s (ID=%(stop_id)s) belongs to both " \ + "subway (ID=%(route_id1)s) and bus line (ID=%(route_id2)s)." + +class TooFastTravel(ExceptionWithContext): + def FormatProblem(self, d=None): + if not d: + d = self.GetDictToFormat() + if not d['speed']: + return "High speed travel detected in trip %(trip_id)s: %(prev_stop)s" \ + " to %(next_stop)s. %(dist).0f meters in %(time)d seconds." % d + else: + return "High speed travel detected in trip %(trip_id)s: %(prev_stop)s" \ + " to %(next_stop)s. %(dist).0f meters in %(time)d seconds." \ + " (%(speed).0f km/h)." % d + def __cmp__(self, y): + # Sort in decreasing order because more distance is more significant. We + # can't sort by speed because not all TooFastTravel objects have a speed. + return cmp(y.dist, self.dist) + +class DuplicateTrip(ExceptionWithContext): + ERROR_TEXT = "Trip %(trip_id1)s of route %(route_id1)s might be duplicated " \ + "with trip %(trip_id2)s of route %(route_id2)s. They go " \ + "through the same stops with same service." + +class OtherProblem(ExceptionWithContext): + ERROR_TEXT = '%(description)s' + + +class ExceptionProblemReporter(ProblemReporter): + def __init__(self, raise_warnings=False): + ProblemReporterBase.__init__(self) + self.raise_warnings = raise_warnings + + def _Report(self, e): + if self.raise_warnings or e.IsError(): + raise e + else: + ProblemReporter._Report(self, e) + + +default_problem_reporter = ExceptionProblemReporter() + +# Add a default handler to send log messages to console +console = logging.StreamHandler() +console.setLevel(logging.WARNING) +log = logging.getLogger("schedule_builder") +log.addHandler(console) + + +class Error(Exception): + pass + + +def IsValidURL(url): + """Checks the validity of a URL value.""" + # TODO: Add more thorough checking of URL + return url.startswith(u'http://') or url.startswith(u'https://') + + +def IsValidColor(color): + """Checks the validity of a hex color value.""" + return not re.match('^[0-9a-fA-F]{6}$', color) == None + + +def ColorLuminance(color): + """Compute the brightness of an sRGB color using the formula from + http://www.w3.org/TR/2000/WD-AERT-20000426#color-contrast. + + Args: + color: a string of six hex digits in the format verified by IsValidColor(). + + Returns: + A floating-point number between 0.0 (black) and 255.0 (white). """ + r = int(color[0:2], 16) + g = int(color[2:4], 16) + b = int(color[4:6], 16) + return (299*r + 587*g + 114*b) / 1000.0 + + +def IsEmpty(value): + return value is None or (isinstance(value, basestring) and not value.strip()) + + +def FindUniqueId(dic): + """Return a string not used as a key in the dictionary dic""" + name = str(len(dic)) + while name in dic: + name = str(random.randint(1, 999999999)) + return name + + +def TimeToSecondsSinceMidnight(time_string): + """Convert HHH:MM:SS into seconds since midnight. + + For example "01:02:03" returns 3723. The leading zero of the hours may be + omitted. HH may be more than 23 if the time is on the following day.""" + m = re.match(r'(\d{1,3}):([0-5]\d):([0-5]\d)$', time_string) + # ignored: matching for leap seconds + if not m: + raise Error, 'Bad HH:MM:SS "%s"' % time_string + return int(m.group(1)) * 3600 + int(m.group(2)) * 60 + int(m.group(3)) + + +def FormatSecondsSinceMidnight(s): + """Formats an int number of seconds past midnight into a string + as "HH:MM:SS".""" + return "%02d:%02d:%02d" % (s / 3600, (s / 60) % 60, s % 60) + + +def DateStringToDateObject(date_string): + """Return a date object for a string "YYYYMMDD".""" + # If this becomes a bottleneck date objects could be cached + return datetime.date(int(date_string[0:4]), int(date_string[4:6]), + int(date_string[6:8])) + + +def FloatStringToFloat(float_string): + """Convert a float as a string to a float or raise an exception""" + # Will raise TypeError unless a string + if not re.match(r"^[+-]?\d+(\.\d+)?$", float_string): + raise ValueError() + return float(float_string) + + +def NonNegIntStringToInt(int_string): + """Convert an non-negative integer string to an int or raise an exception""" + # Will raise TypeError unless a string + if not re.match(r"^(?:0|[1-9]\d*)$", int_string): + raise ValueError() + return int(int_string) + + +EARTH_RADIUS = 6378135 # in meters +def ApproximateDistance(degree_lat1, degree_lng1, degree_lat2, degree_lng2): + """Compute approximate distance between two points in meters. Assumes the + Earth is a sphere.""" + # TODO: change to ellipsoid approximation, such as + # http://www.codeguru.com/Cpp/Cpp/algorithms/article.php/c5115/ + lat1 = math.radians(degree_lat1) + lng1 = math.radians(degree_lng1) + lat2 = math.radians(degree_lat2) + lng2 = math.radians(degree_lng2) + dlat = math.sin(0.5 * (lat2 - lat1)) + dlng = math.sin(0.5 * (lng2 - lng1)) + x = dlat * dlat + dlng * dlng * math.cos(lat1) * math.cos(lat2) + return EARTH_RADIUS * (2 * math.atan2(math.sqrt(x), + math.sqrt(max(0.0, 1.0 - x)))) + + +def ApproximateDistanceBetweenStops(stop1, stop2): + """Compute approximate distance between two stops in meters. Assumes the + Earth is a sphere.""" + return ApproximateDistance(stop1.stop_lat, stop1.stop_lon, + stop2.stop_lat, stop2.stop_lon) + + +class GenericGTFSObject(object): + """Object with arbitrary attributes which may be added to a schedule. + + This class should be used as the base class for GTFS objects which may + be stored in a Schedule. It defines some methods for reading and writing + attributes. If self._schedule is None than the object is not in a Schedule. + + Subclasses must: + * define an __init__ method which sets the _schedule member to None or a + weakref to a Schedule + * Set the _TABLE_NAME class variable to a name such as 'stops', 'agency', ... + * define methods to validate objects of that type + """ + def __getitem__(self, name): + """Return a unicode or str representation of name or "" if not set.""" + if name in self.__dict__ and self.__dict__[name] is not None: + return "%s" % self.__dict__[name] + else: + return "" + + def __getattr__(self, name): + """Return None or the default value if name is a known attribute. + + This method is only called when name is not found in __dict__. + """ + if name in self.__class__._FIELD_NAMES: + return None + else: + raise AttributeError(name) + + def iteritems(self): + """Return a iterable for (name, value) pairs of public attributes.""" + for name, value in self.__dict__.iteritems(): + if (not name) or name[0] == "_": + continue + yield name, value + + def __setattr__(self, name, value): + """Set an attribute, adding name to the list of columns as needed.""" + object.__setattr__(self, name, value) + if name[0] != '_' and self._schedule: + self._schedule.AddTableColumn(self.__class__._TABLE_NAME, name) + + def __eq__(self, other): + """Return true iff self and other are equivalent""" + if not other: + return False + + if id(self) == id(other): + return True + + for k in self.keys().union(other.keys()): + # use __getitem__ which returns "" for missing columns values + if self[k] != other[k]: + return False + return True + + def __ne__(self, other): + return not self.__eq__(other) + + def __repr__(self): + return "<%s %s>" % (self.__class__.__name__, sorted(self.iteritems())) + + def keys(self): + """Return iterable of columns used by this object.""" + columns = set() + for name in vars(self): + if (not name) or name[0] == "_": + continue + columns.add(name) + return columns + + def _ColumnNames(self): + return self.keys() + + +class Stop(GenericGTFSObject): + """Represents a single stop. A stop must have a latitude, longitude and name. + + Callers may assign arbitrary values to instance attributes. + Stop.ParseAttributes validates attributes according to GTFS and converts some + into native types. ParseAttributes may delete invalid attributes. + Accessing an attribute that is a column in GTFS will return None if this + object does not have a value or it is ''. + A Stop object acts like a dict with string values. + + Attributes: + stop_lat: a float representing the latitude of the stop + stop_lon: a float representing the longitude of the stop + All other attributes are strings. + """ + _REQUIRED_FIELD_NAMES = ['stop_id', 'stop_name', 'stop_lat', 'stop_lon'] + _FIELD_NAMES = _REQUIRED_FIELD_NAMES + \ + ['stop_desc', 'zone_id', 'stop_url', 'stop_code', + 'location_type', 'parent_station'] + _TABLE_NAME = 'stops' + + def __init__(self, lat=None, lng=None, name=None, stop_id=None, + field_dict=None, stop_code=None): + """Initialize a new Stop object. + + Args: + field_dict: A dictionary mapping attribute name to unicode string + lat: a float, ignored when field_dict is present + lng: a float, ignored when field_dict is present + name: a string, ignored when field_dict is present + stop_id: a string, ignored when field_dict is present + stop_code: a string, ignored when field_dict is present + """ + self._schedule = None + if field_dict: + if isinstance(field_dict, Stop): + # Special case so that we don't need to re-parse the attributes to + # native types iteritems returns all attributes that don't start with _ + for k, v in field_dict.iteritems(): + self.__dict__[k] = v + else: + self.__dict__.update(field_dict) + else: + if lat is not None: + self.stop_lat = lat + if lng is not None: + self.stop_lon = lng + if name is not None: + self.stop_name = name + if stop_id is not None: + self.stop_id = stop_id + if stop_code is not None: + self.stop_code = stop_code + + def GetTrips(self, schedule=None): + """Return iterable containing trips that visit this stop.""" + return [trip for trip, ss in self._GetTripSequence(schedule)] + + def _GetTripSequence(self, schedule=None): + """Return a list of (trip, stop_sequence) for all trips visiting this stop. + + A trip may be in the list multiple times with different index. + stop_sequence is an integer. + + Args: + schedule: Deprecated, do not use. + """ + if schedule is None: + schedule = getattr(self, "_schedule", None) + if schedule is None: + warnings.warn("No longer supported. _schedule attribute is used to get " + "stop_times table", DeprecationWarning) + cursor = schedule._connection.cursor() + cursor.execute("SELECT trip_id,stop_sequence FROM stop_times " + "WHERE stop_id=?", + (self.stop_id, )) + return [(schedule.GetTrip(row[0]), row[1]) for row in cursor] + + def _GetTripIndex(self, schedule=None): + """Return a list of (trip, index). + + trip: a Trip object + index: an offset in trip.GetStopTimes() + """ + trip_index = [] + for trip, sequence in self._GetTripSequence(schedule): + for index, st in enumerate(trip.GetStopTimes()): + if st.stop_sequence == sequence: + trip_index.append((trip, index)) + break + else: + raise RuntimeError("stop_sequence %d not found in trip_id %s" % + sequence, trip.trip_id) + return trip_index + + def GetStopTimeTrips(self, schedule=None): + """Return a list of (time, (trip, index), is_timepoint). + + time: an integer. It might be interpolated. + trip: a Trip object. + index: the offset of this stop in trip.GetStopTimes(), which may be + different from the stop_sequence. + is_timepoint: a bool + """ + time_trips = [] + for trip, index in self._GetTripIndex(schedule): + secs, stoptime, is_timepoint = trip.GetTimeInterpolatedStops()[index] + time_trips.append((secs, (trip, index), is_timepoint)) + return time_trips + + def ParseAttributes(self, problems): + """Parse all attributes, calling problems as needed.""" + # Need to use items() instead of iteritems() because _CheckAndSetAttr may + # modify self.__dict__ + for name, value in vars(self).items(): + if name[0] == "_": + continue + self._CheckAndSetAttr(name, value, problems) + + def _CheckAndSetAttr(self, name, value, problems): + """If value is valid for attribute name store it. + + If value is not valid call problems. Return a new value of the correct type + or None if value couldn't be converted. + """ + if name == 'stop_lat': + try: + if isinstance(value, (float, int)): + self.stop_lat = value + else: + self.stop_lat = FloatStringToFloat(value) + except (ValueError, TypeError): + problems.InvalidValue('stop_lat', value) + del self.stop_lat + else: + if self.stop_lat > 90 or self.stop_lat < -90: + problems.InvalidValue('stop_lat', value) + elif name == 'stop_lon': + try: + if isinstance(value, (float, int)): + self.stop_lon = value + else: + self.stop_lon = FloatStringToFloat(value) + except (ValueError, TypeError): + problems.InvalidValue('stop_lon', value) + del self.stop_lon + else: + if self.stop_lon > 180 or self.stop_lon < -180: + problems.InvalidValue('stop_lon', value) + elif name == 'stop_url': + if value and not IsValidURL(value): + problems.InvalidValue('stop_url', value) + del self.stop_url + elif name == 'location_type': + if value == '': + self.location_type = 0 + else: + try: + self.location_type = int(value) + except (ValueError, TypeError): + problems.InvalidValue('location_type', value) + del self.location_type + else: + if self.location_type not in (0, 1): + problems.InvalidValue('location_type', value, type=TYPE_WARNING) + + def __getattr__(self, name): + """Return None or the default value if name is a known attribute. + + This method is only called when name is not found in __dict__. + """ + if name == "location_type": + return 0 + elif name == "trip_index": + return self._GetTripIndex() + elif name in Stop._FIELD_NAMES: + return None + else: + raise AttributeError(name) + + def Validate(self, problems=default_problem_reporter): + # First check that all required fields are present because ParseAttributes + # may remove invalid attributes. + for required in Stop._REQUIRED_FIELD_NAMES: + if IsEmpty(getattr(self, required, None)): + # TODO: For now I'm keeping the API stable but it would be cleaner to + # treat whitespace stop_id as invalid, instead of missing + problems.MissingValue(required) + + # Check individual values and convert to native types + self.ParseAttributes(problems) + + # Check that this object is consistent with itself + if (self.stop_lat is not None and self.stop_lon is not None and + abs(self.stop_lat) < 1.0) and (abs(self.stop_lon) < 1.0): + problems.InvalidValue('stop_lat', self.stop_lat, + 'Stop location too close to 0, 0', + type=TYPE_WARNING) + if (self.stop_desc is not None and self.stop_name is not None and + self.stop_desc and self.stop_name and + not IsEmpty(self.stop_desc) and + self.stop_name.strip().lower() == self.stop_desc.strip().lower()): + problems.InvalidValue('stop_desc', self.stop_desc, + 'stop_desc should not be the same as stop_name') + + if self.parent_station and self.location_type == 1: + problems.InvalidValue('parent_station', self.parent_station, + 'Stop row with location_type=1 (a station) must ' + 'not have a parent_station') + + +class Route(GenericGTFSObject): + """Represents a single route.""" + + _REQUIRED_FIELD_NAMES = [ + 'route_id', 'route_short_name', 'route_long_name', 'route_type' + ] + _FIELD_NAMES = _REQUIRED_FIELD_NAMES + [ + 'agency_id', 'route_desc', 'route_url', 'route_color', 'route_text_color' + ] + _ROUTE_TYPES = { + 0: {'name':'Tram', 'max_speed':100}, + 1: {'name':'Subway', 'max_speed':150}, + 2: {'name':'Rail', 'max_speed':300}, + 3: {'name':'Bus', 'max_speed':100}, + 4: {'name':'Ferry', 'max_speed':80}, + 5: {'name':'Cable Car', 'max_speed':50}, + 6: {'name':'Gondola', 'max_speed':50}, + 7: {'name':'Funicular', 'max_speed':50}, + } + # Create a reverse lookup dict of route type names to route types. + _ROUTE_TYPE_IDS = set(_ROUTE_TYPES.keys()) + _ROUTE_TYPE_NAMES = dict((v['name'], k) for k, v in _ROUTE_TYPES.items()) + _TABLE_NAME = 'routes' + + def __init__(self, short_name=None, long_name=None, route_type=None, + route_id=None, agency_id=None, field_dict=None): + self._schedule = None + self._trips = [] + + if not field_dict: + field_dict = {} + if short_name is not None: + field_dict['route_short_name'] = short_name + if long_name is not None: + field_dict['route_long_name'] = long_name + if route_type is not None: + if route_type in Route._ROUTE_TYPE_NAMES: + self.route_type = Route._ROUTE_TYPE_NAMES[route_type] + else: + field_dict['route_type'] = route_type + if route_id is not None: + field_dict['route_id'] = route_id + if agency_id is not None: + field_dict['agency_id'] = agency_id + self.__dict__.update(field_dict) + + def AddTrip(self, schedule, headsign, service_period=None, trip_id=None): + """ Adds a trip to this route. + + Args: + headsign: headsign of the trip as a string + + Returns: + a new Trip object + """ + if trip_id is None: + trip_id = unicode(len(schedule.trips)) + if service_period is None: + service_period = schedule.GetDefaultServicePeriod() + trip = Trip(route=self, headsign=headsign, service_period=service_period, + trip_id=trip_id) + schedule.AddTripObject(trip) + return trip + + def _AddTripObject(self, trip): + # Only class Schedule may call this. Users of the API should call + # Route.AddTrip or schedule.AddTripObject. + self._trips.append(trip) + + def __getattr__(self, name): + """Return None or the default value if name is a known attribute. + + This method overrides GenericGTFSObject.__getattr__ to provide backwards + compatible access to trips. + """ + if name == 'trips': + return self._trips + else: + return GenericGTFSObject.__getattr__(self, name) + + def GetPatternIdTripDict(self): + """Return a dictionary that maps pattern_id to a list of Trip objects.""" + d = {} + for t in self._trips: + d.setdefault(t.pattern_id, []).append(t) + return d + + def Validate(self, problems=default_problem_reporter): + if IsEmpty(self.route_id): + problems.MissingValue('route_id') + if IsEmpty(self.route_type): + problems.MissingValue('route_type') + + if IsEmpty(self.route_short_name) and IsEmpty(self.route_long_name): + problems.InvalidValue('route_short_name', + self.route_short_name, + 'Both route_short_name and ' + 'route_long name are blank.') + + if self.route_short_name and len(self.route_short_name) > 6: + problems.InvalidValue('route_short_name', + self.route_short_name, + 'This route_short_name is relatively long, which ' + 'probably means that it contains a place name. ' + 'You should only use this field to hold a short ' + 'code that riders use to identify a route. ' + 'If this route doesn\'t have such a code, it\'s ' + 'OK to leave this field empty.', type=TYPE_WARNING) + + if self.route_short_name and self.route_long_name: + short_name = self.route_short_name.strip().lower() + long_name = self.route_long_name.strip().lower() + if (long_name.startswith(short_name + ' ') or + long_name.startswith(short_name + '(') or + long_name.startswith(short_name + '-')): + problems.InvalidValue('route_long_name', + self.route_long_name, + 'route_long_name shouldn\'t contain ' + 'the route_short_name value, as both ' + 'fields are often displayed ' + 'side-by-side.', type=TYPE_WARNING) + if long_name == short_name: + problems.InvalidValue('route_long_name', + self.route_long_name, + 'route_long_name shouldn\'t be the same ' + 'the route_short_name value, as both ' + 'fields are often displayed ' + 'side-by-side. It\'s OK to omit either the ' + 'short or long name (but not both).', + type=TYPE_WARNING) + if (self.route_desc and + ((self.route_desc == self.route_short_name) or + (self.route_desc == self.route_long_name))): + problems.InvalidValue('route_desc', + self.route_desc, + 'route_desc shouldn\'t be the same as ' + 'route_short_name or route_long_name') + + if self.route_type is not None: + try: + if not isinstance(self.route_type, int): + self.route_type = NonNegIntStringToInt(self.route_type) + except (TypeError, ValueError): + problems.InvalidValue('route_type', self.route_type) + else: + if self.route_type not in Route._ROUTE_TYPE_IDS: + problems.InvalidValue('route_type', + self.route_type, + type=TYPE_WARNING) + + if self.route_url and not IsValidURL(self.route_url): + problems.InvalidValue('route_url', self.route_url) + + txt_lum = ColorLuminance('000000') # black (default) + bg_lum = ColorLuminance('ffffff') # white (default) + if self.route_color: + if IsValidColor(self.route_color): + bg_lum = ColorLuminance(self.route_color) + else: + problems.InvalidValue('route_color', self.route_color, + 'route_color should be a valid color description ' + 'which consists of 6 hexadecimal characters ' + 'representing the RGB values. Example: 44AA06') + if self.route_text_color: + if IsValidColor(self.route_text_color): + txt_lum = ColorLuminance(self.route_text_color) + else: + problems.InvalidValue('route_text_color', self.route_text_color, + 'route_text_color should be a valid color ' + 'description, which consists of 6 hexadecimal ' + 'characters representing the RGB values. ' + 'Example: 44AA06') + if abs(txt_lum - bg_lum) < 510/7.: + # http://www.w3.org/TR/2000/WD-AERT-20000426#color-contrast recommends + # a threshold of 125, but that is for normal text and too harsh for + # big colored logos like line names, so we keep the original threshold + # from r541 (but note that weight has shifted between RGB components). + problems.InvalidValue('route_color', self.route_color, + 'The route_text_color and route_color should ' + 'be set to contrasting colors, as they are used ' + 'as the text and background color (respectively) ' + 'for displaying route names. When left blank, ' + 'route_text_color defaults to 000000 (black) and ' + 'route_color defaults to FFFFFF (white). A common ' + 'source of issues here is setting route_color to ' + 'a dark color, while leaving route_text_color set ' + 'to black. In this case, route_text_color should ' + 'be set to a lighter color like FFFFFF to ensure ' + 'a legible contrast between the two.', + type=TYPE_WARNING) + + +def SortListOfTripByTime(trips): + trips.sort(key=Trip.GetStartTime) + + +class StopTime(object): + """ + Represents a single stop of a trip. StopTime contains most of the columns + from the stop_times.txt file. It does not contain trip_id, which is implied + by the Trip used to access it. + + See the Google Transit Feed Specification for the semantic details. + + stop: A Stop object + arrival_time: str in the form HH:MM:SS; readonly after __init__ + departure_time: str in the form HH:MM:SS; readonly after __init__ + arrival_secs: int number of seconds since midnight + departure_secs: int number of seconds since midnight + stop_headsign: str + pickup_type: int + drop_off_type: int + shape_dist_traveled: float + stop_id: str; readonly + stop_time: The only time given for this stop. If present, it is used + for both arrival and departure time. + stop_sequence: int + """ + _REQUIRED_FIELD_NAMES = ['trip_id', 'arrival_time', 'departure_time', + 'stop_id', 'stop_sequence'] + _OPTIONAL_FIELD_NAMES = ['stop_headsign', 'pickup_type', + 'drop_off_type', 'shape_dist_traveled'] + _FIELD_NAMES = _REQUIRED_FIELD_NAMES + _OPTIONAL_FIELD_NAMES + _SQL_FIELD_NAMES = ['trip_id', 'arrival_secs', 'departure_secs', + 'stop_id', 'stop_sequence', 'stop_headsign', + 'pickup_type', 'drop_off_type', 'shape_dist_traveled'] + + __slots__ = ('arrival_secs', 'departure_secs', 'stop_headsign', 'stop', + 'stop_headsign', 'pickup_type', 'drop_off_type', + 'shape_dist_traveled', 'stop_sequence') + def __init__(self, problems, stop, + arrival_time=None, departure_time=None, + stop_headsign=None, pickup_type=None, drop_off_type=None, + shape_dist_traveled=None, arrival_secs=None, + departure_secs=None, stop_time=None, stop_sequence=None): + if stop_time != None: + arrival_time = departure_time = stop_time + + if arrival_secs != None: + self.arrival_secs = arrival_secs + elif arrival_time in (None, ""): + self.arrival_secs = None # Untimed + arrival_time = None + else: + try: + self.arrival_secs = TimeToSecondsSinceMidnight(arrival_time) + except Error: + problems.InvalidValue('arrival_time', arrival_time) + self.arrival_secs = None + + if departure_secs != None: + self.departure_secs = departure_secs + elif departure_time in (None, ""): + self.departure_secs = None + departure_time = None + else: + try: + self.departure_secs = TimeToSecondsSinceMidnight(departure_time) + except Error: + problems.InvalidValue('departure_time', departure_time) + self.departure_secs = None + + if not isinstance(stop, Stop): + # Not quite correct, but better than letting the problem propagate + problems.InvalidValue('stop', stop) + self.stop = stop + self.stop_headsign = stop_headsign + + if pickup_type in (None, ""): + self.pickup_type = None + else: + try: + pickup_type = int(pickup_type) + except ValueError: + problems.InvalidValue('pickup_type', pickup_type) + else: + if pickup_type < 0 or pickup_type > 3: + problems.InvalidValue('pickup_type', pickup_type) + self.pickup_type = pickup_type + + if drop_off_type in (None, ""): + self.drop_off_type = None + else: + try: + drop_off_type = int(drop_off_type) + except ValueError: + problems.InvalidValue('drop_off_type', drop_off_type) + else: + if drop_off_type < 0 or drop_off_type > 3: + problems.InvalidValue('drop_off_type', drop_off_type) + self.drop_off_type = drop_off_type + + if (self.pickup_type == 1 and self.drop_off_type == 1 and + self.arrival_secs == None and self.departure_secs == None): + problems.OtherProblem('This stop time has a pickup_type and ' + 'drop_off_type of 1, indicating that riders ' + 'can\'t get on or off here. Since it doesn\'t ' + 'define a timepoint either, this entry serves no ' + 'purpose and should be excluded from the trip.', + type=TYPE_WARNING) + + if ((self.arrival_secs != None) and (self.departure_secs != None) and + (self.departure_secs < self.arrival_secs)): + problems.InvalidValue('departure_time', departure_time, + 'The departure time at this stop (%s) is before ' + 'the arrival time (%s). This is often caused by ' + 'problems in the feed exporter\'s time conversion') + + # If the caller passed a valid arrival time but didn't attempt to pass a + # departure time complain + if (self.arrival_secs != None and + self.departure_secs == None and departure_time == None): + # self.departure_secs might be None because departure_time was invalid, + # so we need to check both + problems.MissingValue('departure_time', + 'arrival_time and departure_time should either ' + 'both be provided or both be left blank. ' + 'It\'s OK to set them both to the same value.') + # If the caller passed a valid departure time but didn't attempt to pass a + # arrival time complain + if (self.departure_secs != None and + self.arrival_secs == None and arrival_time == None): + problems.MissingValue('arrival_time', + 'arrival_time and departure_time should either ' + 'both be provided or both be left blank. ' + 'It\'s OK to set them both to the same value.') + + if shape_dist_traveled in (None, ""): + self.shape_dist_traveled = None + else: + try: + self.shape_dist_traveled = float(shape_dist_traveled) + except ValueError: + problems.InvalidValue('shape_dist_traveled', shape_dist_traveled) + + if stop_sequence is not None: + self.stop_sequence = stop_sequence + + def GetFieldValuesTuple(self, trip_id): + """Return a tuple that outputs a row of _FIELD_NAMES. + + trip must be provided because it is not stored in StopTime. + """ + result = [] + for fn in StopTime._FIELD_NAMES: + if fn == 'trip_id': + result.append(trip_id) + else: + result.append(getattr(self, fn) or '' ) + return tuple(result) + + def GetSqlValuesTuple(self, trip_id): + result = [] + for fn in StopTime._SQL_FIELD_NAMES: + if fn == 'trip_id': + result.append(trip_id) + else: + # This might append None, which will be inserted into SQLite as NULL + result.append(getattr(self, fn)) + return tuple(result) + + def GetTimeSecs(self): + """Return the first of arrival_secs and departure_secs that is not None. + If both are None return None.""" + if self.arrival_secs != None: + return self.arrival_secs + elif self.departure_secs != None: + return self.departure_secs + else: + return None + + def __getattr__(self, name): + if name == 'stop_id': + return self.stop.stop_id + elif name == 'arrival_time': + return (self.arrival_secs != None and + FormatSecondsSinceMidnight(self.arrival_secs) or '') + elif name == 'departure_time': + return (self.departure_secs != None and + FormatSecondsSinceMidnight(self.departure_secs) or '') + elif name == 'shape_dist_traveled': + return '' + raise AttributeError(name) + + +class Trip(GenericGTFSObject): + _REQUIRED_FIELD_NAMES = ['route_id', 'service_id', 'trip_id'] + _FIELD_NAMES = _REQUIRED_FIELD_NAMES + [ + 'trip_headsign', 'direction_id', 'block_id', 'shape_id' + ] + _FIELD_NAMES_HEADWAY = ['trip_id', 'start_time', 'end_time', 'headway_secs'] + _TABLE_NAME= "trips" + + def __init__(self, headsign=None, service_period=None, + route=None, trip_id=None, field_dict=None): + self._schedule = None + self._headways = [] # [(start_time, end_time, headway_secs)] + if not field_dict: + field_dict = {} + if headsign is not None: + field_dict['trip_headsign'] = headsign + if route: + field_dict['route_id'] = route.route_id + if trip_id is not None: + field_dict['trip_id'] = trip_id + if service_period is not None: + field_dict['service_id'] = service_period.service_id + # Earlier versions of transitfeed.py assigned self.service_period here + # and allowed the caller to set self.service_id. Schedule.Validate + # checked the service_id attribute if it was assigned and changed it to a + # service_period attribute. Now only the service_id attribute is used and + # it is validated by Trip.Validate. + if service_period is not None: + # For backwards compatibility + self.service_id = service_period.service_id + self.__dict__.update(field_dict) + + def GetFieldValuesTuple(self): + return [getattr(self, fn) or '' for fn in Trip._FIELD_NAMES] + + def AddStopTime(self, stop, problems=None, schedule=None, **kwargs): + """Add a stop to this trip. Stops must be added in the order visited. + + Args: + stop: A Stop object + kwargs: remaining keyword args passed to StopTime.__init__ + + Returns: + None + """ + if problems is None: + # TODO: delete this branch when StopTime.__init__ doesn't need a + # ProblemReporter + problems = default_problem_reporter + stoptime = StopTime(problems=problems, stop=stop, **kwargs) + self.AddStopTimeObject(stoptime, schedule) + + def _AddStopTimeObjectUnordered(self, stoptime, schedule): + """Add StopTime object to this trip. + + The trip isn't checked for duplicate sequence numbers so it must be + validated later.""" + cursor = schedule._connection.cursor() + insert_query = "INSERT INTO stop_times (%s) VALUES (%s);" % ( + ','.join(StopTime._SQL_FIELD_NAMES), + ','.join(['?'] * len(StopTime._SQL_FIELD_NAMES))) + cursor = schedule._connection.cursor() + cursor.execute( + insert_query, stoptime.GetSqlValuesTuple(self.trip_id)) + + def ReplaceStopTimeObject(self, stoptime, schedule=None): + """Replace a StopTime object from this trip with the given one. + + Keys the StopTime object to be replaced by trip_id, stop_sequence + and stop_id as 'stoptime', with the object 'stoptime'. + """ + + if schedule is None: + schedule = self._schedule + + new_secs = stoptime.GetTimeSecs() + cursor = schedule._connection.cursor() + cursor.execute("DELETE FROM stop_times WHERE trip_id=? and " + "stop_sequence=? and stop_id=?", + (self.trip_id, stoptime.stop_sequence, stoptime.stop_id)) + if cursor.rowcount == 0: + raise Error, 'Attempted replacement of StopTime object which does not exist' + self._AddStopTimeObjectUnordered(stoptime, schedule) + + def AddStopTimeObject(self, stoptime, schedule=None, problems=None): + """Add a StopTime object to the end of this trip. + + Args: + stoptime: A StopTime object. Should not be reused in multiple trips. + schedule: Schedule object containing this trip which must be + passed to Trip.__init__ or here + problems: ProblemReporter object for validating the StopTime in its new + home + + Returns: + None + """ + if schedule is None: + schedule = self._schedule + if schedule is None: + warnings.warn("No longer supported. _schedule attribute is used to get " + "stop_times table", DeprecationWarning) + if problems is None: + problems = schedule.problem_reporter + + new_secs = stoptime.GetTimeSecs() + cursor = schedule._connection.cursor() + cursor.execute("SELECT max(stop_sequence), max(arrival_secs), " + "max(departure_secs) FROM stop_times WHERE trip_id=?", + (self.trip_id,)) + row = cursor.fetchone() + if row[0] is None: + # This is the first stop_time of the trip + stoptime.stop_sequence = 1 + if new_secs == None: + problems.OtherProblem( + 'No time for first StopTime of trip_id "%s"' % (self.trip_id,)) + else: + stoptime.stop_sequence = row[0] + 1 + prev_secs = max(row[1], row[2]) + if new_secs != None and new_secs < prev_secs: + problems.OtherProblem( + 'out of order stop time for stop_id=%s trip_id=%s %s < %s' % + (EncodeUnicode(stoptime.stop_id), EncodeUnicode(self.trip_id), + FormatSecondsSinceMidnight(new_secs), + FormatSecondsSinceMidnight(prev_secs))) + self._AddStopTimeObjectUnordered(stoptime, schedule) + + def GetTimeStops(self): + """Return a list of (arrival_secs, departure_secs, stop) tuples. + + Caution: arrival_secs and departure_secs may be 0, a false value meaning a + stop at midnight or None, a false value meaning the stop is untimed.""" + return [(st.arrival_secs, st.departure_secs, st.stop) for st in + self.GetStopTimes()] + + def GetCountStopTimes(self): + """Return the number of stops made by this trip.""" + cursor = self._schedule._connection.cursor() + cursor.execute( + 'SELECT count(*) FROM stop_times WHERE trip_id=?', (self.trip_id,)) + return cursor.fetchone()[0] + + def GetTimeInterpolatedStops(self): + """Return a list of (secs, stoptime, is_timepoint) tuples. + + secs will always be an int. If the StopTime object does not have explict + times this method guesses using distance. stoptime is a StopTime object and + is_timepoint is a bool. + + Raises: + ValueError if this trip does not have the times needed to interpolate + """ + rv = [] + + stoptimes = self.GetStopTimes() + # If there are no stoptimes [] is the correct return value but if the start + # or end are missing times there is no correct return value. + if not stoptimes: + return [] + if (stoptimes[0].GetTimeSecs() is None or + stoptimes[-1].GetTimeSecs() is None): + raise ValueError("%s must have time at first and last stop" % (self)) + + cur_timepoint = None + next_timepoint = None + distance_between_timepoints = 0 + distance_traveled_between_timepoints = 0 + + for i, st in enumerate(stoptimes): + if st.GetTimeSecs() != None: + cur_timepoint = st + distance_between_timepoints = 0 + distance_traveled_between_timepoints = 0 + if i + 1 < len(stoptimes): + k = i + 1 + distance_between_timepoints += ApproximateDistanceBetweenStops(stoptimes[k-1].stop, stoptimes[k].stop) + while stoptimes[k].GetTimeSecs() == None: + k += 1 + distance_between_timepoints += ApproximateDistanceBetweenStops(stoptimes[k-1].stop, stoptimes[k].stop) + next_timepoint = stoptimes[k] + rv.append( (st.GetTimeSecs(), st, True) ) + else: + distance_traveled_between_timepoints += ApproximateDistanceBetweenStops(stoptimes[i-1].stop, st.stop) + distance_percent = distance_traveled_between_timepoints / distance_between_timepoints + total_time = next_timepoint.GetTimeSecs() - cur_timepoint.GetTimeSecs() + time_estimate = distance_percent * total_time + cur_timepoint.GetTimeSecs() + rv.append( (int(round(time_estimate)), st, False) ) + + return rv + + def ClearStopTimes(self): + """Remove all stop times from this trip. + + StopTime objects previously returned by GetStopTimes are unchanged but are + no longer associated with this trip. + """ + cursor = self._schedule._connection.cursor() + cursor.execute('DELETE FROM stop_times WHERE trip_id=?', (self.trip_id,)) + + def GetStopTimes(self, problems=None): + """Return a sorted list of StopTime objects for this trip.""" + # In theory problems=None should be safe because data from database has been + # validated. See comment in _LoadStopTimes for why this isn't always true. + cursor = self._schedule._connection.cursor() + cursor.execute( + 'SELECT arrival_secs,departure_secs,stop_headsign,pickup_type,' + 'drop_off_type,shape_dist_traveled,stop_id,stop_sequence FROM ' + 'stop_times WHERE ' + 'trip_id=? ORDER BY stop_sequence', (self.trip_id,)) + stop_times = [] + for row in cursor.fetchall(): + stop = self._schedule.GetStop(row[6]) + stop_times.append(StopTime(problems=problems, stop=stop, arrival_secs=row[0], + departure_secs=row[1], + stop_headsign=row[2], + pickup_type=row[3], + drop_off_type=row[4], + shape_dist_traveled=row[5], + stop_sequence=row[7])) + return stop_times + + def GetHeadwayStopTimes(self, problems=None): + """Return a list of StopTime objects for each headway-based run. + + Returns: + a list of list of StopTime objects. Each list of StopTime objects + represents one run. If this trip doesn't have headways returns an empty + list. + """ + stoptimes_list = [] # list of stoptime lists to be returned + stoptime_pattern = self.GetStopTimes() + first_secs = stoptime_pattern[0].arrival_secs # first time of the trip + # for each start time of a headway run + for run_secs in self.GetHeadwayStartTimes(): + # stop time list for a headway run + stoptimes = [] + # go through the pattern and generate stoptimes + for st in stoptime_pattern: + arrival_secs, departure_secs = None, None # default value if the stoptime is not timepoint + if st.arrival_secs != None: + arrival_secs = st.arrival_secs - first_secs + run_secs + if st.departure_secs != None: + departure_secs = st.departure_secs - first_secs + run_secs + # append stoptime + stoptimes.append(StopTime(problems=problems, stop=st.stop, + arrival_secs=arrival_secs, + departure_secs=departure_secs, + stop_headsign=st.stop_headsign, + pickup_type=st.pickup_type, + drop_off_type=st.drop_off_type, + shape_dist_traveled=st.shape_dist_traveled, + stop_sequence=st.stop_sequence)) + # add stoptimes to the stoptimes_list + stoptimes_list.append ( stoptimes ) + return stoptimes_list + + def GetStartTime(self, problems=default_problem_reporter): + """Return the first time of the trip. TODO: For trips defined by frequency + return the first time of the first trip.""" + cursor = self._schedule._connection.cursor() + cursor.execute( + 'SELECT arrival_secs,departure_secs FROM stop_times WHERE ' + 'trip_id=? ORDER BY stop_sequence LIMIT 1', (self.trip_id,)) + (arrival_secs, departure_secs) = cursor.fetchone() + if arrival_secs != None: + return arrival_secs + elif departure_secs != None: + return departure_secs + else: + problems.InvalidValue('departure_time', '', + 'The first stop_time in trip %s is missing ' + 'times.' % self.trip_id) + + def GetHeadwayStartTimes(self): + """Return a list of start time for each headway-based run. + + Returns: + a sorted list of seconds since midnight, the start time of each run. If + this trip doesn't have headways returns an empty list.""" + start_times = [] + # for each headway period of the trip + for start_secs, end_secs, headway_secs in self.GetHeadwayPeriodTuples(): + # reset run secs to the start of the timeframe + run_secs = start_secs + while run_secs < end_secs: + start_times.append(run_secs) + # increment current run secs by headway secs + run_secs += headway_secs + return start_times + + def GetEndTime(self, problems=default_problem_reporter): + """Return the last time of the trip. TODO: For trips defined by frequency + return the last time of the last trip.""" + cursor = self._schedule._connection.cursor() + cursor.execute( + 'SELECT arrival_secs,departure_secs FROM stop_times WHERE ' + 'trip_id=? ORDER BY stop_sequence DESC LIMIT 1', (self.trip_id,)) + (arrival_secs, departure_secs) = cursor.fetchone() + if departure_secs != None: + return departure_secs + elif arrival_secs != None: + return arrival_secs + else: + problems.InvalidValue('arrival_time', '', + 'The last stop_time in trip %s is missing ' + 'times.' % self.trip_id) + + def _GenerateStopTimesTuples(self): + """Generator for rows of the stop_times file""" + stoptimes = self.GetStopTimes() + for i, st in enumerate(stoptimes): + yield st.GetFieldValuesTuple(self.trip_id) + + def GetStopTimesTuples(self): + results = [] + for time_tuple in self._GenerateStopTimesTuples(): + results.append(time_tuple) + return results + + def GetPattern(self): + """Return a tuple of Stop objects, in the order visited""" + stoptimes = self.GetStopTimes() + return tuple(st.stop for st in stoptimes) + + def AddHeadwayPeriod(self, start_time, end_time, headway_secs, + problem_reporter=default_problem_reporter): + """Adds a period to this trip during which the vehicle travels + at regular intervals (rather than specifying exact times for each stop). + + Args: + start_time: The time at which this headway period starts, either in + numerical seconds since midnight or as "HH:MM:SS" since midnight. + end_time: The time at which this headway period ends, either in + numerical seconds since midnight or as "HH:MM:SS" since midnight. + This value should be larger than start_time. + headway_secs: The amount of time, in seconds, between occurences of + this trip. + problem_reporter: Optional parameter that can be used to select + how any errors in the other input parameters will be reported. + Returns: + None + """ + if start_time == None or start_time == '': # 0 is OK + problem_reporter.MissingValue('start_time') + return + if isinstance(start_time, basestring): + try: + start_time = TimeToSecondsSinceMidnight(start_time) + except Error: + problem_reporter.InvalidValue('start_time', start_time) + return + elif start_time < 0: + problem_reporter.InvalidValue('start_time', start_time) + + if end_time == None or end_time == '': + problem_reporter.MissingValue('end_time') + return + if isinstance(end_time, basestring): + try: + end_time = TimeToSecondsSinceMidnight(end_time) + except Error: + problem_reporter.InvalidValue('end_time', end_time) + return + elif end_time < 0: + problem_reporter.InvalidValue('end_time', end_time) + return + + if not headway_secs: + problem_reporter.MissingValue('headway_secs') + return + try: + headway_secs = int(headway_secs) + except ValueError: + problem_reporter.InvalidValue('headway_secs', headway_secs) + return + + if headway_secs <= 0: + problem_reporter.InvalidValue('headway_secs', headway_secs) + return + + if end_time <= start_time: + problem_reporter.InvalidValue('end_time', end_time, + 'should be greater than start_time') + + self._headways.append((start_time, end_time, headway_secs)) + + def ClearHeadwayPeriods(self): + self._headways = [] + + def _HeadwayOutputTuple(self, headway): + return (self.trip_id, + FormatSecondsSinceMidnight(headway[0]), + FormatSecondsSinceMidnight(headway[1]), + unicode(headway[2])) + + def GetHeadwayPeriodOutputTuples(self): + tuples = [] + for headway in self._headways: + tuples.append(self._HeadwayOutputTuple(headway)) + return tuples + + def GetHeadwayPeriodTuples(self): + return self._headways + + def __getattr__(self, name): + if name == 'service_period': + assert self._schedule, "Must be in a schedule to get service_period" + return self._schedule.GetServicePeriod(self.service_id) + elif name == 'pattern_id': + if '_pattern_id' not in self.__dict__: + self.__dict__['_pattern_id'] = hash(self.GetPattern()) + return self.__dict__['_pattern_id'] + else: + return GenericGTFSObject.__getattr__(self, name) + + def Validate(self, problems, validate_children=True): + """Validate attributes of this object. + + Check that this object has all required values set to a valid value without + reference to the rest of the schedule. If the _schedule attribute is set + then check that references such as route_id and service_id are correct. + + Args: + problems: A ProblemReporter object + validate_children: if True and the _schedule attribute is set than call + ValidateChildren + """ + if IsEmpty(self.route_id): + problems.MissingValue('route_id') + if 'service_period' in self.__dict__: + # Some tests assign to the service_period attribute. Patch up self before + # proceeding with validation. See also comment in Trip.__init__. + self.service_id = self.__dict__['service_period'].service_id + del self.service_period + if IsEmpty(self.service_id): + problems.MissingValue('service_id') + if IsEmpty(self.trip_id): + problems.MissingValue('trip_id') + if hasattr(self, 'direction_id') and (not IsEmpty(self.direction_id)) and \ + (self.direction_id != '0') and (self.direction_id != '1'): + problems.InvalidValue('direction_id', self.direction_id, + 'direction_id must be "0" or "1"') + if self._schedule: + if self.shape_id and self.shape_id not in self._schedule._shapes: + problems.InvalidValue('shape_id', self.shape_id) + if self.route_id and self.route_id not in self._schedule.routes: + problems.InvalidValue('route_id', self.route_id) + if (self.service_id and + self.service_id not in self._schedule.service_periods): + problems.InvalidValue('service_id', self.service_id) + + if validate_children: + self.ValidateChildren(problems) + + def ValidateChildren(self, problems): + """Validate StopTimes and headways of this trip.""" + assert self._schedule, "Trip must be in a schedule to ValidateChildren" + # TODO: validate distance values in stop times (if applicable) + cursor = self._schedule._connection.cursor() + cursor.execute("SELECT COUNT(stop_sequence) AS a FROM stop_times " + "WHERE trip_id=? GROUP BY stop_sequence HAVING a > 1", + (self.trip_id,)) + for row in cursor: + problems.InvalidValue('stop_sequence', row[0], + 'Duplicate stop_sequence in trip_id %s' % + self.trip_id) + + stoptimes = self.GetStopTimes(problems) + if stoptimes: + if stoptimes[0].arrival_time is None and stoptimes[0].departure_time is None: + problems.OtherProblem( + 'No time for start of trip_id "%s""' % (self.trip_id)) + if stoptimes[-1].arrival_time is None and stoptimes[-1].departure_time is None: + problems.OtherProblem( + 'No time for end of trip_id "%s""' % (self.trip_id)) + + # Sorts the stoptimes by sequence and then checks that the arrival time + # for each time point is after the departure time of the previous. + stoptimes.sort(key=lambda x: x.stop_sequence) + prev_departure = 0 + prev_stop = None + prev_distance = None + try: + route_type = self._schedule.GetRoute(self.route_id).route_type + max_speed = Route._ROUTE_TYPES[route_type]['max_speed'] + except KeyError, e: + # If route_type cannot be found, assume it is 0 (Tram) for checking + # speeds between stops. + max_speed = Route._ROUTE_TYPES[0]['max_speed'] + for timepoint in stoptimes: + # Distance should be a nonnegative float number, so it should be + # always larger than None. + distance = timepoint.shape_dist_traveled + if distance is not None: + if distance > prev_distance and distance >= 0: + prev_distance = distance + else: + if distance == prev_distance: + type = TYPE_WARNING + else: + type = TYPE_ERROR + problems.InvalidValue('stoptimes.shape_dist_traveled', distance, + 'For the trip %s the stop %s has shape_dist_traveled=%s, ' + 'which should be larger than the previous ones. In this ' + 'case, the previous distance was %s.' % + (self.trip_id, timepoint.stop_id, distance, prev_distance), + type=type) + + if timepoint.arrival_secs is not None: + self._CheckSpeed(prev_stop, timepoint.stop, prev_departure, + timepoint.arrival_secs, max_speed, problems) + + if timepoint.arrival_secs >= prev_departure: + prev_departure = timepoint.departure_secs + prev_stop = timepoint.stop + else: + problems.OtherProblem('Timetravel detected! Arrival time ' + 'is before previous departure ' + 'at sequence number %s in trip %s' % + (timepoint.stop_sequence, self.trip_id)) + + if self.shape_id and self.shape_id in self._schedule._shapes: + shape = self._schedule.GetShape(self.shape_id) + max_shape_dist = shape.max_distance + st = stoptimes[-1] + if (st.shape_dist_traveled and + st.shape_dist_traveled > max_shape_dist): + problems.OtherProblem( + 'In stop_times.txt, the stop with trip_id=%s and ' + 'stop_sequence=%d has shape_dist_traveled=%f, which is larger ' + 'than the max shape_dist_traveled=%f of the corresponding ' + 'shape (shape_id=%s)' % + (self.trip_id, st.stop_sequence, st.shape_dist_traveled, + max_shape_dist, self.shape_id), type=TYPE_WARNING) + + # shape_dist_traveled is valid in shape if max_shape_dist larger than + # 0. + if max_shape_dist > 0: + for st in stoptimes: + if st.shape_dist_traveled is None: + continue + pt = shape.GetPointWithDistanceTraveled(st.shape_dist_traveled) + if pt: + stop = self._schedule.GetStop(st.stop_id) + distance = ApproximateDistance(stop.stop_lat, stop.stop_lon, + pt[0], pt[1]) + if distance > MAX_DISTANCE_FROM_STOP_TO_SHAPE: + problems.StopTooFarFromShapeWithDistTraveled( + self.trip_id, stop.stop_name, stop.stop_id, pt[2], + self.shape_id, distance, MAX_DISTANCE_FROM_STOP_TO_SHAPE) + + # O(n^2), but we don't anticipate many headway periods per trip + for headway_index, headway in enumerate(self._headways[0:-1]): + for other in self._headways[headway_index + 1:]: + if (other[0] < headway[1]) and (other[1] > headway[0]): + problems.OtherProblem('Trip contains overlapping headway periods ' + '%s and %s' % + (self._HeadwayOutputTuple(headway), + self._HeadwayOutputTuple(other))) + + def _CheckSpeed(self, prev_stop, next_stop, depart_time, + arrive_time, max_speed, problems): + # Checks that the speed between two stops is not faster than max_speed + if prev_stop != None: + try: + time_between_stops = arrive_time - depart_time + except TypeError: + return + + try: + dist_between_stops = \ + ApproximateDistanceBetweenStops(next_stop, prev_stop) + except TypeError, e: + return + + if time_between_stops == 0: + # HASTUS makes it hard to output GTFS with times to the nearest second; + # it rounds times to the nearest minute. Therefore stop_times at the + # same time ending in :00 are fairly common. These times off by no more + # than 30 have not caused a problem. See + # http://code.google.com/p/googletransitdatafeed/issues/detail?id=193 + # Show a warning if times are not rounded to the nearest minute or + # distance is more than max_speed for one minute. + if depart_time % 60 != 0 or dist_between_stops / 1000 * 60 > max_speed: + problems.TooFastTravel(self.trip_id, + prev_stop.stop_name, + next_stop.stop_name, + dist_between_stops, + time_between_stops, + speed=None, + type=TYPE_WARNING) + return + # This needs floating point division for precision. + speed_between_stops = ((float(dist_between_stops) / 1000) / + (float(time_between_stops) / 3600)) + if speed_between_stops > max_speed: + problems.TooFastTravel(self.trip_id, + prev_stop.stop_name, + next_stop.stop_name, + dist_between_stops, + time_between_stops, + speed_between_stops, + type=TYPE_WARNING) + +# TODO: move these into a separate file +class ISO4217(object): + """Represents the set of currencies recognized by the ISO-4217 spec.""" + codes = { # map of alpha code to numerical code + 'AED': 784, 'AFN': 971, 'ALL': 8, 'AMD': 51, 'ANG': 532, 'AOA': 973, + 'ARS': 32, 'AUD': 36, 'AWG': 533, 'AZN': 944, 'BAM': 977, 'BBD': 52, + 'BDT': 50, 'BGN': 975, 'BHD': 48, 'BIF': 108, 'BMD': 60, 'BND': 96, + 'BOB': 68, 'BOV': 984, 'BRL': 986, 'BSD': 44, 'BTN': 64, 'BWP': 72, + 'BYR': 974, 'BZD': 84, 'CAD': 124, 'CDF': 976, 'CHE': 947, 'CHF': 756, + 'CHW': 948, 'CLF': 990, 'CLP': 152, 'CNY': 156, 'COP': 170, 'COU': 970, + 'CRC': 188, 'CUP': 192, 'CVE': 132, 'CYP': 196, 'CZK': 203, 'DJF': 262, + 'DKK': 208, 'DOP': 214, 'DZD': 12, 'EEK': 233, 'EGP': 818, 'ERN': 232, + 'ETB': 230, 'EUR': 978, 'FJD': 242, 'FKP': 238, 'GBP': 826, 'GEL': 981, + 'GHC': 288, 'GIP': 292, 'GMD': 270, 'GNF': 324, 'GTQ': 320, 'GYD': 328, + 'HKD': 344, 'HNL': 340, 'HRK': 191, 'HTG': 332, 'HUF': 348, 'IDR': 360, + 'ILS': 376, 'INR': 356, 'IQD': 368, 'IRR': 364, 'ISK': 352, 'JMD': 388, + 'JOD': 400, 'JPY': 392, 'KES': 404, 'KGS': 417, 'KHR': 116, 'KMF': 174, + 'KPW': 408, 'KRW': 410, 'KWD': 414, 'KYD': 136, 'KZT': 398, 'LAK': 418, + 'LBP': 422, 'LKR': 144, 'LRD': 430, 'LSL': 426, 'LTL': 440, 'LVL': 428, + 'LYD': 434, 'MAD': 504, 'MDL': 498, 'MGA': 969, 'MKD': 807, 'MMK': 104, + 'MNT': 496, 'MOP': 446, 'MRO': 478, 'MTL': 470, 'MUR': 480, 'MVR': 462, + 'MWK': 454, 'MXN': 484, 'MXV': 979, 'MYR': 458, 'MZN': 943, 'NAD': 516, + 'NGN': 566, 'NIO': 558, 'NOK': 578, 'NPR': 524, 'NZD': 554, 'OMR': 512, + 'PAB': 590, 'PEN': 604, 'PGK': 598, 'PHP': 608, 'PKR': 586, 'PLN': 985, + 'PYG': 600, 'QAR': 634, 'ROL': 642, 'RON': 946, 'RSD': 941, 'RUB': 643, + 'RWF': 646, 'SAR': 682, 'SBD': 90, 'SCR': 690, 'SDD': 736, 'SDG': 938, + 'SEK': 752, 'SGD': 702, 'SHP': 654, 'SKK': 703, 'SLL': 694, 'SOS': 706, + 'SRD': 968, 'STD': 678, 'SYP': 760, 'SZL': 748, 'THB': 764, 'TJS': 972, + 'TMM': 795, 'TND': 788, 'TOP': 776, 'TRY': 949, 'TTD': 780, 'TWD': 901, + 'TZS': 834, 'UAH': 980, 'UGX': 800, 'USD': 840, 'USN': 997, 'USS': 998, + 'UYU': 858, 'UZS': 860, 'VEB': 862, 'VND': 704, 'VUV': 548, 'WST': 882, + 'XAF': 950, 'XAG': 961, 'XAU': 959, 'XBA': 955, 'XBB': 956, 'XBC': 957, + 'XBD': 958, 'XCD': 951, 'XDR': 960, 'XFO': None, 'XFU': None, 'XOF': 952, + 'XPD': 964, 'XPF': 953, 'XPT': 962, 'XTS': 963, 'XXX': 999, 'YER': 886, + 'ZAR': 710, 'ZMK': 894, 'ZWD': 716, + } + + +class Fare(object): + """Represents a fare type.""" + _REQUIRED_FIELD_NAMES = ['fare_id', 'price', 'currency_type', + 'payment_method', 'transfers'] + _FIELD_NAMES = _REQUIRED_FIELD_NAMES + ['transfer_duration'] + + def __init__(self, + fare_id=None, price=None, currency_type=None, + payment_method=None, transfers=None, transfer_duration=None, + field_list=None): + self.rules = [] + (self.fare_id, self.price, self.currency_type, self.payment_method, + self.transfers, self.transfer_duration) = \ + (fare_id, price, currency_type, payment_method, + transfers, transfer_duration) + if field_list: + (self.fare_id, self.price, self.currency_type, self.payment_method, + self.transfers, self.transfer_duration) = field_list + + try: + self.price = float(self.price) + except (TypeError, ValueError): + pass + try: + self.payment_method = int(self.payment_method) + except (TypeError, ValueError): + pass + if self.transfers == None or self.transfers == "": + self.transfers = None + else: + try: + self.transfers = int(self.transfers) + except (TypeError, ValueError): + pass + if self.transfer_duration == None or self.transfer_duration == "": + self.transfer_duration = None + else: + try: + self.transfer_duration = int(self.transfer_duration) + except (TypeError, ValueError): + pass + + def GetFareRuleList(self): + return self.rules + + def ClearFareRules(self): + self.rules = [] + + def GetFieldValuesTuple(self): + return [getattr(self, fn) for fn in Fare._FIELD_NAMES] + + def __getitem__(self, name): + return getattr(self, name) + + def __eq__(self, other): + if not other: + return False + + if id(self) == id(other): + return True + + if self.GetFieldValuesTuple() != other.GetFieldValuesTuple(): + return False + + self_rules = [r.GetFieldValuesTuple() for r in self.GetFareRuleList()] + self_rules.sort() + other_rules = [r.GetFieldValuesTuple() for r in other.GetFareRuleList()] + other_rules.sort() + return self_rules == other_rules + + def __ne__(self, other): + return not self.__eq__(other) + + def Validate(self, problems=default_problem_reporter): + if IsEmpty(self.fare_id): + problems.MissingValue("fare_id") + + if self.price == None: + problems.MissingValue("price") + elif not isinstance(self.price, float) and not isinstance(self.price, int): + problems.InvalidValue("price", self.price) + elif self.price < 0: + problems.InvalidValue("price", self.price) + + if IsEmpty(self.currency_type): + problems.MissingValue("currency_type") + elif self.currency_type not in ISO4217.codes: + problems.InvalidValue("currency_type", self.currency_type) + + if self.payment_method == "" or self.payment_method == None: + problems.MissingValue("payment_method") + elif (not isinstance(self.payment_method, int) or + self.payment_method not in range(0, 2)): + problems.InvalidValue("payment_method", self.payment_method) + + if not ((self.transfers == None) or + (isinstance(self.transfers, int) and + self.transfers in range(0, 3))): + problems.InvalidValue("transfers", self.transfers) + + if ((self.transfer_duration != None) and + not isinstance(self.transfer_duration, int)): + problems.InvalidValue("transfer_duration", self.transfer_duration) + if self.transfer_duration and (self.transfer_duration < 0): + problems.InvalidValue("transfer_duration", self.transfer_duration) + if (self.transfer_duration and (self.transfer_duration > 0) and + self.transfers == 0): + problems.InvalidValue("transfer_duration", self.transfer_duration, + "can't have a nonzero transfer_duration for " + "a fare that doesn't allow transfers!") + + +class FareRule(object): + """This class represents a rule that determines which itineraries a + fare rule applies to.""" + _REQUIRED_FIELD_NAMES = ['fare_id'] + _FIELD_NAMES = _REQUIRED_FIELD_NAMES + ['route_id', + 'origin_id', 'destination_id', + 'contains_id'] + + def __init__(self, fare_id=None, route_id=None, + origin_id=None, destination_id=None, contains_id=None, + field_list=None): + (self.fare_id, self.route_id, self.origin_id, self.destination_id, + self.contains_id) = \ + (fare_id, route_id, origin_id, destination_id, contains_id) + if field_list: + (self.fare_id, self.route_id, self.origin_id, self.destination_id, + self.contains_id) = field_list + + # canonicalize non-content values as None + if not self.route_id: + self.route_id = None + if not self.origin_id: + self.origin_id = None + if not self.destination_id: + self.destination_id = None + if not self.contains_id: + self.contains_id = None + + def GetFieldValuesTuple(self): + return [getattr(self, fn) for fn in FareRule._FIELD_NAMES] + + def __getitem__(self, name): + return getattr(self, name) + + def __eq__(self, other): + if not other: + return False + + if id(self) == id(other): + return True + + return self.GetFieldValuesTuple() == other.GetFieldValuesTuple() + + def __ne__(self, other): + return not self.__eq__(other) + + +class Shape(object): + """This class represents a geographic shape that corresponds to the route + taken by one or more Trips.""" + _REQUIRED_FIELD_NAMES = ['shape_id', 'shape_pt_lat', 'shape_pt_lon', + 'shape_pt_sequence'] + _FIELD_NAMES = _REQUIRED_FIELD_NAMES + ['shape_dist_traveled'] + def __init__(self, shape_id): + # List of shape point tuple (lat, lng, shape_dist_traveled), where lat and + # lon is the location of the shape point, and shape_dist_traveled is an + # increasing metric representing the distance traveled along the shape. + self.points = [] + # An ID that uniquely identifies a shape in the dataset. + self.shape_id = shape_id + # The max shape_dist_traveled of shape points in this shape. + self.max_distance = 0 + # List of shape_dist_traveled of each shape point. + self.distance = [] + + def AddPoint(self, lat, lon, distance=None, + problems=default_problem_reporter): + + try: + lat = float(lat) + if abs(lat) > 90.0: + problems.InvalidValue('shape_pt_lat', lat) + return + except (TypeError, ValueError): + problems.InvalidValue('shape_pt_lat', lat) + return + + try: + lon = float(lon) + if abs(lon) > 180.0: + problems.InvalidValue('shape_pt_lon', lon) + return + except (TypeError, ValueError): + problems.InvalidValue('shape_pt_lon', lon) + return + + if (abs(lat) < 1.0) and (abs(lon) < 1.0): + problems.InvalidValue('shape_pt_lat', lat, + 'Point location too close to 0, 0, which means ' + 'that it\'s probably an incorrect location.', + type=TYPE_WARNING) + return + + if distance == '': # canonicalizing empty string to None for comparison + distance = None + + if distance != None: + try: + distance = float(distance) + if (distance < self.max_distance and not + (len(self.points) == 0 and distance == 0)): # first one can be 0 + problems.InvalidValue('shape_dist_traveled', distance, + 'Each subsequent point in a shape should ' + 'have a distance value that\'s at least as ' + 'large as the previous ones. In this case, ' + 'the previous distance was %f.' % + self.max_distance) + return + else: + self.max_distance = distance + self.distance.append(distance) + except (TypeError, ValueError): + problems.InvalidValue('shape_dist_traveled', distance, + 'This value should be a positive number.') + return + + self.points.append((lat, lon, distance)) + + def ClearPoints(self): + self.points = [] + + def __eq__(self, other): + if not other: + return False + + if id(self) == id(other): + return True + + return self.points == other.points + + def __ne__(self, other): + return not self.__eq__(other) + + def __repr__(self): + return "" % self.__dict__ + + def Validate(self, problems=default_problem_reporter): + if IsEmpty(self.shape_id): + problems.MissingValue('shape_id') + + if not self.points: + problems.OtherProblem('The shape with shape_id "%s" contains no points.' % + self.shape_id, type=TYPE_WARNING) + + def GetPointWithDistanceTraveled(self, shape_dist_traveled): + """Returns a point on the shape polyline with the input shape_dist_traveled. + + Args: + shape_dist_traveled: The input shape_dist_traveled. + + Returns: + The shape point as a tuple (lat, lng, shape_dist_traveled), where lat and + lng is the location of the shape point, and shape_dist_traveled is an + increasing metric representing the distance traveled along the shape. + Returns None if there is data error in shape. + """ + if not self.distance: + return None + if shape_dist_traveled <= self.distance[0]: + return self.points[0] + if shape_dist_traveled >= self.distance[-1]: + return self.points[-1] + + index = bisect.bisect(self.distance, shape_dist_traveled) + (lat0, lng0, dist0) = self.points[index - 1] + (lat1, lng1, dist1) = self.points[index] + + # Interpolate if shape_dist_traveled does not equal to any of the point + # in shape segment. + # (lat0, lng0) (lat, lng) (lat1, lng1) + # -----|--------------------|---------------------|------ + # dist0 shape_dist_traveled dist1 + # \------- ca --------/ \-------- bc -------/ + # \----------------- ba ------------------/ + ca = shape_dist_traveled - dist0 + bc = dist1 - shape_dist_traveled + ba = bc + ca + if ba == 0: + # This only happens when there's data error in shapes and should have been + # catched before. Check to avoid crash. + return None + # This won't work crossing longitude 180 and is only an approximation which + # works well for short distance. + lat = (lat1 * ca + lat0 * bc) / ba + lng = (lng1 * ca + lng0 * bc) / ba + return (lat, lng, shape_dist_traveled) + + +class ISO639(object): + # Set of all the 2-letter ISO 639-1 language codes. + codes_2letter = set([ + 'aa', 'ab', 'ae', 'af', 'ak', 'am', 'an', 'ar', 'as', 'av', 'ay', 'az', + 'ba', 'be', 'bg', 'bh', 'bi', 'bm', 'bn', 'bo', 'br', 'bs', 'ca', 'ce', + 'ch', 'co', 'cr', 'cs', 'cu', 'cv', 'cy', 'da', 'de', 'dv', 'dz', 'ee', + 'el', 'en', 'eo', 'es', 'et', 'eu', 'fa', 'ff', 'fi', 'fj', 'fo', 'fr', + 'fy', 'ga', 'gd', 'gl', 'gn', 'gu', 'gv', 'ha', 'he', 'hi', 'ho', 'hr', + 'ht', 'hu', 'hy', 'hz', 'ia', 'id', 'ie', 'ig', 'ii', 'ik', 'io', 'is', + 'it', 'iu', 'ja', 'jv', 'ka', 'kg', 'ki', 'kj', 'kk', 'kl', 'km', 'kn', + 'ko', 'kr', 'ks', 'ku', 'kv', 'kw', 'ky', 'la', 'lb', 'lg', 'li', 'ln', + 'lo', 'lt', 'lu', 'lv', 'mg', 'mh', 'mi', 'mk', 'ml', 'mn', 'mo', 'mr', + 'ms', 'mt', 'my', 'na', 'nb', 'nd', 'ne', 'ng', 'nl', 'nn', 'no', 'nr', + 'nv', 'ny', 'oc', 'oj', 'om', 'or', 'os', 'pa', 'pi', 'pl', 'ps', 'pt', + 'qu', 'rm', 'rn', 'ro', 'ru', 'rw', 'sa', 'sc', 'sd', 'se', 'sg', 'si', + 'sk', 'sl', 'sm', 'sn', 'so', 'sq', 'sr', 'ss', 'st', 'su', 'sv', 'sw', + 'ta', 'te', 'tg', 'th', 'ti', 'tk', 'tl', 'tn', 'to', 'tr', 'ts', 'tt', + 'tw', 'ty', 'ug', 'uk', 'ur', 'uz', 've', 'vi', 'vo', 'wa', 'wo', 'xh', + 'yi', 'yo', 'za', 'zh', 'zu', + ]) + + +class Agency(GenericGTFSObject): + """Represents an agency in a schedule. + + Callers may assign arbitrary values to instance attributes. __init__ makes no + attempt at validating the attributes. Call Validate() to check that + attributes are valid and the agency object is consistent with itself. + + Attributes: + All attributes are strings. + """ + _REQUIRED_FIELD_NAMES = ['agency_name', 'agency_url', 'agency_timezone'] + _FIELD_NAMES = _REQUIRED_FIELD_NAMES + ['agency_id', 'agency_lang', + 'agency_phone'] + _TABLE_NAME = 'agency' + + def __init__(self, name=None, url=None, timezone=None, id=None, + field_dict=None, lang=None, **kwargs): + """Initialize a new Agency object. + + Args: + field_dict: A dictionary mapping attribute name to unicode string + name: a string, ignored when field_dict is present + url: a string, ignored when field_dict is present + timezone: a string, ignored when field_dict is present + id: a string, ignored when field_dict is present + kwargs: arbitrary keyword arguments may be used to add attributes to the + new object, ignored when field_dict is present + """ + self._schedule = None + + if not field_dict: + if name: + kwargs['agency_name'] = name + if url: + kwargs['agency_url'] = url + if timezone: + kwargs['agency_timezone'] = timezone + if id: + kwargs['agency_id'] = id + if lang: + kwargs['agency_lang'] = lang + field_dict = kwargs + + self.__dict__.update(field_dict) + + def Validate(self, problems=default_problem_reporter): + """Validate attribute values and this object's internal consistency. + + Returns: + True iff all validation checks passed. + """ + found_problem = False + for required in Agency._REQUIRED_FIELD_NAMES: + if IsEmpty(getattr(self, required, None)): + problems.MissingValue(required) + found_problem = True + + if self.agency_url and not IsValidURL(self.agency_url): + problems.InvalidValue('agency_url', self.agency_url) + found_problem = True + + if (not IsEmpty(self.agency_lang) and + self.agency_lang.lower() not in ISO639.codes_2letter): + problems.InvalidValue('agency_lang', self.agency_lang) + found_problem = True + + try: + import pytz + if self.agency_timezone not in pytz.common_timezones: + problems.InvalidValue( + 'agency_timezone', + self.agency_timezone, + '"%s" is not a common timezone name according to pytz version %s' % + (self.agency_timezone, pytz.VERSION)) + found_problem = True + except ImportError: # no pytz + print ("Timezone not checked " + "(install pytz package for timezone validation)") + return not found_problem + + +class Transfer(object): + """Represents a transfer in a schedule""" + _REQUIRED_FIELD_NAMES = ['from_stop_id', 'to_stop_id', 'transfer_type'] + _FIELD_NAMES = _REQUIRED_FIELD_NAMES + ['min_transfer_time'] + + def __init__(self, schedule=None, from_stop_id=None, to_stop_id=None, transfer_type=None, + min_transfer_time=None, field_dict=None): + if schedule is not None: + self._schedule = weakref.proxy(schedule) # See weakref comment at top + else: + self._schedule = None + if field_dict: + self.__dict__.update(field_dict) + else: + self.from_stop_id = from_stop_id + self.to_stop_id = to_stop_id + self.transfer_type = transfer_type + self.min_transfer_time = min_transfer_time + + if getattr(self, 'transfer_type', None) in ("", None): + # Use the default, recommended transfer, if attribute is not set or blank + self.transfer_type = 0 + else: + try: + self.transfer_type = NonNegIntStringToInt(self.transfer_type) + except (TypeError, ValueError): + pass + + if hasattr(self, 'min_transfer_time'): + try: + self.min_transfer_time = NonNegIntStringToInt(self.min_transfer_time) + except (TypeError, ValueError): + pass + else: + self.min_transfer_time = None + + def GetFieldValuesTuple(self): + return [getattr(self, fn) for fn in Transfer._FIELD_NAMES] + + def __getitem__(self, name): + return getattr(self, name) + + def __eq__(self, other): + if not other: + return False + + if id(self) == id(other): + return True + + return self.GetFieldValuesTuple() == other.GetFieldValuesTuple() + + def __ne__(self, other): + return not self.__eq__(other) + + def __repr__(self): + return "" % self.__dict__ + + def Validate(self, problems=default_problem_reporter): + if IsEmpty(self.from_stop_id): + problems.MissingValue('from_stop_id') + elif self._schedule: + if self.from_stop_id not in self._schedule.stops.keys(): + problems.InvalidValue('from_stop_id', self.from_stop_id) + + if IsEmpty(self.to_stop_id): + problems.MissingValue('to_stop_id') + elif self._schedule: + if self.to_stop_id not in self._schedule.stops.keys(): + problems.InvalidValue('to_stop_id', self.to_stop_id) + + if not IsEmpty(self.transfer_type): + if (not isinstance(self.transfer_type, int)) or \ + (self.transfer_type not in range(0, 4)): + problems.InvalidValue('transfer_type', self.transfer_type) + + if not IsEmpty(self.min_transfer_time): + if (not isinstance(self.min_transfer_time, int)) or \ + self.min_transfer_time < 0: + problems.InvalidValue('min_transfer_time', self.min_transfer_time) + + +class ServicePeriod(object): + """Represents a service, which identifies a set of dates when one or more + trips operate.""" + _DAYS_OF_WEEK = [ + 'monday', 'tuesday', 'wednesday', 'thursday', 'friday', + 'saturday', 'sunday' + ] + _FIELD_NAMES_REQUIRED = [ + 'service_id', 'start_date', 'end_date' + ] + _DAYS_OF_WEEK + _FIELD_NAMES = _FIELD_NAMES_REQUIRED # no optional fields in this one + _FIELD_NAMES_CALENDAR_DATES = ['service_id', 'date', 'exception_type'] + + def __init__(self, id=None, field_list=None): + self.original_day_values = [] + if field_list: + self.service_id = field_list[self._FIELD_NAMES.index('service_id')] + self.day_of_week = [False] * len(self._DAYS_OF_WEEK) + + for day in self._DAYS_OF_WEEK: + value = field_list[self._FIELD_NAMES.index(day)] or '' # can be None + self.original_day_values += [value.strip()] + self.day_of_week[self._DAYS_OF_WEEK.index(day)] = (value == u'1') + + self.start_date = field_list[self._FIELD_NAMES.index('start_date')] + self.end_date = field_list[self._FIELD_NAMES.index('end_date')] + else: + self.service_id = id + self.day_of_week = [False] * 7 + self.start_date = None + self.end_date = None + self.date_exceptions = {} # Map from 'YYYYMMDD' to 1 (add) or 2 (remove) + + def _IsValidDate(self, date): + if re.match('^\d{8}$', date) == None: + return False + + try: + time.strptime(date, "%Y%m%d") + return True + except ValueError: + return False + + def GetDateRange(self): + """Return the range over which this ServicePeriod is valid. + + The range includes exception dates that add service outside of + (start_date, end_date), but doesn't shrink the range if exception + dates take away service at the edges of the range. + + Returns: + A tuple of "YYYYMMDD" strings, (start date, end date) or (None, None) if + no dates have been given. + """ + start = self.start_date + end = self.end_date + + for date in self.date_exceptions: + if self.date_exceptions[date] == 2: + continue + if not start or (date < start): + start = date + if not end or (date > end): + end = date + if start is None: + start = end + elif end is None: + end = start + # If start and end are None we did a little harmless shuffling + return (start, end) + + def GetCalendarFieldValuesTuple(self): + """Return the tuple of calendar.txt values or None if this ServicePeriod + should not be in calendar.txt .""" + if self.start_date and self.end_date: + return [getattr(self, fn) for fn in ServicePeriod._FIELD_NAMES] + + def GenerateCalendarDatesFieldValuesTuples(self): + """Generates tuples of calendar_dates.txt values. Yield zero tuples if + this ServicePeriod should not be in calendar_dates.txt .""" + for date, exception_type in self.date_exceptions.items(): + yield (self.service_id, date, unicode(exception_type)) + + def GetCalendarDatesFieldValuesTuples(self): + """Return a list of date execeptions""" + result = [] + for date_tuple in self.GenerateCalendarDatesFieldValuesTuples(): + result.append(date_tuple) + result.sort() # helps with __eq__ + return result + + def SetDateHasService(self, date, has_service=True, problems=None): + if date in self.date_exceptions and problems: + problems.DuplicateID(('service_id', 'date'), + (self.service_id, date), + type=TYPE_WARNING) + self.date_exceptions[date] = has_service and 1 or 2 + + def ResetDateToNormalService(self, date): + if date in self.date_exceptions: + del self.date_exceptions[date] + + def SetStartDate(self, start_date): + """Set the first day of service as a string in YYYYMMDD format""" + self.start_date = start_date + + def SetEndDate(self, end_date): + """Set the last day of service as a string in YYYYMMDD format""" + self.end_date = end_date + + def SetDayOfWeekHasService(self, dow, has_service=True): + """Set service as running (or not) on a day of the week. By default the + service does not run on any days. + + Args: + dow: 0 for Monday through 6 for Sunday + has_service: True if this service operates on dow, False if it does not. + + Returns: + None + """ + assert(dow >= 0 and dow < 7) + self.day_of_week[dow] = has_service + + def SetWeekdayService(self, has_service=True): + """Set service as running (or not) on all of Monday through Friday.""" + for i in range(0, 5): + self.SetDayOfWeekHasService(i, has_service) + + def SetWeekendService(self, has_service=True): + """Set service as running (or not) on Saturday and Sunday.""" + self.SetDayOfWeekHasService(5, has_service) + self.SetDayOfWeekHasService(6, has_service) + + def SetServiceId(self, service_id): + """Set the service_id for this schedule. Generally the default will + suffice so you won't need to call this method.""" + self.service_id = service_id + + def IsActiveOn(self, date, date_object=None): + """Test if this service period is active on a date. + + Args: + date: a string of form "YYYYMMDD" + date_object: a date object representing the same date as date. + This parameter is optional, and present only for performance + reasons. + If the caller constructs the date string from a date object + that date object can be passed directly, thus avoiding the + costly conversion from string to date object. + + Returns: + True iff this service is active on date. + """ + if date in self.date_exceptions: + if self.date_exceptions[date] == 1: + return True + else: + return False + if (self.start_date and self.end_date and self.start_date <= date and + date <= self.end_date): + if date_object is None: + date_object = DateStringToDateObject(date) + return self.day_of_week[date_object.weekday()] + return False + + def ActiveDates(self): + """Return dates this service period is active as a list of "YYYYMMDD".""" + (earliest, latest) = self.GetDateRange() + if earliest is None: + return [] + dates = [] + date_it = DateStringToDateObject(earliest) + date_end = DateStringToDateObject(latest) + delta = datetime.timedelta(days=1) + while date_it <= date_end: + date_it_string = date_it.strftime("%Y%m%d") + if self.IsActiveOn(date_it_string, date_it): + dates.append(date_it_string) + date_it = date_it + delta + return dates + + def __getattr__(self, name): + try: + # Return 1 if value in day_of_week is True, 0 otherwise + return (self.day_of_week[ServicePeriod._DAYS_OF_WEEK.index(name)] + and 1 or 0) + except KeyError: + pass + except ValueError: # not a day of the week + pass + raise AttributeError(name) + + def __getitem__(self, name): + return getattr(self, name) + + def __eq__(self, other): + if not other: + return False + + if id(self) == id(other): + return True + + if (self.GetCalendarFieldValuesTuple() != + other.GetCalendarFieldValuesTuple()): + return False + + if (self.GetCalendarDatesFieldValuesTuples() != + other.GetCalendarDatesFieldValuesTuples()): + return False + + return True + + def __ne__(self, other): + return not self.__eq__(other) + + def Validate(self, problems=default_problem_reporter): + if IsEmpty(self.service_id): + problems.MissingValue('service_id') + # self.start_date/self.end_date is None in 3 cases: + # ServicePeriod created by loader and + # 1a) self.service_id wasn't in calendar.txt + # 1b) calendar.txt didn't have a start_date/end_date column + # ServicePeriod created directly and + # 2) start_date/end_date wasn't set + # In case 1a no problem is reported. In case 1b the missing required column + # generates an error in _ReadCSV so this method should not report another + # problem. There is no way to tell the difference between cases 1b and 2 + # so case 2 is ignored because making the feedvalidator pretty is more + # important than perfect validation when an API users makes a mistake. + start_date = None + if self.start_date is not None: + if IsEmpty(self.start_date): + problems.MissingValue('start_date') + elif self._IsValidDate(self.start_date): + start_date = self.start_date + else: + problems.InvalidValue('start_date', self.start_date) + end_date = None + if self.end_date is not None: + if IsEmpty(self.end_date): + problems.MissingValue('end_date') + elif self._IsValidDate(self.end_date): + end_date = self.end_date + else: + problems.InvalidValue('end_date', self.end_date) + if start_date and end_date and end_date < start_date: + problems.InvalidValue('end_date', end_date, + 'end_date of %s is earlier than ' + 'start_date of "%s"' % + (end_date, start_date)) + if self.original_day_values: + index = 0 + for value in self.original_day_values: + column_name = self._DAYS_OF_WEEK[index] + if IsEmpty(value): + problems.MissingValue(column_name) + elif (value != u'0') and (value != '1'): + problems.InvalidValue(column_name, value) + index += 1 + if (True not in self.day_of_week and + 1 not in self.date_exceptions.values()): + problems.OtherProblem('Service period with service_id "%s" ' + 'doesn\'t have service on any days ' + 'of the week.' % self.service_id, + type=TYPE_WARNING) + for date in self.date_exceptions: + if not self._IsValidDate(date): + problems.InvalidValue('date', date) + + +class CsvUnicodeWriter: + """ + Create a wrapper around a csv writer object which can safely write unicode + values. Passes all arguments to csv.writer. + """ + def __init__(self, *args, **kwargs): + self.writer = csv.writer(*args, **kwargs) + + def writerow(self, row): + """Write row to the csv file. Any unicode strings in row are encoded as + utf-8.""" + encoded_row = [] + for s in row: + if isinstance(s, unicode): + encoded_row.append(s.encode("utf-8")) + else: + encoded_row.append(s) + try: + self.writer.writerow(encoded_row) + except Exception, e: + print 'error writing %s as %s' % (row, encoded_row) + raise e + + def writerows(self, rows): + """Write rows to the csv file. Any unicode strings in rows are encoded as + utf-8.""" + for row in rows: + self.writerow(row) + + def __getattr__(self, name): + return getattr(self.writer, name) + + +class Schedule: + """Represents a Schedule, a collection of stops, routes, trips and + an agency. This is the main class for this module.""" + + def __init__(self, problem_reporter=default_problem_reporter, + memory_db=True, check_duplicate_trips=False): + # Map from table name to list of columns present in this schedule + self._table_columns = {} + + self._agencies = {} + self.stops = {} + self.routes = {} + self.trips = {} + self.service_periods = {} + self.fares = {} + self.fare_zones = {} # represents the set of all known fare zones + self._shapes = {} # shape_id to Shape + self._transfers = [] # list of transfers + self._default_service_period = None + self._default_agency = None + self.problem_reporter = problem_reporter + self._check_duplicate_trips = check_duplicate_trips + self.ConnectDb(memory_db) + + def AddTableColumn(self, table, column): + """Add column to table if it is not already there.""" + if column not in self._table_columns[table]: + self._table_columns[table].append(column) + + def AddTableColumns(self, table, columns): + """Add columns to table if they are not already there. + + Args: + table: table name as a string + columns: an iterable of column names""" + table_columns = self._table_columns.setdefault(table, []) + for attr in columns: + if attr not in table_columns: + table_columns.append(attr) + + def GetTableColumns(self, table): + """Return list of columns in a table.""" + return self._table_columns[table] + + def __del__(self): + if hasattr(self, '_temp_db_filename'): + os.remove(self._temp_db_filename) + + def ConnectDb(self, memory_db): + if memory_db: + self._connection = sqlite.connect(":memory:") + else: + try: + self._temp_db_file = tempfile.NamedTemporaryFile() + self._connection = sqlite.connect(self._temp_db_file.name) + except sqlite.OperationalError: + # Windows won't let a file be opened twice. mkstemp does not remove the + # file when all handles to it are closed. + self._temp_db_file = None + (fd, self._temp_db_filename) = tempfile.mkstemp(".db") + os.close(fd) + self._connection = sqlite.connect(self._temp_db_filename) + + cursor = self._connection.cursor() + cursor.execute("""CREATE TABLE stop_times ( + trip_id CHAR(50), + arrival_secs INTEGER, + departure_secs INTEGER, + stop_id CHAR(50), + stop_sequence INTEGER, + stop_headsign VAR CHAR(100), + pickup_type INTEGER, + drop_off_type INTEGER, + shape_dist_traveled FLOAT);""") + cursor.execute("""CREATE INDEX trip_index ON stop_times (trip_id);""") + cursor.execute("""CREATE INDEX stop_index ON stop_times (stop_id);""") + + def GetStopBoundingBox(self): + return (min(s.stop_lat for s in self.stops.values()), + min(s.stop_lon for s in self.stops.values()), + max(s.stop_lat for s in self.stops.values()), + max(s.stop_lon for s in self.stops.values()), + ) + + def AddAgency(self, name, url, timezone, agency_id=None): + """Adds an agency to this schedule.""" + agency = Agency(name, url, timezone, agency_id) + self.AddAgencyObject(agency) + return agency + + def AddAgencyObject(self, agency, problem_reporter=None, validate=True): + assert agency._schedule is None + + if not problem_reporter: + problem_reporter = self.problem_reporter + + if agency.agency_id in self._agencies: + problem_reporter.DuplicateID('agency_id', agency.agency_id) + return + + self.AddTableColumns('agency', agency._ColumnNames()) + agency._schedule = weakref.proxy(self) + + if validate: + agency.Validate(problem_reporter) + self._agencies[agency.agency_id] = agency + + def GetAgency(self, agency_id): + """Return Agency with agency_id or throw a KeyError""" + return self._agencies[agency_id] + + def GetDefaultAgency(self): + """Return the default Agency. If no default Agency has been set select the + default depending on how many Agency objects are in the Schedule. If there + are 0 make a new Agency the default, if there is 1 it becomes the default, + if there is more than 1 then return None. + """ + if not self._default_agency: + if len(self._agencies) == 0: + self.NewDefaultAgency() + elif len(self._agencies) == 1: + self._default_agency = self._agencies.values()[0] + return self._default_agency + + def NewDefaultAgency(self, **kwargs): + """Create a new Agency object and make it the default agency for this Schedule""" + agency = Agency(**kwargs) + if not agency.agency_id: + agency.agency_id = FindUniqueId(self._agencies) + self._default_agency = agency + self.SetDefaultAgency(agency, validate=False) # Blank agency won't validate + return agency + + def SetDefaultAgency(self, agency, validate=True): + """Make agency the default and add it to the schedule if not already added""" + assert isinstance(agency, Agency) + self._default_agency = agency + if agency.agency_id not in self._agencies: + self.AddAgencyObject(agency, validate=validate) + + def GetAgencyList(self): + """Returns the list of Agency objects known to this Schedule.""" + return self._agencies.values() + + def GetServicePeriod(self, service_id): + """Returns the ServicePeriod object with the given ID.""" + return self.service_periods[service_id] + + def GetDefaultServicePeriod(self): + """Return the default ServicePeriod. If no default ServicePeriod has been + set select the default depending on how many ServicePeriod objects are in + the Schedule. If there are 0 make a new ServicePeriod the default, if there + is 1 it becomes the default, if there is more than 1 then return None. + """ + if not self._default_service_period: + if len(self.service_periods) == 0: + self.NewDefaultServicePeriod() + elif len(self.service_periods) == 1: + self._default_service_period = self.service_periods.values()[0] + return self._default_service_period + + def NewDefaultServicePeriod(self): + """Create a new ServicePeriod object, make it the default service period and + return it. The default service period is used when you create a trip without + providing an explict service period. """ + service_period = ServicePeriod() + service_period.service_id = FindUniqueId(self.service_periods) + # blank service won't validate in AddServicePeriodObject + self.SetDefaultServicePeriod(service_period, validate=False) + return service_period + + def SetDefaultServicePeriod(self, service_period, validate=True): + assert isinstance(service_period, ServicePeriod) + self._default_service_period = service_period + if service_period.service_id not in self.service_periods: + self.AddServicePeriodObject(service_period, validate=validate) + + def AddServicePeriodObject(self, service_period, problem_reporter=None, + validate=True): + if not problem_reporter: + problem_reporter = self.problem_reporter + + if service_period.service_id in self.service_periods: + problem_reporter.DuplicateID('service_id', service_period.service_id) + return + + if validate: + service_period.Validate(problem_reporter) + self.service_periods[service_period.service_id] = service_period + + def GetServicePeriodList(self): + return self.service_periods.values() + + def GetDateRange(self): + """Returns a tuple of (earliest, latest) dates on which the service + periods in the schedule define service, in YYYYMMDD form.""" + + ranges = [period.GetDateRange() for period in self.GetServicePeriodList()] + starts = filter(lambda x: x, [item[0] for item in ranges]) + ends = filter(lambda x: x, [item[1] for item in ranges]) + + if not starts or not ends: + return (None, None) + + return (min(starts), max(ends)) + + def GetServicePeriodsActiveEachDate(self, date_start, date_end): + """Return a list of tuples (date, [period1, period2, ...]). + + For each date in the range [date_start, date_end) make list of each + ServicePeriod object which is active. + + Args: + date_start: The first date in the list, a date object + date_end: The first date after the list, a date object + + Returns: + A list of tuples. Each tuple contains a date object and a list of zero or + more ServicePeriod objects. + """ + date_it = date_start + one_day = datetime.timedelta(days=1) + date_service_period_list = [] + while date_it < date_end: + periods_today = [] + date_it_string = date_it.strftime("%Y%m%d") + for service in self.GetServicePeriodList(): + if service.IsActiveOn(date_it_string, date_it): + periods_today.append(service) + date_service_period_list.append((date_it, periods_today)) + date_it += one_day + return date_service_period_list + + + def AddStop(self, lat, lng, name): + """Add a stop to this schedule. + + A new stop_id is created for this stop. Do not use this method unless all + stops in this Schedule are created with it. See source for details. + + Args: + lat: Latitude of the stop as a float or string + lng: Longitude of the stop as a float or string + name: Name of the stop, which will appear in the feed + + Returns: + A new Stop object + """ + # TODO: stop_id isn't guarenteed to be unique and conflicts are not + # handled. Please fix. + stop_id = unicode(len(self.stops)) + stop = Stop(stop_id=stop_id, lat=lat, lng=lng, name=name) + self.AddStopObject(stop) + return stop + + def AddStopObject(self, stop, problem_reporter=None): + """Add Stop object to this schedule if stop_id is non-blank.""" + assert stop._schedule is None + if not problem_reporter: + problem_reporter = self.problem_reporter + + if not stop.stop_id: + return + + if stop.stop_id in self.stops: + problem_reporter.DuplicateID('stop_id', stop.stop_id) + return + + stop._schedule = weakref.proxy(self) + self.AddTableColumns('stops', stop._ColumnNames()) + self.stops[stop.stop_id] = stop + if hasattr(stop, 'zone_id') and stop.zone_id: + self.fare_zones[stop.zone_id] = True + + def GetStopList(self): + return self.stops.values() + + def AddRoute(self, short_name, long_name, route_type): + """Add a route to this schedule. + + Args: + short_name: Short name of the route, such as "71L" + long_name: Full name of the route, such as "NW 21st Ave/St Helens Rd" + route_type: A type such as "Tram", "Subway" or "Bus" + Returns: + A new Route object + """ + route_id = unicode(len(self.routes)) + route = Route(short_name=short_name, long_name=long_name, + route_type=route_type, route_id=route_id) + route.agency_id = self.GetDefaultAgency().agency_id + self.AddRouteObject(route) + return route + + def AddRouteObject(self, route, problem_reporter=None): + if not problem_reporter: + problem_reporter = self.problem_reporter + + route.Validate(problem_reporter) + + if route.route_id in self.routes: + problem_reporter.DuplicateID('route_id', route.route_id) + return + + if route.agency_id not in self._agencies: + if not route.agency_id and len(self._agencies) == 1: + # we'll just assume that the route applies to the only agency + pass + else: + problem_reporter.InvalidValue('agency_id', route.agency_id, + 'Route uses an unknown agency_id.') + return + + self.AddTableColumns('routes', route._ColumnNames()) + route._schedule = weakref.proxy(self) + self.routes[route.route_id] = route + + def GetRouteList(self): + return self.routes.values() + + def GetRoute(self, route_id): + return self.routes[route_id] + + def AddShapeObject(self, shape, problem_reporter=None): + if not problem_reporter: + problem_reporter = self.problem_reporter + + shape.Validate(problem_reporter) + + if shape.shape_id in self._shapes: + problem_reporter.DuplicateID('shape_id', shape.shape_id) + return + + self._shapes[shape.shape_id] = shape + + def GetShapeList(self): + return self._shapes.values() + + def GetShape(self, shape_id): + return self._shapes[shape_id] + + def AddTripObject(self, trip, problem_reporter=None, validate=True): + if not problem_reporter: + problem_reporter = self.problem_reporter + + if trip.trip_id in self.trips: + problem_reporter.DuplicateID('trip_id', trip.trip_id) + return + + self.AddTableColumns('trips', trip._ColumnNames()) + trip._schedule = weakref.proxy(self) + self.trips[trip.trip_id] = trip + + # Call Trip.Validate after setting trip._schedule so that references + # are checked. trip.ValidateChildren will be called directly by + # schedule.Validate, after stop_times has been loaded. + if validate: + if not problem_reporter: + problem_reporter = self.problem_reporter + trip.Validate(problem_reporter, validate_children=False) + try: + self.routes[trip.route_id]._AddTripObject(trip) + except KeyError: + # Invalid route_id was reported in the Trip.Validate call above + pass + + def GetTripList(self): + return self.trips.values() + + def GetTrip(self, trip_id): + return self.trips[trip_id] + + def AddFareObject(self, fare, problem_reporter=None): + if not problem_reporter: + problem_reporter = self.problem_reporter + fare.Validate(problem_reporter) + + if fare.fare_id in self.fares: + problem_reporter.DuplicateID('fare_id', fare.fare_id) + return + + self.fares[fare.fare_id] = fare + + def GetFareList(self): + return self.fares.values() + + def GetFare(self, fare_id): + return self.fares[fare_id] + + def AddFareRuleObject(self, rule, problem_reporter=None): + if not problem_reporter: + problem_reporter = self.problem_reporter + + if IsEmpty(rule.fare_id): + problem_reporter.MissingValue('fare_id') + return + + if rule.route_id and rule.route_id not in self.routes: + problem_reporter.InvalidValue('route_id', rule.route_id) + if rule.origin_id and rule.origin_id not in self.fare_zones: + problem_reporter.InvalidValue('origin_id', rule.origin_id) + if rule.destination_id and rule.destination_id not in self.fare_zones: + problem_reporter.InvalidValue('destination_id', rule.destination_id) + if rule.contains_id and rule.contains_id not in self.fare_zones: + problem_reporter.InvalidValue('contains_id', rule.contains_id) + + if rule.fare_id in self.fares: + self.GetFare(rule.fare_id).rules.append(rule) + else: + problem_reporter.InvalidValue('fare_id', rule.fare_id, + '(This fare_id doesn\'t correspond to any ' + 'of the IDs defined in the ' + 'fare attributes.)') + + def AddTransferObject(self, transfer, problem_reporter=None): + assert transfer._schedule is None, "only add Transfer to a schedule once" + transfer._schedule = weakref.proxy(self) # See weakref comment at top + if not problem_reporter: + problem_reporter = self.problem_reporter + + transfer.Validate(problem_reporter) + self._transfers.append(transfer) + + def GetTransferList(self): + return self._transfers + + def GetStop(self, id): + return self.stops[id] + + def GetFareZones(self): + """Returns the list of all fare zones that have been identified by + the stops that have been added.""" + return self.fare_zones.keys() + + def GetNearestStops(self, lat, lon, n=1): + """Return the n nearest stops to lat,lon""" + dist_stop_list = [] + for s in self.stops.values(): + # TODO: Use ApproximateDistanceBetweenStops? + dist = (s.stop_lat - lat)**2 + (s.stop_lon - lon)**2 + if len(dist_stop_list) < n: + bisect.insort(dist_stop_list, (dist, s)) + elif dist < dist_stop_list[-1][0]: + bisect.insort(dist_stop_list, (dist, s)) + dist_stop_list.pop() # Remove stop with greatest distance + return [stop for dist, stop in dist_stop_list] + + def GetStopsInBoundingBox(self, north, east, south, west, n): + """Return a sample of up to n stops in a bounding box""" + stop_list = [] + for s in self.stops.values(): + if (s.stop_lat <= north and s.stop_lat >= south and + s.stop_lon <= east and s.stop_lon >= west): + stop_list.append(s) + if len(stop_list) == n: + break + return stop_list + + def Load(self, feed_path, extra_validation=False): + loader = Loader(feed_path, self, problems=self.problem_reporter, + extra_validation=extra_validation) + loader.Load() + + def _WriteArchiveString(self, archive, filename, stringio): + zi = zipfile.ZipInfo(filename) + # See + # http://stackoverflow.com/questions/434641/how-do-i-set-permissions-attributes-on-a-file-in-a-zip-file-using-pythons-zipf + zi.external_attr = 0666 << 16L # Set unix permissions to -rw-rw-rw + # ZIP_DEFLATED requires zlib. zlib comes with Python 2.4 and 2.5 + zi.compress_type = zipfile.ZIP_DEFLATED + archive.writestr(zi, stringio.getvalue()) + + def WriteGoogleTransitFeed(self, file): + """Output this schedule as a Google Transit Feed in file_name. + + Args: + file: path of new feed file (a string) or a file-like object + + Returns: + None + """ + # Compression type given when adding each file + archive = zipfile.ZipFile(file, 'w') + + if 'agency' in self._table_columns: + agency_string = StringIO.StringIO() + writer = CsvUnicodeWriter(agency_string) + columns = self.GetTableColumns('agency') + writer.writerow(columns) + for a in self._agencies.values(): + writer.writerow([EncodeUnicode(a[c]) for c in columns]) + self._WriteArchiveString(archive, 'agency.txt', agency_string) + + calendar_dates_string = StringIO.StringIO() + writer = CsvUnicodeWriter(calendar_dates_string) + writer.writerow(ServicePeriod._FIELD_NAMES_CALENDAR_DATES) + has_data = False + for period in self.service_periods.values(): + for row in period.GenerateCalendarDatesFieldValuesTuples(): + has_data = True + writer.writerow(row) + wrote_calendar_dates = False + if has_data: + wrote_calendar_dates = True + self._WriteArchiveString(archive, 'calendar_dates.txt', + calendar_dates_string) + + calendar_string = StringIO.StringIO() + writer = CsvUnicodeWriter(calendar_string) + writer.writerow(ServicePeriod._FIELD_NAMES) + has_data = False + for s in self.service_periods.values(): + row = s.GetCalendarFieldValuesTuple() + if row: + has_data = True + writer.writerow(row) + if has_data or not wrote_calendar_dates: + self._WriteArchiveString(archive, 'calendar.txt', calendar_string) + + if 'stops' in self._table_columns: + stop_string = StringIO.StringIO() + writer = CsvUnicodeWriter(stop_string) + columns = self.GetTableColumns('stops') + writer.writerow(columns) + for s in self.stops.values(): + writer.writerow([EncodeUnicode(s[c]) for c in columns]) + self._WriteArchiveString(archive, 'stops.txt', stop_string) + + if 'routes' in self._table_columns: + route_string = StringIO.StringIO() + writer = CsvUnicodeWriter(route_string) + columns = self.GetTableColumns('routes') + writer.writerow(columns) + for r in self.routes.values(): + writer.writerow([EncodeUnicode(r[c]) for c in columns]) + self._WriteArchiveString(archive, 'routes.txt', route_string) + + if 'trips' in self._table_columns: + trips_string = StringIO.StringIO() + writer = CsvUnicodeWriter(trips_string) + columns = self.GetTableColumns('trips') + writer.writerow(columns) + for t in self.trips.values(): + writer.writerow([EncodeUnicode(t[c]) for c in columns]) + self._WriteArchiveString(archive, 'trips.txt', trips_string) + + # write frequencies.txt (if applicable) + headway_rows = [] + for trip in self.GetTripList(): + headway_rows += trip.GetHeadwayPeriodOutputTuples() + if headway_rows: + headway_string = StringIO.StringIO() + writer = CsvUnicodeWriter(headway_string) + writer.writerow(Trip._FIELD_NAMES_HEADWAY) + writer.writerows(headway_rows) + self._WriteArchiveString(archive, 'frequencies.txt', headway_string) + + # write fares (if applicable) + if self.GetFareList(): + fare_string = StringIO.StringIO() + writer = CsvUnicodeWriter(fare_string) + writer.writerow(Fare._FIELD_NAMES) + writer.writerows(f.GetFieldValuesTuple() for f in self.GetFareList()) + self._WriteArchiveString(archive, 'fare_attributes.txt', fare_string) + + # write fare rules (if applicable) + rule_rows = [] + for fare in self.GetFareList(): + for rule in fare.GetFareRuleList(): + rule_rows.append(rule.GetFieldValuesTuple()) + if rule_rows: + rule_string = StringIO.StringIO() + writer = CsvUnicodeWriter(rule_string) + writer.writerow(FareRule._FIELD_NAMES) + writer.writerows(rule_rows) + self._WriteArchiveString(archive, 'fare_rules.txt', rule_string) + stop_times_string = StringIO.StringIO() + writer = CsvUnicodeWriter(stop_times_string) + writer.writerow(StopTime._FIELD_NAMES) + for t in self.trips.values(): + writer.writerows(t._GenerateStopTimesTuples()) + self._WriteArchiveString(archive, 'stop_times.txt', stop_times_string) + + # write shapes (if applicable) + shape_rows = [] + for shape in self.GetShapeList(): + seq = 1 + for (lat, lon, dist) in shape.points: + shape_rows.append((shape.shape_id, lat, lon, seq, dist)) + seq += 1 + if shape_rows: + shape_string = StringIO.StringIO() + writer = CsvUnicodeWriter(shape_string) + writer.writerow(Shape._FIELD_NAMES) + writer.writerows(shape_rows) + self._WriteArchiveString(archive, 'shapes.txt', shape_string) + + # write transfers (if applicable) + if self.GetTransferList(): + transfer_string = StringIO.StringIO() + writer = CsvUnicodeWriter(transfer_string) + writer.writerow(Transfer._FIELD_NAMES) + writer.writerows(f.GetFieldValuesTuple() for f in self.GetTransferList()) + self._WriteArchiveString(archive, 'transfers.txt', transfer_string) + + archive.close() + + def GenerateDateTripsDeparturesList(self, date_start, date_end): + """Return a list of (date object, number of trips, number of departures). + + The list is generated for dates in the range [date_start, date_end). + + Args: + date_start: The first date in the list, a date object + date_end: The first date after the list, a date object + + Returns: + a list of (date object, number of trips, number of departures) tuples + """ + + service_id_to_trips = defaultdict(lambda: 0) + service_id_to_departures = defaultdict(lambda: 0) + for trip in self.GetTripList(): + headway_start_times = trip.GetHeadwayStartTimes() + if headway_start_times: + trip_runs = len(headway_start_times) + else: + trip_runs = 1 + + service_id_to_trips[trip.service_id] += trip_runs + service_id_to_departures[trip.service_id] += ( + (trip.GetCountStopTimes() - 1) * trip_runs) + + date_services = self.GetServicePeriodsActiveEachDate(date_start, date_end) + date_trips = [] + + for date, services in date_services: + day_trips = sum(service_id_to_trips[s.service_id] for s in services) + day_departures = sum( + service_id_to_departures[s.service_id] for s in services) + date_trips.append((date, day_trips, day_departures)) + return date_trips + + def ValidateFeedStartAndExpirationDates(self, + problems, + first_date, + last_date, + today): + """Validate the start and expiration dates of the feed. + Issue a warning if it only starts in the future, or if + it expires within 60 days. + + Args: + problems: The problem reporter object + first_date: A date object representing the first day the feed is active + last_date: A date object representing the last day the feed is active + today: A date object representing the date the validation is being run on + + Returns: + None + """ + warning_cutoff = today + datetime.timedelta(days=60) + if last_date < warning_cutoff: + problems.ExpirationDate(time.mktime(last_date.timetuple())) + + if first_date > today: + problems.FutureService(time.mktime(first_date.timetuple())) + + def ValidateServiceGaps(self, + problems, + validation_start_date, + validation_end_date, + service_gap_interval): + """Validate consecutive dates without service in the feed. + Issue a warning if it finds service gaps of at least + "service_gap_interval" consecutive days in the date range + [validation_start_date, last_service_date) + + Args: + problems: The problem reporter object + validation_start_date: A date object representing the date from which the + validation should take place + validation_end_date: A date object representing the first day the feed is + active + service_gap_interval: An integer indicating how many consecutive days the + service gaps need to have for a warning to be issued + + Returns: + None + """ + if service_gap_interval is None: + return + + departures = self.GenerateDateTripsDeparturesList(validation_start_date, + validation_end_date) + + # The first day without service of the _current_ gap + first_day_without_service = validation_start_date + # The last day without service of the _current_ gap + last_day_without_service = validation_start_date + + consecutive_days_without_service = 0 + + for day_date, day_trips, _ in departures: + if day_trips == 0: + if consecutive_days_without_service == 0: + first_day_without_service = day_date + consecutive_days_without_service += 1 + last_day_without_service = day_date + else: + if consecutive_days_without_service >= service_gap_interval: + problems.TooManyDaysWithoutService(first_day_without_service, + last_day_without_service, + consecutive_days_without_service) + + consecutive_days_without_service = 0 + + # We have to check if there is a gap at the end of the specified date range + if consecutive_days_without_service >= service_gap_interval: + problems.TooManyDaysWithoutService(first_day_without_service, + last_day_without_service, + consecutive_days_without_service) + + def Validate(self, + problems=None, + validate_children=True, + today=None, + service_gap_interval=None): + """Validates various holistic aspects of the schedule + (mostly interrelationships between the various data sets).""" + + if today is None: + today = datetime.date.today() + + if not problems: + problems = self.problem_reporter + + (start_date, end_date) = self.GetDateRange() + if not end_date or not start_date: + problems.OtherProblem('This feed has no effective service dates!', + type=TYPE_WARNING) + else: + try: + last_service_day = datetime.datetime( + *(time.strptime(end_date, "%Y%m%d")[0:6])).date() + first_service_day = datetime.datetime( + *(time.strptime(start_date, "%Y%m%d")[0:6])).date() + + except ValueError: + # Format of start_date and end_date checked in class ServicePeriod + pass + + else: + + self.ValidateFeedStartAndExpirationDates(problems, + first_service_day, + last_service_day, + today) + + # We start checking for service gaps a bit in the past if the + # feed was active then. See + # http://code.google.com/p/googletransitdatafeed/issues/detail?id=188 + # + # We subtract 1 from service_gap_interval so that if today has + # service no warning is issued. + # + # Service gaps are searched for only up to one year from today + if service_gap_interval is not None: + service_gap_timedelta = datetime.timedelta( + days=service_gap_interval - 1) + one_year = datetime.timedelta(days=365) + self.ValidateServiceGaps( + problems, + max(first_service_day, + today - service_gap_timedelta), + min(last_service_day, + today + one_year), + service_gap_interval) + + # TODO: Check Trip fields against valid values + + # Check for stops that aren't referenced by any trips and broken + # parent_station references. Also check that the parent station isn't too + # far from its child stops. + for stop in self.stops.values(): + if validate_children: + stop.Validate(problems) + cursor = self._connection.cursor() + cursor.execute("SELECT count(*) FROM stop_times WHERE stop_id=? LIMIT 1", + (stop.stop_id,)) + count = cursor.fetchone()[0] + if stop.location_type == 0 and count == 0: + problems.UnusedStop(stop.stop_id, stop.stop_name) + elif stop.location_type == 1 and count != 0: + problems.UsedStation(stop.stop_id, stop.stop_name) + + if stop.location_type != 1 and stop.parent_station: + if stop.parent_station not in self.stops: + problems.InvalidValue("parent_station", + EncodeUnicode(stop.parent_station), + "parent_station '%s' not found for stop_id " + "'%s' in stops.txt" % + (EncodeUnicode(stop.parent_station), + EncodeUnicode(stop.stop_id))) + elif self.stops[stop.parent_station].location_type != 1: + problems.InvalidValue("parent_station", + EncodeUnicode(stop.parent_station), + "parent_station '%s' of stop_id '%s' must " + "have location_type=1 in stops.txt" % + (EncodeUnicode(stop.parent_station), + EncodeUnicode(stop.stop_id))) + else: + parent_station = self.stops[stop.parent_station] + distance = ApproximateDistanceBetweenStops(stop, parent_station) + if distance > MAX_DISTANCE_BETWEEN_STOP_AND_PARENT_STATION_ERROR: + problems.StopTooFarFromParentStation( + stop.stop_id, stop.stop_name, parent_station.stop_id, + parent_station.stop_name, distance, TYPE_ERROR) + elif distance > MAX_DISTANCE_BETWEEN_STOP_AND_PARENT_STATION_WARNING: + problems.StopTooFarFromParentStation( + stop.stop_id, stop.stop_name, parent_station.stop_id, + parent_station.stop_name, distance, TYPE_WARNING) + + #TODO: check that every station is used. + # Then uncomment testStationWithoutReference. + + # Check for stops that might represent the same location (specifically, + # stops that are less that 2 meters apart) First filter out stops without a + # valid lat and lon. Then sort by latitude, then find the distance between + # each pair of stations within 2 meters latitude of each other. This avoids + # doing n^2 comparisons in the average case and doesn't need a spatial + # index. + sorted_stops = filter(lambda s: s.stop_lat and s.stop_lon, + self.GetStopList()) + sorted_stops.sort(key=(lambda x: x.stop_lat)) + TWO_METERS_LAT = 0.000018 + for index, stop in enumerate(sorted_stops[:-1]): + index += 1 + while ((index < len(sorted_stops)) and + ((sorted_stops[index].stop_lat - stop.stop_lat) < TWO_METERS_LAT)): + distance = ApproximateDistanceBetweenStops(stop, sorted_stops[index]) + if distance < 2: + other_stop = sorted_stops[index] + if stop.location_type == 0 and other_stop.location_type == 0: + problems.StopsTooClose( + EncodeUnicode(stop.stop_name), + EncodeUnicode(stop.stop_id), + EncodeUnicode(other_stop.stop_name), + EncodeUnicode(other_stop.stop_id), distance) + elif stop.location_type == 1 and other_stop.location_type == 1: + problems.StationsTooClose( + EncodeUnicode(stop.stop_name), EncodeUnicode(stop.stop_id), + EncodeUnicode(other_stop.stop_name), + EncodeUnicode(other_stop.stop_id), distance) + elif (stop.location_type in (0, 1) and + other_stop.location_type in (0, 1)): + if stop.location_type == 0 and other_stop.location_type == 1: + this_stop = stop + this_station = other_stop + elif stop.location_type == 1 and other_stop.location_type == 0: + this_stop = other_stop + this_station = stop + if this_stop.parent_station != this_station.stop_id: + problems.DifferentStationTooClose( + EncodeUnicode(this_stop.stop_name), + EncodeUnicode(this_stop.stop_id), + EncodeUnicode(this_station.stop_name), + EncodeUnicode(this_station.stop_id), distance) + index += 1 + + # Check for multiple routes using same short + long name + route_names = {} + for route in self.routes.values(): + if validate_children: + route.Validate(problems) + short_name = '' + if not IsEmpty(route.route_short_name): + short_name = route.route_short_name.lower().strip() + long_name = '' + if not IsEmpty(route.route_long_name): + long_name = route.route_long_name.lower().strip() + name = (short_name, long_name) + if name in route_names: + problems.InvalidValue('route_long_name', + long_name, + 'The same combination of ' + 'route_short_name and route_long_name ' + 'shouldn\'t be used for more than one ' + 'route, as it is for the for the two routes ' + 'with IDs "%s" and "%s".' % + (route.route_id, route_names[name].route_id), + type=TYPE_WARNING) + else: + route_names[name] = route + + stop_types = {} # a dict mapping stop_id to [route_id, route_type, is_match] + trips = {} # a dict mapping tuple to (route_id, trip_id) + for trip in sorted(self.trips.values()): + if trip.route_id not in self.routes: + continue + route_type = self.GetRoute(trip.route_id).route_type + arrival_times = [] + stop_ids = [] + for index, st in enumerate(trip.GetStopTimes(problems)): + stop_id = st.stop.stop_id + arrival_times.append(st.arrival_time) + stop_ids.append(stop_id) + # Check a stop if which belongs to both subway and bus. + if (route_type == Route._ROUTE_TYPE_NAMES['Subway'] or + route_type == Route._ROUTE_TYPE_NAMES['Bus']): + if stop_id not in stop_types: + stop_types[stop_id] = [trip.route_id, route_type, 0] + elif (stop_types[stop_id][1] != route_type and + stop_types[stop_id][2] == 0): + stop_types[stop_id][2] = 1 + if stop_types[stop_id][1] == Route._ROUTE_TYPE_NAMES['Subway']: + subway_route_id = stop_types[stop_id][0] + bus_route_id = trip.route_id + else: + subway_route_id = trip.route_id + bus_route_id = stop_types[stop_id][0] + problems.StopWithMultipleRouteTypes(st.stop.stop_name, stop_id, + subway_route_id, bus_route_id) + + # Check duplicate trips which go through the same stops with same + # service and start times. + if self._check_duplicate_trips: + if not stop_ids or not arrival_times: + continue + key = (trip.service_id, min(arrival_times), str(stop_ids)) + if key not in trips: + trips[key] = (trip.route_id, trip.trip_id) + else: + problems.DuplicateTrip(trips[key][1], trips[key][0], trip.trip_id, + trip.route_id) + + # Check that routes' agency IDs are valid, if set + for route in self.routes.values(): + if (not IsEmpty(route.agency_id) and + not route.agency_id in self._agencies): + problems.InvalidValue('agency_id', + route.agency_id, + 'The route with ID "%s" specifies agency_id ' + '"%s", which doesn\'t exist.' % + (route.route_id, route.agency_id)) + + # Make sure all trips have stop_times + # We're doing this here instead of in Trip.Validate() so that + # Trips can be validated without error during the reading of trips.txt + for trip in self.trips.values(): + trip.ValidateChildren(problems) + count_stop_times = trip.GetCountStopTimes() + if not count_stop_times: + problems.OtherProblem('The trip with the trip_id "%s" doesn\'t have ' + 'any stop times defined.' % trip.trip_id, + type=TYPE_WARNING) + if len(trip._headways) > 0: # no stoptimes, but there are headways + problems.OtherProblem('Frequencies defined, but no stop times given ' + 'in trip %s' % trip.trip_id, type=TYPE_ERROR) + elif count_stop_times == 1: + problems.OtherProblem('The trip with the trip_id "%s" only has one ' + 'stop on it; it should have at least one more ' + 'stop so that the riders can leave!' % + trip.trip_id, type=TYPE_WARNING) + else: + # These methods report InvalidValue if there's no first or last time + trip.GetStartTime(problems=problems) + trip.GetEndTime(problems=problems) + + # Check for unused shapes + known_shape_ids = set(self._shapes.keys()) + used_shape_ids = set() + for trip in self.GetTripList(): + used_shape_ids.add(trip.shape_id) + unused_shape_ids = known_shape_ids - used_shape_ids + if unused_shape_ids: + problems.OtherProblem('The shapes with the following shape_ids aren\'t ' + 'used by any trips: %s' % + ', '.join(unused_shape_ids), + type=TYPE_WARNING) + + +# Map from literal string that should never be found in the csv data to a human +# readable description +INVALID_LINE_SEPARATOR_UTF8 = { + "\x0c": "ASCII Form Feed 0x0C", + # May be part of end of line, but not found elsewhere + "\x0d": "ASCII Carriage Return 0x0D, \\r", + "\xe2\x80\xa8": "Unicode LINE SEPARATOR U+2028", + "\xe2\x80\xa9": "Unicode PARAGRAPH SEPARATOR U+2029", + "\xc2\x85": "Unicode NEXT LINE SEPARATOR U+0085", +} + +class EndOfLineChecker: + """Wrapper for a file-like object that checks for consistent line ends. + + The check for consistent end of lines (all CR LF or all LF) only happens if + next() is called until it raises StopIteration. + """ + def __init__(self, f, name, problems): + """Create new object. + + Args: + f: file-like object to wrap + name: name to use for f. StringIO objects don't have a name attribute. + problems: a ProblemReporterBase object + """ + self._f = f + self._name = name + self._crlf = 0 + self._crlf_examples = [] + self._lf = 0 + self._lf_examples = [] + self._line_number = 0 # first line will be number 1 + self._problems = problems + + def __iter__(self): + return self + + def next(self): + """Return next line without end of line marker or raise StopIteration.""" + try: + next_line = self._f.next() + except StopIteration: + self._FinalCheck() + raise + + self._line_number += 1 + m_eol = re.search(r"[\x0a\x0d]*$", next_line) + if m_eol.group() == "\x0d\x0a": + self._crlf += 1 + if self._crlf <= 5: + self._crlf_examples.append(self._line_number) + elif m_eol.group() == "\x0a": + self._lf += 1 + if self._lf <= 5: + self._lf_examples.append(self._line_number) + elif m_eol.group() == "": + # Should only happen at the end of the file + try: + self._f.next() + raise RuntimeError("Unexpected row without new line sequence") + except StopIteration: + # Will be raised again when EndOfLineChecker.next() is next called + pass + else: + self._problems.InvalidLineEnd( + codecs.getencoder('string_escape')(m_eol.group())[0], + (self._name, self._line_number)) + next_line_contents = next_line[0:m_eol.start()] + for seq, name in INVALID_LINE_SEPARATOR_UTF8.items(): + if next_line_contents.find(seq) != -1: + self._problems.OtherProblem( + "Line contains %s" % name, + context=(self._name, self._line_number)) + return next_line_contents + + def _FinalCheck(self): + if self._crlf > 0 and self._lf > 0: + crlf_plural = self._crlf > 1 and "s" or "" + crlf_lines = ", ".join(["%s" % e for e in self._crlf_examples]) + if self._crlf > len(self._crlf_examples): + crlf_lines += ", ..." + lf_plural = self._lf > 1 and "s" or "" + lf_lines = ", ".join(["%s" % e for e in self._lf_examples]) + if self._lf > len(self._lf_examples): + lf_lines += ", ..." + + self._problems.OtherProblem( + "Found %d CR LF \"\\r\\n\" line end%s (line%s %s) and " + "%d LF \"\\n\" line end%s (line%s %s). A file must use a " + "consistent line end." % (self._crlf, crlf_plural, crlf_plural, + crlf_lines, self._lf, lf_plural, + lf_plural, lf_lines), + (self._name,)) + # Prevent _FinalCheck() from reporting the problem twice, in the unlikely + # case that it is run twice + self._crlf = 0 + self._lf = 0 + + +# Filenames specified in GTFS spec +KNOWN_FILENAMES = [ + 'agency.txt', + 'stops.txt', + 'routes.txt', + 'trips.txt', + 'stop_times.txt', + 'calendar.txt', + 'calendar_dates.txt', + 'fare_attributes.txt', + 'fare_rules.txt', + 'shapes.txt', + 'frequencies.txt', + 'transfers.txt', +] + +class Loader: + def __init__(self, + feed_path=None, + schedule=None, + problems=default_problem_reporter, + extra_validation=False, + load_stop_times=True, + memory_db=True, + zip=None, + check_duplicate_trips=False): + """Initialize a new Loader object. + + Args: + feed_path: string path to a zip file or directory + schedule: a Schedule object or None to have one created + problems: a ProblemReporter object, the default reporter raises an + exception for each problem + extra_validation: True if you would like extra validation + load_stop_times: load the stop_times table, used to speed load time when + times are not needed. The default is True. + memory_db: if creating a new Schedule object use an in-memory sqlite + database instead of creating one in a temporary file + zip: a zipfile.ZipFile object, optionally used instead of path + """ + if not schedule: + schedule = Schedule(problem_reporter=problems, memory_db=memory_db, + check_duplicate_trips=check_duplicate_trips) + self._extra_validation = extra_validation + self._schedule = schedule + self._problems = problems + self._path = feed_path + self._zip = zip + self._load_stop_times = load_stop_times + + def _DetermineFormat(self): + """Determines whether the feed is in a form that we understand, and + if so, returns True.""" + if self._zip: + # If zip was passed to __init__ then path isn't used + assert not self._path + return True + + if not isinstance(self._path, basestring) and hasattr(self._path, 'read'): + # A file-like object, used for testing with a StringIO file + self._zip = zipfile.ZipFile(self._path, mode='r') + return True + + if not os.path.exists(self._path): + self._problems.FeedNotFound(self._path) + return False + + if self._path.endswith('.zip'): + try: + self._zip = zipfile.ZipFile(self._path, mode='r') + except IOError: # self._path is a directory + pass + except zipfile.BadZipfile: + self._problems.UnknownFormat(self._path) + return False + + if not self._zip and not os.path.isdir(self._path): + self._problems.UnknownFormat(self._path) + return False + + return True + + def _GetFileNames(self): + """Returns a list of file names in the feed.""" + if self._zip: + return self._zip.namelist() + else: + return os.listdir(self._path) + + def _CheckFileNames(self): + filenames = self._GetFileNames() + for feed_file in filenames: + if feed_file not in KNOWN_FILENAMES: + if not feed_file.startswith('.'): + # Don't worry about .svn files and other hidden files + # as this will break the tests. + self._problems.UnknownFile(feed_file) + + def _GetUtf8Contents(self, file_name): + """Check for errors in file_name and return a string for csv reader.""" + contents = self._FileContents(file_name) + if not contents: # Missing file + return + + # Check for errors that will prevent csv.reader from working + if len(contents) >= 2 and contents[0:2] in (codecs.BOM_UTF16_BE, + codecs.BOM_UTF16_LE): + self._problems.FileFormat("appears to be encoded in utf-16", (file_name, )) + # Convert and continue, so we can find more errors + contents = codecs.getdecoder('utf-16')(contents)[0].encode('utf-8') + + null_index = contents.find('\0') + if null_index != -1: + # It is easier to get some surrounding text than calculate the exact + # row_num + m = re.search(r'.{,20}\0.{,20}', contents, re.DOTALL) + self._problems.FileFormat( + "contains a null in text \"%s\" at byte %d" % + (codecs.getencoder('string_escape')(m.group()), null_index + 1), + (file_name, )) + return + + # strip out any UTF-8 Byte Order Marker (otherwise it'll be + # treated as part of the first column name, causing a mis-parse) + contents = contents.lstrip(codecs.BOM_UTF8) + return contents + + def _ReadCsvDict(self, file_name, all_cols, required): + """Reads lines from file_name, yielding a dict of unicode values.""" + assert file_name.endswith(".txt") + table_name = file_name[0:-4] + contents = self._GetUtf8Contents(file_name) + if not contents: + return + + eol_checker = EndOfLineChecker(StringIO.StringIO(contents), + file_name, self._problems) + # The csv module doesn't provide a way to skip trailing space, but when I + # checked 15/675 feeds had trailing space in a header row and 120 had spaces + # after fields. Space after header fields can cause a serious parsing + # problem, so warn. Space after body fields can cause a problem time, + # integer and id fields; they will be validated at higher levels. + reader = csv.reader(eol_checker, skipinitialspace=True) + + raw_header = reader.next() + header_occurrences = defaultdict(lambda: 0) + header = [] + valid_columns = [] # Index into raw_header and raw_row + for i, h in enumerate(raw_header): + h_stripped = h.strip() + if not h_stripped: + self._problems.CsvSyntax( + description="The header row should not contain any blank values. " + "The corresponding column will be skipped for the " + "entire file.", + context=(file_name, 1, [''] * len(raw_header), raw_header), + type=TYPE_ERROR) + continue + elif h != h_stripped: + self._problems.CsvSyntax( + description="The header row should not contain any " + "space characters.", + context=(file_name, 1, [''] * len(raw_header), raw_header), + type=TYPE_WARNING) + header.append(h_stripped) + valid_columns.append(i) + header_occurrences[h_stripped] += 1 + + for name, count in header_occurrences.items(): + if count > 1: + self._problems.DuplicateColumn( + header=name, + file_name=file_name, + count=count) + + self._schedule._table_columns[table_name] = header + + # check for unrecognized columns, which are often misspellings + unknown_cols = set(header) - set(all_cols) + if len(unknown_cols) == len(header): + self._problems.CsvSyntax( + description="The header row did not contain any known column " + "names. The file is most likely missing the header row " + "or not in the expected CSV format.", + context=(file_name, 1, [''] * len(raw_header), raw_header), + type=TYPE_ERROR) + else: + for col in unknown_cols: + # this is provided in order to create a nice colored list of + # columns in the validator output + context = (file_name, 1, [''] * len(header), header) + self._problems.UnrecognizedColumn(file_name, col, context) + + missing_cols = set(required) - set(header) + for col in missing_cols: + # this is provided in order to create a nice colored list of + # columns in the validator output + context = (file_name, 1, [''] * len(header), header) + self._problems.MissingColumn(file_name, col, context) + + line_num = 1 # First line read by reader.next() above + for raw_row in reader: + line_num += 1 + if len(raw_row) == 0: # skip extra empty lines in file + continue + + if len(raw_row) > len(raw_header): + self._problems.OtherProblem('Found too many cells (commas) in line ' + '%d of file "%s". Every row in the file ' + 'should have the same number of cells as ' + 'the header (first line) does.' % + (line_num, file_name), + (file_name, line_num), + type=TYPE_WARNING) + + if len(raw_row) < len(raw_header): + self._problems.OtherProblem('Found missing cells (commas) in line ' + '%d of file "%s". Every row in the file ' + 'should have the same number of cells as ' + 'the header (first line) does.' % + (line_num, file_name), + (file_name, line_num), + type=TYPE_WARNING) + + # raw_row is a list of raw bytes which should be valid utf-8. Convert each + # valid_columns of raw_row into Unicode. + valid_values = [] + unicode_error_columns = [] # index of valid_values elements with an error + for i in valid_columns: + try: + valid_values.append(raw_row[i].decode('utf-8')) + except UnicodeDecodeError: + # Replace all invalid characters with REPLACEMENT CHARACTER (U+FFFD) + valid_values.append(codecs.getdecoder("utf8") + (raw_row[i], errors="replace")[0]) + unicode_error_columns.append(len(valid_values) - 1) + except IndexError: + break + + # The error report may contain a dump of all values in valid_values so + # problems can not be reported until after converting all of raw_row to + # Unicode. + for i in unicode_error_columns: + self._problems.InvalidValue(header[i], valid_values[i], + 'Unicode error', + (file_name, line_num, + valid_values, header)) + + + d = dict(zip(header, valid_values)) + yield (d, line_num, header, valid_values) + + # TODO: Add testing for this specific function + def _ReadCSV(self, file_name, cols, required): + """Reads lines from file_name, yielding a list of unicode values + corresponding to the column names in cols.""" + contents = self._GetUtf8Contents(file_name) + if not contents: + return + + eol_checker = EndOfLineChecker(StringIO.StringIO(contents), + file_name, self._problems) + reader = csv.reader(eol_checker) # Use excel dialect + + header = reader.next() + header = map(lambda x: x.strip(), header) # trim any whitespace + header_occurrences = defaultdict(lambda: 0) + for column_header in header: + header_occurrences[column_header] += 1 + + for name, count in header_occurrences.items(): + if count > 1: + self._problems.DuplicateColumn( + header=name, + file_name=file_name, + count=count) + + # check for unrecognized columns, which are often misspellings + unknown_cols = set(header).difference(set(cols)) + for col in unknown_cols: + # this is provided in order to create a nice colored list of + # columns in the validator output + context = (file_name, 1, [''] * len(header), header) + self._problems.UnrecognizedColumn(file_name, col, context) + + col_index = [-1] * len(cols) + for i in range(len(cols)): + if cols[i] in header: + col_index[i] = header.index(cols[i]) + elif cols[i] in required: + self._problems.MissingColumn(file_name, cols[i]) + + row_num = 1 + for row in reader: + row_num += 1 + if len(row) == 0: # skip extra empty lines in file + continue + + if len(row) > len(header): + self._problems.OtherProblem('Found too many cells (commas) in line ' + '%d of file "%s". Every row in the file ' + 'should have the same number of cells as ' + 'the header (first line) does.' % + (row_num, file_name), (file_name, row_num), + type=TYPE_WARNING) + + if len(row) < len(header): + self._problems.OtherProblem('Found missing cells (commas) in line ' + '%d of file "%s". Every row in the file ' + 'should have the same number of cells as ' + 'the header (first line) does.' % + (row_num, file_name), (file_name, row_num), + type=TYPE_WARNING) + + result = [None] * len(cols) + unicode_error_columns = [] # A list of column numbers with an error + for i in range(len(cols)): + ci = col_index[i] + if ci >= 0: + if len(row) <= ci: # handle short CSV rows + result[i] = u'' + else: + try: + result[i] = row[ci].decode('utf-8').strip() + except UnicodeDecodeError: + # Replace all invalid characters with + # REPLACEMENT CHARACTER (U+FFFD) + result[i] = codecs.getdecoder("utf8")(row[ci], + errors="replace")[0].strip() + unicode_error_columns.append(i) + + for i in unicode_error_columns: + self._problems.InvalidValue(cols[i], result[i], + 'Unicode error', + (file_name, row_num, result, cols)) + yield (result, row_num, cols) + + def _HasFile(self, file_name): + """Returns True if there's a file in the current feed with the + given file_name in the current feed.""" + if self._zip: + return file_name in self._zip.namelist() + else: + file_path = os.path.join(self._path, file_name) + return os.path.exists(file_path) and os.path.isfile(file_path) + + def _FileContents(self, file_name): + results = None + if self._zip: + try: + results = self._zip.read(file_name) + except KeyError: # file not found in archve + self._problems.MissingFile(file_name) + return None + else: + try: + data_file = open(os.path.join(self._path, file_name), 'rb') + results = data_file.read() + except IOError: # file not found + self._problems.MissingFile(file_name) + return None + + if not results: + self._problems.EmptyFile(file_name) + return results + + def _LoadAgencies(self): + for (d, row_num, header, row) in self._ReadCsvDict('agency.txt', + Agency._FIELD_NAMES, + Agency._REQUIRED_FIELD_NAMES): + self._problems.SetFileContext('agency.txt', row_num, row, header) + agency = Agency(field_dict=d) + self._schedule.AddAgencyObject(agency, self._problems) + self._problems.ClearContext() + + def _LoadStops(self): + for (d, row_num, header, row) in self._ReadCsvDict( + 'stops.txt', + Stop._FIELD_NAMES, + Stop._REQUIRED_FIELD_NAMES): + self._problems.SetFileContext('stops.txt', row_num, row, header) + + stop = Stop(field_dict=d) + stop.Validate(self._problems) + self._schedule.AddStopObject(stop, self._problems) + + self._problems.ClearContext() + + def _LoadRoutes(self): + for (d, row_num, header, row) in self._ReadCsvDict( + 'routes.txt', + Route._FIELD_NAMES, + Route._REQUIRED_FIELD_NAMES): + self._problems.SetFileContext('routes.txt', row_num, row, header) + + route = Route(field_dict=d) + self._schedule.AddRouteObject(route, self._problems) + + self._problems.ClearContext() + + def _LoadCalendar(self): + file_name = 'calendar.txt' + file_name_dates = 'calendar_dates.txt' + if not self._HasFile(file_name) and not self._HasFile(file_name_dates): + self._problems.MissingFile(file_name) + return + + # map period IDs to (period object, (file_name, row_num, row, cols)) + periods = {} + + # process calendar.txt + if self._HasFile(file_name): + has_useful_contents = False + for (row, row_num, cols) in \ + self._ReadCSV(file_name, + ServicePeriod._FIELD_NAMES, + ServicePeriod._FIELD_NAMES_REQUIRED): + context = (file_name, row_num, row, cols) + self._problems.SetFileContext(*context) + + period = ServicePeriod(field_list=row) + + if period.service_id in periods: + self._problems.DuplicateID('service_id', period.service_id) + else: + periods[period.service_id] = (period, context) + self._problems.ClearContext() + + # process calendar_dates.txt + if self._HasFile(file_name_dates): + # ['service_id', 'date', 'exception_type'] + fields = ServicePeriod._FIELD_NAMES_CALENDAR_DATES + for (row, row_num, cols) in self._ReadCSV(file_name_dates, + fields, fields): + context = (file_name_dates, row_num, row, cols) + self._problems.SetFileContext(*context) + + service_id = row[0] + + period = None + if service_id in periods: + period = periods[service_id][0] + else: + period = ServicePeriod(service_id) + periods[period.service_id] = (period, context) + + exception_type = row[2] + if exception_type == u'1': + period.SetDateHasService(row[1], True, self._problems) + elif exception_type == u'2': + period.SetDateHasService(row[1], False, self._problems) + else: + self._problems.InvalidValue('exception_type', exception_type) + self._problems.ClearContext() + + # Now insert the periods into the schedule object, so that they're + # validated with both calendar and calendar_dates info present + for period, context in periods.values(): + self._problems.SetFileContext(*context) + self._schedule.AddServicePeriodObject(period, self._problems) + self._problems.ClearContext() + + def _LoadShapes(self): + if not self._HasFile('shapes.txt'): + return + + shapes = {} # shape_id to tuple + for (row, row_num, cols) in self._ReadCSV('shapes.txt', + Shape._FIELD_NAMES, + Shape._REQUIRED_FIELD_NAMES): + file_context = ('shapes.txt', row_num, row, cols) + self._problems.SetFileContext(*file_context) + + (shape_id, lat, lon, seq, dist) = row + if IsEmpty(shape_id): + self._problems.MissingValue('shape_id') + continue + try: + seq = int(seq) + except (TypeError, ValueError): + self._problems.InvalidValue('shape_pt_sequence', seq, + 'Value should be a number (0 or higher)') + continue + + shapes.setdefault(shape_id, []).append((seq, lat, lon, dist, file_context)) + self._problems.ClearContext() + + for shape_id, points in shapes.items(): + shape = Shape(shape_id) + points.sort() + if points and points[0][0] < 0: + self._problems.InvalidValue('shape_pt_sequence', points[0][0], + 'In shape %s, a negative sequence number ' + '%d was found; sequence numbers should be ' + '0 or higher.' % (shape_id, points[0][0])) + + last_seq = None + for (seq, lat, lon, dist, file_context) in points: + if (seq == last_seq): + self._problems.SetFileContext(*file_context) + self._problems.InvalidValue('shape_pt_sequence', seq, + 'The sequence number %d occurs more ' + 'than once in shape %s.' % + (seq, shape_id)) + last_seq = seq + shape.AddPoint(lat, lon, dist, self._problems) + self._problems.ClearContext() + + self._schedule.AddShapeObject(shape, self._problems) + + def _LoadTrips(self): + for (d, row_num, header, row) in self._ReadCsvDict( + 'trips.txt', + Trip._FIELD_NAMES, + Trip._REQUIRED_FIELD_NAMES): + self._problems.SetFileContext('trips.txt', row_num, row, header) + + trip = Trip(field_dict=d) + self._schedule.AddTripObject(trip, self._problems) + + self._problems.ClearContext() + + def _LoadFares(self): + if not self._HasFile('fare_attributes.txt'): + return + for (row, row_num, cols) in self._ReadCSV('fare_attributes.txt', + Fare._FIELD_NAMES, + Fare._REQUIRED_FIELD_NAMES): + self._problems.SetFileContext('fare_attributes.txt', row_num, row, cols) + + fare = Fare(field_list=row) + self._schedule.AddFareObject(fare, self._problems) + + self._problems.ClearContext() + + def _LoadFareRules(self): + if not self._HasFile('fare_rules.txt'): + return + for (row, row_num, cols) in self._ReadCSV('fare_rules.txt', + FareRule._FIELD_NAMES, + FareRule._REQUIRED_FIELD_NAMES): + self._problems.SetFileContext('fare_rules.txt', row_num, row, cols) + + rule = FareRule(field_list=row) + self._schedule.AddFareRuleObject(rule, self._problems) + + self._problems.ClearContext() + + def _LoadHeadways(self): + file_name = 'frequencies.txt' + if not self._HasFile(file_name): # headways are an optional feature + return + + # ['trip_id', 'start_time', 'end_time', 'headway_secs'] + fields = Trip._FIELD_NAMES_HEADWAY + modified_trips = {} + for (row, row_num, cols) in self._ReadCSV(file_name, fields, fields): + self._problems.SetFileContext(file_name, row_num, row, cols) + (trip_id, start_time, end_time, headway_secs) = row + try: + trip = self._schedule.GetTrip(trip_id) + trip.AddHeadwayPeriod(start_time, end_time, headway_secs, + self._problems) + modified_trips[trip_id] = trip + except KeyError: + self._problems.InvalidValue('trip_id', trip_id) + self._problems.ClearContext() + + for trip in modified_trips.values(): + trip.Validate(self._problems) + + def _LoadStopTimes(self): + for (row, row_num, cols) in self._ReadCSV('stop_times.txt', + StopTime._FIELD_NAMES, + StopTime._REQUIRED_FIELD_NAMES): + file_context = ('stop_times.txt', row_num, row, cols) + self._problems.SetFileContext(*file_context) + + (trip_id, arrival_time, departure_time, stop_id, stop_sequence, + stop_headsign, pickup_type, drop_off_type, shape_dist_traveled) = row + + try: + sequence = int(stop_sequence) + except (TypeError, ValueError): + self._problems.InvalidValue('stop_sequence', stop_sequence, + 'This should be a number.') + continue + if sequence < 0: + self._problems.InvalidValue('stop_sequence', sequence, + 'Sequence numbers should be 0 or higher.') + + if stop_id not in self._schedule.stops: + self._problems.InvalidValue('stop_id', stop_id, + 'This value wasn\'t defined in stops.txt') + continue + stop = self._schedule.stops[stop_id] + if trip_id not in self._schedule.trips: + self._problems.InvalidValue('trip_id', trip_id, + 'This value wasn\'t defined in trips.txt') + continue + trip = self._schedule.trips[trip_id] + + # If self._problems.Report returns then StopTime.__init__ will return + # even if the StopTime object has an error. Thus this code may add a + # StopTime that didn't validate to the database. + # Trip.GetStopTimes then tries to make a StopTime from the invalid data + # and calls the problem reporter for errors. An ugly solution is to + # wrap problems and a better solution is to move all validation out of + # __init__. For now make sure Trip.GetStopTimes gets a problem reporter + # when called from Trip.Validate. + stop_time = StopTime(self._problems, stop, arrival_time, + departure_time, stop_headsign, + pickup_type, drop_off_type, + shape_dist_traveled, stop_sequence=sequence) + trip._AddStopTimeObjectUnordered(stop_time, self._schedule) + self._problems.ClearContext() + + # stop_times are validated in Trip.ValidateChildren, called by + # Schedule.Validate + + def _LoadTransfers(self): + file_name = 'transfers.txt' + if not self._HasFile(file_name): # transfers are an optional feature + return + for (d, row_num, header, row) in self._ReadCsvDict(file_name, + Transfer._FIELD_NAMES, + Transfer._REQUIRED_FIELD_NAMES): + self._problems.SetFileContext(file_name, row_num, row, header) + transfer = Transfer(field_dict=d) + self._schedule.AddTransferObject(transfer, self._problems) + self._problems.ClearContext() + + def Load(self): + self._problems.ClearContext() + if not self._DetermineFormat(): + return self._schedule + + self._CheckFileNames() + + self._LoadAgencies() + self._LoadStops() + self._LoadRoutes() + self._LoadCalendar() + self._LoadShapes() + self._LoadTrips() + self._LoadHeadways() + if self._load_stop_times: + self._LoadStopTimes() + self._LoadFares() + self._LoadFareRules() + self._LoadTransfers() + + if self._zip: + self._zip.close() + self._zip = None + + if self._extra_validation: + self._schedule.Validate(self._problems, validate_children=False) + + return self._schedule + + +class ShapeLoader(Loader): + """A subclass of Loader that only loads the shapes from a GTFS file.""" + + def __init__(self, *args, **kwargs): + """Initialize a new ShapeLoader object. + + See Loader.__init__ for argument documentation. + """ + Loader.__init__(self, *args, **kwargs) + + def Load(self): + self._LoadShapes() + return self._schedule + --- /dev/null +++ b/origin-src/transitfeed-1.2.5/transitfeed/shapelib.py @@ -1,1 +1,613 @@ - +#!/usr/bin/python2.4 +# +# Copyright 2007 Google Inc. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +"""A library for manipulating points and polylines. + +This is a library for creating and manipulating points on the unit +sphere, as an approximate model of Earth. The primary use of this +library is to make manipulation and matching of polylines easy in the +transitfeed library. + +NOTE: in this library, Earth is modelled as a sphere, whereas +GTFS specifies that latitudes and longitudes are in WGS84. For the +purpose of comparing and matching latitudes and longitudes that +are relatively close together on the surface of the earth, this +is adequate; for other purposes, this library may not be accurate +enough. +""" + +__author__ = 'chris.harrelson.code@gmail.com (Chris Harrelson)' + +import copy +import decimal +import heapq +import math + +class ShapeError(Exception): + """Thrown whenever there is a shape parsing error.""" + pass + + +EARTH_RADIUS_METERS = 6371010.0 + + +class Point(object): + """ + A class representing a point on the unit sphere in three dimensions. + """ + def __init__(self, x, y, z): + self.x = x + self.y = y + self.z = z + + def __hash__(self): + return hash((self.x, self.y, self.z)) + + def __cmp__(self, other): + if not isinstance(other, Point): + raise TypeError('Point.__cmp__(x,y) requires y to be a "Point", ' + 'not a "%s"' % type(other).__name__) + return cmp((self.x, self.y, self.z), (other.x, other.y, other.z)) + + def __str__(self): + return "(%.15f, %.15f, %.15f) " % (self.x, self.y, self.z) + + def Norm2(self): + """ + Returns the L_2 (Euclidean) norm of self. + """ + sum = self.x * self.x + self.y * self.y + self.z * self.z + return math.sqrt(float(sum)) + + def IsUnitLength(self): + return abs(self.Norm2() - 1.0) < 1e-14 + + def Plus(self, other): + """ + Returns a new point which is the pointwise sum of self and other. + """ + return Point(self.x + other.x, + self.y + other.y, + self.z + other.z) + + def Minus(self, other): + """ + Returns a new point which is the pointwise subtraction of other from + self. + """ + return Point(self.x - other.x, + self.y - other.y, + self.z - other.z) + + def DotProd(self, other): + """ + Returns the (scalar) dot product of self with other. + """ + return self.x * other.x + self.y * other.y + self.z * other.z + + def Times(self, val): + """ + Returns a new point which is pointwise multiplied by val. + """ + return Point(self.x * val, self.y * val, self.z * val) + + def Normalize(self): + """ + Returns a unit point in the same direction as self. + """ + return self.Times(1 / self.Norm2()) + + def RobustCrossProd(self, other): + """ + A robust version of cross product. If self and other + are not nearly the same point, returns the same value + as CrossProd() modulo normalization. Otherwise returns + an arbitrary unit point orthogonal to self. + """ + assert(self.IsUnitLength() and other.IsUnitLength()) + x = self.Plus(other).CrossProd(other.Minus(self)) + if abs(x.x) > 1e-15 or abs(x.y) > 1e-15 or abs(x.z) > 1e-15: + return x.Normalize() + else: + return self.Ortho() + + def LargestComponent(self): + """ + Returns (i, val) where i is the component index (0 - 2) + which has largest absolute value and val is the value + of the component. + """ + if abs(self.x) > abs(self.y): + if abs(self.x) > abs(self.z): + return (0, self.x) + else: + return (2, self.z) + else: + if abs(self.y) > abs(self.z): + return (1, self.y) + else: + return (2, self.z) + + def Ortho(self): + """Returns a unit-length point orthogonal to this point""" + (index, val) = self.LargestComponent() + index = index - 1 + if index < 0: + index = 2 + temp = Point(0.012, 0.053, 0.00457) + if index == 0: + temp.x = 1 + elif index == 1: + temp.y = 1 + elif index == 2: + temp.z = 1 + return self.CrossProd(temp).Normalize() + + def CrossProd(self, other): + """ + Returns the cross product of self and other. + """ + return Point( + self.y * other.z - self.z * other.y, + self.z * other.x - self.x * other.z, + self.x * other.y - self.y * other.x) + + @staticmethod + def _approxEq(a, b): + return abs(a - b) < 1e-11 + + def Equals(self, other): + """ + Returns true of self and other are approximately equal. + """ + return (self._approxEq(self.x, other.x) + and self._approxEq(self.y, other.y) + and self._approxEq(self.z, other.z)) + + def Angle(self, other): + """ + Returns the angle in radians between self and other. + """ + return math.atan2(self.CrossProd(other).Norm2(), + self.DotProd(other)) + + def ToLatLng(self): + """ + Returns that latitude and longitude that this point represents + under a spherical Earth model. + """ + rad_lat = math.atan2(self.z, math.sqrt(self.x * self.x + self.y * self.y)) + rad_lng = math.atan2(self.y, self.x) + return (rad_lat * 180.0 / math.pi, rad_lng * 180.0 / math.pi) + + @staticmethod + def FromLatLng(lat, lng): + """ + Returns a new point representing this latitude and longitude under + a spherical Earth model. + """ + phi = lat * (math.pi / 180.0) + theta = lng * (math.pi / 180.0) + cosphi = math.cos(phi) + return Point(math.cos(theta) * cosphi, + math.sin(theta) * cosphi, + math.sin(phi)) + + def GetDistanceMeters(self, other): + assert(self.IsUnitLength() and other.IsUnitLength()) + return self.Angle(other) * EARTH_RADIUS_METERS + + +def SimpleCCW(a, b, c): + """ + Returns true if the triangle abc is oriented counterclockwise. + """ + return c.CrossProd(a).DotProd(b) > 0 + +def GetClosestPoint(x, a, b): + """ + Returns the point on the great circle segment ab closest to x. + """ + assert(x.IsUnitLength()) + assert(a.IsUnitLength()) + assert(b.IsUnitLength()) + + a_cross_b = a.RobustCrossProd(b) + # project to the great circle going through a and b + p = x.Minus( + a_cross_b.Times( + x.DotProd(a_cross_b) / a_cross_b.Norm2())) + + # if p lies between a and b, return it + if SimpleCCW(a_cross_b, a, p) and SimpleCCW(p, b, a_cross_b): + return p.Normalize() + + # otherwise return the closer of a or b + if x.Minus(a).Norm2() <= x.Minus(b).Norm2(): + return a + else: + return b + + +class Poly(object): + """ + A class representing a polyline. + """ + def __init__(self, points = [], name=None): + self._points = list(points) + self._name = name + + def AddPoint(self, p): + """ + Adds a new point to the end of the polyline. + """ + assert(p.IsUnitLength()) + self._points.append(p) + + def GetName(self): + return self._name + + def GetPoint(self, i): + return self._points[i] + + def GetPoints(self): + return self._points + + def GetNumPoints(self): + return len(self._points) + + def _GetPointSafe(self, i): + try: + return self.GetPoint(i) + except IndexError: + return None + + def GetClosestPoint(self, p): + """ + Returns (closest_p, closest_i), where closest_p is the closest point + to p on the piecewise linear curve represented by the polyline, + and closest_i is the index of the point on the polyline just before + the polyline segment that contains closest_p. + """ + assert(len(self._points) > 0) + closest_point = self._points[0] + closest_i = 0 + + for i in range(0, len(self._points) - 1): + (a, b) = (self._points[i], self._points[i+1]) + cur_closest_point = GetClosestPoint(p, a, b) + if p.Angle(cur_closest_point) < p.Angle(closest_point): + closest_point = cur_closest_point.Normalize() + closest_i = i + + return (closest_point, closest_i) + + def LengthMeters(self): + """Return length of this polyline in meters.""" + assert(len(self._points) > 0) + length = 0 + for i in range(0, len(self._points) - 1): + length += self._points[i].GetDistanceMeters(self._points[i+1]) + return length + + def Reversed(self): + """Return a polyline that is the reverse of this polyline.""" + return Poly(reversed(self.GetPoints()), self.GetName()) + + def CutAtClosestPoint(self, p): + """ + Let x be the point on the polyline closest to p. Then + CutAtClosestPoint returns two new polylines, one representing + the polyline from the beginning up to x, and one representing + x onwards to the end of the polyline. x is the first point + returned in the second polyline. + """ + (closest, i) = self.GetClosestPoint(p) + + tmp = [closest] + tmp.extend(self._points[i+1:]) + return (Poly(self._points[0:i+1]), + Poly(tmp)) + + def GreedyPolyMatchDist(self, shape): + """ + Tries a greedy matching algorithm to match self to the + given shape. Returns the maximum distance in meters of + any point in self to its matched point in shape under the + algorithm. + + Args: shape, a Poly object. + """ + tmp_shape = Poly(shape.GetPoints()) + max_radius = 0 + for (i, point) in enumerate(self._points): + tmp_shape = tmp_shape.CutAtClosestPoint(point)[1] + dist = tmp_shape.GetPoint(0).GetDistanceMeters(point) + max_radius = max(max_radius, dist) + return max_radius + + @staticmethod + def MergePolys(polys, merge_point_threshold=10): + """ + Merge multiple polylines, in the order that they were passed in. + Merged polyline will have the names of their component parts joined by ';'. + Example: merging [a,b], [c,d] and [e,f] will result in [a,b,c,d,e,f]. + However if the endpoints of two adjacent polylines are less than + merge_point_threshold meters apart, we will only use the first endpoint in + the merged polyline. + """ + name = ";".join((p.GetName(), '')[p.GetName() is None] for p in polys) + merged = Poly([], name) + if polys: + first_poly = polys[0] + for p in first_poly.GetPoints(): + merged.AddPoint(p) + last_point = merged._GetPointSafe(-1) + for poly in polys[1:]: + first_point = poly._GetPointSafe(0) + if (last_point and first_point and + last_point.GetDistanceMeters(first_point) <= merge_point_threshold): + points = poly.GetPoints()[1:] + else: + points = poly.GetPoints() + for p in points: + merged.AddPoint(p) + last_point = merged._GetPointSafe(-1) + return merged + + + def __str__(self): + return self._ToString(str) + + def ToLatLngString(self): + return self._ToString(lambda p: str(p.ToLatLng())) + + def _ToString(self, pointToStringFn): + return "%s: %s" % (self.GetName() or "", + ", ".join([pointToStringFn(p) for p in self._points])) + + +class PolyCollection(object): + """ + A class representing a collection of polylines. + """ + def __init__(self): + self._name_to_shape = {} + pass + + def AddPoly(self, poly, smart_duplicate_handling=True): + """ + Adds a new polyline to the collection. + """ + inserted_name = poly.GetName() + if poly.GetName() in self._name_to_shape: + if not smart_duplicate_handling: + raise ShapeError("Duplicate shape found: " + poly.GetName()) + + print ("Warning: duplicate shape id being added to collection: " + + poly.GetName()) + if poly.GreedyPolyMatchDist(self._name_to_shape[poly.GetName()]) < 10: + print " (Skipping as it apears to be an exact duplicate)" + else: + print " (Adding new shape variant with uniquified name)" + inserted_name = "%s-%d" % (inserted_name, len(self._name_to_shape)) + self._name_to_shape[inserted_name] = poly + + def NumPolys(self): + return len(self._name_to_shape) + + def FindMatchingPolys(self, start_point, end_point, max_radius=150): + """ + Returns a list of polylines in the collection that have endpoints + within max_radius of the given start and end points. + """ + matches = [] + for shape in self._name_to_shape.itervalues(): + if start_point.GetDistanceMeters(shape.GetPoint(0)) < max_radius and \ + end_point.GetDistanceMeters(shape.GetPoint(-1)) < max_radius: + matches.append(shape) + return matches + +class PolyGraph(PolyCollection): + """ + A class representing a graph where the edges are polylines. + """ + def __init__(self): + PolyCollection.__init__(self) + self._nodes = {} + + def AddPoly(self, poly, smart_duplicate_handling=True): + PolyCollection.AddPoly(self, poly, smart_duplicate_handling) + start_point = poly.GetPoint(0) + end_point = poly.GetPoint(-1) + self._AddNodeWithEdge(start_point, poly) + self._AddNodeWithEdge(end_point, poly) + + def _AddNodeWithEdge(self, point, edge): + if point in self._nodes: + self._nodes[point].add(edge) + else: + self._nodes[point] = set([edge]) + + def ShortestPath(self, start, goal): + """Uses the A* algorithm to find a shortest path between start and goal. + + For more background see http://en.wikipedia.org/wiki/A-star_algorithm + + Some definitions: + g(x): The actual shortest distance traveled from initial node to current + node. + h(x): The estimated (or "heuristic") distance from current node to goal. + We use the distance on Earth from node to goal as the heuristic. + This heuristic is both admissible and monotonic (see wikipedia for + more details). + f(x): The sum of g(x) and h(x), used to prioritize elements to look at. + + Arguments: + start: Point that is in the graph, start point of the search. + goal: Point that is in the graph, end point for the search. + + Returns: + A Poly object representing the shortest polyline through the graph from + start to goal, or None if no path found. + """ + + assert start in self._nodes + assert goal in self._nodes + closed_set = set() # Set of nodes already evaluated. + open_heap = [(0, start)] # Nodes to visit, heapified by f(x). + open_set = set([start]) # Same as open_heap, but a set instead of a heap. + g_scores = { start: 0 } # Distance from start along optimal path + came_from = {} # Map to reconstruct optimal path once we're done. + while open_set: + (f_x, x) = heapq.heappop(open_heap) + open_set.remove(x) + if x == goal: + return self._ReconstructPath(came_from, goal) + closed_set.add(x) + edges = self._nodes[x] + for edge in edges: + if edge.GetPoint(0) == x: + y = edge.GetPoint(-1) + else: + y = edge.GetPoint(0) + if y in closed_set: + continue + tentative_g_score = g_scores[x] + edge.LengthMeters() + tentative_is_better = False + if y not in open_set: + h_y = y.GetDistanceMeters(goal) + f_y = tentative_g_score + h_y + open_set.add(y) + heapq.heappush(open_heap, (f_y, y)) + tentative_is_better = True + elif tentative_g_score < g_scores[y]: + tentative_is_better = True + if tentative_is_better: + came_from[y] = (x, edge) + g_scores[y] = tentative_g_score + return None + + def _ReconstructPath(self, came_from, current_node): + """ + Helper method for ShortestPath, to reconstruct path. + + Arguments: + came_from: a dictionary mapping Point to (Point, Poly) tuples. + This dictionary keeps track of the previous neighbor to a node, and + the edge used to get from the previous neighbor to the node. + current_node: the current Point in the path. + + Returns: + A Poly that represents the path through the graph from the start of the + search to current_node. + """ + if current_node in came_from: + (previous_node, previous_edge) = came_from[current_node] + if previous_edge.GetPoint(0) == current_node: + previous_edge = previous_edge.Reversed() + p = self._ReconstructPath(came_from, previous_node) + return Poly.MergePolys([p, previous_edge], merge_point_threshold=0) + else: + return Poly([], '') + + def FindShortestMultiPointPath(self, points, max_radius=150, keep_best_n=10, + verbosity=0): + """ + Return a polyline, representing the shortest path through this graph that + has edge endpoints on each of a given list of points in sequence. We allow + fuzziness in matching of input points to points in this graph. + + We limit ourselves to a view of the best keep_best_n paths at any time, as a + greedy optimization. + """ + assert len(points) > 1 + nearby_points = [] + paths_found = [] # A heap sorted by inverse path length. + + for i, point in enumerate(points): + nearby = [p for p in self._nodes.iterkeys() + if p.GetDistanceMeters(point) < max_radius] + if verbosity >= 2: + print ("Nearby points for point %d %s: %s" + % (i + 1, + str(point.ToLatLng()), + ", ".join([str(n.ToLatLng()) for n in nearby]))) + if nearby: + nearby_points.append(nearby) + else: + print "No nearby points found for point %s" % str(point.ToLatLng()) + return None + + pathToStr = lambda start, end, path: (" Best path %s -> %s: %s" + % (str(start.ToLatLng()), + str(end.ToLatLng()), + path and path.GetName() or + "None")) + if verbosity >= 3: + print "Step 1" + step = 2 + + start_points = nearby_points[0] + end_points = nearby_points[1] + + for start in start_points: + for end in end_points: + path = self.ShortestPath(start, end) + if verbosity >= 3: + print pathToStr(start, end, path) + PolyGraph._AddPathToHeap(paths_found, path, keep_best_n) + + for possible_points in nearby_points[2:]: + if verbosity >= 3: + print "\nStep %d" % step + step += 1 + new_paths_found = [] + + start_end_paths = {} # cache of shortest paths between (start, end) pairs + for score, path in paths_found: + start = path.GetPoint(-1) + for end in possible_points: + if (start, end) in start_end_paths: + new_segment = start_end_paths[(start, end)] + else: + new_segment = self.ShortestPath(start, end) + if verbosity >= 3: + print pathToStr(start, end, new_segment) + start_end_paths[(start, end)] = new_segment + + if new_segment: + new_path = Poly.MergePolys([path, new_segment], + merge_point_threshold=0) + PolyGraph._AddPathToHeap(new_paths_found, new_path, keep_best_n) + paths_found = new_paths_found + + if paths_found: + best_score, best_path = max(paths_found) + return best_path + else: + return None + + @staticmethod + def _AddPathToHeap(heap, path, keep_best_n): + if path and path.GetNumPoints(): + new_item = (-path.LengthMeters(), path) + if new_item not in heap: + if len(heap) < keep_best_n: + heapq.heappush(heap, new_item) + else: + heapq.heapreplace(heap, new_item) + --- /dev/null +++ b/origin-src/transitfeed-1.2.5/transitfeed/util.py @@ -1,1 +1,163 @@ +#!/usr/bin/python2.5 +# Copyright (C) 2009 Google Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + + +import optparse +import sys + + +class OptionParserLongError(optparse.OptionParser): + """OptionParser subclass that includes list of options above error message.""" + def error(self, msg): + print >>sys.stderr, self.format_help() + print >>sys.stderr, '\n\n%s: error: %s\n\n' % (self.get_prog_name(), msg) + sys.exit(2) + + +def RunWithCrashHandler(f): + try: + exit_code = f() + sys.exit(exit_code) + except (SystemExit, KeyboardInterrupt): + raise + except: + import inspect + import traceback + + # Save trace and exception now. These calls look at the most recently + # raised exception. The code that makes the report might trigger other + # exceptions. + original_trace = inspect.trace(3)[1:] + formatted_exception = traceback.format_exception_only(*(sys.exc_info()[:2])) + + apology = """Yikes, the program threw an unexpected exception! + +Hopefully a complete report has been saved to transitfeedcrash.txt, +though if you are seeing this message we've already disappointed you once +today. Please include the report in a new issue at +http://code.google.com/p/googletransitdatafeed/issues/entry +or an email to the public group googletransitdatafeed@googlegroups.com. Sorry! + +""" + dashes = '%s\n' % ('-' * 60) + dump = [] + dump.append(apology) + dump.append(dashes) + try: + import transitfeed + dump.append("transitfeed version %s\n\n" % transitfeed.__version__) + except NameError: + # Oh well, guess we won't put the version in the report + pass + + for (frame_obj, filename, line_num, fun_name, context_lines, + context_index) in original_trace: + dump.append('File "%s", line %d, in %s\n' % (filename, line_num, + fun_name)) + if context_lines: + for (i, line) in enumerate(context_lines): + if i == context_index: + dump.append(' --> %s' % line) + else: + dump.append(' %s' % line) + for local_name, local_val in frame_obj.f_locals.items(): + try: + truncated_val = str(local_val)[0:500] + except Exception, e: + dump.append(' Exception in str(%s): %s' % (local_name, e)) + else: + if len(truncated_val) >= 500: + truncated_val = '%s...' % truncated_val[0:499] + dump.append(' %s = %s\n' % (local_name, truncated_val)) + dump.append('\n') + + dump.append(''.join(formatted_exception)) + + open('transitfeedcrash.txt', 'w').write(''.join(dump)) + + print ''.join(dump) + print + print dashes + print apology + + try: + raw_input('Press enter to continue...') + except EOFError: + # Ignore stdin being closed. This happens during some tests. + pass + sys.exit(127) + + +# Pick one of two defaultdict implementations. A native version was added to +# the collections library in python 2.5. If that is not available use Jason's +# pure python recipe. He gave us permission to distribute it. + +# On Mon, Nov 30, 2009 at 07:27, jason kirtland wrote: +# > +# > Hi Tom, sure thing! It's not easy to find on the cookbook site, but the +# > recipe is under the Python license. +# > +# > Cheers, +# > Jason +# > +# > On Thu, Nov 26, 2009 at 3:03 PM, Tom Brown wrote: +# > +# >> I would like to include http://code.activestate.com/recipes/523034/ in +# >> http://code.google.com/p/googletransitdatafeed/wiki/TransitFeedDistribution +# >> which is distributed under the Apache License, Version 2.0 with Copyright +# >> Google. May we include your code with a comment in the source pointing at +# >> the original URL? Thanks, Tom Brown + +try: + # Try the native implementation first + from collections import defaultdict +except: + # Fallback for python2.4, which didn't include collections.defaultdict + class defaultdict(dict): + def __init__(self, default_factory=None, *a, **kw): + if (default_factory is not None and + not hasattr(default_factory, '__call__')): + raise TypeError('first argument must be callable') + dict.__init__(self, *a, **kw) + self.default_factory = default_factory + def __getitem__(self, key): + try: + return dict.__getitem__(self, key) + except KeyError: + return self.__missing__(key) + def __missing__(self, key): + if self.default_factory is None: + raise KeyError(key) + self[key] = value = self.default_factory() + return value + def __reduce__(self): + if self.default_factory is None: + args = tuple() + else: + args = self.default_factory, + return type(self), args, None, None, self.items() + def copy(self): + return self.__copy__() + def __copy__(self): + return type(self)(self.default_factory, self) + def __deepcopy__(self, memo): + import copy + return type(self)(self.default_factory, + copy.deepcopy(self.items())) + def __repr__(self): + return 'defaultdict(%s, %s)' % (self.default_factory, + dict.__repr__(self)) + --- /dev/null +++ b/origin-src/transitfeed-1.2.5/unusual_trip_filter.py @@ -1,1 +1,157 @@ +#!/usr/bin/python2.5 +# Copyright (C) 2007 Google Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +""" +Filters out trips which are not on the defualt routes and + set their trip_typeattribute accordingly. + +For usage information run unusual_trip_filter.py --help +""" + +__author__ = 'Jiri Semecky ' + +import codecs +import os +import os.path +import sys +import time +import transitfeed +from transitfeed import util + + +class UnusualTripFilter(object): + """Class filtering trips going on unusual paths. + + Those are usually trips going to/from depot or changing to another route + in the middle. Sets the 'trip_type' attribute of the trips.txt dataset + so that non-standard trips are marked as special (value 1) + instead of regular (default value 0). + """ + + def __init__ (self, threshold=0.1, force=False, quiet=False, route_type=None): + self._threshold = threshold + self._quiet = quiet + self._force = force + if route_type in transitfeed.Route._ROUTE_TYPE_NAMES: + self._route_type = transitfeed.Route._ROUTE_TYPE_NAMES[route_type] + elif route_type is None: + self._route_type = None + else: + self._route_type = int(route_type) + + def filter_line(self, route): + """Mark unusual trips for the given route.""" + if self._route_type is not None and self._route_type != route.route_type: + self.info('Skipping route %s due to different route_type value (%s)' % + (route['route_id'], route['route_type'])) + return + self.info('Filtering infrequent trips for route %s.' % route.route_id) + trip_count = len(route.trips) + for pattern_id, pattern in route.GetPatternIdTripDict().items(): + ratio = float(1.0 * len(pattern) / trip_count) + if not self._force: + if (ratio < self._threshold): + self.info("\t%d trips on route %s with headsign '%s' recognized " + "as unusual (ratio %f)" % + (len(pattern), + route['route_short_name'], + pattern[0]['trip_headsign'], + ratio)) + for trip in pattern: + trip.trip_type = 1 # special + self.info("\t\tsetting trip_type of trip %s as special" % + trip.trip_id) + else: + self.info("\t%d trips on route %s with headsign '%s' recognized " + "as %s (ratio %f)" % + (len(pattern), + route['route_short_name'], + pattern[0]['trip_headsign'], + ('regular', 'unusual')[ratio < self._threshold], + ratio)) + for trip in pattern: + trip.trip_type = ('0','1')[ratio < self._threshold] + self.info("\t\tsetting trip_type of trip %s as %s" % + (trip.trip_id, + ('regular', 'unusual')[ratio < self._threshold])) + + def filter(self, dataset): + """Mark unusual trips for all the routes in the dataset.""" + self.info('Going to filter infrequent routes in the dataset') + for route in dataset.routes.values(): + self.filter_line(route) + + def info(self, text): + if not self._quiet: + print text.encode("utf-8") + + +def main(): + usage = \ +'''%prog [options] + +Filters out trips which do not follow the most common stop sequences and +sets their trip_type attribute accordingly. is overwritten with +the modifed GTFS file unless the --output option is used. +''' + parser = util.OptionParserLongError( + usage=usage, version='%prog '+transitfeed.__version__) + parser.add_option('-o', '--output', dest='output', metavar='FILE', + help='Name of the output GTFS file (writing to input feed if omitted).') + parser.add_option('-m', '--memory_db', dest='memory_db', action='store_true', + help='Force use of in-memory sqlite db.') + parser.add_option('-t', '--threshold', default=0.1, + dest='threshold', type='float', + help='Frequency threshold for considering pattern as non-regular.') + parser.add_option('-r', '--route_type', default=None, + dest='route_type', type='string', + help='Filter only selected route type (specified by number' + 'or one of the following names: ' + \ + ', '.join(transitfeed.Route._ROUTE_TYPE_NAMES) + ').') + parser.add_option('-f', '--override_trip_type', default=False, + dest='override_trip_type', action='store_true', + help='Forces overwrite of current trip_type values.') + parser.add_option('-q', '--quiet', dest='quiet', + default=False, action='store_true', + help='Suppress information output.') + + (options, args) = parser.parse_args() + if len(args) != 1: + parser.error('You must provide the path of a single feed.') + + filter = UnusualTripFilter(float(options.threshold), + force=options.override_trip_type, + quiet=options.quiet, + route_type=options.route_type) + feed_name = args[0] + feed_name = feed_name.strip() + filter.info('Loading %s' % feed_name) + loader = transitfeed.Loader(feed_name, extra_validation=True, + memory_db=options.memory_db) + data = loader.Load() + filter.filter(data) + print 'Saving data' + + # Write the result + if options.output is None: + data.WriteGoogleTransitFeed(feed_name) + else: + data.WriteGoogleTransitFeed(options.output) + + +if __name__ == '__main__': + util.RunWithCrashHandler(main) + --- /dev/null +++ b/origin-src/transitfeed-1.2.5/validation-results.html @@ -1,1 +1,53 @@ + + + +FeedValidator: good_feed.zip + + + +GTFS validation results for feed:
    +test/data/good_feed.zip +

    + + + + + + + +
    Agencies:Autorité de passage de démonstration
    Routes:5
    Stops:10
    Trips:11
    Shapes:0
    Effective:January 01, 2007 to December 31, 2011
    +
    +During the upcoming service dates Sun Apr 18 to Wed Jun 16: + + + + +
    Average trips per date:141
    Most trips on a date:144, on 17 service dates (Sun Apr 18, Sat Apr 24, Sun Apr 25, ...)
    Least trips on a date:140, on 43 service dates (Mon Apr 19, Tue Apr 20, Wed Apr 21, ...)
    +
    +feed validated successfully +

    + + + + --- /dev/null +++ b/origin-src/wlach-halifax-transit-feed-fef68c1/.gitignore @@ -1,1 +1,3 @@ - +hfxfeed.zip +hfxtable.yml +*~ --- /dev/null +++ b/origin-src/wlach-halifax-transit-feed-fef68c1/1-to-dartmouth.yml @@ -1,1 +1,214 @@ +short_name: 1 +long_name: To Dartmouth +time_points: [ 7284, 7412, 6087, 7351, 7151 ] +between_stops: + 7284-7412: [ 6203, 7423 ] + 7412-6087: [ 7419, 7409, 7402, 6453, 6447, 6449, 6454, 6452, 8328, 8337, 8333, 8336, 8327, 8338, 6121, 6106 ] + 6087-7351: [ 6455, 6773, 6778, 6779, 6775, 6787 ] +stop_times: [ + [ -, -, -, 604a, 607a], + [ 600a, 611a, 625a, 633a, 637a], + [ 610a, 621a, 635a, 643a, 647a], + [ 620a, 631a, 645a, 653a, 657a], + [ 630a, 641a, 655a, 703a, 707a], + [ 640a, 651a, 705a, 713a, 717a], + [ 650a, 701a, 716a, 724a, 728a], + [ 700a, 713a, 728a, 736a, 740a], + [ 710a, 723a, 738a, 746a, 750a], + [ 720a, 733a, 748a, 756a, 800a], + [ 730a, 743a, 758a, 806a, 810a], + [ 740a, 753a, 808a, 816a, 820a], + [ 750a, 803a, 818a, 826a, 830a], + [ 800a, 813a, 828a, 836a, 840a], + [ 810a, 823a, 838a, 846a, 850a], + [ 820a, 833a, 848a, 856a, 900a], + [ 830a, 843a, 858a, 906a, 910a], + [ 840a, 853a, 908a, 916a, 920a], + [ 855a, 908a, 923a, 931a, 935a], + [ 910a, 923a, 938a, 946a, 950a], + [ 925a, 938a, 953a, 1001a, 1005a], + [ 940a, 953a, 1008a, 1016a, 1020a], + [ 955a, 1008a, 1023a, 1031a, 1035a], + [ 1010a, 1023a, 1038a, 1046a, 1050a], + [ 1025a, 1038a, 1053a, 1101a, 1105a], + [ 1040a, 1053a, 1108a, 1116a, 1120a], + [ 1055a, 1108a, 1123a, 1131a, 1135a], + [ 1110a, 1123a, 1138a, 1146a, 1150a], + [ 1125a, 1138a, 1153a, 1201p, 1205p], + [ 1140a, 1153a, 1208p, 1216p, 1220p], + [ 1155a, 1208p, 1223p, 1231p, 1235p], + [ 1210p, 1223p, 1238p, 1246p, 1250p], + [ 1225p, 1238p, 1253p, 101p, 105p], + [ 1240p, 1253p, 108p, 116p, 120p], + [ 1255p, 108p, 123p, 131p, 135p], + [ 110p, 123p, 138p, 146p, 150p], + [ 125p, 138p, 153p, 201p, 205p], + [ 140p, 153p, 208p, 216p, 220p], + [ 155p, 208p, 223p, 231p, 235p], + [ 210p, 223p, 238p, 246p, 250p], + [ 225p, 238p, 253p, 301p, 305p], + [ 240p, 253p, 308p, 316p, 320p], + [ 255p, 308p, 323p, 331p, 335p], + [ 320p, 333p, 348p, 356p, 400p], + [ 340p, 353p, 408p, 416p, 420p], + [ 400p, 413p, 428p, 436p, 440p], + [ 420p, 433p, 448p, 456p, 500p], + [ 430p, 443p, 458p, 506p, 510p], + [ 450p, 503p, 518p, 526p, 530p], + [ 500p, 513p, 528p, 536p, 540p], + [ 510p, 523p, 538p, 546p, 550p], + [ 520p, 533p, 548p, 556p, 600p], + [ 530p, 543p, 558p, 606p, 610p], + [ 540p, 553p, 608p, 616p, 620p], + [ 550p, 603p, 618p, 626p, 630p], + [ 600p, 613p, 627p, 635p, 639p], + [ 615p, 626p, 640p, 648p, 652p], + [ 630p, 641p, 655p, 703p, 707p], + [ 645p, 656p, 710p, 718p, 722p], + [ 700p, 711p, 725p, 733p, 737p], + [ 715p, 726p, 740p, 748p, 752p], + [ 730p, 741p, 755p, 803p, 807p], + [ 745p, 756p, 810p, 818p, 822p], + [ 800p, 811p, 825p, 833p, 837p], + [ 815p, 826p, 840p, 848p, 852p], + [ 830p, 841p, 855p, 903p, 907p], + [ 845p, 856p, 910p, 918p, 922p], + [ 900p, 911p, 925p, 933p, 937p], + [ 915p, 926p, 940p, 948p, 952p], + [ 930p, 941p, 955p, 1003p, 1007p], + [ 945p, 956p, 1010p, 1018p, 1022p], + [ 1000p, 1011p, 1025p, 1033p, 1037p], + [ 1015p, 1026p, 1040p, 1048p, 1052p], + [ 1030p, 1041p, 1055p, 1103p, 1107p], + [ 1045p, 1056p, 1110p, 1118p, 1122p], + [ 1115p, 1126p, 1140p, 1148p, 1152p], + [ 1150p, 1201x, 1215x, 1221x, 1225x], + [ 1215x, 1226x, 1240x, 1248x, 1252x] +] +stop_times_saturday: [ + [ -, -, -, 602a, 605a], + [ -, -, -, -, -], + [ 605a, 616a, 628a, 634a, 637a], + [ 620a, 631a, 643a, 649a, 652a], + [ 635a, 646a, 658a, 704a, 707a], + [ 650a, 701a, 713a, 719a, 722a], + [ 705a, 716a, 728a, 734a, 737a], + [ 720a, 731a, 743a, 749a, 752a], + [ 735a, 746a, 758a, 804a, 807a], + [ 750a, 801a, 813a, 819a, 822a], + [ 805a, 816a, 828a, 834a, 837a], + [ 820a, 831a, 843a, 849a, 852a], + [ 835a, 846a, 858a, 904a, 907a], + [ 850a, 901a, 913a, 919a, 922a], + [ 905a, 916a, 928a, 934a, 937a], + [ 920a, 931a, 943a, 949a, 952a], + [ 935a, 946a, 958a, 1004a, 1007a], + [ 950a, 1001a, 1013a, 1019a, 1022a], + [ 1005a, 1016a, 1028a, 1034a, 1037a], + [ 1020a, 1031a, 1043a, 1049a, 1052a], + [ 1035a, 1046a, 1058a, 1104a, 1107a], + [ 1050a, 1101a, 1113a, 1119a, 1122a], + [ 1105a, 1116a, 1128a, 1134a, 1137a], + [ 1120a, 1131a, 1143a, 1149a, 1152a], + [ 1135a, 1146a, 1158a, 1204p, 1207p], + [ 1150a, 1201p, 1213p, 1219p, 1222p], + [ 1205p, 1216p, 1228p, 1234p, 1237p], + [ 1220p, 1231p, 1243p, 1249p, 1253p], + [ 1233p, 1244p, 1258p, 105p, 109p], + [ 1245p, 1257p, 113p, 120p, 124p], + [ 100p, 112p, 128p, 135p, 139p], + [ 115p, 127p, 143p, 150p, 154p], + [ 130p, 142p, 158p, 205p, 209p], + [ 145p, 157p, 213p, 220p, 224p], + [ 200p, 212p, 228p, 235p, 239p], + [ 215p, 227p, 243p, 250p, 254p], + [ 230p, 242p, 258p, 305p, 309p], + [ 245p, 257p, 313p, 320p, 324p], + [ 300p, 312p, 328p, 335p, 339p], + [ 315p, 327p, 343p, 350p, 354p], + [ 330p, 342p, 359p, 405p, 409p], + [ 345p, 357p, 413p, 420p, 424p], + [ 400p, 412p, 428p, 435p, 439p], + [ 415p, 427p, 443p, 450p, 454p], + [ 430p, 442p, 458p, 505p, 509p], + [ 445p, 457p, 513p, 520p, 524p], + [ 500p, 511p, 528p, 535p, 539p], + [ 515p, 527p, 543p, 550p, 554p], + [ 530p, 542p, 559p, 604p, 607p], + [ 545p, 556p, 610p, 614p, 618p], + [ 600p, 611p, 623p, 629p, 632p], + [ 615p, 626p, 638p, 644p, 647p], + [ 635p, 646p, 658p, 704p, 707p], + [ 650p, 701p, 713p, 719p, 722p], + [ 705p, 716p, 728p, 734p, 737p], + [ 720p, 731p, 743p, 749p, 752p], + [ 735p, 746p, 758p, 804p, 807p], + [ 750p, 801p, 813p, 819p, 822p], + [ 805p, 816p, 828p, 834p, 837p], + [ 820p, 831p, 843p, 849p, 852p], + [ 835p, 846p, 858p, 904p, 907p], + [ 850p, 901p, 913p, 919p, 922p], + [ 905p, 916p, 928p, 934p, 937p], + [ 920p, 931p, 943p, 949p, 952p], + [ 935p, 946p, 958p, 1004p, 1007p], + [ 950p, 1001p, 1013p, 1019p, 1022p], + [ 1005p, 1016p, 1028p, 1034p, 1037p], + [ 1020p, 1031p, 1043p, 1049p, 1052p], + [ 1035p, 1046p, 1058p, 1104p, 1107p], + [ 1050p, 1101p, 1113p, 1119p, 1122p], + [ 1105p, 1116p, 1128p, 1134p, 1137p], + [ 1135p, 1146p, 1158p, 1204x, 1207x], + [ 1205x, 1216x, 1228x, 1234x, 1237x]] +stop_times_sunday: [ + [ 640a, 649a, 700a, 705a, 709a], + [ 710a, 719a, 730a, 735a, 739a], + [ 740a, 749a, 800a, 805a, 809a], + [ 810a, 819a, 830a, 835a, 839a], + [ 840a, 849a, 900a, 905a, 909a], + [ 910a, 919a, 930a, 935a, 939a], + [ 940a, 949a, 1000a, 1005a, 1009a], + [ 1010a, 1019a, 1030a, 1035a, 1039a], + [ 1040a, 1049a, 1100a, 1105a, 1109a], + [ 1055a, 1104a, 1115a, 1120a, 1124a], + [ 1110a, 1119a, 1130a, 1135a, 1139a], + [ 1125a, 1134a, 1145a, 1150a, 1154a], + [ 1140a, 1149a, 1200p, 1205p, 1209p], + [ 1155a, 1204p, 1215p, 1220p, 1224p], + [ 1210p, 1219p, 1230p, 1235p, 1239p], + [ 1225p, 1234p, 1245p, 1250p, 1254p], + [ 1240p, 1249p, 100p, 105p, 109p], + [ 1255p, 104p, 115p, 120p, 124p], + [ 110p, 119p, 130p, 135p, 139p], + [ 125p, 134p, 145p, 150p, 154p], + [ 140p, 149p, 200p, 205p, 209p], + [ 155p, 204p, 215p, 220p, 224p], + [ 210p, 219p, 230p, 235p, 239p], + [ 225p, 234p, 245p, 250p, 254p], + [ 240p, 249p, 300p, 305p, 309p], + [ 255p, 304p, 315p, 320p, 324p], + [ 310p, 319p, 330p, 335p, 339p], + [ 325p, 334p, 345p, 350p, 354p], + [ 340p, 349p, 400p, 405p, 409p], + [ 355p, 404p, 415p, 420p, 424p], + [ 410p, 419p, 430p, 435p, 439p], + [ 425p, 434p, 445p, 450p, 454p], + [ 440p, 449p, 500p, 505p, 509p], + [ 455p, 504p, 515p, 520p, 524p], + [ 510p, 519p, 530p, 535p, 539p], + [ 525p, 534p, 545p, 550p, 554p], + [ 540p, 549p, 600p, 605p, 609p], + [ 555p, 604p, 615p, 620p, 624p], + [ 610p, 619p, 630p, 635p, 639p], + [ 640p, 649p, 700p, 705p, 709p], + [ 710p, 719p, 730p, 735p, 739p], + [ 740p, 749p, 800p, 805p, 809p], + [ 810p, 819p, 830p, 835p, 839p], + [ 840p, 849p, 900p, 905p, 909p], + [ 910p, 919p, 930p, 935p, 939p], + [ 940p, 949p, 1000p, 1005p, 1009p], + [ 1010p, 1019p, 1030p, 1035p, 1039p], + [ 1040p, 1049p, 1100p, 1105p, 1109p], + [ 1110p, 1119p, 1130p, 1135p, 1139p], + [ 1140p, 1149p, 1200x, 1205x, 1209x] +] --- /dev/null +++ b/origin-src/wlach-halifax-transit-feed-fef68c1/1-to-mumford.yml @@ -1,1 +1,208 @@ +short_name: 1 +long_name: To Mumford +time_points: [ 7151, 6105, 7421, 7284 ] +between_stops: + 7151-6105: [ 8638, 6088, 6104, 6125 ] + 6105-7421: [ 6108, 6103, 6102, 6122, 8331, 8330, 8334, 8335, 8329, 8332, 6448, 6450, 6451, 7401, 7403, 7410, 7406 ] + 7421-7284: [ 7404, 6201, 6200 ] +stop_times: [ + [ 610a, 620a, 633a, 644a], + [ 640a, 650a, 703a, 714a], + [ 705a, 715a, 728a, 742a], + [ 715a, 725a, 738a, 752a], + [ 735a, 745a, 758a, 812a], + [ 755a, 805a, 818a, 832a], + [ 815a, 825a, 838a, 852a], + [ 836a, 846a, 859a, 913a], + [ 845a, 855a, 908a, 922a], + [ 855a, 905a, 918a, 932a], + [ 905a, 915a, 928a, 942a], + [ 915a, 925a, 938a, 952a], + [ 925a, 935a, 948a, 1002a], + [ 940a, 950a, 1003a, 1017a], + [ 955a, 1005a, 1018a, 1032a], + [ 1010a, 1020a, 1033a, 1047a], + [ 1025a, 1035a, 1048a, 1102a], + [ 1040a, 1050a, 1103a, 1117a], + [ 1055a, 1105a, 1118a, 1132a], + [ 1110a, 1120a, 1133a, 1147a], + [ 1125a, 1135a, 1148a, 1202p], + [ 1140a, 1150a, 1203p, 1217p], + [ 1155a, 1205p, 1218p, 1232p], + [ 1210p, 1220p, 1233p, 1247p], + [ 1225p, 1235p, 1248p, 102p], + [ 1240p, 1250p, 103p, 117p], + [ 1255p, 105p, 118p, 132p], + [ 110p, 120p, 133p, 147p], + [ 125p, 135p, 148p, 202p], + [ 140p, 150p, 203p, 217p], + [ 155p, 205p, 218p, 232p], + [ 210p, 220p, 233p, 247p], + [ 225p, 235p, 248p, 302p], + [ 240p, 250p, 303p, 317p], + [ 255p, 305p, 318p, 332p], + [ 310p, 320p, 333p, 347p], + [ 325p, 335p, 351p, 405p], + [ 335p, 345p, 401p, 415p], + [ 345p, 355p, 411p, 425p], + [ 355p, 405p, 421p, 435p], + [ 405p, 415p, 431p, 445p], + [ 415p, 425p, 441p, 455p], + [ 425p, 435p, 451p, 505p], + [ 435p, 445p, 501p, 515p], + [ 445p, 455p, 511p, 525p], + [ 455p, 505p, 521p, 535p], + [ 505p, 515p, 531p, 545p], + [ 515p, 525p, 541p, 555p], + [ 525p, 535p, 551p, 605p], + [ 540p, 550p, 606p, 620p], + [ 555p, 605p, 621p, 635p], + [ 610p, 620p, 636p, 650p], + [ 625p, 635p, 651p, 705p], + [ 640p, 650p, 706p, 720p], + [ 655p, 705p, 721p, 735p], + [ 710p, 720p, 736p, 750p], + [ 725p, 735p, 751p, 805p], + [ 740p, 750p, 804p, 818p], + [ 755p, 805p, 818p, 832p], + [ 810p, 820p, 833p, 847p], + [ 825p, 835p, 848p, 902p], + [ 840p, 850p, 903p, 917p], + [ 855p, 905p, 918p, 932p], + [ 910p, 920p, 933p, 947p], + [ 925p, 935p, 948p, 1002p], + [ 940p, 950p, 1003p, 1017p], + [ 955p, 1005p, 1018p, 1032p], + [ 1010p, 1020p, 1033p, 1047p], + [ 1025p, 1035p, 1048p, 1102p], + [ 1040p, 1050p, 1103p, 1114p], + [ 1055p, 1105p, 1116p, 1127p], + [ 1125p, 1135p, 1146p, 1157p], + [ 1155p, 1205x, 1216x, 1227x], + [ 1226x, 1236x, 1247x, 1258x] +] +stop_times_saturday: [ + [ 600a, 608a, 621a, 632a], + [ 620a, 628a, 641a, 652a], + [ 640a, 648a, 701a, 712a], + [ 655a, 703a, 716a, 727a], + [ 710a, 718a, 731a, 742a], + [ 725a, 733a, 746a, 757a], + [ 740a, 748a, 801a, 812a], + [ 755a, 803a, 816a, 827a], + [ 810a, 818a, 831a, 842a], + [ 825a, 833a, 846a, 857a], + [ 840a, 848a, 901a, 912a], + [ 855a, 903a, 916a, 927a], + [ 910a, 918a, 931a, 942a], + [ 925a, 933a, 946a, 957a], + [ 940a, 948a, 1001a, 1012a], + [ 955a, 1003a, 1016a, 1027a], + [ 1010a, 1018a, 1031a, 1042a], + [ 1025a, 1033a, 1046a, 1057a], + [ 1040a, 1049a, 1102a, 1113a], + [ 1055a, 1104a, 1117a, 1128a], + [ 1110a, 1119a, 1132a, 1143a], + [ 1125a, 1134a, 1147a, 1158a], + [ 1140a, 1149a, 1202p, 1215p], + [ 1155a, 1204p, 1219p, 1232p], + [ 1210p, 1220p, 1235p, 1248p], + [ 1225p, 1235p, 1250p, 103p], + [ 1240p, 1250p, 105p, 118p], + [ 1255p, 105p, 120p, 133p], + [ 110p, 120p, 135p, 148p], + [ 125p, 135p, 150p, 203p], + [ 140p, 150p, 205p, 218p], + [ 155p, 205p, 220p, 233p], + [ 210p, 220p, 235p, 248p], + [ 225p, 235p, 250p, 303p], + [ 240p, 250p, 305p, 318p], + [ 255p, 305p, 320p, 333p], + [ 310p, 320p, 335p, 348p], + [ 325p, 335p, 350p, 403p], + [ 340p, 350p, 405p, 418p], + [ 355p, 405p, 420p, 433p], + [ 410p, 420p, 435p, 448p], + [ 425p, 435p, 450p, 459p], + [ 440p, 450p, 504p, 514p], + [ 455p, 505p, 518p, 529p], + [ 510p, 520p, 532p, 543p], + [ 525p, 535p, 547p, 558p], + [ 540p, 550p, 602p, 613p], + [ 555p, 605p, 617p, 628p], + [ 610p, 620p, 632p, 643p], + [ 625p, 635p, 647p, 658p], + [ 640p, 650p, 702p, 713p], + [ 710p, 720p, 732p, 743p], + [ 725p, 735p, 747p, 758p], + [ 740p, 750p, 802p, 813p], + [ 755p, 805p, 817p, 828p], + [ 810p, 820p, 832p, 843p], + [ 825p, 835p, 847p, 858p], + [ 840p, 850p, 902p, 913p], + [ 855p, 905p, 917p, 928p], + [ 910p, 920p, 932p, 943p], + [ 925p, 935p, 947p, 958p], + [ 940p, 950p, 1002p, 1013p], + [ 955p, 1005p, 1017p, 1028p], + [ 1010p, 1020p, 1032p, 1043p], + [ 1025p, 1035p, 1047p, 1058p], + [ 1040p, 1050p, 1102p, 1113p], + [ 1055p, 1105p, 1117p, 1128p], + [ 1125p, 1135p, 1147p, 1158p], + [ 1155p, 1205x, 1217x, 1228x], + [ 1225x, 1235x, 1247x, 1258x] +] +stop_times_sunday: [ + [ 655a, 709a, 720a, 729a], + [ 716a, 730a, 741a, 750a], + [ 746a, 800a, 811a, 820a], + [ 816a, 830a, 841a, 850a], + [ 846a, 900a, 911a, 920a], + [ 916a, 930a, 941a, 950a], + [ 946a, 1000a, 1011a, 1020a], + [ 1016a, 1030a, 1041a, 1050a], + [ 1031a, 1045a, 1056a, 1105a], + [ 1046a, 1100a, 1111a, 1120a], + [ 1101a, 1115a, 1126a, 1135a], + [ 1116a, 1130a, 1141a, 1150a], + [ 1131a, 1145a, 1156a, 1205p], + [ 1146a, 1200p, 1211p, 1220p], + [ 1201p, 1215p, 1226p, 1235p], + [ 1216p, 1230p, 1241p, 1250p], + [ 1231p, 1245p, 1256p, 105p], + [ 1246p, 100p, 111p, 120p], + [ 101p, 115p, 126p, 135p], + [ 116p, 130p, 141p, 150p], + [ 131p, 145p, 156p, 205p], + [ 146p, 200p, 211p, 220p], + [ 201p, 215p, 226p, 235p], + [ 216p, 230p, 241p, 250p], + [ 231p, 245p, 256p, 305p], + [ 246p, 300p, 311p, 320p], + [ 301p, 315p, 326p, 335p], + [ 316p, 330p, 341p, 350p], + [ 331p, 345p, 356p, 405p], + [ 346p, 400p, 411p, 420p], + [ 401p, 415p, 426p, 435p], + [ 416p, 430p, 441p, 450p], + [ 431p, 445p, 456p, 505p], + [ 446p, 500p, 511p, 520p], + [ 501p, 515p, 526p, 535p], + [ 516p, 530p, 541p, 550p], + [ 531p, 545p, 556p, 605p], + [ 546p, 600p, 611p, 620p], + [ 616p, 630p, 641p, 650p], + [ 646p, 700p, 711p, 720p], + [ 716p, 730p, 741p, 750p], + [ 746p, 800p, 811p, 820p], + [ 816p, 830p, 841p, 850p], + [ 846p, 900p, 911p, 920p], + [ 916p, 930p, 941p, 950p], + [ 946p, 1000p, 1011p, 1020p], + [ 1016p, 1030p, 1041p, 1050p], + [ 1046p, 1100p, 1111p, 1120p], + [ 1116p, 1130p, 1141p, 1150p], + [ 1146p, 1200x, 1211x, 1220x] +] --- /dev/null +++ b/origin-src/wlach-halifax-transit-feed-fef68c1/10-to-dalhousie.yml @@ -1,1 +1,124 @@ +short_name: 10 +long_name: To Dalhousie +time_points: [ 6974, 8368, 7218, 6842, 6105, 6966, 7144 ] +between_stops: + 6974-8368: [ 8160, 8161, 8162, 8483, 8484, 8482, 8319 ] + 8368-7218: [ 6835, 7175, 7209 ] + 7218-6842: [ 7211, 7215, 8581, 8583, 8584, 8585, 8424, 8419, 8429, 8427, 8389, 8392, 8614 ] + 6842-6105: [ 7351, 6088, 6104, 6125 ] + 6105-6966: [ 6108, 6103, 6102, 6122, 8331, 8330, 8334, 8313, 8311, 8315, 6960 ] +stop_times: [ + [ 544a, 553a, 558a, 608a, 618a, 626a, 638a], + [ 613a, 622a, 628a, 638a, 648a, 656a, 708a], + [ 638a, 647a, 654a, 705a, 715a, 723a, 735a], + [ 653a, 702a, 709a, 720a, 730a, 738a, 750a], + [ -, 715a, 722a, 733a, 743a, 751a, 803a], + [ 708a, 717a, 724a, 735a, 745a, 753a, 805a], + [ 723a, 732a, 739a, 750a, 800a, 808a, 820a], + [ -, -, 749a, 800a, 810a, 818a, 830a], + [ 738a, 747a, 754a, 805a, 815a, 823a, 835a], + [ -, -, 801a, 812a, 822a, 830a, 842a], + [ 753a, 802a, 809a, 820a, 830a, 838a, 850a], + [ -, -, 816a, 827a, 837a, 845a, 857a], + [ 808a, 817a, 824a, 835a, 845a, 853a, 905a], + [ 823a, 832a, 839a, 850a, 900a, 908a, 920a], + [ 838a, 847a, 854a, 905a, 915a, 923a, 935a], + [ 908a, 917a, 924a, 935a, 945a, 953a, 1005a], + [ 938a, 947a, 954a, 1005a, 1015a, 1023a, 1035a], + [ 1008a, 1017a, 1024a, 1035a, 1045a, 1053a, 1105a], + [ 1038a, 1047a, 1054a, 1105a, 1115a, 1123a, 1135a], + [ 1108a, 1117a, 1124a, 1135a, 1145a, 1153a, 1205p], + [ 1138a, 1147a, 1154a, 1205p, 1215p, 1223p, 1235p], + [ 1208p, 1217p, 1224p, 1235p, 1245p, 1253p, 105p], + [ 1238p, 1247p, 1254p, 105p, 115p, 123p, 135p], + [ 108p, 117p, 124p, 135p, 145p, 153p, 205p], + [ 138p, 147p, 154p, 205p, 215p, 223p, 235p], + [ 208p, 217p, 224p, 235p, 245p, 253p, 305p], + [ 238p, 247p, 254p, 305p, 315p, 323p, 335p], + [ 308p, 317p, 324p, 335p, 345p, 353p, 405p], + [ 338p, 347p, 354p, 405p, 415p, 423p, 435p], + [ 408p, 417p, 424p, 435p, 445p, 453p, 505p], + [ 423p, 432p, 439p, 450p, 500p, 508p, 520p], + [ 438p, 447p, 454p, 505p, 515p, 523p, 535p], + [ 449p, 458p, 505p, 516p, -, -, -], + [ 453p, 502p, 509p, 520p, 530p, 538p, 550p], + [ 508p, 517p, 524p, 535p, 545p, 553p, 605p], + [ 523p, 532p, 539p, 550p, -, -, -], + [ 538p, 547p, 554p, 605p, 615p, 623p, 635p], + [ 547p, 556p, 602p, 613p, -, -, -], + [ 553p, 602p, 607p, 618p, -, -, -], + [ 605p, 614p, 619p, 630p, 640p, 648p, 700p], + [ 622p, 631p, 636p, 647p, -, -, -], + [ 640p, 649p, 654p, 705p, 715p, 723p, 735p], + [ 700p, 709p, 714p, 725p, 735p, 743p, 755p], + [ 730p, 739p, 744p, 755p, 805p, 813p, 825p], + [ 800p, 809p, 814p, 825p, 835p, 843p, 855p], + [ 830p, 839p, 844p, 855p, 905p, 913p, 925p], + [ 900p, 909p, 914p, 925p, 935p, 943p, 955p], + [ 930p, 939p, 944p, 955p, 1005p, 1013p, 1025p], + [ 1000p, 1009p, 1014p, 1025p, 1035p, 1043p, 1055p], + [ 1030p, 1039p, 1044p, 1055p, 1105p, 1113p, 1125p], + [ 1100p, 1109p, 1114p, 1125p, 1135p, 1143p, 1155p], + [ 1130p, 1139p, 1144p, 1155p, -, -, -], + [ 1200x, 1209x, 1214x, 1225x, -, -, -] +] +stop_times_saturday: [ + [ 603a, 612a, 620a, 630a, 640a, 648a, 700a], + [ 633a, 642a, 650a, 700a, 710a, 718a, 730a], + [ 703a, 712a, 720a, 730a, 740a, 748a, 800a], + [ 733a, 742a, 750a, 800a, 810a, 818a, 830a], + [ 803a, 812a, 820a, 830a, 840a, 848a, 900a], + [ 833a, 842a, 850a, 900a, 910a, 918a, 930a], + [ 903a, 912a, 920a, 930a, 940a, 948a, 1000a], + [ 933a, 942a, 950a, 1000a, 1010a, 1018a, 1030a], + [ 1003a, 1012a, 1020a, 1030a, 1040a, 1048a, 1100a], + [ 1033a, 1042a, 1050a, 1100a, 1110a, 1118a, 1130a], + [ 1103a, 1112a, 1120a, 1130a, 1140a, 1148a, 1200p], + [ 1133a, 1142a, 1150a, 1200p, 1210p, 1218p, 1230p], + [ 1203p, 1212p, 1220p, 1230p, 1240p, 1248p, 100p], + [ 1233p, 1242p, 1250p, 100p, 110p, 118p, 130p], + [ 103p, 112p, 120p, 130p, 140p, 148p, 200p], + [ 133p, 142p, 150p, 200p, 210p, 218p, 230p], + [ 203p, 212p, 220p, 230p, 240p, 248p, 300p], + [ 233p, 242p, 250p, 300p, 310p, 318p, 330p], + [ 303p, 312p, 320p, 330p, 340p, 348p, 400p], + [ 333p, 342p, 350p, 400p, 410p, 418p, 430p], + [ 403p, 412p, 420p, 430p, 440p, 448p, 500p], + [ 433p, 442p, 450p, 500p, 510p, 518p, 530p], + [ 503p, 512p, 520p, 530p, 540p, 548p, 600p], + [ 533p, 542p, 550p, 600p, 610p, 618p, 630p], + [ 603p, 612p, 620p, 630p, 640p, 648p, 700p], + [ 633p, 642p, 650p, 700p, 710p, 718p, 730p], + [ 703p, 712p, 720p, 730p, 740p, 748p, 800p], + [ 733p, 742p, 750p, 800p, 810p, 818p, 830p], + [ 803p, 812p, 820p, 830p, 840p, 848p, 900p], + [ 833p, 842p, 850p, 900p, 910p, 918p, 930p], + [ 903p, 912p, 920p, 930p, 940p, 948p, 1000p], + [ 933p, 942p, 950p, 1000p, 1010p, 1018p, 1030p], + [ 1003p, 1012p, 1020p, 1030p, 1040p, 1048p, 1100p], + [ 1033p, 1042p, 1050p, 1100p, 1110p, 1118p, 1130p], + [ 1103p, 1112p, 1120p, 1130p, 1140p, 1148p, 1200x], + [ 1133p, 1142p, 1150p, 1200x, 1210x, 1218x, 1230x], + [ 1200x, 1207x, 1215x, 1225x, -, -, -] +] +stop_times_sunday: [ + [ 712a, 721a, 729a, 740a, 750a, 759a, 809a], + [ 812a, 821a, 829a, 840a, 850a, 859a, 909a], + [ 912a, 921a, 929a, 940a, 950a, 959a, 1009a], + [ 1012a, 1021a, 1029a, 1040a, 1050a, 1059a, 1109a], + [ 1112a, 1121a, 1129a, 1140a, 1150a, 1159a, 1209p], + [ 1212p, 1221p, 1229p, 1240p, 1250p, 1259p, 109p], + [ 112p, 121p, 129p, 140p, 150p, 159p, 209p], + [ 212p, 221p, 229p, 240p, 250p, 259p, 309p], + [ 312p, 321p, 329p, 340p, 350p, 359p, 409p], + [ 412p, 421p, 429p, 440p, 450p, 459p, 509p], + [ 512p, 521p, 529p, 540p, 550p, 559p, 609p], + [ 612p, 621p, 629p, 640p, 650p, 659p, 709p], + [ 712p, 721p, 729p, 740p, 750p, 759p, 809p], + [ 812p, 821p, 829p, 840p, 850p, 859p, 909p], + [ 912p, 921p, 929p, 940p, 950p, 959p, 1009p], + [ 1012p, 1021p, 1029p, 1040p, 1050p, 1059p, 1109p], + [ 1112p, 1121p, 1129p, 1140p, -, -, -], + [ 1212x, 1221x, -, -, -, -, -] +] --- /dev/null +++ b/origin-src/wlach-halifax-transit-feed-fef68c1/10-to-westphal.yml @@ -1,1 +1,127 @@ +short_name: 10 +long_name: To Westphal +time_points: [ 7144, 6965, 8312, 6087, 8640, 7219, 8369, 8598, 6974 ] +between_stops: + 7144-6965: [ 8304, 6206, 6209, 6208, 6961, 6962, 6965 ] + 6965-8312: [ 6969, 8314, 8306, 8307, 8309, 8312 ] + 8312-6087: [ 8336, 8327, 8338, 6121, 6106 ] + 6087-8640: [ 6455, 6773, 6778, 6779, 6775, 6787, 7351 ] + 8640-7219: [ 8616, 6304, 6303, 8428, 8587, 8580, 8586, 8582, 7214, 7213 ] + 7219-8369: [ 7210, 7173, 6834, 8369 ] + 8369-8598: [ 8416, 8323, 8320, 8603 ] + 8598-6974: [ 6306, 6305, 7053, 7052, 7051, 6369, 6591, 6592, 7057 ] +stop_times: [ + [ -, -, -, -, -, -, 535a, 539a, 543a], + [ -, -, -, -, 553a, 559a, 604a, 608a, 612a], + [ -, -, -, -, -, -, 629a, 633a, 637a], + [ -, -, -, -, -, -, 644a, 648a, 652a], + [ -, -, -, -, 643a, 650a, 657a, 701a, 707a], + [ -, -, -, -, 656a, 704a, 711a, 715a, 721a], + [ 641a, 646a, 651a, 658a, 709a, 719a, 726a, 730a, 736a], + [ -, -, -, -, 725a, 735a, 742a, 746a, 752a], + [ 710a, 715a, 721a, 728a, 740a, 750a, 757a, 801a, 807a], + [ 725a, 730a, 736a, 743a, 755a, 805a, 812a, 816a, 822a], + [ 740a, 745a, 751a, 758a, 810a, 820a, 827a, 831a, 837a], + [ 810a, 815a, 821a, 828a, 840a, 850a, 857a, 901a, 907a], + [ 825a, 830a, 836a, 843a, 855a, -, -, -, -], + [ 840a, 845a, 851a, 858a, 910a, 920a, 927a, 931a, 937a], + [ 910a, 915a, 921a, 928a, 940a, 950a, 957a, 1001a, 1007a], + [ 940a, 945a, 951a, 958a, 1010a, 1020a, 1027a, 1031a, 1037a], + [ 1010a, 1015a, 1021a, 1028a, 1040a, 1050a, 1057a, 1101a, 1107a], + [ 1040a, 1045a, 1051a, 1058a, 1110a, 1120a, 1127a, 1131a, 1137a], + [ 1110a, 1115a, 1121a, 1128a, 1140a, 1150a, 1157a, 1201p, 1207p], + [ 1140a, 1145a, 1151a, 1158a, 1210p, 1220p, 1227p, 1231p, 1237p], + [ 1210p, 1215p, 1221p, 1228p, 1240p, 1250p, 1257p, 101p, 107p], + [ 1240p, 1245p, 1251p, 1258p, 110p, 120p, 127p, 131p, 137p], + [ 110p, 115p, 121p, 128p, 140p, 150p, 157p, 201p, 207p], + [ 140p, 145p, 151p, 158p, 210p, 220p, 227p, 231p, 237p], + [ 210p, 215p, 221p, 228p, 240p, 250p, 257p, 301p, 307p], + [ 240p, 245p, 251p, 258p, 310p, 320p, 327p, 331p, 337p], + [ 310p, 315p, 321p, 328p, 340p, 350p, 357p, 401p, 407p], + [ -, 325p, 331p, 338p, 350p, 400p, 407p, 411p, 417p], + [ 325p, 330p, 336p, 343p, 355p, 405p, 412p, 416p, 422p], + [ 340p, 345p, 351p, 358p, 410p, 420p, 427p, 431p, 437p], + [ 350p, 355p, 401p, 408p, 420p, 430p, 437p, 441p, 447p], + [ 355p, 400p, 406p, 413p, 425p, 435p, 442p, 446p, 452p], + [ 403p, 408p, 414p, 421p, 433p, 443p, 450p, 454p, 500p], + [ 410p, 415p, 421p, 428p, 440p, 450p, 457p, 501p, 507p], + [ 417p, 422p, 428p, 435p, 447p, 457p, 504p, 508p, 514p], + [ 425p, 430p, 436p, 443p, 455p, 505p, 512p, 516p, 522p], + [ 440p, 445p, 451p, 458p, 510p, 520p, 527p, 531p, 537p], + [ -, -, 500p, 507p, 519p, 529p, 536p, 540p, 547p], + [ 455p, 500p, 506p, 513p, 525p, 535p, 542p, 546p, 552p], + [ 510p, 515p, 521p, 528p, 540p, 550p, 556p, 600p, 605p], + [ 530p, 535p, 541p, 548p, 600p, 607p, 613p, 617p, 621p], + [ 550p, 555p, 601p, 608p, 618p, 625p, 631p, 635p, 639p], + [ 610p, 615p, 621p, 628p, 638p, 645p, 651p, 655p, 659p], + [ 640p, 645p, 651p, 658p, 708p, 715p, 721p, 725p, 729p], + [ 710p, 715p, 721p, 728p, 738p, 745p, 751p, 755p, 759p], + [ 740p, 745p, 751p, 758p, 808p, 815p, 821p, 825p, 829p], + [ 810p, 815p, 821p, 828p, 838p, 845p, 851p, 855p, 859p], + [ 840p, 845p, 851p, 858p, 908p, 915p, 921p, 925p, 929p], + [ 910p, 915p, 921p, 928p, 938p, 945p, 951p, 955p, 959p], + [ 940p, 945p, 951p, 958p, 1008p, 1015p, 1021p, 1025p, 1029p], + [ 1010p, 1015p, 1021p, 1028p, 1038p, 1045p, 1051p, 1055p, 1059p], + [ 1040p, 1045p, 1051p, 1058p, 1108p, 1115p, 1121p, 1125p, 1129p], + [ 1110p, 1115p, 1121p, 1128p, 1138p, 1145p, 1151p, 1155p, 1159p] +] +stop_times_saturday: [ + [ 603a, 612a, 620a, 630a, 640a, 648a, 700a], + [ 633a, 642a, 650a, 700a, 710a, 718a, 730a], + [ 703a, 712a, 720a, 730a, 740a, 748a, 800a], + [ 733a, 742a, 750a, 800a, 810a, 818a, 830a], + [ 803a, 812a, 820a, 830a, 840a, 848a, 900a], + [ 833a, 842a, 850a, 900a, 910a, 918a, 930a], + [ 903a, 912a, 920a, 930a, 940a, 948a, 1000a], + [ 933a, 942a, 950a, 1000a, 1010a, 1018a, 1030a], + [ 1003a, 1012a, 1020a, 1030a, 1040a, 1048a, 1100a], + [ 1033a, 1042a, 1050a, 1100a, 1110a, 1118a, 1130a], + [ 1103a, 1112a, 1120a, 1130a, 1140a, 1148a, 1200p], + [ 1133a, 1142a, 1150a, 1200p, 1210p, 1218p, 1230p], + [ 1203p, 1212p, 1220p, 1230p, 1240p, 1248p, 100p], + [ 1233p, 1242p, 1250p, 100p, 110p, 118p, 130p], + [ 103p, 112p, 120p, 130p, 140p, 148p, 200p], + [ 133p, 142p, 150p, 200p, 210p, 218p, 230p], + [ 203p, 212p, 220p, 230p, 240p, 248p, 300p], + [ 233p, 242p, 250p, 300p, 310p, 318p, 330p], + [ 303p, 312p, 320p, 330p, 340p, 348p, 400p], + [ 333p, 342p, 350p, 400p, 410p, 418p, 430p], + [ 403p, 412p, 420p, 430p, 440p, 448p, 500p], + [ 433p, 442p, 450p, 500p, 510p, 518p, 530p], + [ 503p, 512p, 520p, 530p, 540p, 548p, 600p], + [ 533p, 542p, 550p, 600p, 610p, 618p, 630p], + [ 603p, 612p, 620p, 630p, 640p, 648p, 700p], + [ 633p, 642p, 650p, 700p, 710p, 718p, 730p], + [ 703p, 712p, 720p, 730p, 740p, 748p, 800p], + [ 733p, 742p, 750p, 800p, 810p, 818p, 830p], + [ 803p, 812p, 820p, 830p, 840p, 848p, 900p], + [ 833p, 842p, 850p, 900p, 910p, 918p, 930p], + [ 903p, 912p, 920p, 930p, 940p, 948p, 1000p], + [ 933p, 942p, 950p, 1000p, 1010p, 1018p, 1030p], + [ 1003p, 1012p, 1020p, 1030p, 1040p, 1048p, 1100p], + [ 1033p, 1042p, 1050p, 1100p, 1110p, 1118p, 1130p], + [ 1103p, 1112p, 1120p, 1130p, 1140p, 1148p, 1200x], + [ 1133p, 1142p, 1150p, 1200x, 1210x, 1218x, 1230x], + [ 1200x, 1207x, 1215x, 1225x, -, -, -] +] +stop_times_sunday: [ + [ 712a, 721a, 729a, 740a, 750a, 759a, 809a], + [ 812a, 821a, 829a, 840a, 850a, 859a, 909a], + [ 912a, 921a, 929a, 940a, 950a, 959a, 1009a], + [ 1012a, 1021a, 1029a, 1040a, 1050a, 1059a, 1109a], + [ 1112a, 1121a, 1129a, 1140a, 1150a, 1159a, 1209p], + [ 1212p, 1221p, 1229p, 1240p, 1250p, 1259p, 109p], + [ 112p, 121p, 129p, 140p, 150p, 159p, 209p], + [ 212p, 221p, 229p, 240p, 250p, 259p, 309p], + [ 312p, 321p, 329p, 340p, 350p, 359p, 409p], + [ 412p, 421p, 429p, 440p, 450p, 459p, 509p], + [ 512p, 521p, 529p, 540p, 550p, 559p, 609p], + [ 612p, 621p, 629p, 640p, 650p, 659p, 709p], + [ 712p, 721p, 729p, 740p, 750p, 759p, 809p], + [ 812p, 821p, 829p, 840p, 850p, 859p, 909p], + [ 912p, 921p, 929p, 940p, 950p, 959p, 1009p], + [ 1012p, 1021p, 1029p, 1040p, 1050p, 1059p, 1109p], + [ 1112p, 1121p, 1129p, 1140p, -, -, -], + [ 1212x, 1221x, -, -, -, -, -] +] --- /dev/null +++ b/origin-src/wlach-halifax-transit-feed-fef68c1/14-to-leiblin-park.yml @@ -1,1 +1,84 @@ +short_name: 14 +long_name: To Leiblin Park +time_points: [ 6105, 6966, 7421, 8643, 8168, 7143 ] +between_stops: + 6105-6966: [ 6108, 6103, 6102, 6122, 8331, 8330, 8334, 8313, 8311, 8315, 6960 ] + 6966-7421: [ 8317, 7401, 7403, 7410, 7406 ] + 7421-8643: [ 7404, 6405, 6404, 6413, 6414, 7275 ] +stop_times: [ + [ -, -, -, 637a, 649a, 701a], + [ 618a, 630a, 637a, 648a, 700a, 712a], + [ 655a, 707a, 714a, 725a, 737a, 749a], + [ 713a, 725a, 732a, 743a, -, -], + [ 715a, 727a, 734a, 745a, -, -], + [ 735a, 747a, 754a, 805a, 817a, 829a], + [ 755a, 807a, 814a, 825a, 837a, 849a], + [ 816a, 828a, 835a, 846a, -, -], + [ 835a, 847a, 854a, 905a, 917a, 929a], + [ 902a, 914a, 921a, 932a, 944a, 956a], + [ 912a, 924a, 931a, 942a, -, -], + [ 930a, 942a, 949a, 1000a, -, -], + [ 932a, 944a, 951a, 1002a, 1014a, 1026a], + [ 1002a, 1014a, 1021a, 1032a, 1044a, 1056a], + [ 1032a, 1044a, 1051a, 1102a, 1114a, 1126a], + [ 1102a, 1114a, 1121a, 1132a, 1144a, 1156a], + [ 1132a, 1144a, 1151a, 1202p, 1214p, 1226p], + [ 1202p, 1214p, 1221p, 1232p, 1244p, 1256p], + [ 1232p, 1244p, 1251p, 102p, 114p, 126p], + [ 102p, 114p, 121p, 132p, 144p, 156p], + [ 132p, 144p, 151p, 202p, 214p, 226p], + [ 202p, 214p, 221p, 232p, 244p, 256p], + [ 232p, 244p, 251p, 302p, 314p, 326p], + [ 302p, 314p, 321p, 332p, 344p, 356p], + [ 332p, 344p, 351p, 402p, 414p, 426p], + [ 402p, 414p, 421p, 432p, 444p, 456p], + [ 432p, 444p, 451p, 502p, 514p, 526p], + [ 458p, 510p, 517p, 528p, -, -], + [ 502p, 514p, 521p, 532p, 544p, 556p], + [ 532p, 544p, 551p, 602p, 614p, 626p], + [ 602p, 614p, 621p, 632p, 644p, 656p], + [ 632p, 644p, 651p, 702p, 714p, 726p], + [ 732p, 744p, 751p, 802p, 814p, 826p], + [ 832p, 844p, 851p, 902p, 914p, 926p], + [ 932p, 944p, 951p, 1002p, 1014p, 1026p], + [ 1032p, 1044p, 1051p, 1102p, 1114p, 1126p], + [ -, -, -, 1210x, 1222x, 1234x] +] +stop_times_saturday: [ + [ -, -, -, 618a, 627a, 640a], + [ -, -, -, 718a, 727a, 740a], + [ 748a, 800a, 807a, 818a, 827a, 840a], + [ 848a, 900a, 907a, 918a, 927a, 940a], + [ 948a, 1000a, 1007a, 1018a, 1027a, 1040a], + [ 1048a, 1100a, 1107a, 1118a, 1127a, 1140a], + [ 1148a, 1200p, 1207p, 1218p, 1227p, 1240p], + [ 1248p, 100p, 107p, 118p, 127p, 140p], + [ 148p, 200p, 207p, 218p, 227p, 240p], + [ 248p, 300p, 307p, 318p, 327p, 340p], + [ 348p, 400p, 407p, 418p, 427p, 440p], + [ 448p, 500p, 507p, 518p, 527p, 540p], + [ 548p, 600p, 607p, 618p, 627p, 640p], + [ 648p, 700p, 707p, 718p, 727p, 740p], + [ 748p, 800p, 807p, 818p, 827p, 840p], + [ 848p, 900p, 907p, 918p, 927p, 940p], + [ 948p, 1000p, 1007p, 1018p, 1027p, 1040p] +] +stop_times_sunday: [ + [ 636a, 645a, 652a, 705a, 712a, 722a], + [ 736a, 745a, 752a, 805a, 812a, 822a], + [ 836a, 845a, 852a, 905a, 912a, 922a], + [ 936a, 945a, 952a, 1005a, 1012a, 1022a], + [ 1036a, 1045a, 1052a, 1105a, 1112a, 1122a], + [ 1136a, 1145a, 1152a, 1205p, 1212p, 1222p], + [ 1236p, 1245p, 1252p, 105p, 112p, 122p], + [ 136p, 145p, 152p, 205p, 212p, 222p], + [ 236p, 245p, 252p, 305p, 312p, 322p], + [ 336p, 345p, 352p, 405p, 412p, 422p], + [ 436p, 445p, 452p, 505p, 512p, 522p], + [ 536p, 545p, 552p, 605p, 612p, 622p], + [ 636p, 645p, 652p, 705p, 712p, 722p], + [ 736p, 745p, 752p, 805p, 812p, 822p], + [ 836p, 845p, 852p, 905p, 912p, 922p], + [ 936p, 945p, 952p, 1005p, 1012p, 1022p] +] --- /dev/null +++ b/origin-src/wlach-halifax-transit-feed-fef68c1/14-to-universities-downtown.yml @@ -1,1 +1,83 @@ +short_name: 14 +long_name: To Universities / Downtown +time_points: [ 7143, 8167, 7285, 7412, 6965, 6087 ] +between_stops: + 7285-7412: [ 7274, 6409, 6403, 6407, 6406, 7355 ] + 7412-6965: [ 7419, 7409, 7402, 8304, 6962 ] + 6965-6087: [ 6969, 8314, 8306, 8307, 8309, 8312, 8336, 8327, 8338, 6121, 6106 ] +stop_times: [ + [ 632a, 642a, 657a, 708a, 715a, 727a], + [ 702a, 712a, 727a, 738a, 745a, 757a], + [ -, -, 755a, 806a, 813a, 825a], + [ 732a, 742a, 757a, 808a, 815a, 827a], + [ 802a, 812a, 827a, 838a, 845a, 857a], + [ 832a, 842a, 857a, 908a, 915a, 927a], + [ 902a, 912a, 927a, 938a, 945a, 957a], + [ 932a, 942a, 957a, 1008a, 1015a, 1027a], + [ 1002a, 1012a, 1027a, 1038a, 1045a, 1057a], + [ 1032a, 1042a, 1057a, 1108a, 1115a, 1127a], + [ 1102a, 1112a, 1127a, 1138a, 1145a, 1157a], + [ 1132a, 1142a, 1157a, 1208p, 1215p, 1227p], + [ 1202p, 1212p, 1227p, 1238p, 1245p, 1257p], + [ 1232p, 1242p, 1257p, 108p, 115p, 127p], + [ 102p, 112p, 127p, 138p, 145p, 157p], + [ 132p, 142p, 157p, 208p, 215p, 227p], + [ 202p, 212p, 227p, 238p, 245p, 257p], + [ 232p, 242p, 257p, 308p, 315p, 327p], + [ 302p, 312p, 327p, 338p, 345p, 357p], + [ -, -, 340p, 351p, 358p, 410p], + [ -, -, -, -, 407p, 419p], + [ 332p, 342p, 357p, 408p, 415p, 427p], + [ -, -, -, -, 428p, 440p], + [ 359p, 409p, 424p, 435p, 442p, 454p], + [ 432p, 442p, 457p, 508p, 515p, 527p], + [ 502p, 512p, 527p, 538p, 545p, 557p], + [ 532p, 542p, 557p, 608p, 615p, 627p], + [ 602p, 612p, 627p, 638p, 645p, 657p], + [ 632p, 642p, 657p, 708p, 715p, 727p], + [ 702p, 712p, 727p, 738p, 745p, 757p], + [ 732p, 742p, 757p, 808p, 815p, 827p], + [ 832p, 842p, 857p, 908p, 915p, 927p], + [ 932p, 942p, 957p, 1008p, 1015p, 1027p], + [ 1032p, 1042p, 1057p, -, -, -], + [ 1132p, 1142p, 1157p, -, -, -], + [ 1235x, 1245x, 100x, -, -, -] +] +stop_times_saturday: [ + [ 646a, 655a, 710a, 721a, 728a, 740a], + [ 746a, 755a, 810a, 821a, 828a, 840a], + [ 846a, 855a, 910a, 921a, 928a, 940a], + [ 946a, 955a, 1010a, 1021a, 1028a, 1040a], + [ 1046a, 1055a, 1110a, 1121a, 1128a, 1140a], + [ 1146a, 1155a, 1210p, 1221p, 1228p, 1240p], + [ 1246p, 1255p, 110p, 121p, 128p, 140p], + [ 146p, 155p, 210p, 221p, 228p, 240p], + [ 246p, 255p, 310p, 321p, 328p, 340p], + [ 346p, 355p, 410p, 421p, 428p, 440p], + [ 446p, 455p, 510p, 521p, 528p, 540p], + [ 546p, 555p, 610p, 621p, 628p, 640p], + [ 646p, 655p, 710p, 721p, 728p, 740p], + [ 746p, 755p, 810p, 821p, 828p, 840p], + [ 846p, 855p, 910p, 921p, 928p, 940p], + [ 946p, 955p, 1010p, 1021p, 1028p, 1040p], + [ 1046p, 1055p, 1107p, -, -, -] +] +stop_times_sunday: [ + [ 728a, 736a, 750a, 801a, 808a, 817a], + [ 828a, 836a, 850a, 901a, 908a, 917a], + [ 928a, 936a, 950a, 1001a, 1008a, 1017a], + [ 1028a, 1036a, 1050a, 1101a, 1108a, 1117a], + [ 1128a, 1136a, 1150a, 1201p, 1208p, 1217p], + [ 1228p, 1236p, 1250p, 101p, 108p, 117p], + [ 128p, 136p, 150p, 201p, 208p, 217p], + [ 228p, 236p, 250p, 301p, 308p, 317p], + [ 328p, 336p, 350p, 401p, 408p, 417p], + [ 428p, 436p, 450p, 501p, 508p, 517p], + [ 528p, 536p, 550p, 601p, 608p, 617p], + [ 628p, 636p, 650p, 701p, 708p, 717p], + [ 728p, 736p, 750p, 801p, 808p, 817p], + [ 828p, 836p, 850p, 901p, 908p, 917p], + [ 928p, 936p, 950p, 1001p, 1008p, 1017p], + [ 1028p, 1036p, 1050p, 1101p, 1108p, 1117p] +] --- /dev/null +++ b/origin-src/wlach-halifax-transit-feed-fef68c1/17-to-hospitals-universities.yml @@ -1,1 +1,38 @@ +short_name: 17 +long_name: "To Hospitals / Universities" +time_points: [ 7087, 7166, 6564, 8561, 8214, 8308, 6966 ] +between_stops: + 6564-8561: [ 6197, 6193, 6203 ] + 8561-8214: [ 8564, 8557, 8566, 8559, 6545, 8205, 8190 ] + 8214-8308: [ 8183, 8196, 8195, 8194, 8328, 8337, 8333 ] + 8308-6966: [ 8313, 8311, 8315, 6960 ] +stop_times: [ + [ 625a, 634a, 638a, 645a, 652a, 658a, 702a], + [ 655a, 704a, 708a, 715a, 722a, 728a, 732a], + [ 725a, 734a, 738a, 745a, 752a, 758a, 802a], + [ 755a, 804a, 808a, 815a, 822a, 828a, 832a], + [ -, -, 825a, 832a, 839a, 845a, 849a], + [ 825a, 834a, 838a, 845a, 852a, 858a, 902a], + [ 838a, 847a, 851a, 858a, 905a, 911a, 915a], + [ 855a, 904a, 908a, 915a, 922a, 928a, 932a], + [ -, -, 910a, 917a, 924a, 930a, 934a], + [ 925a, 934a, 938a, 945a, 952a, 958a, 1002a], + [ 955a, 1004a, 1008a, 1015a, 1022a, 1028a, 1032a], + [ 1025a, 1034a, 1038a, 1045a, 1052a, 1058a, 1102a], + [ 1055a, 1104a, 1108a, 1115a, 1122a, 1128a, 1132a], + [ 1125a, 1134a, 1138a, 1145a, 1152a, 1158a, 1202p], + [ 1155a, 1204p, 1208p, 1215p, 1222p, 1228p, 1232p], + [ 1225p, 1234p, 1238p, 1245p, 1252p, 1258p, 102p], + [ 1255p, 104p, 108p, 115p, 122p, 128p, 132p], + [ 125p, 134p, 138p, 145p, 152p, 158p, 202p], + [ 155p, 204p, 208p, 215p, 222p, 228p, 232p], + [ 225p, 234p, 238p, 245p, 252p, 258p, 302p], + [ 255p, 304p, 308p, 315p, 322p, 328p, 332p], + [ 325p, 334p, 338p, 345p, 352p, 358p, 402p], + [ 355p, 404p, 408p, 415p, 422p, 428p, 432p], + [ 425p, 434p, 438p, 445p, 452p, 458p, 502p], + [ 455p, 504p, 508p, 515p, 522p, 528p, 532p], + [ 525p, 534p, 538p, 545p, 552p, 558p, 602p], + [ 555p, 604p, 608p, 615p, 622p, 628p, 632p] +] --- /dev/null +++ b/origin-src/wlach-halifax-transit-feed-fef68c1/17-to-lacewood.yml @@ -1,1 +1,37 @@ +short_name: 17 +long_name: To Lacewood +time_points: [ 6966, 8185, 8184, 8571, 6563, 6032, 7087 ] +between_stops: + 6966-8185: [ 8220, 8187 ] + 8185-8184: [ 8219, 8179 ] + 8184-8571: [ 8206, 6544, 8558, 8554, 8568, 8556, 8572 ] + 8571-6563: [ 6192, 6196, 6201, 6200, 6199, 6198 ] + 6563-6032: [ 6565, 6984 ] +stop_times: [ + [ 705a, 709a, 711a, 718a, 726a, 731a, 738a], + [ 735a, 739a, 741a, 748a, 756a, 801a, 808a], + [ 805a, 809a, 811a, 818a, 826a, 831a, 838a], + [ 835a, 839a, 841a, 848a, 856a, 901a, 908a], + [ 905a, 909a, 911a, 918a, 926a, 931a, 938a], + [ 935a, 939a, 941a, 948a, 956a, 1001a, 1008a], + [ 1005a, 1009a, 1011a, 1018a, 1026a, 1031a, 1038a], + [ 1035a, 1039a, 1041a, 1048a, 1056a, 1101a, 1108a], + [ 1105a, 1109a, 1111a, 1118a, 1126a, 1131a, 1138a], + [ 1135a, 1139a, 1141a, 1148a, 1156a, 1201p, 1208p], + [ 1205p, 1209p, 1211p, 1218p, 1226p, 1231p, 1238p], + [ 1235p, 1239p, 1241p, 1248p, 1256p, 101p, 108p], + [ 105p, 109p, 111p, 118p, 126p, 131p, 138p], + [ 135p, 139p, 141p, 148p, 156p, 201p, 208p], + [ 205p, 209p, 211p, 218p, 226p, 231p, 238p], + [ 235p, 239p, 241p, 248p, 256p, 301p, 308p], + [ 305p, 309p, 311p, 318p, 326p, 331p, 338p], + [ 335p, 339p, 341p, 348p, 356p, 401p, 408p], + [ 405p, 409p, 411p, 418p, 426p, 431p, 438p], + [ 435p, 439p, 441p, 448p, 456p, 501p, 508p], + [ 505p, 509p, 511p, 518p, 526p, 531p, 538p], + [ 535p, 539p, 541p, 548p, 556p, 601p, 608p], + [ 605p, 609p, 611p, 618p, 626p, 631p, 638p], + [ 635p, 639p, 641p, 648p, 656p, 701p, 708p] +] + --- /dev/null +++ b/origin-src/wlach-halifax-transit-feed-fef68c1/18-to-lacewood.yml @@ -1,1 +1,103 @@ +short_name: 18 +long_name: To Lacewood +time_points: [ 6966, 8184, 6219, 7086 ] +between_stops: + 6966-8184: [ 8220, 8187, 8185, 8219, 8179 ] + 8184-6219: [ 8206, 6544, 8558, 8554, 8568, 8556, 8572, 8571, 8574, 8550, 8549, 8567, 8570, 8552 ] +stop_times: [ + [ -, -, 612a, 625a], + [ -, -, 642a, 655a], + [ 650a, 658a, 715a, 728a], + [ 720a, 728a, 745a, 758a], + [ 750a, 758a, 815a, 828a], + [ 820a, 828a, 845a, 858a], + [ 850a, 858a, 914a, 926a], + [ 921a, 927a, 944a, 956a], + [ 950a, 956a, 1013a, 1025a], + [ 1020a, 1026a, 1043a, 1055a], + [ 1050a, 1056a, 1113a, 1125a], + [ 1120a, 1126a, 1143a, 1155a], + [ 1150a, 1156a, 1213p, 1225p], + [ 1220p, 1226p, 1243p, 1255p], + [ 1250p, 1256p, 113p, 125p], + [ 120p, 126p, 143p, 155p], + [ 150p, 156p, 213p, 225p], + [ 220p, 226p, 248p, 301p], + [ 227p, 234p, 256p, 309p], + [ 247p, 255p, 317p, 330p], + [ 257p, 305p, 327p, 340p], + [ 320p, 328p, 350p, 403p], + [ 340p, 348p, 410p, 423p], + [ 350p, 358p, 420p, 433p], + [ 419p, 427p, 449p, 502p], + [ 450p, 458p, 520p, 533p], + [ 520p, 528p, 550p, 603p], + [ 550p, 558p, 615p, 627p], + [ 621p, 627p, 644p, 656p], + [ 650p, 656p, 713p, 725p], + [ 720p, 726p, 743p, 755p], + [ 750p, 756p, 813p, 825p], + [ 820p, 826p, 843p, 855p], + [ 850p, 856p, 913p, 925p], + [ 920p, 926p, 943p, 955p], + [ 950p, 956p, 1013p, 1025p], + [ 1020p, 1026p, 1043p, 1055p], + [ 1050p, 1056p, 1113p, 1125p], + [ 1120p, 1126p, 1143p, 1155p], + [ 1150p, 1156p, 1213x, 1225x] +] +stop_times_saturday: [ + [ 702a, 713a, 730a, 740a], + [ 802a, 813a, 830a, 840a], + [ 832a, 843a, 900a, 910a], + [ 902a, 913a, 930a, 940a], + [ 932a, 943a, 1000a, 1010a], + [ 1002a, 1013a, 1030a, 1040a], + [ 1032a, 1043a, 1100a, 1110a], + [ 1102a, 1113a, 1130a, 1140a], + [ 1132a, 1143a, 1200p, 1210p], + [ 1202p, 1213p, 1230p, 1240p], + [ 1232p, 1243p, 100p, 110p], + [ 102p, 113p, 130p, 140p], + [ 132p, 143p, 200p, 210p], + [ 202p, 213p, 230p, 240p], + [ 232p, 243p, 300p, 310p], + [ 302p, 313p, 330p, 340p], + [ 332p, 343p, 400p, 410p], + [ 402p, 413p, 430p, 440p], + [ 432p, 443p, 500p, 510p], + [ 502p, 513p, 530p, 540p], + [ 532p, 543p, 600p, 610p], + [ 602p, 613p, 630p, 640p], + [ 632p, 643p, 700p, 710p], + [ 702p, 713p, 730p, 740p], + [ 732p, 743p, 800p, 810p], + [ 802p, 813p, 830p, 840p], + [ 832p, 843p, 900p, 910p], + [ 902p, 913p, 930p, 940p], + [ 932p, 943p, 1000p, 1010p], + [ 1002p, 1013p, 1030p, 1040p], + [ 1102p, 1113p, 1130p, 1140p], + [ 1202x, 1213x, 1230x, 1240x] +] +stop_times_sunday: [ + [ -, -, 655a, 705a], + [ 738a, 742a, 755a, 805a], + [ 838a, 842a, 855a, 905a], + [ 938a, 942a, 955a, 1005a], + [ 1038a, 1042a, 1055a, 1105a], + [ 1138a, 1142a, 1155a, 1205p], + [ 1238p, 1242p, 1255p, 105p], + [ 138p, 142p, 155p, 205p], + [ 238p, 242p, 255p, 305p], + [ 338p, 342p, 355p, 405p], + [ 438p, 442p, 455p, 505p], + [ 538p, 542p, 555p, 605p], + [ 638p, 642p, 655p, 705p], + [ 738p, 742p, 755p, 805p], + [ 838p, 842p, 855p, 905p], + [ 938p, 942p, 955p, 1005p], + [ 1038p, 1042p, 1055p, 1105p], + [ 1138p, 1142p, 1155p, 1205x] +] --- /dev/null +++ b/origin-src/wlach-halifax-transit-feed-fef68c1/18-to-smu.yml @@ -1,1 +1,105 @@ +short_name: 18 +long_name: To SMU +time_points: [ 7087, 6216, 8214, 6966 ] +between_stops: + 6216-8214: [ 8551, 8562, 8565, 8573, 8555, 8561, 8564, 8557, 8566, 8559, 6545, 8205, 8190 ] + 8214-6966: [ 8183, 8196, 8195, 8194, 8328, 8337, 8333, 8308, 8313, 8311, 8315, 6960 ] +stop_times: [ + [ 610a, 622a, 641a, 649a], + [ 640a, 652a, 711a, 719a], + [ 710a, 722a, 741a, 749a], + [ 740a, 752a, 811a, 819a], + [ 810a, 822a, 841a, 849a], + [ 840a, 852a, 911a, 921a], + [ 910a, 922a, 939a, 949a], + [ 940a, 952a, 1009a, 1019a], + [ 1010a, 1022a, 1039a, 1049a], + [ 1040a, 1052a, 1109a, 1119a], + [ 1110a, 1122a, 1139a, 1149a], + [ 1140a, 1152a, 1209p, 1219p], + [ 1210p, 1222p, 1239p, 1249p], + [ 1240p, 1252p, 109p, 119p], + [ 110p, 122p, 139p, 149p], + [ 140p, 152p, 209p, 219p], + [ -, -, 214p, 224p], + [ 210p, 222p, 237p, 244p], + [ -, -, 240p, 247p], + [ 240p, 253p, 310p, 317p], + [ -, -, 330p, 337p], + [ 310p, 323p, 340p, 347p], + [ 340p, 353p, 410p, 417p], + [ 410p, 423p, 440p, 447p], + [ 440p, 453p, 510p, 517p], + [ 510p, 523p, 540p, 547p], + [ 540p, 553p, 611p, 621p], + [ 610p, 622p, 639p, 649p], + [ 640p, 652p, 709p, 719p], + [ 710p, 722p, 739p, 749p], + [ 740p, 752p, 809p, 819p], + [ 810p, 822p, 839p, 849p], + [ 840p, 852p, 909p, 919p], + [ 910p, 922p, 939p, 949p], + [ 940p, 952p, 1009p, 1019p], + [ 1010p, 1022p, 1039p, 1049p], + [ 1040p, 1052p, 1109p, 1119p], + [ 1110p, 1122p, 1139p, 1149p], + [ 1140p, 1152p, 1209x, 1219x], + [ 1210x, 1222x, -, -], + [ 1225x, 1237x, -, -] +] +stop_times_saturday: [ + [ 620a, 630a, 649a, 702a], + [ 720a, 730a, 749a, 802a], + [ 750a, 800a, 819a, 832a], + [ 820a, 830a, 849a, 902a], + [ 850a, 900a, 919a, 932a], + [ 920a, 930a, 949a, 1002a], + [ 950a, 1000a, 1019a, 1032a], + [ 1020a, 1030a, 1049a, 1102a], + [ 1050a, 1100a, 1119a, 1132a], + [ 1120a, 1130a, 1149a, 1202p], + [ 1150a, 1200p, 1219p, 1232p], + [ 1220p, 1230p, 1249p, 102p], + [ 1250p, 100p, 119p, 132p], + [ 120p, 130p, 149p, 202p], + [ 150p, 200p, 219p, 232p], + [ 220p, 230p, 249p, 302p], + [ 250p, 300p, 319p, 332p], + [ 320p, 330p, 349p, 402p], + [ 350p, 400p, 419p, 432p], + [ 420p, 430p, 449p, 502p], + [ 450p, 500p, 519p, 532p], + [ 520p, 530p, 549p, 602p], + [ 550p, 600p, 619p, 632p], + [ 620p, 630p, 649p, 702p], + [ 650p, 700p, 719p, 732p], + [ 720p, 730p, 749p, 802p], + [ 750p, 800p, 819p, 832p], + [ 820p, 830p, 849p, 902p], + [ 850p, 900p, 919p, 932p], + [ 920p, 930p, 949p, 1002p], + [ 1020p, 1030p, 1049p, 1102p], + [ 1120p, 1130p, 1149p, 1202x], + [ 1220x, 1230x, -, -] +] +stop_times_sunday: [ + [ 705a, 715a, 731a, 738a], + [ 805a, 815a, 831a, 838a], + [ 905a, 915a, 931a, 938a], + [ 1005a, 1015a, 1031a, 1038a], + [ 1105a, 1115a, 1131a, 1138a], + [ 1205p, 1215p, 1231p, 1238p], + [ 105p, 115p, 131p, 138p], + [ 205p, 215p, 231p, 238p], + [ 305p, 315p, 331p, 338p], + [ 405p, 415p, 431p, 438p], + [ 505p, 515p, 531p, 538p], + [ 605p, 615p, 631p, 638p], + [ 705p, 715p, 731p, 738p], + [ 805p, 815p, 831p, 838p], + [ 905p, 915p, 931p, 938p], + [ 1005p, 1015p, 1031p, 1038p], + [ 1105p, 1115p, 1131p, 1138p], + [ 1205x, 1215x, -, -] +] --- /dev/null +++ b/origin-src/wlach-halifax-transit-feed-fef68c1/2-to-downtown-via-north.yml @@ -1,1 +1,91 @@ +short_name: 2 +long_name: To Downtown via North Street +time_points: [ 7019, 7023, 7087, 6564, 6612, 7284, 7351, 6105, 8435 ] +between_stops: + 6564-7284: [ 6197, 6193 ] + 7284-7351: [ 7274, 6409, 6403, 6407, 6406, 7355, 7345, 7357, 7352, 7342, 7354, 7347 ] + 7351-6105: [ 7343, 6088, 6104, 6125 ] + 6105-8435: [ 6108, 6733 ] +stop_times: [ + [ 557a, 601a, 610a, 624a, -, 632a, 642a, 646a, 649a], + [ 637a, 641a, 650a, 704a, -, 712a, 722a, 727a, 730a], + [ 707a, 711a, 720a, 734a, -, 742a, 752a, 757a, 800a], + [ 737a, 741a, 750a, 804a, -, 812a, 822a, 827a, 830a], + [ 807a, 811a, 820a, 834a, -, 842a, 852a, 857a, 900a], + [ 837a, 841a, 850a, 904a, -, 912a, 922a, 928a, 931a], + [ 907a, 911a, 920a, 935a, -, 943a, 953a, 959a, 1002a], + [ 937a, 941a, 950a, 1005a, -, 1013a, 1023a, 1029a, 1032a], + [ 1007a, 1011a, 1020a, 1035a, -, 1043a, 1053a, 1059a, 1102a], + [ 1037a, 1041a, 1050a, 1105a, -, 1113a, 1123a, 1129a, 1132a], + [ 1107a, 1111a, 1120a, 1135a, -, 1143a, 1153a, 1159a, 1202p], + [ 1137a, 1141a, 1150a, 1205p, -, 1213p, 1223p, 1229p, 1232p], + [ 1207p, 1211p, 1220p, 1235p, -, 1243p, 1253p, 1259p, 102p], + [ 1237p, 1241p, 1250p, 105p, -, 113p, 123p, 129p, 132p], + [ 107p, 111p, 120p, 135p, -, 143p, 153p, 159p, 202p], + [ 137p, 141p, 150p, 205p, -, 213p, 223p, 229p, 232p], + [ 207p, 211p, 220p, 235p, -, 243p, 253p, 259p, 302p], + [ 237p, 241p, 250p, 305p, -, 313p, 323p, 329p, 332p], + [ 307p, 311p, 320p, 335p, -, 343p, 353p, 359p, 402p], + [ 337p, 341p, 350p, 405p, -, 413p, 423p, 429p, 432p], + [ 407p, 411p, 420p, 435p, -, 443p, 453p, 459p, 502p], + [ 437p, 441p, 450p, 505p, -, 513p, 523p, 529p, 532p], + [ 507p, 511p, 520p, 535p, -, 543p, 553p, 559p, 602p], + [ 537p, 541p, 550p, 604p, -, 610p, 618p, 622p, 625p], + [ 607p, 611p, 620p, 633p, -, 639p, 647p, 651p, 654p], + [ 637p, 641p, 650p, 703p, -, 709p, 717p, 721p, 724p], + [ 737p, 741p, 750p, 803p, -, 809p, 817p, 821p, 824p], + [ 837p, 841p, 850p, 903p, -, 909p, 917p, 921p, 924p], + [ 937p, 941p, 950p, 1003p, 1005p, 1009p, 1017p, 1021p, 1024p], + [ 1037p, 1041p, 1050p, 1103p, 1105p, 1109p, 1117p, 1121p, 1124p], + [ 1137p, 1141p, 1150p, 1203x, 1205x, 1209x, 1217x, 1221x, 1224x], + [ 1237x, 1241x, 1250x, -, -, -, -, -, -] +] +stop_times_saturday: [ + [ 632a, 637a, 645a, 659a, -, 705a, 713a, 716a, 718a], + [ 732a, 737a, 745a, 759a, -, 805a, 813a, 816a, 818a], + [ 832a, 837a, 845a, 859a, -, 905a, 915a, 917a, 920a], + [ 932a, 937a, 945a, 1000a, -, 1006a, 1015a, 1018a, 1021a], + [ 1032a, 1037a, 1045a, 1100a, -, 1106a, 1115a, 1118a, 1121a], + [ 1102a, 1107a, 1115a, 1130a, -, 1136a, 1145a, 1148a, 1151a], + [ 1132a, 1137a, 1145a, 1200a, -, 1206p, 1215p, 1218p, 1221p], + [ 1202p, 1207p, 1215p, 1230p, -, 1236p, 1245p, 1248p, 1251p], + [ 1232p, 1237p, 1245p, 100p, -, 106p, 115p, 118p, 121p], + [ 102p, 107p, 115p, 130p, -, 136p, 145p, 148p, 151p], + [ 132p, 137p, 145p, 200p, -, 206p, 215p, 218p, 221p], + [ 202p, 207p, 215p, 230p, -, 236p, 245p, 248p, 251p], + [ 232p, 237p, 245p, 300p, -, 306p, 315p, 318p, 321p], + [ 302p, 307p, 315p, 330p, -, 336p, 345p, 348p, 351p], + [ 332p, 337p, 345p, 400p, -, 406p, 415p, 418p, 421p], + [ 402p, 407p, 415p, 430p, -, 436p, 445p, 448p, 451p], + [ 432p, 437p, 445p, 500p, -, 506p, 515p, 518p, 521p], + [ 502p, 507p, 515p, 530p, -, 536p, 545p, 548p, 551p], + [ 532p, 537p, 545p, 600p, -, 606p, 615p, 618p, 619p], + [ 602p, 607p, 615p, 628p, -, 634p, 642p, 645p, 647p], + [ 632p, 637p, 645p, 658p, -, 704p, 712p, 715p, 717p], + [ 737p, 742p, 750p, 803p, -, 809p, 817p, 820p, 822p], + [ 837p, 842p, 850p, 903p, -, 909p, 917p, 920p, 922p], + [ 937p, 942p, 950p, 1003p, 1005p, 1009p, 1017p, 1020p, 1022p], + [ 1037p, 1042p, 1050p, 1103p, 1105p, 1109p, 1117p, 1120p, 1122p], + [ 1137p, 1142p, 1150p, 1203x, 1205x, 1209x, 1217x, 1220x, 1222x], + [ 1237x, 1242x, 1250x, -, -, -, -, -] +] +stop_times_sunday: [ + [ 740a, 743a, 750a, 800a, -, 808a, 818a, 821a, 823a], + [ 840a, 843a, 850a, 900a, -, 908a, 918a, 921a, 923a], + [ 940a, 943a, 950a, 1000a, -, 1008a, 1018a, 1021a, 1023a], + [ 1040a, 1043a, 1050a, 1100a, -, 1108a, 1118a, 1121a, 1123a], + [ 1140a, 1143a, 1150a, 1200p, -, 1208p, 1218p, 1221p, 1223p], + [ 1240p, 1243p, 1250p, 100p, -, 108p, 118p, 121p, 123p], + [ 140p, 143p, 150p, 200p, -, 208p, 218p, 221p, 223p], + [ 240p, 243p, 250p, 300p, -, 308p, 318p, 318p, 323p], + [ 340p, 343p, 350p, 400p, -, 408p, 418p, 421p, 423p], + [ 440p, 443p, 450p, 500p, -, 508p, 518p, 521p, 523p], + [ 540p, 543p, 550p, 600p, -, 606p, 616p, 619p, 621p], + [ 640p, 643p, 650p, 700p, -, 706p, 716p, 719p, 721p], + [ 740p, 743p, 750p, 800p, -, 806p, 816p, 819p, 821p], + [ 840p, 843p, 850p, 900p, -, 906p, 916p, 919p, 921p], + [ 940p, 943p, 950p, 1000p, 1002p, 1005p, 1015p, 1018p, 1020p], + [ 1040p, 1043p, 1050p, 1100p, 1102p, 1105p, 1115p, 1118p, 1120p], + [ 1140p, 1143p, 1150p, -, -, -, -, -, -] +] --- /dev/null +++ b/origin-src/wlach-halifax-transit-feed-fef68c1/2-to-wedgewood-via-main.yml @@ -1,1 +1,91 @@ +short_name: 2 +long_name: To Wedgewood via Main +time_points: [ 8435, 7348, 8643, 6612, 6563, 6032, 6597, 7087, 7019 ] +between_stops: + 8435-7348: [ 6089, 6116 ] + 7348-8643: [ 7346, 7341, 7353, 7358, 7344, 7356, 6405, 6404, 6413, 6414, 7275 ] + 8643-6563: [ 6199, 6198 ] + 6563-6032: [ 6563, 6565, 6984 ] +stop_times: [ + [ -, -, -, -, -, 540a, 545a, 550a, 556a], + [ -, -, 610a, -, 616a, 620a, 625a, 630a, 636a], + [ 625a, 631a, 640a, -, 646a, 650a, 655a, 700a, 706a], + [ 655a, 701a, 710a, -, 716a, 720a, 725a, 730a, 736a], + [ 725a, 731a, 740a, -, 746a, 750a, 755a, 800a, 806a], + [ 755a, 801a, 810a, -, 816a, 820a, 825a, 830a, 836a], + [ 818a, 824a, 837a, -, 844a, 849a, 856a, 857a, 906a], + [ 847a, 854a, 907a, -, 914a, 919a, 926a, 930a, 936a], + [ 917a, 924a, 937a, -, 944a, 949a, 956a, 1000a, 1006a], + [ 947a, 954a, 1007a, -, 1014a, 1019a, 1026a, 1030a, 1036a], + [ 1017a, 1024a, 1037a, -, 1044a, 1049a, 1056a, 1100a, 1106a], + [ 1047a, 1054a, 1107a, -, 1114a, 1119a, 1126a, 1130a, 1136a], + [ 1117a, 1124a, 1137a, -, 1144a, 1149a, 1156a, 1200p, 1206p], + [ 1147a, 1154a, 1207p, -, 1214p, 1219p, 1226p, 1230p, 1236p], + [ 1217p, 1224p, 1237p, -, 1244p, 1249p, 1256p, 100p, 106p], + [ 1247p, 1254p, 107p, -, 114p, 119p, 126p, 130p, 136p], + [ 117p, 124p, 137p, -, 144p, 149p, 156p, 200p, 206p], + [ 146p, 153p, 206p, -, 213p, 218p, 225p, 229p, 236p], + [ 216p, 223p, 237p, -, 244p, 249p, 256p, 300p, 306p], + [ 246p, 253p, 307p, -, 314p, 319p, 326p, 330p, 336p], + [ 314p, 322p, 337p, -, 344p, 349p, 356p, 400p, 406p], + [ 344p, 352p, 407p, -, 414p, 419p, 426p, 430p, 436p], + [ 414p, 422p, 437p, -, 444p, 449p, 456p, 500p, 506p], + [ 444p, 452p, 507p, -, 514p, 519p, 526p, 530p, 536p], + [ 514p, 522p, 537p, -, 544p, 549p, 556p, 600p, 606p], + [ 553p, 601p, 610p, -, 616p, 620p, 625p, 630p, 636p], + [ 653p, 700p, 710p, -, 716p, 720p, 725p, 729p, 736p], + [ 753p, 800p, 810p, -, 816p, 820p, 825p, 829p, 836p], + [ 854p, 901p, 910p, -, 916p, 920p, 925p, 930p, 936p], + [ 954p, 1001p, 1010p, 1011p, 1016p, 1020p, 1025p, 1030p, 1036p], + [ 1054p, 1101p, 1110p, 1111p, 1116p, 1120p, 1125p, 1130p, 1136p], + [ 1154p, 1201x, 1210x, 1211x, 1216x, 1220x, 1225x, 1230x, 1236x] +] +stop_times_saturday: [ + [ -, -, 607a, -, 613a, 617a, 622a, 625a, 631a], + [ 652a, 658a, 707a, -, 713a, 717a, 722a, 725a, 731a], + [ 752a, 758a, 807a, -, 813a, 817a, 822a, 825a, 831a], + [ 852a, 858a, 907a, -, 914a, 918a, 923a, 926a, 932a], + [ 944a, 950a, 1004a, -, 1010a, 1015a, 1021a, 1024a, 1030a], + [ 1014a, 1020a, 1034a, -, 1040a, 1045a, 1051a, 1054a, 1100a], + [ 1044a, 1050a, 1104a, -, 1110a, 1115a, 1121a, 1124a, 1130a], + [ 1114a, 1120a, 1134a, -, 1140a, 1145a, 1151a, 1154a, 1200p], + [ 1144a, 1150a, 1204p, -, 1210p, 1215p, 1221p, 1224p, 1230p], + [ 1214p, 1220p, 1234p, -, 1240p, 1245p, 1251p, 1254p, 100p], + [ 1244p, 1250p, 104p, -, 110p, 115p, 121p, 124p, 130p], + [ 114p, 120p, 134p, -, 140p, 145p, 151p, 154p, 200p], + [ 144p, 150p, 204p, -, 210p, 215p, 221p, 224p, 230p], + [ 214p, 220p, 234p, -, 240p, 245p, 251p, 254p, 300p], + [ 244p, 250p, 304p, -, 310p, 315p, 321p, 324p, 330p], + [ 314p, 320p, 334p, -, 340p, 345p, 351p, 354p, 400p], + [ 344p, 350p, 404p, -, 410p, 415p, 421p, 424p, 430p], + [ 414p, 420p, 434p, -, 440p, 445p, 451p, 454p, 500p], + [ 444p, 450p, 504p, -, 510p, 515p, 521p, 524p, 530p], + [ 514p, 520p, 534p, -, 540p, 545p, 551p, 554p, 600p], + [ 553p, 559p, 608p, -, 614p, 617p, 622p, 625p, 631p], + [ 658p, 704p, 713p, -, 719p, 722p, 727p, 730p, 736p], + [ 758p, 804p, 813p, -, 819p, 822p, 827p, 830p, 836p], + [ 858p, 904p, 913p, -, 919p, 921p, 927p, 930p, 936p], + [ 958p, 1004p, 1013p, 1014p, 1018p, 1021p, 1026p, 1029p, 1035p], + [ 1058p, 1104p, 1113p, 1114p, 1118p, 1121p, 1126p, 1129p, 1135p], + [ 1158p, 1204x, 1213x, 1214x, 1218x, 1221x, 1226x, 1229x, 1235x] +] +stop_times_sunday: [ + [ 700a, 705a, 715a, -, 722a, 725a, 730a, 733a, 740a], + [ 800a, 805a, 815a, -, 822a, 825a, 830a, 833a, 840a], + [ 900a, 905a, 915a, -, 922a, 925a, 930a, 933a, 940a], + [ 1000a, 1005a, 1015a, -, 1022a, 1025a, 1030a, 1033a, 1040a], + [ 1100a, 1105a, 1115a, -, 1122a, 1125a, 1130a, 1133a, 1140a], + [ 1200p, 1205p, 1215p, -, 1222p, 1225p, 1230p, 1233p, 1240p], + [ 100p, 105p, 115p, -, 122p, 125p, 130p, 133p, 140p], + [ 200p, 205p, 215p, -, 222p, 225p, 230p, 233p, 240p], + [ 300p, 305p, 315p, -, 322p, 325p, 330p, 333p, 340p], + [ 400p, 405p, 415p, -, 422p, 425p, 430p, 433p, 440p], + [ 500p, 505p, 515p, -, 522p, 525p, 530p, 533p, 540p], + [ 600p, 605p, 615p, -, 622p, 625p, 630p, 633p, 640p], + [ 700p, 705p, 715p, -, 722p, 725p, 730p, 733p, 740p], + [ 800p, 805p, 815p, -, 822p, 825p, 830p, 833p, 840p], + [ 900p, 905p, 915p, -, 922p, 925p, 930p, 933p, 940p], + [ 1000p, 1005p, 1015p, 1016p, 1022p, 1025p, 1030p, 1033p, 1040p], + [ 1100p, 1105p, 1115p, 1116p, 1122p, 1125p, 1130p, 1133p, 1140p] +] --- /dev/null +++ b/origin-src/wlach-halifax-transit-feed-fef68c1/20-to-herring-cove.yml @@ -1,1 +1,113 @@ +short_name: 20 +long_name: To Herring Cove +time_points: [ 8414, 6568, 6105, 8151, 8643, 6797, 6851, 7121 ] +between_stops: + 8151-8643: [ 8138, 8148, 8137, 8134, 8146, 6413, 6414, 7275 ] +stop_times: [ + [ -, -, -, -, 510a, 527a, 532a, 539a], + [ -, -, -, -, 530a, 547a, 552a, 559a], + [ -, -, -, -, 600a, 617a, 622a, 629a], + [ 607a, -, 610a, 619a, 635a, 652a, 657a, 704a], + [ 637a, -, 640a, 649a, 705a, 722a, 727a, 734a], + [ 707a, -, 710a, 719a, 735a, 752a, 757a, 804a], + [ 737a, -, 740a, 749a, 805a, 822a, 827a, 834a], + [ 805a, -, 808a, 817a, 833a, 850a, 855a, 902a], + [ 837a, -, 840a, 849a, 905a, 922a, 927a, 934a], + [ 907a, -, 910a, 919a, 935a, 952a, 957a, 1004a], + [ 937a, -, 940a, 949a, 1005a, 1022a, 1027a, 1034a], + [ 1007a, -, 1010a, 1019a, 1035a, 1052a, 1057a, 1104a], + [ 1037a, -, 1040a, 1049a, 1105a, 1122a, 1127a, 1134a], + [ 1107a, -, 1110a, 1119a, 1135a, 1152a, 1157a, 1204p], + [ 1137a, -, 1140a, 1149a, 1205p, 1222p, 1227p, 1234p], + [ 1205p, -, 1208p, 1217p, 1233p, 1250p, 1255p, 102p], + [ 1237p, -, 1240p, 1249p, 105p, 122p, 127p, 134p], + [ 107p, -, 110p, 119p, 135p, 152p, 157p, 204p], + [ 137p, -, 140p, 149p, 205p, 222p, 227p, 234p], + [ 207p, -, 210p, 219p, 235p, 252p, 257p, 304p], + [ 237p, -, 240p, 249p, 305p, 322p, 327p, 334p], + [ 257p, -, 300p, 309p, 325p, 342p, 347p, 354p], + [ 317p, -, 320p, 329p, 345p, 402p, 407p, 414p], + [ 332p, 336p, 341p, 350p, 406p, 423p, 428p, 435p], + [ 357p, -, 400p, 409p, 425p, 442p, 447p, 454p], + [ 412p, 416p, 421p, 430p, 446p, 503p, 508p, 515p], + [ 437p, -, 440p, 449p, 505p, 522p, 527p, 534p], + [ 507p, -, 510p, 519p, 535p, 552p, 557p, 604p], + [ 537p, -, 540p, 549p, 605p, 622p, 627p, 634p], + [ 607p, -, 610p, 619p, 635p, 652p, 657p, 704p], + [ 642p, -, 645p, 654p, 710p, 727p, 732p, 739p], + [ 712p, -, 715p, 724p, 740p, 756p, 801p, 808p], + [ 742p, -, 745p, 754p, 810p, 826p, 831p, 838p], + [ 812p, -, 815p, 824p, 840p, 856p, 901p, 908p], + [ 842p, -, 845p, 854p, 910p, 926p, 931p, 938p], + [ 912p, -, 915p, 924p, 940p, 956p, 1001p, 1008p], + [ 942p, -, 945p, 954p, 1010p, 1026p, 1031p, 1038p], + [ 1012p, -, 1015p, 1024p, 1040p, 1056p, 1101p, 1108p], + [ 1042p, -, 1045p, 1054p, 1110p, 1126p, 1131p, 1138p], + [ 1112p, -, 1115p, 1124p, 1140p, 1156p, 1201x, 1208x]] +stop_times_saturday: [ + [ -, -, -, -, 530a, 545a, 550a, 600a], + [ -, -, -, -, 635a, 650a, 655a, 705a], + [ -, -, -, -, 705a, 720a, 725a, 735a], + [ -, -, -, -, 735a, 750a, 755a, 805a], + [ -, -, -, -, 805a, 820a, 825a, 835a], + [ -, -, -, -, 835a, 850a, 855a, 905a], + [ -, -, -, -, 905a, 920a, 925a, 935a], + [ -, -, -, -, 935a, 950a, 955a, 1005a], + [ -, -, -, -, 1005a, 1020a, 1025a, 1035a], + [ -, -, -, -, 1035a, 1050a, 1055a, 1105a], + [ -, -, -, -, 1105a, 1120a, 1125a, 1135a], + [ -, -, -, -, 1135a, 1150a, 1155a, 1205p], + [ -, -, -, -, 1205p, 1220p, 1225p, 1235p], + [ -, -, -, -, 1235p, 1250p, 1255p, 105p], + [ -, -, -, -, 105p, 120p, 125p, 135p], + [ -, -, -, -, 135p, 150p, 155p, 205p], + [ -, -, -, -, 205p, 220p, 225p, 235p], + [ -, -, -, -, 235p, 250p, 255p, 305p], + [ -, -, -, -, 305p, 320p, 325p, 335p], + [ -, -, -, -, 335p, 350p, 355p, 405p], + [ -, -, -, -, 405p, 420p, 425p, 435p], + [ -, -, -, -, 435p, 450p, 455p, 505p], + [ -, -, -, -, 505p, 520p, 525p, 535p], + [ -, -, -, -, 535p, 550p, 555p, 605p], + [ -, -, -, -, 605p, 620p, 625p, 635p], + [ -, -, -, -, 635p, 650p, 655p, 705p], + [ -, -, -, -, 710p, 725p, 730p, 740p], + [ -, -, -, -, 740p, 755p, 800p, 810p], + [ -, -, -, -, 810p, 825p, 830p, 840p], + [ -, -, -, -, 840p, 855p, 900p, 910p], + [ -, -, -, -, 910p, 925p, 930p, 940p], + [ -, -, -, -, 940p, 955p, 1000p, 1010p], + [ -, -, -, -, 1040p, 1055p, 1100p, 1110p], + [ -, -, -, -, 1140p, 1155p, 1200x, 1210x] +] +stop_times_sunday: [ + [ -, -, -, -, 635a, 652a, 657a, 705a], + [ -, -, -, -, 735a, 752a, 757a, 805a], + [ -, -, -, -, 835a, 852a, 857a, 905a], + [ -, -, -, -, 905a, 922a, 927a, 935a], + [ -, -, -, -, 935a, 952a, 957a, 1005a], + [ -, -, -, -, 1005a, 1022a, 1027a, 1035a], + [ -, -, -, -, 1035a, 1052a, 1057a, 1105a], + [ -, -, -, -, 1105a, 1122a, 1127a, 1135a], + [ -, -, -, -, 1135a, 1152a, 1157a, 1205p], + [ -, -, -, -, 1205p, 1222p, 1227p, 1235p], + [ -, -, -, -, 1235p, 1252p, 1257p, 105p], + [ -, -, -, -, 105p, 122p, 127p, 135p], + [ -, -, -, -, 135p, 152p, 157p, 205p], + [ -, -, -, -, 205p, 222p, 227p, 235p], + [ -, -, -, -, 235p, 252p, 257p, 305p], + [ -, -, -, -, 305p, 322p, 327p, 335p], + [ -, -, -, -, 335p, 352p, 357p, 405p], + [ -, -, -, -, 405p, 422p, 427p, 435p], + [ -, -, -, -, 435p, 452p, 457p, 505p], + [ -, -, -, -, 505p, 522p, 527p, 535p], + [ -, -, -, -, 535p, 552p, 557p, 605p], + [ -, -, -, -, 605p, 622p, 627p, 635p], + [ -, -, -, -, 635p, 652p, 657p, 705p], + [ -, -, -, -, 735p, 752p, 757p, 805p], + [ -, -, -, -, 835p, 852p, 857p, 905p], + [ -, -, -, -, 935p, 952p, 957p, 1005p], + [ -, -, -, -, 1035p, 1052p, 1057p, 1105p], + [ -, -, -, -, 1135p, 1152p, 1157p, 1205x] +] --- /dev/null +++ b/origin-src/wlach-halifax-transit-feed-fef68c1/20-to-mumford-downtown.yml @@ -1,1 +1,120 @@ +short_name: 20 +long_name: To Mumford / Downtown +time_points: [ 7121, 6859, 6800, 7284, 8150, 6087, 8414, 6572 ] +between_stops: + 7284-8150: [ 7274, 6409, 6403, 6407, 8135, 8133, 8136, 8147, 8149 ] +stop_times: [ + [ 540a, 547a, 552a, 610a, 618a, 630a, 632a, -], + [ 600a, 607a, 612a, 630a, 638a, 650a, 652a, -], + [ -, 622a, 627a, 645a, 653a, 705a, 707a, -], + [ 630a, 637a, 642a, 700a, 708a, 720a, 722a, 725a], + [ -, 652a, 657a, 715a, 723a, 735a, 737a, -], + [ 705a, 712a, 717a, 735a, 743a, 755a, 757a, -], + [ -, 727a, 732a, 750a, 758a, 810a, 812a, -], + [ 735a, 742a, 747a, 805a, 813a, 825a, 827a, -], + [ -, 757a, 802a, 820a, 828a, 840a, 842a, -], + [ 805a, 812a, 817a, 835a, 843a, 855a, 857a, -], + [ -, 825a, 830a, 848a, 856a, 908a, 910a, -], + [ 835a, 842a, 847a, 905a, 913a, 925a, 927a, -], + [ -, 857a, 902a, 920a, 928a, 940a, 942a, -], + [ 905a, 912a, 917a, 935a, 943a, 955a, 957a, -], + [ 935a, 942a, 947a, 1005a, 1013a, 1025a, 1027a, -], + [ 1005a, 1012a, 1017a, 1035a, 1043a, 1055a, 1057a, -], + [ 1035a, 1042a, 1047a, 1105a, 1113a, 1125a, 1127a, -], + [ 1105a, 1112a, 1117a, 1135a, 1143a, 1155a, 1157a, -], + [ 1135a, 1142a, 1147a, 1205p, 1213p, 1225p, 1227p, -], + [ 1205p, 1212p, 1217p, 1235p, 1243p, 1255p, 1257p, -], + [ 1235p, 1242p, 1247p, 105p, 113p, 125p, 127p, -], + [ 104p, 111p, 116p, 134p, 142p, 154p, 156p, -], + [ 135p, 142p, 147p, 205p, 213p, 225p, 227p, -], + [ 205p, 212p, 217p, 235p, 243p, 255p, 257p, -], + [ 235p, 242p, 247p, 305p, 313p, 325p, 327p, -], + [ 305p, 312p, 317p, 335p, 343p, 355p, 357p, -], + [ 335p, 342p, 347p, 405p, 413p, 425p, 427p, -], + [ 355p, 402p, 407p, 425p, 433p, 445p, 447p, -], + [ 415p, 422p, 427p, 445p, 453p, 505p, 507p, -], + [ 435p, 442p, 447p, 505p, 513p, 525p, 527p, -], + [ 455p, 502p, 507p, 525p, 533p, 545p, 547p, -], + [ 515p, 522p, 527p, 545p, 553p, 605p, 607p, -], + [ 535p, 542p, 547p, 605p, 613p, 625p, 627p, -], + [ 605p, 612p, 617p, 635p, 643p, 655p, 657p, -], + [ 635p, 642p, 647p, 705p, 713p, 725p, 727p, -], + [ 705p, 711p, 716p, 733p, 741p, 753p, 755p, -], + [ 740p, 746p, 751p, 808p, 816p, 828p, 830p, -], + [ 809p, 815p, 820p, 837p, 845p, 857p, 859p, -], + [ 839p, 845p, 850p, 907p, 915p, 927p, 929p, -], + [ 909p, 915p, 920p, 937p, 945p, 957p, 959p, -], + [ 939p, 945p, 950p, 1007p, 1015p, 1027p, 1029p, -], + [ 1009p, 1015p, 1020p, 1037p, 1045p, 1057p, 1059p, -], + [ 1039p, 1045p, 1050p, 1107p, 1115p, 1127p, 1129p, -], + [ 1109p, 1115p, 1120p, 1137p, 1145p, 1157p, 1159p, -], + [ 1139p, 1145p, 1150p, 1207x, 1215x, 1227x, 1229x, -], + [ 1209x, 1215x, 1220x, 1237x, -, -, -, -] +] +stop_times_saturday: [ + [ 600a, 605a, 610a, 627a, -, -, -, -], + [ 705a, 710a, 715a, 732a, -, -, -, -], + [ 735a, 740a, 745a, 802a, -, -, -, -], + [ 805a, 810a, 815a, 832a, -, -, -, -], + [ 835a, 840a, 845a, 902a, -, -, -, -], + [ 905a, 910a, 915a, 932a, -, -, -, -], + [ 935a, 940a, 945a, 1002a, -, -, -, -], + [ 1005a, 1010a, 1015a, 1032a, -, -, -, -], + [ 1035a, 1040a, 1045a, 1102a, -, -, -, -], + [ 1105a, 1110a, 1115a, 1132a, -, -, -, -], + [ 1135a, 1140a, 1145a, 1202p, -, -, -, -], + [ 1205p, 1210p, 1215p, 1232p, -, -, -, -], + [ 1235p, 1240p, 1245p, 102p, -, -, -, -], + [ 105p, 110p, 115p, 132p, -, -, -, -], + [ 135p, 140p, 145p, 202p, -, -, -, -], + [ 205p, 210p, 215p, 232p, -, -, -, -], + [ 235p, 240p, 245p, 302p, -, -, -, -], + [ 305p, 310p, 315p, 332p, -, -, -, -], + [ 335p, 340p, 345p, 402p, -, -, -, -], + [ 405p, 410p, 415p, 432p, -, -, -, -], + [ 435p, 440p, 445p, 502p, -, -, -, -], + [ 505p, 510p, 515p, 532p, -, -, -, -], + [ 535p, 540p, 545p, 602p, -, -, -, -], + [ 605p, 610p, 615p, 632p, -, -, -, -], + [ 635p, 640p, 645p, 702p, -, -, -, -], + [ 705p, 710p, 715p, 732p, -, -, -, -], + [ 740p, 745p, 750p, 807p, -, -, -, -], + [ 810p, 815p, 820p, 837p, -, -, -, -], + [ 840p, 845p, 850p, 907p, -, -, -, -], + [ 910p, 915p, 920p, 937p, -, -, -, -], + [ 940p, 945p, 950p, 1007p, -, -, -, -], + [ 1010p, 1015p, 1020p, 1037p, -, -, -, -], + [ 1110p, 1115p, 1120p, 1137p, -, -, -, -], + [ 1210x, 1215x, 1220x, 1237x, -, -, -, -] +] +stop_times_sunday: [ + [ 705a, 710a, 715a, 730a, -, -, -, -], + [ 805a, 810a, 815a, 830a, -, -, -, -], + [ 905a, 910a, 915a, 930a, -, -, -, -], + [ 935a, 940a, 945a, 1000a, -, -, -, -], + [ 1005a, 1010a, 1015a, 1030a, -, -, -, -], + [ 1035a, 1040a, 1045a, 1100a, -, -, -, -], + [ 1105a, 1110a, 1115a, 1130a, -, -, -, -], + [ 1135a, 1140a, 1145a, 1200p, -, -, -, -], + [ 1205p, 1210p, 1215p, 1230p, -, -, -, -], + [ 1235p, 1240p, 1245p, 100p, -, -, -, -], + [ 105p, 110p, 115p, 130p, -, -, -, -], + [ 135p, 140p, 145p, 200p, -, -, -, -], + [ 205p, 210p, 215p, 230p, -, -, -, -], + [ 235p, 240p, 245p, 300p, -, -, -, -], + [ 305p, 310p, 315p, 330p, -, -, -, -], + [ 335p, 340p, 345p, 400p, -, -, -, -], + [ 405p, 410p, 415p, 430p, -, -, -, -], + [ 435p, 440p, 445p, 500p, -, -, -, -], + [ 505p, 510p, 515p, 530p, -, -, -, -], + [ 535p, 540p, 545p, 600p, -, -, -, -], + [ 605p, 610p, 615p, 630p, -, -, -, -], + [ 635p, 640p, 645p, 700p, -, -, -, -], + [ 705p, 710p, 715p, 730p, -, -, -, -], + [ 805p, 810p, 815p, 830p, -, -, -, -], + [ 905p, 910p, 915p, 930p, -, -, -, -], + [ 1005p, 1010p, 1015p, 1030p, -, -, -, -], + [ 1105p, 1110p, 1115p, 1130p, -, -, -, -], + [ 1205x, 1210x, 1215x, 1230x, -, -, -, -] +] --- /dev/null +++ b/origin-src/wlach-halifax-transit-feed-fef68c1/21-to-lacewood-halifax.yml @@ -1,1 +1,106 @@ +short_name: 21 +long_name: To Lacewood / Halifax +time_points: [ 6317, 6189, 7087, 8364 ] +# FIXME: should start at 6722, 5 minutes before +between_stops: + 7087-8364: [ 6983, 8631, 6783, 6776, 6777, 6781, 6774, 6780, 6786, 6790, 6771, 6084, 6103, 6122, 8331, 8330, 8334, 8335 ] +stop_times: [ + [ 550a, 602a, 615a, 651a], + [ 625a, 637a, 650a, 726a], + [ 655a, 707a, 720a, 756a], + [ 730a, 742a, 755a, 831a], + [ 755a, 807a, 820a, 856a], + [ 825a, 837a, 850a, -], + [ 855a, 907a, 920a, -], + [ 925a, 937a, 950a, -], + [ 955a, 1007a, 1020a, -], + [ 1025a, 1037a, 1050a, -], + [ 1055a, 1107a, 1120a, -], + [ 1125a, 1137a, 1150a, -], + [ 1155a, 1207p, 1220p, -], + [ 1229p, 1241p, 1254p, -], + [ 103p, 111p, 123p, -], + [ 133p, 141p, 153p, -], + [ 203p, 211p, 223p, -], + [ 233p, 241p, 253p, -], + [ 303p, 311p, 323p, -], + [ 333p, 341p, 353p, -], + [ 403p, 411p, 423p, -], + [ 443p, 451p, 503p, -], + [ 513p, 521p, 533p, -], + [ 543p, 551p, 603p, -], + [ 613p, 621p, 633p, -], + [ 643p, 651p, 703p, -], + [ 713p, 721p, 733p, -], + [ 737p, 745p, 757p, -], + [ 803p, 811p, 823p, -], + [ 833p, 841p, 853p, -], + [ 903p, 911p, 923p, -], + [ 933p, 941p, 953p, -], + [ 1003p, 1011p, 1023p, -], + [ 1033p, 1041p, 1053p, -], + [ 1103p, 1111p, 1123p, -], + [ 1133p, 1141p, 1153p, -], + [ 1203a, 1211x, 1223x, -], + [ 1233a, 1241x, 1253x, -] +] +stop_times_saturday: [ + [ 625a, 633a, 645a, -], + [ 655a, 703a, 715a, -], + [ 725a, 733a, 745a, -], + [ 755a, 803a, 815a, -], + [ 825a, 833a, 845a, -], + [ 855a, 903a, 915a, -], + [ 925a, 933a, 945a, -], + [ 955a, 1003a, 1015a, -], + [ 1025a, 1033a, 1045a, -], + [ 1055a, 1103a, 1115a, -], + [ 1125a, 1133a, 1145a, -], + [ 1155a, 1203p, 1215p, -], + [ 1225p, 1233p, 1245p, -], + [ 1255p, 103p, 115p, -], + [ 125p, 133p, 145p, -], + [ 155p, 203p, 215p, -], + [ 225p, 233p, 245p, -], + [ 255p, 303p, 315p, -], + [ 325p, 333p, 345p, -], + [ 355p, 403p, 415p, -], + [ 425p, 433p, 445p, -], + [ 455p, 503p, 515p, -], + [ 525p, 533p, 545p, -], + [ 555p, 603p, 615p, -], + [ 625p, 633p, 645p, -], + [ 655p, 703p, 715p, -], + [ 725p, 733p, 745p, -], + [ 800p, 808p, 820p, -], + [ 830p, 838p, 850p, -], + [ 900p, 908p, 920p, -], + [ 930p, 938p, 950p, -], + [ 1000p, 1008p, 1020p, -], + [ 1030p, 1038p, 1050p, -], + [ 1100p, 1108p, 1120p, -], + [ 1130p, 1138p, 1150p, -], + [ 1200x, 1208x, 1220x, -], + [ 1230x, 1238x, 1250x, -] +] +stop_times_sunday: [ + [ 645a, 653a, 705a, -], + [ 745a, 753a, 805a, -], + [ 845a, 853a, 905a, -], + [ 945a, 953a, 1005a, -], + [ 1045a, 1053a, 1105a, -], + [ 1145a, 1153a, 1205p, -], + [ 1245p, 1253p, 105p, -], + [ 145p, 153p, 205p, -], + [ 245p, 253p, 305p, -], + [ 345p, 353p, 405p, -], + [ 445p, 453p, 505p, -], + [ 545p, 553p, 605p, -], + [ 645p, 653p, 705p, -], + [ 745p, 753p, 805p, -], + [ 845p, 853p, 905p, -], + [ 945p, 953p, 1005p, -], + [ 1045p, 1053p, 1105p, -], + [ 1145p, 1153p, 1205x, -] +] --- /dev/null +++ b/origin-src/wlach-halifax-transit-feed-fef68c1/21-to-timberlea.yml @@ -1,1 +1,106 @@ +short_name: 21 +long_name: To Timberlea +time_points: [ 8363, 7086, 6160, 6315, 6722 ] +between_stops: + 8363-7086: [ 8337, 8333, 8336, 8327, 8338, 6121, 6086, 6106, 6455, 6773, 6778, 6779, 6775, 6787, 6769, 6785, 6768, 6782, 8632, 8192 ] +stop_times: [ + [ -, 525a, 531a, 540a, 545a], + [ -, 600a, 606a, 615a, 620a], + [ -, 627a, 633a, 642a, 650a], + [ -, 705a, 711a, 720a, 725a], + [ -, 727a, 733a, 742a, 750a], + [ -, 800a, 806a, 815a, 820a], + [ -, 830a, 836a, 845a, 850a], + [ -, 900a, 906a, 915a, 920a], + [ -, 930a, 936a, 945a, 950a], + [ -, 1000a, 1006a, 1015a, 1020a], + [ -, 1030a, 1036a, 1045a, 1050a], + [ -, 1100a, 1106a, 1115a, 1120a], + [ -, 1130a, 1136a, 1145a, 1150a], + [ -, 1200p, 1206p, 1215p, 1224p], + [ -, 1230p, 1236p, 1245p, 1254p], + [ -, 100p, 106p, 115p, 124p], + [ -, 130p, 136p, 145p, 154p], + [ -, 200p, 206p, 215p, 224p], + [ -, 230p, 236p, 245p, 254p], + [ -, 300p, 308p, 321p, 328p], + [ -, 330p, 338p, 351p, 358p], + [ 331p, 410p, 418p, 431p, 438p], + [ 401p, 440p, 448p, 501p, 508p], + [ 431p, 510p, 518p, 531p, 538p], + [ 501p, 540p, 548p, 601p, 608p], + [ 531p, 610p, 618p, 631p, 638p], + [ 601p, 640p, 648p, 701p, 708p], + [ -, 705p, 713p, 726p, 732p], + [ -, 735p, 743p, 753p, 758p], + [ -, 805p, 813p, 823p, 828p], + [ -, 835p, 843p, 853p, 858p], + [ -, 905p, 913p, 923p, 928p], + [ -, 935p, 943p, 953p, 958p], + [ -, 1005p, 1013p, 1023p, 1028p], + [ -, 1035p, 1043p, 1053p, 1058p], + [ -, 1105p, 1113p, 1123p, 1128p], + [ -, 1135p, 1143p, 1153p, 1158p], + [ -, 1205x, 1213x, 1223x, 1228x] +] +stop_times_saturday: [ + [ -, 555a, 602a, 615a, 620a], + [ -, 625a, 632a, 645a, 650a], + [ -, 655a, 702a, 715a, 720a], + [ -, 725a, 732a, 745a, 750a], + [ -, 755a, 802a, 815a, 820a], + [ -, 825a, 832a, 845a, 850a], + [ -, 855a, 902a, 915a, 920a], + [ -, 925a, 932a, 945a, 950a], + [ -, 955a, 1002a, 1015a, 1020a], + [ -, 1025a, 1032a, 1045a, 1050a], + [ -, 1055a, 1102a, 1115a, 1120a], + [ -, 1055a, 1102a, 1115a, 1120a], + [ -, 1125a, 1132a, 1145a, 1150a], + [ -, 1155a, 1202p, 1215p, 1220p], + [ -, 1225p, 1232p, 1245p, 1250p], + [ -, 1255p, 102p, 115p, 120p], + [ -, 125p, 132p, 145p, 150p], + [ -, 155p, 202p, 215p, 220p], + [ -, 225p, 232p, 245p, 250p], + [ -, 255p, 302p, 315p, 320p], + [ -, 325p, 332p, 345p, 350p], + [ -, 355p, 402p, 415p, 420p], + [ -, 425p, 432p, 445p, 450p], + [ -, 455p, 502p, 515p, 520p], + [ -, 525p, 532p, 545p, 550p], + [ -, 555p, 602p, 615p, 620p], + [ -, 625p, 632p, 645p, 650p], + [ -, 655p, 702p, 715p, 720p], + [ -, 730p, 737p, 750p, 755p], + [ -, 800p, 807p, 820p, 825p], + [ -, 830p, 837p, 850p, 855p], + [ -, 900p, 907p, 920p, 925p], + [ -, 930p, 937p, 950p, 955p], + [ -, 1000p, 1007p, 1020p, 1025p], + [ -, 1030p, 1037p, 1050p, 1055p], + [ -, 1100p, 1107p, 1120p, 1125p], + [ -, 1130p, 1137p, 1150p, 1155p], + [ -, 1200x, 1207x, 1220x, 1225x] +] +stop_times_sunday: [ + [ -, 615a, 622a, 635a, 640a], + [ -, 715a, 722a, 735a, 740a], + [ -, 815a, 822a, 835a, 840a], + [ -, 915a, 922a, 935a, 940a], + [ -, 1015a, 1022a, 1035a, 1040a], + [ -, 1115a, 1122a, 1135a, 1140a], + [ -, 1115a, 1122a, 1135a, 1140a], + [ -, 1215p, 1222p, 1235p, 1240p], + [ -, 115p, 122p, 135p, 140p], + [ -, 215p, 222p, 235p, 240p], + [ -, 315p, 322p, 335p, 340p], + [ -, 415p, 422p, 435p, 440p], + [ -, 515p, 522p, 535p, 540p], + [ -, 615p, 622p, 635p, 640p], + [ -, 715p, 722p, 735p, 740p], + [ -, 815p, 822p, 835p, 840p], + [ -, 915p, 922p, 935p, 940p], + [ -, 1115p, 1122p, 1135p, 1140p] +] --- /dev/null +++ b/origin-src/wlach-halifax-transit-feed-fef68c1/23-to-mumford-halifax.yml @@ -1,1 +1,21 @@ +short_name: 23 +long_name: To Mumford / Halifax +time_points: [ 6722, 6317, 6189, 7284, 8414 ] +between_stops: + 6189-7284: [ 7276 ] + 7284-8414: [ 7274, 6409, 6403, 6407, 6406, 6545, 8205, 8337, 8333, 8336, 8327, 8338, 6087 ] +stop_times: [ + [ 605a, 610a, 617a, 632a, 656a], + [ 635a, 640a, 647a, 703a, 729a], + [ 705a, 710a, 717a, 737a, 803a], + [ 735a, 740a, 747a, 807a, 833a], + [ 805a, 810a, 817a, 837a, 903a], + [ 835a, 840a, 847a, 907a, 933a], + [ 447p, 452p, 459p, 514p, -], + [ 517p, 522p, 529p, 544p, -], + [ 547p, 552p, 559p, 614p, -], + [ 617p, 622p, 629p, 644p, -], + [ 645p, 650p, 657p, 712p, -], + [ 714p, 719p, 726p, 741p, -] +] --- /dev/null +++ b/origin-src/wlach-halifax-transit-feed-fef68c1/23-to-timberlea.yml @@ -1,1 +1,22 @@ +short_name: 23 +long_name: To Timberlea +time_points: [ 8414, 8643, 6160, 6315, 6722 ] +between_stops: + 8414-8643: [ 6125, 6105, 6108, 6103, 6122, 8331, 8330, 8334, 8335, 8184, 8206, 6544, 6405, 6404, 6413, 6414, 7275 ] + 8643-6160: [ 7273 ] +stop_times: [ + [ -, 533a, 548a, 554a, 600a], + [ -, 603a, 618a, 624a, 630a], + [ -, 633a, 648a, 654a, 700a], + [ -, 703a, 718a, 724a, 730a], + [ -, 733a, 748a, 754a, 800a], + [ -, 803a, 818a, 824a, 830a], + [ 346p, 417p, 432p, 438p, 444p], + [ 416p, 447p, 502p, 508p, 514p], + [ 446p, 517p, 532p, 538p, 544p], + [ 516p, 547p, 602p, 608p, 614p], + [ 546p, 614p, 629p, 635p, 641p], + [ 616p, 644p, 659p, 705p, 711p] +] + --- /dev/null +++ b/origin-src/wlach-halifax-transit-feed-fef68c1/3-to-manors.yml @@ -1,1 +1,13 @@ +short_name: 3 +long_name: To Manors +time_points: [ 7284, 6563, 8164, 6505, 6105, 8334, 8430 ] +between_stops: + 8164-6505: [ 7367, 7382, 7364, 6770, 6783, 6776, 6777, 6781, 6774, 6780, 6786 ] + 6105-8337: [ 6108, 6103, 6102, 6122, 8331, 8330, 8334 ] +stop_times: [ + [ 957a, 1012a, 1026a, 1037a, 1040a, 1047a, 1055a], + [ 1207p, 1222p, 1236p, 1247p, 1250p, 1257p, 105p], + [ 217p, 232p, 246p, 257p, 300p, 307p, 315p], + [ 247p, 302p, 316p, 327p, 330p, 337p, 345p] +] --- /dev/null +++ b/origin-src/wlach-halifax-transit-feed-fef68c1/3-to-shopping-malls.yml @@ -1,1 +1,19 @@ +short_name: 3 +long_name: To Shopping Malls +time_points: [ 8430, 6791, 8337, 6087, 6505, 8165, 6564, 7284 ] +between_stops: + 6791-8337: [ 8328 ] + 8337-6087: [ 8333, 8336, 8327, 8338 ] + 6505-8165: [ 6775, 6787, 6769, 6785, 6768, 6782, 7377, 7381, 7373, 7376, 7380 ] + 6564-7284: [ 6197, 6193 ] + 6087-6505: [ 6455, 6773, 6778 ] + 8165-6564: [ 6199, 6198, 6564, 6563, 6565, 6984 ] +stop_times: [ + [ 855a, 858a, 904a, 913a, 916a, 927a, 941a, 950a], + [ 1105a, 1108a, 1114a, 1123a, 1126a, 1137a, 1151a, 1200p], + [ 115p, 118p, 124p, 133p, 136p, 147p, 201p, 210p], + [ 145p, 148p, 154p, 203p, 206p, 217p, 231p, 240p], + [ 317p, 320p, -, -, -, -, -, -], + [ 347p, 350p, -, -, -, -, -, -] +] --- /dev/null +++ b/origin-src/wlach-halifax-transit-feed-fef68c1/4-to-downtown-via-north.yml @@ -1,1 +1,42 @@ +short_name: 4 +long_name: To Downtown via North +time_points: [ 6601, 7087, 6611, 6564, 7284, 7351, 6105, 8435 ] +between_stops: + 7284-7351: [ 7274, 6409, 6403, 6407, 6406, 7355, 7345, 7357, 7352, 7342, 7354, 7347 ] + 7351-6105: [ 7343, 6088, 6104, 6125 ] + 6105-8435: [ 6108, 6733 ] +stop_times: [ + [ 538a, 549a, 558a, 602a, 611a, 620a, 625a, 628a], + [ 624a, 635a, 645a, 649a, 657a, 707a, 712a, 715a], + [ 654a, 705a, 715a, 719a, 727a, 737a, 742a, 745a], + [ 724a, 735a, 745a, 749a, 757a, 807a, 812a, 815a], + [ 754a, 805a, 815a, 819a, 827a, 837a, 842a, 845a], + [ 824a, 835a, 845a, 849a, 857a, 907a, 913a, 916a], + [ 854a, 905a, 915a, 920a, 928a, 938a, 944a, 947a], + [ 924a, 935a, 945a, 950a, 958a, 1008a, 1014a, 1017a], + [ 954a, 1005a, 1015a, 1020a, 1028a, 1038a, 1044a, 1047a], + [ 1024a, 1035a, 1045a, 1050a, 1058a, 1108a, 1114a, 1117a], + [ 1054a, 1105a, 1115a, 1120a, 1128a, 1138a, 1144a, 1147a], + [ 1124a, 1135a, 1145a, 1150a, 1158a, 1208p, 1214p, 1217p], + [ 1154a, 1205p, 1215p, 1220p, 1228p, 1238p, 1244p, 1247p], + [ 1224p, 1235p, 1245p, 1250p, 1258p, 108p, 114p, 117p], + [ 1254p, 105p, 115p, 120p, 128p, 138p, 144p, 147p], + [ 124p, 135p, 145p, 150p, 158p, 208p, 214p, 217p], + [ 154p, 205p, 215p, 220p, 228p, 238p, 244p, 247p], + [ 224p, 235p, 245p, 250p, 258p, 308p, 314p, 317p], + [ 254p, 305p, 315p, 320p, 328p, 338p, 344p, 347p], + [ 324p, 335p, 345p, 350p, 358p, 408p, 414p, 417p], + [ 354p, 405p, 415p, 420p, 428p, 438p, 444p, 447p], + [ 424p, 435p, 445p, 450p, 458p, 508p, 514p, 517p], + [ 454p, 505p, 515p, 520p, 528p, 538p, 543p, 546p], + [ 524p, 535p, 545p, 550p, 558p, 606p, 610p, 614p], + [ 554p, 605p, 615p, 618p, 624p, 632p, 636p, 640p], + [ 626p, 637p, 647p, 650p, 656p, 704p, 708p, 712p], + [ 709p, 720p, 730p, 733p, 739p, 747p, 751p, 755p], + [ 809p, 820p, 830p, 833p, 839p, 847p, 851p, 855p], + [ 909p, 920p, 930p, 933p, 939p, 947p, 951p, 955p], + [ 1009p, 1020p, 1030p, 1033p, 1039p, 1047p, 1051p, 1055p], + [ 1109p, 1120p, 1130p, 1133p, 1139p, 1147p, 1151p, 1155p], + [ 1209x, 1220x, -, -, -, -, -, -] +] --- /dev/null +++ b/origin-src/wlach-halifax-transit-feed-fef68c1/4-to-farnham-gate-via-rosedale.yml @@ -1,1 +1,42 @@ +short_name: 4 +long_name: To Farnham Gate via Rosedale +time_points: [ 8435, 7348, 7284, 6563, 6611, 7087, 6601 ] +between_stops: + 8435-7348: [ 6089, 6116 ] + 7348-7284: [ 7346, 7341, 7353, 7358, 7344, 7356, 6405, 6404, 6413, 6414, 7275 ] + 6563-6611: [ 6565, 6984 ] +stop_times: [ + [ -, -, -, -, 520a, 529a, 537a], + [ -, -, -, -, 606a, 615a, 623a], + [ 610a, 617a, 626a, 632a, 636a, 645a, 653a], + [ 640a, 647a, 656a, 702a, 706a, 715a, 723a], + [ 710a, 717a, 726a, 732a, 736a, 745a, 753a], + [ 740a, 747a, 756a, 802a, 806a, 815a, 823a], + [ 807a, 814a, 823a, 829a, 834a, 845a, 853a], + [ 832a, 839a, 852a, 859a, 904a, 915a, 923a], + [ 902a, 909a, 922a, 929a, 934a, 945a, 953a], + [ 932a, 939a, 952a, 959a, 1004a, 1015a, 1023a], + [ 1002a, 1009a, 1022a, 1029a, 1034a, 1045a, 1053a], + [ 1032a, 1039a, 1052a, 1059a, 1104a, 1115a, 1123a], + [ 1102a, 1109a, 1122a, 1129a, 1134a, 1145a, 1153a], + [ 1132a, 1139a, 1152a, 1159a, 1204p, 1215p, 1223p], + [ 1202p, 1209p, 1222p, 1229p, 1234p, 1245p, 1253p], + [ 1232p, 1239p, 1252p, 1259p, 104p, 115p, 123p], + [ 102p, 109p, 122p, 129p, 134p, 145p, 153p], + [ 132p, 139p, 152p, 159p, 204p, 215p, 223p], + [ 201p, 208p, 222p, 229p, 234p, 245p, 253p], + [ 231p, 238p, 252p, 259p, 304p, 315p, 323p], + [ 300p, 307p, 322p, 329p, 334p, 344p, 353p], + [ 329p, 337p, 352p, 359p, 404p, 415p, 423p], + [ 359p, 407p, 422p, 429p, 434p, 445p, 453p], + [ 429p, 437p, 452p, 459p, 504p, 515p, 523p], + [ 459p, 507p, 522p, 529p, 534p, 545p, 553p], + [ 533p, 540p, 554p, 600p, 605p, 617p, 625p], + [ 624p, 631p, 640p, 646p, 650p, 700p, 708p], + [ 724p, 731p, 740p, 746p, 750p, 800p, 808p], + [ 824p, 831p, 840p, 846p, 850p, 900p, 908p], + [ 924p, 931p, 940p, 946p, 950p, 1000p, 1008p], + [ 1024p, 1031p, 1040p, 1046p, 1050p, 1100p, 1108p], + [ 1124p, 1131p, 1140p, 1146p, 1150p, 1200x, 1208x] +] --- /dev/null +++ b/origin-src/wlach-halifax-transit-feed-fef68c1/41-to-bridge-terminal.yml @@ -1,1 +1,46 @@ +short_name: 41 +long_name: To Bridge Terminal +time_points: [ 7144, 6096, 6087, 8642 ] +between_stops: + 7144-6096: [ 8296, 8303, 8305, 8295, 8299, 8293 ] + 6096-6087: [ 6113, 6124, 6121, 6106 ] + 6087-8642: [ 6455, 6773, 6778, 6779, 6775, 6787, 7351 ] +stop_times: [ + [ 715a, 721a, 728a, 738a], + [ 807a, 813a, 820a, 830a], + [ 827a, 833a, 840a, 850a], + [ 845a, 851a, 858a, 908a], + [ 907a, 913a, 920a, 930a], + [ 924a, 930a, 937a, 947a], + [ 944a, 950a, 957a, 1007a], + [ 1004a, 1010a, 1017a, 1027a], + [ 1024a, 1030a, 1037a, 1047a], + [ 1044a, 1050a, 1057a, 1107a], + [ 1104a, 1110a, 1117a, 1127a], + [ 1124a, 1130a, 1137a, 1147a], + [ 1144a, 1150a, 1157a, 1207p], + [ 1204p, 1210p, 1217p, 1227p], + [ 1224p, 1230p, 1237p, 1247p], + [ 1244p, 1250p, 1257p, 107p], + [ 104p, 110p, 117p, 127p], + [ 124p, 130p, 137p, 147p], + [ 144p, 150p, 157p, 207p], + [ 204p, 210p, 217p, 227p], + [ 224p, 230p, 237p, 247p], + [ 244p, 250p, 257p, 307p], + [ 304p, 310p, 317p, 327p], + [ 324p, 330p, 337p, 347p], + [ 344p, 350p, 357p, 407p], + [ 407p, 413p, 420p, 430p], + [ 427p, 433p, 440p, 450p], + [ 447p, 453p, 500p, 510p], + [ 507p, 513p, 520p, 530p], + [ 527p, 533p, 540p, 550p], + [ 547p, 553p, 600p, 610p], + [ 629p, 635p, 642p, 652p], + [ 649p, 655p, 702p, 712p], + [ 709p, 715p, 722p, 732p] +] +# No service Saturdays, Sundays or Holidays + --- /dev/null +++ b/origin-src/wlach-halifax-transit-feed-fef68c1/41-to-dalhousie.yml @@ -1,1 +1,44 @@ +short_name: 41 +long_name: To Dalhousie +time_points: [ 8642, 6105, 6114, 7144 ] +between_stops: + 8642-6105: [ 8638, 6088, 6104, 6125 ] + 6105-6114: [ 6108, 6103, 6102 ] + 6114-7144: [ 6097, 8294, 8292, 8300, 8301, 8298, 8297, 8317 ] +stop_times: [ + [ 650a, 700a, 707a, 713a], + [ 745a, 755a, 802a, 807a], + [ 815a, 825a, 832a, 838a], + [ 835a, 845a, 852a, 858a], + [ 855a, 905a, 912a, 918a], + [ 915a, 925a, 932a, 938a], + [ 935a, 945a, 952a, 958a], + [ 952a, 1002a, 1009a, 1015a], + [ 1012a, 1022a, 1029a, 1035a], + [ 1032a, 1042a, 1049a, 1055a], + [ 1052a, 1102a, 1109a, 1115a], + [ 1112a, 1122a, 1129a, 1135a], + [ 1132a, 1142a, 1149a, 1155a], + [ 1152a, 1202p, 1209p, 1215p], + [ 1212p, 1222p, 1229p, 1235p], + [ 1232p, 1242p, 1249p, 1255p], + [ 1252p, 102p, 109p, 115p], + [ 112p, 122p, 129p, 135p], + [ 132p, 142p, 149p, 155p], + [ 152p, 202p, 209p, 215p], + [ 212p, 222p, 229p, 235p], + [ 232p, 242p, 249p, 255p], + [ 252p, 302p, 309p, 315p], + [ 312p, 322p, 329p, 335p], + [ 332p, 342p, 349p, 355p], + [ 352p, 402p, 409p, 415p], + [ 412p, 422p, 429p, 435p], + [ 435p, 445p, 452p, 458p], + [ 455p, 505p, 512p, 518p], + [ 515p, 525p, 532p, 538p], + [ 535p, 545p, 552p, 558p], + [ 555p, 605p, 612p, 618p] +] +# No service Saturdays, Sundays or Holidays + --- /dev/null +++ b/origin-src/wlach-halifax-transit-feed-fef68c1/42-to-dalhousie.yml @@ -1,1 +1,45 @@ +short_name: 42 +long_name: To Dalhousie +time_points: [ 7086, 7166, 8193, 8214, 7144 ] +between_stops: + 8193-8214: [ 8202, 8182, 8213, 8178, 8205, 8190 ] + 8214-7144: [ 8183, 8196, 8195, 8194, 8186, 8188, 8317 ] +stop_times: [ + [ 655a, 701a, 708a, 716a, 722a], + [ 755a, 801a, 808a, 816a, 822a], + [ 815a, 821a, 828a, 836a, 842a], + [ 835a, 841a, 848a, 856a, 902a], + [ 855a, 901a, 906a, 913a, 919a], + [ 915a, 921a, 926a, 933a, 939a], + [ 935a, 941a, 946a, 953a, 959a], + [ 955a, 1001a, 1006a, 1013a, 1019a], + [ 1015a, 1021a, 1026a, 1033a, 1039a], + [ 1035a, 1041a, 1046a, 1053a, 1059a], + [ 1055a, 1101a, 1106a, 1113a, 1119a], + [ 1115a, 1121a, 1126a, 1133a, 1139a], + [ 1135a, 1141a, 1146a, 1153a, 1159a], + [ 1155a, 1201p, 1206p, 1213p, 1219p], + [ 1215p, 1221p, 1226p, 1233p, 1239p], + [ 1235p, 1241p, 1246p, 1253p, 1259p], + [ 1255p, 101p, 106p, 113p, 119p], + [ 115p, 121p, 126p, 133p, 139p], + [ 135p, 141p, 146p, 153p, 159p], + [ 155p, 201p, 206p, 213p, 219p], + [ 215p, 221p, 226p, 233p, 239p], + [ 235p, 241p, 246p, 253p, 259p], + [ 255p, 301p, 306p, 313p, 319p], + [ 315p, 321p, 326p, 333p, 339p], + [ 335p, 341p, 348p, 356p, 402p], + [ 355p, 401p, 408p, 416p, 422p], + [ 415p, 421p, 428p, 436p, 442p], + [ 435p, 441p, 448p, 456p, 502p], + [ 455p, 501p, 508p, 516p, 522p], + [ 515p, 521p, 528p, 536p, 542p], + [ 537p, 543p, 550p, 558p, 604p], + [ 557p, 603p, 610p, 618p, 624p], + [ 617p, 623p, 630p, 638p, 644p], + [ 637p, 643p, 650p, 658p, 704p] +] +# No service Saturdays, Sundays or Holidays + --- /dev/null +++ b/origin-src/wlach-halifax-transit-feed-fef68c1/42-to-lacewood.yml @@ -1,1 +1,42 @@ +short_name: 42 +long_name: To Lacewood +time_points: [ 7144, 8184, 8629, 6032, 7086 ] +between_stops: + 7144-8184: [ 8220, 8187, 8185, 8219, 8179 ] + 8184-8629: [ 8206, 8221, 8222, 8181, 8210, 8201, 8251 ] +stop_times: [ + [ 722a, 728a, 736a, 744a, 751a], + [ 843a, 849a, 857a, 904a, 911a], + [ 903a, 909a, 916a, 922a, 929a], + [ 923a, 929a, 936a, 942a, 949a], + [ 943a, 949a, 956a, 1002a, 1009a], + [ 1003a, 1009a, 1016a, 1022a, 1029a], + [ 1020a, 1026a, 1033a, 1039a, 1046a], + [ 1040a, 1046a, 1053a, 1059a, 1106a], + [ 1100a, 1106a, 1113a, 1119a, 1126a], + [ 1120a, 1126a, 1133a, 1139a, 1146a], + [ 1140a, 1146a, 1153a, 1159a, 1206p], + [ 1200p, 1206p, 1213p, 1219p, 1226p], + [ 1220p, 1226p, 1233p, 1239p, 1246p], + [ 1240p, 1246p, 1253p, 1259p, 106p], + [ 100p, 106p, 113p, 119p, 126p], + [ 120p, 126p, 133p, 139p, 146p], + [ 140p, 146p, 153p, 159p, 206p], + [ 200p, 206p, 213p, 219p, 226p], + [ 220p, 226p, 233p, 239p, 246p], + [ 240p, 246p, 253p, 259p, 306p], + [ 300p, 306p, 313p, 319p, 326p], + [ 320p, 326p, 333p, 341p, 348p], + [ 340p, 346p, 354p, 402p, 409p], + [ 400p, 406p, 414p, 422p, 429p], + [ 420p, 426p, 434p, 442p, 449p], + [ 440p, 446p, 454p, 502p, 509p], + [ 503p, 509p, 517p, 525p, 532p], + [ 523p, 529p, 537p, 545p, 552p], + [ 543p, 549p, 557p, 605p, 612p], + [ 603p, 609p, 617p, 625p, 632p], + [ 623p, 629p, 637p, 645p, 652p] +] +# No service Saturdays, Sundays or Holidays + --- /dev/null +++ b/origin-src/wlach-halifax-transit-feed-fef68c1/5-to-downtown.yml @@ -1,1 +1,15 @@ +short_name: 5 +long_name: To Downtown +time_points: [ 6578, 7284, 6397, 8435 ] +between_stops: + 7284-6397: [ 6409, 6403, 6407, 6406 ] + 6397-8435: [ 6545, 6108 ] +stop_times: [ + [ 720a, 730a, 735a, 745a], + [ 750a, 800a, 805a, 815a], + [ 822a, 832a, 837a, 847a], + [ 405p, 415p, -, -], + [ 435p, 445p, -, -], + [ 540p, 550p, -, -] +] --- /dev/null +++ b/origin-src/wlach-halifax-transit-feed-fef68c1/5-to-springvale.yml @@ -1,1 +1,15 @@ +short_name: 5 +long_name: To Springvale +time_points: [ 8435, 6396, 7284, 6578 ] +between_stops: + 8435-6396: [ 6544 ] + 6396-7284: [ 6404, 6413, 6414, 7275 ] +stop_times: [ + [ -, -, 710a, 720a], + [ -, -, 740a, 750a], + [ -, -, 817a, 822a], + [ -, -, 355p, 405p], + [ 410p, 420p, 425p, 435p], + [ 515p, 525p, 530p, 540p] +] --- /dev/null +++ b/origin-src/wlach-halifax-transit-feed-fef68c1/52-to-bridge-terminal-burnside.yml @@ -1,1 +1,133 @@ +short_name: 52 +long_name: To Bridge Terminal - Burnside +time_points: [ 6390, 7087, 7285, 7351, 8641, 6923, 6949, 7153, 6767 ] +between_stops: + 7285-7351: [ 7274, 6409, 6403, 6407, 6406, 7355, 7345, 7357, 7352, 7342, 7354, 7347 ] +stop_times: [ + [ -, 553a, 609a, 619a, 625a, 636a, 642a, 646a, -], + [ 608a, 623a, 639a, 649a, 655a, 706a, 712a, 716a, -], + [ -, 638a, 654a, 704a, 710a, 721a, 727a, 731a, -], + [ 638a, 653a, 709a, 719a, 725a, 736a, 742a, 746a, -], + [ -, 708a, 724a, 734a, 740a, 751a, 757a, 801a, -], + [ 708a, 723a, 739a, 749a, 755a, 806a, 812a, 816a, -], + [ -, 738a, 754a, 804a, 810a, 820a, 825a, 829a, -], + [ 738a, 753a, 809a, 819a, 825a, 836a, 842a, 843a, -], + [ 753a, 808a, 823a, 832a, 838a, 848a, 853a, 857a, -], + [ 810a, 825a, 840a, 849a, 855a, 905a, 910a, -, 921a], + [ 820a, 834a, -, -, -, -, -, -, -], + [ 840a, 855a, 910a, 919a, 925a, 935a, 940a, -, 951a], + [ 850a, 904a, -, -, -, -, -, -, -], + [ 910a, 925a, 940a, 949a, 955a, 1005a, 1010a, -, 1021a], + [ 920a, 934a, -, -, -, -, -, -, -], + [ 940a, 955a, 1010a, 1019a, 1025a, 1035a, 1040a, -, 1051a], + [ 945a, 959a, -, -, -, -, -, -, -], + [ 958a, 1012a, -, -, -, -, -, -, -], + [ 1010a, 1025a, 1040a, 1049a, 1055a, 1105a, 1110a, -, 1121a], + [ 1040a, 1055a, 1110a, 1119a, 1125a, 1135a, 1140a, -, 1151a], + [ 1110a, 1125a, 1140a, 1149a, 1155a, 1205p, 1210p, -, 1221p], + [ 1140a, 1155a, 1210p, 1219p, 1225p, 1235p, 1240p, -, 1251p], + [ 1210p, 1225p, 1240p, 1249p, 1255p, 105p, 110p, -, 121p], + [ 1240p, 1255p, 110p, 119p, 125p, 135p, 140p, -, 151p], + [ 110p, 125p, 140p, 149p, 155p, 205p, 210p, -, 221p], + [ 140p, 155p, 210p, 219p, 225p, 235p, 240p, -, 251p], + [ 210p, 225p, 240p, 249p, 255p, 305p, 310p, -, 321p], + [ 238p, 253p, 309p, 319p, 325p, 336p, 342p, 346p, -], + [ -, -, -, -, -, -, 357p, 401p, -], + [ 308p, 323p, 339p, 349p, 355p, 406p, 412p, 416p, -], + [ -, -, -, -, -, -, 427p, 431p, -], + [ 338p, 353p, 409p, 419p, 425p, 436p, 442p, 446p, -], + [ -, -, -, -, -, -, 457p, 501p, -], + [ 408p, 423p, 439p, 449p, 455p, 506p, 512p, 516p, -], + [ -, -, -, -, -, -, 527p, 531p, -], + [ 438p, 453p, 509p, 519p, 525p, 536p, 542p, 543p, -], + [ -, -, -, -, -, -, 557p, 601p, -], + [ 510p, 525p, 540p, 549p, 555p, 605p, 610p, -, 621p], + [ 525p, 540p, 555p, 604p, 610p, 620p, 625p, -, -], + [ 540p, 555p, 610p, 619p, 625p, 635p, 640p, -, 651p], + [ 555p, 610p, 625p, 634p, 640p, 650p, 655p, -, -], + [ 610p, 625p, 640p, 649p, 655p, 705p, 710p, -, 721p], + [ 625p, 640p, 655p, 704p, 710p, 720p, 725p, -, -], + [ 640p, 655p, 710p, 719p, 725p, 735p, 740p, -, 751p], + [ 650p, 704p, -, -, -, -, -, -, -], + [ 700p, 714p, -, -, -, -, -, -, -], + [ 710p, 725p, 740p, 749p, 755p, 805p, 810p, -, 821p], + [ 740p, 755p, 810p, 819p, 825p, 835p, 840p, -, 851p], + [ 810p, 825p, 840p, 849p, 855p, 905p, 910p, -, 921p], + [ 840p, 855p, 910p, 919p, 925p, 935p, 940p, -, 951p], + [ 910p, 925p, 940p, 949p, 955p, 1005p, 1010p, -, -], + [ 940p, 955p, 1010p, 1019p, 1025p, 1035p, 1040p, -, -], + [ 1010p, 1025p, 1040p, 1049p, 1055p, 1105p, 1110p, -, -], + [ 1110p, 1125p, 1140p, 1149p, 1155p, 1205x, 1210x, -, -], + [ 1210x, 1225x, 1240x, 1249x, 1255x, 105x, 110x, -, -] +] +stop_times_saturday: [ + [ -, -, 640a, 649a, 655a, 705a, 710a, -, 721a], + [ 710a, 725a, 740a, 749a, 755a, 805a, 810a, -, 821a], + [ 810a, 825a, 840a, 849a, 855a, 905a, 910a, -, 921a], + [ 840a, 855a, 910a, 919a, 925a, 935a, 940a, -, 951a], + [ 910a, 925a, 940a, 949a, 955a, 1005a, 1010a, -, 1021a], + [ 940a, 955a, 1010a, 1019a, 1025a, 1035a, 1040a, -, 1051a], + [ 1010a, 1025a, 1040a, 1049a, 1055a, 1105a, 1110a, -, 1121a], + [ 1040a, 1055a, 1110a, 1119a, 1125a, 1135a, 1140a, -, 1151a], + [ 1110a, 1125a, 1140a, 1149a, 1155a, 1205p, 1210p, -, 1221p], + [ 1140a, 1155a, 1210p, 1219p, 1225p, 1235p, 1240p, -, 1251p], + [ 1210p, 1225p, 1240p, 1249p, 1255p, 105p, 110p, -, 121p], + [ 1240p, 1255p, 110p, 119p, 125p, 135p, 140p, -, 151p], + [ 110p, 125p, 140p, 149p, 155p, 205p, 210p, -, 221p], + [ 140p, 155p, 210p, 219p, 225p, 235p, 240p, -, 251p], + [ 210p, 225p, 240p, 249p, 255p, 305p, 310p, -, 321p], + [ 240p, 255p, 310p, 319p, 325p, 335p, 340p, -, 351p], + [ 310p, 325p, 340p, 349p, 355p, 405p, 410p, -, 421p], + [ 340p, 355p, 410p, 419p, 425p, 435p, 440p, -, 451p], + [ 410p, 425p, 440p, 449p, 455p, 505p, 510p, -, 521p], + [ 440p, 455p, 510p, 519p, 525p, 535p, 540p, -, 551p], + [ 510p, 525p, 540p, 549p, 555p, 605p, 610p, -, 621p], + [ 540p, 555p, 610p, 619p, 625p, 635p, 640p, -, 651p], + [ 610p, 625p, 640p, 649p, 655p, 705p, 710p, -, 721p], + [ 640p, 655p, 710p, 719p, 725p, 735p, 740p, -, 751p], + [ 710p, 725p, 740p, 749p, 755p, 805p, 810p, -, 821p], + [ 740p, 755p, 810p, 819p, 825p, 835p, 840p, -, -], + [ 810p, 825p, 840p, 849p, 855p, 905p, 910p, -, 921p], + [ 910p, 925p, 940p, 949p, 955p, 1005p, 1010p, -, 1021p], + [ 1010p, 1025p, 1040p, 1049p, 1055p, 1105p, 1110p, -, 1121p], + [ 1110p, 1125p, 1140p, 1149p, 1155p, 1205x, 1210x, -, -], + [ 1210x, 1225x, 1240x, 1249x, 1255x, 105x, 110x, -, -] +] +stop_times_sunday: [ + [ -, 745a, 800a, 809a, 815a, 823a, 828a, -, -], + [ 800a, 815a, 830a, 839a, 845a, 853a, 858a, -, -], + [ 830a, 845a, 900a, 909a, 915a, 923a, 928a, -, -], + [ 900a, 915a, 930a, 939a, 945a, 953a, 958a, -, -], + [ 930a, 945a, 1000a, 1009a, 1015a, 1023a, 1028a, -, -], + [ 1000a, 1015a, 1030a, 1039a, 1045a, 1053a, 1058a, -, -], + [ 1030a, 1045a, 1100a, 1109a, 1115a, 1123a, 1128a, -, -], + [ 1100a, 1115a, 1130a, 1139a, 1145a, 1153a, 1158a, -, -], + [ 1130a, 1145a, 1200p, 1209p, 1215p, 1223p, 1228p, -, -], + [ 1200p, 1215p, 1230p, 1239p, 1245p, 1253p, 1258p, -, -], + [ 1230p, 1245p, 100p, 109p, 115p, 123p, 128p, -, -], + [ 100p, 115p, 130p, 139p, 145p, 153p, 158p, -, -], + [ 130p, 145p, 200p, 209p, 215p, 223p, 228p, -, -], + [ 200p, 215p, 230p, 239p, 245p, 253p, 258p, -, -], + [ 230p, 245p, 300p, 309p, 315p, 323p, 328p, -, -], + [ 300p, 315p, 330p, 339p, 345p, 353p, 358p, -, -], + [ 330p, 345p, 400p, 409p, 415p, 423p, 428p, -, -], + [ 400p, 415p, 430p, 439p, 445p, 453p, 458p, -, -], + [ 430p, 445p, 500p, 509p, 515p, 523p, 528p, -, -], + [ 500p, 515p, 530p, 539p, 545p, 553p, 558p, -, -], + [ 530p, 545p, 600p, 609p, 615p, 623p, 628p, -, -], + [ 600p, 615p, 630p, 639p, 645p, 653p, 658p, -, -], + [ 630p, 645p, 700p, 709p, 715p, 723p, 728p, -, -], + [ 700p, 715p, 730p, 739p, 745p, 753p, 758p, -, -], + [ 730p, 745p, 800p, 809p, 815p, 823p, 828p, -, -], + [ 800p, 815p, 830p, 839p, 845p, 853p, 858p, -, -], + [ 830p, 845p, 900p, 909p, 915p, 923p, 928p, -, -], + [ 900p, 915p, 930p, 939p, 945p, 953p, 958p, -, -], + [ 930p, 945p, 1000p, 1009p, 1015p, 1023p, 1028p, -, -], + [ 1000p, 1015p, 1030p, 1039p, 1045p, 1053p, 1058p, -, -], + [ 1030p, 1045p, 1100p, 1109p, 1115p, 1123p, 1128p, -, -], + [ 1100p, 1115p, 1130p, 1139p, 1145p, 1153p, 1158p, -, -], + [ 1130p, 1145p, 1200x, 1209x, 1215x, 1223x, 1228x, -, -], + [ 1200x, 1215x, 1230x, 1239x, 1245x, 1253x, 1258x, -, -], + [ 1230x, 1245x, 100x, 109x, 115x, 123x, 128x, -, -] +] --- /dev/null +++ b/origin-src/wlach-halifax-transit-feed-fef68c1/52-to-lacewood-chain-lake-drive.yml @@ -1,1 +1,125 @@ +short_name: 52 +long_name: To Lacewood - Chain Lake Drive +time_points: [ 6767, 7153, 7205, 6918, 7151, 7285, 6032, 7086, 6390 ] +between_stops: + 7151-7285: [ 7346, 7341, 7353, 7358, 7344, 7356, 6405, 6404, 6413, 6414, 7275 ] + 7285-6032: [ 6984 ] +stop_times: [ + [ -, -, -, -, -, -, -, 558a, 608a], + [ -, -, -, -, -, -, -, 628a, 638a], + [ -, -, 550a, 601a, 612a, 632a, 639a, 649a, 659a], + [ -, -, 620a, 631a, 642a, 702a, 709a, 719a, 729a], + [ -, -, 635a, 646a, 657a, 717a, 724a, 734a, 744a], + [ -, 646a, 653a, 704a, 715a, 735a, 742a, 752a, 802a], + [ -, -, 706a, 717a, 728a, 748a, 755a, 805a, 815a], + [ -, 716a, 723a, 734a, 745a, 805a, 812a, 822a, 832a], + [ -, 731a, 738a, 749a, 800a, 820a, 827a, 837a, 847a], + [ -, 746a, 753a, 804a, 815a, 835a, 842a, 852a, 902a], + [ -, 801a, 808a, 819a, 830a, 850a, 857a, 907a, 917a], + [ -, 816a, 823a, 834a, 845a, 905a, 912a, 922a, 932a], + [ -, 829a, 836a, 847a, 858a, 918a, 925a, 932a, 942a], + [ -, 843a, 850a, 901a, 912a, 930a, 938a, 945a, 955a], + [ -, 857a, 904a, 915a, 926a, 941a, 949a, 956a, 1006a], + [ 921a, 929a, 940a, 945a, 955a, 1010a, 1018a, 1025a, 1035a], + [ 951a, 959a, 1010a, 1015a, 1025a, 1040a, 1048a, 1055a, 1105a], + [ 1021a, 1029a, 1040a, 1045a, 1055a, 1110a, 1118a, 1125a, 1135a], + [ 1051a, 1059a, 1110a, 1115a, 1125a, 1140a, 1148a, 1155a, 1205p], + [ 1121a, 1129a, 1140a, 1145a, 1155a, 1210p, 1218p, 1225p, 1235p], + [ 1151a, 1159p, 1210p, 1215p, 1225p, 1240p, 1248p, 1255p, 105p], + [ 1221p, 1229p, 1240p, 1245p, 1255p, 110p, 118p, 125p, 135p], + [ 1251p, 1259p, 110p, 115p, 125p, 140p, 148p, 155p, 205p], + [ 121p, 129p, 140p, 145p, 155p, 210p, 218p, 225p, 235p], + [ 151p, 159p, 210p, 215p, 225p, 240p, 248p, 255p, 305p], + [ 221p, 229p, 240p, 245p, 255p, 310p, 318p, 325p, 335p], + [ 251p, 259p, 310p, 315p, 325p, 340p, 348p, 358p, 405p], + [ 321p, 329p, 340p, 345p, 355p, 416p, 419p, 433p, 435p], + [ -, 346p, 353p, 404p, 415p, 435p, 442p, 452p, 502p], + [ -, 401p, 408p, 419p, 430p, 450p, 457p, 507p, 517p], + [ -, 416p, 423p, 434p, 445p, 505p, 512p, 522p, 532p], + [ -, 431p, 438p, 449p, 500p, 520p, 527p, 537p, 547p], + [ -, 446p, 453p, 504p, 515p, 535p, 542p, 552p, 602p], + [ -, 501p, 508p, 519p, 530p, 550p, 557p, 607p, 617p], + [ -, 516p, 523p, 534p, 545p, 605p, 612p, 622p, 632p], + [ -, 531p, 538p, 549p, 600p, 620p, 627p, 634p, 644p], + [ -, 543p, 550p, 601p, 612p, 630p, 638p, 645p, 655p], + [ -, 601p, 608p, 619p, 630p, 645p, 653p, 700p, 710p], + [ 621p, 629p, 640p, 645p, 655p, 710p, 718p, 725p, 735p], + [ 651p, 659p, 710p, 715p, 725p, 740p, 748p, 755p, 805p], + [ 721p, 729p, 740p, 745p, 755p, 810p, 818p, 825p, 835p], + [ 751p, 759p, 810p, 815p, 825p, 840p, 848p, 855p, 905p], + [ 821p, 829p, 840p, 845p, 855p, 910p, 918p, 925p, 935p], + [ 851p, 859p, 910p, 915p, 925p, 940p, 948p, 955p, 1005p], + [ 951p, 959p, 1010p, 1015p, 1025p, 1040p, 1048p, 1055p, 1105p], + [ -, -, 1110p, 1115p, 1125p, 1140p, 1148p, 1155p, 1205x] +] +stop_times_saturday: [ + [ -, -, 610a, 615a, 625a, 640a, 648a, 655a, 705a], + [ -, -, 710a, 715a, 725a, 740a, 748a, 755a, 805a], + [ 721a, 731a, 740a, 745a, 755a, 810a, 818a, 825a, 835a], + [ 751a, 801a, 810a, 815a, 825a, 840a, 848a, 855a, 905a], + [ 821a, 831a, 840a, 845a, 855a, 910a, 918a, 925a, 935a], + [ 851a, 901a, 910a, 915a, 925a, 940a, 948a, 955a, 1005a], + [ 921a, 931a, 940a, 945a, 955a, 1010a, 1018a, 1025a, 1035a], + [ 951a, 1001a, 1010a, 1015a, 1025a, 1040a, 1048a, 1055a, 1105a], + [ 1021a, 1031a, 1040a, 1045a, 1055a, 1110a, 1118a, 1125a, 1135a], + [ 1051a, 1101a, 1110a, 1115a, 1125a, 1140a, 1148a, 1155a, 1205p], + [ 1121a, 1131a, 1140a, 1145a, 1155a, 1210p, 1218p, 1225p, 1235p], + [ 1151a, 1201p, 1210p, 1215p, 1225p, 1240p, 1248p, 1255p, 105p], + [ 1221p, 1231p, 1240p, 1245p, 1255p, 110p, 118p, 125p, 135p], + [ 1251p, 101p, 110p, 115p, 125p, 140p, 148p, 155p, 205p], + [ 121p, 131p, 140p, 145p, 155p, 210p, 218p, 225p, 235p], + [ 151p, 201p, 210p, 215p, 225p, 240p, 248p, 255p, 305p], + [ 221p, 231p, 240p, 245p, 255p, 310p, 318p, 325p, 335p], + [ 251p, 301p, 310p, 315p, 325p, 340p, 348p, 355p, 405p], + [ 321p, 331p, 340p, 345p, 355p, 410p, 418p, 425p, 435p], + [ 351p, 401p, 410p, 415p, 425p, 440p, 448p, 455p, 505p], + [ 421p, 431p, 440p, 445p, 455p, 510p, 518p, 525p, 535p], + [ 451p, 501p, 510p, 515p, 525p, 540p, 548p, 555p, 605p], + [ 521p, 531p, 540p, 545p, 555p, 610p, 618p, 625p, 635p], + [ 551p, 601p, 610p, 615p, 625p, 640p, 648p, 655p, 705p], + [ 621p, 631p, 640p, 645p, 655p, 710p, 718p, 725p, 735p], + [ 651p, 701p, 710p, 715p, 725p, 740p, 748p, 755p, 805p], + [ 721p, 731p, 740p, 745p, 752p, -, -, -, -], + [ 751p, 801p, 810p, 815p, 825p, 840p, 848p, 855p, 905p], + [ 821p, 831p, 840p, 845p, 852p, -, -, -, -], + [ 851p, 901p, 910p, 915p, 925p, 940p, 948p, 955p, 1005p], + [ 951p, 1001p, 1010p, 1015p, 1025p, 1040p, 1048p, 1055p, 1105p], + [ 1051p, 1101p, 1110p, 1115p, 1125p, 1140p, 1148p, 1155p, 1205x] +] +stop_times_sunday: [ + [ -, -, 700a, 705a, 715a, 730a, 738a, 745a, 755a], + [ -, -, 730a, 735a, 745a, 800a, 808a, 815a, 825a], + [ -, -, 800a, 805a, 815a, 830a, 838a, 845a, 855a], + [ -, -, 830a, 835a, 845a, 900a, 908a, 915a, 925a], + [ -, -, 900a, 905a, 915a, 930a, 938a, 945a, 955a], + [ -, -, 930a, 935a, 945a, 1000a, 1008a, 1015a, 1025a], + [ -, -, 1000a, 1005a, 1015a, 1030a, 1038a, 1045a, 1055a], + [ -, -, 1030a, 1035a, 1045a, 1100a, 1108a, 1115a, 1125a], + [ -, -, 1100a, 1105a, 1115a, 1130a, 1138a, 1145a, 1155a], + [ -, -, 1130a, 1135a, 1145a, 1200p, 1208p, 1215p, 1225p], + [ -, -, 1200p, 1205p, 1215p, 1230p, 1238p, 1245p, 1255p], + [ -, -, 1230p, 1235p, 1245p, 100p, 108p, 115p, 125p], + [ -, -, 100p, 105p, 115p, 130p, 138p, 145p, 155p], + [ -, -, 130p, 135p, 145p, 200p, 208p, 215p, 225p], + [ -, -, 200p, 205p, 215p, 230p, 238p, 245p, 255p], + [ -, -, 230p, 235p, 245p, 300p, 308p, 315p, 325p], + [ -, -, 300p, 305p, 315p, 330p, 338p, 345p, 355p], + [ -, -, 330p, 335p, 345p, 400p, 408p, 415p, 425p], + [ -, -, 400p, 405p, 415p, 430p, 438p, 445p, 455p], + [ -, -, 430p, 435p, 445p, 500p, 508p, 515p, 525p], + [ -, -, 500p, 505p, 515p, 530p, 538p, 545p, 555p], + [ -, -, 530p, 535p, 545p, 600p, 608p, 615p, 625p], + [ -, -, 600p, 605p, 615p, 630p, 638p, 645p, 655p], + [ -, -, 630p, 635p, 645p, 700p, 708p, 715p, 725p], + [ -, -, 700p, 705p, 715p, 730p, 738p, 745p, 755p], + [ -, -, 730p, 735p, 745p, 800p, 808p, 815p, 825p], + [ -, -, 800p, 805p, 815p, 830p, 838p, 845p, 855p], + [ -, -, 830p, 835p, 845p, 900p, 908p, 915p, 925p], + [ -, -, 900p, 905p, 915p, 930p, 938p, 945p, 955p], + [ -, -, 930p, 935p, 945p, 1000p, 1008p, 1015p, 1025p], + [ -, -, 1000p, 1005p, 1015p, 1030p, 1038p, 1045p, 1055p], + [ -, -, 1030p, 1035p, 1045p, 1100p, 1108p, 1115p, 1125p], + [ -, -, 1100p, 1105p, 1115p, 1130p, 1138p, 1145p, 1155p], + [ -, -, 1130p, 1135p, 1145p, 1200x, 1208x, 1215x, 1225x] +] --- /dev/null +++ b/origin-src/wlach-halifax-transit-feed-fef68c1/58-to-lucien-drive.yml @@ -1,1 +1,39 @@ +short_name: 58 +long_name: To Lucien Drive +time_points: [ 7284, 7412, 6087, 8640, 6031, 7445, 6575 ] +between_stops: + 7284-7412: [ 6203, 7423 ] + # nb: I haven't checked that 8328-6106 actually are served by the #58, but I'd + # be shocked if they weren't + 7412-6087: [ 7419, 7409, 7402, 6453, 6447, 6449, 6454, 6452, 8328, 8337, 8333, 8336, 8327, 8338, 6121, 6106 ] +stop_times: [ + [ -, -, -, -, -, 540a, 552a], + [ -, -, -, 555a, 600a, 610a, 622a], + [ -, -, -, 625a, 630a, 640a, 652a], + [ -, -, -, 645a, 650a, 700a, 712a], + [ -, -, -, 705a, 710a, 720a, 732a], + [ -, -, -, 725a, 730a, 740a, 752a], + [ -, -, -, 755a, 800a, 810a, 822a], + [ -, -, -, 825a, 830a, 840a, 852a], + [ -, -, -, 855a, 900a, 910a, 922a], + [ -, -, -, 955a, 1000a, 1010a, 1022a], + [ -, -, -, 1055a, 1100a, 1110a, 1122a], + [ -, -, -, 1155a, 1200p, 1210p, 1222p], + [ -, -, -, 1255p, 100p, 110p, 122p], + [ -, -, -, 155p, 200p, 210p, 222p], + [ -, -, -, 255p, 300p, 310p, 322p], + [ -, -, -, 325p, 330p, 340p, 352p], + [ 310p, 323p, 340p, 352p, 357p, 407p, 419p], + [ 330p, 343p, 400p, 412p, 417p, 427p, 439p], + [ 350p, 403p, 420p, 432p, 437p, 447p, 459p], + [ 410p, 423p, 440p, 452p, 457p, 507p, 519p], + [ 440p, 453p, 510p, 522p, 527p, 537p, 549p], + [ -, -, -, 555p, 600p, 610p, 622p], + [ -, -, -, 655p, 700p, 710p, 722p], + [ -, -, -, 755p, 800p, 810p, 822p], + [ -, -, -, 855p, 900p, 910p, 922p], + [ -, -, -, 955p, 1000p, 1010p, 1022p], + [ -, -, -, 1055p, 1100p, 1110p, 1122p], + [ -, -, -, 1155p, 1200x, 1210x, 1222x] +] --- /dev/null +++ b/origin-src/wlach-halifax-transit-feed-fef68c1/6-to-downtown.yml @@ -1,1 +1,35 @@ +short_name: 6 +long_name: To Downtown +time_points: [ 8361, 7285, 8136, 6105, 8435 ] +between_stops: + 7285-8136: [ 7274, 8135, 8133 ] + 8136-6105: [ 8147, 8149, 8150 ] + 6105-8435: [ 6108 ] +stop_times: [ + [ 615a, 627a, 634a, 645a, 648a], + [ 645a, 657a, 704a, 715a, 718a], + [ 716a, 728a, 735a, 746a, 749a], + [ 748a, 800a, 807a, 818a, 821a], + [ 818a, 830a, 837a, 848a, 851a], + [ 903a, 913a, 920a, 931a, 934a], + [ 932a, 942a, 949a, 1000a, 1003a], + [ 1032a, 1042a, 1049a, 1100a, 1103a], + [ 1132a, 1142a, 1149a, 1200p, 1203p], + [ 1232p, 1242p, 1249p, 100p, 103p], + [ 132p, 142p, 149p, 200p, 203p], + [ 232p, 242p, 249p, 300p, 303p], + [ 336p, 348p, 355p, 406p, 409p], + [ 406p, 418p, 425p, 436p, 439p], + [ 436p, 448p, 455p, 506p, 509p], + [ 506p, 518p, 525p, 536p, 539p], + [ 536p, 548p, 555p, 604p, 607p], + [ 606p, 616p, 623p, 630p, 633p], + [ 632p, 642p, 649p, 656p, 659p], + [ 732p, 742p, 749p, 756p, 759p], + [ 832p, 842p, 849p, 856p, 859p], + [ 932p, 942p, 949p, 956p, 959p], + [ 1032p, 1042p, 1049p, 1056p, 1059p], + [ 1132p, 1142p, 1149p, 1156p, 1159p], + [ 1232x, 1242x, -, -, -] +] --- /dev/null +++ b/origin-src/wlach-halifax-transit-feed-fef68c1/6-to-stonehaven.yml @@ -1,1 +1,34 @@ +short_name: 6 +long_name: To Stonehaven +time_points: [ 8435, 8137, 8643, 8361 ] +between_stops: + 8435-8137: [ 8138, 8148 ] + 8137-8643: [ 8134, 8146, 7275 ] +stop_times: [ + [ -, -, 604a, 614a], + [ -, -, 634a, 644a], + [ 648a, 658a, 705a, 715a], + [ 722a, 730a, 737a, 747a], + [ 752a, 800a, 807a, 817a], + [ 835a, 845a, 852a, 901a], + [ 905a, 915a, 922a, 931a], + [ 1005a, 1015a, 1022a, 1031a], + [ 1105a, 1115a, 1122a, 1131a], + [ 1205p, 1215p, 1222p, 1231p], + [ 105p, 115p, 122p, 131p], + [ 205p, 215p, 222p, 231p], + [ 308p, 318p, 325p, 335p], + [ 338p, 348p, 355p, 405p], + [ 408p, 418p, 425p, 435p], + [ 438p, 448p, 455p, 505p], + [ 508p, 518p, 525p, 535p], + [ 538p, 548p, 555p, 605p], + [ 605p, 615p, 622p, 631p], + [ 705p, 715p, 722p, 731p], + [ 805p, 815p, 822p, 831p], + [ 905p, 915p, 922p, 931p], + [ 1005p, 1015p, 1022p, 1031p], + [ 1105p, 1115p, 1122p, 1131p], + [ 1205x, 1215x, 1222x, 1231x] +] --- /dev/null +++ b/origin-src/wlach-halifax-transit-feed-fef68c1/7-gottingen-to-robie.yml @@ -1,1 +1,114 @@ +short_name: 7 +long_name: Gottingen to Robie via Downtown and South Street +time_points: [ 7378, 6770, 6105, 6114, 8184, 8180, 8165 ] +between_stops: + 7378-6770: [ 7368, 7361, 7382, 7370, 7362, 7365, 7364 ] + 6770-6105: [ 6783, 6776, 6777, 6781, 6774, 6780, 6786, 6790, 6771 ] + 6105-6114: [ 6108, 6103, 6102 ] + 6114-8184: [ 6097, 8294, 8292, 8300, 8301, 8298, 8297, 8220, 8187, 8185, 8219, 8179 ] + 8184-8180: [ 8206, 8221, 8222, 8181, 8210, 8201, 8251, 8192, 8209, 8217, 8204, 8207 ] + 8180-8165: [ 8211, 7131 ] +stop_times: [ + [ 605a, 612a, 620a, 627a, 636a, 643a, 647a], + [ 625a, 632a, 640a, 647a, 656a, 703a, 707a], + [ 650a, 657a, 705a, 712a, 721a, 728a, 732a], + [ 710a, 717a, 725a, 732a, 741a, 748a, 752a], + [ 730a, 737a, 745a, 752a, 801a, 808a, 812a], + [ 750a, 757a, 805a, 812a, 821a, 828a, 832a], + [ 810a, 817a, 825a, 832a, 841a, 848a, 852a], + [ 830a, 837a, 845a, 852a, 901a, 908a, 912a], + [ 850a, 857a, 905a, 912a, 921a, 928a, 932a], + [ 920a, 927a, 937a, 944a, 952a, 1001a, 1005a], + [ 950a, 957a, 1007a, 1014a, 1023a, 1031a, 1035a], + [ 1020a, 1027a, 1037a, 1044a, 1053a, 1101a, 1105a], + [ 1050a, 1057a, 1107a, 1114a, 1123a, 1131a, 1135a], + [ 1120a, 1127a, 1137a, 1144a, 1153a, 1201p, 1205p], + [ 1150a, 1157a, 1207p, 1214p, 1223p, 1231p, 1235p], + [ 1220p, 1227p, 1237p, 1244p, 1253p, 102p, 106p], + [ 1250p, 1257p, 107p, 114p, 124p, 136p, 140p], + [ 120p, 127p, 137p, 144p, 154p, 206p, 210p], + [ 150p, 157p, 207p, 214p, 224p, 236p, 240p], + [ 220p, 227p, 237p, 244p, 254p, 306p, 310p], + [ 240p, 247p, 257p, 304p, 314p, 326p, 330p], + [ 300p, 307p, 317p, 324p, 334p, 346p, 350p], + [ 320p, 327p, 337p, 344p, 354p, 406p, 410p], + [ 340p, 347p, 357p, 404p, 414p, 426p, 430p], + [ 400p, 407p, 417p, 424p, 434p, 446p, 450p], + [ 420p, 427p, 437p, 444p, 454p, 506p, 510p], + [ 440p, 447p, 457p, 504p, 514p, 526p, 530p], + [ 500p, 507p, 517p, 524p, 534p, 540p, 544p], + [ 520p, 527p, 535p, 541p, 551p, 556p, 600p], + [ 550p, 557p, 605p, 611p, 620p, 626p, 630p], + [ 620p, 627p, 635p, 641p, 650p, 656p, 700p], + [ 650p, 657p, 705p, 711p, 720p, 726p, 730p], + [ 720p, 727p, 735p, 741p, 750p, 756p, 800p], + [ 750p, 757p, 805p, 811p, 820p, 826p, 830p], + [ 820p, 827p, 835p, 841p, 850p, 856p, 900p], + [ 850p, 857p, 905p, 911p, 920p, 926p, 930p], + [ 920p, 927p, 935p, 941p, 950p, 956p, 1000p], + [ 950p, 957p, 1005p, 1011p, 1020p, 1026p, 1030p], + [ 1020p, 1027p, 1035p, 1041p, 1050p, 1056p, 1100p], + [ 1050p, 1057p, 1105p, 1111p, 1120p, 1126p, 1130p], + [ 1120p, 1127p, 1135p, 1141p, 1150p, 1156p, 1200x], + [ 1150p, 1157p, 1205x, 1211x, 1220x, 1226x, 1230x] +] +stop_times_saturday: [ + [ 655a, 702a, 710a, 716a, 725a, 731a, 735a], + [ 755a, 802a, 810a, 816a, 825a, 831a, 835a], + [ 825a, 832a, 840a, 846a, 855a, 901a, 905a], + [ 855a, 902a, 910a, 916a, 925a, 931a, 935a], + [ 925a, 932a, 940a, 946a, 955a, 1001a, 1005a], + [ 955a, 1002a, 1010a, 1016a, 1025a, 1031a, 1035a], + [ 1025a, 1032a, 1040a, 1046a, 1055a, 1101a, 1105a], + [ 1055a, 1102a, 1110a, 1116a, 1125a, 1131a, 1135a], + [ 1125a, 1132a, 1140a, 1146a, 1155a, 1201p, 1205p], + [ 1155a, 1202p, 1210p, 1216p, 1225p, 1231p, 1235p], + [ 1225p, 1232p, 1240p, 1246p, 1255p, 101p, 105p], + [ 1255p, 102p, 110p, 116p, 125p, 131p, 135p], + [ 125p, 132p, 140p, 146p, 155p, 201p, 205p], + [ 155p, 202p, 210p, 216p, 225p, 231p, 235p], + [ 225p, 232p, 240p, 246p, 255p, 301p, 305p], + [ 255p, 302p, 310p, 316p, 325p, 331p, 335p], + [ 325p, 332p, 340p, 346p, 355p, 401p, 405p], + [ 355p, 402p, 410p, 416p, 425p, 431p, 435p], + [ 425p, 432p, 440p, 446p, 455p, 501p, 505p], + [ 455p, 502p, 510p, 516p, 525p, 531p, 535p], + [ 525p, 532p, 540p, 546p, 555p, 601p, 605p], + [ 555p, 602p, 610p, 616p, 625p, 631p, 635p], + [ 625p, 632p, 640p, 646p, 655p, 701p, 705p], + [ 655p, 702p, 710p, 716p, 725p, 731p, 735p], + [ 725p, 732p, 740p, 746p, 755p, 801p, 805p], + [ 755p, 802p, 810p, 816p, 825p, 831p, 835p], + [ 825p, 832p, 840p, 846p, 855p, 901p, 905p], + [ 855p, 902p, 910p, 916p, 925p, 931p, 935p], + [ 925p, 932p, 940p, 946p, 955p, 1001p, 1005p], + [ 955p, 1002p, 1010p, 1016p, 1025p, 1031p, 1035p], + [ 1025p, 1032p, 1040p, 1046p, 1055p, 1101p, 1105p], + [ 1055p, 1102p, 1110p, 1116p, 1125p, 1131p, 1135p], + [ 1155p, 1202x, 1210x, 1216x, 1225x, 1231x, 1235x] +] +stop_times_sunday: [ + [ 745a, 752a, 800a, 805a, 814a, 820a, 824a], + [ 830a, 837a, 845a, 850a, 859a, 905a, 909a], + [ 915a, 922a, 930a, 935a, 944a, 950a, 954a], + [ 1000a, 1007a, 1015a, 1020a, 1029a, 1035a, 1039a], + [ 1045a, 1052a, 1100a, 1105a, 1114a, 1120a, 1124a], + [ 1130a, 1137a, 1145a, 1150a, 1159a, 1205p, 1209p], + [ 1215p, 1222p, 1230p, 1235p, 1244p, 1250p, 1254p], + [ 100p, 107p, 115p, 120p, 129p, 135p, 139p], + [ 145p, 152p, 200p, 205p, 214p, 220p, 224p], + [ 230p, 237p, 245p, 250p, 259p, 305p, 309p], + [ 315p, 322p, 330p, 335p, 344p, 350p, 354p], + [ 400p, 407p, 415p, 420p, 429p, 435p, 439p], + [ 445p, 452p, 500p, 505p, 514p, 520p, 524p], + [ 530p, 537p, 545p, 550p, 559p, 605p, 609p], + [ 615p, 622p, 630p, 635p, 644p, 650p, 654p], + [ 700p, 707p, 715p, 720p, 729p, 735p, 739p], + [ 745p, 752p, 800p, 805p, 814p, 820p, 824p], + [ 830p, 837p, 845p, 850p, 859p, 905p, 909p], + [ 915p, 922p, 930p, 935p, 944p, 950p, 954p], + [ 1000p, 1007p, 1015p, 1020p, 1029p, 1035p, 1039p], + [ 1045p, 1052p, 1100p, 1105p, 1114p, 1120p, 1124p], + [ 1130p, 1137p, 1145p, 1150p, 1159p, 1205x, 1209x] +] --- /dev/null +++ b/origin-src/wlach-halifax-transit-feed-fef68c1/7-robie-to-gottingen.yml @@ -1,1 +1,58 @@ +short_name: 7 +long_name: Robie to Gottingen via South Street and Downtown +time_points: [ 8165, 8208, 8214, 6096, 6087, 7366, 7378 ] +between_stops: + 8165-8208: [ 7133, 8212 ] + 8208-8214: [ 8203, 8197, 8218, 8193, 8202, 8182, 8213, 8178, 8205, 8190 ] + 8214-6096: [ 8183, 8196, 8195, 8194, 8186, 8188, 8296, 8303, 8305, 8295, 8299, 8293 ] + 6096-6087: [ 6113, 6124 ] + 6087-7366: [ 6455, 6773, 6778, 6779, 6775, 6787, 6769, 6785, 6768, 6782 ] + 7366-7378: [ 7377, 7381, 7374, 7369, 7373, 7376, 7360 ] +stop_times: [ + [ 600a, 604a, 614a, 621a, 628a, 637a, 645a], + [ 620a, 624a, 634a, 641a, 648a, 657a, 705a], + [ 640a, 644a, 654a, 701a, 708a, 717a, 725a], + [ 700a, 704a, 714a, 721a, 728a, 737a, 745a], + [ 720a, 724a, 734a, 741a, 748a, 757a, 805a], + [ 740a, 744a, 754a, 801a, 808a, 817a, 825a], + [ 800a, 804a, 814a, 821a, 828a, 837a, 845a], + [ 820a, 824a, 834a, 841a, 848a, 857a, 905a], + [ 840a, 844a, 854a, 901a, 908a, 917a, 925a], + [ 900a, 904a, 914a, 921a, 928a, 937a, 945a], + [ 930a, 934a, 944a, 951a, 958a, 1007a, 1015a], + [ 1000a, 1004a, 1014a, 1021a, 1028a, 1037a, 1045a], + [ 1030a, 1034a, 1044a, 1051a, 1058a, 1107a, 1115a], + [ 1100a, 1104a, 1114a, 1121a, 1128a, 1137a, 1145a], + [ 1130a, 1134a, 1144a, 1151a, 1158a, 1207p, 1215p], + [ 1200p, 1204p, 1214p, 1221p, 1228p, 1237p, 1245p], + [ 1230p, 1234p, 1244p, 1251p, 1258p, 108p, 116p], + [ 100p, 104p, 116p, 124p, 132p, 142p, 150p], + [ 130p, 134p, 146p, 154p, 202p, 212p, 220p], + [ 200p, 204p, 216p, 224p, 232p, 242p, 250p], + [ 230p, 234p, 246p, 254p, 302p, 312p, 320p], + [ 250p, 254p, 306p, 314p, 322p, 332p, 340p], + [ 310p, 314p, 326p, 334p, 342p, 352p, 400p], + [ 330p, 334p, 346p, 354p, 402p, 412p, 420p], + [ 350p, 354p, 406p, 414p, 422p, 432p, 440p], + [ 410p, 414p, 426p, 434p, 442p, 452p, 500p], + [ 430p, 434p, 446p, 454p, 502p, 512p, 520p], + [ 450p, 454p, 506p, 514p, 522p, 531p, 539p], + [ 510p, 514p, 526p, 534p, 540p, 547p, 555p], + [ 530p, 534p, 541p, 548p, 555p, 602p, 610p], + [ 550p, 554p, 601p, 608p, 615p, 622p, 630p], + [ 610p, 614p, 621p, 628p, 635p, 642p, 650p], + [ 640p, 644p, 651p, 658p, 705p, 712p, 720p], + [ 710p, 714p, 721p, 728p, 735p, 742p, 750p], + [ 740p, 744p, 751p, 758p, 805p, 812p, 820p], + [ 810p, 814p, 821p, 828p, 835p, 842p, 850p], + [ 840p, 844p, 851p, 858p, 905p, 912p, 920p], + [ 910p, 914p, 921p, 928p, 935p, 942p, 950p], + [ 940p, 944p, 951p, 958p, 1005p, 1012p, 1020p], + [ 1010p, 1014p, 1021p, 1028p, 1035p, 1042p, 1050p], + [ 1040p, 1044p, 1051p, 1058p, 1105p, 1112p, 1120p], + [ 1110p, 1114p, 1121p, 1128p, 1135p, 1142p, 1150p], + [ 1140p, 1144p, 1151p, 1158p, 1205x, 1212x, 1220x], + [ 1210x, 1214x, 1221x, 1228x, 1235x, 1242x, 1250x] +] + --- /dev/null +++ b/origin-src/wlach-halifax-transit-feed-fef68c1/80-to-bedford-halifax.yml @@ -1,1 +1,132 @@ +short_name: 80 +long_name: To Bedford - Halifax +time_points: [ 8260, 8646, 6236, 6216, 6564, 8214, 6087, 8414 ] +between_stops: + 6216-6564: [ 6982, 6985, 8262 ] + 6564-8214: [ 6197, 6193, 6203, 8633, 8193, 8202, 8182, 8213, 8178, 8205, 8190 ] + 8214-6087: [ 8183, 8196, 8195, 8194, 8328, 8337, 8333, 8336, 8327, 8338, 6121, 6106 ] +stop_times: [ + [ 542a, 554a, 602a, 618a, 625a, 635a, 645a, 647a], + [ 633a, 645a, 653a, 708a, 718a, 729a, 746a, 748a], + [ 706a, 721a, 728a, 743a, 753a, 804a, 821a, 823a], + [ 733a, 748a, 755a, 810a, 820a, 831a, 848a, 850a], + [ 800a, 815a, 822a, 837a, 845a, 856a, 913a, 915a], + [ 829a, 844a, 851a, 906a, 914a, 925a, 942a, 944a], + [ 859a, 914a, 921a, 936a, 944a, 955a, 1012a, 1014a], + [ 929a, 944a, 951a, 1006a, 1014a, 1025a, 1042a, 1044a], + [ 959a, 1014a, 1021a, 1036a, 1044a, 1055a, 1112a, 1114a], + [ 1029a, 1044a, 1051a, 1106a, 1114a, 1125a, 1142a, 1144a], + [ 1059a, 1114a, 1121a, 1136a, 1144a, 1155a, 1212p, 1214p], + [ 1129a, 1144a, 1151a, 1206p, 1214p, 1225p, 1242p, 1244p], + [ 1159a, 1214p, 1221p, 1236p, 1244p, 1255p, 112p, 114p], + [ 1229p, 1244p, 1251p, 106p, 114p, 125p, 142p, 144p], + [ 1259p, 114p, 121p, 136p, 144p, 155p, 212p, 214p], + [ 129p, 144p, 151p, 206p, 214p, 225p, 242p, 244p], + [ 159p, 214p, 221p, 236p, 244p, 255p, 312p, 314p], + [ 229p, 244p, 251p, 306p, 314p, 325p, 342p, 344p], + [ 259p, 314p, 321p, 336p, 344p, 355p, 412p, 414p], + [ 329p, 344p, 351p, 406p, 414p, 425p, 442p, 444p], + [ 359p, 414p, 421p, 436p, 444p, 455p, 512p, 514p], + [ 429p, 444p, 451p, 506p, 514p, 525p, 542p, 544p], + [ 459p, 514p, 521p, 536p, 544p, 555p, 607p, 609p], + [ 526p, 541p, 548p, 603p, 607p, 618p, 628p, 630p], + [ 551p, 605p, 610p, 626p, 630p, 641p, 651p, 653p], + [ 621p, 633p, 638p, 654p, 658p, 709p, 719p, 721p], + [ 651p, 703p, 708p, 724p, 728p, 739p, 749p, 751p], + [ 721p, 733p, 738p, 754p, 758p, 809p, 819p, 821p], + [ 751p, 803p, 808p, 824p, 828p, 839p, 849p, 851p], + [ 821p, 833p, 838p, 854p, 858p, 909p, 919p, 921p], + [ 851p, 903p, 908p, 924p, 928p, 939p, 949p, 951p], + [ 921p, 933p, 938p, 954p, 958p, 1009p, 1019p, 1021p], + [ 951p, 1003p, 1008p, 1024p, 1028p, 1039p, 1049p, 1051p], + [ 1021p, 1033p, 1038p, 1054p, 1058p, 1109p, 1119p, 1121p], + [ 1121p, 1133p, 1138p, 1154p, 1158p, 1209x, 1219x, 1221x], + [ 1216x, 1228x, -, -, -, -, -, -], + [ 116x, 128x, -, -, -, -, -, -] +] +stop_times_saturday: [ + [ 545a, 557a, 603a, 613a, 622a, 633a, 643a, 645a], + [ 615a, 627a, 633a, 643a, 652a, 703a, 713a, 715a], + [ 645a, 657a, 703a, 713a, 722a, 734a, 744a, 745a], + [ 725a, 738a, 744a, 754a, 803a, 815a, 825a, 827a], + [ 755a, 808a, 814a, 824a, 833a, 845a, 855a, 857a], + [ 825a, 838a, 844a, 854a, 903a, 915a, 925a, 927a], + [ 855a, 908a, 914a, 924a, 933a, 945a, 955a, 957a], + [ 925a, 938a, 944a, 954a, 1003a, 1015a, 1025a, 1027a], + [ 955a, 1008a, 1014a, 1024a, 1033a, 1045a, 1055a, 1057a], + [ 1025a, 1038a, 1044a, 1054a, 1103a, 1115a, 1125a, 1127a], + [ 1055a, 1108a, 1114a, 1124a, 1133a, 1145a, 1155a, 1157a], + [ 1125a, 1138a, 1144a, 1154a, 1203p, 1215p, 1225p, 1227p], + [ 1155a, 1208p, 1214p, 1224p, 1233p, 1245p, 1255p, 1257p], + [ 1225p, 1238p, 1244p, 1254p, 103p, 115p, 125p, 127p], + [ 1255p, 108p, 114p, 124p, 133p, 145p, 155p, 157p], + [ 125p, 138p, 144p, 154p, 203p, 215p, 225p, 227p], + [ 155p, 208p, 214p, 224p, 233p, 245p, 255p, 257p], + [ 225p, 238p, 244p, 254p, 303p, 315p, 325p, 327p], + [ 255p, 308p, 314p, 324p, 333p, 345p, 355p, 357p], + [ 325p, 338p, 344p, 354p, 403p, 415p, 425p, 427p], + [ 355p, 408p, 414p, 424p, 433p, 445p, 455p, 457p], + [ 425p, 438p, 444p, 454p, 503p, 515p, 525p, 527p], + [ 455p, 508p, 514p, 524p, 533p, 545p, 555p, 557p], + [ 525p, 538p, 544p, 554p, 603p, 615p, 625p, 627p], + [ 555p, 608p, 614p, 624p, 633p, 645p, 655p, 657p], + [ 635p, 648p, 654p, 704p, 713p, 725p, 735p, 737p], + [ 705p, 718p, 724p, 734p, 743p, 755p, 805p, 807p], + [ 735p, 748p, 754p, 804p, 813p, 825p, 835p, 837p], + [ 805p, 818p, 824p, 834p, 843p, 855p, 905p, 907p], + [ 835p, 848p, 854p, 904p, 913p, 925p, 935p, 937p], + [ 905p, 918p, 924p, 934p, 943p, 955p, 1005p, 1007p], + [ 935p, 948p, 954p, 1004p, 1013p, 1023p, 1033p, 1035p], + [ 1005p, 1017p, 1023p, 1033p, 1042p, 1052p, 1102p, 1104p], + [ 1107p, 1119p, 1125p, 1135p, 1144p, 1154p, 1204x, 1206x], + [ 1207x, 1219x, 1225x, -, -, -, -, -], + [ 107x, 119x, 125x, -, -, -, -, -] +] +stop_times_sunday: [ + [ 545a, 605a, 610a, 622a, 630a, 640a, 650a, 652a], + [ 645a, 705a, 710a, 722a, 730a, 740a, 750a, 752a], + [ 715a, 735a, 740a, 752a, 800a, 810a, 820a, 822a], + [ 745a, 805a, 810a, 822a, 830a, 840a, 850a, 852a], + [ 815a, 835a, 840a, 852a, 900a, 910a, 920a, 922a], + [ 845a, 905a, 910a, 922a, 930a, 940a, 950a, 952a], + [ 915a, 935a, 940a, 952a, 1000a, 1010a, 1020a, 1022a], + [ 945a, 1005a, 1010a, 1022a, 1030a, 1040a, 1050a, 1052a], + [ 1015a, 1035a, 1040a, 1052a, 1100a, 1110a, 1120a, 1122a], + [ 1045a, 1105a, 1110a, 1122a, 1130a, 1140a, 1150a, 1152a], + [ 1115a, 1135a, 1140a, 1152a, 1200p, 1210p, 1220p, 1222p], + [ 1135a, 1155a, 1200p, 1212p, 1220p, 1230p, 1240p, 1242p], + [ 1155a, 1215p, 1220p, 1232p, 1240p, 1250p, 100p, 102p], + [ 1215p, 1235p, 1240p, 1252p, 100p, 110p, 120p, 122p], + [ 1235p, 1255p, 100p, 112p, 120p, 130p, 140p, 142p], + [ 1255p, 115p, 120p, 132p, 140p, 150p, 200p, 202p], + [ 115p, 135p, 140p, 152p, 200p, 210p, 220p, 222p], + [ 135p, 155p, 200p, 212p, 220p, 230p, 240p, 242p], + [ 155p, 215p, 220p, 232p, 240p, 250p, 300p, 302p], + [ 215p, 235p, 240p, 252p, 300p, 310p, 320p, 322p], + [ 235p, 255p, 300p, 312p, 320p, 330p, 340p, 342p], + [ 255p, 315p, 320p, 332p, 340p, 350p, 400p, 402p], + [ 315p, 335p, 340p, 352p, 400p, 410p, 420p, 422p], + [ 335p, 355p, 400p, 412p, 420p, 430p, 440p, 442p], + [ 355p, 415p, 420p, 432p, 440p, 450p, 500p, 502p], + [ 415p, 435p, 440p, 452p, 500p, 510p, 520p, 522p], + [ 435p, 455p, 500p, 512p, 520p, 530p, 540p, 542p], + [ 455p, 515p, 520p, 532p, 540p, 550p, 600p, 602p], + [ 515p, 535p, 540p, 552p, 600p, 610p, 620p, 622p], + [ 535p, 555p, 600p, 612p, 620p, 630p, 640p, 642p], + [ 555p, 615p, 620p, 632p, 640p, 650p, 700p, 702p], + [ 615p, 635p, 640p, 652p, 700p, 710p, 720p, 722p], + [ 635p, 655p, 700p, 712p, 720p, 730p, 740p, 742p], + [ 655p, 715p, 720p, 732p, 740p, 750p, 800p, 802p], + [ 715p, 735p, 740p, 752p, 800p, 810p, 820p, 822p], + [ 735p, 755p, 800p, 812p, 820p, 830p, 840p, 842p], + [ 755p, 815p, 820p, 832p, 840p, 850p, 900p, 902p], + [ 815p, 835p, 840p, 852p, 900p, 910p, 920p, 922p], + [ 835p, 855p, 900p, 912p, 920p, 930p, 940p, 942p], + [ 855p, 915p, 920p, 932p, 940p, 950p, 1000p, 1002p], + [ 915p, 935p, 940p, 952p, 1000p, 1010p, 1020p, 1022p], + [ 945p, 1005p, 1010p, 1022p, 1030p, 1040p, 1050p, 1052p], + [ 1015p, 1035p, 1040p, 1052p, 1100p, 1110p, 1120p, 1122p], + [ 1115p, 1135p, 1140p, 1152p, 1200x, 1210x, 1220x, 1222x], + [ 1215x, 1235x, 1240x, -, -, -, -, -] +] --- /dev/null +++ b/origin-src/wlach-halifax-transit-feed-fef68c1/80-to-bedford-sackville.yml @@ -1,1 +1,130 @@ +short_name: 80 +long_name: To Bedford - Sackville +time_points: [ 8414, 6105, 8184, 6563, 6219, 6238, 6445, 8260 ] +between_stops: + 6105-8184: [ 6108, 6103, 6102, 6122, 8331, 8330, 8334, 8335, 8329, 8185, 8219, 8179 ] + 8184-6563: [ 8206, 8221, 8222, 8181, 8210, 8201, 8251, 8629, 8634, 6192, 6196, 6201, 6200, 6199, 6198 ] + 6563-6219: [ 6565, 6984, 6983 ] +stop_times: [ + [ -, -, -, -, -, 519a, 525a, -], + [ -, -, -, -, -, 608a, 615a, 628a], + [ 552a, 555a, 605a, 615a, 619a, 634a, 640a, 658a], + [ 618a, 621a, 631a, 641a, 645a, 701a, 709a, 725a], + [ 642a, 645a, 655a, 708a, 713a, 731a, 738a, 752a], + [ 707a, 710a, 722a, 737a, 742a, 800a, 808a, 821a], + [ 737a, 740a, 752a, 807a, 812a, 830a, 838a, 851a], + [ 807a, 810a, 822a, 837a, 842a, 900a, 908a, 921a], + [ 837a, 840a, 852a, 907a, 912a, 930a, 938a, 951a], + [ 907a, 910a, 922a, 937a, 942a, 1000a, 1008a, 1021a], + [ 937a, 940a, 952a, 1007a, 1012a, 1030a, 1038a, 1051a], + [ 1007a, 1010a, 1022a, 1037a, 1042a, 1100a, 1108a, 1121a], + [ 1037a, 1040a, 1052a, 1107a, 1112a, 1130a, 1138a, 1151a], + [ 1107a, 1110a, 1122a, 1137a, 1142a, 1200p, 1208p, 1221p], + [ 1137a, 1140a, 1152a, 1207p, 1212p, 1230p, 1238p, 1251p], + [ 1207p, 1210p, 1222p, 1237p, 1242p, 100p, 108p, 121p], + [ 1237p, 1240p, 1252p, 107p, 112p, 130p, 138p, 151p], + [ 107p, 110p, 122p, 137p, 142p, 200p, 208p, 221p], + [ 127p, 130p, 142p, 157p, 202p, 220p, 228p, 241p], + [ 147p, 150p, 202p, 217p, 222p, 240p, 248p, 301p], + [ 207p, 210p, 222p, 237p, 242p, 300p, 308p, 321p], + [ 222p, 225p, 237p, 252p, 257p, 315p, 323p, 336p], + [ 235p, 238p, 250p, 305p, 310p, 328p, 338p, 351p], + [ 249p, 252p, 304p, 319p, 324p, 343p, 353p, 406p], + [ 303p, 306p, 318p, 334p, 342p, 401p, 411p, 424p], + [ 316p, 319p, 331p, 350p, 358p, 417p, 427p, 440p], + [ 331p, 334p, 346p, 405p, 413p, 432p, 442p, 455p], + [ 346p, 349p, 401p, 420p, 428p, 447p, 457p, 510p], + [ 401p, 404p, 416p, 435p, 443p, 502p, 512p, 525p], + [ 416p, 419p, 431p, 450p, 458p, 517p, 527p, 540p], + [ 431p, 434p, 446p, 505p, 513p, 532p, 542p, 555p], + [ 446p, 449p, 501p, 520p, 528p, 546p, 553p, 606p], + [ 502p, 505p, 517p, 536p, 544p, 557p, 604p, 617p], + [ 522p, 525p, 537p, 553p, 558p, 611p, 618p, 631p], + [ 542p, 545p, 555p, 608p, 613p, 626p, 633p, 646p], + [ 608p, 610p, 620p, 633p, 638p, 651p, 658p, 711p], + [ 638p, 640p, 650p, 703p, 708p, 721p, 728p, 741p], + [ 708p, 710p, 720p, 733p, 738p, 751p, 758p, 811p], + [ 738p, 740p, 750p, 803p, 808p, 821p, 828p, 841p], + [ 808p, 810p, 820p, 833p, 838p, 851p, 858p, 911p], + [ 838p, 840p, 850p, 903p, 908p, 921p, 928p, 941p], + [ 908p, 910p, 920p, 933p, 938p, 951p, 958p, 1011p], + [ 1008p, 1010p, 1020p, 1033p, 1038p, 1051p, 1058p, 1111p], + [ 1108p, 1110p, 1120p, 1133p, 1138p, 1151p, 1158p, 1211x], + [ 1208x, 1210x, 1220x, 1233x, 1238x, 1251x, 1258x, 111x] +] +stop_times_saturday: [ + [ -, -, -, -, -, 553a, 602a, 615a], + [ -, -, -, -, -, 623a, 632a, 645a], + [ 615a, 618a, 626a, 638a, 644a, 653a, 702a, 714a], + [ 645a, 648a, 658a, 710a, 716a, 725a, 734a, 747a], + [ 715a, 718a, 726a, 738a, 744a, 753a, 802a, 814a], + [ 745a, 748a, 758a, 810a, 816a, 825a, 834a, 847a], + [ 813a, 816a, 826a, 838a, 844a, 853a, 902a, 914a], + [ 837a, 840a, 852a, 907a, 913a, 922a, 931a, 944a], + [ 907a, 910a, 922a, 937a, 943a, 952a, 1001a, 1014a], + [ 937a, 940a, 952a, 1007a, 1013a, 1022a, 1031a, 1044a], + [ 1007a, 1010a, 1022a, 1037a, 1043a, 1052a, 1101a, 1114a], + [ 1037a, 1040a, 1052a, 1107a, 1113a, 1122a, 1131a, 1144a], + [ 1107a, 1110a, 1122a, 1137a, 1143a, 1152a, 1201p, 1214p], + [ 1137a, 1140a, 1152a, 1207p, 1213p, 1222p, 1231p, 1244p], + [ 1207p, 1210p, 1222p, 1237p, 1243p, 1252p, 101p, 114p], + [ 1237p, 1240p, 1252p, 107p, 113p, 122p, 131p, 144p], + [ 107p, 110p, 122p, 137p, 143p, 152p, 201p, 214p], + [ 137p, 140p, 152p, 207p, 213p, 222p, 231p, 244p], + [ 207p, 210p, 222p, 237p, 243p, 252p, 301p, 314p], + [ 237p, 240p, 252p, 307p, 313p, 322p, 331p, 344p], + [ 307p, 310p, 322p, 337p, 343p, 352p, 401p, 414p], + [ 337p, 340p, 352p, 407p, 413p, 422p, 431p, 444p], + [ 407p, 410p, 422p, 437p, 443p, 452p, 501p, 514p], + [ 437p, 440p, 452p, 507p, 513p, 522p, 531p, 544p], + [ 507p, 510p, 522p, 537p, 543p, 552p, 601p, 614p], + [ 537p, 540p, 552p, 607p, 613p, 622p, 631p, 644p], + [ 607p, 610p, 622p, 637p, 643p, 652p, 701p, 714p], + [ 637p, 640p, 652p, 707p, 713p, 722p, 731p, 744p], + [ 707p, 710p, 722p, 737p, 743p, 752p, 801p, 814p], + [ 737p, 740p, 752p, 807p, 813p, 822p, 831p, 844p], + [ 807p, 810p, 822p, 837p, 843p, 852p, 901p, 914p], + [ 837p, 840p, 852p, 907p, 913p, 922p, 931p, 944p], + [ 907p, 910p, 922p, 937p, 943p, 952p, 1001p, 1014p], + [ 937p, 940p, 952p, 1006p, 1012p, 1020p, 1028p, 1041p], + [ 1007p, 1010p, 1020p, 1032p, 1038p, 1046p, 1054p, 1107p], + [ 1037p, 1040p, 1050p, 1102p, 1108p, 1116p, 1124p, 1137p], + [ 1107p, 1110p, 1120p, 1132p, 1138p, 1146p, 1154p, 1207x], + [ 1207x, 1210x, 1220x, 1232x, 1238x, 1246x, 1254x, 107x] +] +stop_times_sunday: [ + [ -, -, -, -, -, 642a, 650a, 707a], + [ -, -, -, -, 659a, 712a, 720a, 737a], + [ 657a, 700a, 710a, 722a, 729a, 742a, 750a, 807a], + [ 727a, 730a, 740a, 752a, 759a, 812a, 820a, 837a], + [ 757a, 800a, 810a, 822a, 829a, 842a, 850a, 907a], + [ 827a, 830a, 840a, 852a, 859a, 912a, 920a, 937a], + [ 857a, 900a, 910a, 922a, 929a, 942a, 950a, 1007a], + [ 927a, 930a, 940a, 952a, 959a, 1012a, 1020a, 1037a], + [ 957a, 1000a, 1010a, 1022a, 1029a, 1042a, 1050a, 1107a], + [ 1027a, 1030a, 1040a, 1052a, 1059a, 1112a, 1120a, 1137a], + [ 1057a, 1100a, 1110a, 1122a, 1129a, 1142a, 1150a, 1207p], + [ 1127a, 1130a, 1140a, 1152a, 1159a, 1212p, 1220p, 1237p], + [ 1157a, 1200p, 1210p, 1222p, 1229p, 1242p, 1250p, 107p], + [ 1227p, 1230p, 1240p, 1252p, 1259p, 112p, 120p, 137p], + [ 1257p, 100p, 110p, 122p, 129p, 142p, 150p, 207p], + [ 127p, 130p, 140p, 152p, 159p, 212p, 220p, 237p], + [ 157p, 200p, 210p, 222p, 229p, 242p, 250p, 307p], + [ 227p, 230p, 240p, 252p, 259p, 312p, 320p, 337p], + [ 257p, 300p, 310p, 322p, 329p, 342p, 350p, 407p], + [ 327p, 330p, 340p, 352p, 359p, 412p, 420p, 437p], + [ 357p, 400p, 410p, 422p, 429p, 442p, 450p, 507p], + [ 427p, 430p, 440p, 452p, 459p, 512p, 520p, 537p], + [ 457p, 500p, 510p, 522p, 529p, 542p, 550p, 607p], + [ 527p, 530p, 540p, 552p, 559p, 612p, 620p, 637p], + [ 557p, 600p, 610p, 622p, 629p, 642p, 650p, 707p], + [ 627p, 630p, 640p, 652p, 659p, 712p, 720p, 737p], + [ 657p, 700p, 710p, 722p, 729p, 742p, 750p, 807p], + [ 727p, 730p, 740p, 752p, 759p, 812p, 820p, 837p], + [ 757p, 800p, 810p, 822p, 829p, 842p, 850p, 907p], + [ 827p, 830p, 840p, 852p, 859p, 912p, 920p, 937p], + [ 857p, 900p, 910p, 922p, 929p, 942p, 950p, 1007p], + [ 957p, 1000p, 1010p, 1022p, 1029p, 1042p, 1050p, 1107p], + [ 1057p, 1100p, 1110p, 1122p, 1129p, 1142p, 1150p, 1207x] +] --- /dev/null +++ b/origin-src/wlach-halifax-transit-feed-fef68c1/81-to-downtown-halifax.yml @@ -1,1 +1,38 @@ +short_name: 81 +long_name: To Downtown Halifax +time_points: [ 7125, 6216, 6564, 8214, 6087, 8414 ] +between_stops: + 6216-6564: [ 6982, 6985, 8262 ] + 6564-8214: [ 6197, 6193, 6203, 8633, 8193, 8202, 8182, 8213, 8178, 8205, 8190 ] + 8214-6087: [ 8183, 8196, 8195, 8194, 8328, 8337, 8333, 8336, 8327, 8338 ] +stop_times: [ + [ 610a, 620a, 627a, 637a, 647a, 649a], + [ 640a, 650a, 657a, 708a, 725a, 727a], + [ 710a, 720a, 728a, 739a, 756a, 758a], + [ 740a, 750a, 758a, 809a, 826a, 828a], + [ 810a, 820a, 828a, 839a, 856a, 858a], + [ 840a, 850a, 858a, 909a, 926a, 928a], + [ 910a, 920a, 928a, 939a, 956a, 958a], + [ 940a, 950a, 958a, 1009a, 1026a, 1028a], + [ 1010a, 1020a, 1028a, 1039a, 1056a, 1058a], + [ 1040a, 1050a, 1058a, 1109a, 1126a, 1128a], + [ 1110a, 1120a, 1128a, 1139a, 1156a, 1158a], + [ 1140a, 1150a, 1158a, 1209p, 1226p, 1228p], + [ 1210p, 1220p, 1228p, 1239p, 1256p, 1258p], + [ 1240p, 1250p, 1258p, 109p, 126p, 128p], + [ 110p, 120p, 128p, 139p, 156p, 158p], + [ 140p, 150p, 158p, 209p, 226p, 228p], + [ 210p, 220p, 228p, 239p, 256p, 258p], + [ 240p, 250p, 258p, 309p, 326p, 328p], + [ 310p, 320p, 328p, 339p, 356p, 358p], + [ 340p, 350p, 358p, 409p, 426p, 428p], + [ 414p, 424p, 432p, 443p, 500p, 502p], + [ 444p, 454p, 502p, 513p, 530p, 532p], + [ 514p, 524p, 532p, 543p, 600p, 602p], + [ 544p, 554p, 602p, 613p, 623p, 625p], + [ 610p, 620p, 624p, 635p, 645p, 647p], + [ 640p, 650p, 654p, 705p, 715p, 717p], + [ 710p, 720p, 724p, 735p, 745p, 747p], + [ 740p, 750p, 754p, 805p, 815p, 817p] +] --- /dev/null +++ b/origin-src/wlach-halifax-transit-feed-fef68c1/81-to-hemlock-ravine.yml @@ -1,1 +1,38 @@ +short_name: 81 +long_name: To Hemlock Ravine +time_points: [ 8414, 6105, 8184, 6563, 6219, 7125 ] +between_stops: + 6105-8184: [ 6108, 6103, 6102, 6122, 8331, 8330, 8334, 8335, 8329 ] + 8184-6563: [ 8206, 8221, 8222, 8181, 8210, 8201, 8251, 8629, 8634, 6192, 6196, 6201, 6200, 6199, 6198 ] + 6563-6219: [ 6565, 6984, 6983 ] +stop_times: [ + [ -, -, -, -, 600a, 610a], + [ 603a, 606a, 616a, 626a, 630a, 640a], + [ 633a, 636a, 646a, 656a, 700a, 710a], + [ 655a, 658a, 710a, 725a, 730a, 740a], + [ 725a, 728a, 740a, 755a, 800a, 810a], + [ 755a, 758a, 810a, 825a, 830a, 840a], + [ 825a, 828a, 840a, 855a, 900a, 910a], + [ 855a, 858a, 910a, 925a, 930a, 940a], + [ 925a, 928a, 940a, 955a, 1000a, 1010a], + [ 955a, 958a, 1010a, 1025a, 1030a, 1040a], + [ 1025a, 1028a, 1040a, 1055a, 1100a, 1110a], + [ 1055a, 1058a, 1110a, 1125a, 1130a, 1140a], + [ 1125a, 1128a, 1140a, 1155a, 1200p, 1210p], + [ 1155a, 1158a, 1210p, 1225p, 1230p, 1240p], + [ 1225p, 1228p, 1240p, 1255p, 100p, 110p], + [ 1255p, 1258p, 110p, 125p, 130p, 140p], + [ 125p, 128p, 140p, 155p, 200p, 210p], + [ 155p, 158p, 210p, 225p, 230p, 240p], + [ 225p, 228p, 240p, 255p, 300p, 310p], + [ 255p, 258p, 310p, 325p, 330p, 340p], + [ 322p, 325p, 337p, 356p, 404p, 414p], + [ 352p, 355p, 407p, 426p, 434p, 444p], + [ 422p, 425p, 437p, 456p, 504p, 514p], + [ 452p, 455p, 507p, 526p, 534p, 544p], + [ 525p, 528p, 540p, 555p, 600p, 610p], + [ 600p, 602p, 612p, 625p, 630p, 640p], + [ 630p, 632p, 642p, 655p, 700p, 710p], + [ 700p, 702p, 712p, 725p, 730p, 740p] +] --- /dev/null +++ b/origin-src/wlach-halifax-transit-feed-fef68c1/9-to-mumford.yml @@ -1,1 +1,45 @@ +short_name: 9 +long_name: To Mumford Terminal +time_points: [ 8649, 8409, 6087, 6583, 7284 ] +between_stops: + 6087-6583: [ 6089, 6116, 6091, 6127, 6094, 6110, 6119, 6128, 6580, 6588, 6584, 6583 ] + 6583-7284: [ 6585, 7096, 8560 ] + 8409-6087: [ 6096, 6113, 6124 ] + 8649-6087: [ 6096, 6113, 6124 ] +stop_times: [ + [ 620a, -, 630a, 641a, 652a], + [ 650a, -, 703a, 714a, 725a], + [ 715a, -, 728a, 739a, 750a], + [ 735a, -, 748a, 759a, 810a], + [ 755a, -, 808a, 819a, 830a], + [ 815a, -, 828a, 839a, 850a], + [ 837a, -, 850a, 901a, 912a], + [ 907a, -, 920a, 931a, 942a], + [ 937a, -, 950a, 1001a, 1012a], + [ 1007a, -, 1020a, 1031a, 1042a], + [ 1037a, -, 1050a, 1101a, 1112a], + [ 1107a, -, 1120a, 1131a, 1142a], + [ 1137a, -, 1150a, 1201p, 1212p], + [ 1207p, -, 1220p, 1231p, 1242p], + [ 1237p, -, 1250p, 101p, 112p], + [ 107p, -, 120p, 131p, 142p], + [ 137p, -, 150p, 201p, 212p], + [ 207p, -, 220p, 231p, 242p], + [ 237p, -, 250p, 301p, 312p], + [ 307p, -, 320p, 331p, 342p], + [ 327p, -, 340p, 351p, 402p], + [ 347p, -, 400p, 411p, 422p], + [ 407p, -, 420p, 431p, 442p], + [ 427p, -, 440p, 451p, 502p], + [ 447p, -, 500p, 511p, 522p], + [ 507p, -, 520p, 531p, 542p], + [ 537p, -, 550p, 601p, 612p], + [ 608p, -, 620p, 631p, 640p], + [ 658p, -, 710p, 721p, 732p], + [ 758p, -, 810p, 821p, 832p], + [ -, 859p, 910p, 920p, 931p], + [ -, 1000p, 1010p, 1019p, 1030p], + [ -, 1100p, 1110p, 1119p, 1130p], + [ -, 1200x, 1210x, 1219x, 1230x] +] --- /dev/null +++ b/origin-src/wlach-halifax-transit-feed-fef68c1/9-to-point-pleasant-park.yml @@ -1,1 +1,44 @@ +short_name: 9 +long_name: To Point Pleasant Park +time_points: [ 7284, 7094, 6105, 8649 ] +between_stops: + 7284-7094: [ 7274, 6409, 6403, 6407, 8552, 8563, 8553 ] + 7094-6105: [ 6581, 6582, 6586, 6587, 6100, 6120, 6109, 6095, 6090, 6107, 6115, 6088, 6104, 6125 ] + 6105-8649: [ 6108, 6103, 6102, 6114, 6097 ] +stop_times: [ + [ 550a, 600a, 610a, 619a], + [ 615a, 625a, 635a, 646a], + [ 635a, 646a, 659a, 713a], + [ 655a, 707a, 720a, 734a], + [ 715a, 727a, 740a, 754a], + [ 735a, 747a, 800a, 814a], + [ 755a, 807a, 820a, 834a], + [ 825a, 837a, 850a, 904a], + [ 855a, 906a, 919a, 933a], + [ 925a, 935a, 948a, 1002a], + [ 955a, 1005a, 1018a, 1032a], + [ 1025a, 1035a, 1048a, 1102a], + [ 1055a, 1105a, 1118a, 1132a], + [ 1125a, 1135a, 1148a, 1202p], + [ 1155a, 1205p, 1218p, 1232p], + [ 1225p, 1235p, 1248p, 102p], + [ 1255p, 105p, 118p, 132p], + [ 125p, 135p, 148p, 202p], + [ 155p, 205p, 218p, 232p], + [ 225p, 237p, 250p, 304p], + [ 245p, 257p, 310p, 324p], + [ 305p, 317p, 330p, 344p], + [ 325p, 337p, 350p, 404p], + [ 345p, 357p, 410p, 424p], + [ 405p, 417p, 430p, 444p], + [ 425p, 437p, 450p, 504p], + [ 455p, 507p, 520p, 534p], + [ 525p, 537p, 550p, 602p], + [ 622p, 632p, 645p, 657p], + [ 722p, 732p, 745p, 757p], + [ 822p, 832p, 845p, -], + [ 922p, 932p, 942p, -], + [ 1022p, 1032p, 1042p, -], + [ 1122p, 1132p, 1142p, -] +] --- /dev/null +++ b/origin-src/wlach-halifax-transit-feed-fef68c1/Makefile @@ -1,1 +1,36 @@ +default: hfxfeed.zip +hfxfeed.zip: hfxtable.yml createfeed.py + ./createfeed.py --input=hfxtable.yml --output=hfxfeed.zip + +ROUTE_FILES=1-to-dartmouth.yml 1-to-mumford.yml \ + 2-to-downtown-via-north.yml 2-to-wedgewood-via-main.yml \ + 3-to-shopping-malls.yml 3-to-manors.yml \ + 4-to-farnham-gate-via-rosedale.yml 4-to-downtown-via-north.yml \ + 5-to-springvale.yml 5-to-downtown.yml \ + 6-to-stonehaven.yml 6-to-downtown.yml \ + 7-robie-to-gottingen.yml 7-gottingen-to-robie.yml \ + 9-to-point-pleasant-park.yml 9-to-mumford.yml \ + 10-to-westphal.yml 10-to-dalhousie.yml \ + 14-to-leiblin-park.yml 14-to-universities-downtown.yml \ + 17-to-hospitals-universities.yml 17-to-lacewood.yml \ + 18-to-smu.yml 18-to-lacewood.yml \ + 20-to-herring-cove.yml 20-to-mumford-downtown.yml \ + 21-to-timberlea.yml 21-to-lacewood-halifax.yml \ + 23-to-timberlea.yml 23-to-mumford-halifax.yml \ + 41-to-dalhousie.yml 41-to-bridge-terminal.yml \ + 42-to-lacewood.yml 42-to-dalhousie.yml \ + 52-to-bridge-terminal-burnside.yml 52-to-lacewood-chain-lake-drive.yml \ + 58-to-lucien-drive.yml \ + 80-to-bedford-halifax.yml 80-to-bedford-sackville.yml \ + 81-to-downtown-halifax.yml 81-to-hemlock-ravine.yml + +hfxtable.yml: hfxtable.yml.in $(ROUTE_FILES) indent-route.pl + cp hfxtable.yml.in hfxtable.yml + @$(foreach ROUTE_FILE, $(ROUTE_FILES), \ + echo "Parsing $(ROUTE_FILE)"; \ + ./indent-route.pl < $(ROUTE_FILE) >> hfxtable.yml;) + +clean: + rm -f hfxtable.yml hfxfeed.zip *~ + --- /dev/null +++ b/origin-src/wlach-halifax-transit-feed-fef68c1/README @@ -1,1 +1,35 @@ +=== Introduction === +This distribution contains everything required to build a basic google transit +feed for Halifax Metro Transit, Nova Scotia, Canada. Note that it is woefully +incomplete at the moment. + +Requirements: GNU Make, Perl, Python 2.5. + +=== Usage === + +First, grab a copy of google transit feed tools: + +cd $HOME/src +wget http://googletransitdatafeed.googlecode.com/files/transitfeed-1.1.7.tar.gz +tar zxvf transitfeed-1.1.7.tar.gz + +Set PYTHONPATH to the python directory in the above checkout: + +export PYTHONPATH=$HOME/src/transitfeed-1.1.7/python + +Then just type "make" to build the feed. The output at the end is "feed.zip". +For fun, you can view this feed using the snazzy transit feed view application: + +$HOME/src/transitfeed-1.1.7/python/schedule_viewer.py --feed=hfxfeed.zip + +=== Copyright === + +With the exception of createfeed.py, which is licensed under the Apache Public +License, please consider all software tools in distribution to be in the public +domain. Use them for what you will. + +I believe the Metro Transit route data is considered factual information +which can not be copyrighted. Note, however, that Metro Transit and/or +the city of Halifax may have claim over its own name and other trademarks. + --- /dev/null +++ b/origin-src/wlach-halifax-transit-feed-fef68c1/add-between-times.pl @@ -1,1 +1,104 @@ +#!/usr/bin/perl +use strict; + +sub parse_time { + my ($time) = @_; + + my ($hour, $minute); + + if ($time =~ /a\Z/) { + $time =~ m/([0-9]+)([0-9][0-9])a/; + ($hour, $minute) = ($1, $2); + } elsif ($time =~ /p\Z/) { + $time =~ m/([0-9]+)([0-9][0-9])p/; + ($hour, $minute) = ($1, $2); + if ($hour < 12) { + $hour += 12; + } + } elsif ($time =~ /x\Z/) { + $time =~ m/([0-9]+)([0-9][0-9])x/; + ($hour, $minute) = ($1, $2); + if ($hour == 12) { + $hour += 12; + } else { + $hour += 24; + } + } elsif ($time =~ /^\ *-\Z/) { + ($hour, $minute) = (0, 0); + # no stop at this time + } else { + print "Should not happen! Time ('$time') misformed.\n"; + exit; + } + + return ($hour, $minute); +} + +my $num_intervals = $ARGV[0] or die "No num intervals given!"; +my $interval = $ARGV[1] or die "No interval given!"; + +my @times; + +$_ = ; +print $_; + +if ($_ !~ /^\#/) { + my @timestrs; + if ($_ =~ m/\[(.*)\]/) { + my $inner = $1; + @timestrs = split (/\,/, $inner); + + } else { + @timestrs = split /\ /; + } + + foreach (@timestrs) { + my ($hour, $minute) = parse_time($_); + push @times, [ $hour, $minute ]; + } +} + +for (my $i=1; $i<($num_intervals+1); $i++) { + my $first = 1; + foreach (@times) { + my $mytime = $_; + my ($hour, $minute) = (@$mytime[0], @$mytime[1]); + if ($hour > 0 || $minute > 0) { + $minute += $interval * $i; + if ($minute > 59) { + $hour += int($minute / 60); + $minute = $minute % 60; + if ($minute < 10) { + $minute = "0" . $minute; + } + } + } + + sub print_time { + my ($hour, $minute) = @_; + if ($hour == 0 && $minute == 0) { + print "-"; + } else { + if ($hour < 12) { + print "$hour$minute" . "a"; + } else { + if ($hour > 12) { + $hour -= 12; + } + print "$hour$minute" . "p"; + } + } + } + + if (!$first) { + print " "; + print_time($hour, $minute); + } else { + $first = 0; + print_time($hour, $minute); + } + } +print "\n"; +} + --- /dev/null +++ b/origin-src/wlach-halifax-transit-feed-fef68c1/createfeed.py @@ -1,1 +1,194 @@ +#!/usr/bin/python +# Copyright (C) 2007 Google Inc. +# Copyright (C) 2008-2009 William Lachance +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import transitfeed +from transitfeed import ServicePeriod +from optparse import OptionParser +import yaml, sys, os.path +import re + +stops = {} + +def ProcessOptions(schedule, options): + + # the follow features are REQUIRED + agency_name = options.get('agency_name') + agency_url = options.get('agency_url') + agency_timezone = options.get('agency_timezone') + + service_periods = [] + + service_periods.append(ServicePeriod(id="weekday")) + service_periods[0].SetWeekdayService() + service_periods.append(ServicePeriod(id="saturday")) + service_periods[1].SetDayOfWeekHasService(5) + service_periods.append(ServicePeriod(id="sunday")) + service_periods[2].SetDayOfWeekHasService(6) + + # the service period options are, well, optional + for service_period in service_periods: + if options.get('start_date'): + service_period.SetStartDate(options['start_date']) + if options.get('end_date'): + service_period.SetEndDate(options['end_date']) + if options.get('add_date'): + service_period.SetDateHasService(options['add_date']) + if options.get('remove_date'): + service_period.SetDateHasService(options['remove_date'], + has_service=False) + + # Add all service period objects to the schedule + schedule.SetDefaultServicePeriod(service_periods[0], validate=False) + schedule.AddServicePeriodObject(service_periods[1], validate=False) + schedule.AddServicePeriodObject(service_periods[2], validate=False) + + if not (agency_name and agency_url and agency_timezone): + print "You must provide agency information" + + schedule.NewDefaultAgency(agency_name=agency_name, agency_url=agency_url, + agency_timezone=agency_timezone) + + +# Remove any stops from stopsdata that aren't serviced by any routes in +# routedata. +def PruneStops(stopsdata, routedata): + stopset = set() + for route in routedata: + stopset.update(route['time_points']) + for between_list in route['between_stops']: + stopset.update(route['between_stops'][between_list]) + + toprune = list() + for i, stop in enumerate(stopsdata): + if stop['stop_code'] not in stopset: + print "Pruning unused stop %s " % stop['stop_code'] + toprune.append(i) + + # Prune the list in reverse order, as the indices will change otherwise. + toprune.sort() + toprune.reverse() + for prunee in toprune: + del stopsdata[prunee] + +def AddStops(schedule, stopsdata): + for stopdata in stopsdata: + stop_code = stopdata['stop_code'] + # we have to manually add the stop instead of using AddStop, cause + # we want the stop_code + stop_id = unicode(len(schedule.stops)) + stop = transitfeed.Stop(stop_id=stop_id, lat=stopdata['lat'], + lng=stopdata['lng'], name=stopdata['name'], + stop_code=stop_code) + schedule.AddStopObject(stop) + stops[stop_code] = stop + + +def AddTripsToSchedule(schedule, route, routedata, service_id, stop_times): + + service_period = schedule.GetServicePeriod(service_id) + timerex = re.compile('^(\d+)(\d\d)([a-z])$') + + for trip in stop_times: + t = route.AddTrip(schedule, headsign=routedata['long_name'], service_period=service_period) + + if len(trip) > len(routedata['time_points']): + print "Length of trip (%s) exceeds number of time points (%s)!" % (len(trip), len(routedata['time_points'])) + class StopTimesError(Exception): pass + raise StopTimesError() + else: + trip_stops = [] # Build a list of (time, stop_code) tuples + i = 0 + for stop_time in trip: + matches = timerex.match(str(stop_time)) + if matches and len(matches.groups()) == 3: + hour, minute, shift = (int(matches.group(1)), + str(matches.group(2)), + matches.group(3)) + if shift == 'p' and hour < 12: + hour += 12 + elif shift == 'x': + if hour == 12: + hour += 12 + else: + hour += 24 + + # munge hours and minutes if they're < 10 + if hour < 10: + hour = "0" + str(hour) + + clock_time = str(hour) + ":" + minute + ":00" + seconds = transitfeed.TimeToSecondsSinceMidnight(clock_time) + trip_stops.append((seconds, routedata['time_points'][i]) ) + elif re.search(r'^\-$', str(stop_time)): + pass + else: + class InvalidStopTimeError(Exception): pass + raise InvalidStopTimeError, 'Bad stoptime "%s"' % stop_time + i = i + 1 + + trip_stops.sort() # Sort by time + prev_stop_code = None + between_stops = routedata.get('between_stops') + + for (time, stop_code) in trip_stops: + if prev_stop_code and between_stops: + between_stop_list = between_stops.get('%s-%s' % (prev_stop_code, stop_code)) + if between_stop_list: + for between_stop_code in between_stop_list: + t.AddStopTime(stop=stops[between_stop_code]) + + t.AddStopTime(stop=stops[stop_code], arrival_secs=time, + departure_secs=time) + prev_stop_code = stop_code + + + +def AddRouteToSchedule(schedule, routedata): + r = schedule.AddRoute(short_name=str(routedata['short_name']), + long_name=routedata['long_name'], + route_type='Bus') + AddTripsToSchedule(schedule, r, routedata, "weekday", routedata['stop_times']) + if routedata.get('stop_times_saturday'): + AddTripsToSchedule(schedule, r, routedata, "saturday", routedata['stop_times_saturday']) + if routedata.get('stop_times_sunday'): + AddTripsToSchedule(schedule, r, routedata, "sunday", routedata['stop_times_sunday']) + +def main(): + parser = OptionParser() + parser.add_option('--input', dest='input', + help='Path of input file') + parser.add_option('--output', dest='output', + help='Path of output file, should end in .zip') + parser.set_defaults(output='feed.zip') + (options, args) = parser.parse_args() + + schedule = transitfeed.Schedule() + stream = open(options.input, 'r') + data = yaml.load(stream) + ProcessOptions(schedule, data['options']) + PruneStops(data['stops'], data['routes']) + AddStops(schedule, data['stops']) + + for route in data['routes']: + AddRouteToSchedule(schedule, route) + + schedule.WriteGoogleTransitFeed(options.output) + + +if __name__ == '__main__': + main() + --- /dev/null +++ b/origin-src/wlach-halifax-transit-feed-fef68c1/hfxtable.yml.in @@ -1,1 +1,530 @@ - +options: + start_date: 20080315 + end_date: 20081215 + remove_date: 20080704 + agency_name: Halifax Metro Transit + agency_url: http://www.halifax.ca/metrotransit/ + agency_timezone: America/Halifax + +stops: +# the following waypoints are derived from google maps / google earth + - { name: Robie & Lady Hammond, stop_code: 8208, lat: 44.6647155, lng: -63.61677 } + - { name: Kearney Lake & Grosvenor, stop_code: 7019, lat: 44.681611, lng: -63.667767 } + - { name: Kearney Lake & Wedgewood, stop_code: 7023, lat: 44.681611, lng: -63.667767 } + - { name: Lacewood Terminal, stop_code: 7087, lat: 44.661511, lng: -63.656975 } + - { name: Lacewood Terminal, stop_code: 7086, lat: 44.661511, lng: -63.656975 } + - { name: Mumford Road & Joseph Howe, stop_code: 6612, lat: 44.648528, lng: -63.629597 } + - { name: Alma & Dutch Village, stop_code: 6032, lat: 44.659581, lng: -63.630681 } + - { name: Dunbrack & Main, stop_code: 6597, lat: 44.652714, lng: -63.649692 } + - { name: Joseph Howe Manor, stop_code: 8430, lat: 44.634822, lng: -63.576186 } + - { name: J.H. MacKenzie Manor, stop_code: 6791, lat: 44.637589, lng: -63.571281 } + - { name: Gordon B. Isnor Manor, stop_code: 6505, lat: 44.651589, lng: -63.585292 } + - { name: Ross Street & Dunbrack, stop_code: 6601, lat: 44.676468, lng: -63.66354 } + - { name: Dutch Village & Deal Street, stop_code: 6611, lat: 44.659069, lng: -63.631546 } + - { name: Downs & Milson, stop_code: 6578, lat: 44.641788, lng: -63.636932 } + - { name: Chebucto & Oxford (north bound), stop_code: 6396, lat: 44.648689, lng: -63.601871 } + - { name: Chebucto & Oxford (south bound), stop_code: 6397, lat: 44.648689, lng: -63.601871 } + - { name: Point Pleasant Park, stop_code: 8649, lat: 44.625298, lng: -63.56406 } + - { name: Tower Road Turing Loop, stop_code: 8409, lat: 44.6259, lng: -63.574524 } +# the following waypoints are derived from geobase + - { name: Sunnyside (Bedford Highway & Dartmouth), stop_code: 6236, lat: 44.731726, lng: -63.657578 } + - { name: Sunnyside (Bedford Highway & Dartmouth), stop_code: 6238, lat: 44.731726, lng: -63.657578 } + - { name: Stonehaven & Glenmore, stop_code: 8361, lat: 44.635224, lng: -63.630198 } + - { name: Seton Road & Bedford Highway (Mount Saint Vincent University), stop_code: 6216, lat: 44.670526, lng: -63.641589 } + - { name: Seton Road & Bedford Highway (Mount Saint Vincent University), stop_code: 6219, lat: 44.670526, lng: -63.641589 } + - { name: Walker Avenue & Old Sackville Road (Sackville Terminal), stop_code: 8260, lat: 44.769474, lng: -63.696749 } + - { name: Cobequid Drive and Memory Lane (Cobequid Terminal), stop_code: 8646, lat: 44.753835, lng: -63.66313 } + - { name: Cobequid Drive and Memory Lane (Cobequid Terminal), stop_code: 6445, lat: 44.753835, lng: -63.66313 } + - { name: Ash Lake & Chain Lake Drive (TeleTech), stop_code: 6390, lat: 44.637817, lng: -63.664118 } + - { name: Victoria Road & Highfield Park Drive (Highfield Terminal), stop_code: 6923, lat: 44.682821, lng: -63.590579 } + - { name: Victoria Road & Highfield Park Drive (Highfield Terminal), stop_code: 6918, lat: 44.682821, lng: -63.590579 } + - { name: Macdonald Avenue & Ilsley Avenue (near Metro Transit H.Q.), stop_code: 6949, lat: 44.695728, lng: -63.583946 } + - { name: Macdonald Avenue & Ilsley Avenue (near Metro Transit H.Q.), stop_code: 7205, lat: 44.695728, lng: -63.583946 } + - { name: Wright Avenue & Macdonald, stop_code: 7153, lat: 44.701754, lng: -63.600508 } + - { name: Akerley & Colford, stop_code: 6767, lat: 44.716152, lng: -63.586565 } + - { name: Cowie Hill & Ridge Valley, stop_code: 8167, lat: 44.63064, lng: -63.622718 } + - { name: Cowie Hill & Ridge Valley, stop_code: 8168, lat: 44.63064, lng: -63.622718 } + - { name: Juniper Crescent & Leiblin Drive, stop_code: 7143, lat: 44.595027, lng: -63.636932 } + +# major timepoints for the 20 +# this is a big lie... I have no actual idea exactly where the dockyards are +# (not really wanting to waltz around a military installation with a gps), +# and decided to put them on some random place on provo wallis + - { name: Dockyard, stop_code: 6568, lat: 44.654879, lng: -63.580571, bad: 1 } + - { name: Dockyard, stop_code: 6572, lat: 44.654879, lng: -63.580571, bad: 1 } + - { name: Greystone (turning loop), stop_code: 6797, lat: 44.599446, lng: -63.614456 } + - { name: Greystone (turning loop), stop_code: 6800, lat: 44.599446, lng: -63.614456 } + - { name: Herring Cove & Fotherby, stop_code: 6851, lat: 44.588541, lng: -63.593062 } + - { name: Herring Cove & Fotherby, stop_code: 6859, lat: 44.588541, lng: -63.593062 } + - { name: Herring Cove (St. Paul's Ave & School), stop_code: 7121, lat: 44.571368, lng: -63.561739 } +# the following waypoints are derived from openstreetmap + - { name: Titus & Main, stop_code: 7166, lat: 44.6594, lng: -63.6343 } + - { name: Spring Garden & South Park, stop_code: 8308, lat: 44.6422, lng: -63.5797 } + - { name: Romans & Bayers, stop_code: 6197, lat: 44.6526, lng: -63.6214 } + - { name: Coleman & Bayers, stop_code: 6193, lat: 44.6516, lng: -63.6178 } + - { name: Connolly & Bayers, stop_code: 6203, lat: 44.6530, lng: -63.6132 } + - { name: Upper Water & Cornwallis, stop_code: 8414, lat: 44.6539, lng: -63.5814 } + - { name: Oxford & Young, stop_code: 7423, lat: 44.6543, lng: -63.6097 } + - { name: Larry Uteck Boulevard & Bedford Highway, stop_code: 7125, lat: 44.6989, lng: -63.6639 } + - { name: Dorothea & Lucien, stop_code: 6575, lat: 44.6784, lng: -63.5109, bad: 1 } + - { name: Penhorn Terminal, stop_code: 7445, lat: 44.6730, lng: -63.5413, bad: 1 } + - { name: Alderney Ferry Terminal, stop_code: 6031, lat: 44.6645, lng: -63.5685 } + - { name: South & LeMarchant, stop_code: 7144, lat: 44.6359, lng: -63.5895 } + - { name: Robie & Young, stop_code: 8269, lat: 44.6597, lng: -63.6021 } + - { name: Robie & Spring Garden (north bound), stop_code: 8185, lat: 44.6407, lng: -63.5867 } + - { name: Summer Street (QEII Health Sciences), stop_code: 8363, lat: 44.6457, lng: -63.5856, bad: 1 } + - { name: Chain Lake & Lakelands, stop_code: 6160, lat: 44.6375, lng: -63.6691 } + - { name: Greenwood Heights, stop_code: 6315, lat: 44.6553, lng: -63.7361, bad: 1 } + - { name: Glengary Gardens, stop_code: 6722, lat: 44.6605, lng: -63.7467, bad: 1 } + - { name: Summer Street (QEII Health Sciences), stop_code: 8364, lat: 44.6457, lng: -63.5856, bad: 1 } + - { name: Chain Lake & Lakelands, stop_code: 6189, lat: 44.6375, lng: -63.6691 } + - { name: Greenwood Heights, stop_code: 6317, lat: 44.6553, lng: -63.7361, bad: 1 } + +# the following were taken by wlach's GPS device + +# mumford rd. and chebucto, moving to terminal (going downtown afterwards) + - { name: Mumford & Chebucto, stop_code: 7276, lat: 44.64591, lng: -63.61571 } + +# mumford rd. and chebucto, moving out of terminal (going away from dwntwn) + - { name: Mumford & Leppert, stop_code: 7273, lat: 44.64604, lng: -63.61616 } + +# robie stops + - { name: Robie & Spring Garden, stop_code: 8332, lat: 44.64055, lng: -63.58652 } + - { name: Robie & Binney (north bound), stop_code: 8219, lat: 44.64235, lng: -63.58776 } + - { name: Robie & Cherry (north bound), stop_code: 8179, lat: 44.64426, lng: -63.58869 } + - { name: North & Gottingen, stop_code: 7346, lat: 44.65704, lng: -63.59146 } + - { name: North & Gottingen, stop_code: 6781, lat: 44.65768, lng: -63.59172 } + - { name: North & Gottingen, stop_code: 6769, lat: 44.65748, lng: -63.59123 } + - { name: Robie & Bliss (south bound), stop_code: 8194, lat: 44.64158, lng: -63.58758 } + - { name: Robie & Jubilee (south bound), stop_code: 8195, lat: 44.64328, lng: -63.58837 } + - { name: Robie & Cherry (south bound), stop_code: 8196, lat: 44.64467, lng: -63.58923 } + - { name: Robie & Shirley (south bound), stop_code: 8183, lat: 44.64612, lng: -63.59004 } + - { name: Robie & Quinpool (south bound), stop_code: 8214, lat: 44.64768, lng: -63.59078 } + - { name: Robie & Welsford (south bound), stop_code: 8190, lat: 44.64857, lng: -63.59114 } + - { name: Robie & Cunard (south bound), stop_code: 8205, lat: 44.65038, lng: -63.59227 } + - { name: Robie & Charles (south bound), stop_code: 8178, lat: 44.65305, lng: -63.59373 } + - { name: Robie & North (south bound), stop_code: 8213, lat: 44.65498, lng: -63.59537 } + - { name: Robie & May (south bound), stop_code: 8182, lat: 44.65603, lng: -63.59684 } + - { name: Robie & Almon (south bound), stop_code: 8202, lat: 44.65759, lng: -63.59930 } + - { name: Robie & Young (south bound), stop_code: 8193, lat: 44.65899, lng: -63.60136 } + + - { name: Robie & North, stop_code: 7341, lat: 44.65499, lng: -63.59449 } + - { name: North & Windsor, stop_code: 7358, lat: 44.65234, lng: -63.59830 } + - { name: North & Clifton, stop_code: 7353, lat: 44.65381, lng: -63.59615 } + + - { name: North & Gottingen, stop_code: 7347, lat: 44.65706, lng: -63.59150 } + - { name: North & Maynard, stop_code: 7354, lat: 44.65610, lng: -63.59275 } + - { name: North & Robie, stop_code: 7342, lat: 44.65448, lng: -63.59507 } + - { name: North & Clifton, stop_code: 7352, lat: 44.65361, lng: -63.59630 } + - { name: North & Windsor, stop_code: 7357, lat: 44.65211, lng: -63.59844 } + - { name: Robie & Quinpool (north bound), stop_code: 8184, lat: 44.64795, lng: -63.59068 } + - { name: Robie & Cunard (north bound), stop_code: 8206, lat: 44.65001, lng: -63.59176 } + - { name: Robie & Garrick (north bound), stop_code: 8221, lat: 44.65171, lng: -63.59281 } + - { name: Robie & Willow (north bound), stop_code: 8222, lat: 44.65372, lng: -63.59403 } + - { name: Robie & North (north bound), stop_code: 8181, lat: 44.65494, lng: -63.59510 } + - { name: Robie & May (north bound), stop_code: 8210, lat: 44.65609, lng: -63.59674 } + - { name: Robie & Almon (north bound), stop_code: 8201, lat: 44.65724, lng: -63.59846 } + - { name: Robie & Russell (north bound), stop_code: 8251, lat: 44.65879, lng: -63.60080 } + + - { name: North & Brunswick, stop_code: 7348, lat: 44.65833, lng: -63.59029 } + - { name: Novalea & Duffus, stop_code: 6583, lat: 44.66729, lng: -63.60655 } + - { name: Gottingen & Black, stop_code: 6785, lat: 44.65825, lng: -63.59252 } + - { name: Gottingen & Bloomfield, stop_code: 6768, lat: 44.65982, lng: -63.59452 } + - { name: Gottingen & Macara, stop_code: 6782, lat: 44.66113, lng: -63.59659 } + # all of these are headed towards the mackay bridge + - { name: Novalea & Livingstone, stop_code: 7377, lat: 44.66388, lng: -63.60046 } + - { name: Novalea & Stanley, stop_code: 7381, lat: 44.66479, lng: -63.60187 } + - { name: Novalea & Cabot, stop_code: 7374, lat: 44.66624, lng: -63.60420 } + - { name: Novalea & Devonshire, stop_code: 7369, lat: 44.66717, lng: -63.60568 } + - { name: Novalea & Vestby, stop_code: 7373, lat: 44.66871, lng: -63.60773 } + - { name: Novalea & Leeds, stop_code: 7376, lat: 44.67042, lng: -63.61035 } + - { name: Novalea (Samuel Prince Manor), stop_code: 7380, lat: 44.67160, lng: -63.61214 } + - { name: Novalea (Samuel Prince Manor), stop_code: 7379, lat: 44.67160, lng: -63.61214 } + - { name: Novalea (Samuel Prince Manor), stop_code: 7378, lat: 44.67176, lng: -63.61225 } + # the following three are headed AWAY from the mackay bridge + - { name: Novalea (Samuel Prince Manor), stop_code: 7367, lat: 44.67160, lng: -63.61234 } + - { name: Novalea (Samuel Prince Manor), stop_code: 7368, lat: 44.67160, lng: -63.61234 } + - { name: Novalea & Vestby, stop_code: 7382, lat: 44.66854, lng: -63.60771 } + - { name: Novalea & Duffus, stop_code: 7370, lat: 44.66755, lng: -63.60648 } + - { name: Novalea & Cabot, stop_code: 7362, lat: 44.66645, lng: -63.60485 } + - { name: Novalea & Stanley, stop_code: 7365, lat: 44.66493, lng: -63.60239 } + - { name: Novalea & Livingstone, stop_code: 7364, lat: 44.66404, lng: -63.60110 } + - { name: Gottingen & Young, stop_code: 6770, lat: 44.66227, lng: -63.59841 } + - { name: Gottingen & Sullivan, stop_code: 6783, lat: 44.66177, lng: -63.59768 } + - { name: Gottingen & Almon, stop_code: 6776, lat: 44.66018, lng: -63.59534 } + - { name: Gottingen & Black, stop_code: 6777, lat: 44.65850, lng: -63.59289 } + - { name: North & Brunswick, stop_code: 7351, lat: 44.65810, lng: -63.58967 } + - { name: North & Barrington (south bound), stop_code: 7343, lat: 44.65865, lng: -63.58818 } + - { name: Barrington & Duke (south bound), stop_code: 6105, lat: 44.64973, lng: -63.57519 } + - { name: Barrington & Duke (north bound), stop_code: 6087, lat: 44.65066, lng: -63.57561 } + # headed towards the north end on barrington + - { name: Barrington & Duke (north bound), stop_code: 6086, lat: 44.65041, lng: -63.57559 } + - { name: Barrington & Cornwallis (north bound), stop_code: 6089, lat: 44.65436, lng: -63.58181 } + # on barrington and north, just east of the macdonald bridge + - { name: Barrington & North, stop_code: 6116, lat: 44.65875, lng: -63.58761 } + # ditto, just west of the bridge + - { name: Barrington & North, stop_code: 6091, lat: 44.66043, lng: -63.58963 } + # headed west on barrington from north, up towards duffus (following the 9) + - { name: Barrington & Russell, stop_code: 6127, lat: 44.66310, lng: -63.59233 } + + # now headed towards the macdonald bridge from the west, on barrington + - { name: Barrington & Russell (south bound), stop_code: 6090, lat: 44.66327, lng: -63.59279 } + - { name: Barrington (south bound), stop_code: 6107, lat: 44.66067, lng: -63.59007 } + - { name: Barrington & North (south bound), stop_code: 6115, lat: 44.65940, lng: -63.58880 } + - { name: Gottingen & Young (north bound), stop_code: 7366, lat: 44.66267, lng: -63.59887 } + - { name: North & Brunswick (south bound), stop_code: 8638, lat: 44.65853, lng: -63.58986 } + - { name: Barrington (south bound), stop_code: 6088, lat: 44.65675, lng: -63.58524 } + - { name: Barrington & Cornwallis (south bound), stop_code: 6104, lat: 44.65441, lng: -63.58209 } + - { name: Barrington & Cornwallis (south bound), stop_code: 6125, lat: 44.65348, lng: -63.58080 } + - { name: Northridge Road (Richmond Manor), stop_code: 8165, lat: 44.67192, lng: -63.61618 } + - { name: Northridge Road (Richmond Manor), stop_code: 8164, lat: 44.67193, lng: -63.61618 } + - { name: Novalea & Sentinel, stop_code: 7361, lat: 44.67318, lng: -63.61473 } + - { name: Novalea & Ridge, stop_code: 7360, lat: 44.67323, lng: -63.61551 } + + - { name: Windsor & Charles (south bound), stop_code: 8559, lat: 44.65146, lng: -63.59704 } + - { name: Hunter & Cunard (south bound), stop_code: 6545, lat: 44.65035, lng: -63.59433 } + - { name: Hunter & Cunard (north bound), stop_code: 6544, lat: 44.65039, lng: -63.59469 } + - { name: Windsor & Charles (north bound), stop_code: 8558, lat: 44.65127, lng: -63.59656 } + + - { name: Windsor & North (north bound), stop_code: 8554, lat: 44.65252, lng: -63.59893 } + - { name: Windsor & Summit (north bound), stop_code: 8568, lat: 44.65307, lng: -63.60000 } + - { name: Windsor & Almon (north bound), stop_code: 8556, lat: 44.65420, lng: -63.60240 } + - { name: Windsor & London (north bound), stop_code: 8572, lat: 44.65505, lng: -63.60432 } + - { name: Windsor & Young (north bound), stop_code: 8571, lat: 44.65581, lng: -63.60568 } + + - { name: Windsor & Bayers (north bound), stop_code: 8574, lat: 44.65717, lng: -63.60773 } + - { name: Windsor & Ashton (north bound), stop_code: 8550, lat: 44.65829, lng: -63.60944 } + - { name: Windsor & Maxwell (north bound), stop_code: 8549, lat: 44.66015, lng: -63.61264 } + - { name: Windsor & Hood (north bound), stop_code: 8567, lat: 44.66087, lng: -63.61576 } + - { name: Windsor & Connolly (north bound), stop_code: 8570, lat: 44.66040, lng: -63.61868 } + - { name: Windsor & Strawberry (north bound), stop_code: 8552, lat: 44.66066, lng: -63.62020 } + + - { name: Windsor & Kempt (north bound), stop_code: 8563, lat: 44.66192, lng: -63.62175 } + - { name: Windsor & Kempt (north bound), stop_code: 8553, lat: 44.66236, lng: -63.62236 } + - { name: Windsor & Connaught, stop_code: 8560, lat: 44.66040, lng: -63.62022 } + + - { name: Windsor & Connolly (south bound), stop_code: 8551, lat: 44.66026, lng: -63.61910 } + - { name: Windsor & Hood (south bound), stop_code: 8562, lat: 44.66074, lng: -63.61580 } + - { name: Windsor & Maxwell (south bound), stop_code: 8565, lat: 44.66006, lng: -63.61291 } + - { name: Windsor & Ashton (south bound), stop_code: 8573, lat: 44.65826, lng: -63.60962 } + - { name: Windsor & Bayers (south bound), stop_code: 8555, lat: 44.65711, lng: -63.60793 } + + - { name: Windsor & Young (south bound), stop_code: 8561, lat: 44.65604, lng: -63.60638 } + - { name: Windsor & London (south bound), stop_code: 8564, lat: 44.65511, lng: -63.60471 } + - { name: Windsor & Almon (south bound), stop_code: 8557, lat: 44.65430, lng: -63.60298 } +# - { name: Windsor & Summit (south bound), stop_code: 856x, lat: 44.65322, lng: -63.60061 } + - { name: Windsor & North (south bound), stop_code: 8566, lat: 44.65227, lng: -63.59863 } + + - { name: Cogswell & Brunswick (north bound), stop_code: 6455, lat: 44.65094, lng: -63.57983 } + - { name: Cogswell & Gottingen (north bound), stop_code: 6773, lat: 44.65095, lng: -63.58157 } + - { name: Gottingen & Cornwallis (north bound), stop_code: 6778, lat: 44.65198, lng: -63.58330 } + - { name: Gottingen & Cunard (north bound), stop_code: 6779, lat: 44.65325, lng: -63.58519 } + - { name: Gottingen & Gerrish (north bound), stop_code: 6775, lat: 44.65407, lng: -63.58622 } + - { name: Gottingen & Charles (north bound), stop_code: 6787, lat: 44.65573, lng: -63.58866 } + + - { name: Gottingen & Charles (south bound), stop_code: 6774, lat: 44.65557, lng: -63.58865 } + - { name: Gottingen & Gerrish (south bound), stop_code: 6780, lat: 44.65489, lng: -63.58771 } + - { name: Gottingen & Cunard (south bound), stop_code: 6786, lat: 44.65361, lng: -63.58581 } + - { name: Gottigen & Cornwallis (south bound), stop_code: 6790, lat: 44.65200, lng: -63.58332 } + - { name: Gottingen & Falkland (south bound), stop_code: 6771, lat: 44.65089, lng: -63.58185 } + + # following stops are for routes heading from dartmouth to mumford + - { name: North & Dublin, stop_code: 7344, lat: 44.65061, lng: -63.60074 } + - { name: North & Oxford, stop_code: 7356, lat: 44.64929, lng: -63.60253 } + - { name: Chebucto & Connolly, stop_code: 6405, lat: 44.64818, lng: -63.60497 } + - { name: Chebucto & Connaught, stop_code: 6404, lat: 44.64759, lng: -63.60729 } + - { name: Chebucto & Arm Crescent, stop_code: 6413, lat: 44.64631, lng: -63.61047 } + - { name: Chebucto & Quinn, stop_code: 6414, lat: 44.64602, lng: -63.61234 } + - { name: Mumford & Chebucto, stop_code: 7275, lat: 44.64592, lng: -63.61570 } + - { name: Mumford Terminal, stop_code: 7284, lat: 44.64786, lng: -63.62035 } + - { name: Mumford Terminal, stop_code: 7285, lat: 44.64786, lng: -63.62035 } + - { name: Mumford Terminal, stop_code: 8643, lat: 44.64786, lng: -63.62035 } + + # following stops are for routes heading from mumford towards dartmouth + - { name: Mumford & Leppert, stop_code: 7274, lat: 44.64599, lng: -63.61618 } + - { name: Chebucto & Quinn, stop_code: 6409, lat: 44.64571, lng: -63.61272 } + - { name: Chebucto & Arm Crescent, stop_code: 6403, lat: 44.64599, lng: -63.61064 } + - { name: Chebucto & Newton, stop_code: 6407, lat: 44.64664, lng: -63.60843 } + - { name: Chebucto & Elm, stop_code: 6406, lat: 44.64787, lng: -63.60564 } + - { name: North & Oxford, stop_code: 7355, lat: 44.64896, lng: -63.60284 } + - { name: North & Dublin, stop_code: 7345, lat: 44.65013, lng: -63.60110 } + + # following stops are for routes heading from scotia square towards spring garden + - { name: Barrington & George, stop_code: 6108, lat: 44.64848, lng: -63.57482 } + - { name: Barrington & Prince, stop_code: 6084, lat: 44.64760, lng: -63.57435 } + - { name: Barrington & Sackville, stop_code: 6103, lat: 44.64660, lng: -63.57368 } + - { name: Barrington & Blowers, stop_code: 6102, lat: 44.64557, lng: -63.57322 } + - { name: Barrington & Spring Garden, stop_code: 6122, lat: 44.64454, lng: -63.57279 } + + # following stops heading from scotia square to water street terminal / ferry terminal + - { name: George & Hollis, stop_code: 6733, lat: 44.64851, lng: -63.57341 } + - { name: Water Street Terminal, stop_code: 8435, lat: 44.64922, lng: -63.57225 } + + # following stops are for routes heading on spring garden, barrington to robie + - { name: Spring Garden & Queen, stop_code: 8331, lat: 44.64348, lng: -63.57551 } + - { name: Spring Garden & Dresden Row, stop_code: 8330, lat: 44.64288, lng: -63.57759 } + - { name: Spring Garden & South Park, stop_code: 8334, lat: 44.64247, lng: -63.57942 } + - { name: Spring Garden & Summer, stop_code: 8335, lat: 44.64144, lng: -63.58316 } + - { name: Spring Garden & Carlton, stop_code: 8329, lat: 44.64105, lng: -63.58481 } + + # following stop data was collected going along robie, onto south, onto barrington + - { name: Robie & Coburg, stop_code: 8186, lat: 44.63979, lng: -63.58671 } + - { name: Robie & University, stop_code: 8188, lat: 44.63776, lng: -63.58559 } + - { name: Robie & South, stop_code: 8296, lat: 44.63665, lng: -63.58400 } + - { name: South & Wellington, stop_code: 8303, lat: 44.63715, lng: -63.58187 } + - { name: South & Tower, stop_code: 8305, lat: 44.63778, lng: -63.57941 } + - { name: South & South Park, stop_code: 8295, lat: 44.63840, lng: -63.57627 } + - { name: South & Queen, stop_code: 8299, lat: 44.63886, lng: -63.57421 } + - { name: South & Barrington, stop_code: 8293, lat: 44.63972, lng: -63.57165 } + - { name: South & Barrington, stop_code: 6096, lat: 44.64023, lng: -63.57041 } + - { name: Barrington & Morris, stop_code: 6113, lat: 44.64155, lng: -63.57109 } + - { name: Barrington & Spring Garden, stop_code: 6124, lat: 44.64404, lng: -63.57232 } + +# following stop is what you get continuing into south, towards lemarchant + - { name: Robie & South, stop_code: 8317, lat: 44.63633, lng: -63.58584 } + + # following stop data was collected going along barrington, onto south, onto robie + - { name: Barrington & Morris, stop_code: 6114, lat: 44.64188, lng: -63.57140 } + - { name: Barrington & South, stop_code: 6097, lat: 44.64024, lng: -63.57055 } + - { name: South & Church, stop_code: 8294, lat: 44.63949, lng: -63.57253 } + - { name: South & Queen, stop_code: 8292, lat: 44.63894, lng: -63.57462 } + - { name: South & South Park, stop_code: 8300, lat: 44.63836, lng: -63.57711 } + - { name: South & Tower, stop_code: 8301, lat: 44.63795, lng: -63.57863 } + - { name: South & Wellington, stop_code: 8298, lat: 44.63723, lng: -63.58183 } + - { name: South & Robie, stop_code: 8297, lat: 44.63684, lng: -63.58358 } + - { name: South & Robie, stop_code: 8220, lat: 44.63685, lng: -63.58488 } + - { name: Robie & University, stop_code: 8187, lat: 44.63857, lng: -63.58576 } + + # ~quinpool and oxford, heading to spring garden and robie + - { name: Quinpool & Oxford, stop_code: 7412, lat: 44.64424, lng: -63.60031 } + - { name: Oxford & Norwood, stop_code: 7419, lat: 44.64296, lng: -63.59948 } + - { name: Oxford & Jubilee, stop_code: 7409, lat: 44.64031, lng: -63.59805 } + - { name: Oxford & Jennings, stop_code: 7402, lat: 44.63898, lng: -63.59738 } + - { name: Coburg & Oxford, stop_code: 6453, lat: 44.63778, lng: -63.59617 } + - { name: Coburg & Lilac, stop_code: 6447, lat: 44.63842, lng: -63.59361 } + - { name: Coburg & Lemarchant, stop_code: 6449, lat: 44.63903, lng: -63.59137 } + - { name: Coburg & Henry, stop_code: 6454, lat: 44.63957, lng: -63.58905 } + - { name: Coburg & Robie, stop_code: 6452, lat: 44.64007, lng: -63.58723 } + + # from ~ Young and Robie to the Bedford Hwy + - { name: Young & Monaghan, stop_code: 8629, lat: 44.65816, lng: -63.60450 } + - { name: Young & Windsor, stop_code: 8634, lat: 44.65684, lng: -63.60654 } + - { name: Bayers & Dublin, stop_code: 6192, lat: 44.65630, lng: -63.60857 } + - { name: Bayers & Oxford, stop_code: 6196, lat: 44.65519, lng: -63.60994 } + - { name: Bayers & Connolly, stop_code: 6201, lat: 44.65365, lng: -63.61245 } + - { name: Bayers & Connaught, stop_code: 6200, lat: 44.65252, lng: -63.61456 } + - { name: Bayers & Vaughan, stop_code: 6199, lat: 44.65202, lng: -63.61846 } + - { name: Bayers & Romans, stop_code: 6198, lat: 44.65318, lng: -63.62294 } + # forgot to get the stop code for this one, it's between + #- { name: Village at Bayers Road, stop_code: xxxx, lat: 44.65380, lng: -63.62662 } + - { name: Village at Bayers Road, stop_code: 6563, lat: 44.65469, lng: -63.62804 } + - { name: Desmond & Scott, stop_code: 6565, lat: 44.65629, lng: -63.62758 } + - { name: Joseph Howe & Dutch Village, stop_code: 6984, lat: 44.65830, lng: -63.62902 } + - { name: Joseph Howe & Dutch Village, stop_code: 6983, lat: 44.66037, lng: -63.62809 } + + # from ~bedford highway to young and robie (partial) + - { name: Joseph Howe & Dutch Village, stop_code: 6982, lat: 44.65839, lng: -63.62920 } + - { name: Joseph Howe & Scot, stop_code: 6985, lat: 44.65692, lng: -63.62963 } + - { name: Scot & Desmond, stop_code: 8262, lat: 44.65638, lng: -63.62809 } + - { name: Village at Bayers Road, stop_code: 6564, lat: 44.65470, lng: -63.62797 } + - { name: Young & Monaghan, stop_code: 8633, lat: 44.65808, lng: -63.60435 } + + # robie -> barrington on spring garden + - { name: Spring Garden & Carlton, stop_code: 8328, lat: 44.64091, lng: -63.58551 } + - { name: Spring Garden & Summer, stop_code: 8337, lat: 44.64141, lng: -63.58288 } + - { name: Spring Garden & South Park, stop_code: 8333, lat: 44.64217, lng: -63.57999 } + - { name: Spring Garden & Dresden Row, stop_code: 8336, lat: 44.64302, lng: -63.57741 } + - { name: Spring Garden & Queen, stop_code: 8327, lat: 44.64342, lng: -63.57547 } + - { name: Spring Garden & Barrington, stop_code: 8338, lat: 44.64393, lng: -63.57325 } + +# barrington, from spring garden to duke + - { name: Barrington & Sackville, stop_code: 6121, lat: 44.64590, lng: -63.57344 } +# I have marked down 6086 as being the gotime id for the stop below, but +# that can't be right: 6086 is for barrington & duke (south bound). the +# schedules say so +#- { name: Barrington & Prince, stop_code: xxxx, lat: 44.64671, lng: -63.57383 } + - { name: Barrington & George, stop_code: 6106, lat: 44.64871, lng: -63.57474 } + + # dartmouth + - { name: Bridge Terminal (Dartmouth Sportsplex), stop_code: 6842, lat: 44.67016, lng: -63.57624 } + - { name: Bridge Terminal (Dartmouth Sportsplex), stop_code: 7151, lat: 44.67016, lng: -63.57624 } + - { name: Bridge Terminal (Dartmouth Sportsplex), stop_code: 8640, lat: 44.67016, lng: -63.57624 } + - { name: Bridge Terminal (Dartmouth Sportsplex), stop_code: 8641, lat: 44.67016, lng: -63.57624 } + - { name: Bridge Terminal (Dartmouth Sportsplex), stop_code: 8642, lat: 44.67016, lng: -63.57624 } + +# following the #10, from bridge terminal to mic mac mall + - { name: Wyse & Dawson, stop_code: 8616, lat: 44.67162, lng: -63.58015 } + - { name: Boland & Cairn, stop_code: 6304, lat: 44.67344, lng: -63.57982 } + - { name: Boland & Victoria, stop_code: 6303, lat: 44.67510, lng: -63.57797 } + - { name: Boland & Frances, stop_code: 8428, lat: 44.67642, lng: -63.57897 } + - { name: Slayter & Woodland, stop_code: 8587, lat: 44.67776, lng: -63.57929 } + - { name: Sheridan & Woodland, stop_code: 8580, lat: 44.67969, lng: -63.57693 } + - { name: Frederick & Woodland, stop_code: 8586, lat: 44.68060, lng: -63.57579 } + - { name: Laurier & Woodland, stop_code: 8582, lat: 44.68202, lng: -63.57399 } + - { name: Woodland & Micmac, stop_code: 7214, lat: 44.68501, lng: -63.56801 } + - { name: Micmac & Glencarin, stop_code: 7213, lat: 44.68667, lng: -63.56355 } + - { name: Mic Mac Terminal (Mic Mac Mall), stop_code: 7219, lat: 44.68536, lng: -63.56081 } + +# following the #10, Micmac Mall -> Tacoma Center + - { name: Micmac & Brookdale, stop_code: 7210, lat: 44.68456, lng: -63.55681 } +# the following stop doesn't come between 15:30-17:30, leave it out until +# we can express this in GTFS (or alternately, just leave this mostly +# useless top out indefinitely-- 7173 comes right after it) +#- { name: Main & Gordon, stop_code: 7174, lat: 44.68137, lng: -63.54327 } + - { name: Main & Gordon, stop_code: 7173, lat: 44.68169, lng: -63.54023 } + - { name: Main & Hartlen, stop_code: 6834, lat: 44.68168, lng: -63.53737 } + - { name: Tacoma Center, stop_code: 8369, lat: 44.68039, lng: -63.53747 } + +# following the #10, Tacoma Center -> Woodlawn & Main + - { name: Valleyfield & Oakwood, stop_code: 8416, lat: 44.67932, lng: -63.53463 } + - { name: Spikenard & Margaree Parkway, stop_code: 8323, lat: 44.68022, lng: -63.53151 } + - { name: Spikenard & Woodlawn, stop_code: 8320, lat: 44.68092, lng: -63.52837 } + - { name: Spikenard & Woodlawn, stop_code: 8603, lat: 44.68174, lng: -63.52757 } + - { name: Woodlawn & Main, stop_code: 8598, lat: 44.68351, lng: -63.52875 } + +# Following #10, Woodlawn & Main -> Inverary & Strath + - { name: Booth & Main, stop_code: 6306, lat: 44.68593, lng: -63.52412 } + - { name: Booth & Scotsburn, stop_code: 6305, lat: 44.68786, lng: -63.52550 } + - { name: Booth & David, stop_code: 7053, lat: 44.68977, lng: -63.52740 } + - { name: Kennedy, stop_code: 7052, lat: 44.69088, lng: -63.52839 } + - { name: Kennedy, stop_code: 7051, lat: 44.69151, lng: -63.53071 } + - { name: Caladonia, stop_code: 6369, lat: 44.69199, lng: -63.53286 } + - { name: Caladonia & Dumbarton, stop_code: 6591, lat: 44.69308, lng: -63.53429 } + - { name: Kincardine & Dumbarton, stop_code: 6592, lat: 44.69247, lng: -63.53681 } + - { name: Kincardine & Greenoch, stop_code: 7057, lat: 44.68988, lng: -63.53612 } + - { name: Inverary & Strath, stop_code: 6974, lat: 44.68847, lng: -63.53599 } + +# following #10, iverary & strath -> tacoma + - { name: Strath & Raymoor, stop_code: 8160, lat: 44.68717, lng: -63.53591 } + - { name: Raymoor, stop_code: 8161, lat: 44.68559, lng: -63.53323 } + - { name: Raymoor & Main, stop_code: 8162, lat: 44.68428, lng: -63.53158 } + - { name: Weyburn & Main, stop_code: 8483, lat: 44.68355, lng: -63.53236 } + - { name: Weyburn & Athabaskan, stop_code: 8484, lat: 44.68215, lng: -63.53176 } + - { name: Weyburn & Spikenard, stop_code: 8482, lat: 44.68078, lng: -63.53111 } + - { name: Spikenard & Valleyfield, stop_code: 8319, lat: 44.67957, lng: -63.53369 } + - { name: Tacoma Center, stop_code: 8368, lat: 44.67997, lng: -63.53717 } + +# following #10, tacoma -> micmac + - { name: Hartlen & Main, stop_code: 6835, lat: 44.68165, lng: -63.53725 } + - { name: Major & Main, stop_code: 7175, lat: 44.68195, lng: -63.54001 } + - { name: Micmac & Brookdlae, stop_code: 7209, lat: 44.68466, lng: -63.55729 } + - { name: Micmac & Glencairn, stop_code: 7218, lat: 44.68576, lng: -63.56118 } + +# following #10, micmac -> bridge terminal + - { name: Micmac & Glencairn, stop_code: 7211, lat: 44.68671, lng: -63.56299 } + - { name: Micmac & Woodland, stop_code: 7215, lat: 44.68532, lng: -63.56827 } + - { name: Woodland & Laurier, stop_code: 8581, lat: 44.68175, lng: -63.57462 } + - { name: Woodland & Pinehill, stop_code: 8583, lat: 44.68072, lng: -63.57587 } + - { name: Woodland & Sheridan, stop_code: 8584, lat: 44.67979, lng: -63.57703 } + - { name: Woodland & Slayter, stop_code: 8585, lat: 44.67778, lng: -63.57946 } + - { name: Victoria & Francse, stop_code: 8424, lat: 44.67623, lng: -63.57887 } + - { name: Victoria & Boland, stop_code: 8419, lat: 44.67515, lng: -63.57724 } + - { name: Nantucket & Victoria, stop_code: 8429, lat: 44.67296, lng: -63.57360 } + - { name: Thistle & Victoria, stop_code: 8427, lat: 44.67185, lng: -63.57202 } + - { name: Thistle & Victoria, stop_code: 8389, lat: 44.67106, lng: -63.57202 } + - { name: Wyse & Thistle, stop_code: 8392, lat: 44.66898, lng: -63.57436 } + - { name: Wyse & Thistle, stop_code: 8614, lat: 44.66898, lng: -63.57540 } + +# ~coburg and Henry -> quinpool and oxford + - { name: Coburg & Henry, stop_code: 6448, lat: 44.63967, lng: -63.58873 } + - { name: Coburg & Lemarchant, stop_code: 6450, lat: 44.63914, lng: -63.59097 } + - { name: Coburg & Lilac, stop_code: 6451, lat: 44.63833, lng: -63.59454 } + - { name: Oxford & Coburg, stop_code: 7401, lat: 44.63822, lng: -63.59685 } + - { name: Oxford & Jennings, stop_code: 7403, lat: 44.63940, lng: -63.59741 } + - { name: Oxford & Cornwall, stop_code: 7410, lat: 44.64129, lng: -63.59843 } + - { name: Oxford & Norwood, stop_code: 7406, lat: 44.64270, lng: -63.59924 } + - { name: Quinpool & Oxford, stop_code: 7421, lat: 44.64444, lng: -63.60015 } + +# ~ quinpool and oxford, to young and oxford + - { name: Oxford & Allan, stop_code: 7404, lat: 44.64699, lng: -63.60142 } + +# south & dalhousie -> spring garden & south park (following #10) + - { name: South & Dalhousie, stop_code: 8304, lat: 44.63488, lng: -63.59253 } + - { name: Beaufort & South, stop_code: 6206, lat: 44.63380, lng: -63.59510 } + - { name: Beaufort & Oakland, stop_code: 6209, lat: 44.63272, lng: -63.59301 } + - { name: Beaufort & Inglis, stop_code: 6208, lat: 44.63124, lng: -63.58960 } + - { name: Inglis & Greenwood, stop_code: 6961, lat: 44.63194, lng: -63.58596 } + - { name: Inglis & Robie, stop_code: 6962, lat: 44.63272, lng: -63.58260 } + - { name: Inglis & Wellington, stop_code: 6965, lat: 44.63311, lng: -63.58089 } + - { name: Tower & Inglis, stop_code: 6969, lat: 44.63378, lng: -63.57829 } + - { name: South Park & Victoria, stop_code: 8314, lat: 44.63568, lng: -63.57665 } + - { name: South Park & South, stop_code: 8306, lat: 44.63739, lng: -63.57736 } + - { name: South Park & South, stop_code: 8307, lat: 44.63846, lng: -63.57769 } + - { name: South Park & University, stop_code: 8309, lat: 44.64016, lng: -63.57856 } + - { name: South Park & Spring Garden, stop_code: 8312, lat: 44.64203, lng: -63.57956 } + +# south park and spring garden -> robie and inglis (following #10) + - { name: Spring Garden & South Park, stop_code: 8308, lat: 44.64172, lng: -63.57946 } + - { name: South Park & Morris, stop_code: 8313, lat: 44.64017, lng: -63.57877 } + - { name: South Park & South, stop_code: 8311, lat: 44.63837, lng: -63.57793 } + - { name: South Park & Fenwick, stop_code: 8315, lat: 44.63687, lng: -63.57716 } + # (two between stops with no gotime id #'s...) + - { name: Inglis (SMU), stop_code: 6960, lat: 44.63326, lng: -63.58090 } + - { name: Inglish & Robie, stop_code: 6966, lat: 44.63280, lng: -63.58262 } + +# robie & young -> Leeds & Rosemeade + - { name: Robie & Young, stop_code: 8192, lat: 44.66001, lng: -63.60277 } + - { name: Robie & Livingstone, stop_code: 8209, lat: 44.66097, lng: -63.60419 } + - { name: Robie & Stanley, stop_code: 8217, lat: 44.66197, lng: -63.60570 } + - { name: Robie & Cabot, stop_code: 8204, lat: 44.66341, lng: -63.60783 } + - { name: Robie & Duffus, stop_code: 8207, lat: 44.66454, lng: -63.60950 } + - { name: Robie & Lady Hammond, stop_code: 8180, lat: 44.66556, lng: -63.61093 } + - { name: Robie & Normandy, stop_code: 8211, lat: 44.66670, lng: -63.61254 } + - { name: Leeds & Rosemeade, stop_code: 7131, lat: 44.66823, lng: -63.61368 } + +# Leeds & Rosemeade -> Robie & Young + - { name: Leeds & Rosemeade, stop_code: 7133, lat: 44.66856, lng: -63.61347 } + - { name: Robie & Normandy, stop_code: 8212, lat: 44.66695, lng: -63.61309 } + - { name: Robie & Lady Hammond, stop_code: 8208, lat: 44.66551, lng: -63.61100 } + - { name: Robie & Cabot, stop_code: 8203, lat: 44.66370, lng: -63.60844 } + - { name: Robie & Stanley, stop_code: 8197, lat: 44.66168, lng: -63.60543 } + - { name: Robie & Kaye, stop_code: 8218, lat: 44.66006, lng: -63.60306 } + +# Quinpool & Robie -> Quinpool & Connaught + - { name: Quinpool & Robie, stop_code: 8151, lat: 44.64716, lng: -63.59127 } + - { name: Quinpool & Monastery, stop_code: 8138, lat: 44.64620, lng: -63.59458 } + - { name: Quinpool & Preston, stop_code: 8148, lat: 44.64528, lng: -63.59772 } + - { name: Quinpool & Oxford, stop_code: 8137, lat: 44.64446, lng: -63.60074 } + - { name: Quinpool & Beech, stop_code: 8134, lat: 44.64408, lng: -63.60197 } + - { name: Quinpool & Poplar, stop_code: 8146, lat: 44.64353, lng: -63.60386 } + +# Quinpool & Connaught -> Quinpool & Robie + - { name: Quinpool & Poplar, stop_code: 8135, lat: 44.64339, lng: -63.60391 } + - { name: Quinpool & Beech, stop_code: 8133, lat: 44.64389, lng: -63.60237 } + - { name: Quinpool & Oxford, stop_code: 8136, lat: 44.64460, lng: -63.59996 } + - { name: Quinpool & Preston, stop_code: 8147, lat: 44.64531, lng: -63.59756 } + - { name: Quinpool & Vernon, stop_code: 8149, lat: 44.64639, lng: -63.59360 } + - { name: Quinpool & Robie, stop_code: 8150, lat: 44.64702, lng: -63.59167 } + +# heading away from bayer's road, onto gottingen (at least for #21) + - { name: Young & Isleville, stop_code: 8632, lat: 44.66144, lng: -63.59985 } + +# heading towards bayer's road, from gottingen (at least for #21) + - { name: Young & Agricola, stop_code: 8631, lat: 44.66096, lng: -63.60080 } + +# robie and lady hammond -> barrington, following the #9 + - { name: Robie & Lady Hammond, stop_code: 7094, lat: 44.66531, lng: -63.60980 } + - { name: Duffus & Isleville, stop_code: 6581, lat: 44.66669, lng: -63.60707 } + - { name: Novalea & Duffus, stop_code: 6582, lat: 44.66782, lng: -63.60558 } + - { name: Albert & Duffus, stop_code: 6586, lat: 44.66926, lng: -63.60375 } + - { name: Duffus & Barrington, stop_code: 6587, lat: 44.67051, lng: -63.60189 } + - { name: Barrington & Marginal, stop_code: 6100, lat: 44.66868, lng: -63.60024 } + - { name: Barrington & Richmond, stop_code: 6120, lat: 44.66799, lng: -63.59873 } + - { name: Barrington & Hannover, stop_code: 6109, lat: 44.66677, lng: -63.59710 } + - { name: Barrington & Young, stop_code: 6095, lat: 44.66502, lng: -63.59504 } + +# barrington -> robie and lady hammond, following the #9 + - { name: Barrington & Young, stop_code: 6094, lat: 44.66523, lng: -63.59504 } + - { name: Barrington & Hanover, stop_code: 6110, lat: 44.66667, lng: -63.59676 } + - { name: Barrington & Richmond, stop_code: 6119, lat: 44.66803, lng: -63.59848 } + - { name: Barrington & Marginal, stop_code: 6128, lat: 44.66892, lng: -63.60026 } + - { name: Barrington & Duffus, stop_code: 6580, lat: 44.67064, lng: -63.60203 } + - { name: Duffus & Albert, stop_code: 6588, lat: 44.66975, lng: -63.60318 } + - { name: Duffus & Acadia, stop_code: 6584, lat: 44.66852, lng: -63.60486 } + - { name: Duffus & Novalea, stop_code: 6583, lat: 44.66728, lng: -63.60654 } + - { name: Duffus & Agricola, stop_code: 6585, lat: 44.66585, lng: -63.60852 } + - { name: Robie & Lady Hammond, stop_code: 7096, lat: 44.66546, lng: -63.61008 } + + +routes: + --- /dev/null +++ b/origin-src/wlach-halifax-transit-feed-fef68c1/indent-route.pl @@ -1,1 +1,14 @@ +#!/usr/bin/perl +use strict; + +my $first = 1; +while () { + if ($first) { + $first = 0; + print " - $_"; + } else { + print " $_"; + } +} + --- /dev/null +++ b/origin-src/wlach-halifax-transit-feed-fef68c1/parse-times.pl @@ -1,1 +1,35 @@ +#!/usr/bin/perl +use strict; + +my $first = 1; +my $prev_comment = 0; +while () { + if ($_ !~ /^\#/) { + if (!$first && !$prev_comment) { + print ",\n"; + } else { + $first = 0; + $prev_comment = 0; + } + chomp; + my @times = split /\ +/; + print " [ "; + my $first = 1; + foreach (@times) { + if (!$first) { + print ", "; + } else { + $first = 0; + } + print $_; + } + print "]"; + } else { + # yes, this conditional is loaded with assumptions... + print ",\n" . $_; + $prev_comment = 1; + } +} + + --- /dev/null +++ b/origin-src/wlach-libroutez-272ef93/.gitignore @@ -1,1 +1,20 @@ +*.o +*.routez +*~ +.*.swp +aclocal.m4 +autom4te.cache +config.log +config.mk +config.status +configure +libroutez.so +python/libroutez/tripgraph.py +python/libroutez/tripgraph_wrap_py.cc +python/libroutez/_tripgraph.so +ruby/routez.so +ruby/routez_wrap_rb.cc +ruby/routez_wrap_rb.o +tags +examples/testgraph --- /dev/null +++ b/origin-src/wlach-libroutez-272ef93/AUTHORS @@ -1,1 +1,18 @@ +=== Current maintainer and primary author === +William Lachance + +=== Contributors === + +Brandon Martin Anderson + - Core concepts of a trip planning library (in graphserver: + http://graphserver.sourceforge.net) + - Python OSM parsing library + +Peter McCurdy + - Support for delaying walking to a stop at the start of a trip. + +Jason Madigan +- Some updates to the README regarding building, and updates to + the ruby examples. + --- /dev/null +++ b/origin-src/wlach-libroutez-272ef93/LICENSE @@ -1,1 +1,23 @@ +Copyright (c) 2008-2009 William Lachance, Brandon Martin Anderson, and others. +Permission is hereby granted, free of charge, to any person +obtaining a copy of this software and associated documentation +files (the "Software"), to deal in the Software without +restriction, including without limitation the rights to use, +copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the +Software is furnished to do so, subject to the following +conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +OTHER DEALINGS IN THE SOFTWARE. + --- /dev/null +++ b/origin-src/wlach-libroutez-272ef93/Makefile @@ -1,1 +1,82 @@ +include config.mk +default: libroutez.so examples/loadgraph examples/testgraph \ + python/libroutez/tripgraph.py python/libroutez/_tripgraph.so \ + ruby/routez.so + +include install.mk + +# Always always compile with fPIC +CFLAGS += -fPIC +CXXFLAGS += -fPIC + +# libroutez should be compiled as a shared library by default +ifeq (${OS},MACOS) + LDFLAGS += -dynamiclib +else + LDFLAGS += -shared +endif + +config.mk: + @echo "Please run ./configure. Stop." + @exit 1 + +%.o: %.cc + g++ $< -c -o $@ $(CXXFLAGS) $(PYTHON_CFLAGS) $(RUBY_CFLAGS) -D WVTEST_CONFIGURED -I./include -I./wvtest/cpp -g + @g++ $< -MM $(CXXFLAGS) $(PYTHON_CFLAGS) $(RUBY_CFLAGS) -D WVTEST_CONFIGURED -I./include -I./wvtest/cpp > $*.d + @mv -f $*.d $*.d.tmp + @sed -e 's|.*:|$*.o:|' < $*.d.tmp > $*.d + @sed -e 's/.*://' -e 's/\\$$//' < $*.d.tmp | fmt -1 | \ + sed -e 's/^ *//' -e 's/$$/:/' >> $*.d + @rm -f $*.d.tmp + +TRIPGRAPH_OBJECTS=lib/tripgraph.o lib/trippath.o lib/tripstop.o lib/serviceperiod.o + +# libroutez: the main library +libroutez.so: $(TRIPGRAPH_OBJECTS) + g++ $(TRIPGRAPH_OBJECTS) $(LDFLAGS) -o libroutez.so -fPIC -g + +# python bindings +python/libroutez/tripgraph.py python/libroutez/tripgraph_wrap_py.cc: tripgraph.i + swig -classic -c++ -python -I./include -outdir python/libroutez -o python/libroutez/tripgraph_wrap_py.cc $< +python/libroutez/_tripgraph.so: libroutez.so python/libroutez/tripgraph_wrap_py.o + g++ -o $@ python/libroutez/tripgraph_wrap_py.o libroutez.so $(LDFLAGS) $(PYTHON_LDFLAGS) -fPIC + +# ruby bindings +ruby/routez_wrap_rb.cc: routez.i tripgraph.i + swig -c++ -ruby -I./include -o $@ $< + +ruby/routez.so: libroutez.so ruby/routez_wrap_rb.o + g++ -o ruby/routez.so ruby/routez_wrap_rb.o libroutez.so $(LDFLAGS) $(RUBY_LDFLAGS) -I./include -fPIC + +# stupid test programs +examples/loadgraph: examples/loadgraph.o libroutez.so + g++ $< -o $@ libroutez.so -fPIC -g -I./include +examples/testgraph: examples/testgraph.o libroutez.so + g++ $< -o $@ libroutez.so -fPIC -g -I./include + +# unit test suite +TEST_OBJS=t/tripgraph.t.o t/tripstop.t.o t/all.t.o +WVTEST_OBJS=wvtest/cpp/wvtest.o wvtest/cpp/wvtestmain.o +t/all.t: $(TEST_OBJS) $(WVTEST_OBJS) libroutez.so + g++ $(TEST_OBJS) $(WVTEST_OBJS) -o $@ libroutez.so -fPIC -g + +.PHONY: test test-cpp test-python +test: test-cpp test-python + +test-cpp: t/all.t + LD_LIBRARY_PATH=$(PWD) valgrind --tool=memcheck wvtest/wvtestrun t/all.t + +test-python: python/libroutez/tripgraph.py python/libroutez/_tripgraph.so + LD_LIBRARY_PATH=$(PWD) PYTHONPATH=$(PWD)/t:$(PWD)/python:$(PYTHONPATH) wvtest/wvtestrun python wvtest/python/wvtestmain.py pytest.py + +clean: + rm -f *.so lib/*.o python/*.pyc */*.pyc examples/testgraph \ + python/libroutez/_tripgraph.so python/libroutez/tripgraph.py \ + python/libroutez/tripgraph_wrap_py.cc python/libroutez/*.o \ + ruby/routez.so ruby/*.o ruby/routez_wrap_rb.cc \ + wvtest/cpp/*.o wvtest/cpp/*.d \ + t/*.o t/*.d t/all.t *.d + +-include $(TRIPGRAPH_OBJECTS:.o=.d) + --- /dev/null +++ b/origin-src/wlach-libroutez-272ef93/README @@ -1,1 +1,147 @@ +=== About === +libroutez is a library written to help plan trips. Currently, its focus is +on providing directions for using public transit, but it could be easily +extended to cover other things (e.g. cycle-path planning). Specifically, +it provides an interface to solving the following problems: + +- Finding the closest point in a road/transportation network to a specific +latitude-longitude pair. +- Finding the shortest path between two points in a road/transportation +network. + +The design of libroutez is based on the following principles: + +- Focus on solving user and developer problems, not data structures or +algorithms. For example, though libroutez uses the astar algorithm internally, +we try to expose the interface to that through a simple method called +"find_path". +- Be extensible, but only in response to demonstrative developer need. +- Be fast. libroutez uses an internal graph representation that, after being +generated, can be loaded very quickly on program startup. Trips on a modest +sized transit network can be generated in a fraction of a second on modern +hardware (and there are plans to speed things up further). +- Minimize memory use. Obviously the scale of public transit systems will +incur some overhead here, but we want to be maximally useful on embedded +systems and virtual servers where such resources may be scarce. +- Minimize dependancies. For example, we don't assume the user wants to use a +PostGRES database to store trip planning data (though they can if the want to). +libroutez itself has no run-time dependancies beyond the C++ standard library. + +The libroutez has utility functions for converting Google Transit Feed +(http://code.google.com/p/googletransitdatafeed) and OpenStreetMap +(http://openstreetmap.org) data into its own format. It should be very easy +to add a simple converter for your preferred data type. + +=== Setup and use === + +These instructions assume you are on a UNIX-based system (e.g. Linux or +MacOS X). + +1. Download and install the following packages: + - Boost + - Probably any recent version should do. We need shared_ptr, unordered_set, + and unordered_map (unordered_map.hpp is included in debian/ubuntu packages + libboost1.37-dev & libboost1.38-dev) + - Google Transit Data Feed (http://code.google.com/p/googletransitdatafeed/) + - As of this writing you need what's in SVN, as it has a fix that I made + to actually handle interpolated stops correctly. The next version after + 1.1.9 should have my fix. + - SWIG + - A recentish version is desirable. I used 1.3.36. + +Helpful hint: To install a python package in a local prefix, do: + + "python setup.py install --home=$HOME --prefix=" when inside the package + +You may need to set PYTHONPATH to $HOME/lib/python first. + +2. Build the C++ graph module and the bindings. + +The usual... + + - ./autogen.sh && ./configure && make + +For an install into your home directory, try running ./configure like so: + + - ./configure --prefix=$HOME --libdir=$HOME/lib + +or maybe: + + - ./configure --prefix=$HOME --libdir=$HOME/lib64 # (for many 64-bit systems) + +3. Set up your language environment. + +You can either 'make install' to install the binding source to the library +directory you chose with './configure', or set your library and ruby/python +path to the libroutez source directory of your choice. e.g.: + +export LD_LIBRARY_PATH=/path/to/libroutez and +export PYTHONPATH=/path/to/libroutez/python +export RUBYLIB=/path/to/libroutez/ruby + +(setting RUBYLIB is only necessary if you want to use ruby, python is used +by the graph creation utility, so you almost certainly do want that in your +path) + +4. Build a graph. + +To reduce application start up time, libroutez uses a custom graph format +which is created from GTFS and OpenStreetMap data. The creategraph utility +is used to create this. + +The invocation is pretty simple. If you want to create a graph file called +'mygraph.routez', simply invoke creategraph.py as follows: + + ./creategraph.py /path/to/gtfsfeed.zip --osm="/path/to/osmfile.osm" \ + mygraph.routez mygraph-gtfsmapping.yml + +Want some sample data to play with? You can download the combination of +William Lachance's Halifax GTFS and Geobase data: + + - http://wlach.masalalabs.ca/hfxfeed.zip + - http://wlach.masalalabs.ca/greater-hrm-geobase.osm.gz + +The Halifax GTFS feed is produced by me, based on information provided +by the city of Halifax. For more information, please see: +http://github.com/wlach/halifax-transit-feed. + +The OSM data is derived from the GeoBase dataset provided by the +government of Canada, and is distributed under the following terms: +http://geobase.ca/geobase/en/licence.jsp + +5. Starting playing with the library. + +Now that you've built a graph, you can start planning trips. Try the +'testgraph' mini program in examples. The following corresponds to +a trip from Cogswell and Maynard to Glengarry Gardens at 9am on Monday, +September 14th 2009 in Halifax, Nova Scotia, Canada: + +"testgraph mygraph.routez 44.649942 -63.583457 44.6605 -63.7467 1252933200" + +You should get a bunch of directions in "routezspeak" in response. :) Note that +no attempt is currently made to prettify the output, but hopefully this will at +least give you a starting point (perhaps that would be a fun little first +project for someone who wants to contribute?). Correlating the descriptive +information contained within a Google Transit feed with libroutez's internal +data structures will necessitate using the gtfs mapping yaml file generated +by the creategraph utility. + +For those who like programming in ruby and python, there are examples of both +in the same directory as 'testgraph'. + +=== Contributing === + +Contributions to libroutez are gratefully accepted. The easiest thing to do +is fork my repository on github (http://github.com/wlach/libroutez), apply +your change (make sure to add your name and nature of contribution to +AUTHORS), then either email me (at wrlach@gmail.com) or the libroutez mailing +list (http://groups.google.com/group/libroutez) to let us know about what you +did. Patch files sent directly to wrlach@gmail.com are also welcome. + +In order for changes to be committed, they will need to be licensed under the +same terms as the rest of libroutez (the MIT license). Please note that you +consent to this in your git commit message or mail the libroutez mailing list +saying that this (or all) your contributions are released under the MIT +license. + --- /dev/null +++ b/origin-src/wlach-libroutez-272ef93/acinclude.m4 @@ -1,1 +1,856 @@ - +# =========================================================================== +# http://autoconf-archive.cryp.to/ax_with_prog.html +# =========================================================================== +# +# SYNOPSIS +# +# AX_WITH_PROG([VARIABLE],[program],[VALUE-IF-NOT-FOUND],[PATH]) +# +# DESCRIPTION +# +# Locates an installed program binary, placing the result in the precious +# variable VARIABLE. Accepts a present VARIABLE, then --with-program, and +# failing that searches for program in the given path (which defaults to +# the system path). If program is found, VARIABLE is set to the full path +# of the binary; if it is not found VARIABLE is set to VALUE-IF-NOT-FOUND +# if provided, unchanged otherwise. +# +# A typical example could be the following one: +# +# AX_WITH_PROG(PERL,perl) +# +# NOTE: This macro is based upon the original AX_WITH_PYTHON macro from +# Dustin J. Mitchell . +# +# LAST MODIFICATION +# +# 2008-05-05 +# +# COPYLEFT +# +# Copyright (c) 2008 Francesco Salvestrini +# Copyright (c) 2008 Dustin J. Mitchell +# +# Copying and distribution of this file, with or without modification, are +# permitted in any medium without royalty provided the copyright notice +# and this notice are preserved. + +AC_DEFUN([AX_WITH_PROG],[ + AC_PREREQ([2.61]) + + pushdef([VARIABLE],$1) + pushdef([EXECUTABLE],$2) + pushdef([VALUE_IF_NOT_FOUND],$3) + pushdef([PATH_PROG],$4) + + AC_ARG_VAR(VARIABLE,Absolute path to EXECUTABLE executable) + + AS_IF(test -z "$VARIABLE",[ + AC_MSG_CHECKING(whether EXECUTABLE executable path has been provided) + AC_ARG_WITH(EXECUTABLE,AS_HELP_STRING([--with-EXECUTABLE=[[[[PATH]]]]],absolute path to EXECUTABLE executable), [ + AS_IF([test "$withval" != "yes"],[ + VARIABLE="$withval" + AC_MSG_RESULT($VARIABLE) + ],[ + VARIABLE="" + AC_MSG_RESULT([no]) + ]) + ],[ + AC_MSG_RESULT([no]) + ]) + + AS_IF(test -z "$VARIABLE",[ + AC_PATH_PROG([]VARIABLE[],[]EXECUTABLE[],[]VALUE_IF_NOT_FOUND[],[]PATH_PROG[]) + ]) + ]) + + popdef([PATH_PROG]) + popdef([VALUE_IF_NOT_FOUND]) + popdef([EXECUTABLE]) + popdef([VARIABLE]) +]) + +# =========================================================================== +# http://www.nongnu.org/autoconf-archive/ax_python_devel.html +# =========================================================================== +# +# SYNOPSIS +# +# AX_PYTHON_DEVEL([version]) +# +# DESCRIPTION +# +# Note: Defines as a precious variable "PYTHON_VERSION". Don't override it +# in your configure.ac. +# +# This macro checks for Python and tries to get the include path to +# 'Python.h'. It provides the $(PYTHON_CPPFLAGS) and $(PYTHON_LDFLAGS) +# output variables. It also exports $(PYTHON_EXTRA_LIBS) and +# $(PYTHON_EXTRA_LDFLAGS) for embedding Python in your code. +# +# You can search for some particular version of Python by passing a +# parameter to this macro, for example ">= '2.3.1'", or "== '2.4'". Please +# note that you *have* to pass also an operator along with the version to +# match, and pay special attention to the single quotes surrounding the +# version number. Don't use "PYTHON_VERSION" for this: that environment +# variable is declared as precious and thus reserved for the end-user. +# +# This macro should work for all versions of Python >= 2.1.0. As an end +# user, you can disable the check for the python version by setting the +# PYTHON_NOVERSIONCHECK environment variable to something else than the +# empty string. +# +# If you need to use this macro for an older Python version, please +# contact the authors. We're always open for feedback. +# +# LICENSE +# +# Copyright (c) 2009 Sebastian Huber +# Copyright (c) 2009 Alan W. Irwin +# Copyright (c) 2009 Rafael Laboissiere +# Copyright (c) 2009 Andrew Collier +# Copyright (c) 2009 Matteo Settenvini +# Copyright (c) 2009 Horst Knorr +# +# This program is free software: you can redistribute it and/or modify it +# under the terms of the GNU General Public License as published by the +# Free Software Foundation, either version 3 of the License, or (at your +# option) any later version. +# +# This program is distributed in the hope that it will be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General +# Public License for more details. +# +# You should have received a copy of the GNU General Public License along +# with this program. If not, see . +# +# As a special exception, the respective Autoconf Macro's copyright owner +# gives unlimited permission to copy, distribute and modify the configure +# scripts that are the output of Autoconf when processing the Macro. You +# need not follow the terms of the GNU General Public License when using +# or distributing such scripts, even though portions of the text of the +# Macro appear in them. The GNU General Public License (GPL) does govern +# all other use of the material that constitutes the Autoconf Macro. +# +# This special exception to the GPL applies to versions of the Autoconf +# Macro released by the Autoconf Archive. When you make and distribute a +# modified version of the Autoconf Macro, you may extend this special +# exception to the GPL to apply to your modified version as well. + +AU_ALIAS([AC_PYTHON_DEVEL], [AX_PYTHON_DEVEL]) +AC_DEFUN([AX_PYTHON_DEVEL],[ + # + # Allow the use of a (user set) custom python version + # + AC_ARG_VAR([PYTHON_VERSION],[The installed Python + version to use, for example '2.3'. This string + will be appended to the Python interpreter + canonical name.]) + + AC_PATH_PROG([PYTHON],[python[$PYTHON_VERSION]]) + if test -z "$PYTHON"; then + AC_MSG_ERROR([Cannot find python$PYTHON_VERSION in your system path]) + PYTHON_VERSION="" + fi + + # + # Check for a version of Python >= 2.1.0 + # + AC_MSG_CHECKING([for a version of Python >= '2.1.0']) + ac_supports_python_ver=`$PYTHON -c "import sys; \ + ver = sys.version.split ()[[0]]; \ + print (ver >= '2.1.0')"` + if test "$ac_supports_python_ver" != "True"; then + if test -z "$PYTHON_NOVERSIONCHECK"; then + AC_MSG_RESULT([no]) + AC_MSG_FAILURE([ +This version of the AC@&t@_PYTHON_DEVEL macro +doesn't work properly with versions of Python before +2.1.0. You may need to re-run configure, setting the +variables PYTHON_CPPFLAGS, PYTHON_LDFLAGS, PYTHON_SITE_PKG, +PYTHON_EXTRA_LIBS and PYTHON_EXTRA_LDFLAGS by hand. +Moreover, to disable this check, set PYTHON_NOVERSIONCHECK +to something else than an empty string. +]) + else + AC_MSG_RESULT([skip at user request]) + fi + else + AC_MSG_RESULT([yes]) + fi + + # + # if the macro parameter ``version'' is set, honour it + # + if test -n "$1"; then + AC_MSG_CHECKING([for a version of Python $1]) + ac_supports_python_ver=`$PYTHON -c "import sys; \ + ver = sys.version.split ()[[0]]; \ + print (ver $1)"` + if test "$ac_supports_python_ver" = "True"; then + AC_MSG_RESULT([yes]) + else + AC_MSG_RESULT([no]) + AC_MSG_ERROR([this package requires Python $1. +If you have it installed, but it isn't the default Python +interpreter in your system path, please pass the PYTHON_VERSION +variable to configure. See ``configure --help'' for reference. +]) + PYTHON_VERSION="" + fi + fi + + # + # Check if you have distutils, else fail + # + AC_MSG_CHECKING([for the distutils Python package]) + ac_distutils_result=`$PYTHON -c "import distutils" 2>&1` + if test -z "$ac_distutils_result"; then + AC_MSG_RESULT([yes]) + else + AC_MSG_RESULT([no]) + AC_MSG_ERROR([cannot import Python module "distutils". +Please check your Python installation. The error was: +$ac_distutils_result]) + PYTHON_VERSION="" + fi + + # + # Check for Python include path + # + AC_MSG_CHECKING([for Python include path]) + if test -z "$PYTHON_CPPFLAGS"; then + python_path=`$PYTHON -c "import distutils.sysconfig; \ + print (distutils.sysconfig.get_python_inc ());"` + if test -n "${python_path}"; then + python_path="-I$python_path" + fi + PYTHON_CPPFLAGS=$python_path + fi + AC_MSG_RESULT([$PYTHON_CPPFLAGS]) + AC_SUBST([PYTHON_CPPFLAGS]) + + # + # Check for Python library path + # + AC_MSG_CHECKING([for Python library path]) + if test -z "$PYTHON_LDFLAGS"; then + # (makes two attempts to ensure we've got a version number + # from the interpreter) + ac_python_version=`cat<]], + [[Py_Initialize();]]) + ],[pythonexists=yes],[pythonexists=no]) + AC_LANG_POP([C]) + # turn back to default flags + CPPFLAGS="$ac_save_CPPFLAGS" + LIBS="$ac_save_LIBS" + + AC_MSG_RESULT([$pythonexists]) + + if test ! "x$pythonexists" = "xyes"; then + AC_MSG_FAILURE([ + Could not link test program to Python. Maybe the main Python library has been + installed in some non-standard library path. If so, pass it to configure, + via the LDFLAGS environment variable. + Example: ./configure LDFLAGS="-L/usr/non-standard-path/python/lib" + ============================================================================ + ERROR! + You probably have to install the development version of the Python package + for your distribution. The exact name of this package varies among them. + ============================================================================ + ]) + PYTHON_VERSION="" + fi + + # + # all done! + # +]) + +# =========================================================================== +# http://autoconf-archive.cryp.to/ax_with_ruby.html +# =========================================================================== +# +# SYNOPSIS +# +# AX_WITH_RUBY([VALUE-IF-NOT-FOUND],[PATH]) +# +# DESCRIPTION +# +# Locates an installed Ruby binary, placing the result in the precious +# variable $RUBY. Accepts a present $RUBY, then --with-ruby, and failing +# that searches for ruby in the given path (which defaults to the system +# path). If ruby is found, $RUBY is set to the full path of the binary; if +# it is not found $RUBY is set to VALUE-IF-NOT-FOUND if provided, +# unchanged otherwise. +# +# A typical use could be the following one: +# +# AX_WITH_RUBY +# +# LAST MODIFICATION +# +# 2008-05-05 +# +# COPYLEFT +# +# Copyright (c) 2008 Francesco Salvestrini +# +# Copying and distribution of this file, with or without modification, are +# permitted in any medium without royalty provided the copyright notice +# and this notice are preserved. + +AC_DEFUN([AX_WITH_RUBY],[ + AX_WITH_PROG(RUBY,ruby,$1,$2) +]) + +# =========================================================================== +# http://autoconf-archive.cryp.to/ax_compare_version.html +# =========================================================================== +# +# SYNOPSIS +# +# AX_COMPARE_VERSION(VERSION_A, OP, VERSION_B, [ACTION-IF-TRUE], [ACTION-IF-FALSE]) +# +# DESCRIPTION +# +# This macro compares two version strings. Due to the various number of +# minor-version numbers that can exist, and the fact that string +# comparisons are not compatible with numeric comparisons, this is not +# necessarily trivial to do in a autoconf script. This macro makes doing +# these comparisons easy. +# +# The six basic comparisons are available, as well as checking equality +# limited to a certain number of minor-version levels. +# +# The operator OP determines what type of comparison to do, and can be one +# of: +# +# eq - equal (test A == B) +# ne - not equal (test A != B) +# le - less than or equal (test A <= B) +# ge - greater than or equal (test A >= B) +# lt - less than (test A < B) +# gt - greater than (test A > B) +# +# Additionally, the eq and ne operator can have a number after it to limit +# the test to that number of minor versions. +# +# eq0 - equal up to the length of the shorter version +# ne0 - not equal up to the length of the shorter version +# eqN - equal up to N sub-version levels +# neN - not equal up to N sub-version levels +# +# When the condition is true, shell commands ACTION-IF-TRUE are run, +# otherwise shell commands ACTION-IF-FALSE are run. The environment +# variable 'ax_compare_version' is always set to either 'true' or 'false' +# as well. +# +# Examples: +# +# AX_COMPARE_VERSION([3.15.7],[lt],[3.15.8]) +# AX_COMPARE_VERSION([3.15],[lt],[3.15.8]) +# +# would both be true. +# +# AX_COMPARE_VERSION([3.15.7],[eq],[3.15.8]) +# AX_COMPARE_VERSION([3.15],[gt],[3.15.8]) +# +# would both be false. +# +# AX_COMPARE_VERSION([3.15.7],[eq2],[3.15.8]) +# +# would be true because it is only comparing two minor versions. +# +# AX_COMPARE_VERSION([3.15.7],[eq0],[3.15]) +# +# would be true because it is only comparing the lesser number of minor +# versions of the two values. +# +# Note: The characters that separate the version numbers do not matter. An +# empty string is the same as version 0. OP is evaluated by autoconf, not +# configure, so must be a string, not a variable. +# +# The author would like to acknowledge Guido Draheim whose advice about +# the m4_case and m4_ifvaln functions make this macro only include the +# portions necessary to perform the specific comparison specified by the +# OP argument in the final configure script. +# +# LAST MODIFICATION +# +# 2008-04-12 +# +# COPYLEFT +# +# Copyright (c) 2008 Tim Toolan +# +# Copying and distribution of this file, with or without modification, are +# permitted in any medium without royalty provided the copyright notice +# and this notice are preserved. + +dnl ######################################################################### +AC_DEFUN([AX_COMPARE_VERSION], [ + AC_PROG_AWK + + # Used to indicate true or false condition + ax_compare_version=false + + # Convert the two version strings to be compared into a format that + # allows a simple string comparison. The end result is that a version + # string of the form 1.12.5-r617 will be converted to the form + # 0001001200050617. In other words, each number is zero padded to four + # digits, and non digits are removed. + AS_VAR_PUSHDEF([A],[ax_compare_version_A]) + A=`echo "$1" | sed -e 's/\([[0-9]]*\)/Z\1Z/g' \ + -e 's/Z\([[0-9]]\)Z/Z0\1Z/g' \ + -e 's/Z\([[0-9]][[0-9]]\)Z/Z0\1Z/g' \ + -e 's/Z\([[0-9]][[0-9]][[0-9]]\)Z/Z0\1Z/g' \ + -e 's/[[^0-9]]//g'` + + AS_VAR_PUSHDEF([B],[ax_compare_version_B]) + B=`echo "$3" | sed -e 's/\([[0-9]]*\)/Z\1Z/g' \ + -e 's/Z\([[0-9]]\)Z/Z0\1Z/g' \ + -e 's/Z\([[0-9]][[0-9]]\)Z/Z0\1Z/g' \ + -e 's/Z\([[0-9]][[0-9]][[0-9]]\)Z/Z0\1Z/g' \ + -e 's/[[^0-9]]//g'` + + dnl # In the case of le, ge, lt, and gt, the strings are sorted as necessary + dnl # then the first line is used to determine if the condition is true. + dnl # The sed right after the echo is to remove any indented white space. + m4_case(m4_tolower($2), + [lt],[ + ax_compare_version=`echo "x$A +x$B" | sed 's/^ *//' | sort -r | sed "s/x${A}/false/;s/x${B}/true/;1q"` + ], + [gt],[ + ax_compare_version=`echo "x$A +x$B" | sed 's/^ *//' | sort | sed "s/x${A}/false/;s/x${B}/true/;1q"` + ], + [le],[ + ax_compare_version=`echo "x$A +x$B" | sed 's/^ *//' | sort | sed "s/x${A}/true/;s/x${B}/false/;1q"` + ], + [ge],[ + ax_compare_version=`echo "x$A +x$B" | sed 's/^ *//' | sort -r | sed "s/x${A}/true/;s/x${B}/false/;1q"` + ],[ + dnl Split the operator from the subversion count if present. + m4_bmatch(m4_substr($2,2), + [0],[ + # A count of zero means use the length of the shorter version. + # Determine the number of characters in A and B. + ax_compare_version_len_A=`echo "$A" | $AWK '{print(length)}'` + ax_compare_version_len_B=`echo "$B" | $AWK '{print(length)}'` + + # Set A to no more than B's length and B to no more than A's length. + A=`echo "$A" | sed "s/\(.\{$ax_compare_version_len_B\}\).*/\1/"` + B=`echo "$B" | sed "s/\(.\{$ax_compare_version_len_A\}\).*/\1/"` + ], + [[0-9]+],[ + # A count greater than zero means use only that many subversions + A=`echo "$A" | sed "s/\(\([[0-9]]\{4\}\)\{m4_substr($2,2)\}\).*/\1/"` + B=`echo "$B" | sed "s/\(\([[0-9]]\{4\}\)\{m4_substr($2,2)\}\).*/\1/"` + ], + [.+],[ + AC_WARNING( + [illegal OP numeric parameter: $2]) + ],[]) + + # Pad zeros at end of numbers to make same length. + ax_compare_version_tmp_A="$A`echo $B | sed 's/./0/g'`" + B="$B`echo $A | sed 's/./0/g'`" + A="$ax_compare_version_tmp_A" + + # Check for equality or inequality as necessary. + m4_case(m4_tolower(m4_substr($2,0,2)), + [eq],[ + test "x$A" = "x$B" && ax_compare_version=true + ], + [ne],[ + test "x$A" != "x$B" && ax_compare_version=true + ],[ + AC_WARNING([illegal OP parameter: $2]) + ]) + ]) + + AS_VAR_POPDEF([A])dnl + AS_VAR_POPDEF([B])dnl + + dnl # Execute ACTION-IF-TRUE / ACTION-IF-FALSE. + if test "$ax_compare_version" = "true" ; then + m4_ifvaln([$4],[$4],[:])dnl + m4_ifvaln([$5],[else $5])dnl + fi +]) dnl AX_COMPARE_VERSION + + +# =========================================================================== +# http://autoconf-archive.cryp.to/ax_prog_ruby_version.html +# =========================================================================== +# +# SYNOPSIS +# +# AX_PROG_RUBY_VERSION([VERSION],[ACTION-IF-TRUE],[ACTION-IF-FALSE]) +# +# DESCRIPTION +# +# Makes sure that ruby supports the version indicated. If true the shell +# commands in ACTION-IF-TRUE are executed. If not the shell commands in +# ACTION-IF-FALSE are run. Note if $RUBY is not set (for example by +# running AC_CHECK_PROG or AC_PATH_PROG), +# +# Example: +# +# AC_PATH_PROG([RUBY],[ruby]) +# AC_PROG_RUBY_VERSION([1.8.0],[ ... ],[ ... ]) +# +# This will check to make sure that the ruby you have supports at least +# version 1.6.0. +# +# NOTE: This macro uses the $RUBY variable to perform the check. +# AX_WITH_RUBY can be used to set that variable prior to running this +# macro. The $RUBY_VERSION variable will be valorized with the detected +# version. +# +# LAST MODIFICATION +# +# 2008-04-12 +# +# COPYLEFT +# +# Copyright (c) 2008 Francesco Salvestrini +# +# Copying and distribution of this file, with or without modification, are +# permitted in any medium without royalty provided the copyright notice +# and this notice are preserved. + +AC_DEFUN([AX_PROG_RUBY_VERSION],[ + AC_REQUIRE([AC_PROG_SED]) + AC_REQUIRE([AC_PROG_GREP]) + + AS_IF([test -n "$RUBY"],[ + ax_ruby_version="$1" + + AC_MSG_CHECKING([for ruby version]) + changequote(<<,>>) + ruby_version=`$RUBY --version 2>&1 | $GREP "^ruby " | $SED -e 's/^.* \([0-9]*\.[0-9]*\.[0-9]*\) .*/\1/'` + changequote([,]) + AC_MSG_RESULT($ruby_version) + + AC_SUBST([RUBY_VERSION],[$ruby_version]) + + AX_COMPARE_VERSION([$ax_ruby_version],[le],[$ruby_version],[ + : + $2 + ],[ + : + $3 + ]) + ],[ + AC_MSG_WARN([could not find the ruby interpreter]) + $3 + ]) +]) + +# =========================================================================== +# http://autoconf-archive.cryp.to/ax_ruby_devel.html +# =========================================================================== +# +# SYNOPSIS +# +# AX_RUBY_DEVEL([version]) +# +# DESCRIPTION +# +# This macro checks for Ruby and tries to get the include path to +# 'ruby.h'. It provides the $(RUBY_CPPFLAGS) and $(RUBY_LDFLAGS) output +# variables. It also exports $(RUBY_EXTRA_LIBS) for embedding Ruby in your +# code. +# +# You can search for some particular version of Ruby by passing a +# parameter to this macro, for example "1.8.6". +# +# LAST MODIFICATION +# +# 2008-04-12 +# +# COPYLEFT +# +# Copyright (c) 2008 Rafal Rzepecki +# Copyright (c) 2008 Sebastian Huber +# Copyright (c) 2008 Alan W. Irwin +# Copyright (c) 2008 Rafael Laboissiere +# Copyright (c) 2008 Andrew Collier +# Copyright (c) 2008 Matteo Settenvini +# Copyright (c) 2008 Horst Knorr +# +# This program is free software: you can redistribute it and/or modify it +# under the terms of the GNU General Public License as published by the +# Free Software Foundation, either version 3 of the License, or (at your +# option) any later version. +# +# This program is distributed in the hope that it will be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General +# Public License for more details. +# +# You should have received a copy of the GNU General Public License along +# with this program. If not, see . +# +# As a special exception, the respective Autoconf Macro's copyright owner +# gives unlimited permission to copy, distribute and modify the configure +# scripts that are the output of Autoconf when processing the Macro. You +# need not follow the terms of the GNU General Public License when using +# or distributing such scripts, even though portions of the text of the +# Macro appear in them. The GNU General Public License (GPL) does govern +# all other use of the material that constitutes the Autoconf Macro. +# +# This special exception to the GPL applies to versions of the Autoconf +# Macro released by the Autoconf Macro Archive. When you make and +# distribute a modified version of the Autoconf Macro, you may extend this +# special exception to the GPL to apply to your modified version as well. + +AC_DEFUN([AX_RUBY_DEVEL],[ + AC_REQUIRE([AX_WITH_RUBY]) + AS_IF([test -n "$1"], [AX_PROG_RUBY_VERSION([$1])]) + + # + # Check if you have mkmf, else fail + # + AC_MSG_CHECKING([for the mkmf Ruby package]) + ac_mkmf_result=`$RUBY -rmkmf -e ";" 2>&1` + if test -z "$ac_mkmf_result"; then + AC_MSG_RESULT([yes]) + else + AC_MSG_RESULT([no]) + AC_MSG_ERROR([cannot import Ruby module "mkmf". +Please check your Ruby installation. The error was: +$ac_distutils_result]) + fi + + # + # Check for Ruby include path + # + AC_MSG_CHECKING([for Ruby include path]) + if test -z "$RUBY_CPPFLAGS"; then + ruby_path=`$RUBY -rmkmf -e 'print Config::CONFIG[["archdir"]]'` + if test -n "${ruby_path}"; then + ruby_path="-I$ruby_path" + fi + RUBY_CPPFLAGS=$ruby_path + fi + AC_MSG_RESULT([$RUBY_CPPFLAGS]) + AC_SUBST([RUBY_CPPFLAGS]) + + # + # Check for Ruby library path + # + AC_MSG_CHECKING([for Ruby library path]) + if test -z "$RUBY_LDFLAGS"; then + RUBY_LDFLAGS=`$RUBY -rmkmf -e 'print Config::CONFIG[["LIBRUBYARG_SHARED"]]'` + fi + AC_MSG_RESULT([$RUBY_LDFLAGS]) + AC_SUBST([RUBY_LDFLAGS]) + + # + # Check for site packages + # + AC_MSG_CHECKING([for Ruby site-packages path]) + if test -z "$RUBY_SITE_PKG"; then + RUBY_SITE_PKG=`$RUBY -rmkmf -e 'print Config::CONFIG[["sitearchdir"]]'` + fi + AC_MSG_RESULT([$RUBY_SITE_PKG]) + AC_SUBST([RUBY_SITE_PKG]) + + # + # libraries which must be linked in when embedding + # + AC_MSG_CHECKING(ruby extra libraries) + if test -z "$RUBY_EXTRA_LIBS"; then + RUBY_EXTRA_LIBS=`$RUBY -rmkmf -e 'print Config::CONFIG[["SOLIBS"]]'` + fi + AC_MSG_RESULT([$RUBY_EXTRA_LIBS]) + AC_SUBST(RUBY_EXTRA_LIBS) + + # + # linking flags needed when embedding + # (is it even needed for Ruby?) + # + # AC_MSG_CHECKING(ruby extra linking flags) + # if test -z "$RUBY_EXTRA_LDFLAGS"; then + # RUBY_EXTRA_LDFLAGS=`$RUBY -rmkmf -e 'print Config::CONFIG[["LINKFORSHARED"]]'` + # fi + # AC_MSG_RESULT([$RUBY_EXTRA_LDFLAGS]) + # AC_SUBST(RUBY_EXTRA_LDFLAGS) + + # this flags breaks ruby.h, and is sometimes defined by KDE m4 macros + CFLAGS="`echo "$CFLAGS" | sed -e 's/-std=iso9899:1990//g;'`" + # + # final check to see if everything compiles alright + # + AC_MSG_CHECKING([consistency of all components of ruby development environment]) + AC_LANG_PUSH([C]) + # save current global flags + ac_save_LIBS="$LIBS" + LIBS="$ac_save_LIBS $RUBY_LDFLAGS" + ac_save_CPPFLAGS="$CPPFLAGS" + CPPFLAGS="$ac_save_CPPFLAGS $RUBY_CPPFLAGS" + AC_TRY_LINK([ + #include + ],[ + ruby_init(); + ],[rubyexists=yes],[rubyexists=no]) + + AC_MSG_RESULT([$rubyexists]) + + if test ! "$rubyexists" = "yes"; then + AC_MSG_ERROR([ + Could not link test program to Ruby. Maybe the main Ruby library has been + installed in some non-standard library path. If so, pass it to configure, + via the LDFLAGS environment variable. + Example: ./configure LDFLAGS="-L/usr/non-standard-path/ruby/lib" + ============================================================================ + ERROR! + You probably have to install the development version of the Ruby package + for your distribution. The exact name of this package varies among them. + ============================================================================ + ]) + RUBY_VERSION="" + fi + AC_LANG_POP + # turn back to default flags + CPPFLAGS="$ac_save_CPPFLAGS" + LIBS="$ac_save_LIBS" + + # + # all done! + # +]) + --- /dev/null +++ b/origin-src/wlach-libroutez-272ef93/autogen.sh @@ -1,1 +1,4 @@ +#!/bin/sh +aclocal +autoconf --- /dev/null +++ b/origin-src/wlach-libroutez-272ef93/config.guess @@ -1,1 +1,1467 @@ - +#! /bin/sh +# Attempt to guess a canonical system name. +# Copyright (C) 1992, 1993, 1994, 1995, 1996, 1997, 1998, 1999, +# 2000, 2001, 2002, 2003, 2004, 2005 Free Software Foundation, Inc. + +timestamp='2005-08-03' + +# This file is free software; you can redistribute it and/or modify it +# under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street - Fifth Floor, Boston, MA +# 02110-1301, USA. +# +# As a special exception to the GNU General Public License, if you +# distribute this file as part of a program that contains a +# configuration script generated by Autoconf, you may include it under +# the same distribution terms that you use for the rest of that program. + + +# Originally written by Per Bothner . +# Please send patches to . Submit a context +# diff and a properly formatted ChangeLog entry. +# +# This script attempts to guess a canonical system name similar to +# config.sub. If it succeeds, it prints the system name on stdout, and +# exits with 0. Otherwise, it exits with 1. +# +# The plan is that this can be called by configure scripts if you +# don't specify an explicit build system type. + +me=`echo "$0" | sed -e 's,.*/,,'` + +usage="\ +Usage: $0 [OPTION] + +Output the configuration name of the system \`$me' is run on. + +Operation modes: + -h, --help print this help, then exit + -t, --time-stamp print date of last modification, then exit + -v, --version print version number, then exit + +Report bugs and patches to ." + +version="\ +GNU config.guess ($timestamp) + +Originally written by Per Bothner. +Copyright (C) 1992, 1993, 1994, 1995, 1996, 1997, 1998, 1999, 2000, 2001, 2002, 2003, 2004, 2005 +Free Software Foundation, Inc. + +This is free software; see the source for copying conditions. There is NO +warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE." + +help=" +Try \`$me --help' for more information." + +# Parse command line +while test $# -gt 0 ; do + case $1 in + --time-stamp | --time* | -t ) + echo "$timestamp" ; exit ;; + --version | -v ) + echo "$version" ; exit ;; + --help | --h* | -h ) + echo "$usage"; exit ;; + -- ) # Stop option processing + shift; break ;; + - ) # Use stdin as input. + break ;; + -* ) + echo "$me: invalid option $1$help" >&2 + exit 1 ;; + * ) + break ;; + esac +done + +if test $# != 0; then + echo "$me: too many arguments$help" >&2 + exit 1 +fi + +trap 'exit 1' 1 2 15 + +# CC_FOR_BUILD -- compiler used by this script. Note that the use of a +# compiler to aid in system detection is discouraged as it requires +# temporary files to be created and, as you can see below, it is a +# headache to deal with in a portable fashion. + +# Historically, `CC_FOR_BUILD' used to be named `HOST_CC'. We still +# use `HOST_CC' if defined, but it is deprecated. + +# Portable tmp directory creation inspired by the Autoconf team. + +set_cc_for_build=' +trap "exitcode=\$?; (rm -f \$tmpfiles 2>/dev/null; rmdir \$tmp 2>/dev/null) && exit \$exitcode" 0 ; +trap "rm -f \$tmpfiles 2>/dev/null; rmdir \$tmp 2>/dev/null; exit 1" 1 2 13 15 ; +: ${TMPDIR=/tmp} ; + { tmp=`(umask 077 && mktemp -d -q "$TMPDIR/cgXXXXXX") 2>/dev/null` && test -n "$tmp" && test -d "$tmp" ; } || + { test -n "$RANDOM" && tmp=$TMPDIR/cg$$-$RANDOM && (umask 077 && mkdir $tmp) ; } || + { tmp=$TMPDIR/cg-$$ && (umask 077 && mkdir $tmp) && echo "Warning: creating insecure temp directory" >&2 ; } || + { echo "$me: cannot create a temporary directory in $TMPDIR" >&2 ; exit 1 ; } ; +dummy=$tmp/dummy ; +tmpfiles="$dummy.c $dummy.o $dummy.rel $dummy" ; +case $CC_FOR_BUILD,$HOST_CC,$CC in + ,,) echo "int x;" > $dummy.c ; + for c in cc gcc c89 c99 ; do + if ($c -c -o $dummy.o $dummy.c) >/dev/null 2>&1 ; then + CC_FOR_BUILD="$c"; break ; + fi ; + done ; + if test x"$CC_FOR_BUILD" = x ; then + CC_FOR_BUILD=no_compiler_found ; + fi + ;; + ,,*) CC_FOR_BUILD=$CC ;; + ,*,*) CC_FOR_BUILD=$HOST_CC ;; +esac ; set_cc_for_build= ;' + +# This is needed to find uname on a Pyramid OSx when run in the BSD universe. +# (ghazi@noc.rutgers.edu 1994-08-24) +if (test -f /.attbin/uname) >/dev/null 2>&1 ; then + PATH=$PATH:/.attbin ; export PATH +fi + +UNAME_MACHINE=`(uname -m) 2>/dev/null` || UNAME_MACHINE=unknown +UNAME_RELEASE=`(uname -r) 2>/dev/null` || UNAME_RELEASE=unknown +UNAME_SYSTEM=`(uname -s) 2>/dev/null` || UNAME_SYSTEM=unknown +UNAME_VERSION=`(uname -v) 2>/dev/null` || UNAME_VERSION=unknown + +# Note: order is significant - the case branches are not exclusive. + +case "${UNAME_MACHINE}:${UNAME_SYSTEM}:${UNAME_RELEASE}:${UNAME_VERSION}" in + *:NetBSD:*:*) + # NetBSD (nbsd) targets should (where applicable) match one or + # more of the tupples: *-*-netbsdelf*, *-*-netbsdaout*, + # *-*-netbsdecoff* and *-*-netbsd*. For targets that recently + # switched to ELF, *-*-netbsd* would select the old + # object file format. This provides both forward + # compatibility and a consistent mechanism for selecting the + # object file format. + # + # Note: NetBSD doesn't particularly care about the vendor + # portion of the name. We always set it to "unknown". + sysctl="sysctl -n hw.machine_arch" + UNAME_MACHINE_ARCH=`(/sbin/$sysctl 2>/dev/null || \ + /usr/sbin/$sysctl 2>/dev/null || echo unknown)` + case "${UNAME_MACHINE_ARCH}" in + armeb) machine=armeb-unknown ;; + arm*) machine=arm-unknown ;; + sh3el) machine=shl-unknown ;; + sh3eb) machine=sh-unknown ;; + *) machine=${UNAME_MACHINE_ARCH}-unknown ;; + esac + # The Operating System including object format, if it has switched + # to ELF recently, or will in the future. + case "${UNAME_MACHINE_ARCH}" in + arm*|i386|m68k|ns32k|sh3*|sparc|vax) + eval $set_cc_for_build + if echo __ELF__ | $CC_FOR_BUILD -E - 2>/dev/null \ + | grep __ELF__ >/dev/null + then + # Once all utilities can be ECOFF (netbsdecoff) or a.out (netbsdaout). + # Return netbsd for either. FIX? + os=netbsd + else + os=netbsdelf + fi + ;; + *) + os=netbsd + ;; + esac + # The OS release + # Debian GNU/NetBSD machines have a different userland, and + # thus, need a distinct triplet. However, they do not need + # kernel version information, so it can be replaced with a + # suitable tag, in the style of linux-gnu. + case "${UNAME_VERSION}" in + Debian*) + release='-gnu' + ;; + *) + release=`echo ${UNAME_RELEASE}|sed -e 's/[-_].*/\./'` + ;; + esac + # Since CPU_TYPE-MANUFACTURER-KERNEL-OPERATING_SYSTEM: + # contains redundant information, the shorter form: + # CPU_TYPE-MANUFACTURER-OPERATING_SYSTEM is used. + echo "${machine}-${os}${release}" + exit ;; + *:OpenBSD:*:*) + UNAME_MACHINE_ARCH=`arch | sed 's/OpenBSD.//'` + echo ${UNAME_MACHINE_ARCH}-unknown-openbsd${UNAME_RELEASE} + exit ;; + *:ekkoBSD:*:*) + echo ${UNAME_MACHINE}-unknown-ekkobsd${UNAME_RELEASE} + exit ;; + macppc:MirBSD:*:*) + echo powerppc-unknown-mirbsd${UNAME_RELEASE} + exit ;; + *:MirBSD:*:*) + echo ${UNAME_MACHINE}-unknown-mirbsd${UNAME_RELEASE} + exit ;; + alpha:OSF1:*:*) + case $UNAME_RELEASE in + *4.0) + UNAME_RELEASE=`/usr/sbin/sizer -v | awk '{print $3}'` + ;; + *5.*) + UNAME_RELEASE=`/usr/sbin/sizer -v | awk '{print $4}'` + ;; + esac + # According to Compaq, /usr/sbin/psrinfo has been available on + # OSF/1 and Tru64 systems produced since 1995. I hope that + # covers most systems running today. This code pipes the CPU + # types through head -n 1, so we only detect the type of CPU 0. + ALPHA_CPU_TYPE=`/usr/sbin/psrinfo -v | sed -n -e 's/^ The alpha \(.*\) processor.*$/\1/p' | head -n 1` + case "$ALPHA_CPU_TYPE" in + "EV4 (21064)") + UNAME_MACHINE="alpha" ;; + "EV4.5 (21064)") + UNAME_MACHINE="alpha" ;; + "LCA4 (21066/21068)") + UNAME_MACHINE="alpha" ;; + "EV5 (21164)") + UNAME_MACHINE="alphaev5" ;; + "EV5.6 (21164A)") + UNAME_MACHINE="alphaev56" ;; + "EV5.6 (21164PC)") + UNAME_MACHINE="alphapca56" ;; + "EV5.7 (21164PC)") + UNAME_MACHINE="alphapca57" ;; + "EV6 (21264)") + UNAME_MACHINE="alphaev6" ;; + "EV6.7 (21264A)") + UNAME_MACHINE="alphaev67" ;; + "EV6.8CB (21264C)") + UNAME_MACHINE="alphaev68" ;; + "EV6.8AL (21264B)") + UNAME_MACHINE="alphaev68" ;; + "EV6.8CX (21264D)") + UNAME_MACHINE="alphaev68" ;; + "EV6.9A (21264/EV69A)") + UNAME_MACHINE="alphaev69" ;; + "EV7 (21364)") + UNAME_MACHINE="alphaev7" ;; + "EV7.9 (21364A)") + UNAME_MACHINE="alphaev79" ;; + esac + # A Pn.n version is a patched version. + # A Vn.n version is a released version. + # A Tn.n version is a released field test version. + # A Xn.n version is an unreleased experimental baselevel. + # 1.2 uses "1.2" for uname -r. + echo ${UNAME_MACHINE}-dec-osf`echo ${UNAME_RELEASE} | sed -e 's/^[PVTX]//' | tr 'ABCDEFGHIJKLMNOPQRSTUVWXYZ' 'abcdefghijklmnopqrstuvwxyz'` + exit ;; + Alpha\ *:Windows_NT*:*) + # How do we know it's Interix rather than the generic POSIX subsystem? + # Should we change UNAME_MACHINE based on the output of uname instead + # of the specific Alpha model? + echo alpha-pc-interix + exit ;; + 21064:Windows_NT:50:3) + echo alpha-dec-winnt3.5 + exit ;; + Amiga*:UNIX_System_V:4.0:*) + echo m68k-unknown-sysv4 + exit ;; + *:[Aa]miga[Oo][Ss]:*:*) + echo ${UNAME_MACHINE}-unknown-amigaos + exit ;; + *:[Mm]orph[Oo][Ss]:*:*) + echo ${UNAME_MACHINE}-unknown-morphos + exit ;; + *:OS/390:*:*) + echo i370-ibm-openedition + exit ;; + *:z/VM:*:*) + echo s390-ibm-zvmoe + exit ;; + *:OS400:*:*) + echo powerpc-ibm-os400 + exit ;; + arm:RISC*:1.[012]*:*|arm:riscix:1.[012]*:*) + echo arm-acorn-riscix${UNAME_RELEASE} + exit ;; + arm:riscos:*:*|arm:RISCOS:*:*) + echo arm-unknown-riscos + exit ;; + SR2?01:HI-UX/MPP:*:* | SR8000:HI-UX/MPP:*:*) + echo hppa1.1-hitachi-hiuxmpp + exit ;; + Pyramid*:OSx*:*:* | MIS*:OSx*:*:* | MIS*:SMP_DC-OSx*:*:*) + # akee@wpdis03.wpafb.af.mil (Earle F. Ake) contributed MIS and NILE. + if test "`(/bin/universe) 2>/dev/null`" = att ; then + echo pyramid-pyramid-sysv3 + else + echo pyramid-pyramid-bsd + fi + exit ;; + NILE*:*:*:dcosx) + echo pyramid-pyramid-svr4 + exit ;; + DRS?6000:unix:4.0:6*) + echo sparc-icl-nx6 + exit ;; + DRS?6000:UNIX_SV:4.2*:7* | DRS?6000:isis:4.2*:7*) + case `/usr/bin/uname -p` in + sparc) echo sparc-icl-nx7; exit ;; + esac ;; + sun4H:SunOS:5.*:*) + echo sparc-hal-solaris2`echo ${UNAME_RELEASE}|sed -e 's/[^.]*//'` + exit ;; + sun4*:SunOS:5.*:* | tadpole*:SunOS:5.*:*) + echo sparc-sun-solaris2`echo ${UNAME_RELEASE}|sed -e 's/[^.]*//'` + exit ;; + i86pc:SunOS:5.*:*) + echo i386-pc-solaris2`echo ${UNAME_RELEASE}|sed -e 's/[^.]*//'` + exit ;; + sun4*:SunOS:6*:*) + # According to config.sub, this is the proper way to canonicalize + # SunOS6. Hard to guess exactly what SunOS6 will be like, but + # it's likely to be more like Solaris than SunOS4. + echo sparc-sun-solaris3`echo ${UNAME_RELEASE}|sed -e 's/[^.]*//'` + exit ;; + sun4*:SunOS:*:*) + case "`/usr/bin/arch -k`" in + Series*|S4*) + UNAME_RELEASE=`uname -v` + ;; + esac + # Japanese Language versions have a version number like `4.1.3-JL'. + echo sparc-sun-sunos`echo ${UNAME_RELEASE}|sed -e 's/-/_/'` + exit ;; + sun3*:SunOS:*:*) + echo m68k-sun-sunos${UNAME_RELEASE} + exit ;; + sun*:*:4.2BSD:*) + UNAME_RELEASE=`(sed 1q /etc/motd | awk '{print substr($5,1,3)}') 2>/dev/null` + test "x${UNAME_RELEASE}" = "x" && UNAME_RELEASE=3 + case "`/bin/arch`" in + sun3) + echo m68k-sun-sunos${UNAME_RELEASE} + ;; + sun4) + echo sparc-sun-sunos${UNAME_RELEASE} + ;; + esac + exit ;; + aushp:SunOS:*:*) + echo sparc-auspex-sunos${UNAME_RELEASE} + exit ;; + # The situation for MiNT is a little confusing. The machine name + # can be virtually everything (everything which is not + # "atarist" or "atariste" at least should have a processor + # > m68000). The system name ranges from "MiNT" over "FreeMiNT" + # to the lowercase version "mint" (or "freemint"). Finally + # the system name "TOS" denotes a system which is actually not + # MiNT. But MiNT is downward compatible to TOS, so this should + # be no problem. + atarist[e]:*MiNT:*:* | atarist[e]:*mint:*:* | atarist[e]:*TOS:*:*) + echo m68k-atari-mint${UNAME_RELEASE} + exit ;; + atari*:*MiNT:*:* | atari*:*mint:*:* | atarist[e]:*TOS:*:*) + echo m68k-atari-mint${UNAME_RELEASE} + exit ;; + *falcon*:*MiNT:*:* | *falcon*:*mint:*:* | *falcon*:*TOS:*:*) + echo m68k-atari-mint${UNAME_RELEASE} + exit ;; + milan*:*MiNT:*:* | milan*:*mint:*:* | *milan*:*TOS:*:*) + echo m68k-milan-mint${UNAME_RELEASE} + exit ;; + hades*:*MiNT:*:* | hades*:*mint:*:* | *hades*:*TOS:*:*) + echo m68k-hades-mint${UNAME_RELEASE} + exit ;; + *:*MiNT:*:* | *:*mint:*:* | *:*TOS:*:*) + echo m68k-unknown-mint${UNAME_RELEASE} + exit ;; + m68k:machten:*:*) + echo m68k-apple-machten${UNAME_RELEASE} + exit ;; + powerpc:machten:*:*) + echo powerpc-apple-machten${UNAME_RELEASE} + exit ;; + RISC*:Mach:*:*) + echo mips-dec-mach_bsd4.3 + exit ;; + RISC*:ULTRIX:*:*) + echo mips-dec-ultrix${UNAME_RELEASE} + exit ;; + VAX*:ULTRIX*:*:*) + echo vax-dec-ultrix${UNAME_RELEASE} + exit ;; + 2020:CLIX:*:* | 2430:CLIX:*:*) + echo clipper-intergraph-clix${UNAME_RELEASE} + exit ;; + mips:*:*:UMIPS | mips:*:*:RISCos) + eval $set_cc_for_build + sed 's/^ //' << EOF >$dummy.c +#ifdef __cplusplus +#include /* for printf() prototype */ + int main (int argc, char *argv[]) { +#else + int main (argc, argv) int argc; char *argv[]; { +#endif + #if defined (host_mips) && defined (MIPSEB) + #if defined (SYSTYPE_SYSV) + printf ("mips-mips-riscos%ssysv\n", argv[1]); exit (0); + #endif + #if defined (SYSTYPE_SVR4) + printf ("mips-mips-riscos%ssvr4\n", argv[1]); exit (0); + #endif + #if defined (SYSTYPE_BSD43) || defined(SYSTYPE_BSD) + printf ("mips-mips-riscos%sbsd\n", argv[1]); exit (0); + #endif + #endif + exit (-1); + } +EOF + $CC_FOR_BUILD -o $dummy $dummy.c && + dummyarg=`echo "${UNAME_RELEASE}" | sed -n 's/\([0-9]*\).*/\1/p'` && + SYSTEM_NAME=`$dummy $dummyarg` && + { echo "$SYSTEM_NAME"; exit; } + echo mips-mips-riscos${UNAME_RELEASE} + exit ;; + Motorola:PowerMAX_OS:*:*) + echo powerpc-motorola-powermax + exit ;; + Motorola:*:4.3:PL8-*) + echo powerpc-harris-powermax + exit ;; + Night_Hawk:*:*:PowerMAX_OS | Synergy:PowerMAX_OS:*:*) + echo powerpc-harris-powermax + exit ;; + Night_Hawk:Power_UNIX:*:*) + echo powerpc-harris-powerunix + exit ;; + m88k:CX/UX:7*:*) + echo m88k-harris-cxux7 + exit ;; + m88k:*:4*:R4*) + echo m88k-motorola-sysv4 + exit ;; + m88k:*:3*:R3*) + echo m88k-motorola-sysv3 + exit ;; + AViiON:dgux:*:*) + # DG/UX returns AViiON for all architectures + UNAME_PROCESSOR=`/usr/bin/uname -p` + if [ $UNAME_PROCESSOR = mc88100 ] || [ $UNAME_PROCESSOR = mc88110 ] + then + if [ ${TARGET_BINARY_INTERFACE}x = m88kdguxelfx ] || \ + [ ${TARGET_BINARY_INTERFACE}x = x ] + then + echo m88k-dg-dgux${UNAME_RELEASE} + else + echo m88k-dg-dguxbcs${UNAME_RELEASE} + fi + else + echo i586-dg-dgux${UNAME_RELEASE} + fi + exit ;; + M88*:DolphinOS:*:*) # DolphinOS (SVR3) + echo m88k-dolphin-sysv3 + exit ;; + M88*:*:R3*:*) + # Delta 88k system running SVR3 + echo m88k-motorola-sysv3 + exit ;; + XD88*:*:*:*) # Tektronix XD88 system running UTekV (SVR3) + echo m88k-tektronix-sysv3 + exit ;; + Tek43[0-9][0-9]:UTek:*:*) # Tektronix 4300 system running UTek (BSD) + echo m68k-tektronix-bsd + exit ;; + *:IRIX*:*:*) + echo mips-sgi-irix`echo ${UNAME_RELEASE}|sed -e 's/-/_/g'` + exit ;; + ????????:AIX?:[12].1:2) # AIX 2.2.1 or AIX 2.1.1 is RT/PC AIX. + echo romp-ibm-aix # uname -m gives an 8 hex-code CPU id + exit ;; # Note that: echo "'`uname -s`'" gives 'AIX ' + i*86:AIX:*:*) + echo i386-ibm-aix + exit ;; + ia64:AIX:*:*) + if [ -x /usr/bin/oslevel ] ; then + IBM_REV=`/usr/bin/oslevel` + else + IBM_REV=${UNAME_VERSION}.${UNAME_RELEASE} + fi + echo ${UNAME_MACHINE}-ibm-aix${IBM_REV} + exit ;; + *:AIX:2:3) + if grep bos325 /usr/include/stdio.h >/dev/null 2>&1; then + eval $set_cc_for_build + sed 's/^ //' << EOF >$dummy.c + #include + + main() + { + if (!__power_pc()) + exit(1); + puts("powerpc-ibm-aix3.2.5"); + exit(0); + } +EOF + if $CC_FOR_BUILD -o $dummy $dummy.c && SYSTEM_NAME=`$dummy` + then + echo "$SYSTEM_NAME" + else + echo rs6000-ibm-aix3.2.5 + fi + elif grep bos324 /usr/include/stdio.h >/dev/null 2>&1; then + echo rs6000-ibm-aix3.2.4 + else + echo rs6000-ibm-aix3.2 + fi + exit ;; + *:AIX:*:[45]) + IBM_CPU_ID=`/usr/sbin/lsdev -C -c processor -S available | sed 1q | awk '{ print $1 }'` + if /usr/sbin/lsattr -El ${IBM_CPU_ID} | grep ' POWER' >/dev/null 2>&1; then + IBM_ARCH=rs6000 + else + IBM_ARCH=powerpc + fi + if [ -x /usr/bin/oslevel ] ; then + IBM_REV=`/usr/bin/oslevel` + else + IBM_REV=${UNAME_VERSION}.${UNAME_RELEASE} + fi + echo ${IBM_ARCH}-ibm-aix${IBM_REV} + exit ;; + *:AIX:*:*) + echo rs6000-ibm-aix + exit ;; + ibmrt:4.4BSD:*|romp-ibm:BSD:*) + echo romp-ibm-bsd4.4 + exit ;; + ibmrt:*BSD:*|romp-ibm:BSD:*) # covers RT/PC BSD and + echo romp-ibm-bsd${UNAME_RELEASE} # 4.3 with uname added to + exit ;; # report: romp-ibm BSD 4.3 + *:BOSX:*:*) + echo rs6000-bull-bosx + exit ;; + DPX/2?00:B.O.S.:*:*) + echo m68k-bull-sysv3 + exit ;; + 9000/[34]??:4.3bsd:1.*:*) + echo m68k-hp-bsd + exit ;; + hp300:4.4BSD:*:* | 9000/[34]??:4.3bsd:2.*:*) + echo m68k-hp-bsd4.4 + exit ;; + 9000/[34678]??:HP-UX:*:*) + HPUX_REV=`echo ${UNAME_RELEASE}|sed -e 's/[^.]*.[0B]*//'` + case "${UNAME_MACHINE}" in + 9000/31? ) HP_ARCH=m68000 ;; + 9000/[34]?? ) HP_ARCH=m68k ;; + 9000/[678][0-9][0-9]) + if [ -x /usr/bin/getconf ]; then + sc_cpu_version=`/usr/bin/getconf SC_CPU_VERSION 2>/dev/null` + sc_kernel_bits=`/usr/bin/getconf SC_KERNEL_BITS 2>/dev/null` + case "${sc_cpu_version}" in + 523) HP_ARCH="hppa1.0" ;; # CPU_PA_RISC1_0 + 528) HP_ARCH="hppa1.1" ;; # CPU_PA_RISC1_1 + 532) # CPU_PA_RISC2_0 + case "${sc_kernel_bits}" in + 32) HP_ARCH="hppa2.0n" ;; + 64) HP_ARCH="hppa2.0w" ;; + '') HP_ARCH="hppa2.0" ;; # HP-UX 10.20 + esac ;; + esac + fi + if [ "${HP_ARCH}" = "" ]; then + eval $set_cc_for_build + sed 's/^ //' << EOF >$dummy.c + + #define _HPUX_SOURCE + #include + #include + + int main () + { + #if defined(_SC_KERNEL_BITS) + long bits = sysconf(_SC_KERNEL_BITS); + #endif + long cpu = sysconf (_SC_CPU_VERSION); + + switch (cpu) + { + case CPU_PA_RISC1_0: puts ("hppa1.0"); break; + case CPU_PA_RISC1_1: puts ("hppa1.1"); break; + case CPU_PA_RISC2_0: + #if defined(_SC_KERNEL_BITS) + switch (bits) + { + case 64: puts ("hppa2.0w"); break; + case 32: puts ("hppa2.0n"); break; + default: puts ("hppa2.0"); break; + } break; + #else /* !defined(_SC_KERNEL_BITS) */ + puts ("hppa2.0"); break; + #endif + default: puts ("hppa1.0"); break; + } + exit (0); + } +EOF + (CCOPTS= $CC_FOR_BUILD -o $dummy $dummy.c 2>/dev/null) && HP_ARCH=`$dummy` + test -z "$HP_ARCH" && HP_ARCH=hppa + fi ;; + esac + if [ ${HP_ARCH} = "hppa2.0w" ] + then + eval $set_cc_for_build + + # hppa2.0w-hp-hpux* has a 64-bit kernel and a compiler generating + # 32-bit code. hppa64-hp-hpux* has the same kernel and a compiler + # generating 64-bit code. GNU and HP use different nomenclature: + # + # $ CC_FOR_BUILD=cc ./config.guess + # => hppa2.0w-hp-hpux11.23 + # $ CC_FOR_BUILD="cc +DA2.0w" ./config.guess + # => hppa64-hp-hpux11.23 + + if echo __LP64__ | (CCOPTS= $CC_FOR_BUILD -E - 2>/dev/null) | + grep __LP64__ >/dev/null + then + HP_ARCH="hppa2.0w" + else + HP_ARCH="hppa64" + fi + fi + echo ${HP_ARCH}-hp-hpux${HPUX_REV} + exit ;; + ia64:HP-UX:*:*) + HPUX_REV=`echo ${UNAME_RELEASE}|sed -e 's/[^.]*.[0B]*//'` + echo ia64-hp-hpux${HPUX_REV} + exit ;; + 3050*:HI-UX:*:*) + eval $set_cc_for_build + sed 's/^ //' << EOF >$dummy.c + #include + int + main () + { + long cpu = sysconf (_SC_CPU_VERSION); + /* The order matters, because CPU_IS_HP_MC68K erroneously returns + true for CPU_PA_RISC1_0. CPU_IS_PA_RISC returns correct + results, however. */ + if (CPU_IS_PA_RISC (cpu)) + { + switch (cpu) + { + case CPU_PA_RISC1_0: puts ("hppa1.0-hitachi-hiuxwe2"); break; + case CPU_PA_RISC1_1: puts ("hppa1.1-hitachi-hiuxwe2"); break; + case CPU_PA_RISC2_0: puts ("hppa2.0-hitachi-hiuxwe2"); break; + default: puts ("hppa-hitachi-hiuxwe2"); break; + } + } + else if (CPU_IS_HP_MC68K (cpu)) + puts ("m68k-hitachi-hiuxwe2"); + else puts ("unknown-hitachi-hiuxwe2"); + exit (0); + } +EOF + $CC_FOR_BUILD -o $dummy $dummy.c && SYSTEM_NAME=`$dummy` && + { echo "$SYSTEM_NAME"; exit; } + echo unknown-hitachi-hiuxwe2 + exit ;; + 9000/7??:4.3bsd:*:* | 9000/8?[79]:4.3bsd:*:* ) + echo hppa1.1-hp-bsd + exit ;; + 9000/8??:4.3bsd:*:*) + echo hppa1.0-hp-bsd + exit ;; + *9??*:MPE/iX:*:* | *3000*:MPE/iX:*:*) + echo hppa1.0-hp-mpeix + exit ;; + hp7??:OSF1:*:* | hp8?[79]:OSF1:*:* ) + echo hppa1.1-hp-osf + exit ;; + hp8??:OSF1:*:*) + echo hppa1.0-hp-osf + exit ;; + i*86:OSF1:*:*) + if [ -x /usr/sbin/sysversion ] ; then + echo ${UNAME_MACHINE}-unknown-osf1mk + else + echo ${UNAME_MACHINE}-unknown-osf1 + fi + exit ;; + parisc*:Lites*:*:*) + echo hppa1.1-hp-lites + exit ;; + C1*:ConvexOS:*:* | convex:ConvexOS:C1*:*) + echo c1-convex-bsd + exit ;; + C2*:ConvexOS:*:* | convex:ConvexOS:C2*:*) + if getsysinfo -f scalar_acc + then echo c32-convex-bsd + else echo c2-convex-bsd + fi + exit ;; + C34*:ConvexOS:*:* | convex:ConvexOS:C34*:*) + echo c34-convex-bsd + exit ;; + C38*:ConvexOS:*:* | convex:ConvexOS:C38*:*) + echo c38-convex-bsd + exit ;; + C4*:ConvexOS:*:* | convex:ConvexOS:C4*:*) + echo c4-convex-bsd + exit ;; + CRAY*Y-MP:*:*:*) + echo ymp-cray-unicos${UNAME_RELEASE} | sed -e 's/\.[^.]*$/.X/' + exit ;; + CRAY*[A-Z]90:*:*:*) + echo ${UNAME_MACHINE}-cray-unicos${UNAME_RELEASE} \ + | sed -e 's/CRAY.*\([A-Z]90\)/\1/' \ + -e y/ABCDEFGHIJKLMNOPQRSTUVWXYZ/abcdefghijklmnopqrstuvwxyz/ \ + -e 's/\.[^.]*$/.X/' + exit ;; + CRAY*TS:*:*:*) + echo t90-cray-unicos${UNAME_RELEASE} | sed -e 's/\.[^.]*$/.X/' + exit ;; + CRAY*T3E:*:*:*) + echo alphaev5-cray-unicosmk${UNAME_RELEASE} | sed -e 's/\.[^.]*$/.X/' + exit ;; + CRAY*SV1:*:*:*) + echo sv1-cray-unicos${UNAME_RELEASE} | sed -e 's/\.[^.]*$/.X/' + exit ;; + *:UNICOS/mp:*:*) + echo craynv-cray-unicosmp${UNAME_RELEASE} | sed -e 's/\.[^.]*$/.X/' + exit ;; + F30[01]:UNIX_System_V:*:* | F700:UNIX_System_V:*:*) + FUJITSU_PROC=`uname -m | tr 'ABCDEFGHIJKLMNOPQRSTUVWXYZ' 'abcdefghijklmnopqrstuvwxyz'` + FUJITSU_SYS=`uname -p | tr 'ABCDEFGHIJKLMNOPQRSTUVWXYZ' 'abcdefghijklmnopqrstuvwxyz' | sed -e 's/\///'` + FUJITSU_REL=`echo ${UNAME_RELEASE} | sed -e 's/ /_/'` + echo "${FUJITSU_PROC}-fujitsu-${FUJITSU_SYS}${FUJITSU_REL}" + exit ;; + 5000:UNIX_System_V:4.*:*) + FUJITSU_SYS=`uname -p | tr 'ABCDEFGHIJKLMNOPQRSTUVWXYZ' 'abcdefghijklmnopqrstuvwxyz' | sed -e 's/\///'` + FUJITSU_REL=`echo ${UNAME_RELEASE} | tr 'ABCDEFGHIJKLMNOPQRSTUVWXYZ' 'abcdefghijklmnopqrstuvwxyz' | sed -e 's/ /_/'` + echo "sparc-fujitsu-${FUJITSU_SYS}${FUJITSU_REL}" + exit ;; + i*86:BSD/386:*:* | i*86:BSD/OS:*:* | *:Ascend\ Embedded/OS:*:*) + echo ${UNAME_MACHINE}-pc-bsdi${UNAME_RELEASE} + exit ;; + sparc*:BSD/OS:*:*) + echo sparc-unknown-bsdi${UNAME_RELEASE} + exit ;; + *:BSD/OS:*:*) + echo ${UNAME_MACHINE}-unknown-bsdi${UNAME_RELEASE} + exit ;; + *:FreeBSD:*:*) + echo ${UNAME_MACHINE}-unknown-freebsd`echo ${UNAME_RELEASE}|sed -e 's/[-(].*//'` + exit ;; + i*:CYGWIN*:*) + echo ${UNAME_MACHINE}-pc-cygwin + exit ;; + i*:MINGW*:*) + echo ${UNAME_MACHINE}-pc-mingw32 + exit ;; + i*:windows32*:*) + # uname -m includes "-pc" on this system. + echo ${UNAME_MACHINE}-mingw32 + exit ;; + i*:PW*:*) + echo ${UNAME_MACHINE}-pc-pw32 + exit ;; + x86:Interix*:[34]*) + echo i586-pc-interix${UNAME_RELEASE}|sed -e 's/\..*//' + exit ;; + [345]86:Windows_95:* | [345]86:Windows_98:* | [345]86:Windows_NT:*) + echo i${UNAME_MACHINE}-pc-mks + exit ;; + i*:Windows_NT*:* | Pentium*:Windows_NT*:*) + # How do we know it's Interix rather than the generic POSIX subsystem? + # It also conflicts with pre-2.0 versions of AT&T UWIN. Should we + # UNAME_MACHINE based on the output of uname instead of i386? + echo i586-pc-interix + exit ;; + i*:UWIN*:*) + echo ${UNAME_MACHINE}-pc-uwin + exit ;; + amd64:CYGWIN*:*:* | x86_64:CYGWIN*:*:*) + echo x86_64-unknown-cygwin + exit ;; + p*:CYGWIN*:*) + echo powerpcle-unknown-cygwin + exit ;; + prep*:SunOS:5.*:*) + echo powerpcle-unknown-solaris2`echo ${UNAME_RELEASE}|sed -e 's/[^.]*//'` + exit ;; + *:GNU:*:*) + # the GNU system + echo `echo ${UNAME_MACHINE}|sed -e 's,[-/].*$,,'`-unknown-gnu`echo ${UNAME_RELEASE}|sed -e 's,/.*$,,'` + exit ;; + *:GNU/*:*:*) + # other systems with GNU libc and userland + echo ${UNAME_MACHINE}-unknown-`echo ${UNAME_SYSTEM} | sed 's,^[^/]*/,,' | tr '[A-Z]' '[a-z]'``echo ${UNAME_RELEASE}|sed -e 's/[-(].*//'`-gnu + exit ;; + i*86:Minix:*:*) + echo ${UNAME_MACHINE}-pc-minix + exit ;; + arm*:Linux:*:*) + echo ${UNAME_MACHINE}-unknown-linux-gnu + exit ;; + cris:Linux:*:*) + echo cris-axis-linux-gnu + exit ;; + crisv32:Linux:*:*) + echo crisv32-axis-linux-gnu + exit ;; + frv:Linux:*:*) + echo frv-unknown-linux-gnu + exit ;; + ia64:Linux:*:*) + echo ${UNAME_MACHINE}-unknown-linux-gnu + exit ;; + m32r*:Linux:*:*) + echo ${UNAME_MACHINE}-unknown-linux-gnu + exit ;; + m68*:Linux:*:*) + echo ${UNAME_MACHINE}-unknown-linux-gnu + exit ;; + mips:Linux:*:*) + eval $set_cc_for_build + sed 's/^ //' << EOF >$dummy.c + #undef CPU + #undef mips + #undef mipsel + #if defined(__MIPSEL__) || defined(__MIPSEL) || defined(_MIPSEL) || defined(MIPSEL) + CPU=mipsel + #else + #if defined(__MIPSEB__) || defined(__MIPSEB) || defined(_MIPSEB) || defined(MIPSEB) + CPU=mips + #else + CPU= + #endif + #endif +EOF + eval `$CC_FOR_BUILD -E $dummy.c 2>/dev/null | grep ^CPU=` + test x"${CPU}" != x && { echo "${CPU}-unknown-linux-gnu"; exit; } + ;; + mips64:Linux:*:*) + eval $set_cc_for_build + sed 's/^ //' << EOF >$dummy.c + #undef CPU + #undef mips64 + #undef mips64el + #if defined(__MIPSEL__) || defined(__MIPSEL) || defined(_MIPSEL) || defined(MIPSEL) + CPU=mips64el + #else + #if defined(__MIPSEB__) || defined(__MIPSEB) || defined(_MIPSEB) || defined(MIPSEB) + CPU=mips64 + #else + CPU= + #endif + #endif +EOF + eval `$CC_FOR_BUILD -E $dummy.c 2>/dev/null | grep ^CPU=` + test x"${CPU}" != x && { echo "${CPU}-unknown-linux-gnu"; exit; } + ;; + or32:Linux:*:*) + echo or32-unknown-linux-gnu + exit ;; + ppc:Linux:*:*) + echo powerpc-unknown-linux-gnu + exit ;; + ppc64:Linux:*:*) + echo powerpc64-unknown-linux-gnu + exit ;; + alpha:Linux:*:*) + case `sed -n '/^cpu model/s/^.*: \(.*\)/\1/p' < /proc/cpuinfo` in + EV5) UNAME_MACHINE=alphaev5 ;; + EV56) UNAME_MACHINE=alphaev56 ;; + PCA56) UNAME_MACHINE=alphapca56 ;; + PCA57) UNAME_MACHINE=alphapca56 ;; + EV6) UNAME_MACHINE=alphaev6 ;; + EV67) UNAME_MACHINE=alphaev67 ;; + EV68*) UNAME_MACHINE=alphaev68 ;; + esac + objdump --private-headers /bin/sh | grep ld.so.1 >/dev/null + if test "$?" = 0 ; then LIBC="libc1" ; else LIBC="" ; fi + echo ${UNAME_MACHINE}-unknown-linux-gnu${LIBC} + exit ;; + parisc:Linux:*:* | hppa:Linux:*:*) + # Look for CPU level + case `grep '^cpu[^a-z]*:' /proc/cpuinfo 2>/dev/null | cut -d' ' -f2` in + PA7*) echo hppa1.1-unknown-linux-gnu ;; + PA8*) echo hppa2.0-unknown-linux-gnu ;; + *) echo hppa-unknown-linux-gnu ;; + esac + exit ;; + parisc64:Linux:*:* | hppa64:Linux:*:*) + echo hppa64-unknown-linux-gnu + exit ;; + s390:Linux:*:* | s390x:Linux:*:*) + echo ${UNAME_MACHINE}-ibm-linux + exit ;; + sh64*:Linux:*:*) + echo ${UNAME_MACHINE}-unknown-linux-gnu + exit ;; + sh*:Linux:*:*) + echo ${UNAME_MACHINE}-unknown-linux-gnu + exit ;; + sparc:Linux:*:* | sparc64:Linux:*:*) + echo ${UNAME_MACHINE}-unknown-linux-gnu + exit ;; + x86_64:Linux:*:*) + echo x86_64-unknown-linux-gnu + exit ;; + i*86:Linux:*:*) + # The BFD linker knows what the default object file format is, so + # first see if it will tell us. cd to the root directory to prevent + # problems with other programs or directories called `ld' in the path. + # Set LC_ALL=C to ensure ld outputs messages in English. + ld_supported_targets=`cd /; LC_ALL=C ld --help 2>&1 \ + | sed -ne '/supported targets:/!d + s/[ ][ ]*/ /g + s/.*supported targets: *// + s/ .*// + p'` + case "$ld_supported_targets" in + elf32-i386) + TENTATIVE="${UNAME_MACHINE}-pc-linux-gnu" + ;; + a.out-i386-linux) + echo "${UNAME_MACHINE}-pc-linux-gnuaout" + exit ;; + coff-i386) + echo "${UNAME_MACHINE}-pc-linux-gnucoff" + exit ;; + "") + # Either a pre-BFD a.out linker (linux-gnuoldld) or + # one that does not give us useful --help. + echo "${UNAME_MACHINE}-pc-linux-gnuoldld" + exit ;; + esac + # Determine whether the default compiler is a.out or elf + eval $set_cc_for_build + sed 's/^ //' << EOF >$dummy.c + #include + #ifdef __ELF__ + # ifdef __GLIBC__ + # if __GLIBC__ >= 2 + LIBC=gnu + # else + LIBC=gnulibc1 + # endif + # else + LIBC=gnulibc1 + # endif + #else + #ifdef __INTEL_COMPILER + LIBC=gnu + #else + LIBC=gnuaout + #endif + #endif + #ifdef __dietlibc__ + LIBC=dietlibc + #endif +EOF + eval `$CC_FOR_BUILD -E $dummy.c 2>/dev/null | grep ^LIBC=` + test x"${LIBC}" != x && { + echo "${UNAME_MACHINE}-pc-linux-${LIBC}" + exit + } + test x"${TENTATIVE}" != x && { echo "${TENTATIVE}"; exit; } + ;; + i*86:DYNIX/ptx:4*:*) + # ptx 4.0 does uname -s correctly, with DYNIX/ptx in there. + # earlier versions are messed up and put the nodename in both + # sysname and nodename. + echo i386-sequent-sysv4 + exit ;; + i*86:UNIX_SV:4.2MP:2.*) + # Unixware is an offshoot of SVR4, but it has its own version + # number series starting with 2... + # I am not positive that other SVR4 systems won't match this, + # I just have to hope. -- rms. + # Use sysv4.2uw... so that sysv4* matches it. + echo ${UNAME_MACHINE}-pc-sysv4.2uw${UNAME_VERSION} + exit ;; + i*86:OS/2:*:*) + # If we were able to find `uname', then EMX Unix compatibility + # is probably installed. + echo ${UNAME_MACHINE}-pc-os2-emx + exit ;; + i*86:XTS-300:*:STOP) + echo ${UNAME_MACHINE}-unknown-stop + exit ;; + i*86:atheos:*:*) + echo ${UNAME_MACHINE}-unknown-atheos + exit ;; + i*86:syllable:*:*) + echo ${UNAME_MACHINE}-pc-syllable + exit ;; + i*86:LynxOS:2.*:* | i*86:LynxOS:3.[01]*:* | i*86:LynxOS:4.0*:*) + echo i386-unknown-lynxos${UNAME_RELEASE} + exit ;; + i*86:*DOS:*:*) + echo ${UNAME_MACHINE}-pc-msdosdjgpp + exit ;; + i*86:*:4.*:* | i*86:SYSTEM_V:4.*:*) + UNAME_REL=`echo ${UNAME_RELEASE} | sed 's/\/MP$//'` + if grep Novell /usr/include/link.h >/dev/null 2>/dev/null; then + echo ${UNAME_MACHINE}-univel-sysv${UNAME_REL} + else + echo ${UNAME_MACHINE}-pc-sysv${UNAME_REL} + fi + exit ;; + i*86:*:5:[678]*) + # UnixWare 7.x, OpenUNIX and OpenServer 6. + case `/bin/uname -X | grep "^Machine"` in + *486*) UNAME_MACHINE=i486 ;; + *Pentium) UNAME_MACHINE=i586 ;; + *Pent*|*Celeron) UNAME_MACHINE=i686 ;; + esac + echo ${UNAME_MACHINE}-unknown-sysv${UNAME_RELEASE}${UNAME_SYSTEM}${UNAME_VERSION} + exit ;; + i*86:*:3.2:*) + if test -f /usr/options/cb.name; then + UNAME_REL=`sed -n 's/.*Version //p' /dev/null >/dev/null ; then + UNAME_REL=`(/bin/uname -X|grep Release|sed -e 's/.*= //')` + (/bin/uname -X|grep i80486 >/dev/null) && UNAME_MACHINE=i486 + (/bin/uname -X|grep '^Machine.*Pentium' >/dev/null) \ + && UNAME_MACHINE=i586 + (/bin/uname -X|grep '^Machine.*Pent *II' >/dev/null) \ + && UNAME_MACHINE=i686 + (/bin/uname -X|grep '^Machine.*Pentium Pro' >/dev/null) \ + && UNAME_MACHINE=i686 + echo ${UNAME_MACHINE}-pc-sco$UNAME_REL + else + echo ${UNAME_MACHINE}-pc-sysv32 + fi + exit ;; + pc:*:*:*) + # Left here for compatibility: + # uname -m prints for DJGPP always 'pc', but it prints nothing about + # the processor, so we play safe by assuming i386. + echo i386-pc-msdosdjgpp + exit ;; + Intel:Mach:3*:*) + echo i386-pc-mach3 + exit ;; + paragon:*:*:*) + echo i860-intel-osf1 + exit ;; + i860:*:4.*:*) # i860-SVR4 + if grep Stardent /usr/include/sys/uadmin.h >/dev/null 2>&1 ; then + echo i860-stardent-sysv${UNAME_RELEASE} # Stardent Vistra i860-SVR4 + else # Add other i860-SVR4 vendors below as they are discovered. + echo i860-unknown-sysv${UNAME_RELEASE} # Unknown i860-SVR4 + fi + exit ;; + mini*:CTIX:SYS*5:*) + # "miniframe" + echo m68010-convergent-sysv + exit ;; + mc68k:UNIX:SYSTEM5:3.51m) + echo m68k-convergent-sysv + exit ;; + M680?0:D-NIX:5.3:*) + echo m68k-diab-dnix + exit ;; + M68*:*:R3V[5678]*:*) + test -r /sysV68 && { echo 'm68k-motorola-sysv'; exit; } ;; + 3[345]??:*:4.0:3.0 | 3[34]??A:*:4.0:3.0 | 3[34]??,*:*:4.0:3.0 | 3[34]??/*:*:4.0:3.0 | 4400:*:4.0:3.0 | 4850:*:4.0:3.0 | SKA40:*:4.0:3.0 | SDS2:*:4.0:3.0 | SHG2:*:4.0:3.0 | S7501*:*:4.0:3.0) + OS_REL='' + test -r /etc/.relid \ + && OS_REL=.`sed -n 's/[^ ]* [^ ]* \([0-9][0-9]\).*/\1/p' < /etc/.relid` + /bin/uname -p 2>/dev/null | grep 86 >/dev/null \ + && { echo i486-ncr-sysv4.3${OS_REL}; exit; } + /bin/uname -p 2>/dev/null | /bin/grep entium >/dev/null \ + && { echo i586-ncr-sysv4.3${OS_REL}; exit; } ;; + 3[34]??:*:4.0:* | 3[34]??,*:*:4.0:*) + /bin/uname -p 2>/dev/null | grep 86 >/dev/null \ + && { echo i486-ncr-sysv4; exit; } ;; + m68*:LynxOS:2.*:* | m68*:LynxOS:3.0*:*) + echo m68k-unknown-lynxos${UNAME_RELEASE} + exit ;; + mc68030:UNIX_System_V:4.*:*) + echo m68k-atari-sysv4 + exit ;; + TSUNAMI:LynxOS:2.*:*) + echo sparc-unknown-lynxos${UNAME_RELEASE} + exit ;; + rs6000:LynxOS:2.*:*) + echo rs6000-unknown-lynxos${UNAME_RELEASE} + exit ;; + PowerPC:LynxOS:2.*:* | PowerPC:LynxOS:3.[01]*:* | PowerPC:LynxOS:4.0*:*) + echo powerpc-unknown-lynxos${UNAME_RELEASE} + exit ;; + SM[BE]S:UNIX_SV:*:*) + echo mips-dde-sysv${UNAME_RELEASE} + exit ;; + RM*:ReliantUNIX-*:*:*) + echo mips-sni-sysv4 + exit ;; + RM*:SINIX-*:*:*) + echo mips-sni-sysv4 + exit ;; + *:SINIX-*:*:*) + if uname -p 2>/dev/null >/dev/null ; then + UNAME_MACHINE=`(uname -p) 2>/dev/null` + echo ${UNAME_MACHINE}-sni-sysv4 + else + echo ns32k-sni-sysv + fi + exit ;; + PENTIUM:*:4.0*:*) # Unisys `ClearPath HMP IX 4000' SVR4/MP effort + # says + echo i586-unisys-sysv4 + exit ;; + *:UNIX_System_V:4*:FTX*) + # From Gerald Hewes . + # How about differentiating between stratus architectures? -djm + echo hppa1.1-stratus-sysv4 + exit ;; + *:*:*:FTX*) + # From seanf@swdc.stratus.com. + echo i860-stratus-sysv4 + exit ;; + i*86:VOS:*:*) + # From Paul.Green@stratus.com. + echo ${UNAME_MACHINE}-stratus-vos + exit ;; + *:VOS:*:*) + # From Paul.Green@stratus.com. + echo hppa1.1-stratus-vos + exit ;; + mc68*:A/UX:*:*) + echo m68k-apple-aux${UNAME_RELEASE} + exit ;; + news*:NEWS-OS:6*:*) + echo mips-sony-newsos6 + exit ;; + R[34]000:*System_V*:*:* | R4000:UNIX_SYSV:*:* | R*000:UNIX_SV:*:*) + if [ -d /usr/nec ]; then + echo mips-nec-sysv${UNAME_RELEASE} + else + echo mips-unknown-sysv${UNAME_RELEASE} + fi + exit ;; + BeBox:BeOS:*:*) # BeOS running on hardware made by Be, PPC only. + echo powerpc-be-beos + exit ;; + BeMac:BeOS:*:*) # BeOS running on Mac or Mac clone, PPC only. + echo powerpc-apple-beos + exit ;; + BePC:BeOS:*:*) # BeOS running on Intel PC compatible. + echo i586-pc-beos + exit ;; + SX-4:SUPER-UX:*:*) + echo sx4-nec-superux${UNAME_RELEASE} + exit ;; + SX-5:SUPER-UX:*:*) + echo sx5-nec-superux${UNAME_RELEASE} + exit ;; + SX-6:SUPER-UX:*:*) + echo sx6-nec-superux${UNAME_RELEASE} + exit ;; + Power*:Rhapsody:*:*) + echo powerpc-apple-rhapsody${UNAME_RELEASE} + exit ;; + *:Rhapsody:*:*) + echo ${UNAME_MACHINE}-apple-rhapsody${UNAME_RELEASE} + exit ;; + *:Darwin:*:*) + UNAME_PROCESSOR=`uname -p` || UNAME_PROCESSOR=unknown + case $UNAME_PROCESSOR in + *86) UNAME_PROCESSOR=i686 ;; + unknown) UNAME_PROCESSOR=powerpc ;; + esac + echo ${UNAME_PROCESSOR}-apple-darwin${UNAME_RELEASE} + exit ;; + *:procnto*:*:* | *:QNX:[0123456789]*:*) + UNAME_PROCESSOR=`uname -p` + if test "$UNAME_PROCESSOR" = "x86"; then + UNAME_PROCESSOR=i386 + UNAME_MACHINE=pc + fi + echo ${UNAME_PROCESSOR}-${UNAME_MACHINE}-nto-qnx${UNAME_RELEASE} + exit ;; + *:QNX:*:4*) + echo i386-pc-qnx + exit ;; + NSE-?:NONSTOP_KERNEL:*:*) + echo nse-tandem-nsk${UNAME_RELEASE} + exit ;; + NSR-?:NONSTOP_KERNEL:*:*) + echo nsr-tandem-nsk${UNAME_RELEASE} + exit ;; + *:NonStop-UX:*:*) + echo mips-compaq-nonstopux + exit ;; + BS2000:POSIX*:*:*) + echo bs2000-siemens-sysv + exit ;; + DS/*:UNIX_System_V:*:*) + echo ${UNAME_MACHINE}-${UNAME_SYSTEM}-${UNAME_RELEASE} + exit ;; + *:Plan9:*:*) + # "uname -m" is not consistent, so use $cputype instead. 386 + # is converted to i386 for consistency with other x86 + # operating systems. + if test "$cputype" = "386"; then + UNAME_MACHINE=i386 + else + UNAME_MACHINE="$cputype" + fi + echo ${UNAME_MACHINE}-unknown-plan9 + exit ;; + *:TOPS-10:*:*) + echo pdp10-unknown-tops10 + exit ;; + *:TENEX:*:*) + echo pdp10-unknown-tenex + exit ;; + KS10:TOPS-20:*:* | KL10:TOPS-20:*:* | TYPE4:TOPS-20:*:*) + echo pdp10-dec-tops20 + exit ;; + XKL-1:TOPS-20:*:* | TYPE5:TOPS-20:*:*) + echo pdp10-xkl-tops20 + exit ;; + *:TOPS-20:*:*) + echo pdp10-unknown-tops20 + exit ;; + *:ITS:*:*) + echo pdp10-unknown-its + exit ;; + SEI:*:*:SEIUX) + echo mips-sei-seiux${UNAME_RELEASE} + exit ;; + *:DragonFly:*:*) + echo ${UNAME_MACHINE}-unknown-dragonfly`echo ${UNAME_RELEASE}|sed -e 's/[-(].*//'` + exit ;; + *:*VMS:*:*) + UNAME_MACHINE=`(uname -p) 2>/dev/null` + case "${UNAME_MACHINE}" in + A*) echo alpha-dec-vms ; exit ;; + I*) echo ia64-dec-vms ; exit ;; + V*) echo vax-dec-vms ; exit ;; + esac ;; + *:XENIX:*:SysV) + echo i386-pc-xenix + exit ;; + i*86:skyos:*:*) + echo ${UNAME_MACHINE}-pc-skyos`echo ${UNAME_RELEASE}` | sed -e 's/ .*$//' + exit ;; +esac + +#echo '(No uname command or uname output not recognized.)' 1>&2 +#echo "${UNAME_MACHINE}:${UNAME_SYSTEM}:${UNAME_RELEASE}:${UNAME_VERSION}" 1>&2 + +eval $set_cc_for_build +cat >$dummy.c < +# include +#endif +main () +{ +#if defined (sony) +#if defined (MIPSEB) + /* BFD wants "bsd" instead of "newsos". Perhaps BFD should be changed, + I don't know.... */ + printf ("mips-sony-bsd\n"); exit (0); +#else +#include + printf ("m68k-sony-newsos%s\n", +#ifdef NEWSOS4 + "4" +#else + "" +#endif + ); exit (0); +#endif +#endif + +#if defined (__arm) && defined (__acorn) && defined (__unix) + printf ("arm-acorn-riscix\n"); exit (0); +#endif + +#if defined (hp300) && !defined (hpux) + printf ("m68k-hp-bsd\n"); exit (0); +#endif + +#if defined (NeXT) +#if !defined (__ARCHITECTURE__) +#define __ARCHITECTURE__ "m68k" +#endif + int version; + version=`(hostinfo | sed -n 's/.*NeXT Mach \([0-9]*\).*/\1/p') 2>/dev/null`; + if (version < 4) + printf ("%s-next-nextstep%d\n", __ARCHITECTURE__, version); + else + printf ("%s-next-openstep%d\n", __ARCHITECTURE__, version); + exit (0); +#endif + +#if defined (MULTIMAX) || defined (n16) +#if defined (UMAXV) + printf ("ns32k-encore-sysv\n"); exit (0); +#else +#if defined (CMU) + printf ("ns32k-encore-mach\n"); exit (0); +#else + printf ("ns32k-encore-bsd\n"); exit (0); +#endif +#endif +#endif + +#if defined (__386BSD__) + printf ("i386-pc-bsd\n"); exit (0); +#endif + +#if defined (sequent) +#if defined (i386) + printf ("i386-sequent-dynix\n"); exit (0); +#endif +#if defined (ns32000) + printf ("ns32k-sequent-dynix\n"); exit (0); +#endif +#endif + +#if defined (_SEQUENT_) + struct utsname un; + + uname(&un); + + if (strncmp(un.version, "V2", 2) == 0) { + printf ("i386-sequent-ptx2\n"); exit (0); + } + if (strncmp(un.version, "V1", 2) == 0) { /* XXX is V1 correct? */ + printf ("i386-sequent-ptx1\n"); exit (0); + } + printf ("i386-sequent-ptx\n"); exit (0); + +#endif + +#if defined (vax) +# if !defined (ultrix) +# include +# if defined (BSD) +# if BSD == 43 + printf ("vax-dec-bsd4.3\n"); exit (0); +# else +# if BSD == 199006 + printf ("vax-dec-bsd4.3reno\n"); exit (0); +# else + printf ("vax-dec-bsd\n"); exit (0); +# endif +# endif +# else + printf ("vax-dec-bsd\n"); exit (0); +# endif +# else + printf ("vax-dec-ultrix\n"); exit (0); +# endif +#endif + +#if defined (alliant) && defined (i860) + printf ("i860-alliant-bsd\n"); exit (0); +#endif + + exit (1); +} +EOF + +$CC_FOR_BUILD -o $dummy $dummy.c 2>/dev/null && SYSTEM_NAME=`$dummy` && + { echo "$SYSTEM_NAME"; exit; } + +# Apollos put the system type in the environment. + +test -d /usr/apollo && { echo ${ISP}-apollo-${SYSTYPE}; exit; } + +# Convex versions that predate uname can use getsysinfo(1) + +if [ -x /usr/convex/getsysinfo ] +then + case `getsysinfo -f cpu_type` in + c1*) + echo c1-convex-bsd + exit ;; + c2*) + if getsysinfo -f scalar_acc + then echo c32-convex-bsd + else echo c2-convex-bsd + fi + exit ;; + c34*) + echo c34-convex-bsd + exit ;; + c38*) + echo c38-convex-bsd + exit ;; + c4*) + echo c4-convex-bsd + exit ;; + esac +fi + +cat >&2 < in order to provide the needed +information to handle your system. + +config.guess timestamp = $timestamp + +uname -m = `(uname -m) 2>/dev/null || echo unknown` +uname -r = `(uname -r) 2>/dev/null || echo unknown` +uname -s = `(uname -s) 2>/dev/null || echo unknown` +uname -v = `(uname -v) 2>/dev/null || echo unknown` + +/usr/bin/uname -p = `(/usr/bin/uname -p) 2>/dev/null` +/bin/uname -X = `(/bin/uname -X) 2>/dev/null` + +hostinfo = `(hostinfo) 2>/dev/null` +/bin/universe = `(/bin/universe) 2>/dev/null` +/usr/bin/arch -k = `(/usr/bin/arch -k) 2>/dev/null` +/bin/arch = `(/bin/arch) 2>/dev/null` +/usr/bin/oslevel = `(/usr/bin/oslevel) 2>/dev/null` +/usr/convex/getsysinfo = `(/usr/convex/getsysinfo) 2>/dev/null` + +UNAME_MACHINE = ${UNAME_MACHINE} +UNAME_RELEASE = ${UNAME_RELEASE} +UNAME_SYSTEM = ${UNAME_SYSTEM} +UNAME_VERSION = ${UNAME_VERSION} +EOF + +exit 1 + +# Local variables: +# eval: (add-hook 'write-file-hooks 'time-stamp) +# time-stamp-start: "timestamp='" +# time-stamp-format: "%:y-%02m-%02d" +# time-stamp-end: "'" +# End: + --- /dev/null +++ b/origin-src/wlach-libroutez-272ef93/config.mk.in @@ -1,1 +1,19 @@ +OS=@OS@ +PYTHON_LDFLAGS=@PYTHON_LDFLAGS@ +PYTHON_CFLAGS=@PYTHON_CPPFLAGS@ +RUBY_LDFLAGS=@RUBY_LDFLAGS@ +RUBY_CFLAGS=@RUBY_CPPFLAGS@ +RUBY_VERSION=@RUBY_VERSION@ +INSTALL=@INSTALL@ +INSTALL_DATA=@INSTALL_DATA@ +INSTALL_PROGRAM=@INSTALL_PROGRAM@ +INSTALL_SCRIPT=@INSTALL_SCRIPT@ + +prefix=@prefix@ +exec_prefix=@exec_prefix@ +bindir=@bindir@ +libdir=@libdir@ +libexecdir=@libexecdir@ +sbindir=@sbindir@ + --- /dev/null +++ b/origin-src/wlach-libroutez-272ef93/config.sub @@ -1,1 +1,1580 @@ - +#! /bin/sh +# Configuration validation subroutine script. +# Copyright (C) 1992, 1993, 1994, 1995, 1996, 1997, 1998, 1999, +# 2000, 2001, 2002, 2003, 2004, 2005 Free Software Foundation, Inc. + +timestamp='2005-07-08' + +# This file is (in principle) common to ALL GNU software. +# The presence of a machine in this file suggests that SOME GNU software +# can handle that machine. It does not imply ALL GNU software can. +# +# This file is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street - Fifth Floor, Boston, MA +# 02110-1301, USA. +# +# As a special exception to the GNU General Public License, if you +# distribute this file as part of a program that contains a +# configuration script generated by Autoconf, you may include it under +# the same distribution terms that you use for the rest of that program. + + +# Please send patches to . Submit a context +# diff and a properly formatted ChangeLog entry. +# +# Configuration subroutine to validate and canonicalize a configuration type. +# Supply the specified configuration type as an argument. +# If it is invalid, we print an error message on stderr and exit with code 1. +# Otherwise, we print the canonical config type on stdout and succeed. + +# This file is supposed to be the same for all GNU packages +# and recognize all the CPU types, system types and aliases +# that are meaningful with *any* GNU software. +# Each package is responsible for reporting which valid configurations +# it does not support. The user should be able to distinguish +# a failure to support a valid configuration from a meaningless +# configuration. + +# The goal of this file is to map all the various variations of a given +# machine specification into a single specification in the form: +# CPU_TYPE-MANUFACTURER-OPERATING_SYSTEM +# or in some cases, the newer four-part form: +# CPU_TYPE-MANUFACTURER-KERNEL-OPERATING_SYSTEM +# It is wrong to echo any other type of specification. + +me=`echo "$0" | sed -e 's,.*/,,'` + +usage="\ +Usage: $0 [OPTION] CPU-MFR-OPSYS + $0 [OPTION] ALIAS + +Canonicalize a configuration name. + +Operation modes: + -h, --help print this help, then exit + -t, --time-stamp print date of last modification, then exit + -v, --version print version number, then exit + +Report bugs and patches to ." + +version="\ +GNU config.sub ($timestamp) + +Copyright (C) 1992, 1993, 1994, 1995, 1996, 1997, 1998, 1999, 2000, 2001, 2002, 2003, 2004, 2005 +Free Software Foundation, Inc. + +This is free software; see the source for copying conditions. There is NO +warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE." + +help=" +Try \`$me --help' for more information." + +# Parse command line +while test $# -gt 0 ; do + case $1 in + --time-stamp | --time* | -t ) + echo "$timestamp" ; exit ;; + --version | -v ) + echo "$version" ; exit ;; + --help | --h* | -h ) + echo "$usage"; exit ;; + -- ) # Stop option processing + shift; break ;; + - ) # Use stdin as input. + break ;; + -* ) + echo "$me: invalid option $1$help" + exit 1 ;; + + *local*) + # First pass through any local machine types. + echo $1 + exit ;; + + * ) + break ;; + esac +done + +case $# in + 0) echo "$me: missing argument$help" >&2 + exit 1;; + 1) ;; + *) echo "$me: too many arguments$help" >&2 + exit 1;; +esac + +# Separate what the user gave into CPU-COMPANY and OS or KERNEL-OS (if any). +# Here we must recognize all the valid KERNEL-OS combinations. +maybe_os=`echo $1 | sed 's/^\(.*\)-\([^-]*-[^-]*\)$/\2/'` +case $maybe_os in + nto-qnx* | linux-gnu* | linux-dietlibc | linux-uclibc* | uclinux-uclibc* | uclinux-gnu* | \ + kfreebsd*-gnu* | knetbsd*-gnu* | netbsd*-gnu* | storm-chaos* | os2-emx* | rtmk-nova*) + os=-$maybe_os + basic_machine=`echo $1 | sed 's/^\(.*\)-\([^-]*-[^-]*\)$/\1/'` + ;; + *) + basic_machine=`echo $1 | sed 's/-[^-]*$//'` + if [ $basic_machine != $1 ] + then os=`echo $1 | sed 's/.*-/-/'` + else os=; fi + ;; +esac + +### Let's recognize common machines as not being operating systems so +### that things like config.sub decstation-3100 work. We also +### recognize some manufacturers as not being operating systems, so we +### can provide default operating systems below. +case $os in + -sun*os*) + # Prevent following clause from handling this invalid input. + ;; + -dec* | -mips* | -sequent* | -encore* | -pc532* | -sgi* | -sony* | \ + -att* | -7300* | -3300* | -delta* | -motorola* | -sun[234]* | \ + -unicom* | -ibm* | -next | -hp | -isi* | -apollo | -altos* | \ + -convergent* | -ncr* | -news | -32* | -3600* | -3100* | -hitachi* |\ + -c[123]* | -convex* | -sun | -crds | -omron* | -dg | -ultra | -tti* | \ + -harris | -dolphin | -highlevel | -gould | -cbm | -ns | -masscomp | \ + -apple | -axis | -knuth | -cray) + os= + basic_machine=$1 + ;; + -sim | -cisco | -oki | -wec | -winbond) + os= + basic_machine=$1 + ;; + -scout) + ;; + -wrs) + os=-vxworks + basic_machine=$1 + ;; + -chorusos*) + os=-chorusos + basic_machine=$1 + ;; + -chorusrdb) + os=-chorusrdb + basic_machine=$1 + ;; + -hiux*) + os=-hiuxwe2 + ;; + -sco5) + os=-sco3.2v5 + basic_machine=`echo $1 | sed -e 's/86-.*/86-pc/'` + ;; + -sco4) + os=-sco3.2v4 + basic_machine=`echo $1 | sed -e 's/86-.*/86-pc/'` + ;; + -sco3.2.[4-9]*) + os=`echo $os | sed -e 's/sco3.2./sco3.2v/'` + basic_machine=`echo $1 | sed -e 's/86-.*/86-pc/'` + ;; + -sco3.2v[4-9]*) + # Don't forget version if it is 3.2v4 or newer. + basic_machine=`echo $1 | sed -e 's/86-.*/86-pc/'` + ;; + -sco*) + os=-sco3.2v2 + basic_machine=`echo $1 | sed -e 's/86-.*/86-pc/'` + ;; + -udk*) + basic_machine=`echo $1 | sed -e 's/86-.*/86-pc/'` + ;; + -isc) + os=-isc2.2 + basic_machine=`echo $1 | sed -e 's/86-.*/86-pc/'` + ;; + -clix*) + basic_machine=clipper-intergraph + ;; + -isc*) + basic_machine=`echo $1 | sed -e 's/86-.*/86-pc/'` + ;; + -lynx*) + os=-lynxos + ;; + -ptx*) + basic_machine=`echo $1 | sed -e 's/86-.*/86-sequent/'` + ;; + -windowsnt*) + os=`echo $os | sed -e 's/windowsnt/winnt/'` + ;; + -psos*) + os=-psos + ;; + -mint | -mint[0-9]*) + basic_machine=m68k-atari + os=-mint + ;; +esac + +# Decode aliases for certain CPU-COMPANY combinations. +case $basic_machine in + # Recognize the basic CPU types without company name. + # Some are omitted here because they have special meanings below. + 1750a | 580 \ + | a29k \ + | alpha | alphaev[4-8] | alphaev56 | alphaev6[78] | alphapca5[67] \ + | alpha64 | alpha64ev[4-8] | alpha64ev56 | alpha64ev6[78] | alpha64pca5[67] \ + | am33_2.0 \ + | arc | arm | arm[bl]e | arme[lb] | armv[2345] | armv[345][lb] | avr \ + | bfin \ + | c4x | clipper \ + | d10v | d30v | dlx | dsp16xx \ + | fr30 | frv \ + | h8300 | h8500 | hppa | hppa1.[01] | hppa2.0 | hppa2.0[nw] | hppa64 \ + | i370 | i860 | i960 | ia64 \ + | ip2k | iq2000 \ + | m32r | m32rle | m68000 | m68k | m88k | maxq | mcore \ + | mips | mipsbe | mipseb | mipsel | mipsle \ + | mips16 \ + | mips64 | mips64el \ + | mips64vr | mips64vrel \ + | mips64orion | mips64orionel \ + | mips64vr4100 | mips64vr4100el \ + | mips64vr4300 | mips64vr4300el \ + | mips64vr5000 | mips64vr5000el \ + | mips64vr5900 | mips64vr5900el \ + | mipsisa32 | mipsisa32el \ + | mipsisa32r2 | mipsisa32r2el \ + | mipsisa64 | mipsisa64el \ + | mipsisa64r2 | mipsisa64r2el \ + | mipsisa64sb1 | mipsisa64sb1el \ + | mipsisa64sr71k | mipsisa64sr71kel \ + | mipstx39 | mipstx39el \ + | mn10200 | mn10300 \ + | ms1 \ + | msp430 \ + | ns16k | ns32k \ + | or32 \ + | pdp10 | pdp11 | pj | pjl \ + | powerpc | powerpc64 | powerpc64le | powerpcle | ppcbe \ + | pyramid \ + | sh | sh[1234] | sh[24]a | sh[23]e | sh[34]eb | shbe | shle | sh[1234]le | sh3ele \ + | sh64 | sh64le \ + | sparc | sparc64 | sparc64b | sparc86x | sparclet | sparclite \ + | sparcv8 | sparcv9 | sparcv9b \ + | strongarm \ + | tahoe | thumb | tic4x | tic80 | tron \ + | v850 | v850e \ + | we32k \ + | x86 | xscale | xscalee[bl] | xstormy16 | xtensa \ + | z8k) + basic_machine=$basic_machine-unknown + ;; + m32c) + basic_machine=$basic_machine-unknown + ;; + m6811 | m68hc11 | m6812 | m68hc12) + # Motorola 68HC11/12. + basic_machine=$basic_machine-unknown + os=-none + ;; + m88110 | m680[12346]0 | m683?2 | m68360 | m5200 | v70 | w65 | z8k) + ;; + + # We use `pc' rather than `unknown' + # because (1) that's what they normally are, and + # (2) the word "unknown" tends to confuse beginning users. + i*86 | x86_64) + basic_machine=$basic_machine-pc + ;; + # Object if more than one company name word. + *-*-*) + echo Invalid configuration \`$1\': machine \`$basic_machine\' not recognized 1>&2 + exit 1 + ;; + # Recognize the basic CPU types with company name. + 580-* \ + | a29k-* \ + | alpha-* | alphaev[4-8]-* | alphaev56-* | alphaev6[78]-* \ + | alpha64-* | alpha64ev[4-8]-* | alpha64ev56-* | alpha64ev6[78]-* \ + | alphapca5[67]-* | alpha64pca5[67]-* | arc-* \ + | arm-* | armbe-* | armle-* | armeb-* | armv*-* \ + | avr-* \ + | bfin-* | bs2000-* \ + | c[123]* | c30-* | [cjt]90-* | c4x-* | c54x-* | c55x-* | c6x-* \ + | clipper-* | craynv-* | cydra-* \ + | d10v-* | d30v-* | dlx-* \ + | elxsi-* \ + | f30[01]-* | f700-* | fr30-* | frv-* | fx80-* \ + | h8300-* | h8500-* \ + | hppa-* | hppa1.[01]-* | hppa2.0-* | hppa2.0[nw]-* | hppa64-* \ + | i*86-* | i860-* | i960-* | ia64-* \ + | ip2k-* | iq2000-* \ + | m32r-* | m32rle-* \ + | m68000-* | m680[012346]0-* | m68360-* | m683?2-* | m68k-* \ + | m88110-* | m88k-* | maxq-* | mcore-* \ + | mips-* | mipsbe-* | mipseb-* | mipsel-* | mipsle-* \ + | mips16-* \ + | mips64-* | mips64el-* \ + | mips64vr-* | mips64vrel-* \ + | mips64orion-* | mips64orionel-* \ + | mips64vr4100-* | mips64vr4100el-* \ + | mips64vr4300-* | mips64vr4300el-* \ + | mips64vr5000-* | mips64vr5000el-* \ + | mips64vr5900-* | mips64vr5900el-* \ + | mipsisa32-* | mipsisa32el-* \ + | mipsisa32r2-* | mipsisa32r2el-* \ + | mipsisa64-* | mipsisa64el-* \ + | mipsisa64r2-* | mipsisa64r2el-* \ + | mipsisa64sb1-* | mipsisa64sb1el-* \ + | mipsisa64sr71k-* | mipsisa64sr71kel-* \ + | mipstx39-* | mipstx39el-* \ + | mmix-* \ + | ms1-* \ + | msp430-* \ + | none-* | np1-* | ns16k-* | ns32k-* \ + | orion-* \ + | pdp10-* | pdp11-* | pj-* | pjl-* | pn-* | power-* \ + | powerpc-* | powerpc64-* | powerpc64le-* | powerpcle-* | ppcbe-* \ + | pyramid-* \ + | romp-* | rs6000-* \ + | sh-* | sh[1234]-* | sh[24]a-* | sh[23]e-* | sh[34]eb-* | shbe-* \ + | shle-* | sh[1234]le-* | sh3ele-* | sh64-* | sh64le-* \ + | sparc-* | sparc64-* | sparc64b-* | sparc86x-* | sparclet-* \ + | sparclite-* \ + | sparcv8-* | sparcv9-* | sparcv9b-* | strongarm-* | sv1-* | sx?-* \ + | tahoe-* | thumb-* \ + | tic30-* | tic4x-* | tic54x-* | tic55x-* | tic6x-* | tic80-* \ + | tron-* \ + | v850-* | v850e-* | vax-* \ + | we32k-* \ + | x86-* | x86_64-* | xps100-* | xscale-* | xscalee[bl]-* \ + | xstormy16-* | xtensa-* \ + | ymp-* \ + | z8k-*) + ;; + m32c-*) + ;; + # Recognize the various machine names and aliases which stand + # for a CPU type and a company and sometimes even an OS. + 386bsd) + basic_machine=i386-unknown + os=-bsd + ;; + 3b1 | 7300 | 7300-att | att-7300 | pc7300 | safari | unixpc) + basic_machine=m68000-att + ;; + 3b*) + basic_machine=we32k-att + ;; + a29khif) + basic_machine=a29k-amd + os=-udi + ;; + abacus) + basic_machine=abacus-unknown + ;; + adobe68k) + basic_machine=m68010-adobe + os=-scout + ;; + alliant | fx80) + basic_machine=fx80-alliant + ;; + altos | altos3068) + basic_machine=m68k-altos + ;; + am29k) + basic_machine=a29k-none + os=-bsd + ;; + amd64) + basic_machine=x86_64-pc + ;; + amd64-*) + basic_machine=x86_64-`echo $basic_machine | sed 's/^[^-]*-//'` + ;; + amdahl) + basic_machine=580-amdahl + os=-sysv + ;; + amiga | amiga-*) + basic_machine=m68k-unknown + ;; + amigaos | amigados) + basic_machine=m68k-unknown + os=-amigaos + ;; + amigaunix | amix) + basic_machine=m68k-unknown + os=-sysv4 + ;; + apollo68) + basic_machine=m68k-apollo + os=-sysv + ;; + apollo68bsd) + basic_machine=m68k-apollo + os=-bsd + ;; + aux) + basic_machine=m68k-apple + os=-aux + ;; + balance) + basic_machine=ns32k-sequent + os=-dynix + ;; + c90) + basic_machine=c90-cray + os=-unicos + ;; + convex-c1) + basic_machine=c1-convex + os=-bsd + ;; + convex-c2) + basic_machine=c2-convex + os=-bsd + ;; + convex-c32) + basic_machine=c32-convex + os=-bsd + ;; + convex-c34) + basic_machine=c34-convex + os=-bsd + ;; + convex-c38) + basic_machine=c38-convex + os=-bsd + ;; + cray | j90) + basic_machine=j90-cray + os=-unicos + ;; + craynv) + basic_machine=craynv-cray + os=-unicosmp + ;; + cr16c) + basic_machine=cr16c-unknown + os=-elf + ;; + crds | unos) + basic_machine=m68k-crds + ;; + crisv32 | crisv32-* | etraxfs*) + basic_machine=crisv32-axis + ;; + cris | cris-* | etrax*) + basic_machine=cris-axis + ;; + crx) + basic_machine=crx-unknown + os=-elf + ;; + da30 | da30-*) + basic_machine=m68k-da30 + ;; + decstation | decstation-3100 | pmax | pmax-* | pmin | dec3100 | decstatn) + basic_machine=mips-dec + ;; + decsystem10* | dec10*) + basic_machine=pdp10-dec + os=-tops10 + ;; + decsystem20* | dec20*) + basic_machine=pdp10-dec + os=-tops20 + ;; + delta | 3300 | motorola-3300 | motorola-delta \ + | 3300-motorola | delta-motorola) + basic_machine=m68k-motorola + ;; + delta88) + basic_machine=m88k-motorola + os=-sysv3 + ;; + djgpp) + basic_machine=i586-pc + os=-msdosdjgpp + ;; + dpx20 | dpx20-*) + basic_machine=rs6000-bull + os=-bosx + ;; + dpx2* | dpx2*-bull) + basic_machine=m68k-bull + os=-sysv3 + ;; + ebmon29k) + basic_machine=a29k-amd + os=-ebmon + ;; + elxsi) + basic_machine=elxsi-elxsi + os=-bsd + ;; + encore | umax | mmax) + basic_machine=ns32k-encore + ;; + es1800 | OSE68k | ose68k | ose | OSE) + basic_machine=m68k-ericsson + os=-ose + ;; + fx2800) + basic_machine=i860-alliant + ;; + genix) + basic_machine=ns32k-ns + ;; + gmicro) + basic_machine=tron-gmicro + os=-sysv + ;; + go32) + basic_machine=i386-pc + os=-go32 + ;; + h3050r* | hiux*) + basic_machine=hppa1.1-hitachi + os=-hiuxwe2 + ;; + h8300hms) + basic_machine=h8300-hitachi + os=-hms + ;; + h8300xray) + basic_machine=h8300-hitachi + os=-xray + ;; + h8500hms) + basic_machine=h8500-hitachi + os=-hms + ;; + harris) + basic_machine=m88k-harris + os=-sysv3 + ;; + hp300-*) + basic_machine=m68k-hp + ;; + hp300bsd) + basic_machine=m68k-hp + os=-bsd + ;; + hp300hpux) + basic_machine=m68k-hp + os=-hpux + ;; + hp3k9[0-9][0-9] | hp9[0-9][0-9]) + basic_machine=hppa1.0-hp + ;; + hp9k2[0-9][0-9] | hp9k31[0-9]) + basic_machine=m68000-hp + ;; + hp9k3[2-9][0-9]) + basic_machine=m68k-hp + ;; + hp9k6[0-9][0-9] | hp6[0-9][0-9]) + basic_machine=hppa1.0-hp + ;; + hp9k7[0-79][0-9] | hp7[0-79][0-9]) + basic_machine=hppa1.1-hp + ;; + hp9k78[0-9] | hp78[0-9]) + # FIXME: really hppa2.0-hp + basic_machine=hppa1.1-hp + ;; + hp9k8[67]1 | hp8[67]1 | hp9k80[24] | hp80[24] | hp9k8[78]9 | hp8[78]9 | hp9k893 | hp893) + # FIXME: really hppa2.0-hp + basic_machine=hppa1.1-hp + ;; + hp9k8[0-9][13679] | hp8[0-9][13679]) + basic_machine=hppa1.1-hp + ;; + hp9k8[0-9][0-9] | hp8[0-9][0-9]) + basic_machine=hppa1.0-hp + ;; + hppa-next) + os=-nextstep3 + ;; + hppaosf) + basic_machine=hppa1.1-hp + os=-osf + ;; + hppro) + basic_machine=hppa1.1-hp + os=-proelf + ;; + i370-ibm* | ibm*) + basic_machine=i370-ibm + ;; +# I'm not sure what "Sysv32" means. Should this be sysv3.2? + i*86v32) + basic_machine=`echo $1 | sed -e 's/86.*/86-pc/'` + os=-sysv32 + ;; + i*86v4*) + basic_machine=`echo $1 | sed -e 's/86.*/86-pc/'` + os=-sysv4 + ;; + i*86v) + basic_machine=`echo $1 | sed -e 's/86.*/86-pc/'` + os=-sysv + ;; + i*86sol2) + basic_machine=`echo $1 | sed -e 's/86.*/86-pc/'` + os=-solaris2 + ;; + i386mach) + basic_machine=i386-mach + os=-mach + ;; + i386-vsta | vsta) + basic_machine=i386-unknown + os=-vsta + ;; + iris | iris4d) + basic_machine=mips-sgi + case $os in + -irix*) + ;; + *) + os=-irix4 + ;; + esac + ;; + isi68 | isi) + basic_machine=m68k-isi + os=-sysv + ;; + m88k-omron*) + basic_machine=m88k-omron + ;; + magnum | m3230) + basic_machine=mips-mips + os=-sysv + ;; + merlin) + basic_machine=ns32k-utek + os=-sysv + ;; + mingw32) + basic_machine=i386-pc + os=-mingw32 + ;; + miniframe) + basic_machine=m68000-convergent + ;; + *mint | -mint[0-9]* | *MiNT | *MiNT[0-9]*) + basic_machine=m68k-atari + os=-mint + ;; + mips3*-*) + basic_machine=`echo $basic_machine | sed -e 's/mips3/mips64/'` + ;; + mips3*) + basic_machine=`echo $basic_machine | sed -e 's/mips3/mips64/'`-unknown + ;; + monitor) + basic_machine=m68k-rom68k + os=-coff + ;; + morphos) + basic_machine=powerpc-unknown + os=-morphos + ;; + msdos) + basic_machine=i386-pc + os=-msdos + ;; + mvs) + basic_machine=i370-ibm + os=-mvs + ;; + ncr3000) + basic_machine=i486-ncr + os=-sysv4 + ;; + netbsd386) + basic_machine=i386-unknown + os=-netbsd + ;; + netwinder) + basic_machine=armv4l-rebel + os=-linux + ;; + news | news700 | news800 | news900) + basic_machine=m68k-sony + os=-newsos + ;; + news1000) + basic_machine=m68030-sony + os=-newsos + ;; + news-3600 | risc-news) + basic_machine=mips-sony + os=-newsos + ;; + necv70) + basic_machine=v70-nec + os=-sysv + ;; + next | m*-next ) + basic_machine=m68k-next + case $os in + -nextstep* ) + ;; + -ns2*) + os=-nextstep2 + ;; + *) + os=-nextstep3 + ;; + esac + ;; + nh3000) + basic_machine=m68k-harris + os=-cxux + ;; + nh[45]000) + basic_machine=m88k-harris + os=-cxux + ;; + nindy960) + basic_machine=i960-intel + os=-nindy + ;; + mon960) + basic_machine=i960-intel + os=-mon960 + ;; + nonstopux) + basic_machine=mips-compaq + os=-nonstopux + ;; + np1) + basic_machine=np1-gould + ;; + nsr-tandem) + basic_machine=nsr-tandem + ;; + op50n-* | op60c-*) + basic_machine=hppa1.1-oki + os=-proelf + ;; + openrisc | openrisc-*) + basic_machine=or32-unknown + ;; + os400) + basic_machine=powerpc-ibm + os=-os400 + ;; + OSE68000 | ose68000) + basic_machine=m68000-ericsson + os=-ose + ;; + os68k) + basic_machine=m68k-none + os=-os68k + ;; + pa-hitachi) + basic_machine=hppa1.1-hitachi + os=-hiuxwe2 + ;; + paragon) + basic_machine=i860-intel + os=-osf + ;; + pbd) + basic_machine=sparc-tti + ;; + pbb) + basic_machine=m68k-tti + ;; + pc532 | pc532-*) + basic_machine=ns32k-pc532 + ;; + pentium | p5 | k5 | k6 | nexgen | viac3) + basic_machine=i586-pc + ;; + pentiumpro | p6 | 6x86 | athlon | athlon_*) + basic_machine=i686-pc + ;; + pentiumii | pentium2 | pentiumiii | pentium3) + basic_machine=i686-pc + ;; + pentium4) + basic_machine=i786-pc + ;; + pentium-* | p5-* | k5-* | k6-* | nexgen-* | viac3-*) + basic_machine=i586-`echo $basic_machine | sed 's/^[^-]*-//'` + ;; + pentiumpro-* | p6-* | 6x86-* | athlon-*) + basic_machine=i686-`echo $basic_machine | sed 's/^[^-]*-//'` + ;; + pentiumii-* | pentium2-* | pentiumiii-* | pentium3-*) + basic_machine=i686-`echo $basic_machine | sed 's/^[^-]*-//'` + ;; + pentium4-*) + basic_machine=i786-`echo $basic_machine | sed 's/^[^-]*-//'` + ;; + pn) + basic_machine=pn-gould + ;; + power) basic_machine=power-ibm + ;; + ppc) basic_machine=powerpc-unknown + ;; + ppc-*) basic_machine=powerpc-`echo $basic_machine | sed 's/^[^-]*-//'` + ;; + ppcle | powerpclittle | ppc-le | powerpc-little) + basic_machine=powerpcle-unknown + ;; + ppcle-* | powerpclittle-*) + basic_machine=powerpcle-`echo $basic_machine | sed 's/^[^-]*-//'` + ;; + ppc64) basic_machine=powerpc64-unknown + ;; + ppc64-*) basic_machine=powerpc64-`echo $basic_machine | sed 's/^[^-]*-//'` + ;; + ppc64le | powerpc64little | ppc64-le | powerpc64-little) + basic_machine=powerpc64le-unknown + ;; + ppc64le-* | powerpc64little-*) + basic_machine=powerpc64le-`echo $basic_machine | sed 's/^[^-]*-//'` + ;; + ps2) + basic_machine=i386-ibm + ;; + pw32) + basic_machine=i586-unknown + os=-pw32 + ;; + rom68k) + basic_machine=m68k-rom68k + os=-coff + ;; + rm[46]00) + basic_machine=mips-siemens + ;; + rtpc | rtpc-*) + basic_machine=romp-ibm + ;; + s390 | s390-*) + basic_machine=s390-ibm + ;; + s390x | s390x-*) + basic_machine=s390x-ibm + ;; + sa29200) + basic_machine=a29k-amd + os=-udi + ;; + sb1) + basic_machine=mipsisa64sb1-unknown + ;; + sb1el) + basic_machine=mipsisa64sb1el-unknown + ;; + sei) + basic_machine=mips-sei + os=-seiux + ;; + sequent) + basic_machine=i386-sequent + ;; + sh) + basic_machine=sh-hitachi + os=-hms + ;; + sh64) + basic_machine=sh64-unknown + ;; + sparclite-wrs | simso-wrs) + basic_machine=sparclite-wrs + os=-vxworks + ;; + sps7) + basic_machine=m68k-bull + os=-sysv2 + ;; + spur) + basic_machine=spur-unknown + ;; + st2000) + basic_machine=m68k-tandem + ;; + stratus) + basic_machine=i860-stratus + os=-sysv4 + ;; + sun2) + basic_machine=m68000-sun + ;; + sun2os3) + basic_machine=m68000-sun + os=-sunos3 + ;; + sun2os4) + basic_machine=m68000-sun + os=-sunos4 + ;; + sun3os3) + basic_machine=m68k-sun + os=-sunos3 + ;; + sun3os4) + basic_machine=m68k-sun + os=-sunos4 + ;; + sun4os3) + basic_machine=sparc-sun + os=-sunos3 + ;; + sun4os4) + basic_machine=sparc-sun + os=-sunos4 + ;; + sun4sol2) + basic_machine=sparc-sun + os=-solaris2 + ;; + sun3 | sun3-*) + basic_machine=m68k-sun + ;; + sun4) + basic_machine=sparc-sun + ;; + sun386 | sun386i | roadrunner) + basic_machine=i386-sun + ;; + sv1) + basic_machine=sv1-cray + os=-unicos + ;; + symmetry) + basic_machine=i386-sequent + os=-dynix + ;; + t3e) + basic_machine=alphaev5-cray + os=-unicos + ;; + t90) + basic_machine=t90-cray + os=-unicos + ;; + tic54x | c54x*) + basic_machine=tic54x-unknown + os=-coff + ;; + tic55x | c55x*) + basic_machine=tic55x-unknown + os=-coff + ;; + tic6x | c6x*) + basic_machine=tic6x-unknown + os=-coff + ;; + tx39) + basic_machine=mipstx39-unknown + ;; + tx39el) + basic_machine=mipstx39el-unknown + ;; + toad1) + basic_machine=pdp10-xkl + os=-tops20 + ;; + tower | tower-32) + basic_machine=m68k-ncr + ;; + tpf) + basic_machine=s390x-ibm + os=-tpf + ;; + udi29k) + basic_machine=a29k-amd + os=-udi + ;; + ultra3) + basic_machine=a29k-nyu + os=-sym1 + ;; + v810 | necv810) + basic_machine=v810-nec + os=-none + ;; + vaxv) + basic_machine=vax-dec + os=-sysv + ;; + vms) + basic_machine=vax-dec + os=-vms + ;; + vpp*|vx|vx-*) + basic_machine=f301-fujitsu + ;; + vxworks960) + basic_machine=i960-wrs + os=-vxworks + ;; + vxworks68) + basic_machine=m68k-wrs + os=-vxworks + ;; + vxworks29k) + basic_machine=a29k-wrs + os=-vxworks + ;; + w65*) + basic_machine=w65-wdc + os=-none + ;; + w89k-*) + basic_machine=hppa1.1-winbond + os=-proelf + ;; + xbox) + basic_machine=i686-pc + os=-mingw32 + ;; + xps | xps100) + basic_machine=xps100-honeywell + ;; + ymp) + basic_machine=ymp-cray + os=-unicos + ;; + z8k-*-coff) + basic_machine=z8k-unknown + os=-sim + ;; + none) + basic_machine=none-none + os=-none + ;; + +# Here we handle the default manufacturer of certain CPU types. It is in +# some cases the only manufacturer, in others, it is the most popular. + w89k) + basic_machine=hppa1.1-winbond + ;; + op50n) + basic_machine=hppa1.1-oki + ;; + op60c) + basic_machine=hppa1.1-oki + ;; + romp) + basic_machine=romp-ibm + ;; + mmix) + basic_machine=mmix-knuth + ;; + rs6000) + basic_machine=rs6000-ibm + ;; + vax) + basic_machine=vax-dec + ;; + pdp10) + # there are many clones, so DEC is not a safe bet + basic_machine=pdp10-unknown + ;; + pdp11) + basic_machine=pdp11-dec + ;; + we32k) + basic_machine=we32k-att + ;; + sh[1234] | sh[24]a | sh[34]eb | sh[1234]le | sh[23]ele) + basic_machine=sh-unknown + ;; + sparc | sparcv8 | sparcv9 | sparcv9b) + basic_machine=sparc-sun + ;; + cydra) + basic_machine=cydra-cydrome + ;; + orion) + basic_machine=orion-highlevel + ;; + orion105) + basic_machine=clipper-highlevel + ;; + mac | mpw | mac-mpw) + basic_machine=m68k-apple + ;; + pmac | pmac-mpw) + basic_machine=powerpc-apple + ;; + *-unknown) + # Make sure to match an already-canonicalized machine name. + ;; + *) + echo Invalid configuration \`$1\': machine \`$basic_machine\' not recognized 1>&2 + exit 1 + ;; +esac + +# Here we canonicalize certain aliases for manufacturers. +case $basic_machine in + *-digital*) + basic_machine=`echo $basic_machine | sed 's/digital.*/dec/'` + ;; + *-commodore*) + basic_machine=`echo $basic_machine | sed 's/commodore.*/cbm/'` + ;; + *) + ;; +esac + +# Decode manufacturer-specific aliases for certain operating systems. + +if [ x"$os" != x"" ] +then +case $os in + # First match some system type aliases + # that might get confused with valid system types. + # -solaris* is a basic system type, with this one exception. + -solaris1 | -solaris1.*) + os=`echo $os | sed -e 's|solaris1|sunos4|'` + ;; + -solaris) + os=-solaris2 + ;; + -svr4*) + os=-sysv4 + ;; + -unixware*) + os=-sysv4.2uw + ;; + -gnu/linux*) + os=`echo $os | sed -e 's|gnu/linux|linux-gnu|'` + ;; + # First accept the basic system types. + # The portable systems comes first. + # Each alternative MUST END IN A *, to match a version number. + # -sysv* is not here because it comes later, after sysvr4. + -gnu* | -bsd* | -mach* | -minix* | -genix* | -ultrix* | -irix* \ + | -*vms* | -sco* | -esix* | -isc* | -aix* | -sunos | -sunos[34]*\ + | -hpux* | -unos* | -osf* | -luna* | -dgux* | -solaris* | -sym* \ + | -amigaos* | -amigados* | -msdos* | -newsos* | -unicos* | -aof* \ + | -aos* \ + | -nindy* | -vxsim* | -vxworks* | -ebmon* | -hms* | -mvs* \ + | -clix* | -riscos* | -uniplus* | -iris* | -rtu* | -xenix* \ + | -hiux* | -386bsd* | -knetbsd* | -mirbsd* | -netbsd* | -openbsd* \ + | -ekkobsd* | -kfreebsd* | -freebsd* | -riscix* | -lynxos* \ + | -bosx* | -nextstep* | -cxux* | -aout* | -elf* | -oabi* \ + | -ptx* | -coff* | -ecoff* | -winnt* | -domain* | -vsta* \ + | -udi* | -eabi* | -lites* | -ieee* | -go32* | -aux* \ + | -chorusos* | -chorusrdb* \ + | -cygwin* | -pe* | -psos* | -moss* | -proelf* | -rtems* \ + | -mingw32* | -linux-gnu* | -linux-uclibc* | -uxpv* | -beos* | -mpeix* | -udk* \ + | -interix* | -uwin* | -mks* | -rhapsody* | -darwin* | -opened* \ + | -openstep* | -oskit* | -conix* | -pw32* | -nonstopux* \ + | -storm-chaos* | -tops10* | -tenex* | -tops20* | -its* \ + | -os2* | -vos* | -palmos* | -uclinux* | -nucleus* \ + | -morphos* | -superux* | -rtmk* | -rtmk-nova* | -windiss* \ + | -powermax* | -dnix* | -nx6 | -nx7 | -sei* | -dragonfly* \ + | -skyos* | -haiku*) + # Remember, each alternative MUST END IN *, to match a version number. + ;; + -qnx*) + case $basic_machine in + x86-* | i*86-*) + ;; + *) + os=-nto$os + ;; + esac + ;; + -nto-qnx*) + ;; + -nto*) + os=`echo $os | sed -e 's|nto|nto-qnx|'` + ;; + -sim | -es1800* | -hms* | -xray | -os68k* | -none* | -v88r* \ + | -windows* | -osx | -abug | -netware* | -os9* | -beos* | -haiku* \ + | -macos* | -mpw* | -magic* | -mmixware* | -mon960* | -lnews*) + ;; + -mac*) + os=`echo $os | sed -e 's|mac|macos|'` + ;; + -linux-dietlibc) + os=-linux-dietlibc + ;; + -linux*) + os=`echo $os | sed -e 's|linux|linux-gnu|'` + ;; + -sunos5*) + os=`echo $os | sed -e 's|sunos5|solaris2|'` + ;; + -sunos6*) + os=`echo $os | sed -e 's|sunos6|solaris3|'` + ;; + -opened*) + os=-openedition + ;; + -os400*) + os=-os400 + ;; + -wince*) + os=-wince + ;; + -osfrose*) + os=-osfrose + ;; + -osf*) + os=-osf + ;; + -utek*) + os=-bsd + ;; + -dynix*) + os=-bsd + ;; + -acis*) + os=-aos + ;; + -atheos*) + os=-atheos + ;; + -syllable*) + os=-syllable + ;; + -386bsd) + os=-bsd + ;; + -ctix* | -uts*) + os=-sysv + ;; + -nova*) + os=-rtmk-nova + ;; + -ns2 ) + os=-nextstep2 + ;; + -nsk*) + os=-nsk + ;; + # Preserve the version number of sinix5. + -sinix5.*) + os=`echo $os | sed -e 's|sinix|sysv|'` + ;; + -sinix*) + os=-sysv4 + ;; + -tpf*) + os=-tpf + ;; + -triton*) + os=-sysv3 + ;; + -oss*) + os=-sysv3 + ;; + -svr4) + os=-sysv4 + ;; + -svr3) + os=-sysv3 + ;; + -sysvr4) + os=-sysv4 + ;; + # This must come after -sysvr4. + -sysv*) + ;; + -ose*) + os=-ose + ;; + -es1800*) + os=-ose + ;; + -xenix) + os=-xenix + ;; + -*mint | -mint[0-9]* | -*MiNT | -MiNT[0-9]*) + os=-mint + ;; + -aros*) + os=-aros + ;; + -kaos*) + os=-kaos + ;; + -zvmoe) + os=-zvmoe + ;; + -none) + ;; + *) + # Get rid of the `-' at the beginning of $os. + os=`echo $os | sed 's/[^-]*-//'` + echo Invalid configuration \`$1\': system \`$os\' not recognized 1>&2 + exit 1 + ;; +esac +else + +# Here we handle the default operating systems that come with various machines. +# The value should be what the vendor currently ships out the door with their +# machine or put another way, the most popular os provided with the machine. + +# Note that if you're going to try to match "-MANUFACTURER" here (say, +# "-sun"), then you have to tell the case statement up towards the top +# that MANUFACTURER isn't an operating system. Otherwise, code above +# will signal an error saying that MANUFACTURER isn't an operating +# system, and we'll never get to this point. + +case $basic_machine in + *-acorn) + os=-riscix1.2 + ;; + arm*-rebel) + os=-linux + ;; + arm*-semi) + os=-aout + ;; + c4x-* | tic4x-*) + os=-coff + ;; + # This must come before the *-dec entry. + pdp10-*) + os=-tops20 + ;; + pdp11-*) + os=-none + ;; + *-dec | vax-*) + os=-ultrix4.2 + ;; + m68*-apollo) + os=-domain + ;; + i386-sun) + os=-sunos4.0.2 + ;; + m68000-sun) + os=-sunos3 + # This also exists in the configure program, but was not the + # default. + # os=-sunos4 + ;; + m68*-cisco) + os=-aout + ;; + mips*-cisco) + os=-elf + ;; + mips*-*) + os=-elf + ;; + or32-*) + os=-coff + ;; + *-tti) # must be before sparc entry or we get the wrong os. + os=-sysv3 + ;; + sparc-* | *-sun) + os=-sunos4.1.1 + ;; + *-be) + os=-beos + ;; + *-haiku) + os=-haiku + ;; + *-ibm) + os=-aix + ;; + *-knuth) + os=-mmixware + ;; + *-wec) + os=-proelf + ;; + *-winbond) + os=-proelf + ;; + *-oki) + os=-proelf + ;; + *-hp) + os=-hpux + ;; + *-hitachi) + os=-hiux + ;; + i860-* | *-att | *-ncr | *-altos | *-motorola | *-convergent) + os=-sysv + ;; + *-cbm) + os=-amigaos + ;; + *-dg) + os=-dgux + ;; + *-dolphin) + os=-sysv3 + ;; + m68k-ccur) + os=-rtu + ;; + m88k-omron*) + os=-luna + ;; + *-next ) + os=-nextstep + ;; + *-sequent) + os=-ptx + ;; + *-crds) + os=-unos + ;; + *-ns) + os=-genix + ;; + i370-*) + os=-mvs + ;; + *-next) + os=-nextstep3 + ;; + *-gould) + os=-sysv + ;; + *-highlevel) + os=-bsd + ;; + *-encore) + os=-bsd + ;; + *-sgi) + os=-irix + ;; + *-siemens) + os=-sysv4 + ;; + *-masscomp) + os=-rtu + ;; + f30[01]-fujitsu | f700-fujitsu) + os=-uxpv + ;; + *-rom68k) + os=-coff + ;; + *-*bug) + os=-coff + ;; + *-apple) + os=-macos + ;; + *-atari*) + os=-mint + ;; + *) + os=-none + ;; +esac +fi + +# Here we handle the case where we know the os, and the CPU type, but not the +# manufacturer. We pick the logical manufacturer. +vendor=unknown +case $basic_machine in + *-unknown) + case $os in + -riscix*) + vendor=acorn + ;; + -sunos*) + vendor=sun + ;; + -aix*) + vendor=ibm + ;; + -beos*) + vendor=be + ;; + -hpux*) + vendor=hp + ;; + -mpeix*) + vendor=hp + ;; + -hiux*) + vendor=hitachi + ;; + -unos*) + vendor=crds + ;; + -dgux*) + vendor=dg + ;; + -luna*) + vendor=omron + ;; + -genix*) + vendor=ns + ;; + -mvs* | -opened*) + vendor=ibm + ;; + -os400*) + vendor=ibm + ;; + -ptx*) + vendor=sequent + ;; + -tpf*) + vendor=ibm + ;; + -vxsim* | -vxworks* | -windiss*) + vendor=wrs + ;; + -aux*) + vendor=apple + ;; + -hms*) + vendor=hitachi + ;; + -mpw* | -macos*) + vendor=apple + ;; + -*mint | -mint[0-9]* | -*MiNT | -MiNT[0-9]*) + vendor=atari + ;; + -vos*) + vendor=stratus + ;; + esac + basic_machine=`echo $basic_machine | sed "s/unknown/$vendor/"` + ;; +esac + +echo $basic_machine$os +exit + +# Local variables: +# eval: (add-hook 'write-file-hooks 'time-stamp) +# time-stamp-start: "timestamp='" +# time-stamp-format: "%:y-%02m-%02d" +# time-stamp-end: "'" +# End: + --- /dev/null +++ b/origin-src/wlach-libroutez-272ef93/configure.ac @@ -1,1 +1,56 @@ +# Process this file with autoconf to produce a configure script. +AC_PREREQ(2.57) +AC_INIT(libroutez, 0.1.0, libroutez@googlegroups.com, libroutez) +AC_CONFIG_SRCDIR(lib/tripgraph.cc) +AC_PROG_CC +AC_PROG_CXX +AC_PROG_CPP +AC_PROG_CXXCPP +AC_PROG_INSTALL + +# Detect target build environment +AC_CANONICAL_TARGET +case "$target" in + *-linux*) + OS="LINUX" + ;; + *-sunos*|*-solaris*) + OS="SOLARIS" + ;; + *-win*) + OS="WIN32" + ;; + *-apple*) + OS="MACOS" + ;; + *) + OS="OTHER" + ;; +esac + +AC_SUBST(OS) + +AX_PYTHON_DEVEL +AX_WITH_RUBY +AX_RUBY_DEVEL + +AC_CHECK_PROG(SWIG, swig, swig) + +if test x"$SWIG" = "x"; then + AC_MSG_ERROR("swig not found") +fi + +if test x"$PYTHON_CPPFLAGS" = "x"; then + AC_MSG_ERROR("python not found") +fi + +if test x"$RUBY_CPPFLAGS" = "x"; then + AC_MSG_ERROR("ruby not found") +fi + +AC_CONFIG_FILES(config.mk) + +AC_OUTPUT + + --- /dev/null +++ b/origin-src/wlach-libroutez-272ef93/examples/loadgraph.cc @@ -1,1 +1,24 @@ +#include +#include "tripgraph.h" +using namespace std; + + +int main(int argc, char *argv[]) +{ + // this example does nothing other than simply load a graph into memory + // useful for profiling memory usage + + if (argc < 2) + { + printf("Usage: %s ", argv[0]); + return 1; + } + + printf("Loading graph...\n"); + TripGraph g; + g.load(argv[1]); + + return 0; +} + --- /dev/null +++ b/origin-src/wlach-libroutez-272ef93/examples/testgraph.cc @@ -1,1 +1,52 @@ +#include +#include "tripgraph.h" +using namespace std; +using namespace tr1; + + +void print_actions(shared_ptr &action) +{ + shared_ptr parent(action->parent); + if (parent) + print_actions(parent); + + printf("%d->%d; route: %d; start time: %.2f; end time: %.2f\n", + action->src_id, action->dest_id, action->route_id, + action->start_time, action->end_time); +} + + +int main(int argc, char *argv[]) +{ + if (argc < 7) + { + printf("Usage: %s " + " \n", argv[0]); + return 1; + } + + float src_lat = atof(argv[2]); + float src_lng = atof(argv[3]); + float dest_lat = atof(argv[4]); + float dest_lng = atof(argv[5]); + int start_time = atoi(argv[6]); + + printf("Loading graph...\n"); + TripGraph g; + g.load(argv[1]); + + printf("Calculating path...\n"); + TripPath *p = g.find_path(start_time, false, src_lat, src_lng, + dest_lat, dest_lng); + + if (p) + print_actions(p->last_action); + else + printf("Couldn't find path.\n"); + + delete p; + + return 0; +} + --- /dev/null +++ b/origin-src/wlach-libroutez-272ef93/examples/testpython.py @@ -1,1 +1,28 @@ +#!/usr/bin/python +# This example is just to give a quick example of using the API for python +# hackers. + +# FIXME: flesh this out a bit more + +from libroutez.tripgraph import * + + +if __name__ == '__main__': + g = TripGraph() + + g.add_tripstop(0, TripStop.GTFS, 0.0, 0.0) + g.add_tripstop(1, TripStop.GTFS, 0.5, 0.0) + s = ServicePeriod(0, 1, 0, 0, 7, 0, 100, 2000, True, True, True) + g.add_service_period(s); + g.add_triphop(500, 1000, 0, 1, 1, 1, 0) + g.add_walkhop(0, 1) + + path = g.find_path(0, False, 0.0, 0.0, 0.5, 0.0) + + for action in path.get_actions(): + print "src: %s dest: %s st: %s et: %s rid: %s" % \ + (action.src_id, action.dest_id, action.start_time, action.end_time, + action.route_id) + + --- /dev/null +++ b/origin-src/wlach-libroutez-272ef93/examples/testruby.rb @@ -1,1 +1,47 @@ +#!/usr/bin/ruby +# This example is just here to give a simple example of using the ruby +# API. + +# FIXME: It would be nice to have some more ruby examples, but I'll leave +# that to the ruby hackers. + +# This is all slightly icky, but it's probably enough to +# help you get started + +require 'routez' + +g = Routez::TripGraph.new() +g.add_tripstop(0, Routez::TripStop::OSM, 0.0, 0.0) +g.add_tripstop(1, Routez::TripStop::OSM, 1.0, 0.0) +g.add_walkhop(0, 1) + +path = g.find_path(0, false, 0.0, 0.0, 1.0, 0.0) + +path.get_actions().each do |action| + puts "src: #{action.src_id} dest: #{action.dest_id} st: #{action.start_time} et: #{action.end_time} rid: #{action.route_id}" +end + +s = Routez::ServicePeriod.new(0, 1, 0, 0, 7, 0, 100, 2000, true, true, true) +g.add_service_period(s); +g.add_triphop(500, 1000, 0, 1, 1, 1, 0) +path2 = g.find_path(0, false, 0.0, 0.0, 1.0, 0.0) + +path2.get_actions().each do |action| + puts "src: #{action.src_id} dest: #{action.dest_id} st: #{action.start_time} et: #{action.end_time} rid: #{action.route_id}" +end + + +g = Routez::TripGraph.new() + +g.add_tripstop(0, Routez::TripStop::GTFS, 44.6554236, -63.5936968) # north and agricola +g.add_tripstop(1, Routez::TripStop::OSM, 44.6546407, -63.5948438) # north and robie (just north of north&agricola) +g.add_tripstop(2, Routez::TripStop::GTFS, 44.6567144, -63.5919115) # north and northwood (just south of north&agricola) +g.add_tripstop(3, Routez::TripStop::GTFS, 44.6432423, -63.6045261) # Quinpool and Connaught (a few kms away from north&agricola) + +stops = g.find_tripstops_in_range(44.6554236, -63.5936968, Routez::TripStop::GTFS, 500.0) + +stops.each do |stop| + puts "id: #{stop.id} lat: #{stop.lat} lon: #{stop.lng} type: #{stop.type}" +end + --- /dev/null +++ b/origin-src/wlach-libroutez-272ef93/include/defuns.h @@ -1,1 +1,9 @@ +#ifndef __DEFUNS_H +#define __DEFUNS_H +// max length of an identifier field (i.e. a service period) +// this simplifies the saving/loading code considerably +const int MAX_ID_LEN = 20; + +#endif // __DEFUNS_H + --- /dev/null +++ b/origin-src/wlach-libroutez-272ef93/include/serviceperiod.h @@ -1,1 +1,61 @@ +#ifndef __SERVICEPERIOD_H +#define __SERVICEPERIOD_H +#include +#include +#include +#include + +struct ServicePeriodException +{ + ServicePeriodException(int32_t _tm_mday, int32_t _tm_mon, int32_t _tm_year); + ServicePeriodException(); + + int32_t tm_mday; + int32_t tm_mon; + int32_t tm_year; +}; + +class ServicePeriod +{ + public: + ServicePeriod(int32_t id, + int32_t start_mday, int32_t start_mon, int32_t start_year, + int32_t end_mday, int32_t end_mon, int32_t end_year, + int32_t duration, bool weekday, bool saturday, bool sunday); + ServicePeriod(const ServicePeriod &s); + ServicePeriod(); + ServicePeriod(FILE *fp); + + void add_exception_on(int32_t tm_mday, int32_t tm_mon, int32_t tm_year); + void add_exception_off(int32_t tm_mday, int32_t tm_mon, int32_t tm_year); + + bool is_turned_on(int32_t tm_mday, int32_t tm_mon, int32_t tm_year); + bool is_turned_off(int32_t tm_mday, int32_t tm_mon, int32_t tm_year); + + void write(FILE *fp); + + int32_t id; + + // start/end time: the range of dates for which the service period is + // valid (e.g. Jan 2008 - Sep 2009) + time_t start_time; + time_t end_time; + + // duration and days of the week that the service period is active + int32_t duration; + bool weekday; + bool saturday; + bool sunday; + + // days that the service period is off (regardless of what the normal + // schedule) says. E.g. a weekday sched on Xmas + std::vector exceptions_off; + + // days that the service period is on (regardless of what the normal + // schedule) says. E.g. a sunday sched on Xmas + std::vector exceptions_on; +}; + +#endif // __SERVICEPERIOD_H + --- /dev/null +++ b/origin-src/wlach-libroutez-272ef93/include/tripgraph.h @@ -1,1 +1,82 @@ +#ifndef __TRIPGRAPH_H +#define __TRIPGRAPH_H +#include +#include +#include +#include +#include +#include +#include "serviceperiod.h" +#include "trippath.h" +#include "tripstop.h" + + +class TripGraph +{ + public: + TripGraph(); + void load(std::string fname); + void save(std::string fname); + + void set_timezone(std::string timezone); + void add_service_period(ServicePeriod &service_period); + void add_triphop(int32_t start_time, int32_t end_time, int32_t src_id, + int32_t dest_id, int32_t route_id, int32_t trip_id, + int32_t service_id); + void add_tripstop(int32_t id, TripStop::Type type, float lat, float lng); + void add_walkhop(int32_t src_id, int32_t dest_id); + + void link_osm_gtfs(); + + TripStop get_tripstop(int32_t id); + + std::vector > get_service_period_ids_for_time(int secs); + +#ifdef SWIG + %newobject find_path; +#endif + TripPath * find_path(double start, bool walkonly, + double src_lat, double src_lng, + double dest_lat, double dest_lng); + // various internal types + struct PathCompare + { + inline bool operator() (const std::tr1::shared_ptr &x, + const std::tr1::shared_ptr &y) + { + return x->heuristic_weight > y->heuristic_weight; + } + }; + + typedef std::vector > TripPathList; + typedef std::tr1::unordered_map > > VisitedRouteMap; + typedef std::tr1::unordered_map > > VisitedWalkMap; + typedef std::priority_queue, std::vector >, PathCompare> PathQueue; + + typedef std::vector ServicePeriodList; + typedef std::vector > TripStopList; + + std::vector find_tripstops_in_range(double lat, double lng, + TripStop::Type type, + double range); + + private: + // internal copy of get_tripstop: returns a pointer, not a copy, so + // much faster (when called many times) + std::tr1::shared_ptr _get_tripstop(int32_t id); + std::tr1::shared_ptr get_nearest_stop(double lat, double lng); + + void extend_path(std::tr1::shared_ptr &path, + bool walkonly, int32_t end_id, int &num_paths_considered, + VisitedRouteMap &visited_routes, + VisitedWalkMap &visited_walks, + PathQueue &uncompleted_paths, PathQueue &completed_paths); + + std::string timezone; + TripStopList tripstops; + ServicePeriodList splist; +}; + +#endif // __TRIPGRAPH_H + --- /dev/null +++ b/origin-src/wlach-libroutez-272ef93/include/trippath.h @@ -1,1 +1,69 @@ +#ifndef __TRIPPATH_H +#define __TRIPPATH_H +#include +#include +#include +#include "tripstop.h" + +struct TripAction +{ + TripAction(int32_t _src_id, int32_t _dest_id, int _route_id, + double _start_time, double _end_time); + TripAction() {} // for swig, which wants to call resize for some dumb reason + TripAction(const TripAction &other); + ~TripAction() { } + + TripAction &operator=(const TripAction &other); + + int32_t src_id, dest_id; + double start_time, end_time; + int route_id; + + // pointer to the action which preceded this one + std::tr1::shared_ptr parent; +}; + + +struct TripPath +{ + public: + TripPath(double _time, double _fastest_speed, + std::tr1::shared_ptr &_dest_stop, + std::tr1::shared_ptr &_last_stop); + TripPath() {} + + std::tr1::shared_ptr add_action( + std::tr1::shared_ptr &action, + std::deque &_possible_route_ids, + std::tr1::shared_ptr &_last_stop); + + // the following are mostly for the benefit of language bindings + // C++ code should be able to access this directly with less overhead... + std::deque get_actions(); + //tr1python::object get_last_action(); + + double time; + double fastest_speed; + std::tr1::shared_ptr dest_stop; + std::tr1::shared_ptr last_stop; + std::tr1::shared_ptr last_action; + + double walking_time; + double route_time; + int traversed_route_ids; + std::tr1::unordered_set possible_route_ids; + int last_route_id; + double weight; + double heuristic_weight; + +private: + void _get_heuristic_weight(); + + // Given an action just after the end of a walk in the path, delays + // that walk by the given number of seconds. + void delay_walk(std::tr1::shared_ptr walk, float secs); +}; + +#endif // __TRIPPATH_H + --- /dev/null +++ b/origin-src/wlach-libroutez-272ef93/include/tripstop.h @@ -1,1 +1,87 @@ +#ifndef __TRIPSTOP_H +#define __TRIPSTOP_H +#include +#include +#include +#include +#include +#include +#include +#include +#include + +// a triphop represents a hop to a specific node on the graph at a +// particular time (with a particular duration) +struct TripHop +{ + TripHop() { } + + TripHop(int32_t _start_time, int32_t _end_time, int32_t _dest_id, + int32_t _trip_id) + { + start_time = _start_time; + end_time = _end_time; + dest_id = _dest_id; + trip_id = _trip_id; + } + + int32_t start_time; + int32_t end_time; + int32_t dest_id; + int32_t trip_id; +}; + + +struct WalkHop +{ + WalkHop() { } + WalkHop(int32_t _dest_id, float _walktime) + { + dest_id = _dest_id; + walktime = _walktime; + } + + int32_t dest_id; + float walktime; +}; + + +struct TripStop +{ + int32_t id; + enum Type { OSM, GTFS }; + Type type; + float lat, lng; + + TripStop(FILE *fp); + TripStop(int32_t _id, Type _type, float _lat, float _lng); + TripStop(); + + void write(FILE *fp); + + void add_triphop(int32_t start_time, int32_t end_time, int32_t dest_id, + int32_t route_id, int32_t trip_id, int32_t service_id); + void add_walkhop(int32_t dest_id, float walktime); + std::deque get_routes(int32_t service_id); + const TripHop * find_triphop(int time, int route_id, int32_t service_id); + std::vector find_triphops( + int time, int route_id, int32_t service_id, int num); + + typedef std::vector TripHopList; + typedef std::tr1::unordered_map TripHopDict; + typedef std::tr1::unordered_map ServiceDict; + + // we keep a shared pointer to a tdict, as most nodes won't have one and + // we don't want the memory overhead of one if not strictly needed + // (note: we use a shared pointer instead of a standard pointer because + // the same tripstop may have multiple instances, but we only want one + // instance of its internal servicedict because it can be really huge...) + std::tr1::shared_ptr tdict; + + typedef std::list WalkHopList; + WalkHopList wlist; +}; + +#endif // __TRIPSTOP_H + --- /dev/null +++ b/origin-src/wlach-libroutez-272ef93/install-sh @@ -1,1 +1,120 @@ +#!/bin/sh +# +# install - install a program, script, or datafile +# This comes from X11R5; it is not part of GNU. +# +# $XConsortium: install.sh,v 1.2 89/12/18 14:47:22 jim Exp $ +# +# This script is compatible with the BSD install script, but was written +# from scratch. +# + + +# set DOITPROG to echo to test this script + +# Don't use :- since 4.3BSD and earlier shells don't like it. +doit="${DOITPROG-}" + + +# put in absolute paths if you don't have them in your path; or use env. vars. + +mvprog="${MVPROG-mv}" +cpprog="${CPPROG-cp}" +chmodprog="${CHMODPROG-chmod}" +chownprog="${CHOWNPROG-chown}" +chgrpprog="${CHGRPPROG-chgrp}" +stripprog="${STRIPPROG-strip}" +rmprog="${RMPROG-rm}" + +instcmd="$mvprog" +chmodcmd="" +chowncmd="" +chgrpcmd="" +stripcmd="" +rmcmd="$rmprog -f" +mvcmd="$mvprog" +src="" +dst="" + +while [ x"$1" != x ]; do + case $1 in + -c) instcmd="$cpprog" + shift + continue;; + + -m) chmodcmd="$chmodprog $2" + shift + shift + continue;; + + -o) chowncmd="$chownprog $2" + shift + shift + continue;; + + -g) chgrpcmd="$chgrpprog $2" + shift + shift + continue;; + + -s) stripcmd="$stripprog" + shift + continue;; + + *) if [ x"$src" = x ] + then + src=$1 + else + dst=$1 + fi + shift + continue;; + esac +done + +if [ x"$src" = x ] +then + echo "install: no input file specified" + exit 1 +fi + +if [ x"$dst" = x ] +then + echo "install: no destination specified" + exit 1 +fi + + +# If destination is a directory, append the input filename; if your system +# does not like double slashes in filenames, you may need to add some logic + +if [ -d $dst ] +then + dst="$dst"/`basename $src` +fi + +# Make a temp file name in the proper directory. + +dstdir=`dirname $dst` +dsttmp=$dstdir/#inst.$$# + +# Move or copy the file name to the temp name + +$doit $instcmd $src $dsttmp + +# and set any options; do chmod last to preserve setuid bits + +if [ x"$chowncmd" != x ]; then $doit $chowncmd $dsttmp; fi +if [ x"$chgrpcmd" != x ]; then $doit $chgrpcmd $dsttmp; fi +if [ x"$stripcmd" != x ]; then $doit $stripcmd $dsttmp; fi +if [ x"$chmodcmd" != x ]; then $doit $chmodcmd $dsttmp; fi + +# Now rename the file to the real destination. + +$doit $rmcmd $dst +$doit $mvcmd $dsttmp $dst + + +exit 0 + --- /dev/null +++ b/origin-src/wlach-libroutez-272ef93/install.mk @@ -1,1 +1,26 @@ +install-libroutez: libroutez.so + $(INSTALL) -d $(DESTDIR)$(libdir) + $(INSTALL_PROGRAM) libroutez.so $(DESTDIR)$(libdir)/ ; +# note: this is very non-idiomatic way of installing a python library. it +# probably doesn't handle edge cases well. but it works for me. +install-python: python/libroutez/_tripgraph.so python/libroutez/tripgraph.py + $(INSTALL) -d $(DESTDIR)$(libdir)/python/libroutez + $(INSTALL) python/libroutez/osm.py $(DESTDIR)$(libdir)/python/libroutez + $(INSTALL) python/libroutez/_tripgraph.so $(DESTDIR)$(libdir)/python/libroutez + $(INSTALL) python/libroutez/tripgraph.py $(DESTDIR)$(libdir)/python/libroutez + $(INSTALL) python/libroutez/__init__.py $(DESTDIR)$(libdir)/python/libroutez + +# likewise, this is a very non idiomatic way of installing a ruby module... +install-ruby: + $(INSTALL) -d $(DESTDIR)$(libdir)/ruby + $(INSTALL) ruby/routez.so $(DESTDIR)$(libdir)/ruby + +install-util: + $(INSTALL) -d $(DESTDIR)$(bindir) + $(INSTALL) utils/creategraph.py $(DESTDIR)$(bindir) + $(INSTALL) utils/get-gtfs-bounds.py $(DESTDIR)$(bindir) + + +install: install-libroutez install-python install-util install-ruby + --- /dev/null +++ b/origin-src/wlach-libroutez-272ef93/lib/serviceperiod.cc @@ -1,1 +1,199 @@ +#include +#include +#include "serviceperiod.h" +#include "defuns.h" +using namespace std; + + +static time_t get_time_t(int tm_mday, int tm_mon, int tm_year) +{ + struct tm t; + t.tm_sec = 0; + t.tm_min = 0; + t.tm_hour = 0; + t.tm_mday = tm_mday; + t.tm_mon = tm_mon; + t.tm_year = tm_year; + t.tm_wday = -1; + t.tm_yday = -1; + t.tm_isdst = -1; + + return mktime(&t); +} + + +ServicePeriodException::ServicePeriodException(int32_t _tm_mday, int32_t _tm_mon, + int32_t _tm_year) +{ + tm_mday = _tm_mday; + tm_mon = _tm_mon; + tm_year = _tm_year; +} + + +ServicePeriodException::ServicePeriodException() +{ + tm_mday = 0; + tm_mon = 0; + tm_year = 0; +} + + +ServicePeriod::ServicePeriod(int32_t _id, int32_t _start_mday, + int32_t _start_mon, int32_t _start_year, + int32_t _end_mday, int32_t _end_mon, + int32_t _end_year, int32_t _duration, + bool _weekday, bool _saturday, + bool _sunday) +{ + id = _id; + + start_time = get_time_t(_start_mday, _start_mon, _start_year); + end_time = get_time_t(_end_mday, _end_mon, _end_year); + + duration = _duration; + weekday = _weekday; + saturday = _saturday; + sunday = _sunday; +} + + +ServicePeriod::ServicePeriod(const ServicePeriod &s) +{ + id = s.id; + + start_time = s.start_time; + end_time = s.end_time; + + duration = s.duration; + weekday = s.weekday; + saturday = s.saturday; + sunday = s.sunday; + + for (vector::const_iterator i = s.exceptions_on.begin(); + i != s.exceptions_on.end(); i++) + add_exception_on(i->tm_mday, i->tm_mon, i->tm_year); + + for (vector::const_iterator i = s.exceptions_off.begin(); + i != s.exceptions_off.end(); i++) + add_exception_off(i->tm_mday, i->tm_mon, i->tm_year); +} + + +ServicePeriod::ServicePeriod() +{ + // blank service period object + start_time = 0; + end_time = 0; + + duration = 0; + weekday = false; + saturday = false; + sunday = false; +} + + +ServicePeriod::ServicePeriod(FILE *fp) +{ + assert(fread(&id, sizeof(int32_t), 1, fp) == 1); + + assert(fread(&start_time, sizeof(time_t), 1, fp) == 1); + assert(fread(&end_time, sizeof(time_t), 1, fp) == 1); + + assert(fread(&duration, sizeof(int32_t), 1, fp) == 1); + assert(fread(&weekday, sizeof(bool), 1, fp) == 1); + assert(fread(&saturday, sizeof(bool), 1, fp) == 1); + assert(fread(&sunday, sizeof(bool), 1, fp) == 1); + + uint32_t num_exceptions_on; + assert(fread(&num_exceptions_on, sizeof(uint32_t), 1, fp) == 1); + for (int i=0; i < num_exceptions_on; i++) + { + ServicePeriodException e; + assert(fread(&e, sizeof(ServicePeriodException), 1, fp) == 1); + add_exception_on(e.tm_mday, e.tm_mon, e.tm_year); + } + + uint32_t num_exceptions_off; + assert(fread(&num_exceptions_off, sizeof(uint32_t), 1, fp) == 1); + for (int i=0; i < num_exceptions_off; i++) + { + ServicePeriodException e; + assert(fread(&e, sizeof(ServicePeriodException), 1, fp) == 1); + add_exception_off(e.tm_mday, e.tm_mon, e.tm_year); + } +} + + +void ServicePeriod::write(FILE *fp) +{ + assert(fwrite(&id, sizeof(int32_t), 1, fp) == 1); + + assert(fwrite(&start_time, sizeof(time_t), 1, fp) == 1); + assert(fwrite(&end_time, sizeof(time_t), 1, fp) == 1); + + assert(fwrite(&duration, sizeof(int32_t), 1, fp) == 1); + assert(fwrite(&weekday, sizeof(bool), 1, fp) == 1); + assert(fwrite(&saturday, sizeof(bool), 1, fp) == 1); + assert(fwrite(&sunday, sizeof(bool), 1, fp) == 1); + + uint32_t num_exceptions_on = exceptions_on.size(); + assert(fwrite(&num_exceptions_on, sizeof(uint32_t), 1, fp) == 1); + for (vector::iterator i = exceptions_on.begin(); + i != exceptions_on.end(); i++) + { + ServicePeriodException &e = (*i); + assert(fwrite(&e, sizeof(ServicePeriodException), 1, fp) == 1); + } + + uint32_t num_exceptions_off = exceptions_off.size(); + assert(fwrite(&num_exceptions_off, sizeof(uint32_t), 1, fp) == 1); + for (vector::iterator i = exceptions_off.begin(); + i != exceptions_off.end(); i++) + { + ServicePeriodException &e = (*i); + assert(fwrite(&e, sizeof(ServicePeriodException), 1, fp) == 1); + } +} + + +void ServicePeriod::add_exception_on(int32_t tm_mday, int32_t tm_mon, int32_t tm_year) +{ + exceptions_on.push_back(ServicePeriodException(tm_mday, tm_mon, tm_year)); +} + + +void ServicePeriod::add_exception_off(int32_t tm_mday, int32_t tm_mon, int32_t tm_year) +{ + exceptions_off.push_back(ServicePeriodException(tm_mday, tm_mon, tm_year)); +} + + +bool ServicePeriod::is_turned_on(int32_t tm_mday, int32_t tm_mon, int32_t tm_year) +{ + for (vector::iterator i = exceptions_on.begin(); + i != exceptions_on.end(); i++) + { + if ((*i).tm_mday == tm_mday && (*i).tm_mon == tm_mon && + (*i).tm_year == tm_year) + return true; + } + + return false; +} + + +bool ServicePeriod::is_turned_off(int32_t tm_mday, int32_t tm_mon, int32_t tm_year) +{ + for (vector::iterator i = exceptions_off.begin(); + i != exceptions_off.end(); i++) + { + if ((*i).tm_mday == tm_mday && (*i).tm_mon == tm_mon && + (*i).tm_year == tm_year) + return true; + } + + return false; +} + --- /dev/null +++ b/origin-src/wlach-libroutez-272ef93/lib/tripgraph.cc @@ -1,1 +1,646 @@ - +#include "tripgraph.h" +#include +#include +#include +#include +#include + +using namespace std; +using namespace tr1; + +// set to 1 to see what find_path is doing (VERY verbose) +#if 0 +# define DEBUGPATH(fmt, args...) fprintf(stderr, fmt, ## args) +#else +# define DEBUGPATH +#endif + +// Estimated walking speed in m/s +static const float EST_WALK_SPEED = 1.1f; +static int SECS_IN_DAY = (60*60*24); + + +static inline double radians(double degrees) +{ + return degrees/180.0f*M_PI; +} + +static inline double degrees(double radians) +{ + return radians*180.0f/M_PI; +} + +static double distance(double src_lat, double src_lng, double dest_lat, + double dest_lng) +{ + // returns distance in meters + static const double EPSILON = 0.00005; + + if (fabs(src_lat - dest_lat) < EPSILON && fabs(src_lng - dest_lng) < EPSILON) { + return 0.0f; + } + + double theta = src_lng - dest_lng; + double src_lat_radians = radians(src_lat); + double dest_lat_radians = radians(dest_lat); + double dist = sin(src_lat_radians) * sin(dest_lat_radians) + + cos(src_lat_radians) * cos(dest_lat_radians) * + cos(radians(theta)); + dist = acos(dist); + dist = degrees(dist); + dist *= (60.0f * 1.1515 * 1.609344 * 1000.0f); + return dist; +} + + +TripGraph::TripGraph() +{ + set_timezone("UTC"); +} + + +void TripGraph::load(string fname) +{ + FILE *fp = fopen(fname.c_str(), "r"); + if (!fp) + { + printf("Error: Couldn't open graph file %s: %s (%d).\n", + fname.c_str(), strerror(errno), errno); + return; + } + + uint32_t timezone_len; + assert(fread(&timezone_len, sizeof(uint32_t), 1, fp) == 1); + char tz[timezone_len+1]; + assert(fread(tz, sizeof(char), timezone_len, fp) == timezone_len); + tz[timezone_len] = '\0'; + set_timezone(tz); + + uint32_t num_service_periods; + if (fread(&num_service_periods, sizeof(uint32_t), 1, fp) != 1) + { + printf("Error: Couldn't read the number of service periods.\n"); + return; + } + for (int i=0; i < num_service_periods; i++) + { + ServicePeriod s(fp); + add_service_period(s); + } + + uint32_t num_tripstops; + if (fread(&num_tripstops, sizeof(uint32_t), 1, fp) != 1) + { + printf("Error: Couldn't read the number of tripstops.\n"); + return; + } + + tripstops.reserve(num_tripstops); + for (uint32_t i=0; i < num_tripstops; i++) + { + shared_ptr s(new TripStop(fp)); + assert(tripstops.size() == s->id); + tripstops.push_back(s); + } + + fclose(fp); +} + + +void TripGraph::save(string fname) +{ + FILE *fp = fopen(fname.c_str(), "w"); + if (!fp) + { + printf("Error: Couldn't open graph %s for writing: %s (%d).\n", + fname.c_str(), strerror(errno), errno); + return; + } + + // write timezone + uint32_t timezone_len = timezone.size(); + assert(fwrite(&timezone_len, sizeof(uint32_t), 1, fp) == 1); + assert(fwrite(timezone.c_str(), sizeof(char), timezone_len, fp) == + timezone_len); + + // write service periods + uint32_t num_service_periods = splist.size(); + assert(fwrite(&num_service_periods, sizeof(uint32_t), 1, fp) == 1); + for (ServicePeriodList::iterator i = splist.begin(); i != splist.end(); + i++) + i->write(fp); + + // write tripstops + uint32_t num_tripstops = tripstops.size(); + assert(fwrite(&num_tripstops, sizeof(uint32_t), 1, fp) == 1); + for (TripStopList::iterator i = tripstops.begin(); + i != tripstops.end(); i++) + { + (*i)->write(fp); + } + + fclose(fp); +} + + +void TripGraph::set_timezone(std::string _timezone) +{ + timezone = _timezone; + setenv("TZ", timezone.c_str(), 1); + tzset(); +} + + +void TripGraph::add_service_period(ServicePeriod &service_period) +{ + assert(service_period.id == splist.size()); + splist.push_back(service_period); +} + + +void TripGraph::add_triphop(int32_t start_time, int32_t end_time, + int32_t src_id, int32_t dest_id, int32_t route_id, + int32_t trip_id, int32_t service_id) +{ + // will assert if src_id doesn't exist!! + _get_tripstop(src_id)->add_triphop(start_time, end_time, dest_id, route_id, + trip_id, service_id); +} + + +void TripGraph::add_tripstop(int32_t id, TripStop::Type type, float lat, float lng) +{ + // id must equal size of tripstops + assert(id == tripstops.size()); + + tripstops.push_back(shared_ptr(new TripStop(id, type, lat, lng))); +} + + +void TripGraph::add_walkhop(int32_t src_id, int32_t dest_id) +{ + // will assert if src_id or dest_id doesn't exist!! + shared_ptr ts_src = _get_tripstop(src_id); + shared_ptr ts_dest = _get_tripstop(dest_id); + + double dist = distance(ts_src->lat, ts_src->lng, + ts_dest->lat, ts_dest->lng); + + ts_src->add_walkhop(dest_id, dist / EST_WALK_SPEED); +} + + +struct Point +{ + Point(double _lat, double _lng) { lat=_lat; lng=_lng; } + double lat; + double lng; +}; + +bool operator==(const Point &p1, const Point &p2) +{ + // We say that anything within a distance of 1 meter is identical. + return (distance(p1.lat, p1.lng, p2.lat, p2.lng) < 1.0f); +} + +Point get_closest_point(Point &a, Point &b, Point &c) +{ + // Given a line made up of a and b, and a point c, + // return the point on the line closest to c (may be a or b). + double ab2 = pow((b.lat - a.lat), 2) + pow((b.lng - a.lng), 2); + double ap_ab = (c.lat - a.lat)*(b.lat-a.lat) + (c.lng-a.lng)*(b.lng-a.lng); + double t = ap_ab / ab2; + + // Clamp t to be between a and b. + if (t < 0.0f) + t = 0.0f; + else if (t>1.0f) + t = 1.0f; + + return Point(a.lat + (b.lat - a.lat)*t, a.lng + (b.lng - a.lng)*t); +} + + +// This complicated-looking method attempts to link gtfs stops to osm nodes. +// If a stop lies between two osm nodes on a polyline, we will link the gtfs +// stop to both of them. +void TripGraph::link_osm_gtfs() +{ + map > new_walkhops; + + // do some counting of the actual number of gtfs + int gtfs_tripstop_count = 0; + int gtfs_tripstop_total = 0; + for (TripStopList::iterator i = tripstops.begin(); + i != tripstops.end(); i++) + { + if ((*i)->type == TripStop::GTFS) + gtfs_tripstop_total++; + } + + for (TripStopList::iterator i = tripstops.begin(); + i != tripstops.end(); i++) + { + gtfs_tripstop_count++; + // For each GTFS stop... + if ((*i)->type == TripStop::GTFS) + { + Point gtfs_pt((*i)->lat, (*i)->lng); + + pair nearest_walkhop(-1, -1); + double min_dist; + + // Check each other trip stop and all its walkhops... + // FIXME: This is begging to be optimized. We need some way to + // exclude the bulk of tripstops that are a million miles away. + // One idea is to do some sort of quadtree-like partitioning of + // the tripstops; then we'd mostly only have to check other stops + // within our partition. + // Another idea is to put a bounding box around each tripstop and + // its associated walkhops, saving us from having to examine each + // walkhop of some faraway triphop. + for (TripStopList::iterator j = tripstops.begin(); + j != tripstops.end(); j++) + { + for (TripStop::WalkHopList::iterator k = (*j)->wlist.begin(); + k != (*j)->wlist.end(); k++) + { + Point trip_pt((*j)->lat, (*j)->lng); + + shared_ptr dest_stop = _get_tripstop(k->dest_id); + Point walk_pt(dest_stop->lat, dest_stop->lng); + + Point p = get_closest_point(trip_pt, walk_pt, gtfs_pt); + + // Find the closest OSM hop to the GTFS stop + double dist = distance(gtfs_pt.lat, gtfs_pt.lng, + p.lat, p.lng); + if ((nearest_walkhop.first == (-1) && + nearest_walkhop.second == (-1)) || dist < min_dist) + { + nearest_walkhop = pair(-1, -1); + // If the GTFS stop is on one of the OSM nodes, use + // that node. Otherwise remember both nodes. + if (trip_pt == p) + nearest_walkhop.first = (*j)->id; + else if (walk_pt == p) + nearest_walkhop.first = k->dest_id; + else + { + nearest_walkhop.first = (*j)->id; + nearest_walkhop.second = k->dest_id; + } + + min_dist = dist; + } + } + } + + new_walkhops[(*i)->id] = nearest_walkhop; + printf("%02.2f%% done: Linking %d -> %d, %d\n", + ((float)gtfs_tripstop_count * 100.0f) / ((float)gtfs_tripstop_total), + (*i)->id, + nearest_walkhop.first, + nearest_walkhop.second); + } + } + + for (map >::iterator i = new_walkhops.begin(); + i != new_walkhops.end(); i++) + { + int32_t osmstop1 = i->second.first; + int32_t osmstop2 = i->second.second; + + assert(osmstop1 >= 0); + add_walkhop(i->first, osmstop1); + add_walkhop(osmstop1, i->first); + + if (osmstop2 >= 0) + { + add_walkhop(i->first, osmstop2); + add_walkhop(osmstop2, i->first); + } + } +} + + +shared_ptr TripGraph::get_nearest_stop(double lat, double lng) +{ + // FIXME: use a quadtree to speed this up, see link_osm_gtfs() for + // more thoughts on this + + shared_ptr closest_stop; + double min_dist = 0.0f; + for (TripStopList::iterator i = tripstops.begin(); + i != tripstops.end(); i++) + { + double dist = pow(((*i)->lat - lat), 2) + pow(((*i)->lng - lng), 2); + if (!closest_stop || dist < min_dist) + { + closest_stop = (*i); + min_dist = dist; + } + } + + return closest_stop; +} + + +TripStop TripGraph::get_tripstop(int32_t id) +{ + shared_ptr ts = _get_tripstop(id); + return TripStop(*ts); +} + + +vector > TripGraph::get_service_period_ids_for_time(int secs) +{ + vector > vsp; + + for (ServicePeriodList::iterator i = splist.begin(); i != splist.end(); i++) + { + for (int offset = 0; offset < i->duration; offset += SECS_IN_DAY) + { + time_t mysecs = secs - offset; + struct tm * t = localtime(&mysecs); + if (i->start_time <= mysecs && + i->end_time >= mysecs && + (((t->tm_wday == 6 && i->saturday) || + (t->tm_wday == 0 && i->sunday) || + (t->tm_wday > 0 && t->tm_wday < 6 && i->weekday)) && + !i->is_turned_off(t->tm_mday, t->tm_mon, t->tm_year)) || + i->is_turned_on(t->tm_mday, t->tm_mon, t->tm_year)) + { + vsp.push_back(pair(i->id, offset)); + } + } + } + + return vsp; +} + + +TripPath * TripGraph::find_path(double start, bool walkonly, + double src_lat, double src_lng, + double dest_lat, double dest_lng) +{ + PathQueue uncompleted_paths; + PathQueue completed_paths; + + VisitedRouteMap visited_routes; + VisitedWalkMap visited_walks; + + shared_ptr start_node = get_nearest_stop(src_lat, src_lng); + shared_ptr end_node = get_nearest_stop(dest_lat, dest_lng); + DEBUGPATH("Find path. Secs: %f walkonly: %d " + "src lat: %f src lng: %f dest_lat: %f dest_lng: %f\n", + start, walkonly, src_lat, src_lng, dest_lat, dest_lng); + DEBUGPATH("- Start: %d End: %d\n", start_node->id, end_node->id); + + //DEBUGPATH("..service period determination.."); + + // Consider the distance required to reach the start node from the + // beginning, and add that to our start time. + double dist_from_start = distance(src_lat, src_lng, + start_node->lat, start_node->lng); + start += (dist_from_start / EST_WALK_SPEED); + + DEBUGPATH("- Start time - %f (dist from start: %f)\n", start, dist_from_start); + shared_ptr start_path(new TripPath(start, EST_WALK_SPEED, + end_node, start_node)); + if (start_node == end_node) + return new TripPath(*start_path); + + uncompleted_paths.push(start_path); + + int num_paths_considered = 0; + + while (uncompleted_paths.size() > 0) + { + DEBUGPATH("Continuing\n"); + shared_ptr path = uncompleted_paths.top(); + uncompleted_paths.pop(); + extend_path(path, walkonly, end_node->id, num_paths_considered, + visited_routes, visited_walks, uncompleted_paths, + completed_paths); + + // If we've still got open paths, but their weight exceeds that + // of the weight of a completed path, break. + if (uncompleted_paths.size() > 0 && completed_paths.size() > 0 && + uncompleted_paths.top()->heuristic_weight > + completed_paths.top()->heuristic_weight) + { + DEBUGPATH("Breaking with %d uncompleted paths (paths " + "considered: %d).\n", uncompleted_paths.size(), + num_paths_considered); + return new TripPath(*(completed_paths.top())); + } + + //if len(completed_paths) > 0 and len(uncompleted_paths) > 0: + // print "Weight of best completed path: %s, uncompleted: %s" % \ + // (completed_paths[0].heuristic_weight, uncompleted_paths[0].heuristic_weight) + } + + if (completed_paths.size()) + return new TripPath(*(completed_paths.top())); + + return NULL; +} + + +shared_ptr TripGraph::_get_tripstop(int32_t id) +{ + assert(id < tripstops.size()); + + return tripstops[id]; +} + + +void TripGraph::extend_path(shared_ptr &path, + bool walkonly, + int32_t goal_id, + int &num_paths_considered, + VisitedRouteMap &visited_routes, + VisitedWalkMap &visited_walks, + PathQueue &uncompleted_paths, + PathQueue &completed_paths) +{ + TripPathList newpaths; + int32_t src_id = path->last_stop->id; + int last_route_id = path->last_route_id; + +#if 0 + if (path->last_action) + { + string last_src_id = path->last_action->src_id; + if (cb) + python::call(cb, tripstops[last_src_id]->lat, + tripstops[last_src_id]->lng, + tripstops[src_id]->lat, + tripstops[src_id]->lng, + last_route_id); + } +#endif + time_t mysecs = (time_t)path->time; + struct tm * tm = localtime(&mysecs); + double elapsed_daysecs = tm->tm_sec + (60*tm->tm_min) + (60*60*tm->tm_hour); + double daystart = path->time - elapsed_daysecs; + + // Figure out service period based on start time, then figure out + // seconds since midnight on our particular day + vector > vsp = get_service_period_ids_for_time(path->time); + + DEBUGPATH("Extending path at vertex %d (on %d) @ %f (walktime: %f, " + "routetime: %f elapsed_daysecs: %f)\n", src_id, last_route_id, path->time, + path->walking_time, path->route_time, elapsed_daysecs); + shared_ptr src_stop = _get_tripstop(src_id); + + // Keep track of outgoing route ids at this node: make sure that we + // don't get on a route later when we could have gotten on here. + deque outgoing_route_ids; + if (!walkonly) + { + for (vector >::iterator i = vsp.begin(); i != vsp.end(); i++) + { + deque route_ids = src_stop->get_routes(i->first); + for (deque::iterator j = route_ids.begin(); j != route_ids.end(); j++) + outgoing_route_ids.push_back(*j); + } + } + + // Explore walkhops that are better than the ones we've already visited. + // If we're on a bus, don't allow a transfer if we've been on for + // less than 5 minutes (FIXME: probably better to measure distance + // travelled?) + if (last_route_id == -1 || path->route_time > (2 * 60)) + { + for (TripStop::WalkHopList::iterator i = src_stop->wlist.begin(); + i != src_stop->wlist.end(); i++) + { + int32_t dest_id = i->dest_id; + double walktime = i->walktime; + + // Do a quick test to make sure that the potential basis for a + // new path isn't worse than what we have already, before + // incurring the cost of creating a new path and evaluating it. + unordered_map > vsrc = visited_walks[src_id]; + unordered_map >::iterator v1 = vsrc.find(dest_id); + if (v1 != vsrc.end() && path->heuristic_weight > v1->second->heuristic_weight) + continue; + + shared_ptr action( + new TripAction(src_id, dest_id, -1, path->time, + (path->time + walktime))); + shared_ptr ds = _get_tripstop(dest_id); + shared_ptr path2 = path->add_action( + action, outgoing_route_ids, ds); + + DEBUGPATH("- Considering walkpath to %d\n", dest_id); + + if (v1 == vsrc.end() || + v1->second->heuristic_weight > path2->heuristic_weight || + ((v1->second->heuristic_weight - path2->heuristic_weight) < 1.0f && + v1->second->walking_time > path2->walking_time)) + { + DEBUGPATH("-- Adding walkpath to %d (walktime: %f (%f, %f))\n", dest_id, walktime, action->start_time, action->end_time); + if (dest_id == goal_id) + completed_paths.push(path2); + else + uncompleted_paths.push(path2); + + num_paths_considered++; + visited_walks[src_id][dest_id] = path2; + } + } + } + + + // If we're doing a walkonly path (mostly for generating shapes?), stop + // and return here. + if (walkonly) + return; + + // Find outgoing triphops from the source and get a list of paths to them. + for (vector >::iterator sp = vsp.begin(); sp != vsp.end(); + sp++) + { + deque route_ids = src_stop->get_routes(sp->first); + for (deque::iterator j = route_ids.begin(); j != route_ids.end(); j++) + { + int LEEWAY = 0; + if ((*j) != last_route_id) + LEEWAY = (5*60); // give 5 mins to make a transfer + + const TripHop * t = src_stop->find_triphop( + elapsed_daysecs + sp->second + LEEWAY, (*j), sp->first); + if (t) + { + // If we've been on the route before (or could have been), + // don't get on again. + if ((*j) != last_route_id && path->possible_route_ids.count(*j)) + { + // pass + } + // Disallow more than three transfers. + else if ((*j) != last_route_id && + path->traversed_route_ids > 3) + { + // pass + } + else + { + // Do a quick test to make sure that the potential basis for a + // new path isn't worse than what we have already, before + // incurring the cost of creating a new path and evaluating it. + unordered_map >::iterator v = visited_routes[src_id].find(*j); + if (v != visited_routes[src_id].end() && path->heuristic_weight > v->second->heuristic_weight) + continue; + + shared_ptr action = shared_ptr( + new TripAction(src_id, t->dest_id, (*j), daystart + t->start_time, + daystart + t->end_time)); + shared_ptr ds = _get_tripstop(t->dest_id); + shared_ptr path2 = path->add_action( + action, outgoing_route_ids, ds); + + + if (v == visited_routes[src_id].end() || + v->second->heuristic_weight > path2->heuristic_weight || + ((v->second->heuristic_weight - path2->heuristic_weight) < 1.0f && + v->second->walking_time > path2->walking_time)) + { + if (t->dest_id == goal_id) + completed_paths.push(path2); + else + uncompleted_paths.push(path2); + + num_paths_considered++; + visited_routes[src_id][(*j)] = path2; + } + } + } + } + } +} + + +vector TripGraph::find_tripstops_in_range(double lat, double lng, + TripStop::Type type, + double range) +{ + vector tripstops_in_range; + + for (TripStopList::iterator i = tripstops.begin(); + i != tripstops.end(); i++) + { + if ((*i)->type != type) + continue; + + double dist = distance((*i)->lat, (*i)->lng, lat, lng); + if (dist <= range) + tripstops_in_range.push_back(*(*i)); + } + + return tripstops_in_range; +} + --- /dev/null +++ b/origin-src/wlach-libroutez-272ef93/lib/trippath.cc @@ -1,1 +1,260 @@ - +#include "trippath.h" +#include + +#if 0 +#define LOG(...) fprintf(stderr, __VA_ARGS__) +#else +#define LOG(...) +#endif + +using namespace std; +using namespace tr1; + +static inline double radians(double degrees) +{ + return degrees/180.0f*M_PI; +} + +static inline double degrees(double radians) +{ + return radians*180.0f/M_PI; +} + +static double distance(double src_lat, double src_lng, double dest_lat, double dest_lng) +{ + if (src_lat == dest_lat && src_lng == dest_lng) + return 0.0f; + + double theta = src_lng - dest_lng; + double src_lat_radians = radians(src_lat); + double dest_lat_radians = radians(dest_lat); + double dist = sin(src_lat_radians) * sin(dest_lat_radians) + + cos(src_lat_radians) * cos(dest_lat_radians) * + cos(radians(theta)); + dist = acos(dist); + dist = degrees(dist); + dist *= (60.0f * 1.1515 * 1.609344 * 1000.0f); + return dist; +} + + +TripAction::TripAction(int32_t _src_id, int32_t _dest_id, + int _route_id, double _start_time, double _end_time) : + src_id(_src_id), + dest_id(_dest_id), + route_id(_route_id), + start_time(_start_time), + end_time(_end_time), + parent() +{ +} + + +TripAction::TripAction(const TripAction &other): + src_id(other.src_id), + dest_id(other.dest_id), + route_id(other.route_id), + start_time(other.start_time), + end_time(other.end_time), + parent(other.parent) +{ +} + + +TripAction& TripAction::operator=(const TripAction &other) +{ + src_id = other.src_id; + dest_id = other.dest_id; + route_id = other.route_id; + start_time = other.start_time; + end_time = other.end_time; + parent = other.parent; +} + +TripPath::TripPath(double _time, double _fastest_speed, + shared_ptr &_dest_stop, + shared_ptr &_last_stop) +{ + fastest_speed = _fastest_speed; + dest_stop = _dest_stop; + last_stop = _last_stop; + time = _time; + + walking_time = 0.0f; + weight = _time; + traversed_route_ids = 0; + last_route_id = -1; + route_time = 0.0f; + _get_heuristic_weight(); +} + +#if 0 +python::object TripPath::get_last_action() +{ + if (last_action) + return python::object(*last_action); + + return python::object(); +} +#endif + +void TripPath::_get_heuristic_weight() +{ + // start off with heuristic weight being equivalent to its real weight + heuristic_weight = weight; + + // then, calculate the time remaining based on going directly + // from the last vertex to the destination vertex at the fastest + // possible speed in the graph + double remaining_distance = distance(last_stop->lat, last_stop->lng, + dest_stop->lat, dest_stop->lng); + heuristic_weight += remaining_distance / 5; //(fastest_speed / 3); + + // now, add 5 minutes per each transfer, multiplied to the power of 2 + // (to make transfers exponentially more painful) + if (traversed_route_ids > 1) + heuristic_weight += (pow(2.0f, (int)(traversed_route_ids-2)) * 5.0f * 60.0f); + + // double the cost of walking after 5 mins, quadruple after 10 mins, + // octuple after 15, etc. (up to a maximum of 20 iterations of this, to + // make sure we don't freeze for particularly long walking times-- mostly + // useful for obscure test cases) + double excess_walking_time = walking_time - 300.0f; + int iter = 0; + while (excess_walking_time > 0 && iter < 20) + { + double iter_walking_time = 0; + if (excess_walking_time > 300.0f) + iter_walking_time = 300.0f; + else + iter_walking_time = excess_walking_time; + heuristic_weight += (iter_walking_time * pow(2.0f, iter)); + excess_walking_time -= 300.0f; + iter++; + } + + // add 5 mins to our weight if we were walking and remaining distance + // >1000m, to account for the fact that we're probably going to + // want to wait for another bus. this prevents us from repeatedly + // getting out of the bus and walking around + if (last_route_id == -1 && remaining_distance > 1000) + heuristic_weight += (5*60); +} + +static void _add_actions_to_list(deque &l, + shared_ptr &action) +{ + if (action) + { + if (action->parent) + _add_actions_to_list(l, action->parent); + l.push_back(TripAction(*action)); + } +} + +deque TripPath::get_actions() +{ + deque l; + + // recursively add actions to list, so we get them back in the + // correct order + _add_actions_to_list(l, last_action); + + return l; +} + +shared_ptr TripPath::add_action(shared_ptr &action, + deque &_possible_route_ids, + shared_ptr &_last_stop) +{ + shared_ptr new_trippath(new TripPath(*this)); + + float departure_delay = 0.0f; + + if (action->route_id == -1) + { + new_trippath->walking_time += (action->end_time - action->start_time); + new_trippath->route_time = 0; + } + else if (new_trippath->last_action) + { + // Starting first bus route, adjust the start time to match. + if (new_trippath->traversed_route_ids == 0) + { + departure_delay = + action->start_time - new_trippath->last_action->end_time; + // Aim to be at the bus stop 3 minutes early. + departure_delay -= 3*60; + } + + if (action->route_id != new_trippath->last_action->route_id) + { + new_trippath->traversed_route_ids++; + new_trippath->route_time = 0; + } + } + + for (deque::iterator i = _possible_route_ids.begin(); + i != _possible_route_ids.end(); i++) + { + new_trippath->possible_route_ids.insert(*i); + } + + new_trippath->route_time += (action->end_time - action->start_time); + new_trippath->weight += (action->end_time - action->start_time); + new_trippath->weight += (action->start_time - time); + + if (new_trippath->last_action) + action->parent = new_trippath->last_action; + new_trippath->last_action = shared_ptr(new TripAction(*action)); + new_trippath->last_stop = _last_stop; + new_trippath->last_route_id = action->route_id; + new_trippath->_get_heuristic_weight(); + new_trippath->time = action->end_time; + + if (departure_delay > 0.0f) + { + LOG("Delaying start by %f seconds\n", departure_delay); + new_trippath->delay_walk(new_trippath->last_action, departure_delay); + } + + return new_trippath; +} + + +void TripPath::delay_walk(shared_ptr walk, float secs) +{ + if (!walk) + return; + + // Don't delay partial walks; we need to be given the element *after* + // the final walk. + if (walk->route_id == -1) + return; + + // Only delay actual walks. + if (!walk->parent || walk->parent->route_id != -1) + return; + + shared_ptr w(walk); + while (w && w->parent && w->parent->route_id == -1) + { + // We need to clone the actions, as they're no longer safe to share + // (for instance, they could be shared by another bus trip that leaves + // earlier). + w->parent = shared_ptr(new TripAction(*(w->parent))); + w = w->parent; + + w->start_time += secs; + w->end_time += secs; + } + + // If we delayed the initial walk, then we've reduced the total trip time. + if (!w) + { + weight -= secs; + _get_heuristic_weight(); + } +} + + --- /dev/null +++ b/origin-src/wlach-libroutez-272ef93/lib/tripstop.cc @@ -1,1 +1,208 @@ - +#include "defuns.h" +#include "tripstop.h" +#include +#include + + +using namespace std; +using namespace tr1; + + +TripStop::TripStop(FILE *fp) +{ + assert(fread(&id, sizeof(int32_t), 1, fp) == 1); + assert(fread(&type, sizeof(Type), 1, fp) == 1); + assert(fread(&lat, sizeof(float), 1, fp) == 1); + assert(fread(&lng, sizeof(float), 1, fp) == 1); + + uint8_t have_triphops; + assert(fread(&have_triphops, sizeof(uint8_t), 1, fp) == 1); + + if (have_triphops) + { + tdict = shared_ptr(new ServiceDict); + + uint32_t num_service_periods; + assert(fread(&num_service_periods, sizeof(uint32_t), 1, fp) == 1); + for (uint32_t i=0; i= t.start_time); // FIXME: should be >, no? + (*tdict)[sp_id][route_id].push_back(t); + } + } + + } + } + + uint32_t num_walkhops = 0; + assert(fread(&num_walkhops, sizeof(uint32_t), 1, fp) == 1); + for (int i=0; i= 0.0f); // FIXME, should be >, no? + add_walkhop(dest_id, walktime); + } +} + + +TripStop::TripStop(int32_t _id, Type _type, float _lat, float _lng) +{ + id = _id; + type = _type; + lat = _lat; + lng = _lng; +} + + +TripStop::TripStop() +{ +} + + +void TripStop::write(FILE *fp) +{ + assert(fwrite(&id, sizeof(int32_t), 1, fp) == 1); + assert(fwrite(&type, sizeof(Type), 1, fp) == 1); + assert(fwrite(&lat, sizeof(float), 1, fp) == 1); + assert(fwrite(&lng, sizeof(float), 1, fp) == 1); + + uint8_t have_triphops = tdict ? 1 : 0; + assert(fwrite(&have_triphops, sizeof(uint8_t), 1, fp) == 1); + + if (tdict) + { + uint32_t num_service_periods = tdict->size(); + assert(fwrite(&num_service_periods, sizeof(uint32_t), 1, fp) == 1); + + for (ServiceDict::iterator i = tdict->begin(); i != tdict->end(); i++) + { + assert(fwrite(&(i->first), sizeof(int32_t), 1, fp) == 1); + uint32_t num_route_ids = i->second.size(); + assert(fwrite(&num_route_ids, sizeof(uint32_t), 1, fp) == 1); + for (TripHopDict::iterator j = i->second.begin(); + j != i->second.end(); j++) + { + int32_t route_id = j->first; + assert(fwrite(&route_id, sizeof(int32_t), 1, fp) == 1); + uint32_t num_triphops = j->second.size(); + assert(fwrite(&num_triphops, sizeof(uint32_t), 1, fp) == 1); + for (TripHopList::iterator k = j->second.begin(); + k != j->second.end(); k++) + { + assert(fwrite(&(*k), sizeof(TripHop), 1, fp) == 1); + } + } + } + } + + uint32_t num_walkhops = wlist.size(); + assert(fwrite(&num_walkhops, sizeof(uint32_t), 1, fp) == 1); + for (WalkHopList::iterator i = wlist.begin(); i != wlist.end(); i++) + { + assert(fwrite(&(*i), sizeof(WalkHop), 1, fp) == 1); + } +} + + +static bool sort_triphops(const TripHop &x, + const TripHop &y) +{ + return x.start_time < y.start_time; +} + + +void TripStop::add_triphop(int32_t start_time, int32_t end_time, + int32_t dest_id, int32_t route_id, int32_t trip_id, + int32_t service_id) +{ + if (!tdict) + tdict = shared_ptr(new ServiceDict); + + (*tdict)[service_id][route_id].push_back(TripHop(start_time, end_time, + dest_id, trip_id)); + ::sort((*tdict)[service_id][route_id].begin(), + (*tdict)[service_id][route_id].end(), sort_triphops); +} + + +void TripStop::add_walkhop(int32_t dest_id, float walktime) +{ + wlist.push_front(WalkHop(dest_id, walktime)); +} + + +const TripHop * TripStop::find_triphop(int time, int route_id, + int32_t service_id) +{ + if (tdict) + { + for (TripHopList::iterator i = (*tdict)[service_id][route_id].begin(); + i != (*tdict)[service_id][route_id].end(); i++) + { + if ((*i).start_time >= time) + return &(*i); + } + } + + return NULL; +} + + +vector TripStop::find_triphops(int time, int route_id, + int32_t service_id, + int num) +{ + vector tlist; + + if (tdict) + { + for (TripHopList::iterator i = (*tdict)[service_id][route_id].begin(); + (i != ((*tdict)[service_id][route_id].end()) && tlist.size() < num); + i++) + { + if ((*i).start_time >= time) + tlist.push_back(*i); + } + } + + return tlist; +} + + +deque TripStop::get_routes(int32_t service_id) +{ + deque routes; + + if (tdict) + { + for (TripHopDict::iterator i = (*tdict)[service_id].begin(); + i != (*tdict)[service_id].end(); i++) + { + routes.push_back(i->first); + } + } + + return routes; +} + + --- /dev/null +++ b/origin-src/wlach-libroutez-272ef93/python/libroutez/__init__.py --- /dev/null +++ b/origin-src/wlach-libroutez-272ef93/python/libroutez/osm.py @@ -1,1 +1,171 @@ +#!/usr/bin/python +# The code in this module was gratuitously stolen from +# graphserver (http://graphserver.sourceforge.net/) +# Copyright (c) 2007, Brandon Martin-Anderson + +import xml.sax +import copy +import sys +from math import * + +class Node: + def __init__(self, id, lon, lat): + self.id = id + self.lon = lon + self.lat = lat + self.tags = {} + +class Way: + def __init__(self, id, osm): + self.osm = osm + self.id = id + self.nds = [] + self.tags = {} + + def split(self, dividers): + # slice the node-array using this nifty recursive function + def slice_array(ar, dividers): + for i in range(1,len(ar)-1): + if dividers[ar[i]]>1: + #print "slice at %s"%ar[i] + left = ar[:i+1] + right = ar[i:] + + rightsliced = slice_array(right, dividers) + + return [left]+rightsliced + return [ar] + + slices = slice_array(self.nds, dividers) + + # create a way object for each node-array slice + ret = [] + i=0 + for slice in slices: + littleway = copy.copy( self ) + littleway.id += "-%d"%i + littleway.nds = slice + ret.append( littleway ) + i += 1 + + return ret + + def get_projected_points(self, reprojection_func=lambda x,y:(x,y)): + """nodedir is a dictionary of nodeid->node objects. If reprojection_func is None, returns unprojected points""" + ret = [] + + for nodeid in self.nds: + node = self.osm.nodes[ nodeid ] + ret.append( reprojection_func(node.lon,node.lat) ) + + return ret + + def to_canonical(self, srid, reprojection_func=None): + """Returns canonical string for this geometry""" + + return "SRID=%d;LINESTRING(%s)"%(srid, ",".join( ["%f %f"%(x,y) for x,y in self.get_projected_points()] ) ) + + @property + def fromv(self): + return self.nds[0] + + @property + def tov(self): + return self.nds[-1] + +class OSM: + + def __init__(self, filename_or_stream): + """ File can be either a filename or stream/file object.""" + nodes = {} + ways = {} + + superself = self + + class OSMHandler(xml.sax.ContentHandler): + @classmethod + def setDocumentLocator(self,loc): + pass + + @classmethod + def startDocument(self): + pass + + @classmethod + def endDocument(self): + pass + + @classmethod + def startElement(self, name, attrs): + if name=='node': + if (int(attrs['id']) % 1000) == 0: + print "Parsing node %s" % attrs['id'] + self.currElem = Node(attrs['id'], float(attrs['lon']), float(attrs['lat'])) + elif name=='way': + if (int(attrs['id']) % 1000) == 0: + print "Parsing way %s" % attrs['id'] + self.currElem = Way(attrs['id'], superself) + elif name=='tag': + pass + #self.currElem.tags[attrs['k']] = attrs['v'] + elif name=='nd': + self.currElem.nds.append( attrs['ref'] ) + + @classmethod + def endElement(self,name): + if name=='node': + nodes[self.currElem.id] = self.currElem + elif name=='way': + ways[self.currElem.id] = self.currElem + + @classmethod + def characters(self, chars): + pass + + xml.sax.parse(filename_or_stream, OSMHandler) + + self.nodes = nodes + self.ways = ways + + #count times each node is used + node_histogram = dict.fromkeys( self.nodes.keys(), 0 ) + print "Counting and pruning ways" + for way in self.ways.values(): + #if a way has only one node, delete it out of the osm collection + #similarly if it's not a road + if len(way.nds) < 2:# or not way.tags.get('highway') or way.tags['highway'] == 'footway': + del self.ways[way.id] + else: + for node in way.nds: + # toss out any ways that don't have all nodes on map + if not self.nodes.get(node) and self.ways.get(way.id): + del self.ways[way.id] + elif self.ways.get(way.id): + node_histogram[node] += 1 + + # delete nodes that don't appear in ways + for node in self.nodes.values(): + if node_histogram[node.id] == 0: + del self.nodes[node.id] + + #use that histogram to split all ways, replacing the member set of ways + print "Splitting ways" + new_ways = {} + for id, way in self.ways.iteritems(): + split_ways = way.split(node_histogram) + for split_way in split_ways: + new_ways[split_way.id] = split_way + self.ways = new_ways + + @property + def connecting_nodes(self): + """List of nodes that are the endpoint of one or more ways""" + + ret = {} + for way in self.ways.values(): + ret[way.fromv] = self.nodes[way.fromv] + ret[way.tov] = self.nodes[way.tov] + + return ret + --- /dev/null +++ b/origin-src/wlach-libroutez-272ef93/routez.i @@ -1,1 +1,2 @@ - +%module routez +%include tripgraph.i --- /dev/null +++ b/origin-src/wlach-libroutez-272ef93/ruby/.gitignore --- /dev/null +++ b/origin-src/wlach-libroutez-272ef93/t/all.t.cc @@ -1,1 +1,2 @@ +// just a blank file to get the unit test main function going --- /dev/null +++ b/origin-src/wlach-libroutez-272ef93/t/pytest.py @@ -1,1 +1,46 @@ +#!/usr/bin/python +# This is here mostly just to test that the python bindings actually work + +from libroutez.tripgraph import * +import time +from wvtest import * + +last=None + +@wvtest +def basic_find_path(): + graph = TripGraph() + graph.add_tripstop(0, TripStop.OSM, 0.0, 0.0) + graph.add_tripstop(1, TripStop.OSM, 1.0, 0.0) + + # no path available + p = graph.find_path(0, True, 0.0, 0.0, 1.0, 0.0) + WVPASSEQ(p, None) + + # walking only + graph.add_walkhop(0, 1) + p = graph.find_path(0, True, 0.0, 0.0, 1.0, 0.0) + actions = p.get_actions() + WVPASSEQ(len(actions), 1) + + +@wvtest +def get_service_period_offsets(): + graph = TripGraph() + graph.add_service_period(ServicePeriod(0, 1, 0, 108, 7, 0, 108, 2000, + False, True, False)) + t = time.mktime((2008, 1, 5, 0, 0, 0, 0, 0, -1)) + splist = graph.get_service_period_ids_for_time(int(t)) + WVPASSEQ(splist[0][0], 0) + WVPASSEQ(splist[0][1], 0) + + +@wvtest +def tripstop(): + ts = TripStop(0, TripStop.OSM, 0.0, 0.0); + ts.add_triphop(500, 1000, 1, 1, 1, 0); + route_ids = ts.get_routes(0) + WVPASSEQ(len(route_ids), 1) + WVPASSEQ(route_ids[0], 1) + --- /dev/null +++ b/origin-src/wlach-libroutez-272ef93/t/tripgraph.t.cc @@ -1,1 +1,310 @@ - +#include "wvtest.h" +#include "tripgraph.h" + +using namespace std; + + +WVTEST_MAIN("basic_graph_pathfinding") +{ + TripGraph g; + + // simple path, just walking + g.add_tripstop(0, TripStop::OSM, 0.0f, 0.0f); + g.add_tripstop(1, TripStop::OSM, 1.0f, 0.0f); + g.add_walkhop(0, 1); + + { + TripPath *p = g.find_path(0, false, 0.0, 0.0, 1.0, 0.0); + + std::deque actions = p->get_actions(); + WVPASSEQ(actions.size(), 1); + + TripAction action = actions.front(); + WVPASSEQ(action.src_id, 0); + WVPASSEQ(action.dest_id, 1); + + delete p; + } + + // take the triphop if we have it + { + ServicePeriod s(0, 0, 0, 0, 7, 0, 100, 2000, true, true, true); + g.add_service_period(s); + g.add_triphop(500, 1000, 0, 1, 1, 1, 0); + } + + { + TripPath *p = g.find_path(0, false, 0.0, 0.0, 1.0, 0.0); + + std::deque actions = p->get_actions(); + WVPASSEQ(actions.size(), 1); + + TripAction action = actions.front(); + WVPASSEQ(action.src_id, 0); + WVPASSEQ(action.dest_id, 1); + WVPASSEQ(action.start_time, 500.0f); + WVPASSEQ(action.end_time, 1000.0f); + + delete p; + } +} + + +WVTEST_MAIN("basic_graph_saveload") +{ + TripGraph g; + g.add_tripstop(0, TripStop::OSM, 0.0f, 0.0f); + g.add_tripstop(1, TripStop::OSM, 1.0f, 0.0f); + g.add_walkhop(0, 1); + + ServicePeriod s(0, 1, 0, 0, 7, 0, 100, 2000, true, true, true); + g.add_service_period(s); + g.add_triphop(500, 1000, 0, 1, 1, 1, 0); + + char *tmpgraphname = tmpnam(NULL); // security issues in unit tests? bah. + unlink(tmpgraphname); + g.save(tmpgraphname); + + TripGraph g2; + g2.load(tmpgraphname); + + // verify that we have two tripstops + for (int i=0; i<2; i++) + { + TripStop ts = g2.get_tripstop(i); + WVPASSEQ(ts.type, TripStop::OSM); + } + + // verify that we can still solve a basic path + { + TripPath *p = g.find_path(0, false, 0.0, 0.0, 1.0, 0.0); + + std::deque actions = p->get_actions(); + WVPASSEQ(actions.size(), 1); + + TripAction action = actions.front(); + WVPASSEQ(action.src_id, 0); + WVPASSEQ(action.dest_id, 1); + WVPASSEQ(action.start_time, 500.0f); + WVPASSEQ(action.end_time, 1000.0f); + + delete p; + } +} + + +WVTEST_MAIN("impossible_path") +{ + TripGraph g; + g.add_tripstop(0, TripStop::OSM, 0.0f, 0.0f); + g.add_tripstop(1, TripStop::OSM, 1.0f, 0.0f); + g.add_tripstop(2, TripStop::OSM, 0.0f, 1.0f); + g.add_walkhop(0, 1); + + TripPath *p = g.find_path(0, false, 0.0, 0.0, 0.0, 1.0); + WVPASS(!p); +} + + +WVTEST_MAIN("tripstops_in_range") +{ + TripGraph g; + // north and agricola + g.add_tripstop(0, TripStop::GTFS, 44.6554236f, -63.5936968f); + // north and robie (just north of north&agricola) + g.add_tripstop(1, TripStop::OSM, 44.6546407f, -63.5948438f); + // north and northwood (just south of north&agricola) + g.add_tripstop(2, TripStop::GTFS, 44.6567144f, -63.5919115f); + // Quinpool and Connaught (a few kms away from north&agricola) + g.add_tripstop(3, TripStop::GTFS, 44.6432423f, -63.6045261f); + + { + vector v = g.find_tripstops_in_range(44.6554236f, + -63.5936968f, + TripStop::GTFS, + 500.0f); + WVPASSEQ(v.size(), 2); + WVPASS(v[0].id == 0 || v[0].id == 2); + WVPASS(v[1].id == 0 || v[1].id == 2); + } +} + + +static time_t get_time_t(int tm_mday, int tm_mon, int tm_year) +{ + struct tm t; + t.tm_sec = 0; + t.tm_min = 0; + t.tm_hour = 0; + t.tm_mday = tm_mday; + t.tm_mon = tm_mon; + t.tm_year = tm_year; + t.tm_wday = -1; + t.tm_yday = -1; + t.tm_isdst = -1; + + return mktime(&t); +} + +WVTEST_MAIN("service_periods") +{ + TripGraph g; + + // from the 1st to the 7th (i.e. 1st saturday only) + { + ServicePeriod s(0, 1, 0, 108, 7, 0, 108, 2000, false, true, false); + g.add_service_period(s); + } + + // test something that's within a supported service period + // Saturday Midnight Jan 5th 2008 + { + vector > vsp = g.get_service_period_ids_for_time(get_time_t(5, 0, 108)); + WVPASSEQ(vsp.size(), 1); + WVPASSEQ(vsp[0].first, 0); + } + + // test something outside a supported service period: day + // Saturday Midnight Jan 11th 2008 + { + vector > vsp = g.get_service_period_ids_for_time(get_time_t(11, 0, 108)); + WVPASSEQ(vsp.size(), 0); + } + // test something outside a supported service period: month + // Saturday Midnight Feb 5th 2008 + { + vector > vsp = g.get_service_period_ids_for_time(get_time_t(5, 1, 108)); + WVPASSEQ(vsp.size(), 0); + } + + // test something outside a supported service period: year + // Saturday Midnight Jan 11th 2009 + { + vector > vsp = g.get_service_period_ids_for_time(get_time_t(5, 1, 109)); + WVPASSEQ(vsp.size(), 0); + } + + // add another service period (saturdays for month of january) + { + ServicePeriod s(1, 1, 0, 108, 31, 0, 108, 2000, false, true, + false); + g.add_service_period(s); + } + + // test something that's within _two_ supported service periods + // Saturday Midnight Jan 5th 2008 + { + vector > vsp = g.get_service_period_ids_for_time(get_time_t(5, 0, 108)); + WVPASSEQ(vsp.size(), 2); + WVPASS(vsp[0].first==0 || vsp[0].first==1); + WVPASS(vsp[1].first==0 || vsp[1].first==1); + WVFAILEQ(vsp[0].first, vsp[1].first); + } + + // save graph, reload, make sure service periods are still there +} + + +WVTEST_MAIN("service_periods_overlapping") +{ + TripGraph g; + + // from the 1st to the 7th (i.e. 1st saturday only) + // (weekday and saturday schedules) + { + ServicePeriod s1(0, 1, 0, 108, 7, 0, 108, 90000, false, true, false); + g.add_service_period(s1); + ServicePeriod s2(1, 1, 0, 108, 7, 0, 108, 90000, true, false, false); + g.add_service_period(s2); + } + + vector > vsp = g.get_service_period_ids_for_time(get_time_t(5, 0, 108)); + WVPASSEQ(vsp.size(), 2); + WVPASS(vsp[0].first==0 || vsp[0].first==1); + WVPASS(vsp[1].first==0 || vsp[1].first==1); + WVFAILEQ(vsp[0].first, vsp[1].first); + + int weekday_index = (vsp[0].first == 1) ? 0 : 1; + WVPASSEQ(vsp[weekday_index].second, 86400); +} + + +WVTEST_MAIN("service_periods_turned_on_or_off") +{ + TripGraph g; + + // from the 1st to the 7th (i.e. 1st saturday only) + // turn off weekday service on the 2nd (wednesday) + // turn on saturday service on the 3rd (keeping weekday service) + { + ServicePeriod s1(0, 1, 0, 108, 7, 0, 108, 80000, false, true, false); + s1.add_exception_on(3, 0, 108); + WVPASSEQ(s1.is_turned_on(3, 0, 108), true); + WVPASSEQ(s1.is_turned_on(4, 0, 108), false); + g.add_service_period(s1); + ServicePeriod s2(1, 1, 0, 108, 7, 0, 108, 80000, true, false, false); + s2.add_exception_off(2, 0, 108); + WVPASSEQ(s2.is_turned_off(2, 0, 108), true); + WVPASSEQ(s2.is_turned_off(3, 0, 108), false); + g.add_service_period(s2); + } + + { + // should be no service on the 2nd + vector > vsp = g.get_service_period_ids_for_time(get_time_t(2, 0, 108)); + WVPASSEQ(vsp.size(), 0); + } + + { + // should be two service periods on the 3rd (saturday and weekday) + vector > vsp = g.get_service_period_ids_for_time(get_time_t(3, 0, 108)); + WVPASSEQ(vsp.size(), 2); + WVPASS(vsp[0].first==0 || vsp[0].first==1); + WVPASS(vsp[1].first==0 || vsp[1].first==1); + WVFAILEQ(vsp[0].first, vsp[1].first); + } +} + + +WVTEST_MAIN("service_periods_save_load") +{ + TripGraph g; + + // use the same setup as the previous test: saturday and weekday schedules + // with a few exceptions + + // from the 1st to the 7th (i.e. 1st saturday only) + // turn off weekday service on the 2nd (wednesday) + // turn on saturday service on the 3rd (keeping weekday service) + { + ServicePeriod s1(0, 1, 0, 108, 7, 0, 108, 80000, false, true, false); + s1.add_exception_on(3, 0, 108); + g.add_service_period(s1); + ServicePeriod s2(1, 1, 0, 108, 7, 0, 108, 80000, true, false, false); + s2.add_exception_off(2, 0, 108); + g.add_service_period(s2); + } + + char *tmpgraphname = tmpnam(NULL); // security issues in unit tests? bah. + unlink(tmpgraphname); + g.save(tmpgraphname); + + TripGraph g2; + g2.load(tmpgraphname); + + { + // should be no service on the 2nd + vector > vsp = g2.get_service_period_ids_for_time(get_time_t(2, 0, 108)); + WVPASSEQ(vsp.size(), 0); + } + + { + // should be two service periods on the 3rd (saturday and weekday) + vector > vsp = g2.get_service_period_ids_for_time(get_time_t(3, 0, 108)); + WVPASSEQ(vsp.size(), 2); + WVPASS(vsp[0].first==0 || vsp[0].first==1); + WVPASS(vsp[1].first==0 || vsp[1].first==1); + WVFAILEQ(vsp[0].first, vsp[1].first); + } +} + --- /dev/null +++ b/origin-src/wlach-libroutez-272ef93/t/tripstop.t.cc @@ -1,1 +1,71 @@ +#include "wvtest.h" +#include "tripstop.h" +#include +using namespace std; +using namespace tr1; + + +WVTEST_MAIN("save/load") +{ + TripStop t1(1, TripStop::OSM, 44.5f, 54.4f); + t1.add_triphop(500, 550, 0, 0, 0, 0); + t1.add_triphop(550, 600, 0, 0, 0, 0); + + char *tmpname = tmpnam(NULL); // security issues in unit tests? bah. + unlink(tmpname); + FILE *fp1 = fopen(tmpname, "w"); + t1.write(fp1); + fclose(fp1); + + FILE *fp2 = fopen(tmpname, "r"); + TripStop t2(fp2); + + WVPASSEQ(t2.id, t1.id); + WVPASSEQ(t2.type, t1.type); + WVPASSEQ(t2.lat, t1.lat); + WVPASSEQ(t2.lng, t1.lng); + shared_ptr tdict = t2.tdict; + WVPASSEQ(tdict->size(), 1); + WVPASSEQ(((*tdict))[0].size(), 1); + WVPASSEQ(((*tdict))[0][0].size(), 2); + WVPASSEQ(((*tdict))[0][0][0].start_time, 500); + WVPASSEQ(((*tdict))[0][0][1].start_time, 550); + + fclose(fp2); +} + + +WVTEST_MAIN("get_multiple_triphops") +{ + TripStop t; + t.add_triphop(500, 550, 0, 0, 0, 0); + t.add_triphop(550, 600, 0, 0, 0, 0); + t.add_triphop(600, 650, 0, 0, 0, 0); + t.add_triphop(600, 650, 0, 0, 0, 1); + + // Ask for different amounts... + + vector v = t.find_triphops(499, 0, 0, 3); + WVPASSEQ(v.size(), 3); + WVPASSEQ(v[0].start_time, 500); + WVPASSEQ(v[1].start_time, 550); + WVPASSEQ(v[2].start_time, 600); + + v = t.find_triphops(499, 0, 0, 2); + WVPASSEQ(v.size(), 2); + WVPASSEQ(v[0].start_time, 500); + WVPASSEQ(v[1].start_time, 550); + + v = t.find_triphops(499, 0, 0, 4); + WVPASSEQ(v.size(), 3); + WVPASSEQ(v[0].start_time, 500); + WVPASSEQ(v[1].start_time, 550); + WVPASSEQ(v[2].start_time, 600); + + v = t.find_triphops(551, 0, 0, 2); + WVPASSEQ(v.size(), 1); + WVPASSEQ(v[0].start_time, 600); + +} + --- /dev/null +++ b/origin-src/wlach-libroutez-272ef93/tripgraph.i @@ -1,1 +1,25 @@ +%module tripgraph +%{ +#include "serviceperiod.h" +#include "tripgraph.h" +#include "trippath.h" +#include "tripstop.h" +%} + +%include "std_string.i" +%include "std_deque.i" +%include "std_vector.i" +%include "std_pair.i" +%include "inttypes.i" +%template(ListTripAction) std::deque; +%template(ListId) std::deque; +%template(ListTripHop) std::vector; +%template(ListTripStop) std::vector; +%template(ServicePeriodTuple) std::pair; +%template(ListServicePeriodTuple) std::vector >; +%include "serviceperiod.h" +%include "tripgraph.h" +%include "trippath.h" +%include "tripstop.h" + --- /dev/null +++ b/origin-src/wlach-libroutez-272ef93/utils/creategraph.py @@ -1,1 +1,179 @@ +#!/usr/bin/python +import transitfeed +import libroutez.osm as osm +import time +import sys +from libroutez.tripgraph import * +from optparse import OptionParser + +class IdMap: + '''class which maps from gtfs ids -> libroutez ids''' + def __init__(self): + self.spmap = {} + self.stopmap = {} + self.routemap = {} + self.tripmap = {} + + def save(self, fname): + f = open(fname, 'w') + + print >> f, "Service Periods: {" + for gtfs_sp_id in sorted(self.spmap.keys()): + print >>f, " '%s': %s," % (gtfs_sp_id, self.spmap[gtfs_sp_id]) + print >> f, "}" + + print >> f, "Stops: {" + for gtfs_stop_id in sorted(self.stopmap.keys()): + print >>f, " '%s': %s," % (gtfs_stop_id, self.stopmap[gtfs_stop_id]) + print >> f, "}" + + print >> f, "Routes: {" + for gtfs_route_id in sorted(self.routemap.keys()): + print >>f, " '%s': %s," % (gtfs_route_id, + self.routemap[gtfs_route_id]) + print >> f, "}" + + print >> f, "Trips: {" + for gtfs_trip_id in sorted(self.tripmap.keys()): + print >> f, " '%s': %s," % (gtfs_trip_id, self.tripmap[gtfs_trip_id]) + print >> f, "}" + + f.close() + +def load_gtfs(tripgraph, sched, idmap): + print "Setting timezone to %s" % sched.GetDefaultAgency().agency_timezone + tripgraph.set_timezone(str(sched.GetDefaultAgency().agency_timezone)) + + stops = sched.GetStopList() + for stop in stops: + idmap.stopmap[stop.stop_id] = len(idmap.stopmap) + tripgraph.add_tripstop(idmap.stopmap[stop.stop_id], TripStop.GTFS, + stop.stop_lat, stop.stop_lon) + + for sp_id in sched.service_periods.keys(): + idmap.spmap[sp_id] = len(idmap.spmap) + + service_period_bounds = {} + + trips = sched.GetTripList() + for trip in trips: + interpolated_stops = trip.GetTimeInterpolatedStops() + prevstop = None + prevsecs = 0 + for (secs, stoptime, is_timepoint) in interpolated_stops: + stop = stoptime.stop + if prevstop: + # stupid side-effect of google's transit feed python script being broken + if int(secs) < int(prevsecs): + print "WARNING: Negative edge in gtfs. This probably means you " + "need a more recent version of the google transit feed " + "package (see README)" + if not idmap.tripmap.has_key(trip.trip_id): + idmap.tripmap[trip.trip_id] = len(idmap.tripmap) + if not idmap.routemap.has_key(trip.route_id): + idmap.routemap[trip.route_id] = len(idmap.routemap) + + if not service_period_bounds.has_key(trip.service_id): + service_period_bounds[trip.service_id] = prevsecs + elif prevsecs > service_period_bounds[trip.service_id]: + service_period_bounds[trip.service_id] = prevsecs + + if prevstop.stop_id != stop.stop_id: + # only add triphop if we're not going to ourselves. there are + # some feeds (cough, cough, Halifax) which actually do this + tripgraph.add_triphop(prevsecs, secs, idmap.stopmap[prevstop.stop_id], + idmap.stopmap[stop.stop_id], + idmap.routemap[trip.route_id], + idmap.tripmap[trip.trip_id], + idmap.spmap[trip.service_id]) + prevstop = stop + prevsecs = secs + + for sp_id in sched.service_periods.keys(): + sp = sched.service_periods[sp_id] + if not sp.start_date or not sp.end_date: + continue + tm_start = time.strptime(sp.start_date, "%Y%m%d") + tm_end = time.strptime(sp.end_date, "%Y%m%d") + # FIXME: currently assume weekday service is uniform, i.e. + # monday service == mon-fri service + if service_period_bounds.has_key(sp_id): + s = ServicePeriod(idmap.spmap[sp_id], + tm_start.tm_mday, tm_start.tm_mon - 1, + (tm_start.tm_year - 1900), + tm_end.tm_mday, tm_end.tm_mon - 1, + (tm_end.tm_year - 1900), + int(service_period_bounds[sp_id]), + sp.day_of_week[0], sp.day_of_week[5], + sp.day_of_week[6]) + for ex in sp.date_exceptions.keys(): + tm_ex = time.strptime(ex, "%Y%m%d") + if sp.date_exceptions[ex] == 1: + s.add_exception_on(tm_ex.tm_mday, tm_ex.tm_mon - 1, + tm_ex.tm_year - 1900) + else: + s.add_exception_off(tm_ex.tm_mday, tm_ex.tm_mon - 1, + tm_ex.tm_year - 1900) + + tripgraph.add_service_period(s) + else: + print "WARNING: It appears as if we have a service period with no " + "bound. This implies that it's not actually being used for anything." + + +def load_osm(tripgraph, map, idmap): + # map of osm ids -> libroutez ids. libroutez ids are positive integers, + # starting from the last gtfs id. I'm assuming that OSM ids can be pretty + # much anything + osm_nodemap = {} + for node in map.nodes.values(): + osm_nodemap[node.id] = len(osm_nodemap) + len(idmap.stopmap) + tripgraph.add_tripstop(osm_nodemap[node.id], TripStop.OSM, + node.lat, node.lon) + + for way in map.ways.values(): + previd = None + for id in way.nds: + if previd: + tripgraph.add_walkhop(osm_nodemap[previd], osm_nodemap[id]) + tripgraph.add_walkhop(osm_nodemap[id], osm_nodemap[previd]) + previd = id + +if __name__ == '__main__': + + usage = "usage: %prog [options] " + parser = OptionParser(usage) + parser.add_option('--osm', dest='osm', + help='Path of OSM file (optional)') + + (options, args) = parser.parse_args() + + if len(args) < 3: + parser.error("incorrect number of arguments") + exit(1) + + print "Loading schedule." + schedule = transitfeed.Schedule( + problem_reporter=transitfeed.ProblemReporter()) + schedule.Load(args[0]) + print "Creating graph" + g = TripGraph() + print "Inserting gtfs into graph" + idmap = IdMap() + load_gtfs(g, schedule, idmap) + + if options.osm: + print "Loading OSM." + map = osm.OSM(options.osm) + print "Inserting osm into graph" + load_osm(g, map, idmap) + print "Linking osm with gtfs" + g.link_osm_gtfs() + + print "Saving idmap" + idmap.save(args[2]) + + print "Saving graph" + g.save(args[1]) + --- /dev/null +++ b/origin-src/wlach-libroutez-272ef93/utils/get-gtfs-bounds.py @@ -1,1 +1,45 @@ +#!/usr/bin/python + +import zipfile +from optparse import OptionParser + +if __name__ == '__main__': + parser = OptionParser() + (options, args) = parser.parse_args() + + zip = zipfile.ZipFile(args[0], mode='r') + stoptext = zip.read("stops.txt") + lines = stoptext.split('\n') + + descriptors = lines[0].split(',') + (stop_lat_descriptor, stop_lng_descriptor) = (-1, -1) + id = 0 + for descriptor in descriptors: + if descriptor == "stop_lat": + stop_lat_descriptor = id + elif descriptor == "stop_lon": + stop_lng_descriptor = id + id+=1 + + (min_lat, min_lng, max_lat, max_lng) = (0.0, 0.0, 0.0, 0.0) + for line in lines[1:-2:]: + stop_info = line.split(',') + (lat, lng) = (float(stop_info[stop_lat_descriptor]), + float(stop_info[stop_lng_descriptor])) + if min_lat == 0.0 or lat < min_lat: + min_lat = lat + if min_lng == 0.0 or lng < min_lng: + min_lng = lng + if max_lat == 0.0 or lat > max_lat: + max_lat = lat + if max_lng == 0.0 or lng > max_lng: + max_lng = lng + + print "Polygon:" + print "%s\t%s" % (min_lat, min_lng) + print "%s\t%s" % (min_lat, max_lng) + print "%s\t%s" % (max_lat, max_lng) + print "%s\t%s" % (max_lat, min_lng) + print "min_lat, min_lng, max_lat, max_lng: %s %s %s %s" % \ + (min_lat, min_lng, max_lat, max_lng) --- /dev/null +++ b/origin-src/wlach-libroutez-272ef93/wvtest/LICENSE @@ -1,1 +1,482 @@ - + GNU LIBRARY GENERAL PUBLIC LICENSE + Version 2, June 1991 + + Copyright (C) 1991 Free Software Foundation, Inc. + 675 Mass Ave, Cambridge, MA 02139, USA + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + +[This is the first released version of the library GPL. It is + numbered 2 because it goes with version 2 of the ordinary GPL.] + + Preamble + + The licenses for most software are designed to take away your +freedom to share and change it. By contrast, the GNU General Public +Licenses are intended to guarantee your freedom to share and change +free software--to make sure the software is free for all its users. + + This license, the Library General Public License, applies to some +specially designated Free Software Foundation software, and to any +other libraries whose authors decide to use it. You can use it for +your libraries, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +this service if you wish), that you receive source code or can get it +if you want it, that you can change the software or use pieces of it +in new free programs; and that you know you can do these things. + + To protect your rights, we need to make restrictions that forbid +anyone to deny you these rights or to ask you to surrender the rights. +These restrictions translate to certain responsibilities for you if +you distribute copies of the library, or if you modify it. + + For example, if you distribute copies of the library, whether gratis +or for a fee, you must give the recipients all the rights that we gave +you. You must make sure that they, too, receive or can get the source +code. If you link a program with the library, you must provide +complete object files to the recipients so that they can relink them +with the library, after making changes to the library and recompiling +it. And you must show them these terms so they know their rights. + + Our method of protecting your rights has two steps: (1) copyright +the library, and (2) offer you this license which gives you legal +permission to copy, distribute and/or modify the library. + + Also, for each distributor's protection, we want to make certain +that everyone understands that there is no warranty for this free +library. If the library is modified by someone else and passed on, we +want its recipients to know that what they have is not the original +version, so that any problems introduced by others will not reflect on +the original authors' reputations. + + Finally, any free program is threatened constantly by software +patents. We wish to avoid the danger that companies distributing free +software will individually obtain patent licenses, thus in effect +transforming the program into proprietary software. To prevent this, +we have made it clear that any patent must be licensed for everyone's +free use or not licensed at all. + + Most GNU software, including some libraries, is covered by the ordinary +GNU General Public License, which was designed for utility programs. This +license, the GNU Library General Public License, applies to certain +designated libraries. This license is quite different from the ordinary +one; be sure to read it in full, and don't assume that anything in it is +the same as in the ordinary license. + + The reason we have a separate public license for some libraries is that +they blur the distinction we usually make between modifying or adding to a +program and simply using it. Linking a program with a library, without +changing the library, is in some sense simply using the library, and is +analogous to running a utility program or application program. However, in +a textual and legal sense, the linked executable is a combined work, a +derivative of the original library, and the ordinary General Public License +treats it as such. + + Because of this blurred distinction, using the ordinary General +Public License for libraries did not effectively promote software +sharing, because most developers did not use the libraries. We +concluded that weaker conditions might promote sharing better. + + However, unrestricted linking of non-free programs would deprive the +users of those programs of all benefit from the free status of the +libraries themselves. This Library General Public License is intended to +permit developers of non-free programs to use free libraries, while +preserving your freedom as a user of such programs to change the free +libraries that are incorporated in them. (We have not seen how to achieve +this as regards changes in header files, but we have achieved it as regards +changes in the actual functions of the Library.) The hope is that this +will lead to faster development of free libraries. + + The precise terms and conditions for copying, distribution and +modification follow. Pay close attention to the difference between a +"work based on the library" and a "work that uses the library". The +former contains code derived from the library, while the latter only +works together with the library. + + Note that it is possible for a library to be covered by the ordinary +General Public License rather than by this special one. + + GNU LIBRARY GENERAL PUBLIC LICENSE + TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + + 0. This License Agreement applies to any software library which +contains a notice placed by the copyright holder or other authorized +party saying it may be distributed under the terms of this Library +General Public License (also called "this License"). Each licensee is +addressed as "you". + + A "library" means a collection of software functions and/or data +prepared so as to be conveniently linked with application programs +(which use some of those functions and data) to form executables. + + The "Library", below, refers to any such software library or work +which has been distributed under these terms. A "work based on the +Library" means either the Library or any derivative work under +copyright law: that is to say, a work containing the Library or a +portion of it, either verbatim or with modifications and/or translated +straightforwardly into another language. (Hereinafter, translation is +included without limitation in the term "modification".) + + "Source code" for a work means the preferred form of the work for +making modifications to it. For a library, complete source code means +all the source code for all modules it contains, plus any associated +interface definition files, plus the scripts used to control compilation +and installation of the library. + + Activities other than copying, distribution and modification are not +covered by this License; they are outside its scope. The act of +running a program using the Library is not restricted, and output from +such a program is covered only if its contents constitute a work based +on the Library (independent of the use of the Library in a tool for +writing it). Whether that is true depends on what the Library does +and what the program that uses the Library does. + + 1. You may copy and distribute verbatim copies of the Library's +complete source code as you receive it, in any medium, provided that +you conspicuously and appropriately publish on each copy an +appropriate copyright notice and disclaimer of warranty; keep intact +all the notices that refer to this License and to the absence of any +warranty; and distribute a copy of this License along with the +Library. + + You may charge a fee for the physical act of transferring a copy, +and you may at your option offer warranty protection in exchange for a +fee. + + 2. You may modify your copy or copies of the Library or any portion +of it, thus forming a work based on the Library, and copy and +distribute such modifications or work under the terms of Section 1 +above, provided that you also meet all of these conditions: + + a) The modified work must itself be a software library. + + b) You must cause the files modified to carry prominent notices + stating that you changed the files and the date of any change. + + c) You must cause the whole of the work to be licensed at no + charge to all third parties under the terms of this License. + + d) If a facility in the modified Library refers to a function or a + table of data to be supplied by an application program that uses + the facility, other than as an argument passed when the facility + is invoked, then you must make a good faith effort to ensure that, + in the event an application does not supply such function or + table, the facility still operates, and performs whatever part of + its purpose remains meaningful. + + (For example, a function in a library to compute square roots has + a purpose that is entirely well-defined independent of the + application. Therefore, Subsection 2d requires that any + application-supplied function or table used by this function must + be optional: if the application does not supply it, the square + root function must still compute square roots.) + +These requirements apply to the modified work as a whole. If +identifiable sections of that work are not derived from the Library, +and can be reasonably considered independent and separate works in +themselves, then this License, and its terms, do not apply to those +sections when you distribute them as separate works. But when you +distribute the same sections as part of a whole which is a work based +on the Library, the distribution of the whole must be on the terms of +this License, whose permissions for other licensees extend to the +entire whole, and thus to each and every part regardless of who wrote +it. + +Thus, it is not the intent of this section to claim rights or contest +your rights to work written entirely by you; rather, the intent is to +exercise the right to control the distribution of derivative or +collective works based on the Library. + +In addition, mere aggregation of another work not based on the Library +with the Library (or with a work based on the Library) on a volume of +a storage or distribution medium does not bring the other work under +the scope of this License. + + 3. You may opt to apply the terms of the ordinary GNU General Public +License instead of this License to a given copy of the Library. To do +this, you must alter all the notices that refer to this License, so +that they refer to the ordinary GNU General Public License, version 2, +instead of to this License. (If a newer version than version 2 of the +ordinary GNU General Public License has appeared, then you can specify +that version instead if you wish.) Do not make any other change in +these notices. + + Once this change is made in a given copy, it is irreversible for +that copy, so the ordinary GNU General Public License applies to all +subsequent copies and derivative works made from that copy. + + This option is useful when you wish to copy part of the code of +the Library into a program that is not a library. + + 4. You may copy and distribute the Library (or a portion or +derivative of it, under Section 2) in object code or executable form +under the terms of Sections 1 and 2 above provided that you accompany +it with the complete corresponding machine-readable source code, which +must be distributed under the terms of Sections 1 and 2 above on a +medium customarily used for software interchange. + + If distribution of object code is made by offering access to copy +from a designated place, then offering equivalent access to copy the +source code from the same place satisfies the requirement to +distribute the source code, even though third parties are not +compelled to copy the source along with the object code. + + 5. A program that contains no derivative of any portion of the +Library, but is designed to work with the Library by being compiled or +linked with it, is called a "work that uses the Library". Such a +work, in isolation, is not a derivative work of the Library, and +therefore falls outside the scope of this License. + + However, linking a "work that uses the Library" with the Library +creates an executable that is a derivative of the Library (because it +contains portions of the Library), rather than a "work that uses the +library". The executable is therefore covered by this License. +Section 6 states terms for distribution of such executables. + + When a "work that uses the Library" uses material from a header file +that is part of the Library, the object code for the work may be a +derivative work of the Library even though the source code is not. +Whether this is true is especially significant if the work can be +linked without the Library, or if the work is itself a library. The +threshold for this to be true is not precisely defined by law. + + If such an object file uses only numerical parameters, data +structure layouts and accessors, and small macros and small inline +functions (ten lines or less in length), then the use of the object +file is unrestricted, regardless of whether it is legally a derivative +work. (Executables containing this object code plus portions of the +Library will still fall under Section 6.) + + Otherwise, if the work is a derivative of the Library, you may +distribute the object code for the work under the terms of Section 6. +Any executables containing that work also fall under Section 6, +whether or not they are linked directly with the Library itself. + + 6. As an exception to the Sections above, you may also compile or +link a "work that uses the Library" with the Library to produce a +work containing portions of the Library, and distribute that work +under terms of your choice, provided that the terms permit +modification of the work for the customer's own use and reverse +engineering for debugging such modifications. + + You must give prominent notice with each copy of the work that the +Library is used in it and that the Library and its use are covered by +this License. You must supply a copy of this License. If the work +during execution displays copyright notices, you must include the +copyright notice for the Library among them, as well as a reference +directing the user to the copy of this License. Also, you must do one +of these things: + + a) Accompany the work with the complete corresponding + machine-readable source code for the Library including whatever + changes were used in the work (which must be distributed under + Sections 1 and 2 above); and, if the work is an executable linked + with the Library, with the complete machine-readable "work that + uses the Library", as object code and/or source code, so that the + user can modify the Library and then relink to produce a modified + executable containing the modified Library. (It is understood + that the user who changes the contents of definitions files in the + Library will not necessarily be able to recompile the application + to use the modified definitions.) + + b) Accompany the work with a written offer, valid for at + least three years, to give the same user the materials + specified in Subsection 6a, above, for a charge no more + than the cost of performing this distribution. + + c) If distribution of the work is made by offering access to copy + from a designated place, offer equivalent access to copy the above + specified materials from the same place. + + d) Verify that the user has already received a copy of these + materials or that you have already sent this user a copy. + + For an executable, the required form of the "work that uses the +Library" must include any data and utility programs needed for +reproducing the executable from it. However, as a special exception, +the source code distributed need not include anything that is normally +distributed (in either source or binary form) with the major +components (compiler, kernel, and so on) of the operating system on +which the executable runs, unless that component itself accompanies +the executable. + + It may happen that this requirement contradicts the license +restrictions of other proprietary libraries that do not normally +accompany the operating system. Such a contradiction means you cannot +use both them and the Library together in an executable that you +distribute. + + 7. You may place library facilities that are a work based on the +Library side-by-side in a single library together with other library +facilities not covered by this License, and distribute such a combined +library, provided that the separate distribution of the work based on +the Library and of the other library facilities is otherwise +permitted, and provided that you do these two things: + + a) Accompany the combined library with a copy of the same work + based on the Library, uncombined with any other library + facilities. This must be distributed under the terms of the + Sections above. + + b) Give prominent notice with the combined library of the fact + that part of it is a work based on the Library, and explaining + where to find the accompanying uncombined form of the same work. + + 8. You may not copy, modify, sublicense, link with, or distribute +the Library except as expressly provided under this License. Any +attempt otherwise to copy, modify, sublicense, link with, or +distribute the Library is void, and will automatically terminate your +rights under this License. However, parties who have received copies, +or rights, from you under this License will not have their licenses +terminated so long as such parties remain in full compliance. + + 9. You are not required to accept this License, since you have not +signed it. However, nothing else grants you permission to modify or +distribute the Library or its derivative works. These actions are +prohibited by law if you do not accept this License. Therefore, by +modifying or distributing the Library (or any work based on the +Library), you indicate your acceptance of this License to do so, and +all its terms and conditions for copying, distributing or modifying +the Library or works based on it. + + 10. Each time you redistribute the Library (or any work based on the +Library), the recipient automatically receives a license from the +original licensor to copy, distribute, link with or modify the Library +subject to these terms and conditions. You may not impose any further +restrictions on the recipients' exercise of the rights granted herein. +You are not responsible for enforcing compliance by third parties to +this License. + + 11. If, as a consequence of a court judgment or allegation of patent +infringement or for any other reason (not limited to patent issues), +conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot +distribute so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you +may not distribute the Library at all. For example, if a patent +license would not permit royalty-free redistribution of the Library by +all those who receive copies directly or indirectly through you, then +the only way you could satisfy both it and this License would be to +refrain entirely from distribution of the Library. + +If any portion of this section is held invalid or unenforceable under any +particular circumstance, the balance of the section is intended to apply, +and the section as a whole is intended to apply in other circumstances. + +It is not the purpose of this section to induce you to infringe any +patents or other property right claims or to contest validity of any +such claims; this section has the sole purpose of protecting the +integrity of the free software distribution system which is +implemented by public license practices. Many people have made +generous contributions to the wide range of software distributed +through that system in reliance on consistent application of that +system; it is up to the author/donor to decide if he or she is willing +to distribute software through any other system and a licensee cannot +impose that choice. + +This section is intended to make thoroughly clear what is believed to +be a consequence of the rest of this License. + + 12. If the distribution and/or use of the Library is restricted in +certain countries either by patents or by copyrighted interfaces, the +original copyright holder who places the Library under this License may add +an explicit geographical distribution limitation excluding those countries, +so that distribution is permitted only in or among countries not thus +excluded. In such case, this License incorporates the limitation as if +written in the body of this License. + + 13. The Free Software Foundation may publish revised and/or new +versions of the Library General Public License from time to time. +Such new versions will be similar in spirit to the present version, +but may differ in detail to address new problems or concerns. + +Each version is given a distinguishing version number. If the Library +specifies a version number of this License which applies to it and +"any later version", you have the option of following the terms and +conditions either of that version or of any later version published by +the Free Software Foundation. If the Library does not specify a +license version number, you may choose any version ever published by +the Free Software Foundation. + + 14. If you wish to incorporate parts of the Library into other free +programs whose distribution conditions are incompatible with these, +write to the author to ask for permission. For software which is +copyrighted by the Free Software Foundation, write to the Free +Software Foundation; we sometimes make exceptions for this. Our +decision will be guided by the two goals of preserving the free status +of all derivatives of our free software and of promoting the sharing +and reuse of software generally. + + NO WARRANTY + + 15. BECAUSE THE LIBRARY IS LICENSED FREE OF CHARGE, THERE IS NO +WARRANTY FOR THE LIBRARY, TO THE EXTENT PERMITTED BY APPLICABLE LAW. +EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR +OTHER PARTIES PROVIDE THE LIBRARY "AS IS" WITHOUT WARRANTY OF ANY +KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE +LIBRARY IS WITH YOU. SHOULD THE LIBRARY PROVE DEFECTIVE, YOU ASSUME +THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + + 16. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN +WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY +AND/OR REDISTRIBUTE THE LIBRARY AS PERMITTED ABOVE, BE LIABLE TO YOU +FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR +CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE +LIBRARY (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING +RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A +FAILURE OF THE LIBRARY TO OPERATE WITH ANY OTHER SOFTWARE), EVEN IF +SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH +DAMAGES. + + END OF TERMS AND CONDITIONS + + Appendix: How to Apply These Terms to Your New Libraries + + If you develop a new library, and you want it to be of the greatest +possible use to the public, we recommend making it free software that +everyone can redistribute and change. You can do so by permitting +redistribution under these terms (or, alternatively, under the terms of the +ordinary General Public License). + + To apply these terms, attach the following notices to the library. It is +safest to attach them to the start of each source file to most effectively +convey the exclusion of warranty; and each file should have at least the +"copyright" line and a pointer to where the full notice is found. + + + Copyright (C) + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public + License along with this library; if not, write to the Free + Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + +Also add information on how to contact you by electronic and paper mail. + +You should also get your employer (if you work as a programmer) or your +school, if any, to sign a "copyright disclaimer" for the library, if +necessary. Here is a sample; alter the names: + + Yoyodyne, Inc., hereby disclaims all copyright interest in the + library `Frob' (a library for tweaking knobs) written by James Random Hacker. + + , 1 April 1990 + Ty Coon, President of Vice + +That's all there is to it! + --- /dev/null +++ b/origin-src/wlach-libroutez-272ef93/wvtest/README @@ -1,1 +1,6 @@ +This is a snapshot of various files from the wvtest project that we use +to test libroutez. It is licensed under the LGPL. For more information, +see here: +http://github.com/apenwarr/wvtest/tree/master + --- /dev/null +++ b/origin-src/wlach-libroutez-272ef93/wvtest/cpp/wvtest.cc @@ -1,1 +1,444 @@ - +/* + * WvTest: + * Copyright (C) 1997-2009 Net Integration Technologies, Inc. + * Licensed under the GNU Library General Public License, version 2. + * See the included file named LICENSE for license information. + */ +#include "wvtest.h" +#include +#include +#include +#include +#ifdef _WIN32 +#include +#else +#include +#include +#endif +#include +#include + +#include + +#ifdef HAVE_VALGRIND_MEMCHECK_H +# include +# include +#else +# define VALGRIND_COUNT_ERRORS 0 +# define VALGRIND_DO_LEAK_CHECK +# define VALGRIND_COUNT_LEAKS(a,b,c,d) (a=b=c=d=0) +#endif + +#define MAX_TEST_TIME 40 // max seconds for a single test to run +#define MAX_TOTAL_TIME 120*60 // max seconds for the entire suite to run + +#define TEST_START_FORMAT "! %s:%-5d %-40s " + +static int memerrs() +{ + return (int)VALGRIND_COUNT_ERRORS; +} + +static int memleaks() +{ + int leaked = 0, dubious = 0, reachable = 0, suppressed = 0; + VALGRIND_DO_LEAK_CHECK; + VALGRIND_COUNT_LEAKS(leaked, dubious, reachable, suppressed); + printf("memleaks: sure:%d dubious:%d reachable:%d suppress:%d\n", + leaked, dubious, reachable, suppressed); + fflush(stdout); + + // dubious+reachable are normally non-zero because of globals... + // return leaked+dubious+reachable; + return leaked; +} + +// Return 1 if no children are running or zombies, 0 if there are any running +// or zombie children. +// Will wait for any already-terminated children first. +// Passes if no rogue children were running, fails otherwise. +// If your test gets a failure in here, either you're not killing all your +// children, or you're not calling waitpid(2) on all of them. +static bool no_running_children() +{ +#ifndef _WIN32 + pid_t wait_result; + + // Acknowledge and complain about any zombie children + do + { + int status = 0; + wait_result = waitpid(-1, &status, WNOHANG); + + if (wait_result > 0) + { + char buf[256]; + snprintf(buf, sizeof(buf) - 1, "%d", wait_result); + buf[sizeof(buf)-1] = '\0'; + WVFAILEQ("Unclaimed dead child process", buf); + } + } while (wait_result > 0); + + // There should not be any running children, so waitpid should return -1 + WVPASSEQ(errno, ECHILD); + WVPASSEQ(wait_result, -1); + return (wait_result == -1 && errno == ECHILD); +#endif + return true; +} + + +WvTest *WvTest::first, *WvTest::last; +int WvTest::fails, WvTest::runs; +time_t WvTest::start_time; +bool WvTest::run_twice = false; + +void WvTest::alarm_handler(int) +{ + printf("\n! WvTest Current test took longer than %d seconds! FAILED\n", + MAX_TEST_TIME); + fflush(stdout); + abort(); +} + + +static const char *pathstrip(const char *filename) +{ + const char *cptr; + cptr = strrchr(filename, '/'); + if (cptr) filename = cptr + 1; + cptr = strrchr(filename, '\\'); + if (cptr) filename = cptr + 1; + return filename; +} + + +WvTest::WvTest(const char *_descr, const char *_idstr, MainFunc *_main, + int _slowness) : + descr(_descr), + idstr(pathstrip(_idstr)), + main(_main), + slowness(_slowness), + next(NULL) +{ + if (first) + last->next = this; + else + first = this; + last = this; +} + + +static bool prefix_match(const char *s, const char * const *prefixes) +{ + for (const char * const *prefix = prefixes; prefix && *prefix; prefix++) + { + if (!strncasecmp(s, *prefix, strlen(*prefix))) + return true; + } + return false; +} + + +int WvTest::run_all(const char * const *prefixes) +{ + int old_valgrind_errs = 0, new_valgrind_errs; + int old_valgrind_leaks = 0, new_valgrind_leaks; + +#ifdef _WIN32 + /* I should be doing something to do with SetTimer here, + * not sure exactly what just yet */ +#else + char *disable(getenv("WVTEST_DISABLE_TIMEOUT")); + if (disable != NULL && disable[0] != '\0' && disable[0] != '0') + signal(SIGALRM, SIG_IGN); + else + signal(SIGALRM, alarm_handler); + alarm(MAX_TEST_TIME); +#endif + start_time = time(NULL); + + // make sure we can always start out in the same directory, so tests have + // access to their files. If a test uses chdir(), we want to be able to + // reverse it. + char wd[1024]; + if (!getcwd(wd, sizeof(wd))) + strcpy(wd, "."); + + const char *slowstr1 = getenv("WVTEST_MIN_SLOWNESS"); + const char *slowstr2 = getenv("WVTEST_MAX_SLOWNESS"); + int min_slowness = 0, max_slowness = 65535; + if (slowstr1) min_slowness = atoi(slowstr1); + if (slowstr2) max_slowness = atoi(slowstr2); + +#ifdef _WIN32 + run_twice = false; +#else + char *parallel_str = getenv("WVTEST_PARALLEL"); + if (parallel_str) + run_twice = atoi(parallel_str) > 0; +#endif + + // there are lots of fflush() calls in here because stupid win32 doesn't + // flush very often by itself. + fails = runs = 0; + for (WvTest *cur = first; cur; cur = cur->next) + { + if (cur->slowness <= max_slowness + && cur->slowness >= min_slowness + && (!prefixes + || prefix_match(cur->idstr, prefixes) + || prefix_match(cur->descr, prefixes))) + { +#ifndef _WIN32 + // set SIGPIPE back to default, helps catch tests which don't set + // this signal to SIG_IGN (which is almost always what you want) + // on startup + signal(SIGPIPE, SIG_DFL); + + pid_t child = 0; + if (run_twice) + { + // I see everything twice! + printf("Running test in parallel.\n"); + child = fork(); + } +#endif + + printf("\nTesting \"%s\" in %s:\n", cur->descr, cur->idstr); + fflush(stdout); + + cur->main(); + chdir(wd); + + new_valgrind_errs = memerrs(); + WVPASS(new_valgrind_errs == old_valgrind_errs); + old_valgrind_errs = new_valgrind_errs; + + new_valgrind_leaks = memleaks(); + WVPASS(new_valgrind_leaks == old_valgrind_leaks); + old_valgrind_leaks = new_valgrind_leaks; + + fflush(stderr); + printf("\n"); + fflush(stdout); + +#ifndef _WIN32 + if (run_twice) + { + if (!child) + { + // I see everything once! + printf("Child exiting.\n"); + _exit(0); + } + else + { + printf("Waiting for child to exit.\n"); + int result; + while ((result = waitpid(child, NULL, 0)) == -1 && + errno == EINTR) + printf("Waitpid interrupted, retrying.\n"); + } + } +#endif + + WVPASS(no_running_children()); + } + } + + WVPASS(runs > 0); + + if (prefixes && *prefixes && **prefixes) + printf("WvTest: WARNING: only ran tests starting with " + "specifed prefix(es).\n"); + else + printf("WvTest: ran all tests.\n"); + printf("WvTest: %d test%s, %d failure%s.\n", + runs, runs==1 ? "" : "s", + fails, fails==1 ? "": "s"); + fflush(stdout); + + return fails != 0; +} + + +// If we aren't running in parallel, we want to output the name of the test +// before we run it, so we know what happened if it crashes. If we are +// running in parallel, outputting this information in multiple printf()s +// can confuse parsers, so we want to output everything in one printf(). +// +// This function gets called by both start() and check(). If we're not +// running in parallel, just print the data. If we're running in parallel, +// and we're starting a test, save a copy of the file/line/description until +// the test is done and we can output it all at once. +// +// Yes, this is probably the worst API of all time. +void WvTest::print_result(bool start, const char *_file, int _line, + const char *_condstr, bool result) +{ + static char *file; + static char *condstr; + static int line; + + if (start) + { + if (file) + free(file); + if (condstr) + free(condstr); + file = strdup(pathstrip(_file)); + condstr = strdup(_condstr); + line = _line; + + for (char *cptr = condstr; *cptr; cptr++) + { + if (!isprint((unsigned char)*cptr)) + *cptr = '!'; + } + } + + const char *result_str = result ? "ok\n" : "FAILED\n"; + if (run_twice) + { + if (!start) + printf(TEST_START_FORMAT "%s", file, line, condstr, result_str); + } + else + { + if (start) + printf(TEST_START_FORMAT, file, line, condstr); + else + printf("%s", result_str); + } + fflush(stdout); + + if (!start) + { + if (file) + free(file); + if (condstr) + free(condstr); + file = condstr = NULL; + } +} + + +void WvTest::start(const char *file, int line, const char *condstr) +{ + // Either print the file, line, and condstr, or save them for later. + print_result(true, file, line, condstr, 0); +} + + +void WvTest::check(bool cond) +{ +#ifndef _WIN32 + alarm(MAX_TEST_TIME); // restart per-test timeout +#endif + if (!start_time) start_time = time(NULL); + + if (time(NULL) - start_time > MAX_TOTAL_TIME) + { + printf("\n! WvTest Total run time exceeded %d seconds! FAILED\n", + MAX_TOTAL_TIME); + fflush(stdout); + abort(); + } + + runs++; + + print_result(false, NULL, 0, NULL, cond); + + if (!cond) + { + fails++; + + if (getenv("WVTEST_DIE_FAST")) + abort(); + } +} + + +bool WvTest::start_check_eq(const char *file, int line, + const char *a, const char *b, bool expect_pass) +{ + if (!a) a = ""; + if (!b) b = ""; + + size_t len = strlen(a) + strlen(b) + 8 + 1; + char *str = new char[len]; + sprintf(str, "[%s] %s [%s]", a, expect_pass ? "==" : "!=", b); + + start(file, line, str); + delete[] str; + + bool cond = !strcmp(a, b); + if (!expect_pass) + cond = !cond; + + check(cond); + return cond; +} + + +bool WvTest::start_check_eq(const char *file, int line, + const std::string &a, const std::string &b, + bool expect_pass) +{ + return start_check_eq(file, line, a.c_str(), b.c_str(), expect_pass); +} + + +bool WvTest::start_check_eq(const char *file, int line, + int a, int b, bool expect_pass) +{ + size_t len = 128 + 128 + 8 + 1; + char *str = new char[len]; + sprintf(str, "%d %s %d", a, expect_pass ? "==" : "!=", b); + + start(file, line, str); + delete[] str; + + bool cond = (a == b); + if (!expect_pass) + cond = !cond; + + check(cond); + return cond; +} + + +bool WvTest::start_check_lt(const char *file, int line, + const char *a, const char *b) +{ + if (!a) a = ""; + if (!b) b = ""; + + size_t len = strlen(a) + strlen(b) + 8 + 1; + char *str = new char[len]; + sprintf(str, "[%s] < [%s]", a, b); + + start(file, line, str); + delete[] str; + + bool cond = strcmp(a, b) < 0; + check(cond); + return cond; +} + + +bool WvTest::start_check_lt(const char *file, int line, int a, int b) +{ + size_t len = 128 + 128 + 8 + 1; + char *str = new char[len]; + sprintf(str, "%d < %d", a, b); + + start(file, line, str); + delete[] str; + + bool cond = a < b; + check(cond); + return cond; +} + --- /dev/null +++ b/origin-src/wlach-libroutez-272ef93/wvtest/cpp/wvtest.h @@ -1,1 +1,78 @@ +/* -*- Mode: C++ -*- + * WvTest: + * Copyright (C) 1997-2009 Net Integration Technologies, Inc. + * Licensed under the GNU Library General Public License, version 2. + * See the included file named LICENSE for license information. + */ +#ifndef __WVTEST_H +#define __WVTEST_H +#ifndef WVTEST_CONFIGURED +# error "Missing settings: HAVE_VALGRIND_MEMCHECK_H HAVE_WVCRASH WVTEST_CONFIGURED" +#endif + +#include +#include + +class WvTest +{ + typedef void MainFunc(); + const char *descr, *idstr; + MainFunc *main; + int slowness; + WvTest *next; + static WvTest *first, *last; + static int fails, runs; + static time_t start_time; + static bool run_twice; + + static void alarm_handler(int sig); + + static void print_result(bool start, const char *file, int line, + const char *condstr, bool result); +public: + WvTest(const char *_descr, const char *_idstr, MainFunc *_main, int _slow); + static int run_all(const char * const *prefixes = NULL); + static void start(const char *file, int line, const char *condstr); + static void check(bool cond); + static inline bool start_check(const char *file, int line, + const char *condstr, bool cond) + { start(file, line, condstr); check(cond); return cond; } + static bool start_check_eq(const char *file, int line, + const char *a, const char *b, bool expect_pass); + static bool start_check_eq(const char *file, int line, + const std::string &a, const std::string &b, + bool expect_pass); + static bool start_check_eq(const char *file, int line, int a, int b, + bool expect_pass); + static bool start_check_lt(const char *file, int line, + const char *a, const char *b); + static bool start_check_lt(const char *file, int line, int a, int b); +}; + + +#define WVPASS(cond) \ + WvTest::start_check(__FILE__, __LINE__, #cond, (cond)) +#define WVPASSEQ(a, b) \ + WvTest::start_check_eq(__FILE__, __LINE__, (a), (b), true) +#define WVPASSLT(a, b) \ + WvTest::start_check_lt(__FILE__, __LINE__, (a), (b)) +#define WVFAIL(cond) \ + WvTest::start_check(__FILE__, __LINE__, "NOT(" #cond ")", !(cond)) +#define WVFAILEQ(a, b) \ + WvTest::start_check_eq(__FILE__, __LINE__, (a), (b), false) +#define WVPASSNE(a, b) WVFAILEQ(a, b) +#define WVFAILNE(a, b) WVPASSEQ(a, b) + +#define WVTEST_MAIN3(descr, ff, ll, slowness) \ + static void _wvtest_main_##ll(); \ + static WvTest _wvtest_##ll(descr, ff, _wvtest_main_##ll, slowness); \ + static void _wvtest_main_##ll() +#define WVTEST_MAIN2(descr, ff, ll, slowness) \ + WVTEST_MAIN3(descr, ff, ll, slowness) +#define WVTEST_MAIN(descr) WVTEST_MAIN2(descr, __FILE__, __LINE__, 0) +#define WVTEST_SLOW_MAIN(descr) WVTEST_MAIN2(descr, __FILE__, __LINE__, 1) + + +#endif // __WVTEST_H + --- /dev/null +++ b/origin-src/wlach-libroutez-272ef93/wvtest/cpp/wvtestmain.cc @@ -1,1 +1,100 @@ +/* + * WvTest: + * Copyright (C) 1997-2009 Net Integration Technologies, Inc. + * Licensed under the GNU Library General Public License, version 2. + * See the included file named LICENSE for license information. + */ +#include "wvtest.h" +#ifdef HAVE_WVCRASH +# include "wvcrash.h" +#endif +#include +#include +#ifdef _WIN32 +#include +#include +#else +#include +#include +#endif +static bool fd_is_valid(int fd) +{ +#ifdef _WIN32 + if ((HANDLE)_get_osfhandle(fd) != INVALID_HANDLE_VALUE) return true; +#endif + int nfd = dup(fd); + if (nfd >= 0) + { + close(nfd); + return true; + } + return false; + +} + + +static int fd_count(const char *when) +{ + int count = 0; + + printf("fds open at %s:", when); + + for (int fd = 0; fd < 1024; fd++) + { + if (fd_is_valid(fd)) + { + count++; + printf(" %d", fd); + fflush(stdout); + } + } + printf("\n"); + + return count; +} + + +int main(int argc, char **argv) +{ + char buf[200]; +#if defined(_WIN32) && defined(HAVE_WVCRASH) + setup_console_crash(); +#endif + + // test wvtest itself. Not very thorough, but you have to draw the + // line somewhere :) + WVPASS(true); + WVPASS(1); + WVFAIL(false); + WVFAIL(0); + int startfd, endfd; + char * const *prefixes = NULL; + + if (argc > 1) + prefixes = argv + 1; + + startfd = fd_count("start"); + int ret = WvTest::run_all(prefixes); + + if (ret == 0) // don't pollute the strace output if we failed anyway + { + endfd = fd_count("end"); + + WVPASS(startfd == endfd); +#ifndef _WIN32 + if (startfd != endfd) + { + sprintf(buf, "ls -l /proc/%d/fd", getpid()); + system(buf); + } +#endif + } + + // keep 'make' from aborting if this environment variable is set + if (getenv("WVTEST_NO_FAIL")) + return 0; + else + return ret; +} + --- /dev/null +++ b/origin-src/wlach-libroutez-272ef93/wvtest/wvtestrun @@ -1,1 +1,187 @@ +#!/usr/bin/perl -w +# +# WvTest: +# Copyright (C)2007-2009 Versabanq Innovations Inc. and contributors. +# Licensed under the GNU Library General Public License, version 2. +# See the included file named LICENSE for license information. +# +use strict; +use Time::HiRes qw(time); +# always flush +$| = 1; + +if (@ARGV < 1) { + print STDERR "Usage: $0 \n"; + exit 127; +} + +print STDERR "Testing \"all\" in @ARGV:\n"; + +my $pid = open(my $fh, "-|"); +if (!$pid) { + # child + setpgrp(); + open STDERR, '>&STDOUT' or die("Can't dup stdout: $!\n"); + exec(@ARGV); + exit 126; # just in case +} + +my $istty = -t STDOUT; +my @log = (); +my ($gpasses, $gfails) = (0,0); + +sub bigkill($) +{ + my $pid = shift; + + if (@log) { + print "\n" . join("\n", @log) . "\n"; + } + + print STDERR "\n! Killed by signal FAILED\n"; + + ($pid > 0) || die("pid is '$pid'?!\n"); + + local $SIG{CHLD} = sub { }; # this will wake us from sleep() faster + kill 15, $pid; + sleep(2); + + if ($pid > 1) { + kill 9, -$pid; + } + kill 9, $pid; + + exit(125); +} + +# parent +local $SIG{INT} = sub { bigkill($pid); }; +local $SIG{TERM} = sub { bigkill($pid); }; +local $SIG{ALRM} = sub { + print STDERR "Alarm timed out! No test results for too long.\n"; + bigkill($pid); +}; + +sub colourize($) +{ + my $result = shift; + my $pass = ($result eq "ok"); + + if ($istty) { + my $colour = $pass ? "\e[32;1m" : "\e[31;1m"; + return "$colour$result\e[0m"; + } else { + return $result; + } +} + +sub mstime($$$) +{ + my ($floatsec, $warntime, $badtime) = @_; + my $ms = int($floatsec * 1000); + my $str = sprintf("%d.%03ds", $ms/1000, $ms % 1000); + + if ($istty && $ms > $badtime) { + return "\e[31;1m$str\e[0m"; + } elsif ($istty && $ms > $warntime) { + return "\e[33;1m$str\e[0m"; + } else { + return "$str"; + } +} + +sub resultline($$) +{ + my ($name, $result) = @_; + return sprintf("! %-65s %s", $name, colourize($result)); +} + +my $allstart = time(); +my ($start, $stop); + +sub endsect() +{ + $stop = time(); + if ($start) { + printf " %s %s\n", mstime($stop - $start, 500, 1000), colourize("ok"); + } +} + +while (<$fh>) +{ + chomp; + s/\r//g; + + if (/^\s*Testing "(.*)" in (.*):\s*$/) + { + alarm(120); + my ($sect, $file) = ($1, $2); + + endsect(); + + printf("! %s %s: ", $file, $sect); + @log = (); + $start = $stop; + } + elsif (/^!\s*(.*?)\s+(\S+)\s*$/) + { + alarm(120); + + my ($name, $result) = ($1, $2); + my $pass = ($result eq "ok"); + + if (!$start) { + printf("\n! Startup: "); + $start = time(); + } + + push @log, resultline($name, $result); + + if (!$pass) { + $gfails++; + if (@log) { + print "\n" . join("\n", @log) . "\n"; + @log = (); + } + } else { + $gpasses++; + print "."; + } + } + else + { + push @log, $_; + } +} + +endsect(); + +my $newpid = waitpid($pid, 0); +if ($newpid != $pid) { + die("waitpid returned '$newpid', expected '$pid'\n"); +} + +my $code = $?; +my $ret = ($code >> 8); + +# return death-from-signal exits as >128. This is what bash does if you ran +# the program directly. +if ($code && !$ret) { $ret = $code | 128; } + +if ($ret && @log) { + print "\n" . join("\n", @log) . "\n"; +} + +if ($code != 0) { + print resultline("Program returned non-zero exit code ($ret)", "FAILED"); +} + +my $gtotal = $gpasses+$gfails; +printf("\nWvTest: %d test%s, %d failure%s, total time %s.\n", + $gtotal, $gtotal==1 ? "" : "s", + $gfails, $gfails==1 ? "" : "s", + mstime(time() - $allstart, 2000, 5000)); +print STDERR "\nWvTest result code: $ret\n"; +exit( $ret ? $ret : ($gfails ? 125 : 0) ); +