XRX/Map Navigation

From Wikibooks, open books for an open world
< XRX
Jump to: navigation, search

Motivation[edit]

You would like to add map navigation to your XRX application.

Although map controls were not defined within the original XForms specification, they can be added by using a bit of JavaScript.

Method[edit]

We will use three frameworks.

  1. XSLTForms - that enables the model, controls and bindings
  2. JQuery - JQuery UI, JQuery layout - for the user interface. This includes the controls to do pans north, south, east, west
  3. OpenLayers - for the map navigation, pan and zoom functions

The XForms model will contain the following:

  1. A default instance that contains a search query with the min and max longitude and latitude (default).
  2. A place to store the response to the query (response).
  3. A locate instance (locate)
  4. A few binding statements
  5. A submission to get the new map data from the openmap database

Sample User Interface[edit]

This example has a simple map user interface control that allows for panning to the North/South/East/West and zoom and zoom out.

Map-navigation-user-interface-control-screenshot.png

This user interface uses a JQuery object that is associated with XForms input user interface controls.

Sample Model Source[edit]

The following is stored in the XForms model:

Note that the following namespaces are used in this example:

xmlns:geo="http://schematronic.ru/geo"
xmlns:param="http://schematronic.ru/param"
xmlns:ev="http://www.w3.org/2001/xml-events"
<xf:model>
   <!-- this holds the parameters for the outbound search query. -->
   <xf:instance id="default" xmlns="http://schematronic.ru/geo">
       <geo:search>
           <param:query/>
           <!-- variables for the min and max longitude and latitude -->
           <param:min-lon/>
           <param:min-lat/>
           <param:max-lon/>
           <param:max-lat/>
        </geo:search>
    </xf:instance>
 
    <!-- this holds the search results -->
    <xf:instance xmlns="" id="response">
       <response/>
    </xf:instance>
 
    <xf:instance id="locate">
       <eval>
           javascript:g.locate(<lon></lon>, <lat></lat>, "<icon></icon>")
       </eval>
    </xf:instance>
 
    <!-- These binds associate an id with a variable in the search form and a calculation -->
    <xf:bind id="query" nodeset="instance('default')/param:query"/>
    <xf:bind id="min-lon" nodeset="instance('default')/param:min-lon" calculate="min-lon()"/>
    <xf:bind id="min-lat" nodeset="instance('default')/param:min-lat" calculate="min-lat()"/>
    <xf:bind id="max-lon" nodeset="instance('default')/param:max-lon" calculate="max-lon()"/>
    <xf:bind id="max-lat" nodeset="instance('default')/param:max-lat" calculate="max-lat()"/>
    <!-- These binds associate an ID with 
    <xf:bind id="lon" nodeset="instance('locate')/lon"/>
    <xf:bind id="lat" nodeset="instance('locate')/lat"/>
    <xf:bind id="icon" nodeset="instance('locate')/icon"/>
 
    <!-- When the user selects any of the navigation controls, the following does a POST to the server. -->
    <!-- Note that the response to the search is places in the "response instance -->
     <xf:submission id="do-search" method="post" ref="instance('default')" replace="instance" instance="response" resource="/gate">
         <xf:load 
            ev:event="xforms-submit-done" 
            resource="javascript:showResult()"/>
     </xf:submission>
</xf:model>

JavaScript Imports[edit]

The Javascript libraries will come from the openstreemap.org site. We will also use some JQuery user interface controls.

Here are the static JavaScript files you will need to add.

Importing Static JavaScript Libraries[edit]

   <script type="text/javascript" src="http://openlayers.org/api/OpenLayers.js"></script>
   <script type="text/javascript" src="http://www.openstreetmap.org/openlayers/OpenStreetMap.js"></script>
   <script type="text/javascript" src="/share/geo.js"></script>
   <script type="text/javascript" src="/share/jquery.js"></script>
   <script type="text/javascript" src="/share/jquery-ui.js"></script>
   <script type="text/javascript" src="/share/jquery-layout.js"></script>

Inline JavaScript Data[edit]

In addition to the above, the following JavaScript data must be used.

<script type="text/javascript">
    var g;
    function map(){
       var lat  = 51.30505;
       var lon  = 37.85331;
       var zoom = 12;
       g = geo("map", lon, lat, zoom);            
    }
    var isDebug = false;                   
    var layout;
 
    function showResult (){
                    layout.open("west");
    }      
    jQuery(function (){      
        jQuery("#search input").addClass("ui-state-default ui-corner-left");
        jQuery("#search button").addClass("ui-button ui-state-default ui-corner-right");
        layout = jQuery("body").layout({
              north : {
                            resizable          : false,
                            slidable           : false,
                            closable           : false,
                            spacing_open       : 0,
                            spacing_closed     : 0,
                            size               : 40
                        },
              south : {
                            size               : 100,
                            resizable          : true,
                            slidable           : true,
                            closable           : true,
                            initHidden         : !isDebug
                        },
              west : {
                            size               : 300,
                            minSize            : 200,
                            maxSize            : 400,
                            resizable          : true,
                            slidable           : true,
                            closable           : true,
                            initHidden         : true
                        }
 
                    }); 
             map();             
     if (isDebug) jQuery("#console").show();    
})
 
</script>

Form Body[edit]

<body>
   <div id="header" class="ui-layout-north ui-widget-header">
      <span id="logo">
         <a href="/">
            <img src="/site/stkurier/images/logo/logo.png" alt="logo" />
         </a>
      </span>
      <span id="search">
          <xf:input xmlns:xf="http://www.w3.org/2002/xforms" bind="query">
               <xf:send submission="do-search" ev:event="xforms-value-changed"/>
          </xf:input>
          <xf:submit submission="do-search">
              <xf:label>
                  <img alt="search" src="/share/icons/fugue-icons/icons/magnifier-medium-left.png" />
               </xf:label>
           </xf:submit>
            </span>
        </div>
        <div class="ui-layout-west">
            <div id="result">
                <ol>
                    <xf:repeat xmlns:xf="http://www.w3.org/2002/xforms" id="place_list" nodeset="instance('response')//*:place">
                        <li>
                            <xf:trigger appearance="minimal">
                                <xf:action xmlns:ev="http://www.w3.org/2001/xml-events" ev:event="DOMActivate">
                                    <xf:setvalue bind="lon" value="instance('response')//*:place[index('place_list')]/@lon"></xf:setvalue>
                                    <xf:setvalue bind="lat" value="instance('response')//*:place[index('place_list')]/@lat"></xf:setvalue>
                                    <xf:setvalue bind="icon" value="instance('response')//*:place[index('place_list')]/@icon"></xf:setvalue>
                                    <xf:load>
                                        <xf:resource value="instance('locate')"></xf:resource>
                                    </xf:load>
 
                                </xf:action>
                                <xf:label>
                                    <xf:output ref="@icon" mediatype="image/*" if="@icon"></xf:output>
                                    <xf:output ref="@display_name"></xf:output>
                                </xf:label>
                            </xf:trigger>
                        </li>
                    </xf:repeat>
                </ol>
            </div>
        </div>
        <div class="ui-layout-center">
            <div id="map"></div>
        </div>
        <div div="#debug" class="ui-layout-south">
            <div id="console"></div>
        </div>
    </body>

XQuery[edit]

The following is a sample of the server-side XQuery code.

   geo:search($query, $min-lon, $min-lat, $max-lon, $max-lat)

will be evaluated at server side for instance:

<geo:search>
     <param:query/>
     <param:min-lon/>
     <param:min-lat/>
     <param:max-lon/>
     <param:max-lat/>
</geo:search>

Geo XQuery Module[edit]

module namespace geo = "http://schematronic.ru/geo";
 
import module namespace http    = "http://exist-db.org/xquery/httpclient";
import module namespace request = "http://exist-db.org/xquery/request";
 
declare variable $geo:search-service-uri := "http://nominatim.openstreetmap.org/search";
 
declare function geo:search($query  as xs:string,
                           $min-lon as xs:float, $min-lat as xs:float,
                           $max-lon as xs:float, $max-lat as xs:float) {
 
   let $view-box := string-join(($min-lon, $min-lat, $max-lon, $max-lat), ",")
   let $lon      := ($min-lon + $max-lon) div 2
   let $lat      := ($min-lat + $max-lat) div 2
   let $uri      := escape-uri(xs:anyURI(concat(
                       $geo:search-service-uri,
                       "?format=xml",
                       "&amp;viewbox=", $view-box,
                       "&amp;addressdetails=1&amp;limit=100",
                       "&amp;polygon=0",
                       "&amp;q=", $query
                    )), false())
   let $response := http:get($uri, false(), ())
 
   let $results  := $response//searchresults
 
   return
       element {name($results)} {
           $response/@*,
           for $i in $results/place
           order by ($lon - $i/@lon) * ($lon - $i/@lon) + ($lat - $i/@lat) * ($lat - $i/@lat)
           return $i
       }
 
};
 
declare function geo:search($query as xs:string, $view-box as xs:float*) {
   geo:search($query, $view-box[1], $view-box[2], $view-box[3], $view-box[4])
};
 
declare function geo:search($query as xs:string) {
   geo:search($query, -180, -90, 180, 90)
};

Credits[edit]

All the work was done by Evgeny Gazdovsky. The writeup was done by Dan McCreary.