Skip to content

Instantly share code, notes, and snippets.

@mattwigway
Created December 31, 2011 04:01
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save mattwigway/1542816 to your computer and use it in GitHub Desktop.
Save mattwigway/1542816 to your computer and use it in GitHub Desktop.
OTP to Everywhere
#!/usr/bin/python
# otpeverywhere - make otp maps like
# http://www.flickr.com/photos/walkingsf/6536396399/
# Copyright 2011 Matt Conway
# 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.
# Use the new, Python 2.7/3 argument parser
import argparse
from urllib2 import Request, urlopen
from psycopg2 import connect
from time import time
import json
parser = argparse.ArgumentParser(description="OTP Everywhere Maps")
parser.add_argument('-o', '--out-table', dest='outtable', type=str,
help='the table to store data in', action='store',
metavar='TABLE', default='otpeverywhere')
parser.add_argument('-i', '--in-table', dest='intable', type=str, action='store',
help='the table to retrieve destinations from',
metavar='TABLE', default='intersections')
parser.add_argument('-d', '--dsn', dest='dsn', type=str,
help='database dsn, as documented at http://initd.org/psycopg/docs/module.html#psycopg2.connect',
metavar='DSN', default='dbname=otp')
parser.add_argument('--otp-url', '-u', action='store', dest='otpurl',
help='your OTP base URL, default: http://localhost:8080/opentripplanner-api-webapp',
default='http://localhost:8080/opentripplanner-api-webapp')
parser.add_argument('--start', '-s', action='store',
help='the start lat,lon (WGS84)',
default='37.7847,-122.40766') # Market and Powell, downtown San Francisco, CA
parser.add_argument('--date', '-a', action='store',
help='date, format mm/dd/yyyy',
default='12/29/2011')
parser.add_argument('--time', '-t', action='store',
help='start time',
default='9:00 am')
parser.add_argument('--mode', '-m', action='store',
help='mode, e.g. TRANSIT,BICYCLE',
default='TRANSIT,WALK')
opts = parser.parse_args()
# Need two connections b/c named cursor is invalidated on commit
conn = connect(opts.dsn)
inconn = connect(opts.dsn)
incursor = inconn.cursor(name='input')
outcursor = conn.cursor()
# From http://seewah.blogspot.com/2009/11/gpolyline-decoding-in-python.html
def decode_line(encoded):
"""Decodes a polyline that was encoded using the Google Maps method.
See http://code.google.com/apis/maps/documentation/polylinealgorithm.html
This is a straightforward Python port of Mark McClure's JavaScript polyline decoder
(http://facstaff.unca.edu/mcmcclur/GoogleMaps/EncodePolyline/decode.js)
and Peter Chng's PHP polyline decode
(http://unitstep.net/blog/2008/08/02/decoding-google-maps-encoded-polylines-using-php/)
"""
encoded_len = len(encoded)
index = 0
array = []
lat = 0
lng = 0
while index < encoded_len:
b = 0
shift = 0
result = 0
while True:
b = ord(encoded[index]) - 63
index = index + 1
result |= (b & 0x1f) << shift
shift += 5
if b < 0x20:
break
dlat = ~(result >> 1) if result & 1 else result >> 1
lat += dlat
shift = 0
result = 0
while True:
b = ord(encoded[index]) - 63
index = index + 1
result |= (b & 0x1f) << shift
shift += 5
if b < 0x20:
break
dlng = ~(result >> 1) if result & 1 else result >> 1
lng += dlng
array.append((lat * 1e-5, lng * 1e-5))
return array
# I know, SQL injection, but you can't have psycopg escape a table name properly
# I think the source is safe, unless you install the script suid postgres or something
incursor.execute('SELECT X(transform(the_geom, 4326)) as lon, Y(transform(the_geom, 4326)) as lat FROM ' + opts.intable)
start_time = time()
i = 0
for geom in incursor:
# build the url
url = '%s/ws/plan' % opts.otpurl
url += '?fromPlace=%s' % opts.start
url += '&toPlace=%s,%s' % (geom[1], geom[0])
url += '&date=%s' % opts.date
url += '&time=%s' % opts.time.replace(' ', '%20')
url += '&arriveBy=false'
url += '&mode=%s' % opts.mode
url += '&numItineraries=1'
# Request JSON format
headers=dict(Accept='application/json')
# the user doesn't review the request, so it's technically unverifiable
r = Request(url, headers=headers, unverifiable=True)
# Parse the response
d = json.loads(urlopen(r).read())
# Build an array of lat,lon
points = []
# Were there errors? If so, d['plan'] == None
if d['plan']:
# parse the geometries. also add the times.
for leg in d['plan']['itineraries'][0]['legs']:
legPoints = decode_line(leg['legGeometry']['points'])
assert len(legPoints) == leg['legGeometry']['length']
points += legPoints
wkt = 'SRID=4326;LINESTRING(%s)' % ', '.join(['%s %s' % (point[1], point[0]) for point in points])
# ms -> s
totalTime = (d['plan']['itineraries'][0]['endTime'] - d['plan']['itineraries'][0]['startTime'])/1000
outcursor.execute('INSERT INTO ' + opts.outtable + ' (the_geom, time) VALUES (ST_GeomFromEWKT(%s), %s)', (wkt, totalTime))
else:
print 'Location %s, %s is unreachable, skipping' % (geom[1], geom[0])
print d['error']['msg']
i += 1
if i % 100 == 0:
conn.commit()
newtime = time()
print '100 requests, %s seconds, average %s secs/request' % (newtime - start_time, (newtime - start_time)/100)
start_time = newtime
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment