pyproj is a Python wrapper around PROJ.4. Here's a quick walkthrough.

Initialize a geodetic converter:

``````>>> from pyproj import Geod
>>> g = Geod(ellps='clrk66')
``````

where `ellps='clrk66'` selects Clarke's 1866 reference ellipsoid. `help(Geod.__new__)` gives a list of possible ellipsoids.

Calculate the distance between two points, as well as the local heading, try

``````>>> lat1,lng1 = (40.7143528, -74.0059731)  # New York, NY
>>> lat2,lng2 = (49.261226, -123.1139268)   # Vancouver, Canada
>>> az12,az21,dist = g.inv(lng1,lat1,lng2,lat2)
>>> az12,az21,dist
(-59.10918706123901, 84.99453463527395, 3914198.2912370963)
``````

which gives forward and back azimuths as well as the geodesic distance in meters. Not that longitude comes before latitude in the these pyproj argument lists.

Calculate the terminus of a geodesic from an initial point, azimuth, and distance with:

``````>>> lng3,lat3,az3 = g.fwd(lng1,lat1,az12, dist)
>>> lat3,lng3,az3
(49.26122600306212, -123.11392684861474, 84.99453467574762)
``````

Plan your trip with:

``````>>> pts = g.npts(lng1,lat1,lng2,lat2,npts=5)
>>> pts.insert(0, (lng1, lat1))
>>> pts.append((lng2, lat2))
>>> import numpy
>>> npts = numpy.array(pts)
>>> npts
array([[ -74.0059731 ,   40.7143528 ],
[ -80.93566289,   43.52686057],
[ -88.48167748,   45.87969433],
[ -96.61187851,   47.6930911 ],
[-105.22271807,   48.89347605],
[-114.13503215,   49.42510006],
[-123.1139268 ,   49.261226  ]])
``````

To plot the above New York to Vancouver route on a flat map, we need a `Proj` instance:

``````>>> from pyproj import Proj
>>> awips221 = Proj(proj='lcc', R=6371200, lat_1=50, lat_2=50,
...     lon_0=-107, ellps='clrk66')
>>> awips218 = Proj(proj='lcc', R=6371200, lat_1=25, lat_2=25,
...     lon_0=-95, ellps='clrk66')  #x_0=-llcrnrx,y_0=-llcrnry)

#llcrnrlon,llcrnrlat are lon and lat (in degrees) of lower
#    left hand corner of projection region.
``````

where `proj='lcc'` selects the Lambert conformal conic projection for the x/y points, and `ellps='clrk66'` selects the reference ellipsoid for the lat/lng coordinates. The other coordinates are LCC parameters that select the AWIPS 221 and AWIPS 226 projections respectively (`lat_1` corresponds to `Latin1`, `lat_2` corresponds to `Latin2`, and `lon_0` corresponds to `Lov`; see this description of the two-standard-parallel LCC and its PROJ.4 parameters).

Convert our lat/lng pairs into grid points:

``````>>> awips221(lng1, lat1)
(2725283.842678774, 5823260.730665273)
>>> x221,y221 = awips221(npts[:,0], npts[:,1])
>>> # xy221 = numpy.concatenate((a1, a2, ...), axis=0)  # numpy-2.0
>>> xy221 = numpy.ndarray(shape=npts.shape, dtype=npts.dtype)
>>> xy221[:,0] = x221
>>> xy221[:,1] = y221
>>> xy221
array([[ 2725283.84267877,  5823260.73066527],
[ 2071820.3526011 ,  5892518.49630526],
[ 1422529.71760395,  5967565.49899035],
[  775650.03731228,  6046475.43928965],
[  129946.46495299,  6127609.80532071],
[ -515306.57275941,  6209785.69230076],
[-1160447.80254759,  6292455.41884832]])
``````

Finally, you can convert points from one projection to another.

``````>>> from pyproj import transform
>>> x218,y218 = transform(awips221, awips218, x221, y221)
>>> xy218 = numpy.ndarray(shape=npts.shape, dtype=npts.dtype)
>>> xy218[:,0] = x218
>>> xy218[:,1] = y218
>>> xy218
array([[ 1834251.59591561,  4780900.70184736],
[ 1197541.13209718,  5028862.9881648 ],
[  542391.04388716,  5258740.71523961],
[ -131577.34942316,  5464828.45934687],
[ -822685.42269932,  5641393.59760613],
[-1527077.85176048,  5783597.16169582],
[-2239159.34620498,  5888495.91009021]])
``````

Another useful coordinate system is the Universal Transverse Mercator projection which slices the world into zones.

``````>>> p = Proj(proj='utm', zone=10, ellps='clrk66')
``````

Putting everything together, here's a route map based on digital lat/lng pairs stored in a text file:

``````>>> from numpy import array
>>> from pylab import plot, show
>>> from pyproj import Geod, Proj
>>> latlng = array([[float(x) for x in ln.split()]
...                for ln in open('coords', 'r')
...                if not ln.startswith('#')])
>>> g = Geod(ellps='WGS84')
>>> az12s,az21s,dists = g.inv(latlng[:-1,1], latlng[:-1,0],
...                           latlng[1:,1], latlng[1:,0])
>>> print('total distance: %g m' % dists.sum())
total distance: 2078.93 m
>>> mlng = latlng[:,1].mean()
>>> zone = int(round((mlng + 180) / 6.))
>>> p = Proj(proj='utm', zone=zone, ellps='WGS84')
>>> xs,ys = p(latlng[:,1], latlng[:,0])
>>> lines = plot(xs, ys, 'r.-')
>>> show()
``````

I've written up a simple script using this approach: maproute.py. I've also written up a simple script to draw a map with labeled points: maplabel.py.

Note that you can easily get lat/lng pairs using geopy (ebuild in my Gentoo overlay):

``````>>> import geopy
>>> g = geopy.geocoders.Google()
>>> place1,(lat1,lng1) = g.geocode("New York, NY")
>>> place2,(lat2,lng2) = g.geocode("Vancouver, Canada")
>>> place1,(lat1,lng1)
(u'New York, NY, USA', (40.7143528, -74.0059731))
>>> place2,(lat2,lng2)
(u'Vancouver, BC, Canada', (49.261226, -123.1139268))
``````

If you're looking for a more compact C++ package for geographic conversions, GeographicLib looks promising.