1. Introducción
En el paquete FMap, un mapa está
compuesto por:
Para obtener una imagen de un mapa hay que:
LayerFactory.setDriversPath("C:\\drivers");
ViewPort vp = new ViewPort(ProjectionPool.get("ed50utm30"));
vp.setImageSize(new Dimension(100, 100));
FMap mapa = new FMap(vp);
l = LayerFactory.createLayer("Vias", "DemoSHPDriver", new File("c:\\vias.shp"), ProjectionPool.get("ed50utm30"));
mapa.getLayers().addLayer(l);
BufferedImage img = new BufferedImage(100, 100, BufferedImage.TYPE_INT_ARGB);
mapa.draw(img, img.createGraphics());
El ejemplo concreto que se acaba de mostrar se corresponde al ImageFrame del
ejemplo "com.iver.cit.gvsig.fmap.DrawImage". Para un ejemplo de uso de FMap
desde un elemento complejo de interfaz de usuario ver Interfaz de usuario
2. Capas
Cada mapa visualiza en una imagen cartografía
cuyo origen puede ser muy diverso: ficheros, servidores WMS, ... Para añadir
un origen de datos cartográficos a un mapa aparece el concepto de Capa.
Las capas representan un origen de datos cartográficos independientemente
de su ubicación y naturaleza. Fmap tiene un método getLayers()
el cual devuelve una capa especial, consistente en un conjunto de capas inicialmente
vacío que son utilizadas para realizar las operaciones de dibujado, impresión,
...
En FMap, una capa viene definida por la interfaz FLayer de
modo que toda clase que implemente la interfaz FLayer es una capa. Además
de esta interfaz, hay un conjunto de interfaces que definen las características
de una capa. Estas están en el paquete "com.iver.cit.gvsig.fmap.layer.layerOperations"
y permiten crear capas con distintas capacidades a medida de la necesidad del
programador, usuario, estandar, ...
Por otro lado, la creación de las capas que inicialmente
parten con gvSIG está centralizada en FLayers teniendo ésta métodos
estáticos para crear cualquiera de estas capas fácilmente.
Una vez se obtiene una referencia a FLayer, si se quiere
realizar una operación concreta, se debe de comprobar si dicha capa implementa
la interfaz del paquete "com.iver.cit.gvsig.fmap.layer.layerOperations" que
da soporte a dicha operación, teniendo que hacer un casting para realizar
la operación. Por ejemplo, el siguiente código borraría
la selección de todas las capas activas con soporte de selección
de un array layers :
for (Iterator iter = layers.iterator(); iter.hasNext();) {
FLayer layer = (FLayer) iter.next();
if (layer.isActive()) {
if (layer instanceof Selectable) {
((Selectable) layer).clearSelection();
}
}
}
Para entender de forma más completa las operaciones
que se pueden hacer con las capas se puede leer la documentación a nivel
de API de las interfaces del paquete mencionado anteriormente.
(TODO: Poner la descripción de las interfaces aquí)
2.1 VectorialData
Mención a parte merece la interfaz VectorialData
por su sofistificación. Las capas vectoriales en FMap pueden tener una
fuente de datos secuencial o aleatoria, en función del driver utilizado,
por lo que una selección por rectángulo debería de implementarse
dos veces, una para cada tipo de driver. Para evitar esto hemos empleado un
mecanismo mediante el cual, el programador debe implementar unas clases que
derivan de com.iver.cit.gvsig.fmap.operations.strategies.FeatureVisitor. En
esta interfaz hay 3 métodos: start, visit y stop. Estos métodos
están documentados en la API. Pongamos un sencillo ejemplo: para realizar
una selección por rectángulo tendra que
public void setRect(Rectangle2D r) {
rect = r;
}
public boolean start(FLayer layer) {
return layer instanceof Selectable;
}
public void visit(IGeometry g, int index) {
if (g.intersects(rect)) {
bitset.set(index, true);
} else {
bitset.set(index, false);
}
}
((Selectable) layer).setSelection(bitset);
((VectorialData) layer).process(new SelectionByRectVisitor(r));
Como se puede comprobar, en ningún momento hemos
necesitado saber si el origen de la capa es secuencial o aleatorio. En el paquete
"com.iver.cit.gvsig.fmap.operations.strategies" hay muchos más ejemplos
de FeatureVisitors.
3. ViewPort
La clase ViewPort guarda la información relativa a las transformaciónes de coordenadas y datos acerca de la proyección
actual. Para ello, almacena el tamaño de la imagen sobre la que se dibuja, el rectángulo de visualización, el rectángulo
ajustado al marco de visualización, etc.
También se ocupa de gestionar los "listeners" que escuchan los eventos de cambio de "extent", y de realizar los cálculos
de área, perímetro y distancia.
4. Eventos
FMap pone a disposición del programador todo un mecanismo
para que se pueda saber "lo que está pasando por dentro del mapa". Cada
elemento de FMap tiene un método de la forma addXXXListener, mediente
el cual el programador puede registrarse como observador de los eventos que
ocurren en el objeto en cuestión. Por ejemplo, el ViewPort tiene un addViewPortListener
que recibe una interfaz ViewPortListener. La clase que implemente esta interfaz
y sea registrada mediante el método addViewPortListener será notificada
de los eventos de cambio de extent y cambio de color de fondo en el ViewPort
mediante invocaciones a los métodos de la interfaz que implementa. Esto
presenta un problema y para mostrarlo vamos a suponer que tenemos un control
de interfaz de usuario que escucha eventos del ViewPort y de la colección
de capas, de manera que cuando se añade una capa o se modifica el extent
se redibuja la imagen que muestra. Resulta que cuando se añade la primera
capa se modifica también el extent por lo que en el caso del control
del ejemplo se refrescará la imagen dos veces de manera innecesaria.
La solución son los AtomicEvent's.
4.1 AtomicEvent's
Para solucionar el problema anterior FMap incorpora dos métodos
beginAtomicEvent y endAtomicEvent. Estos métodos no afectan a la gestión
de eventos de los elementos individuales de FMap, afecta a la gestión
de eventos desde FMap. Al igual que otros elementos del paquete FMap contiene
un método addAtomicEventListener con la funcionalidad análoga
a los addXXXListener comentados antes. Una vez registrado, el listener será
notificado de cualquier evento que suceda por dentro de esa instancia de FMap
(en las capas, viewport, leyenda, ...) con la única diferencia que podrá
ser notificado de varios eventos al mismo tiempo. Si un trozo de código
se encuentra entre las instrucciones beginAtomicEvent y endAtomicEvent, los
objetos individuales (ViewPort, Layers, ...) dispararán eventos de la
misma manera, pero la instancia de FMap acumulará los eventos desde que
se ejecuta beginAtomicEvent hasta que se ejecuta endAtomicEvent, momento en
el cual se disparará un AtomicEvent con los eventos acumulados embebidos
en el anterior. En caso de que no se use beginAtomicEvent y endAtomicEvent FMap
no acumulará eventos, pero seguirá disparándolos a medida
que le van llegando. Como ejemplo de listener de AtomicEvent tenemos la clase
NewMapControl, la cual escucha atomic events en la clase interna MapContextListener.
Como ejemplo de código que usa beginAtomicEvent y endAtomicEvent tenemos
el método execute de la extensión com.iver.cit.gvsig.Abrir en
gvSIG
5 Drivers
FMap lee las fuentes de datos mediante el uso de drivers,
lo cual permite a cualquiera implementar un driver determinado para cualquier
formato existente. Para ello hay que configurar un directorio en el que se colocan
los drivers cada uno dentro de su directorio de la siguiente manera:
Cada tipo de driver (vectorial, raster, ...) debe ser implementado
mediante una interfaz distinta (VectorialFileDriver, WMSDriver, ...) y además
de implementar esta interfaz, se pueden implementar otras interfaces que añaden
un valor añadido al driver.
5.1 VectorialFileDriver
Para crear un driver de un fichero de
tipo vectorial hay que implementar la interfaz VectorialFileDriver cuyos métodos
están documentados en el JavaDoc. Mediante esta interfaz el driver obtiene
el acceso a los datos geográficos de los ficheros vectoriales. Además
de esta interfaz hay que implementar una interfaz para el acceso a los datos
alfanuméricos. Dependiendo de cómo estén organizados estos
datos se puede implementar com.iver.cit.gvsig.fmap.drivers.ExternalData, que
es una interfaz útil para cuando los datos alfanuméricos se encuentran
en un fichero distinto al fichero de datos geográficos (caso del shapefile),
o se puede implementar com.hardcode.gdbms.engine.data.FileDriver que es adecuado
para los casos en los que la tabla de datos alfanuméricos se encuentra
en el mismo soporte que los datos geográficos (caso del DGN).
Una vez implementadas estas dos interfaces, se pueden implementar
otras para darle un valor añadido a los drivers:
6 Interfaz de usuario
Con el paquete FMap se proporciona un control de interfaz
de usuario junto con una serie de herramientas diseñadas para este control.
MapControl es la interfaz de usuario sobre FMap que se proporciona con gvSIG
y automatiza gran parte de la programación del interfaz gráfico
de un mapa, lanzando el dibujado en un segundo plano, redibujando automáticamente
cuando el FMap que tiene por debajo queda invalidado, ... Además se proporcionan
una serie de herramientas preparadas para su uso y extensibles de manera que
la incorporación de nuevas herramientas por parte del usuario sea un
proceso trivial siempre que el comportamiento de la herramienta ya esté
programado. En las secciones posteriores se verá esto en más detalle.
6.1 MapControl
MapControl es un control de usuario que tiene
como modelo una instancia de FMap a la que se puede acceder mediante el método
getMapContext(). A continuación presentamos los pasos básicos
para mostrar un Frame con un MapControl sin herramientas. En la sección
siguiente se añadirán herramientas con comportamientos ya existentes
y cómo añadir herramientas con un comportamiento no programado
todavía.
LayerFactory.setDriversPath(
"C:\\eclipse3\\workspace\\Andami\\gvSIG\\extensiones\\com.iver.cit.gvsig\\drivers");
FLayer l = LayerFactory.createLayer("Vias", "gvSIG shp driver",
new File("C:/Documents and Settings/fernando/Mis documentos/vias.shp"),
ProjectionPool.get("EPSG:23030"));
newMapControl.getMapContext().getLayers().addLayer(l);
newMapControl.addMapTool("zoom", new RectangleBehavior(new ZoomInListenerImpl(newMapControl)));
newMapControl.setTool("zoom");
6.2 MapBehaviors
Las herramientas que se proporcionan en FMap
son en realidad comportamientos. Se define un comportamiento del ratón
tal como hacer un rectángulo (clase RectangleBehavior), dibujar una polilinea
(PolylineBehavior), etc. y estos comportamientos disparan eventos relacionados
con su propio comportamiento, por ejemplo, la herramienta de hacer rectángulo
cuando el usuario termina de dibujar el rectángulo se lanza un evento
rectangle definido en la interfaz com.iver.cit.gvsig.fmap.tools.RectangleListener.
De esta manera, para implementar las herramientas que tengan el comportamiento
de dibujado de rectángulo (zoom in, selección por rectángulo,
...) sólo tienen que implementarse los listeners de los eventos. En el
ejemplo anterior hemos visto como se añadía una herramienta al
MapControl mediante el behavior RectangleMapBehavior el cual toma en su constructor
la acción que se realiza con el rectángulo (acercar la imagen).
6.2.1 Creación
de una herramienta en base a un behavior existente
Para crear una herramienta con un comportamiento ya
implementado, hay que implementar la interfaz que dicho behavior espera. Para
averiguar qué interfaz es ésta hay que leer la documentación
de cada behavior. En el paquete "com.iver.cit.gvsig.fmap.tools" hay múltiples
ejemplos sobre como implementar las interfaces de los distintos behaviors.
6.2.2 Creación
de una herramienta con un comportamiento no implementado
Para realizar una herramienta para la cual no haya
un behavior definido se pueden realizar dos aproximaciones:
6.3 Composición
de herramientas
El modelo de herramientas sigue un patrón
composite. Esto quiere decir que existe una herramienta especial que consiste
realmente en un conjunto de herramientas. Teoría a parte, esto quiere
decir que podemos tener varias herramientas seleccionadas simultáneamente
como una sóla. Por ejemplo, podemos tener una herramienta que haga zoom
in, a la vez que podemos tener la herramienta que haga zoomout con el botón
derecho del ratón y a la vez que tenemos una maptool que muestra la coordenada
de la posición actual del ratón. Tenemos un ejemplo de esto en
la clase View de gvSIG:
m_MapControl.addMapTool("zoomIn", new CompoundBehavior(new RectangleBehavior(zil),
new PointBehavior(zoil), new MouseMovementBehavior(sbl)));
Que añade la herramienta compuesta por las 3 herramientas
simples deseadas. También existe un método de conveniencia que
acepta un array de Behaviour's y crea internamente el CompoundBehavior.