XQuery/SMS tracker

Motivation

edit

BrightKite 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

edit

Dependencies

edit

eXist-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

edit

Each 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

edit

Inbound 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,"&amp;output=xml&amp;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

edit

The 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} &#160;{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

edit

Google Map

Note that one address has been miscoded but the feedback allowed the address to be changed and resent.

To do

edit
  1. edit track to remove or correct bad geo-coding
  2. add events from a browser