Defining a new action behavior

An user-defined action is actually a custom request in disguise: it's another way of implementing a Dracones extension, but this time tied to specific mouse behaviors.

We already looked at the select action in a previous tutorial. Actions are triggered by the use of CTRL + left mouse button: a single click while holding CTRL for a point action, and CTRL + drag for a box action. A point action is defined with (x, y) coordinates, whereas a box action is defined over a rectangular region: (x, y, width, height). By default, Dracones understands two predefined actions, for both modes: select and draw. You can select an item or a region over some items, and you can draw an item at a precise location, or over a certain region.

Each action mode can also be overridden, and here we will show how to create a custom point action, whose goal will be to put an "info-marker" on the location of a click, showing the coordinates of the point, along with the region ID it is in (in other words, a variation on the draw action theme). We will let the box action be a normal selection of info-markers (since selection is a Dracones default behavior, there will be no need to supply custom Python or PHP code for it).

We first need to create a corresponding mapfile, with one region layer, and one "feature" layer, destined to be first empty, waiting to be filled with info-markers:

    EXTENT <minx> <miny> <maxx> <maxy> 
        TEMPLATE tmpl
        NAME "region"
        STATUS off
        TYPE polygon
        DATA "<region_shapefile>"
        CLASSITEM "<region_id>"
        CLASS STYLE COLOR 232 232 232 OUTLINECOLOR 105 105 105  END END
        TEMPLATE tmpl
        NAME "info_marker"
        STATUS off
        TYPE point
        CLASS STYLE COLOR 255 50 0 SYMBOL "star" SIZE 11 WIDTH 1 END END
        CLASS STYLE COLOR 0 50 255 SYMBOL "star" SIZE 11 WIDTH 1 END END


We then need to add a new function to our Python or PHP script (see the Custom Query tutorial for more information about this topic). It is a bit more complex this time because it has more things to do. First the Python version:

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

    params, sess, dmap = beginDracones(req)

    x = int(params.get('x', 0))
    y = int(params.get('y', 0))

    dmap.dlayers['info_marker'].drawFeature(x, y)'region', x, y, 0, 0)
    selected_region = dmap.getSelected('region')
    if selected_region:
        attributes = dmap.getDLayer('region').getRecordAttributes('<region_id>', 
        g = pix2geo(dmap, x, y)
        html = '%s, %s<br><b>%s</b>' % (g.x, g.y, attributes['<region_name>'])
        dmap.getDLayer('info_marker').addHoverItem({'gx': g.x,
                                                    'gy': g.y,
                                                    'html': html})
    json_out = endDracones(dmap)
    return exitDracones(json_out)

And the corresponding PHP version:


function setInfoMarker() {

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

    x = get($params, 'x');
    y = get($params, 'y');

    $dmap->dlayers['info_marker']->drawFeature($x, $y);
    $dmap->select('region', $x, $y, 0, 0);
    $selected_region = $dmap.getSelected('region');
    if ($selected_region) {
        $attributes = $dmap->getDLayer('region')->getRecordAttributes('<region_id>', 
        $g = pix2geo($dmap->ms_map, x, y); // note the difference with the Python version here..
        $html = sprintf('%s, %s<br><b>%s</b>', $g->x, $g->y, $attributes['<region_name>']);
        $dmap->getDLayer('info_marker')->addHoverItem(array('gx'=> $g->x,
                                                            'gy'=> $g->y,
                                                            'html'=> $html));
    $json_out = endDracones($dmap);
    return exitDracones($json_out);


// Main entry point
doDracones(array('exampleQuery', 'setInfoMarker'));


We first gather the x and y coordinates of our point action. Note that for a box action, we would need to get the w and h values as well. We then draw a feature (a star) on the "info_marker" layer, using the point coordinates. Note that we could have also used the dlayers list parameter that was sent by the action calling code (actually the content of the point_action_dlayers client JS variable, but since we know exactly what layer we want to operate on, this step would be useless.

The next task is to find the region in which the just drawn info-marker lies, to display its name in an hover item. For this we use a little trick: since we are dealing with a point action, we can reuse its coordinates to select the underlying region, whose layer is selectable because we have set a CLASSITEM attribute for it in the mapfile. The selection won't have a visible effect, since we didn't provide a second CLASS attribute to specify how it should be displayed, but it will make no difference from the perspective of Dracones. We can then use the selected region's ID to retrieve the attributes of the corresponding record (with the DLayer.getRecordAttributes method), from which we will extract the region's name, that will be set in the hover item HTML template, along with the pixel-to-geographic converted coordinates of our initial point action.


A few modifications have to be made to the client code as well. We have first to register our newly created action:

                     name: 'set_info_marker', 
                     url: '<script_name>/setInfoMarker',
                     callback: function(resp) { }

Next we need to set the desired action, for both modes (notice that we use the predefined "select" for the box mode):

                     point_action: 'set_info_marker',
                     point_action_dlayers: ['info_marker'],
                     box_action: 'select',
                     box_action_dlayers: ['info_marker']

This method can be called by a UI control whenever it is needed to switch action behaviors and/or targets. And finally, here's what it looks like (remember to hold CTRL for mouse actions):