XQuery/Google Geocoding
Motivation
You have one or more geographic names and you want to create a map of these locations.
Method
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.
Querying Google's API
The following script takes a location and returns the xml from the service:
let $key := "ABQIAAAAVehr0_0wqgw_UOdLv0TYtxSGVrvsBPWDlNZ2fWdNTHNT32FpbBR1ygnaHxJdv-8mkOaL2BJb4V_yOQ"
(: get the geo name as parameter from the incomming URL :)
let $location := request:get-parameter("location",())
(: adjust the escape codes :)
let $location := escape-uri($location, false())
(: construct a new URL that appends the geo-name and key to the service URL. Tell the service that we want XML output format. :)
let $url := concat("http://maps.google.com/maps/geo?q=",$location,"&output=xml&key=",$key)
(: send the URL out and put the result in the $response variable :)
let $response := doc($url)
return
$response
Examples
Single City Examples:
- Minneapolis - example using a single city with no country
- Bristol,UK - example using a city and country
Multiple matches may be returned:
- Utopia
- or none: Santa's House
Response as KML
The XML response can be reformated as a simpler KML file. Note the addition of the relevant media-type for KML and the declaration of the KML namespace required to access the returned XML.
declare option exist:serialize "method=xml media-type=application/vnd.google-earth.kml+xml indent=yes";
declare namespace kml = "http://earth.google.com/kml/2.0";
let $key := "ABQIAAAAVehr0_0wqgw_UOdLv0TYtxSGVrvsBPWDlNZ2fWdNTHNT32FpbBR1ygnaHxJdv-8mkOaL2BJb4V_yOQ"
let $location := request:get-parameter("location",())
let $location := escape-uri($location,false())
let $url := concat("http://maps.google.com/maps/geo?q=",$location,"&output=xml&key=",$key)
let $response := doc($url)
let $x := response:set-header('Content-disposition',concat('inline;filename="',$location,'.kml";'))
return
<kml xmlns="http://earth.google.com/kml/2.0">
<Folder>
<name>{$location}</name>
{
for $place in $response//kml:Placemark
return
<Placemark>
<name>{string($place/kml:address)}</name>
{$place/kml:Point}
</Placemark>
}
</Folder>
</kml>
If you have GoogleEarth, this should load an overlay: Utopia KML
GoogleMap
A simple way to view the generated kml is to use GoogleMaps. This script simply constructs the relevant GoogleMap URL and then redirects to that URL:
let $location := request:get-parameter("location",())
let $location := escape-uri($location,false())
let $wikiurl := escape-uri(concat("http://www.cems.uwe.ac.uk/xmlwiki/geocodekml.xq?location=",$location),false())
let $url := concat("http://maps.google.co.uk/maps?q=",$wikiurl)
return
response:redirect-to(xs:anyURI($url))
This mimic of the GoogleMap is useful to check that the scripts are working, but more usefully, the geocoding service could be used within an application.
Simple Location Service
In the UK, the Google service geocodes full postcodes. In the case where only the latitude and longitude of the place is required, the following script may be sufficient:
declare namespace kml = "http://earth.google.com/kml/2.0";
declare option exist:serialize "method=xml media-type=text/xml omit-xml-declaration=no";
let $key := "ABQIAAAAVehr0_0wqgw_UOdLv0TYtxSGVrvsBPWDlNZ2fWdNTHNT32FpbBR1ygnaHxJdv-8mkOaL2BJb4V_yOQ"
let $location := request:get-parameter("location",())
let $location := escape-uri($location,false())
let $url := concat("http://maps.google.com/maps/geo?q=",$location,"&output=xml&key=",$key)
let $response := doc($url)
let $place := $response//kml:Placemark[1]
let $point := $place/kml:Point/kml:coordinates
let $coords := tokenize($point,",")
return
<location>
<lat>{$coords[2]}</lat>
<long>{$coords[1]}</long>
</location>
This is now a service which can be used as a REST service.
Here's a Yahoo Pipe to take some data on Scotch Whiskies:
<?xml version="1.0" encoding="UTF-8"?> <WhiskyList> <Whisky> <Brand>Glen Ord</Brand> <Address>Glen Ord Distillery, Muir of Ord, Ross-shire</Address> <Postcode>IV67UJ</Postcode> </Whisky> <Whisky> <Brand>Dalwhinnie</Brand> <Address>Dalwhinnie Distillery, Dalwhinnie, Inverness-shire</Address> <Postcode>PH191AB</Postcode> </Whisky> <Whisky> <Brand>Laphroaig</Brand> <Address>Laphroaig Distillery, Port Ellen, Isle of Islay</Address> <Postcode>PA427DU</Postcode> </Whisky> </WhiskyList>
and generate a geo-coded RSS feed:
RSS feed
Of course this feed could be generated in XQuery alone:
declare namespace geo = "http://www.w3.org/2003/01/geo/wgs84_pos#";
declare namespace kml = "http://earth.google.com/kml/2.0";
declare option exist:serialize "method=xml omit-xml-declaration=no indent=yes encoding=iso-8859-1 media-type=application/rss+xml";
declare variable $key := "ABQIAAAAVehr0_0wqgw_UOdLv0TYtxSGVrvsBPWDlNZ2fWdNTHNT32FpbBR1ygnaHxJdv-8mkOaL2BJb4V_yOQ";
declare function local:geocode-location($location as xs:string) {
let $url := concat("http://maps.google.com/maps/geo?q=",$location,"&output=xml&key=",$key)
let $response := doc($url)
let $place := $response//kml:Placemark[1]
let $point := $place/kml:Point/kml:coordinates
let $coords := tokenize($point,",")
return
(
<geo:lat>{$coords[2]}</geo:lat>,
<geo:long>{$coords[1]}</geo:long>
)
};
<rss version='2.0'
xmlns:geo = "http://www.w3.org/2003/01/geo/wgs84_pos#">
<channel>
<title>Whiskies of Scotland</title>
{
for $whisky in //Whisky
let $postcode := $whisky/Postcode
let $location := local:geocode-location($postcode)
return
<item>
<title>{string($whisky/Brand)}</title>
<description>{string($whisky/Address)}</description>
{$location}
</item>
}
</channel>
</rss>