;; $Id: google-maps.cl,v 1.7 2007/01/16 15:15:50 layer Exp $
;;
;; This software is Copyright (c) Kevin Layer, 2006-2007.
;; You are granted the rights to distribute
;; and use this software as governed by the terms
;; of the Lisp Lesser GNU Public License
;; (http://opensource.franz.com/preamble.html),
;; known as the LLGPL.

(eval-when (compile eval load)
  (require :aserve)
  (require :measures)
  (require :zipcodes)
  )

(defpackage :util.google.maps
  (:use #:common-lisp #:excl #:net.aserve #:net.aserve.client
	#:util.measures #:util.zipcodes)
  (:export #:*default-key*
	   #:geocode
	   #:place-to-location
	   #:location-to-place))

(provide :google-maps)

(in-package :util.google.maps)

;; See 
;; http://www.google.com/apis/maps/documentation/#Geocoding_HTTP_Request

(defvar *default-key* nil
  "A default Google Maps API key for calls to geocode.  You can obtain a
key by visiting http://www.google.com/apis/maps/signup.html.")

(defun geocode (&key q (key *default-key*) (output :csv))
  "Geocoding is the process of converting addresses (like \"1600
Amphitheatre Parkway, Mountain View, CA\") into geographic coodinates (like
latitude 37.423021 and longitude -122.083739), which you can use to place
markers or position the map based on street addresses in your database or
addresses supplied by users. The Google Maps API includes a geocoder that
can be accessed via HTTP request."
  (when (null key)
    (error "You must specify a Google Maps API key, which you can ~
obtain from the http://www.google.com/apis/maps/signup.html."))
  (when (null q)
    (error "You must specify an address to geocode (e.g., \"Oakland, CA\")."))
  (setq output
    (case output
      ((:csv :xml :kml :json) (string-downcase (symbol-name output)))
      (t (error "Bad :output keyword value: ~s." output))))
  (let ((query (query-to-form-urlencoded
		`(("q" . ,q) ("output" . ,output) ("key" . ,key))))
	(url-base "http://maps.google.com/maps/geo"))
    (values
     (do-http-request (format nil "~a?~a" url-base query)
       :method :get
       ;; Dunno if this is needed.  I started getting "connection reset by
       ;; peer" for a while, so I added this.  About the time I added it I
       ;; stopped getting them.  Hmmmmmmm.
       :user-agent "Mozilla/5.0 (Windows; U; Windows NT 5.1; en-GB; rv:1.8.0.1) Gecko/20060111 Firefox/1.5.0.9"))))

(defun place-to-location (place &key (key *default-key*))
  (let ((result (geocode :q place :key key :output :csv)))
    ;; Using :csv, a good result starts with "200," and a bad result with
    ;; "602,".
    (when (and result (stringp result))
      (multiple-value-bind (found whole lat lon)
	  (match-re "200,[^,]+,(-?[0-9.]+),(-?[0-9.]+)" result)
	(declare (ignore whole))
	(when found
	  (make-location
	   :latitude (read-from-string lat)
	   :longitude (read-from-string lon)))))))

(defun location-to-place (location)
  (let ((zip (location-to-zip location)))
    (when zip
      (format nil "~@[~a, ~]~a"
	      (zip-city zip)
	      (zip-state zip)))))
