XQuery/Google Geocoding
Motivation
editYou have one or more geographic names and you want to create a map of these locations.
Method
editWe 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
editThe 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
editA 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* Bristol * London
[Setting the zoom level doesn't seem to work]
Data to overlay
editOften 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