FMAP
En gvSIG hemos intentado crear librerías que sean reusables fuera de gvSIG. Existen muchas librerías que se pueden utilizar de este modo, y dan servicio a muchos problemas con los que se puede encontrar un programador que trabaje con Sistemas de Información Geográfica (SIG).
De entre todas esas librerías, hay una que viene a ser un nivel superior de cara al programador, ya que es el punto de entrada de gvSIG a la mayoría de estas librerías.
FMap se puede entender como el motór gráfico de gvSIG. Es la librería que gestiona la colección de capas con las que se puede trabajar con gvSIG, y el encargado de dibujar esas capas en un componente (MapControl) con el que el usuario puede interactuar.
Dentro de la librería se definen unas plantillas o herramientas típicas (Behavior) con el comportamiento habitual de la interacción usuario-mapa (mirar en el package tools).
Por ejemplo, los comportamientos de
- Hacer click en el mapa y hacer algo basándose en ese punto (mostrar información) => PointBehavior.
- Mover el ratón y hacer algo con sus coordenadas (mostrar en la barra de estatus) => MouseMovementBehavior.
- Dibujar un rectángulo y hacer algo con ese rectángulo (seleccionar por rectángulo) => RectangleBehavior.
- Pinchar, mover el ratón y soltar (por ej., la herramienta encuadre (pan) => DraggerBehavior.
- PolylineBehavior, PolygonBehavior, CircleBehavior => Permiten al usuario dibujar polilíneas, polígonos o círculos, y aportan feedback al usuario (el usuario ve cómo dibuja esas geometrías).
Estos behaviors solo lanzan eventos. En realidad, el código que ejecuta lo que realmente desea el usuario se encuentra en las clases listener. Puedes echar un vistazo a las clases PanListener, PointListener, CircleListener, etc.
El motivo de esta separación es que existen comportamientos comunes a muchas herramientas. Por ejemplo, puedo dibujar un rectángulo y luego añadirlo como un gráfico al mapa, o bien utilizar ese rectángulo para lanzar un queryByRect sobre una capa.
En cuanto a la forma de usarlo:
//Selección por polígono
PolygonSelectListener poligSel = new PolygonSelectListener(m_MapControl);
m_MapControl.addMapTool("polSelection", new Behavior[]{new PolygonBehavior(poligSel), new MouseMovementBehavior(sbl)});
Vemos que se crea el listener adecuado y se asocia a un determinado Behavior con el método addMapTool del MapControl. La cadena “polSelection” se utiliza internamente para seleccionar una herramienta:
} else if (actionCommand.equals("SELPOL")) {
mapCtrl.setTool("polSelection");
}
Las clases más utilizadas en FMap (o al menos esa es mi opinión) son:
- MapControl. Es el componente que muestra al usuario el mapa, y sobre el que interactúa a base de tools. El método más importante es getMapContext(), que devuelve un objeto de tipo MapContext.
- MapContext. Es el modelo del MapControl. Con él, tenemos acceso a la colección de capas (vectoriales, raster y de cualquier tipo) y al objeto ViewPort, que define el rectángulo visible, el sistema de coordenadas que se está usando, la proyección, y algunos métodos muy útiles para transformar entre coordenadas de pantalla (pixels) y coordenadas de mundo real (metros, km, etc).
- FLayer. Es el interfaz común a todas las capas. Es la raíz de una estructura jerárquica que define los tipos de capas con los que vamos a trabajar. En la FIGURA XXXX se puede ver esta estructura. Las capas vectoriales son FLyrVect, y las capas raster derivan de FLyrRaster.
- El acceso a los datos vectoriales de una capa se hace a través del interfaz ReadableVectorial (FLyrVect.getSource()). Con eso tenemos los métodos necesarios para recorrer todas las features de una capa.
- IFeature => Geometría + datos alfanuméricos.
- SelectableDataSource (FLyrVect.getRecordset()). Nos brinda acceso a los datos alfanuméricos, con la posibilidad de seleccionar registros (getSelection()) y obtener información acerca de los campos de la tabla asociada. También es el modelo interno de una tabla alfanumérica aislada, no asociada a ninguna capa.
Las capas se suelen crear (no es obligatorio) a partir de la factoría de capas, que une los drivers (acceso a bajo nivel de las entidades en los ficheros o bases de datos espaciales) con las capas vectoriales y/o sus leyendas por defecto. Ej:
// Crear una capa a partir de un driver basado en fichero
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);
}
Una capa raster se crea de manera similar, a partir de un RasterDriver:
if (drivers[iFile] instanceof RasterDriver) {
lyr = LayerFactory.createLayer(layerName, (RasterDriver) drivers[iFile], fich, proj);
}
El acceso a bajo nivel de los datos se basa en un sistema de drivers. La ubicación de esos drivers de lectura se especifica con la llamada LayerFactory.setDriversPath(String path). Esta llamada se debe hacer cuando se está inicializando la aplicación.
En FMap encontraremos también los driver necesarios para trabajar con las capas en diversos formatos, sobre todo aquellos que sean vectoriales, ya que FMap nació en un principio como una librería para manejar datos vectoriales.
En gvSIG, el directorio donde tienen que estar los drivers está en el directorio gvSIG/extensiones/com.iver.cit.gvsig/drivers. Ahí se pueden encontrar los drivers de acceso a shp, dxf, dgn, dwg, mysql, postgresql, oracle, gml, etc.
Los drivers los gestiona la librería libDriverManager. La condición que debe cumplir un driver la define el interfaz Driver, que únicamente exige un nombre para ese driver (para buscarlo en la colección de driver registrados (getName()). Como curiosidad, advertir que hay una condición extra un tanto extraña: las clases que son drivers deben llamarse LoQueSea**Driver**.java. Es decir, deben terminar en la palabra Driver.
Eso en cuanto a la lectura de datos. La escritura sigue una estructura similar. Existe un interfaz IWriter, que hereda de Driver y que define los métodos de escritura. Volveremos más tarde sobre la edición de los datos, porque es un tema bastante complejo.
La unión de la capa con el driver se hace según un patrón Adapter. Existen clases internas que pueden adaptar los métodos de los drivers a las exigencias de lectura de una capa.
El package com.iver.cit.fmap.drivers contiene las clases y los interfaces para la lectura de datos. A revisar: ITableDefinition, ILayerDefinition, VectorialDriver y sus interfaces derivados, IFeatureIterator, MemoryDriver y ConnectionFactory.
Una pregunta típica que aparece de vez en cuando es:
¿Dónde puedo ver el código donde pinta gvSIG?.
No hay una respuesta simple. Es más, en las próximas versiones de gvSIG, la parte de simbología está sufriendo un cambio espectacular, así que mucho de lo que cuente aquí, puede no ser válido en la siguiente revisión. Sin embargo, intentaré centrarme en las partes que no van a cambiar.
Ya hemos dicho que MapControl es el componente que muestra el mapa, y con el que interactúa el usuario. Sin embargo, no es el responsable de pintar el mapa. MapControl gestiona las peticiones de repintado, y mantiene un Timer que permite refrescar el componente cada cierto tiempo para que el usuario vea cómo se está pintando el mapa. El pintado real NO ocurre sobre el Graphics de MapControl. De hecho, en background es MapContext el que está pintando sobre un graphics que MapControl ha obtenido de un BufferImage.
Así que la respuesta rápida es que lo que vemos ocurre en un thread aparte, para que el usuario no note ralentización en el interfaz, y que se pinta sobre una imagen en memoria.
Si miramos el código de MapContext, encontramos este método:
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 despues: " + viewPort.toString());
/*
* if ((viewPort.getImageWidth() <=0) || (viewPort.getImageHeight() <=
* 0)) { return; }
*/
prepareDrawing(image, g, scale);
// Más cálidad al texto
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("Tiempo de dibujado:" + (t2 - t1) +
" mseg. Memoria libre:" + Runtime.getRuntime().freeMemory() / 1024 + " KB");
/*
* Free resources
*/
System.gc();
}
Como mínimo hay 3 cosas importantes a destacar.
La primera es el objeto Cancellable. Se utiliza para interrumpir el dibujado, y dentro del recorrido de la capa, cuando se están dibujando las geometrías, se comprueba si este objeto devuelve true (ha sido cancelado). Si es así, hay que salir del recorrido de las geometrías. Esto es importante si alguna vez implementamos una estrategia de pintado distinta a las ya implementadas, y queremos mantener la posiblidad de que se interrumpa el dibujado. Si no queremos que se interrumpa, podemos pasar a este método un objeto Cancellable que siempre devuelva false. La segunda se refiere a la línea
layers.draw(image, g, viewPort, cancel, scale);
layers es la colección de capas de nuestro objeto MapContext. Es un objeto de tipo FLayers, lo que quiere decir que aquí hemos seguido el patrón Composite para implementar la agrupación de capas. Una colección de capas se comporta a su vez como una capa más. Internamente, recorrerá las capas que forman esa agrupación y llamará al método draw de cada una de ellas. La tercera cosa a destacar es la capa TracLayer. Es una capa que se utiliza para dibujar gráficos encima de todas las demás capas (gráficos persistentes). Es importante darse cuenta también de los eventos que se lanzan ANTES y DESPUÉS de dibujar la capa. Si alguna vez necesitamos hacer algo después de pintar el mapa (por ejemplo, hacemos zoom a una zona y luego mostramos un cuadro de diálogo al usuario, que tiene que ver esa zona), lo mejor es crear un listener de este evento, y meter el código en respuesta a
LayerDrawEvent.GRAPHICLAYER_AFTER_DRAW.
Si no lo hacemos así, el usuario no verá la zona que quería ver, ya que el pintado del mapcontext ocurre en otro thread, y sobre una imagen en memoria. Estos eventos nos permiten estar seguros de que la imagen en memoria ya ha sido pintada.
También se lanzan eventos antes y después del pintado de cada capa. Puedes revisar los eventos LayerDrawEvent y el interfaz LayerDrawingListener.
Otro package interesante para revisar es com.iver.cit.gvsig.fmap.core. Dentro se define la estructura jerárquica de las geometrías en FMap. Puedes consultar un resúmen en la FIGURA XXX.
Las geometrías es una de las cosas (junto con todo lo relativo a las leyendas y simbología) que más puede cambiar en las versiones iguales o superiores a la 2.0.
El motivo es que mientras este manual se está creando, a la vez se está desarrollando un pliego que permitirá dotar a gvSIG capacidades de dibujado hasta ahora sólo al alcance de otros productos comerciales muy avanzados.
Otro de los grandes avances internos que se están preparando es la posibilidad de crear topología sobre las capas, editar nodos, crear automáticamente polígonos a partir de líneas, y asociar restricciones y validación a todo el sistema.
La simbología es la parte que afectará más a las leyendas, y las geometrías muy probablemente cambiarán para permitir las nuevas opciones avanzadas de validación y topología.
Por el momento, lo que seguramente no va a cambiar es que existirá un interfaz que es el padre del resto de geometrías (IGeometry). Internamente, por ahora se trabaja con FGeometry, que normalmente se basa en una geometría GeneralPathX. Esta clase es exáctamente igual que GeneralPath, salvo que las coordenadas se guardan realmente en un array de doubles en lugar de floats.
![no lo encuentro](../../../docs/manual-para-desarrolladores-gvsig-1/librerias-internas/fmap/images/diagramaDeClases.gif)