#!/usr/bin/python2.5 |
#!/usr/bin/python2.5 |
|
|
# Copyright (C) 2007 Google Inc. |
# Copyright (C) 2007 Google Inc. |
# |
# |
# Licensed under the Apache License, Version 2.0 (the "License"); |
# Licensed under the Apache License, Version 2.0 (the "License"); |
# you may not use this file except in compliance with the License. |
# you may not use this file except in compliance with the License. |
# You may obtain a copy of the License at |
# You may obtain a copy of the License at |
# |
# |
# http://www.apache.org/licenses/LICENSE-2.0 |
# http://www.apache.org/licenses/LICENSE-2.0 |
# |
# |
# Unless required by applicable law or agreed to in writing, software |
# Unless required by applicable law or agreed to in writing, software |
# distributed under the License is distributed on an "AS IS" BASIS, |
# distributed under the License is distributed on an "AS IS" BASIS, |
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
# See the License for the specific language governing permissions and |
# See the License for the specific language governing permissions and |
# limitations under the License. |
# limitations under the License. |
|
|
|
|
"""Output Google Transit URLs for queries near stops. |
"""Output Google Transit URLs for queries near stops. |
|
|
The output can be used to speed up manual testing. Load the output from this |
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 |
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 |
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 |
look at the time of each leg. Also check the route names and headsigns are |
formatted correctly and not redundant. |
formatted correctly and not redundant. |
""" |
""" |
|
|
from datetime import datetime |
from datetime import datetime |
from datetime import timedelta |
from datetime import timedelta |
import math |
import math |
import optparse |
import optparse |
import os.path |
import os.path |
import random |
import random |
import sys |
import sys |
import transitfeed |
import transitfeed |
import urllib |
import urllib |
import urlparse |
import urlparse |
|
|
|
|
def Distance(lat0, lng0, lat1, lng1): |
def Distance(lat0, lng0, lat1, lng1): |
""" |
""" |
Compute the geodesic distance in meters between two points on the |
Compute the geodesic distance in meters between two points on the |
surface of the Earth. The latitude and longitude angles are in |
surface of the Earth. The latitude and longitude angles are in |
degrees. |
degrees. |
|
|
Approximate geodesic distance function (Haversine Formula) assuming |
Approximate geodesic distance function (Haversine Formula) assuming |
a perfect sphere of radius 6367 km (see "What are some algorithms |
a perfect sphere of radius 6367 km (see "What are some algorithms |
for calculating the distance between 2 points?" in the GIS Faq at |
for calculating the distance between 2 points?" in the GIS Faq at |
http://www.census.gov/geo/www/faq-index.html). The approximate |
http://www.census.gov/geo/www/faq-index.html). The approximate |
radius is adequate for our needs here, but a more sophisticated |
radius is adequate for our needs here, but a more sophisticated |
geodesic function should be used if greater accuracy is required |
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 |
(see "When is it NOT okay to assume the Earth is a sphere?" in the |
same faq). |
same faq). |
""" |
""" |
deg2rad = math.pi / 180.0 |
deg2rad = math.pi / 180.0 |
lat0 = lat0 * deg2rad |
lat0 = lat0 * deg2rad |
lng0 = lng0 * deg2rad |
lng0 = lng0 * deg2rad |
lat1 = lat1 * deg2rad |
lat1 = lat1 * deg2rad |
lng1 = lng1 * deg2rad |
lng1 = lng1 * deg2rad |
dlng = lng1 - lng0 |
dlng = lng1 - lng0 |
dlat = lat1 - lat0 |
dlat = lat1 - lat0 |
a = math.sin(dlat*0.5) |
a = math.sin(dlat*0.5) |
b = math.sin(dlng*0.5) |
b = math.sin(dlng*0.5) |
a = a * a + math.cos(lat0) * math.cos(lat1) * b * b |
a = a * a + math.cos(lat0) * math.cos(lat1) * b * b |
c = 2.0 * math.atan2(math.sqrt(a), math.sqrt(1.0 - a)) |
c = 2.0 * math.atan2(math.sqrt(a), math.sqrt(1.0 - a)) |
return 6367000.0 * c |
return 6367000.0 * c |
|
|
|
|
def AddNoiseToLatLng(lat, lng): |
def AddNoiseToLatLng(lat, lng): |
"""Add up to 500m of error to each coordinate of 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_lat = Distance(lat, lng, lat + 0.1, lng) |
m_per_tenth_lng = Distance(lat, lng, lat, lng + 0.1) |
m_per_tenth_lng = Distance(lat, lng, lat, lng + 0.1) |
lat_per_100m = 1 / m_per_tenth_lat * 10 |
lat_per_100m = 1 / m_per_tenth_lat * 10 |
lng_per_100m = 1 / m_per_tenth_lng * 10 |
lng_per_100m = 1 / m_per_tenth_lng * 10 |
return (lat + (lat_per_100m * 5 * (random.random() * 2 - 1)), |
return (lat + (lat_per_100m * 5 * (random.random() * 2 - 1)), |
lng + (lng_per_100m * 5 * (random.random() * 2 - 1))) |
lng + (lng_per_100m * 5 * (random.random() * 2 - 1))) |
|
|
|
|
def GetRandomLocationsNearStops(schedule): |
def GetRandomLocationsNearStops(schedule): |
"""Return a list of (lat, lng) tuples.""" |
"""Return a list of (lat, lng) tuples.""" |
locations = [] |
locations = [] |
for s in schedule.GetStopList(): |
for s in schedule.GetStopList(): |
locations.append(AddNoiseToLatLng(s.stop_lat, s.stop_lon)) |
locations.append(AddNoiseToLatLng(s.stop_lat, s.stop_lon)) |
return locations |
return locations |
|
|
|
|
def GetRandomDatetime(): |
def GetRandomDatetime(): |
"""Return a datetime in the next week.""" |
"""Return a datetime in the next week.""" |
seconds_offset = random.randint(0, 60 * 60 * 24 * 7) |
seconds_offset = random.randint(0, 60 * 60 * 24 * 7) |
dt = datetime.today() + timedelta(seconds=seconds_offset) |
dt = datetime.today() + timedelta(seconds=seconds_offset) |
return dt.replace(second=0, microsecond=0) |
return dt.replace(second=0, microsecond=0) |
|
|
|
|
def FormatLatLng(lat_lng): |
def FormatLatLng(lat_lng): |
"""Format a (lat, lng) tuple into a string for maps.google.com.""" |
"""Format a (lat, lng) tuple into a string for maps.google.com.""" |
return "%0.6f,%0.6f" % lat_lng |
return "%0.6f,%0.6f" % lat_lng |
|
|
|
|
def LatLngsToGoogleUrl(source, destination, dt): |
def LatLngsToGoogleUrl(source, destination, dt): |
"""Return a URL for routing between two (lat, lng) at a datetime.""" |
"""Return a URL for routing between two (lat, lng) at a datetime.""" |
params = {"saddr": FormatLatLng(source), |
params = {"saddr": FormatLatLng(source), |
"daddr": FormatLatLng(destination), |
"daddr": FormatLatLng(destination), |
"time": dt.strftime("%I:%M%p"), |
"time": dt.strftime("%I:%M%p"), |
"date": dt.strftime("%Y-%m-%d"), |
"date": dt.strftime("%Y-%m-%d"), |
"dirflg": "r", |
"dirflg": "r", |
"ie": "UTF8", |
"ie": "UTF8", |
"oe": "UTF8"} |
"oe": "UTF8"} |
url = urlparse.urlunsplit(("http", "maps.google.com", "/maps", |
url = urlparse.urlunsplit(("http", "maps.google.com", "/maps", |
urllib.urlencode(params), "")) |
urllib.urlencode(params), "")) |
return url |
return url |
|
|
|
|
def LatLngsToGoogleLink(source, destination): |
def LatLngsToGoogleLink(source, destination): |
"""Return a string "<a ..." for a trip at a random time.""" |
"""Return a string "<a ..." for a trip at a random time.""" |
dt = GetRandomDatetime() |
dt = GetRandomDatetime() |
return "<a href='%s'>from:%s to:%s on %s</a>" % ( |
return "<a href='%s'>from:%s to:%s on %s</a>" % ( |
LatLngsToGoogleUrl(source, destination, dt), |
LatLngsToGoogleUrl(source, destination, dt), |
FormatLatLng(source), FormatLatLng(destination), |
FormatLatLng(source), FormatLatLng(destination), |
dt.ctime()) |
dt.ctime()) |
|
|
|
|
def WriteOutput(title, locations, limit, f): |
def WriteOutput(title, locations, limit, f): |
"""Write html to f for up to limit trips between locations. |
"""Write html to f for up to limit trips between locations. |
|
|
Args: |
Args: |
title: String used in html title |
title: String used in html title |
locations: list of (lat, lng) tuples |
locations: list of (lat, lng) tuples |
limit: maximum number of queries in the html |
limit: maximum number of queries in the html |
f: a file object |
f: a file object |
""" |
""" |
output_prefix = """ |
output_prefix = """ |
<html> |
<html> |
<head> |
<head> |
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8"> |
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8"> |
<title>%(title)s</title> |
<title>%(title)s</title> |
</head> |
</head> |
<body> |
<body> |
Random queries for %(title)s<p> |
Random queries for %(title)s<p> |
This list of random queries should speed up important manual testing. Here are |
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. |
some things to check when looking at the results of a query. |
<ul> |
<ul> |
<li> Check the agency attribution under the trip results: |
<li> Check the agency attribution under the trip results: |
<ul> |
<ul> |
<li> has correct name and spelling of the agency |
<li> has correct name and spelling of the agency |
<li> opens a page with general information about the service |
<li> opens a page with general information about the service |
</ul> |
</ul> |
<li> For each alternate trip check that each of these is reasonable: |
<li> For each alternate trip check that each of these is reasonable: |
<ul> |
<ul> |
<li> the total time of the trip |
<li> the total time of the trip |
<li> the time for each leg. Bad data frequently results in a leg going a long |
<li> the time for each leg. Bad data frequently results in a leg going a long |
way in a few minutes. |
way in a few minutes. |
<li> the icons and mode names (Tram, Bus, etc) are correct for each leg |
<li> the icons and mode names (Tram, Bus, etc) are correct for each leg |
<li> the route names and headsigns are correctly formatted and not |
<li> the route names and headsigns are correctly formatted and not |
redundant. |
redundant. |
For a good example see <a |
For a good example see <a |
href="http://code.google.com/transit/spec/transit_feed_specification.html#transitScreenshots">the |
href="http://code.google.com/transit/spec/transit_feed_specification.html#transitScreenshots">the |
screenshots in the Google Transit Feed Specification</a>. |
screenshots in the Google Transit Feed Specification</a>. |
<li> the shape line on the map looks correct. Make sure the polyline does |
<li> 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 |
not zig-zag, loop, skip stops or jump far away unless the trip does the |
same thing. |
same thing. |
<li> the route is active on the day the trip planner returns |
<li> the route is active on the day the trip planner returns |
</ul> |
</ul> |
</ul> |
</ul> |
If you find a problem be sure to save the URL. This file is generated randomly. |
If you find a problem be sure to save the URL. This file is generated randomly. |
<ol> |
<ol> |
""" % locals() |
""" % locals() |
|
|
output_suffix = """ |
output_suffix = """ |
</ol> |
</ol> |
</body> |
</body> |
</html> |
</html> |
""" % locals() |
""" % locals() |
|
|
f.write(transitfeed.EncodeUnicode(output_prefix)) |
f.write(transitfeed.EncodeUnicode(output_prefix)) |
for source, destination in zip(locations[0:limit], locations[1:limit + 1]): |
for source, destination in zip(locations[0:limit], locations[1:limit + 1]): |
f.write(transitfeed.EncodeUnicode("<li>%s\n" % |
f.write(transitfeed.EncodeUnicode("<li>%s\n" % |
LatLngsToGoogleLink(source, destination))) |
LatLngsToGoogleLink(source, destination))) |
f.write(transitfeed.EncodeUnicode(output_suffix)) |
f.write(transitfeed.EncodeUnicode(output_suffix)) |
|
|
|
|
def ParentAndBaseName(path): |
def ParentAndBaseName(path): |
"""Given a path return only the parent name and file name as a string.""" |
"""Given a path return only the parent name and file name as a string.""" |
dirname, basename = os.path.split(path) |
dirname, basename = os.path.split(path) |
dirname = dirname.rstrip(os.path.sep) |
dirname = dirname.rstrip(os.path.sep) |
if os.path.altsep: |
if os.path.altsep: |
dirname = dirname.rstrip(os.path.altsep) |
dirname = dirname.rstrip(os.path.altsep) |
_, parentname = os.path.split(dirname) |
_, parentname = os.path.split(dirname) |
return os.path.join(parentname, basename) |
return os.path.join(parentname, basename) |
|
|
|
|
def main(): |
def main(): |
|
usage = \ |
|
"""%prog [options] <input GTFS.zip> |
|
|
|
Create an HTML page of random URLs for the Google Maps transit trip |
|
planner. The queries go between places near stops listed in a <input GTFS.zip>. |
|
By default 50 random URLs are saved to google_random_queries.html. |
|
|
|
For more information see |
|
http://code.google.com/p/googletransitdatafeed/wiki/GoogleRandomQueries |
|
""" |
|
|
parser = optparse.OptionParser( |
parser = optparse.OptionParser( |
usage="usage: %prog [options] feed_filename output_filename", |
usage=usage, |
version="%prog "+transitfeed.__version__) |
version="%prog "+transitfeed.__version__) |
parser.add_option("-l", "--limit", dest="limit", type="int", |
parser.add_option("-l", "--limit", dest="limit", type="int", |
help="Maximum number of URLs to generate") |
help="Maximum number of URLs to generate") |
parser.add_option('-o', '--output', dest='output', metavar='FILE', |
parser.add_option("-o", "--output", dest="output", metavar="HTML_OUTPUT_PATH", |
help='write html output to FILE') |
help="write HTML output to HTML_OUTPUT_PATH") |
parser.set_defaults(output="google_random_queries.html", limit=50) |
parser.set_defaults(output="google_random_queries.html", limit=50) |
(options, args) = parser.parse_args() |
(options, args) = parser.parse_args() |
if len(args) != 1: |
if len(args) != 1: |
print >>sys.stderr, parser.format_help() |
print >>sys.stderr, parser.format_help() |
print >>sys.stderr, "\n\nYou must provide the path of a single feed\n\n" |
print >>sys.stderr, "\n\nYou must provide the path of a single feed\n\n" |
sys.exit(2) |
sys.exit(2) |
feed_path = args[0] |
feed_path = args[0] |
|
|
# ProblemReporter prints problems on console. |
# ProblemReporter prints problems on console. |
loader = transitfeed.Loader(feed_path, problems=transitfeed.ProblemReporter(), |
loader = transitfeed.Loader(feed_path, problems=transitfeed.ProblemReporter(), |
load_stop_times=False) |
load_stop_times=False) |
schedule = loader.Load() |
schedule = loader.Load() |
locations = GetRandomLocationsNearStops(schedule) |
locations = GetRandomLocationsNearStops(schedule) |
random.shuffle(locations) |
random.shuffle(locations) |
agencies = ", ".join([a.agency_name for a in schedule.GetAgencyList()]) |
agencies = ", ".join([a.agency_name for a in schedule.GetAgencyList()]) |
title = "%s (%s)" % (agencies, ParentAndBaseName(feed_path)) |
title = "%s (%s)" % (agencies, ParentAndBaseName(feed_path)) |
|
|
WriteOutput(title, |
WriteOutput(title, |
locations, |
locations, |
options.limit, |
options.limit, |
open(options.output, "w")) |
open(options.output, "w")) |
print ("Load %s in your web browser. It contains more instructions." % |
print ("Load %s in your web browser. It contains more instructions." % |
options.output) |
options.output) |
|
|
|
|
if __name__ == "__main__": |
if __name__ == "__main__": |
main() |
main() |
|
|