next up previous index
Next: Xlib Summary Up: Using Xlib Primitives Previous: Using Pixmaps

Displaying Maps

  ProTcXl provides two special predicates that allow to display and access maps. A map is normally a collection of polygons filled with different colours.   The polygons may have a large number of points and we may want them active, e.g. responding to mouse clicks. This is a clear case for Tk, because it can both display all areas and make them active. Unfortunately, if the map data is stored in an external database and contains a large number of items, it may be infeasible to display it with Tk, because Tk would in fact duplicate all the external data in its main memory and this would break down the system. The ProTcXl predicate

xlib_polygon(XID, List, OffX, OffY, FX, FY, Xpose, Border, Fill)
is a very general predicate to display a list of points as a polygon. OffX and OffY is a fixed X and Y offset which is added to all coordinates in List, FX and FY are scale factors used to scale the resulting X and Y coordinate. The boolean variable Xpose specifies whether the data should be transposed before displaying, Border is the outline colour and Fill is the colour of the polygon itself. This predicate can be used to display fixed-size polygons as well as to scale a given map with a given scale.

As an example, let use define a window which displays a given map. The map will be sensitive to window resizing: when the window is resized, the map is scaled and redisplayed according to the new size. We have a data file map_data.pl which contains facts data(List, Color) with polygon coordinates and the corresponding colours. We have a problem caused by Tk event processing: if we display our data right after a resizing event, it will be immediately erased because Tk itself redraws the contents of its window after the resize event. One possible solution to handle this problem is to display the map with the after idle     command, so that our map is displayed only after all Tk activity stops. We use the Configure event to find out when the window   has been resized.   Note that one resize can cause several Configure events and to avoid unnecessary redisplays we use the %a event field - when it is zero we know that no new Configure event will be issued for the last modification. Here is the Prolog source map.pl:

:- lib(tk).
:- lib('tk/xlib').

:- [map_data].

:- tcl_source.

top :-
    tk([]),
    tcl 'tkwait visibility .',        % wait until '.' becomes visible
    xlib_init('.', X1),
    setval(xid, X1),
    tcl_source('map.tcl'),
    set_error_handler(333, handle_events/2).

handle_events(_, ["draw"]) :-
    !,
    tcl('winfo width .', [], W),
    tcl('winfo height .', [], H),
    draw(W, H).
handle_events(_, ["redraw", A, W, H]) :-
    !,
    (A = "0x0" ->
            draw(W, H)
    ;
            true
    ).
handle_events(N, ["exit"]) :-
    !,
    reset_error_handler(N).

draw(W, H) :-
    RX is W/1980,
    RY is H/2000,
    getval(xid, XID),
    xlib_color(XID, black, Black),
    data(List, Color),
    xlib_color(XID, Color, Pixel),
    xlib_polygon(XID, List, 0, 0, RX, RY, 0, Black, Pixel),
    xlib_flush(XID),
    fail; true.

and here the short Tcl script map.tcl:

bind . <Configure> {after idle {prolog_event redraw %a %w %h}}
bind . <ButtonPress-3> {prolog_event draw}
bind . <q> {exit}
after idle {prolog_event draw}
The Tcl script needs a few explanations:

We compile the Prolog source and call top/0. After a while, the following picture appears:

We can now try to move or resize the window and see the display refreshed   after each change and the map rescaled to fit the new window:

Now what about active polygons?   We have given up Tk because of speed, but we have also lost all its features, like the ability to specify events for canvas items. We can simulate them by an explicit lookup: When we click with a mouse button over a particular map polygon, we can find out its current coordinates, scale them according to the current size and then look for the polygon which contains this point. A good spatial database should provide an efficient indexing mechanism to support this operation. If it is not available, the ProTcXl predicate inside/4   may help: it takes a point, a polygon and a rectangle which contains the polygon and it succeeds if the point is contained in the rectangle and inside the polygon. The rectangle can be used to do fast rough filtering of possible candidates before the whole polygon is tested. We can enhance our map example by adding a mouse click with the first button: it will blacken the polygon with the cursor. To handle it, we add the following line to map.tcl, to bind   the mouse click to a Prolog graphics event:  

bind . <ButtonPress-1> {prolog_event click %x %y}
We also add the corresponding case to our event handling predicate and define a predicate click/2 which tries all polygons in sequence and looks for the right one. It also has to scale the point coordinates according to the current window size (the modifications are already in map.pl):
click(X, Y) :-
    tcl('winfo width .', [], W),
    tcl('winfo height .', [], H),
    RX is W/1980,
    RY is H/2000,
    Xn is fix(X/RX),
    Yn is fix(Y/RY),
    data(List, _),
    inside(Xn, Yn, [0, 0, 2000, 2000], List),
    !,
    getval(xid, XID),
    xlib_color(XID, black, Black),
    xlib_polygon(XID, List, 0, 0, RX, RY, 0, Black, Black),
    xlib_flush(XID).

handle_events(_, ["click", X, Y]) :-
    !,
    click(X, Y).

Although our lookup predicate performs a linear search through all available polygons and without using the covering rectangle, its response time is quite satisfactory:



next up previous index
Next: Xlib Summary Up: Using Xlib Primitives Previous: Using Pixmaps



Micha Meier
Tue Jul 2 09:49:39 MET DST 1996