# ACTIVESTATE TEAPOT-PKG BEGIN TM -*- tcl -*- # -- Tcl Module # @@ Meta Begin # Package geocode 0.1 # Meta as::author {Steve Havelka} # Meta as::build::date 2015-02-23 # Meta as::origin https://bitbucket.org/smh377/google-maps-geocode # Meta platform tcl # Meta require http # Meta require tls # Meta require xsxp # Meta require {Tcl 8.5} # @@ Meta End # ACTIVESTATE TEAPOT-PKG BEGIN REQUIREMENTS package require http package require tls package require xsxp package require Tcl 8.5 # ACTIVESTATE TEAPOT-PKG END REQUIREMENTS # ACTIVESTATE TEAPOT-PKG BEGIN DECLARE package provide geocode 0.1 # ACTIVESTATE TEAPOT-PKG END DECLARE # ACTIVESTATE TEAPOT-PKG END TM # Use the Google geocoding API to find lat/long coordinates for # street addresses. package require http package require tls package require xsxp package provide geocode 0.1 # make sure we can use https in the http package ::http::register https 443 ::tls::socket namespace eval geocode { set coords {} set failed {} # This might need to be changed... set api_key "" set api_referer "http://stevehavelka.com" set uri_base "http://maps.googleapis.com/maps/api/geocode/xml" set uri_base_key "https://maps.googleapis.com/maps/api/geocode/xml" # This won't... set addr {} set addr_queue {} set cb_step "" set cb_done "" } # Take an address, run it through Google's geocoding API, and return # a latitude/longitude pair. # # Addr is a dict, containing one or more of the following: # * address_1 # * address_2 # * address_3 # * city # * state # * country # * zip # proc geocode::lookup { addr } { variable api_key variable api_referer variable uri_base variable uri_base_key # make sure that whatever keys we got, they're sorted in the # proper order and joined into a string set ordering { address_1 address_2 address_3 city state country zip } set addr_sorted {} foreach key $ordering { if { [dict exists $addr $key] } { lappend addr_sorted [dict get $addr $key] } } set addr_string [join $addr_sorted ", "] set uri_parts [list address $addr_string] set headers {} # have api key? if { $api_key ne "" } { lappend uri_parts key $api_key lappend headers Referer $api_referer # make the URI for the https call set uri "${uri_base_key}?[http::formatQuery {*}$uri_parts]" } else { # make the URI for the http call set uri "${uri_base}?[http::formatQuery {*}$uri_parts]" } # http call after 100 [list http::geturl $uri -command geocode::next -headers $headers] } # Process the next address in the queue proc geocode::next { token } { variable addr variable addr_queue variable cb_step variable cb_done variable coords variable failed # save the data set xml [http::data $token] set pxml [xsxp::parse $xml] set status [xsxp::fetch $pxml { status } %PCDATA] if { $status eq "OK" } { # we've got a lat/long pair set lat [xsxp::fetch $pxml { result geometry location lat } %PCDATA] set lng [xsxp::fetch $pxml { result geometry location lng } %PCDATA] lappend coords [list $lat $lng] } else { # this address failed for some reason lappend failed $addr } # call the callback eval $cb_step # get the next address set addr [lindex $addr_queue 0] set addr_queue [lrange $addr_queue 1 end] # any more to process? if { $addr ne {} } { # yes! next one geocode::lookup $addr } else { # no! we're done eval $cb_done } } # Process a whole bunch of addresses proc geocode::batch { addrs cmd_step cmd_done } { variable addr variable addr_queue variable cb_step variable cb_done variable coords variable failed set coords {} set failed {} # save the callback set cb_step $cmd_step set cb_done $cmd_done # get the first address and save the rest set addr [lindex $addrs 0] set addr_queue [lrange $addrs 1 end] # and begin processing geocode::lookup $addr }