Skip to content

Carpool Planner Version 1.0

Okay, I just wrote a neat Python script which uses the Google Maps API to find the optimal setup for a group of people carpooling to a given location. I'll be doing some optimizations and rewriting the entire thing in Javascript soon (to add fancy map images), but this version works.

It is intended for Python 2.6, and you'll need to pickle an empty dictionary to a file named "cache" (no extension) and place it in the same directory as the script for a successful run, along with geopy (which is meant for Python 2.5, but is easy to modify for compatibility).

?View Code PYTHON
from geopy.distance import distance
from itertools import combinations
from geopy import geocoders
from time import sleep
import pickle
import urllib
import json
 
with open("cache", "rb") as f:
    cache = pickle.load(f)
 
def dist(start, end):
    return distance(start[1], end[1]).meters
 
def duration(start, end):
    if not (start[0], end[0]) in cache:
        url = 'http://maps.google.com/maps/nav?key=ABQIAAAAzr2EBOXUKnm_jVnk0OJI7xSosDVG8KKPE1-m51RBrvYughuyMxQ-i1QfUnH94QxWIa6N4U6MouMmBA&q='
        url += 'from:' + start[0].replace(' ', '+') + '+to:' + end[0].replace(' ', '+')
 
        data = json.loads(urllib.urlopen(url).read())
 
        cache[(start[0], end[0])] = data['Directions']['Duration']['seconds']
        with open("cache", "wb") as f:
            pickle.dump(cache, f)
 
        sleep(5)
    return cache[(start[0], end[0])]
 
g = geocoders.Google()
#us = geocoders.GeocoderDotUS()
 
def geocode_wait(loc, geocoder=g):
    if not loc in cache:
        r = geocoder.geocode(loc)
 
        cache[loc] = r
        with open("cache", "wb") as f:
            pickle.dump(cache, f)
 
        sleep(2)
    return cache[loc]
 
def tri(n):
    return (n**2 + n) / 2
 
def selections(mset, k):
    res = set()
    for c in combinations(mset, k):
        res.add(tuple(sorted(c)))
    return res
 
read = str(raw_input("Read data from file? (y/n): "))
print ""
while read not in ['y', 'n']:
    print "I did not understand that. Please respond with 'y' or 'n'."
    read = str(raw_input("Read data from file? (y/n): "))
    print ""
 
if read == 'n':
    print "Please enter your destination."
    dest = str(raw_input("Destination: "))
    print ""
 
    print "Please enter the number of people who need rides."
    need_count = int(raw_input("Rides needed: "))
    print ""
 
    print "Please enter the number of people who can offer rides."
    avail_count = int(raw_input("Rides available: "))
    print ""
 
    need = []
    print "Please enter the locations of the people who need rides."
    for i in xrange(need_count):
        need.append(str(raw_input("Passenger " + str(i + 1) + ": ")))
    print ""
 
    del need_count
 
    avail = []
    slots = []
    print "Please enter the locations of the people who can offer rides and the number of slots available in each vehicle."
    for i in xrange(avail_count):
        avail.append(str(raw_input("Driver " + str(i + 1) + ": ")))
        slots.append(int(raw_input("Slots: ")))
    print ""
 
    del avail_count
else:
    name = str(raw_input("Please enter file name: "))
    print ""
 
    dest = ""
    need = []
    avail = []
    slots = []
 
    print "Reading data from file..."
    with open(name) as f:
        for line in f:
            ldata = line.split(";")
            if ldata[0] == "destination":
                dest = ldata[1]
            elif ldata[0] == "passenger":
                need.append(ldata[1])
            elif ldata[0] == "driver":
                avail.append(ldata[1])
                slots.append(int(ldata[2]))
    print "Done reading data from file.\n"
 
if sum(slots) < len(need):
    exit("Not enough slots available for all passengers. Had " + str(sum(slots)) + " slots, needed a total of " + str(len(need)) + ".")
 
print "Geocoding locations..."
#Destination
dest = g.geocode(dest)
 
#Passengers
need = map(geocode_wait, need)
 
#Drivers
avail = map(geocode_wait, avail)
print "Done Geocoding locations.\n"
 
print "Destination is", dest[0]
print ""
 
for p in xrange(len(need)):
    print "Passenger", p + 1, "is at", need[p][0]
print ""
 
for d in xrange(len(avail)):
    if slots[d] == 1:
        print "Driver", d + 1, "is at", avail[d][0], "and can take", slots[d], "passenger"
    else:
        print "Driver", d + 1, "is at", avail[d][0], "and can take", slots[d], "passengers"
print ""
 
mset = []
for n in xrange(len(slots)):
    mset += [n] * slots[n]
choices = selections(mset, len(need))
del mset
 
def routes(plan, nodes, start=None):
    if not start is None:
        origin = start
    else:
        origin = avail[plan[0]]
    paths = dict()
    for i in xrange(len(nodes)):
        cnode = nodes[i]
        if plan[1:] != tuple():
            if plan[1] == plan[0]:
                ops = routes(plan[1:], nodes[:i] + nodes[i+1:], start=cnode)
            else:
                ops = routes(plan[1:], nodes[:i] + nodes[i+1:])
            for op in ops:
                paths[(cnode, ) + op] = duration(origin, cnode) + ops[op]
        else:
            paths[(cnode, )] = duration(origin, cnode)
    return paths
 
best = None
 
for layout in choices:
    pool = need
    options = routes(layout, pool)
 
    subbest = (min(options), options[min(options)])
    if best is None:
        best = (layout,) + subbest
    if subbest[1] < best[2]:
        best = (layout, ) + subbest
 
last = -1
for g in xrange(len(best[0])):
    if best[0][g] == last:
        print "...then continue on to pick up", best[1][g][0]
    else:
        print "Driver", best[0][g] + 1, "should pick up", best[1][g][0]
    last = best[0][g]
print "This setup should yield a total driving time of", best[2], "seconds"

6 Comments

  1. Ashok Pershad

    Hi!!
    Please tell me something more about this Carpooling Software.

    Posted on 26-Jun-09 at 5:52:47 | Permalink
  2. Hey! What would you like to know about it?

    Posted on 30-Jun-09 at 15:26:37 | Permalink
  3. Hello,
    I found your blog and your script looking for geopy on python 2.6 and yes, it seems I did find it. Thank you for telling me "it's easy to modify for compatibility". Would you please provide some details or a patched geopy?

    Posted on 01-Aug-09 at 21:55:48 | Permalink
  4. Sure! I've uploaded my patched version to http://integerzero.net/geopy.7z

    I don't remember my modifications, and don't feel like downloading the original to do a diff, but this should work.

    Posted on 02-Aug-09 at 22:29:43 | Permalink
  5. jameshancock

    Meyermagic, would like to contact you re script, can you email me spcfnz@xtra.co.nz attn james

    Posted on 28-Aug-09 at 17:37:48 | Permalink
  6. Jelle Fresen

    Hi Meyer,

    Thanks for this tool, it's about the only such tool available in the first x pages of google, where x denotes the number of pages I can scan through before losing my interest ;)
    I've got some remarks, questions and/or improvements.

    First of all I'd like to point out that in the mean time, geopy has become available for 2.6 as well.

    Second, adding the following lines before opening the cache file removes the need to manually initialize a cache:

    ?View Code PYTHON
    #add 'import os.path' at the list of import statements
    if not os.path.exists("cache"):
        cache = {}
    else:

    Third, you forgot to add the route of each car from its final pickup location to the destination. Within the routes function, change the lines

    ?View Code PYTHON
                for op in ops:
                    paths[(cnode, ) + op] = duration(origin, cnode) + ops[op]
            else:
                paths[(cnode, )] = duration(origin, cnode)

    to

    ?View Code PYTHON
                for op in ops:
                    newpath = (cnode, ) + op
                    paths[newpath] = duration(origin, cnode) + ops[op]
                    if plan[1] != plan[0]:
                        paths[newpath] += duration(cnode, dest)
            else:
                paths[(cnode, )] = duration(origin, cnode) + duration(cnode, dest)

    Fourth, I think it can be optimized greatly in space requirements if you don't return all possible routes in the route function, but only the most optimal route. I haven't tested this though, as I'm not sure that that would actually be correct. I think it is, because all options from a returned set op options are prepended with the same nodes.

    Fifth, I changed some more code to make it more human readable, adding names of the passengers and drivers and outputting those instead of numbers and addresses. Interested in the code?

    Regards,
    Jelle

    Posted on 15-Jul-10 at 7:31:28 | Permalink

Post a Comment

Your email is never published nor shared. Required fields are marked *
*
*