```"""
By Kirby Urner, 4D Solutions

Revised Jan 17:  'N' & 'W' swapped in cities dictionary (good eye Valerie Harvey)

A rich data structure:

lists of lat/long tuples in a city indexed dictionary.  Distance function
provided.  Could be used to introduce data structures in Python.

Although spherical trig gets used, lessons needn't focus on that.

Typical exercise:  write getkms(citya, cityb) to return distance in
kilometers.

Typical usage:

>>> from gis import cities, getmiles
>>> pdx = cities['Portland, Ore.']
>>> miami = cities['Miami, Fla.']
>>> sea = cities['Seattle, Wash.']
>>> getmiles(pdx, sea)
146.20520973103621
>>> getmiles(pdx, miami)
2707.8603168909253

"""
from math import acos, cos, sin, radians
from xml.sax import make_parser
from xml.sax.handler import ContentHandler

cities = {
'Albany, N.Y.': [(42, 40, 'N'), (73, 45, 'W')],
'Albuquerque, N.M.': [(35, 5, 'N'), (106, 39, 'W')],
'Amarillo, Tex.': [(35, 11, 'N'), (101, 50, 'W')],
'Anchorage, Alaska': [(61, 13, 'N'), (149, 54, 'W')],
'Atlanta, Ga.': [(33, 45, 'N'), (84, 23, 'W')],
'Austin, Tex.': [(30, 16, 'N'), (97, 44, 'W')],
'Baker, Ore.': [(44, 47, 'N'), (117, 50, 'W')],
'Baltimore, Md.': [(39, 18, 'N'), (76, 38, 'W')],
'Bangor, Maine': [(44, 48, 'N'), (68, 47, 'W')],
'Birmingham, Ala.': [(33, 30, 'N'), (86, 50, 'W')],
'Bismarck, N.D.': [(46, 48, 'N'), (100, 47, 'W')],
'Boise, Idaho': [(43, 36, 'N'), (116, 13, 'W')],
'Boston, Mass.': [(42, 21, 'N'), (71, 5, 'W')],
'Buffalo, N.Y.': [(42, 55, 'N'), (78, 50, 'W')],
'Calgary, Alba., Can.': [(51, 1, 'N'), (114, 1, 'W')],
'Carlsbad, N.M.': [(32, 26, 'N'), (104, 15, 'W')],
'Charleston, S.C.': [(32, 47, 'N'), (79, 56, 'W')],
'Charleston, W. Va.': [(38, 21, 'N'), (81, 38, 'W')],
'Charlotte, N.C.': [(35, 14, 'N'), (80, 50, 'W')],
'Cheyenne, Wyo.': [(41, 9, 'N'), (104, 52, 'W')],
'Chicago, Ill.': [(41, 50, 'N'), (87, 37, 'W')],
'Cincinnati, Ohio': [(39, 8, 'N'), (84, 30, 'W')],
'Cleveland, Ohio': [(41, 28, 'N'), (81, 37, 'W')],
'Columbia, S.C.': [(34, 0, 'N'), (81, 2, 'W')],
'Columbus, Ohio': [(40, 0, 'N'), (83, 1, 'W')],
'Dallas, Tex.': [(32, 46, 'N'), (96, 46, 'W')],
'Denver, Colo.': [(39, 45, 'N'), (105, 0, 'W')],
'Des Moines, Iowa': [(41, 35, 'N'), (93, 37, 'W')],
'Detroit, Mich.': [(42, 20, 'N'), (83, 3, 'W')],
'Dubuque, Iowa': [(42, 31, 'N'), (90, 40, 'W')],
'Duluth, Minn.': [(46, 49, 'N'), (92, 5, 'W')],
'Eastport, Maine': [(44, 54, 'N'), (67, 0, 'W')],
'Edmonton, Alb., Can.': [(53, 34, 'N'), (113, 28, 'W')],
'El Centro, Calif.': [(32, 38, 'N'), (115, 33, 'W')],
'El Paso, Tex.': [(31, 46, 'N'), (106, 29, 'W')],
'Eugene, Ore.': [(44, 3, 'N'), (123, 5, 'W')],
'Fargo, N.D.': [(46, 52, 'N'), (96, 48, 'W')],
'Flagstaff, Ariz.': [(35, 13, 'N'), (111, 41, 'W')],
'Fort Worth, Tex.': [(32, 43, 'N'), (97, 19, 'W')],
'Fresno, Calif.': [(36, 44, 'N'), (119, 48, 'W')],
'Grand Junction, Colo.': [(39, 5, 'N'), (108, 33, 'W')],
'Grand Rapids, Mich.': [(42, 58, 'N'), (85, 40, 'W')],
'Havre, Mont.': [(48, 33, 'N'), (109, 43, 'W')],
'Helena, Mont.': [(46, 35, 'N'), (112, 2, 'W')],
'Honolulu, Hawaii': [(21, 18, 'N'), (157, 50, 'W')],
'Hot Springs, Ark.': [(34, 31, 'N'), (93, 3, 'W')],
'Houston, Tex.': [(29, 45, 'N'), (95, 21, 'W')],
'Idaho Falls, Idaho': [(43, 30, 'N'), (112, 1, 'W')],
'Indianapolis, Ind.': [(39, 46, 'N'), (86, 10, 'W')],
'Jackson, Miss.': [(32, 20, 'N'), (90, 12, 'W')],
'Jacksonville, Fla.': [(30, 22, 'N'), (81, 40, 'W')],
'Juneau, Alaska': [(58, 18, 'N'), (134, 24, 'W')],
'Kansas City, Mo.': [(39, 6, 'N'), (94, 35, 'W')],
'Key West, Fla.': [(24, 33, 'N'), (81, 48, 'W')],
'Kingston, Ont., Can.': [(44, 15, 'N'), (76, 30, 'W')],
'Klamath Falls, Ore.': [(42, 10, 'N'), (121, 44, 'W')],
'Knoxville, Tenn.': [(35, 57, 'N'), (83, 56, 'W')],
'Las Vegas, Nev.': [(36, 10, 'N'), (115, 12, 'W')],
'Lewiston, Idaho': [(46, 24, 'N'), (117, 2, 'W')],
'Lincoln, Neb.': [(40, 50, 'N'), (96, 40, 'W')],
'London, Ont., Can.': [(43, 2, 'N'), (81, 34, 'W')],
'Long Beach, Calif.': [(33, 46, 'N'), (118, 11, 'W')],
'Los Angeles, Calif.': [(34, 3, 'N'), (118, 15, 'W')],
'Louisville, Ky.': [(38, 15, 'N'), (85, 46, 'W')],
'Manchester, N.H.': [(43, 0, 'N'), (71, 30, 'W')],
'Memphis, Tenn.': [(35, 9, 'N'), (90, 3, 'W')],
'Miami, Fla.': [(25, 46, 'N'), (80, 12, 'W')],
'Milwaukee, Wis.': [(43, 2, 'N'), (87, 55, 'W')],
'Minneapolis, Minn.': [(44, 59, 'N'), (93, 14, 'W')],
'Mobile, Ala.': [(30, 42, 'N'), (88, 3, 'W')],
'Montgomery, Ala.': [(32, 21, 'N'), (86, 18, 'W')],
'Montpelier, Vt.': [(44, 15, 'N'), (72, 32, 'W')],
'Montreal, Que., Can.': [(45, 30, 'N'), (73, 35, 'W')],
'Moose Jaw, Sask., Can.': [(50, 37, 'N'), (105, 31, 'W')],
'Nashville, Tenn.': [(36, 10, 'N'), (86, 47, 'W')],
'Nelson, B.C., Can.': [(49, 30, 'N'), (117, 17, 'W')],
'New Haven, Conn.': [(41, 19, 'N'), (72, 55, 'W')],
'New Orleans, La.': [(29, 57, 'N'), (90, 4, 'W')],
'New York, N.Y.': [(40, 47, 'N'), (73, 58, 'W')],
'Newark, N.J.': [(40, 44, 'N'), (74, 10, 'W')],
'Nome, Alaska': [(64, 25, 'N'), (165, 30, 'W')],
'Oakland, Calif.': [(37, 48, 'N'), (122, 16, 'W')],
'Oklahoma City, Okla.': [(35, 26, 'N'), (97, 28, 'W')],
'Omaha, Neb.': [(41, 15, 'N'), (95, 56, 'W')],
'Ottawa, Ont., Can.': [(45, 24, 'N'), (75, 43, 'W')],
'Philadelphia, Pa.': [(39, 57, 'N'), (75, 10, 'W')],
'Phoenix, Ariz.': [(33, 29, 'N'), (112, 4, 'W')],
'Pierre, S.D.': [(44, 22, 'N'), (100, 21, 'W')],
'Pittsburgh, Pa.': [(40, 27, 'N'), (79, 57, 'W')],
'Portland, Maine': [(43, 40, 'N'), (70, 15, 'W')],
'Portland, Ore.': [(45, 31, 'N'), (122, 41, 'W')],
'Providence, R.I.': [(41, 50, 'N'), (71, 24, 'W')],
'Quebec, Que., Can.': [(46, 49, 'N'), (71, 11, 'W')],
'Raleigh, N.C.': [(35, 46, 'N'), (78, 39, 'W')],
'Reno, Nev.': [(39, 30, 'N'), (119, 49, 'W')],
'Richfield, Utah': [(38, 46, 'N'), (112, 5, 'W')],
'Richmond, Va.': [(37, 33, 'N'), (77, 29, 'W')],
'Roanoke, Va.': [(37, 17, 'N'), (79, 57, 'W')],
'Sacramento, Calif.': [(38, 35, 'N'), (121, 30, 'W')],
'Salt Lake City, Utah': [(40, 46, 'N'), (111, 54, 'W')],
'San Antonio, Tex.': [(29, 23, 'N'), (98, 33, 'W')],
'San Diego, Calif.': [(32, 42, 'N'), (117, 10, 'W')],
'San Francisco, Calif.': [(37, 47, 'N'), (122, 26, 'W')],
'San Jose, Calif.': [(37, 20, 'N'), (121, 53, 'W')],
'San Juan, P.R.': [(18, 30, 'N'), (66, 10, 'W')],
'Santa Fe, N.M.': [(35, 41, 'N'), (105, 57, 'W')],
'Savannah, Ga.': [(32, 5, 'N'), (81, 5, 'W')],
'Seattle, Wash.': [(47, 37, 'N'), (122, 20, 'W')],
'Shreveport, La.': [(32, 28, 'N'), (93, 42, 'W')],
'Sioux Falls, S.D.': [(43, 33, 'N'), (96, 44, 'W')],
'Sitka, Alaska': [(57, 10, 'N'), (135, 15, 'W')],
'Spokane, Wash.': [(47, 40, 'N'), (117, 26, 'W')],
'Springfield, Ill.': [(39, 48, 'N'), (89, 38, 'W')],
'Springfield, Mass.': [(42, 6, 'N'), (72, 34, 'W')],
'Springfield, Mo.': [(37, 13, 'N'), (93, 17, 'W')],
'St. John, N.B., Can.': [(45, 18, 'N'), (66, 10, 'W')],
'St. Louis, Mo.': [(38, 35, 'N'), (90, 12, 'W')],
'Syracuse, N.Y.': [(43, 2, 'N'), (76, 8, 'W')],
'Tampa, Fla.': [(27, 57, 'N'), (82, 27, 'W')],
'Toledo, Ohio': [(41, 39, 'N'), (83, 33, 'W')],
'Toronto, Ont., Can.': [(43, 40, 'N'), (79, 24, 'W')],
'Tulsa, Okla.': [(36, 9, 'N'), (95, 59, 'W')],
'Vancouver, B.C., Can.': [(49, 13, 'N'), (123, 6, 'W')],
'Victoria, B.C., Can.': [(48, 25, 'N'), (123, 21, 'W')],
'Virginia Beach, Va.': [(36, 51, 'N'), (75, 58, 'W')],
'Washington, D.C.': [(38, 53, 'N'), (77, 2, 'W')],
'Wichita, Kan.': [(37, 43, 'N'), (97, 17, 'W')],
'Wilmington, N.C.': [(34, 14, 'N'), (77, 57, 'W')],
'Winnipeg, Man., Can.': [(49, 54, 'N'), (97, 7, 'W')]}

if compass in ['S','W']:
r = -r
return r

def distance(lat1, lon1, lat2, lon2, r):
return acos( cos(lat1)*cos(lon1) * cos(lat2)*cos(lon2) + \
cos(lat1)*sin(lon1) * cos(lat2)*sin(lon2) + \
sin(lat1)*sin(lat2) ) * r

def getmiles(citya, cityb):
return distance(lat1, lon1, lat2, lon2, 3963.1)

thepath = "c:/documents and settings/kirby/My Documents/Projects/winterhaven/"

def makexml(thefile):
f = open(thepath + thefile, 'w')
f.write('<?xml version="1.0" encoding="ISO-8859-1"?>\n')
f.write('<cities>\n')
for city in cities:  # cities is a global in this context

loc = tuple([s.strip() for s in city.split(',')])
if len(loc)==3:
f.write('<city name="%s" state="%s" country="%s">\n' % loc)
else:
f.write('<city name="%s" state="%s">\n' % loc)
f.write('\t<lat  deg="%s" min="%s" dir="%s" />\n' % cities[city][0])
f.write('\t<long deg="%s" min="%s" dir="%s" />\n' % cities[city][1])
f.write('</city>\n')
f.write('</cities>\n')
f.close()

class CityHandler(ContentHandler):

def __init__(self):
self.thedict = {}
self.key = ''
self.latlong = []

def startElement(self, name, attrs):
if name=='city':
key = "%s, %s" % (str(attrs.get('name')),
str(attrs.get('state')))
country = str(attrs.get('country',''))
if country<>'':
key = key + ", " + country
self.key = key

if name in ['lat','long']:
self.latlong.append(
(int(attrs.get('deg')),
int(attrs.get('min')),
str(attrs.get('dir'))))
if name=='long':
self.thedict[self.key]=self.latlong
self.latlong=[]
self.key=''

def makecities(thefile):
parser = make_parser()
thehandler = CityHandler()
parser.setContentHandler(thehandler)
parser.parse(thepath + thefile)
print thehandler.thedict

```
`# code highlighted using py2html.py version 0.8`