Personal tools
You are here: Home Development Developers guide gvSIG Internal Libraries


FMAP

We have tried to create libraries that are reusable outside of gvSIG. There are many libraries that can be used in such a way, and that can help with many of the problems a programmer can find in the Geographic Information Systems (GIS).

Among those libraries, there is one with the higher hierarchy for the programmer since it is the starting point for the majority of the libraries within gvSIG.

FMap can be seen as the graphic engine of gvSIG. It is the library that manages the layer collection gvSIG can work with and it is in charge of drawing those layers in a component (MapControl) in which the user can interact with.

Within the library there are some templates or typical tools defined (Behavior) with the usual user-map interaction behavior (look in the tools package).

For example, the behaviors of:

  • Click in the map and execute an action based on the clicked point (show information) => PointBehavior.
  • Move the mouse and execute an action based on the coordinates (show on the status bar) => MouseMovementBehavior.
  • Draw a rectangle and execute an operation with it (select by rectangle) => RectangleBehavior.
  • Click, move the mouse and realese (e.g. the pan tool) => DraggerBehavior.
  • PolylineBehavior, PolygonBehavior, CircleBehavior => They allow the user to draw polylines, polygons or circles and they provide feedback to the user (the user sees how he draws these geometries).

These behaviors only launch events. In reality, the code executing what the user really wants is located in the listener classes. You can take a look to the classes PanListener, PointListener, CircleListener, etc.

The reason for this separation is that there are common behaviors to many of the tools. For instance, we can draw a rectangle and then added as a graphic in the map, or we can use the rectangle to execute a queryByRect over a layer.

As for the way to use it:

//Selection by polygon
PolygonSelectListener poligSel = new PolygonSelectListener(m_MapControl);
m_MapControl.addMapTool("polSelection", new Behavior[]{new PolygonBehavior(poligSel),   new         MouseMovementBehavior(sbl)});

The adequate listener is created and it is associated with a particular Behavior with the addMapTool method from MapControl. The “polSelection” chain is used internally to select a tool:

} else if (actionCommand.equals("SELPOL")) {
    mapCtrl.setTool("polSelection");
}

The most used classes in FMap (or at least in our opinion) are:

  • MapControl. This is the component that shows the user the map, and over which it interacts with its tools. The most important method is getMapContext(), that returns objects of type MapContext.
  • MapContext. This is the MapControl model. With it we have access to the collection of layers (vector, raster and of any type) and the object ViewPort, that defines the visible extent, the coordinate system being used, the projection, and some methods very useful to transform between the screen coordinates (pixels) and the real world coordinates (meter, km, etc.)
  • FLayer. This is the common interface to all layers. It is the root to a hierarchical structure defining the types of layers that we will be working with. In FIGURE XXXX we could see this structure. The vector layers are FLyrVect and the raster layers are derived from FLyrRaster.
  • The access to the vector data of a layer is done through the ReadableVectorial interface (FLyrVect.getSource()). With that we have the necessary methods to search through the features of a layer.
  • IFeature => Geometry + alphanumeric data.
  • SelectableDataSource (FLyrVect.getRecordset()). It offers access to alphanumeric data with the option to select records (getSelection()) and obtain information related to the fields of the associated table. It is also the internal model of any isolated alphanumeric table, not associated to any layer.

Layers are created (not mandatory) from the layer factory that joins the drivers (low-level access to the entities in the files or spatial database) with the vector layers and/or their legends by default. Example:

// Create a layer from a file-based driver 
File fich = files[iFile];
String layerName = fich.getName();
String layerPath = fich.getAbsolutePath();
if (drivers[iFile] instanceof VectorialFileDriver) {
    lyr = LayerFactory.createLayer(layerName, (VectorialFileDriver) drivers[iFile], fich, proj);
}

A raster layer is created similarly from a RasterDriver:

if (drivers[iFile] instanceof RasterDriver) {
    lyr = LayerFactory.createLayer(layerName, (RasterDriver) drivers[iFile], fich, proj);
}

The low-level access to the data is based on a drivers system. The location of those reading drivers is specified with the call LayerFactory.setDriversPath(String path). This call needs to be done upon initialising the application.

We will also find in FMAP the drivers needed for working with layers of different formats, especially those that are vector, since originally FMAP was created as a library to manage vector data.

In gvSIG, the drivers should be in the gvSIG/extensiones/com.iver.cit.gvsig/drivers directory. There it should be found the drivers to access shp, dxf, dgn, dwg, mysql, postgresql, oracle, gml, etc.

The drivers are managed by the libDriverManager library. The condition a driver must comply is defined by the Driver interface, that it only requires a name for that driver (to find it in the registered driver collection (getName()). Curiously enough, there is an additional strange condition: the classes that are drivers must be named WhatEver**Driver**.java. That is, they should end in the world Driver.

That is all as for the data reading. Data writing follows a similar structure. There is an interface IWriter that inherits from Driver and that defines de writing methods. We will come back to data edition later, since it is a very complex subject.

The union of the layer with the driver is done according to the pattern Adapter. There are internal classes that can adapt the driver’s methods to the requirements of a layer reading.

The package com.iver.cit.fmap.drivers contains the classes and the interfaces for the data reading. To be revised: ITableDefinition, ILayerDefinition, VectorialDriver and their derived interfaces: IFeatureIterator, MemoryDriver y ConnectionFactory.

A typical question that shows up once in a while:

Where can I see the code that draws gvSIG?

There is no a simple answer. In addition, the symbology part of the next gvSIG versions is going through a spectacular change, thus anything it is said here may not be valid for the next version. However, let’s concentrate on the areas that will not change.

We have said that MapControl is the component that shows the map and the one the user interacts with. However, MapControl is not responsible for drawing the map, it manages the drawing requests and it keeps a Timer that allows to refresh the component every once in a while so the user sees the map drawing. The real drawing DOES NOT happen over the MapControl Graphics. In fact, it is MapContext the one drawing in the background over a graphic that MapControl has obtained from a BufferImage.

So the quick answer is that what we see it is happening in a separate thread, so the user will not notice the interface slowing down and that it is drawn over an image in memory.

If we look at the MapContext code, we will find the following method:

public void draw(BufferedImage image, Graphics2D g, Cancellable cancel,
                double scale) throws DriverException {
        if (viewPort.getExtent() == null) {
                // System.err.println("viewPort.getExtent() = null");
                return;
        }
        System.out.println("Viewport after: " + viewPort.toString());
        /*
         * if ((viewPort.getImageWidth() <=0) || (viewPort.getImageHeight() <=
         * 0)) { return; }
         */

        prepareDrawing(image, g, scale);

        // More text quality
        RenderingHints renderHints = new RenderingHints(
                        RenderingHints.KEY_ANTIALIASING,
                        RenderingHints.VALUE_ANTIALIAS_ON);
        renderHints.put(RenderingHints.KEY_RENDERING,
                        RenderingHints.VALUE_RENDER_QUALITY);
        renderHints.put(RenderingHints.KEY_TEXT_ANTIALIASING,
                        RenderingHints.VALUE_TEXT_ANTIALIAS_ON);
        g.setRenderingHints(renderHints);

        long t1 = System.currentTimeMillis();
        layers.draw(image, g, viewPort, cancel, scale);

        LayerDrawEvent beforeTracLayerEvent = new LayerDrawEvent(tracLayer,
                        g, viewPort, LayerDrawEvent.GRAPHICLAYER_BEFORE_DRAW);
        fireLayerDrawingEvent(beforeTracLayerEvent);
        tracLayer.draw(image, g, viewPort, cancel, scale);
        LayerDrawEvent afterTracLayerEvent = new LayerDrawEvent(tracLayer,
                        g, viewPort, LayerDrawEvent.GRAPHICLAYER_AFTER_DRAW);
        fireLayerDrawingEvent(afterTracLayerEvent);

        //layers.setDirty(false);

        long t2 = System.currentTimeMillis();
        System.err.println("Drawing Time:" + (t2 - t1) +
                        " mseg. Free Memory:" + Runtime.getRuntime().freeMemory() / 1024  + " KB");
        /*
         * Free resources
         */
        System.gc();
}

At least there are 3 important things to stand out.

The first one is the object Cancellable. It is used for interrupting the drawing, and within the layer extent, it verifies if the object returns true (it has been cancelled) while it is drawing the geometries. If that is the case, it has to get out of the geometries extent. This is important if anytime we would like to implement a different drawing strategy to the ones already implemented and we would like to keep the option of interrupting the drawing. If we do not want the interruption, we could pass a Cancellable object to this method that will always return false. The second one refers to the line

layers.draw(image, g, viewPort, cancel, scale);

layers is the collection of layers of our object MapContext. It is an object of type Flayers, which means that we have follow here the pattern Composite to implement the layer grouping. A collection of layers behaves in turn as a layer itself. Internally it will search through the layers forming the grouping and it will call the draw method of each of one of them. The third one to show up is the layer TrackLayer. It is a layer used for drawing graphics on top of all the other layers (persistent graphic). It is important to also notice the events that are thrown BEFORE and AFTER of drawing the layer. If we ever need to do something after drawing the map (e.g., zoom in an area and show the user a dialog box related to the area), the best option is to create a listener of that event, and to put the code in response to

LayerDrawEvent.GRAPHICLAYER_AFTER_DRAW.

If we do not do it this way, the user will not see the area that he want it to see, since the drawing of the mapcontext happens in another thread and over an image on memory. Those events allow us to be certain that the image on memory has already being drawn.

Also, there are events launched before and after the drawing of each layer. You can check the events LayerDrawEvent and the interface LayerDrawingListener.

Another interesting package to check is com.iver.cit.gvsig.fmap.core. The hierarchic structure of the geometries in FMAP is defined inside. You can check a summary in FIGURE XXX.

The geometries are one of the things (together with everything relative to legend and symbology) that can change the most in versions 2.0 and up.

The reasons is that while this manual is been created, gvSIG is being developed with drawing capabilities available until now in very advance commercial products.

Another of the great internal advances that is being prepared is the possibility to create topology over the layers, edit nodes, create polygons automatically from lines and to associate restrictions and validations to all the system.

Symbology is the part that most will affect the legend, and very likely the geometries will change to allow for the new options of validation and topology.

For now, the least likely to change is that there it would be an interface that is the father of all the geometries (IGeometry). Internally, for the present one works with FGeometry, that normally it is based on the GeneralPathX geometry. This class is exactly the same as GeneralPath, except that the coordinates are kept in a double array instead of a float array.

no lo encuentro

Examples of FMAP Usage

Add a layer by code

To add a layer by code usually it is necessary to create the driver and then to call LayerFactory. Let’s take a look:

LayerFactory.setDriversPath(
    "C:\\eclipse3\\workspace\\Andami\\gvSIG\\extensiones\\com.iver.cit.gvsig\\drivers");
FLayer l = LayerFactory.createLayer("Vias",
    (VectorialFileDriver)   LayerFactory.getDM().getDriver("gvSIG shp driver"),
    new File("C:/vias.shp"),
    CRSFactory.getCRS("EPSG:23030"));
newMapControl.getMapContext().getLayers().addLayer(l);

A PostGIS layer is created as follows:

String dburl = "jdbc:postgresql://localhost/latin1";
String dbuser = "postgres";
String dbpass = "XXXXXXX";
    // String dburl = "jdbc:postgresql://192.168.0.217/postgis";
// String dbuser = "gvsig";
// String dbpass = "";

    // String dbtable = "carreteras_lin_5k_t10";
String dbtable = "provin"; // BECAREFUL WITH CAPITAL LETTERS!!!!!!!
IConnection conn = null;
System.out.println("Creating JDBC connection...");
Class.forName("org.postgresql.Driver");
conn = ConnectionFactory.createConnection(dburl, dbuser, dbpass);

((ConnectionJDBC)conn).getConnection().setAutoCommit(false);

DBLayerDefinition lyrDef = new DBLayerDefinition();
lyrDef.setName(dbtable);
lyrDef.setTableName(dbtable);
lyrDef.setWhereClause("");
String[] fields = {"nom_provin", "gid"};
lyrDef.setFieldNames(fields);
lyrDef.setFieldGeometry("the_geom");
lyrDef.setFieldID("gid");

Statement st = ((ConnectionJDBC)conn).getConnection().createStatement(ResultSet.TYPE_SCROLL_INSENSITIVE, ResultSet.CONCUR_READ_ONLY);
    rsGood = st.executeQuery("SELECT NOM_PROVIN, GID FROM " + dbtable           + " ORDER BY GID");
    driver.setData(conn, lyrDef);
}
catch (Exception e){
    e.printStackTrace();
}

It is necessary to notice a vector layer is defined normally based on a driver and an object ILayerDefinition, where there are specified the layer’s name, fields, coordinate system, etc.

Spatial Query

Spatial queries are very common. For example, we search for features within a determined polygon (the same as searching for something using certain tolerance).

There are several ways to do it and they can be consulted by looking at the codes for the information and selection tools in gvSIG. Here we will show a simple one, especially to demonstrate the FBitMap usage.

The FBitMap class represents a selection of records. It is based on the Bitmap Java class, which contains 0 and 1, a “1” when the record is selected and a “0” when it is not selected. The advantage of this binary storage is the very low memory space taken by the selection and easiness to iterate, count, change the selection, etc.

The disadvantage is that we start from the assumption we always have a random access available to our driver. In other words, the operation to get the umpteenth record is available and fast. That allows us, if there are 10 records selected to obtain their record numbers, get their Feature and do something with them. The problem arises when the data sources do not allow for this type of random access and return their Features in a sequential manner, without knowing how many records will be returned before hand.

That is why we cover BitMap with FBitMap, so in the future we can deliver a selection adapted to databases with random or sequential access.

Then, upon a selection the driver is run from beginning to end while the condition is evaluated (spatial and/or alphanumeric) and the selected records are marked with bitmaps. If any driver were only sequential, we would have to storage the selected records in an array or something similar to avoid searching through the database to position the records, which would be a slow operation.

Nowadays, the drivers in gvSIG are sufficiently fast to allow for random access. Therefore, we can make a query such as:

Point2D p = event.getPoint();
Point2D mapPoint = mapCtrl.getViewPort().toMapPoint((int) p.getX(), (int) p.getY());

// 3 pixels tolerance
double tol = mapCtrl.getViewPort().toMapDistance(3);
FLayer[] actives = mapCtrl.getMapContext()
.getLayers().getActives();
for (int i=0; i < actives.length; i++)
{
    if (actives[i] instanceof FLyrVect) {
        FLyrVect lyrVect = (FLyrVect) actives[i];
        FBitSet oldBitSet = lyrVect.getSource().getRecordset().getSelection();
        FBitSet newBitSet = lyrVect.queryByPoint(mapPoint, tol);
        if (event.getEvent().isControlDown())
            newBitSet.xor(oldBitSet);
        lyrVect.getRecordset().setSelection(newBitSet);
    }
}

This code has been extracted from the com.iver.cit.gvsig.fmap.tools.PointSelectionListener.java class. What is does is to receive a point in pixels, transform it to map coordinates and search through all the active layers. If they are of vector type, it uses the queryByPoint query to obtain the FBitSet (the selection).

Then we can use this selection for anything we want. The search through the selected records is done this way:

SelectableDataSource ds =lyrVect.getRecordset(); 
int idField = ds.getFieldIndexByName("NOM_PROVIN");
FBitSet bs = lyrVect.queryByPoint(mapPoint, tol);
for (int i=bs.nextSetBit(0); i >=0; i=bs.nextSetBit(i+1))
{
    long idRec = i;
    String nom = ds.getFieldValue(idRec, idField).toString();
    dlg.setProvinName(nom);
}

Here we have selected the province that contains the point “mapPoint” and we have search through the selected records, recovering the field value “NOM_PROVIN”.

We will see more examples in chapter 6.


Powered by Plone CMS, the Open Source Content Management System

This site conforms to the following standards: