XQuery/SMS tracker
Motivation
editBrightKite provides a service to micro-blog your location and message to a service to geocode the address, map it, find other tweeters nearby and forward to other micro-blogs.
However for UK users the service lacks the availability of an SMS service. The following scripts provide a basic SMS tracker service, allowing a user to text an address and a message to an SMS service and see that location on a generated map. This simple application does not provide the social aspects of BrightKite, being confined to creating a simple track.
Implementation
editDependencies
editeXist-db Modules
edit- xmldb - to update the track
- datetime - for date formatting
- util - serialize to convert XML to CDATA
Other
edit- an SMS two-way service
- GoogleGeocoding service
- kml-based mapping such as GoogleMap or GoogleEarth
The Track structure
editEach track is represented as a single XML file, containing a unique name, a title, one or more mobile phone numbers and a list of events. Each event is time-stamped and contains the original address, its latitude and longitude when geo-coded and a message. A local namespace is used for the XML data and for associated functions. Full address geo-coding is not supported in the UK due to copyright restrictions on full addresses and postcodes.
<?xml version="1.0" encoding="UTF-8"?>
<track xmlns="http://www.cems.uwe.ac.uk/exist/geo" >
<name>wiki</name>
<mobile>44771234578</mobile>
<title>Demo Track</title>
<entries>
<entry date="2008-06-12T09:56:08.593Z">
<address>bristol parkway station</address>
<location latitude="53.580320" longitude="-0.683640" ambiguous="true"/>
<message>Waiting for the paddington train</message>
</entry>
<entry date="2008-06-12T10:30:51.454Z">
<address>swindon</address>
<location latitude="51.558418" longitude="-1.781985"/>
<message>Nice empty train</message>
</entry>
<entry date="2008-06-12T10:51:12.429Z">
<address>didcot parkway</address>
<location latitude="51.610994" longitude="-1.242799"/>
<message>Grey and its been raining</message>
</entry>
...
In-bound messages
editInbound messages have the structure:
geo {address} ! {message}
SMS messages are sent to the UWE SMS two-way service described in here. The router uses the first word to route the message to the associate service, in this case track2sms.xq. This service is invoked viat HTTP, passing the prefix ('prefix), the originating mobile number (from) and the text of the message (text) following the prefix.
The script uses the originating mobile number to find the associated track. If there is one, the message is parsed into the address and message text. The address is passed to the Google geocoding service. If the address is recognised, a new event is created and appended to the rest of the events in the track and a confirmation returned to the originator (via the SMS two-way service).
declare namespace geo = "http://www.cems.uwe.ac.uk/exist/geo";
declare namespace kml = "http://earth.google.com/kml/2.0";
declare variable $geo:googleKey := "ABQIAAAAVehr0_0wqgw_UOdLv0TYtxSGVrvsBPWDlNZ2fWdNTHNT32FpbBR1ygnaHxJdv-8mkOaL2BJb4V_yOQ";
declare variable $geo:googleUrl := "http://maps.google.com/maps/geo?q=";
declare function geo:geocode($address as xs:string) as element(geo:location)* {
let $address := normalize-space($address)
let $address := encode-for-uri($address)
let $url := concat($geo:googleUrl,$address,"&output=xml&key=",$geo:googleKey)
let $response := doc($url)
for $placemark in $response//kml:Placemark
let $point := $placemark/kml:Point/kml:coordinates
let $latlong := tokenize($point,",")
return
<geo:location latitude="{$latlong[2]}" longitude="{$latlong[1]}"/>
};
declare variable $sep := "!";
declare variable $from := request:get-parameter("from",());
declare variable $text := request:get-parameter("text",());
declare variable $track := //geo:track[geo:mobile = $from];
declare variable $now := string(adjust-dateTime-to-timezone(current-dateTime()));
declare option exist:serialize "method=text media-type=text/text indent=yes";
if (exists($track))
then
let $address :=
if (contains($text,$sep))
then normalize-space(substring-before($text,$sep))
else normalize-space($text)
let $message := substring-after($text,$sep)
let $location := geo:geocode($address)
return
if (exists($location) and count($location)=1)
then
let $update :=
update
insert
<entry xmlns="http://www.cems.uwe.ac.uk/exist/geo" date='{$now}' >
<address>{$address}</address>
{$location}
<message>
{$message}
</message>
</entry>
into $track/geo:entries
return
concat("Reply: ",$address, " is at lat: ", $location/@latitude, " long:.", $location/@longitude)
else
concat("Reply: ",$track/name," address :", $address, "not geocoded or ambiguous", $text,":",$message)
else
()
Generating the Map
editThe track is identified by name and a KML file of the events on the track is generated.
declare namespace geo = "http://www.cems.uwe.ac.uk/exist/geo";
declare namespace kml = "http://earth.google.com/kml/2.1" ;
declare function geo:entry-to-kml($entry as element(geo:entry)) as element(Placemark) {
let $location := $entry/geo:location
let $latlong := concat($location/@latitude," ",$location/@longitude)
let $dt := datetime:format-dateTime($entry/@date,"yy/MM/dd HH:mm")
let $popup :=
<div xmlns="http://www.w3.org/1999/xhtml">
<h3>{string($entry/geo:address)}</h3>
<p> {string($entry/geo:message)} </p>
</div>
return
<Placemark>
<name>{$dt}  {string($entry/geo:title)}</name>
<description>
{util:serialize($popup,"method=xhtml")}
</description>
<Point>
<coordinates>
{string-join(($location/@longitude,$location/@latitude),",")}
</coordinates>
</Point>
</Placemark>
};
declare option exist:serialize "method=xml indent=yes
media-type=application/vnd.google-earth.kml+xml";
declare variable $name := request:get-parameter("name",());
declare variable $track := //geo:track[geo:name=$name];
let $dummy := response:set-header('Content-Disposition',concat('inline;filename=',$name,'.kml;'))
return
<kml xmlns="http://earth.google.com/kml/2.1" >
<Folder>
<name>{$name}</name>
<title>{$track/geo:title}</title>
{ for $entry in $track//geo:entry
return geo:entry-to-kml($entry)
}
</Folder>
</kml>
Example Map
editNote that one address has been miscoded but the feedback allowed the address to be changed and resent.
To do
edit- edit track to remove or correct bad geo-coding
- add events from a browser