XQuery/Google Geocoding

Motivation

edit

You have one or more geographic names and you want to create a map of these locations.

Method

edit

We will use a Google RESTful web service to return geographical data from a list of place names. Google provides an HTTP-based Geocoding service. This requires registration of a site for a API key and there are limitations on the usage of this API. Results can be returned in XML or JSON.

Querying Google's API

edit

The following script takes a location and returns the xml from the service:

let $key := "apikey"
let $location := request:get-parameter("location",())
let $location := escape-uri($location,false())
let $url := concat("https://maps.googleapis.com/maps/api/geocode/xml?address=",$location,"&key=",$key)
let $response := doc($url)
return 
   $response
</source >

==== Examples ====
Single City Examples:

* [http://kitwallace.co.uk/xqbook/geo/googlegeocode.xq?location=Minneapolis Minneapolis] - example using a single city with no country
* [http://kitwallace.co.uk/xqbook/geo/googlegeocode.xq?location=Bristol,UK  Bristol,UK] - example using a city and country

Multiple matches may be returned:
* [http://kitwallace.co.uk/xqbook/geo/googlegeocode.xq?location=Bristol Bristol]
* or a false positive: [http://kitwallace.co.uk/xqbook/geo/googlegeocode.xq?location=Santas+House  Santa's House]

In the UK, this service will geocode postcodes:
* [http://kitwallace.co.uk/xqbook/geo/googlegeocode.xq?location=BS3+4EA Bristol Hackspace]

== Response as KML ==
The XML response can be reformated as a  KML file.  Note the addition of the relevant media-type for KML .

<syntaxhighlight lang="xml">
let $key := "my apikey"
let $location := request:get-parameter("location",())
let $location := escape-uri($location,false())
let $url := concat("https://maps.googleapis.com/maps/api/geocode/xml?address=",$location,"&amp;key=",$key)
let $response := doc($url)/GeocodeResponse
return 
   if ($response/status="OK")
   then 
      let $x := response:set-header('Content-disposition',concat('inline;filename="',$location,'.kml";'))
      let $result := $response/result[1]
      let $serialize := util:declare-option("exist:serialize", "method=xml media-type=application/vnd.google-earth.kml+xml  indent=yes")
      return
 <kml>
   <Folder>
      <name>{$location}</name>
       {for $result in $response/result
        return
         <Placemark>
           <name>{string($result/formatted_address)}</name>
           <Point>
              <coordinates>{$result/geometry/location/lng/string()},{$result/geometry/location/lat/string()},0</coordinates>
           </Point>
         </Placemark>
        }
   </Folder>
 </kml>
   else $response

If you have GoogleEarth, this should load an overlay showing multiple Bristol locations: Bristol KML

GoogleMap

edit

A simple way to view the generated kml is to use GoogleMaps. This script simply constructs the relevant URL to use the script above and then redirects to that URL:

let $location := request:get-parameter("location",())
let $wikiurl := escape-uri(concat("http:/kitwallace.co.uk/geocodekml.xq?location=",$location),false())
let $url := concat("http://maps.google.co.uk/maps?q=",$wikiurl)
return 
   response:redirect-to(xs:anyURI($url))

Sadly this approach no longer works with GoogleMap - see support lost Bristol

An alternative approach is to use GoogleMap's JavaScript API. Note that to embed JavaScript requires doubling { so it is generally easier to put the Javascript in a separate file. This approach makes use of any existing kml overlay and is simpler, but more restricted than implementing the map using the JavaScript API

declare option exist:serialize "method=xhtml media-type=text/html";

let $location := request:get-parameter("location",())
let $url := escape-uri(concat("http://kitwallace.co.uk/xqbook/geo/geocodekml.xq?location=",$location),false())
return
<html>
   <head>
     <script src="https://maps.googleapis.com/maps/api/js?v=3&amp;key=myapikey"></script> 
     <script type="text/javascript">
function initialize(url) {{
  var map = new google.maps.Map(document.getElementById("map_canvas"));

  var kmlLayer = new google.maps.KmlLayer({{
    url: url,
    map:map
  }}); 
}}    
      </script>
    </head>
    <body  onload="initialize('{$url}')">
       <h2>Map of {$location}</h2>
       <div id="map_canvas" style="height: 80%;width: 80%; margin: 0px; padding: 0px">
       </div>
    </body>
</html>


Examples

edit
*  Bristol
*  London

[Setting the zoom level doesn't seem to work]

Data to overlay

edit

Often the locations are defined in a data file. The following script reads a local file of (a sample of ) whisky distilleries and creates a KML overlay.

let $key := "apikey"
let $locations := doc("/db/apps/xqbook/geo/whisky.xml")
let $serialize := util:declare-option("exist:serialize", "method=xml media-type=application/vnd.google-earth.kml+xml") 
let $x := response:set-header('Content-disposition','inline;filename=whisky.kml')
return
 <kml>
   <Folder>
      <name>Distilleries</name>
      {for $location in $locations//Whisky
       let $url := concat("https://maps.googleapis.com/maps/api/geocode/xml?address=",$location/Postcode,"&amp;key=",$key)
       let $response := doc($url)/GeocodeResponse
       let $result := $response/result[1]
       return
      <Placemark>
           <name>{$location/Brand/string()}</name>
           <description>{$location/Address/string()}</description>
           <Point>
              <coordinates>{$result/geometry/location/lng/string()},{$result/geometry/location/lat/string()},0</coordinates>
           </Point>
      </Placemark>
      }
   </Folder>
 </kml>

Whisky overlay

and a modified version of the JavaScript can use the output to create a Whisky map