XQuery/Google Geocoding
Motivation
[edit | edit source]You have one or more geographic names and you want to create a map of these locations.
Method
[edit | edit source]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 | edit source]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,"&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 | edit source]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&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 | edit source]* Bristol * London
[Setting the zoom level doesn't seem to work]
Data to overlay
[edit | edit source]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,"&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>
and a modified version of the JavaScript can use the output to create a Whisky map