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](../../docs/manual-para-desarrolladores-gvsig-1/librerias-internas/fmap-1/images/diagramaDeClases.gif)