At the last meeting I took down everyone’s zipcodes, in order to determine the geographic center of the members present. The goal was to determine (roughly!) where a better group project meeting site would need to be located if we move away from bRainSilo.
There were only 14 people present, though many regular members were there. I added Fred to the list, even though he was not present. I could not easily find an online tool that would determine the center of a set of zipcodes, so I wrote my own using a combination of a geonames.org api and a google maps api and Python. I basically find the latitude and longitude of each address or centroid of each zipcode; average them all, ignoring spherical distortion since this is a pretty small patch of the Earth; then convert that back into an address.
(Turns out the google maps api does not give the official census bureau zipcode centroid, so I use geonames’ api when all I have is a zipcode. If I had a full address, I used the google maps api.)
Anyway, the center is surprisingly central east-west but further south than I expected. It’s within 2 miles of PCC Sylvania and also 2 miles north of the I-5 and Hwy217 interchange.
Also interesting is that even though google map’s idea of the center of each zipcode is different from the official center in most cases, the errors all seem to average out, so the address I get just using google’s api is within 0.1 miles of the more accurate one.
#
# center_of_zipcodes.py
#
# find address corresponding to center of a set of zipcodes using google maps api
# written by Pete Skeggs, 4/5/2011, for PARTS
# updated 4/13/2011 to use geonames.org API to get true census bureau centroids when just given a zipcode; also, use an address when known and look that up with the google maps api
#
import httplib
import json
import urllib
# define functions
def ztoll(z):
conn = httplib.HTTPConnection("api.geonames.org")
# note: you need to replace 'demo' below with a valid username; I decided not
# to share mine
url = "/postalCodeLookupJSON?postalcode=%s&country=US&username=demo" % z
conn.request("GET", url)
r = conn.getresponse()
o = json.load(r)
c = {'lat' : 0, 'lng' : 0}
# format returned:
#{"postalcodes":[{"adminName2":"Multnomah","adminCode2":"051","postalcode":"97210","adminCode1":"OR","countryCode":"US",
# "lng":-122.703348,"placeName":"Portland","lat":45.530318,"adminName1":"Oregon"}]}
c['lat'] = o[u'postalcodes'][0][u'lat']
c['lng'] = o[u'postalcodes'][0][u'lng']
c['addr'] = z
return c
def addrtoll(addr):
conn = httplib.HTTPConnection("maps.googleapis.com")
url = "/maps/api/geocode/json?address=%s&sensor=false" % urllib.quote_plus(addr)
conn.request("GET", url)
r = conn.getresponse()
o = json.load(r)
c = {'lat' : 0, 'lng' : 0}
# format returned:
# { "status": "OK", "results": [{"types":["postal_code"],"formatted_address":"Portland, OR 97210, USA",
# "address_components":[{"long_name":"97210","short_name":"97210","types":["postal_code"]},
# {"long_name":"Portland","short_name":"Portland","types":["locality","political"]},
# {"long_name":"Oregon","short_name":"OR","types":["administrative_area_level_1","political"]},
# {"long_name":"UnitedStates","short_name":"US","types":["country","political"]}],
#"geometry":{"location":{"lat":45.5552753,"lng":-122.7384041},"location_type":"APPROXIMATE",
#"viewport":{"southwest":{"lat":45.5158719,"lng":-122.7897450},"northeast":{"lat":45.5858230,"lng":-122.6920030}},
#"bounds":{"southwest":{"lat":45.5158719,"lng":-122.7897450},"northeast":{"lat":45.5858230,"lng":-122.6920030}}}}]}
c['lat'] = o[u'results'][0][u'geometry'][u'location'][u'lat']
c['lng'] = o[u'results'][0][u'geometry'][u'location'][u'lng']
c['addr'] = o[u'results'][0][u'formatted_address']
return c
def lltoz(lat, lng):
conn = httplib.HTTPConnection("maps.googleapis.com")
url = "/maps/api/geocode/json?latlng=%f,%f&sensor=false" % (lat, lng)
conn.request("GET", url)
r = conn.getresponse()
o = json.load(r)
addr = o[u'results'][0][u'formatted_address']
return addr
# define variables
# list of zipcodes for PARTS members at April 2011 meeting
addresses = [
"97007",
"97210",
"97223",
"97034",
"97202",
"97232",
"97224",
"97071",
"97219",
"97086",
"97224",
"97211",
"97006",
"97224",
"97225",
"97229"]
lat = 0
lng = 0
count = 0
# main code
for a in addresses:
if len(a) == 5:
c = ztoll(a)
else:
c = addrtoll(a)
print "%d. %s: %f, %f" % (count+1, c['addr'], c['lat'], c['lng'])
lat = lat + c['lat']
lng = lng + c['lng']
count = count + 1
lat = lat / count
lng = lng / count
print "average of %d zipcodes: lat %f, lng %f" % (count, lat, lng)
print "address at center: %s" % lltoz(lat, lng)
This yields the following output (using a valid geonames.org username — you need to register for one of your own):
~$ python center_of_zipcodes.py 1. 97007: 45.450489, -122.865171 2. 97210: 45.530318, -122.703348 3. 97223: 45.447390, -122.795294 4. 97034: 45.409263, -122.684721 5. 97202: 45.484007, -122.636534 6. 97232: 45.528712, -122.636310 7. 97224: 45.409448, -122.801400 8. 97071: 45.144617, -122.858342 9. 97219: 45.457956, -122.707380 10. 97086: 45.444600, -122.537200 11. 97224: 45.409448, -122.801400 12. 97211: 45.565259, -122.644815 13. 97006: 45.520130, -122.860376 14. 97224: 45.409448, -122.801400 15. 97225: 45.498473, -122.778659 16. 97229: 45.548317, -122.827561 average of 16 zipcodes: lat 45.453617, lng -122.746244 address at center: 6655 SW Kingsview Ct, Tigard, OR 97223, USA ~$
Very interesting GIS analysis. I would have thought a stronger pull from the Washington county area. But, PCC Sylvania is not a bad location – maybe they would allow us to use one of their shop classrooms, or even make a class out of it to draw people in wanting to earn credits and work on robots – though that is probably too much administrative overhead.
Btw.
My home zipcode is 97229, and I am temporarily in 77002.