Documentation


Tutorials

Implementing a query

A Dracones layer with a FILTERITEM clause will by default have all of its elements (whatever they be: regions, points, etc) hidden. Only by setting the FILTER attribute can we reveal all, or a subset of those elements. This is the goal of a query, that we will demonstrate in this tutorial.

But first... a custom request

For this, we need to introduce an important (and probably the most useful) Dracones concept: the custom request. Until now, everything we needed on the server side was supplied by the Dracones module, and we didn't need to write any additional Python or PHP code. From now on, we will need to do so, to allow customized mapping behaviors.

If you are using Dracones-Python..

We will first create a Python script, that will actually be a WSGI interface, thus called directly by Apache. It will contain functions that can be called through AJAX requests, via the JS map widget. Our application codebase will now contain a new file:

<app_location>/conf.json
<app_location>/index.html
<app_location>/<script_name>.py

We will then need to instruct Apache to execute this script through WSGI, by modifying the application's Apache configuration file:

# Because the Alias directive takes precedence over WSGIScriptAlias, 
# we use the solution #2 described at: 
# http://serverfault.com/questions/59791/configure-apache-to-handle-a-sub-path-using-wsgi

Alias /<app_name>/<script_name> <script_location>/<script_name>.py

<Directory <script_location> >
  WSGIApplicationGroup %{GLOBAL}
  AddHandler wsgi-script .py
  Options ExecCGI -Indexes FollowSymLinks Multiviews  
  Order allow,deny
  Allow from all
</Directory>

Alias /<app_name> <app_location>

<Directory <app_location> >
  Options -Indexes FollowSymLinks Multiviews  
  Order allow,deny
  Allow from all
</Directory>

# If you need additional entry in the WSGIPythonPath, edit the one found in
# the Dracones Core Apache conf file.

Having done that, we can now look at the basic structure of a customized Dracones Python script:

from dracones.web_interface import *

@dispatcher.match('/<function_name>', 'GET')
@simple_tb_catcher
def <function_name>(req):

    params, sess, dmap = beginDracones(req)

    # Whatever map transformation you want to implement goes here (probably using params)..

    json_out = endDracones(dmap)

    # Whatever info to be returned to the client can be set in the json_out variable..

    return exitDracones(json_out)

There are two required decorators: @dispatcher.match is Pesto's middleware URL dispatching mechanism, while @simple_tb_catcher is a minimal mechanism for trapping any Python exception, and redirecting it nicely to the client, for easy debugging. The function itself has one argument (req, and three required steps.

beginDracones

The beginDracones call processes the request to extract the parameters (coming from the client) and the current session. Most importantly, it also initializes and restores the state of the application's DMap object (a Dracones wrapper around a MapServer map object). You can use the returned dmap and params variables to implement whatever transformation you want, after this step. For instance, this example query was implemented using only two additional lines:

from dracones.web_interface import *

@dispatcher.match('/exampleQuery', 'GET')
@simple_tb_catcher
def exampleQuery(req):

    params, sess, dmap = beginDracones(req)

    selected_regs = dmap.getDLayer('montreal').selected
    dmap.getDLayer('random_montreal_addresses').queryByAttributes('region_id',
                                                                  selected_regs,
                                                                  '<b>{street_num} {street_name}</b>')

    json_out = endDracones(dmap)

    return exitDracones(json_out)

The previously selected regions are first extracted from the polygonal "montreal" layer, and used next to filter the "random_montreal_addresses" point layer, by using its queryByAttributes method, whose third argument corresponds to the HTML template of the hover items (appearing when hovering over points with the mouse).

On the client side, the corresponding calling mechanism is also simple. Provided that we have an already created control to trigger the query (a button for instance):

jQuery(<control_id>).bind('click', function() { 
    map.customRequest({
        url: '<script_name>/<function_name>', 
        data: {/* any additional data */}, 
        callback: function(json_in) { /* optional callback function */ }
    });
});

Or if you are using Dracones-PHP..

We will first create a PHP script containing functions that can be called through AJAX requests, via the JS map widget. Our application codebase will now contain a new file:

<app_location>/conf.json
<app_location>/index.html
<app_location>/<script_name>.php

We now need to tell Apache how to handle that script. Since both versions of Dracones (Python and PHP) share a common interface, we are using "RewriteRules" to translate the request into a WSGI-like form:

Alias /<app_name> <app_location>/htdocs/

<Directory <app_location>/htdocs/>
  # To enable the rewrite engine, make sure that the mod_rewrite is loaded
  RewriteEngine on
  # The rewrite flags are all mandatory (if any is missing, Dracones won't work)
  # R: redirect, QSA: append query string, NE: no URI escaping
  RewriteRule <script_name>/(.+) /<php_app_alias>/<script_name>.php?do=$1 [R,QSA,NE]
  Options -Indexes FollowSymLinks Multiviews 
  Order allow,deny
  Allow from all
</Directory>

Alias /<php_app_alias> <app_location>/php/

<Directory <app_location>/php/>
  Options -Indexes FollowSymLinks Multiviews 
  Order allow,deny
  Allow from all
</Directory>

Having done that, we can now look at the basic structure of a customized Dracones PHP script:

<?

function <function_name>() {

    $arr = beginDracones();
    $params = $arr[0]; $dmap = $arr[1]; $sess = &$_SESSION;

    // Whatever map transformation you want to implement goes here (probably using params)..

    $json_out = endDracones($dmap);

    // Whatever info to be returned to the client can be set in the json_out variable

    return exitDracones($json_out);

}

// Main entry point
doDracones(array(<exposed functions for the current script>));


?>

beginDracones

The beginDracones call processes the request to extract the parameters (coming from the client). Most importantly, it also initializes and restores the state of the application's DMap object (a Dracones wrapper around a MapServer map object). You can use the returned dmap and params variables to implement whatever transformation you want, after this step. For instance, this example query was implemented using only two additional lines:

<?

function exampleQuery() {

    $arr = beginDracones();
    $params = $arr[0]; $dmap = $arr[1]; $sess = &$_SESSION;

    $selected_regs = $dmap->getDLayer('montreal')->selected;
    $dmap->getDLayer('random_montreal_addresses')->queryByAttributes('region_id',
                                                                 $selected_regs,
                                                                 '<b>{street_num} {street_name}</b>');

    $json_out = endDracones($dmap);

    return exitDracones($json_out);

}

doDracones('exampleQuery');


?>

The previously selected regions are first extracted from the polygonal "montreal" layer, and used next to filter the "random_montreal_addresses" point layer, by using its queryByAttributes method, whose third argument corresponds to the HTML template of the hover items (appearing when hovering over points with the mouse).

On the client side, the corresponding calling mechanism is also simple. Provided that we have an already created control to trigger the query (a button for instance):

jQuery(<control_id>).bind('click', function() { 
    map.customRequest({
        url: '<script_name>/<function_name>', 
        data: {/* any additional data */}, 
        callback: function(json_in) { /* optional callback function */ }
    });
});

endDracones, exitDracones

The content of the second argument will be received as the params variable in the Python function, while the json_out JSON object, with any additional content that may have been added between the endDracones and exitDracones calls, is returned to the client, and passed as the json_in argument to the anonymous callback supplied as dracones.customRequest's last argument, in which it can be processed in any desired way.

This is the resulting map widget, along with its query control button just below: